Fri, 01 Jul 2022 16:45:40 +0300
Use different cursors depending on edit mode
#include <QApplication> #include <QCloseEvent> #include <QFileDialog> #include <QMdiSubWindow> #include <QMessageBox> #include <QScrollBar> #include <QStackedWidget> #include <QTranslator> #include <ui_about.h> #include <ui_mainwindow.h> #include "src/gl/partrenderer.h" #include "src/layers/axeslayer.h" #include "src/layers/edittools.h" #include "src/layers/gridlayer.h" #include "src/ldrawalgorithm.h" #include "src/messagelog.h" #include "src/settings.h" #include "src/settingseditor/settingseditor.h" #include "src/ui/circletooloptionswidget.h" #include "src/ui/objecteditor.h" #include "src/version.h" #include "src/widgets/colorselectdialog.h" #include <GL/glew.h> static const QDir LOCALE_DIR {":/locale"}; class ModelSubWindow : public QMdiSubWindow { Q_OBJECT public: const ModelId modelId; explicit ModelSubWindow(ModelId modelId, QWidget* widget = nullptr) : QMdiSubWindow{widget}, modelId{modelId} { } protected: void closeEvent(QCloseEvent* event) override { event->ignore(); } }; class ModelData : public QObject { Q_OBJECT public: ModelData(QObject* parent) : QObject {parent} {} std::unique_ptr<PartRenderer> canvas; std::unique_ptr<QItemSelectionModel> itemSelectionModel; std::unique_ptr<EditTools> tools; std::unique_ptr<AxesLayer> axesLayer; std::unique_ptr<GridLayer> gridLayer; Model* model; }; class Signal final : public QObject { Q_OBJECT public: Signal() : QObject{}{} virtual ~Signal(){} void emit() { Q_EMIT this->triggered(); } Q_SIGNALS: void triggered(); }; #include <main.moc> static void doQtRegistrations() { QCoreApplication::setApplicationName(QStringLiteral(CMAKE_PROJECT_NAME)); QCoreApplication::setOrganizationName("hecknology.net"); QCoreApplication::setOrganizationDomain("hecknology.net"); qRegisterMetaType<Message>(); qRegisterMetaType<Library>(); qRegisterMetaType<QList<Library>>(); qRegisterMetaType<QMdiArea::ViewMode>(); qRegisterMetaType<gl::RenderStyle>(); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) qRegisterMetaTypeStreamOperators<Library>("Library"); qRegisterMetaTypeStreamOperators<Libraries>("Libraries"); qRegisterMetaTypeStreamOperators<gl::RenderStyle>(); qRegisterMetaTypeStreamOperators<QMdiArea::ViewMode>(); #endif } template<typename BaseType, typename MemberType, typename DataType> struct MemberData { std::size_t member; DataType payload; constexpr MemberType memberInstance(BaseType* instance) const { return *reinterpret_cast<MemberType*>(reinterpret_cast<char*>(instance) + this->member); } }; static constexpr MemberData<Ui_MainWindow, QAction*, gl::RenderStyle> renderStyleButtons[] = { { offsetof(Ui_MainWindow, actionRenderStyleNormal), gl::RenderStyle::Normal }, { offsetof(Ui_MainWindow, actionRenderStyleBfc), gl::RenderStyle::BfcRedGreen }, { offsetof(Ui_MainWindow, actionRenderStyleRandom), gl::RenderStyle::RandomColors }, { offsetof(Ui_MainWindow, actionRenderStylePickScene), gl::RenderStyle::PickScene }, }; static std::optional<ModelId> openModelFromPath( const QString& path, const LibrariesModel* libraries, DocumentManager* documents, QWidget* parent) { QString errorString; QTextStream errorStream{&errorString}; const std::optional<ModelId> modelIdOpt = documents->openModel( path, errorStream, OpenType::ManuallyOpened); if (modelIdOpt.has_value()) { const DocumentManager::MissingDependencies missing = documents->loadDependenciesForAllModels(*libraries); if (not missing.empty()) { QMessageBox::warning( parent, QObject::tr("Problem loading references"), errorStringFromMissingDependencies(missing)); } } else { QMessageBox::critical( parent, QObject::tr("Problem opening file"), QObject::tr("Could not open %1: %2").arg(quoted(path), errorString) ); } return modelIdOpt; } static QString getOpenModelPath(QWidget* parent) { return QFileDialog::getOpenFileName( parent, QObject::tr("Open model"), "", QObject::tr("LDraw models (*.ldr *.dat)")); } static ModelData* findModelData(const DocumentManager* documents, ModelId modelId) { return documents->findPayload<ModelData>(modelId); } static ModelSubWindow* currentModelSubWindow(Ui_MainWindow* ui) { auto* w = ui->mdiArea->activeSubWindow(); return qobject_cast<ModelSubWindow*>(w); } static ModelData* currentModelData(Ui_MainWindow* ui, const DocumentManager* documents) { if (auto* const activeSubWindow = currentModelSubWindow(ui)) { return findModelData(documents, activeSubWindow->modelId); } else { return nullptr; } } static Model* currentModelBody(Ui_MainWindow* ui, DocumentManager* documents) { if (auto* const activeSubWindow = currentModelSubWindow(ui)) { return documents->getModelById(activeSubWindow->modelId); } else { return nullptr; } } static std::optional<ModelId> findCurrentModelId(Ui_MainWindow* ui) { ModelSubWindow* activeSubWindow = qobject_cast<ModelSubWindow*>(ui->mdiArea->activeSubWindow()); if (activeSubWindow != nullptr) { return activeSubWindow->modelId; } else { return {}; } } static QString title(Ui_MainWindow* ui) { QMdiSubWindow* subWindow = ui->mdiArea->activeSubWindow(); QString titlestring; const QString versionString = fullVersionString(QLocale::ShortFormat); if (subWindow != nullptr) { titlestring = QObject::tr("%1 - %2").arg(subWindow->windowTitle(), versionString); } else { titlestring = versionString; } if (/* DISABLES CODE */ (true) and std::strcmp(CMAKE_BUILD_TYPE, "Release") != 0 and std::strcmp(CMAKE_BUILD_TYPE, "MinSizeRel") != 0 ) { titlestring += QObject::tr(" [%1]").arg(CMAKE_BUILD_TYPE); } return titlestring; } static ColorTable loadColors(const LibrariesModel* libraries) { QTextStream errors; return libraries->loadColorTable(errors); } static QString tabName(const QFileInfo& fileInfo) { QString result = fileInfo.baseName(); if (result.isEmpty()) { result = QObject::tr("<unnamed>"); } return result; } void rebuildRecentFilesMenu(QMenu* menu, const QStringList& strings, QWidget* parent) { menu->clear(); for (const QString& path : strings) { QAction* action = new QAction{path, parent}; action->setData(path); menu->addAction(action); } } template<typename Fn> static void forModel(const DocumentManager* documents, Fn&& fn) { forValueInMap(*documents, [&fn](const DocumentManager::ModelInfo& info) { ModelData* modelSpecificData = qobject_cast<ModelData*>(info.payload); if (modelSpecificData != nullptr) { fn(&info, modelSpecificData); } }); } static void updateRenderPreferences( Ui_MainWindow* ui, const gl::RenderPreferences* renderPreferences, const DocumentManager* documents) { forModel(documents, [&renderPreferences](const void*, const ModelData* data){ if (data->canvas != nullptr) { data->canvas->setRenderPreferences(*renderPreferences); data->canvas->setLayerEnabled(data->axesLayer.get(), renderPreferences->drawAxes); } }); for (auto data : ::renderStyleButtons) { QAction* action = data.memberInstance(ui); action->setChecked(renderPreferences->style == data.payload); } ui->actionDrawAxes->setChecked(renderPreferences->drawAxes); ui->actionWireframe->setChecked(renderPreferences->wireframe); } static gl::RenderPreferences loadRenderPreferences() { return gl::RenderPreferences{ .style = setting<Setting::RenderStyle>(), .mainColor = setting<Setting::MainColor>(), .backgroundColor = setting<Setting::BackgroundColor>(), .selectedColor = setting<Setting::SelectedColor>(), .lineThickness = setting<Setting::LineThickness>(), .lineAntiAliasing = setting<Setting::LineAntiAliasing>(), .drawAxes = setting<Setting::DrawAxes>(), .wireframe = setting<Setting::Wireframe>(), }; } struct ToolWidgets { CircleToolOptionsWidget* circleToolOptions; ObjectEditor* objectEditor; }; void initializeTools(Ui_MainWindow* ui, ToolWidgets* toolWidgets, QWidget* parent) { const struct { QString name, tooltip; QPixmap icon; QWidget* widget; } editingModesInfo[] = { { .name = QObject::tr("Select"), .tooltip = QObject::tr("Select elements from the model."), .icon = {":/icons/navigate-outline.png"}, .widget = toolWidgets->objectEditor, }, { .name = QObject::tr("Draw"), .tooltip = QObject::tr("Draw new elements into the model."), .icon = {":/icons/pencil-outline.png"}, .widget = nullptr, }, { .name = QObject::tr("Circle"), .tooltip = QObject::tr("Draw circular primitives."), .icon = {":/icons/linetype-circularprimitive.png"}, .widget = toolWidgets->circleToolOptions, }, }; for (int i = 0; i < countof(editingModesInfo); ++i) { const auto& editingModeInfo = editingModesInfo[i]; QAction* action = new QAction{editingModeInfo.name, parent}; action->setCheckable(true); action->setEnabled(false); action->setData(static_cast<EditingMode>(i)); action->setToolTip(editingModeInfo.tooltip); action->setIcon(QPixmap{editingModeInfo.icon}); ui->editingModesToolBar->addAction(action); QWidget* widget = editingModeInfo.widget; if (widget == nullptr) { widget = new QWidget{parent}; } ui->toolWidgetStack->addWidget(widget); QObject::connect(action, &QAction::triggered, [ui, i]{ ui->toolWidgetStack->setCurrentIndex(i); }); } } constexpr bool sortModelIndexesByRow(const QModelIndex& a, const QModelIndex& b) { return a.row() < b.row(); } std::vector<int> rows(const QModelIndexList& indexList) { std::vector<int> result; result.reserve(unsigned_cast(indexList.size())); for (const QModelIndex& index : indexList) { result.push_back(index.row()); } return result; } static void about(QWidget* parent) { QDialog dialog{parent}; Ui_About ui; ui.setupUi(&dialog); const char* glVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION)); const QString extensions = []{ GLint numExtensions; glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions); QStringList extensionsList; for (GLint i = 0; i < numExtensions; i++) { const GLubyte* ext = glGetStringi(GL_EXTENSIONS, i); extensionsList.push_back(reinterpret_cast<const char*>(ext)); } return extensionsList.join(" "); }(); for (QTextBrowser* browser : dialog.findChildren<QTextBrowser*>()) { browser->setHtml( browser->toHtml() .replace("%APPNAME%", CMAKE_PROJECT_NAME) .replace("%COPYRIGHT%", COPYRIGHT) .replace("%QTVERSION%", qVersion()) .replace("%VERSION%", detailedVersionString(QLocale::LongFormat)) .replace("%REVDATE%", revisionDateString(QLocale::LongFormat)) .replace("%BUILDTYPE%", CMAKE_BUILD_TYPE) .replace("%COMPILER_ID%", CMAKE_CXX_COMPILER_ID) .replace("%COMPILER_VERSION%", CMAKE_CXX_COMPILER_VERSION) .replace("%COMPILER_FLAGS%", CMAKE_CXX_FLAGS) .replace("%COMPILER_CPU%", CMAKE_SYSTEM_PROCESSOR) .replace("%COMPILER_SYSTEM%", CMAKE_SYSTEM) .replace("%GLMVERSIONSTRING%", GLM_VERSION_MESSAGE) .replace("%GL_VERSION%", glVersion) .replace("%GL_EXTENSIONS%", extensions) ); } dialog.setWindowTitle(QObject::tr("About %1").arg(CMAKE_PROJECT_NAME)); dialog.exec(); } template<class SubWindow, class... Args> SubWindow* createSubWindow(QMdiArea* mdiArea, Args&&... args) { // Qt seems to have a bug where the first created sub window does not render // properly until it is minimized and maximized again. This only happens // if we give the mdi area as a parent argument. As a work-around, we create // the sub window with parent=nullptr, and add it manually. // c.f. https://bugreports.qt.io/browse/QTBUG-69495 SubWindow* subWindow = new SubWindow{args..., nullptr}; mdiArea->addSubWindow(subWindow); return subWindow; } static QSet<ElementId> resolveIdsFromSelection(const ModelData* data) { const auto selection = data->itemSelectionModel->selection(); QSet<ElementId> selectedIndexes; for (const QModelIndex& qindex : selection.indexes()) { const std::size_t row = unsigned_cast(qindex.row()); selectedIndexes.insert(data->model->idAt(row)); } return selectedIndexes; } int main(int argc, char *argv[]) { doQtRegistrations(); QApplication app{argc, argv}; QApplication::setWindowIcon(QIcon{":/icons/appicon.png"}); QMainWindow mainWindow; Ui_MainWindow ui; DocumentManager documents; QString currentLanguage = "en"; QTranslator translator{&mainWindow}; LibrariesModel libraries{&mainWindow}; QStringList recentlyOpenedFiles; ColorTable colorTable; gl::RenderPreferences renderPreferences; MessageLog messageLog; Signal settingsChanged; ui.setupUi(&mainWindow); ToolWidgets toolWidgets{ .circleToolOptions = new CircleToolOptionsWidget{&mainWindow}, .objectEditor = new ObjectEditor{&mainWindow}, }; const auto updateTitle = [&ui, &mainWindow]{ mainWindow.setWindowTitle(title(&ui)); }; const uiutilities::KeySequenceMap defaultKeyboardShortcuts = uiutilities::makeKeySequenceMap(uiutilities::collectActions(&mainWindow)); const auto saveSettings = [&]{ setSetting<Setting::MainWindowGeometry>(mainWindow.saveGeometry()); setSetting<Setting::MainWindowState>(mainWindow.saveState()); setSetting<Setting::RecentFiles>(recentlyOpenedFiles); setSetting<Setting::RenderStyle>(renderPreferences.style); setSetting<Setting::DrawAxes>(renderPreferences.drawAxes); setSetting<Setting::Wireframe>(renderPreferences.wireframe); libraries.storeToSettings(); settingsChanged.emit(); }; const auto updateRecentlyOpenedDocumentsMenu = [&]{ rebuildRecentFilesMenu(ui.menuRecentFiles, recentlyOpenedFiles, &mainWindow); for (QAction* action : ui.menuRecentFiles->findChildren<QAction*>()) { QString path = action->data().toString(); QObject::connect( action, &QAction::triggered, [path, &libraries, &documents, &mainWindow]() { openModelFromPath(path, &libraries, &documents, &mainWindow); } ); } }; static constexpr auto executeAction = [&]( Model* model, const ModelAction& action ) { std::visit(overloaded{ [model](const AppendToModel& action){ model->append(action.newElement); }, [model](const DeleteFromModel& action){ model->remove(action.position); }, [model](const ModifyModel& action){ model->assignAt(action.position, action.newElement); }, }, action); }; const auto restoreSettings = [&]{ recentlyOpenedFiles = setting<Setting::RecentFiles>(); renderPreferences = loadRenderPreferences(); libraries.restoreFromSettings(); updateRecentlyOpenedDocumentsMenu(); colorTable = loadColors(&libraries); updateRenderPreferences(&ui, &renderPreferences, &documents); ui.mdiArea->setViewMode(setting<Setting::ViewMode>()); ui.retranslateUi(&mainWindow); settingsChanged.emit(); }; const auto addRecentlyOpenedFile = [&](const QString& path){ constexpr int maxRecentlyOpenedFiles = 10; recentlyOpenedFiles.removeAll(path); recentlyOpenedFiles.insert(0, path); while (recentlyOpenedFiles.size() > maxRecentlyOpenedFiles) { recentlyOpenedFiles.removeLast(); } saveSettings(); updateRecentlyOpenedDocumentsMenu(); }; const auto openModelForEditing = [&](const ModelId modelId){ Model* model = documents.getModelById(modelId); if (model != nullptr) { ModelData* data = new ModelData(&documents); data->tools = std::make_unique<EditTools>(); data->canvas = std::make_unique<PartRenderer>(model, &documents, colorTable); data->itemSelectionModel = std::make_unique<QItemSelectionModel>(); data->itemSelectionModel->setModel(model); data->axesLayer = std::make_unique<AxesLayer>(); constexpr glm::mat4 XZ = {{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}; data->gridLayer = std::make_unique<GridLayer>(); data->gridLayer->setGridMatrix(XZ); data->tools->setGridMatrix(XZ); data->model = model; data->canvas->addRenderLayer(data->axesLayer.get()); data->canvas->setLayerEnabled(data->axesLayer.get(), setting<Setting::DrawAxes>()); data->canvas->addRenderLayer(data->gridLayer.get()); data->canvas->addRenderLayer(data->tools.get()); documents.setModelPayload(modelId, data); QObject::connect( data->tools.get(), &EditTools::modelAction, std::bind(executeAction, model, std::placeholders::_1)); QObject::connect( data->itemSelectionModel.get(), &QItemSelectionModel::selectionChanged, [modelId, &documents, &toolWidgets]{ ModelData* data = findModelData(&documents, modelId); if (data != nullptr) { data->canvas->setSelection(resolveIdsFromSelection(data)); /* if (indices.size() == 1) { opt<std::size_t> index = data->model->find(*indices.begin()); if (index.has_value()) { toolWidgets.objectEditor->setObject((*data->model)[*index]); } } else { toolWidgets.objectEditor->reset(); } */ } }); data->canvas->setRenderPreferences(renderPreferences); QObject::connect( data->tools.get(), &EditTools::newStatusText, [&](const QString& newStatusText) { mainWindow.statusBar()->showMessage(newStatusText); }); QObject::connect( data->tools.get(), &EditTools::select, [modelId, &documents](const QSet<ElementId>& indices, bool retain) { ModelData* data = findModelData(&documents, modelId); if (data != nullptr) { if (not retain) { data->itemSelectionModel->clear(); } for (const ElementId id : indices) { opt<int> index = data->model->find(id); if (index.has_value()) { const QModelIndex qindex = data->model->index(*index); data->itemSelectionModel->select(qindex, QItemSelectionModel::Select); } } } }); QObject::connect(&settingsChanged, &Signal::triggered, [modelId, &documents]{ ModelData* data = findModelData(&documents, modelId); if (data != nullptr) { data->gridLayer->settingsChanged(); } }); QObject::connect(data->canvas.get(), &PartRenderer::message, &messageLog, &MessageLog::addMessage); QObject::connect( data->tools.get(), &EditTools::suggestCursor, data->canvas.get(), &QWidget::setCursor); data->tools->setEditMode(SelectMode); const QFileInfo fileInfo{*documents.modelPath(modelId)}; auto* const subWindow = createSubWindow<ModelSubWindow>(ui.mdiArea, modelId); subWindow->setMinimumSize({96, 96}); subWindow->resize({320, 200}); subWindow->setWidget(data->canvas.get()); subWindow->setWindowTitle(tabName(fileInfo)); subWindow->show(); } }; QObject::connect(ui.actionNew, &QAction::triggered, [&]{ openModelForEditing(documents.newModel()); }); QObject::connect(ui.actionOpen, &QAction::triggered, [&]{ const QString path = getOpenModelPath(&mainWindow); if (not path.isEmpty()) { const std::optional<ModelId> id = openModelFromPath(path, &libraries, &documents, &mainWindow); if (id.has_value()) { openModelForEditing(id.value()); addRecentlyOpenedFile(path); } } }); QObject::connect(ui.actionSettingsEditor, &QAction::triggered, [&]{ if (ui.mdiArea->findChildren<SettingsEditor*>().isEmpty()) { auto* const settingsEditor = createSubWindow<SettingsEditor>(ui.mdiArea, defaultKeyboardShortcuts); QObject::connect(&settingsChanged, &Signal::triggered, settingsEditor, &SettingsEditor::loadSettings); QObject::connect(settingsEditor, &SettingsEditor::settingsChanged, restoreSettings); settingsEditor->setAttribute(Qt::WA_DeleteOnClose); settingsEditor->show(); } }); QObject::connect(ui.actionQuit, &QAction::triggered, &mainWindow, &QMainWindow::close); #if 0 QObject::connect(ui.actionAdjustGridToView, &QAction::triggered, [&]{ if (ModelData* data = currentModelData(&ui, &documents)) { adjustGridToView(data->canvas.get()); } }); #endif QObject::connect(ui.actionClose, &QAction::triggered, [&ui, &documents]{ if (ModelData* data = currentModelData(&ui, &documents)) { // TODO } }); const auto save = [&](ModelId modelId){ QString error; QTextStream errorStream{&error}; const bool succeeded = documents.saveModel(modelId, errorStream); if (not succeeded) { QMessageBox::critical(&mainWindow, QObject::tr("Save error"), error); } else { const QString* pathPtr = documents.modelPath(modelId); if (pathPtr != nullptr) { addRecentlyOpenedFile(*pathPtr); } } };; const auto actionSaveAs = [&]{ const std::optional<ModelId> modelId = findCurrentModelId(&ui); if (modelId.has_value()) { const QString* pathPtr = documents.modelPath(*modelId); QString defaultPath = (pathPtr != nullptr) ? *pathPtr : ""; const QString newPath = QFileDialog::getSaveFileName( &mainWindow, QObject::tr("Save as…"), QFileInfo{defaultPath}.absoluteDir().path(), QObject::tr("LDraw files (*.ldr *dat);;All files (*)") ); if (not newPath.isEmpty()) { QString error; QTextStream errorStream{&error}; documents.setModelPath(*modelId, newPath, libraries, errorStream); QMdiSubWindow* const subWindow = ui.mdiArea->currentSubWindow(); if (subWindow != nullptr) { subWindow->setWindowTitle(tabName(QFileInfo{newPath})); } save(*modelId); } } }; QObject::connect(ui.actionSaveAs, &QAction::triggered, actionSaveAs); QObject::connect(ui.actionSave, &QAction::triggered, [&]{ const std::optional<ModelId> modelId = findCurrentModelId(&ui); if (modelId.has_value()) { const QString* path = documents.modelPath(*modelId); if (path == nullptr or path->isEmpty()) { actionSaveAs(); } else { save(*modelId); } } }); QObject::connect(ui.actionDelete, &QAction::triggered, [&]{ if (Model* model = currentModelBody(&ui, &documents)) { std::vector<int> selectedRows = rows(ui.modelListView->selectionModel()->selectedRows()); std::sort(selectedRows.begin(), selectedRows.end(), std::greater<int>{}); for (int row : selectedRows) { executeAction(model, DeleteFromModel{.position = unsigned_cast(row)}); } } }); QObject::connect(ui.actionDrawAxes, &QAction::triggered, [&](bool drawAxes){ renderPreferences.drawAxes = drawAxes; saveSettings(); updateRenderPreferences(&ui, &renderPreferences, &documents); }); QObject::connect(ui.actionWireframe, &QAction::triggered, [&](bool enabled){ renderPreferences.wireframe = enabled; saveSettings(); updateRenderPreferences(&ui, &renderPreferences, &documents); }); for (auto data : ::renderStyleButtons) { QAction* action = data.memberInstance(&ui); QObject::connect(action, &QAction::triggered, [&, data]{ renderPreferences.style = data.payload; saveSettings(); updateRenderPreferences(&ui, &renderPreferences, &documents); }); } const auto checkEditingModeAction = [&ui, &documents](EditingMode mode) { const bool hasDocument = currentModelData(&ui, &documents) != nullptr; for (QAction* action : ui.editingModesToolBar->actions()) { action->setEnabled(hasDocument); action->setChecked(hasDocument and action->data().value<EditingMode>() == mode); } }; initializeTools(&ui, &toolWidgets, &mainWindow); for (QAction* action : ui.editingModesToolBar->actions()) { QObject::connect(action, &QAction::triggered, [&, action]{ if (ModelData* data = currentModelData(&ui, &documents)) { const EditingMode mode = action->data().value<EditingMode>(); data->tools->setEditMode(mode); checkEditingModeAction(mode); } }); } QObject::connect(ui.mdiArea, &QMdiArea::subWindowActivated, [&](QMdiSubWindow* subWindow) { ModelSubWindow* modelSubWindow = qobject_cast<ModelSubWindow*>(subWindow); if (modelSubWindow != nullptr) { if (ModelData* data = documents.findPayload<ModelData>(modelSubWindow->modelId)) { checkEditingModeAction(data->tools->currentEditingMode()); if (data->itemSelectionModel != nullptr) { ui.modelListView->setModel(data->model); ui.modelListView->setSelectionModel(data->itemSelectionModel.get()); } } } else { checkEditingModeAction(EditingMode::SelectMode); } updateTitle(); }); ui.messageLog->setModel(&messageLog); QObject::connect(ui.actionAboutQt, &QAction::triggered, &app, &QApplication::aboutQt); QObject::connect(&documents, &DocumentManager::message, &messageLog, &MessageLog::addMessage); QObject::connect(&messageLog, &MessageLog::rowsAboutToBeInserted, [&]{ const auto bar = ui.messageLog->verticalScrollBar(); ui.messageLog->setProperty("shouldAutoScroll", bar->value() == bar->maximum()); }); QObject::connect(&messageLog, &MessageLog::rowsInserted, [&]{ ui.messageLog->resizeRowsToContents(); if (ui.messageLog->property("shouldAutoScroll").toBool()) { ui.messageLog->scrollToBottom(); } }); QObject::connect( toolWidgets.circleToolOptions, &CircleToolOptionsWidget::optionsChanged, [&ui, &documents](const CircleToolOptions& options) { if (ModelData* data = currentModelData(&ui, &documents)) { data->tools->setCircleToolOptions(options); } }); QObject::connect( ui.actionMakeUnofficial, &QAction::triggered, [&]{ if (ModelData* data = currentModelData(&ui, &documents)) { Model* const model = data->model; for (const ModelAction& action : ldraw::makeUnofficial(model)) { executeAction(model, action); } } }); QObject::connect( ui.actionAbout, &QAction::triggered, [&mainWindow, &ui]{ // Make sure that there's an OpenGL context active, otherwise // we cannot obtain OpenGL information if (ui.mdiArea->findChildren<ModelSubWindow*>().empty()) { ui.actionNew->trigger(); } about(&mainWindow); } ); mainWindow.tabifyDockWidget(ui.messageLogDock, ui.toolOptionsDock); mainWindow.restoreGeometry(setting<Setting::MainWindowGeometry>()); mainWindow.restoreState(setting<Setting::MainWindowState>()); // If a dock is made floating and the app is closed, the dock becomes invisible // after the restoreState call. So we make them visible again here. for (QDockWidget* dock : mainWindow.findChildren<QDockWidget*>()) { dock->setVisible(true); } restoreSettings(); updateRenderPreferences(&ui, &renderPreferences, &documents); ui.actionAbout->setText(ui.actionAbout->text().arg(CMAKE_PROJECT_NAME)); updateTitle(); mainWindow.show(); const int result = app.exec(); saveSettings(); return result; }