From e04ff5e0c16e475009cf0ec6daa182d201bcf513 Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Tue, 16 Jun 2026 14:40:28 +0200 Subject: [PATCH] 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; +}; +}