Interface mockup 2

This commit is contained in:
Dion Moult
2026-05-05 09:40:58 +10:00
parent 278c1e5068
commit 5ccb655e10
13 changed files with 920 additions and 384 deletions
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_PANELS_PROPERTIESPANELTYPES_H
#define IFCINTERFACE_PANELS_PROPERTIESPANELTYPES_H
#include <QList>
#include <QPair>
#include <QString>
namespace ifcinterface::panels::properties {
struct KeyValueRow {
QString key;
QString value;
};
struct RelationshipRow {
QString key;
QString value;
};
struct PropertySet {
QString title;
QList<KeyValueRow> rows;
};
struct EntitySummary {
QString entity_class;
QString predefined_type;
};
struct PropertiesPanelState {
EntitySummary entity;
QList<KeyValueRow> attributes;
QList<RelationshipRow> relationships;
QList<PropertySet> property_sets;
QList<PropertySet> quantity_sets;
};
} // namespace ifcinterface::panels::properties
#endif
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#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
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_PANELS_PROPERTIESPANELVIEW_H
#define IFCINTERFACE_PANELS_PROPERTIESPANELVIEW_H
#include "PropertiesPanelTypes.h"
#include <QObject>
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
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "PropertiesPanelWidget.h"
#include <QFile>
#include <QFormLayout>
#include <QFrame>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPainter>
#include <QPixmap>
#include <QRegularExpression>
#include <QScrollArea>
#include <QSvgRenderer>
#include <QToolButton>
#include <QVBoxLayout>
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<QWidget*>& 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<QLineEdit*>(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<ifcinterface::panels::properties::KeyValueRow>& 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<ifcinterface::panels::properties::RelationshipRow>& 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<QVBoxLayout*>(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<QWidget*> property_set_widgets;
for (const auto& property_set : state.property_sets) {
property_set_widgets.append(makePropertySetPanel(property_set, content));
}
QList<QWidget*> 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
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_PANELS_PROPERTIESPANELWIDGET_H
#define IFCINTERFACE_PANELS_PROPERTIESPANELWIDGET_H
#include "PropertiesPanelTypes.h"
#include <QWidget>
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
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELTYPES_H
#define IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELTYPES_H
#include <QList>
#include <QString>
#include <QStringList>
namespace ifcinterface::panels::spatial_hierarchy {
enum class ItemKind {
Site,
Building,
Storey,
Space,
};
struct TreeNode {
QString name;
ItemKind kind = ItemKind::Space;
bool visible = true;
QList<TreeNode> children;
};
using NodePath = QStringList;
} // namespace ifcinterface::panels::spatial_hierarchy
#endif
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "SpatialHierarchyPanelView.h"
#include "SpatialHierarchyPanelWidget.h"
namespace ifcinterface::panels::spatial_hierarchy {
namespace {
TreeNode* findNodeRecursive(QList<TreeNode>& 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
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELVIEW_H
#define IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELVIEW_H
#include "SpatialHierarchyPanelTypes.h"
#include <QObject>
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<TreeNode> nodes_;
};
} // namespace ifcinterface::panels::spatial_hierarchy
#endif
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "SpatialHierarchyPanelWidget.h"
#include <QFile>
#include <QHeaderView>
#include <QPainter>
#include <QPixmap>
#include <QRegularExpression>
#include <QSvgRenderer>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>
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<TreeNode>& 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
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELWIDGET_H
#define IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELWIDGET_H
#include "SpatialHierarchyPanelTypes.h"
#include <QWidget>
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<TreeNode>& 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