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).
Menu¶
Ś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;
};
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:
title— menu najwyższego poziomu; odpowiada pierwszemu segmentowi TwojejmenuPathrozdzielanej/.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ątrztitle(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.