File indexing completed on 2024-03-24 15:15:34

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 }