Hyppää sisältöön

Komennot ja valikot

Komennot

ICommandRegistry on se yksi suppilo, jonka kautta jokainen käyttäjän käynnistettävissä oleva toiminto kulkee — valikkokohdat, työkalurivin painikkeet, pikanäppäimet ja (myöhemmin) Python-skriptit.

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 palauttaa CommandToken-poletin (RAII). Sen tuhoaminen poistaa komennon, sen valikkokohdat ja sen pikanäppäimen. Pidä jokaista polettia jäsenenä, jotta koko liitännäinen purkautuu automaattisesti funktiossa shutdown().

Editorivaatimukset

requiresEditor / requiresWritableEditor kertovat isännälle, mitä komento tarvitsee aktiiviselta asiakirjalta, jotta se voi harmaannuttaa komennon sen sijaan että antaisi käyttäjän käynnistää jotain, joka ei hiljaisesti tee mitään.

Lippu Aseta se, kun komento … Se harmaantuu …
requiresEditor lukee puskuria, valintaa tai kohdistinta tai asettaa merkkejä (Kirjanmerkit, Merkitse, Etsi, Siirry määrittelyyn) välilehdellä, jolla ei ole lainkaan asiakirjaa — vertailu- tai lokinäkymävälilehdellä, jossa activeDocument() palauttaa arvon 0
requiresWritableEditor muuttaa tekstiä (Kommentti, Muunna kirjainkoko, Sarake-editori, Lisää sisennystä, Rivit …, Muotoile asiakirja, Nimeä symboli uudelleen) edellä mainitulla sekä vain luettavassa asiakirjassa: tiedostossa, jota seurataan komennolla tail -f, tai --viewer- tai --readonly-istunnossa

Kirjoituslippu sisältää toisen, joten kuvaus voi asettaa vain vahvemman niistä. Molempien oletus on false — »aina käytettävissä«, oikea vastaus komennoille, jotka avaavat ikkunan, kansion tai paneelin.

Harmaannutus yltää valikkokohtaan, komentopalettiin ja pikanäppäimeen. Se voi vain viedä käytettävyyden pois: kohta, jonka liitännäisesi itse poisti käytöstä funktiolla IMenuRegistry::setItemEnabled, pysyy pois käytöstä riippumatta siitä, mikä aktiivinen asiakirja on.

Kääri kuvaus kutsupaikassa:

using MTE::plugin::needsWritableEditor;

m_toggleComment = ctx.commands().registerCommand(
    needsWritableEditor({ "comment.toggle", "Toggle Comment",
                          "Ctrl+Q", "Comment" }),
    [this] { onToggleComment(); });

Pythonissa välität avainsana-argumentit:

@ctx.commands.command(id="wc.upper", title="Upper Case",
                      requires_writable_editor=True)
def _upper():
    ...

Alustakohtaiset pikanäppäimet

shortcut on yksi siirrettävä näppäinyhdistelmä. Qt kuvaa jo "Ctrl"-muuntonäppäimen alustan omaan — ⌘ Command macOS:ssä — joten "Ctrl+F" on siellä ⌘F ilman lisätyötä. Tarvitset enemmän vain silloin, kun tuo automaattinen kuvaus osuisi yhdistelmään, jonka käyttöjärjestelmä on varannut (esimerkiksi "Ctrl+Q" → ⌘Q = Lopeta macOS:ssä).

Aseta noissa tapauksissa platformShortcuts, avaimena platform_id. Isäntä palauttaa kohdan, joka vastaa käynnissä olevaa alustaa, tai turvautuu arvoon shortcut, kun alustaa ei ole mainittu (tai se on kuvattu tyhjäksi merkkijonoksi). Ei #ifdef-lauseita, ei Qt-tyyppejä — pelkkää dataa — joten sidonta pysyy taustajärjestelmästä riippumattomana ja uusi alusta on yksi uusi avain:

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";

Varattuja macOS:n ⌘-yhdistelmiä, joita vasten yksittäistä Ctrl+<key>-yhdistelmää kannattaa tarkistaa: ⌘Q (Lopeta), ⌘W (Sulje), ⌘H (Kätke), ⌘M (Pienennä), ⌘, (Asetukset), ⌘välilyönti (Spotlight). Ctrl+Alt+<key>-yhdistelmän (⌥⌘ macOS:ssä) kohdalla vältä myös yhdistelmiä ⌥⌘H (Kätke muut), ⌥⌘M (Pienennä kaikki), ⌥⌘D (Dock) ja ⌥⌘Esc (Pakota lopetus).

Kaksi sääntöä lisää pitää pikanäppäimet pois kirjoittamisen tieltä:

  • Ei koskaan paljasta Alt+<letter> siirrettävänä yhdistelmänä. Windowsissa se on valikoiden muistiavainten aluetta, ja macOS:ssä ⌥ + kirjain muodostaa aksenttimerkkejä (⌥U on treema-kuollutnäppäin, ⌥C kirjoittaa ç), joten pikanäppäin nielisi ne — anna tällaiselle komennolle {"macos", "Meta+Alt+<key>"}-ohitus (⌃⌥).
  • Ctrl+Alt+<key> on AltGr+<key> Windowsissa. Kansalliset näppäinasettelut kirjoittavat siellä kirjaimia (puola: ś ł ą ć ę ń ó ź ż €; saksa: @ € µ). Isäntä pitää ne kirjoitettavina — oikean Alt-näppäimen painallus, joka tuottaa tulostettavan merkin, toimitetaan tekstinä, ja vasemman Ctrl+Alt -painallus ajaa komennon — joten tällaiset yhdistelmät ovat sallittuja, mutta suosi kirjaimia, jotka eivät kirjoita mitään yleisillä asetteluilla (B D F G H I J K P R T V W Y), äläkä koskaan käytä yhdistelmää Ctrl+Alt+<digit>.

Käyttäjän uudelleenmääritys

Ilmoitetut shortcut / platformShortcuts ovat oletuksia. Käyttäjä voi määrittää jokaisen komennon uudelleen tai poistaa sen määrityksen ytimen pikanäppäinten määrityksessä (Asetukset ▸ Pikanäppäimet), ja ristiriidat ratkaistaan isännän puolella: saman laajuuden kaksoiskappaleet eivät koskaan esiinny rinnakkain — häviäjä keskeytetään istunnon ajaksi ja siitä ilmoitetaan. Mikään tästä ei vaadi liitännäiskoodia tavallisille valikkoon sidotuille komennoille: isäntä omistaa niiden QAction-olion ja määrittää sen uudelleen lennossa.

scope — komennot liitännäisen omistamissa ikkunoissa

Komennot, joiden yhdistelmä sijaitsee liitännäisen omistamassa ohjausobjektissa (työkaluikkunan työkalurivillä päävalikon sijaan), ilmoittavat arvon scope = command_scope::kPluginWindow. Isäntä ei silloin koskaan sido yhdistelmää itse; se vain luettelee komennon määrityksessä ja ratkaisee käyttäjän valinnan. Liitännäinen ottaa sidonnan käyttöön omissa ohjausobjekteissaan:

  • kysy ctx.commands().effectiveShortcut(id) ohjausobjekteja rakentaessasi ja
  • tilaa events::CommandShortcutChanged { commandId, sequence } ja ota sekvenssi uudelleen käyttöön, kun jokin sen tunnisteista muuttuu ("" = nyt ilman määritystä).

Ristiriitojen alue on v1:ssä yleinen — pluginWindow-yhdistelmä kieltäytyy törmäämästä sovellusyhdistelmään. Toimiva esimerkki: Lokianalyysin lokinäkymän työkalurivi (loganalyzer.logView.nextError F8 / .prevError Shift+F8 / .goToTime Ctrl+G).

Valikot

Valikkopolut käyttävät merkkiä / erottimena; isäntä luo välialivalikot tarvittaessa:

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;
};
m_menu = ctx.menus().addItem("File/Export/As HTML...", "myplugin.exportHtml");

MenuToken on RAII kuten CommandToken.

Valittavat valikkokohdat

Valikkokohta, joka edustaa pysyvää päälle/pois-tilaa, voi kantaa näkyvää valintamerkkiä. setItemChecked ottaa kohdan TokenId-arvon — käytä poletin tuhoamatonta id()-käyttäjää (release() luovuttaisi omistajuuden):

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);

Ensimmäinen kutsu tekee kohdasta valittavan; seuraavat kutsut vain kääntävät tilan. Tuntematon poletti ei tee hiljaisesti mitään, joten viivästynyt takaisinkutsu, joka laukeaa sammutuksen jälkeen, on vaaraton.

Valikkokohtien harmaannuttaminen

Komennon, joka tarvitsee edellytyksen — suljettavan kansion, palautettavan suljetun välilehden — tulisi harmaannuttaa valikkokohtansa, kun tuo edellytys puuttuu, sen sijaan että tarjoaisi kohtaa, joka ei hiljaisesti tee mitään. setItemEnabled ottaa kohdan TokenId-arvon, aivan kuten 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());

Käytöstä poistettu kohta pysyy näkyvissä muttei ole käynnistettävissä, ei napsautuksella eikä pikanäppäimellään. Kutsu on idempotentti — saman arvon toistaminen on odotettua, joten sinun ei tarvitse koskaan muistaa edellistä tilaa — ja tuntematon poletti ei tee hiljaisesti mitään, kuten yllä. Kohdat alkavat käytössä olevina, joten kohta, joka ei ole käytettävissä käynnistyksessä, tarvitsee yhden kutsun heti funktion addItem jälkeen.

Valikon sijoitus (plugin.json)

Se, missä liitännäisen ylimmän tason valikko sijaitsee, on datavetoista tiedoston plugin.json menus-taulukon kautta — isäntä ei koskaan kovakoodaa liitännäisvalikkojen nimiä:

"menus": [
  { "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
]
  • title — ylimmän tason valikko; vastaa menuPath-polkusi ensimmäistä /-erotettua osaa. Anna sille näppäinvihje merkillä & ("Lo&g Analyzer") ja käytä samaa kirjoitusasua jokaisessa menuPath-polussa: Windowsissa Alt+kirjain avaa valikon, ja jokainen palkin otsikko tarvitsee oman kirjaimensa, jokaisessa kielessä — valitse vapaa niistä, jotka eivät ole käytössä nimissä Tiedosto, Muokkaa, Haku, Merkistökoodaus, Vertaa, Näytä, Kieli, Liitännäiset, Ikkuna, Tietoja ja Ohje, ja anna jokaisen käännöksen valita omansa. Valikoiden täsmäytys ohittaa merkin.
  • barPriority — vaakasuora sijainti valikkopalkissa (pienempi = kauempana vasemmalla). Ytimen valikoilla on kiinteät, 100:n välein sijoitetut sijainnit: Tiedosto 100, Muokkaa 200, Näytä 300, Kieli 400, Vertaa 500, Ikkuna 600 (Tietoja 1000, aina kaukana oikealla); liitännäiset täyttävät aukot (Haku 250, Merkistökoodaus 350, Lokianalyysi 540, Liitännäiset 570).
  • itemPriority — tämän liitännäisen koko lohkon pystysuora sijainti valikon title sisällä (pienempi = ylempänä; oletus 1000). Yhden liitännäisen lohkon sisällä kohdat säilyttävät sen järjestyksen, jossa ne lisätään koodissa.

Note

Koska plugin.json upotetaan Qt-liitännäismetatietoina, sen muokkaukset tulevat voimaan vasta uudelleenkäännöksen jälkeen.