Przejdź do treści

Doki, karty, pasek stanu i motyw

Powierzchnie UI, do których wtyczka może kontrybuować poza menu. Wszystkie działają według tego samego wzorca: opisz, czego chcesz, danymi niezależnymi od backendu, przekaż hostowi fabrykę, zatrzymaj zwrócony token RAII.

Panele dokowane

ctx.docks() dodaje dokowany panel boczny. Publiczna powierzchnia celowo przekazuje widżet jako nieprzezroczysty void* — host oparty na Qt rzutuje go na QWidget*; wtyczka w Pythonie buduje ten sam panel deklaratywnie (zobacz dokumentację ctx.docks w Pythonie).

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));

Host tworzy panel leniwie (przy pierwszym wyświetleniu) i może utworzyć go ponownie po resecie układu — trzymaj stan panelu poza widżetem albo odbudowuj go w create(). DockToken usuwa panel przy destrukcji.

Typy kart pierwszej klasy

Gdy funkcja zasługuje na własny rodzaj karty (wyniki diffa, widok logów, podgląd heksadecymalny), a nie bufor tekstowy czy panel boczny, zarejestruj typ karty przez 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 */});

Host umieszcza Twój IDocumentTab w pasku kart obok kart edytora i kieruje do niego zapis/zamykanie. Karty zwracające niepuste sessionParams() są przechwytywane przez utrwalanie sesji i odtwarzane przez openTab() przy przywracaniu; jeśli w chwili przywracania brakuje wtyczki-właściciela, host zbiera osierocone wpisy w jeden komunikat, zamiast je porzucać. Przykłady we wtyczkach własnych projektu: Diff Compare i Log Analyzer.

Widok porównania

ctx.compare() otwiera dwupanelowe okno porównania edytora dla dowolnej pary źródeł — ścieżek plików, otwartych dokumentów lub tekstu w pamięci:

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

Zawsze bezpieczne do wywołania: host bez widoku porównania dostarcza no-op zwracający false.

Pasek stanu

Dwa niezależne kanały — tymczasowe powiadomienia oraz trwałe segmenty należące do wtyczki:

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

Powiadomienie o wyższej wadze natychmiast zastępuje to widoczne na ekranie; powiadomienie o niższej wadze może zostać na chwilę wstrzymane, aby Error nie został od razu zmazany przez rutynowe Info. Odświeżanie segmentu nigdy nie nadpisuje powiadomienia i odwrotnie.

W odróżnieniu od IMenuRegistry — statycznych pozycji paska menu powiązanych z poleceniami — pozycje menu kontekstowego są dynamiczne: przy każdym kliknięciu prawym przyciskiem w obszarze tekstu host pyta każdego zarejestrowanego providera, co ma zastosowanie w klikniętej pozycji, i dokleja odpowiedzi poniżej wbudowanych akcji edycji (Cofnij/Ponów, Wytnij/Kopiuj/Wklej, Zaznacz wszystko):

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

Providery działają w wątku UI, podczas gdy użytkownik czeka na menu — odpowiadaj ze stanu w pamięci, nigdy nie blokuj. Tytuły pozycji to dynamiczne stringi, które wtyczka tłumaczy samodzielnie. Sprawdzanie pisowni używa tego do swoich podpowiedzi / pozycji Dodaj do słownika / Ignoruj w tej sesji.

Paleta motywu

Wtyczki malujące własne kolory rejestrują nazwane kategorie z domyślnymi wartościami dla motywu jasnego/ciemnego i rozwiązują je do RGB w momencie malowania — nigdy nie hardkoduj kolorów:

// 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 zwraca nadpisanie z motywu, jeśli istnieje, w przeciwnym razie zarejestrowaną wartość domyślną dla aktywnego wariantu, a 0x000000 dla niezarejestrowanego id (traktuj to jako „użyj własnego fallbacku").

Każda zarejestrowana kategoria automatycznie pojawia się w edytorze motywów, a nadpisania użytkownika wędrują w obie strony w pliku motywu pod pluginColors. Przemaluj przy events::ThemeChanged.