Kommandon och menyer¶
Kommandon¶
ICommandRegistry är den enda exekveringstratten för varje åtgärd en användare kan
anropa — menyposter, verktygsfältsknappar, kortkommandon och (senare)
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 returnerar en CommandToken (RAII). Att förstöra den tar bort
kommandot, dess menyposter och dess kortkommando. Håll varje symbol som medlem så
att hela insticksmodulen rivs ned automatiskt i shutdown().
Krav på redigeraren¶
requiresEditor / requiresWritableEditor talar om för värden vad kommandot
behöver från det aktiva dokumentet, så att den kan gråtona kommandot i stället för
att låta användaren anropa något som tyst inte gör något.
| Flagga | Sätt den när kommandot… | Den gråtonas vid… |
|---|---|---|
requiresEditor |
läser bufferten, markeringen eller markören, eller placerar markörer (Bokmärken, Markera, Sök, Gå till definitionen) | en flik som inte rymmer något dokument alls — en flik med en jämförelse eller en loggvy, där activeDocument() returnerar 0 |
requiresWritableEditor |
ändrar texten (Kommentar, Ändra skiftläge, Kolumnredigerare, Öka indraget, Rader…, Formatera dokumentet, Byt namn på symbolen) | ovanstående och ett skrivskyddat dokument: en fil som följs med tail -f, en session med --viewer eller --readonly |
Skrivflaggan medför den andra, så en beskrivning får sätta bara den starkare. Båda
är false som standard — «alltid tillgängligt», rätt svar för kommandon som öppnar
en dialogruta, en mapp eller en panel.
Gråtoningen når menyposten, kommandopaletten och kortkommandot. Den kan bara ta bort
tillgänglighet: en post som din insticksmodul själv inaktiverat genom
IMenuRegistry::setItemEnabled förblir inaktiverad oavsett vilket dokument som är
aktivt.
Kapsla in beskrivningen på anropsplatsen:
using MTE::plugin::needsWritableEditor;
m_toggleComment = ctx.commands().registerCommand(
needsWritableEditor({ "comment.toggle", "Toggle Comment",
"Ctrl+Q", "Comment" }),
[this] { onToggleComment(); });
I Python skickar du med de namngivna argumenten:
@ctx.commands.command(id="wc.upper", title="Upper Case",
requires_writable_editor=True)
def _upper():
...
Kortkommandon per plattform¶
shortcut är en enda portabel tangentkombination. Qt kopplar redan modifieraren
"Ctrl" till plattformens inhemska — ⌘ Command på macOS — så "Ctrl+F" är ⌘F
där utan extra arbete. Du behöver mer bara när den automatiska kopplingen skulle
hamna på en kombination som operativsystemet reserverat (till exempel "Ctrl+Q" →
⌘Q = Avsluta på macOS).
För de fallen anger du platformShortcuts, med platform_id som nyckel. Värden
returnerar den post som matchar den körande plattformen, eller faller tillbaka på
shortcut när en plattform saknas (eller kopplats till en tom sträng). Ingen
#ifdef, inga Qt-typer — bara data — så att kopplingen förblir backend-oberoende,
och en ny plattform är en enda ny nyckel:
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";
Reserverade ⌘-kombinationer i macOS att stämma av ett enskilt Ctrl+<key> mot:
⌘Q (Avsluta), ⌘W (Stäng), ⌘H (Göm), ⌘M (Minimera), ⌘, (Inställningar), ⌘Mellanslag
(Spotlight). För en kombination Ctrl+Alt+<key> (⌥⌘ på macOS) undvik också
⌥⌘H (Göm övriga), ⌥⌘M (Minimera alla), ⌥⌘D (Dock) och ⌥⌘Esc (Tvångsavsluta).
Två regler till håller kortkommandon ur vägen för skrivandet:
- Aldrig ett ensamt
Alt+<letter>som portabel kombination. På Windows är det menymnemonikernas område, och på macOS sätter ⌥+bokstav samman accenttecken (⌥U är den döda tangenten för trema, ⌥C skriver ç), så kortkommandot skulle svälja dem — ge ett sådant kommando en åsidosättning{"macos", "Meta+Alt+<key>"}(⌃⌥). Ctrl+Alt+<key>är AltGr+<key>på Windows. Nationella layouter skriver bokstäver där (polska: ś ł ą ć ę ń ó ź ż €; tyska: @ € µ). Värden håller dem skrivbara — en tryckning på höger Alt som ger ett utskrivbart tecken levereras som text, en tryckning på vänster Ctrl+Alt kör kommandot — så sådana kombinationer är tillåtna, men föredra bokstäver som inte skriver något i de vanliga layouterna (B D F G H I J K P R T V W Y) och använd aldrigCtrl+Alt+<digit>.
Omtilldelning av användaren¶
De deklarerade shortcut / platformShortcuts är standardvärden. Användaren
kan tilldela om eller lossa varje kommando i kärnans tilldelning av kortkommandon
(Inställningar ▸ Kortkommandon), och konflikter löses på värdsidan: dubbletter inom
samma omfattning samexisterar aldrig — förloraren stängs av för sessionen och
rapporteras. Inget av detta kräver kod i insticksmodulen för vanliga menybundna
kommandon: värden äger deras QAction och kopplar om den under körning.
scope — kommandon i fönster som en insticksmodul äger¶
Kommandon vars kombination bor i en widget som insticksmodulen äger (ett
verktygsfönsters verktygsfält snarare än huvudmenyn) deklarerar
scope = command_scope::kPluginWindow. Värden kopplar då aldrig kombinationen
själv; den listar bara kommandot i tilldelningen och löser användarens val.
Insticksmodulen tillämpar kopplingen på sina egna widgetar:
- fråga
ctx.commands().effectiveShortcut(id)när du bygger widgetarna, och - prenumerera på
events::CommandShortcutChanged { commandId, sequence }och tillämpa på nytt när ett av dess id:n ändras (""= nu lossad).
Konfliktdomänen är global i v1 — en pluginWindow-kombination vägrar krocka med en
programkombination. Fungerande exempel: verktygsfältet i Logganalysens loggvy
(loganalyzer.logView.nextError F8 / .prevError Shift+F8 / .goToTime Ctrl+G).
Menyer¶
Menysökvägar använder / som avskiljare; värden skapar mellanliggande undermenyer
på begäran:
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 är RAII precis som CommandToken.
Kryssbara menyposter¶
En menypost som representerar ett bestående på/av-tillstånd kan bära ett synligt
kryss. setItemChecked tar postens TokenId — använd symbolens icke-förstörande
åtkomst id() (release() skulle ge upp ägandet):
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örsta anropet gör posten kryssbar; senare anrop vänder bara tillståndet. En okänd symbol är en tyst nulloperation, så ett fördröjt återanrop som utlöses efter avslutet är ofarligt.
Gråtona menyposter¶
Ett kommando som behöver ett villkor — en mapp att stänga, en stängd flik att
återställa — bör gråtona sin menypost medan villkoret saknas i stället för att
erbjuda en post som tyst inte gör något. setItemEnabled tar postens TokenId,
precis 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 inaktiverad post förblir synlig men kan inte utlösas, varken med ett klick eller
med sitt kortkommando. Anropet är idempotent — att upprepa samma värde är väntat, så
du behöver aldrig minnas det tidigare tillståndet — och en okänd symbol är en tyst
nulloperation, som ovan. Poster börjar aktiverade, så en post som inte är tillgänglig
vid start behöver ett anrop direkt efter addItem.
Menyplacering (plugin.json)¶
Var en insticksmoduls toppnivåmeny sitter är datadrivet genom vektorn menus i
plugin.json — värden hårdkodar aldrig insticksmodulers menynamn:
title— toppnivåmenyn; stämmer med det första/-avskilda segmentet i dinmenuPath. Ge den en tangentbordsgenväg med&("Lo&g Analyzer") och använd samma stavning i varjemenuPath: på Windows öppnar Alt+bokstav menyn, och varje titel i raden behöver en egen bokstav, i varje språk — välj en som är fri bland Fil, Redigera, Sök, Teckenkodning, Jämför, Visa, Språk, Insticksmoduler, Fönster, Om och Hjälp, och låt varje översättning välja sin egen. Matchningen av menyer bortser från markören.barPriority— vågrät position i menyraden (lägre = längre till vänster). Kärnmenyerna är fasta, med 100 emellan: Fil 100, Redigera 200, Visa 300, Språk 400, Jämför 500, Fönster 600 (Om 1000, alltid längst till höger); insticksmodulerna fyller luckorna (Sök 250, Teckenkodning 350, Logganalys 540, Insticksmoduler 570).itemPriority— lodrät rang för den här insticksmodulens block inomtitle(lägre = högre upp; standard 1000). Poster inom en insticksmoduls block behåller den ordning de läggs till i koden.
Note
Eftersom plugin.json är inbäddad som Qt-metadata för insticksmodulen får
ändringar i den effekt först efter en ombyggnad.