Kstars

viewsdialog.cpp
1/*
2 SPDX-FileCopyrightText: 2003 Jason Harris <kstars@30doradus.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "viewsdialog.h"
8#include <QPointer>
9#include <QPainter>
10#include <QMessageBox>
11#include <QPainterPath>
12#include <QPainterPathStroker>
13#include <kstars_debug.h>
14
15#include "Options.h"
16
17ViewsDialogUI::ViewsDialogUI(QWidget *parent) : QFrame(parent)
18{
19 setupUi(this);
20}
21
22//----ViewsDialogStringListModel-----//
23Qt::ItemFlags ViewsDialogStringListModel::flags(const QModelIndex &index) const
24{
26 if (index.isValid())
27 {
28 return defaultFlags & (~Qt::ItemIsDropEnabled);
29 }
30 return defaultFlags;
31}
32
33//---------ViewsDialog---------------//
34ViewsDialog::ViewsDialog(QWidget *p) : QDialog(p)
35{
36#ifdef Q_OS_MACOS
37 setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
38#endif
39 ui = new ViewsDialogUI(this);
40
41 setWindowTitle(i18nc("@title:window", "Manage Sky Map Views"));
42
43 QVBoxLayout *mainLayout = new QVBoxLayout;
44 mainLayout->addWidget(ui);
45 setLayout(mainLayout);
46
48 mainLayout->addWidget(buttonBox);
49 connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
50 connect(buttonBox, SIGNAL(rejected()), this, SLOT(close()));
51
52 // Read list of Views and for each view, create a listbox entry
53 m_model = new ViewsDialogStringListModel(this);
54 syncModel();
55 ui->ViewListBox->setModel(m_model);
56 ui->ViewListBox->setDragDropMode(QAbstractItemView::InternalMove);
57 ui->ViewListBox->setDefaultDropAction(Qt::MoveAction);
58 ui->ViewListBox->setDragDropOverwriteMode(false);
59
60 connect(ui->ViewListBox->selectionModel(), &QItemSelectionModel::currentChanged, this, &ViewsDialog::slotSelectionChanged);
61 connect(m_model, &ViewsDialogStringListModel::rowsMoved, this, &ViewsDialog::syncFromModel);
62 connect(ui->NewButton, SIGNAL(clicked()), SLOT(slotNewView()));
63 connect(ui->EditButton, SIGNAL(clicked()), SLOT(slotEditView()));
64 connect(ui->RemoveButton, SIGNAL(clicked()), SLOT(slotRemoveView()));
65
66}
67
68void ViewsDialog::syncModel()
69{
70 QStringList viewNames;
71 for(const auto &view : SkyMapViewManager::getViews())
72 {
73 viewNames.append(view.name);
74 }
75 m_model->setStringList(viewNames);
76}
77
78void ViewsDialog::syncFromModel()
79{
80 // FIXME: Inefficient code, but it's okay because number of items is small
81 QHash<QString, SkyMapView> nameToViewMap;
82 for(const auto &view : SkyMapViewManager::getViews())
83 {
84 nameToViewMap.insert(view.name, view);
85 }
86 QStringList updatedList = m_model->stringList();
88 for (const auto &view : updatedList)
89 {
90 SkyMapViewManager::addView(nameToViewMap[view]);
91 }
93}
94
95void ViewsDialog::slotSelectionChanged(const QModelIndex &current, const QModelIndex &prev)
96{
97 Q_UNUSED(prev);
98 bool enable = current.isValid();
99 ui->RemoveButton->setEnabled(enable);
100 ui->EditButton->setEnabled(enable);
101}
102
103void ViewsDialog::slotNewView()
104{
105 QPointer<NewView> newViewDialog = new NewView(this);
106 if (newViewDialog->exec() == QDialog::Accepted)
107 {
108 const auto view = newViewDialog->getView();
110 m_model->insertRow(m_model->rowCount());
111 QModelIndex index = m_model->index(m_model->rowCount() - 1, 0);
112 m_model->setData(index, view.name);
113 ui->ViewListBox->setCurrentIndex(index);
114 }
115 delete newViewDialog;
116}
117
118void ViewsDialog::slotEditView()
119{
120 //Preload current values
121 QModelIndex currentIndex = ui->ViewListBox->currentIndex();
122 if (!currentIndex.isValid())
123 return;
124 const QString viewName = m_model->data(currentIndex).toString();
125 std::optional<SkyMapView> view = SkyMapViewManager::viewNamed(viewName);
126 Q_ASSERT(!!view);
127 if (!view)
128 {
129 qCCritical(KSTARS) << "Programming Error";
130 return; // Eh?
131 }
132
133 // Create dialog
134 QPointer<NewView> newViewDialog = new NewView(this, view);
135 if (newViewDialog->exec() == QDialog::Accepted)
136 {
137 // Overwrite Views
139 const auto view = newViewDialog->getView();
141 syncModel();
142 }
143 delete newViewDialog;
144}
145
146void ViewsDialog::slotRemoveView()
147{
148 QModelIndex currentIndex = ui->ViewListBox->currentIndex();
149 if (!currentIndex.isValid())
150 return;
151 const QString viewName = m_model->data(currentIndex).toString();
152 if (SkyMapViewManager::removeView(viewName))
153 {
154 m_model->removeRow(currentIndex.row());
155 }
156}
157
158//-------------NewViews------------------//
159
160class SliderResetEventFilter : public QObject
161{
162 public:
163 SliderResetEventFilter(QSlider *slider, QObject *parent = nullptr)
164 : QObject(parent)
165 , m_slider(slider)
166 {
167 if (m_slider)
168 {
169 m_slider->installEventFilter(this);
170 }
171 }
172
173 bool eventFilter(QObject *obj, QEvent *event) override
174 {
175 if (obj == m_slider && event->type() == QEvent::MouseButtonDblClick)
176 {
177 QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
178 Q_ASSERT(!!mouseEvent);
179 if (mouseEvent->button() == Qt::LeftButton)
180 {
181 m_slider->setValue(0);
182 return true;
183 }
184 }
185 return QObject::eventFilter(obj, event);
186 }
187
188 private:
189 QSlider *m_slider;
190};
191
192NewView::NewView(QWidget *parent, std::optional<SkyMapView> _view) : QDialog(parent)
193{
194 setupUi(this);
195
196 if (_view)
197 {
198 setWindowTitle(i18nc("@title:window", "Edit View"));
199 }
200 else
201 {
202 setWindowTitle(i18nc("@title:window", "New View"));
203 }
204
205 fieldOfViewSpinBox->addUnit("degrees", 1.0);
206 fieldOfViewSpinBox->addUnit("arcmin", 1 / 60.);
207 fieldOfViewSpinBox->addUnit("arcsec", 1 / 3600.);
208 fieldOfViewSpinBox->doubleSpinBox->setMaximum(600.0);
209 fieldOfViewSpinBox->doubleSpinBox->setMinimum(0.01);
210 fieldOfViewSpinBox->setEnabled(false);
211 fieldOfViewSpinBox->doubleSpinBox->setValue(1.0);
212
213 // Enable the "OK" button only when the "Name" field is not empty
214 connect(viewNameLineEdit, &QLineEdit::textChanged, [&](const QString & text)
215 {
216 buttonBox->button(QDialogButtonBox::Ok)->setDisabled(text.isEmpty());
217 });
218
219 // Enable the FOV spin box and unit combo only when the Set FOV checkbox is checked
220 connect(fieldOfViewCheckBox, &QCheckBox::toggled, fieldOfViewSpinBox, &UnitSpinBoxWidget::setEnabled);
221
222 // Update the angle value and graphic when the viewing angle slider is changed
223 connect(viewingAngleSlider, &QSlider::valueChanged, [&](const double value)
224 {
225 viewingAngleLabel->setText(QString("%1°").arg(QString::number(value)));
226 this->updateViewingAnglePreviews();
227 });
228 viewingAngleSlider->setValue(0); // Force the updates
229
230 // Update the viewing angle graphic when the erect observer correction is enabled / disabled
231 connect(disableErectObserverCheckBox, &QCheckBox::toggled, this, &NewView::updateViewingAnglePreviews);
232
233 // Disable erect observer when using equatorial mount
234 connect(mountTypeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [&](const int index) {
235 if (index == 0)
236 {
237 // Equatorial
238 disableErectObserverCheckBox->setChecked(true);
239 disableErectObserverCheckBox->setEnabled(false);
240 }
241 else
242 {
243 // Altazimuth
244 disableErectObserverCheckBox->setEnabled(true);
245 }
246 });
247
248
249 // Set up everything else
250 m_topPreview = new QPixmap(400, 300);
251 m_bottomPreview = new QPixmap(400, 300);
252 m_observerPixmap = new QPixmap(":/images/observer.png");
253 new SliderResetEventFilter(viewingAngleSlider);
254
255 // Finally, initialize fields as required
256 if (_view)
257 {
258 const auto view = *_view;
259 m_originalName = view.name;
260 viewNameLineEdit->setText(view.name);
261 mountTypeComboBox->setCurrentIndex(view.useAltAz ? 1 : 0);
262 if (view.inverted && view.mirror)
263 {
264 invertedMirroredViewType->setChecked(true);
265 }
266 else if (view.inverted)
267 {
268 invertedViewType->setChecked(true);
269 }
270 else if (view.mirror)
271 {
272 mirroredViewType->setChecked(true);
273 }
274 else
275 {
276 correctViewType->setChecked(true);
277 }
278
279 viewingAngleSlider->setValue(view.viewAngle);
280 disableErectObserverCheckBox->setChecked(!view.erectObserver);
281 if (!std::isnan(view.fov))
282 {
283 fieldOfViewCheckBox->setChecked(true);
284 fieldOfViewSpinBox->doubleSpinBox->setValue(view.fov);
285 }
286 }
287
288}
289
290NewView::~NewView()
291{
292 delete m_topPreview;
293 delete m_bottomPreview;
294 delete m_observerPixmap;
295}
296
298{
299 struct SkyMapView view;
300
301 view.name = viewNameLineEdit->text();
302 view.useAltAz = (mountTypeComboBox->currentIndex() > 0);
303 view.viewAngle = viewingAngleSlider->value();
304 view.mirror = invertedMirroredViewType->isChecked() || mirroredViewType->isChecked();
305 view.inverted = invertedMirroredViewType->isChecked() || invertedViewType->isChecked();
306 view.fov = fieldOfViewCheckBox->isChecked() ? fieldOfViewSpinBox->value() : NaN::d;
307 view.erectObserver = !(disableErectObserverCheckBox->isChecked());
308
309 return view;
310}
311
312void NewView::done(int r)
313{
314 if (r == QDialog::Accepted)
315 {
316 const QString name = viewNameLineEdit->text();
317 if (name != m_originalName)
318 {
320 {
321 QMessageBox::critical(this, i18n("Conflicting View Name"),
322 i18n("There already exists a view with the name you attempted to use. Please choose a different name for this view."));
323 return;
324 }
325 }
326 }
327 QDialog::done(r);
328 return;
329}
330
331void NewView::updateViewingAnglePreviews()
332{
333 Q_ASSERT(!!m_topPreview);
334 Q_ASSERT(!!m_bottomPreview);
335 Q_ASSERT(!!m_observerPixmap);
336
337 QPen pen(this->palette().color(QPalette::WindowText));
338 {
339 m_topPreview->fill(Qt::transparent);
340 float cx = m_topPreview->width() / 2., cy = m_topPreview->height() / 2.;
341 float size = std::min(m_topPreview->width(), m_topPreview->height());
342 float r = 0.75 * (size / 2.);
343 QPainter p(m_topPreview);
344
345 // Circle representing tube / secondary cage
346 pen.setWidth(5);
347 p.setPen(pen);
348 p.drawEllipse(QPointF(cx, cy), r, r);
349
350 // Cross hairs representing secondary vanes
351 pen.setWidth(3);
352 p.setPen(pen);
353 p.drawLine(cx - r, cy, cx + r, cy);
354 p.drawLine(cx, cy - r, cx, cy + r);
355
356 // Focuser
357 QPainterPathStroker stroker;
358 stroker.setWidth(20.f);
359 QPainterPath focuserPath;
360 double theta = dms::DegToRad * (viewingAngleSlider->value() - 90);
361 focuserPath.moveTo(cx + (r + 5.) * std::cos(theta), cy + (r + 5.) * std::sin(theta));
362 focuserPath.lineTo(cx + (r + 25.) * std::cos(theta), cy + (r + 25.) * std::sin(theta));
363 p.drawPath(stroker.createStroke(focuserPath));
364
365 // Observer
366 if (!disableErectObserverCheckBox->isChecked() && std::abs(viewingAngleSlider->value()) > 1)
367 {
368 p.drawPixmap(QPointF(
369 viewingAngleSlider->value() > 0 ? m_topPreview->width() - m_observerPixmap->width() : 0,
370 m_topPreview->height() - m_observerPixmap->height()),
371 viewingAngleSlider->value() < 0 ?
372 m_observerPixmap->transformed(QTransform(-1, 0, 0, 1, 0, 0)) :
373 *m_observerPixmap);
374 }
375 p.end();
376
377 // Display the pixmap to the QLabel
378 viewingAnglePreviewTop->setPixmap(m_topPreview->scaled(viewingAnglePreviewTop->width(), viewingAnglePreviewTop->height(),
380 }
381
382 {
383 m_bottomPreview->fill(Qt::transparent);
384 float cx = m_bottomPreview->width() / 2., cy = m_bottomPreview->height() / 2.;
385 float size = std::min(m_bottomPreview->width(), m_bottomPreview->height());
386 float r = 0.75 * (size / 2.);
387 QPainter p(m_bottomPreview);
388
389 // Circle representing the back of an SCT
390 pen.setWidth(5);
391 p.setPen(pen);
392 p.drawEllipse(QPointF(cx, cy), r, r);
393
394 // Focuser
395 QPainterPathStroker stroker;
396 stroker.setWidth(20.f);
397 QPainterPath focuserPath;
398 double theta = dms::DegToRad * (-viewingAngleSlider->value() - 90);
399 focuserPath.moveTo(cx, cy);
400 focuserPath.lineTo(cx + 25. * std::cos(theta), cy + 25. * std::sin(theta));
401 p.drawPath(stroker.createStroke(focuserPath));
402
403 // Observer
404 if (!disableErectObserverCheckBox->isChecked() && std::abs(viewingAngleSlider->value()) > 1)
405 {
406 p.drawPixmap(QPointF(
407 viewingAngleSlider->value() < 0 ? m_bottomPreview->width() - m_observerPixmap->width() : 0,
408 m_bottomPreview->height() - m_observerPixmap->height()),
409 viewingAngleSlider->value() > 0 ?
410 m_observerPixmap->transformed(QTransform(-1, 0, 0, 1, 0, 0)) :
411 *m_observerPixmap);
412 }
413
414 // Display the pixmap on the QLabel
415 p.end();
416 viewingAnglePreviewBottom->setPixmap(m_bottomPreview->scaled(
417 viewingAnglePreviewBottom->width(), viewingAnglePreviewBottom->height(),
419 }
420}
Dialog for defining a new View.
Definition viewsdialog.h:81
const SkyMapView getView() const
Return the view struct.
NewView(QWidget *parent=nullptr, std::optional< SkyMapView > view=std::nullopt)
Create new dialog.
static void drop()
Drop the list.
static bool save()
Commit the list of views to the database.
static bool removeView(const QString &name)
Remove a view Note: This is currently an O(N) operation.
static const QList< SkyMapView > & getViews()
Get the list of available views.
Definition skymapview.h:66
static std::optional< SkyMapView > viewNamed(const QString &name)
Get the view with the given name.
static void addView(const SkyMapView &newView)
Add a view.
Definition skymapview.h:49
void setEnabled(bool enabled)
Enables the widget.
static constexpr double DegToRad
DegToRad is a const static member equal to the number of radians in one degree (dms::PI/180....
Definition dms.h:390
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KGuiItem close()
void toggled(bool checked)
bool insertRow(int row, const QModelIndex &parent)
bool removeRow(int row, const QModelIndex &parent)
void rowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
void setValue(int)
void valueChanged(int value)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void currentIndexChanged(int index)
virtual void done(int r)
QPushButton * button(StandardButton which) const const
MouseButtonDblClick
iterator insert(const Key &key, const T &value)
void currentChanged(const QModelIndex &current, const QModelIndex &previous)
void textChanged(const QString &text)
void append(QList< T > &&value)
StandardButton critical(QWidget *parent, const QString &title, const QString &text, StandardButtons buttons, StandardButton defaultButton)
bool isValid() const const
int row() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
virtual bool eventFilter(QObject *watched, QEvent *event)
void installEventFilter(QObject *filterObj)
QObject * parent() const const
void lineTo(const QPointF &endPoint)
void moveTo(const QPointF &point)
QPainterPath createStroke(const QPainterPath &path) const const
void setWidth(qreal width)
void fill(const QColor &color)
int height() const const
QPixmap scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
QPixmap transformed(const QTransform &transform, Qt::TransformationMode mode) const const
int width() const const
Qt::MouseButton button() const const
bool isEmpty() const const
QString number(double n, char format, int precision)
virtual QVariant data(const QModelIndex &index, int role) const const override
virtual Qt::ItemFlags flags(const QModelIndex &index) const const override
virtual int rowCount(const QModelIndex &parent) const const override
virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override
void setStringList(const QStringList &strings)
QStringList stringList() const const
KeepAspectRatio
MoveAction
transparent
typedef ItemFlags
LeftButton
SmoothTransformation
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString toString() const const
void setEnabled(bool)
void setDisabled(bool disable)
void setupUi(QWidget *widget)
void setWindowTitle(const QString &)
Carries parameters of a sky map view.
Definition skymapview.h:19
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:38:42 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.