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