LCOV - code coverage report
Current view: top level - muse/framework/global - settings.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 39.3 % 191 75
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2025 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "settings.h"
      24              : 
      25              : #include <QDateTime>
      26              : #include <QSettings>
      27              : #include <QStandardPaths>
      28              : #include <QDir>
      29              : 
      30              : #ifdef MUSE_MODULE_MULTIWINDOWS
      31              : #include "multiwindows/resourcelockguard.h"
      32              : #endif
      33              : 
      34              : #include "muse_framework_config.h"
      35              : 
      36              : #include "log.h"
      37              : 
      38              : using namespace muse;
      39              : using namespace muse::async;
      40              : 
      41              : static const std::string SETTINGS_RESOURCE_NAME("SETTINGS");
      42              : 
      43        41277 : Settings* Settings::instance()
      44              : {
      45        41277 :     static Settings s;
      46        41277 :     return &s;
      47              : }
      48              : 
      49           46 : Settings::Settings()
      50              : {
      51              : #ifdef WIN_PORTABLE
      52              :     QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, dataPath());
      53              :     QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, dataPath());
      54              : #endif
      55              : 
      56              : #ifndef Q_OS_MAC
      57           46 :     QSettings::setDefaultFormat(QSettings::IniFormat);
      58              : #endif
      59              : 
      60           46 :     m_settings = new QSettings();
      61           46 : }
      62              : 
      63           46 : Settings::~Settings()
      64              : {
      65           46 :     delete m_settings;
      66           46 : }
      67              : 
      68            0 : io::path_t Settings::filePath() const
      69              : {
      70            0 :     return m_settings->fileName();
      71              : }
      72              : 
      73            0 : const Settings::Items& Settings::items() const
      74              : {
      75            0 :     return m_isTransactionStarted ? m_localSettings : m_items;
      76              : }
      77              : 
      78              : /**
      79              :  * @brief Settings::reload method needed only for compatibility with the old MU preferences
      80              :  */
      81            0 : void Settings::reload()
      82              : {
      83            0 :     Items items = readItems();
      84              : 
      85            0 :     for (auto it = items.cbegin(); it != items.cend(); ++it) {
      86            0 :         setSharedValue(it->first, it->second.value);
      87              :     }
      88            0 : }
      89              : 
      90           46 : void Settings::load()
      91              : {
      92           46 :     m_items = readItems();
      93           46 : }
      94              : 
      95            0 : void Settings::reset(bool keepDefaultSettings, bool notifyAboutChanges, bool notifyOtherInstances)
      96              : {
      97            0 :     m_settings->clear();
      98              : 
      99            0 :     m_isTransactionStarted = false;
     100              : 
     101            0 :     std::vector<Settings::Key> locallyAddedKeys;
     102            0 :     for (auto it = m_localSettings.begin(); it != m_localSettings.end(); ++it) {
     103            0 :         auto item = m_items.find(it->first);
     104            0 :         if (item == m_items.end()) {
     105            0 :             locallyAddedKeys.push_back(it->first);
     106              :         } else {
     107              :             // UI currently has the values from m_localSettings but we've turned off the transaction.
     108            0 :             item->second.value = it->second.value;
     109              :         }
     110              :     }
     111              : 
     112            0 :     m_localSettings.clear();
     113              : 
     114            0 :     if (!keepDefaultSettings) {
     115            0 :         QDir(dataPath()).removeRecursively();
     116            0 :         QDir().mkpath(dataPath());
     117              :     }
     118              : 
     119            0 :     if (!notifyAboutChanges) {
     120            0 :         return;
     121              :     }
     122              : 
     123            0 :     for (auto it = m_items.begin(); it != m_items.end(); ++it) {
     124            0 :         if (it->second.value == it->second.defaultValue) {
     125            0 :             continue;
     126              :         }
     127              : 
     128            0 :         it->second.value = it->second.defaultValue;
     129              : 
     130            0 :         Channel<Val>& channel = findChannel(it->first);
     131            0 :         channel.send(it->second.value);
     132              :     }
     133              : 
     134            0 :     for (auto it = locallyAddedKeys.cbegin(); it != locallyAddedKeys.cend(); ++it) {
     135            0 :         Channel<Val>& channel = findChannel(*it);
     136            0 :         channel.send(Val());
     137              :     }
     138              : 
     139              :     UNUSED(notifyOtherInstances);
     140              : #ifdef MUSE_MODULE_MULTIWINDOWS
     141            0 :     if (notifyOtherInstances && multiwindowsProvider()) {
     142            0 :         multiwindowsProvider()->settingsReset();
     143              :     }
     144              : #endif
     145            0 : }
     146              : 
     147            0 : static Val compat_QVariantToVal(const QVariant& var)
     148              : {
     149            0 :     if (!var.isValid()) {
     150            0 :         return Val();
     151              :     }
     152              : 
     153            0 :     switch (var.typeId()) {
     154            0 :     case QMetaType::QByteArray: return Val(var.toByteArray().toStdString());
     155            0 :     case QMetaType::QDateTime: return Val(var.toDateTime().toString(Qt::ISODate));
     156            0 :     case QMetaType::QStringList: {
     157            0 :         QStringList sl = var.toStringList();
     158            0 :         ValList vl;
     159            0 :         for (const QString& s : sl) {
     160            0 :             vl.push_back(Val(s));
     161              :         }
     162            0 :         return Val(vl);
     163            0 :     }
     164            0 :     default:
     165            0 :         break;
     166              :     }
     167              : 
     168            0 :     return Val::fromQVariant(var);
     169              : }
     170              : 
     171           46 : Settings::Items Settings::readItems() const
     172              : {
     173           46 :     Items result;
     174              : #ifdef MUSE_MODULE_MULTIWINDOWS
     175           46 :     muse::mi::ReadResourceLockGuard resource_lock(multiwindowsProvider.get(), SETTINGS_RESOURCE_NAME);
     176              : #endif
     177           46 :     for (const QString& key : m_settings->allKeys()) {
     178            0 :         Item item;
     179            0 :         item.key = Key(std::string(), key.toStdString());
     180            0 :         item.value = compat_QVariantToVal(m_settings->value(key));
     181              : 
     182            0 :         result[item.key] = item;
     183           46 :     }
     184              : 
     185           46 :     return result;
     186           46 : }
     187              : 
     188        38194 : const Val& Settings::value(const Key& key) const
     189              : {
     190        38194 :     return findItem(key).value;
     191              : }
     192              : 
     193            0 : const Val& Settings::defaultValue(const Key& key) const
     194              : {
     195            0 :     return findItem(key).defaultValue;
     196              : }
     197              : 
     198         2035 : void Settings::setSharedValue(const Key& key, const Val& value)
     199              : {
     200         2035 :     setLocalValue(key, value);
     201              : #ifdef MUSE_MODULE_MULTIWINDOWS
     202         2035 :     if (multiwindowsProvider()) {
     203            0 :         multiwindowsProvider()->settingsSetValue(key.key, value);
     204              :     }
     205              : #endif
     206         2035 : }
     207              : 
     208         2035 : void Settings::setLocalValue(const Key& key, const Val& value)
     209              : {
     210         2035 :     Item& item = findItem(key);
     211              : 
     212         2035 :     if (!item.isNull() && item.value == value) {
     213         1874 :         return;
     214              :     }
     215              : 
     216          161 :     if (!m_isTransactionStarted) {
     217          161 :         writeValue(key, value);
     218              :     }
     219              : 
     220          161 :     if (item.isNull()) {
     221            0 :         insertNewItem(key, value);
     222              :     } else {
     223          161 :         item.value = value;
     224              :     }
     225              : 
     226          161 :     auto it = m_channels.find(key);
     227          161 :     if (it != m_channels.end()) {
     228            3 :         async::Channel<Val> channel = it->second;
     229            3 :         channel.send(value);
     230            3 :     }
     231              : }
     232              : 
     233          161 : void Settings::writeValue(const Key& key, const Val& value)
     234              : {
     235              : #ifdef MUSE_MODULE_MULTIWINDOWS
     236          161 :     muse::mi::WriteResourceLockGuard resource_lock(multiwindowsProvider.get(), SETTINGS_RESOURCE_NAME);
     237              : #endif
     238              :     // TODO: implement writing/reading first part of key (module name)
     239          161 :     m_settings->setValue(QString::fromStdString(key.key), value.toQVariant());
     240          161 : }
     241              : 
     242            0 : QString Settings::dataPath() const
     243              : {
     244              : #ifdef WIN_PORTABLE
     245              :     return QDir::cleanPath(QString("%1/../../../Data/settings").arg(QCoreApplication::applicationDirPath()));
     246              : #else
     247            0 :     return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
     248              : #endif
     249              : }
     250              : 
     251          366 : void Settings::setDefaultValue(const Key& key, const Val& value)
     252              : {
     253          366 :     Item& item = findItem(key);
     254              : 
     255          366 :     if (item.isNull()) {
     256         1098 :         m_items[key] = Item{ key, value, value, "", false, Val(), Val() };
     257              :     } else {
     258            0 :         item.defaultValue = value;
     259            0 :         item.value.setType(value.type());
     260              :     }
     261          366 : }
     262              : 
     263          194 : void Settings::setDescription(const Key& key, const std::string& value)
     264              : {
     265          194 :     Item& item = findItem(key);
     266          194 :     if (item.isNull()) {
     267            0 :         return;
     268              :     }
     269              : 
     270          194 :     item.description = value;
     271              : }
     272              : 
     273          194 : void Settings::setCanBeManuallyEdited(const Settings::Key& key, bool canBeManuallyEdited, const Val& minValue, const Val& maxValue)
     274              : {
     275          194 :     Item& item = findItem(key);
     276              : 
     277          194 :     if (item.isNull()) {
     278            0 :         m_items[key] = Item{ key, Val(), Val(), "", canBeManuallyEdited, minValue, maxValue };
     279              :     } else {
     280          194 :         item.canBeManuallyEdited = canBeManuallyEdited;
     281          194 :         item.minValue = minValue;
     282          194 :         item.maxValue = maxValue;
     283              :     }
     284          194 : }
     285              : 
     286            0 : void Settings::insertNewItem(const Settings::Key& key, const Val& value)
     287              : {
     288            0 :     Item item = Item{ key, value, value, "", false, Val(), Val() };
     289            0 :     if (m_isTransactionStarted) {
     290            0 :         m_localSettings[key] = item;
     291              :     } else {
     292            0 :         m_items[key] = item;
     293              :     }
     294            0 : }
     295              : 
     296            0 : void Settings::beginTransaction(bool notifyToOtherInstances)
     297              : {
     298            0 :     if (m_isTransactionStarted) {
     299            0 :         LOGW() << "Transaction is already started";
     300            0 :         return;
     301              :     }
     302              : 
     303            0 :     m_localSettings = m_items;
     304            0 :     m_isTransactionStarted = true;
     305              : 
     306              :     UNUSED(notifyToOtherInstances)
     307              : #ifdef MUSE_MODULE_MULTIWINDOWS
     308            0 :     if (notifyToOtherInstances && multiwindowsProvider()) {
     309            0 :         multiwindowsProvider()->settingsBeginTransaction();
     310              :     }
     311              : #endif
     312              : }
     313              : 
     314            0 : void Settings::commitTransaction(bool notifyToOtherInstances)
     315              : {
     316            0 :     m_isTransactionStarted = false;
     317              : 
     318            0 :     for (auto it = m_localSettings.begin(); it != m_localSettings.end(); ++it) {
     319            0 :         Item& item = findItem(it->first);
     320            0 :         if (item.value == it->second.value) {
     321            0 :             continue;
     322              :         }
     323              : 
     324            0 :         if (item.isNull()) {
     325            0 :             insertNewItem(it->first, it->second.value);
     326              :         } else {
     327            0 :             item.value = it->second.value;
     328              :         }
     329              : 
     330            0 :         writeValue(it->first, it->second.value);
     331              :     }
     332              : 
     333            0 :     m_localSettings.clear();
     334              : 
     335              :     UNUSED(notifyToOtherInstances)
     336              : #ifdef MUSE_MODULE_MULTIWINDOWS
     337            0 :     if (notifyToOtherInstances && multiwindowsProvider()) {
     338            0 :         multiwindowsProvider()->settingsCommitTransaction();
     339              :     }
     340              : #endif
     341            0 : }
     342              : 
     343            0 : void Settings::rollbackTransaction(bool notifyToOtherInstances)
     344              : {
     345            0 :     m_isTransactionStarted = false;
     346              : 
     347            0 :     for (auto it = m_localSettings.begin(); it != m_localSettings.end(); ++it) {
     348            0 :         Item item = findItem(it->first);
     349            0 :         if (item.value == it->second.value) {
     350            0 :             continue;
     351              :         }
     352              : 
     353            0 :         Channel<Val>& channel = findChannel(it->first);
     354            0 :         channel.send(item.value);
     355            0 :     }
     356              : 
     357            0 :     m_localSettings.clear();
     358              : 
     359              :     UNUSED(notifyToOtherInstances)
     360              : #ifdef MUSE_MODULE_MULTIWINDOWS
     361            0 :     if (notifyToOtherInstances && multiwindowsProvider()) {
     362            0 :         multiwindowsProvider()->settingsRollbackTransaction();
     363              :     }
     364              : #endif
     365            0 : }
     366              : 
     367        40983 : Settings::Item& Settings::findItem(const Key& key) const
     368              : {
     369        40983 :     Items& items = m_isTransactionStarted ? m_localSettings : m_items;
     370              : 
     371        40983 :     auto it = items.find(key);
     372              : 
     373        40983 :     if (it == items.end()) {
     374          366 :         static Item null;
     375          366 :         return null;
     376              :     }
     377              : 
     378        40617 :     return it->second;
     379              : }
     380              : 
     381            0 : async::Channel<Val>& Settings::findChannel(const Settings::Key& key) const
     382              : {
     383            0 :     auto it = m_channels.find(key);
     384              : 
     385            0 :     if (it == m_channels.end()) {
     386            0 :         static async::Channel<Val> null;
     387            0 :         return null;
     388              :     }
     389              : 
     390            0 :     return it->second;
     391            0 : }
     392              : 
     393          248 : async::Channel<Val> Settings::valueChanged(const Key& key) const
     394              : {
     395          248 :     return m_channels[key];
     396              : }
     397              : 
     398         2390 : Settings::Key::Key(std::string moduleName, std::string key)
     399         2390 :     : moduleName(std::move(moduleName)), key(std::move(key))
     400              : {
     401         2390 : }
     402              : 
     403            0 : bool Settings::Key::operator==(const Key& k) const
     404              : {
     405            0 :     return key == k.key;
     406              : }
     407              : 
     408       288657 : bool Settings::Key::operator<(const Key& k) const
     409              : {
     410       288657 :     return key < k.key;
     411              : }
     412              : 
     413         2950 : bool Settings::Key::isNull() const
     414              : {
     415         2950 :     return key.empty();
     416              : }
        

Generated by: LCOV version 2.0-1