LCOV - code coverage report
Current view: top level - muse/framework/uicomponents/qml/Muse/UiComponents - abstractmenumodel.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 11.8 % 296 35
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) 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 "abstractmenumodel.h"
      23              : 
      24              : #include "rcommand/commandtypes.h"
      25              : #include "types/translatablestring.h"
      26              : 
      27              : #include "log.h"
      28              : 
      29              : using namespace muse::uicomponents;
      30              : using namespace muse::ui;
      31              : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
      32              : using namespace muse::actions;
      33              : #endif
      34              : 
      35              : const int AbstractMenuModel::INVALID_ITEM_INDEX = -1;
      36              : 
      37            3 : AbstractMenuModel::AbstractMenuModel(QObject* parent)
      38            3 :     : QAbstractListModel(parent), muse::Contextable(muse::iocCtxForQmlObject(this))
      39              : {
      40            3 : }
      41              : 
      42            0 : QVariant AbstractMenuModel::data(const QModelIndex& index, int role) const
      43              : {
      44            0 :     int row = index.row();
      45              : 
      46            0 :     if (!isIndexValid(row)) {
      47            0 :         return QVariant();
      48              :     }
      49              : 
      50            0 :     MenuItem* item = m_items.at(row);
      51              : 
      52            0 :     switch (role) {
      53            0 :     case ItemRole: return QVariant::fromValue(item);
      54            0 :     case UserRole: return QVariant();
      55              :     }
      56              : 
      57            0 :     return QVariant();
      58              : }
      59              : 
      60            0 : bool AbstractMenuModel::isIndexValid(int index) const
      61              : {
      62            0 :     return index >= 0 && index < m_items.size();
      63              : }
      64              : 
      65            0 : int AbstractMenuModel::rowCount(const QModelIndex&) const
      66              : {
      67            0 :     return m_items.count();
      68              : }
      69              : 
      70            0 : QHash<int, QByteArray> AbstractMenuModel::roleNames() const
      71              : {
      72              :     static const QHash<int, QByteArray> roles {
      73            0 :         { ItemRole, "item" }
      74            0 :     };
      75              : 
      76            0 :     return roles;
      77            0 : }
      78              : 
      79            0 : void AbstractMenuModel::handleMenuItem(const QString& itemId)
      80              : {
      81            0 :     MenuItem& menuItem = findItem(itemId);
      82              : 
      83            0 :     std::string intent = menuItem.intent();
      84            0 :     if (muse::strings::startsWith(intent, "command://")) {
      85            0 :         auto params = menuItem.params();
      86            0 :         if (params.empty()) {
      87            0 :             dispatchCommand(rcommand::CommandQuery(intent));
      88              :         } else {
      89            0 :             dispatchCommand(rcommand::Command(intent), params);
      90              :         }
      91            0 :     } else {
      92              : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
      93            0 :         UriQuery query(intent);
      94            0 :         if (query.isValid()) {
      95            0 :             dispatchAction(query);
      96              :         } else {
      97            0 :             dispatchAction(actions::ActionCode(intent), menuItem.args());
      98              :         }
      99              : #else
     100              :         UNREACHABLE;
     101              : #endif
     102            0 :     }
     103            0 : }
     104              : 
     105              : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
     106            0 : void AbstractMenuModel::dispatchAction(const muse::actions::ActionCode& code, const muse::actions::ActionData& args)
     107              : {
     108            0 :     IF_ASSERT_FAILED(!muse::strings::startsWith(code, "command://")) {
     109            0 :         dispatchCommand(rcommand::Command(code));
     110            0 :         return;
     111              :     }
     112            0 :     dispatcher()->dispatch(code, args);
     113              : }
     114              : 
     115            0 : void AbstractMenuModel::dispatchAction(const muse::UriQuery& query)
     116              : {
     117            0 :     IF_ASSERT_FAILED(!muse::strings::startsWith(query.uri().scheme(), "command")) {
     118            0 :         dispatchCommand(rcommand::CommandQuery(query));
     119            0 :         return;
     120              :     }
     121            0 :     dispatcher()->dispatch(query);
     122              : }
     123              : 
     124              : #endif
     125              : 
     126            0 : void AbstractMenuModel::dispatchCommand(const muse::rcommand::Command& command, const muse::rcommand::Params& params)
     127              : {
     128            0 :     commandDispatcher()->dispatch(command, params);
     129            0 : }
     130              : 
     131            0 : void AbstractMenuModel::dispatchCommand(const muse::rcommand::CommandQuery& query)
     132              : {
     133            0 :     commandDispatcher()->dispatch(query);
     134            0 : }
     135              : 
     136            0 : QVariantMap AbstractMenuModel::get(int index)
     137              : {
     138            0 :     QVariantMap result;
     139              : 
     140            0 :     QHash<int, QByteArray> names = roleNames();
     141            0 :     QHashIterator<int, QByteArray> i(names);
     142            0 :     while (i.hasNext()) {
     143            0 :         i.next();
     144            0 :         QModelIndex idx = this->index(index, 0);
     145            0 :         QVariant data = idx.data(i.key());
     146            0 :         result[i.value()] = data;
     147            0 :     }
     148              : 
     149            0 :     return result;
     150            0 : }
     151              : 
     152            0 : void AbstractMenuModel::load()
     153              : {
     154            0 :     subscribeOnChanges();
     155            0 : }
     156              : 
     157            0 : QVariantList AbstractMenuModel::itemsProperty() const
     158              : {
     159            0 :     QVariantList items;
     160              : 
     161            0 :     for (MenuItem* item: m_items) {
     162            0 :         items << QVariant::fromValue(item);
     163              :     }
     164              : 
     165            0 :     return items;
     166            0 : }
     167              : 
     168            0 : const MenuItemList& AbstractMenuModel::items() const
     169              : {
     170            0 :     return m_items;
     171              : }
     172              : 
     173            3 : void AbstractMenuModel::setItems(const MenuItemList& items)
     174              : {
     175            4 :     TRACEFUNC;
     176              : 
     177            3 :     beginResetModel();
     178            3 :     m_items = items;
     179            3 :     updateShortcutsAll();
     180            3 :     endResetModel();
     181              : 
     182            3 :     emit itemsChanged();
     183            3 : }
     184              : 
     185            0 : void AbstractMenuModel::clear()
     186              : {
     187            0 :     setItems(MenuItemList());
     188            0 : }
     189              : 
     190            0 : int AbstractMenuModel::itemIndex(const QString& itemId) const
     191              : {
     192            0 :     for (int i = 0; i < m_items.size(); ++i) {
     193            0 :         if (m_items[i]->id() == itemId) {
     194            0 :             return i;
     195              :         }
     196              :     }
     197              : 
     198            0 :     return INVALID_ITEM_INDEX;
     199              : }
     200              : 
     201            0 : MenuItem& AbstractMenuModel::item(int index)
     202              : {
     203            0 :     MenuItem& item = *m_items[index];
     204            0 :     if (item.isValid()) {
     205            0 :         return item;
     206              :     }
     207              : 
     208            0 :     static MenuItem dummy;
     209            0 :     return dummy;
     210              : }
     211              : 
     212            0 : MenuItem& AbstractMenuModel::findItem(const QString& itemId)
     213              : {
     214            0 :     return item(m_items, itemId);
     215              : }
     216              : 
     217              : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
     218            0 : MenuItem& AbstractMenuModel::findItem(const ActionCode& actionCode)
     219              : {
     220            0 :     MenuItemList list = items(m_items, actionCode);
     221            0 :     if (list.empty()) {
     222            0 :         static MenuItem dummy;
     223            0 :         return dummy;
     224              :     }
     225              : 
     226            0 :     if (list.size() > 1) {
     227            0 :         LOGD() << "There is more than one item for " << actionCode << ", will return the first one found";
     228              :     }
     229              : 
     230            0 :     return *list.front();
     231            0 : }
     232              : 
     233            0 : MenuItemList AbstractMenuModel::findItems(const ActionCode& actionCode)
     234              : {
     235            0 :     return items(m_items, actionCode);
     236              : }
     237              : 
     238              : #endif
     239              : 
     240            0 : MenuItem& AbstractMenuModel::findItem(const muse::rcommand::Command& command)
     241              : {
     242            0 :     MenuItemList list = items(m_items, command);
     243            0 :     if (list.empty()) {
     244            0 :         static MenuItem dummy;
     245            0 :         return dummy;
     246              :     }
     247              : 
     248            0 :     if (list.size() > 1) {
     249            0 :         LOGD() << "There is more than one item for " << command << ", will return the first one found";
     250              :     }
     251              : 
     252            0 :     return *list.front();
     253            0 : }
     254              : 
     255            0 : MenuItemList AbstractMenuModel::findItems(const muse::rcommand::Command& command)
     256              : {
     257            0 :     return items(m_items, command);
     258              : }
     259              : 
     260            0 : MenuItem& AbstractMenuModel::findMenu(const QString& menuId)
     261              : {
     262            0 :     return menu(m_items, menuId);
     263              : }
     264              : 
     265            0 : MenuItem* AbstractMenuModel::makeMenu(const TranslatableString& title, const MenuItemList& items,
     266              :                                       const QString& menuId, bool enabled)
     267              : {
     268            0 :     MenuItem* item = new MenuItem(this);
     269            0 :     item->setId(menuId);
     270            0 :     item->setTitle(title);
     271            0 :     item->setSubitems(items);
     272            0 :     item->setEnabled(enabled);
     273              : 
     274            0 :     updateShortcuts(item);
     275              : 
     276            0 :     return item;
     277              : }
     278              : 
     279            6 : MenuItem* AbstractMenuModel::makeMenuItem(const muse::rcommand::Command& command,
     280              :                                           const muse::rcommand::Params& params,
     281              :                                           const TranslatableString& title)
     282              : {
     283            6 :     const rcommand::CommandInfo& info = commandsRegister()->commandInfo(command);
     284            6 :     if (!info.isValid()) {
     285            0 :         LOGW() << "not found command: " << command.toString();
     286            0 :         return nullptr;
     287              :     }
     288              : 
     289            6 :     MenuItem* item = new MenuItem(info, this);
     290            6 :     item->setCommand(command);
     291            6 :     item->setParams(params);
     292            6 :     item->setCommandState(commandsState()->commandState(command));
     293              : 
     294            6 :     if (!title.isEmpty()) {
     295            6 :         item->setTitle(title);
     296              :     }
     297              : 
     298            6 :     return item;
     299              : }
     300              : 
     301            6 : MenuItem* AbstractMenuModel::makeMenuItem(const rcommand::Command& command, const TranslatableString& title)
     302              : {
     303            6 :     return makeMenuItem(command, {}, title);
     304              : }
     305              : 
     306            0 : MenuItem* AbstractMenuModel::makeMenuItem(const rcommand::CommandQuery& query, const TranslatableString& title)
     307              : {
     308            0 :     return makeMenuItem(rcommand::Command(query.uri()), query.params(), title);
     309              : }
     310              : 
     311              : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
     312            0 : MenuItem* AbstractMenuModel::makeMenuItem(const ActionCode& actionCode, const TranslatableString& title)
     313              : {
     314            0 :     if (muse::strings::startsWith(actionCode, "command://")) {
     315            0 :         return makeMenuItem(rcommand::CommandQuery(actionCode), title);
     316              :     }
     317              : 
     318            0 :     const UiAction& action = uiActionsRegister()->action(actionCode);
     319            0 :     if (!action.isValid()) {
     320            0 :         LOGW() << "not found action: " << actionCode;
     321            0 :         return nullptr;
     322              :     }
     323              : 
     324            0 :     MenuItem* item = new MenuItem(action, this);
     325            0 :     item->setState(uiActionsRegister()->actionState(actionCode));
     326              : 
     327            0 :     if (!title.isEmpty()) {
     328            0 :         item->setTitle(title);
     329              :     }
     330              : 
     331            0 :     ActionQuery q(actionCode);
     332            0 :     if (q.isValid()) {
     333            0 :         item->setQuery(q);
     334              :     }
     335              : 
     336            0 :     return item;
     337            0 : }
     338              : 
     339              : #endif
     340              : 
     341            0 : MenuItem* AbstractMenuModel::makeSeparator()
     342              : {
     343            0 :     MenuItem* item = new MenuItem(this);
     344            0 :     item->setTitle({});
     345            0 :     return item;
     346              : }
     347              : 
     348            0 : void AbstractMenuModel::subscribeOnChanges()
     349              : {
     350            0 :     if (m_subscribedOnChanges) {
     351            0 :         return;
     352              :     }
     353              : 
     354            0 :     commandsState()->commandStateChanged().onReceive(this, [this](const rcommand::Command& command, const rcommand::CommandState& state) {
     355            0 :         onCommandStateChanged(command, state);
     356            0 :     });
     357              : 
     358              : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
     359            0 :     uiActionsRegister()->actionStateChanged().onReceive(this, [this](const ActionCodeList& codes) {
     360            0 :         onActionsStateChanges(codes);
     361            0 :     });
     362              : #endif
     363              : 
     364              : #ifdef MUSE_MODULE_SHORTCUTS_V2
     365            0 :     commandShortcutsRegister()->shortcutsChanged().onNotify(this, [this]() {
     366            0 :         updateShortcutsAll();
     367            0 :     });
     368              : #else
     369              :     shortcutsRegister()->shortcutsChanged().onNotify(this, [this]() {
     370              :         updateShortcutsAll();
     371              :     });
     372              : #endif
     373              : 
     374            0 :     m_subscribedOnChanges = true;
     375              : }
     376              : 
     377            0 : void AbstractMenuModel::onCommandStateChanged(const rcommand::Command& command, const rcommand::CommandState& state)
     378              : {
     379            0 :     updateState(m_items, command, state);
     380            0 : }
     381              : 
     382              : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
     383            0 : void AbstractMenuModel::onActionsStateChanges(const muse::actions::ActionCodeList& codes)
     384              : {
     385            0 :     TRACEFUNC;
     386              : 
     387            0 :     if (codes.empty()) {
     388            0 :         return;
     389              :     }
     390              : 
     391            0 :     std::map<actions::ActionCode, ui::UiActionState> states;
     392            0 :     updateState(m_items, codes, states);
     393            0 : }
     394              : 
     395              : #endif
     396              : 
     397            0 : void AbstractMenuModel::setItem(int index, MenuItem* item)
     398              : {
     399            0 :     if (!isIndexValid(index)) {
     400            0 :         return;
     401              :     }
     402              : 
     403            0 :     m_items[index] = item;
     404              : 
     405            0 :     QModelIndex modelIndex = this->index(index);
     406            0 :     emit dataChanged(modelIndex, modelIndex);
     407              : }
     408              : 
     409            0 : MenuItem& AbstractMenuModel::item(MenuItemList& items, const QString& itemId)
     410              : {
     411            0 :     for (MenuItem* menuItem : items) {
     412            0 :         if (!menuItem) {
     413            0 :             continue;
     414              :         }
     415            0 :         if (menuItem->id() == itemId) {
     416            0 :             return *menuItem;
     417              :         }
     418              : 
     419            0 :         auto subitems = menuItem->subitems();
     420            0 :         if (!subitems.empty()) {
     421            0 :             MenuItem& subitem = item(subitems, itemId);
     422            0 :             if (subitem.id() == itemId) {
     423            0 :                 return subitem;
     424              :             }
     425              :         }
     426            0 :     }
     427              : 
     428            0 :     static MenuItem dummy;
     429            0 :     return dummy;
     430              : }
     431              : 
     432              : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
     433            0 : MenuItemList AbstractMenuModel::items(const MenuItemList& items, const ActionCode& actionCode) const
     434              : {
     435            0 :     MenuItemList result;
     436              : 
     437            0 :     for (MenuItem* menuItem : items) {
     438            0 :         if (!menuItem) {
     439            0 :             continue;
     440              :         }
     441              : 
     442            0 :         if (menuItem->actionCode() == actionCode) {
     443            0 :             result.append(menuItem);
     444              :         }
     445              : 
     446            0 :         auto subitems = menuItem->subitems();
     447            0 :         if (!subitems.empty()) {
     448            0 :             MenuItemList list = this->items(subitems, actionCode);
     449            0 :             if (!list.empty()) {
     450            0 :                 result.append(list);
     451              :             }
     452            0 :         }
     453            0 :     }
     454              : 
     455            0 :     return result;
     456            0 : }
     457              : 
     458              : #endif
     459              : 
     460            0 : MenuItemList AbstractMenuModel::items(const MenuItemList& items, const muse::rcommand::Command& command) const
     461              : {
     462            0 :     MenuItemList result;
     463              : 
     464            0 :     for (MenuItem* menuItem : items) {
     465            0 :         if (!menuItem) {
     466            0 :             continue;
     467              :         }
     468              : 
     469            0 :         if (menuItem->command() == command) {
     470            0 :             result.append(menuItem);
     471              :         }
     472              : 
     473            0 :         auto subitems = menuItem->subitems();
     474            0 :         if (!subitems.empty()) {
     475            0 :             MenuItemList list = this->items(subitems, command);
     476            0 :             if (!list.empty()) {
     477            0 :                 result.append(list);
     478              :             }
     479            0 :         }
     480            0 :     }
     481              : 
     482            0 :     return result;
     483            0 : }
     484              : 
     485            0 : MenuItem& AbstractMenuModel::menu(MenuItemList& items, const QString& menuId)
     486              : {
     487            0 :     for (MenuItem* item : items) {
     488            0 :         if (!item) {
     489            0 :             continue;
     490              :         }
     491              : 
     492            0 :         if (item->id() == menuId) {
     493            0 :             return *item;
     494              :         }
     495              : 
     496            0 :         auto subitems = item->subitems();
     497            0 :         MenuItem& menuItem = menu(subitems, menuId);
     498            0 :         if (menuItem.isValid()) {
     499            0 :             return menuItem;
     500              :         }
     501            0 :     }
     502              : 
     503            0 :     static MenuItem dummy;
     504            0 :     return dummy;
     505              : }
     506              : 
     507            0 : void AbstractMenuModel::updateState(MenuItemList& items, const rcommand::Command& command, const rcommand::CommandState& state)
     508              : {
     509            0 :     for (MenuItem* menuItem : items) {
     510            0 :         if (!menuItem) {
     511            0 :             continue;
     512              :         }
     513              : 
     514            0 :         if (command == menuItem->command()) {
     515            0 :             menuItem->setCommandState(state);
     516              :         }
     517              : 
     518            0 :         MenuItemList subitems = menuItem->subitems();
     519            0 :         if (!subitems.empty()) {
     520            0 :             updateState(subitems, command, state);
     521              :         }
     522            0 :     }
     523            0 : }
     524              : 
     525              : #ifdef MUSE_MODULE_ACTIONS_SUPPORT
     526            0 : void AbstractMenuModel::updateState(MenuItemList& items, const actions::ActionCodeList& codes,
     527              :                                     std::map<actions::ActionCode, ui::UiActionState>& states)
     528              : {
     529            0 :     for (MenuItem* menuItem : items) {
     530            0 :         if (!menuItem) {
     531            0 :             continue;
     532              :         }
     533              : 
     534            0 :         ActionCode code = menuItem->actionCode();
     535            0 :         if (muse::contains(codes, code)) {
     536            0 :             if (!muse::contains(states, code)) {
     537            0 :                 states.insert({ code, uiActionsRegister()->actionState(code) });
     538              :             }
     539            0 :             menuItem->setState(states.at(code));
     540              :         }
     541              : 
     542            0 :         MenuItemList subitems = menuItem->subitems();
     543            0 :         if (!subitems.empty()) {
     544            0 :             updateState(subitems, codes, states);
     545              :         }
     546            0 :     }
     547            0 : }
     548              : 
     549              : #endif
     550              : 
     551            3 : void AbstractMenuModel::updateShortcutsAll()
     552              : {
     553            9 :     for (MenuItem* menuItem : m_items) {
     554            6 :         if (!menuItem) {
     555            0 :             continue;
     556              :         }
     557              : 
     558            6 :         updateShortcuts(menuItem);
     559              :     }
     560            3 : }
     561              : 
     562            6 : void AbstractMenuModel::updateShortcuts(MenuItem* item)
     563              : {
     564            6 :     std::vector<std::string> shortcuts;
     565              : 
     566              : #ifdef MUSE_MODULE_SHORTCUTS_V2
     567            6 :     shortcuts = commandShortcutsRegister()->shortcut(item->command()).sequences;
     568              : #else
     569              :     shortcuts = shortcutsRegister()->shortcut(item->actionCode()).sequences;
     570              : #endif
     571            6 :     item->setShortcuts(shortcuts);
     572              : 
     573            6 :     for (MenuItem* subItem : item->subitems()) {
     574            0 :         if (!subItem) {
     575            0 :             continue;
     576              :         }
     577              : 
     578            0 :         updateShortcuts(subItem);
     579            6 :     }
     580            6 : }
        

Generated by: LCOV version 2.0-1