From a8f786d8d19429663904d9f9f5774510da188f1c Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Sat, 21 Mar 2026 20:14:29 +0000 Subject: [PATCH] add context panel --- app/gcompose/CMakeLists.txt | 6 ++ app/gcompose/src/ContextModel.cpp | 132 +++++++++++++++++++++++++++++ app/gcompose/src/ContextModel.h | 26 ++++++ app/gcompose/src/ContextPanel.cpp | 43 ++++++++++ app/gcompose/src/ContextPanel.h | 31 +++++++ app/gcompose/src/MainPanel.cpp | 62 +++++++++++--- app/gcompose/src/MainPanel.h | 13 +++ app/gcompose/src/MainWindow.cpp | 5 ++ app/gcompose/src/MainWindow.h | 2 + app/gcompose/src/QViewportPane.cpp | 6 +- app/gcompose/src/StyleManager.cpp | 44 ++++++++++ app/gcompose/src/StyleManager.h | 13 +++ app/gcompose/src/ViewportPane.cpp | 6 +- app/gcompose/src/main.cpp | 9 ++ 14 files changed, 381 insertions(+), 17 deletions(-) create mode 100644 app/gcompose/src/ContextModel.cpp create mode 100644 app/gcompose/src/ContextModel.h create mode 100644 app/gcompose/src/ContextPanel.cpp create mode 100644 app/gcompose/src/ContextPanel.h create mode 100644 app/gcompose/src/StyleManager.cpp create mode 100644 app/gcompose/src/StyleManager.h diff --git a/app/gcompose/CMakeLists.txt b/app/gcompose/CMakeLists.txt index 6ed246d..961fe98 100644 --- a/app/gcompose/CMakeLists.txt +++ b/app/gcompose/CMakeLists.txt @@ -7,6 +7,12 @@ add_executable(gcompose src/ViewportPane.cpp src/MainPanel.h src/MainPanel.cpp + src/ContextPanel.h + src/ContextPanel.cpp + src/ContextModel.h + src/ContextModel.cpp + src/StyleManager.h + src/StyleManager.cpp ) set_target_properties(gcompose PROPERTIES diff --git a/app/gcompose/src/ContextModel.cpp b/app/gcompose/src/ContextModel.cpp new file mode 100644 index 0000000..8a410c4 --- /dev/null +++ b/app/gcompose/src/ContextModel.cpp @@ -0,0 +1,132 @@ +#include "ContextModel.h" +#include +#include +#include +#include + +ContextModel::ContextModel(QObject* parent) + : QAbstractItemModel(parent), m_rootContext(nullptr) {} + +ContextModel::~ContextModel() {} + +void ContextModel::setContext(uLib::ObjectsContext* context) { + beginResetModel(); + m_rootContext = context; + endResetModel(); +} + +QModelIndex ContextModel::index(int row, int column, const QModelIndex& parent) const { + if (!hasIndex(row, column, parent) || !m_rootContext) { + return QModelIndex(); + } + + if (!parent.isValid()) { + if (row < m_rootContext->GetCount()) { + return createIndex(row, column, m_rootContext->GetObject(row)); + } + } else { + uLib::Object* parentObj = static_cast(parent.internalPointer()); + uLib::ObjectsContext* parentCtx = dynamic_cast(parentObj); + if (parentCtx && row < parentCtx->GetCount()) { + return createIndex(row, column, parentCtx->GetObject(row)); + } + } + return QModelIndex(); +} + +QModelIndex ContextModel::parent(const QModelIndex& child) const { + if (!child.isValid() || !m_rootContext) { + return QModelIndex(); + } + + uLib::Object* childObj = static_cast(child.internalPointer()); + + // Finding the parent of childObj is O(N) since there is no parent pointer. + // We just do a recursive search starting from root context. + std::function findParent = + [&findParent](uLib::ObjectsContext* ctx, uLib::Object* target) -> uLib::ObjectsContext* { + for (const auto& obj : ctx->GetObjects()) { + if (obj == target) return ctx; + if (auto subCtx = dynamic_cast(obj)) { + if (auto p = findParent(subCtx, target)) return p; + } + } + return nullptr; + }; + + uLib::ObjectsContext* parentCtx = findParent(m_rootContext, childObj); + if (!parentCtx || parentCtx == m_rootContext) { + return QModelIndex(); // Root items have invalid parent index + } + + // Now need to find the row of parentCtx in its own parent Context. + uLib::ObjectsContext* grandParentCtx = findParent(m_rootContext, parentCtx); + if (!grandParentCtx) grandParentCtx = m_rootContext; + + int row = -1; + for (size_t i = 0; i < grandParentCtx->GetCount(); ++i) { + if (grandParentCtx->GetObject(i) == parentCtx) { + row = (int)i; + break; + } + } + + if (row != -1) { + return createIndex(row, 0, parentCtx); + } + return QModelIndex(); +} + +int ContextModel::rowCount(const QModelIndex& parent) const { + if (!m_rootContext) return 0; + + if (!parent.isValid()) { + return m_rootContext->GetCount(); + } + + uLib::Object* parentObj = static_cast(parent.internalPointer()); + if (auto parentCtx = dynamic_cast(parentObj)) { + return parentCtx->GetCount(); + } + return 0; // leaf node +} + +int ContextModel::columnCount(const QModelIndex& parent) const { + return 1; +} + +static QString getDemangledName(const std::type_info& info) { + int status = -4; + char* demangled = abi::__cxa_demangle(info.name(), nullptr, nullptr, &status); + QString res = (status == 0 && demangled) ? QString::fromUtf8(demangled) : QString::fromUtf8(info.name()); + if (demangled) free(demangled); + + // Remove namespaces + int lastColon = res.lastIndexOf("::"); + if (lastColon != -1) { + res = res.mid(lastColon + 2); + } + + // Remove "class " prefix if any + if (res.startsWith("class ")) res = res.mid(6); + return res; +} + +QVariant ContextModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) return QVariant(); + + uLib::Object* obj = static_cast(index.internalPointer()); + + if (role == Qt::DisplayRole) { + return getDemangledName(typeid(*obj)); + } + + return QVariant(); +} + +QVariant ContextModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) { + return "Object Type"; + } + return QVariant(); +} diff --git a/app/gcompose/src/ContextModel.h b/app/gcompose/src/ContextModel.h new file mode 100644 index 0000000..759255e --- /dev/null +++ b/app/gcompose/src/ContextModel.h @@ -0,0 +1,26 @@ +#ifndef CONTEXT_MODEL_H +#define CONTEXT_MODEL_H + +#include +#include "Core/ObjectsContext.h" + +class ContextModel : public QAbstractItemModel { + Q_OBJECT +public: + explicit ContextModel(QObject* parent = nullptr); + virtual ~ContextModel(); + + void setContext(uLib::ObjectsContext* context); + + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + +private: + uLib::ObjectsContext* m_rootContext; +}; + +#endif // CONTEXT_MODEL_H diff --git a/app/gcompose/src/ContextPanel.cpp b/app/gcompose/src/ContextPanel.cpp new file mode 100644 index 0000000..718faab --- /dev/null +++ b/app/gcompose/src/ContextPanel.cpp @@ -0,0 +1,43 @@ +#include "ContextPanel.h" +#include "ContextModel.h" +#include +#include +#include +#include + +ContextPanel::ContextPanel(QWidget* parent) : QWidget(parent) { + m_layout = new QVBoxLayout(this); + m_layout->setContentsMargins(0, 0, 0, 0); + m_layout->setSpacing(0); + + // Title bar setup + m_titleBar = new QWidget(this); + m_titleBar->setObjectName("PaneTitleBar"); + m_titleBar->setFixedHeight(22); + + auto* titleLayout = new QHBoxLayout(m_titleBar); + titleLayout->setContentsMargins(5, 0, 5, 0); + + m_titleLabel = new QLabel("Context Panel", m_titleBar); + m_titleLabel->setObjectName("TitleLabel"); + titleLayout->addWidget(m_titleLabel); + titleLayout->addStretch(); + + m_layout->addWidget(m_titleBar); + + m_treeView = new QTreeView(this); + m_treeView->setObjectName("ContextTree"); + m_treeView->setHeaderHidden(false); + + m_model = new ContextModel(this); + m_treeView->setModel(m_model); + + m_layout->addWidget(m_treeView); +} + +ContextPanel::~ContextPanel() {} + +void ContextPanel::setContext(uLib::ObjectsContext* context) { + m_model->setContext(context); + m_treeView->expandAll(); +} diff --git a/app/gcompose/src/ContextPanel.h b/app/gcompose/src/ContextPanel.h new file mode 100644 index 0000000..76d991c --- /dev/null +++ b/app/gcompose/src/ContextPanel.h @@ -0,0 +1,31 @@ +#ifndef CONTEXT_PANEL_H +#define CONTEXT_PANEL_H + +#include + +class QTreeView; +class QVBoxLayout; +class QLabel; +class ContextModel; + +namespace uLib { + class ObjectsContext; +} + +class ContextPanel : public QWidget { + Q_OBJECT +public: + explicit ContextPanel(QWidget* parent = nullptr); + virtual ~ContextPanel(); + + void setContext(uLib::ObjectsContext* context); + +private: + QVBoxLayout* m_layout; + QWidget* m_titleBar; + QLabel* m_titleLabel; + QTreeView* m_treeView; + ContextModel* m_model; +}; + +#endif // CONTEXT_PANEL_H diff --git a/app/gcompose/src/MainPanel.cpp b/app/gcompose/src/MainPanel.cpp index b5399e7..ca2c093 100644 --- a/app/gcompose/src/MainPanel.cpp +++ b/app/gcompose/src/MainPanel.cpp @@ -1,10 +1,15 @@ #include "MainPanel.h" #include "ViewportPane.h" +#include "ContextPanel.h" #include #include #include #include #include +#include +#include +#include +#include "StyleManager.h" MainPanel::MainPanel(QWidget* parent) : QWidget(parent) { auto* mainLayout = new QVBoxLayout(this); @@ -13,37 +18,72 @@ MainPanel::MainPanel(QWidget* parent) : QWidget(parent) { // 1. Top Menu Panel auto* menuPanel = new QWidget(this); + menuPanel->setObjectName("MenuPanel"); menuPanel->setFixedHeight(36); - menuPanel->setStyleSheet("background-color: #2b2b2b; border-bottom: 1px solid #111;"); auto* menuLayout = new QHBoxLayout(menuPanel); menuLayout->setContentsMargins(10, 0, 10, 0); menuLayout->setSpacing(15); auto* logo = new QLabel("G-COMPOSE", menuPanel); - logo->setStyleSheet("font-weight: bold; color: #0078d7; font-size: 14px; letter-spacing: 1px;"); + logo->setObjectName("LogoLabel"); - auto* btnOpen = new QPushButton("Open", menuPanel); - auto* btnSave = new QPushButton("Save", menuPanel); - - QString btnStyle = "QPushButton { background: transparent; color: #ccc; border: none; padding: 5px 10px; }" - "QPushButton:hover { background: #3c3c3c; color: white; border-radius: 4px; }"; - btnOpen->setStyleSheet(btnStyle); - btnSave->setStyleSheet(btnStyle); + // File Menu Button + auto* btnFile = new QPushButton("File", menuPanel); + btnFile->setObjectName("MenuButton"); + auto* fileMenu = new QMenu(btnFile); + fileMenu->addAction("Open", this, &MainPanel::onOpen); + fileMenu->addAction("Save", this, &MainPanel::onSave); + btnFile->setMenu(fileMenu); + + // Theme Menu Button + auto* btnTheme = new QPushButton("Theme", menuPanel); + btnTheme->setObjectName("MenuButton"); + auto* themeMenu = new QMenu(btnTheme); + themeMenu->addAction("Dark", this, &MainPanel::onDarkTheme); + themeMenu->addAction("Bright", this, &MainPanel::onBrightTheme); + btnTheme->setMenu(themeMenu); menuLayout->addWidget(logo); - menuLayout->addWidget(btnOpen); - menuLayout->addWidget(btnSave); + menuLayout->addWidget(btnFile); + menuLayout->addWidget(btnTheme); menuLayout->addStretch(); mainLayout->addWidget(menuPanel); // 2. Central Splitter Area m_rootSplitter = new QSplitter(Qt::Horizontal, this); + m_contextPanel = new ContextPanel(m_rootSplitter); + m_rootSplitter->addWidget(m_contextPanel); m_firstPane = new ViewportPane(m_rootSplitter); m_rootSplitter->addWidget(m_firstPane); + + // Set initial sizes + QList sizes; + sizes << 200 << 1000; + m_rootSplitter->setSizes(sizes); mainLayout->addWidget(m_rootSplitter, 1); } +void MainPanel::setContext(uLib::ObjectsContext* context) { + m_contextPanel->setContext(context); +} + +void MainPanel::onOpen() { + // Placeholder for open logic +} + +void MainPanel::onSave() { + // Placeholder for save logic +} + +void MainPanel::onDarkTheme() { + StyleManager::applyStyle(qApp, "dark"); +} + +void MainPanel::onBrightTheme() { + StyleManager::applyStyle(qApp, "bright"); +} + MainPanel::~MainPanel() {} diff --git a/app/gcompose/src/MainPanel.h b/app/gcompose/src/MainPanel.h index 4c1af4c..5b85f4a 100644 --- a/app/gcompose/src/MainPanel.h +++ b/app/gcompose/src/MainPanel.h @@ -5,6 +5,11 @@ class QSplitter; class ViewportPane; +class ContextPanel; + +namespace uLib { + class ObjectsContext; +} class MainPanel : public QWidget { Q_OBJECT @@ -12,11 +17,19 @@ public: explicit MainPanel(QWidget* parent = nullptr); virtual ~MainPanel(); + void setContext(uLib::ObjectsContext* context); ViewportPane* getFirstPane() const { return m_firstPane; } +private slots: + void onOpen(); + void onSave(); + void onDarkTheme(); + void onBrightTheme(); + private: QSplitter* m_rootSplitter; ViewportPane* m_firstPane; + ContextPanel* m_contextPanel; }; #endif // MAINPANEL_H diff --git a/app/gcompose/src/MainWindow.cpp b/app/gcompose/src/MainWindow.cpp index 153ff4d..8287348 100644 --- a/app/gcompose/src/MainWindow.cpp +++ b/app/gcompose/src/MainWindow.cpp @@ -1,6 +1,7 @@ #include "MainWindow.h" #include #include "MainPanel.h" +#include "Core/ObjectsContext.h" using namespace uLib; @@ -14,3 +15,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { MainWindow::~MainWindow() { } + +void MainWindow::setContext(uLib::ObjectsContext* context) { + m_panel->setContext(context); +} diff --git a/app/gcompose/src/MainWindow.h b/app/gcompose/src/MainWindow.h index d5b54ee..7a2adee 100644 --- a/app/gcompose/src/MainWindow.h +++ b/app/gcompose/src/MainWindow.h @@ -10,6 +10,7 @@ class ViewportPane; namespace uLib { namespace Vtk { } +class ObjectsContext; } class MainWindow : public QMainWindow { @@ -18,6 +19,7 @@ public: MainWindow(QWidget* parent = nullptr); virtual ~MainWindow(); + void setContext(uLib::ObjectsContext* context); MainPanel* getPanel() { return m_panel; } private: diff --git a/app/gcompose/src/QViewportPane.cpp b/app/gcompose/src/QViewportPane.cpp index 7331101..b851564 100644 --- a/app/gcompose/src/QViewportPane.cpp +++ b/app/gcompose/src/QViewportPane.cpp @@ -17,19 +17,19 @@ QViewportPane::QViewportPane(QWidget* parent) : QWidget(parent), m_viewport(null // Title bar setup m_titleBar = new QWidget(this); + m_titleBar->setObjectName("PaneTitleBar"); m_titleBar->setFixedHeight(22); - m_titleBar->setStyleSheet("background-color: #333; color: white;"); auto* titleLayout = new QHBoxLayout(m_titleBar); titleLayout->setContentsMargins(5, 0, 5, 0); m_titleLabel = new QLabel("Viewport", m_titleBar); + m_titleLabel->setObjectName("TitleLabel"); auto* closeBtn = new QToolButton(m_titleBar); + closeBtn->setObjectName("PaneCloseButton"); closeBtn->setText("X"); closeBtn->setFixedSize(18, 18); - closeBtn->setStyleSheet("QToolButton { border: none; font-weight: bold; background: transparent; color: #ccc; } " - "QToolButton:hover { color: white; background: red; }"); titleLayout->addWidget(m_titleLabel); titleLayout->addStretch(); diff --git a/app/gcompose/src/StyleManager.cpp b/app/gcompose/src/StyleManager.cpp new file mode 100644 index 0000000..521e277 --- /dev/null +++ b/app/gcompose/src/StyleManager.cpp @@ -0,0 +1,44 @@ +#include "StyleManager.h" +#include + +static const QString DARK_THEME = R"( +QWidget#MenuPanel { background-color: #2b2b2b; border-bottom: 1px solid #111; } +QLabel#LogoLabel { font-weight: bold; color: #0078d7; font-size: 14px; letter-spacing: 1px; } +QPushButton#MenuButton { background: transparent; color: #ccc; border: none; padding: 5px 10px; } +QPushButton#MenuButton:hover { background: #3c3c3c; color: white; border-radius: 4px; } +QWidget#PaneTitleBar { background-color: #333; color: white; } +QToolButton#PaneCloseButton { border: none; font-weight: bold; background: transparent; color: #ccc; } +QToolButton#PaneCloseButton:hover { color: white; background: red; } +QMenu { background-color: #2b2b2b; color: white; border: 1px solid #111; } +QMenu::item:selected { background-color: #3c3c3c; } +QTreeView#ContextTree { background-color: #1e1e1e; color: #ccc; border: none; } +QTreeView#ContextTree::item:hover { background-color: #2a2d2e; } +QTreeView#ContextTree::item:selected { background-color: #094771; color: white; } +QHeaderView::section { background-color: #252526; color: #ccc; border: 1px solid #323233; padding: 4px; } +)"; + +static const QString BRIGHT_THEME = R"( +QWidget#MenuPanel { background-color: #f0f0f0; border-bottom: 1px solid #ccc; } +QLabel#LogoLabel { font-weight: bold; color: #005a9e; font-size: 14px; letter-spacing: 1px; } +QPushButton#MenuButton { background: transparent; color: #333; border: none; padding: 5px 10px; } +QPushButton#MenuButton:hover { background: #d0d0d0; color: black; border-radius: 4px; } +QWidget#PaneTitleBar { background-color: #e0e0e0; color: black; } +QToolButton#PaneCloseButton { border: none; font-weight: bold; background: transparent; color: #666; } +QToolButton#PaneCloseButton:hover { color: white; background: #e81123; } +QMenu { background-color: #f0f0f0; color: black; border: 1px solid #ccc; } +QMenu::item:selected { background-color: #d0d0d0; } +QTreeView#ContextTree { background-color: #ffffff; color: #333; border: none; } +QTreeView#ContextTree::item:hover { background-color: #f2f2f2; } +QTreeView#ContextTree::item:selected { background-color: #0078d7; color: white; } +QHeaderView::section { background-color: #f3f3f3; color: #333; border: 1px solid #ccc; padding: 4px; } +)"; + +void StyleManager::applyStyle(QApplication* app, const QString& themeName) { + if (!app) return; + + if (themeName == "bright") { + app->setStyleSheet(BRIGHT_THEME); + } else { + app->setStyleSheet(DARK_THEME); // default + } +} diff --git a/app/gcompose/src/StyleManager.h b/app/gcompose/src/StyleManager.h new file mode 100644 index 0000000..5cde819 --- /dev/null +++ b/app/gcompose/src/StyleManager.h @@ -0,0 +1,13 @@ +#ifndef STYLEMANAGER_H +#define STYLEMANAGER_H + +#include + +class QApplication; + +class StyleManager { +public: + static void applyStyle(QApplication* app, const QString& themeName); +}; + +#endif // STYLEMANAGER_H diff --git a/app/gcompose/src/ViewportPane.cpp b/app/gcompose/src/ViewportPane.cpp index 0c2e594..d43c014 100644 --- a/app/gcompose/src/ViewportPane.cpp +++ b/app/gcompose/src/ViewportPane.cpp @@ -17,19 +17,19 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt // Title bar setup m_titleBar = new QWidget(this); + m_titleBar->setObjectName("PaneTitleBar"); m_titleBar->setFixedHeight(22); - m_titleBar->setStyleSheet("background-color: #333; color: white;"); auto* titleLayout = new QHBoxLayout(m_titleBar); titleLayout->setContentsMargins(5, 0, 5, 0); m_titleLabel = new QLabel("Viewport", m_titleBar); + m_titleLabel->setObjectName("TitleLabel"); auto* closeBtn = new QToolButton(m_titleBar); + closeBtn->setObjectName("PaneCloseButton"); closeBtn->setText("X"); closeBtn->setFixedSize(18, 18); - closeBtn->setStyleSheet("QToolButton { border: none; font-weight: bold; background: transparent; color: #ccc; } " - "QToolButton:hover { color: white; background: red; }"); titleLayout->addWidget(m_titleLabel); titleLayout->addStretch(); diff --git a/app/gcompose/src/main.cpp b/app/gcompose/src/main.cpp index 8755d19..737827c 100644 --- a/app/gcompose/src/main.cpp +++ b/app/gcompose/src/main.cpp @@ -2,6 +2,7 @@ #include "MainWindow.h" #include "MainPanel.h" #include "ViewportPane.h" +#include "StyleManager.h" #include "Math/ContainerBox.h" #include @@ -11,6 +12,8 @@ #include #include +#include "Core/ObjectsContext.h" + #include #include #include @@ -26,6 +29,7 @@ using namespace uLib::literals; int main(int argc, char** argv) { QApplication app(argc, argv); + StyleManager::applyStyle(&app, "dark"); std::cout << "Starting gcompose Qt application..." << std::endl; ContainerBox world_box(Vector3f(1, 1, 1)); @@ -35,8 +39,13 @@ int main(int argc, char** argv) { scene.ConstructWorldBox(world_box.GetSize(), "G4_AIR"); scene.Initialize(); + uLib::ObjectsContext globalContext; + globalContext.AddObject(&world_box); + globalContext.AddObject(&scene); + // 2. Initialize MainWindow (contains embedded VTK QViewport) MainWindow window; + window.setContext(&globalContext); MainPanel* panel = window.getPanel(); ViewportPane* pane = panel->getFirstPane(); Vtk::QViewport* viewport = qobject_cast(pane->currentViewport());