Dokker, faner, statuslinje og tema¶
De flatene i grensesnittet et programtillegg kan bidra til, utover menyene. De følger alle det samme mønsteret: beskriv det du vil ha, som backend-nøytrale data, gi verten en fabrikk, og behold det RAII-symbolet du får tilbake.
Dokkpaneler¶
ctx.docks() legger til et dokkbart sidepanel. Den offentlige flaten sender med vilje
kontrollen videre som en ugjennomsiktig void* — den Qt-baserte verten omformer den til
en QWidget*; et Python-programtillegg bygger i stedet det samme panelet deklarativt (se
Python-dokumentasjonen for ctx.docks).
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));
Verten lager panelet dovent (ved første visning) og kan lage det på nytt etter en
tilbakestilling av oppsettet — hold tilstand per panel utenfor kontrollen, eller bygg den
opp igjen i create(). DockToken fjerner panelet når det ødelegges.
Piksler eller en andel av vinduet — proportionalSize¶
0 (standardvalget) er den enkle oppførselen: panelet holder en størrelse i piksler, og det å
endre vindusstørrelsen lar det være. Det er hva et verktøypanel vil — en disposisjon eller
en resultatliste er like nyttig ved én bredde uansett hva vinduet gjør. En verdi i (0, 1)
erklærer i stedet en standardandel av hovedvinduet: av bredden i områdene Left/Right, av
høyden i Top/Bottom. Det er hva en forhåndsvisning vil — maksimer vinduet, og den
gjengitte siden blir bredere med det.
Det er nettopp derfor et enkelt tall, slik at en annen backend enn C++ kan innfri den samme
kontrakten (Python-backenden tar det som
proportional_size). Dette garanterer verten rundt
det:
- Du oppgir bare standarden. I det øyeblikket brukeren drar skilleren til panelet, tar verten i bruk den andelen de valgte, husker den sammen med vindusoppsettet, og verdien din gjelder aldri mer for det panelet.
- Verdier utenfor området klemmes, ikke avvises — andelen holdes innenfor et fornuftig
belte, og både panelet ditt og editoren beholder en minste utstrekning, så ingen av dem kan
klappes sammen. En negativ eller ikke-endelig verdi leses som
0. - Panelets eget minimum vinner. Verten ber aldri oppsettet om mindre enn kontrollen kan presses inn i; panelet stopper ved den grensen uten å glemme andelen, og går tilbake til den når vinduet blir bredere igjen. Et panel som er ment å være proporsjonalt, bør derfor kunne flyte om — en verktøylinje som stabler gruppene sine i stedet for å kreve én rad, gjør at panelet fortsatt kan smalnes.
- Flytende og skjulte paneler hoppes over. Et flytende panel er sitt eget vindu og beholder størrelsen sin; et skjult får størrelse når det kommer fram, et gjeninnfestet når det er tilbake.
- Paneler i faner deler én plass, så det er panelet forrest som styrer den — det brukeren ser på, er det hvis andel betyr noe. To paneler ved siden av hverandre på én akse deler ett budsjett og skaleres sammen, med forholdet bevart.
Et panel som er registrert med initiallyVisible = false, kommer fram senere med
setPanelVisible — typisk fra den kommandoen som «åpner» det. Å vise det løfter også
panelet over de dokkene det deler område med, så det virkelig ender opp foran brukeren.
Kallet kan gjentas (gjenta den samme verdien fritt) og gjør i stillhet ingenting for et
symbol som ikke lenger lever, så det er ingenting å holde styr på og ingen QDockWidget å
strekke seg etter:
ctx.docks().setPanelVisible(m_dock.id(), true); // reveal + raise
ctx.docks().setPanelVisible(m_dock.id(), false); // hide, keep registered
isPanelVisible svarer på om et panel er åpent i vindusoppsettet —
tilstanden som setPanelVisible skriver, og som brukerens bryter eller
lukkeknapp slår om. Det betyr ikke «er pikslene på skjermen»: et panel som
ligger som fane bak et annet, er åpent; et panel som ble registrert skjult og
aldri vist, er det ikke, og heller ikke et token som ikke lenger lever. Å
spørre oppretter aldri panelet. Sammen lar de to kallene et programtillegg vise
panelene sine mens en oppgave pågår, og så sette tilbake nøyaktig det brukeren
hadde:
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);
Paneler som hører sammen — tabGroup¶
Paneler som oppgir den samme ikke-tomme tabGroup og den samme
preferredArea, deler én dokk som faner i stedet for å ligge side om side
— fem feilsøkerpaneler blir én fanestripe nederst i vinduet. Gi ID-en et
navnerom som en panel-ID ("myplugin/panels"); den samme ID-en i to områder
gir to stabler, og et programtillegg kan med vilje bli med i gruppen til et
annet programtillegg. Fanene vises i registreringsrekkefølge; det første åpne
panelet ligger foran til brukeren eller setPanelVisible henter fram et annet.
Gruppen er bare standardoppstillingen. Et oppsett brukeren har lagret, går foran: et panel brukeren har dratt ut av stabelen, til et annet område eller til et flytende vindu, blir der mellom omstarter. Et panel det lagrede oppsettet ennå ikke kjenner — nytt i denne versjonen av programtillegget ditt — blir med i gruppen sin.
Fullverdige fanetyper¶
Når en funksjon fortjener sitt eget slag fane (sammenligningsresultater, en loggvisning,
en heksfremviser) heller enn en tekstbuffer eller et sidepanel, så registrer en fanetype
med 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 */});
Verten plasserer din IDocumentTab i faneraden ved siden av redigererfanene og sender
lagring og lukking videre til den. openTab() gir et DocumentTabId-håndtak tilbake
(0 når ingenting ble åpnet — ukjent type, tomt fabrikkresultat); id-er er stabile fanens
levetid ut og gjenbrukes aldri innenfor en kjøring, så et foreldet håndtak leses som
lukket og ikke som en annens fane. Behold det for å følge fanen etterpå, eller kast det
for en åpning du ikke tenker mer på:
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
Idiomet «åpne eller vis fram» er activateTab() når isTabOpen() sier at håndtaket
fortsatt lever, og ellers openTab(). Faner som gir et ikke-tomt sessionParams()
tilbake, fanges av øktbevaringen og spilles av igjen gjennom openTab() ved
gjenopprettingen; er det eiende programtillegget borte ved gjenopprettingen, samler verten
de foreldreløse postene i én melding i stedet for å la dem falle bort. Egetutviklede
eksempler: Sammenlign og Logganalyse.
Sammenligningsvisningen¶
ctx.compare() åpner redigererens sammenligningsvindu med to ruter for et hvilket som
helst par av kilder — filstier, åpne dokumenter eller tekst i minnet:
CompareSource left{...}, right{...};
bool shown = ctx.compare().openCompare(left, right, CompareOptions{});
Alltid trygt å kalle: en vert uten en sammenligningsvisning leverer en tom utgave som gir
false tilbake.
Statuslinje¶
To uavhengige kanaler — forbigående meldinger og varige avsnitt som programtillegget eier:
// 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
En melding med høyere alvorsgrad erstatter den som er på skjermen, med det samme; en med
lavere alvorsgrad kan bli holdt tilbake et øyeblikk, så en Error ikke straks viskes ut
av en rutinemessig Info. Å oppdatere et avsnitt ødelegger aldri en melding, og omvendt.
Redigererens kontekstmeny¶
I motsetning til IMenuRegistry — faste poster på menylinjen bundet til kommandoer — er
kontekstmenypostene dynamiske: ved hvert høyreklikk i tekstområdet — eller i
margen ved siden av — spør verten hver registrert leverandør hva som gjelder
der det ble klikket, og legger svarene til under de innebygde
redigeringshandlingene (Angre og Gjør om, Klipp ut, Kopier og Lim inn, Merk
alt). Forespørselen sier hvor klikket var:
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;
};
areaerMarginved et klikk i en hvilken som helst kolonne til venstre for teksten (linjenumre, bokmerkekolonnen, brettekolonnen) og ellersText; en meny som åpnes fra tastaturet, er alltidText.lineer linjen det ble klikket på (markørens linje for en tastaturmeny), så du trenger aldri regne ompositionselv.positioner byteforskyvningen under klikket i teksten (markøren for en tastaturmeny). Et klikk i margen har ikke noe tegn under seg, så der erpositionforskyvningen til starten avline— gyldig, men les aldri en kolonne ut av den.
Bestem per område hva du tilbyr: oppføringer på linjenivå (Add Conditional Breakpoint…, Revert Hunk) hører til i margen, oppføringer på ordnivå i teksten. Stavekontrollen, for eksempel, tilbyr ingenting for et klikk i margen.
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
Leverandører kjører på grensesnittråden mens brukeren venter på menyen — svar ut fra tilstand i minnet, og sperr aldri. Postenes titler er dynamiske tekststrenger som programtillegget selv oversetter. Stavekontrollen bruker dette til forslagene sine og til postene Legg til i ordlisten og Ignorer i denne økten.
Temapaletten¶
Programtillegg som tegner egne farger, registrerer navngitte kategorier med lyse og mørke standardverdier og oversetter dem til RGB på tegnetidspunktet — hardkod aldri farger:
// 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 gir temaets overstyring tilbake når det finnes en, ellers den registrerte
standarden for den aktive varianten, og 0x000000 for en uregistrert id (behandl det som
«bruk min egen reserve»).
Hver registrert kategori kommer av seg selv fram i Temaredigereren, og brukerens
overstyringer lagres og leses igjen i temafilen under pluginColors. Tegn på nytt ved
events::ThemeChanged.