Przejdź do treści

Polecenia i menu

Polecenia

ICommandRegistry to jedyny kanał wykonywania każdej akcji dostępnej dla użytkownika — pozycji menu, przycisków paska narzędzi, skrótów i (w przyszłości) skryptów Pythona.

namespace platform_id {              // keys for platformShortcuts
    inline constexpr std::string_view kWindows = "windows";
    inline constexpr std::string_view kMacOS   = "macos";
    inline constexpr std::string_view kLinux   = "linux";
}

namespace command_scope {            // values for CommandDescriptor::scope
    inline constexpr std::string_view kApplication  = "application";
    inline constexpr std::string_view kPluginWindow = "pluginWindow";
}

struct CommandDescriptor {
    std::string id;          // "helloworld.sayHi"
    std::string title;       // "Say Hi"
    std::string shortcut;    // portable default: "Ctrl+Alt+H"
    std::string category;    // "Hello"
    std::string scope;       // "" / "application" | "pluginWindow"
    std::map<std::string, std::string> platformShortcuts; // optional per-platform
};

using CommandHandler = std::function<void()>;

class ICommandRegistry {
public:
    virtual CommandToken registerCommand(CommandDescriptor, CommandHandler) = 0;
    virtual void         invoke(std::string_view commandId) = 0;
    virtual bool         exists(std::string_view commandId) const = 0;
    // The user-resolved sequence in effect right now ("" = unbound),
    // NOT the declared default. See "User remapping" below.
    virtual std::string  effectiveShortcut(std::string_view commandId) const = 0;
};

registerCommand zwraca CommandToken (RAII). Jego zniszczenie usuwa polecenie, jego pozycje menu i jego skrót. Trzymaj każdy token jako pole składowe, aby cała wtyczka sprzątała się automatycznie w shutdown().

Skróty per platforma

shortcut to pojedynczy przenośny akord klawiszowy. Qt samo mapuje modyfikator "Ctrl" na natywny dla platformy — ⌘ Command na macOS — więc "Ctrl+F" jest tam ⌘F bez dodatkowej pracy. Więcej potrzeba tylko wtedy, gdy to automatyczne mapowanie trafiłoby w akord zarezerwowany przez system (np. "Ctrl+Q" → ⌘Q = Zakończ na macOS).

W takich przypadkach ustaw platformShortcuts, kluczowane przez platform_id. Host zwraca wpis pasujący do bieżącej platformy albo wraca do shortcut, gdy platformy brakuje (lub jest zmapowana na pusty string). Bez #ifdef, bez typów Qt — same dane — więc powiązanie pozostaje niezależne od backendu, a nowa platforma to jeden nowy klucz:

MTE::plugin::CommandDescriptor desc;
desc.id       = "comment.toggle";
desc.title    = "Toggle Single Line Comment";
desc.shortcut = "Ctrl+Q";                       // Windows / Linux → Ctrl+Q
desc.platformShortcuts = {                       // macOS → ⌃Q (the portable
    { std::string(MTE::plugin::platform_id::kMacOS), "Meta+Q" } }; // "Meta" = ⌃
desc.category = "Comment";

Zarezerwowane akordy ⌘ na macOS, z którymi warto zestawić pojedynczy Ctrl+<key>: ⌘Q (Zakończ), ⌘W (Zamknij), ⌘H (Ukryj), ⌘M (Minimalizuj), ⌘, (Preferencje), ⌘Spacja (Spotlight).

Zmiana skrótów przez użytkownika

Zadeklarowane shortcut / platformShortcuts to wartości domyślne. Użytkownik może przemapować lub odpiąć każde polecenie w rdzennym mapperze skrótów (Preferencje ▸ Skróty), a konflikty rozstrzyga host: duplikaty w tym samym zakresie nigdy nie współistnieją — przegrany jest zawieszany na czas sesji i raportowany. Nic z tego nie wymaga kodu wtyczki dla zwykłych poleceń powiązanych z menu: host jest właścicielem ich QAction i przepina skrót na żywo.

scope — polecenia w oknach należących do wtyczki

Polecenia, których akord żyje w widżecie należącym do wtyczki (pasek narzędzi okna narzędziowego, a nie główne menu), deklarują scope = command_scope::kPluginWindow. Host wtedy nigdy sam nie wiąże akordu; jedynie wymienia polecenie w mapperze i rozstrzyga wybór użytkownika. Wtyczka sama stosuje powiązanie do własnych widżetów:

  • odpytaj ctx.commands().effectiveShortcut(id) podczas budowania widżetów, oraz
  • zasubskrybuj events::CommandShortcutChanged { commandId, sequence } i zastosuj ponownie, gdy zmieni się któreś z jej id ("" = teraz niezwiązane).

Domena konfliktów w v1 jest globalna — akord pluginWindow nie może kolidować z akordem application. Działający przykład: pasek narzędzi Log View w Log Analyzerze (loganalyzer.logView.nextError F8 / .prevError Shift+F8 / .goToTime Ctrl+G).

Ścieżki menu używają / jako separatora; host tworzy pośrednie podmenu na żądanie:

class IMenuRegistry {
public:
    virtual MenuToken addItem(std::string menuPath, std::string commandId) = 0;
    virtual MenuToken addSeparator(std::string menuPath) = 0;
    virtual void      setItemChecked(TokenId itemToken, bool checked) = 0;
};
m_menu = ctx.menus().addItem("File/Export/As HTML...", "myplugin.exportHtml");

MenuToken jest RAII tak samo jak CommandToken.

Pozycje menu z zaznaczeniem

Pozycja menu reprezentująca trwały stan włącz/wyłącz może nosić widoczny znacznik zaznaczenia. setItemChecked przyjmuje TokenId pozycji — użyj nieniszczącego akcesora id() tokenu (release() oddałoby własność):

m_toggle = ctx.menus().addItem("View/Auto-open preview", "myplugin.toggle");

// Seed the mark from the persisted setting so the menu matches reality
// before the user first opens it:
ctx.menus().setItemChecked(m_toggle.id(),
                           ctx.settings().getBool("autoOpen", true));

// Inside the command handler, after flipping the setting:
ctx.menus().setItemChecked(m_toggle.id(), nowEnabled);

Pierwsze wywołanie zamienia pozycję w zaznaczalną; kolejne tylko przełączają stan. Nieznany token to ciche no-op, więc opóźniony callback odpalony po shutdownie jest nieszkodliwy.

Umiejscowienie menu (plugin.json)

To, gdzie znajdzie się menu najwyższego poziomu wtyczki, jest sterowane danymi przez tablicę menus w plugin.json — host nigdy nie hardkoduje nazw menu wtyczek:

"menus": [
  { "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
]
  • title — menu najwyższego poziomu; odpowiada pierwszemu segmentowi Twojej menuPath rozdzielanej /.
  • barPriority — pozycja pozioma na pasku menu (niższa = bardziej w lewo). Menu rdzenia są stałe, rozstawione co 100: Plik 100, Edycja 200, Widok 300, Language 400, Compare 500, Window 600 (About 1000, zawsze skrajnie po prawej); wtyczki wypełniają luki (Szukaj 250, Encoding 350, Log Analyzer 540, Wtyczki 570).
  • itemPriority — pionowa ranga bloku tej wtyczki wewnątrz title (niższa = wyżej; domyślnie 1000). Pozycje w obrębie bloku jednej wtyczki zachowują kolejność dodawania w kodzie.

Note

Ponieważ plugin.json jest osadzany jako metadane wtyczki Qt, zmiany w nim zaczynają obowiązywać dopiero po przebudowaniu.