LCOV - code coverage report
Current view: top level - muse/framework/ui/internal - uiactionsregister.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 189 0
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 "uiactionsregister.h"
      23              : 
      24              : #include "global/async/async.h"
      25              : #include "global/stringutils.h"
      26              : 
      27              : #include "muse_framework_config.h"
      28              : 
      29              : #include "log.h"
      30              : 
      31              : using namespace muse;
      32              : using namespace muse::ui;
      33              : using namespace muse::actions;
      34              : 
      35            0 : void UiActionsRegister::init()
      36              : {
      37              :     // init state
      38            0 :     updateCheckedAll();
      39            0 :     updateEnabledAll();
      40              : 
      41              :     // listen
      42            0 :     if (uicontextResolver()) {
      43            0 :         uicontextResolver()->currentUiContextChanged().onNotify(this, [this]() {
      44            0 :             requestUpdateEnabledAll();
      45            0 :         });
      46              :     }
      47              : 
      48            0 :     if (commandsState()) {
      49            0 :         commandsState()->commandStateChanged().onReceive(this, [this](const rcommand::Command& command, const rcommand::CommandState&) {
      50            0 :             m_actionStateChanged.send({ command.toString() });
      51            0 :         });
      52              :     }
      53            0 : }
      54              : 
      55            0 : void UiActionsRegister::reg(const IUiActionsModulePtr& module)
      56              : {
      57            0 :     const UiActionList& alist = module->actionsList();
      58            0 :     for (const UiAction& action : alist) {
      59            0 :         Info info;
      60            0 :         info.module = module;
      61            0 :         info.action = action;
      62            0 :         m_actions[action.code] = std::move(info);
      63            0 :     }
      64              : 
      65            0 :     module->actionsChanged().onReceive(this, [this](const UiActionList& actions) {
      66            0 :         updateActions(actions);
      67            0 :     }, async::Asyncable::Mode::SetReplace); // see IUiActionsModule::actionsChanged()
      68              : 
      69            0 :     module->actionEnabledChanged().onReceive(this, [this](const ActionCodeList& codes) {
      70            0 :         updateEnabled(codes);
      71            0 :         m_actionStateChanged.send(codes);
      72            0 :     });
      73              : 
      74            0 :     module->actionCheckedChanged().onReceive(this, [this](const ActionCodeList& codes) {
      75            0 :         updateChecked(codes);
      76            0 :         m_actionStateChanged.send(codes);
      77            0 :     });
      78            0 : }
      79              : 
      80            0 : void UiActionsRegister::unreg(const IUiActionsModulePtr& module)
      81              : {
      82            0 :     for (auto it = m_actions.begin(); it != m_actions.end();) {
      83            0 :         if (it->second.module == module) {
      84            0 :             it = m_actions.erase(it);
      85              :         } else {
      86            0 :             ++it;
      87              :         }
      88              :     }
      89              : 
      90            0 :     module->actionsChanged().disconnect(this);
      91            0 :     module->actionEnabledChanged().disconnect(this);
      92            0 :     module->actionCheckedChanged().disconnect(this);
      93            0 : }
      94              : 
      95            0 : UiActionsRegister::Info& UiActionsRegister::info(const ActionCode& code)
      96              : {
      97            0 :     auto it = m_actions.find(code);
      98            0 :     if (it != m_actions.end()) {
      99            0 :         return it->second;
     100              :     }
     101              : 
     102            0 :     static Info null;
     103            0 :     return null;
     104              : }
     105              : 
     106            0 : const UiActionsRegister::Info& UiActionsRegister::info(const ActionCode& code) const
     107              : {
     108            0 :     auto it = m_actions.find(code);
     109            0 :     if (it != m_actions.end()) {
     110            0 :         return it->second;
     111              :     }
     112              : 
     113              :     // is query?
     114            0 :     ActionQuery q(code);
     115            0 :     if (q.isValid()) {
     116            0 :         it = m_actions.find(q.uri().toString());
     117            0 :         if (it != m_actions.end()) {
     118            0 :             return it->second;
     119              :         }
     120              :     }
     121              : 
     122            0 :     static Info null;
     123            0 :     return null;
     124            0 : }
     125              : 
     126            0 : std::vector<UiAction> UiActionsRegister::actionList() const
     127              : {
     128            0 :     std::vector<UiAction> allActions;
     129              : 
     130            0 :     for (auto it = m_actions.begin(); it != m_actions.end(); it++) {
     131            0 :         allActions.push_back(it->second.action);
     132              :     }
     133              : 
     134            0 :     return allActions;
     135            0 : }
     136              : 
     137            0 : const UiAction& UiActionsRegister::action(const ActionCode& code) const
     138              : {
     139            0 :     if (muse::strings::startsWith(code, rcommand::COMMAND_SCHEME)) {
     140            0 :         auto it = m_commandActions.find(code);
     141            0 :         if (it != m_commandActions.end()) {
     142            0 :             return it->second;
     143              :         }
     144              : 
     145            0 :         const rcommand::CommandInfo& info = commandsRegister()->commandInfo(rcommand::Command(code));
     146            0 :         if (!info.isValid()) {
     147            0 :             LOGE() << "not valid command: " << code;
     148            0 :             static UiAction null;
     149            0 :             return null;
     150              :         }
     151              : 
     152            0 :         UiAction action;
     153            0 :         action.code = code;
     154            0 :         action.title = info.title;
     155            0 :         action.description = info.description;
     156            0 :         action.iconCode = info.decoration.iconCode;
     157            0 :         if (info.decoration.iconColor.isValid()) {
     158            0 :             action.iconColor = QString::fromStdString(info.decoration.iconColor.toString());
     159              :         }
     160            0 :         action.checkable = static_cast<Checkable>(info.decoration.checkable);
     161              : 
     162            0 :         it = m_commandActions.emplace(code, action).first;
     163            0 :         return it->second;
     164            0 :     }
     165              : 
     166            0 :     return info(code).action;
     167              : }
     168              : 
     169            0 : const actions::ActionCode& UiActionsRegister::parentActionCode(const actions::ActionCode& code) const
     170              : {
     171            0 :     for (auto it = m_actions.begin(); it != m_actions.end(); it++) {
     172            0 :         if (muse::contains(it->second.action.children, code)) {
     173            0 :             return it->second.action.code;
     174              :         }
     175              :     }
     176              : 
     177            0 :     static ActionCode null;
     178            0 :     return null;
     179              : }
     180              : 
     181            0 : async::Channel<UiActionList> UiActionsRegister::actionsChanged() const
     182              : {
     183            0 :     return m_actionsChanged;
     184              : }
     185              : 
     186            0 : UiActionState UiActionsRegister::actionState(const ActionCode& code) const
     187              : {
     188            0 :     if (muse::strings::startsWith(code, rcommand::COMMAND_SCHEME)) {
     189            0 :         const rcommand::CommandState& state = commandsState()->commandState(rcommand::Command(code));
     190            0 :         return UiActionState { state.enabled, state.checked };
     191              :     }
     192              : 
     193            0 :     const Info& inf = info(code);
     194            0 :     if (!inf.action.isValid()) {
     195            0 :         LOGE() << "not found action with code: " << code;
     196            0 :         return UiActionState::make_disabled();
     197              :     }
     198              : 
     199            0 :     return inf.state;
     200              : }
     201              : 
     202            0 : void UiActionsRegister::updateActions(const UiActionList& actions)
     203              : {
     204            0 :     ActionCodeList codes;
     205            0 :     for (const UiAction& act : actions) {
     206            0 :         Info& inf = info(act.code);
     207            0 :         IF_ASSERT_FAILED(inf.isValid()) {
     208            0 :             continue;
     209              :         }
     210              : 
     211            0 :         inf.action = act;
     212              : 
     213            0 :         codes.push_back(act.code);
     214              :     }
     215              : 
     216            0 :     m_actionsChanged.send(actions);
     217              : 
     218            0 :     updateEnabled(codes);
     219            0 :     updateChecked(codes);
     220            0 : }
     221              : 
     222            0 : void UiActionsRegister::doUpdateEnabled(Info& inf,
     223              :                                         const IUiContextResolverPtr& ctxResolver,
     224              :                                         const UiContext& currentCtx,
     225              :                                         ActionCodeList& changedList)
     226              : {
     227            0 :     bool oldEnabled = inf.state.enabled;
     228            0 :     inf.state.enabled = false;
     229              : 
     230            0 :     if (ctxResolver->match(currentCtx, inf.action.uiCtx)) {
     231            0 :         inf.state.enabled = inf.module->actionEnabled(inf.action);
     232              :     }
     233              : 
     234            0 :     if (oldEnabled != inf.state.enabled) {
     235            0 :         changedList.push_back(inf.action.code);
     236              :     }
     237            0 : }
     238              : 
     239            0 : void UiActionsRegister::updateEnabled(const ActionCodeList& codes)
     240              : {
     241            0 :     TRACEFUNC;
     242              : 
     243            0 :     if (!uicontextResolver()) {
     244            0 :         return;
     245              :     }
     246              : 
     247            0 :     ActionCodeList changedList;
     248            0 :     auto ctxResolver = uicontextResolver();
     249            0 :     const ui::UiContext& currentCtx = ctxResolver->currentUiContext();
     250            0 :     for (const ActionCode& code : codes) {
     251            0 :         Info& inf = info(code);
     252            0 :         if (!inf.isValid()) {
     253            0 :             continue;
     254              :         }
     255              : 
     256            0 :         doUpdateEnabled(inf, ctxResolver, currentCtx, changedList);
     257              :     }
     258              : 
     259            0 :     if (!changedList.empty()) {
     260            0 :         m_actionStateChanged.send(changedList);
     261              :     }
     262            0 : }
     263              : 
     264            0 : void UiActionsRegister::requestUpdateEnabledAll()
     265              : {
     266            0 :     if (m_isUpdateEnabledAllRequested) {
     267            0 :         return;
     268              :     }
     269              : 
     270            0 :     m_isUpdateEnabledAllRequested = true;
     271            0 :     muse::async::Async::call(this, [this]() {
     272            0 :         updateEnabledAll();
     273            0 :         m_isUpdateEnabledAllRequested = false;
     274            0 :     });
     275              : }
     276              : 
     277            0 : void UiActionsRegister::updateEnabledAll()
     278              : {
     279            0 :     TRACEFUNC;
     280              : 
     281            0 :     if (!uicontextResolver()) {
     282            0 :         return;
     283              :     }
     284              : 
     285            0 :     ActionCodeList changedList;
     286            0 :     auto ctxResolver = uicontextResolver();
     287            0 :     const ui::UiContext& currentCtx = ctxResolver->currentUiContext();
     288            0 :     LOGD() << "currentCtx: " << currentCtx.toString();
     289            0 :     for (auto it = m_actions.begin(); it != m_actions.end(); ++it) {
     290            0 :         Info& inf = it->second;
     291            0 :         doUpdateEnabled(inf, ctxResolver, currentCtx, changedList);
     292              :     }
     293              : 
     294            0 :     if (!changedList.empty()) {
     295            0 :         m_actionStateChanged.send(changedList);
     296              :     }
     297            0 : }
     298              : 
     299            0 : void UiActionsRegister::updateCheckedAll()
     300              : {
     301            0 :     TRACEFUNC;
     302              : 
     303            0 :     ActionCodeList changedList;
     304            0 :     for (auto it = m_actions.begin(); it != m_actions.end(); ++it) {
     305            0 :         Info& inf = it->second;
     306              : 
     307            0 :         bool oldChecked = inf.state.checked;
     308            0 :         inf.state.checked = inf.module->actionChecked(inf.action);
     309              : 
     310            0 :         if (oldChecked != inf.state.checked) {
     311            0 :             changedList.push_back(inf.action.code);
     312              :         }
     313              :     }
     314              : 
     315            0 :     if (!changedList.empty()) {
     316            0 :         m_actionStateChanged.send(changedList);
     317              :     }
     318            0 : }
     319              : 
     320            0 : void UiActionsRegister::updateChecked(const ActionCodeList& codes)
     321              : {
     322            0 :     TRACEFUNC;
     323              : 
     324            0 :     ActionCodeList changedList;
     325            0 :     for (const ActionCode& code : codes) {
     326            0 :         Info& inf = info(code);
     327            0 :         if (!inf.isValid()) {
     328            0 :             continue;
     329              :         }
     330              : 
     331            0 :         bool oldChecked = inf.state.checked;
     332            0 :         inf.state.checked = inf.module->actionChecked(inf.action);
     333              : 
     334            0 :         if (oldChecked != inf.state.checked) {
     335            0 :             changedList.push_back(inf.action.code);
     336              :         }
     337              :     }
     338              : 
     339            0 :     if (!changedList.empty()) {
     340            0 :         m_actionStateChanged.send(changedList);
     341              :     }
     342            0 : }
     343              : 
     344            0 : async::Channel<ActionCodeList> UiActionsRegister::actionStateChanged() const
     345              : {
     346            0 :     return m_actionStateChanged;
     347              : }
        

Generated by: LCOV version 2.0-1