From 278c1e506891525d7bf3866ffa2f80052283b91e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 5 May 2026 07:29:32 +1000 Subject: [PATCH] Interface mockup --- src/interface/CMakeLists.txt | 50 + src/interface/MockMainWindow.cpp | 1292 ++++++++++++++++++ src/interface/MockMainWindow.h | 88 ++ src/interface/icons/box-3d-center.svg | 8 + src/interface/icons/box-3d-three-points.svg | 8 + src/interface/icons/building.svg | 9 + src/interface/icons/cellar.svg | 6 + src/interface/icons/city.svg | 9 + src/interface/icons/clock-rotate-right.svg | 5 + src/interface/icons/cloud-square.svg | 4 + src/interface/icons/cube-dots.svg | 8 + src/interface/icons/cube-scan-solid.svg | 7 + src/interface/icons/cube-scan.svg | 8 + src/interface/icons/cube.svg | 5 + src/interface/icons/cursor-pointer.svg | 3 + src/interface/icons/download-square.svg | 5 + src/interface/icons/drone.svg | 11 + src/interface/icons/eye-closed.svg | 6 + src/interface/icons/eye-solid.svg | 4 + src/interface/icons/eye.svg | 4 + src/interface/icons/face-3d-draft.svg | 7 + src/interface/icons/filter.svg | 3 + src/interface/icons/floppy-disk-arrow-in.svg | 8 + src/interface/icons/floppy-disk.svg | 5 + src/interface/icons/folder-minus.svg | 5 + src/interface/icons/folder-plus.svg | 5 + src/interface/icons/folder.svg | 3 + src/interface/icons/frame-alt.svg | 7 + src/interface/icons/home-alt.svg | 5 + src/interface/icons/home.svg | 4 + src/interface/icons/intersect.svg | 12 + src/interface/icons/minus-square.svg | 4 + src/interface/icons/perspective-view.svg | 9 + src/interface/icons/planimetry.svg | 10 + src/interface/icons/plus-square.svg | 4 + src/interface/icons/refresh-double.svg | 6 + src/interface/icons/rotate-camera-right.svg | 6 + src/interface/icons/select-edge3d.svg | 1 + src/interface/icons/select-face3d.svg | 1 + src/interface/icons/select-point3d.svg | 1 + src/interface/icons/settings.svg | 4 + src/interface/icons/square3d-from-center.svg | 4 + src/interface/interface_resources.qrc | 59 + src/interface/main.cpp | 77 ++ 44 files changed, 1790 insertions(+) create mode 100644 src/interface/CMakeLists.txt create mode 100644 src/interface/MockMainWindow.cpp create mode 100644 src/interface/MockMainWindow.h create mode 100644 src/interface/icons/box-3d-center.svg create mode 100644 src/interface/icons/box-3d-three-points.svg create mode 100644 src/interface/icons/building.svg create mode 100644 src/interface/icons/cellar.svg create mode 100644 src/interface/icons/city.svg create mode 100644 src/interface/icons/clock-rotate-right.svg create mode 100644 src/interface/icons/cloud-square.svg create mode 100644 src/interface/icons/cube-dots.svg create mode 100644 src/interface/icons/cube-scan-solid.svg create mode 100644 src/interface/icons/cube-scan.svg create mode 100644 src/interface/icons/cube.svg create mode 100644 src/interface/icons/cursor-pointer.svg create mode 100644 src/interface/icons/download-square.svg create mode 100644 src/interface/icons/drone.svg create mode 100644 src/interface/icons/eye-closed.svg create mode 100644 src/interface/icons/eye-solid.svg create mode 100644 src/interface/icons/eye.svg create mode 100644 src/interface/icons/face-3d-draft.svg create mode 100644 src/interface/icons/filter.svg create mode 100644 src/interface/icons/floppy-disk-arrow-in.svg create mode 100644 src/interface/icons/floppy-disk.svg create mode 100644 src/interface/icons/folder-minus.svg create mode 100644 src/interface/icons/folder-plus.svg create mode 100644 src/interface/icons/folder.svg create mode 100644 src/interface/icons/frame-alt.svg create mode 100644 src/interface/icons/home-alt.svg create mode 100644 src/interface/icons/home.svg create mode 100644 src/interface/icons/intersect.svg create mode 100644 src/interface/icons/minus-square.svg create mode 100644 src/interface/icons/perspective-view.svg create mode 100644 src/interface/icons/planimetry.svg create mode 100644 src/interface/icons/plus-square.svg create mode 100644 src/interface/icons/refresh-double.svg create mode 100644 src/interface/icons/rotate-camera-right.svg create mode 100644 src/interface/icons/select-edge3d.svg create mode 100644 src/interface/icons/select-face3d.svg create mode 100644 src/interface/icons/select-point3d.svg create mode 100644 src/interface/icons/settings.svg create mode 100644 src/interface/icons/square3d-from-center.svg create mode 100644 src/interface/interface_resources.qrc create mode 100644 src/interface/main.cpp diff --git a/src/interface/CMakeLists.txt b/src/interface/CMakeLists.txt new file mode 100644 index 0000000000..5e43a3171b --- /dev/null +++ b/src/interface/CMakeLists.txt @@ -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 . # +# # +################################################################################ + +message("Running CMakeLists.txt in /src/interface") + +set(QT_VERSION 6 CACHE STRING "Qt version") +find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets Svg REQUIRED PATHS ${QT_DIR}) + +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}/interface_resources.qrc +) + +add_executable(IfcInterfaceMockup ${INTERFACE_FILES}) + +set_target_properties(IfcInterfaceMockup PROPERTIES + AUTOMOC ON + AUTORCC ON + WIN32_EXECUTABLE ON + MACOSX_BUNDLE ON +) + +target_link_libraries(IfcInterfaceMockup PRIVATE + IfcViewer + Qt${QT_VERSION}::Core + Qt${QT_VERSION}::Gui + Qt${QT_VERSION}::Svg + Qt${QT_VERSION}::Widgets +) + +install(TARGETS IfcInterfaceMockup EXPORT ${IFCOPENSHELL_EXPORT_TARGETS}) diff --git a/src/interface/MockMainWindow.cpp b/src/interface/MockMainWindow.cpp new file mode 100644 index 0000000000..85cefdd4ed --- /dev/null +++ b/src/interface/MockMainWindow.cpp @@ -0,0 +1,1292 @@ +// 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 "MockMainWindow.h" + +#include "AppSettings.h" +#include "SceneLoader.h" +#include "ViewportWindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +QPixmap renderTintedSvgPixmap(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()); + QString tinted = svg; + tinted.replace("currentColor", color, Qt::CaseSensitive); + tinted.replace(QRegularExpression(R"(stroke="[^"]*")"), QString("stroke=\"%1\"").arg(color)); + tinted.replace(QRegularExpression(R"(fill="none")"), "fill=\"none\""); + QByteArray data = tinted.toUtf8(); + QSvgRenderer renderer(data); + QPixmap pixmap(size); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + renderer.render(&painter); + return pixmap; +} + +QIcon makeTintedSvgIcon(const QString& icon_path, const QString& normal = "#39b54a", + const QString& active = "#53c763", const QString& disabled = "#6f7988") { + QFile file(icon_path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return QIcon(icon_path); + } + + QIcon icon; + icon.addPixmap(renderTintedSvgPixmap(icon_path, normal, QSize(20, 20)), QIcon::Normal, QIcon::Off); + icon.addPixmap(renderTintedSvgPixmap(icon_path, active, QSize(20, 20)), QIcon::Active, QIcon::Off); + icon.addPixmap(renderTintedSvgPixmap(icon_path, active, QSize(20, 20)), QIcon::Selected, QIcon::Off); + icon.addPixmap(renderTintedSvgPixmap(icon_path, disabled, QSize(20, 20)), QIcon::Disabled, QIcon::Off); + return icon; +} + +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) + : QWidget(parent) + { + auto* layout = new QHBoxLayout(this); + layout->setContentsMargins(10, 6, 6, 6); + layout->setSpacing(6); + + auto* text = new QLabel(title.toUpper(), this); + text->setObjectName("dockTitleText"); + + layout->addWidget(text); + layout->addStretch(1); + if (has_settings) { + auto* settings = new QToolButton(this); + settings->setIcon(makePanelSvgIcon(":/icons/settings.svg")); + settings->setAutoRaise(true); + settings->setCursor(Qt::ArrowCursor); + settings->setFixedSize(18, 18); + settings->setObjectName("dockTitleButton"); + settings->setToolTip(QString("%1 settings").arg(title)); + connect(settings, &QToolButton::clicked, this, [this, title]() { + auto* anchor = parentWidget(); + QMenu menu(anchor); + menu.addAction(QString("%1 settings coming soon").arg(title)); + menu.exec(QCursor::pos()); + }); + layout->addWidget(settings); + } + } +}; + +QDockWidget* makeDock(const QString& title, QWidget* content, QWidget* parent, bool has_settings = false) { + 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) { + auto* outer = new QFrame(); + auto* outer_layout = new QVBoxLayout(outer); + outer_layout->setContentsMargins(6, 6, 6, 6); + outer_layout->setSpacing(0); + + auto* frame = new QFrame(outer); + frame->setObjectName("panelFrame"); + auto* layout = new QVBoxLayout(frame); + layout->setContentsMargins(8, 8, 8, 8); + layout->setSpacing(0); + layout->addWidget(inner); + + outer_layout->addWidget(frame); + return outer; +} + +QFrame* wrapInspectorPanel(QWidget* inner) { + auto* outer = new QFrame(); + auto* outer_layout = new QVBoxLayout(outer); + outer_layout->setContentsMargins(6, 6, 6, 6); + 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); + + outer_layout->addWidget(frame); + 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) + : QMainWindow(parent) +{ + setupChrome(); + setupViewport(); + setupDocks(); + setupStatus(); + setupLoader(); + setupRibbon(); + resize(1720, 980); +} + +void MockMainWindow::setupChrome() { + setWindowTitle("IfcOpenShell Interface"); + 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; + } + 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#inspectorSection { + background: transparent; + } + QWidget#inspectorFilterWrapper { + background: transparent; + } + QWidget#relationshipRow { + background: transparent; + } + QLabel#relationshipIconLabel { + background: transparent; + } + QWidget#inspectorPanel { + background: #2b2f36; + } + QToolButton#inspectorSectionButton { + background: transparent; + border: none; + color: #e1e7f0; + font-weight: 700; + text-align: left; + padding: 2px; + margin: 0; + } + QToolButton#inspectorSectionButton:hover { + color: #ffffff; + } + QToolButton#inspectorSectionButton::menu-indicator { + image: none; + width: 0; + } + QFrame#inspectorSectionHeader { + background: #26292f; + } + QToolButton#inspectorFilterToggle { + background: transparent; + border: none; + padding: 2px; + } + QToolButton#inspectorFilterToggle:hover { + background: #353a42; + } + QWidget#inspectorSectionBody { + background: transparent; + } + QLabel#propertyKeyLabel { + color: #9aa4b3; + background: transparent; + } + QLabel#propertyValueLabel { + color: #dce2eb; + background: transparent; + } + QLabel#relationshipValueLabel { + color: #dce2eb; + background: transparent; + } + )"); +} + +QToolButton* MockMainWindow::makeRibbonAction(const QString& text, const QString& icon_path) { + auto* button = new QToolButton(this); + button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + button->setIcon(icon_path.endsWith(".svg") + ? makeTintedSvgIcon(icon_path) + : QIcon(icon_path)); + button->setIconSize(QSize(20, 20)); + button->setText(text); + button->setMinimumSize(QSize(68, 54)); + button->setObjectName("ribbonButton"); + button->setAutoRaise(false); + return button; +} + +QWidget* MockMainWindow::makeRibbonGroup(const QString& title, const QList& buttons) { + auto* group = new QFrame(this); + group->setObjectName("ribbonGroup"); + auto* group_layout = new QVBoxLayout(group); + group_layout->setContentsMargins(8, 6, 8, 4); + group_layout->setSpacing(4); + auto* button_row = new QHBoxLayout(); + button_row->setContentsMargins(0, 0, 0, 0); + button_row->setSpacing(4); + for (auto* button : buttons) { + button_row->addWidget(button); + } + auto* label = new QLabel(title, group); + label->setObjectName("ribbonGroupLabel"); + label->setAlignment(Qt::AlignCenter); + group_layout->addLayout(button_row); + group_layout->addWidget(label); + return group; +} + +QWidget* MockMainWindow::makeComingSoonPanel(const QString& title) { + auto* widget = new QWidget(this); + auto* layout = new QVBoxLayout(widget); + layout->setContentsMargins(12, 12, 12, 12); + auto* heading = new QLabel(title, widget); + heading->setStyleSheet("font-size:14px; font-weight:600; color:#e1e6ee;"); + auto* body = new QLabel("Coming soon", widget); + body->setAlignment(Qt::AlignCenter); + body->setStyleSheet("color:#8f98a6;"); + layout->addWidget(heading); + layout->addStretch(1); + layout->addWidget(body); + layout->addStretch(1); + return widget; +} + +void MockMainWindow::setStatusMessage(const QString& mode, const QString& detail) { + status_mode_label_->setText(mode); + status_selection_label_->setText(detail); +} + +QToolButton* MockMainWindow::makePanelToggle(const QString& text, QDockWidget* dock) { + auto* button = makeRibbonAction(text, ":/icons/dm_toggle_openings.png"); + button->setCheckable(true); + button->setChecked(dock->isVisible()); + connect(button, &QToolButton::toggled, dock, [dock](bool checked) { + dock->setVisible(checked); + if (checked) dock->raise(); + }); + connect(dock, &QDockWidget::visibilityChanged, button, [button](bool visible) { + const QSignalBlocker blocker(button); + button->setChecked(visible); + }); + return button; +} + +QWidget* MockMainWindow::buildHomeRibbonPage() { + auto* page = new QFrame(this); + page->setObjectName("ribbonPage"); + auto* row = new QHBoxLayout(page); + row->setContentsMargins(6, 4, 6, 4); + row->setSpacing(0); + + auto* new_project = makeRibbonAction("New Project", ":/icons/plus-square.svg"); + connect(new_project, &QToolButton::clicked, this, [this]() { + setStatusMessage("Project", "New Project coming soon"); + }); + auto* open_project = makeRibbonAction("Open Project", ":/icons/download-square.svg"); + connect(open_project, &QToolButton::clicked, this, [this]() { + setStatusMessage("Project", "Open Project coming soon"); + }); + auto* open_cloud = makeRibbonAction("Open Cloud", ":/icons/cloud-square.svg"); + connect(open_cloud, &QToolButton::clicked, this, [this]() { + setStatusMessage("Project", "Open Cloud Project coming soon"); + }); + auto* open_recent = makeRibbonAction("Open Recent", ":/icons/clock-rotate-right.svg"); + connect(open_recent, &QToolButton::clicked, this, [this]() { + setStatusMessage("Project", "Open Recent coming soon"); + }); + auto* save_project = makeRibbonAction("Save Project", ":/icons/floppy-disk.svg"); + connect(save_project, &QToolButton::clicked, this, [this]() { + setStatusMessage("Project", "Save Project coming soon"); + }); + auto* save_project_as = makeRibbonAction("Save As", ":/icons/floppy-disk-arrow-in.svg"); + connect(save_project_as, &QToolButton::clicked, this, [this]() { + setStatusMessage("Project", "Save Project As coming soon"); + }); + + auto* add_model = makeRibbonAction("Add Model", ":/icons/cube.svg"); + connect(add_model, &QToolButton::clicked, this, &MockMainWindow::onAddFiles); + auto* sync_models = makeRibbonAction("Sync Models", ":/icons/refresh-double.svg"); + connect(sync_models, &QToolButton::clicked, this, [this]() { + setStatusMessage("Models", "Sync models coming soon"); + }); + + auto* settings_button = makeRibbonAction("Settings", ":/icons/settings.svg"); + connect(settings_button, &QToolButton::clicked, this, [this]() { + setStatusMessage("Settings", "Settings coming soon"); + }); + + row->addWidget(makeRibbonGroup("PROJECT", {new_project, open_project, open_cloud, open_recent, save_project, save_project_as})); + row->addWidget(makeRibbonGroup("MODELS", {add_model, sync_models})); + row->addWidget(makeRibbonGroup("SETTINGS", {settings_button})); + row->addStretch(1); + return page; +} + +QWidget* MockMainWindow::buildNavigateRibbonPage() { + auto* page = new QFrame(this); + page->setObjectName("ribbonPage"); + auto* row = new QHBoxLayout(page); + row->setContentsMargins(2, 4, 2, 4); + row->setSpacing(0); + + auto* set_home = makeRibbonAction("Set Home", ":/icons/home.svg"); + connect(set_home, &QToolButton::clicked, this, [this]() { + setStatusMessage("Camera", "Set home view coming soon"); + }); + auto* go_home = makeRibbonAction("Go Home", ":/icons/home-alt.svg"); + connect(go_home, &QToolButton::clicked, this, [this]() { + setStatusMessage("Camera", "Go to home view coming soon"); + }); + auto* view_all = makeRibbonAction("View All", ":/icons/cube-scan.svg"); + connect(view_all, &QToolButton::clicked, this, [this]() { + if (viewport_) viewport_->viewAll(); + }); + auto* view_selected = makeRibbonAction("View Selected", ":/icons/cube-scan-solid.svg"); + connect(view_selected, &QToolButton::clicked, this, [this]() { + if (viewport_) viewport_->focusOnSelectedObject(); + }); + + auto* plan_view = makeRibbonAction("Plan", ":/icons/planimetry.svg"); + connect(plan_view, &QToolButton::clicked, this, [this]() { + if (viewport_) viewport_->setStandardView(90.0f, 90.0f); + }); + auto* front_view = makeRibbonAction("Front", ":/icons/city.svg"); + connect(front_view, &QToolButton::clicked, this, [this]() { + if (viewport_) viewport_->setStandardView(0.0f, 0.0f); + }); + auto* side_view = makeRibbonAction("Side", ":/icons/building.svg"); + connect(side_view, &QToolButton::clicked, this, [this]() { + if (viewport_) viewport_->setStandardView(90.0f, 0.0f); + }); + auto* align_object = makeRibbonAction("Align Object", ":/icons/cellar.svg"); + connect(align_object, &QToolButton::clicked, this, [this]() { + setStatusMessage("Orientation", "Align to object coming soon"); + }); + auto* projection_button = makeRibbonAction("Perspective", ":/icons/perspective-view.svg"); + connect(projection_button, &QToolButton::clicked, this, [this, projection_button]() { + if (!viewport_) return; + viewport_->toggleProjection(); + projection_button->setText(viewport_->projectionOrtho() ? "Ortho" : "Perspective"); + }); + + auto* orbit_mode = makeRibbonAction("Orbit", ":/icons/rotate-camera-right.svg"); + connect(orbit_mode, &QToolButton::clicked, this, [this]() { + setStatusMessage("Mode", "Orbit mode active"); + }); + auto* fly_mode = makeRibbonAction("Fly", ":/icons/drone.svg"); + connect(fly_mode, &QToolButton::clicked, this, [this]() { + setStatusMessage("Mode", "Fly mode coming soon"); + }); + + row->addWidget(makeRibbonGroup("CAMERA", {set_home, go_home, view_all, view_selected})); + row->addWidget(makeRibbonGroup("ORIENTATION", {plan_view, front_view, side_view, align_object, projection_button})); + row->addWidget(makeRibbonGroup("MODE", {orbit_mode, fly_mode})); + row->addStretch(1); + return page; +} + +QWidget* MockMainWindow::buildInspectRibbonPage() { + auto* page = new QFrame(this); + page->setObjectName("ribbonPage"); + auto* row = new QHBoxLayout(page); + row->setContentsMargins(2, 4, 2, 4); + row->setSpacing(0); + + auto* hide_selected = makeRibbonAction("Hide", ":/icons/eye-closed.svg"); + connect(hide_selected, &QToolButton::clicked, this, [this]() { + setStatusMessage("Selection", "Hide selected coming soon"); + }); + auto* isolate_selected = makeRibbonAction("Isolate", ":/icons/eye-solid.svg"); + connect(isolate_selected, &QToolButton::clicked, this, [this]() { + setStatusMessage("Selection", "Isolate selected coming soon"); + }); + auto* show_all = makeRibbonAction("Show All", ":/icons/eye.svg"); + connect(show_all, &QToolButton::clicked, this, [this]() { + setStatusMessage("Selection", "Show all coming soon"); + }); + auto* invert_selection = makeRibbonAction("Invert", ":/icons/intersect.svg"); + connect(invert_selection, &QToolButton::clicked, this, [this]() { + setStatusMessage("Selection", "Invert selection coming soon"); + }); + + auto* distance = makeRibbonAction("Distance", ":/icons/select-edge3d.svg"); + connect(distance, &QToolButton::clicked, this, [this]() { + setStatusMessage("Measure", "Distance coming soon"); + }); + auto* area = makeRibbonAction("Area", ":/icons/select-face3d.svg"); + connect(area, &QToolButton::clicked, this, [this]() { + setStatusMessage("Measure", "Area coming soon"); + }); + auto* volume = makeRibbonAction("Volume", ":/icons/select-point3d.svg"); + connect(volume, &QToolButton::clicked, this, [this]() { + setStatusMessage("Measure", "Volume coming soon"); + }); + + row->addWidget(makeRibbonGroup("SELECTION", {hide_selected, isolate_selected, show_all, invert_selection})); + row->addWidget(makeRibbonGroup("MEASURE", {distance, area, volume})); + row->addStretch(1); + return page; +} + +QWidget* MockMainWindow::buildPanelsRibbonPage() { + auto* page = new QFrame(this); + page->setObjectName("ribbonPage"); + auto* row = new QHBoxLayout(page); + row->setContentsMargins(2, 4, 2, 4); + row->setSpacing(0); + + row->addWidget(makeRibbonGroup("DATA", { + makePanelToggle("Models", models_dock_), + makePanelToggle("Spatial", spatial_dock_), + makePanelToggle("Layers", layers_dock_), + makePanelToggle("Properties", properties_dock_) + })); + row->addWidget(makeRibbonGroup("QUERY", { + makePanelToggle("Views", stored_views_dock_), + makePanelToggle("Search", search_dock_), + makePanelToggle("Sheets", spreadsheet_dock_) + })); + row->addWidget(makeRibbonGroup("COLLABORATE", { + makePanelToggle("Clash", clash_dock_), + makePanelToggle("Issues", issues_dock_) + })); + row->addStretch(1); + return page; +} + +void MockMainWindow::setupRibbon() { + auto* shell = new QFrame(this); + shell->setObjectName("ribbonShell"); + + auto* shell_layout = new QVBoxLayout(shell); + shell_layout->setContentsMargins(0, 0, 0, 0); + shell_layout->setSpacing(0); + + ribbon_tabs_ = new QTabBar(shell); + ribbon_tabs_->addTab("Home"); + ribbon_tabs_->addTab("Navigate"); + ribbon_tabs_->addTab("Inspect"); + ribbon_tabs_->addTab("Panels"); + ribbon_tabs_->setCurrentIndex(0); + ribbon_tabs_->setExpanding(false); + ribbon_tabs_->setDrawBase(false); + + auto* ribbon_band = new QFrame(shell); + ribbon_band->setObjectName("ribbonBand"); + auto* band_layout = new QVBoxLayout(ribbon_band); + band_layout->setContentsMargins(0, 0, 0, 0); + band_layout->setSpacing(0); + + ribbon_pages_ = new QStackedWidget(ribbon_band); + ribbon_pages_->addWidget(buildHomeRibbonPage()); + ribbon_pages_->addWidget(buildNavigateRibbonPage()); + ribbon_pages_->addWidget(buildInspectRibbonPage()); + ribbon_pages_->addWidget(buildPanelsRibbonPage()); + + band_layout->addWidget(ribbon_pages_); + shell_layout->addWidget(ribbon_tabs_); + shell_layout->addWidget(ribbon_band); + + connect(ribbon_tabs_, &QTabBar::currentChanged, + ribbon_pages_, &QStackedWidget::setCurrentIndex); + + setMenuWidget(shell); +} + +void MockMainWindow::setupViewport() { + viewport_ = new ViewportWindow(); + viewport_container_ = QWidget::createWindowContainer(viewport_, this); + viewport_container_->setMinimumSize(400, 300); + viewport_container_->setFocusPolicy(Qt::StrongFocus); + + auto* shell = new QFrame(this); + shell->setObjectName("viewportShell"); + auto* root = new QVBoxLayout(shell); + root->setContentsMargins(10, 10, 10, 10); + root->setSpacing(0); + + auto* frame = new QFrame(shell); + frame->setObjectName("viewportFrame"); + auto* frame_layout = new QVBoxLayout(frame); + frame_layout->setContentsMargins(0, 0, 0, 0); + frame_layout->addWidget(viewport_container_); + + root->addWidget(frame); + setCentralWidget(shell); +} + +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_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(); + + 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); + + 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); + 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); + spreadsheet_dock_ = makeDock("Spreadsheet", wrapPanel(makeComingSoonPanel("Spreadsheet")), this); + clash_dock_ = makeDock("Clash", wrapPanel(makeComingSoonPanel("Clash")), this); + issues_dock_ = makeDock("Issues", wrapPanel(makeComingSoonPanel("Issues")), this); + + addDockWidget(Qt::LeftDockWidgetArea, models_dock_); + addDockWidget(Qt::LeftDockWidgetArea, spatial_dock_); + splitDockWidget(models_dock_, spatial_dock_, 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_); + + 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(); + + layers_dock_->hide(); + stored_views_dock_->hide(); + search_dock_->hide(); + spreadsheet_dock_->hide(); + clash_dock_->hide(); + issues_dock_->hide(); + + resizeDocks({models_dock_, properties_dock_}, {290, 330}, Qt::Horizontal); + resizeDocks({models_dock_, spatial_dock_}, {280, 240}, Qt::Vertical); +} + +void MockMainWindow::setupStatus() { + status_mode_label_ = new QLabel("Ready", this); + status_selection_label_ = new QLabel("No selection", this); + status_perf_label_ = new QLabel(this); + status_perf_label_->setVisible(AppSettings::instance().showStats()); + + statusBar()->setSizeGripEnabled(false); + statusBar()->addWidget(status_mode_label_); + statusBar()->addWidget(status_selection_label_, 1); + statusBar()->addPermanentWidget(status_perf_label_); + + connect(&AppSettings::instance(), &AppSettings::showStatsChanged, this, [this](bool show) { + status_perf_label_->setVisible(show); + if (!show) status_perf_label_->clear(); + }); +} + +void MockMainWindow::setupLoader() { + AppSettings::instance().setLoadDataSource(false); + loader_ = new SceneLoader(viewport_, this); + connect(loader_, &SceneLoader::loadStarted, this, &MockMainWindow::onLoadStarted); + connect(loader_, &SceneLoader::loadedFromSidecar, this, &MockMainWindow::onLoadedFromSidecar); + connect(loader_, &SceneLoader::loadedFromStream, this, &MockMainWindow::onLoadedFromStream); + connect(loader_, &SceneLoader::loadCancelled, this, &MockMainWindow::onLoadCancelled); + connect(loader_, &SceneLoader::loadError, this, &MockMainWindow::onLoadError); + connect(loader_, &SceneLoader::allLoadsFinished, this, &MockMainWindow::onAllLoadsFinished); + + connect(viewport_, &ViewportWindow::frameStatsUpdated, this, + [this](const ViewportWindow::FrameStats& s) { + if (!status_perf_label_->isVisible()) return; + status_perf_label_->setText( + QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 draws") + .arg(s.fps, 0, 'f', 1) + .arg(s.frame_time_ms, 0, 'f', 1) + .arg(s.visible_objects) + .arg(s.total_objects) + .arg(s.visible_triangles) + .arg(s.total_triangles) + .arg(s.gl_draw_calls)); + }); +} + +void MockMainWindow::addFiles(const QStringList& paths) { + if (paths.isEmpty()) return; + loader_->addFiles(paths); +} + +QString MockMainWindow::formatElapsed(qint64 ms) const { + return (ms >= 1000) + ? QString::number(ms / 1000.0, 'f', 2) + " s" + : QString::number(ms) + " ms"; +} + +void MockMainWindow::onAddFiles() { + const QStringList paths = QFileDialog::getOpenFileNames( + this, "Add IFC Files", QString(), + "IFC Viewer Cache (*.ifcview)"); + addFiles(paths); +} + +void MockMainWindow::onLoadStarted(uint32_t /*mid*/, QString display_name) { + status_mode_label_->setText("Loading"); + status_selection_label_->setText(display_name); +} + +void MockMainWindow::onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms) { + status_mode_label_->setText("Loaded"); + status_selection_label_->setText( + QString("%1 from cache in %2") + .arg(loader_->displayName(mid)) + .arg(formatElapsed(elapsed_ms))); +} + +void MockMainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) { + status_mode_label_->setText("Loaded"); + status_selection_label_->setText( + QString("%1 streamed in %2") + .arg(loader_->displayName(mid)) + .arg(formatElapsed(elapsed_ms))); +} + +void MockMainWindow::onLoadCancelled(uint32_t mid) { + status_mode_label_->setText("Cancelled"); + status_selection_label_->setText(loader_->displayName(mid)); +} + +void MockMainWindow::onLoadError(uint32_t /*mid*/, QString message) { + status_mode_label_->setText("Error"); + status_selection_label_->setText(message); + QMessageBox::warning(this, "IfcInterfaceMockup", message); +} + +void MockMainWindow::onAllLoadsFinished() { + status_mode_label_->setText("Loaded"); + status_selection_label_->setText(QString("%1 model(s)").arg(loader_->modelCount())); +} diff --git a/src/interface/MockMainWindow.h b/src/interface/MockMainWindow.h new file mode 100644 index 0000000000..81da53db90 --- /dev/null +++ b/src/interface/MockMainWindow.h @@ -0,0 +1,88 @@ +// 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_MOCKMAINWINDOW_H +#define IFCINTERFACE_MOCKMAINWINDOW_H + +#include +#include + +class QLabel; +class QDockWidget; +class QStackedWidget; +class QTabBar; +class QToolButton; +class ViewportWindow; +class SceneLoader; + +class MockMainWindow : public QMainWindow { + Q_OBJECT +public: + explicit MockMainWindow(QWidget* parent = nullptr); + +private: + void setupChrome(); + void setupRibbon(); + void setupViewport(); + void setupDocks(); + void setupStatus(); + void setupLoader(); + QWidget* buildHomeRibbonPage(); + QWidget* buildNavigateRibbonPage(); + QWidget* buildInspectRibbonPage(); + 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); + QString formatElapsed(qint64 ms) const; + +private slots: + void onAddFiles(); + void onLoadStarted(uint32_t mid, QString display_name); + void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms); + void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms); + void onLoadCancelled(uint32_t mid); + void onLoadError(uint32_t mid, QString message); + void onAllLoadsFinished(); + +private: + QLabel* status_mode_label_ = nullptr; + QLabel* status_selection_label_ = nullptr; + QLabel* status_perf_label_ = nullptr; + QTabBar* ribbon_tabs_ = nullptr; + QStackedWidget* ribbon_pages_ = nullptr; + ViewportWindow* viewport_ = nullptr; + SceneLoader* loader_ = 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; +}; + +#endif diff --git a/src/interface/icons/box-3d-center.svg b/src/interface/icons/box-3d-center.svg new file mode 100644 index 0000000000..2821094abd --- /dev/null +++ b/src/interface/icons/box-3d-center.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/interface/icons/box-3d-three-points.svg b/src/interface/icons/box-3d-three-points.svg new file mode 100644 index 0000000000..a925b676b1 --- /dev/null +++ b/src/interface/icons/box-3d-three-points.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/interface/icons/building.svg b/src/interface/icons/building.svg new file mode 100644 index 0000000000..3c1cbcd15a --- /dev/null +++ b/src/interface/icons/building.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/interface/icons/cellar.svg b/src/interface/icons/cellar.svg new file mode 100644 index 0000000000..a7548be21d --- /dev/null +++ b/src/interface/icons/cellar.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/interface/icons/city.svg b/src/interface/icons/city.svg new file mode 100644 index 0000000000..d7ea85ade2 --- /dev/null +++ b/src/interface/icons/city.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/interface/icons/clock-rotate-right.svg b/src/interface/icons/clock-rotate-right.svg new file mode 100644 index 0000000000..38216101ec --- /dev/null +++ b/src/interface/icons/clock-rotate-right.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/interface/icons/cloud-square.svg b/src/interface/icons/cloud-square.svg new file mode 100644 index 0000000000..03be85183d --- /dev/null +++ b/src/interface/icons/cloud-square.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/interface/icons/cube-dots.svg b/src/interface/icons/cube-dots.svg new file mode 100644 index 0000000000..f42a21121a --- /dev/null +++ b/src/interface/icons/cube-dots.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/interface/icons/cube-scan-solid.svg b/src/interface/icons/cube-scan-solid.svg new file mode 100644 index 0000000000..ba150bae1a --- /dev/null +++ b/src/interface/icons/cube-scan-solid.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/interface/icons/cube-scan.svg b/src/interface/icons/cube-scan.svg new file mode 100644 index 0000000000..286cb4d9b4 --- /dev/null +++ b/src/interface/icons/cube-scan.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/interface/icons/cube.svg b/src/interface/icons/cube.svg new file mode 100644 index 0000000000..85290d7c18 --- /dev/null +++ b/src/interface/icons/cube.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/interface/icons/cursor-pointer.svg b/src/interface/icons/cursor-pointer.svg new file mode 100644 index 0000000000..c05a6ab739 --- /dev/null +++ b/src/interface/icons/cursor-pointer.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/interface/icons/download-square.svg b/src/interface/icons/download-square.svg new file mode 100644 index 0000000000..9fa31e236f --- /dev/null +++ b/src/interface/icons/download-square.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/interface/icons/drone.svg b/src/interface/icons/drone.svg new file mode 100644 index 0000000000..278a3a9d35 --- /dev/null +++ b/src/interface/icons/drone.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/interface/icons/eye-closed.svg b/src/interface/icons/eye-closed.svg new file mode 100644 index 0000000000..1854ee3f67 --- /dev/null +++ b/src/interface/icons/eye-closed.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/interface/icons/eye-solid.svg b/src/interface/icons/eye-solid.svg new file mode 100644 index 0000000000..030e83b5ac --- /dev/null +++ b/src/interface/icons/eye-solid.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/interface/icons/eye.svg b/src/interface/icons/eye.svg new file mode 100644 index 0000000000..9db8457784 --- /dev/null +++ b/src/interface/icons/eye.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/interface/icons/face-3d-draft.svg b/src/interface/icons/face-3d-draft.svg new file mode 100644 index 0000000000..f1f458a5e2 --- /dev/null +++ b/src/interface/icons/face-3d-draft.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/interface/icons/filter.svg b/src/interface/icons/filter.svg new file mode 100644 index 0000000000..7f01001139 --- /dev/null +++ b/src/interface/icons/filter.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/interface/icons/floppy-disk-arrow-in.svg b/src/interface/icons/floppy-disk-arrow-in.svg new file mode 100644 index 0000000000..49811f93a0 --- /dev/null +++ b/src/interface/icons/floppy-disk-arrow-in.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/interface/icons/floppy-disk.svg b/src/interface/icons/floppy-disk.svg new file mode 100644 index 0000000000..bb7828aab1 --- /dev/null +++ b/src/interface/icons/floppy-disk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/interface/icons/folder-minus.svg b/src/interface/icons/folder-minus.svg new file mode 100644 index 0000000000..4173fc6dba --- /dev/null +++ b/src/interface/icons/folder-minus.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/interface/icons/folder-plus.svg b/src/interface/icons/folder-plus.svg new file mode 100644 index 0000000000..b39508e411 --- /dev/null +++ b/src/interface/icons/folder-plus.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/interface/icons/folder.svg b/src/interface/icons/folder.svg new file mode 100644 index 0000000000..ccc42cb6f8 --- /dev/null +++ b/src/interface/icons/folder.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/interface/icons/frame-alt.svg b/src/interface/icons/frame-alt.svg new file mode 100644 index 0000000000..45d917240a --- /dev/null +++ b/src/interface/icons/frame-alt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/interface/icons/home-alt.svg b/src/interface/icons/home-alt.svg new file mode 100644 index 0000000000..9278f10f59 --- /dev/null +++ b/src/interface/icons/home-alt.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/interface/icons/home.svg b/src/interface/icons/home.svg new file mode 100644 index 0000000000..d8f1e2262a --- /dev/null +++ b/src/interface/icons/home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/interface/icons/intersect.svg b/src/interface/icons/intersect.svg new file mode 100644 index 0000000000..64cc346750 --- /dev/null +++ b/src/interface/icons/intersect.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/interface/icons/minus-square.svg b/src/interface/icons/minus-square.svg new file mode 100644 index 0000000000..cf268693b8 --- /dev/null +++ b/src/interface/icons/minus-square.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/interface/icons/perspective-view.svg b/src/interface/icons/perspective-view.svg new file mode 100644 index 0000000000..fd980d30ff --- /dev/null +++ b/src/interface/icons/perspective-view.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/interface/icons/planimetry.svg b/src/interface/icons/planimetry.svg new file mode 100644 index 0000000000..dca2cf94c3 --- /dev/null +++ b/src/interface/icons/planimetry.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/interface/icons/plus-square.svg b/src/interface/icons/plus-square.svg new file mode 100644 index 0000000000..d347206808 --- /dev/null +++ b/src/interface/icons/plus-square.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/interface/icons/refresh-double.svg b/src/interface/icons/refresh-double.svg new file mode 100644 index 0000000000..6d70aaef0b --- /dev/null +++ b/src/interface/icons/refresh-double.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/interface/icons/rotate-camera-right.svg b/src/interface/icons/rotate-camera-right.svg new file mode 100644 index 0000000000..fca1c4e747 --- /dev/null +++ b/src/interface/icons/rotate-camera-right.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/interface/icons/select-edge3d.svg b/src/interface/icons/select-edge3d.svg new file mode 100644 index 0000000000..39700d70fe --- /dev/null +++ b/src/interface/icons/select-edge3d.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/interface/icons/select-face3d.svg b/src/interface/icons/select-face3d.svg new file mode 100644 index 0000000000..f7b04f3d6a --- /dev/null +++ b/src/interface/icons/select-face3d.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/interface/icons/select-point3d.svg b/src/interface/icons/select-point3d.svg new file mode 100644 index 0000000000..3a6bbd6fd1 --- /dev/null +++ b/src/interface/icons/select-point3d.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/interface/icons/settings.svg b/src/interface/icons/settings.svg new file mode 100644 index 0000000000..177771faee --- /dev/null +++ b/src/interface/icons/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/interface/icons/square3d-from-center.svg b/src/interface/icons/square3d-from-center.svg new file mode 100644 index 0000000000..531d99bd76 --- /dev/null +++ b/src/interface/icons/square3d-from-center.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/interface/interface_resources.qrc b/src/interface/interface_resources.qrc new file mode 100644 index 0000000000..9be3928f7d --- /dev/null +++ b/src/interface/interface_resources.qrc @@ -0,0 +1,59 @@ + + + + ../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 + icons/clock-rotate-right.svg + icons/floppy-disk.svg + icons/floppy-disk-arrow-in.svg + icons/cube.svg + icons/refresh-double.svg + icons/settings.svg + icons/home.svg + icons/home-alt.svg + icons/cube-scan.svg + icons/cube-scan-solid.svg + icons/planimetry.svg + icons/city.svg + icons/building.svg + icons/cellar.svg + icons/perspective-view.svg + icons/rotate-camera-right.svg + icons/drone.svg + icons/folder-plus.svg + icons/folder-minus.svg + icons/folder.svg + icons/minus-square.svg + icons/eye-closed.svg + icons/eye.svg + icons/eye-solid.svg + icons/intersect.svg + icons/select-edge3d.svg + icons/select-face3d.svg + icons/select-point3d.svg + icons/frame-alt.svg + icons/square3d-from-center.svg + icons/filter.svg + icons/cube-dots.svg + icons/cursor-pointer.svg + + diff --git a/src/interface/main.cpp b/src/interface/main.cpp new file mode 100644 index 0000000000..7a1e1d1970 --- /dev/null +++ b/src/interface/main.cpp @@ -0,0 +1,77 @@ +// 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 "MockMainWindow.h" + +#include +#include +#include +#include +#include + +namespace { + +void installUiFont() { + const QString blender_font = + "/home/dion/drive/blender/blender-5.1.0-linux-x64/5.1/datafiles/fonts/Inter.woff2"; + + int font_id = QFontDatabase::addApplicationFont(blender_font); + if (font_id < 0) { + font_id = QFontDatabase::addApplicationFont( + ":/fonts/DMSans-VariableFont_opsz,wght.ttf"); + } + QString family; + if (font_id >= 0) { + const QStringList families = QFontDatabase::applicationFontFamilies(font_id); + if (!families.isEmpty()) { + family = families.front(); + } + } + if (!family.isEmpty()) { + QApplication::setFont(QFont(family, 10)); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + QApplication app(argc, argv); + app.setApplicationName("IfcInterfaceMockup"); + app.setOrganizationName("IfcOpenShell"); + + QSurfaceFormat fmt; + fmt.setVersion(4, 5); + fmt.setProfile(QSurfaceFormat::CoreProfile); + fmt.setDepthBufferSize(24); + fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer); + fmt.setSamples(4); + QSurfaceFormat::setDefaultFormat(fmt); + + QCommandLineParser parser; + parser.setApplicationDescription("IfcOpenShell interface mockup with live viewport"); + parser.addHelpOption(); + parser.process(app); + + installUiFont(); + + MockMainWindow window; + window.show(); + return app.exec(); +}