Hyppää sisältöön

Telakat, välilehdet, tilarivi ja teema

Ne käyttöliittymäpinnat, joihin liitännäinen voi osallistua valikoiden lisäksi. Ne kaikki noudattavat samaa kaavaa: kuvaa haluamasi taustajärjestelmäneutraalina datana, anna isännälle tehdas ja säilytä palautettu RAII-poletti.

Telakkapaneelit

ctx.docks() lisää telakoitavan sivupaneelin. Julkinen pinta välittää ohjausobjektin tarkoituksella läpinäkymättömänä void*-osoittimena — Qt-pohjainen isäntä muuntaa sen QWidget*-osoittimeksi; Python-liitännäinen rakentaa saman paneelin kuvailevasti (katso ctx.docks-Python-dokumentaatio).

class IDockPanel {
public:
    void*       nativeWidget();   // your QWidget*, host takes ownership
    std::string title() const;
};

class IDockPanelFactory {
public:
    std::unique_ptr<IDockPanel> create();
};

struct DockPanelDescriptor {
    std::string id;                            // "myplugin.outline"
    std::string defaultTitle;
    DockArea    preferredArea    = DockArea::Right;   // Left/Right/Top/Bottom
    bool        initiallyVisible = true;              // false = created hidden
    double      proportionalSize = 0.0;                // 0 = pixel-sized
    std::string tabGroup;                              // "" = a dock of its own
};

m_dock = ctx.docks().addPanel(descriptor, std::move(factory));

Isäntä luo paneelin laiskasti (ensimmäisellä näytöllä) ja voi luoda sen uudelleen asettelun nollauksen jälkeen — pidä paneelikohtainen tila ohjausobjektin ulkopuolella tai rakenna se uudelleen funktiossa create(). DockToken poistaa paneelin tuhoutuessaan.

Pikselit vai osuus ikkunasta — proportionalSize

0 (oletus) on tavallinen käyttäytyminen: paneeli pitää koon pikseleinä, eikä ikkunan koon muuttaminen kosketa sitä. Sitä työkalupaneeli haluaa — jäsennys tai tulosluettelo on yhtä käyttökelpoinen yhdellä leveydellä riippumatta siitä, mitä ikkuna tekee. Arvo välillä (0, 1) sen sijaan ilmoittaa oletusosuuden pääikkunasta: sen leveydestä alueilla Left/Right, sen korkeudesta alueilla Top/Bottom. Sitä esikatselu haluaa — suurenna ikkuna, ja piirretty sivu levenee sen mukana.

desc.proportionalSize = 0.40;   // open at 40 % of the window

Se on juuri siksi pelkkä luku, että muu kuin C++-taustaosa voi täyttää saman sopimuksen (Python-taustaosa ottaa sen nimellä proportional_size). Tämän isäntä takaa sen ympärillä:

  • Sinä annat vain oletuksen. Sillä hetkellä, kun käyttäjä vetää paneelin erotinta, isäntä ottaa käyttöön hänen valitsemansa osuuden, muistaa sen ikkuna-asettelun mukana, eikä sinun arvosi koske tuota paneelia enää koskaan.
  • Rajojen ulkopuoliset arvot rajataan, ei hylätä — osuus pidetään järkevällä alueella, ja sekä paneelisi että editori säilyttävät vähimmäislaajuuden, joten kumpaakaan ei voi litistää. Negatiivinen tai ei-äärellinen arvo luetaan arvoksi 0.
  • Paneelisi oma vähimmäiskoko voittaa. Isäntä ei koskaan pyydä asettelulta vähempää kuin mihin ohjausobjekti voidaan puristaa; paneeli pysähtyy siihen rajaan unohtamatta osuutta ja palaa siihen, kun ikkuna jälleen levenee. Paneelin, jonka on tarkoitus olla suhteellinen, pitäisi siksi pystyä asettumaan uudelleen — työkalurivi, joka pinoaa ryhmänsä sen sijaan että vaatisi yhtä riviä, pitää paneelin kavennettavissa.
  • Irralliset ja piilotetut paneelit ohitetaan. Irrallinen paneeli on oma ikkunansa ja pitää kokonsa; piilotetulle koko annetaan, kun se tuodaan esiin, ja takaisin telakoidulle, kun se on taas paikallaan.
  • Välilehtiryhmän paneelit jakavat yhden paikan, joten sitä ohjaa edessä oleva paneeli — se, jota käyttäjä katsoo, on se, jonka osuus merkitsee jotakin. Kaksi vierekkäistä paneelia samalla akselilla jakavat yhden budjetin ja skaalautuvat yhdessä suhteensa säilyttäen.

Paneeli, joka on rekisteröity arvolla initiallyVisible = false, tuodaan esiin myöhemmin funktiolla setPanelVisible — tyypillisesti siitä komennosta, joka sen »avaa«. Näyttäminen myös nostaa paneelin niiden telakoiden yläpuolelle, joiden kanssa se jakaa alueen, joten se todella päätyy käyttäjän eteen. Kutsu on idempotentti (toista sama arvo vapaasti) eikä tee hiljaisesti mitään poletille, joka ei enää elä, joten mitään ei tarvitse seurata eikä QDockWidget-oliota tavoitella:

ctx.docks().setPanelVisible(m_dock.id(), true);    // reveal + raise
ctx.docks().setPanelVisible(m_dock.id(), false);   // hide, keep registered

isPanelVisible kertoo, onko paneeli auki ikkunan asettelussa — tila, jonka setPanelVisible kirjoittaa ja jonka käyttäjän vaihtokytkin tai sulkemispainike kääntää. Se ei tarkoita »ovatko sen pikselit näytöllä«: toisen taakse välilehdeksi jäänyt paneeli on auki; piilotettuna rekisteröity eikä koskaan näytetty paneeli ei ole, eikä myöskään tunnus, joka ei ole enää elossa. Kysyminen ei koskaan luo paneelia. Yhdessä nämä kaksi kutsua antavat liitännäisen näyttää paneelinsa tehtävän ajaksi ja palauttaa sen jälkeen täsmälleen sen, mitä käyttäjällä oli:

const bool wasOpen = ctx.docks().isPanelVisible(m_dock.id());
ctx.docks().setPanelVisible(m_dock.id(), true);
// ... when the task ends:
ctx.docks().setPanelVisible(m_dock.id(), wasOpen);

Yhteen kuuluvat paneelit — tabGroup

Paneelit, jotka antavat saman ei-tyhjän tabGroup-arvon ja saman preferredArea-arvon, jakavat yhden telakan välilehtinä sen sijaan, että olisivat vierekkäin — viidestä debuggerin paneelista tulee yksi välilehtirivi ikkunan alareunaan. Anna tunnisteelle nimiavaruus kuten paneelin tunnisteelle ("myplugin/panels"); sama tunniste kahdella alueella muodostaa kaksi pinoa, ja liitännäinen voi tarkoituksella liittyä toisen liitännäisen ryhmään. Välilehdet näkyvät rekisteröintijärjestyksessä; ensimmäinen avoin paneeli on edessä, kunnes käyttäjä tai setPanelVisible nostaa toisen esiin.

desc.preferredArea = DockArea::Bottom;
desc.tabGroup      = "myplugin/panels";

Ryhmä on vain oletusjärjestely. Käyttäjän tallentama asettelu voittaa: paneeli, jonka käyttäjä on vetänyt pois pinosta, toiselle alueelle tai kelluvaan ikkunaan, pysyy siellä uudelleenkäynnistysten yli. Paneeli, jota tallennettu asettelu ei vielä tunne — uusi tässä liitännäisesi versiossa — liittyy ryhmäänsä.

Täysivaltaiset välilehtityypit

Kun ominaisuus ansaitsee oman välilehtilajinsa (vertailutulokset, lokinäkymä, heksakatselin) tekstipuskurin tai sivupaneelin sijaan, rekisteröi välilehtityyppi funktiolla ctx.documents():

struct DocumentTabTypeDescriptor {
    std::string typeId;         // "log-view" -- lowercase-with-hyphens
    std::string displayName;    // localized type name
    DefaultPlacement defaultPlacement;  // ActiveGroup / NewGroupRight / NewGroupBottom
};

m_type = ctx.documents().registerType(descriptor, std::move(factory));
DocumentTabId tab = ctx.documents().openTab("log-view", {/* params */});

Isäntä sijoittaa IDocumentTab-oliosi välilehtipalkkiin editorivälilehtien rinnalle ja ohjaa tallennuksen ja sulkemisen sille. openTab() palauttaa DocumentTabId-kahvan (0, kun mitään ei avattu — tuntematon tyyppi, tyhjä tehdastulos); tunnisteet ovat vakaita välilehden elinajan ja niitä ei koskaan käytetä uudelleen saman ajon aikana, joten vanhentunut kahva luetaan suljetuksi eikä jonkun toisen välilehdeksi. Säilytä se seurataksesi välilehteä jälkeenpäin, tai heitä se pois kertaluonteista avausta varten:

bool open = ctx.documents().isTabOpen(tab);   // false once the user closed it
ctx.documents().activateTab(tab);             // raise + focus (no-op if closed)
bool closed = ctx.documents().closeTab(tab);  // honours canClose(); false on veto

Idiomi »avaa tai tuo esiin« on activateTab(), kun isTabOpen() sanoo kahvan olevan vielä elossa, ja muutoin openTab(). Välilehdet, jotka palauttavat ei-tyhjän arvon sessionParams(), tallennetaan istunnon säilytykseen ja toistetaan funktiolla openTab() palautuksessa; jos omistava liitännäinen puuttuu palautushetkellä, isäntä kokoaa orvot kohdat yhdeksi viestiksi sen sijaan että pudottaisi ne. Omatekoisia esimerkkejä: Vertaa ja Lokianalyysi.

Vertailunäkymä

ctx.compare() avaa editorin kahden ruudun vertailuikkunan mille tahansa lähdeparille — tiedostopoluille, avoimille asiakirjoille tai muistissa olevalle tekstille:

CompareSource left{...}, right{...};
bool shown = ctx.compare().openCompare(left, right, CompareOptions{});

Aina turvallista kutsua: isäntä, jolla ei ole vertailunäkymää, toimittaa tyhjän toteutuksen, joka palauttaa arvon false.

Tilarivi

Kaksi riippumatonta kanavaa — ohimenevät ilmoitukset ja pysyvät, liitännäisen omistamat osiot:

// Transient: replaces/queues by severity; do NOT use for live state.
ctx.statusBar().showMessage("Wrapped", 2000, StatusLevel::Info);

// Persistent segment: an always-on indicator you own and refresh.
m_segment = ctx.statusBar().addSegment("wordcount.total");
m_segment->setText("2,431 words");
m_segment->setVisible(false);        // hide without destroying
// dropping the unique_ptr removes the segment

Vakavampi ilmoitus korvaa näytöllä olevan heti; vähemmän vakavaa voidaan pidättää hetken, jottei Error-tason ilmoitusta pyyhitä heti pois rutiininomaisella Info-ilmoituksella. Osion päivittäminen ei koskaan tuhoa ilmoitusta eikä päinvastoin.

Editorin kontekstivalikko

Toisin kuin IMenuRegistry — kiinteät valikkopalkin kohdat, jotka on sidottu komentoihin — kontekstivalikon kohdat ovat dynaamisia: jokaisella hiiren oikean napin napsautuksella tekstialueella — tai sen vieressä olevassa marginaalissa — isäntä kysyy kultakin rekisteröidyltä toimittajalta, mikä pätee napsautetussa kohdassa, ja lisää vastaukset sisäänrakennettujen muokkaustoimintojen (Kumoa/Tee uudelleen, Leikkaa/Kopioi/Liitä, Valitse kaikki) alle. Pyyntö kertoo, missä napsautus tapahtui:

enum class ContextMenuArea : std::int32_t { Text = 0, Margin = 1 };

struct ContextMenuRequest {
    DocumentId      doc;
    std::int64_t    position; // byte offset; the start of `line` for Margin
    std::int64_t    line;     // 0-based
    ContextMenuArea area;
};
  • area on Margin, kun napsautetaan mitä tahansa saraketta tekstin vasemmalla puolella (rivinumerot, kirjanmerkkisarake, laskostussarake), ja muuten Text; näppäimistöltä avattu valikko on aina Text.
  • line on napsautettu rivi (näppäimistövalikossa kohdistimen rivi), joten sinun ei koskaan tarvitse muuntaa position-arvoa itse.
  • position on tavusiirtymä napsautuksen kohdalla tekstissä (näppäimistövalikossa kohdistin). Marginaalin napsautuksen alla ei ole merkkiä, joten siellä position on rivin line alun siirtymä — kelvollinen, mutta älä koskaan lue siitä saraketta.

Päätä alueittain, mitä tarjoat: rivitason kohdat (Add Conditional Breakpoint…, Revert Hunk) kuuluvat marginaaliin, sanatason kohdat tekstiin. Oikoluku ei esimerkiksi tarjoa mitään marginaalin napsautukselle.

class MyProvider final : public plugin::IContextMenuProvider
{
    std::vector<plugin::ContextMenuItem>
    contextMenuItems(const plugin::ContextMenuRequest& req) override
    {
        // Word-level entries: text clicks only. Empty vector = decline.
        if (req.area != plugin::ContextMenuArea::Text) { return {}; }
        if (!appliesAt(req.doc, req.position)) { return {}; }
        return {
            { "Do the thing", [this, req] { doTheThing(req); } },
            { "", {} },                       // empty title = separator
            { "Another action", [this] { another(); } },
        };
    }
};

m_token = ctx.contextMenu().registerProvider(m_provider); // RAII token

Toimittajat ajetaan käyttöliittymäsäikeessä käyttäjän odottaessa valikkoa — vastaa muistissa olevasta tilasta, älä koskaan estä. Kohtien otsikot ovat dynaamisia merkkijonoja, jotka liitännäinen kääntää itse. Oikoluku käyttää tätä ehdotuksiinsa sekä kohtiin Lisää sanakirjaan ja Ohita tässä istunnossa.

Teemapaletti

Liitännäiset, jotka piirtävät omia värejään, rekisteröivät nimettyjä luokkia vaaleine ja tummine oletuksineen ja ratkaisevat ne RGB-arvoiksi piirtohetkellä — älä koskaan kovakoodaa värejä:

// ThemeCategory: {id, displayLabel, defaultLight, defaultDark}.
// The id is namespaced "<plugin.id>/<role>" by convention.
m_cat = ctx.themePalette().registerCategory(
    {"com.example.myplugin/match", "Match highlight",
     /*light*/ 0xFFF3B0, /*dark*/ 0x8A6D00});

std::uint32_t rgb = ctx.themePalette().resolveColor("com.example.myplugin/match");
bool themed       = ctx.themePalette().hasOverride("com.example.myplugin/match");
std::string variant = ctx.themePalette().activeVariant();  // "light" / "dark" / ""

resolveColor palauttaa teeman ohituksen, kun sellainen on, muutoin rekisteröidyn oletuksen aktiiviselle muunnelmalle, ja arvon 0x000000 rekisteröimättömälle tunnisteelle (käsittele sitä merkityksessä »käytä omaa varavaihtoehtoani«).

Kukin rekisteröity luokka ilmestyy automaattisesti Teemaeditoriin, ja käyttäjän ohitukset kulkevat edestakaisin teematiedostossa avaimen pluginColors alla. Piirrä uudelleen tapahtumassa events::ThemeChanged.