Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 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 "notationstatusbarmodel.h"
24 :
25 : #include "defer.h"
26 : #include "log.h"
27 : #include "thirdparty/kors_logger/src/log_base.h"
28 : #include "types/translatablestring.h"
29 :
30 : #include "muse_framework_config.h"
31 : #include <algorithm>
32 :
33 : #ifdef MUSE_MODULE_WORKSPACE
34 : #include "workspace/qml/Muse/Workspace/workspacesmenumodel.h"
35 : #endif
36 :
37 : #include "engraving/dom/score.h"
38 :
39 : #include "notation/inotation.h"
40 : #include "notation/inotationaccessibility.h"
41 : #include "notation/inotationstyle.h" // IWYU pragma: keep
42 : #include "notation/inotationundostack.h" // IWYU pragma: keep
43 : #include "notation/inotationviewstate.h" // IWYU pragma: keep
44 :
45 : #include "notationscene/notationcommands.h"
46 : #include "workspace/workspacecommands.h"
47 :
48 : using namespace mu::appshell;
49 : using namespace mu::notation;
50 : using namespace muse;
51 : using namespace muse::rcommand;
52 : using namespace muse::ui;
53 : using namespace muse::uicomponents;
54 : using namespace muse::workspace;
55 :
56 : static const QString ICON_KEY("icon");
57 : static const QString SELECTABLE_KEY("selectable");
58 : static const QString SELECTED_KEY("selected");
59 : static const QString TYPE_KEY("type");
60 : static const QString ID_KEY("id");
61 : static const QString VALUE_KEY("value");
62 :
63 : static constexpr int MIN_DISPLAYED_ZOOM_PERCENTAGE = 25;
64 :
65 : static const std::map<ViewMode, Command> ALL_VIEW_MODE_MAP {
66 : { ViewMode::PAGE, VIEW_MODE_PAGE_COMMAND },
67 : { ViewMode::LINE, VIEW_MODE_CONTINUOUS_COMMAND },
68 : { ViewMode::SYSTEM, VIEW_MODE_SINGLE_COMMAND },
69 : { ViewMode::FLOAT, VIEW_MODE_FLOAT_COMMAND },
70 : };
71 :
72 0 : static Command zoomTypeToCommand(ZoomType type)
73 : {
74 0 : switch (type) {
75 0 : case ZoomType::Percentage: return ZOOM_TO_PERCENT_COMMAND;
76 0 : case ZoomType::PageWidth: return ZOOM_TO_PAGE_WIDTH_COMMAND;
77 0 : case ZoomType::WholePage: return ZOOM_TO_WHOLE_PAGE_COMMAND;
78 0 : case ZoomType::TwoPages: return ZOOM_TO_TWO_PAGES_COMMAND;
79 : }
80 :
81 0 : return {};
82 : }
83 :
84 0 : NotationStatusBarModel::NotationStatusBarModel(QObject* parent)
85 0 : : QObject(parent), muse::Contextable(muse::iocCtxForQmlObject(this))
86 : {
87 : #ifdef MUSE_MODULE_WORKSPACE
88 0 : m_workspacesMenuModel = std::make_shared<WorkspacesMenuModel>(this);
89 : #endif
90 0 : }
91 :
92 0 : void NotationStatusBarModel::classBegin()
93 : {
94 0 : init();
95 0 : }
96 :
97 0 : void NotationStatusBarModel::init()
98 : {
99 0 : TRACEFUNC;
100 :
101 0 : m_concertPitchItem = makeMenuItem(TOGGLE_CONCERT_PITCH_COMMAND);
102 0 : m_currentWorkspaceItem = makeMenuItem(WORKSPACES_CONFIGURE_COMMAND);
103 :
104 0 : setNotation(context()->currentNotation());
105 :
106 0 : context()->currentNotationChanged().onNotify(this, [this]() {
107 0 : setNotation(context()->currentNotation());
108 0 : });
109 :
110 : #ifdef MUSE_MODULE_WORKSPACE
111 0 : m_workspacesMenuModel->load();
112 0 : connect(m_workspacesMenuModel.get(), &WorkspacesMenuModel::itemsChanged, this, [this]() {
113 0 : updateCurrentWorkspaceItem();
114 0 : });
115 0 : workspaceConfiguration()->currentWorkspaceNameChanged().onReceive(this, [this](const std::string&){
116 0 : updateCurrentWorkspaceItem();
117 0 : });
118 : #endif
119 :
120 0 : updateCurrentWorkspaceItem();
121 0 : }
122 :
123 0 : QString NotationStatusBarModel::accessibilityInfo() const
124 : {
125 0 : return accessibility() ? QString::fromStdString(accessibility()->accessibilityInfo().val) : QString();
126 : }
127 :
128 0 : MenuItem* NotationStatusBarModel::concertPitchItem()
129 : {
130 0 : return m_concertPitchItem;
131 : }
132 :
133 0 : void NotationStatusBarModel::updateConcertPitchItem()
134 : {
135 0 : m_concertPitchItem->setEnabled(m_notation ? true : false);
136 0 : m_concertPitchItem->setChecked(m_notation ? m_notation->style()->styleValue(StyleId::concertPitch).toBool() : false);
137 0 : }
138 :
139 0 : MenuItem* NotationStatusBarModel::currentWorkspaceItem()
140 : {
141 0 : return m_currentWorkspaceItem;
142 : }
143 :
144 0 : void NotationStatusBarModel::updateCurrentWorkspaceItem()
145 : {
146 0 : m_currentWorkspaceItem->setTitle(
147 0 : muse::TranslatableString::untranslatable("%1 %2")
148 0 : .arg(muse::TranslatableString("workspace", "Workspace:"),
149 0 : String::fromStdString(workspaceConfiguration()->currentWorkspaceName())));
150 :
151 : #ifdef MUSE_MODULE_WORKSPACE
152 0 : m_currentWorkspaceItem->setSubitems(m_workspacesMenuModel->items());
153 : #endif
154 0 : }
155 :
156 0 : MenuItem* NotationStatusBarModel::makeMenuItem(const Command& command)
157 : {
158 0 : MenuItem* item = new MenuItem(commandsRegister()->commandInfo(command), this);
159 0 : item->setCommandState(commandsState()->commandState(command));
160 0 : return item;
161 : }
162 :
163 0 : MenuItem* NotationStatusBarModel::currentViewMode()
164 : {
165 0 : ViewMode viewMode = m_notation ? m_notation->viewMode() : ViewMode::PAGE;
166 :
167 0 : for (MenuItem* modeItem : m_availableViewModeList) {
168 0 : ViewMode mode = muse::key(ALL_VIEW_MODE_MAP, modeItem->command());
169 :
170 0 : if (mode == viewMode) {
171 0 : if (viewMode == ViewMode::LINE || viewMode == ViewMode::SYSTEM) {
172 : // In continuous view, we don't want to see "horizontal" or "vertical" (those should only be visible in the menu)
173 0 : modeItem->setTitle(muse::TranslatableString("notation", "Continuous view"));
174 : }
175 :
176 0 : return modeItem;
177 : }
178 : }
179 :
180 0 : return nullptr;
181 : }
182 :
183 0 : void NotationStatusBarModel::initAvailableViewModeList()
184 : {
185 0 : TRACEFUNC;
186 :
187 0 : qDeleteAll(m_availableViewModeList);
188 0 : m_availableViewModeList.clear();
189 :
190 0 : DEFER {
191 0 : emit availableViewModeListChanged();
192 0 : emit currentViewModeChanged();
193 0 : };
194 :
195 0 : if (!m_notation) {
196 0 : return;
197 : }
198 :
199 0 : ViewMode currentViewMode = m_notation->viewMode();
200 :
201 0 : for (const auto& pair: ALL_VIEW_MODE_MAP) {
202 0 : if (pair.first == ViewMode::FLOAT && !globalConfiguration()->devModeEnabled()) {
203 0 : continue;
204 : }
205 :
206 0 : MenuItem* viewModeItem = makeMenuItem(pair.second);
207 0 : viewModeItem->setEnabled(true);
208 0 : viewModeItem->setSelectable(true);
209 0 : viewModeItem->setSelected(currentViewMode == pair.first);
210 :
211 0 : m_availableViewModeList << viewModeItem;
212 : }
213 0 : }
214 :
215 0 : bool NotationStatusBarModel::zoomEnabled() const
216 : {
217 0 : return m_notation != nullptr;
218 : }
219 :
220 0 : int NotationStatusBarModel::currentZoomPercentage() const
221 : {
222 0 : if (!m_notation) {
223 0 : return 100;
224 : }
225 :
226 0 : return m_notation->viewState()->zoomPercentage().val;
227 : }
228 :
229 0 : void NotationStatusBarModel::setCurrentZoomPercentage(int zoomPercentage)
230 : {
231 0 : if (zoomPercentage == currentZoomPercentage()) {
232 0 : return;
233 : }
234 :
235 0 : dispatch(zoomTypeToCommand(ZoomType::Percentage), { { "percent", Val(zoomPercentage) } });
236 0 : }
237 :
238 0 : ZoomType NotationStatusBarModel::currentZoomType() const
239 : {
240 0 : if (!m_notation) {
241 0 : return ZoomType::Percentage;
242 : }
243 :
244 0 : return m_notation->viewState()->zoomType().val;
245 : }
246 :
247 0 : void NotationStatusBarModel::setNotation(const INotationPtr& notation)
248 : {
249 0 : if (m_notation == notation) {
250 0 : return;
251 : }
252 :
253 0 : if (m_notation) {
254 0 : m_notation->undoStack()->changesChannel().disconnect(this);
255 0 : m_notation->viewModeChanged().disconnect(this);
256 0 : m_notation->viewState()->zoomPercentage().ch.disconnect(this);
257 0 : m_notation->viewState()->zoomType().ch.disconnect(this);
258 0 : m_notation->accessibility()->accessibilityInfo().ch.disconnect(this);
259 : }
260 :
261 0 : m_notation = notation;
262 :
263 0 : emit zoomEnabledChanged();
264 0 : updateConcertPitchItem();
265 :
266 0 : initAvailableViewModeList();
267 0 : initAvailableZoomList();
268 :
269 0 : if (!notation) {
270 0 : return;
271 : }
272 :
273 0 : notation->undoStack()->changesChannel().onReceive(this, [this](const mu::engraving::ScoreChanges& changes) {
274 0 : if (muse::contains(changes.changedStyleIdSet, mu::engraving::Sid::concertPitch)) {
275 0 : updateConcertPitchItem();
276 : }
277 0 : });
278 :
279 0 : notation->viewModeChanged().onNotify(this, [this]() {
280 0 : initAvailableViewModeList();
281 0 : });
282 :
283 0 : notation->viewState()->zoomPercentage().ch.onReceive(this, [this](int) {
284 0 : initAvailableZoomList();
285 0 : });
286 :
287 0 : notation->viewState()->zoomType().ch.onReceive(this, [this](ZoomType) {
288 0 : initAvailableZoomList();
289 0 : });
290 :
291 0 : emit accessibilityInfoChanged();
292 :
293 0 : notation->accessibility()->accessibilityInfo().ch.onReceive(this, [this](const std::string&) {
294 0 : emit accessibilityInfoChanged();
295 0 : });
296 : }
297 :
298 0 : void NotationStatusBarModel::toggleConcertPitch()
299 : {
300 0 : dispatch(TOGGLE_CONCERT_PITCH_COMMAND);
301 0 : }
302 :
303 0 : void NotationStatusBarModel::setCurrentViewMode(const QString& modeCode)
304 : {
305 0 : dispatch(Command(modeCode.toStdString()));
306 0 : }
307 :
308 0 : void NotationStatusBarModel::initAvailableZoomList()
309 : {
310 0 : TRACEFUNC;
311 :
312 0 : qDeleteAll(m_availableZoomList);
313 0 : m_availableZoomList.clear();
314 :
315 0 : DEFER {
316 0 : emit availableZoomListChanged();
317 0 : emit currentZoomPercentageChanged();
318 0 : };
319 :
320 0 : if (!m_notation) {
321 0 : return;
322 : }
323 :
324 0 : int currZoomPercentage = currentZoomPercentage();
325 0 : ZoomType currZoomType = currentZoomType();
326 :
327 0 : auto zoomPercentageTitle = [](int percentage) {
328 0 : return muse::TranslatableString::untranslatable("%1%").arg(percentage);
329 : };
330 :
331 : auto buildZoomItem
332 0 : = [this, currZoomType, currZoomPercentage](ZoomType type, const muse::TranslatableString& title = {}, int value = 0) {
333 0 : MenuItem* menuItem = new MenuItem(this);
334 0 : menuItem->setId(QString::number(static_cast<int>(type)) + QString::number(value));
335 0 : menuItem->setTitle(title.isEmpty() ? zoomTypeTitle(type) : title);
336 0 : menuItem->setSelectable(true);
337 0 : if (currZoomType == type) {
338 0 : menuItem->setSelected(type == ZoomType::Percentage ? value == currZoomPercentage : true);
339 : }
340 :
341 0 : menuItem->setParams({ { "type", Val(type) }, { "percent", Val(value) } });
342 :
343 0 : return menuItem;
344 0 : };
345 :
346 0 : QList<int> possibleZoomList = possibleZoomPercentageList();
347 :
348 0 : for (int zoom : possibleZoomList) {
349 0 : m_availableZoomList << buildZoomItem(ZoomType::Percentage, zoomPercentageTitle(zoom), zoom);
350 : }
351 :
352 0 : m_availableZoomList << buildZoomItem(ZoomType::PageWidth);
353 0 : m_availableZoomList << buildZoomItem(ZoomType::WholePage);
354 0 : m_availableZoomList << buildZoomItem(ZoomType::TwoPages);
355 :
356 0 : bool isCustomZoom = currZoomType == ZoomType::Percentage && !possibleZoomList.contains(currZoomPercentage);
357 0 : if (isCustomZoom) {
358 0 : MenuItem* customZoom = buildZoomItem(ZoomType::Percentage, zoomPercentageTitle(currZoomPercentage), currZoomPercentage);
359 0 : customZoom->setSelected(true);
360 0 : m_availableZoomList << customZoom;
361 : }
362 0 : }
363 :
364 0 : void NotationStatusBarModel::setCurrentZoom(const QString& zoomId)
365 : {
366 0 : auto it = std::find_if(m_availableZoomList.begin(), m_availableZoomList.end(), [zoomId](const MenuItem* item) {
367 0 : return item->id() == zoomId;
368 : });
369 :
370 0 : if (it == m_availableZoomList.end()) {
371 0 : return;
372 : }
373 :
374 0 : const Params& params = (*it)->params();
375 0 : ZoomType type = params.at("type").toEnum<ZoomType>();
376 0 : int percent = params.at("percent").toInt();
377 :
378 0 : dispatch(zoomTypeToCommand(type), { { "percent", Val(percent) } });
379 :
380 0 : emit availableZoomListChanged();
381 0 : }
382 :
383 0 : int NotationStatusBarModel::minZoomPercentage() const
384 : {
385 0 : return 5;
386 : }
387 :
388 0 : int NotationStatusBarModel::maxZoomPercentage() const
389 : {
390 0 : return possibleZoomPercentageList().last();
391 : }
392 :
393 0 : void NotationStatusBarModel::zoomIn()
394 : {
395 0 : dispatch(ZOOM_IN_COMMAND);
396 0 : }
397 :
398 0 : void NotationStatusBarModel::zoomOut()
399 : {
400 0 : dispatch(ZOOM_OUT_COMMAND);
401 0 : }
402 :
403 0 : void NotationStatusBarModel::handleMenuItem(const QString& itemId)
404 : {
405 0 : LOGDA() << itemId;
406 0 : if (m_concertPitchItem->id() == itemId) {
407 0 : dispatch(m_concertPitchItem->command());
408 0 : } else if (m_currentWorkspaceItem->id() == itemId) {
409 0 : dispatch(m_currentWorkspaceItem->command());
410 : } else {
411 0 : UNREACHABLE;
412 : }
413 0 : }
414 :
415 0 : void NotationStatusBarModel::handleWorkspacesMenuItem(const QString& itemId)
416 : {
417 : #ifdef MUSE_MODULE_WORKSPACE
418 0 : m_workspacesMenuModel->handleMenuItem(itemId);
419 : #else
420 : UNUSED(itemId);
421 : #endif
422 0 : }
423 :
424 0 : INotationPtr NotationStatusBarModel::notation() const
425 : {
426 0 : return context()->currentNotation();
427 : }
428 :
429 0 : INotationAccessibilityPtr NotationStatusBarModel::accessibility() const
430 : {
431 0 : return notation() ? notation()->accessibility() : nullptr;
432 : }
433 :
434 0 : void NotationStatusBarModel::dispatch(const muse::rcommand::Command& command, const muse::rcommand::Params& params)
435 : {
436 0 : dispatcher()->dispatch(command, params);
437 0 : }
438 :
439 0 : QList<int> NotationStatusBarModel::possibleZoomPercentageList() const
440 : {
441 0 : QList<int> result;
442 :
443 0 : for (int zoom : notationConfiguration()->possibleZoomPercentageList()) {
444 0 : if (zoom >= MIN_DISPLAYED_ZOOM_PERCENTAGE) {
445 0 : result << zoom;
446 : }
447 0 : }
448 :
449 0 : return result;
450 0 : }
451 :
452 0 : QVariantList NotationStatusBarModel::availableViewModeList_property()
453 : {
454 0 : return menuItemListToVariantList(m_availableViewModeList);
455 : }
456 :
457 0 : QVariantList NotationStatusBarModel::availableZoomList_property()
458 : {
459 0 : return menuItemListToVariantList(m_availableZoomList);
460 : }
461 :
462 0 : QVariantList NotationStatusBarModel::menuItemListToVariantList(const MenuItemList& list) const
463 : {
464 0 : QVariantList result;
465 0 : for (MenuItem* item: list) {
466 0 : result << QVariant::fromValue(item);
467 : }
468 :
469 0 : return result;
470 0 : }
|