diff --git a/src/interface/CMakeLists.txt b/src/interface/CMakeLists.txt index 5e43a3171b..4bbf886756 100644 --- a/src/interface/CMakeLists.txt +++ b/src/interface/CMakeLists.txt @@ -27,6 +27,21 @@ set(INTERFACE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MockMainWindow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MockMainWindow.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 + ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelView.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/PropertiesPanelTypes.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/PropertiesPanelWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/PropertiesPanelWidget.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/PropertiesPanelView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/PropertiesPanelView.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/SpatialHierarchyPanelTypes.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/SpatialHierarchyPanelView.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/SpatialHierarchyPanelView.h ${CMAKE_CURRENT_SOURCE_DIR}/interface_resources.qrc ) diff --git a/src/interface/MockMainWindow.cpp b/src/interface/MockMainWindow.cpp index 85cefdd4ed..70864390b9 100644 --- a/src/interface/MockMainWindow.cpp +++ b/src/interface/MockMainWindow.cpp @@ -23,6 +23,12 @@ #include "AppSettings.h" #include "SceneLoader.h" #include "ViewportWindow.h" +#include "panels/models/ModelsPanelView.h" +#include "panels/models/ModelsPanelWidget.h" +#include "panels/properties/PropertiesPanelView.h" +#include "panels/properties/PropertiesPanelWidget.h" +#include "panels/spatial_hierarchy/SpatialHierarchyPanelView.h" +#include "panels/spatial_hierarchy/SpatialHierarchyPanelWidget.h" #include #include @@ -93,10 +99,6 @@ QIcon makePanelSvgIcon(const QString& icon_path) { return makeTintedSvgIcon(icon_path, "#e7ebf2", "#ffffff", "#6f7988"); } -QPixmap makePanelSvgPixmap(const QString& icon_path, const QSize& size) { - return renderTintedSvgPixmap(icon_path, "#e7ebf2", size); -} - class DockTitleBar : public QWidget { public: explicit DockTitleBar(const QString& title, bool has_settings = false, QWidget* parent = nullptr) @@ -175,165 +177,6 @@ QFrame* wrapInspectorPanel(QWidget* inner) { return outer; } -QWidget* makeInspectorFilterField(const QString& placeholder, QWidget* parent = nullptr) { - auto* field = new QLineEdit(parent); - field->setPlaceholderText(placeholder); - field->setClearButtonEnabled(true); - field->addAction(makePanelSvgIcon(":/icons/filter.svg"), QLineEdit::LeadingPosition); - return field; -} - -QWidget* makePropertySetPanel(const QString& title, - const QList>& rows, - QWidget* parent = nullptr) { - auto* group = new QGroupBox(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); - - for (const auto& [name, value] : rows) { - auto* key = new QLabel(name, group); - key->setObjectName("propertyKeyLabel"); - auto* val = new QLabel(value, group); - val->setObjectName("propertyValueLabel"); - val->setWordWrap(true); - form->addRow(key, val); - } - - return group; -} - -QWidget* makeInspectorSection(const QString& title, - const QString& filter_placeholder, - const QList& groups, - QWidget* parent = nullptr) { - auto* section = new QWidget(parent); - section->setObjectName("inspectorSection"); - auto* layout = new QVBoxLayout(section); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(6); - - auto* header = new QFrame(section); - header->setObjectName("inspectorSectionHeader"); - auto* header_layout = new QHBoxLayout(header); - header_layout->setContentsMargins(0, 0, 0, 0); - header_layout->setSpacing(6); - - auto* toggle = new QToolButton(header); - toggle->setObjectName("inspectorSectionButton"); - toggle->setText(title); - toggle->setCheckable(true); - toggle->setChecked(true); - toggle->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - toggle->setArrowType(Qt::DownArrow); - header_layout->addWidget(toggle); - header_layout->addStretch(1); - - QLineEdit* filter_field = nullptr; - if (!filter_placeholder.isEmpty()) { - auto* filter_toggle = new QToolButton(header); - filter_toggle->setObjectName("inspectorFilterToggle"); - filter_toggle->setCheckable(true); - filter_toggle->setChecked(false); - filter_toggle->setIcon(makePanelSvgIcon(":/icons/filter.svg")); - filter_toggle->setAutoRaise(true); - filter_toggle->setToolTip(QString("Filter %1").arg(title.toLower())); - header_layout->addWidget(filter_toggle); - - filter_field = qobject_cast(makeInspectorFilterField(filter_placeholder, section)); - filter_field->setVisible(false); - QObject::connect(filter_toggle, &QToolButton::toggled, filter_field, [filter_field](bool visible) { - filter_field->setVisible(visible); - if (visible) filter_field->setFocus(); - }); - } - - auto* body = new QWidget(section); - body->setObjectName("inspectorSectionBody"); - auto* body_layout = new QVBoxLayout(body); - body_layout->setContentsMargins(10, 6, 10, 0); - body_layout->setSpacing(6); - for (auto* group : groups) body_layout->addWidget(group); - - QObject::connect(toggle, &QToolButton::toggled, body, [toggle, body](bool expanded) { - toggle->setArrowType(expanded ? Qt::DownArrow : Qt::RightArrow); - body->setVisible(expanded); - }); - - layout->addWidget(header); - if (filter_field) { - auto* filter_wrapper = new QWidget(section); - filter_wrapper->setObjectName("inspectorFilterWrapper"); - auto* filter_wrapper_layout = new QVBoxLayout(filter_wrapper); - filter_wrapper_layout->setContentsMargins(10, 0, 10, 0); - filter_wrapper_layout->setSpacing(0); - filter_wrapper_layout->addWidget(filter_field); - layout->addWidget(filter_wrapper); - } - layout->addWidget(body); - return section; -} - -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); - - for (const auto& [name, value] : rows) { - auto* key = new QLabel(name, panel); - key->setObjectName("propertyKeyLabel"); - auto* val = new QLabel(value, panel); - val->setObjectName("propertyValueLabel"); - val->setWordWrap(true); - form->addRow(key, val); - } - - return panel; -} - -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); - - for (const auto& [name, value] : 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(name, row); - key->setObjectName("propertyKeyLabel"); - key->setMinimumWidth(72); - - auto* target = new QLabel(value, row); - target->setObjectName("relationshipValueLabel"); - target->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - - auto* icon = new QLabel(row); - icon->setObjectName("relationshipIconLabel"); - icon->setPixmap(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); - } - - return panel; -} - } // namespace MockMainWindow::MockMainWindow(QWidget* parent) @@ -931,231 +774,22 @@ void MockMainWindow::setupViewport() { } void MockMainWindow::setupDocks() { - auto* models_panel = new QWidget(this); - auto* models_panel_layout = new QVBoxLayout(models_panel); - models_panel_layout->setContentsMargins(0, 0, 0, 0); - models_panel_layout->setSpacing(0); + auto* models_panel = new ifcinterface::panels::models::ModelsPanelWidget(this); + auto* spatial_panel = new ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelWidget(this); + auto* properties_panel = new ifcinterface::panels::properties::PropertiesPanelWidget(this); - auto* models_tree = new QTreeWidget(this); - models_tree->setColumnCount(2); - models_tree->setHeaderLabels({"Model", ""}); - models_tree->setIconSize(QSize(16, 16)); - models_tree->setSelectionMode(QAbstractItemView::ExtendedSelection); - models_tree->setContextMenuPolicy(Qt::CustomContextMenu); - models_tree->setUniformRowHeights(true); - models_tree->header()->setStretchLastSection(false); - models_tree->header()->setSectionResizeMode(0, QHeaderView::Stretch); - models_tree->header()->setSectionResizeMode(1, QHeaderView::Fixed); - models_tree->header()->resizeSection(1, 28); - models_tree->header()->hide(); + models_panel_view_ = new ifcinterface::panels::models::ModelsPanelView(models_panel, this); + spatial_panel_view_ = new ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelView(spatial_panel, this); + properties_panel_view_ = new ifcinterface::panels::properties::PropertiesPanelView(properties_panel, this); - auto* local_group = new QTreeWidgetItem(models_tree, {"Local Models", ""}); - local_group->setIcon(0, makePanelSvgIcon(":/icons/folder.svg")); - local_group->setIcon(1, makePanelSvgIcon(":/icons/eye.svg")); - local_group->setData(1, Qt::UserRole, true); - local_group->setSizeHint(0, QSize(0, 24)); - auto* linked_group = new QTreeWidgetItem(models_tree, {"Linked Models", ""}); - linked_group->setIcon(0, makePanelSvgIcon(":/icons/folder.svg")); - linked_group->setIcon(1, makePanelSvgIcon(":/icons/eye.svg")); - linked_group->setData(1, Qt::UserRole, true); - linked_group->setSizeHint(0, QSize(0, 24)); - - auto make_model_item = [this](QTreeWidgetItem* parent, const QString& name, bool visible) { - auto* item = new QTreeWidgetItem(parent, {name, ""}); - item->setIcon(0, makePanelSvgIcon(":/icons/cube.svg")); - item->setIcon(1, makePanelSvgIcon(visible ? ":/icons/eye-solid.svg" : ":/icons/eye.svg")); - item->setData(1, Qt::UserRole, visible); - item->setSizeHint(0, QSize(0, 24)); - return item; - }; - - make_model_item(local_group, "Architecture.ifc", true); - make_model_item(local_group, "Structure.ifc", true); - make_model_item(linked_group, "MEP.ifc", false); - models_tree->expandAll(); - - models_panel_layout->addWidget(models_tree); - - connect(models_tree, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem* item, int column) { - if (!item || column != 1) return; - const bool visible = item->data(1, Qt::UserRole).toBool(); - const bool next_visible = !visible; - item->setData(1, Qt::UserRole, next_visible); - if (item->childCount() > 0) { - item->setIcon(1, makePanelSvgIcon(next_visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg")); - } else { - item->setIcon(1, makePanelSvgIcon(next_visible ? ":/icons/eye-solid.svg" : ":/icons/eye-closed.svg")); - } - setStatusMessage("Models", next_visible ? "Item shown" : "Item hidden"); - }); - - connect(models_tree, &QTreeWidget::customContextMenuRequested, this, [this, models_tree](const QPoint& pos) { - auto* item = models_tree->itemAt(pos); - QMenu menu(models_tree); - if (item && item->childCount() > 0) { - menu.addAction(makePanelSvgIcon(":/icons/folder-plus.svg"), "Add Group", [this]() { - setStatusMessage("Models", "Add Group coming soon"); - }); - menu.addAction(makePanelSvgIcon(":/icons/folder-minus.svg"), "Remove Group", [this]() { - setStatusMessage("Models", "Remove Group coming soon"); - }); - } - if (item && item->childCount() == 0) { - menu.addAction(makePanelSvgIcon(":/icons/minus-square.svg"), "Remove Model", [this]() { - setStatusMessage("Models", "Remove Model coming soon"); - }); - } - menu.addAction(makePanelSvgIcon(":/icons/intersect.svg"), "Invert Visibility", [this]() { - setStatusMessage("Models", "Invert visibility coming soon"); - }); - if (!menu.actions().isEmpty()) menu.exec(models_tree->viewport()->mapToGlobal(pos)); - }); - - auto* spatial_tree = new QTreeWidget(this); - spatial_tree->setColumnCount(2); - spatial_tree->setHeaderLabels({"Spatial Item", ""}); - spatial_tree->setIconSize(QSize(16, 16)); - spatial_tree->setSelectionMode(QAbstractItemView::ExtendedSelection); - spatial_tree->setUniformRowHeights(true); - spatial_tree->header()->setStretchLastSection(false); - spatial_tree->header()->setSectionResizeMode(0, QHeaderView::Stretch); - spatial_tree->header()->setSectionResizeMode(1, QHeaderView::Fixed); - spatial_tree->header()->resizeSection(1, 28); - spatial_tree->header()->hide(); - - auto make_spatial_item = [this](QTreeWidgetItem* parent, const QString& name, const QString& icon_path) { - auto* item = new QTreeWidgetItem(parent, {name, ""}); - item->setIcon(0, makePanelSvgIcon(icon_path)); - item->setIcon(1, makePanelSvgIcon(":/icons/eye.svg")); - item->setData(1, Qt::UserRole, true); - item->setSizeHint(0, QSize(0, 24)); - return item; - }; - - auto* site = make_spatial_item(spatial_tree->invisibleRootItem(), "Site A", ":/icons/frame-alt.svg"); - auto* building = make_spatial_item(site, "Building 01", ":/icons/city.svg"); - auto* storey = make_spatial_item(building, "Level 02", ":/icons/planimetry.svg"); - make_spatial_item(storey, "Lobby", ":/icons/square3d-from-center.svg"); - make_spatial_item(storey, "Core", ":/icons/square3d-from-center.svg"); - spatial_tree->expandAll(); - - connect(spatial_tree, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem* item, int column) { - if (!item || column != 1) return; - const bool visible = item->data(1, Qt::UserRole).toBool(); - const bool next_visible = !visible; - item->setData(1, Qt::UserRole, next_visible); - item->setIcon(1, makePanelSvgIcon(next_visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg")); - setStatusMessage("Spatial", next_visible ? "Item shown" : "Item hidden"); - }); - - auto* properties_content = new QWidget(this); - properties_content->setObjectName("inspectorPanel"); - auto* properties_layout = new QVBoxLayout(properties_content); - properties_layout->setContentsMargins(0, 0, 0, 0); - properties_layout->setSpacing(12); - - auto* entity_card = new QFrame(properties_content); - entity_card->setObjectName("entityClassCard"); - auto* entity_layout = new QHBoxLayout(entity_card); - entity_layout->setContentsMargins(10, 8, 10, 8); - entity_layout->setSpacing(10); - auto* entity_icon = new QLabel(entity_card); - entity_icon->setPixmap(makePanelSvgPixmap(":/icons/cube-dots.svg", QSize(28, 28))); - entity_icon->setAlignment(Qt::AlignCenter); - auto* entity_text = new QWidget(entity_card); - auto* entity_text_layout = new QVBoxLayout(entity_text); - entity_text_layout->setContentsMargins(0, 0, 0, 0); - entity_text_layout->setSpacing(2); - auto* entity_class = new QLabel("IfcWall", entity_text); - entity_class->setObjectName("entityClassLabel"); - auto* entity_type = new QLabel("SOLIDWALL", entity_text); - entity_type->setObjectName("entityTypeLabel"); - entity_text_layout->addWidget(entity_class); - entity_text_layout->addWidget(entity_type); - entity_layout->addWidget(entity_icon, 0, Qt::AlignVCenter); - entity_layout->addWidget(entity_text, 1, Qt::AlignVCenter); - - auto* attributes_section = makeInspectorSection( - "Attributes", "", - { - makeAttributeList({{"GlobalId", "2Q$n5SLPP9Q8B7wQKjKfUQ"}, - {"Name", "Core-EXT-204"}, - {"Description", "External load-bearing wall"}}, properties_content) - }, - properties_content); - - auto* properties_section = makeInspectorSection( - "Properties", "Filter properties or sets", - { - makePropertySetPanel("Pset_WallCommon", - {{"Reference", "Core-EXT-204"}, - {"Status", "Reviewed"}, - {"Fire Rating", "120 min"}, - {"LoadBearing", "True"}}, - properties_content), - makePropertySetPanel("Identity Data", - {{"Type", "IfcWall"}, - {"Name", "Core-EXT-204"}, - {"Owner", "Architecture"}, - {"Phase", "Construction"}}, - properties_content), - makePropertySetPanel("BIM Collaboration", - {{"Issue Count", "2 open"}, - {"Last Review", "2026-04-30"}, - {"Assigned To", "Design Coordination"}}, - properties_content) - }, - properties_content); - - qobject_cast(attributes_section->layout())->setSpacing(8); - - auto* relationships_section = makeInspectorSection( - "Relationships", "", - { - makeRelationshipList({{"Type", "Basic Wall: Exterior - 200mm"}, - {"Container", "Level 02"}}, properties_content) - }, - properties_content); - - auto* quantities_section = makeInspectorSection( - "Quantities", "Filter quantities or sets", - { - makePropertySetPanel("BaseQuantities", - {{"Length", "6.20 m"}, - {"Height", "3.45 m"}, - {"Width", "0.30 m"}, - {"Volume", "6.42 m3"}}, - properties_content), - makePropertySetPanel("Finish Quantities", - {{"NetSideArea", "21.39 m2"}, - {"GrossArea", "22.10 m2"}, - {"Paint Coverage", "42.78 m2"}}, - properties_content) - }, - properties_content); - - auto* entity_wrapper = new QWidget(properties_content); - entity_wrapper->setObjectName("inspectorSectionBody"); - auto* entity_wrapper_layout = new QVBoxLayout(entity_wrapper); - entity_wrapper_layout->setContentsMargins(10, 0, 10, 0); - entity_wrapper_layout->setSpacing(0); - entity_wrapper_layout->addWidget(entity_card); - - properties_layout->addWidget(entity_wrapper); - properties_layout->addWidget(attributes_section); - properties_layout->addWidget(relationships_section); - properties_layout->addWidget(properties_section); - properties_layout->addWidget(quantities_section); - properties_layout->addStretch(1); - - auto* properties_scroll = new QScrollArea(this); - properties_scroll->setWidgetResizable(true); - properties_scroll->setFrameShape(QFrame::NoFrame); - properties_scroll->setWidget(properties_content); + connect(models_panel_view_, &ifcinterface::panels::models::ModelsPanelView::statusMessageRequested, + this, &MockMainWindow::setStatusMessage); + connect(spatial_panel_view_, &ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelView::statusMessageRequested, + this, &MockMainWindow::setStatusMessage); models_dock_ = makeDock("Models", wrapPanel(models_panel), this, true); - spatial_dock_ = makeDock("Spatial Hierarchy", wrapPanel(spatial_tree), this); - properties_dock_ = makeDock("Properties", wrapInspectorPanel(properties_scroll), this); + spatial_dock_ = makeDock("Spatial Hierarchy", wrapPanel(spatial_panel), this); + properties_dock_ = makeDock("Properties", wrapInspectorPanel(properties_panel), this); layers_dock_ = makeDock("Layers", wrapPanel(makeComingSoonPanel("Layers")), this); stored_views_dock_ = makeDock("Stored Views", wrapPanel(makeComingSoonPanel("Stored Views")), this); search_dock_ = makeDock("Search and Query", wrapPanel(makeComingSoonPanel("Search and Query")), this); diff --git a/src/interface/MockMainWindow.h b/src/interface/MockMainWindow.h index 81da53db90..a038cb68b7 100644 --- a/src/interface/MockMainWindow.h +++ b/src/interface/MockMainWindow.h @@ -31,6 +31,9 @@ class QTabBar; class QToolButton; class ViewportWindow; class SceneLoader; +namespace ifcinterface::panels::models { class ModelsPanelView; } +namespace ifcinterface::panels::spatial_hierarchy { class SpatialHierarchyPanelView; } +namespace ifcinterface::panels::properties { class PropertiesPanelView; } class MockMainWindow : public QMainWindow { Q_OBJECT @@ -83,6 +86,9 @@ private: 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; }; #endif diff --git a/src/interface/panels/properties/PropertiesPanelTypes.h b/src/interface/panels/properties/PropertiesPanelTypes.h new file mode 100644 index 0000000000..9a1f8da9e7 --- /dev/null +++ b/src/interface/panels/properties/PropertiesPanelTypes.h @@ -0,0 +1,60 @@ +// 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_PANELS_PROPERTIESPANELTYPES_H +#define IFCINTERFACE_PANELS_PROPERTIESPANELTYPES_H + +#include +#include +#include + +namespace ifcinterface::panels::properties { + +struct KeyValueRow { + QString key; + QString value; +}; + +struct RelationshipRow { + QString key; + QString value; +}; + +struct PropertySet { + QString title; + QList rows; +}; + +struct EntitySummary { + QString entity_class; + QString predefined_type; +}; + +struct PropertiesPanelState { + EntitySummary entity; + QList attributes; + QList relationships; + QList property_sets; + QList quantity_sets; +}; + +} // namespace ifcinterface::panels::properties + +#endif diff --git a/src/interface/panels/properties/PropertiesPanelView.cpp b/src/interface/panels/properties/PropertiesPanelView.cpp new file mode 100644 index 0000000000..1b74f40fff --- /dev/null +++ b/src/interface/panels/properties/PropertiesPanelView.cpp @@ -0,0 +1,71 @@ +// 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 "PropertiesPanelView.h" + +#include "PropertiesPanelWidget.h" + +namespace ifcinterface::panels::properties { + +PropertiesPanelView::PropertiesPanelView(PropertiesPanelWidget* widget, QObject* parent) + : QObject(parent), widget_(widget) +{ + state_.entity = {"IfcWall", "SOLIDWALL"}; + state_.attributes = { + {"GlobalId", "2Q$n5SLPP9Q8B7wQKjKfUQ"}, + {"Name", "Core-EXT-204"}, + {"Description", "External load-bearing wall"}, + }; + state_.relationships = { + {"Type", "Basic Wall: Exterior - 200mm"}, + {"Container", "Level 02"}, + }; + state_.property_sets = { + {"Pset_WallCommon", + {{"Reference", "Core-EXT-204"}, + {"Status", "Reviewed"}, + {"Fire Rating", "120 min"}, + {"LoadBearing", "True"}}}, + {"Identity Data", + {{"Type", "IfcWall"}, + {"Name", "Core-EXT-204"}, + {"Owner", "Architecture"}, + {"Phase", "Construction"}}}, + {"BIM Collaboration", + {{"Issue Count", "2 open"}, + {"Last Review", "2026-04-30"}, + {"Assigned To", "Design Coordination"}}}, + }; + state_.quantity_sets = { + {"BaseQuantities", + {{"Length", "6.20 m"}, + {"Height", "3.45 m"}, + {"Width", "0.30 m"}, + {"Volume", "6.42 m3"}}}, + {"Finish Quantities", + {{"NetSideArea", "21.39 m2"}, + {"GrossArea", "22.10 m2"}, + {"Paint Coverage", "42.78 m2"}}}, + }; + + widget_->setState(state_); +} + +} // namespace ifcinterface::panels::properties diff --git a/src/interface/panels/properties/PropertiesPanelView.h b/src/interface/panels/properties/PropertiesPanelView.h new file mode 100644 index 0000000000..a61c393d31 --- /dev/null +++ b/src/interface/panels/properties/PropertiesPanelView.h @@ -0,0 +1,44 @@ +// 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_PANELS_PROPERTIESPANELVIEW_H +#define IFCINTERFACE_PANELS_PROPERTIESPANELVIEW_H + +#include "PropertiesPanelTypes.h" + +#include + +namespace ifcinterface::panels::properties { + +class PropertiesPanelWidget; + +class PropertiesPanelView : public QObject { + Q_OBJECT +public: + explicit PropertiesPanelView(PropertiesPanelWidget* widget, QObject* parent = nullptr); + +private: + PropertiesPanelWidget* widget_ = nullptr; + PropertiesPanelState state_; +}; + +} // namespace ifcinterface::panels::properties + +#endif diff --git a/src/interface/panels/properties/PropertiesPanelWidget.cpp b/src/interface/panels/properties/PropertiesPanelWidget.cpp new file mode 100644 index 0000000000..4459faa5dd --- /dev/null +++ b/src/interface/panels/properties/PropertiesPanelWidget.cpp @@ -0,0 +1,313 @@ +// 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 "PropertiesPanelWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +QPixmap renderPanelSvgPixmap(const QString& icon_path, const QString& color, const QSize& size) { + QFile file(icon_path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return QIcon(icon_path).pixmap(size); + } + + QString svg = QString::fromUtf8(file.readAll()); + svg.replace("currentColor", color, Qt::CaseSensitive); + svg.replace(QRegularExpression(R"(stroke="[^"]*")"), QString("stroke=\"%1\"").arg(color)); + svg.replace(QRegularExpression(R"(fill="none")"), "fill=\"none\""); + + QSvgRenderer renderer(svg.toUtf8()); + QPixmap pixmap(size); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + renderer.render(&painter); + return pixmap; +} + +QIcon makePanelSvgIcon(const QString& icon_path) { + QIcon icon; + icon.addPixmap(renderPanelSvgPixmap(icon_path, "#e7ebf2", QSize(20, 20)), QIcon::Normal, QIcon::Off); + icon.addPixmap(renderPanelSvgPixmap(icon_path, "#ffffff", QSize(20, 20)), QIcon::Active, QIcon::Off); + icon.addPixmap(renderPanelSvgPixmap(icon_path, "#ffffff", QSize(20, 20)), QIcon::Selected, QIcon::Off); + icon.addPixmap(renderPanelSvgPixmap(icon_path, "#6f7988", QSize(20, 20)), QIcon::Disabled, QIcon::Off); + return icon; +} + +QPixmap makePanelSvgPixmap(const QString& icon_path, const QSize& size) { + return renderPanelSvgPixmap(icon_path, "#e7ebf2", size); +} + +QWidget* makeInspectorFilterField(const QString& placeholder, QWidget* parent = nullptr) { + auto* field = new QLineEdit(parent); + field->setPlaceholderText(placeholder); + field->setClearButtonEnabled(true); + field->addAction(makePanelSvgIcon(":/icons/filter.svg"), QLineEdit::LeadingPosition); + return field; +} + +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); + + 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); + } + + return group; +} + +QWidget* makeInspectorSection(const QString& title, + const QString& filter_placeholder, + const QList& groups, + QWidget* parent = nullptr) { + auto* section = new QWidget(parent); + section->setObjectName("inspectorSection"); + auto* layout = new QVBoxLayout(section); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(6); + + auto* header = new QFrame(section); + header->setObjectName("inspectorSectionHeader"); + auto* header_layout = new QHBoxLayout(header); + header_layout->setContentsMargins(0, 0, 0, 0); + header_layout->setSpacing(6); + + auto* toggle = new QToolButton(header); + toggle->setObjectName("inspectorSectionButton"); + toggle->setText(title); + toggle->setCheckable(true); + toggle->setChecked(true); + toggle->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + toggle->setArrowType(Qt::DownArrow); + header_layout->addWidget(toggle); + header_layout->addStretch(1); + + QLineEdit* filter_field = nullptr; + if (!filter_placeholder.isEmpty()) { + auto* filter_toggle = new QToolButton(header); + filter_toggle->setObjectName("inspectorFilterToggle"); + filter_toggle->setCheckable(true); + filter_toggle->setIcon(makePanelSvgIcon(":/icons/filter.svg")); + filter_toggle->setAutoRaise(true); + header_layout->addWidget(filter_toggle); + + filter_field = qobject_cast(makeInspectorFilterField(filter_placeholder, section)); + filter_field->setVisible(false); + QObject::connect(filter_toggle, &QToolButton::toggled, filter_field, [filter_field](bool visible) { + filter_field->setVisible(visible); + if (visible) filter_field->setFocus(); + }); + } + + auto* body = new QWidget(section); + body->setObjectName("inspectorSectionBody"); + auto* body_layout = new QVBoxLayout(body); + body_layout->setContentsMargins(10, 6, 10, 0); + body_layout->setSpacing(6); + for (auto* group : groups) body_layout->addWidget(group); + + QObject::connect(toggle, &QToolButton::toggled, body, [toggle, body](bool expanded) { + toggle->setArrowType(expanded ? Qt::DownArrow : Qt::RightArrow); + body->setVisible(expanded); + }); + + layout->addWidget(header); + if (filter_field) { + auto* filter_wrapper = new QWidget(section); + filter_wrapper->setObjectName("inspectorFilterWrapper"); + auto* filter_wrapper_layout = new QVBoxLayout(filter_wrapper); + filter_wrapper_layout->setContentsMargins(10, 0, 10, 0); + filter_wrapper_layout->setSpacing(0); + filter_wrapper_layout->addWidget(filter_field); + layout->addWidget(filter_wrapper); + } + layout->addWidget(body); + return section; +} + +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); + + 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); + } + + return panel; +} + +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); + + 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(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); + } + + return panel; +} + +} // namespace + +namespace ifcinterface::panels::properties { + +PropertiesPanelWidget::PropertiesPanelWidget(QWidget* parent) + : QWidget(parent) +{ + auto* root = new QVBoxLayout(this); + root->setContentsMargins(0, 0, 0, 0); + root->setSpacing(0); +} + +void PropertiesPanelWidget::setState(const PropertiesPanelState& state) { + auto* root = qobject_cast(layout()); + while (auto* item = root->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); + entity_card->setObjectName("entityClassCard"); + auto* entity_layout = new QHBoxLayout(entity_card); + entity_layout->setContentsMargins(10, 8, 10, 8); + entity_layout->setSpacing(10); + auto* entity_icon = new QLabel(entity_card); + entity_icon->setPixmap(makePanelSvgPixmap(":/icons/cube-dots.svg", QSize(28, 28))); + entity_icon->setAlignment(Qt::AlignCenter); + auto* entity_text = new QWidget(entity_card); + auto* entity_text_layout = new QVBoxLayout(entity_text); + entity_text_layout->setContentsMargins(0, 0, 0, 0); + entity_text_layout->setSpacing(2); + auto* entity_class = new QLabel(state.entity.entity_class, entity_text); + entity_class->setObjectName("entityClassLabel"); + auto* entity_type = new QLabel(state.entity.predefined_type, entity_text); + entity_type->setObjectName("entityTypeLabel"); + entity_text_layout->addWidget(entity_class); + entity_text_layout->addWidget(entity_type); + entity_layout->addWidget(entity_icon, 0, Qt::AlignVCenter); + entity_layout->addWidget(entity_text, 1, Qt::AlignVCenter); + + auto* entity_wrapper = new QWidget(content); + entity_wrapper->setObjectName("inspectorSectionBody"); + auto* entity_wrapper_layout = new QVBoxLayout(entity_wrapper); + entity_wrapper_layout->setContentsMargins(10, 0, 10, 0); + entity_wrapper_layout->setSpacing(0); + entity_wrapper_layout->addWidget(entity_card); + + QList property_set_widgets; + for (const auto& property_set : state.property_sets) { + 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)); + } + + auto* attributes_section = makeInspectorSection( + "Attributes", "", {makeAttributeList(state.attributes, content)}, content); + auto* relationships_section = makeInspectorSection( + "Relationships", "", {makeRelationshipList(state.relationships, content)}, content); + auto* properties_section = makeInspectorSection( + "Properties", "Filter properties or sets", property_set_widgets, content); + auto* quantities_section = makeInspectorSection( + "Quantities", "Filter quantities or sets", quantity_set_widgets, content); + + content_layout->addWidget(entity_wrapper); + 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); +} + +} // namespace ifcinterface::panels::properties diff --git a/src/interface/panels/properties/PropertiesPanelWidget.h b/src/interface/panels/properties/PropertiesPanelWidget.h new file mode 100644 index 0000000000..dcd600cbb7 --- /dev/null +++ b/src/interface/panels/properties/PropertiesPanelWidget.h @@ -0,0 +1,40 @@ +// 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_PANELS_PROPERTIESPANELWIDGET_H +#define IFCINTERFACE_PANELS_PROPERTIESPANELWIDGET_H + +#include "PropertiesPanelTypes.h" + +#include + +namespace ifcinterface::panels::properties { + +class PropertiesPanelWidget : public QWidget { + Q_OBJECT +public: + explicit PropertiesPanelWidget(QWidget* parent = nullptr); + + void setState(const PropertiesPanelState& state); +}; + +} // namespace ifcinterface::panels::properties + +#endif diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelTypes.h b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelTypes.h new file mode 100644 index 0000000000..90dd67b8fd --- /dev/null +++ b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelTypes.h @@ -0,0 +1,48 @@ +// 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_PANELS_SPATIALHIERARCHYPANELTYPES_H +#define IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELTYPES_H + +#include +#include +#include + +namespace ifcinterface::panels::spatial_hierarchy { + +enum class ItemKind { + Site, + Building, + Storey, + Space, +}; + +struct TreeNode { + QString name; + ItemKind kind = ItemKind::Space; + bool visible = true; + QList children; +}; + +using NodePath = QStringList; + +} // namespace ifcinterface::panels::spatial_hierarchy + +#endif diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.cpp b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.cpp new file mode 100644 index 0000000000..a3948558b8 --- /dev/null +++ b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.cpp @@ -0,0 +1,71 @@ +// 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 "SpatialHierarchyPanelView.h" + +#include "SpatialHierarchyPanelWidget.h" + +namespace ifcinterface::panels::spatial_hierarchy { + +namespace { + +TreeNode* findNodeRecursive(QList& nodes, const NodePath& path, int depth) { + for (auto& node : nodes) { + if (node.name != path.at(depth)) continue; + if (depth == path.size() - 1) return &node; + return findNodeRecursive(node.children, path, depth + 1); + } + return nullptr; +} + +} // namespace + +SpatialHierarchyPanelView::SpatialHierarchyPanelView(SpatialHierarchyPanelWidget* widget, QObject* parent) + : QObject(parent), widget_(widget) +{ + nodes_ = { + {"Site A", ItemKind::Site, true, + {{"Building 01", ItemKind::Building, true, + {{"Level 02", ItemKind::Storey, true, + {{"Lobby", ItemKind::Space, true, {}}, + {"Core", ItemKind::Space, true, {}}}}}}}}, + }; + + connect(widget_, &SpatialHierarchyPanelWidget::visibilityToggleRequested, this, [this](const NodePath& path) { + if (auto* node = findNode(path)) { + node->visible = !node->visible; + reload(); + emit statusMessageRequested("Spatial", node->visible ? "Item shown" : "Item hidden"); + } + }); + + reload(); +} + +void SpatialHierarchyPanelView::reload() { + widget_->setNodes(nodes_); +} + +TreeNode* SpatialHierarchyPanelView::findNode(const NodePath& path) { + if (path.isEmpty()) return nullptr; + return findNodeRecursive(nodes_, path, 0); +} + +} // namespace ifcinterface::panels::spatial_hierarchy diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.h b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.h new file mode 100644 index 0000000000..1924b7f9f3 --- /dev/null +++ b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.h @@ -0,0 +1,50 @@ +// 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_PANELS_SPATIALHIERARCHYPANELVIEW_H +#define IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELVIEW_H + +#include "SpatialHierarchyPanelTypes.h" + +#include + +namespace ifcinterface::panels::spatial_hierarchy { + +class SpatialHierarchyPanelWidget; + +class SpatialHierarchyPanelView : public QObject { + Q_OBJECT +public: + explicit SpatialHierarchyPanelView(SpatialHierarchyPanelWidget* widget, QObject* parent = nullptr); + +signals: + void statusMessageRequested(const QString& mode, const QString& detail); + +private: + void reload(); + TreeNode* findNode(const NodePath& path); + + SpatialHierarchyPanelWidget* widget_ = nullptr; + QList nodes_; +}; + +} // namespace ifcinterface::panels::spatial_hierarchy + +#endif diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp new file mode 100644 index 0000000000..efb312cd1e --- /dev/null +++ b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp @@ -0,0 +1,131 @@ +// 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 "SpatialHierarchyPanelWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +QPixmap renderPanelSvgPixmap(const QString& icon_path, const QString& color, const QSize& size) { + QFile file(icon_path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return QIcon(icon_path).pixmap(size); + } + + QString svg = QString::fromUtf8(file.readAll()); + svg.replace("currentColor", color, Qt::CaseSensitive); + svg.replace(QRegularExpression(R"(stroke="[^"]*")"), QString("stroke=\"%1\"").arg(color)); + svg.replace(QRegularExpression(R"(fill="none")"), "fill=\"none\""); + + QSvgRenderer renderer(svg.toUtf8()); + QPixmap pixmap(size); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + renderer.render(&painter); + return pixmap; +} + +QIcon makePanelSvgIcon(const QString& icon_path) { + QIcon icon; + icon.addPixmap(renderPanelSvgPixmap(icon_path, "#e7ebf2", QSize(20, 20)), QIcon::Normal, QIcon::Off); + icon.addPixmap(renderPanelSvgPixmap(icon_path, "#ffffff", QSize(20, 20)), QIcon::Active, QIcon::Off); + icon.addPixmap(renderPanelSvgPixmap(icon_path, "#ffffff", QSize(20, 20)), QIcon::Selected, QIcon::Off); + icon.addPixmap(renderPanelSvgPixmap(icon_path, "#6f7988", QSize(20, 20)), QIcon::Disabled, QIcon::Off); + return icon; +} + +} // namespace + +namespace ifcinterface::panels::spatial_hierarchy { + +SpatialHierarchyPanelWidget::SpatialHierarchyPanelWidget(QWidget* parent) + : QWidget(parent) +{ + auto* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + tree_ = new QTreeWidget(this); + tree_->setColumnCount(2); + tree_->setHeaderLabels({"Spatial Item", ""}); + tree_->setIconSize(QSize(16, 16)); + tree_->setSelectionMode(QAbstractItemView::ExtendedSelection); + tree_->setUniformRowHeights(true); + tree_->header()->setStretchLastSection(false); + tree_->header()->setSectionResizeMode(0, QHeaderView::Stretch); + tree_->header()->setSectionResizeMode(1, QHeaderView::Fixed); + tree_->header()->resizeSection(1, 28); + tree_->header()->hide(); + layout->addWidget(tree_); + + connect(tree_, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem* item, int column) { + if (!item || column != 1) return; + emit visibilityToggleRequested(itemPath(item)); + }); +} + +void SpatialHierarchyPanelWidget::setNodes(const QList& nodes) { + tree_->clear(); + for (const auto& node : nodes) { + addNode(tree_->invisibleRootItem(), node); + } + tree_->expandAll(); +} + +void SpatialHierarchyPanelWidget::addNode(QTreeWidgetItem* parent, const TreeNode& node) { + auto* item = new QTreeWidgetItem(parent, {node.name, ""}); + item->setData(1, Qt::UserRole, node.visible); + item->setSizeHint(0, QSize(0, 24)); + item->setIcon(0, makePanelSvgIcon(iconPath(node.kind))); + item->setIcon(1, makePanelSvgIcon(node.visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg")); + for (const auto& child : node.children) { + addNode(item, child); + } +} + +NodePath SpatialHierarchyPanelWidget::itemPath(QTreeWidgetItem* item) const { + NodePath path; + while (item) { + path.prepend(item->text(0)); + item = item->parent(); + } + return path; +} + +QString SpatialHierarchyPanelWidget::iconPath(ItemKind kind) const { + switch (kind) { + case ItemKind::Site: return ":/icons/frame-alt.svg"; + case ItemKind::Building: return ":/icons/city.svg"; + case ItemKind::Storey: return ":/icons/planimetry.svg"; + case ItemKind::Space: return ":/icons/square3d-from-center.svg"; + } + return ":/icons/frame-alt.svg"; +} + +} // namespace ifcinterface::panels::spatial_hierarchy diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.h b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.h new file mode 100644 index 0000000000..b5e15720ce --- /dev/null +++ b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.h @@ -0,0 +1,53 @@ +// 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_PANELS_SPATIALHIERARCHYPANELWIDGET_H +#define IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELWIDGET_H + +#include "SpatialHierarchyPanelTypes.h" + +#include + +class QTreeWidget; +class QTreeWidgetItem; + +namespace ifcinterface::panels::spatial_hierarchy { + +class SpatialHierarchyPanelWidget : public QWidget { + Q_OBJECT +public: + explicit SpatialHierarchyPanelWidget(QWidget* parent = nullptr); + + void setNodes(const QList& nodes); + +signals: + void visibilityToggleRequested(const NodePath& path); + +private: + void addNode(QTreeWidgetItem* parent, const TreeNode& node); + NodePath itemPath(QTreeWidgetItem* item) const; + QString iconPath(ItemKind kind) const; + + QTreeWidget* tree_ = nullptr; +}; + +} // namespace ifcinterface::panels::spatial_hierarchy + +#endif