Opdrachten en menu's¶
Opdrachten¶
ICommandRegistry is de enige trechter voor de uitvoering van elke actie die een
gebruiker kan aanroepen — menu-items, werkbalkknoppen, sneltoetsen en (later)
Python-scripts.
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
bool requiresEditor = false; // needs an active document
bool requiresWritableEditor = false; // ...and changes its text
};
// Convenience wrappers, so an existing aggregate literal only has to be
// wrapped rather than rewritten:
CommandDescriptor needsEditor(CommandDescriptor); // requiresEditor
CommandDescriptor needsWritableEditor(CommandDescriptor); // + writable
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 levert een CommandToken (RAII) op. Hem vernietigen verwijdert
de opdracht, haar menu-items en haar sneltoets. Houd elk token als lid vast, zodat
de hele plug-in in shutdown() automatisch afgebroken wordt.
Eisen aan de editor¶
requiresEditor / requiresWritableEditor vertellen de host wat de opdracht van
het actieve document nodig heeft, zodat hij de opdracht grijs kan maken in plaats
van de gebruiker iets te laten aanroepen dat stilzwijgend niets doet.
| Vlag | Zet hem wanneer de opdracht… | Hij wordt grijs bij… |
|---|---|---|
requiresEditor |
de buffer, de selectie of de cursor leest, of markeringen plaatst (Bladwijzers, Markeren, Zoeken, Ga naar definitie) | een tabblad dat helemaal geen document herbergt — een tabblad met een vergelijking of een logweergave, waar activeDocument() 0 oplevert |
requiresWritableEditor |
de tekst wijzigt (Commentaar, Hoofdlettergebruik omzetten, Kolomeditor, Inspringing vergroten, Regels…, Document opmaken, Symboolnaam wijzigen) | het bovenstaande en een alleen-lezen document: een bestand dat met tail -f gevolgd wordt, een sessie met --viewer of --readonly |
De schrijfvlag impliceert de andere, dus een beschrijving mag alleen de sterkste
van de twee zetten. Beide staan standaard op false — «altijd beschikbaar», het
juiste antwoord voor opdrachten die een venster, een map of een paneel openen.
Het grijs maken bereikt het menu-item, het opdrachtenpalet en de sneltoets. Het kan
alleen beschikbaarheid wegnemen: een item dat uw plug-in zelf via
IMenuRegistry::setItemEnabled uitgeschakeld heeft blijft uitgeschakeld, wat het
actieve document ook is.
Verpak de beschrijving op de plek van de aanroep:
using MTE::plugin::needsWritableEditor;
m_toggleComment = ctx.commands().registerCommand(
needsWritableEditor({ "comment.toggle", "Toggle Comment",
"Ctrl+Q", "Comment" }),
[this] { onToggleComment(); });
In Python geeft u de benoemde argumenten mee:
@ctx.commands.command(id="wc.upper", title="Upper Case",
requires_writable_editor=True)
def _upper():
...
Sneltoetsen per platform¶
shortcut is één draagbare toetscombinatie. Qt koppelt de modificatietoets
"Ctrl" al aan de inheemse van het platform — ⌘ Command op macOS — dus
"Ctrl+F" is daar zonder extra werk ⌘F. U hebt alleen meer nodig wanneer die
automatische koppeling op een combinatie zou uitkomen die het besturingssysteem
zich voorbehoudt (bijvoorbeeld "Ctrl+Q" → ⌘Q = Afsluiten op macOS).
Stel voor die gevallen platformShortcuts in, met platform_id als sleutel. De
host levert het item op dat bij het draaiende platform past, of valt terug op
shortcut wanneer een platform ontbreekt (of aan een lege tekenreeks gekoppeld
is). Geen #ifdef, geen Qt-typen — alleen gegevens — zodat de koppeling
backend-onafhankelijk blijft, en een nieuw platform één nieuwe sleutel is:
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";
Gereserveerde ⌘-combinaties van macOS om één Ctrl+<key> tegen te houden: ⌘Q
(Afsluiten), ⌘W (Sluiten), ⌘H (Verbergen), ⌘M (Minimaliseren), ⌘, (Voorkeuren),
⌘Spatie (Spotlight). Vermijd bij een combinatie Ctrl+Alt+<key> (⌥⌘ op macOS)
ook ⌥⌘H (Andere verbergen), ⌥⌘M (Alles minimaliseren), ⌥⌘D (Dock) en ⌥⌘Esc
(Geforceerd stoppen).
Nog twee regels houden sneltoetsen uit de weg van het typen:
- Nooit een kale
Alt+<letter>als draagbare combinatie. Op Windows is dat de ruimte van de menumnemonieken, en op macOS stelt ⌥+letter accenttekens samen (⌥U is de dode toets voor de trema, ⌥C typt ç), zodat de sneltoets die zou opslokken — geef zo'n opdracht een overschrijving{"macos", "Meta+Alt+<key>"}(⌃⌥). Ctrl+Alt+<key>is AltGr+<key>op Windows. Nationale indelingen typen daar letters (Pools: ś ł ą ć ę ń ó ź ż €; Duits: @ € µ). De host houdt ze typbaar — een druk op de rechter Alt die een afdrukbaar teken oplevert wordt als tekst afgeleverd, een druk op de linker Ctrl+Alt voert de opdracht uit — dus zulke combinaties zijn toegestaan, maar geef de voorkeur aan letters die op de gangbare indelingen niets typen (B D F G H I J K P R T V W Y) en gebruik nooitCtrl+Alt+<digit>.
Opnieuw toewijzen door de gebruiker¶
De verklaarde shortcut / platformShortcuts zijn standaarden. De gebruiker
kan elke opdracht opnieuw toewijzen of loskoppelen in de sneltoetstoewijzing van de
kern (Voorkeuren ▸ Sneltoetsen), en conflicten worden aan de kant van de host
opgelost: duplicaten binnen hetzelfde bereik bestaan nooit naast elkaar — de
verliezer wordt voor de sessie geschorst en gemeld. Niets hiervan vergt code in de
plug-in voor gewone menu-gebonden opdrachten: de host bezit hun QAction en wijst
die live opnieuw toe.
scope — opdrachten in vensters die een plug-in bezit¶
Opdrachten waarvan de toetscombinatie in een widget woont die de plug-in bezit
(de werkbalk van een gereedschapsvenster in plaats van het hoofdmenu) verklaren
scope = command_scope::kPluginWindow. De host koppelt de combinatie dan nooit
zelf; hij somt de opdracht alleen in de toewijzing op en lost de keuze van de
gebruiker op. De plug-in past de koppeling op haar eigen widgets toe:
- vraag
ctx.commands().effectiveShortcut(id)op terwijl u de widgets bouwt, en - schrijf u in op
events::CommandShortcutChanged { commandId, sequence }en pas opnieuw toe wanneer een van haar id's verandert (""= nu losgekoppeld).
Het conflictdomein is in v1 globaal — een combinatie in pluginWindow weigert met
een combinatie van de toepassing te botsen. Werkend voorbeeld: de werkbalk van de
Logboekweergave van de Logboekanalyse (loganalyzer.logView.nextError F8 /
.prevError Shift+F8 / .goToTime Ctrl+G).
Menu's¶
Menupaden gebruiken / als scheiding; de host maakt tussenliggende submenu's op
verzoek aan:
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;
virtual void setItemEnabled(TokenId itemToken, bool enabled) = 0;
};
MenuToken is RAII, net als CommandToken.
Aanvinkbare menu-items¶
Een menu-item dat een blijvende aan/uit-staat vertegenwoordigt kan een zichtbaar
vinkje dragen. setItemChecked neemt de TokenId van het item — gebruik de
niet-vernietigende toegang id() van het token (release() zou het eigendom
opgeven):
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);
De eerste aanroep maakt het item aanvinkbaar; latere aanroepen kantelen alleen de staat. Een onbekend token is een stille niet-bewerking, dus een vertraagde callback die na het afsluiten scherpstelt is onschadelijk.
Menu-items grijs maken¶
Een opdracht die een voorwaarde nodig heeft — een map om te sluiten, een gesloten
tabblad om te herstellen — hoort haar menu-item grijs te maken zolang die
voorwaarde ontbreekt, in plaats van een item aan te bieden dat stilletjes niets
doet. setItemEnabled neemt de TokenId van het item, net als setItemChecked:
m_closeItem = ctx.menus().addItem("File/Close Folder", "myplugin.closeFolder");
// Nothing to close yet:
ctx.menus().setItemEnabled(m_closeItem.id(), false);
// ... and from every place that changes the state the command needs:
ctx.menus().setItemEnabled(m_closeItem.id(), !folder.empty());
Een uitgeschakeld item blijft zichtbaar maar kan niet in gang gezet worden, niet
met een klik en niet met zijn sneltoets. De aanroep is idempotent — dezelfde waarde
herhalen wordt verwacht, zodat u de vorige staat nooit hoeft te onthouden — en een
onbekend token is een stille niet-bewerking, zoals hierboven. Items beginnen
ingeschakeld, dus een item dat bij het starten niet beschikbaar is heeft één
aanroep vlak na addItem nodig.
Plaatsing in het menu (plugin.json)¶
Waar het menu van het hoogste niveau van een plug-in zit is gegevensgestuurd
via de array menus in plugin.json — de host legt de namen van plug-inmenu's
nooit in code vast:
title— het menu van het hoogste niveau; komt overeen met het eerste door/gescheiden segment van uwmenuPath. Geef het met&een toetsenbordmnemoniek ("Lo&g Analyzer") en gebruik dezelfde spelling in elkmenuPath: op Windows opent Alt+letter het menu, en elke balktitel heeft een eigen letter nodig, in elke taal — kies er een die vrij is onder Bestand, Bewerken, Zoeken, Tekencodering, Vergelijken, Beeld, Taal, Plug-ins, Venster, Over en Help, en laat elke vertaling haar eigen letter kiezen. Het vergelijken van menu's negeert de markering.barPriority— horizontale positie op de menubalk (lager = verder naar links). De kernmenu's staan vast, met 100 ertussen: Bestand 100, Bewerken 200, Beeld 300, Taal 400, Vergelijken 500, Venster 600 (Over 1000, altijd helemaal rechts); plug-ins vullen de gaten (Zoeken 250, Tekencodering 350, Logboekanalyse 540, Plug-ins 570).itemPriority— de verticale rang van het blok van deze plug-in binnentitle(lager = hoger; standaard 1000). Items binnen het blok van één plug-in behouden de volgorde waarin ze in de code worden toegevoegd.
Note
Omdat plugin.json als metagegevens van een Qt-plug-in ingebed is, worden
wijzigingen eraan pas na een herbouw van kracht.