Commands & Menüs¶
Commands¶
ICommandRegistry ist der einzige Ausführungstrichter für jede vom Benutzer
auslösbare Aktion — Menüeinträge, Toolbar-Buttons, Shortcuts und (später)
Python-Skripte.
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 liefert ein CommandToken (RAII) zurück. Wird es zerstört,
werden das Command, seine Menüeinträge und sein Shortcut entfernt. Halten Sie
jedes Token als Member, damit das gesamte Plugin in shutdown() automatisch
abgebaut wird.
Plattformspezifische Shortcuts¶
shortcut ist ein einzelnes portables Tastenkürzel. Qt bildet den Modifikator
"Ctrl" bereits auf den nativen der Plattform ab — ⌘ Command auf macOS —,
sodass "Ctrl+F" dort ohne Zusatzaufwand ⌘F ist. Mehr brauchen Sie nur, wenn
diese automatische Abbildung auf einem vom Betriebssystem reservierten Kürzel
landen würde (z. B. "Ctrl+Q" → ⌘Q = Beenden auf macOS).
Setzen Sie für diese Fälle platformShortcuts, mit platform_id als
Schlüssel. Der Host liefert den Eintrag der laufenden Plattform oder fällt auf
shortcut zurück, wenn eine Plattform fehlt (oder auf einen leeren String
abgebildet ist). Kein #ifdef, keine Qt-Typen — nur Daten —, sodass die
Bindung Backend-agnostisch bleibt und eine neue Plattform nur ein neuer
Schlüssel ist:
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";
Reservierte macOS-⌘-Kürzel, gegen die ein einzelnes Ctrl+<key> zu prüfen
ist: ⌘Q (Beenden), ⌘W (Schließen), ⌘H (Ausblenden), ⌘M (Im Dock ablegen),
⌘, (Einstellungen), ⌘Leertaste (Spotlight).
Benutzer-Remapping¶
Die deklarierten shortcut / platformShortcuts sind Standardwerte. Der
Benutzer kann jedes Command im Shortcut-Mapper des Kerns (Einstellungen ▸
Shortcuts) neu belegen oder die Belegung aufheben; Konflikte werden hostseitig
aufgelöst: Duplikate im selben Scope koexistieren nie — der Verlierer wird für
die Sitzung ausgesetzt und gemeldet. Nichts davon erfordert Plugin-Code für
gewöhnliche menügebundene Commands: Der Host besitzt deren QAction und
bindet sie live neu.
scope — Commands in Plugin-eigenen Fenstern¶
Commands, deren Tastenkürzel in einem Widget lebt, das dem Plugin gehört
(die Toolbar eines Werkzeugfensters statt des Hauptmenüs), deklarieren
scope = command_scope::kPluginWindow. Der Host bindet das Kürzel dann
niemals selbst; er listet das Command nur im Mapper auf und löst die Wahl des
Benutzers auf. Das Plugin wendet die Belegung auf seine eigenen Widgets an:
- Fragen Sie beim Aufbau der Widgets
ctx.commands().effectiveShortcut(id)ab, und - abonnieren Sie
events::CommandShortcutChanged { commandId, sequence }und wenden Sie die Belegung erneut an, wenn sich eine Ihrer Ids ändert (""= jetzt unbelegt).
Die Konfliktdomäne ist in v1 global — ein pluginWindow-Kürzel darf nicht mit
einem application-Kürzel kollidieren. Funktionierendes Beispiel: die
Log-View-Toolbar des Log Analyzers (loganalyzer.logView.nextError F8 /
.prevError Shift+F8 / .goToTime Ctrl+G).
Menüs¶
Menüpfade verwenden / als Trennzeichen; der Host erzeugt Zwischen-Untermenüs
bei Bedarf:
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 ist RAII wie CommandToken.
Ankreuzbare Menüeinträge¶
Ein Menüeintrag, der einen persistenten Ein/Aus-Zustand darstellt, kann ein
sichtbares Häkchen tragen. setItemChecked nimmt die TokenId des Eintrags —
verwenden Sie den nicht-destruktiven id()-Accessor des Tokens (release()
würde die Eigentümerschaft aufgeben):
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);
Der erste Aufruf macht den Eintrag ankreuzbar; nachfolgende Aufrufe schalten nur den Zustand um. Ein unbekanntes Token ist ein stilles No-op, sodass ein verzögerter Callback, der nach dem Shutdown feuert, harmlos ist.
Menüplatzierung (plugin.json)¶
Wo das Top-Level-Menü eines Plugins sitzt, ist datengesteuert über das
menus-Array in plugin.json — der Host codiert Plugin-Menünamen niemals
fest:
title— das Top-Level-Menü; entspricht dem ersten mit/getrennten Segment IhresmenuPath.barPriority— horizontale Position in der Menüleiste (niedriger = weiter links). Kernmenüs sind fest, im Abstand von 100: Datei 100, Bearbeiten 200, Ansicht 300, Language 400, Compare 500, Window 600 (About 1000, immer ganz rechts); Plugins füllen die Lücken (Suchen 250, Encoding 350, Log Analyzer 540, Plugins 570).itemPriority— vertikaler Rang des Blocks dieses Plugins innerhalb vontitle(niedriger = weiter oben; Standard 1000). Einträge innerhalb des Blocks eines Plugins behalten die Reihenfolge, in der sie im Code hinzugefügt werden.
Note
Da plugin.json als Qt-Plugin-Metadaten eingebettet ist, wirken sich
Änderungen daran erst nach einem Rebuild aus.