From e04ff5e0c16e475009cf0ec6daa182d201bcf513 Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Tue, 16 Jun 2026 14:40:28 +0200 Subject: [PATCH 1/2] added commands state --- framework/rcommand/CMakeLists.txt | 7 +- framework/rcommand/commandtypes.h | 21 ++++ framework/rcommand/icommandsregister.h | 11 +- framework/rcommand/icommandsstate.h | 43 ++++++++ ...lecommands.h => imodulecommandsregister.h} | 12 ++- framework/rcommand/imodulecommandsstate.h | 45 ++++++++ .../rcommand/internal/commandsregister.cpp | 35 +++++- .../rcommand/internal/commandsregister.h | 12 ++- framework/rcommand/internal/commandsstate.cpp | 102 ++++++++++++++++++ framework/rcommand/internal/commandsstate.h | 49 +++++++++ 10 files changed, 322 insertions(+), 15 deletions(-) create mode 100644 framework/rcommand/icommandsstate.h rename framework/rcommand/{imodulecommands.h => imodulecommandsregister.h} (74%) create mode 100644 framework/rcommand/imodulecommandsstate.h create mode 100644 framework/rcommand/internal/commandsstate.cpp create mode 100644 framework/rcommand/internal/commandsstate.h diff --git a/framework/rcommand/CMakeLists.txt b/framework/rcommand/CMakeLists.txt index dfb5d399f3..02791d626f 100644 --- a/framework/rcommand/CMakeLists.txt +++ b/framework/rcommand/CMakeLists.txt @@ -24,7 +24,10 @@ target_sources(muse_rcommand PRIVATE rcommandmodule.cpp rcommandmodule.h icommanddispatcher.h - imodulecommands.h + icommandsregister.h + imodulecommandsregister.h + icommandsstate.h + imodulecommandsstate.h commandable.h commandtypes.h @@ -32,6 +35,8 @@ target_sources(muse_rcommand PRIVATE internal/commanddispatcher.h internal/commandsregister.cpp internal/commandsregister.h + internal/commandsstate.cpp + internal/commandsstate.h ) #if (MUSE_MODULE_RCOMMAND_TESTS) diff --git a/framework/rcommand/commandtypes.h b/framework/rcommand/commandtypes.h index 57b5cf68b4..38635625eb 100644 --- a/framework/rcommand/commandtypes.h +++ b/framework/rcommand/commandtypes.h @@ -89,6 +89,27 @@ struct CommandInfo Decoration decoration; }; +struct CommandState { + bool enabled = false; + bool checked = false; + + CommandState() = default; + CommandState(bool enabled, bool checked) + : enabled(enabled), checked(checked) {} + CommandState(bool enabled) + : enabled(enabled), checked(false) {} + + bool operator==(const CommandState& other) const + { + return enabled == other.enabled && checked == other.checked; + } + + bool operator!=(const CommandState& other) const + { + return !this->operator==(other); + } +}; + // Call using CallId = uint64_t; diff --git a/framework/rcommand/icommandsregister.h b/framework/rcommand/icommandsregister.h index 4340d5df0e..adbb20f7eb 100644 --- a/framework/rcommand/icommandsregister.h +++ b/framework/rcommand/icommandsregister.h @@ -20,7 +20,9 @@ #pragma once #include "modularity/imoduleinterface.h" -#include "imodulecommands.h" + +#include "imodulecommandsregister.h" +#include "commandtypes.h" namespace muse::rcommand { class ICommandsRegister : MODULE_GLOBAL_INTERFACE @@ -29,9 +31,12 @@ class ICommandsRegister : MODULE_GLOBAL_INTERFACE public: virtual ~ICommandsRegister() = default; - virtual void reg(const IModuleCommandsPtr& module) = 0; - virtual void unreg(const IModuleCommandsPtr& module) = 0; + virtual void reg(const IModuleCommandsRegisterPtr& module) = 0; + virtual void unreg(const IModuleCommandsRegisterPtr& module) = 0; + virtual IModuleCommandsRegisterPtr moduleRegister(const std::string& moduleName) const = 0; virtual std::vector commandList() const = 0; + + virtual const std::string& commandModuleName(const Command& command) const = 0; }; } diff --git a/framework/rcommand/icommandsstate.h b/framework/rcommand/icommandsstate.h new file mode 100644 index 0000000000..43949a3154 --- /dev/null +++ b/framework/rcommand/icommandsstate.h @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore/Audacity CLA applies + * + * Copyright (C) MuseScore/Audacity and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "modularity/imoduleinterface.h" + +#include "global/async/channel.h" + +#include "imodulecommandsstate.h" +#include "commandtypes.h" + +namespace muse::rcommand { +class ICommandsState : MODULE_CONTEXT_INTERFACE +{ + INTERFACE_ID(ICommandsState); + +public: + virtual ~ICommandsState() = default; + + virtual void reg(const IModuleCommandsStatePtr& module) = 0; + virtual void unreg(const IModuleCommandsStatePtr& module) = 0; + + virtual CommandState commandState(const Command& command) const = 0; + virtual async::Channel commandStateChanged() const = 0; +}; +} diff --git a/framework/rcommand/imodulecommands.h b/framework/rcommand/imodulecommandsregister.h similarity index 74% rename from framework/rcommand/imodulecommands.h rename to framework/rcommand/imodulecommandsregister.h index f3a61991d4..3303bad300 100644 --- a/framework/rcommand/imodulecommands.h +++ b/framework/rcommand/imodulecommandsregister.h @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -#pragma once + #pragma once #include #include @@ -26,15 +26,17 @@ #include "commandtypes.h" namespace muse::rcommand { -class IModuleCommands +class IModuleCommandsRegister { public: - virtual ~IModuleCommands() = default; + virtual ~IModuleCommandsRegister() = default; virtual std::string moduleName() const = 0; - virtual const std::vector& commandInfos() const = 0; + + virtual const std::vector& commandList() const = 0; + virtual const std::vector& commandInfoList() const = 0; }; -using IModuleCommandsPtr = std::shared_ptr; +using IModuleCommandsRegisterPtr = std::shared_ptr; } diff --git a/framework/rcommand/imodulecommandsstate.h b/framework/rcommand/imodulecommandsstate.h new file mode 100644 index 0000000000..21f3e02ee2 --- /dev/null +++ b/framework/rcommand/imodulecommandsstate.h @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore/Audacity CLA applies + * + * Copyright (C) MuseScore/Audacity and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#include "global/async/channel.h" + +#include "commandtypes.h" + +namespace muse::rcommand { +class IModuleCommandsState +{ +public: + virtual ~IModuleCommandsState() = default; + + virtual std::string moduleName() const = 0; + + virtual void init() = 0; + virtual void deinit() = 0; + + virtual CommandState commandState(const Command& command) const = 0; + virtual async::Channel commandStateChanged() const = 0; +}; + +using IModuleCommandsStatePtr = std::shared_ptr; +} diff --git a/framework/rcommand/internal/commandsregister.cpp b/framework/rcommand/internal/commandsregister.cpp index 1c040d4390..a5e5543ce1 100644 --- a/framework/rcommand/internal/commandsregister.cpp +++ b/framework/rcommand/internal/commandsregister.cpp @@ -24,7 +24,7 @@ using namespace muse; using namespace muse::rcommand; -void CommandsRegister::reg(const IModuleCommandsPtr& module) +void CommandsRegister::reg(const IModuleCommandsRegisterPtr& module) { IF_ASSERT_FAILED(module) { return; @@ -41,9 +41,13 @@ void CommandsRegister::reg(const IModuleCommandsPtr& module) } m_modules[moduleName] = module; + + for (const auto& info : module->commandInfoList()) { + m_commandModuleNames[info.command] = moduleName; + } } -void CommandsRegister::unreg(const IModuleCommandsPtr& module) +void CommandsRegister::unreg(const IModuleCommandsRegisterPtr& module) { IF_ASSERT_FAILED(module) { return; @@ -55,14 +59,39 @@ void CommandsRegister::unreg(const IModuleCommandsPtr& module) } m_modules.erase(moduleName); + + for (const auto& info : module->commandInfoList()) { + m_commandModuleNames.erase(info.command); + } +} + +IModuleCommandsRegisterPtr CommandsRegister::moduleRegister(const std::string& moduleName) const +{ + auto it = m_modules.find(moduleName); + if (it != m_modules.end()) { + return it->second; + } + + return nullptr; } std::vector CommandsRegister::commandList() const { std::vector commands; for (const auto& module : m_modules) { - const auto& infos = module.second->commandInfos(); + const auto& infos = module.second->commandInfoList(); commands.insert(commands.end(), infos.begin(), infos.end()); } return commands; } + +const std::string& CommandsRegister::commandModuleName(const Command& command) const +{ + auto it = m_commandModuleNames.find(command); + if (it != m_commandModuleNames.end()) { + return it->second; + } + + static const std::string empty; + return empty; +} diff --git a/framework/rcommand/internal/commandsregister.h b/framework/rcommand/internal/commandsregister.h index 10665b8855..8a3f77a450 100644 --- a/framework/rcommand/internal/commandsregister.h +++ b/framework/rcommand/internal/commandsregister.h @@ -29,11 +29,17 @@ class CommandsRegister : public ICommandsRegister public: CommandsRegister() = default; - void reg(const IModuleCommandsPtr& module) override; - void unreg(const IModuleCommandsPtr& module) override; + void reg(const IModuleCommandsRegisterPtr& module) override; + void unreg(const IModuleCommandsRegisterPtr& module) override; + IModuleCommandsRegisterPtr moduleRegister(const std::string& moduleName) const override; + std::vector commandList() const override; + const std::string& commandModuleName(const Command& command) const override; + private: - std::map m_modules; + std::map m_modules; + + std::map m_commandModuleNames; }; } diff --git a/framework/rcommand/internal/commandsstate.cpp b/framework/rcommand/internal/commandsstate.cpp new file mode 100644 index 0000000000..1f2df8876a --- /dev/null +++ b/framework/rcommand/internal/commandsstate.cpp @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore/Audacity CLA applies + * + * Copyright (C) 2026 MuseScore/Audacity and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "commandsstate.h" + +using namespace muse; +using namespace muse::rcommand; + +void CommandsState::reg(const IModuleCommandsStatePtr& module) +{ + IF_ASSERT_FAILED(module) { + return; + } + + const std::string& moduleName = module->moduleName(); + IF_ASSERT_FAILED(!moduleName.empty()) { + return; + } + + IF_ASSERT_FAILED(m_modules.find(moduleName) == m_modules.end()) { + LOGW() << "module already registered: " << moduleName; + return; + } + + m_modules[moduleName] = module; + + module->commandStateChanged().onReceive(this, [this](const Command& command, const CommandState& state) { + m_cache[command] = state; + m_commandStateChanged.send(command, state); + }); + + module->init(); +} + +void CommandsState::unreg(const IModuleCommandsStatePtr& module) +{ + IF_ASSERT_FAILED(module) { + return; + } + + const std::string& moduleName = module->moduleName(); + IF_ASSERT_FAILED(!moduleName.empty()) { + return; + } + + module->commandStateChanged().disconnect(this); + module->deinit(); + + m_modules.erase(moduleName); +} + +async::Channel CommandsState::commandStateChanged() const +{ + return m_commandStateChanged; +} + +CommandState CommandsState::commandState(const Command& command) const +{ + { + auto it = m_cache.find(command); + if (it != m_cache.end()) { + return it->second; + } + } + + const std::string& moduleName = commandsRegister()->commandModuleName(command); + IF_ASSERT_FAILED(!moduleName.empty()) { + return CommandState(); + } + + auto mit = m_modules.find(moduleName); + IF_ASSERT_FAILED(mit != m_modules.end()) { + return CommandState(); + } + + const IModuleCommandsStatePtr& module = mit->second; + IF_ASSERT_FAILED(module) { + return CommandState(); + } + + CommandState state = module->commandState(command); + + m_cache[command] = state; + + return state; +} diff --git a/framework/rcommand/internal/commandsstate.h b/framework/rcommand/internal/commandsstate.h new file mode 100644 index 0000000000..6a3a1bf9ed --- /dev/null +++ b/framework/rcommand/internal/commandsstate.h @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore/Audacity CLA applies + * + * Copyright (C) 2026 MuseScore/Audacity and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +#include "../icommandsstate.h" +#include "global/async/asyncable.h" +#include "global/modularity/ioc.h" +#include "../icommandsregister.h" + +namespace muse::rcommand { +class CommandsState : public ICommandsState, public async::Asyncable +{ + GlobalInject commandsRegister; + +public: + CommandsState() = default; + + void reg(const IModuleCommandsStatePtr& module) override; + void unreg(const IModuleCommandsStatePtr& module) override; + + CommandState commandState(const Command& command) const override; + async::Channel commandStateChanged() const override; + +private: + + std::map m_modules; + async::Channel m_commandStateChanged; + + mutable std::map m_cache; +}; +} From 80e75be6a182d498f3217d5f2846c86f6d11f3fb Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Wed, 17 Jun 2026 15:33:28 +0200 Subject: [PATCH 2/2] added commands diagnostic --- .../internal/diagnosticsactions.cpp | 6 + .../internal/diagnosticsactionscontroller.cpp | 2 + framework/rcommand/CMakeLists.txt | 11 +- framework/rcommand/icommanddispatcher.h | 5 + .../rcommand/qml/Muse/RCommand/CMakeLists.txt | 34 ++++ .../qml/Muse/RCommand/CommandListDialog.qml | 35 ++++ .../qml/Muse/RCommand/CommandListPanel.qml | 109 +++++++++++++ .../qml/Muse/RCommand/commandlistmodel.cpp | 152 ++++++++++++++++++ .../qml/Muse/RCommand/commandlistmodel.h | 79 +++++++++ framework/rcommand/rcommandmodule.cpp | 12 ++ framework/rcommand/rcommandmodule.h | 1 + 11 files changed, 441 insertions(+), 5 deletions(-) create mode 100644 framework/rcommand/qml/Muse/RCommand/CMakeLists.txt create mode 100644 framework/rcommand/qml/Muse/RCommand/CommandListDialog.qml create mode 100644 framework/rcommand/qml/Muse/RCommand/CommandListPanel.qml create mode 100644 framework/rcommand/qml/Muse/RCommand/commandlistmodel.cpp create mode 100644 framework/rcommand/qml/Muse/RCommand/commandlistmodel.h diff --git a/framework/diagnostics/internal/diagnosticsactions.cpp b/framework/diagnostics/internal/diagnosticsactions.cpp index f05aa44ec9..cd7b94e03f 100644 --- a/framework/diagnostics/internal/diagnosticsactions.cpp +++ b/framework/diagnostics/internal/diagnosticsactions.cpp @@ -96,6 +96,12 @@ const UiActionList DiagnosticsActions::m_actions = { TranslatableString("action", "Show &actions list"), TranslatableString("action", "Show actions list") ), + UiAction("diagnostic-show-rcommands", + muse::ui::UiCtxAny, + muse::shortcuts::CTX_DISABLED, + TranslatableString("action", "Show &rcommands list"), + TranslatableString("action", "Show rcommands list") + ), UiAction("action://diagnostic/actions/query", muse::ui::UiCtxAny, muse::shortcuts::CTX_DISABLED, diff --git a/framework/diagnostics/internal/diagnosticsactionscontroller.cpp b/framework/diagnostics/internal/diagnosticsactionscontroller.cpp index b4c4279a62..2c41a3e81c 100644 --- a/framework/diagnostics/internal/diagnosticsactionscontroller.cpp +++ b/framework/diagnostics/internal/diagnosticsactionscontroller.cpp @@ -41,6 +41,7 @@ static const muse::UriQuery ENGRAVING_ELEMENTS_URI("musescore://diagnostics/engr static const muse::UriQuery ENGRAVING_UNDOSTACK_URI("musescore://diagnostics/engraving/undostack?modal=false&floating=true"); static const muse::UriQuery ENGRAVING_STYLE_URI("musescore://diagnostics/engraving/style?modal=false&floating=true"); static const muse::UriQuery ACTIONS_LIST_URI("muse://diagnostics/actions/list?modal=false&floating=true"); +static const muse::UriQuery RCOMMAND_LIST_URI("muse://diagnostics/rcommand/list?modal=false&floating=true"); void DiagnosticsActionsController::init() { @@ -55,6 +56,7 @@ void DiagnosticsActionsController::init() dispatcher()->reg(this, "diagnostic-show-engraving-style", [this]() { openUri(ENGRAVING_STYLE_URI, false); }); dispatcher()->reg(this, "diagnostic-save-diagnostic-files", this, &DiagnosticsActionsController::saveDiagnosticFiles); dispatcher()->reg(this, "diagnostic-show-actions", [this]() { openUri(ACTIONS_LIST_URI); }); + dispatcher()->reg(this, "diagnostic-show-rcommands", [this]() { openUri(RCOMMAND_LIST_URI); }); dispatcher()->reg(this, ActionQuery("action://diagnostic/actions/query"), this, &DiagnosticsActionsController::onActionQuery); dispatcher()->reg(this, ActionQuery("action://diagnostic/actions/query_params1"), this, &DiagnosticsActionsController::onActionQuery); diff --git a/framework/rcommand/CMakeLists.txt b/framework/rcommand/CMakeLists.txt index 02791d626f..cd731cc636 100644 --- a/framework/rcommand/CMakeLists.txt +++ b/framework/rcommand/CMakeLists.txt @@ -1,10 +1,7 @@ # SPDX-License-Identifier: GPL-3.0-only -# MuseScore-Studio-CLA-applies +# MuseScore/Audacity CLA applies # -# MuseScore Studio -# Music Composition & Notation -# -# Copyright (C) 2026 MuseScore Limited and others +# Copyright (C) MuseScore/Audacity and others # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 as @@ -39,6 +36,10 @@ target_sources(muse_rcommand PRIVATE internal/commandsstate.h ) +if (MUSE_MODULE_RCOMMAND_QML) + add_subdirectory(qml/Muse/RCommand) +endif() + #if (MUSE_MODULE_RCOMMAND_TESTS) # add_subdirectory(tests) #endif() diff --git a/framework/rcommand/icommanddispatcher.h b/framework/rcommand/icommanddispatcher.h index 28ac162d2f..894e98e566 100644 --- a/framework/rcommand/icommanddispatcher.h +++ b/framework/rcommand/icommanddispatcher.h @@ -43,5 +43,10 @@ class ICommandDispatcher : MODULE_CONTEXT_INTERFACE { return dispatch(make_request(query)); } + + async::Promise dispatch(const Command& query) + { + return dispatch(CommandQuery(query)); + } }; } diff --git a/framework/rcommand/qml/Muse/RCommand/CMakeLists.txt b/framework/rcommand/qml/Muse/RCommand/CMakeLists.txt new file mode 100644 index 0000000000..d9dcaa8f22 --- /dev/null +++ b/framework/rcommand/qml/Muse/RCommand/CMakeLists.txt @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-3.0-only +# MuseScore/Audacity CLA applies +# +# Copyright (C) MuseScore/Audacity and others +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +muse_create_qml_module(muse_rcommand_qml ALIAS muse::rcommand_qml FOR muse_rcommand) + +qt_add_qml_module(muse_rcommand_qml + URI Muse.RCommand + VERSION 1.0 + SOURCES + commandlistmodel.cpp + commandlistmodel.h + QML_FILES + CommandListDialog.qml + CommandListPanel.qml + IMPORTS + TARGET muse_ui_qml + TARGET muse_uicomponents_qml +) + +fixup_qml_module_dependencies(muse_rcommand_qml) diff --git a/framework/rcommand/qml/Muse/RCommand/CommandListDialog.qml b/framework/rcommand/qml/Muse/RCommand/CommandListDialog.qml new file mode 100644 index 0000000000..7c4efee419 --- /dev/null +++ b/framework/rcommand/qml/Muse/RCommand/CommandListDialog.qml @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore/Audacity CLA applies + * + * Copyright (C) MuseScore/Audacity and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import Muse.Ui +import Muse.UiComponents + +StyledDialogView { + id: root + + title: "Diagnostic: RCommands" + + contentHeight: 800 + contentWidth: 600 + resizable: true + + CommandListPanel { + anchors.fill: parent + } +} diff --git a/framework/rcommand/qml/Muse/RCommand/CommandListPanel.qml b/framework/rcommand/qml/Muse/RCommand/CommandListPanel.qml new file mode 100644 index 0000000000..f129334688 --- /dev/null +++ b/framework/rcommand/qml/Muse/RCommand/CommandListPanel.qml @@ -0,0 +1,109 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore/Audacity CLA applies + * + * Copyright (C) MuseScore/Audacity and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import Muse.Ui +import Muse.UiComponents +import Muse.RCommand + +Rectangle { + + id: root + + objectName: "DiagnosticCommandsPanel" + color: ui.theme.backgroundPrimaryColor + + CommandListModel { + id: commandsModel + } + + Item { + id: toolPanel + anchors.left: parent.left + anchors.right: parent.right + height: 48 + + TextInputField { + id: inputItem + anchors.left: parent.left + anchors.right: btnRow.left + anchors.verticalCenter: parent.verticalCenter + anchors.margins: 16 + clearTextButtonVisible: true + onTextChanged: function(newTextValue) { + commandsModel.find(newTextValue) + } + } + + Row { + id: btnRow + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: 16 + width: childrenRect.width + spacing: 8 + + FlatButton { + anchors.verticalCenter: parent.verticalCenter + text: "Print" + onClicked: commandsModel.print() + } + } + } + + ListView { + anchors.top: toolPanel.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + clip: true + model: commandsModel + // section.property: "groupRole" + // section.delegate: Rectangle { + // width: parent.width + // height: 24 + // color: ui.theme.backgroundSecondaryColor + // StyledTextLabel { + // anchors.fill: parent + // anchors.margins: 2 + // horizontalAlignment: Qt.AlignLeft + // text: section + // } + // } + delegate: ListItemBlank { + + anchors.left: parent ? parent.left : undefined + anchors.right: parent ? parent.right : undefined + height: 64 + + StyledTextLabel { + anchors.fill: parent + anchors.leftMargin: 16 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: "Consolas" + text: itemData + } + + onClicked: function() { + commandsModel.dispatch(model.index) + } + } + } +} \ No newline at end of file diff --git a/framework/rcommand/qml/Muse/RCommand/commandlistmodel.cpp b/framework/rcommand/qml/Muse/RCommand/commandlistmodel.cpp new file mode 100644 index 0000000000..4ec6b30682 --- /dev/null +++ b/framework/rcommand/qml/Muse/RCommand/commandlistmodel.cpp @@ -0,0 +1,152 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore/Audacity CLA applies + * + * Copyright (C) MuseScore/Audacity and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "commandlistmodel.h" + + #include "log.h" +#include + +using namespace muse::rcommand; + +CommandListModel::CommandListModel(QObject* parent) + : QAbstractListModel(parent), Contextable(muse::iocCtxForQmlObject(this)) +{ +} + +void CommandListModel::classBegin() +{ + const std::vector& infos = commandsRegister()->commandList(); + + beginResetModel(); + + for (const CommandInfo& info : infos) { + Item item; + item.info = info; + item.state = commandsState()->commandState(item.info.command); + + formatItem(item); + + m_allItems.append(item); + } + + std::sort(m_allItems.begin(), m_allItems.end(), [](const Item& f, const Item& s) { + return f.info.command < s.info.command; + }); + + m_items = m_allItems; + + endResetModel(); + + commandsState()->commandStateChanged().onReceive(this, [this](const Command& command, const CommandState& state) { + updateState(command, state); + }); +} + +void CommandListModel::formatItem(Item& item) +{ + item.formatted = QString("%1\ntitle: %2\ndescription: %3\nenabled: %4, checked: %5") + .arg(item.info.command.toString()) + .arg(item.info.title.qTranslatedWithoutMnemonic()) + .arg(item.info.description.qTranslated()) + .arg(item.state.enabled) + .arg(item.state.checked); +} + +void CommandListModel::updateState(const Command& command, const CommandState& state) +{ + // update state in m_allItems + + auto ait = std::find_if(m_allItems.begin(), m_allItems.end(), [&command](const Item& item) { + return item.info.command == command; + }); + + if (ait != m_allItems.end()) { + ait->state = state; + formatItem(*ait); + } + + // update state in m_items (current visible items) + auto cit = std::find_if(m_items.begin(), m_items.end(), [&command](const Item& item) { + return item.info.command == command; + }); + + if (cit != m_items.end()) { + int idx = (int)std::distance(m_items.begin(), cit); + *cit = *ait; + emit dataChanged(index(idx), index(idx), { rItemData }); + } +} + +QVariant CommandListModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + switch (role) { + case rItemData: return QString::number(index.row()) + ". " + m_items.at(index.row()).formatted; + } + + return QVariant(); +} + +int CommandListModel::rowCount(const QModelIndex&) const +{ + return m_items.count(); +} + +QHash CommandListModel::roleNames() const +{ + return { { rItemData, "itemData" } }; +} + +void CommandListModel::dispatch(int index) +{ + const Item& item = m_items.at(index); + commandDispatcher()->dispatch(item.info.command); + LOGD() << "dispatch: " << item.info.command.toString(); +} + +void CommandListModel::find(const QString& str) +{ + beginResetModel(); + + m_searchText = str; + + if (m_searchText.isEmpty()) { + m_items = m_allItems; + } else { + m_items.clear(); + for (const Item& item : m_allItems) { + if (item.formatted.contains(m_searchText, Qt::CaseInsensitive)) { + m_items.append(item); + } + } + } + + //LOGD() << "searchText: " << m_searchText << ", items: " << m_items.count(); + + endResetModel(); +} + +void CommandListModel::print() +{ + for (const Item& item : m_items) { + std::cout << item.formatted.toStdString() << std::endl; + } +} diff --git a/framework/rcommand/qml/Muse/RCommand/commandlistmodel.h b/framework/rcommand/qml/Muse/RCommand/commandlistmodel.h new file mode 100644 index 0000000000..5ebeb7dd14 --- /dev/null +++ b/framework/rcommand/qml/Muse/RCommand/commandlistmodel.h @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore/Audacity CLA applies + * + * Copyright (C) MuseScore/Audacity and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#include "global/async/asyncable.h" + +#include "commandtypes.h" +#include "modularity/ioc.h" +#include "icommandsregister.h" +#include "icommandsstate.h" +#include "icommanddispatcher.h" + +namespace muse::rcommand { +class CommandListModel : public QAbstractListModel, public QQmlParserStatus, public Contextable, public async::Asyncable +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + QML_ELEMENT + + GlobalInject commandsRegister; + ContextInject commandsState = { this }; + ContextInject commandDispatcher = { this }; + +public: + explicit CommandListModel(QObject* parent = nullptr); + + Q_INVOKABLE void dispatch(int index); + Q_INVOKABLE void find(const QString& str); + Q_INVOKABLE void print(); + + QVariant data(const QModelIndex& index, int role) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QHash roleNames() const override; + +private: + enum Roles { + rItemData = Qt::UserRole + 1 + }; + + struct Item { + CommandInfo info; + CommandState state; + + QString formatted; + }; + + void classBegin() override; + void componentComplete() override {} + + void formatItem(Item& item); + void updateState(const Command& command, const CommandState& state); + + QList m_allItems; + QList m_items; + + QString m_searchText; +}; +} diff --git a/framework/rcommand/rcommandmodule.cpp b/framework/rcommand/rcommandmodule.cpp index fb068a858e..a0d062b330 100644 --- a/framework/rcommand/rcommandmodule.cpp +++ b/framework/rcommand/rcommandmodule.cpp @@ -19,7 +19,10 @@ #include "rcommandmodule.h" +#include "interactive/iinteractiveuriregister.h" + #include "internal/commandsregister.h" +#include "internal/commandsstate.h" #include "internal/commanddispatcher.h" using namespace muse; @@ -37,6 +40,14 @@ void RCommandModule::registerExports() globalIoc()->registerExport(mname, new CommandsRegister()); } +void RCommandModule::resolveImports() +{ + auto ir = globalIoc()->resolve(mname); + if (ir) { + ir->registerQmlUri(Uri("muse://diagnostics/rcommand/list"), "Muse.RCommand", "CommandListDialog"); + } +} + modularity::IContextSetup* RCommandModule::newContext(const muse::modularity::ContextPtr& ctx) const { return new RCommandContext(ctx); @@ -44,5 +55,6 @@ modularity::IContextSetup* RCommandModule::newContext(const muse::modularity::Co void RCommandContext::registerExports() { + ioc()->registerExport(mname, new CommandsState()); ioc()->registerExport(mname, new CommandDispatcher()); } diff --git a/framework/rcommand/rcommandmodule.h b/framework/rcommand/rcommandmodule.h index 71386c2147..7a1a4d7d1e 100644 --- a/framework/rcommand/rcommandmodule.h +++ b/framework/rcommand/rcommandmodule.h @@ -28,6 +28,7 @@ class RCommandModule : public modularity::IModuleSetup std::string moduleName() const override; void registerExports() override; + void resolveImports() override; modularity::IContextSetup* newContext(const muse::modularity::ContextPtr& ctx) const override; };