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) 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 : #include "abstracttoolbarmodel.h"
23 :
24 : #include "types/translatablestring.h"
25 :
26 : #include "toolbaritem.h"
27 : #include "menuitem.h"
28 :
29 : #include "log.h"
30 :
31 : using namespace muse::uicomponents;
32 : using namespace muse::ui;
33 : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
34 : using namespace muse::actions;
35 : #endif
36 :
37 : const int AbstractToolBarModel::INVALID_ITEM_INDEX = -1;
38 :
39 0 : AbstractToolBarModel::AbstractToolBarModel(QObject* parent)
40 0 : : QAbstractListModel(parent), muse::Contextable(muse::iocCtxForQmlObject(this))
41 : {
42 0 : }
43 :
44 0 : QVariant AbstractToolBarModel::data(const QModelIndex& index, int role) const
45 : {
46 0 : int row = index.row();
47 :
48 0 : if (!isIndexValid(row)) {
49 0 : return QVariant();
50 : }
51 :
52 0 : ToolBarItem* item = m_items.at(row);
53 :
54 0 : switch (role) {
55 0 : case ItemRole: return QVariant::fromValue(item);
56 0 : case UserRole: return QVariant();
57 : }
58 :
59 0 : return QVariant();
60 : }
61 :
62 0 : bool AbstractToolBarModel::isIndexValid(int index) const
63 : {
64 0 : return index >= 0 && index < m_items.size();
65 : }
66 :
67 0 : int AbstractToolBarModel::rowCount(const QModelIndex&) const
68 : {
69 0 : return m_items.count();
70 : }
71 :
72 0 : QHash<int, QByteArray> AbstractToolBarModel::roleNames() const
73 : {
74 : static const QHash<int, QByteArray> roles {
75 0 : { ItemRole, "item" }
76 0 : };
77 :
78 0 : return roles;
79 0 : }
80 :
81 0 : QVariantMap AbstractToolBarModel::get(int index)
82 : {
83 0 : QVariantMap result;
84 :
85 0 : QHash<int, QByteArray> names = roleNames();
86 0 : QHashIterator<int, QByteArray> i(names);
87 0 : while (i.hasNext()) {
88 0 : i.next();
89 0 : QModelIndex idx = this->index(index, 0);
90 0 : QVariant data = idx.data(i.key());
91 0 : result[i.value()] = data;
92 0 : }
93 :
94 0 : return result;
95 0 : }
96 :
97 0 : void AbstractToolBarModel::load()
98 : {
99 0 : commandsState()->commandStateChanged().onReceive(this, [this](const rcommand::Command& command, const rcommand::CommandState& state) {
100 0 : onCommandStateChanged(command, state);
101 0 : }, async::Asyncable::Mode::SetReplace);
102 :
103 : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
104 0 : uiActionsRegister()->actionStateChanged().onReceive(this, [this](const ActionCodeList& codes) {
105 0 : onActionsStateChanges(codes);
106 0 : }, async::Asyncable::Mode::SetReplace);
107 : #endif
108 :
109 : #ifdef MUSE_MODULE_SHORTCUTS_V2
110 0 : commandShortcutsRegister()->shortcutsChanged().onNotify(this, [this]() {
111 0 : updateShortcutsAll();
112 0 : }, async::Asyncable::Mode::SetReplace);
113 : #else
114 : shortcutsRegister()->shortcutsChanged().onNotify(this, [this]() {
115 : updateShortcutsAll();
116 : }, async::Asyncable::Mode::SetReplace);
117 : #endif
118 0 : }
119 :
120 0 : void AbstractToolBarModel::onCommandStateChanged(const rcommand::Command& command, const rcommand::CommandState& state)
121 : {
122 0 : updateState(m_items, command, state);
123 0 : }
124 :
125 0 : void AbstractToolBarModel::updateState(ToolBarItemList& items, const rcommand::Command& command, const rcommand::CommandState& state)
126 : {
127 0 : for (ToolBarItem* toolBarItem : items) {
128 0 : if (!toolBarItem) {
129 0 : continue;
130 : }
131 :
132 0 : if (command == toolBarItem->command()) {
133 0 : toolBarItem->setCommandState(state);
134 : }
135 :
136 0 : QList<MenuItem*> subitems = toolBarItem->menuItems();
137 0 : if (!subitems.empty()) {
138 0 : updateState(subitems, command, state);
139 : }
140 0 : }
141 0 : }
142 :
143 0 : void AbstractToolBarModel::updateState(QList<MenuItem*>& items, const rcommand::Command& command, const rcommand::CommandState& state)
144 : {
145 0 : for (MenuItem* menuItem : items) {
146 0 : if (!menuItem) {
147 0 : continue;
148 : }
149 :
150 0 : if (command == menuItem->command()) {
151 0 : menuItem->setCommandState(state);
152 : }
153 :
154 0 : MenuItemList subitems = menuItem->subitems();
155 0 : if (!subitems.empty()) {
156 0 : updateState(subitems, command, state);
157 : }
158 0 : }
159 0 : }
160 :
161 0 : ToolBarItem* AbstractToolBarModel::makeItem(const rcommand::Command& command,
162 : const rcommand::Params& params,
163 : const TranslatableString& title)
164 : {
165 0 : const rcommand::CommandInfo& info = commandsRegister()->commandInfo(command);
166 0 : if (!info.isValid()) {
167 0 : LOGW() << "not found command: " << command;
168 0 : return nullptr;
169 : }
170 :
171 0 : ToolBarItem* item = new ToolBarItem(info, ToolBarItemType::ACTION, this);
172 0 : item->setCommand(command);
173 0 : item->setParams(params);
174 0 : item->setCommandState(commandsState()->commandState(command));
175 :
176 0 : if (!title.isEmpty()) {
177 0 : item->setTitle(title);
178 : }
179 :
180 0 : return item;
181 : }
182 :
183 0 : ToolBarItem* AbstractToolBarModel::makeItem(const rcommand::Command& command, const TranslatableString& title)
184 : {
185 0 : return makeItem(command, {}, title);
186 : }
187 :
188 0 : ToolBarItem& AbstractToolBarModel::findItem(const rcommand::Command& command) const
189 : {
190 0 : if (ToolBarItem* toolBarItem = findItemPtr(command)) {
191 0 : return *toolBarItem;
192 : }
193 :
194 0 : static ToolBarItem dummy;
195 0 : return dummy;
196 : }
197 :
198 0 : ToolBarItem* AbstractToolBarModel::findItemPtr(const rcommand::Command& command) const
199 : {
200 0 : for (ToolBarItem* toolBarItem : std::as_const(m_items)) {
201 0 : if (toolBarItem->command() == command) {
202 0 : return toolBarItem;
203 : }
204 : }
205 :
206 0 : return nullptr;
207 : }
208 :
209 0 : QVariantList AbstractToolBarModel::itemsProperty() const
210 : {
211 0 : QVariantList items;
212 :
213 0 : for (ToolBarItem* item: m_items) {
214 0 : items << QVariant::fromValue(item);
215 : }
216 :
217 0 : return items;
218 0 : }
219 :
220 0 : const ToolBarItemList& AbstractToolBarModel::items() const
221 : {
222 0 : return m_items;
223 : }
224 :
225 0 : void AbstractToolBarModel::setItems(const ToolBarItemList& items)
226 : {
227 0 : TRACEFUNC;
228 :
229 0 : beginResetModel();
230 :
231 0 : qDeleteAll(m_items);
232 0 : m_items.clear();
233 :
234 : //! NOTE: make sure that we don't have two separators sequentially
235 0 : bool isPreviousSeparator = false;
236 :
237 0 : for (ToolBarItem* item : items) {
238 0 : if (item->type() == ToolBarItemType::SEPARATOR) {
239 0 : if (isPreviousSeparator) {
240 0 : delete item;
241 0 : continue;
242 : }
243 :
244 0 : isPreviousSeparator = true;
245 : } else {
246 0 : isPreviousSeparator = false;
247 : }
248 :
249 0 : m_items << item;
250 : }
251 :
252 0 : updateShortcutsAll();
253 :
254 0 : endResetModel();
255 :
256 0 : emit itemsChanged();
257 0 : }
258 :
259 0 : void AbstractToolBarModel::clear()
260 : {
261 0 : setItems(ToolBarItemList());
262 0 : }
263 :
264 0 : int AbstractToolBarModel::itemIndex(const QString& itemId) const
265 : {
266 0 : for (int i = 0; i < m_items.size(); ++i) {
267 0 : if (m_items[i]->id() == itemId) {
268 0 : return i;
269 : }
270 : }
271 :
272 0 : return INVALID_ITEM_INDEX;
273 : }
274 :
275 0 : ToolBarItem& AbstractToolBarModel::item(int index)
276 : {
277 0 : ToolBarItem& item = *m_items[index];
278 0 : if (item.isValid()) {
279 0 : return item;
280 : }
281 :
282 0 : static ToolBarItem dummy;
283 0 : return dummy;
284 : }
285 :
286 0 : ToolBarItem& AbstractToolBarModel::findItem(const QString& itemId)
287 : {
288 0 : return item(m_items, itemId);
289 : }
290 :
291 0 : ToolBarItem* AbstractToolBarModel::findItemPtr(const QString& itemId)
292 : {
293 0 : for (ToolBarItem* toolBarItem : std::as_const(m_items)) {
294 0 : if (toolBarItem->id() == itemId) {
295 0 : return toolBarItem;
296 : }
297 : }
298 :
299 0 : return nullptr;
300 : }
301 :
302 0 : ToolBarItem* AbstractToolBarModel::makeSeparator()
303 : {
304 0 : return new ToolBarItem(ToolBarItemType::SEPARATOR, this);
305 : }
306 :
307 0 : void AbstractToolBarModel::setItem(int index, ToolBarItem* item)
308 : {
309 0 : if (!isIndexValid(index)) {
310 0 : return;
311 : }
312 :
313 0 : m_items[index] = item;
314 :
315 0 : QModelIndex modelIndex = this->index(index);
316 0 : emit dataChanged(modelIndex, modelIndex);
317 : }
318 :
319 0 : ToolBarItem& AbstractToolBarModel::item(const ToolBarItemList& items, const QString& itemId) const
320 : {
321 0 : for (ToolBarItem* toolBarItem : items) {
322 0 : if (toolBarItem->id() == itemId) {
323 0 : return *toolBarItem;
324 : }
325 : }
326 :
327 0 : static ToolBarItem dummy;
328 0 : return dummy;
329 : }
330 :
331 : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
332 0 : ToolBarItem* AbstractToolBarModel::makeItem(const ActionCode& actionCode, const TranslatableString& title)
333 : {
334 0 : const UiAction& action = uiActionsRegister()->action(actionCode);
335 0 : if (!action.isValid()) {
336 0 : LOGW() << "not found action: " << actionCode;
337 0 : return nullptr;
338 : }
339 :
340 0 : ToolBarItem* item = new ToolBarItem(action, ToolBarItemType::ACTION, this);
341 0 : item->setState(uiActionsRegister()->actionState(actionCode));
342 :
343 0 : if (!title.isEmpty()) {
344 0 : item->setTitle(title);
345 : }
346 :
347 0 : return item;
348 : }
349 :
350 0 : ToolBarItem* AbstractToolBarModel::makeMenuItem(const TranslatableString& title, const ActionCodeList& subitemsActionCodesList,
351 : const QString& menuId, bool enabled)
352 : {
353 0 : ToolBarItem* item = new ToolBarItem(this);
354 0 : item->setId(menuId);
355 0 : item->setTitle(title);
356 0 : item->setEnabled(enabled);
357 :
358 0 : MenuItemList subitems;
359 0 : for (const ActionCode& subitemActionCode: subitemsActionCodesList) {
360 0 : const UiAction& action = uiActionsRegister()->action(subitemActionCode);
361 0 : if (!action.isValid()) {
362 0 : LOGW() << "not found action: " << subitemActionCode;
363 0 : continue;
364 : }
365 :
366 0 : MenuItem* subitem = new MenuItem(action, this);
367 0 : subitem->setState(uiActionsRegister()->actionState(subitemActionCode));
368 :
369 0 : updateShortcuts(subitem);
370 :
371 0 : subitems << subitem;
372 : }
373 0 : item->setMenuItems(subitems);
374 :
375 0 : return item;
376 0 : }
377 :
378 0 : ToolBarItem& AbstractToolBarModel::item(const ToolBarItemList& items, const ActionCode& actionCode) const
379 : {
380 0 : for (ToolBarItem* toolBarItem : items) {
381 0 : if (!toolBarItem) {
382 0 : continue;
383 : }
384 :
385 0 : if (toolBarItem->actionCode() == actionCode) {
386 0 : return *toolBarItem;
387 : }
388 : }
389 :
390 0 : static ToolBarItem dummy;
391 0 : return dummy;
392 : }
393 :
394 0 : ToolBarItem& AbstractToolBarModel::findItem(const ActionCode& actionCode)
395 : {
396 0 : return item(m_items, actionCode);
397 : }
398 :
399 0 : ToolBarItem* AbstractToolBarModel::findItemPtr(const actions::ActionCode& actionCode)
400 : {
401 0 : for (ToolBarItem* toolBarItem : std::as_const(m_items)) {
402 0 : if (toolBarItem->actionCode() == actionCode) {
403 0 : return toolBarItem;
404 : }
405 : }
406 :
407 0 : return nullptr;
408 : }
409 :
410 0 : void AbstractToolBarModel::onActionsStateChanges(const muse::actions::ActionCodeList& codes)
411 : {
412 0 : if (codes.empty()) {
413 0 : return;
414 : }
415 :
416 0 : for (const ActionCode& code : codes) {
417 0 : ToolBarItem& actionItem = findItem(code);
418 0 : if (actionItem.isValid()) {
419 0 : actionItem.setState(uiActionsRegister()->actionState(code));
420 : }
421 : }
422 : }
423 :
424 : #endif // MUSE_MODULE_ACTIONS_SUPPORT
425 :
426 0 : bool AbstractToolBarModel::isCompactMode() const
427 : {
428 0 : return m_isCompactMode;
429 : }
430 :
431 0 : void AbstractToolBarModel::setIsCompactMode(bool isCompactMode)
432 : {
433 0 : if (m_isCompactMode == isCompactMode) {
434 0 : return;
435 : }
436 :
437 0 : for (ToolBarItem* item : std::as_const(m_items)) {
438 0 : if (item) {
439 0 : item->setShowTitle(!isCompactMode);
440 : }
441 : }
442 :
443 0 : m_isCompactMode = isCompactMode;
444 0 : emit isCompactModeChanged();
445 : }
446 :
447 0 : void AbstractToolBarModel::updateShortcutsAll()
448 : {
449 0 : for (ToolBarItem* toolBarItem : std::as_const(m_items)) {
450 0 : if (!toolBarItem) {
451 0 : continue;
452 : }
453 :
454 0 : std::vector<std::string> shortcuts;
455 : #ifdef MUSE_MODULE_SHORTCUTS_V2
456 0 : shortcuts = commandShortcutsRegister()->shortcut(toolBarItem->command()).sequences;
457 : #else
458 : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
459 : shortcuts = shortcutsRegister()->shortcut(toolBarItem->actionCode()).sequences;
460 : #endif
461 : #endif
462 0 : toolBarItem->setShortcuts(shortcuts);
463 :
464 0 : for (MenuItem* menuItem : std::as_const(toolBarItem->menuItems())) {
465 0 : if (!menuItem) {
466 0 : continue;
467 : }
468 :
469 0 : updateShortcuts(menuItem);
470 : }
471 0 : }
472 0 : }
473 :
474 0 : void AbstractToolBarModel::updateShortcuts(MenuItem* menuItem)
475 : {
476 0 : std::vector<std::string> shortcuts;
477 : #ifdef MUSE_MODULE_SHORTCUTS_V2
478 0 : shortcuts = commandShortcutsRegister()->shortcut(menuItem->command()).sequences;
479 : #else
480 : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
481 : shortcuts = shortcutsRegister()->shortcut(menuItem->actionCode()).sequences;
482 : #endif
483 : #endif
484 0 : menuItem->setShortcuts(shortcuts);
485 :
486 0 : for (MenuItem* subItem : menuItem->subitems()) {
487 0 : if (!subItem) {
488 0 : continue;
489 : }
490 :
491 0 : updateShortcuts(subItem);
492 0 : }
493 0 : }
|