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.
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.
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;
};
areaonMargin, kun napsautetaan mitä tahansa saraketta tekstin vasemmalla puolella (rivinumerot, kirjanmerkkisarake, laskostussarake), ja muutenText; näppäimistöltä avattu valikko on ainaText.lineon napsautettu rivi (näppäimistövalikossa kohdistimen rivi), joten sinun ei koskaan tarvitse muuntaaposition-arvoa itse.positionon tavusiirtymä napsautuksen kohdalla tekstissä (näppäimistövalikossa kohdistin). Marginaalin napsautuksen alla ei ole merkkiä, joten sielläpositionon rivinlinealun 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.