Something went wrong. Try again.
The agentic engineering control plane for the posthuman future
Something went wrong. Try again.
51 kB · 1330 lines
C++
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331#include "Commands.h"
#include <QGuiApplication>#include <QJSValue>#include <QKeySequence>#include <QMetaObject>#include <QQmlEngine>
#include <ranges>
#include "AppModel.h"#include "PanePool.h"#include "TerminalQuickItem.h"#include "Workspace.h"#include "XenoSettings.h"
// ---------------------------------------------------------------------------// CommandDef// ---------------------------------------------------------------------------
CommandDef::CommandDef(QString commandId, QString title, QString category, QStringList menus, QObject *parent) : QObject(parent), m_commandId(std::move(commandId)), m_title(std::move(title)), m_category(std::move(category)), m_menus(std::move(menus)) {}
void CommandDef::setEnabled(bool enabled) { if (m_enabled == enabled) { return; } m_enabled = enabled; emit enabledChanged();}
QString CommandDef::shortcutHint() const { if (m_sequence.isEmpty()) { return {}; } // On macOS "Ctrl" IS the Cmd key (Qt's modifier mapping); render the // human display form accordingly.#ifdef Q_OS_MACOS QString hint = m_sequence; return hint.replace(QStringLiteral("Ctrl"), QStringLiteral("Cmd"));#else return m_sequence;#endif}
CommandDef *CommandDef::setMenuLabel(QString label) { m_menuLabel = std::move(label); return this;}void CommandDef::applySequence(QString sequence) { if (m_sequence == sequence) { return; } m_sequence = std::move(sequence); emit sequenceChanged();}
CommandDef *CommandDef::setSequence(QString sequence) { m_defaultSequence = sequence; m_sequence = std::move(sequence); return this;}CommandDef *CommandDef::setIntercept(bool intercept) { m_intercept = intercept; return this;}CommandDef *CommandDef::setSeparators(QStringList separators) { m_separators = std::move(separators); return this;}CommandDef *CommandDef::setPaletteVisible(bool visible) { m_paletteVisible = visible; return this;}CommandDef *CommandDef::setTarget(QString target) { m_target = std::move(target); return this;}
// ---------------------------------------------------------------------------// Commands singleton// ---------------------------------------------------------------------------
Commands *Commands::s_instance = nullptr;
Commands::Commands(QObject *parent) : QObject(parent) { s_instance = this; ensureModelConnected();
// --- Editor history ------------------------------------------------------- // Undo and redo act on the focused editor pane's buffer, which lives in the // pooled EditorController, so these publish to TerminalPageController (target // "terminalPage") like the other pane-targeted commands. Category "edit", not // "pane": globalEnabled's "pane" category gate is "any pane has focus", which // would enable these in a terminal. auto* editorUndo = new CommandDef( QStringLiteral("editorUndo"), tr("Undo"), QStringLiteral("edit"), {QStringLiteral("edit"), QStringLiteral("pane")}, this); editorUndo->setSequence(QStringLiteral("Ctrl+Z")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(editorUndo);
auto* editorRedo = new CommandDef( QStringLiteral("editorRedo"), tr("Redo"), QStringLiteral("edit"), {QStringLiteral("edit"), QStringLiteral("pane")}, this); editorRedo->setSequence(QStringLiteral("Ctrl+Shift+Z")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(editorRedo);
// --- Clipboard ------------------------------------------------------------ // The QML registry derived copy's global `enabled` from the focused pane's // hasSelection, but selection changes do not notify the model, so the // binding could go stale. Keep both enabled=true globally; the ctx-gated // commandEnabledFor decides (menu refresh calls commandEnabledFor before // showing). // clang-format off auto* copy = new CommandDef( QStringLiteral("copy"), tr("Copy"), QStringLiteral("clipboard"), {QStringLiteral("edit"), QStringLiteral("pane")}, this); copy->setSequence(QStringLiteral("Ctrl+C"))->setIntercept(false) ->setTarget(QStringLiteral("terminalPage")); // clang-format on m_commands.append(copy);
auto *paste = new CommandDef( QStringLiteral("paste"), tr("Paste"), QStringLiteral("clipboard"), {QStringLiteral("edit"), QStringLiteral("pane")}, this); paste->setSequence(QStringLiteral("Ctrl+V")) ->setIntercept(false) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(paste);
// --- Pane splitting // --------------------------------------------------------- auto *splitHorizontal = new CommandDef(QStringLiteral("splitHorizontal"), tr("Split Horizontal"), QStringLiteral("pane"), {QStringLiteral("view"), QStringLiteral("pane")}, this); splitHorizontal->setSequence(QStringLiteral("Ctrl+Shift+H")) ->setSeparators({QStringLiteral("pane")}) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(splitHorizontal);
auto *splitVertical = new CommandDef(QStringLiteral("splitVertical"), tr("Split Vertical"), QStringLiteral("pane"), {QStringLiteral("view"), QStringLiteral("pane")}, this); splitVertical->setSequence(QStringLiteral("Ctrl+Shift+V")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(splitVertical);
auto *splitWithTerminal = new CommandDef(QStringLiteral("splitWithTerminal"), tr("Split with Terminal"), QStringLiteral("pane"), {QStringLiteral("view"), QStringLiteral("pane")}, this); splitWithTerminal->setSequence(QStringLiteral("Ctrl+Shift+T")) ->setSeparators({QStringLiteral("pane")}) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(splitWithTerminal);
auto *splitWithEditor = new CommandDef(QStringLiteral("splitWithEditor"), tr("Split with Editor"), QStringLiteral("pane"), {QStringLiteral("view"), QStringLiteral("pane")}, this); splitWithEditor->setSequence(QStringLiteral("Ctrl+Shift+E")) ->setSeparators({QStringLiteral("pane")}) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(splitWithEditor);
// --- Pane lifecycle // --------------------------------------------------------- auto *closePane = new CommandDef( QStringLiteral("closePane"), tr("Close Pane"), QStringLiteral("pane"), {QStringLiteral("view"), QStringLiteral("pane")}, this); closePane->setSequence(QStringLiteral("Ctrl+Alt+W")) ->setSeparators({QStringLiteral("view"), QStringLiteral("pane")}) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(closePane);
auto *keepOnlyPane = new CommandDef(QStringLiteral("keepOnlyPane"), tr("Keep Only This Pane"), QStringLiteral("pane"), {QStringLiteral("view"), QStringLiteral("pane")}, this); keepOnlyPane->setSequence(QStringLiteral("Ctrl+Alt+O")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(keepOnlyPane);
auto *focusNextPane = new CommandDef(QStringLiteral("focusNextPane"), tr("Focus Next Pane"), QStringLiteral("pane"), {QStringLiteral("view"), QStringLiteral("pane")}, this); focusNextPane->setSequence(QStringLiteral("Ctrl+]")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(focusNextPane);
// --- Pane zoom ------------------------------------------------------------ // One per-tab zoom state on XenoTab; maximize is a zoom that also // re-asserts focus (they share the model state, two spellings of the // intent). globalEnabled's "pane"-category gate (hasFocus) is exactly // "a zoom is possible or active": a zoom needs a focused pane, and an // active zoom implies one (the zoomed pane keeps focus). // Neither command carries a default sequence: Ctrl+Z and Ctrl+Shift+Z // belong to editor undo and redo (one command per sequence is what // setCommandSequence's conflict check and Qt's shortcut matching require). auto *togglePaneZoom = new CommandDef(QStringLiteral("togglePaneZoom"), tr("Toggle Pane Zoom"), QStringLiteral("pane"), {QStringLiteral("view"), QStringLiteral("pane")}, this); togglePaneZoom->setTarget(QStringLiteral("terminalPage")); m_commands.append(togglePaneZoom);
auto *maximizePane = new CommandDef(QStringLiteral("maximizePane"), tr("Maximize Pane"), QStringLiteral("pane"), {QStringLiteral("pane")}, this); maximizePane->setSeparators({QStringLiteral("pane")}) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(maximizePane);
// --- Pane focus movement (shortcuts + palette only) // -------------------------- auto *focusPaneLeft = new CommandDef(QStringLiteral("focusPaneLeft"), tr("Focus Pane Left"), QStringLiteral("pane"), {}, this); focusPaneLeft->setSequence(QStringLiteral("Ctrl+Left")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(focusPaneLeft);
auto *focusPaneRight = new CommandDef(QStringLiteral("focusPaneRight"), tr("Focus Pane Right"), QStringLiteral("pane"), {}, this); focusPaneRight->setSequence(QStringLiteral("Ctrl+Right")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(focusPaneRight);
auto *focusPaneUp = new CommandDef(QStringLiteral("focusPaneUp"), tr("Focus Pane Up"), QStringLiteral("pane"), {}, this); focusPaneUp->setSequence(QStringLiteral("Ctrl+Up")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(focusPaneUp);
auto *focusPaneDown = new CommandDef(QStringLiteral("focusPaneDown"), tr("Focus Pane Down"), QStringLiteral("pane"), {}, this); focusPaneDown->setSequence(QStringLiteral("Ctrl+Down")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(focusPaneDown);
// --- Pane swapping (shortcuts + palette only) // -------------------------------- auto *swapPaneLeft = new CommandDef(QStringLiteral("swapPaneLeft"), tr("Swap Pane Left"), QStringLiteral("pane"), {}, this); swapPaneLeft->setSequence(QStringLiteral("Ctrl+Shift+Left")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(swapPaneLeft);
auto *swapPaneRight = new CommandDef(QStringLiteral("swapPaneRight"), tr("Swap Pane Right"), QStringLiteral("pane"), {}, this); swapPaneRight->setSequence(QStringLiteral("Ctrl+Shift+Right")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(swapPaneRight);
auto *swapPaneUp = new CommandDef(QStringLiteral("swapPaneUp"), tr("Swap Pane Up"), QStringLiteral("pane"), {}, this); swapPaneUp->setSequence(QStringLiteral("Ctrl+Shift+Up")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(swapPaneUp);
auto *swapPaneDown = new CommandDef(QStringLiteral("swapPaneDown"), tr("Swap Pane Down"), QStringLiteral("pane"), {}, this); swapPaneDown->setSequence(QStringLiteral("Ctrl+Shift+Down")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(swapPaneDown);
// --- Terminal reset // ----------------------------------------------------------- auto *resetTerminal = new CommandDef(QStringLiteral("resetTerminal"), tr("Reset Terminal"), QStringLiteral("pane"), {QStringLiteral("edit"), QStringLiteral("pane")}, this); resetTerminal->setMenuLabel(tr("Reset")) ->setSeparators({QStringLiteral("edit"), QStringLiteral("pane")}) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(resetTerminal);
// --- Scrollback search ------------------------------------------------ // openSearch/closeSearch are pane-targeted UI decisions (the search bar // lives on the pane); next/previous route to the focused pane's item. auto *openSearch = new CommandDef(QStringLiteral("openSearch"), tr("Find"), QStringLiteral("pane"), {}, this); openSearch->setSequence(QStringLiteral("Ctrl+F")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(openSearch);
auto *closeSearch = new CommandDef(QStringLiteral("closeSearch"), tr("Close Search"), QStringLiteral("pane"), {}, this); // Escape is handled by the search bar itself (Keys.onEscapePressed) and by // the copy-mode key path on the focused item. It must NOT enter the global // intercept whitelist, because that accepts the ShortcutOverride for every // Escape and stops the key from ever reaching the program in the pane. closeSearch->setSequence(QStringLiteral("Esc")) ->setIntercept(false) ->setPaletteVisible(false) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(closeSearch);
auto *searchNext = new CommandDef(QStringLiteral("searchNext"), tr("Next Match"), QStringLiteral("pane"), {}, this); searchNext->setSequence(QStringLiteral("Ctrl+G")) ->setPaletteVisible(false) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(searchNext);
auto *searchPrevious = new CommandDef(QStringLiteral("searchPrevious"), tr("Previous Match"), QStringLiteral("pane"), {}, this); searchPrevious->setSequence(QStringLiteral("Ctrl+Shift+G")) ->setPaletteVisible(false) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(searchPrevious);
// --- Copy mode ----------------------------------------------------------- // enterCopyMode/exitCopyMode are pane-targeted UI decisions routed like // the search commands; move/select/yank route straight to the focused // pane's item (the mode's own key handling drives them normally). auto *enterCopyMode = new CommandDef(QStringLiteral("enterCopyMode"), tr("Copy Mode"), QStringLiteral("pane"), {}, this); enterCopyMode->setTarget(QStringLiteral("terminalPage")); m_commands.append(enterCopyMode);
auto *exitCopyMode = new CommandDef(QStringLiteral("exitCopyMode"), tr("Leave Copy Mode"), QStringLiteral("pane"), {}, this); exitCopyMode->setPaletteVisible(false) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(exitCopyMode);
// --- Tabs ----------------------------------------------------------------- auto *newTab = new CommandDef( QStringLiteral("newTab"), tr("New Tab"), QStringLiteral("tab"), {QStringLiteral("file"), QStringLiteral("workspace")}, this); newTab->setSequence(QStringLiteral("Ctrl+N")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(newTab);
auto *closeTab = new CommandDef(QStringLiteral("closeTab"), tr("Close Tab"), QStringLiteral("tab"), {QStringLiteral("file")}, this); closeTab->setSequence(QStringLiteral("Ctrl+W")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(closeTab);
auto *nextTab = new CommandDef(QStringLiteral("nextTab"), tr("Next Tab"), QStringLiteral("tab"), {}, this); nextTab->setSequence(QStringLiteral("Ctrl+PgDown")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(nextTab);
auto *previousTab = new CommandDef(QStringLiteral("previousTab"), tr("Previous Tab"), QStringLiteral("tab"), {}, this); previousTab->setSequence(QStringLiteral("Ctrl+PgUp")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(previousTab);
// Tab reorder: move the ACTIVE tab one slot left/right inside its // workspace. No-op at the strip's ends; the enabled predicate below // mirrors the model's guards exactly. auto *moveTabLeft = new CommandDef(QStringLiteral("moveTabLeft"), tr("Move Tab Left"), QStringLiteral("tab"), {}, this); moveTabLeft->setSequence(QStringLiteral("Ctrl+Shift+PgUp")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(moveTabLeft);
auto *moveTabRight = new CommandDef(QStringLiteral("moveTabRight"), tr("Move Tab Right"), QStringLiteral("tab"), {}, this); moveTabRight->setSequence(QStringLiteral("Ctrl+Shift+PgDown")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(moveTabRight);
auto *renameTab = new CommandDef(QStringLiteral("renameTab"), tr("Rename Tab…"), QStringLiteral("tab"), {QStringLiteral("file")}, this); renameTab->setTarget(QStringLiteral("terminalPage")); m_commands.append(renameTab);
// --- Workspace navigation (palette + shortcuts) // ------------------------------- auto *previousWorkspace = new CommandDef( QStringLiteral("previousWorkspace"), tr("Previous Workspace"), QStringLiteral("workspace"), {}, this); previousWorkspace->setSequence(QStringLiteral("Alt+Up")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(previousWorkspace);
auto *nextWorkspace = new CommandDef(QStringLiteral("nextWorkspace"), tr("Next Workspace"), QStringLiteral("workspace"), {}, this); nextWorkspace->setSequence(QStringLiteral("Alt+Down")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(nextWorkspace);
// Jump-to-workspace N (1..9): paletteVisible=false; enabled when that // many workspaces exist. for (const int n : std::views::iota(1, 10)) { auto *jump = new CommandDef(QStringLiteral("selectWorkspace%1").arg(n), tr("Jump to Workspace %1").arg(n), QStringLiteral("workspace"), {}, this); jump->setSequence(QStringLiteral("Ctrl+%1").arg(n)) ->setPaletteVisible(false) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(jump); }
// --- Fuzzy jump (Cmd+P) and the command palette (Cmd+Shift+P) // --------------- auto *jumpToPane = new CommandDef(QStringLiteral("jumpToPane"), tr("Jump to Pane"), QStringLiteral("workspace"), {}, this); jumpToPane->setSequence(QStringLiteral("Ctrl+P")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(jumpToPane);
auto *commandPalette = new CommandDef(QStringLiteral("commandPalette"), tr("Command Palette"), QStringLiteral("app"), {}, this); commandPalette->setSequence(QStringLiteral("Ctrl+Shift+P")) ->setPaletteVisible(false) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(commandPalette);
// --- View ----------------------------------------------------------------- auto *resetLayout = new CommandDef(QStringLiteral("resetLayout"), tr("Reset Layout"), QStringLiteral("view"), {QStringLiteral("file")}, this); resetLayout->setTarget(QStringLiteral("terminalPage")); m_commands.append(resetLayout);
auto *equalizeSplits = new CommandDef(QStringLiteral("equalizeSplits"), tr("Equalize Splits"), QStringLiteral("view"), {QStringLiteral("view")}, this); equalizeSplits->setTarget(QStringLiteral("terminalPage")); m_commands.append(equalizeSplits);
auto *toggleSidebar = new CommandDef(QStringLiteral("toggleSidebar"), tr("Toggle Sidebar"), QStringLiteral("view"), {}, this); toggleSidebar->setSequence(QStringLiteral("Ctrl+B")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(toggleSidebar);
// The wrap setting is global, so this command carries no pane target and // stays enabled on every surface. auto *toggleWrap = new CommandDef(QStringLiteral("toggleWrap"), tr("Toggle Wrap"), QStringLiteral("view"), {QStringLiteral("view"), QStringLiteral("pane")}, this); toggleWrap->setSequence(QStringLiteral("Ctrl+Shift+W")); m_commands.append(toggleWrap);
auto *missionControl = new CommandDef(QStringLiteral("missionControl"), tr("Mission Control"), QStringLiteral("view"), {}, this); missionControl->setSequence(QStringLiteral("Ctrl+M")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(missionControl);
// --- Application ----------------------------------------------------------- auto *preferences = new CommandDef(QStringLiteral("preferences"), tr("Preferences…"), QStringLiteral("app"), {QStringLiteral("app")}, this); preferences->setSequence(QStringLiteral("Ctrl+,")) ->setTarget(QStringLiteral("terminalPage")); m_commands.append(preferences);
auto *quit = new CommandDef(QStringLiteral("quit"), tr("Quit Xenomorphic"), QStringLiteral("app"), {QStringLiteral("app")}, this); quit->setSequence(QStringLiteral("Ctrl+Q")) ->setSeparators({QStringLiteral("app")}) ->setTarget(QStringLiteral("app")); m_commands.append(quit);
// --- New workspace (opens the AddWorkspaceMenu dropdown) ------------------- auto *newWorkspace = new CommandDef(QStringLiteral("newWorkspace"), tr("New Workspace…"), QStringLiteral("add"), {QStringLiteral("file")}, this); newWorkspace->setSeparators({QStringLiteral("file")}) ->setPaletteVisible(false) ->setTarget(QStringLiteral("workspaces")); m_commands.append(newWorkspace);
// --- Add-workspace sources (AddWorkspaceMenu + workspace-row menu) // -------------------------------------------------------------------------- auto *addSameDirectory = new CommandDef(QStringLiteral("addSameDirectory"), tr("Same Directory"), QStringLiteral("add"), {QStringLiteral("add")}, this); addSameDirectory->setTarget(QStringLiteral("workspaces")); m_commands.append(addSameDirectory);
auto *addJjWorkspace = new CommandDef(QStringLiteral("addJjWorkspace"), tr("New jj Workspace"), QStringLiteral("add"), {QStringLiteral("add"), QStringLiteral("workspace")}, this); addJjWorkspace->setSeparators({QStringLiteral("workspace")}) ->setTarget(QStringLiteral("workspaces")); m_commands.append(addJjWorkspace);
auto *addGitWorktree = new CommandDef(QStringLiteral("addGitWorktree"), tr("New Git Worktree"), QStringLiteral("add"), {QStringLiteral("add"), QStringLiteral("workspace")}, this); addGitWorktree->setTarget(QStringLiteral("workspaces")); m_commands.append(addGitWorktree);
auto *addLocation = new CommandDef(QStringLiteral("addLocation"), tr("New Location…"), QStringLiteral("add"), {QStringLiteral("add")}, this); addLocation->setTarget(QStringLiteral("workspaces")); m_commands.append(addLocation);
// --- Close workspace // --------------------------------------------------------- Deletes the // active workspace; deleting the last workspace rebuilds a default one // (AppModel::deleteWorkspace never leaves zero workspaces). auto *closeWorkspace = new CommandDef( QStringLiteral("closeWorkspace"), tr("Close Workspace"), QStringLiteral("workspace"), {QStringLiteral("file")}, this); closeWorkspace->setTarget(QStringLiteral("workspaces")); m_commands.append(closeWorkspace);
// --- Workspace row commands (target the row the menu was opened on) -------- // Context is {index, sidebar}. Menus-only: paletteVisible=false and no // global shortcut. auto *workspaceRename = new CommandDef( QStringLiteral("workspaceRename"), tr("Rename"), QStringLiteral("workspace"), {QStringLiteral("workspace")}, this); workspaceRename->setPaletteVisible(false)->setTarget( QStringLiteral("workspaces")); m_commands.append(workspaceRename);
auto *workspaceSetDirectory = new CommandDef( QStringLiteral("workspaceSetDirectory"), tr("Set Directory"), QStringLiteral("workspace"), {QStringLiteral("workspace")}, this); workspaceSetDirectory->setPaletteVisible(false)->setTarget( QStringLiteral("workspaces")); m_commands.append(workspaceSetDirectory);
auto *workspaceForgetJj = new CommandDef( QStringLiteral("workspaceForgetJj"), tr("Forget jj Workspace"), QStringLiteral("workspace"), {QStringLiteral("workspace")}, this); workspaceForgetJj->setSeparators({QStringLiteral("workspace")}) ->setPaletteVisible(false) ->setTarget(QStringLiteral("workspaces")); m_commands.append(workspaceForgetJj);
auto *workspaceDelete = new CommandDef( QStringLiteral("workspaceDelete"), tr("Delete Workspace"), QStringLiteral("workspace"), {QStringLiteral("workspace")}, this); workspaceDelete->setSeparators({QStringLiteral("workspace")}) ->setPaletteVisible(false) ->setTarget(QStringLiteral("workspaces")); m_commands.append(workspaceDelete);
for (CommandDef *command : std::as_const(m_commands)) { m_byId.insert(command->commandId(), command); }
recomputeEnabled();
// Resolve effective sequences from the persisted overrides and keep // following them. ensureModelConnected retries via the event loop when // the AppModel singleton is not up yet, so applyOverrides also runs on // the (re)connect path. applyOverrides();
// The app-level command: quitting needs no controller. connect(this, &Commands::runCommand, this, [this](const QString &commandId) { if (commandId != QLatin1String("quit")) { return; } // A quit writes every dirty editor, so a pane that cannot be written stops // the quit instead of losing its edits. The page reveals the first one and // its own note says why. if (PanePool *pool = PanePool::instance()) { const QStringList blocked = pool->dirtyEditorsThatCannotSave(); if (!blocked.isEmpty()) { emit quitBlocked(blocked); return; } } if (QGuiApplication *gui = qGuiApp) { // Queued so the dispatch() caller unwinds before the loop exits. QMetaObject::invokeMethod(gui, &QCoreApplication::quit, Qt::QueuedConnection); } });}
Commands::~Commands() { if (s_instance == this) { s_instance = nullptr; }}
Commands *Commands::instance() { return s_instance; }
Commands *Commands::create(QQmlEngine *engine, QJSEngine * /*scriptEngine*/) { auto *commands = new Commands(engine); QQmlEngine::setObjectOwnership(commands, QQmlEngine::CppOwnership); return commands;}
// ---------------------------------------------------------------------------// Registry reads// ---------------------------------------------------------------------------
QVariantList Commands::commands() const { QVariantList out; out.reserve(m_commands.size()); for (CommandDef *command : m_commands) { out.append(QVariant::fromValue(command)); } return out;}
QVariantList Commands::shortcutCommands() const { QVariantList out; for (CommandDef *command : m_commands) { if (!command->sequence().isEmpty()) { out.append(QVariant::fromValue(command)); } } return out;}
QVariantList Commands::paletteCommands() const { QVariantList out; for (CommandDef *command : m_commands) { if (command->paletteVisible()) { out.append(QVariant::fromValue(command)); } } return out;}
QVariantList Commands::byMenu(const QString &menu) const { QVariantList out; for (CommandDef *command : m_commands) { if (command->menus().contains(menu)) { out.append(QVariant::fromValue(command)); } } return out;}
QStringList Commands::whitelistSequences() const { QStringList out; QSet<QString> seen; for (CommandDef *command : m_commands) { const QString &sequence = command->sequence(); if (command->intercept() && !sequence.isEmpty() && !seen.contains(sequence)) { seen.insert(sequence); out.append(sequence); } } return out;}
// ---------------------------------------------------------------------------// Sequence overrides// ---------------------------------------------------------------------------
void Commands::applyOverrides() { ensureModelConnected(); const QVariantMap overrides = (m_model != nullptr && m_model->settings() != nullptr) ? m_model->settings()->commandSequences() : QVariantMap(); bool changed = false; for (CommandDef *command : std::as_const(m_commands)) { const QString overridden = overrides.value(command->commandId()).toString(); const QString next = overridden.isEmpty() ? command->defaultSequence() : overridden; if (command->sequence() != next) { changed = true; } command->applySequence(next); } if (changed) { emit commandsChanged(); }}
QString Commands::commandForSequence(const QString &sequence) const { if (sequence.trimmed().isEmpty()) { return {}; } const QKeySequence wanted(sequence); if (wanted.isEmpty()) { return {}; } for (CommandDef *command : m_commands) { if (!command->sequence().isEmpty() && QKeySequence(command->sequence()) == wanted) { return command->commandId(); } } return {};}
bool Commands::setCommandSequence(const QString &commandId, const QString &sequence) { ensureModelConnected(); CommandDef *command = find(commandId); if (command == nullptr || (m_model == nullptr || m_model->settings() == nullptr)) { return false; } const QString trimmed = sequence.trimmed(); if (trimmed.isEmpty()) { // Clear: restore the built-in literal. m_model->settings()->clearCommandSequence(commandId); return true; } const QKeySequence wanted(trimmed); if (wanted.isEmpty()) { return false; } // Conflict rejection: the sequence must be free or already this // command's (re-binding to itself, or its own literal, is fine). for (CommandDef *other : std::as_const(m_commands)) { if (other != command && !other->sequence().isEmpty() && QKeySequence(other->sequence()) == wanted) { return false; } } m_model->settings()->setCommandSequence(commandId, trimmed); return true;}
void Commands::clearCommandSequence(const QString &commandId) { ensureModelConnected(); if (m_model != nullptr && m_model->settings() != nullptr) { m_model->settings()->clearCommandSequence(commandId); }}
void Commands::clearAllCommandSequences() { ensureModelConnected(); if (m_model != nullptr && m_model->settings() != nullptr) { m_model->settings()->clearAllCommandSequences(); }}
// ---------------------------------------------------------------------------// Per-target state// ---------------------------------------------------------------------------
CommandDef *Commands::find(const QString &commandId) const { return m_byId.value(commandId, nullptr);}
namespace {
// QML hands JS objects across the boundary as QJSValue-carrying variants// (var-property round-trips), not QVariantMaps. Unwrap once so every ctx// gate sees the map form.QVariant unwrapJsCtx(const QVariant &ctx) { if (ctx.metaType() == QMetaType::fromType<QJSValue>()) { return ctx.value<QJSValue>().toVariant(); } return ctx;}
} // namespace
int Commands::workspaceIndexFromCtx(const QVariant &ctx) { const QVariant unwrapped = unwrapJsCtx(ctx); if (!unwrapped.isValid() || unwrapped.isNull()) { return -1; } if (unwrapped.metaType() == QMetaType::fromType<QVariantMap>()) { return unwrapped.toMap().value(QStringLiteral("index"), -1).toInt(); } if (unwrapped.canConvert<QVariantMap>()) { return unwrapped.toMap().value(QStringLiteral("index"), -1).toInt(); } return -1;}
QString Commands::paneIdFromCtx(const QVariant &ctx) { const QVariant unwrapped = unwrapJsCtx(ctx); if (!unwrapped.isValid() || unwrapped.isNull()) { return {}; } // Pane contexts are the TerminalQuickItem itself. if (auto *term = unwrapped.value<TerminalQuickItem *>()) { return PanePool::instance() != nullptr ? PanePool::instance()->paneIdOf(term) : QString(); } if (unwrapped.metaType() == QMetaType::fromType<QVariantMap>()) { return unwrapped.toMap().value(QStringLiteral("paneId")).toString(); } return {};}
bool Commands::globalEnabled(const CommandDef &command) const { if (m_model == nullptr) { return true; } const bool hasFocus = !m_model->focusedPaneId().isEmpty(); const qsizetype workspaceCount = m_model->workspaces().size();
if (command.commandId() == QLatin1String("copy") || command.commandId() == QLatin1String("paste")) { // See the constructor note: selection state does not notify the model; // keep these globally enabled, the ctx gate decides. return true; } if (command.commandId() == QLatin1String("resetTerminal")) { return hasFocus && PanePool::instance() != nullptr && PanePool::instance()->item(m_model->focusedPaneId()) != nullptr; } // Editor history: the focused pane must be an editor pane. The undo stack's // depth is deliberately not part of this gate: nothing notifies Commands when // it changes, and undo() on an empty stack is already a no-op. if (command.commandId() == QLatin1String("editorUndo") || command.commandId() == QLatin1String("editorRedo")) { const QString paneId = m_model->focusedPaneId(); return !paneId.isEmpty() && m_model->paneKindOf(paneId) == QStringLiteral("editor"); } // Find is per-pane chrome: an editor pane keeps its bar state in a pooled // controller, a terminal pane filters through its live item. Frozen before // the generic search block below, which speaks only for live items. if (command.commandId() == QLatin1String("openSearch") || command.commandId() == QLatin1String("closeSearch")) { const QString paneId = m_model->focusedPaneId(); if (paneId.isEmpty()) { return false; } if (m_model->paneKindOf(paneId) == QStringLiteral("editor")) { return true; } return PanePool::instance() != nullptr && PanePool::instance()->item(paneId) != nullptr; } // Search navigation and copy mode: a focused pane with a live item. if (command.commandId() == QLatin1String("searchNext") || command.commandId() == QLatin1String("searchPrevious") || command.commandId() == QLatin1String("enterCopyMode") || command.commandId() == QLatin1String("exitCopyMode")) { return hasFocus && PanePool::instance() != nullptr && PanePool::instance()->item(m_model->focusedPaneId()) != nullptr; } // Pane zoom needs a sibling to hide: a single-pane tab zooming to // itself changes nothing, so the command greys out. Maximize stays // enabled on a single-pane tab — it still collapses the chrome. Checked // BEFORE the generic pane-category gate below, which would otherwise // answer for every pane command first. if (command.commandId() == QLatin1String("togglePaneZoom")) { if (!hasFocus) { return false; } const Workspace *ws = activeWorkspace(); if (ws == nullptr || ws->tabs().isEmpty()) { return false; } const XenoTab *tab = ws->tabs().at(ws->activeTabIndex()); return tab != nullptr && tab->paneIdList().size() > 1; } // Pane-splitting, pane lifecycle, focus movement, swapping. if (command.category() == QLatin1String("pane")) { return hasFocus; } // Tab reorder: needs >=2 tabs and room in the asked direction. Checked // BEFORE the generic tab-category gate below. if (command.commandId() == QLatin1String("moveTabLeft") || command.commandId() == QLatin1String("moveTabRight")) { const Workspace *ws = activeWorkspace(); if (ws == nullptr) { return false; } const int count = static_cast<int>(ws->tabs().size()); if (count < 2) { return false; } const int index = ws->activeTabIndex(); if (command.commandId() == QLatin1String("moveTabLeft")) { return index > 0; } return index < count - 1; } // Tab commands: any workspace exists. if (command.category() == QLatin1String("tab")) { return workspaceCount > 0; } // Workspace navigation: previous/next need >1; selectWorkspaceN needs >=n. if (command.commandId() == QLatin1String("previousWorkspace") || command.commandId() == QLatin1String("nextWorkspace")) { return workspaceCount > 1; } if (command.commandId().startsWith(QLatin1String("selectWorkspace"))) { bool ok = false; QString id = command.commandId(); const int n = id.remove(QLatin1String("selectWorkspace")).toInt(&ok); return ok && workspaceCount >= n; } if (command.commandId() == QLatin1String("closeWorkspace")) { return workspaceCount > 0; } if (command.commandId() == QLatin1String("addJjWorkspace")) { const Workspace *ws = activeWorkspace(); return ws != nullptr && ws->isJjRepo(); } if (command.commandId() == QLatin1String("addGitWorktree")) { const Workspace *ws = activeWorkspace(); return ws != nullptr && ws->isGitRepo(); } // quit, preferences, palette, jump, view, add-menu popups: always true. return true;}
bool Commands::commandEnabledFor(const QString &commandId, const QVariant &ctx) { ensureModelConnected(); CommandDef *command = find(commandId); if (command == nullptr) { return false; } const bool hasCtx = ctx.isValid() && !ctx.isNull(); if (!hasCtx) { return command->enabled(); }
// Context-gated commands (ported from CommandRegistry.qml enabledFor). if (commandId == QLatin1String("copy")) { if (auto *term = ctx.value<TerminalQuickItem *>()) { return term->hasActiveSelection(); } return false; } if (commandId == QLatin1String("paste")) { if (auto *term = ctx.value<TerminalQuickItem *>()) { return !term->hasActiveSelection(); } return false; } if (commandId == QLatin1String("workspaceForgetJj")) { const int index = workspaceIndexFromCtx(ctx); if (index < 0 || m_model == nullptr || index >= m_model->workspaces().size()) { return false; } return m_model->workspaces().at(index)->kind() == WorkspaceKind::JjWorkspace; } if (commandId == QLatin1String("addJjWorkspace") || commandId == QLatin1String("addGitWorktree")) { // Workspace-row context: offer what the CLICKED card's repo supports. // A null ctx (the add menu, the palette) keeps using command->enabled, // which globalEnabled gates on the active workspace. const int index = workspaceIndexFromCtx(ctx); if (index < 0 || m_model == nullptr || index >= m_model->workspaces().size()) { return false; } const Workspace *ws = m_model->workspaces().at(index); return commandId == QLatin1String("addJjWorkspace") ? ws->isJjRepo() : ws->isGitRepo(); } if (commandId == QLatin1String("newTab")) { // Workspace-row context: the row's index must be valid. const int index = workspaceIndexFromCtx(ctx); return index >= 0 || (m_model != nullptr && !m_model->workspaces().isEmpty()); } if (commandId == QLatin1String("editorUndo") || commandId == QLatin1String("editorRedo")) { const QString paneId = paneIdFromCtx(ctx); return m_model != nullptr && !paneId.isEmpty() && m_model->paneKindOf(paneId) == QStringLiteral("editor"); } // Workspace-row commands (rename/setDirectory/forget/delete): need a // valid row index. if (command->menus().contains(QLatin1String("workspace"))) { return workspaceIndexFromCtx(ctx) >= 0; } // Pane-context commands otherwise: the pane must still exist. if (command->menus().contains(QLatin1String("pane"))) { const QString paneId = paneIdFromCtx(ctx); return !paneId.isEmpty(); } return command->enabled();}
bool Commands::commandVisibleFor(const QString &commandId, const QVariant &ctx) { // Ported from CommandRegistry.qml: only workspaceForgetJj overrides the // base (always-visible) implementation, and its visibleFor == enabledFor. if (commandId == QLatin1String("workspaceForgetJj")) { return commandEnabledFor(commandId, ctx); } return true;}
void Commands::dispatch(const QString &commandId, const QVariant &ctx) { // The Commands singleton may be created before AppModel's (singleton // create order is not guaranteed); retry the connection on every // dispatch so model-executed commands never silently no-op. ensureModelConnected(); CommandDef *command = find(commandId); if (command == nullptr) { return; } if (!commandEnabledFor(commandId, ctx)) { return; }
// Pure model/pool commands execute directly inside Commands (they have // model access); everything else publishes to the controllers. const QVariant effectiveCtx = unwrapJsCtx(ctx); const QString focusedPaneId = m_model != nullptr ? m_model->focusedPaneId() : QString(); // Pane-context commands fall back to the focused pane (the QML registry's // `context ? context.paneId : AppModel.focusedPaneId` pattern). const QString targetPaneId = [&effectiveCtx, &focusedPaneId]() { const QString fromCtx = paneIdFromCtx(effectiveCtx); return fromCtx.isEmpty() ? focusedPaneId : fromCtx; }();
if (m_model == nullptr) { emit runCommand(commandId, effectiveCtx); return; }
if (commandId == QLatin1String("splitHorizontal")) { m_model->splitPaneFromString(targetPaneId, QStringLiteral("down")); return; } if (commandId == QLatin1String("splitVertical")) { m_model->splitPaneFromString(targetPaneId, QStringLiteral("right")); return; } if (commandId == QLatin1String("splitWithTerminal")) { m_model->splitPaneWithKind(targetPaneId, QStringLiteral("right"), QStringLiteral("terminal"), QString(), 0, 0); return; } if (commandId == QLatin1String("splitWithEditor")) { // An editor request with no path becomes a file-only picker, so this key // opens a chooser and never an empty editor. m_model->splitPaneWithKind(targetPaneId, QStringLiteral("right"), QStringLiteral("editor"), QString(), 0, 0); return; } if (commandId == QLatin1String("closePane")) { if (m_model->paneDirty(targetPaneId)) { emit dirtyPaneCloseRequested(targetPaneId); return; } m_model->closePane(targetPaneId); return; } if (commandId == QLatin1String("keepOnlyPane")) { if (m_model->paneDirty(targetPaneId)) { emit dirtyPaneCloseRequested(targetPaneId); return; } m_model->keepOnlyPane(targetPaneId); return; } if (commandId == QLatin1String("focusNextPane")) { m_model->focusNextPane(); return; } if (commandId == QLatin1String("togglePaneZoom")) { m_model->togglePaneZoom(); return; } if (commandId == QLatin1String("maximizePane")) { m_model->maximizePane(); return; } if (commandId == QLatin1String("focusPaneLeft")) { m_model->focusDirectionFromString(targetPaneId, QStringLiteral("left")); return; } if (commandId == QLatin1String("focusPaneRight")) { m_model->focusDirectionFromString(targetPaneId, QStringLiteral("right")); return; } if (commandId == QLatin1String("focusPaneUp")) { m_model->focusDirectionFromString(targetPaneId, QStringLiteral("up")); return; } if (commandId == QLatin1String("focusPaneDown")) { m_model->focusDirectionFromString(targetPaneId, QStringLiteral("down")); return; } if (commandId == QLatin1String("swapPaneLeft")) { if (PanePool *pool = PanePool::instance()) { pool->swapFocusedDirection(QStringLiteral("left")); } return; } if (commandId == QLatin1String("swapPaneRight")) { if (PanePool *pool = PanePool::instance()) { pool->swapFocusedDirection(QStringLiteral("right")); } return; } if (commandId == QLatin1String("swapPaneUp")) { if (PanePool *pool = PanePool::instance()) { pool->swapFocusedDirection(QStringLiteral("up")); } return; } if (commandId == QLatin1String("swapPaneDown")) { if (PanePool *pool = PanePool::instance()) { pool->swapFocusedDirection(QStringLiteral("down")); } return; } if (commandId == QLatin1String("resetTerminal")) { if (auto *term = qobject_cast<TerminalQuickItem *>( PanePool::instance() != nullptr ? PanePool::instance()->item(targetPaneId) : nullptr)) { term->resetTerminal(); } return; } if (commandId == QLatin1String("copy")) { if (ctx.isValid() && !ctx.isNull()) { if (auto *term = ctx.value<TerminalQuickItem *>()) { term->copySelection(); } return; } if (auto *term = qobject_cast<TerminalQuickItem *>( PanePool::instance() != nullptr ? PanePool::instance()->item(focusedPaneId) : nullptr)) { term->copySelection(); } return; } if (commandId == QLatin1String("paste")) { if (ctx.isValid() && !ctx.isNull()) { if (auto *term = ctx.value<TerminalQuickItem *>()) { term->pasteFromClipboard(); } return; } if (auto *term = qobject_cast<TerminalQuickItem *>( PanePool::instance() != nullptr ? PanePool::instance()->item(focusedPaneId) : nullptr)) { term->pasteFromClipboard(); } return; } if (commandId == QLatin1String("newTab")) { const int index = workspaceIndexFromCtx(effectiveCtx); if (index >= 0) { m_model->newTab(index); return; } m_model->newTab(m_model->workspaceIndexOf(m_model->activeWorkspaceId())); return; } if (commandId == QLatin1String("closeTab")) { const int wsIndex = m_model->workspaceIndexOf(m_model->activeWorkspaceId()); const QList<Workspace*>& workspaces = m_model->workspaces(); if (wsIndex >= 0 && wsIndex < workspaces.size()) { const int tabIndex = workspaces.at(wsIndex)->activeTabIndex(); if (!m_model->dirtyEditorPanesInTab(wsIndex, tabIndex).isEmpty()) { emit dirtyTabCloseRequested(wsIndex, tabIndex); return; } m_model->closeTab(wsIndex, tabIndex); } return; } if (commandId == QLatin1String("nextTab")) { m_model->nextTab(); return; } if (commandId == QLatin1String("previousTab")) { m_model->previousTab(); return; } if (commandId == QLatin1String("moveTabLeft") || commandId == QLatin1String("moveTabRight")) { const int wsIndex = m_model->workspaceIndexOf(m_model->activeWorkspaceId()); const QList<Workspace*>& workspaces = m_model->workspaces(); if (wsIndex < 0 || wsIndex >= workspaces.size()) { return; } const int tabIndex = workspaces.at(wsIndex)->activeTabIndex(); m_model->moveTab(wsIndex, tabIndex, tabIndex + (commandId == QLatin1String("moveTabLeft") ? -1 : 1)); return; } if (commandId == QLatin1String("renameTab")) { // A dialog decision point: publish to the controllers. emit runCommand(commandId, effectiveCtx); return; } if (commandId == QLatin1String("previousWorkspace")) { m_model->previousWorkspace(); return; } if (commandId == QLatin1String("nextWorkspace")) { m_model->nextWorkspace(); return; } if (commandId.startsWith(QLatin1String("selectWorkspace"))) { bool ok = false; QString id = commandId; const int n = id.remove(QLatin1String("selectWorkspace")).toInt(&ok); if (ok) { m_model->selectWorkspaceByNumber(n); } return; } if (commandId == QLatin1String("resetLayout")) { m_model->resetLayout(); return; } if (commandId == QLatin1String("equalizeSplits")) { m_model->equalizeSplits(); return; } if (commandId == QLatin1String("toggleWrap")) { if (XenoSettings *settings = AppModel::instance()->settings()) { settings->setEditorWrap(!settings->editorWrap()); } return; }
// Everything else (jumpToPane, commandPalette, resetLayout aside — // handled above — toggleSidebar, missionControl, preferences, quit, // newWorkspace, the add-workspace sources, closeWorkspace, and the // workspace-row commands) needs page/dialog/menu state that Commands // does not know about: publish to the controllers. emit runCommand(commandId, effectiveCtx);}
// ---------------------------------------------------------------------------// Enabled recompute// ---------------------------------------------------------------------------
Workspace *Commands::activeWorkspace() { AppModel *model = AppModel::instance(); if (model == nullptr) { return nullptr; } const QString activeId = model->activeWorkspaceId(); if (activeId.isEmpty()) { return nullptr; } for (Workspace *ws : model->workspaces()) { if (ws != nullptr && ws->id() == activeId) { return ws; } } return nullptr;}
void Commands::ensureModelConnected() { if (m_modelConnected && m_model != nullptr) { return; } AppModel *model = AppModel::instance(); if (model == nullptr) { return; } if (m_modelConnected) { // The previous connection target died; reconnect to the new instance. m_modelConnected = false; } m_model = model; connect(model, &AppModel::focusedPaneChanged, this, &Commands::recomputeEnabled); connect(model, &AppModel::activeWorkspaceChanged, this, &Commands::recomputeEnabled); connect(model, &AppModel::workspacesChanged, this, &Commands::recomputeEnabled); connect(model, &AppModel::tabsChanged, this, &Commands::recomputeEnabled); connect(model, &AppModel::activeTabChanged, this, &Commands::recomputeEnabled); connect(model, &AppModel::structureChanged, this, &Commands::recomputeEnabled); connect(model, &AppModel::workspaceDataChanged, this, &Commands::recomputeEnabled); if (model->settings() != nullptr) { connect(model->settings(), &XenoSettings::commandSequencesChanged, this, &Commands::applyOverrides); } m_modelConnected = true; applyOverrides();}
void Commands::recomputeEnabled() { ensureModelConnected(); for (CommandDef *command : std::as_const(m_commands)) { command->setEnabled(globalEnabled(*command)); }}