File indexing completed on 2024-05-12 17:22:41

0001 // This file is part of the SpeedCrunch project
0002 // Copyright (C) 2004, 2005, 2007, 2008 Ariya Hidayat <ariya@kde.org>
0003 // Copyright (C) 2005-2006 Johan Thelin <e8johan@gmail.com>
0004 // Copyright (C) 2007-2016 @heldercorreia
0005 // Copyright (C) 2015 Pol Welter <polwelter@gmail.com>
0006 //
0007 // This program is free software; you can redistribute it and/or
0008 // modify it under the terms of the GNU General Public License
0009 // as published by the Free Software Foundation; either version 2
0010 // of the License, or (at your option) any later version.
0011 //
0012 // This program is distributed in the hope that it will be useful,
0013 // but WITHOUT ANY WARRANTY; without even the implied warranty of
0014 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015 // GNU General Public License for more details.
0016 //
0017 // You should have received a copy of the GNU General Public License
0018 // along with this program; see the file COPYING.  If not, write to
0019 // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020 // Boston, MA 02110-1301, USA.
0021 
0022 #include "settings.h"
0023 
0024 #include "floatconfig.h"
0025 
0026 #include <QDir>
0027 #include <QLocale>
0028 #include <QSettings>
0029 #include <QApplication>
0030 #include <QFont>
0031 #include <QtCore/QStandardPaths>
0032 
0033 #ifdef Q_OS_WIN
0034 # define WIN32_LEAN_AND_MEAN
0035 # ifndef NOMINMAX
0036 #  define NOMINMAX
0037 # endif
0038 # include <windows.h>
0039 # include <shlobj.h>
0040 #endif
0041 
0042 
0043 // The current config revision. Based on the application version number
0044 // ('1200' corresponds to 0.12.0, '10300' would be 1.3.0 etc.). When making
0045 // a backwards-incompatible change to the config format, bump this number to
0046 // the next release (if not already happened), then update the migration code
0047 // in createQSettings. Don't bump the config version unnecessarily for
0048 // releases that don't contain incompatible changes.
0049 static const int ConfigVersion = 1200;
0050 
0051 
0052 static const char* DefaultColorScheme = "Terminal";
0053 
0054 QString Settings::getConfigPath()
0055 {
0056 #ifdef SPEEDCRUNCH_PORTABLE
0057     return QApplication::applicationDirPath();
0058 #elif defined(Q_OS_WIN)
0059     // On Windows, use AppData/Roaming/SpeedCrunch, the same path as getDataPath.
0060     return getDataPath();
0061 #else
0062     // Everywhere else, use `QStandardPaths::ConfigLocation`/SpeedCrunch:
0063     // * OSX: ~/Library/Preferences/SpeedCrunch
0064     // * Linux: ~/.config/SpeedCrunch
0065     return QString("%1/%2").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation),
0066                                 QCoreApplication::applicationName());
0067 #endif
0068 }
0069 
0070 QString Settings::getDataPath()
0071 {
0072 #ifdef SPEEDCRUNCH_PORTABLE
0073     return QApplication::applicationDirPath();
0074 #elif QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
0075     return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0076 #elif defined(Q_OS_WIN)
0077     // We can't use AppDataLocation, so we simply use the Win32 API to emulate it.
0078     WCHAR w32path[MAX_PATH];
0079     HRESULT result = SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, w32path);
0080     Q_ASSERT(SUCCEEDED(result));
0081     QString path = QString::fromWCharArray(w32path);
0082     QString orgName = QCoreApplication::organizationName();
0083     QString appName = QCoreApplication::applicationName();
0084     if (!orgName.isEmpty()) {
0085         path.append('\\');
0086         path.append(orgName);
0087     }
0088     if (!appName.isEmpty()) {
0089         path.append('\\');
0090         path.append(appName);
0091     }
0092     return QDir::fromNativeSeparators(path);
0093 #else
0094     // Any non-Windows with Qt < 5.4. Since DataLocation and AppDataLocation are
0095     // equivalent outside of Windows, that should be fine.
0096     return QStandardPaths::writableLocation(QStandardPaths::DataLocation);
0097 #endif
0098 }
0099 
0100 QString Settings::getCachePath()
0101 {
0102 #ifdef SPEEDCRUNCH_PORTABLE
0103     return QApplication::applicationDirPath();
0104 #else
0105     return QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
0106 #endif
0107 }
0108 
0109 static Settings* s_settingsInstance = 0;
0110 static char s_radixCharacter = 0;
0111 
0112 static void s_deleteSettings()
0113 {
0114     delete s_settingsInstance;
0115 }
0116 
0117 static QSettings* createQSettings(const QString& key);
0118 
0119 Settings* Settings::instance()
0120 {
0121     if (!s_settingsInstance) {
0122         s_settingsInstance = new Settings;
0123         s_settingsInstance->load();
0124         qAddPostRoutine(s_deleteSettings);
0125     }
0126 
0127     return s_settingsInstance;
0128 }
0129 
0130 Settings::Settings()
0131 {
0132 }
0133 
0134 void Settings::load()
0135 {
0136     const char* KEY = "SpeedCrunch";
0137 
0138     QSettings* settings = createQSettings(KEY);
0139     if (!settings)
0140         return;
0141 
0142     QString key = QString::fromLatin1(KEY) + QLatin1String("/General/");
0143 
0144     // Angle mode special case.
0145     QString angleUnitStr;
0146     angleUnitStr = settings->value(key + QLatin1String("AngleMode"), "r").toString();
0147     if (angleUnitStr != QLatin1String("r") && angleUnitStr != QLatin1String("d") && angleUnitStr != QLatin1String("g"))
0148         angleUnit = 'r';
0149     else
0150         angleUnit = angleUnitStr.at(0).toLatin1();
0151 
0152     // Radix character special case.
0153     QString radixCharStr;
0154     radixCharStr = settings->value(key + QLatin1String("RadixCharacter"), "*").toString();
0155     setRadixCharacter(radixCharStr.at(0).toLatin1());
0156 
0157     autoAns = settings->value(key + QLatin1String("AutoAns"), true).toBool();
0158     autoCalc = settings->value(key + QLatin1String("AutoCalc"), true).toBool();
0159     autoCompletion = settings->value(key + QLatin1String("AutoCompletion"), true).toBool();
0160     sessionSave = settings->value(key + QLatin1String("SessionSave"), true).toBool();
0161     leaveLastExpression = settings->value(key + QLatin1String("LeaveLastExpression"), false).toBool();
0162     language = settings->value(key + QLatin1String("Language"), "C").toString();
0163     syntaxHighlighting = settings->value(key + QLatin1String("SyntaxHighlighting"), true).toBool();
0164     autoResultToClipboard = settings->value(key + QLatin1String("AutoResultToClipboard"), false).toBool();
0165     windowPositionSave = settings->value(key + QLatin1String("WindowPositionSave"), true).toBool();
0166     complexNumbers = settings->value(key + QLatin1String("ComplexNumbers"), false).toBool();
0167 
0168     digitGrouping = settings->value(key + QLatin1String("DigitGrouping"), 0).toInt();
0169     digitGrouping = std::min(3, std::max(0, digitGrouping));
0170 
0171     key = KEY + QLatin1String("/Format/");
0172 
0173     // Format special case.
0174     QString format;
0175     format = settings->value(key + QLatin1String("Type"), 'f').toString();
0176     if (format != "g" && format != "f" && format != "e" && format != "n"&& format != "h" && format != "o" && format != "b")
0177         resultFormat = 'f';
0178     else
0179         resultFormat = format.at(0).toLatin1();
0180 
0181     // Complex format special case.
0182     QString cmplxFormat;
0183     cmplxFormat = settings->value(key + QLatin1String("ComplexForm"), 'c').toString();
0184     if (cmplxFormat != "c" && cmplxFormat != "p")
0185         resultFormatComplex = 'c';
0186     else
0187         resultFormatComplex = cmplxFormat.at(0).toLatin1();
0188 
0189     resultPrecision = settings->value(key + QLatin1String("Precision"), -1).toInt();
0190 
0191     if (resultPrecision > DECPRECISION)
0192         resultPrecision = DECPRECISION;
0193 
0194     key = KEY + QLatin1String("/Layout/");
0195     windowOnfullScreen = settings->value(key + QLatin1String("WindowOnFullScreen"), false).toBool();
0196     historyDockVisible = settings->value(key + QLatin1String("HistoryDockVisible"), false).toBool();
0197     keypadVisible = settings->value(key + QLatin1String("KeypadVisible"), false).toBool();
0198     statusBarVisible = settings->value(key + QLatin1String("StatusBarVisible"), false).toBool();
0199     functionsDockVisible = settings->value(key + QLatin1String("FunctionsDockVisible"), false).toBool();
0200     variablesDockVisible = settings->value(key + QLatin1String("VariablesDockVisible"), false).toBool();
0201     userFunctionsDockVisible = settings->value(key + QLatin1String("UserFunctionsDockVisible"), false).toBool();
0202     formulaBookDockVisible = settings->value(key + QLatin1String("FormulaBookDockVisible"), false).toBool();
0203     constantsDockVisible = settings->value(key + QLatin1String("ConstantsDockVisible"), false).toBool();
0204     windowAlwaysOnTop = settings->value(key + QLatin1String("WindowAlwaysOnTop"), false).toBool();
0205     bitfieldVisible = settings->value(key + QLatin1String("BitfieldVisible"), false).toBool();
0206 
0207     windowState = settings->value(key + QLatin1String("State")).toByteArray();
0208     windowGeometry = settings->value(key + QLatin1String("WindowGeometry")).toByteArray();
0209     manualWindowGeometry = settings->value(key + QLatin1String("ManualWindowGeometry")).toByteArray();
0210 
0211     key = KEY + QLatin1String("/Display/");
0212     displayFont = settings->value(key + QLatin1String("DisplayFont"), QFont().toString()).toString();
0213     colorScheme = settings->value(key + QLatin1String("ColorSchemeName"), DefaultColorScheme).toString();
0214 
0215     delete settings;
0216 }
0217 
0218 void Settings::save()
0219 {
0220     const QString KEY = QString::fromLatin1("SpeedCrunch");
0221 
0222     QSettings* settings = createQSettings(KEY);
0223     if (!settings)
0224         return;
0225 
0226     QString key = KEY + QLatin1String("/General/");
0227 
0228     settings->setValue(key + QLatin1String("SessionSave"), sessionSave);
0229     settings->setValue(key + QLatin1String("LeaveLastExpression"), leaveLastExpression);
0230     settings->setValue(key + QLatin1String("AutoCompletion"), autoCompletion);
0231     settings->setValue(key + QLatin1String("AutoAns"), autoAns);
0232     settings->setValue(key + QLatin1String("AutoCalc"), autoCalc);
0233     settings->setValue(key + QLatin1String("SyntaxHighlighting"), syntaxHighlighting);
0234     settings->setValue(key + QLatin1String("DigitGrouping"), digitGrouping);
0235     settings->setValue(key + QLatin1String("AutoResultToClipboard"), autoResultToClipboard);
0236     settings->setValue(key + QLatin1String("Language"), language);
0237     settings->setValue(key + QLatin1String("WindowPositionSave"), windowPositionSave);
0238     settings->setValue(key + QLatin1String("ComplexNumbers"), complexNumbers);
0239 
0240     settings->setValue(key + QLatin1String("AngleMode"), QString(QChar(angleUnit)));
0241 
0242     char c = 'C';
0243     if (s_radixCharacter != 0)
0244         c = s_radixCharacter;
0245     settings->setValue(key + QLatin1String("RadixCharacter"), QString(QChar(c)));
0246 
0247     key = KEY + QLatin1String("/Format/");
0248 
0249     settings->setValue(key + QLatin1String("Type"), QString(QChar(resultFormat)));
0250     settings->setValue(key + QLatin1String("ComplexForm"), QString(QChar(resultFormatComplex)));
0251     settings->setValue(key + QLatin1String("Precision"), resultPrecision);
0252 
0253     key = KEY + QLatin1String("/Layout/");
0254 
0255     settings->setValue(key + QLatin1String("FormulaBookDockVisible"), formulaBookDockVisible);
0256     settings->setValue(key + QLatin1String("ConstantsDockVisible"), constantsDockVisible);
0257     settings->setValue(key + QLatin1String("FunctionsDockVisible"), functionsDockVisible);
0258     settings->setValue(key + QLatin1String("HistoryDockVisible"), historyDockVisible);
0259     settings->setValue(key + QLatin1String("WindowOnFullScreen"), windowOnfullScreen);
0260     settings->setValue(key + QLatin1String("KeypadVisible"), keypadVisible);
0261     settings->setValue(key + QLatin1String("StatusBarVisible"), statusBarVisible);
0262     settings->setValue(key + QLatin1String("VariablesDockVisible"), variablesDockVisible);
0263     settings->setValue(key + QLatin1String("UserFunctionsDockVisible"), userFunctionsDockVisible);
0264     settings->setValue(key + QLatin1String("WindowAlwaysOnTop"), windowAlwaysOnTop);
0265     settings->setValue(key + QLatin1String("State"), windowState);
0266     settings->setValue(key + QLatin1String("WindowGeometry"), windowGeometry);
0267     settings->setValue(key + QLatin1String("ManualWindowGeometry"), manualWindowGeometry);
0268     settings->setValue(key + QLatin1String("BitfieldVisible"), bitfieldVisible);
0269 
0270     key = KEY + QLatin1String("/Display/");
0271 
0272     settings->setValue(key + QLatin1String("DisplayFont"), displayFont);
0273     settings->setValue(key + QLatin1String("ColorSchemeName"), colorScheme);
0274 
0275 
0276     delete settings;
0277 }
0278 
0279 char Settings::radixCharacter() const
0280 {
0281     if (isRadixCharacterAuto() || isRadixCharacterBoth())
0282         return QLocale().decimalPoint().toLatin1();
0283 
0284     return s_radixCharacter;
0285 }
0286 
0287 bool Settings::isRadixCharacterAuto() const
0288 {
0289     return s_radixCharacter == 0;
0290 }
0291 
0292 bool Settings::isRadixCharacterBoth() const
0293 {
0294     return s_radixCharacter == '*';
0295 }
0296 
0297 void Settings::setRadixCharacter(char c)
0298 {
0299     s_radixCharacter = (c != ',' && c != '.' && c != '*') ? 0 : c;
0300 }
0301 
0302 
0303 // Settings migration from legacy (0.11 and before) to 0.12 (ConfigVersion 1200).
0304 static void migrateSettings_legacyTo1200(QSettings* settings, const QString& KEY)
0305 {
0306 #ifdef SPEEDCRUNCH_PORTABLE
0307     // This is the same as the new path, but let's cut down
0308     QSettings* legacy = new QSettings(Settings::getConfigPath() + "/" + KEY + ".ini", QSettings::IniFormat);
0309 #else
0310     QSettings* legacy = new QSettings(QSettings::NativeFormat, QSettings::UserScope, KEY, KEY);
0311 #endif
0312 
0313     QString legacyFile = legacy->fileName();
0314     if (legacyFile != settings->fileName()) {
0315         // If the file names are different, we assume the legacy settings were in a
0316         // different location (most were, but e.g. not on the portable version) so we
0317         // copy everything over, then delete the old settings. On Windows, the file
0318         // name may also be a registry path, but the same reasoning applies.
0319         for (auto& key : legacy->allKeys())
0320             settings->setValue(key, legacy->value(key));
0321 
0322 #ifdef Q_OS_WIN
0323         // On Windows, check if the legacy settings were in the registry; if so, just clear()
0324         // them.
0325         if (legacy->format() == QSettings::NativeFormat) {
0326             legacy->clear();
0327             delete legacy;
0328         } else {
0329             delete legacy;
0330             QFile::remove(legacyFile);
0331         }
0332 #else
0333         // On other platforms, delete the legacy settings file.
0334         delete legacy;
0335         QFile::remove(legacyFile);
0336 #endif
0337     } else {
0338         // If both settings objects point to the same location, removing the old stuff
0339         // may well break the new settings.
0340         delete legacy;
0341     }
0342 
0343 
0344     // ColorScheme -> ColorSchemeName
0345     QString colorSchemeName;
0346     switch (settings->value("SpeedCrunch/Display/ColorScheme", -1).toInt()) {
0347     case 0:
0348         colorSchemeName = "Terminal";
0349         break;
0350     case 1:
0351         colorSchemeName = "Standard";
0352         break;
0353     case 2:
0354         colorSchemeName = "Sublime";
0355         break;
0356     default:
0357         colorSchemeName = DefaultColorScheme;
0358         break;
0359     }
0360     settings->setValue("SpeedCrunch/Display/ColorSchemeName", colorSchemeName);
0361     settings->remove("SpeedCrunch/Display/ColorScheme");
0362 
0363     // DigitGrouping (bool) -> DigitGrouping (int)
0364     bool groupDigits = settings->value("SpeedCrunch/General/DigitGrouping", false).toBool();
0365     settings->setValue("SpeedCrunch/General/DigitGrouping", groupDigits ? 1 : 0);
0366 }
0367 
0368 
0369 QSettings* createQSettings(const QString& KEY)
0370 {
0371     QSettings* settings = new QSettings(Settings::getConfigPath() + "/" + KEY + ".ini", QSettings::IniFormat);
0372     int ver = settings->value("ConfigVersion", 0).toInt();
0373     switch (ver) {
0374     case 0:
0375         migrateSettings_legacyTo1200(settings, KEY);
0376         break;
0377     }
0378     settings->setValue("ConfigVersion", ConfigVersion);
0379     return settings;
0380 }