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.
Menu contextuel de l'éditeur¶
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.