Panely, karty, stavový riadok a motív¶
Plochy rozhrania, do ktorých môže zásuvný modul prispieť nad rámec ponúk. Všetky sa riadia tým istým vzorom: opíšte, čo chcete, v údajoch nezávislých od backendu, odovzdajte hostiteľovi továreň a ponechajte si vrátený token RAII.
Ukotviteľné panely¶
ctx.docks() pridáva ukotviteľný bočný panel. Verejná plocha zámerne odovzdáva widget ako
nepriehľadný void* — hostiteľ postavený na Qt ho pretypuje na QWidget*; zásuvný modul
v Pythone namiesto toho zostaví ten istý panel deklaratívne (pozri
referenciu ctx.docks pre Python).
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));
Hostiteľ panel vytvára až v prípade potreby (pri prvom zobrazení) a po obnovení rozloženia ho
môže vytvoriť znova — stav jednotlivých panelov preto držte mimo widgetu, prípadne ho v create()
zostavte nanovo. DockToken panel pri zničení odstráni.
Pixely, alebo podiel okna — proportionalSize¶
0 (predvolená hodnota) je obyčajné správanie: panel si drží veľkosť v pixeloch a zmena veľkosti
okna sa ho nedotkne. To chce nástrojový panel — prehľad dokumentu alebo zoznam výsledkov je pri
jednej šírke rovnako užitočný, nech okno robí čokoľvek. Hodnota z intervalu (0, 1) naopak vyhlasuje
predvolený podiel hlavného okna: jeho šírky v oblastiach Left/Right, jeho výšky v Top/Bottom.
To chce náhľad — okno maximalizujte a vykreslená stránka sa s ním rozšíri.
Je to obyčajné číslo práve preto, aby rovnaký kontrakt mohol naplniť aj iný backend než v C++
(pythonový backend ho prijíma ako
proportional_size). Čo okolo toho hostiteľ zaručuje:
- Vy dodávate len predvolenú hodnotu. Vo chvíli, keď používateľ pretiahne oddeľovač panela, hostiteľ prijme podiel, ktorý si zvolil, zapamätá si ho spolu s rozložením okna a vaša hodnota sa na ten panel už nikdy nevzťahuje.
- Hodnoty mimo rozsah sa obmedzia, nie odmietnu — podiel sa drží v rozumnom pásme a tak váš
panel, ako aj editor si podržia minimálny rozmer, takže ani jeden nemožno zložiť. Negatívna alebo
nekonečná hodnota sa číta ako
0. - Vyhráva vlastné minimum vášho panela. Hostiteľ nikdy nežiada od rozloženia menej, než na čo sa dá widget stlačiť; panel sa na tejto hranici zastaví bez toho, aby podiel zabudol, a vráti sa k nemu, len čo sa okno opäť rozšíri. Panel, ktorý má byť proporcionálny, by sa preto mal vedieť preskladať — panel nástrojov, ktorý svoje skupiny skladá pod seba namiesto toho, aby vyžadoval jediný riadok, ponecháva panel zúžiteľný.
- Plávajúce a skryté panely sa preskakujú. Plávajúci panel je vlastným oknom a drží si svoju veľkosť; skrytému sa veľkosť pridelí, až keď sa zobrazí, a znova ukotvenému, až keď je späť.
- Panely v kartách zdieľajú jedno miesto, takže ho riadi panel vpredu — ten, na ktorý sa používateľ pozerá, je ten, ktorého podiel niečo znamená. Dva panely vedľa seba na jednej osi zdieľajú jediný rozpočet a škálujú sa spoločne so zachovaním svojho pomeru.
Panel zaregistrovaný s initiallyVisible = false sa neskôr zobrazí pomocou setPanelVisible —
zvyčajne z príkazu, ktorý ho „otvára“. Zobrazenie panel zároveň vynesie nad panely, s ktorými
zdieľa oblasť, takže naozaj skončí pred používateľom. Volanie je idempotentné (tú istú hodnotu
môžete pokojne opakovať) a pri tokene, ktorý už nie je živý, je tichou prázdnou operáciou, takže
niet čo sledovať a netreba siahať po žiadnom QDockWidget:
ctx.docks().setPanelVisible(m_dock.id(), true); // reveal + raise
ctx.docks().setPanelVisible(m_dock.id(), false); // hide, keep registered
isPanelVisible odpovedá, či je panel v rozložení okna otvorený — stav,
ktorý zapisuje setPanelVisible a ktorý prepína prepínač alebo zatváracie
tlačidlo používateľa. Neznamená to „sú jeho pixely na obrazovke“: panel
schovaný ako karta za iným je otvorený, panel zaregistrovaný ako skrytý a
nikdy nezobrazený nie je, a nie je ním ani token, ktorý už nežije. Otázka
panel nikdy nevytvorí. Obe volania spolu umožňujú modulu zobraziť svoje panely
počas úlohy a potom vrátiť presne to, čo mal používateľ:
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);
Panely, ktoré patria k sebe — tabGroup¶
Panely, ktoré uvádzajú rovnakú neprázdnu tabGroup a rovnakú
preferredArea, zdieľajú jeden dok ako karty, namiesto aby ležali vedľa
seba — päť panelov ladiaceho programu sa zmení na jeden pás kariet pri dolnom
okraji okna. Dajte identifikátoru menný priestor ako identifikátoru panela
("myplugin/panels"); rovnaký identifikátor v dvoch oblastiach vytvorí dva
zásobníky a modul sa môže zámerne pripojiť ku skupine iného modulu. Karty sa
zobrazujú v poradí registrácie; prvý otvorený panel je vpredu, kým používateľ
alebo setPanelVisible neprenesie dopredu iný.
Skupina je len predvolené usporiadanie. Rozloženie uložené používateľom má prednosť: panel, ktorý používateľ vytiahol zo zásobníka, presunul do inej oblasti alebo do plávajúceho okna, tam zostane aj po reštarte. Panel, ktorý uložené rozloženie ešte nepozná — nový v tejto verzii vášho modulu —, sa pripojí k svojej skupine.
Plnohodnotné typy kariet¶
Keď si funkcia zaslúži vlastný druh karty (výsledky porovnania, zobrazenie logu, prehliadač
šestnástkových údajov) namiesto textového bufferu či bočného panela, zaregistrujte typ karty cez
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 */});
Hostiteľ umiestni vaše IDocumentTab do pásu kariet vedľa kariet editora a smeruje k nemu
ukladanie a zatváranie. openTab() vracia popisovač DocumentTabId (0, keď sa nič neotvorilo
— neznámy typ, prázdny výsledok továrne); id sú počas života karty stabilné a v rámci jedného
behu sa nikdy nepoužijú znova, takže sa zastaraný popisovač číta ako zatvorený, a nie ako cudzia
karta. Ponechajte si ho, ak chcete kartu ďalej sledovať, prípadne ho zahoďte pri otvorení bez
ďalšej starosti:
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
Zvyklosťou „otvoriť alebo zobraziť“ je zavolať activateTab(), keď isTabOpen() hovorí, že je
popisovač stále živý, a inak openTab(). Karty, ktoré vracajú neprázdne sessionParams(),
zachytáva uchovávanie relácie a pri obnove ich prehrá cez openTab(); ak v čase obnovy chýba
vlastniaci zásuvný modul, zhrnie hostiteľ osirené položky do jedinej správy namiesto toho, aby
ich zahodil. Vlastné príklady: Porovnanie a Analyzátor logov.
Zobrazenie porovnania¶
ctx.compare() otvára dvojtablové okno porovnania editora pre ľubovoľnú dvojicu zdrojov — cesty
k súborom, otvorené dokumenty alebo text v pamäti:
CompareSource left{...}, right{...};
bool shown = ctx.compare().openCompare(left, right, CompareOptions{});
Volať sa dá vždy bezpečne: hostiteľ bez zobrazenia porovnania poskytne prázdnu implementáciu,
ktorá vracia false.
Stavový riadok¶
Dva nezávislé kanály — dočasné upozornenia a trvalé segmenty vlastnené zásuvným modulom:
// 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
Upozornenie s vyššou závažnosťou to na obrazovke okamžite nahradí; upozornenie s nižšou
závažnosťou sa môže krátko zadržať, aby Error hneď nezmazalo rutinné Info. Obnovenie segmentu
nikdy neprebije upozornenie a naopak.
Kontextová ponuka editora¶
Na rozdiel od IMenuRegistry — statických položiek panela ponuky viazaných na príkazy — sú
položky kontextovej ponuky dynamické: pri každom kliknutí pravým tlačidlom
do textovej oblasti — alebo do okraja vedľa nej — sa hostiteľ spýta každého
zaregistrovaného poskytovateľa, čo na mieste kliknutia platí, a odpovede
pripojí pod vstavané akcie úprav (Späť/Znova, Vystrihnúť/Kopírovať/Prilepiť,
Vybrať všetko). Požiadavka hovorí, kde ku kliknutiu došlo:
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;
};
areajeMarginpri kliknutí do ľubovoľného stĺpca naľavo od textu (čísla riadkov, stĺpec záložiek, stĺpec zbaľovania) a inakText; ponuka otvorená z klávesnice je vždyText.lineje riadok, na ktorý sa kliklo (pri ponuke z klávesnice riadok kurzora), takžepositionnikdy nemusíte prevádzať sami.positionje posun v bajtoch pod kliknutím v texte (pri ponuke z klávesnice kurzor). Pod kliknutím do okraja nie je žiadny znak, takže tam jepositionposun začiatku riadkaline— platný, ale nikdy z neho nečítajte stĺpec.
Rozhodnite pre každú oblasť zvlášť, čo ponúkate: položky na úrovni riadka (Add Conditional Breakpoint…, Revert Hunk) patria do okraja, položky na úrovni slova do textu. Kontrola pravopisu napríklad pri kliknutí do okraja neponúka nič.
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
Poskytovatelia bežia vo vlákne rozhrania, kým používateľ čaká na ponuku — odpovedajte zo stavu v pamäti a nikdy neblokujte. Názvy položiek sú dynamické reťazce, ktoré si zásuvný modul prekladá sám. Kontrola pravopisu to využíva pre svoje položky s návrhmi, Pridať do slovníka a Ignorovať v tejto relácii.
Paleta motívu¶
Zásuvné moduly, ktoré vykresľujú vlastné farby, registrujú pomenované kategórie so svetlými a tmavými predvoľbami a pri vykresľovaní ich vyhodnocujú na RGB — farby nikdy nepíšte natvrdo:
// 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 vracia prekrytie z motívu, ak existuje, inak zaregistrovanú predvoľbu pre aktívny
variant a 0x000000 pri nezaregistrovanom id (berte to ako „použi moju vlastnú záložnú
hodnotu“).
Každá zaregistrovaná kategória sa automaticky objaví v Editore motívov a prekrytia používateľa
putujú tam a späť v súbore motívu pod kľúčom pluginColors. Prekresľujte pri
events::ThemeChanged.