Zum Inhalt

Docks, Tabs, Statusleiste & Theme

Die UI-Flächen, zu denen ein Plugin über Menüs hinaus beitragen kann. Alle folgen demselben Muster: Beschreiben Sie Ihr Anliegen in Backend-neutralen Daten, übergeben Sie dem Host eine Factory und behalten Sie das zurückgegebene RAII-Token.

Dock-Panels

ctx.docks() fügt ein andockbares Seitenpanel hinzu. Die öffentliche Oberfläche übergibt das Widget bewusst als opakes void* — der Qt-gestützte Host castet es zu QWidget*; ein Python-Plugin baut dasselbe Panel stattdessen deklarativ (siehe die Python-Referenz zu 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
};

m_dock = ctx.docks().addPanel(descriptor, std::move(factory));

Der Host instanziiert das Panel lazy (bei der ersten Anzeige) und kann es nach einem Layout-Reset erneut instanziieren — halten Sie den Zustand pro Panel außerhalb des Widgets oder bauen Sie ihn in create() neu auf. Das DockToken entfernt das Panel bei seiner Zerstörung.

Vollwertige Tab-Typen

Wenn ein Feature eine eigene Art von Tab verdient (Diff-Ergebnisse, eine Log-Ansicht, ein Hex-Viewer) statt eines Textpuffers oder eines Seitenpanels, registrieren Sie einen Tab-Typ mit 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));
ctx.documents().openTab("log-view", {/* params */});

Der Host platziert Ihren IDocumentTab in der Tab-Leiste neben den Editor-Tabs und leitet Speichern/Schließen an ihn weiter. Tabs, die ein nicht-leeres sessionParams() zurückgeben, werden von der Sitzungspersistenz erfasst und beim Wiederherstellen über openTab() wieder abgespielt; fehlt das besitzende Plugin zum Zeitpunkt der Wiederherstellung, fasst der Host die verwaisten Einträge in einer einzigen Meldung zusammen, statt sie zu verwerfen. First-Party-Beispiele: Diff Compare und der Log Analyzer.

Die Vergleichsansicht

ctx.compare() öffnet das zweigeteilte Vergleichsfenster des Editors für ein beliebiges Quellenpaar — Dateipfade, offene Dokumente oder Text im Speicher:

CompareSource left{...}, right{...};
bool shown = ctx.compare().openCompare(left, right, CompareOptions{});

Der Aufruf ist immer sicher: Ein Host ohne Vergleichsansicht liefert ein No-op, das false zurückgibt.

Statusleiste

Zwei unabhängige Kanäle — flüchtige Benachrichtigungen und persistente, Plugin-eigene Segmente:

// 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

Eine Benachrichtigung höherer Dringlichkeit ersetzt die angezeigte sofort; eine mit niedrigerer Dringlichkeit kann kurz zurückgehalten werden, damit ein Error nicht sofort von routinemäßigem Info überschrieben wird. Das Aktualisieren eines Segments überschreibt niemals eine Benachrichtigung und umgekehrt.

Editor-Kontextmenü

Anders als IMenuRegistry — statische Menüleisten-Einträge, die an Commands gebunden sind — sind Kontextmenü-Einträge dynamisch: Bei jedem Rechtsklick in den Textbereich fragt der Host jeden registrierten Provider, was an der angeklickten Position zutrifft, und hängt die Antworten unter die eingebauten Bearbeitungsaktionen (Undo/Redo, Ausschneiden/Kopieren/Einfügen, Alles auswählen):

class MyProvider final : public plugin::IContextMenuProvider
{
    std::vector<plugin::ContextMenuItem>
    contextMenuItems(const plugin::ContextMenuRequest& req) override
    {
        // req.doc + req.position (byte offset under the click; the caret
        // for keyboard-triggered menus). Empty vector = decline.
        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

Provider laufen auf dem UI-Thread, während der Benutzer auf das Menü wartet — antworten Sie aus dem Zustand im Speicher, blockieren Sie niemals. Die Eintragstitel sind dynamische Strings, die das Plugin selbst übersetzt. Die Rechtschreibprüfung nutzt dies für ihre Vorschläge / Add to Dictionary / Ignore for This Session-Einträge.

Theme-Palette

Plugins, die eigene Farben zeichnen, registrieren benannte Kategorien mit Hell/Dunkel-Standardwerten und lösen sie zur Zeichenzeit zu RGB auf — Farben niemals fest codieren:

// 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 liefert die Überschreibung des Themes, falls vorhanden, sonst den registrierten Standard der aktiven Variante, und 0x000000 für eine nicht registrierte Id (behandeln Sie das als „eigenen Fallback verwenden“).

Jede registrierte Kategorie erscheint automatisch im Theme-Editor, und Benutzer-Überschreibungen werden in der Theme-Datei unter pluginColors verlustfrei gespeichert. Zeichnen Sie bei events::ThemeChanged neu.