File indexing completed on 2025-02-16 09:47:20
0001 /* 0002 SPDX-FileCopyrightText: 2003 Jason Harris <kstars@30doradus.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "fovdialog.h" 0008 0009 #include <QFile> 0010 #include <QFrame> 0011 #include <QPainter> 0012 #include <QTextStream> 0013 #include <QPaintEvent> 0014 #include <QDebug> 0015 #include <QPushButton> 0016 #include <QComboBox> 0017 #include <QDoubleSpinBox> 0018 #include <QLineEdit> 0019 #include <QDBusReply> 0020 #include <QDBusInterface> 0021 #include <QPointer> 0022 0023 #include <KActionCollection> 0024 #include <KLocalizedString> 0025 #include <kcolorbutton.h> 0026 #include <KMessageBox> 0027 0028 #include "kstars.h" 0029 #include "kstarsdata.h" 0030 #include "widgets/fovwidget.h" 0031 #include "Options.h" 0032 0033 // This is needed to make FOV work with QVariant 0034 Q_DECLARE_METATYPE(FOV *) 0035 0036 int FOVDialog::fovID = -1; 0037 0038 namespace 0039 { 0040 // Try to convert text in KLine edit to double 0041 inline double textToDouble(const QLineEdit *edit, bool *ok = nullptr) 0042 { 0043 return edit->text().replace(QLocale().decimalPoint(), ".").toDouble(ok); 0044 } 0045 0046 // Extract FOV from QListWidget. No checking is done 0047 FOV *getFOV(QListWidgetItem *item) 0048 { 0049 return item->data(Qt::UserRole).value<FOV *>(); 0050 } 0051 0052 // Convert double to QString 0053 QString toString(double x, int precision = 2) 0054 { 0055 return QString::number(x, 'f', precision).replace('.', QLocale().decimalPoint()); 0056 } 0057 } 0058 0059 FOVDialogUI::FOVDialogUI(QWidget *parent) : QFrame(parent) 0060 { 0061 setupUi(this); 0062 } 0063 0064 NewFOVUI::NewFOVUI(QWidget *parent) : QFrame(parent) 0065 { 0066 setupUi(this); 0067 } 0068 0069 //---------FOVDialog---------------// 0070 FOVDialog::FOVDialog(QWidget *p) : QDialog(p) 0071 { 0072 #ifdef Q_OS_OSX 0073 setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); 0074 #endif 0075 // Register FOV* data type 0076 if (fovID == -1) 0077 fovID = qRegisterMetaType<FOV *>("FOV*"); 0078 fov = new FOVDialogUI(this); 0079 0080 setWindowTitle(i18nc("@title:window", "Set FOV Indicator")); 0081 0082 QVBoxLayout *mainLayout = new QVBoxLayout; 0083 mainLayout->addWidget(fov); 0084 setLayout(mainLayout); 0085 0086 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close); 0087 mainLayout->addWidget(buttonBox); 0088 connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 0089 connect(buttonBox, SIGNAL(rejected()), this, SLOT(close())); 0090 0091 connect(fov->FOVListBox, SIGNAL(currentRowChanged(int)), SLOT(slotSelect(int))); 0092 connect(fov->NewButton, SIGNAL(clicked()), SLOT(slotNewFOV())); 0093 connect(fov->EditButton, SIGNAL(clicked()), SLOT(slotEditFOV())); 0094 connect(fov->RemoveButton, SIGNAL(clicked()), SLOT(slotRemoveFOV())); 0095 0096 // Read list of FOVs and for each FOV create listbox entry, which stores it. 0097 foreach (FOV *f, FOVManager::getFOVs()) 0098 { 0099 addListWidget(f); 0100 } 0101 } 0102 0103 FOVDialog::~FOVDialog() 0104 { 0105 // Delete FOVs 0106 //for(int i = 0; i < fov->FOVListBox->count(); i++) { 0107 //delete getFOV( fov->FOVListBox->item(i) ); 0108 //} 0109 } 0110 0111 QListWidgetItem *FOVDialog::addListWidget(FOV *f) 0112 { 0113 QListWidgetItem *item = new QListWidgetItem(f->name(), fov->FOVListBox); 0114 item->setData(Qt::UserRole, QVariant::fromValue<FOV *>(f)); 0115 return item; 0116 } 0117 0118 void FOVDialog::slotSelect(int irow) 0119 { 0120 bool enable = irow >= 0; 0121 fov->RemoveButton->setEnabled(enable); 0122 fov->EditButton->setEnabled(enable); 0123 if (enable) 0124 { 0125 //paint dialog with selected FOV symbol 0126 fov->ViewBox->setFOV(getFOV(fov->FOVListBox->currentItem())); 0127 fov->ViewBox->update(); 0128 } 0129 } 0130 0131 void FOVDialog::slotNewFOV() 0132 { 0133 QPointer<NewFOV> newfdlg = new NewFOV(this); 0134 if (newfdlg->exec() == QDialog::Accepted) 0135 { 0136 FOV *newfov = new FOV(newfdlg->getFOV()); 0137 FOVManager::addFOV(newfov); 0138 addListWidget(newfov); 0139 fov->FOVListBox->setCurrentRow(fov->FOVListBox->count() - 1); 0140 } 0141 delete newfdlg; 0142 } 0143 0144 void FOVDialog::slotEditFOV() 0145 { 0146 //Preload current values 0147 QListWidgetItem *item = fov->FOVListBox->currentItem(); 0148 if (item == nullptr) 0149 return; 0150 FOV *f = item->data(Qt::UserRole).value<FOV *>(); 0151 0152 // Create dialog 0153 QPointer<NewFOV> newfdlg = new NewFOV(this, f); 0154 if (newfdlg->exec() == QDialog::Accepted) 0155 { 0156 // Overwrite FOV 0157 f->sync(newfdlg->getFOV()); 0158 fov->ViewBox->update(); 0159 } 0160 delete newfdlg; 0161 } 0162 0163 void FOVDialog::slotRemoveFOV() 0164 { 0165 int i = fov->FOVListBox->currentRow(); 0166 if (i >= 0) 0167 { 0168 QListWidgetItem *item = fov->FOVListBox->takeItem(i); 0169 FOVManager::removeFOV(getFOV(item)); 0170 delete item; 0171 } 0172 } 0173 0174 //-------------NewFOV------------------// 0175 0176 NewFOV::NewFOV(QWidget *parent, const FOV *fov) : QDialog(parent), f() 0177 { 0178 ui = new NewFOVUI(this); 0179 0180 setWindowTitle(i18nc("@title:window", "New FOV Indicator")); 0181 0182 QVBoxLayout *mainLayout = new QVBoxLayout; 0183 mainLayout->addWidget(ui); 0184 setLayout(mainLayout); 0185 0186 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0187 mainLayout->addWidget(buttonBox); 0188 connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 0189 connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 0190 0191 okB = buttonBox->button(QDialogButtonBox::Ok); 0192 0193 // Initialize FOV if required 0194 if (fov != nullptr) 0195 { 0196 f.sync(*fov); 0197 ui->FOVName->setText(f.name()); 0198 ui->FOVEditX->setText(toString(f.sizeX())); 0199 ui->FOVEditY->setText(toString(f.sizeY())); 0200 ui->FOVEditOffsetX->setText(toString(f.offsetX())); 0201 ui->FOVEditOffsetY->setText(toString(f.offsetY())); 0202 ui->FOVEditRotation->setText(toString(f.PA())); 0203 ui->ColorButton->setColor(QColor(f.color())); 0204 ui->ShapeBox->setCurrentIndex(f.shape()); 0205 ui->FOVLockCP->setChecked(f.lockCelestialPole()); 0206 0207 ui->TLength2->setValue(Options::telescopeFocalLength()); 0208 ui->cameraWidth->setValue(Options::cameraWidth()); 0209 ui->cameraHeight->setValue(Options::cameraHeight()); 0210 ui->cameraPixelSizeW->setValue(Options::cameraPixelWidth()); 0211 ui->cameraPixelSizeH->setValue(Options::cameraPixelHeight()); 0212 0213 ui->ViewBox->setFOV(&f); 0214 ui->ViewBox->update(); 0215 } 0216 0217 connect(ui->FOVName, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); 0218 connect(ui->FOVEditX, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); 0219 connect(ui->FOVEditY, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); 0220 connect(ui->FOVEditOffsetX, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); 0221 connect(ui->FOVEditOffsetY, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); 0222 connect(ui->FOVEditRotation, SIGNAL(textChanged(QString)), SLOT(slotUpdateFOV())); 0223 connect(ui->FOVLockCP, SIGNAL(toggled(bool)), SLOT(slotUpdateFOV())); 0224 connect(ui->ColorButton, SIGNAL(changed(QColor)), SLOT(slotUpdateFOV())); 0225 connect(ui->ShapeBox, SIGNAL(activated(int)), SLOT(slotUpdateFOV())); 0226 connect(ui->ComputeEyeFOV, SIGNAL(clicked()), SLOT(slotComputeFOV())); 0227 connect(ui->ComputeCameraFOV, SIGNAL(clicked()), SLOT(slotComputeFOV())); 0228 connect(ui->ComputeHPBW, SIGNAL(clicked()), SLOT(slotComputeFOV())); 0229 connect(ui->ComputeBinocularFOV, SIGNAL(clicked()), SLOT(slotComputeFOV())); 0230 connect(ui->ComputeTLengthFromFNum1, SIGNAL(clicked()), SLOT(slotComputeTelescopeFL())); 0231 connect(ui->DetectFromINDI, SIGNAL(clicked()), SLOT(slotDetectFromINDI())); 0232 0233 #ifndef HAVE_INDI 0234 ui->DetectFromINDI->setEnabled(false); 0235 #endif 0236 0237 // Populate eyepiece AFOV options. The userData field contains the apparent FOV associated with that option 0238 ui->EyepieceAFOV->insertItem(0, i18nc("Specify the apparent field of view (AFOV) manually", "Specify AFOV"), -1); 0239 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Ramsden (Typical)"), 30); 0240 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Orthoscopic (Typical)"), 45); 0241 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Ploessl (Typical)"), 50); 0242 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Erfle (Typical)"), 60); 0243 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Radian"), 60); 0244 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Baader Hyperion"), 68); 0245 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Panoptic"), 68); 0246 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Delos"), 72); 0247 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Meade UWA"), 82); 0248 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Nagler"), 82); 0249 ui->EyepieceAFOV->addItem(i18nc("Eyepiece Design / Brand / Name; Optional", "Tele Vue Ethos (Typical)"), 100); 0250 0251 connect(ui->EyepieceAFOV, SIGNAL(currentIndexChanged(int)), SLOT(slotEyepieceAFOVChanged(int))); 0252 0253 ui->LinearFOVDistance->insertItem(0, i18n("1000 yards")); 0254 ui->LinearFOVDistance->insertItem(1, i18n("1000 meters")); 0255 connect(ui->LinearFOVDistance, SIGNAL(currentIndexChanged(int)), SLOT(slotBinocularFOVDistanceChanged(int))); 0256 0257 slotUpdateFOV(); 0258 } 0259 0260 void NewFOV::slotBinocularFOVDistanceChanged(int index) 0261 { 0262 QString text = (index == 0 ? i18n("feet") : i18n("meters")); 0263 ui->LabelUnits->setText(text); 0264 } 0265 0266 void NewFOV::slotUpdateFOV() 0267 { 0268 bool okX, okY; 0269 f.setName(ui->FOVName->text()); 0270 float sizeX = textToDouble(ui->FOVEditX, &okX); 0271 float sizeY = textToDouble(ui->FOVEditY, &okY); 0272 if (okX && okY) 0273 f.setSize(sizeX, sizeY); 0274 0275 float xoffset = textToDouble(ui->FOVEditOffsetX, &okX); 0276 float yoffset = textToDouble(ui->FOVEditOffsetY, &okY); 0277 if (okX && okY) 0278 f.setOffset(xoffset, yoffset); 0279 0280 float rot = textToDouble(ui->FOVEditRotation, &okX); 0281 if (okX) 0282 f.setPA(rot); 0283 0284 f.setShape(static_cast<FOV::Shape>(ui->ShapeBox->currentIndex())); 0285 f.setColor(ui->ColorButton->color().name()); 0286 f.setLockCelestialPole(ui->FOVLockCP->isChecked()); 0287 0288 okB->setEnabled(!f.name().isEmpty() && okX && okY); 0289 0290 ui->ViewBox->setFOV(&f); 0291 ui->ViewBox->update(); 0292 } 0293 0294 void NewFOV::slotComputeFOV() 0295 { 0296 if (sender() == ui->ComputeEyeFOV && ui->TLength1->value() > 0.0) 0297 { 0298 ui->FOVEditX->setText(toString(60.0 * ui->EyeFOV->value() * ui->EyeLength->value() / ui->TLength1->value())); 0299 ui->FOVEditY->setText(ui->FOVEditX->text()); 0300 } 0301 else if (sender() == ui->ComputeCameraFOV && ui->TLength2->value() > 0.0) 0302 { 0303 /*double sx = (double)ui->ChipWidth->value() * 3438.0 / ui->TLength2->value(); 0304 double sy = (double)ui->ChipHeight->value() * 3438.0 / ui->TLength2->value(); 0305 //const double aspectratio = 3.0/2.0; // Use the default aspect ratio for DSLRs / Film (i.e. 3:2)*/ 0306 0307 // FOV in arcmins 0308 double fov_x = 206264.8062470963552 * ui->cameraWidth->value() * ui->cameraPixelSizeW->value() / 60000.0 / ui->TLength2->value(); 0309 double fov_y = 206264.8062470963552 * ui->cameraHeight->value() * ui->cameraPixelSizeH->value() / 60000.0 / ui->TLength2->value(); 0310 0311 ui->FOVEditX->setText(toString(fov_x)); 0312 ui->FOVEditY->setText(toString(fov_y)); 0313 } 0314 else if (sender() == ui->ComputeHPBW && ui->RTDiameter->value() > 0.0 && ui->WaveLength->value() > 0.0) 0315 { 0316 ui->FOVEditX->setText(toString(34.34 * 1.2 * ui->WaveLength->value() / ui->RTDiameter->value())); 0317 // Beam width for an antenna is usually a circle on the sky. 0318 ui->ShapeBox->setCurrentIndex(4); 0319 ui->FOVEditY->setText(ui->FOVEditX->text()); 0320 slotUpdateFOV(); 0321 } 0322 else if (sender() == ui->ComputeBinocularFOV && ui->LinearFOV->value() > 0.0 && 0323 ui->LinearFOVDistance->currentIndex() >= 0) 0324 { 0325 double sx = 0326 atan((double)ui->LinearFOV->value() / ((ui->LinearFOVDistance->currentIndex() == 0) ? 3000.0 : 1000.0)) * 0327 180.0 * 60.0 / dms::PI; 0328 ui->FOVEditX->setText(toString(sx)); 0329 ui->FOVEditY->setText(ui->FOVEditX->text()); 0330 } 0331 } 0332 0333 void NewFOV::slotEyepieceAFOVChanged(int index) 0334 { 0335 if (index == 0) 0336 { 0337 ui->EyeFOV->setEnabled(true); 0338 } 0339 else 0340 { 0341 bool ok; 0342 ui->EyeFOV->setEnabled(false); 0343 ui->EyeFOV->setValue(ui->EyepieceAFOV->itemData(index).toFloat(&ok)); 0344 Q_ASSERT(ok); 0345 } 0346 } 0347 0348 void NewFOV::slotComputeTelescopeFL() 0349 { 0350 TelescopeFL *telescopeFLDialog = new TelescopeFL(this); 0351 if (telescopeFLDialog->exec() == QDialog::Accepted) 0352 { 0353 ui->TLength1->setValue(telescopeFLDialog->computeFL()); 0354 } 0355 delete telescopeFLDialog; 0356 } 0357 0358 void NewFOV::slotDetectFromINDI() 0359 { 0360 QDBusInterface alignInterface("org.kde.kstars", 0361 "/KStars/Ekos/Align", 0362 "org.kde.kstars.Ekos.Align", 0363 QDBusConnection::sessionBus()); 0364 0365 QDBusReply<QList<double>> cameraReply = alignInterface.call("cameraInfo"); 0366 if (cameraReply.isValid()) 0367 { 0368 QList<double> values = cameraReply.value(); 0369 0370 ui->cameraWidth->setValue(values[0]); 0371 ui->cameraHeight->setValue(values[1]); 0372 ui->cameraPixelSizeW->setValue(values[2]); 0373 ui->cameraPixelSizeH->setValue(values[3]); 0374 } 0375 0376 QDBusReply<QList<double>> telescopeReply = alignInterface.call("telescopeInfo"); 0377 if (telescopeReply.isValid()) 0378 { 0379 QList<double> values = telescopeReply.value(); 0380 ui->TLength2->setValue(values[0]); 0381 } 0382 0383 QDBusReply<QList<double>> solutionReply = alignInterface.call("getSolutionResult"); 0384 if (solutionReply.isValid()) 0385 { 0386 QList<double> values = solutionReply.value(); 0387 if (values[0] > -1e6) 0388 ui->FOVEditRotation->setText(QString::number(values[0])); 0389 } 0390 } 0391 0392 0393 //-------------TelescopeFL------------------// 0394 0395 TelescopeFL::TelescopeFL(QWidget *parent) : QDialog(parent), aperture(nullptr), fNumber(nullptr), apertureUnit(nullptr) 0396 { 0397 setWindowTitle(i18nc("@title:window", "Telescope Focal Length Calculator")); 0398 0399 //QWidget *mainWidget = new QWidget( this ); 0400 QGridLayout *mainLayout = new QGridLayout(this); 0401 setLayout(mainLayout); 0402 0403 aperture = new QDoubleSpinBox(); 0404 aperture->setRange(0.0, 100000.0); 0405 aperture->setDecimals(2); 0406 aperture->setSingleStep(0.1); 0407 0408 fNumber = new QDoubleSpinBox(); 0409 fNumber->setRange(0.0, 99.9); 0410 fNumber->setDecimals(2); 0411 fNumber->setSingleStep(0.1); 0412 0413 apertureUnit = new QComboBox(this); 0414 apertureUnit->insertItem(0, i18nc("millimeters", "mm")); 0415 apertureUnit->insertItem(1, i18n("inch")); 0416 0417 mainLayout->addWidget(new QLabel(i18n("Aperture diameter: "), this), 0, 0); 0418 mainLayout->addWidget(aperture, 0, 1); 0419 mainLayout->addWidget(apertureUnit, 0, 2); 0420 mainLayout->addWidget(new QLabel(i18nc("F-Number or F-Ratio of optical system", "F-Number: "), this), 1, 0); 0421 mainLayout->addWidget(fNumber, 1, 1); 0422 0423 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0424 mainLayout->addWidget(buttonBox); 0425 connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 0426 connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 0427 0428 show(); 0429 } 0430 0431 double TelescopeFL::computeFL() const 0432 { 0433 const double inch_to_mm = 25.4; // 1 inch, by definition, is 25.4 mm 0434 return (aperture->value() * fNumber->value() * 0435 ((apertureUnit->currentIndex() == 1) ? 0436 inch_to_mm : 0437 1.0)); // Focal Length = Aperture * F-Number, by definition of F-Number 0438 } 0439 0440 unsigned int FOVDialog::currentItem() const 0441 { 0442 return fov->FOVListBox->currentRow(); 0443 }