1 /* SPDX-FileCopyrightText: 2020-2021 Tobias Leupold <tl@l3u.de>
2 
3    SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
4 */
5 
6 // Local includes
7 #include "SettingsDialog.h"
8 #include "Settings.h"
9 
10 // KDE includes
11 #include <KLocalizedString>
12 
13 // Qt includes
14 #include <QVBoxLayout>
15 #include <QDialogButtonBox>
16 #include <QDebug>
17 #include <QGroupBox>
18 #include <QGridLayout>
19 #include <QLabel>
20 #include <QComboBox>
21 #include <QSpinBox>
22 #include <QPushButton>
23 #include <QColorDialog>
24 #include <QCheckBox>
25 #include <QScrollArea>
26 #include <QScrollBar>
27 #include <QMessageBox>
28 #include <QHBoxLayout>
29 
SettingsDialog(Settings * settings,QWidget * parent)30 SettingsDialog::SettingsDialog(Settings *settings, QWidget *parent)
31     : QDialog(parent), m_settings(settings)
32 {
33     setAttribute(Qt::WA_DeleteOnClose, true);
34     setWindowTitle(i18n("KGeoTag: Settings"));
35 
36     auto *mainLayout = new QVBoxLayout(this);
37 
38     // Header
39 
40     auto *header = new QLabel(i18n("KGeoTag settings"));
41     header->setStyleSheet(QStringLiteral("QLabel { font-weight: bold; font-size: %1pt; }").arg(
42         (int) double(header->font().pointSize()) * 1.2));
43     header->setAlignment(Qt::AlignCenter);
44     mainLayout->addWidget(header);
45 
46     // Settings
47 
48     setStyleSheet(QStringLiteral("QGroupBox { font-weight: bold; }"));
49 
50     auto *settingsWidget = new QWidget;
51 
52     int row;
53 
54     auto *layout = new QVBoxLayout(settingsWidget);
55 
56     // Image lists
57 
58     auto *listsBox = new QGroupBox(i18n("Image lists"));
59     layout->addWidget(listsBox);
60 
61     auto *listsBoxLayout = new QVBoxLayout(listsBox);
62 
63     auto *listsModeLabel = new QLabel(i18n(
64         "<p>Loaded images can either be listed in two different lists (one for all images without "
65         "and one for images with coordinates), or using a combined consecutive list for all images."
66         "</p>"
67         "<p>Use the following images list(s) mode:</p>"));
68     listsModeLabel->setWordWrap(true);
69     listsBoxLayout->addWidget(listsModeLabel);
70 
71     m_imageListsMode = new QComboBox;
72     m_imageListsMode->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding);
73 
74     m_imageListsMode->addItem(i18n("Separate \"Assigned\" and \"Unassigned\" list"));
75     m_imageListsMode->addItem(i18n("One combined list for all images"));
76 
77     m_imageListsMode->setCurrentIndex(m_settings->splitImagesList() ? 0 : 1);
78 
79     listsBoxLayout->addWidget(m_imageListsMode);
80 
81     // Automatic matching
82 
83     auto *searchMatchesBox = new QGroupBox(i18n("Image Assignment"));
84     layout->addWidget(searchMatchesBox);
85 
86     auto *searchMatchesBoxLayout = new QVBoxLayout(searchMatchesBox);
87 
88     auto *matchModeLabel = new QLabel(i18n("Search type for the main menu \"Assign images to GPS "
89                                            "data\" entry"));
90     matchModeLabel->setWordWrap(true);
91     searchMatchesBoxLayout->addWidget(matchModeLabel);
92 
93     m_automaticMatchingMode = new QComboBox;
94     m_automaticMatchingMode->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding);
95 
96     m_automaticMatchingMode->addItem(i18n("Combined match search"), KGeoTag::CombinedMatchSearch);
97     m_automaticMatchingMode->addItem(i18n("Search exact matches only"), KGeoTag::ExactMatchSearch);
98     m_automaticMatchingMode->addItem(i18n("Search interpolated matches only"),
99                                      KGeoTag::InterpolatedMatchSearch);
100 
101     m_automaticMatchingMode->setCurrentIndex(
102         m_automaticMatchingMode->findData(m_settings->defaultMatchingMode()));
103 
104     searchMatchesBoxLayout->addWidget(m_automaticMatchingMode);
105 
106     auto *matchModeNoteLabel = new QLabel(i18n(
107         "This triggers an automatic (re-)assignment of all images, respecting the \"Exclude "
108         "manually tagged images\" setting from the \"Automatic assignment\" dock."));
109     matchModeNoteLabel->setWordWrap(true);
110     searchMatchesBoxLayout->addWidget(matchModeNoteLabel);
111 
112     // Images
113 
114     auto *imagesBox = new QGroupBox(i18n("Thumbnails and previews"));
115     auto *imagesBoxLayout = new QVBoxLayout(imagesBox);
116     layout->addWidget(imagesBox);
117 
118     auto *sizesLayoutWrapper = new QHBoxLayout;
119     imagesBoxLayout->addLayout(sizesLayoutWrapper);
120     auto *sizesLayout = new QGridLayout;
121     sizesLayoutWrapper->addLayout(sizesLayout);
122     row = -1;
123 
124     sizesLayout->addWidget(new QLabel(i18n("Thumbnail size:")), ++row, 0);
125     m_thumbnailSize = new QSpinBox;
126     m_thumbnailSize->setMinimum(16);
127     m_thumbnailSize->setMaximum(512);
128     m_originalThumbnailSizeValue = m_settings->thumbnailSize();
129     m_thumbnailSize->setValue(m_originalThumbnailSizeValue);
130     sizesLayout->addWidget(m_thumbnailSize, row, 1);
131     sizesLayout->addWidget(new QLabel(i18n("px")), row, 2);
132 
133     sizesLayout->addWidget(new QLabel(i18n("Preview size:")), ++row, 0);
134     m_previewSize = new QSpinBox;
135     m_previewSize->setMinimum(100);
136     m_previewSize->setMaximum(1920);
137     m_originalPreviewSizeValue = m_settings->previewSize();
138     m_previewSize->setValue(m_originalPreviewSizeValue);
139     sizesLayout->addWidget(m_previewSize, row, 1);
140     sizesLayout->addWidget(new QLabel(i18n("px")), row, 2);
141 
142     sizesLayoutWrapper->addStretch();
143 
144     auto *imagesChangesLabel = new QLabel(i18n("Please restart the program after changes to these "
145                                                "values so that they are applied and become "
146                                                "visible."));
147     imagesChangesLabel->setWordWrap(true);
148     imagesBoxLayout->addWidget(imagesChangesLabel);
149 
150     // GPX track rendering
151 
152     auto *trackBox = new QGroupBox(i18n("GPX track rendering"));
153     auto *trackBoxLayout = new QHBoxLayout(trackBox);
154     layout->addWidget(trackBox);
155 
156     auto *renderingLayout = new QGridLayout;
157     trackBoxLayout->addLayout(renderingLayout);
158     row = -1;
159 
160     renderingLayout->addWidget(new QLabel(i18n("Color:")), ++row, 0);
161     m_trackColor = new QPushButton;
162     m_trackColor->setFlat(true);
163     m_currentTrackColor = m_settings->trackColor();
164     connect(m_trackColor, &QPushButton::clicked, this, &SettingsDialog::setTrackColor);
165     renderingLayout->addWidget(m_trackColor, row, 1);
166 
167     m_trackOpacity = new QLabel;
168     renderingLayout->addWidget(m_trackOpacity, row, 2);
169 
170     updateTrackColor();
171 
172     renderingLayout->addWidget(new QLabel(i18n("Line width:")), ++row, 0);
173     m_trackWidth = new QSpinBox;
174     m_trackWidth->setMinimum(1);
175     m_trackWidth->setMaximum(50);
176     m_trackWidth->setValue(m_settings->trackWidth());
177     renderingLayout->addWidget(m_trackWidth, row, 1);
178 
179     renderingLayout->addWidget(new QLabel(i18n("Line style:")), ++row, 0);
180     m_trackStyle = new QComboBox;
181     m_trackStyle->addItem(i18n("Solid"), static_cast<int>(Qt::SolidLine));
182     m_trackStyle->addItem(i18n("Dashes"), static_cast<int>(Qt::DashLine));
183     m_trackStyle->addItem(i18n("Dots"), static_cast<int>(Qt::DotLine));
184     m_trackStyle->addItem(i18n("Dash-Dot"), static_cast<int>(Qt::DashDotLine));
185     m_trackStyle->addItem(i18n("Dash-Dot-Dot"), static_cast<int>(Qt::DashDotDotLine));
186     m_trackStyle->setCurrentIndex(
187         m_trackStyle->findData(static_cast<int>(m_settings->trackStyle())));
188     renderingLayout->addWidget(m_trackStyle, row, 1);
189 
190     trackBoxLayout->addStretch();
191 
192     // Elevation lookup
193 
194     auto *elevationBox = new QGroupBox(i18n("Elevation lookup"));
195     auto *elevationBoxLayout = new QVBoxLayout(elevationBox);
196     layout->addWidget(elevationBox);
197 
198     auto *lookupLabel = new QLabel(i18n("Elevations can be looked up using opentopodata.org's web "
199                                         "API."));
200     lookupLabel->setWordWrap(true);
201     elevationBoxLayout->addWidget(lookupLabel);
202 
203     auto *datasetLayout = new QHBoxLayout;
204     elevationBoxLayout->addLayout(datasetLayout);
205 
206     datasetLayout->addWidget(new QLabel(i18n("Elevation dataset:")));
207 
208     m_elevationDataset = new QComboBox;
209 
210     m_elevationDataset->addItem(i18nc("opentopodata.org elevation dataset", "ASTER"),
211                                 QStringLiteral("aster30m"));
212     m_elevationDataset->addItem(i18nc("opentopodata.org elevation dataset", "ETOPO1"),
213                                 QStringLiteral("etopo1"));
214     m_elevationDataset->addItem(i18nc("opentopodata.org elevation dataset", "EU-DEM"),
215                                 QStringLiteral("eudem25m"));
216     m_elevationDataset->addItem(i18nc("opentopodata.org elevation dataset", "Mapzen"),
217                                 QStringLiteral("mapzen"));
218     m_elevationDataset->addItem(i18nc("opentopodata.org elevation dataset", "NED"),
219                                 QStringLiteral("ned10m"));
220     m_elevationDataset->addItem(i18nc("opentopodata.org elevation dataset", "NZ DEM"),
221                                 QStringLiteral("nzdem8m"));
222     m_elevationDataset->addItem(i18nc("opentopodata.org elevation dataset", "SRTM (30 m)"),
223                                 QStringLiteral("srtm30m"));
224     m_elevationDataset->addItem(i18nc("opentopodata.org elevation dataset", "SRTM (90 m)"),
225                                 QStringLiteral("srtm90m"));
226     m_elevationDataset->addItem(i18nc("opentopodata.org elevation dataset",
227                                       "EMODnet 2018 Bathymetry"),
228                                 QStringLiteral("emod2018"));
229     m_elevationDataset->addItem(i18nc("opentopodata.org elevation dataset",
230                                       "GEBCO 2020 Bathymetry"),
231                                 QStringLiteral("gebco2020"));
232 
233     m_elevationDataset->setCurrentIndex(
234         m_elevationDataset->findData(m_settings->elevationDataset()));
235 
236     datasetLayout->addWidget(m_elevationDataset);
237 
238     datasetLayout->addStretch();
239 
240     auto *datasetInfoLabel = new QLabel(i18n("Cf. <a href=\"https://www.opentopodata.org/\">"
241                                              "https://www.opentopodata.org/</a> for further "
242                                              "information about the available datasets, like the "
243                                              "respective coverage!"));
244     datasetInfoLabel->setWordWrap(true);
245     datasetInfoLabel->setOpenExternalLinks(true);
246     elevationBoxLayout->addWidget(datasetInfoLabel);
247 
248     m_lookupElevationAutomatically = new QCheckBox(i18n("Request and set altitudes automatically"));
249     m_lookupElevationAutomatically->setChecked(m_settings->lookupElevationAutomatically());
250     elevationBoxLayout->addWidget(m_lookupElevationAutomatically);
251 
252     // Data saving
253 
254     auto *saveBox = new QGroupBox(i18n("Saving"));
255     auto *saveBoxLayout = new QVBoxLayout(saveBox);
256     layout->addWidget(saveBox);
257 
258     auto *saveTargetLayout = new QHBoxLayout;
259     saveBoxLayout->addLayout(saveTargetLayout);
260 
261     saveTargetLayout->addWidget(new QLabel(i18n("Write changes to:")));
262 
263     m_writeMode = new QComboBox;
264 
265     m_writeMode->addItem(i18n("Exif header"),
266                          QStringLiteral("WRITETOIMAGEONLY"));
267     m_writeMode->addItem(i18n("XMP sidecar file"),
268                          QStringLiteral("WRITETOSIDECARONLY"));
269     m_writeMode->addItem(i18n("Exif header and XMP sidecar file"),
270                          QStringLiteral("WRITETOSIDECARANDIMAGE"));
271 
272     m_writeMode->setCurrentIndex(m_writeMode->findData(m_settings->writeMode()));
273 
274     saveTargetLayout->addWidget(m_writeMode);
275 
276     saveTargetLayout->addStretch();
277 
278     m_createBackups = new QCheckBox(i18n("Create a backups before altering a file"));
279     m_createBackups->setChecked(m_settings->createBackups());
280     saveBoxLayout->addWidget(m_createBackups);
281 
282     // Scroll area
283 
284     auto *scrollArea = new QScrollArea;
285     scrollArea->setWidgetResizable(true);
286     const int styleAddition = scrollArea->width() - scrollArea->viewport()->width();
287     scrollArea->setWidget(settingsWidget);
288     mainLayout->addWidget(scrollArea);
289 
290     show();
291     const int widgetWidth = settingsWidget->width() + scrollArea->verticalScrollBar()->width()
292                             + styleAddition;
293     scrollArea->setMinimumWidth(widgetWidth);
294 
295     // Button box
296 
297     auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Close);
298     connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::accept);
299     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
300     mainLayout->addWidget(buttonBox);
301 }
302 
updateTrackColor()303 void SettingsDialog::updateTrackColor()
304 {
305     m_trackColor->setStyleSheet(QStringLiteral("border: none; background-color: %1;").arg(
306                                                m_currentTrackColor.name()));
307     m_trackOpacity->setText(i18n("Opacity: %1%", int(m_currentTrackColor.alphaF() * 100.0)));
308 }
309 
setTrackColor()310 void SettingsDialog::setTrackColor()
311 {
312     QColorDialog dialog(m_currentTrackColor);
313     dialog.setOption(QColorDialog::ShowAlphaChannel, true);
314 
315     if (! dialog.exec()) {
316         return;
317     }
318 
319     m_currentTrackColor = dialog.currentColor();
320     updateTrackColor();
321 }
322 
accept()323 void SettingsDialog::accept()
324 {
325     const auto splitImagesList = m_imageListsMode->currentIndex() == 0;
326     m_settings->saveSplitImagesList(splitImagesList);
327 
328     m_settings->saveDefaultMatchingMode(static_cast<KGeoTag::SearchType>(
329         m_automaticMatchingMode->currentData().toInt()));
330 
331     const auto thumbnailSize = m_thumbnailSize->value();
332     m_settings->saveThumbnailSize(thumbnailSize);
333     const auto previewSize = m_previewSize->value();
334     m_settings->savePreviewSize(previewSize);
335 
336     m_settings->saveTrackColor(m_currentTrackColor);
337     m_settings->saveTrackWidth(m_trackWidth->value());
338     m_settings->saveTrackStyle(static_cast<Qt::PenStyle>(m_trackStyle->currentData().toInt()));
339 
340     m_settings->saveLookupElevationAutomatically(m_lookupElevationAutomatically->isChecked());
341     m_settings->saveElevationDataset(m_elevationDataset->currentData().toString());
342 
343     m_settings->saveWriteMode(m_writeMode->currentData().toString());
344     m_settings->saveCreateBackups(m_createBackups->isChecked());
345 
346     if (   thumbnailSize != m_originalThumbnailSizeValue
347         || previewSize != m_originalPreviewSizeValue) {
348 
349         QMessageBox::information(this, i18n("Settings changed"),
350             i18n("Please restart KGeoTag to apply the changed settings and make them visible!"));
351     }
352 
353     if (splitImagesList != m_originalSplitImagesListValue) {
354         emit imagesListsModeChanged();
355     }
356 
357     QDialog::accept();
358 }
359