1 /****************************************************************************
2 **
3 ** Copyright (C) 2015-2016 Oleg Shparber
4 ** Contact: https://go.zealdocs.org/l/contact
5 **
6 ** This file is part of Zeal.
7 **
8 ** Zeal is free software: you can redistribute it and/or modify
9 ** it under the terms of the GNU General Public License as published by
10 ** the Free Software Foundation, either version 3 of the License, or
11 ** (at your option) any later version.
12 **
13 ** Zeal is distributed in the hope that it will be useful,
14 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ** GNU General Public License for more details.
17 **
18 ** You should have received a copy of the GNU General Public License
19 ** along with Zeal. If not, see <https://www.gnu.org/licenses/>.
20 **
21 ****************************************************************************/
22 
23 #include "settings.h"
24 
25 #include "filemanager.h"
26 
27 #include <QCoreApplication>
28 #include <QDir>
29 #include <QSettings>
30 #include <QStandardPaths>
31 #include <QUrl>
32 #include <QUuid>
33 #include <QWebSettings>
34 
35 namespace {
36 // Configuration file groups
37 const char GroupContent[] = "content";
38 const char GroupDocsets[] = "docsets";
39 const char GroupGlobalShortcuts[] = "global_shortcuts";
40 const char GroupSearch[] = "search";
41 const char GroupTabs[] = "tabs";
42 const char GroupInternal[] = "internal";
43 const char GroupState[] = "state";
44 const char GroupProxy[] = "proxy";
45 }
46 
47 using namespace Zeal::Core;
48 
49 Settings::Settings(QObject *parent) :
50     QObject(parent)
51 {
52     qRegisterMetaTypeStreamOperators<ExternalLinkPolicy>("ExternalLinkPolicy");
53 
54     // Enable local storage due to https://github.com/zealdocs/zeal/issues/872.
55     QWebSettings *webSettings = QWebSettings::globalSettings();
56     webSettings->setLocalStoragePath(FileManager::cacheLocation() + QLatin1String("/localStorage"));
57     webSettings->setAttribute(QWebSettings::LocalStorageEnabled, true);
58 
59     load();
60 }
61 
62 Settings::~Settings()
63 {
64     save();
65 }
66 
67 void Settings::load()
68 {
69     QScopedPointer<QSettings> settings(qsettings());
70     migrate(settings.data());
71 
72     // TODO: Put everything in groups
73     startMinimized = settings->value(QStringLiteral("start_minimized"), false).toBool();
74     checkForUpdate = settings->value(QStringLiteral("check_for_update"), true).toBool();
75 
76     showSystrayIcon = settings->value(QStringLiteral("show_systray_icon"), true).toBool();
77     minimizeToSystray = settings->value(QStringLiteral("minimize_to_systray"), false).toBool();
78     hideOnClose = settings->value(QStringLiteral("hide_on_close"), false).toBool();
79 
80     settings->beginGroup(GroupGlobalShortcuts);
81     showShortcut = settings->value(QStringLiteral("show")).value<QKeySequence>();
82     settings->endGroup();
83 
84     settings->beginGroup(GroupTabs);
85     openNewTabAfterActive = settings->value(QStringLiteral("open_new_tab_after_active"), false).toBool();
86     settings->endGroup();
87 
88     settings->beginGroup(GroupSearch);
89     fuzzySearchEnabled = settings->value(QStringLiteral("fuzzy_search_enabled"), false).toBool();
90     settings->endGroup();
91 
92     settings->beginGroup(GroupContent);
93     // Fonts
94     QWebSettings *webSettings = QWebSettings::globalSettings();
95     serifFontFamily = settings->value(QStringLiteral("serif_font_family"),
96                                       webSettings->fontFamily(QWebSettings::SerifFont)).toString();
97     sansSerifFontFamily = settings->value(QStringLiteral("sans_serif_font_family"),
98                                           webSettings->fontFamily(QWebSettings::SansSerifFont)).toString();
99     fixedFontFamily = settings->value(QStringLiteral("fixed_font_family"),
100                                       webSettings->fontFamily(QWebSettings::FixedFont)).toString();
101 
102     static const QMap<QString, QWebSettings::FontFamily> fontFamilies = {
103         {QStringLiteral("sans-serif"), QWebSettings::SansSerifFont},
104         {QStringLiteral("serif"), QWebSettings::SerifFont},
105         {QStringLiteral("monospace"), QWebSettings::FixedFont}
106     };
107 
108     defaultFontFamily = settings->value(QStringLiteral("default_font_family"),
109                                         QStringLiteral("serif")).toString();
110 
111     // Fallback to the serif font family.
112     if (!fontFamilies.contains(defaultFontFamily)) {
113         defaultFontFamily = QStringLiteral("serif");
114     }
115 
116     webSettings->setFontFamily(QWebSettings::SansSerifFont, sansSerifFontFamily);
117     webSettings->setFontFamily(QWebSettings::SerifFont, serifFontFamily);
118     webSettings->setFontFamily(QWebSettings::FixedFont, fixedFontFamily);
119 
120     const QString defaultFontFamilyResolved = webSettings->fontFamily(fontFamilies.value(defaultFontFamily));
121     webSettings->setFontFamily(QWebSettings::StandardFont, defaultFontFamilyResolved);
122 
123     defaultFontSize = settings->value(QStringLiteral("default_font_size"),
124                                       webSettings->fontSize(QWebSettings::DefaultFontSize)).toInt();
125     defaultFixedFontSize = settings->value(QStringLiteral("default_fixed_font_size"),
126                                            webSettings->fontSize(QWebSettings::DefaultFixedFontSize)).toInt();
127     minimumFontSize = settings->value(QStringLiteral("minimum_font_size"),
128                                       webSettings->fontSize(QWebSettings::MinimumFontSize)).toInt();
129 
130     webSettings->setFontSize(QWebSettings::DefaultFontSize, defaultFontSize);
131     webSettings->setFontSize(QWebSettings::DefaultFixedFontSize, defaultFixedFontSize);
132     webSettings->setFontSize(QWebSettings::MinimumFontSize, minimumFontSize);
133 
134     darkModeEnabled = settings->value(QStringLiteral("dark_mode"), false).toBool();
135     highlightOnNavigateEnabled = settings->value(QStringLiteral("highlight_on_navigate"), true).toBool();
136     customCssFile = settings->value(QStringLiteral("custom_css_file")).toString();
137     externalLinkPolicy = settings->value(QStringLiteral("external_link_policy"),
138                                          QVariant::fromValue(ExternalLinkPolicy::Ask)).value<ExternalLinkPolicy>();
139     isSmoothScrollingEnabled = settings->value(QStringLiteral("smooth_scrolling"), false).toBool();
140     isAdDisabled = settings->value(QStringLiteral("disable_ad"), false).toBool();
141     settings->endGroup();
142 
143     settings->beginGroup(GroupProxy);
144     proxyType = static_cast<ProxyType>(settings->value(QStringLiteral("type"),
145                                                        ProxyType::System).toUInt());
146     proxyHost = settings->value(QStringLiteral("host")).toString();
147     proxyPort = static_cast<quint16>(settings->value(QStringLiteral("port"), 0).toUInt());
148     proxyAuthenticate = settings->value(QStringLiteral("authenticate"), false).toBool();
149     proxyUserName = settings->value(QStringLiteral("username")).toString();
150     proxyPassword = settings->value(QStringLiteral("password")).toString();
151     settings->endGroup();
152 
153     settings->beginGroup(GroupDocsets);
154     if (settings->contains(QStringLiteral("path"))) {
155         docsetPath = settings->value(QStringLiteral("path")).toString();
156     } else {
157 #ifndef PORTABLE_BUILD
158         docsetPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation)
159                 + QLatin1String("/docsets");
160 #else
161         docsetPath = QCoreApplication::applicationDirPath() + QLatin1String("/docsets");
162 #endif
163         QDir().mkpath(docsetPath);
164     }
165     settings->endGroup();
166 
167     settings->beginGroup(GroupState);
168     windowGeometry = settings->value(QStringLiteral("window_geometry")).toByteArray();
169     verticalSplitterGeometry = settings->value(QStringLiteral("splitter_geometry")).toByteArray();
170     tocSplitterState = settings->value(QStringLiteral("toc_splitter_state")).toByteArray();
171     settings->endGroup();
172 
173     settings->beginGroup(GroupInternal);
174     installId = settings->value(QStringLiteral("install_id"),
175                                 // Avoid curly braces (QTBUG-885)
176                                 QUuid::createUuid().toString().mid(1, 36)).toString();
177     settings->endGroup();
178 }
179 
180 void Settings::save()
181 {
182     QScopedPointer<QSettings> settings(qsettings());
183 
184     // TODO: Put everything in groups
185     settings->setValue(QStringLiteral("start_minimized"), startMinimized);
186     settings->setValue(QStringLiteral("check_for_update"), checkForUpdate);
187 
188     settings->setValue(QStringLiteral("show_systray_icon"), showSystrayIcon);
189     settings->setValue(QStringLiteral("minimize_to_systray"), minimizeToSystray);
190     settings->setValue(QStringLiteral("hide_on_close"), hideOnClose);
191 
192     settings->beginGroup(GroupGlobalShortcuts);
193     settings->setValue(QStringLiteral("show"), showShortcut);
194     settings->endGroup();
195 
196     settings->beginGroup(GroupTabs);
197     settings->setValue(QStringLiteral("open_new_tab_after_active"), openNewTabAfterActive);
198     settings->endGroup();
199 
200     settings->beginGroup(GroupSearch);
201     settings->setValue(QStringLiteral("fuzzy_search_enabled"), fuzzySearchEnabled);
202     settings->endGroup();
203 
204     settings->beginGroup(GroupContent);
205     settings->setValue(QStringLiteral("default_font_family"), defaultFontFamily);
206     settings->setValue(QStringLiteral("serif_font_family"), serifFontFamily);
207     settings->setValue(QStringLiteral("sans_serif_font_family"), sansSerifFontFamily);
208     settings->setValue(QStringLiteral("fixed_font_family"), fixedFontFamily);
209 
210     settings->setValue(QStringLiteral("default_font_size"), defaultFontSize);
211     settings->setValue(QStringLiteral("default_fixed_font_size"), defaultFixedFontSize);
212     settings->setValue(QStringLiteral("minimum_font_size"), minimumFontSize);
213 
214     settings->setValue(QStringLiteral("dark_mode"), darkModeEnabled);
215     settings->setValue(QStringLiteral("highlight_on_navigate"), highlightOnNavigateEnabled);
216     settings->setValue(QStringLiteral("custom_css_file"), customCssFile);
217     settings->setValue(QStringLiteral("external_link_policy"), QVariant::fromValue(externalLinkPolicy));
218     settings->setValue(QStringLiteral("smooth_scrolling"), isSmoothScrollingEnabled);
219     settings->setValue(QStringLiteral("disable_ad"), isAdDisabled);
220     settings->endGroup();
221 
222     settings->beginGroup(GroupProxy);
223     settings->setValue(QStringLiteral("type"), proxyType);
224     settings->setValue(QStringLiteral("host"), proxyHost);
225     settings->setValue(QStringLiteral("port"), proxyPort);
226     settings->setValue(QStringLiteral("authenticate"), proxyAuthenticate);
227     settings->setValue(QStringLiteral("username"), proxyUserName);
228     settings->setValue(QStringLiteral("password"), proxyPassword);
229     settings->endGroup();
230 
231     settings->beginGroup(GroupDocsets);
232     settings->setValue(QStringLiteral("path"), docsetPath);
233     settings->endGroup();
234 
235     settings->beginGroup(GroupState);
236     settings->setValue(QStringLiteral("window_geometry"), windowGeometry);
237     settings->setValue(QStringLiteral("splitter_geometry"), verticalSplitterGeometry);
238     settings->setValue(QStringLiteral("toc_splitter_state"), tocSplitterState);
239     settings->endGroup();
240 
241     settings->beginGroup(GroupInternal);
242     settings->setValue(QStringLiteral("install_id"), installId);
243     // Version of configuration file format, should match Zeal version. Used for migration rules.
244     settings->setValue(QStringLiteral("version"), QCoreApplication::applicationVersion());
245     settings->endGroup();
246 
247     settings->sync();
248 
249     emit updated();
250 }
251 
252 /*!
253  * \internal
254  * \brief Migrates settings from older application versions.
255  * \param settings QSettings object to update.
256  *
257  * The settings migration process relies on 'internal/version' option, that was introduced in the
258  * release 0.2.0, so a missing option indicates pre-0.2 release.
259  */
260 void Settings::migrate(QSettings *settings) const
261 {
262     settings->beginGroup(GroupInternal);
263     // TODO: [Qt 5.6] Use QVersionNumber.
264     const QString version = settings->value(QStringLiteral("version")).toString();
265     settings->endGroup();
266 
267     //
268     // Pre 0.4
269     //
270 
271     // Rename 'browser' group into 'content'.
272     if (version < QLatin1String("0.4")) {
273         settings->beginGroup(QStringLiteral("browser"));
274         const QVariant tmpMinimumFontSize = settings->value(QStringLiteral("minimum_font_size"));
275         settings->endGroup();
276         settings->remove(QStringLiteral("browser"));
277 
278         if (tmpMinimumFontSize.isValid()) {
279             settings->beginGroup(GroupContent);
280             settings->setValue(QStringLiteral("minimum_font_size"), tmpMinimumFontSize);
281             settings->endGroup();
282         }
283     }
284 
285 
286     //
287     // Pre 0.3
288     //
289 
290     // Unset 'state/splitter_geometry', because custom styles were removed.
291     if (version < QLatin1String("0.3")) {
292         settings->beginGroup(GroupState);
293         settings->remove(QStringLiteral("splitter_geometry"));
294         settings->endGroup();
295     }
296 }
297 
298 /*!
299  * \internal
300  * \brief Returns an initialized QSettings object.
301  * \param parent Optional parent object.
302  * \return QSettings object.
303  *
304  * QSettings is initialized according to build options, e.g. standard vs portable.
305  * Caller is responsible for deleting the returned object.
306  */
307 QSettings *Settings::qsettings(QObject *parent)
308 {
309 #ifndef PORTABLE_BUILD
310     return new QSettings(parent);
311 #else
312     return new QSettings(QCoreApplication::applicationDirPath() + QLatin1String("/zeal.ini"),
313                          QSettings::IniFormat, parent);
314 #endif
315 }
316 
317 QDataStream &operator<<(QDataStream &out, const Settings::ExternalLinkPolicy &policy)
318 {
319     out << static_cast<std::underlying_type<Settings::ExternalLinkPolicy>::type>(policy);
320     return out;
321 }
322 
323 QDataStream &operator>>(QDataStream &in, Settings::ExternalLinkPolicy &policy)
324 {
325     std::underlying_type<Settings::ExternalLinkPolicy>::type value;
326     in >> value;
327     policy = static_cast<Settings::ExternalLinkPolicy>(value);
328     return in;
329 }
330