Dockor, flikar, statusrad och tema¶
De gränssnittsytor en insticksmodul kan bidra till, utöver menyerna. Alla följer samma mönster: beskriv vad du vill ha i backend-neutrala data, ge värden en fabrik, och behåll den RAII-symbol du får tillbaka.
Dockpaneler¶
ctx.docks() lägger till en dockningsbar sidopanel. Den publika ytan skickar med
avsikt widgeten som en ogenomskinlig void* — den Qt-baserade värden konverterar
den till QWidget*; en Python-insticksmodul bygger i stället samma panel
deklarativt (se
Python-referensen för 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));
Värden instansierar panelen lat (vid första visningen) och kan skapa om den
efter en återställning av layouten — håll tillståndet per panel utanför widgeten
eller bygg upp det på nytt i create(). DockToken tar bort panelen när den
förstörs.
Pixlar eller en andel av fönstret — proportionalSize¶
0 (standardvärdet) är det enkla beteendet: panelen håller en storlek i pixlar, och att
ändra fönstrets storlek lämnar den i fred. Det är vad en verktygspanel vill — en
dispositionsvy eller en resultatlista är lika användbar vid en bredd vad fönstret än gör. Ett
värde i (0, 1) förklarar i stället en standardandel av huvudfönstret: av dess bredd i
områdena Left/Right, av dess höjd i Top/Bottom. Det är vad en förhandsgranskning vill —
maximera fönstret och den renderade sidan blir bredare med det.
Det är just därför ett enkelt tal, så att en backend utanför C++ kan uppfylla samma kontrakt
(Python-backenden tar det som
proportional_size). Vad värden garanterar omkring
det:
- Du anger bara standardvärdet. I det ögonblick användaren drar panelens avdelare tar värden över den andel de valde, kommer ihåg den med fönsterlayouten, och ditt värde gäller aldrig mer för den panelen.
- Värden utanför intervallet klipps, inte avvisas — andelen hålls inom ett förnuftigt
band, och både din panel och editorn behåller en minsta utsträckning, så ingen av dem kan
fällas ihop. Ett negativt eller icke-ändligt värde läses som
0. - Panelens eget minimum vinner. Värden ber aldrig layouten om mindre än vad widgeten kan pressas in i; panelen stannar vid det golvet utan att glömma andelen, och återgår till den när fönstret breddas igen. En panel som är tänkt att vara proportionell bör därför kunna flöda om — ett verktygsfält som staplar sina grupper i stället för att kräva en enda rad gör panelen möjlig att göra smalare.
- Flytande och dolda paneler hoppas över. En flytande panel är sitt eget fönster och behåller sin storlek; en dold får sin storlek när den tas fram, en åter dockad när den är tillbaka.
- Paneler i flikar delar en enda plats, så det är panelen framtill som styr den — den användaren tittar på är den vars andel betyder något. Två paneler sida vid sida på samma axel delar en enda budget och skalas tillsammans, med kvoten bevarad.
En panel som registrerats med initiallyVisible = false tas fram senare med
setPanelVisible — vanligtvis från det kommando som «öppnar» den. Att visa lyfter
också panelen över de dockor den delar område med, så att den verkligen hamnar
framför användaren. Anropet är idempotent (upprepa samma värde fritt) och är en tyst
nulloperation för en symbol som inte längre lever, så det finns inget att hålla reda
på och ingen QDockWidget att gripa efter:
ctx.docks().setPanelVisible(m_dock.id(), true); // reveal + raise
ctx.docks().setPanelVisible(m_dock.id(), false); // hide, keep registered
isPanelVisible svarar på om en panel är öppen i fönsterlayouten — det
tillstånd som setPanelVisible skriver och som användarens växlare eller
stängningsknapp slår om. Det betyder inte «syns dess pixlar på skärmen»: en
panel som ligger som flik bakom en annan är öppen, en panel som registrerats
dold och aldrig visats är det inte, och inte heller en token som inte längre
lever. Att fråga skapar aldrig panelen. Tillsammans låter de två anropen en
insticksmodul visa sina paneler medan en uppgift pågår och sedan återställa
exakt det användaren hade:
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ör ihop — tabGroup¶
Paneler som anger samma icke-tomma tabGroup och samma preferredArea delar
en docka som flikar i stället för att ligga sida vid sida — fem
felsökarpaneler blir en enda flikrad längst ned i fönstret. Ge id:t ett
namnområde som ett panel-id ("myplugin/panels"); samma id i två områden ger
två staplar, och en modul får avsiktligt gå med i en annan moduls grupp.
Flikarna visas i registreringsordning; den första öppna panelen ligger överst
tills användaren eller setPanelVisible lyfter fram en annan.
Gruppen är bara standardarrangemanget. En layout som användaren har sparat går före: en panel som användaren dragit ut ur stapeln, till ett annat område eller till ett flytande fönster stannar där mellan omstarter. En panel som den sparade layouten ännu inte känner till — ny i den här versionen av din modul — går med i sin grupp.
Fullvärdiga fliktyper¶
När en funktion förtjänar sin egen sorts flik (jämförelseresultat, en loggvy, en
hexvisning) snarare än en textbuffert eller en sidopanel, registrerar du en fliktyp
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 */});
Värden placerar din IDocumentTab i flikraden bredvid redigerarflikarna och leder
sparande och stängning dit. openTab() returnerar ett handtag DocumentTabId (0
när inget öppnades — okänd typ, fabrik som inte gav något); id:n är stabila så länge
fliken lever och återanvänds aldrig inom en körning, så att ett inaktuellt handtag
läses som stängt snarare än som någon annans flik. Behåll det för att följa fliken
efteråt, eller släng det för ett öppna-och-glöm:
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
Mönstret «öppna eller lyft fram» är activateTab() när isTabOpen() säger att
handtaget fortfarande lever, annars openTab(). Flikar som returnerar en icke-tom
sessionParams() fångas av sessionsbevarandet och spelas upp via openTab() vid
återställning; saknas den ägande insticksmodulen vid återställningen rullar värden
ihop de föräldralösa posterna i ett enda meddelande i stället för att släppa dem.
Egenutvecklade exempel: Jämför och Logganalysen.
Jämförelsevyn¶
ctx.compare() öppnar redigerarens jämförelsefönster med två rutor för vilket par
källor som helst — filsökvägar, öppna dokument, eller text i minnet:
CompareSource left{...}, right{...};
bool shown = ctx.compare().openCompare(left, right, CompareOptions{});
Alltid säkert att anropa: en värd utan jämförelsevy tillhandahåller en nulloperation
som returnerar false.
Statusrad¶
Två oberoende kanaler — flyktiga aviseringar och bestående segment som insticksmodulen äger:
// 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 avisering med högre allvarlighetsgrad ersätter den på skärmen omedelbart; en med
lägre grad kan hållas tillbaka en kort stund så att ett Error inte genast sopas
bort av ett rutinmässigt Info. Att uppdatera ett segment skriver aldrig över en
avisering, och tvärtom.
Redigerarens snabbmeny¶
Till skillnad från IMenuRegistry — statiska poster i menyraden bundna till
kommandon — är snabbmenyns poster dynamiska: vid varje högerklick i
textområdet — eller i marginalen bredvid det — frågar värden varje registrerad
leverantör vad som gäller på den klickade platsen och lägger svaren under de
inbyggda redigeringsåtgärderna (Ångra/Gör om, Klipp ut/Kopiera/Klistra in,
Markera allt). Förfrågan talar om var klicket 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;
};
areaärMarginvid ett klick i någon kolumn till vänster om texten (radnummer, bokmärkeskolumnen, vikningskolumnen) och annarsText; en meny som öppnas från tangentbordet är alltidText.lineär den klickade raden (markörens rad för en tangentbordsmeny), så du behöver aldrig räkna ompositionsjälv.positionär byteförskjutningen under klicket i texten (markören för en tangentbordsmeny). Under ett klick i marginalen finns inget tecken, så där ärpositionförskjutningen för början avline— giltig, men läs aldrig ut en kolumn ur den.
Bestäm per område vad du erbjuder: poster på radnivå (Add Conditional Breakpoint…, Revert Hunk) hör hemma i marginalen, poster på ordnivå i texten. Stavningskontrollen, till exempel, erbjuder ingenting vid ett klick i marginalen.
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
Leverantörer körs på gränssnittstråden medan användaren väntar på menyn — svara från tillstånd i minnet, blockera aldrig. Posttitlarna är dynamiska strängar som insticksmodulen själv översätter. Stavningskontrollen använder detta för sina poster med förslag, Lägg till i ordlistan och Ignorera under den här sessionen.
Temapalett¶
Insticksmoduler som målar egna färger registrerar namngivna kategorier med standardvärden för ljust och mörkt och löser dem till RGB vid ritningen — hårdkoda aldrig färger:
// 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 returnerar temats åsidosättning när en sådan finns, annars det
registrerade standardvärdet för den aktiva varianten, och 0x000000 för ett
oregistrerat id (behandla det som «använd mitt eget reservvärde»).
Varje registrerad kategori dyker automatiskt upp i Temaredigeraren, och användarens
åsidosättningar går fram och tillbaka i temafilen under pluginColors. Rita om vid
events::ThemeChanged.