Aller au contenu

Panneaux, onglets, barre d'état et thème

Les surfaces d'interface auxquelles un plugin peut contribuer, au-delà des menus. Toutes suivent le même schéma : décrivez ce que vous voulez sous forme de données neutres vis-à-vis du backend, confiez une fabrique à l'hôte, conservez le jeton RAII renvoyé.

Panneaux ancrables

ctx.docks() ajoute un panneau latéral ancrable. La surface publique passe délibérément le widget sous forme de void* opaque — l'hôte basé sur Qt le convertit en QWidget* ; un plugin Python construit le même panneau de façon déclarative (voir la référence Python 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));

L'hôte instancie le panneau paresseusement (au premier affichage) et peut le réinstancier après une réinitialisation de la disposition — conservez l'état propre au panneau hors du widget ou reconstruisez-le dans create(). Le DockToken supprime le panneau à sa destruction.

Types d'onglets de première classe

Quand une fonctionnalité mérite son propre type d'onglet (résultats de diff, vue de log, visionneuse hexadécimale) plutôt qu'un tampon texte ou un panneau latéral, enregistrez un type d'onglet auprès de 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 */});

L'hôte place votre IDocumentTab dans la barre d'onglets aux côtés des onglets d'édition et lui achemine les opérations d'enregistrement/fermeture. Les onglets qui renvoient un sessionParams() non vide sont capturés par la persistance de session et rejoués via openTab() à la restauration ; si le plugin propriétaire est absent au moment de la restauration, l'hôte regroupe les entrées orphelines dans un seul message au lieu de les abandonner. Exemples internes : Diff Compare et le Log Analyzer.

La vue de comparaison

ctx.compare() ouvre la fenêtre de comparaison à deux volets de l'éditeur pour n'importe quelle paire de sources — chemins de fichiers, documents ouverts ou texte en mémoire :

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

Toujours sûr à appeler : un hôte sans vue de comparaison fournit un no-op qui renvoie false.

Barre d'état

Deux canaux indépendants — notifications transitoires et segments persistants possédés par le plugin :

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

Une notification de sévérité supérieure remplace immédiatement celle à l'écran ; une notification de sévérité inférieure peut être brièvement retenue afin qu'une Error ne soit pas aussitôt effacée par une Info de routine. Rafraîchir un segment n'écrase jamais une notification, et inversement.

Contrairement à IMenuRegistry — des entrées statiques de la barre de menus liées à des commandes — les éléments du menu contextuel sont dynamiques : à chaque clic droit dans la zone de texte, l'hôte demande à chaque fournisseur enregistré ce qui s'applique à la position cliquée et ajoute les réponses sous les actions d'édition intégrées (Annuler/Rétablir, Couper/Copier/Coller, Tout sélectionner) :

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

Les fournisseurs s'exécutent sur le thread UI pendant que l'utilisateur attend le menu — répondez à partir d'un état en mémoire, ne bloquez jamais. Les titres des éléments sont des chaînes dynamiques que le plugin traduit lui-même. Le correcteur orthographique s'en sert pour ses suggestions et ses entrées Ajouter au dictionnaire / Ignorer pour cette session.

Palette de thème

Les plugins qui peignent des couleurs personnalisées enregistrent des catégories nommées avec des valeurs par défaut claire/sombre et les résolvent en RGB au moment du dessin — ne codez jamais les couleurs en dur :

// 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 renvoie la surcharge du thème quand elle existe, sinon la valeur par défaut enregistrée pour la variante active, et 0x000000 pour un id non enregistré (traitez cela comme « utiliser mon propre repli »).

Chaque catégorie enregistrée apparaît automatiquement dans l'éditeur de thèmes, et les surcharges de l'utilisateur font l'aller-retour dans le fichier de thème sous pluginColors. Repeignez sur events::ThemeChanged.