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) 2026 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 "uistate.h"
24 :
25 : #include "global/async/async.h"
26 :
27 : using namespace muse;
28 : using namespace muse::ui;
29 : using namespace muse::async;
30 :
31 : static const QString WINDOW_GEOMETRY_KEY("window");
32 :
33 0 : void UiState::init()
34 : {
35 0 : m_uiArrangement.stateChanged(WINDOW_GEOMETRY_KEY).onNotify(this, [this]() {
36 0 : m_windowGeometryChanged.notify();
37 0 : });
38 :
39 0 : m_uiArrangement.load();
40 0 : }
41 :
42 0 : QByteArray UiState::windowGeometry() const
43 : {
44 0 : return m_uiArrangement.state(WINDOW_GEOMETRY_KEY);
45 : }
46 :
47 0 : void UiState::setWindowGeometry(const QByteArray& geometry)
48 : {
49 0 : m_uiArrangement.setState(WINDOW_GEOMETRY_KEY, geometry);
50 0 : }
51 :
52 0 : muse::async::Notification UiState::windowGeometryChanged() const
53 : {
54 0 : return m_windowGeometryChanged;
55 : }
56 :
57 0 : ValNt<QByteArray> UiState::pageState(const QString& pageName) const
58 : {
59 0 : ValNt<QByteArray> result;
60 0 : result.val = m_uiArrangement.state(pageName);
61 0 : result.notification = m_uiArrangement.stateChanged(pageName);
62 :
63 0 : return result;
64 0 : }
65 :
66 0 : void UiState::setPageState(const QString& pageName, const QByteArray& state)
67 : {
68 0 : m_uiArrangement.setState(pageName, state);
69 0 : }
70 :
71 0 : ToolConfig UiState::toolConfig(const QString& toolName, const ToolConfig& defaultConfig) const
72 : {
73 0 : ToolConfig config = m_uiArrangement.toolConfig(toolName);
74 0 : if (!config.isValid()) {
75 0 : return defaultConfig;
76 : }
77 :
78 0 : updateToolConfig(toolName, config, defaultConfig);
79 0 : return config;
80 0 : }
81 :
82 0 : void UiState::setToolConfig(const QString& toolName, const ToolConfig& config)
83 : {
84 0 : m_uiArrangement.setToolConfig(toolName, config);
85 0 : }
86 :
87 0 : async::Notification UiState::toolConfigChanged(const QString& toolName) const
88 : {
89 0 : return m_uiArrangement.toolConfigChanged(toolName);
90 : }
91 :
92 0 : void UiState::updateToolConfig(const QString& toolName, ToolConfig& userConfig, const ToolConfig& defaultConfig) const
93 : {
94 0 : bool hasChanged = false;
95 :
96 : // Remove items that are not in the default config
97 : {
98 0 : QList<ToolConfig::Item> itemsToRemove;
99 0 : for (const auto& item : userConfig.items) {
100 0 : if (item.isSeparator()) {
101 0 : continue;
102 : }
103 :
104 0 : if (std::find_if(defaultConfig.items.cbegin(), defaultConfig.items.cend(), [item](const auto& defaultItem) {
105 0 : return item.intent == defaultItem.intent;
106 0 : }) == defaultConfig.items.cend()) {
107 0 : itemsToRemove << item;
108 : }
109 : }
110 :
111 0 : for (const auto& itemToRemove : itemsToRemove) {
112 0 : hasChanged = true;
113 0 : userConfig.items.removeAll(itemToRemove);
114 : }
115 0 : }
116 :
117 : // Insert items that are missing in the user config
118 : {
119 0 : for (const auto& defaultItem : defaultConfig.items) {
120 0 : if (defaultItem.isSeparator()) {
121 0 : continue;
122 : }
123 :
124 0 : if (std::find_if(userConfig.items.cbegin(), userConfig.items.cend(), [defaultItem](const auto& item) {
125 0 : return defaultItem.intent == item.intent;
126 0 : }) == userConfig.items.cend()) {
127 0 : hasChanged = true;
128 :
129 : // Try to find a good place to insert the item
130 0 : int indexOfDefaultItem = defaultConfig.items.indexOf(defaultItem);
131 :
132 : // If it was at the start of the default items...
133 0 : if (indexOfDefaultItem == 0) {
134 : // insert it at the start of the user items
135 0 : userConfig.items.prepend(defaultItem);
136 0 : continue;
137 : }
138 :
139 : // If it was at the end of the default items...
140 0 : if (indexOfDefaultItem == defaultConfig.items.size() - 1) {
141 : // insert it at the end of the user items
142 0 : userConfig.items.append(defaultItem);
143 0 : continue;
144 : }
145 :
146 : // Look at the item before it...
147 : {
148 0 : const auto& itemBefore = defaultConfig.items[indexOfDefaultItem - 1];
149 0 : if (!itemBefore.isSeparator()) {
150 0 : auto it = std::find_if(userConfig.items.begin(), userConfig.items.end(), [itemBefore](const auto& item) {
151 0 : return item.intent == itemBefore.intent;
152 : });
153 :
154 0 : if (it != userConfig.items.end()) {
155 0 : userConfig.items.insert(++it, defaultItem);
156 0 : continue;
157 : }
158 : }
159 : }
160 :
161 : // Look at the item after it...
162 : {
163 0 : const auto& itemAfter = defaultConfig.items[indexOfDefaultItem + 1];
164 0 : if (!itemAfter.isSeparator()) {
165 0 : auto it = std::find_if(userConfig.items.begin(), userConfig.items.end(), [itemAfter](const auto& item) {
166 0 : return item.intent == itemAfter.intent;
167 : });
168 :
169 0 : userConfig.items.insert(it, defaultItem);
170 0 : continue;
171 0 : }
172 : }
173 :
174 : // Last resort: just insert at the end
175 0 : userConfig.items.append(defaultItem);
176 : }
177 : }
178 : }
179 :
180 0 : if (hasChanged) {
181 : // Save for later
182 0 : auto self = const_cast<UiState*>(this);
183 :
184 0 : Async::call(self, [self, toolName, userConfig]() {
185 0 : self->setToolConfig(toolName, userConfig);
186 0 : });
187 : }
188 0 : }
189 :
190 0 : QString UiState::uiItemState(const QString& itemName) const
191 : {
192 0 : return m_uiArrangement.value(itemName);
193 : }
194 :
195 0 : void UiState::setUiItemState(const QString& itemName, const QString& value)
196 : {
197 0 : m_uiArrangement.setValue(itemName, value);
198 0 : }
199 :
200 0 : Notification UiState::uiItemStateChanged(const QString& itemName) const
201 : {
202 0 : return m_uiArrangement.valueChanged(itemName);
203 : }
204 :
205 0 : bool UiState::isVisible(const QString& key, bool def) const
206 : {
207 0 : QString val = m_uiArrangement.value(key);
208 0 : bool ok = false;
209 0 : int valInt = val.toInt(&ok);
210 0 : return ok ? bool(valInt) : def;
211 0 : }
212 :
213 0 : void UiState::setIsVisible(const QString& key, bool val)
214 : {
215 0 : m_uiArrangement.setValue(key, QString::number(val ? 1 : 0));
216 0 : }
217 :
218 0 : async::Notification UiState::isVisibleChanged(const QString& key) const
219 : {
220 0 : return m_uiArrangement.valueChanged(key);
221 : }
|