#include "Workspace.h" // Workspace hierarchy + tab object implementation. These objects ARE the // model (no variant, no proxy mirror): AppModel mutates them in place and // QML binds them directly. #include #include // --- Workspace (abstract base) ---------------------------------------------- Workspace::Workspace(QString id, QString name, QString path, QObject* parent) : QObject(parent), m_id(std::move(id)), m_name(std::move(name)), m_path(std::move(path)) {} Workspace::~Workspace() = default; void Workspace::setName(const QString& name) { if (m_name == name) { return; } m_name = name; emit nameChanged(); } void Workspace::setPath(const QString& path) { if (m_path == path) { return; } m_path = path; emit pathChanged(); } void Workspace::setJjRepo(bool isJjRepo) { if (m_isJjRepo == isJjRepo) { return; } m_isJjRepo = isJjRepo; emit isJjRepoChanged(); } void Workspace::setGitRepo(bool isGitRepo) { if (m_isGitRepo == isGitRepo) { return; } m_isGitRepo = isGitRepo; emit isGitRepoChanged(); } // Kind-dependent fields: "" / no-op where the kind has no such field. QString Workspace::repoRoot() const { return {}; } void Workspace::setRepoRoot(const QString& repoRoot) { Q_UNUSED(repoRoot); } QString Workspace::jjWorkspaceName() const { return {}; } void Workspace::setJjWorkspaceName(const QString& name) { Q_UNUSED(name); } void Workspace::setTabs(const QList& tabs) { m_tabs = tabs; emit tabsChanged(); } void Workspace::insertTab(int index, XenoTab* tab) { m_tabs.insert(m_tabs.begin() + index, tab); emit tabsChanged(); } void Workspace::removeTab(int index) { m_tabs.removeAt(index); emit tabsChanged(); } void Workspace::moveTab(int from, int to) { const int count = static_cast(m_tabs.size()); if (count < 2) { return; } if (from < 0 || from >= count) { return; } const int dst = std::clamp(to, 0, count - 1); if (dst == from) { return; } // Move the object, not a copy: the active flag rides along, so the // active tab is the same tab at its new index. XenoTab* tab = m_tabs.at(from); m_tabs.removeAt(from); m_tabs.insert(m_tabs.begin() + dst, tab); emit tabsChanged(); } int Workspace::activeTabIndex() const { for (auto&& [i, tab] : std::views::zip(std::views::iota(0), m_tabs)) { if (tab->active()) { return static_cast(i); } } return 0; } void Workspace::setActiveTabIndex(int index) { for (auto&& [i, tab] : std::views::zip(std::views::iota(0), m_tabs)) { tab->setActive(static_cast(i) == index); } emit activeTabChanged(); } // --- XenoTab ------------------------------------------------------------------ XenoTab::XenoTab(QString id, QObject* parent) : QObject(parent), m_id(std::move(id)) {} XenoTab::~XenoTab() = default; void XenoTab::setLastActivePaneId(const QString& paneId) { if (m_lastActivePaneId == paneId) { return; } m_lastActivePaneId = paneId; emit lastActivePaneIdChanged(); } // Zoom is validated by AppModel (the pane must belong to this tab); the // setter itself only guards identity. A pane that leaves the tab (close, // move) clears the zoom through the model so the id never dangles. void XenoTab::setZoomedPaneId(QString paneId) { if (m_zoomedPaneId == paneId) { return; } m_zoomedPaneId = paneId; emit zoomedPaneIdChanged(); } void XenoTab::setTreeRoot(SplitContainerNode* root) { if (m_treeRoot == root) { return; } if (m_treeRoot != nullptr) { m_treeRoot->deleteLater(); } m_treeRoot = root; if (m_treeRoot != nullptr) { m_treeRoot->setParent(this); } emit treeRootChanged(); emit structureChanged(); } QVariantList XenoTab::paneIds() const { QVariantList out; if (m_treeRoot == nullptr) { return out; } const QStringList ids = paneIdsInLayoutOrder(*m_treeRoot); out.reserve(ids.size()); for (const QString& id : ids) { out.append(QVariant(id)); } return out; } void XenoTab::notifyStructureChanged() { emit structureChanged(); } QStringList XenoTab::paneIdList() const { if (m_treeRoot == nullptr) { return {}; } return paneIdsInLayoutOrder(*m_treeRoot); } void XenoTab::setActive(bool active) { if (m_active == active) { return; } m_active = active; emit activeChanged(); } void XenoTab::setTitleOverride(const QString& overrideTitle) { if (m_titleOverride == overrideTitle) { return; } m_titleOverride = overrideTitle; emit titleOverrideChanged(); // The displayed title flips between override and reported. emit titleChanged(); } const QString& XenoTab::title() const { return m_titleOverride.isEmpty() ? m_title : m_titleOverride; } void XenoTab::setTitle(const QString& title) { // Always track the reported title underneath; only notify when it is // actually visible (no override set) or changed. if (m_title == title) { return; } m_title = title; if (m_titleOverride.isEmpty()) { emit titleChanged(); } } // --- concrete workspace kinds ------------------------------------------------- DirWorkspace::DirWorkspace(QString id, QString name, QString path, QObject* parent) : Workspace(std::move(id), std::move(name), std::move(path), parent) {} DirWorkspace::~DirWorkspace() = default; RepositoryWorkspace::RepositoryWorkspace(QString id, QString name, QString path, QString repoRoot, QObject* parent) : Workspace(std::move(id), std::move(name), std::move(path), parent), m_repoRoot(std::move(repoRoot)) {} RepositoryWorkspace::~RepositoryWorkspace() = default; JjWorkspace::JjWorkspace(QString id, QString name, QString path, QString repoRoot, QString jjWorkspaceName, QObject* parent) : Workspace(std::move(id), std::move(name), std::move(path), parent), m_repoRoot(std::move(repoRoot)), m_jjWorkspaceName(std::move(jjWorkspaceName)) {} JjWorkspace::~JjWorkspace() = default; GitWorktree::GitWorktree(QString id, QString name, QString path, QString repoRoot, QObject* parent) : Workspace(std::move(id), std::move(name), std::move(path), parent), m_repoRoot(std::move(repoRoot)) {} GitWorktree::~GitWorktree() = default;