refactor: collapse speed-dial action source abstraction

Only one action source ever existed, so the
IActionSource interface and ScriptActionSource
are gone. ActionRegistry now subscribes to the
plugin loader and enumerates actions directly
in init() - no polymorphism for one impl.

Replace the opaque FNV-hash SpeedDialActionId
with a readable composed id of the form
prefix:title:source_key. Split AppAction's
single source field into source_key (stable
identity, e.g. plugin_key) and source_name
(display), so identity and display no longer
share one field.
This commit is contained in:
Andrew
2026-07-14 18:37:02 +08:00
parent 8ba478913f
commit c816e48b78
12 changed files with 229 additions and 389 deletions

View File

@@ -1,7 +1,6 @@
#include <catch2/catch_test_macros.hpp>
#include "slic3r/GUI/ActionRegistry.hpp"
#include "slic3r/GUI/IActionSource.hpp"
#include <memory>
#include <string>
@@ -10,45 +9,42 @@
using Slic3r::GUI::AppAction;
using Slic3r::GUI::AppActionRunResult;
using Slic3r::GUI::ActionRegistry;
using Slic3r::GUI::IActionSource;
namespace {
class RecordingActionSource final : public IActionSource
{
public:
explicit RecordingActionSource(bool& started) : m_started(started) {}
void start(ActionRegistry&) override { m_started = true; }
private:
bool& m_started;
};
// AppAction is abstract; this minimal concrete action lets the tests exercise
// its constructor-set definition without involving a plugin runner.
// AppAction is abstract; this minimal concrete action lets the tests exercise its
// constructor-composed identity without involving a plugin runner.
class TestAppAction final : public AppAction
{
public:
TestAppAction() : AppAction("action-id", "Action title", "Action source") {}
TestAppAction() : AppAction("test", "Action title", "src-key", "Action source") {}
AppActionRunResult run() const override { return {}; }
};
} // namespace
TEST_CASE("AppAction composes a stable id from prefix:title:source_key", "[speeddial][actions]")
{
CHECK(AppAction::compose_id("test", "Action title", "src-key") == "test:Action title:src-key");
// source_key (not the display name) carries identity, so it is the third field.
CHECK(AppAction::compose_id("script", "Do Thing", "pack.py") == "script:Do Thing:pack.py");
}
TEST_CASE("AppAction definitions are immutable after construction", "[speeddial][actions]")
{
using StringAccessor = const std::string& (AppAction::*)() const;
STATIC_CHECK(std::is_same_v<decltype(&AppAction::id), StringAccessor>);
STATIC_CHECK(std::is_same_v<decltype(&AppAction::title), StringAccessor>);
STATIC_CHECK(std::is_same_v<decltype(&AppAction::source), StringAccessor>);
STATIC_CHECK(std::is_same_v<decltype(&AppAction::source_key), StringAccessor>);
STATIC_CHECK(std::is_same_v<decltype(&AppAction::source_name), StringAccessor>);
const TestAppAction action;
CHECK(action.id() == "action-id");
CHECK(action.id() == "test:Action title:src-key");
CHECK(action.title() == "Action title");
CHECK(action.source() == "Action source");
CHECK(action.source_key() == "src-key");
CHECK(action.source_name() == "Action source");
}
TEST_CASE("ActionRegistry takes exclusive ownership of published actions", "[speeddial][actions]")
@@ -57,14 +53,3 @@ TEST_CASE("ActionRegistry takes exclusive ownership of published actions", "[spe
STATIC_CHECK(std::is_same_v<decltype(&ActionRegistry::upsert), ExpectedUpsert>);
}
TEST_CASE("ActionRegistry starts registered action sources", "[speeddial][actions]")
{
bool started = false;
ActionRegistry registry;
registry.add_source(std::make_unique<RecordingActionSource>(started));
registry.init();
CHECK(started);
}