File indexing completed on 2024-04-28 15:10:33

0001 /*
0002     SPDX-FileCopyrightText: 2023 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "collimationoverlayoptions.h"
0008 #include <kstars_debug.h>
0009 
0010 #include "kstarsdata.h"
0011 #include "kstars.h"
0012 #include "qmetaobject.h"
0013 
0014 #include <QTimer>
0015 #include <QSqlTableModel>
0016 #include <QSqlRecord>
0017 #include <basedevice.h>
0018 #include <algorithm>
0019 
0020 CollimationOverlayOptions *CollimationOverlayOptions::m_Instance = nullptr;
0021 
0022 CollimationOverlayOptions *CollimationOverlayOptions::Instance(QWidget *parent)
0023 {
0024     if (m_Instance == nullptr) {
0025         m_Instance = new CollimationOverlayOptions(parent);
0026     }
0027     return m_Instance;
0028 }
0029 
0030 void CollimationOverlayOptions::release()
0031 {
0032     delete(m_Instance);
0033     m_Instance = nullptr;
0034 }
0035 
0036 CollimationOverlayOptions::CollimationOverlayOptions(QWidget *parent) : QDialog(parent)
0037 {
0038 #ifdef Q_OS_OSX
0039     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0040 #endif
0041 
0042     setupUi(this);
0043 
0044     // Enable Checkbox
0045     connect(EnableCheckBox, static_cast<void (QCheckBox::*)(int)>(&QCheckBox::stateChanged), this,
0046             [this](int state) {
0047                 updateValue(state, "Enabled");
0048             });
0049 
0050     // Type Combo
0051     connect(typeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0052             [this]() {
0053                 updateValue(typeComboBox, "Type");
0054 
0055                 // Anchor types can't have a size, rotation, count, PCD, thickness, or colour
0056                 if (typeComboBox->currentIndex() == 0) {
0057                     sizeXSpinBox->setValue(0);
0058                     sizeYSpinBox->setValue(0);
0059                     rotationDoubleSpinBox->setValue(0.0);
0060                     countSpinBox->setValue(0);
0061                     pcdSpinBox->setValue(0);
0062                     thicknessSpinBox->setEnabled(false);
0063                     sizeXSpinBox->setEnabled(false);
0064                     sizeYSpinBox->setEnabled(false);
0065                     rotationDoubleSpinBox->setEnabled(false);
0066                     countSpinBox->setEnabled(false);
0067                     pcdSpinBox->setEnabled(false);
0068                     thicknessSpinBox->setEnabled(false);
0069                     colourButton->setColor("Black");
0070                     colourButton->setEnabled(false);
0071                 } else {
0072                     sizeXSpinBox->setEnabled(true);
0073                     sizeYSpinBox->setEnabled(true);
0074                     rotationDoubleSpinBox->setEnabled(true);
0075                     countSpinBox->setEnabled(true);
0076                     pcdSpinBox->setEnabled(true);
0077                     thicknessSpinBox->setEnabled(true);
0078                     colourButton->setEnabled(true);
0079                 }
0080 
0081                 // Default to linked XY size for Ellipse types only
0082                 if (typeComboBox->currentIndex() == 1) {
0083                     linkXYB->setIcon(QIcon::fromTheme("document-encrypt"));
0084                 } else {
0085                     linkXYB->setIcon(QIcon::fromTheme("document-decrypt"));
0086                 }
0087 
0088                 // Allow sizeY = 0 for lines
0089                 if (typeComboBox->currentIndex() == 3){
0090                     sizeYSpinBox->setMinimum(0);
0091                 } else {
0092                     sizeYSpinBox->setMinimum(1);
0093                 }
0094             });
0095 
0096     //Populate typeComboBox
0097     QStringList typeValues;
0098     collimationoverlaytype m_types;
0099     const QMetaObject *m_metaobject = m_types.metaObject();
0100     QMetaEnum m_metaEnum = m_metaobject->enumerator(m_metaobject->indexOfEnumerator("Types"));
0101     for (int i = 0; i < m_metaEnum.keyCount(); i++) {
0102         typeValues << tr(m_metaEnum.key(i));
0103     }
0104     typeComboBox->clear();
0105     typeComboBox->addItems(typeValues);
0106     typeComboBox->setCurrentIndex(0);
0107 
0108     // SizeX SpinBox
0109     connect(sizeXSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
0110             [this](int value) {
0111                 updateValue(value, "SizeX");
0112                 if (linkXYB->icon().name() == "document-encrypt") {
0113                     sizeYSpinBox->setValue(sizeXSpinBox->value());
0114                     updateValue(value, "SizeY");
0115                 }
0116             });
0117 
0118     // SizeY SpinBox
0119     connect(sizeYSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
0120             [this](int value) {
0121                 updateValue(value, "SizeY");
0122             });
0123 
0124     //LinkXY Button
0125     linkXYB->setIcon(QIcon::fromTheme("document-decrypt"));
0126     connect(linkXYB, &QPushButton::clicked, this, [this] {
0127         if (linkXYB->icon().name() == "document-decrypt") {
0128             sizeYSpinBox->setValue(sizeXSpinBox->value());
0129             linkXYB->setIcon(QIcon::fromTheme("document-encrypt"));
0130             sizeYSpinBox->setEnabled(false);
0131         } else if (linkXYB->icon().name() == "document-encrypt") {
0132             linkXYB->setIcon(QIcon::fromTheme("document-decrypt"));
0133             sizeYSpinBox->setEnabled(true);
0134         }
0135     });
0136 
0137     // OffsetX SpinBox
0138     connect(offsetXSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
0139             [this](int value) {
0140                 updateValue(value, "OffsetX");
0141             });
0142 
0143     // OffsetY SpinBox
0144     connect(offsetYSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
0145             [this](int value) {
0146                 updateValue(value, "OffsetY");
0147             });
0148 
0149     // Count SpinBox
0150     connect(countSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
0151             [this](int value) {
0152                 updateValue(value, "Count");
0153                 // Single count elements can't have rotation or PCD
0154                 if (value == 1) {
0155                     pcdSpinBox->setEnabled(false);
0156                     rotationDoubleSpinBox->setEnabled(false);
0157                 } else {
0158                     pcdSpinBox->setEnabled(true);
0159                     rotationDoubleSpinBox->setEnabled(true);
0160                 }
0161             });
0162 
0163     //PCD SpinBox
0164     connect(pcdSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
0165             [this](int value) {
0166                 updateValue(value, "PCD");
0167             });
0168 
0169     // Rotation DoubleSpinBox
0170     connect(rotationDoubleSpinBox, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
0171             [this](double value) {
0172                 updateValue(value, "Rotation");
0173             });
0174 
0175     // Color KColorButton
0176     colourButton->setAlphaChannelEnabled(true);
0177     connect(colourButton, static_cast<void (KColorButton::*)(const QColor&)>(&KColorButton::changed), this,
0178             [this](QColor value) {
0179                 updateValue(value, "Colour");
0180             });
0181 
0182     // Thickness SpinBox
0183     connect(thicknessSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
0184             [this](int value) {
0185                 updateValue(value, "Thickness");
0186             });
0187 
0188     connect(addB, &QPushButton::clicked, this, [this]() {
0189         if (addB->icon().name() == "dialog-ok-apply") {
0190             elementNamesList->clearSelection();
0191             addB->setIcon(QIcon::fromTheme("list-add"));
0192             selectCollimationOverlayElement("");
0193         } else {
0194             addElement(nameLineEdit->text());
0195             m_CollimationOverlayElementsModel->select();
0196             refreshModel();
0197             elementNamesList->clearSelection();
0198         }
0199     });
0200 
0201     connect(removeB, &QPushButton::clicked, this, [this]() {
0202         if (elementNamesList->currentItem() != nullptr) {
0203             removeCollimationOverlayElement(elementNamesList->currentItem()->text());
0204             refreshElements();
0205             elementNamesList->clearSelection();
0206             addB->setIcon(QIcon::fromTheme("list-add"));
0207         }
0208     });
0209 
0210     connect(elementNamesList, &QListWidget::itemClicked, this, [this](QListWidgetItem * item) {
0211                 Q_UNUSED(item);
0212                 addB->setIcon(QIcon::fromTheme("list-add"));
0213                 removeB->setEnabled(true);
0214             });
0215 
0216     connect(elementNamesList, &QListWidget::itemDoubleClicked, this, [this](QListWidgetItem * item) {
0217         selectCollimationOverlayElement(item);
0218         addB->setIcon(QIcon::fromTheme("dialog-ok-apply"));
0219         if (typeComboBox->currentIndex() == 1) {
0220             linkXYB->setIcon(QIcon::fromTheme("document-encrypt"));
0221         } else {
0222             linkXYB->setIcon(QIcon::fromTheme("document-decrypt"));
0223         }
0224     });
0225 
0226     connect(renameB, &QPushButton::clicked, this, [this] {
0227         renameCollimationOverlayElement(nameLineEdit->text());
0228     })  ;
0229 
0230     connect(elementNamesList, &QListWidget::currentRowChanged, this, [this](int row) {
0231         Q_UNUSED(row);
0232         selectCollimationOverlayElement("");
0233     });
0234 
0235     initModel();
0236 }
0237 
0238 void CollimationOverlayOptions::initModel()
0239 {
0240     m_CollimationOverlayElements.clear();
0241     auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName());
0242     m_CollimationOverlayElementsModel = new QSqlTableModel(this, userdb);
0243     connect(m_CollimationOverlayElementsModel, &QSqlTableModel::dataChanged, this, [this]() {
0244         m_CollimationOverlayElements.clear();
0245         for (int i = 0; i < m_CollimationOverlayElementsModel->rowCount(); ++i) {
0246             QVariantMap recordMap;
0247             QSqlRecord record = m_CollimationOverlayElementsModel->record(i);
0248             for (int j = 0; j < record.count(); j++)
0249                 recordMap[record.fieldName(j)] = record.value(j);
0250 
0251             m_CollimationOverlayElements.append(recordMap);
0252         }
0253         m_ElementNames.clear();
0254         for (auto &oneElement : m_CollimationOverlayElements) {
0255             m_ElementNames << oneElement["Name"].toString();
0256         }
0257         elementNamesList->clear();
0258         elementNamesList->addItems(m_ElementNames);
0259         elementNamesList->setEditTriggers(QAbstractItemView::AllEditTriggers);
0260         emit updated();
0261     });
0262     refreshModel();
0263 }
0264 
0265 void CollimationOverlayOptions::refreshModel()
0266 {
0267     m_CollimationOverlayElements.clear();
0268     KStars::Instance()->data()->userdb()->GetCollimationOverlayElements(m_CollimationOverlayElements);
0269     m_ElementNames.clear();
0270     for (auto &oneElement : m_CollimationOverlayElements) {
0271         m_ElementNames << oneElement["Name"].toString();
0272     }
0273     elementNamesList->clear();
0274     elementNamesList->addItems(m_ElementNames);
0275 }
0276 
0277 QString CollimationOverlayOptions::addElement(const QString &name)
0278 {
0279     QVariantMap element;
0280     element["Name"] = uniqueElementName(name, typeComboBox->currentText());
0281     element["Enabled"] = EnableCheckBox->checkState();
0282     element["Type"] = typeComboBox->currentText();
0283     element["SizeX"] = sizeXSpinBox->value();
0284     element["SizeY"] = sizeYSpinBox->value();
0285     element["OffsetX"] = offsetXSpinBox->value();
0286     element["OffsetY"] = offsetYSpinBox->value();
0287     element["Count"] = countSpinBox->value();
0288     element["PCD"] = pcdSpinBox->value();
0289     element["Rotation"] = rotationDoubleSpinBox->value();
0290     element["Colour"] = colourButton->color();
0291     element["Thickness"] = thicknessSpinBox->value();
0292 
0293     KStarsData::Instance()->userdb()->AddCollimationOverlayElement(element);
0294     emit updated();
0295     return element["Name"].toString();
0296 }
0297 
0298 bool CollimationOverlayOptions::setCollimationOverlayElementValue(const QString &name, const QString &field, const QVariant &value)
0299 {
0300     for (auto &oneElement : m_CollimationOverlayElements) {
0301         if (oneElement["Name"].toString() == name) {
0302             // If value did not change, just return true
0303             if (oneElement[field] == value) {
0304                 return true;
0305             }
0306             // Update field and database.
0307             oneElement[field] = value;
0308             KStarsData::Instance()->userdb()->UpdateCollimationOverlayElement(oneElement, oneElement["id"].toInt());
0309             emit updated();
0310             return true;
0311         }
0312     }
0313     return false;
0314 }
0315 
0316 void CollimationOverlayOptions::renameCollimationOverlayElement(const QString &name)
0317 {
0318     if (m_CurrentElement != nullptr && (*m_CurrentElement)["Name"] != name) {
0319         auto pos = elementNamesList->currentRow();
0320         // ensure element name uniqueness
0321         auto unique = uniqueElementName(name, (*m_CurrentElement)["Type"].toString());
0322         // update the element database entry
0323         setCollimationOverlayElementValue((*m_CurrentElement)["Name"].toString(), "Name", unique);
0324         // propagate the unique name to the current selection
0325         elementNamesList->currentItem()->setText(unique);
0326         // refresh the trains
0327         refreshElements();
0328         // refresh selection
0329         elementNamesList->setCurrentRow(pos);
0330         selectCollimationOverlayElement(unique);
0331     }
0332 }
0333 
0334 bool CollimationOverlayOptions::setCollimationOverlayElement(const QJsonObject &element)
0335 {
0336     auto oneElement = getCollimationOverlayElement(element["id"].toInt());
0337     if (!oneElement.empty()) {
0338         KStarsData::Instance()->userdb()->UpdateCollimationOverlayElement(element.toVariantMap(), oneElement["id"].toInt());
0339         refreshElements();
0340         return true;
0341     }
0342     return false;
0343 }
0344 
0345 bool CollimationOverlayOptions::removeCollimationOverlayElement(const QString &name)
0346 {
0347     for (auto &oneElement : m_CollimationOverlayElements) {
0348         if (oneElement["Name"].toString() == name) {
0349             auto id = oneElement["id"].toInt();
0350             KStarsData::Instance()->userdb()->DeleteCollimationOverlayElement(id);
0351             refreshElements();
0352             return true;
0353         }
0354     }
0355     return false;
0356 }
0357 
0358 QString CollimationOverlayOptions::uniqueElementName(QString name, QString type)
0359 {
0360     if ("" == name) name = type;
0361     QString result = name;
0362     int nr = 1;
0363     while (m_ElementNames.contains(result)) {
0364         result = QString("%1 (%2)").arg(name).arg(nr++);
0365     }
0366     return result;
0367 }
0368 
0369 bool CollimationOverlayOptions::selectCollimationOverlayElement(QListWidgetItem *item)
0370 {
0371     if (item != nullptr && selectCollimationOverlayElement(item->text())) {
0372         return true;
0373     }
0374     return false;
0375 }
0376 
0377 bool CollimationOverlayOptions::selectCollimationOverlayElement(const QString &name)
0378 {
0379     for (auto &oneElement : m_CollimationOverlayElements) {
0380         if (oneElement["Name"].toString() == name) {
0381             editing = true;
0382             m_CurrentElement = &oneElement;
0383             nameLineEdit->setText(oneElement["Name"].toString());
0384             renameB->setEnabled(true);
0385             EnableCheckBox->setChecked(oneElement["Enabled"].toUInt());
0386             typeComboBox->setCurrentText(oneElement["Type"].toString());
0387             sizeXSpinBox->setValue(oneElement["SizeX"].toUInt());
0388             sizeYSpinBox->setValue(oneElement["SizeY"].toUInt());
0389             offsetXSpinBox->setValue(oneElement["OffsetX"].toUInt());
0390             offsetYSpinBox->setValue(oneElement["OffsetY"].toUInt());
0391             countSpinBox->setValue(oneElement["Count"].toUInt());
0392             pcdSpinBox->setValue(oneElement["PCD"].toUInt());
0393             rotationDoubleSpinBox->setValue(oneElement["Rotation"].toDouble());
0394             QColor tempColour;
0395             tempColour.setNamedColor(oneElement["Colour"].toString());
0396             colourButton->setColor(tempColour);
0397             thicknessSpinBox->setValue(oneElement["Thickness"].toUInt());
0398             removeB->setEnabled(m_CollimationOverlayElements.length() > 0);
0399             elementConfigBox->setEnabled(true);
0400             return true;
0401         }
0402     }
0403 
0404     // none found
0405     editing = false;
0406     nameLineEdit->setText("");
0407     renameB->setEnabled(false);
0408     EnableCheckBox->setCheckState(Qt::Checked);
0409     typeComboBox->setCurrentText("--");
0410     sizeXSpinBox->setValue(0);
0411     sizeYSpinBox->setValue(0);
0412     offsetXSpinBox->setValue(0);
0413     offsetYSpinBox->setValue(0);
0414     countSpinBox->setValue(0);
0415     pcdSpinBox->setValue(0);
0416     rotationDoubleSpinBox->setValue(0.0);
0417     QColor tempColour;
0418     tempColour.setNamedColor("White");
0419     colourButton->setColor(tempColour);
0420     thicknessSpinBox->setValue(1);
0421 
0422     removeB->setEnabled(false);
0423     return false;
0424 }
0425 
0426 void CollimationOverlayOptions::openEditor()
0427 {
0428     initModel();
0429     show();
0430 }
0431 
0432 const QVariantMap CollimationOverlayOptions::getCollimationOverlayElement(uint8_t id) const
0433 {
0434     for (auto &oneElement : m_CollimationOverlayElements) {
0435         if (oneElement["id"].toInt() == id)
0436             return oneElement;
0437     }
0438     return QVariantMap();
0439 }
0440 
0441 bool CollimationOverlayOptions::exists(uint8_t id) const
0442 {
0443     for (auto &oneElement : m_CollimationOverlayElements) {
0444         if (oneElement["id"].toInt() == id)
0445             return true;
0446     }
0447     return false;
0448 }
0449 
0450 const QVariantMap CollimationOverlayOptions::getCollimationOverlayElement(const QString &name) const
0451 {
0452     for (auto &oneElement : m_CollimationOverlayElements) {
0453         if (oneElement["Name"].toString() == name) {
0454             return oneElement;
0455         }
0456     }
0457     return QVariantMap();
0458 }
0459 
0460 void CollimationOverlayOptions::refreshElements()
0461 {
0462     refreshModel();
0463     emit updated();
0464 }
0465 
0466 int CollimationOverlayOptions::id(const QString &name) const
0467 {
0468     for (auto &oneElement : m_CollimationOverlayElements) {
0469         if (oneElement["Name"].toString() == name)
0470             return oneElement["id"].toUInt();
0471     }
0472     return -1;
0473 }
0474 
0475 QString CollimationOverlayOptions::name(int id) const
0476 {
0477     for (auto &oneElement : m_CollimationOverlayElements) {
0478         if (oneElement["id"].toInt() == id)
0479             return oneElement["name"].toString();
0480     }
0481     return QString();
0482 }
0483 
0484 void CollimationOverlayOptions::updateValue(QComboBox *cb, const QString &element)
0485 {
0486     if (elementNamesList->currentItem() != nullptr && editing == true) {
0487         setCollimationOverlayElementValue(elementNamesList->currentItem()->text(), element, cb->currentText());
0488     }
0489 }
0490 
0491 void CollimationOverlayOptions::updateValue(double value, const QString &element)
0492 {
0493     if (elementNamesList->currentItem() != nullptr && editing == true) {
0494         setCollimationOverlayElementValue(elementNamesList->currentItem()->text(), element, value);
0495     }
0496 }
0497 
0498 void CollimationOverlayOptions::updateValue(int value, const QString &element)
0499 {
0500     if (elementNamesList->currentItem() != nullptr && editing == true) {
0501         setCollimationOverlayElementValue(elementNamesList->currentItem()->text(), element, value);
0502     }
0503 }
0504 
0505 void CollimationOverlayOptions::updateValue(QColor value, const QString &element)
0506 {
0507     if (elementNamesList->currentItem() != nullptr && editing == true) {
0508         setCollimationOverlayElementValue(elementNamesList->currentItem()->text(), element, value);
0509     }
0510 }
0511 
0512 void CollimationOverlayOptions::updateValue(QString value, const QString &element)
0513 {
0514     if (elementNamesList->currentItem() != nullptr && editing == true) {
0515         setCollimationOverlayElementValue(elementNamesList->currentItem()->text(), element, value);
0516     }
0517 }