mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-12 14:33:28 +00:00
spatial hierarchy: storey elevation + Long Name column + resizable layout
- helpers/placement: port get_storey_elevation (placement Z, falling back to the Elevation attribute), matching ifcopenshell.util.placement. - Add a secondary column: the storey elevation for IfcBuildingStorey, otherwise the LongName when filled. Elevations are right-aligned. - Columns: Name is drag-resizable (interactive) and defaults to 20% of the width, Long Name stretches to fill the rest, and the eye is pinned to the right at a fixed width. Header shown so the divider can be grabbed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QShowEvent>
|
||||
#include <QSizePolicy>
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
@@ -50,21 +51,26 @@ SpatialHierarchyPanel::SpatialHierarchyPanel(QWidget* parent)
|
||||
|
||||
tree_ = new QTreeWidget(section);
|
||||
tree_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
tree_->setColumnCount(2);
|
||||
tree_->setHeaderLabels({"Spatial Item", ""});
|
||||
tree_->setColumnCount(3);
|
||||
tree_->setHeaderLabels({"Name", "Long Name", ""});
|
||||
tree_->setIconSize(QSize(16, 16));
|
||||
tree_->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
tree_->setUniformRowHeights(true);
|
||||
// Name is drag-resizable, Long Name fills the rest, the eye is pinned to
|
||||
// the right at a fixed width. Header stays visible so the Name/Long-Name
|
||||
// divider can be dragged. Initial 20/80 split applied in showEvent once the
|
||||
// real width is known.
|
||||
tree_->header()->setStretchLastSection(false);
|
||||
tree_->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
tree_->header()->setSectionResizeMode(1, QHeaderView::Fixed);
|
||||
tree_->header()->resizeSection(1, 28);
|
||||
tree_->header()->hide();
|
||||
tree_->header()->setSectionsMovable(false);
|
||||
tree_->header()->setSectionResizeMode(0, QHeaderView::Interactive); // name
|
||||
tree_->header()->setSectionResizeMode(1, QHeaderView::Stretch); // LongName / elevation
|
||||
tree_->header()->setSectionResizeMode(2, QHeaderView::Fixed); // visibility
|
||||
tree_->header()->resizeSection(2, 28);
|
||||
section->addBodyWidget(tree_);
|
||||
addBodyWidget(section);
|
||||
|
||||
connect(tree_, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem* item, int column) {
|
||||
if (!item || column != 1) return;
|
||||
if (!item || column != 2) return;
|
||||
emit visibilityToggleRequested(itemPath(item));
|
||||
});
|
||||
|
||||
@@ -83,6 +89,20 @@ SpatialHierarchyPanel::SpatialHierarchyPanel(QWidget* parent)
|
||||
});
|
||||
}
|
||||
|
||||
void SpatialHierarchyPanel::showEvent(QShowEvent* event) {
|
||||
components::Panel::showEvent(event);
|
||||
// Default the Name column to 20% of the width once the panel has a real
|
||||
// layout size; Long Name (stretch) takes the rest. Left interactive after,
|
||||
// so the user's own drag persists.
|
||||
if (!column_widths_initialized_) {
|
||||
const int available = tree_->viewport()->width();
|
||||
if (available > 100) {
|
||||
tree_->header()->resizeSection(0, available / 5);
|
||||
column_widths_initialized_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpatialHierarchyPanel::setNodes(const QList<TreeNode>& nodes) {
|
||||
tree_->clear();
|
||||
for (const auto& node : nodes) {
|
||||
@@ -92,11 +112,15 @@ void SpatialHierarchyPanel::setNodes(const QList<TreeNode>& nodes) {
|
||||
}
|
||||
|
||||
void SpatialHierarchyPanel::addNode(QTreeWidgetItem* parent, const TreeNode& node) {
|
||||
auto* item = new QTreeWidgetItem(parent, {node.name, ""});
|
||||
item->setData(1, Qt::UserRole, node.visible);
|
||||
auto* item = new QTreeWidgetItem(parent, {node.name, node.detail, ""});
|
||||
item->setData(2, Qt::UserRole, node.visible);
|
||||
item->setSizeHint(0, QSize(0, 24));
|
||||
item->setIcon(0, components::icons::makeSvgIcon(iconPath(node.kind)));
|
||||
item->setIcon(1, components::icons::makeSvgIcon(node.visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg"));
|
||||
// Storey elevations read as right-aligned numbers; LongNames stay left.
|
||||
if (node.kind == ItemKind::Storey) {
|
||||
item->setTextAlignment(1, Qt::AlignRight | Qt::AlignVCenter);
|
||||
}
|
||||
item->setIcon(2, components::icons::makeSvgIcon(node.visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg"));
|
||||
for (const auto& child : node.children) {
|
||||
addNode(item, child);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
class QTreeWidget;
|
||||
class QTreeWidgetItem;
|
||||
class QShowEvent;
|
||||
|
||||
namespace bonsaiviewer::modules::spatial_hierarchy {
|
||||
|
||||
@@ -40,12 +41,16 @@ public:
|
||||
signals:
|
||||
void visibilityToggleRequested(const NodePath& path);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
|
||||
private:
|
||||
void addNode(QTreeWidgetItem* parent, const TreeNode& node);
|
||||
NodePath itemPath(QTreeWidgetItem* item) const;
|
||||
QString iconPath(ItemKind kind) const;
|
||||
|
||||
QTreeWidget* tree_ = nullptr;
|
||||
bool column_widths_initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace bonsaiviewer::modules::spatial_hierarchy
|
||||
|
||||
@@ -36,6 +36,7 @@ enum class ItemKind {
|
||||
|
||||
struct TreeNode {
|
||||
QString name;
|
||||
QString detail; // secondary column: LongName, or the elevation for storeys
|
||||
ItemKind kind = ItemKind::Space;
|
||||
bool visible = true;
|
||||
QList<TreeNode> children;
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
#include "../../../ifcparse/file.h"
|
||||
#include "../../../ifcparse/schema.h"
|
||||
|
||||
#include "element.h" // helpers: get_spatial_children, get_string_attribute
|
||||
#include "element.h" // helpers: get_spatial_children, get_string_attribute
|
||||
#include "placement.h" // helpers: get_storey_elevation
|
||||
|
||||
#include <QCollator>
|
||||
|
||||
@@ -79,6 +80,13 @@ TreeNode buildNode(const express::Base& element) {
|
||||
node.name = displayName(element);
|
||||
node.kind = kindOf(element);
|
||||
node.visible = true;
|
||||
// Secondary column: the storey elevation, else the LongName when filled.
|
||||
if (node.kind == ItemKind::Storey) {
|
||||
node.detail = QString::number(get_storey_elevation(element));
|
||||
} else if (auto long_name = get_string_attribute(element, "LongName");
|
||||
long_name && !long_name->empty()) {
|
||||
node.detail = QString::fromStdString(*long_name);
|
||||
}
|
||||
for (const auto& child : get_spatial_children(element)) {
|
||||
node.children.append(buildNode(child));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user