1 /*
2     SPDX-FileCopyrightText: 2017 Jasem Mutlaq <mutlaqja@ikarustech.com>
3     SPDX-FileCopyrightText: 2017 Robert Lancaster <rlancaste@gmail.com>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "stellarsolverprofileeditor.h"
9 
10 #include "kstars.h"
11 #include "Options.h"
12 #include "kspaths.h"
13 #include "ksmessagebox.h"
14 #include "stellarsolverprofile.h"
15 
16 #include <KConfigDialog>
17 #include <QInputDialog>
18 #include <QFileDialog>
19 #include <QSettings>
20 #include <stellarsolver.h>
21 
22 namespace Ekos
23 {
StellarSolverProfileEditor(QWidget * parent,ProfileGroup group,KConfigDialog * dialog)24 StellarSolverProfileEditor::StellarSolverProfileEditor(QWidget *parent, ProfileGroup group,
25         KConfigDialog *dialog) : QWidget(KStars::Instance())
26 {
27     Q_UNUSED(parent);
28     setupUi(this);
29 
30     //Get a pointer to the KConfigDialog
31     m_ConfigDialog = dialog;
32 
33     setProfileGroup(group);
34     optionsProfileGroup->setEnabled(false);
35 
36     /* // we may want to do this eventually
37     connect(optionsProfileGroup, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index){
38         setProfileGroup((ProfileGroup)index);
39     });
40     */
41 
42     openProfile->setIcon(
43         QIcon::fromTheme("document-open"));
44     openProfile->setAttribute(Qt::WA_LayoutUsesWidgetRect);
45     connect(openProfile, &QPushButton::clicked, this, &StellarSolverProfileEditor::openSingleProfile);
46 
47     saveProfile->setIcon(
48         QIcon::fromTheme("document-save"));
49     saveProfile->setAttribute(Qt::WA_LayoutUsesWidgetRect);
50     connect(saveProfile, &QPushButton::clicked, this, &StellarSolverProfileEditor::saveSingleProfile);
51 
52     /*  This is not implemented yet.
53     copyProfile->setIcon(
54         QIcon::fromTheme("edit-copy"));
55     copyProfile->setAttribute(Qt::WA_LayoutUsesWidgetRect);
56     */
57     copyProfile->setVisible(false);  //until we enable it.
58 
59     saveBackups->setIcon(
60         QIcon::fromTheme("document-save"));
61     saveBackups->setAttribute(Qt::WA_LayoutUsesWidgetRect);
62     connect(saveBackups, &QPushButton::clicked, this, &StellarSolverProfileEditor::saveBackupProfiles);
63 
64     loadBackups->setIcon(
65         QIcon::fromTheme("document-open"));
66     loadBackups->setAttribute(Qt::WA_LayoutUsesWidgetRect);
67     connect(loadBackups, &QPushButton::clicked, this, &StellarSolverProfileEditor::openBackupProfiles);
68 
69     reloadProfiles->setIcon(
70         QIcon::fromTheme("system-reboot"));
71     reloadProfiles->setAttribute(Qt::WA_LayoutUsesWidgetRect);
72     connect(reloadProfiles, &QPushButton::clicked, this, &StellarSolverProfileEditor::loadProfiles);
73 
74     loadDefaults->setIcon(
75         QIcon::fromTheme("go-down"));
76     loadDefaults->setAttribute(Qt::WA_LayoutUsesWidgetRect);
77     connect(loadDefaults, &QPushButton::clicked, this, &StellarSolverProfileEditor::loadDefaultProfiles);
78 
79     connect(addOptionProfile, &QAbstractButton::clicked, this, [this]()
80     {
81         bool ok;
82         QString name = QInputDialog::getText(this, tr("New Options Profile"),
83                                              tr("What would you like your profile to be called?"), QLineEdit::Normal,
84                                              "", &ok);
85         if (ok && !name.isEmpty())
86         {
87             disconnectOptionsProfileComboBox();
88             SSolver::Parameters params = getSettingsFromUI();
89             params.listName = name;
90             optionsList.append(params);
91             optionsProfile->addItem(name);
92             optionsProfile->setCurrentText(name);
93             openOptionsProfileNum = optionsProfile->count() - 1;
94             settingJustChanged();
95             connectOptionsProfileComboBox();
96         }
97     });
98 
99     connect(removeOptionProfile, &QAbstractButton::clicked, this, [this]()
100     {
101         if(optionsList.count() == 0)
102             return;
103         int item = optionsProfile->currentIndex();
104         disconnectOptionsProfileComboBox();
105         optionsProfile->removeItem(item);
106         optionsList.removeAt(item);
107         if(optionsProfile->count() > 0)
108         {
109             if(item > optionsList.count() - 1) //So that it doesn't try to set the new list to a nonexistant one
110                 item--;
111             openOptionsProfileNum = item;
112             loadOptionsProfileIgnoreOldSettings(item); //Because the old one no longer exists
113         }
114         settingJustChanged();
115         connectOptionsProfileComboBox();
116     });
117 
118     connect(description, &QTextEdit::textChanged, this, &StellarSolverProfileEditor::settingJustChanged);
119 
120     QList<QLineEdit *> lines = this->findChildren<QLineEdit *>();
121     for(QLineEdit *line : lines)
122         connect(line, &QLineEdit::textEdited, this, &StellarSolverProfileEditor::settingJustChanged);
123 
124     QList<QCheckBox *> checks = this->findChildren<QCheckBox *>();
125     for(QCheckBox *check : checks)
126         connect(check, &QCheckBox::stateChanged, this, &StellarSolverProfileEditor::settingJustChanged);
127 
128     QList<QComboBox *> combos = this->findChildren<QComboBox *>();
129     for(QComboBox *combo : combos)
130     {
131         if(combo != optionsProfile)
132             connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &StellarSolverProfileEditor::settingJustChanged);
133     }
134 
135     QList<QSpinBox *> spins = this->findChildren<QSpinBox *>();
136     for(QSpinBox *spin : spins)
137         connect(spin, QOverload<int>::of(&QSpinBox::valueChanged), this, &StellarSolverProfileEditor::settingJustChanged);
138 
139     if(selectedProfileGroup != AlignProfiles)
140         astrometryOptions->setVisible(false);
141     connect(m_ConfigDialog->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(slotApply()));
142     connect(m_ConfigDialog->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(slotApply()));
143 }
144 
setProfileGroup(ProfileGroup group)145 void StellarSolverProfileEditor::setProfileGroup(ProfileGroup group)
146 {
147     selectedProfileGroup = group;
148     optionsProfileGroup->setCurrentIndex(static_cast<int>(group));
149     QString profileGroupFileName;
150     switch(selectedProfileGroup)
151     {
152         case AlignProfiles:
153             profileGroupFileName = "SavedAlignProfiles.ini";
154             break;
155         case FocusProfiles:
156             profileGroupFileName = "SavedFocusProfiles.ini";
157             break;
158         case GuideProfiles:
159             profileGroupFileName = "SavedGuideProfiles.ini";
160             break;
161         case HFRProfiles:
162             profileGroupFileName = "SavedHFRProfiles.ini";
163             break;
164     }
165 
166     savedOptionsProfiles = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath(profileGroupFileName);
167     loadProfiles();
168 }
169 
connectOptionsProfileComboBox()170 void StellarSolverProfileEditor::connectOptionsProfileComboBox()
171 {
172     connect(optionsProfile, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
173             &StellarSolverProfileEditor::loadOptionsProfile);
174 }
175 
disconnectOptionsProfileComboBox()176 void StellarSolverProfileEditor::disconnectOptionsProfileComboBox()
177 {
178     disconnect(optionsProfile, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
179                &StellarSolverProfileEditor::loadOptionsProfile);
180 }
181 
settingJustChanged()182 void StellarSolverProfileEditor::settingJustChanged()
183 {
184     optionsAreSaved = false;
185     m_ConfigDialog->button(QDialogButtonBox::Apply)->setEnabled(true);
186 }
187 
loadProfile(int profile)188 void StellarSolverProfileEditor::loadProfile(int profile)
189 {
190     optionsProfile->setCurrentIndex(profile);
191 }
192 
loadOptionsProfile()193 void StellarSolverProfileEditor::loadOptionsProfile()
194 {
195     if(optionsProfile->count() == 0)
196         return;
197     if(openOptionsProfileNum < optionsList.count() && openOptionsProfileNum > 0)
198     {
199         SSolver::Parameters editorOptions = getSettingsFromUI();
200         SSolver::Parameters currentProfile = optionsList.at(openOptionsProfileNum);
201         if(!(editorOptions == currentProfile) || editorOptions.description != currentProfile.description)
202         {
203             editorOptions.listName = currentProfile.listName;
204             optionsList.replace(openOptionsProfileNum, editorOptions);
205         }
206     }
207     SSolver::Parameters newProfile = optionsList.at(optionsProfile->currentIndex());
208     QList<QWidget *> controls = this->findChildren<QWidget *>();
209     for(QWidget *control : controls)
210         control->blockSignals(true);
211     sendSettingsToUI(newProfile);
212     for(QWidget *control : controls)
213         control->blockSignals(false);
214     openOptionsProfileNum = optionsProfile->currentIndex();
215 }
216 
loadOptionsProfileIgnoreOldSettings(int index)217 void StellarSolverProfileEditor::loadOptionsProfileIgnoreOldSettings(int index)
218 {
219     if(index >= optionsProfile->count())
220         return;
221     SSolver::Parameters newProfile = optionsList.at(index);
222     QList<QWidget *> controls = this->findChildren<QWidget *>();
223     for(QWidget *control : controls)
224         control->blockSignals(true);
225     sendSettingsToUI(newProfile);
226     optionsProfile->setCurrentIndex(index);
227     openOptionsProfileNum = optionsProfile->currentIndex();
228     for(QWidget *control : controls)
229         control->blockSignals(false);
230 }
231 
232 //This sets all the settings for either the internal or external sextractor
233 //based on the requested settings in the mainwindow interface.
234 //If you are implementing the StellarSolver Library in your progra, you may choose to change some or all of these settings or use the defaults.
getSettingsFromUI()235 SSolver::Parameters StellarSolverProfileEditor::getSettingsFromUI()
236 {
237     SSolver::Parameters params;
238     params.description = description->toPlainText();
239     //These are to pass the parameters to the internal sextractor
240     params.apertureShape = (SSolver::Shape) apertureShape->currentIndex();
241     params.kron_fact = kron_fact->text().toDouble();
242     params.subpix = subpix->text().toInt() ;
243     params.r_min = r_min->text().toFloat();
244     //params.inflags
245     params.magzero = magzero->text().toFloat();
246     params.minarea = minarea->text().toFloat();
247     params.deblend_thresh = deblend_thresh->text().toInt();
248     params.deblend_contrast = deblend_contrast->text().toFloat();
249     params.clean = (cleanCheckBox->isChecked()) ? 1 : 0;
250     params.clean_param = clean_param->text().toDouble();
251     StellarSolver::createConvFilterFromFWHM(&params, fwhm->value());
252 
253     //Star Filter Settings
254     params.resort = resort->isChecked();
255     params.maxSize = maxSize->text().toDouble();
256     params.minSize = minSize->text().toDouble();
257     params.maxEllipse = maxEllipse->text().toDouble();
258     params.initialKeep = initialKeep->text().toInt();
259     params.keepNum = keepNum->text().toInt();
260     params.removeBrightest = brightestPercent->text().toDouble();
261     params.removeDimmest = dimmestPercent->text().toDouble();
262     params.saturationLimit = saturationLimit->text().toDouble();
263 
264     //Settings that usually get set by the config file
265 
266     params.maxwidth = maxWidth->text().toDouble();
267     params.minwidth = minWidth->text().toDouble();
268     params.inParallel = inParallel->isChecked();
269     params.multiAlgorithm = (SSolver::MultiAlgo)multiAlgo->currentIndex();
270     params.solverTimeLimit = solverTimeLimit->text().toInt();
271 
272     params.resort = resort->isChecked();
273     params.autoDownsample = autoDownsample->isChecked();
274     params.downsample = downsample->value();
275     params.search_radius = radius->text().toDouble();
276 
277     return params;
278 }
279 
sendSettingsToUI(SSolver::Parameters a)280 void StellarSolverProfileEditor::sendSettingsToUI(SSolver::Parameters a)
281 {
282 
283     description->setText(a.description);
284     //Sextractor Settings
285 
286     apertureShape->setCurrentIndex(a.apertureShape);
287     kron_fact->setText(QString::number(a.kron_fact));
288     subpix->setText(QString::number(a.subpix));
289     r_min->setText(QString::number(a.r_min));
290 
291     magzero->setText(QString::number(a.magzero));
292     minarea->setText(QString::number(a.minarea));
293     deblend_thresh->setText(QString::number(a.deblend_thresh));
294     deblend_contrast->setText(QString::number(a.deblend_contrast));
295     cleanCheckBox->setChecked(a.clean == 1);
296     clean_param->setText(QString::number(a.clean_param));
297     fwhm->setValue(a.fwhm);
298 
299     //Star Filter Settings
300 
301     maxSize->setText(QString::number(a.maxSize));
302     minSize->setText(QString::number(a.minSize));
303     maxEllipse->setText(QString::number(a.maxEllipse));
304     initialKeep->setText(QString::number(a.initialKeep));
305     keepNum->setText(QString::number(a.keepNum));
306     brightestPercent->setText(QString::number(a.removeBrightest));
307     dimmestPercent->setText(QString::number(a.removeDimmest));
308     saturationLimit->setText(QString::number(a.saturationLimit));
309 
310     //Astrometry Settings
311 
312     autoDownsample->setChecked(a.autoDownsample);
313     downsample->setValue(a.downsample);
314     inParallel->setChecked(a.inParallel);
315     multiAlgo->setCurrentIndex(a.multiAlgorithm);
316     solverTimeLimit->setText(QString::number(a.solverTimeLimit));
317     minWidth->setText(QString::number(a.minwidth));
318     maxWidth->setText(QString::number(a.maxwidth));
319     radius->setText(QString::number(a.search_radius));
320     resort->setChecked(a.resort);
321 }
322 
copySingleProfile()323 void StellarSolverProfileEditor::copySingleProfile()
324 {
325 
326 }
327 
openSingleProfile()328 void StellarSolverProfileEditor::openSingleProfile()
329 {
330     QString fileURL = QFileDialog::getOpenFileName(nullptr, "Load Options Profiles File", dirPath,
331                       "INI files(*.ini)");
332     if (fileURL.isEmpty())
333         return;
334     if(!QFileInfo(fileURL).exists())
335     {
336         KSMessageBox::warning(this, "Message", "The file doesn't exist");
337         return;
338     }
339     QSettings settings(fileURL, QSettings::IniFormat);
340     QStringList groups = settings.childGroups();
341     for(QString group : groups)
342     {
343         settings.beginGroup(group);
344         QStringList keys = settings.childKeys();
345         QMap<QString, QVariant> map;
346         for(QString key : keys)
347             map.insert(key, settings.value(key));
348         SSolver::Parameters newParams = SSolver::Parameters::convertFromMap(map);
349         optionsList.append(newParams);
350         optionsProfile->addItem(group);
351     }
352     if(optionsProfile->count() > 0)
353         loadProfile(optionsProfile->count() - 1);
354     m_ConfigDialog->button(QDialogButtonBox::Apply)->setEnabled(true);
355 }
356 
loadProfiles()357 void StellarSolverProfileEditor::loadProfiles()
358 {
359     if( !optionsAreSaved )
360     {
361         if(QMessageBox::question(this, "Abort?",
362                                  "You made unsaved changes in the settings, do you really wish to overwrite them?") == QMessageBox::No)
363         {
364             return;
365         }
366         optionsAreSaved = true; //They just got overwritten
367     }
368     disconnectOptionsProfileComboBox();
369     optionsProfile->clear();
370     if(QFile(savedOptionsProfiles).exists())
371         optionsList = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
372     else
373         optionsList = getDefaultProfiles();
374     for(SSolver::Parameters params : optionsList)
375         optionsProfile->addItem(params.listName);
376     if(optionsList.count() > 0)
377     {
378         sendSettingsToUI(optionsList.at(0));
379         openOptionsProfileNum = 0;
380     }
381     connectOptionsProfileComboBox();
382     m_ConfigDialog->button(QDialogButtonBox::Apply)->setEnabled(false);
383 }
384 
saveSingleProfile()385 void StellarSolverProfileEditor::saveSingleProfile()
386 {
387     QString fileURL = QFileDialog::getSaveFileName(nullptr, "Save Options Profiles", dirPath,
388                       "INI files(*.ini)");
389     if (fileURL.isEmpty())
390         return;
391     QSettings settings(fileURL, QSettings::IniFormat);
392     SSolver::Parameters params = optionsList.at(optionsProfile->currentIndex());
393     settings.beginGroup(params.listName);
394     QMap<QString, QVariant> map = SSolver::Parameters::convertToMap(params);
395     QMapIterator<QString, QVariant> it(map);
396     while(it.hasNext())
397     {
398         it.next();
399         settings.setValue(it.key(), it.value());
400     }
401     settings.endGroup();
402 }
403 
saveProfiles()404 void StellarSolverProfileEditor::saveProfiles()
405 {
406     QSettings settings(savedOptionsProfiles, QSettings::IniFormat);
407     for(int i = 0 ; i < optionsList.count(); i++)
408     {
409         SSolver::Parameters params = optionsList.at(i);
410         settings.beginGroup(params.listName);
411         QMap<QString, QVariant> map = SSolver::Parameters::convertToMap(params);
412         QMapIterator<QString, QVariant> it(map);
413         while(it.hasNext())
414         {
415             it.next();
416             settings.setValue(it.key(), it.value());
417         }
418         settings.endGroup();
419     }
420     QStringList groups = settings.childGroups();
421     for(QString group : groups)
422     {
423         bool groupInList = false;
424         for(SSolver::Parameters params : optionsList)
425         {
426             if(params.listName == group)
427                 groupInList = true;
428         }
429         if( ! groupInList)
430             settings.remove(group);
431     }
432 }
433 
getDefaultProfiles()434 QList<SSolver::Parameters> StellarSolverProfileEditor::getDefaultProfiles()
435 {
436     switch(selectedProfileGroup)
437     {
438         case FocusProfiles:
439             return getDefaultFocusOptionsProfiles();
440         case HFRProfiles:
441             return getDefaultHFROptionsProfiles();
442         case GuideProfiles:
443             return getDefaultGuideOptionsProfiles();
444         default:
445         case AlignProfiles:
446             return getDefaultAlignOptionsProfiles();
447     }
448 }
449 
loadDefaultProfiles()450 void StellarSolverProfileEditor::loadDefaultProfiles()
451 {
452     if( !optionsAreSaved )
453     {
454         if(QMessageBox::question(this, "Abort?",
455                                  "You made unsaved changes in the settings, do you really wish to overwrite them?") == QMessageBox::No)
456         {
457             return;
458         }
459         optionsAreSaved = true; //They just got overwritten
460     }
461     disconnectOptionsProfileComboBox();
462     optionsList = getDefaultProfiles();
463     optionsProfile->clear();
464     for(SSolver::Parameters param : optionsList)
465         optionsProfile->addItem(param.listName);
466     connectOptionsProfileComboBox();
467     if(optionsProfile->count() > 0)
468         loadOptionsProfileIgnoreOldSettings(0);
469     m_ConfigDialog->button(QDialogButtonBox::Apply)->setEnabled(true);
470 }
471 
saveBackupProfiles()472 void StellarSolverProfileEditor::saveBackupProfiles()
473 {
474     QString fileURL = QFileDialog::getSaveFileName(nullptr, "Save Options Profiles", dirPath,
475                       "INI files(*.ini)");
476     if (fileURL.isEmpty())
477         return;
478     QSettings settings(fileURL, QSettings::IniFormat);
479     for(int i = 0 ; i < optionsList.count(); i++)
480     {
481         SSolver::Parameters params = optionsList.at(i);
482         settings.beginGroup(params.listName);
483         QMap<QString, QVariant> map = SSolver::Parameters::convertToMap(params);
484         QMapIterator<QString, QVariant> it(map);
485         while(it.hasNext())
486         {
487             it.next();
488             settings.setValue(it.key(), it.value());
489         }
490         settings.endGroup();
491     }
492 }
493 
openBackupProfiles()494 void StellarSolverProfileEditor::openBackupProfiles()
495 {
496     QString fileURL = QFileDialog::getOpenFileName(nullptr, "Load Options Profiles File", dirPath,
497                       "INI files(*.ini)");
498     if (fileURL.isEmpty())
499         return;
500     if(!QFileInfo(fileURL).exists())
501     {
502         QMessageBox::warning(this, "Message", "The file doesn't exist");
503         return;
504     }
505     disconnectOptionsProfileComboBox();
506     optionsList.clear();
507     QSettings settings(fileURL, QSettings::IniFormat);
508     QStringList groups = settings.childGroups();
509     for(QString group : groups)
510     {
511         settings.beginGroup(group);
512         QStringList keys = settings.childKeys();
513         QMap<QString, QVariant> map;
514         for(QString key : keys)
515             map.insert(key, settings.value(key));
516         SSolver::Parameters newParams = SSolver::Parameters::convertFromMap(map);
517         optionsList.append(newParams);
518         optionsProfile->addItem(group);
519     }
520     if(optionsProfile->count() > 0)
521         loadOptionsProfileIgnoreOldSettings(0);
522     connectOptionsProfileComboBox();
523     m_ConfigDialog->button(QDialogButtonBox::Apply)->setEnabled(true);
524 }
525 
slotApply()526 void StellarSolverProfileEditor::slotApply()
527 {
528     int index = optionsProfile->currentIndex();
529     SSolver::Parameters currentParams = getSettingsFromUI();
530     SSolver::Parameters savedParams = optionsList.at(index);
531     if(!(currentParams == savedParams) || currentParams.description != savedParams.description)
532     {
533         currentParams.listName = savedParams.listName;
534         optionsList.replace(index, currentParams);
535     }
536     optionsAreSaved = true;
537 
538     saveProfiles();
539     emit optionsProfilesUpdated();
540 }
541 
542 }
543