1 /*
2     SPDX-FileCopyrightText: 2003 Jason Harris <kstars@30doradus.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "fovdialog.h"
8 
9 #include <QFile>
10 #include <QFrame>
11 #include <QPainter>
12 #include <QTextStream>
13 #include <QPaintEvent>
14 #include <QDebug>
15 #include <QPushButton>
16 #include <QComboBox>
17 #include <QDoubleSpinBox>
18 #include <QLineEdit>
19 
20 #include <KActionCollection>
21 #include <KLocalizedString>
22 #include <kcolorbutton.h>
23 #include <KMessageBox>
24 
25 #include "kstars.h"
26 #include "kstarsdata.h"
27 #include "widgets/fovwidget.h"
28 #include "Options.h"
29 
30 // This is needed to make FOV work with QVariant
31 Q_DECLARE_METATYPE(FOV *)
32 
33 int FOVDialog::fovID = -1;
34 
35 namespace
36 {
37 // Try to convert text in KLine edit to double
textToDouble(const QLineEdit * edit,bool * ok=nullptr)38 inline double textToDouble(const QLineEdit *edit, bool *ok = nullptr)
39 {
40     return edit->text().replace(QLocale().decimalPoint(), ".").toDouble(ok);
41 }
42 
43 // Extract FOV from QListWidget. No checking is done
getFOV(QListWidgetItem * item)44 FOV *getFOV(QListWidgetItem *item)
45 {
46     return item->data(Qt::UserRole).value<FOV *>();
47 }
48 
49 // Convert double to QString
toString(double x,int precision=2)50 QString toString(double x, int precision = 2)
51 {
52     return QString::number(x, 'f', precision).replace('.', QLocale().decimalPoint());
53 }
54 }
55 
FOVDialogUI(QWidget * parent)56 FOVDialogUI::FOVDialogUI(QWidget *parent) : QFrame(parent)
57 {
58     setupUi(this);
59 }
60 
NewFOVUI(QWidget * parent)61 NewFOVUI::NewFOVUI(QWidget *parent) : QFrame(parent)
62 {
63     setupUi(this);
64 }
65 
66 //---------FOVDialog---------------//
FOVDialog(QWidget * p)67 FOVDialog::FOVDialog(QWidget *p) : QDialog(p)
68 {
69 #ifdef Q_OS_OSX
70     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
71 #endif
72     // Register FOV* data type
73     if (fovID == -1)
74         fovID = qRegisterMetaType<FOV *>("FOV*");
75     fov = new FOVDialogUI(this);
76 
77     setWindowTitle(i18nc("@title:window", "Set FOV Indicator"));
78 
79     QVBoxLayout *mainLayout = new QVBoxLayout;
80     mainLayout->addWidget(fov);
81     setLayout(mainLayout);
82 
83     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close);
84     mainLayout->addWidget(buttonBox);
85     connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
86     connect(buttonBox, SIGNAL(rejected()), this, SLOT(close()));
87 
88     connect(fov->FOVListBox, SIGNAL(currentRowChanged(int)), SLOT(slotSelect(int)));
89     connect(fov->NewButton, SIGNAL(clicked()), SLOT(slotNewFOV()));
90     connect(fov->EditButton, SIGNAL(clicked()), SLOT(slotEditFOV()));
91     connect(fov->RemoveButton, SIGNAL(clicked()), SLOT(slotRemoveFOV()));
92 
93     // Read list of FOVs and for each FOV create listbox entry, which stores it.
94     foreach (FOV *f, FOVManager::getFOVs())
95     {
96         addListWidget(f);
97     }
98 }
99 
~FOVDialog()100 FOVDialog::~FOVDialog()
101 {
102     // Delete FOVs
103     //for(int i = 0; i < fov->FOVListBox->count(); i++) {
104     //delete getFOV( fov->FOVListBox->item(i) );
105     //}
106 }
107 
addListWidget(FOV * f)108 QListWidgetItem *FOVDialog::addListWidget(FOV *f)
109 {
110     QListWidgetItem *item = new QListWidgetItem(f->name(), fov->FOVListBox);
111     item->setData(Qt::UserRole, QVariant::fromValue<FOV *>(f));
112     return item;
113 }
114 
slotSelect(int irow)115 void FOVDialog::slotSelect(int irow)
116 {
117     bool enable = irow >= 0;
118     fov->RemoveButton->setEnabled(enable);
119     fov->EditButton->setEnabled(enable);
120     if (enable)
121     {
122         //paint dialog with selected FOV symbol
123         fov->ViewBox->setFOV(getFOV(fov->FOVListBox->currentItem()));
124         fov->ViewBox->update();
125     }
126 }
127 
slotNewFOV()128 void FOVDialog::slotNewFOV()
129 {
130     QPointer<NewFOV> newfdlg = new NewFOV(this);
131     if (newfdlg->exec() == QDialog::Accepted)
132     {
133         FOV *newfov = new FOV(newfdlg->getFOV());
134         FOVManager::addFOV(newfov);
135         addListWidget(newfov);
136         fov->FOVListBox->setCurrentRow(fov->FOVListBox->count() - 1);
137     }
138     delete newfdlg;
139 }
140 
slotEditFOV()141 void FOVDialog::slotEditFOV()
142 {
143     //Preload current values
144     QListWidgetItem *item = fov->FOVListBox->currentItem();
145     if (item == nullptr)
146         return;
147     FOV *f = item->data(Qt::UserRole).value<FOV *>();
148 
149     // Create dialog
150     QPointer<NewFOV> newfdlg = new NewFOV(this, f);
151     if (newfdlg->exec() == QDialog::Accepted)
152     {
153         // Overwrite FOV
154         f->sync(newfdlg->getFOV());
155         fov->ViewBox->update();
156     }
157     delete newfdlg;
158 }
159 
slotRemoveFOV()160 void FOVDialog::slotRemoveFOV()
161 {
162     int i = fov->FOVListBox->currentRow();
163     if (i >= 0)
164     {
165         QListWidgetItem *item = fov->FOVListBox->takeItem(i);
166         FOVManager::removeFOV(getFOV(item));
167         delete item;
168     }
169 }
170 
171 //-------------NewFOV------------------//
172 
NewFOV(QWidget * parent,const FOV * fov)173 NewFOV::NewFOV(QWidget *parent, const FOV *fov) : QDialog(parent), f()
174 {
175     ui = new NewFOVUI(this);
176 
177     setWindowTitle(i18nc("@title:window", "New FOV Indicator"));
178 
179     QVBoxLayout *mainLayout = new QVBoxLayout;
180     mainLayout->addWidget(ui);
181     setLayout(mainLayout);
182 
183     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
184     mainLayout->addWidget(buttonBox);
185     connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
186     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
187 
188     okB = buttonBox->button(QDialogButtonBox::Ok);
189 
190     // Initialize FOV if required
191     if (fov != nullptr)
192     {
193         f.sync(*fov);
194         ui->FOVName->setText(f.name());
195         ui->FOVEditX->setText(toString(f.sizeX()));
196         ui->FOVEditY->setText(toString(f.sizeY()));
197         ui->FOVEditOffsetX->setText(toString(f.offsetX()));
198         ui->FOVEditOffsetY->setText(toString(f.offsetY()));
199         ui->FOVEditRotation->setText(toString(f.PA()));
200         ui->ColorButton->setColor(QColor(f.color()));
201         ui->ShapeBox->setCurrentIndex(f.shape());
202         ui->FOVLockCP->setChecked(f.lockCelestialPole());
203 
204         ui->TLength2->setValue(Options::telescopeFocalLength());
205         ui->cameraWidth->setValue(Options::cameraWidth());
206         ui->cameraHeight->setValue(Options::cameraHeight());
207         ui->cameraPixelSizeW->setValue(Options::cameraPixelWidth());
208         ui->cameraPixelSizeH->setValue(Options::cameraPixelHeight());
209 
210         ui->ViewBox->setFOV(&f);
211         ui->ViewBox->update();
212     }
213 
214     connect(ui->FOVName, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV()));
215     connect(ui->FOVEditX, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV()));
216     connect(ui->FOVEditY, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV()));
217     connect(ui->FOVEditOffsetX, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV()));
218     connect(ui->FOVEditOffsetY, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV()));
219     connect(ui->FOVEditRotation, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV()));
220     connect(ui->FOVLockCP, SIGNAL(toggled(bool)), SLOT(slotUpdateFOV()));
221     connect(ui->ColorButton, SIGNAL(changed(QColor)), SLOT(slotUpdateFOV()));
222     connect(ui->ShapeBox, SIGNAL(activated(int)), SLOT(slotUpdateFOV()));
223     connect(ui->ComputeEyeFOV, SIGNAL(clicked()), SLOT(slotComputeFOV()));
224     connect(ui->ComputeCameraFOV, SIGNAL(clicked()), SLOT(slotComputeFOV()));
225     connect(ui->ComputeHPBW, SIGNAL(clicked()), SLOT(slotComputeFOV()));
226     connect(ui->ComputeBinocularFOV, SIGNAL(clicked()), SLOT(slotComputeFOV()));
227     connect(ui->ComputeTLengthFromFNum1, SIGNAL(clicked()), SLOT(slotComputeTelescopeFL()));
228     connect(ui->DetectFromINDI, SIGNAL(clicked()), SLOT(slotDetectFromINDI()));
229 
230 #ifndef HAVE_INDI
231     ui->DetectFromINDI->setEnabled(false);
232 #endif
233 
234     // Populate eyepiece AFOV options. The userData field contains the apparent FOV associated with that option
235     ui->EyepieceAFOV->insertItem(0, i18nc("Specify the apparent field of view (AFOV) manually", "Specify AFOV"), -1);
236     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Ramsden (Typical)"), 30);
237     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Orthoscopic (Typical)"), 45);
238     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Ploessl (Typical)"), 50);
239     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Erfle (Typical)"), 60);
240     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Radian"), 60);
241     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Baader Hyperion"), 68);
242     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Panoptic"), 68);
243     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Delos"), 72);
244     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Meade UWA"), 82);
245     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Nagler"), 82);
246     ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Ethos (Typical)"), 100);
247 
248     connect(ui->EyepieceAFOV, SIGNAL(currentIndexChanged(int)), SLOT(slotEyepieceAFOVChanged(int)));
249 
250     ui->LinearFOVDistance->insertItem(0, i18n("1000 yards"));
251     ui->LinearFOVDistance->insertItem(1, i18n("1000 meters"));
252     connect(ui->LinearFOVDistance, SIGNAL(currentIndexChanged(int)), SLOT(slotBinocularFOVDistanceChanged(int)));
253 
254     slotUpdateFOV();
255 }
256 
slotBinocularFOVDistanceChanged(int index)257 void NewFOV::slotBinocularFOVDistanceChanged(int index)
258 {
259     QString text = (index == 0 ? i18n("feet") : i18n("meters"));
260     ui->LabelUnits->setText(text);
261 }
262 
slotUpdateFOV()263 void NewFOV::slotUpdateFOV()
264 {
265     bool okX, okY;
266     f.setName(ui->FOVName->text());
267     float sizeX = textToDouble(ui->FOVEditX, &okX);
268     float sizeY = textToDouble(ui->FOVEditY, &okY);
269     if (okX && okY)
270         f.setSize(sizeX, sizeY);
271 
272     float xoffset = textToDouble(ui->FOVEditOffsetX, &okX);
273     float yoffset = textToDouble(ui->FOVEditOffsetY, &okY);
274     if (okX && okY)
275         f.setOffset(xoffset, yoffset);
276 
277     float rot = textToDouble(ui->FOVEditRotation, &okX);
278     if (okX)
279         f.setPA(rot);
280 
281     f.setShape(static_cast<FOV::Shape>(ui->ShapeBox->currentIndex()));
282     f.setColor(ui->ColorButton->color().name());
283     f.setLockCelestialPole(ui->FOVLockCP->isChecked());
284 
285     okB->setEnabled(!f.name().isEmpty() && okX && okY);
286 
287     ui->ViewBox->setFOV(&f);
288     ui->ViewBox->update();
289 }
290 
slotComputeFOV()291 void NewFOV::slotComputeFOV()
292 {
293     if (sender() == ui->ComputeEyeFOV && ui->TLength1->value() > 0.0)
294     {
295         ui->FOVEditX->setText(toString(60.0 * ui->EyeFOV->value() * ui->EyeLength->value() / ui->TLength1->value()));
296         ui->FOVEditY->setText(ui->FOVEditX->text());
297     }
298     else if (sender() == ui->ComputeCameraFOV && ui->TLength2->value() > 0.0)
299     {
300         /*double sx = (double)ui->ChipWidth->value() * 3438.0 / ui->TLength2->value();
301         double sy = (double)ui->ChipHeight->value() * 3438.0 / ui->TLength2->value();
302         //const double aspectratio = 3.0/2.0; // Use the default aspect ratio for DSLRs / Film (i.e. 3:2)*/
303 
304         // FOV in arcmins
305         double fov_x = 206264.8062470963552 * ui->cameraWidth->value() * ui->cameraPixelSizeW->value() / 60000.0 / ui->TLength2->value();
306         double fov_y = 206264.8062470963552 * ui->cameraHeight->value() * ui->cameraPixelSizeH->value() / 60000.0 / ui->TLength2->value();
307 
308         ui->FOVEditX->setText(toString(fov_x));
309         ui->FOVEditY->setText(toString(fov_y));
310     }
311     else if (sender() == ui->ComputeHPBW && ui->RTDiameter->value() > 0.0 && ui->WaveLength->value() > 0.0)
312     {
313         ui->FOVEditX->setText(toString(34.34 * 1.2 * ui->WaveLength->value() / ui->RTDiameter->value()));
314         // Beam width for an antenna is usually a circle on the sky.
315         ui->ShapeBox->setCurrentIndex(4);
316         ui->FOVEditY->setText(ui->FOVEditX->text());
317         slotUpdateFOV();
318     }
319     else if (sender() == ui->ComputeBinocularFOV && ui->LinearFOV->value() > 0.0 &&
320              ui->LinearFOVDistance->currentIndex() >= 0)
321     {
322         double sx =
323             atan((double)ui->LinearFOV->value() / ((ui->LinearFOVDistance->currentIndex() == 0) ? 3000.0 : 1000.0)) *
324             180.0 * 60.0 / dms::PI;
325         ui->FOVEditX->setText(toString(sx));
326         ui->FOVEditY->setText(ui->FOVEditX->text());
327     }
328 }
329 
slotEyepieceAFOVChanged(int index)330 void NewFOV::slotEyepieceAFOVChanged(int index)
331 {
332     if (index == 0)
333     {
334         ui->EyeFOV->setEnabled(true);
335     }
336     else
337     {
338         bool ok;
339         ui->EyeFOV->setEnabled(false);
340         ui->EyeFOV->setValue(ui->EyepieceAFOV->itemData(index).toFloat(&ok));
341         Q_ASSERT(ok);
342     }
343 }
344 
slotComputeTelescopeFL()345 void NewFOV::slotComputeTelescopeFL()
346 {
347     TelescopeFL *telescopeFLDialog = new TelescopeFL(this);
348     if (telescopeFLDialog->exec() == QDialog::Accepted)
349     {
350         ui->TLength1->setValue(telescopeFLDialog->computeFL());
351     }
352     delete telescopeFLDialog;
353 }
354 
slotDetectFromINDI()355 void NewFOV::slotDetectFromINDI()
356 {
357     QDBusInterface alignInterface("org.kde.kstars",
358                                   "/KStars/Ekos/Align",
359                                   "org.kde.kstars.Ekos.Align",
360                                   QDBusConnection::sessionBus());
361 
362     QDBusReply<QList<double>> cameraReply = alignInterface.call("cameraInfo");
363     if (cameraReply.isValid())
364     {
365         QList<double> values = cameraReply.value();
366 
367         ui->cameraWidth->setValue(values[0]);
368         ui->cameraHeight->setValue(values[1]);
369         ui->cameraPixelSizeW->setValue(values[2]);
370         ui->cameraPixelSizeH->setValue(values[3]);
371     }
372 
373     QDBusReply<QList<double>> telescopeReply = alignInterface.call("telescopeInfo");
374     if (telescopeReply.isValid())
375     {
376         QList<double> values = telescopeReply.value();
377         ui->TLength2->setValue(values[0]);
378     }
379 
380     QDBusReply<QList<double>> solutionReply = alignInterface.call("getSolutionResult");
381     if (solutionReply.isValid())
382     {
383         QList<double> values = solutionReply.value();
384         if (values[0] > -1e6)
385             ui->FOVEditRotation->setText(QString::number(values[0]));
386     }
387 }
388 
389 
390 //-------------TelescopeFL------------------//
391 
TelescopeFL(QWidget * parent)392 TelescopeFL::TelescopeFL(QWidget *parent) : QDialog(parent), aperture(nullptr), fNumber(nullptr), apertureUnit(nullptr)
393 {
394     setWindowTitle(i18nc("@title:window", "Telescope Focal Length Calculator"));
395 
396     //QWidget *mainWidget = new QWidget( this );
397     QGridLayout *mainLayout = new QGridLayout(this);
398     setLayout(mainLayout);
399 
400     aperture = new QDoubleSpinBox();
401     aperture->setRange(0.0, 100000.0);
402     aperture->setDecimals(2);
403     aperture->setSingleStep(0.1);
404 
405     fNumber = new QDoubleSpinBox();
406     fNumber->setRange(0.0, 99.9);
407     fNumber->setDecimals(2);
408     fNumber->setSingleStep(0.1);
409 
410     apertureUnit = new QComboBox(this);
411     apertureUnit->insertItem(0, i18nc("millimeters", "mm"));
412     apertureUnit->insertItem(1, i18n("inch"));
413 
414     mainLayout->addWidget(new QLabel(i18n("Aperture diameter: "), this), 0, 0);
415     mainLayout->addWidget(aperture, 0, 1);
416     mainLayout->addWidget(apertureUnit, 0, 2);
417     mainLayout->addWidget(new QLabel(i18nc("F-Number or F-Ratio of optical system", "F-Number: "), this), 1, 0);
418     mainLayout->addWidget(fNumber, 1, 1);
419 
420     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
421     mainLayout->addWidget(buttonBox);
422     connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
423     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
424 
425     show();
426 }
427 
computeFL() const428 double TelescopeFL::computeFL() const
429 {
430     const double inch_to_mm = 25.4; // 1 inch, by definition, is 25.4 mm
431     return (aperture->value() * fNumber->value() *
432             ((apertureUnit->currentIndex() == 1) ?
433              inch_to_mm :
434              1.0)); // Focal Length = Aperture * F-Number, by definition of F-Number
435 }
436 
currentItem() const437 unsigned int FOVDialog::currentItem() const
438 {
439     return fov->FOVListBox->currentRow();
440 }
441