1 /*
2 
3     Copyright (C) 2013  Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 
20 
21 #include "preferencesdialog.h"
22 #include "application.h"
23 #include "settings.h"
24 #include <QDir>
25 #include <QHash>
26 #include <QStringBuilder>
27 #include <QSettings>
28 
29 #include <libfm-qt/folderview.h>
30 #include <libfm-qt/core/terminal.h>
31 #include <libfm-qt/core/archiver.h>
32 
33 namespace PCManFM {
34 
PreferencesDialog(QString activePage,QWidget * parent)35 PreferencesDialog::PreferencesDialog(QString activePage, QWidget* parent):
36     QDialog(parent) {
37     ui.setupUi(this);
38     setAttribute(Qt::WA_DeleteOnClose);
39     warningCounter_ = 0;
40     ui.warningLabel->hide();
41 
42     // resize the list widget according to the width of its content.
43     ui.listWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
44     ui.listWidget->setMaximumWidth(ui.listWidget->sizeHintForColumn(0) + ui.listWidget->frameWidth() * 2 + 4);
45 
46     initFromSettings();
47 
48     selectPage(activePage);
49     adjustSize();
50 }
51 
52 PreferencesDialog::~PreferencesDialog() = default;
53 
findIconThemesInDir(QHash<QString,QString> & iconThemes,QString dirName)54 static void findIconThemesInDir(QHash<QString, QString>& iconThemes, QString dirName) {
55     QDir dir(dirName);
56     const QStringList subDirs = dir.entryList(QDir::AllDirs);
57     GKeyFile* kf = g_key_file_new();
58     for(const QString& subDir : subDirs) {
59         QString indexFile = dirName + QLatin1Char('/') + subDir + QStringLiteral("/index.theme");
60         if(g_key_file_load_from_file(kf, indexFile.toLocal8Bit().constData(), GKeyFileFlags(0), nullptr)) {
61             // FIXME: skip hidden ones
62             // icon theme must have this key, so it has icons if it has this key
63             // otherwise, it might be a cursor theme or any other kind of theme.
64             if(g_key_file_has_key(kf, "Icon Theme", "Directories", nullptr)) {
65                 char* dispName = g_key_file_get_locale_string(kf, "Icon Theme", "Name", nullptr, nullptr);
66                 // char* comment = g_key_file_get_locale_string(kf, "Icon Theme", "Comment", nullptr, nullptr);
67                 iconThemes[subDir] = QString::fromUtf8(dispName);
68                 g_free(dispName);
69             }
70         }
71     }
72     g_key_file_free(kf);
73 }
74 
initIconThemes(Settings & settings)75 void PreferencesDialog::initIconThemes(Settings& settings) {
76     // check if auto-detection is done (for example, from xsettings)
77     if(settings.useFallbackIconTheme()) { // auto-detection failed
78         // load xdg icon themes and select the current one
79         QHash<QString, QString> iconThemes;
80         // user customed icon themes
81         findIconThemesInDir(iconThemes, QString::fromUtf8(g_get_home_dir()) + QStringLiteral("/.icons"));
82 
83         // search for icons in system data dir
84         const char* const* dataDirs = g_get_system_data_dirs();
85         for(const char* const* dataDir = dataDirs; *dataDir; ++dataDir) {
86             findIconThemesInDir(iconThemes, QString::fromUtf8(*dataDir) + QStringLiteral("/icons"));
87         }
88 
89         iconThemes.remove(QStringLiteral("hicolor")); // remove hicolor, which is only a fallback
90         QHash<QString, QString>::const_iterator it;
91         for(it = iconThemes.constBegin(); it != iconThemes.constEnd(); ++it) {
92             ui.iconTheme->addItem(it.value(), it.key());
93         }
94         ui.iconTheme->model()->sort(0); // sort the list of icon theme names
95 
96         // select current theme name
97         int n = ui.iconTheme->count();
98         int i;
99         for(i = 0; i < n; ++i) {
100             QVariant itemData = ui.iconTheme->itemData(i);
101             if(itemData == settings.fallbackIconThemeName()) {
102                 break;
103             }
104         }
105         if(i >= n) {
106             i = 0;
107         }
108         ui.iconTheme->setCurrentIndex(i);
109     }
110     else { // auto-detection of icon theme works, hide the fallback icon theme combo box.
111         ui.iconThemeLabel->hide();
112         ui.iconTheme->hide();
113     }
114 
115     ui.hMargin->setValue(settings.folderViewCellMargins().width());
116     ui.vMargin->setValue(settings.folderViewCellMargins().height());
117     connect(ui.lockMargins, &QAbstractButton::clicked, this, &PreferencesDialog::lockMargins);
118 }
119 
initArchivers(Settings & settings)120 void PreferencesDialog::initArchivers(Settings& settings) {
121     auto& allArchivers = Fm::Archiver::allArchivers();
122     for(int i = 0; i < int(allArchivers.size()); ++i) {
123         auto& archiver = allArchivers[i];
124         ui.archiver->addItem(QString::fromUtf8(archiver->program()), QString::fromUtf8(archiver->program()));
125         if(QString::fromUtf8(archiver->program()) == settings.archiver()) {
126             ui.archiver->setCurrentIndex(i);
127         }
128     }
129 }
130 
initDisplayPage(Settings & settings)131 void PreferencesDialog::initDisplayPage(Settings& settings) {
132     initIconThemes(settings);
133     // icon sizes
134     int i = 0;
135     for (const auto & size : Settings::iconSizes(Settings::Big)) {
136         ui.bigIconSize->addItem(QStringLiteral("%1 x %1").arg(size), size);
137         if(settings.bigIconSize() == size) {
138             ui.bigIconSize->setCurrentIndex(i);
139         }
140         ++i;
141     }
142     i = 0;
143     for (const auto & size : Settings::iconSizes(Settings::Small)) {
144         QString text = QStringLiteral("%1 x %1").arg(size);
145         ui.smallIconSize->addItem(text, size);
146         if(settings.smallIconSize() == size) {
147             ui.smallIconSize->setCurrentIndex(i);
148         }
149 
150         ui.sidePaneIconSize->addItem(text, size);
151         if(settings.sidePaneIconSize() == size) {
152             ui.sidePaneIconSize->setCurrentIndex(i);
153         }
154         ++i;
155     }
156     i = 0;
157     for (const auto & size : Settings::iconSizes(Settings::Thumbnail)) {
158         ui.thumbnailIconSize->addItem(QStringLiteral("%1 x %1").arg(size), size);
159         if(settings.thumbnailIconSize() == size) {
160             ui.thumbnailIconSize->setCurrentIndex(i);
161         }
162         ++i;
163     }
164 
165     ui.siUnit->setChecked(settings.siUnit());
166     ui.backupAsHidden->setChecked(settings.backupAsHidden());
167 
168     ui.showFullNames->setChecked(settings.showFullNames());
169     ui.shadowHidden->setChecked(settings.shadowHidden());
170     ui.noItemTooltip->setChecked(settings.noItemTooltip());
171     ui.noScrollPerPixel->setChecked(!settings.scrollPerPixel());
172 
173     // app restart warning
174     connect(ui.showFullNames, &QAbstractButton::toggled, [this, &settings] (bool checked) {
175        restartWarning(settings.showFullNames() != checked);
176     });
177     connect(ui.shadowHidden, &QAbstractButton::toggled, [this, &settings] (bool checked) {
178        restartWarning(settings.shadowHidden() != checked);
179     });
180 }
181 
initUiPage(Settings & settings)182 void PreferencesDialog::initUiPage(Settings& settings) {
183     ui.alwaysShowTabs->setChecked(settings.alwaysShowTabs());
184     ui.showTabClose->setChecked(settings.showTabClose());
185     ui.switchToNewTab->setChecked(settings.switchToNewTab());
186     ui.reopenLastTabs->setChecked(settings.reopenLastTabs());
187     ui.rememberWindowSize->setChecked(settings.rememberWindowSize());
188     ui.fixedWindowWidth->setValue(settings.fixedWindowWidth());
189     ui.fixedWindowHeight->setValue(settings.fixedWindowHeight());
190 }
191 
initBehaviorPage(Settings & settings)192 void PreferencesDialog::initBehaviorPage(Settings& settings) {
193     ui.singleClick->setChecked(settings.singleClick());
194     ui.autoSelectionDelay->setValue(double(settings.autoSelectionDelay()) / 1000);
195     ui.ctrlRightClick->setChecked(settings.ctrlRightClick());
196 
197     ui.bookmarkOpenMethod->setCurrentIndex(settings.bookmarkOpenMethod());
198 
199     ui.viewMode->addItem(tr("Icon View"), (int)Fm::FolderView::IconMode);
200     ui.viewMode->addItem(tr("Compact View"), (int)Fm::FolderView::CompactMode);
201     ui.viewMode->addItem(tr("Thumbnail View"), (int)Fm::FolderView::ThumbnailMode);
202     ui.viewMode->addItem(tr("Detailed List View"), (int)Fm::FolderView::DetailedListMode);
203     const Fm::FolderView::ViewMode modes[] = {
204         Fm::FolderView::IconMode,
205         Fm::FolderView::CompactMode,
206         Fm::FolderView::ThumbnailMode,
207         Fm::FolderView::DetailedListMode
208     };
209     for(std::size_t i = 0; i < G_N_ELEMENTS(modes); ++i) {
210         if(modes[i] == settings.viewMode()) {
211             ui.viewMode->setCurrentIndex(i);
212             break;
213         }
214     }
215 
216     ui.configmDelete->setChecked(settings.confirmDelete());
217 
218     if(settings.supportTrash()) {
219         ui.useTrash->setChecked(settings.useTrash());
220     }
221     else {
222         ui.useTrash->hide();
223     }
224 
225     ui.noUsbTrash->setChecked(settings.noUsbTrash());
226     ui.confirmTrash->setChecked(settings.confirmTrash());
227     ui.quickExec->setChecked(settings.quickExec());
228     ui.selectNewFiles->setChecked(settings.selectNewFiles());
229     ui.singleWindowMode->setChecked(settings.singleWindowMode());
230 
231     // app restart warning
232     connect(ui.quickExec, &QAbstractButton::toggled, [this, &settings] (bool checked) {
233        restartWarning(settings.quickExec() != checked);
234     });
235 }
236 
initThumbnailPage(Settings & settings)237 void PreferencesDialog::initThumbnailPage(Settings& settings) {
238     ui.showThumbnails->setChecked(settings.showThumbnails());
239     ui.thumbnailLocal->setChecked(settings.thumbnailLocalFilesOnly());
240 
241     // the max. thumbnail size spinboxes are in MiB
242     double m = settings.maxThumbnailFileSize();
243     ui.maxThumbnailFileSize->setValue(qBound(0.0, m / 1024, 1024.0));
244     int m1 = settings.maxExternalThumbnailFileSize();
245     ui.maxExternalThumbnailFileSize->setValue(m1 < 0 ? -1 : qMin(m1 / 1024, 2048));
246 }
247 
initVolumePage(Settings & settings)248 void PreferencesDialog::initVolumePage(Settings& settings) {
249     ui.mountOnStartup->setChecked(settings.mountOnStartup());
250     ui.mountRemovable->setChecked(settings.mountRemovable());
251     ui.autoRun->setChecked(settings.autoRun());
252     if(settings.closeOnUnmount()) {
253         ui.closeOnUnmount->setChecked(true);
254     }
255     else {
256         ui.goHomeOnUnmount->setChecked(true);
257     }
258 }
259 
initTerminals(Settings & settings)260 void PreferencesDialog::initTerminals(Settings& settings) {
261     // load the known terminal list from the terminal.list file of libfm
262     for(auto& terminal: Fm::allKnownTerminals()) {
263         ui.terminal->addItem(QString::fromUtf8(terminal.get()));
264     }
265     ui.terminal->setEditText(settings.terminal());
266 }
267 
initAdvancedPage(Settings & settings)268 void PreferencesDialog::initAdvancedPage(Settings& settings) {
269     initArchivers(settings);
270     initTerminals(settings);
271     ui.suCommand->setText(settings.suCommand());
272 
273     ui.onlyUserTemplates->setChecked(settings.onlyUserTemplates());
274     ui.templateTypeOnce->setChecked(settings.templateTypeOnce());
275 
276     ui.templateRunApp->setChecked(settings.templateRunApp());
277 
278     // FIXME: Hide options that we don't support yet.
279     ui.templateRunApp->hide();
280 }
281 
initFromSettings()282 void PreferencesDialog::initFromSettings() {
283     Settings& settings = static_cast<Application*>(qApp)->settings();
284     initDisplayPage(settings);
285     initUiPage(settings);
286     initBehaviorPage(settings);
287     initThumbnailPage(settings);
288     initVolumePage(settings);
289     initAdvancedPage(settings);
290 }
291 
applyDisplayPage(Settings & settings)292 void PreferencesDialog::applyDisplayPage(Settings& settings) {
293     if(settings.useFallbackIconTheme()) {
294         // only apply the value if icon theme combo box is in use
295         // the combo box is hidden when auto-detection of icon theme from xsettings works.
296         QString newIconTheme = ui.iconTheme->itemData(ui.iconTheme->currentIndex()).toString();
297         if(newIconTheme != settings.fallbackIconThemeName()) {
298             settings.setFallbackIconThemeName(newIconTheme);
299             QIcon::setThemeName(settings.fallbackIconThemeName());
300             // update the UI by emitting a style change event
301             const auto widgets = QApplication::allWidgets();
302             for(QWidget* widget : widgets) {
303                 QEvent event(QEvent::StyleChange);
304                 QApplication::sendEvent(widget, &event);
305             }
306         }
307     }
308 
309     settings.setBigIconSize(ui.bigIconSize->itemData(ui.bigIconSize->currentIndex()).toInt());
310     settings.setSmallIconSize(ui.smallIconSize->itemData(ui.smallIconSize->currentIndex()).toInt());
311     settings.setThumbnailIconSize(ui.thumbnailIconSize->itemData(ui.thumbnailIconSize->currentIndex()).toInt());
312     settings.setSidePaneIconSize(ui.sidePaneIconSize->itemData(ui.sidePaneIconSize->currentIndex()).toInt());
313 
314     settings.setSiUnit(ui.siUnit->isChecked());
315     settings.setBackupAsHidden(ui.backupAsHidden->isChecked());
316     settings.setShowFullNames(ui.showFullNames->isChecked());
317     settings.setShadowHidden(ui.shadowHidden->isChecked());
318     settings.setNoItemTooltip(ui.noItemTooltip->isChecked());
319     settings.setScrollPerPixel(!ui.noScrollPerPixel->isChecked());
320     settings.setFolderViewCellMargins(QSize(ui.hMargin->value(), ui.vMargin->value()));
321 }
322 
applyUiPage(Settings & settings)323 void PreferencesDialog::applyUiPage(Settings& settings) {
324     settings.setAlwaysShowTabs(ui.alwaysShowTabs->isChecked());
325     settings.setShowTabClose(ui.showTabClose->isChecked());
326     settings.setSwitchToNewTab(ui.switchToNewTab->isChecked());
327     settings.setReopenLastTabs(ui.reopenLastTabs->isChecked());
328     settings.setRememberWindowSize(ui.rememberWindowSize->isChecked());
329     settings.setFixedWindowWidth(ui.fixedWindowWidth->value());
330     settings.setFixedWindowHeight(ui.fixedWindowHeight->value());
331 }
332 
applyBehaviorPage(Settings & settings)333 void PreferencesDialog::applyBehaviorPage(Settings& settings) {
334     settings.setSingleClick(ui.singleClick->isChecked());
335     settings.setAutoSelectionDelay(int(ui.autoSelectionDelay->value() * 1000));
336     settings.setCtrlRightClick(ui.ctrlRightClick->isChecked());
337 
338     settings.setBookmarkOpenMethod(OpenDirTargetType(ui.bookmarkOpenMethod->currentIndex()));
339 
340     // FIXME: bug here?
341     Fm::FolderView::ViewMode mode = Fm::FolderView::ViewMode(ui.viewMode->itemData(ui.viewMode->currentIndex()).toInt());
342     settings.setViewMode(mode);
343     settings.setConfirmDelete(ui.configmDelete->isChecked());
344 
345     if(settings.supportTrash()) {
346         settings.setUseTrash(ui.useTrash->isChecked());
347     }
348 
349     settings.setNoUsbTrash(ui.noUsbTrash->isChecked());
350     settings.setConfirmTrash(ui.confirmTrash->isChecked());
351     settings.setQuickExec(ui.quickExec->isChecked());
352     settings.setSelectNewFiles(ui.selectNewFiles->isChecked());
353     settings.setSingleWindowMode(ui.singleWindowMode->isChecked());
354 }
355 
applyThumbnailPage(Settings & settings)356 void PreferencesDialog::applyThumbnailPage(Settings& settings) {
357     settings.setShowThumbnails(ui.showThumbnails->isChecked());
358     settings.setThumbnailLocalFilesOnly(ui.thumbnailLocal->isChecked());
359     // the max. thumbnail size spinboxes are in MiB
360     settings.setMaxThumbnailFileSize(qRound(ui.maxThumbnailFileSize->value() * 1024));
361     int m = ui.maxExternalThumbnailFileSize->value();
362     settings.setMaxExternalThumbnailFileSize(m < 0 ? -1 : m * 1024);
363 }
364 
applyVolumePage(Settings & settings)365 void PreferencesDialog::applyVolumePage(Settings& settings) {
366     settings.setAutoRun(ui.autoRun->isChecked());
367     settings.setMountOnStartup(ui.mountOnStartup->isChecked());
368     settings.setMountRemovable(ui.mountRemovable->isChecked());
369     settings.setCloseOnUnmount(ui.closeOnUnmount->isChecked());
370 }
371 
applyAdvancedPage(Settings & settings)372 void PreferencesDialog::applyAdvancedPage(Settings& settings) {
373     settings.setTerminal(ui.terminal->currentText());
374     settings.setSuCommand(ui.suCommand->text());
375     settings.setArchiver(ui.archiver->itemData(ui.archiver->currentIndex()).toString());
376 
377     settings.setOnlyUserTemplates(ui.onlyUserTemplates->isChecked());
378     settings.setTemplateTypeOnce(ui.templateTypeOnce->isChecked());
379     settings.setTemplateRunApp(ui.templateRunApp->isChecked());
380 }
381 
382 
applySettings()383 void PreferencesDialog::applySettings() {
384     Settings& settings = static_cast<Application*>(qApp)->settings();
385     applyDisplayPage(settings);
386     applyUiPage(settings);
387     applyBehaviorPage(settings);
388     applyThumbnailPage(settings);
389     applyVolumePage(settings);
390     applyAdvancedPage(settings);
391 
392     settings.save();
393 
394     Application* app = static_cast<Application*>(qApp);
395     app->updateFromSettings();
396 }
397 
accept()398 void PreferencesDialog::accept() {
399     applySettings();
400     QDialog::accept();
401 }
402 
selectPage(QString name)403 void PreferencesDialog::selectPage(QString name) {
404     if(!name.isEmpty()) {
405         QWidget* page = findChild<QWidget*>(name + QStringLiteral("Page"));
406         if(page) {
407             int index = ui.stackedWidget->indexOf(page);
408             ui.listWidget->setCurrentRow(index);
409         }
410     }
411 }
412 
lockMargins(bool lock)413 void PreferencesDialog::lockMargins(bool lock) {
414     ui.vMargin->setDisabled(lock);
415     if(lock) {
416         ui.vMargin->setValue(ui.hMargin->value());
417         connect(ui.hMargin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), ui.vMargin, &QSpinBox::setValue);
418     }
419     else {
420         disconnect(ui.hMargin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), ui.vMargin, &QSpinBox::setValue);
421     }
422 }
423 
restartWarning(bool warn)424 void PreferencesDialog::restartWarning(bool warn) {
425     if(warn) {
426         ++warningCounter_;
427     }
428     else {
429         --warningCounter_;
430     }
431     ui.warningLabel->setVisible(warningCounter_ > 0);
432 }
433 
434 } // namespace PCManFM
435