diff --git a/src/interface/CMakeLists.txt b/src/interface/CMakeLists.txt index 015aac0da6..370dabb600 100644 --- a/src/interface/CMakeLists.txt +++ b/src/interface/CMakeLists.txt @@ -25,14 +25,20 @@ find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets Svg REQUIRED PATHS ${QT set(INTERFACE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ElementRegistry.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ElementRegistry.h ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.h ${CMAKE_CURRENT_SOURCE_DIR}/components/SvgIcon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/components/SvgIcon.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/Style.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/Style.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/KeyValueTable.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/KeyValueTable.h ${CMAKE_CURRENT_SOURCE_DIR}/components/Section.cpp ${CMAKE_CURRENT_SOURCE_DIR}/components/Section.h - ${CMAKE_CURRENT_SOURCE_DIR}/components/PanelChrome.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/components/PanelChrome.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.h ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelTypes.h ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelWidget.h diff --git a/src/interface/ElementRegistry.cpp b/src/interface/ElementRegistry.cpp new file mode 100644 index 0000000000..1e41385d33 --- /dev/null +++ b/src/interface/ElementRegistry.cpp @@ -0,0 +1,82 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "ElementRegistry.h" + +#include "../ifcviewer/GeometryStreamer.h" +#include "../ifcviewer/SceneLoader.h" +#include "../ifcviewer/SidecarCache.h" + +namespace ifcinterface { + +ElementRegistry::ElementRegistry(QObject* parent) + : QObject(parent) +{ +} + +void ElementRegistry::bindLoader(SceneLoader* loader) { + connect(loader, &SceneLoader::sidecarElementsReady, + this, &ElementRegistry::onSidecarElementsReady); + connect(loader, &SceneLoader::streamedElementsReady, + this, &ElementRegistry::onStreamedElementsReady); +} + +std::optional ElementRegistry::find(uint32_t object_id) const { + auto it = elements_.find(object_id); + if (it == elements_.end()) return std::nullopt; + return it->second; +} + +void ElementRegistry::onSidecarElementsReady(uint32_t /*mid*/, + std::vector elements, + std::string string_table) { + auto str = [&](uint32_t offset, uint32_t length) -> QString { + if (length == 0 || offset + length > string_table.size()) return {}; + return QString::fromStdString(string_table.substr(offset, length)); + }; + + for (const auto& pe : elements) { + BasicElementInfo info; + info.object_id = pe.object_id; + info.model_id = pe.model_id; + info.ifc_id = pe.ifc_id; + info.parent_id = pe.parent_id; + info.guid = str(pe.guid_offset, pe.guid_length); + info.name = str(pe.name_offset, pe.name_length); + info.type = str(pe.type_offset, pe.type_length); + elements_[info.object_id] = info; + } +} + +void ElementRegistry::onStreamedElementsReady(uint32_t /*mid*/, std::vector elements) { + for (const auto& e : elements) { + BasicElementInfo info; + info.object_id = e.object_id; + info.model_id = e.model_id; + info.ifc_id = e.ifc_id; + info.parent_id = e.parent_id; + info.guid = QString::fromStdString(e.guid); + info.name = QString::fromStdString(e.name); + info.type = QString::fromStdString(e.type); + elements_[info.object_id] = info; + } +} + +} // namespace ifcinterface diff --git a/src/interface/ElementRegistry.h b/src/interface/ElementRegistry.h new file mode 100644 index 0000000000..471e53f31f --- /dev/null +++ b/src/interface/ElementRegistry.h @@ -0,0 +1,66 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef IFCINTERFACE_ELEMENTREGISTRY_H +#define IFCINTERFACE_ELEMENTREGISTRY_H + +#include +#include +#include +#include +#include +#include + +class SceneLoader; +struct PackedElementInfo; +struct ElementInfo; + +namespace ifcinterface { + +struct BasicElementInfo { + uint32_t object_id = 0; + uint32_t model_id = 0; + int ifc_id = 0; + int parent_id = 0; + QString guid; + QString name; + QString type; +}; + +class ElementRegistry : public QObject { + Q_OBJECT +public: + explicit ElementRegistry(QObject* parent = nullptr); + + void bindLoader(SceneLoader* loader); + std::optional find(uint32_t object_id) const; + +private: + void onSidecarElementsReady(uint32_t mid, + std::vector elements, + std::string string_table); + void onStreamedElementsReady(uint32_t mid, std::vector elements); + + std::unordered_map elements_; +}; + +} // namespace ifcinterface + +#endif diff --git a/src/interface/MainWindow.cpp b/src/interface/MainWindow.cpp index 1f0fb7b021..28907cc495 100644 --- a/src/interface/MainWindow.cpp +++ b/src/interface/MainWindow.cpp @@ -23,7 +23,9 @@ #include "../ifcviewer/AppSettings.h" #include "../ifcviewer/SceneLoader.h" #include "../ifcviewer/ViewportWindow.h" -#include "components/PanelChrome.h" +#include "ElementRegistry.h" +#include "components/Panel.h" +#include "components/Style.h" #include "components/SvgIcon.h" #include "panels/todo/TodoPanelWidget.h" #include "panels/models/ModelsPanelView.h" @@ -52,9 +54,10 @@ namespace ifcinterface::shell { MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { + element_registry_ = new ifcinterface::ElementRegistry(this); setupChrome(); setupViewport(); - setupDocks(); + setupPanels(); setupStatus(); setupLoader(); setupRibbon(); @@ -66,280 +69,7 @@ void MainWindow::setupChrome() { setDockOptions(QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks | QMainWindow::GroupedDragging); - - setStyleSheet(R"( - QMainWindow { - background: #26292f; - } - QWidget { - color: #d0d5dd; - background: #26292f; - selection-background-color: #39b54a; - selection-color: #14161a; - } - QFrame#ribbonShell { - background: #2d3138; - border-bottom: 1px solid #1b1d22; - } - QTabBar::tab { - background: transparent; - color: #8d97a7; - padding: 8px 14px; - margin-right: 2px; - border-bottom: 2px solid transparent; - } - QTabBar::tab:selected { - color: #f2f5fa; - border-bottom: 2px solid #39b54a; - } - QTabBar::tab:hover { - color: #ffffff; - } - QFrame#ribbonBand { - background: #31353d; - border-top: 1px solid #3b4048; - } - QFrame#ribbonPage { - background: transparent; - } - QFrame#ribbonGroup { - background: transparent; - border-right: 1px solid #434852; - } - QLabel#ribbonGroupLabel { - color: #7f8796; - font-size: 9px; - font-weight: 600; - letter-spacing: 0.08em; - } - QToolButton#ribbonButton { - background: transparent; - border: none; - padding: 6px 4px 4px 4px; - font-size: 11px; - color: #d6dce6; - } - QToolButton#ribbonButton:hover { - background: #3a3f48; - } - QToolButton#ribbonButton:pressed { - background: #24282f; - } - QFrame#viewportShell { - background: #202329; - border-top: 1px solid #1d2025; - } - QFrame#viewportFrame { - background: #1a1d22; - border: 1px solid #333942; - } - QDockWidget { - color: #d0d5dd; - } - QLabel#dockTitleText { - color: #dfe4ec; - font-size: 10px; - font-weight: 700; - letter-spacing: 0.08em; - } - QToolButton#dockTitleButton { - color: #8e97a5; - border: none; - background: transparent; - } - QToolButton#dockTitleButton:hover { - color: #ffffff; - background: #353a42; - } - QFrame#panelFrame { - background: #2b2f36; - border: 1px solid #3e444e; - border-radius: 3px; - } - QWidget#panelBody { - background: #2b2f36; - } - QTreeWidget, QListWidget, QTableWidget, QAbstractScrollArea { - background: #2b2f36; - border: none; - outline: none; - gridline-color: #333842; - } - QTreeWidget::viewport, QListWidget::viewport, QTableWidget::viewport { - background: #2b2f36; - } - QHeaderView::section { - background: #31353d; - color: #b5becc; - border: none; - border-bottom: 1px solid #434a55; - padding: 7px 8px; - font-weight: 600; - } - QTableCornerButton::section { - background: #31353d; - border: none; - } - QScrollArea { - background: #2b2f36; - border: none; - } - QScrollArea > QWidget > QWidget { - background: #2b2f36; - } - QLineEdit { - background: #31353d; - border: 1px solid #434a55; - border-radius: 3px; - padding: 6px 8px; - color: #d9dfeb; - } - QLineEdit:focus { - border: 1px solid #5b6472; - } - QFrame#entityClassCard { - background: #26292f; - border: 1px solid #404650; - border-radius: 3px; - } - QLabel#entityClassLabel { - color: #eef2f8; - font-weight: 700; - background: transparent; - } - QLabel#entityTypeLabel { - color: #9aa4b3; - background: transparent; - } - QWidget#attributeList { - background: transparent; - } - QTreeView::item, QListView::item, QTableView::item { - padding: 4px; - } - QScrollBar:vertical { - background: transparent; - width: 10px; - margin: 2px 2px 2px 0; - } - QScrollBar:horizontal { - background: transparent; - height: 10px; - margin: 0 2px 2px 2px; - } - QScrollBar::handle:vertical, QScrollBar::handle:horizontal { - background: #525a67; - border-radius: 3px; - min-height: 24px; - min-width: 24px; - } - QScrollBar::handle:vertical:hover, QScrollBar::handle:horizontal:hover { - background: #697385; - } - QScrollBar::add-line, QScrollBar::sub-line, - QScrollBar::add-page, QScrollBar::sub-page { - background: transparent; - border: none; - } - QStatusBar { - background: #24272c; - border-top: 1px solid #1a1c20; - } - QStatusBar QLabel { - color: #97a1af; - background: transparent; - border: none; - padding: 2px 8px; - } - QGroupBox { - background: transparent; - border: 1px solid #404650; - border-radius: 3px; - margin-top: 10px; - padding-top: 10px; - } - QGroupBox#propertySetCard { - background: #26292f; - border: 1px solid #404650; - border-radius: 3px; - } - QGroupBox#propertySetCard::title { - subcontrol-origin: margin; - left: 10px; - padding: 0 4px; - color: #d5dbe5; - } - QGroupBox#propertySetCard > QWidget { - background: #26292f; - } - QWidget#panelSection { - background: transparent; - } - QWidget#panelSectionFilterWrapper { - background: transparent; - } - QWidget#relationshipRow { - background: transparent; - } - QLabel#relationshipIconLabel { - background: transparent; - } - QWidget#inspectorPanel { - background: #2b2f36; - } - QFrame#panelSectionHeader { - background: #26292f; - } - QToolButton#panelSectionHeaderButton { - background: transparent; - border: none; - color: #e1e7f0; - font-weight: 700; - text-align: left; - padding: 2px; - margin: 0; - } - QToolButton#panelSectionHeaderButton:hover { - color: #ffffff; - } - QToolButton#panelSectionHeaderButton::menu-indicator { - image: none; - width: 0; - } - QToolButton#panelSectionFilterToggle { - background: transparent; - border: none; - padding: 2px; - } - QToolButton#panelSectionFilterToggle:hover { - background: #353a42; - } - QWidget#panelSectionBody { - background: transparent; - } - QLabel#propertyKeyLabel { - color: #9aa4b3; - background: transparent; - } - QLabel#propertyValueLabel { - color: #dce2eb; - background: transparent; - } - QLabel#relationshipValueLabel { - color: #dce2eb; - background: transparent; - } - QLabel#todoPanelTitle { - font-size: 14px; - font-weight: 600; - color: #e1e6ee; - background: transparent; - } - QLabel#todoPanelBody { - color: #8f98a6; - background: transparent; - } - )"); + setStyleSheet(components::style::buildAppStyleSheet()); } QToolButton* MainWindow::makeRibbonAction(const QString& text, const QString& icon_path) { @@ -376,17 +106,13 @@ QWidget* MainWindow::makeRibbonGroup(const QString& title, const QListsetText(mode); status_selection_label_->setText(detail); } QToolButton* MainWindow::makePanelToggle(const QString& text, QDockWidget* dock) { - auto* button = makeRibbonAction(text, ":/icons/dm_toggle_openings.png"); + auto* button = makeRibbonAction(text, ":/icons/sidebar-expand.svg"); button->setCheckable(true); button->setChecked(dock->isVisible()); connect(button, &QToolButton::toggled, dock, [dock](bool checked) { @@ -565,19 +291,20 @@ QWidget* MainWindow::buildPanelsRibbonPage() { row->setSpacing(0); row->addWidget(makeRibbonGroup("DATA", { - makePanelToggle("Models", models_dock_), - makePanelToggle("Spatial", spatial_dock_), - makePanelToggle("Layers", layers_dock_), - makePanelToggle("Properties", properties_dock_) + makePanelToggle("Models", models_panel_), + makePanelToggle("Spatial", spatial_panel_), + makePanelToggle("Layers", layers_panel_), + makePanelToggle("Properties", properties_panel_) })); row->addWidget(makeRibbonGroup("QUERY", { - makePanelToggle("Views", stored_views_dock_), - makePanelToggle("Search", search_dock_), - makePanelToggle("Sheets", spreadsheet_dock_) + makePanelToggle("Views", stored_views_panel_), + makePanelToggle("Search", search_panel_), + makePanelToggle("Sheets", spreadsheet_panel_), + makePanelToggle("Audit", audit_panel_) })); row->addWidget(makeRibbonGroup("COLLABORATE", { - makePanelToggle("Clash", clash_dock_), - makePanelToggle("Issues", issues_dock_) + makePanelToggle("Clash", clash_panel_), + makePanelToggle("Issues", issues_panel_) })); row->addStretch(1); return page; @@ -644,68 +371,67 @@ void MainWindow::setupViewport() { setCentralWidget(shell); } -void MainWindow::setupDocks() { - auto* models_panel = new panels::models::ModelsPanelWidget(this); - auto* spatial_panel = new panels::spatial_hierarchy::SpatialHierarchyPanelWidget(this); - auto* properties_panel = new panels::properties::PropertiesPanelWidget(this); +void MainWindow::setupPanels() { + auto* models_widget = new panels::models::ModelsPanelWidget(this); + auto* spatial_widget = new panels::spatial_hierarchy::SpatialHierarchyPanelWidget(this); + auto* properties_widget = new panels::properties::PropertiesPanelWidget(this); - models_panel_view_ = new panels::models::ModelsPanelView(models_panel, this); - spatial_panel_view_ = new panels::spatial_hierarchy::SpatialHierarchyPanelView(spatial_panel, this); - properties_panel_view_ = new panels::properties::PropertiesPanelView(properties_panel, this); + models_view_ = new panels::models::ModelsPanelView(models_widget, this); + spatial_view_ = new panels::spatial_hierarchy::SpatialHierarchyPanelView(spatial_widget, this); + properties_view_ = new panels::properties::PropertiesPanelView( + properties_widget, viewport_, element_registry_, this); - connect(models_panel_view_, &panels::models::ModelsPanelView::statusMessageRequested, + connect(models_view_, &panels::models::ModelsPanelView::statusMessageRequested, this, &MainWindow::setStatusMessage); - connect(spatial_panel_view_, &panels::spatial_hierarchy::SpatialHierarchyPanelView::statusMessageRequested, + connect(spatial_view_, &panels::spatial_hierarchy::SpatialHierarchyPanelView::statusMessageRequested, this, &MainWindow::setStatusMessage); - models_dock_ = components::panel::makeDock( - "Models", components::panel::wrapPanel(models_panel), this, true); - spatial_dock_ = components::panel::makeDock( - "Spatial Hierarchy", components::panel::wrapPanel(spatial_panel), this); - properties_dock_ = components::panel::makeDock( - "Properties", components::panel::wrapPanel(properties_panel), this); - layers_dock_ = components::panel::makeDock( - "Layers", components::panel::wrapPanel(makeComingSoonPanel("Layers")), this); - stored_views_dock_ = components::panel::makeDock( - "Stored Views", components::panel::wrapPanel(makeComingSoonPanel("Stored Views")), this); - search_dock_ = components::panel::makeDock( - "Search and Query", components::panel::wrapPanel(makeComingSoonPanel("Search and Query")), this); - spreadsheet_dock_ = components::panel::makeDock( - "Spreadsheet", components::panel::wrapPanel(makeComingSoonPanel("Spreadsheet")), this); - clash_dock_ = components::panel::makeDock( - "Clash", components::panel::wrapPanel(makeComingSoonPanel("Clash")), this); - issues_dock_ = components::panel::makeDock( - "Issues", components::panel::wrapPanel(makeComingSoonPanel("Issues")), this); + models_panel_ = new components::Panel("Models", models_widget, this, true); + spatial_panel_ = new components::Panel("Spatial Hierarchy", spatial_widget, this); + properties_panel_ = new components::Panel("Properties", properties_widget, this); + layers_panel_ = new components::Panel("Layers", new panels::todo::TodoPanelWidget("Layers", this), this); + stored_views_panel_ = new components::Panel( + "Stored Views", new panels::todo::TodoPanelWidget("Stored Views", this), this); + search_panel_ = new components::Panel( + "Search and Query", new panels::todo::TodoPanelWidget("Search and Query", this), this); + spreadsheet_panel_ = new components::Panel( + "Spreadsheet", new panels::todo::TodoPanelWidget("Spreadsheet", this), this); + audit_panel_ = new components::Panel("Audit", new panels::todo::TodoPanelWidget("Audit", this), this); + clash_panel_ = new components::Panel("Clash", new panels::todo::TodoPanelWidget("Clash", this), this); + issues_panel_ = new components::Panel("Issues", new panels::todo::TodoPanelWidget("Issues", this), this); - addDockWidget(Qt::LeftDockWidgetArea, models_dock_); - addDockWidget(Qt::LeftDockWidgetArea, spatial_dock_); - splitDockWidget(models_dock_, spatial_dock_, Qt::Vertical); + addDockWidget(Qt::LeftDockWidgetArea, models_panel_); + addDockWidget(Qt::LeftDockWidgetArea, spatial_panel_); + splitDockWidget(models_panel_, spatial_panel_, Qt::Vertical); - addDockWidget(Qt::RightDockWidgetArea, properties_dock_); - addDockWidget(Qt::RightDockWidgetArea, layers_dock_); - addDockWidget(Qt::RightDockWidgetArea, stored_views_dock_); - addDockWidget(Qt::RightDockWidgetArea, search_dock_); - addDockWidget(Qt::RightDockWidgetArea, spreadsheet_dock_); - addDockWidget(Qt::RightDockWidgetArea, clash_dock_); - addDockWidget(Qt::RightDockWidgetArea, issues_dock_); + addDockWidget(Qt::RightDockWidgetArea, properties_panel_); + addDockWidget(Qt::RightDockWidgetArea, layers_panel_); + addDockWidget(Qt::RightDockWidgetArea, stored_views_panel_); + addDockWidget(Qt::RightDockWidgetArea, search_panel_); + addDockWidget(Qt::RightDockWidgetArea, spreadsheet_panel_); + addDockWidget(Qt::RightDockWidgetArea, audit_panel_); + addDockWidget(Qt::RightDockWidgetArea, clash_panel_); + addDockWidget(Qt::RightDockWidgetArea, issues_panel_); - tabifyDockWidget(properties_dock_, layers_dock_); - tabifyDockWidget(layers_dock_, stored_views_dock_); - tabifyDockWidget(stored_views_dock_, search_dock_); - tabifyDockWidget(search_dock_, spreadsheet_dock_); - tabifyDockWidget(spreadsheet_dock_, clash_dock_); - tabifyDockWidget(clash_dock_, issues_dock_); - properties_dock_->raise(); + tabifyDockWidget(properties_panel_, layers_panel_); + tabifyDockWidget(layers_panel_, stored_views_panel_); + tabifyDockWidget(stored_views_panel_, search_panel_); + tabifyDockWidget(search_panel_, spreadsheet_panel_); + tabifyDockWidget(spreadsheet_panel_, audit_panel_); + tabifyDockWidget(audit_panel_, clash_panel_); + tabifyDockWidget(clash_panel_, issues_panel_); + properties_panel_->raise(); - layers_dock_->hide(); - stored_views_dock_->hide(); - search_dock_->hide(); - spreadsheet_dock_->hide(); - clash_dock_->hide(); - issues_dock_->hide(); + layers_panel_->hide(); + stored_views_panel_->hide(); + search_panel_->hide(); + spreadsheet_panel_->hide(); + audit_panel_->hide(); + clash_panel_->hide(); + issues_panel_->hide(); - resizeDocks({models_dock_, properties_dock_}, {290, 330}, Qt::Horizontal); - resizeDocks({models_dock_, spatial_dock_}, {280, 240}, Qt::Vertical); + resizeDocks({models_panel_, properties_panel_}, {290, 330}, Qt::Horizontal); + resizeDocks({models_panel_, spatial_panel_}, {280, 240}, Qt::Vertical); } void MainWindow::setupStatus() { @@ -728,6 +454,7 @@ void MainWindow::setupStatus() { void MainWindow::setupLoader() { AppSettings::instance().setLoadDataSource(false); loader_ = new SceneLoader(viewport_, this); + element_registry_->bindLoader(loader_); connect(loader_, &SceneLoader::loadStarted, this, &MainWindow::onLoadStarted); connect(loader_, &SceneLoader::loadedFromSidecar, this, &MainWindow::onLoadedFromSidecar); connect(loader_, &SceneLoader::loadedFromStream, this, &MainWindow::onLoadedFromStream); diff --git a/src/interface/MainWindow.h b/src/interface/MainWindow.h index e4a491a403..0ca4fc317a 100644 --- a/src/interface/MainWindow.h +++ b/src/interface/MainWindow.h @@ -31,6 +31,7 @@ class QTabBar; class QToolButton; class ViewportWindow; class SceneLoader; +namespace ifcinterface { class ElementRegistry; } namespace ifcinterface::panels::models { class ModelsPanelView; } namespace ifcinterface::panels::spatial_hierarchy { class SpatialHierarchyPanelView; } namespace ifcinterface::panels::properties { class PropertiesPanelView; } @@ -46,7 +47,7 @@ private: void setupChrome(); void setupRibbon(); void setupViewport(); - void setupDocks(); + void setupPanels(); void setupStatus(); void setupLoader(); QWidget* buildHomeRibbonPage(); @@ -55,7 +56,6 @@ private: QWidget* buildPanelsRibbonPage(); QToolButton* makeRibbonAction(const QString& text, const QString& icon_path); QWidget* makeRibbonGroup(const QString& title, const QList& buttons); - QWidget* makeComingSoonPanel(const QString& title); QToolButton* makePanelToggle(const QString& text, QDockWidget* dock); void setStatusMessage(const QString& mode, const QString& detail); void addFiles(const QStringList& paths); @@ -78,19 +78,21 @@ private: QStackedWidget* ribbon_pages_ = nullptr; ViewportWindow* viewport_ = nullptr; SceneLoader* loader_ = nullptr; + ifcinterface::ElementRegistry* element_registry_ = nullptr; QWidget* viewport_container_ = nullptr; - QDockWidget* models_dock_ = nullptr; - QDockWidget* spatial_dock_ = nullptr; - QDockWidget* layers_dock_ = nullptr; - QDockWidget* properties_dock_ = nullptr; - QDockWidget* stored_views_dock_ = nullptr; - QDockWidget* search_dock_ = nullptr; - QDockWidget* spreadsheet_dock_ = nullptr; - QDockWidget* clash_dock_ = nullptr; - QDockWidget* issues_dock_ = nullptr; - ifcinterface::panels::models::ModelsPanelView* models_panel_view_ = nullptr; - ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelView* spatial_panel_view_ = nullptr; - ifcinterface::panels::properties::PropertiesPanelView* properties_panel_view_ = nullptr; + QDockWidget* models_panel_ = nullptr; + QDockWidget* spatial_panel_ = nullptr; + QDockWidget* layers_panel_ = nullptr; + QDockWidget* properties_panel_ = nullptr; + QDockWidget* stored_views_panel_ = nullptr; + QDockWidget* search_panel_ = nullptr; + QDockWidget* spreadsheet_panel_ = nullptr; + QDockWidget* audit_panel_ = nullptr; + QDockWidget* clash_panel_ = nullptr; + QDockWidget* issues_panel_ = nullptr; + ifcinterface::panels::models::ModelsPanelView* models_view_ = nullptr; + ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelView* spatial_view_ = nullptr; + ifcinterface::panels::properties::PropertiesPanelView* properties_view_ = nullptr; }; } // namespace ifcinterface::shell diff --git a/src/interface/components/KeyValueTable.cpp b/src/interface/components/KeyValueTable.cpp new file mode 100644 index 0000000000..0fbc436b3d --- /dev/null +++ b/src/interface/components/KeyValueTable.cpp @@ -0,0 +1,82 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "KeyValueTable.h" + +#include "SvgIcon.h" + +#include +#include +#include + +namespace ifcinterface::components { + +KeyValueTable::KeyValueTable(const QList& rows, QWidget* parent) + : QWidget(parent) +{ + setObjectName("attributeList"); + + auto* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(6); + + for (const auto& row_data : rows) { + auto* row = new QWidget(this); + if (!row_data.trailing_icon_path.isEmpty()) { + row->setObjectName("relationshipRow"); + } else { + row->setObjectName("keyValueRow"); + } + + auto* row_layout = new QHBoxLayout(row); + row_layout->setContentsMargins(0, 0, 0, 0); + row_layout->setSpacing(12); + + auto* key = new QLabel(row_data.key, row); + key->setObjectName("propertyKeyLabel"); + if (row_data.key_minimum_width > 0) { + key->setMinimumWidth(row_data.key_minimum_width); + } + + auto* value = new QLabel(row_data.value, row); + value->setObjectName(row_data.value_object_name.isEmpty() + ? "propertyValueLabel" + : row_data.value_object_name); + value->setWordWrap(true); + value->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + row_layout->addWidget(key); + row_layout->addWidget(value, 1); + + if (!row_data.trailing_icon_path.isEmpty()) { + auto* icon = new QLabel(row); + icon->setObjectName(row_data.trailing_icon_object_name.isEmpty() + ? "relationshipIconLabel" + : row_data.trailing_icon_object_name); + icon->setPixmap(icons::makePanelSvgPixmap(row_data.trailing_icon_path, QSize(14, 14))); + icon->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + row_layout->addWidget(icon, 0, Qt::AlignRight | Qt::AlignVCenter); + } + + layout->addWidget(row); + } +} + +} // namespace ifcinterface::components diff --git a/src/interface/components/KeyValueTable.h b/src/interface/components/KeyValueTable.h new file mode 100644 index 0000000000..3e1ba10f73 --- /dev/null +++ b/src/interface/components/KeyValueTable.h @@ -0,0 +1,47 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef IFCINTERFACE_COMPONENTS_KEYVALUETABLE_H +#define IFCINTERFACE_COMPONENTS_KEYVALUETABLE_H + +#include +#include +#include + +namespace ifcinterface::components { + +struct KeyValueTableRow { + QString key; + QString value; + QString value_object_name = "propertyValueLabel"; + QString trailing_icon_path; + QString trailing_icon_object_name; + int key_minimum_width = 0; +}; + +class KeyValueTable : public QWidget { + Q_OBJECT +public: + explicit KeyValueTable(const QList& rows, QWidget* parent = nullptr); +}; + +} // namespace ifcinterface::components + +#endif diff --git a/src/interface/components/PanelChrome.cpp b/src/interface/components/Panel.cpp similarity index 76% rename from src/interface/components/PanelChrome.cpp rename to src/interface/components/Panel.cpp index 4e49219788..3d73c98f6c 100644 --- a/src/interface/components/PanelChrome.cpp +++ b/src/interface/components/Panel.cpp @@ -18,8 +18,9 @@ * * ********************************************************************************/ -#include "PanelChrome.h" +#include "Panel.h" +#include "Style.h" #include "SvgIcon.h" #include @@ -30,7 +31,7 @@ #include #include -namespace ifcinterface::components::panel { +namespace ifcinterface::components { namespace { @@ -69,32 +70,31 @@ public: } // namespace -QDockWidget* makeDock(const QString& title, QWidget* content, QWidget* parent, bool has_settings) { - auto* dock = new QDockWidget(title, parent); - dock->setObjectName(title); - dock->setFeatures(QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable | - QDockWidget::DockWidgetClosable); - dock->setTitleBarWidget(new DockTitleBar(title, has_settings, dock)); - dock->setWidget(content); - return dock; -} - -QFrame* wrapPanel(QWidget* inner) { +Panel::Panel(const QString& title, QWidget* content, QWidget* parent, bool has_settings) + : QDockWidget(title, parent) +{ auto* outer = new QFrame(); auto* outer_layout = new QVBoxLayout(outer); - outer_layout->setContentsMargins(6, 6, 6, 6); + outer_layout->setContentsMargins(style::metrics::padding, + style::metrics::padding, + style::metrics::padding, + style::metrics::padding); outer_layout->setSpacing(0); auto* frame = new QFrame(outer); - frame->setObjectName("panelFrame"); - auto* layout = new QVBoxLayout(frame); - layout->setContentsMargins(0, 8, 0, 8); - layout->setSpacing(0); - layout->addWidget(inner); - + frame->setObjectName("panel"); + auto* frame_layout = new QVBoxLayout(frame); + frame_layout->setContentsMargins(0, style::metrics::padding, 0, style::metrics::padding); + frame_layout->setSpacing(0); + frame_layout->addWidget(content); outer_layout->addWidget(frame); - return outer; + + setObjectName(title); + setFeatures(QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable | + QDockWidget::DockWidgetClosable); + setTitleBarWidget(new DockTitleBar(title, has_settings, this)); + setWidget(outer); } -} // namespace ifcinterface::components::panel +} // namespace ifcinterface::components diff --git a/src/interface/components/PanelChrome.h b/src/interface/components/Panel.h similarity index 85% rename from src/interface/components/PanelChrome.h rename to src/interface/components/Panel.h index 806dfeefcd..9739d53552 100644 --- a/src/interface/components/PanelChrome.h +++ b/src/interface/components/Panel.h @@ -21,17 +21,19 @@ #ifndef IFCINTERFACE_COMPONENTS_PANEL_PANELCHROME_H #define IFCINTERFACE_COMPONENTS_PANEL_PANELCHROME_H -#include +#include -class QDockWidget; -class QFrame; class QWidget; -namespace ifcinterface::components::panel { +namespace ifcinterface::components { -QDockWidget* makeDock(const QString& title, QWidget* content, QWidget* parent, bool has_settings = false); -QFrame* wrapPanel(QWidget* inner); +class Panel : public QDockWidget { + Q_OBJECT -} // namespace ifcinterface::components::panel +public: + explicit Panel(const QString& title, QWidget* content, QWidget* parent = nullptr, bool has_settings = false); +}; + +} // namespace ifcinterface::components #endif diff --git a/src/interface/components/Section.cpp b/src/interface/components/Section.cpp index 34790e7960..a7899b9ce6 100644 --- a/src/interface/components/Section.cpp +++ b/src/interface/components/Section.cpp @@ -20,6 +20,7 @@ #include "Section.h" +#include "Style.h" #include "SvgIcon.h" #include @@ -49,14 +50,14 @@ Section::Section(const QString& title, SectionHeaderMode header_mode, setObjectName("panelSection"); auto* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(6); + layout->setSpacing(style::metrics::section_spacing); if (header_mode == SectionHeaderMode::Visible) { auto* header = new QFrame(this); header->setObjectName("panelSectionHeader"); auto* header_layout = new QHBoxLayout(header); header_layout->setContentsMargins(0, 0, 0, 0); - header_layout->setSpacing(6); + header_layout->setSpacing(style::metrics::section_spacing); auto* toggle = new QToolButton(header); toggle->setObjectName("panelSectionHeaderButton"); @@ -89,7 +90,8 @@ Section::Section(const QString& title, SectionHeaderMode header_mode, auto* filter_wrapper = new QWidget(this); filter_wrapper->setObjectName("panelSectionFilterWrapper"); auto* filter_wrapper_layout = new QVBoxLayout(filter_wrapper); - filter_wrapper_layout->setContentsMargins(10, 0, 10, 0); + filter_wrapper_layout->setContentsMargins(style::metrics::filter_body_padding_x, 0, + style::metrics::filter_body_padding_x, 0); filter_wrapper_layout->setSpacing(0); filter_wrapper_layout->addWidget(filter_field_); layout->addWidget(filter_wrapper); @@ -108,7 +110,8 @@ Section::Section(const QString& title, SectionHeaderMode header_mode, auto* filter_wrapper = new QWidget(this); filter_wrapper->setObjectName("panelSectionFilterWrapper"); auto* filter_wrapper_layout = new QVBoxLayout(filter_wrapper); - filter_wrapper_layout->setContentsMargins(8, 0, 8, 0); + filter_wrapper_layout->setContentsMargins(style::metrics::hidden_filter_body_padding_x, 0, + style::metrics::hidden_filter_body_padding_x, 0); filter_wrapper_layout->setSpacing(0); filter_wrapper_layout->addWidget(filter_field_); layout->addWidget(filter_wrapper); @@ -118,11 +121,14 @@ Section::Section(const QString& title, SectionHeaderMode header_mode, body_->setObjectName("panelSectionBody"); body_layout_ = new QVBoxLayout(body_); if (header_mode == SectionHeaderMode::Visible) { - body_layout_->setContentsMargins(10, 6, 10, 0); + body_layout_->setContentsMargins(style::metrics::section_body_padding_x, + style::metrics::section_body_padding_top, + style::metrics::section_body_padding_x, 0); } else { - body_layout_->setContentsMargins(8, 0, 8, 0); + body_layout_->setContentsMargins(style::metrics::hidden_section_body_padding_x, 0, + style::metrics::hidden_section_body_padding_x, 0); } - body_layout_->setSpacing(6); + body_layout_->setSpacing(style::metrics::section_spacing); layout->addWidget(body_); } diff --git a/src/interface/components/Style.cpp b/src/interface/components/Style.cpp new file mode 100644 index 0000000000..9c0b14a924 --- /dev/null +++ b/src/interface/components/Style.cpp @@ -0,0 +1,303 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "Style.h" + +namespace ifcinterface::components::style { + +QString buildAppStyleSheet() { + return QString(R"( + QMainWindow { + background: #26292f; + } + QWidget { + color: #d0d5dd; + background: #26292f; + selection-background-color: #39b54a; + selection-color: #14161a; + } + QFrame#ribbonShell { + background: #2d3138; + border-bottom: 1px solid #1b1d22; + } + QTabBar::tab { + background: transparent; + color: #8d97a7; + padding: 8px 14px; + margin-right: 2px; + border-bottom: 2px solid transparent; + } + QTabBar::tab:selected { + color: #f2f5fa; + border-bottom: 2px solid #39b54a; + } + QTabBar::tab:hover { + color: #ffffff; + } + QFrame#ribbonBand { + background: #31353d; + border-top: 1px solid #3b4048; + } + QFrame#ribbonPage { + background: transparent; + } + QFrame#ribbonGroup { + background: transparent; + border-right: 1px solid #434852; + } + QLabel#ribbonGroupLabel { + color: #7f8796; + font-size: 9px; + font-weight: 600; + letter-spacing: 0.08em; + } + QToolButton#ribbonButton { + background: transparent; + border: none; + padding: 6px 4px 4px 4px; + font-size: 11px; + color: #d6dce6; + } + QToolButton#ribbonButton:hover { + background: #3a3f48; + } + QToolButton#ribbonButton:pressed { + background: #24282f; + } + QFrame#viewportShell { + background: #202329; + border-top: 1px solid #1d2025; + } + QFrame#viewportFrame { + background: #1a1d22; + border: 1px solid #333942; + } + QDockWidget { + color: #d0d5dd; + } + QLabel#dockTitleText { + color: #dfe4ec; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.08em; + } + QToolButton#dockTitleButton { + color: #8e97a5; + border: none; + background: transparent; + } + QToolButton#dockTitleButton:hover { + color: #ffffff; + background: #353a42; + } + QFrame#panel { + background: #2b2f36; + border: 1px solid #3e444e; + border-radius: %1px; + } + QTreeWidget, QListWidget, QTableWidget, QAbstractScrollArea { + background: #2b2f36; + border: none; + outline: none; + gridline-color: #333842; + } + QTreeWidget::viewport, QListWidget::viewport, QTableWidget::viewport { + background: #2b2f36; + } + QHeaderView::section { + background: #31353d; + color: #b5becc; + border: none; + border-bottom: 1px solid #434a55; + padding: 7px 8px; + font-weight: 600; + } + QTableCornerButton::section { + background: #31353d; + border: none; + } + QScrollArea { + background: #2b2f36; + border: none; + } + QScrollArea > QWidget > QWidget { + background: #2b2f36; + } + QLineEdit { + background: #31353d; + border: 1px solid #434a55; + border-radius: %1px; + padding: %2px %3px; + color: #d9dfeb; + } + QLineEdit:focus { + border: 1px solid #5b6472; + } + QFrame#entityClassCard { + background: #26292f; + border: 1px solid #404650; + border-radius: %1px; + } + QLabel#entityClassLabel { + color: #eef2f8; + font-weight: 700; + background: transparent; + } + QLabel#entityTypeLabel { + color: #9aa4b3; + background: transparent; + } + QWidget#attributeList { + background: transparent; + } + QTreeView::item, QListView::item, QTableView::item { + padding: 4px; + } + QScrollBar:vertical { + background: transparent; + width: 10px; + margin: 2px 2px 2px 0; + } + QScrollBar:horizontal { + background: transparent; + height: 10px; + margin: 0 2px 2px 2px; + } + QScrollBar::handle:vertical, QScrollBar::handle:horizontal { + background: #525a67; + border-radius: %1px; + min-height: 24px; + min-width: 24px; + } + QScrollBar::handle:vertical:hover, QScrollBar::handle:horizontal:hover { + background: #697385; + } + QScrollBar::add-line, QScrollBar::sub-line, + QScrollBar::add-page, QScrollBar::sub-page { + background: transparent; + border: none; + } + QStatusBar { + background: #24272c; + border-top: 1px solid #1a1c20; + } + QStatusBar QLabel { + color: #97a1af; + background: transparent; + border: none; + padding: 2px 8px; + } + QGroupBox { + background: transparent; + border: 1px solid #404650; + border-radius: %1px; + margin-top: 10px; + padding-top: 10px; + } + QGroupBox#propertySetCard { + background: #26292f; + border: 1px solid #404650; + border-radius: %1px; + } + QGroupBox#propertySetCard::title { + subcontrol-origin: margin; + left: %4px; + padding: 0 4px; + color: #d5dbe5; + } + QGroupBox#propertySetCard > QWidget { + background: #26292f; + } + QWidget#panelSection { + background: transparent; + } + QWidget#panelSectionFilterWrapper { + background: transparent; + } + QWidget#relationshipRow { + background: transparent; + } + QLabel#relationshipIconLabel { + background: transparent; + } + QWidget#inspectorPanel { + background: #2b2f36; + } + QFrame#panelSectionHeader { + background: #26292f; + } + QToolButton#panelSectionHeaderButton { + background: transparent; + border: none; + color: #e1e7f0; + font-weight: 700; + text-align: left; + padding: %5px; + margin: 0; + } + QToolButton#panelSectionHeaderButton:hover { + color: #ffffff; + } + QToolButton#panelSectionHeaderButton::menu-indicator { + image: none; + width: 0; + } + QToolButton#panelSectionFilterToggle { + background: transparent; + border: none; + padding: %5px; + } + QToolButton#panelSectionFilterToggle:hover { + background: #353a42; + } + QWidget#panelSectionBody { + background: transparent; + } + QLabel#propertyKeyLabel { + color: #9aa4b3; + background: transparent; + } + QLabel#propertyValueLabel { + color: #dce2eb; + background: transparent; + } + QLabel#relationshipValueLabel { + color: #dce2eb; + background: transparent; + } + QLabel#todoPanelTitle { + font-size: 14px; + font-weight: 600; + color: #e1e6ee; + background: transparent; + } + QLabel#todoPanelBody { + color: #8f98a6; + background: transparent; + } + )") + .arg(metrics::panel_radius) + .arg(metrics::control_padding_y) + .arg(metrics::control_padding_x) + .arg(metrics::card_padding) + .arg(metrics::section_header_padding); +} + +} // namespace ifcinterface::components::style diff --git a/src/interface/components/Style.h b/src/interface/components/Style.h new file mode 100644 index 0000000000..8c382fa804 --- /dev/null +++ b/src/interface/components/Style.h @@ -0,0 +1,49 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef IFCINTERFACE_COMPONENTS_STYLE_H +#define IFCINTERFACE_COMPONENTS_STYLE_H + +#include + +namespace ifcinterface::components::style::metrics { + +inline constexpr int padding = 6; +inline constexpr int section_spacing = 6; +inline constexpr int section_body_padding_x = 10; +inline constexpr int section_body_padding_top = 6; +inline constexpr int hidden_section_body_padding_x = 8; +inline constexpr int section_header_padding = 2; +inline constexpr int filter_body_padding_x = 10; +inline constexpr int hidden_filter_body_padding_x = 8; +inline constexpr int card_padding = 10; +inline constexpr int control_padding_y = 6; +inline constexpr int control_padding_x = 8; +inline constexpr int panel_radius = 3; + +} // namespace ifcinterface::components::style::metrics + +namespace ifcinterface::components::style { + +QString buildAppStyleSheet(); + +} // namespace ifcinterface::components::style + +#endif diff --git a/src/interface/icons/sidebar-expand.svg b/src/interface/icons/sidebar-expand.svg new file mode 100644 index 0000000000..6d7695253c --- /dev/null +++ b/src/interface/icons/sidebar-expand.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/interface/interface_resources.qrc b/src/interface/interface_resources.qrc index 9be3928f7d..453e64d9e7 100644 --- a/src/interface/interface_resources.qrc +++ b/src/interface/interface_resources.qrc @@ -4,21 +4,6 @@ ../ifctester/webapp/public/fonts/dmsans/DMSans-VariableFont_opsz,wght.ttf - ../bonsai/bonsai/bim/data/icons/IFC.png - ../bonsai/bonsai/bim/data/icons/dm_add.png - ../bonsai/bonsai/bim/data/icons/dm_add_type.png - ../bonsai/bonsai/bim/data/icons/dm_assign.png - ../bonsai/bonsai/bim/data/icons/dm_centerline.png - ../bonsai/bonsai/bim/data/icons/dm_connect_mep_elements.png - ../bonsai/bonsai/bim/data/icons/dm_decomposition.png - ../bonsai/bonsai/bim/data/icons/dm_edit_profile.png - ../bonsai/bonsai/bim/data/icons/dm_ifc.png - ../bonsai/bonsai/bim/data/icons/dm_perform_quantity_take-off.png - ../bonsai/bonsai/bim/data/icons/dm_rectangle.png - ../bonsai/bonsai/bim/data/icons/dm_refresh.png - ../bonsai/bonsai/bim/data/icons/dm_rotate_90.png - ../bonsai/bonsai/bim/data/icons/dm_split.png - ../bonsai/bonsai/bim/data/icons/dm_toggle_openings.png icons/plus-square.svg icons/download-square.svg icons/cloud-square.svg @@ -55,5 +40,6 @@ icons/filter.svg icons/cube-dots.svg icons/cursor-pointer.svg + icons/sidebar-expand.svg diff --git a/src/interface/panels/properties/PropertiesPanelView.cpp b/src/interface/panels/properties/PropertiesPanelView.cpp index 1b74f40fff..291589762a 100644 --- a/src/interface/panels/properties/PropertiesPanelView.cpp +++ b/src/interface/panels/properties/PropertiesPanelView.cpp @@ -22,22 +22,36 @@ #include "PropertiesPanelWidget.h" +#include "../../ElementRegistry.h" +#include "../../../ifcviewer/ViewportWindow.h" + namespace ifcinterface::panels::properties { -PropertiesPanelView::PropertiesPanelView(PropertiesPanelWidget* widget, QObject* parent) - : QObject(parent), widget_(widget) +PropertiesPanelView::PropertiesPanelView(PropertiesPanelWidget* widget, + ViewportWindow* viewport, + ifcinterface::ElementRegistry* registry, + QObject* parent) + : QObject(parent), widget_(widget), registry_(registry) { - state_.entity = {"IfcWall", "SOLIDWALL"}; - state_.attributes = { + connect(viewport, &ViewportWindow::objectPicked, this, [this](uint32_t object_id) { + refresh(object_id); + }); + refresh(0); +} + +void PropertiesPanelView::refresh(uint32_t object_id) { + PropertiesPanelState state; + state.entity = {"IfcWall", "SOLIDWALL"}; + state.attributes = { {"GlobalId", "2Q$n5SLPP9Q8B7wQKjKfUQ"}, {"Name", "Core-EXT-204"}, {"Description", "External load-bearing wall"}, }; - state_.relationships = { + state.relationships = { {"Type", "Basic Wall: Exterior - 200mm"}, {"Container", "Level 02"}, }; - state_.property_sets = { + state.property_sets = { {"Pset_WallCommon", {{"Reference", "Core-EXT-204"}, {"Status", "Reviewed"}, @@ -53,7 +67,7 @@ PropertiesPanelView::PropertiesPanelView(PropertiesPanelWidget* widget, QObject* {"Last Review", "2026-04-30"}, {"Assigned To", "Design Coordination"}}}, }; - state_.quantity_sets = { + state.quantity_sets = { {"BaseQuantities", {{"Length", "6.20 m"}, {"Height", "3.45 m"}, @@ -65,7 +79,25 @@ PropertiesPanelView::PropertiesPanelView(PropertiesPanelWidget* widget, QObject* {"Paint Coverage", "42.78 m2"}}}, }; - widget_->setState(state_); + if (registry_) { + auto info = registry_->find(object_id); + if (info && !info->type.isEmpty()) { + state.entity.entity_class = info->type; + if (!state.property_sets.isEmpty() && !state.property_sets[1].rows.isEmpty()) { + state.property_sets[1].rows[0].value = info->type; + } + } + if (info && !info->name.isEmpty()) { + state.attributes[1].value = info->name; + if (state.property_sets.size() > 1 && state.property_sets[1].rows.size() > 1) { + state.property_sets[1].rows[1].value = info->name; + } + } + if (info && !info->guid.isEmpty()) { + state.attributes[0].value = info->guid; + } + } + widget_->render(state); } } // namespace ifcinterface::panels::properties diff --git a/src/interface/panels/properties/PropertiesPanelView.h b/src/interface/panels/properties/PropertiesPanelView.h index a61c393d31..65abe82f4f 100644 --- a/src/interface/panels/properties/PropertiesPanelView.h +++ b/src/interface/panels/properties/PropertiesPanelView.h @@ -25,6 +25,8 @@ #include +namespace ifcinterface { class ElementRegistry; } +class ViewportWindow; namespace ifcinterface::panels::properties { class PropertiesPanelWidget; @@ -32,11 +34,16 @@ class PropertiesPanelWidget; class PropertiesPanelView : public QObject { Q_OBJECT public: - explicit PropertiesPanelView(PropertiesPanelWidget* widget, QObject* parent = nullptr); + explicit PropertiesPanelView(PropertiesPanelWidget* widget, + ViewportWindow* viewport, + ifcinterface::ElementRegistry* registry, + QObject* parent = nullptr); private: + void refresh(uint32_t object_id); + PropertiesPanelWidget* widget_ = nullptr; - PropertiesPanelState state_; + ifcinterface::ElementRegistry* registry_ = nullptr; }; } // namespace ifcinterface::panels::properties diff --git a/src/interface/panels/properties/PropertiesPanelWidget.cpp b/src/interface/panels/properties/PropertiesPanelWidget.cpp index 2c17e4c176..33db92fbb4 100644 --- a/src/interface/panels/properties/PropertiesPanelWidget.cpp +++ b/src/interface/panels/properties/PropertiesPanelWidget.cpp @@ -20,10 +20,10 @@ #include "PropertiesPanelWidget.h" +#include "../../components/KeyValueTable.h" #include "../../components/Section.h" #include "../../components/SvgIcon.h" -#include #include #include #include @@ -36,79 +36,37 @@ namespace { QWidget* makePropertySetPanel(const ifcinterface::panels::properties::PropertySet& property_set, QWidget* parent = nullptr) { auto* group = new QGroupBox(property_set.title, parent); group->setObjectName("propertySetCard"); - auto* form = new QFormLayout(group); - form->setContentsMargins(10, 10, 10, 10); - form->setHorizontalSpacing(16); - form->setVerticalSpacing(6); - form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + auto* layout = new QVBoxLayout(group); + layout->setContentsMargins(10, 10, 10, 10); + layout->setSpacing(0); + QList rows; for (const auto& row : property_set.rows) { - auto* key = new QLabel(row.key, group); - key->setObjectName("propertyKeyLabel"); - auto* value = new QLabel(row.value, group); - value->setObjectName("propertyValueLabel"); - value->setWordWrap(true); - form->addRow(key, value); + rows.append({row.key, row.value, "propertyValueLabel", "", "", 0}); } - + layout->addWidget(new ifcinterface::components::KeyValueTable(rows, group)); return group; } QWidget* makeAttributeList(const QList& rows, QWidget* parent = nullptr) { - auto* panel = new QWidget(parent); - panel->setObjectName("attributeList"); - auto* form = new QFormLayout(panel); - form->setContentsMargins(0, 0, 0, 0); - form->setHorizontalSpacing(16); - form->setVerticalSpacing(6); - form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - + QList table_rows; for (const auto& row : rows) { - auto* key = new QLabel(row.key, panel); - key->setObjectName("propertyKeyLabel"); - auto* value = new QLabel(row.value, panel); - value->setObjectName("propertyValueLabel"); - value->setWordWrap(true); - form->addRow(key, value); + table_rows.append({row.key, row.value, "propertyValueLabel", "", "", 0}); } - - return panel; + return new ifcinterface::components::KeyValueTable(table_rows, parent); } QWidget* makeRelationshipList(const QList& rows, QWidget* parent = nullptr) { - auto* panel = new QWidget(parent); - panel->setObjectName("attributeList"); - auto* layout = new QVBoxLayout(panel); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(6); - + QList table_rows; for (const auto& row_data : rows) { - auto* row = new QWidget(panel); - row->setObjectName("relationshipRow"); - auto* row_layout = new QHBoxLayout(row); - row_layout->setContentsMargins(0, 0, 0, 0); - row_layout->setSpacing(12); - - auto* key = new QLabel(row_data.key, row); - key->setObjectName("propertyKeyLabel"); - key->setMinimumWidth(72); - - auto* target = new QLabel(row_data.value, row); - target->setObjectName("relationshipValueLabel"); - target->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - - auto* icon = new QLabel(row); - icon->setObjectName("relationshipIconLabel"); - icon->setPixmap(ifcinterface::components::icons::makePanelSvgPixmap(":/icons/cursor-pointer.svg", QSize(14, 14))); - icon->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - - row_layout->addWidget(key); - row_layout->addWidget(target, 1); - row_layout->addWidget(icon, 0, Qt::AlignRight | Qt::AlignVCenter); - layout->addWidget(row); + table_rows.append({row_data.key, + row_data.value, + "relationshipValueLabel", + ":/icons/cursor-pointer.svg", + "relationshipIconLabel", + 72}); } - - return panel; + return new ifcinterface::components::KeyValueTable(table_rows, parent); } } // namespace @@ -121,22 +79,28 @@ PropertiesPanelWidget::PropertiesPanelWidget(QWidget* parent) auto* root = new QVBoxLayout(this); root->setContentsMargins(0, 0, 0, 0); root->setSpacing(0); + + scroll_ = new QScrollArea(this); + scroll_->setWidgetResizable(true); + scroll_->setFrameShape(QFrame::NoFrame); + + content_ = new QWidget(scroll_); + content_->setObjectName("inspectorPanel"); + content_layout_ = new QVBoxLayout(content_); + content_layout_->setContentsMargins(0, 0, 0, 0); + content_layout_->setSpacing(12); + + scroll_->setWidget(content_); + root->addWidget(scroll_); } -void PropertiesPanelWidget::setState(const PropertiesPanelState& state) { - auto* root = qobject_cast(layout()); - while (auto* item = root->takeAt(0)) { +void PropertiesPanelWidget::render(const PropertiesPanelState& state) { + while (auto* item = content_layout_->takeAt(0)) { if (auto* widget = item->widget()) widget->deleteLater(); delete item; } - auto* content = new QWidget(this); - content->setObjectName("inspectorPanel"); - auto* content_layout = new QVBoxLayout(content); - content_layout->setContentsMargins(0, 0, 0, 0); - content_layout->setSpacing(12); - - auto* entity_card = new QFrame(content); + auto* entity_card = new QFrame(content_); entity_card->setObjectName("entityClassCard"); auto* entity_layout = new QHBoxLayout(entity_card); entity_layout->setContentsMargins(10, 8, 10, 8); @@ -159,37 +123,31 @@ void PropertiesPanelWidget::setState(const PropertiesPanelState& state) { QList property_set_widgets; for (const auto& property_set : state.property_sets) { - property_set_widgets.append(makePropertySetPanel(property_set, content)); + property_set_widgets.append(makePropertySetPanel(property_set, content_)); } QList quantity_set_widgets; for (const auto& property_set : state.quantity_sets) { - quantity_set_widgets.append(makePropertySetPanel(property_set, content)); + quantity_set_widgets.append(makePropertySetPanel(property_set, content_)); } - auto* entity_section = new components::Section("", components::SectionHeaderMode::Hidden, "", content); + auto* entity_section = new components::Section("", components::SectionHeaderMode::Hidden, "", content_); entity_section->addBodyWidget(entity_card); - auto* attributes_section = new components::Section("Attributes", components::SectionHeaderMode::Visible, "", content); - attributes_section->addBodyWidget(makeAttributeList(state.attributes, content)); - auto* relationships_section = new components::Section("Relationships", components::SectionHeaderMode::Visible, "", content); - relationships_section->addBodyWidget(makeRelationshipList(state.relationships, content)); - auto* properties_section = new components::Section("Properties", components::SectionHeaderMode::Visible, "Filter properties or sets", content); + auto* attributes_section = new components::Section("Attributes", components::SectionHeaderMode::Visible, "", content_); + attributes_section->addBodyWidget(makeAttributeList(state.attributes, content_)); + auto* relationships_section = new components::Section("Relationships", components::SectionHeaderMode::Visible, "", content_); + relationships_section->addBodyWidget(makeRelationshipList(state.relationships, content_)); + auto* properties_section = new components::Section("Properties", components::SectionHeaderMode::Visible, "Filter properties or sets", content_); for (auto* widget : property_set_widgets) properties_section->addBodyWidget(widget); - auto* quantities_section = new components::Section("Quantities", components::SectionHeaderMode::Visible, "Filter quantities or sets", content); + auto* quantities_section = new components::Section("Quantities", components::SectionHeaderMode::Visible, "Filter quantities or sets", content_); for (auto* widget : quantity_set_widgets) quantities_section->addBodyWidget(widget); - content_layout->addWidget(entity_section); - content_layout->addWidget(attributes_section); - content_layout->addWidget(relationships_section); - content_layout->addWidget(properties_section); - content_layout->addWidget(quantities_section); - content_layout->addStretch(1); - - auto* scroll = new QScrollArea(this); - scroll->setWidgetResizable(true); - scroll->setFrameShape(QFrame::NoFrame); - scroll->setWidget(content); - root->addWidget(scroll); + content_layout_->addWidget(entity_section); + content_layout_->addWidget(attributes_section); + content_layout_->addWidget(relationships_section); + content_layout_->addWidget(properties_section); + content_layout_->addWidget(quantities_section); + content_layout_->addStretch(1); } } // namespace ifcinterface::panels::properties diff --git a/src/interface/panels/properties/PropertiesPanelWidget.h b/src/interface/panels/properties/PropertiesPanelWidget.h index dcd600cbb7..ea929c22de 100644 --- a/src/interface/panels/properties/PropertiesPanelWidget.h +++ b/src/interface/panels/properties/PropertiesPanelWidget.h @@ -25,6 +25,9 @@ #include +class QScrollArea; +class QVBoxLayout; + namespace ifcinterface::panels::properties { class PropertiesPanelWidget : public QWidget { @@ -32,7 +35,12 @@ class PropertiesPanelWidget : public QWidget { public: explicit PropertiesPanelWidget(QWidget* parent = nullptr); - void setState(const PropertiesPanelState& state); + void render(const PropertiesPanelState& state); + +private: + QScrollArea* scroll_ = nullptr; + QWidget* content_ = nullptr; + QVBoxLayout* content_layout_ = nullptr; }; } // namespace ifcinterface::panels::properties