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 : }
|