Kommandoer og menuer¶
Kommandoer¶
ICommandRegistry er den ene tragt, som hver handling, brugeren kan udløse, går
igennem — menupunkter, værktøjslinjeknapper, genvejstaster og (senere)
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 giver et CommandToken tilbage (RAII). Ødelægger man det, fjernes
kommandoen, dens menupunkter og dens genvejstast. Behold hvert symbol som et medlem,
så hele udvidelsesmodulet rives ned af sig selv i shutdown().
Krav til editoren¶
requiresEditor / requiresWritableEditor fortæller værten, hvad kommandoen har
brug for fra det aktive dokument, så den kan nedtone kommandoen i stedet for at lade
brugeren udløse noget, der i stilhed ikke gør noget.
| Flag | Sæt det, når kommandoen … | Den nedtones ved … |
|---|---|---|
requiresEditor |
læser bufferen, markeringen eller markøren, eller sætter markører (Bogmærker, Markér, Søg, Gå til definition) | et faneblad, der slet ikke rummer et dokument — et sammenlignings- eller logvisningsfaneblad, hvor activeDocument() giver 0 tilbage |
requiresWritableEditor |
ændrer teksten (Kommentar, Konvertér bogstaver, Kolonneeditor, Forøg indrykning, Linjer …, Formatér dokument, Omdøb symbol) | det ovenstående og et skrivebeskyttet dokument: en fil, der følges med tail -f, eller en --viewer- eller --readonly-session |
Skriveflaget indebærer det andet, så en beskrivelse må gerne kun sætte det stærkeste
af dem. Begge er false som standard — »altid tilgængelig«, det rigtige svar for
kommandoer, der åbner et vindue, en mappe eller et panel.
Nedtoningen når menupunktet, kommandopaletten og genvejstasten. Den kan kun fjerne
tilgængelighed: et punkt, dit udvidelsesmodul selv har slået fra med
IMenuRegistry::setItemEnabled, bliver ved med at være slået fra, uanset hvad det
aktive dokument er.
Pak beskrivelsen ind på kaldstedet:
using MTE::plugin::needsWritableEditor;
m_toggleComment = ctx.commands().registerCommand(
needsWritableEditor({ "comment.toggle", "Toggle Comment",
"Ctrl+Q", "Comment" }),
[this] { onToggleComment(); });
I Python giver du nøgleordsargumenterne med:
@ctx.commands.command(id="wc.upper", title="Upper Case",
requires_writable_editor=True)
def _upper():
...
Genvejstaster pr. platform¶
shortcut er én flytbar tastekombination. Qt oversætter allerede "Ctrl"-modifikatoren
til platformens egen — ⌘ Command på macOS — så "Ctrl+F" er ⌘F der uden yderligere
arbejde. Du har kun brug for mere, når den automatiske oversættelse ville lande på en
kombination, styresystemet har taget (f.eks. "Ctrl+Q" → ⌘Q = Afslut på macOS).
Sæt i de tilfælde platformShortcuts med platform_id som nøgle. Værten giver den
post tilbage, der passer til den kørende platform, eller falder tilbage på shortcut,
når en platform ikke er nævnt (eller er sat til en tom streng). Ingen #ifdef, ingen
Qt-typer — bare data — så bindingen bliver ved med at være backend-uafhængig, og en ny
platform er én ny nøgle:
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";
Reserverede ⌘-kombinationer på macOS, som en enkelt Ctrl+<key> bør holdes op imod:
⌘Q (Afslut), ⌘W (Luk), ⌘H (Skjul), ⌘M (Minimér), ⌘, (Indstillinger), ⌘Mellemrum
(Spotlight). Ved en Ctrl+Alt+<key>-kombination (⌥⌘ på macOS) skal du desuden undgå
⌥⌘H (Skjul andre), ⌥⌘M (Minimér alle), ⌥⌘D (Dock) og ⌥⌘Esc (Tving til at afslutte).
To regler mere holder genvejstaster ude af vejen for skrivningen:
- Aldrig en bar
Alt+<letter>som den flytbare kombination. På Windows er det menuernes bogstavområde, og på macOS sætter ⌥ + bogstav accenttegn sammen (⌥U er omlydstasten, ⌥C skriver ç), så genvejstasten ville sluge dem — giv en sådan kommando en{"macos", "Meta+Alt+<key>"}-tilsidesættelse (⌃⌥). Ctrl+Alt+<key>er AltGr+<key>på Windows. Nationale tastaturlayout skriver bogstaver der (polsk: ś ł ą ć ę ń ó ź ż €; tysk: @ € µ). Værten holder dem skrivbare — et tryk på højre Alt, der frembringer et tegn, der kan udskrives, leveres som tekst, mens et tryk på venstre Ctrl+Alt kører kommandoen — så sådanne kombinationer er tilladt, men foretræk bogstaver, der ikke skriver noget på de almindelige layout (B D F G H I J K P R T V W Y), og brug aldrigCtrl+Alt+<digit>.
Brugerens omtildeling¶
Den erklærede shortcut / platformShortcuts er standardværdier. Brugeren kan
tildele hver kommando en anden tast eller slet ingen i kernens genvejstastoversigt
(Indstillinger ▸ Genvejstaster), og konflikter løses hos værten: dubletter inden for
samme omfang findes aldrig side om side — taberen sættes på pause sessionen ud, og der
meldes om det. Intet af dette kræver kode i udvidelsesmodulet for almindelige
menubundne kommandoer: værten ejer deres QAction og tildeler den en ny tast
undervejs.
scope — kommandoer i vinduer, udvidelsesmodulet ejer¶
Kommandoer, hvis tastekombination hører hjemme i en kontrol, udvidelsesmodulet ejer
(en værktøjslinje i et værktøjsvindue snarere end hovedmenuen), erklærer
scope = command_scope::kPluginWindow. Værten binder så aldrig selv kombinationen;
den opregner bare kommandoen i oversigten og afgør brugerens valg. Udvidelsesmodulet
anvender selv bindingen på sine egne kontroller:
- spørg
ctx.commands().effectiveShortcut(id), når kontrollerne bygges, og - abonnér på
events::CommandShortcutChanged { commandId, sequence }, og anvend den igen, når et af dets id'er ændrer sig (""= nu uden tildeling).
Konfliktområdet er globalt i v1 — en pluginWindow-kombination nægter at støde sammen
med en programkombination. Et fungerende eksempel: værktøjslinjen i Loganalysens
logvisning (loganalyzer.logView.nextError F8 / .prevError Shift+F8 / .goToTime
Ctrl+G).
Menuer¶
Menustier bruger / som skilletegn; værten opretter mellemliggende undermenuer efter
behov:
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 er RAII ligesom CommandToken.
Afkrydselige menupunkter¶
Et menupunkt, der står for en vedvarende til/fra-tilstand, kan bære et synligt
flueben. setItemChecked tager punktets TokenId — brug symbolets ikke-ødelæggende
id()-tilgang (release() ville opgive ejerskabet):
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);
Det første kald gør punktet afkrydseligt; de følgende kald vender bare tilstanden. Et ukendt symbol gør i stilhed ingenting, så et forsinket tilbagekald, der udløses efter nedlukningen, er harmløst.
At nedtone menupunkter¶
En kommando, der har brug for en forudsætning — en mappe at lukke, et lukket faneblad
at gendanne — bør nedtone sit menupunkt, mens den forudsætning mangler, i stedet for
at tilbyde en post, der i stilhed ikke gør noget. setItemEnabled tager punktets
TokenId, præcis som 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());
Et frakoblet punkt bliver ved med at være synligt, men kan ikke udløses, hverken med
et klik eller med sin genvejstast. Kaldet kan gentages — det er meningen, at den samme
værdi gentages, så du aldrig behøver at huske den forrige tilstand — og et ukendt
symbol gør i stilhed ingenting som ovenfor. Punkter begynder tilkoblede, så et punkt,
der ikke er tilgængeligt ved opstart, kræver ét kald lige efter addItem.
Menuplacering (plugin.json)¶
Hvor et udvidelsesmoduls øverste menu sidder, er datastyret via menus-tabellen i
plugin.json — værten hardkoder aldrig navne på modulmenuer:
title— den øverste menu; passer til det første/-adskilte led i dinmenuPath. Giv den en tastaturgenvej med&("Lo&g Analyzer"), og brug den samme skrivemåde i hvermenuPath: på Windows åbner Alt+bogstav menuen, og hver titel på linjen har brug for sit eget bogstav, i hvert sprog — vælg et, der er ledigt blandt Filer, Rediger, Søg, Tegnkodning, Sammenlign, Vis, Sprog, Udvidelsesmoduler, Vindue, Om og Hjælp, og lad hver oversættelse vælge sit eget. Menusammenligningen ser bort fra markøren.barPriority— den vandrette placering på menulinjen (lavere = længere til venstre). Kernens menuer ligger fast med 100 imellem: Filer 100, Rediger 200, Vis 300, Sprog 400, Sammenlign 500, Vindue 600 (Om 1000, altid yderst til højre); udvidelsesmodulerne udfylder mellemrummene (Søg 250, Tegnkodning 350, Loganalyse 540, Udvidelsesmoduler 570).itemPriority— den lodrette rang for dette moduls blok inde ititle(lavere = længere oppe; 1000 som standard). Punkterne inden for ét moduls blok beholder den rækkefølge, de tilføjes i i koden.
Note
Fordi plugin.json indlejres som Qt-metadata for udvidelsesmodulet, får
ændringer i den først virkning efter en ny bygning.