Príkazy a ponuky¶
Príkazy¶
ICommandRegistry je jediným lievikom na vykonanie každej akcie vyvolateľnej používateľom —
položiek ponuky, tlačidiel panela nástrojov, skratiek a (neskôr) skriptov v Pythone.
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 vracia CommandToken (RAII). Jeho zničením sa odstráni príkaz, jeho položky
ponuky aj jeho skratka. Každý token si držte ako člen, aby sa celý zásuvný modul v shutdown()
zrušil automaticky.
Požiadavky na editor¶
requiresEditor a requiresWritableEditor hovoria hostiteľovi, čo príkaz od aktívneho dokumentu
potrebuje, aby ho mohol zosivieť namiesto toho, aby používateľ vyvolal niečo, čo potichu neurobí
nič.
| Príznak | Nastavte ho, keď príkaz… | Zosivie pri… |
|---|---|---|
requiresEditor |
číta buffer, výber alebo kurzor, prípadne umiestňuje značky (Záložky, Označiť, Nájsť, Prejsť na definíciu) | karte, ktorá nehostí žiadny dokument — karte porovnania alebo zobrazenia logu, kde activeDocument() vracia 0 |
requiresWritableEditor |
mení text (Komentár, Previesť veľkosť písmen, Stĺpcový editor, Zväčšiť odsadenie, Riadky…, Formátovať dokument, Premenovať symbol) | tom istom a zároveň pri dokumente iba na čítanie: pri súbore sledovanom cez tail -f, pri relácii s --viewer alebo --readonly |
Príznak zapisovateľnosti zahŕňa ten druhý, takže deskriptor môže nastaviť iba ten silnejší.
Obidva sú predvolene false — „vždy dostupné“, čo je správna odpoveď pre príkazy, ktoré otvárajú
dialóg, priečinok alebo panel.
Zosivenie zasiahne položku ponuky, paletu príkazov aj skratku. Dostupnosť môže vždy iba ubrať:
položka, ktorú váš zásuvný modul sám vypol cez IMenuRegistry::setItemEnabled, ostane vypnutá,
nech je aktívny dokument akýkoľvek.
Deskriptor obaľte na mieste volania:
using MTE::plugin::needsWritableEditor;
m_toggleComment = ctx.commands().registerCommand(
needsWritableEditor({ "comment.toggle", "Toggle Comment",
"Ctrl+Q", "Comment" }),
[this] { onToggleComment(); });
V Pythone odovzdajte kľúčové argumenty:
@ctx.commands.command(id="wc.upper", title="Upper Case",
requires_writable_editor=True)
def _upper():
...
Skratky pre jednotlivé platformy¶
shortcut je jediná prenosná kombinácia klávesov. Qt už modifikátor "Ctrl" mapuje na natívny
modifikátor platformy — na macOS na ⌘ Command — takže "Ctrl+F" je tam ⌘F bez akejkoľvek
práce navyše. Viac potrebujete iba vtedy, keď by toto automatické mapovanie pristálo na
kombinácii, ktorú si vyhradzuje operačný systém (napr. "Ctrl+Q" → ⌘Q = Ukončiť na macOS).
Pre tieto prípady nastavte platformShortcuts s kľúčmi z platform_id. Hostiteľ vráti položku
zodpovedajúcu bežiacej platforme, prípadne sa vráti k shortcut, keď platforma chýba (alebo je
priradená k prázdnemu reťazcu). Žiadne #ifdef, žiadne typy z Qt — iba údaje — takže priradenie
ostáva nezávislé od backendu a nová platforma je iba novým kľúčom:
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";
Vyhradené kombinácie s ⌘ na macOS, oproti ktorým si treba overiť jednoduché Ctrl+<key>:
⌘Q (Ukončiť), ⌘W (Zavrieť), ⌘H (Skryť), ⌘M (Minimalizovať), ⌘, (Predvoľby), ⌘Space (Spotlight).
Pri kombinácii Ctrl+Alt+<key> (⌥⌘ na macOS) sa vyhnite aj ⌥⌘H (Skryť ostatné), ⌥⌘M
(Minimalizovať všetko), ⌥⌘D (Dock) a ⌥⌘Esc (Vynútiť ukončenie).
Ďalšie dve pravidlá držia skratky mimo cesty pri písaní:
- Nikdy nie samotné
Alt+<letter>ako prenosná kombinácia. Vo Windows je to priestor mnemoník ponuky a na macOS ⌥+písmeno skladá písmená s diakritikou (⌥U je mŕtvy kláves pre prehlásku, ⌥C napíše ç), takže by ich skratka pohltila — takému príkazu dajte prekrytie{"macos", "Meta+Alt+<key>"}(⌃⌥). Ctrl+Alt+<key>je vo Windows AltGr+<key>. Národné rozloženia tam píšu písmená (poľské: ś ł ą ć ę ń ó ź ż €; nemecké: @ € µ). Hostiteľ ich ponecháva napísateľné — stlačenie pravého Altu, ktoré vytvorí tlačiteľný znak, sa doručí ako text, stlačenie ľavého Ctrl+Alt spustí príkaz — takže sú takéto kombinácie povolené, ale uprednostnite písmená, ktoré na bežných rozloženiach nepíšu nič (B D F G H I J K P R T V W Y), a nikdy nepoužívajteCtrl+Alt+<digit>.
Premapovanie používateľom¶
Deklarované shortcut a platformShortcuts sú predvoľbami. Používateľ môže každý príkaz
premapovať alebo mu priradenie zrušiť v Mapovaní skratiek v jadre (Predvoľby ▸ Skratky)
a konflikty sa riešia na strane hostiteľa: duplicity v tom istom rozsahu nikdy neexistujú
súbežne — porazený sa pozastaví na danú reláciu a ohlási sa. Pri bežných príkazoch viazaných na
ponuku si to nevyžaduje žiadny kód zásuvného modulu: hostiteľ vlastní ich QAction a premapuje
ho naživo.
scope — príkazy v oknách vlastnených zásuvným modulom¶
Príkazy, ktorých kombinácia klávesov žije vo widgete vlastnenom zásuvným modulom (v paneli
nástrojov nástrojového okna, a nie v hlavnej ponuke), deklarujú
scope = command_scope::kPluginWindow. Hostiteľ potom kombináciu sám nikdy nepriradí; príkaz iba
uvedie v mapovaní a vyhodnotí voľbu používateľa. Priradenie na vlastné widgety uplatní zásuvný
modul:
- pri zostavovaní widgetov sa spýtajte
ctx.commands().effectiveShortcut(id)a - prihláste sa na
events::CommandShortcutChanged { commandId, sequence }a pri zmene niektorého z vašich id priradenie uplatnite znova (""= teraz bez priradenia).
Doména konfliktov je vo verzii 1 globálna — kombinácia typu pluginWindow odmietne kolidovať
s kombináciou na úrovni aplikácie. Fungujúcim príkladom je panel nástrojov Zobrazenia logu
v Analyzátore logov (loganalyzer.logView.nextError F8 / .prevError Shift+F8 / .goToTime
Ctrl+G).
Ponuky¶
Cesty v ponukách používajú ako oddeľovač /; hostiteľ medziľahlé podponuky vytvára na
požiadanie:
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 je RAII rovnako ako CommandToken.
Začiarkavacie položky ponuky¶
Položka ponuky, ktorá predstavuje trvalý stav zapnuté/vypnuté, môže niesť viditeľnú značku
začiarknutia. setItemChecked prijíma TokenId položky — použite nedeštruktívny prístupový
prvok tokenu id() (release() by sa vzdal vlastníctva):
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);
Prvé volanie povýši položku na začiarkavaciu; ďalšie volania už iba prepínajú stav. Neznámy token je tichou prázdnou operáciou, takže oneskorené zavolanie po ukončení nič nepokazí.
Zosivenie položiek ponuky¶
Príkaz, ktorý potrebuje splnenú podmienku — priečinok na zatvorenie, zatvorenú kartu na obnovenie
— by mal svoju položku ponuky zosivieť, kým podmienka nie je splnená, namiesto toho, aby ponúkal
položku, ktorá potichu neurobí nič. setItemEnabled prijíma TokenId položky presne tak ako
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());
Vypnutá položka ostáva viditeľná, ale nedá sa vyvolať ani kliknutím, ani jej klávesovou skratkou.
Volanie je idempotentné — opakovanie tej istej hodnoty sa očakáva, takže si predchádzajúci stav
nikdy nemusíte pamätať — a neznámy token je tichou prázdnou operáciou, ako vyššie. Položky sú na
začiatku zapnuté, takže položka, ktorá nie je dostupná pri spustení, potrebuje hneď po addItem
jedno volanie.
Umiestnenie v ponuke (plugin.json)¶
To, kde sa hlavná ponuka zásuvného modulu nachádza, sa riadi údajmi cez pole menus v súbore
plugin.json — hostiteľ názvy ponúk zásuvných modulov nikdy nemá napevno v kóde:
title— hlavná ponuka; zodpovedá prvému segmentu vašejmenuPathoddelenému znakom/. Dajte jej klávesový mnemonik znakom&("Lo&g Analyzer") a používajte rovnaký zápis v každejmenuPath: vo Windowse otvára ponuku Alt+písmeno a každý titulok na paneli potrebuje vlastné písmeno, v každom jazyku — vyberte voľné spomedzi Súbor, Úpravy, Hľadať, Kódovanie, Porovnať, Zobraziť, Jazyk, Zásuvné moduly, Okno, O aplikácii a Pomocník a každému prekladu dovoľte vybrať si vlastné. Porovnávanie ponúk značku ignoruje.barPriority— vodorovná poloha na paneli ponuky (nižšie = viac vľavo). Ponuky jadra sú pevné, rozostúpené po 100: Súbor 100, Úpravy 200, Zobraziť 300, Jazyk 400, Porovnať 500, Okno 600 (O aplikácii 1000, vždy úplne vpravo); zásuvné moduly vypĺňajú medzery (Hľadať 250, Kódovanie 350, Analyzátor logov 540, Zásuvné moduly 570).itemPriority— zvislé poradie bloku tohto zásuvného modulu v rámcititle(nižšie = vyššie; predvolene 1000). Položky v rámci bloku jedného modulu si ponechávajú poradie, v akom sú pridané v kóde.
Note
Keďže je plugin.json vložený ako metaúdaje zásuvného modulu Qt, jeho úpravy sa prejavia až
po opätovnom zostavení.