Kommandoer og menyer¶
Kommandoer¶
ICommandRegistry er den ene trakten hver handling en bruker kan utløse, går gjennom —
menyposter, verktøylinjeknapper, hurtigtaster og (senere) Python-skript.
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 gir et CommandToken tilbake (RAII). Ødelegger man det, fjernes
kommandoen, menypostene dens og hurtigtasten dens. Behold hvert symbol som et medlem, så
hele programtillegget rives ned av seg selv i shutdown().
Krav til redigereren¶
requiresEditor / requiresWritableEditor forteller verten hva kommandoen trenger fra
det aktive dokumentet, så den kan nedtone kommandoen i stedet for å la brukeren utløse noe
som i stillhet ikke gjør noe.
| Flagg | Sett det når kommandoen … | Den nedtones ved … |
|---|---|---|
requiresEditor |
leser bufferen, merkingen eller markøren, eller setter markører (Bokmerker, Merk, Søk, Gå til definisjonen) | en fane som slett ikke rommer et dokument — en sammenlignings- eller loggvisningsfane, der activeDocument() gir 0 |
requiresWritableEditor |
endrer teksten (Kommentar, Endre store/små bokstaver, Kolonneeditor, Øk innrykket, Linjer …, Formater dokumentet, Gi symbolet nytt navn) | det ovenstående og et skrivebeskyttet dokument: en fil som følges med tail -f, eller en --viewer- eller --readonly-økt |
Skriveflagget innebærer det andre, så en beskrivelse kan gjerne bare sette det sterkeste
av dem. Begge er false som standard — «alltid tilgjengelig», det riktige svaret for
kommandoer som åpner et vindu, en mappe eller et panel.
Nedtoningen når menyposten, kommandopaletten og hurtigtasten. Den kan bare ta bort
tilgjengelighet: en post programtillegget ditt selv har slått av med
IMenuRegistry::setItemEnabled, blir værende av, uansett hva det aktive dokumentet er.
Pakk beskrivelsen inn på kallstedet:
using MTE::plugin::needsWritableEditor;
m_toggleComment = ctx.commands().registerCommand(
needsWritableEditor({ "comment.toggle", "Toggle Comment",
"Ctrl+Q", "Comment" }),
[this] { onToggleComment(); });
I Python gir du nøkkelordargumentene med:
@ctx.commands.command(id="wc.upper", title="Upper Case",
requires_writable_editor=True)
def _upper():
...
Hurtigtaster per plattform¶
shortcut er én flyttbar tastekombinasjon. Qt oversetter alt "Ctrl"-modifikatoren til
plattformens egen — ⌘ Command på macOS — så "Ctrl+F" er ⌘F der uten mer arbeid. Du
trenger bare mer når den automatiske oversettelsen ville landet på en kombinasjon
operativsystemet har tatt (for eksempel "Ctrl+Q" → ⌘Q = Avslutt på macOS).
Sett i de tilfellene platformShortcuts, med platform_id som nøkkel. Verten gir den
posten tilbake som passer til plattformen som kjører, eller faller tilbake på shortcut
når en plattform ikke er nevnt (eller er satt til en tom streng). Ingen #ifdef, ingen
Qt-typer — bare data — så bindingen blir værende backend-uavhengig, og en ny plattform er
én ny nøkkel:
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";
Reserverte ⌘-kombinasjoner på macOS som en enkelt Ctrl+<key> bør holdes opp mot: ⌘Q
(Avslutt), ⌘W (Lukk), ⌘H (Skjul), ⌘M (Minimer), ⌘, (Innstillinger), ⌘Mellomrom
(Spotlight). Ved en Ctrl+Alt+<key>-kombinasjon (⌥⌘ på macOS) bør du dessuten unngå ⌥⌘H
(Skjul andre), ⌥⌘M (Minimer alle), ⌥⌘D (Dock) og ⌥⌘Esc (Tving avslutning).
To regler til holder hurtigtastene unna skrivingen:
- Aldri en bar
Alt+<letter>som den flyttbare kombinasjonen. På Windows er det menyenes bokstavområde, og på macOS setter ⌥ + bokstav sammen tegn med aksenter (⌥U er tremataksenten, ⌥C skriver ç), så hurtigtasten ville slukt dem — gi en slik kommando en{"macos", "Meta+Alt+<key>"}-overstyring (⌃⌥). Ctrl+Alt+<key>er AltGr+<key>på Windows. Nasjonale tastaturoppsett skriver bokstaver der (polsk: ś ł ą ć ę ń ó ź ż €; tysk: @ € µ). Verten holder dem skrivbare — et trykk på høyre Alt som gir et tegn som kan skrives ut, leveres som tekst, mens et trykk med venstre Ctrl+Alt kjører kommandoen — så slike kombinasjoner er tillatt, men foretrekk bokstaver som ikke skriver noe på de vanlige oppsettene (B D F G H I J K P R T V W Y), og bruk aldriCtrl+Alt+<digit>.
Brukerens omtildeling¶
Den erklærte shortcut / platformShortcuts er standardverdier. Brukeren kan gi hver
kommando en ny binding eller ingen binding i kjernens hurtigtasttildeling (Innstillinger ▸
Hurtigtaster), og konflikter løses hos verten: dubletter innenfor samme omfang finnes
aldri side om side — taperen settes på pause økten ut, og det meldes fra om det. Ingenting
av dette krever kode i programtillegget for vanlige menybundne kommandoer: verten eier
QAction-en deres og gir den en ny binding underveis.
scope — kommandoer i vinduer programtillegget eier¶
Kommandoer der kombinasjonen hører hjemme i en kontroll programtillegget eier (en
verktøylinje i et verktøyvindu heller enn hovedmenyen), erklærer
scope = command_scope::kPluginWindow. Verten binder da aldri kombinasjonen selv; den
bare lister kommandoen i tildelingen og avgjør brukerens valg. Programtillegget tar selv
bindingen i bruk på sine egne kontroller:
- spør
ctx.commands().effectiveShortcut(id)når kontrollene bygges, og - abonner på
events::CommandShortcutChanged { commandId, sequence }, og ta den i bruk på nytt når en av id-ene endrer seg (""= nå uten binding).
Konfliktområdet er globalt i v1 — en pluginWindow-kombinasjon nekter å kollidere med en
programkombinasjon. Et fungerende eksempel: verktøylinjen i Logganalysens loggvisning
(loganalyzer.logView.nextError F8 / .prevError Shift+F8 / .goToTime Ctrl+G).
Menyer¶
Menystier bruker / som skilletegn; verten lager mellomliggende undermenyer ved 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 slik CommandToken er det.
Avkryssbare menyposter¶
En menypost som står for en varig av/på-tilstand, kan bære et synlig kryss.
setItemChecked tar postens TokenId — bruk symbolets ikke-ødeleggende id()-tilgang
(release() ville gi opp eierskapet):
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 kallet gjør posten avkryssbar; de følgende kallene snur bare tilstanden. Et ukjent symbol gjør i stillhet ingenting, så et forsinket tilbakekall som utløses etter nedstengningen, er harmløst.
Å nedtone menyposter¶
En kommando som trenger en forutsetning — en mappe å lukke, en lukket fane å gjenopprette
— bør nedtone menyposten sin mens den forutsetningen mangler, i stedet for å tilby en post
som i stillhet ikke gjør noe. setItemEnabled tar postens TokenId, nøyaktig 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());
En post som er slått av, blir værende synlig, men kan ikke utløses, verken med et klikk
eller med hurtigtasten sin. Kallet kan gjentas — det er meningen at den samme verdien
gjentas, så du aldri trenger å huske den forrige tilstanden — og et ukjent symbol gjør i
stillhet ingenting, som ovenfor. Poster begynner påslått, så en post som ikke er
tilgjengelig ved oppstart, trenger ett kall rett etter addItem.
Menyplassering (plugin.json)¶
Hvor et programtilleggs øverste meny sitter, er datastyrt via menus-tabellen i
plugin.json — verten hardkoder aldri navn på programtilleggsmenyer:
title— den øverste menyen; passer til det første/-adskilte leddet i dinmenuPath. Gi den en tastatursnarvei med&("Lo&g Analyzer"), og bruk den samme skrivemåten i hvermenuPath: på Windows åpner Alt+bokstav menyen, og hver tittel på linjen trenger sin egen bokstav, i hvert språk — velg en som er ledig blant Fil, Rediger, Søk, Tegnkoding, Sammenlign, Vis, Språk, Programtillegg, Vindu, Om og Hjelp, og la hver oversettelse velge sin egen. Menysammenligningen ser bort fra markøren.barPriority— den vannrette plasseringen på menylinjen (lavere = lenger til venstre). Kjernens menyer ligger fast med 100 imellom: Fil 100, Rediger 200, Vis 300, Språk 400, Sammenlign 500, Vindu 600 (Om 1000, alltid ytterst til høyre); programtilleggene fyller hullene (Søk 250, Tegnkoding 350, Logganalyse 540, Programtillegg 570).itemPriority— den loddrette rangen for dette programtilleggets blokk inne ititle(lavere = lenger opp; 1000 som standard). Postene innenfor ett programtilleggs blokk beholder den rekkefølgen de legges til i i koden.
Note
Fordi plugin.json legges inn som Qt-metadata for programtillegget, får endringer i
den først virkning etter et nytt bygg.