Dokken, tabbladen, statusbalk en thema¶
De interfacevlakken waaraan een plug-in kan bijdragen, buiten de menu's om. Ze volgen alle hetzelfde patroon: beschrijf wat u wilt in backend-neutrale gegevens, geef de host een fabriek, en houd het teruggegeven RAII-token vast.
Dokpanelen¶
ctx.docks() voegt een dokbaar zijpaneel toe. Het publieke vlak geeft de widget
met opzet door als een ondoorzichtige void* — de op Qt gebouwde host zet die om
naar QWidget*; een Python-plug-in bouwt hetzelfde paneel in plaats daarvan
declaratief op (zie
de Python-naslag voor 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));
De host maakt het paneel lui aan (bij de eerste weergave) en kan het na het
terugzetten van een indeling opnieuw aanmaken — houd de staat per paneel buiten de
widget of bouw hem opnieuw op in create(). Het DockToken verwijdert het paneel
wanneer het vernietigd wordt.
Pixels of een aandeel van het venster — proportionalSize¶
0 (de standaard) is het gewone gedrag: het paneel houdt een grootte in pixels, en het
venster van grootte veranderen laat het met rust. Dat is wat een gereedschapspaneel wil
— een overzicht of een resultatenlijst is bij één breedte even nuttig, wat het venster ook
doet. Een waarde in (0, 1) verklaart in plaats daarvan een standaardaandeel van het
hoofdvenster: van de breedte in de gebieden Left/Right, van de hoogte in Top/Bottom. Dat
is wat een voorbeeld wil — maximaliseer het venster en de weergegeven pagina wordt
breder met het venster mee.
Het is juist daarom een eenvoudig getal, zodat een backend buiten C++ hetzelfde contract kan
nakomen (de Python-backend neemt het als
proportional_size). Wat de host eromheen garandeert:
- U levert alleen de standaard. Op het moment dat de gebruiker de scheiding van het paneel versleept, neemt de host het aandeel over dat hij koos, onthoudt het bij de vensterindeling, en uw waarde geldt nooit meer voor dat paneel.
- Waarden buiten het bereik worden afgekapt, niet geweigerd — het aandeel blijft binnen
een verstandige band, en zowel uw paneel als de editor houden een minimale omvang, zodat
geen van beide kan worden dichtgeklapt. Een negatieve of niet-eindige waarde leest als
0. - Het eigen minimum van uw paneel wint. De host vraagt de indeling nooit om minder dan waarin de widget geperst kan worden; het paneel stopt op die bodem zonder het aandeel te vergeten, en gaat ernaar terug wanneer het venster weer breder wordt. Een paneel dat proportioneel bedoeld is, moet dus kunnen herschikken — een werkbalk die haar groepen stapelt in plaats van één rij te eisen, houdt het paneel smaller te maken.
- Zwevende en verborgen panelen worden overgeslagen. Een zwevend paneel is zijn eigen venster en houdt zijn grootte; een verborgen paneel krijgt zijn grootte wanneer het tevoorschijn komt, een weer aangekoppeld paneel wanneer het terug is.
- Panelen in tabbladen delen één plaats, dus het paneel op de voorgrond bepaalt die — dat wat de gebruiker bekijkt, is het paneel waarvan het aandeel iets betekent. Twee panelen naast elkaar op één as delen één budget en worden samen geschaald, met behoud van hun verhouding.
Een paneel dat met initiallyVisible = false geregistreerd is wordt later met
setPanelVisible tevoorschijn gehaald — doorgaans vanuit de opdracht die het
«opent». Tonen brengt het paneel ook boven de dokken waarmee het een gebied deelt,
zodat het werkelijk voor de gebruiker eindigt. De aanroep is idempotent (herhaal
dezelfde waarde gerust) en is een stille niet-bewerking voor een token dat niet
meer leeft, dus er valt niets te volgen en er is geen QDockWidget om naar te
grijpen:
ctx.docks().setPanelVisible(m_dock.id(), true); // reveal + raise
ctx.docks().setPanelVisible(m_dock.id(), false); // hide, keep registered
isPanelVisible beantwoordt of een paneel in de vensterindeling open is —
de staat die setPanelVisible schrijft en die de schakelaar of de sluitknop
van de gebruiker omzet. Het betekent niet «staan zijn pixels op het scherm»:
een paneel dat als tabblad achter een ander zit, is open; een paneel dat
verborgen is geregistreerd en nooit getoond, is het niet, en een token dat niet
meer leeft evenmin. Vragen maakt het paneel nooit aan. Samen laten de twee
aanroepen een plug-in zijn panelen tonen zolang een taak duurt en daarna
precies terugzetten wat de gebruiker had:
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);
Panelen die bij elkaar horen — tabGroup¶
Panelen die dezelfde niet-lege tabGroup en dezelfde preferredArea opgeven,
delen één dok als tabbladen in plaats van naast elkaar te staan — vijf
debuggerpanelen worden één tabstrook onder in het venster. Geef de id een
naamruimte zoals een paneel-id ("myplugin/panels"); dezelfde id in twee
gebieden vormt twee stapels, en een plug-in mag bewust bij de groep van een
andere plug-in aansluiten. Tabbladen verschijnen in volgorde van registratie;
het eerste open paneel ligt vooraan totdat de gebruiker of setPanelVisible
een ander naar voren haalt.
De groep is alleen de standaardindeling. Een door de gebruiker opgeslagen indeling gaat voor: een paneel dat de gebruiker uit de stapel, naar een ander gebied of in een zwevend venster heeft gesleept, blijft daar na een herstart. Een paneel dat de opgeslagen indeling nog niet kent — nieuw in deze versie van je plug-in — sluit zich bij zijn groep aan.
Volwaardige tabbladsoorten¶
Wanneer een functie een eigen soort tabblad verdient (vergelijkingsresultaten, een
logweergave, een hexweergave) in plaats van een tekstbuffer of een zijpaneel,
registreert u een tabbladsoort met 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 */});
De host plaatst uw IDocumentTab in de tabbladbalk naast de editortabbladen en
leidt opslaan en sluiten ernaartoe. openTab() levert een greep DocumentTabId
op (0 wanneer er niets geopend is — onbekende soort, fabriek die niets
opleverde); id's zijn stabiel zolang het tabblad leeft en worden binnen één
uitvoering nooit hergebruikt, zodat een verouderde greep als gesloten gelezen wordt
en niet als het tabblad van iemand anders. Houd hem vast om het tabblad daarna te
volgen, of laat hem vallen voor een openen-en-vergeten:
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
Het patroon «openen of tonen» is activateTab() wanneer isTabOpen() zegt dat de
greep nog leeft, en anders openTab(). Tabbladen die een niet-lege
sessionParams() opleveren worden door de sessiebewaring vastgelegd en bij het
herstellen via openTab() afgespeeld; ontbreekt de bezittende plug-in op het
moment van herstellen, dan rolt de host de verweesde items in één bericht samen in
plaats van ze te laten vallen. Voorbeelden van eigen makelij: Vergelijken en de
Logboekanalyse.
De vergelijkingsweergave¶
ctx.compare() opent het vergelijkingsvenster met twee deelvensters van de editor
voor elk paar bronnen — bestandspaden, geopende documenten of tekst in het
geheugen:
CompareSource left{...}, right{...};
bool shown = ctx.compare().openCompare(left, right, CompareOptions{});
Altijd veilig aan te roepen: een host zonder vergelijkingsweergave levert een
niet-bewerking die false oplevert.
Statusbalk¶
Twee onafhankelijke kanalen — vluchtige meldingen en blijvende segmenten die de plug-in bezit:
// 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
Een melding met een hogere ernstgraad vervangt die op het scherm meteen; een met
een lagere graad kan even tegengehouden worden, zodat een Error niet op slag door
een routinematige Info weggevaagd wordt. Een segment verversen overschrijft nooit
een melding, en omgekeerd.
Contextmenu van de editor¶
Anders dan IMenuRegistry — statische items op de menubalk die aan opdrachten
gebonden zijn — zijn items van het contextmenu dynamisch: bij elke rechtsklik
in het tekstgebied — of in de marge ernaast — vraagt de host aan elke
geregistreerde leverancier wat er op de aangeklikte plek van toepassing is, en
voegt hij de antwoorden onder de ingebouwde bewerkacties toe (Ongedaan
maken/Opnieuw, Knippen/Kopiëren/Plakken, Alles selecteren). Het verzoek zegt
waar de klik was:
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;
};
areaisMarginbij een klik in een willekeurige kolom links van de tekst (regelnummers, de bladwijzerkolom, de vouwkolom) en andersText; een menu dat via het toetsenbord is geopend, is altijdText.lineis de aangeklikte regel (de regel van de cursor bij een toetsenbordmenu), dus je hoeftpositionnooit zelf om te rekenen.positionis de byte-offset onder de klik in de tekst (de cursor bij een toetsenbordmenu). Onder een klik in de marge staat geen teken, dus daar ispositionde offset van het begin vanline— geldig, maar lees er nooit een kolom uit af.
Bepaal per gebied wat je aanbiedt: items op regelniveau (Add Conditional Breakpoint…, Revert Hunk) horen in de marge, items op woordniveau in de tekst. De spellingcontrole biedt bijvoorbeeld niets aan bij een klik in de marge.
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
Leveranciers draaien op de interfacethread terwijl de gebruiker op het menu wacht — antwoord vanuit staat in het geheugen, blokkeer nooit. Itemtitels zijn dynamische tekenreeksen die de plug-in zelf vertaalt. De spellingcontrole gebruikt dit voor haar items met suggesties, Aan woordenboek toevoegen en Negeren in deze sessie.
Themapalet¶
Plug-ins die eigen kleuren schilderen registreren benoemde categorieën met standaarden voor licht en donker en lossen die bij het tekenen naar RGB op — schrijf kleuren nooit hard in de code:
// 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 levert de overschrijving van het thema op wanneer die er is, anders
de geregistreerde standaard voor de actieve variant, en 0x000000 voor een niet
geregistreerde id (behandel dat als «gebruik mijn eigen terugvalwaarde»).
Elke geregistreerde categorie verschijnt automatisch in de Thema-editor, en
overschrijvingen van de gebruiker gaan heen en weer in het themabestand onder
pluginColors. Teken opnieuw bij events::ThemeChanged.