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