Tue, 07 Jun 2022 01:37:26 +0300
Continue giant refactor
#include <QApplication> #include <QFileDialog> #include <QMessageBox> #include "mainwindow.h" #include "ui_mainwindow.h" #include "version.h" #include "document.h" #include "settingseditor/settingseditor.h" #include "widgets/colorselectdialog.h" static const QDir LOCALE_DIR {":/locale"}; static void doQtRegistrations() { QCoreApplication::setApplicationName(::appName); QCoreApplication::setOrganizationName("hecknology.net"); QCoreApplication::setOrganizationDomain("hecknology.net"); qRegisterMetaTypeStreamOperators<Library>("Library"); qRegisterMetaTypeStreamOperators<Libraries>("Libraries"); } 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 LibraryManager* libraries, DocumentManager* documents, QWidget* parent) { QString errorString; QTextStream errorStream{&errorString}; const std::optional<ModelId> modelIdOpt = documents->openModel( path, errorStream, DocumentManager::OpenType::ManuallyOpened); if (modelIdOpt.has_value()) { documents->loadDependenciesForModel(modelIdOpt.value(), path, *libraries, errorStream); if (not errorString.isEmpty()) { QMessageBox::warning( parent, QObject::tr("Problem loading references"), errorString); } } else { QMessageBox::critical( parent, QObject::tr("Problem opening file"), format(QObject::tr("Could not open %1: %2"), path, errorString)); } return modelIdOpt; } static QString getOpenModelPath(QWidget* parent) { return QFileDialog::getOpenFileName( parent, QObject::tr("Open model"), "", QObject::tr("LDraw models (*.ldr *.dat)")); } static const QString localeCode(const QString& locale) { if (locale == "system") { return QLocale::system().name(); } else { return locale; } } /** * @brief Changes the application language to the specified language */ static void changeLanguage(const QString& locale, QTranslator* translator) { if (not locale.isEmpty()) { const QString localeCode = ::localeCode(locale); QLocale::setDefault({localeCode}); qApp->removeTranslator(translator); const QString path = LOCALE_DIR.filePath(localeCode + ".qm"); const bool loadSuccessful = translator->load(path); if (loadSuccessful) { qApp->installTranslator(translator); } } } /* void MainWindow::handleDocumentSplitterChange() { EditorTabWidget* currentDocument = this->currentDocument(); if (currentDocument != nullptr) { this->documentSplitterState = currentDocument->saveSplitterState(); for (int i = 0; i < this->ui->tabs->count(); i += 1) { EditorTabWidget* document = qobject_cast<EditorTabWidget*>(this->ui->tabs->widget(i)); if (document != nullptr and document != currentDocument) { document->restoreSplitterState(this->documentSplitterState); } } this->settings.setMainSplitterState(this->documentSplitterState); } } */ static EditorTabWidget* currentTabWidget(Ui_MainWindow* ui) { return qobject_cast<EditorTabWidget*>(ui->tabs->currentWidget()); }; static void closeDocument(DocumentManager* documents, EditorTabWidget *document) { std::optional<ModelId> modelId = documents->findIdForModel(document->model); if (modelId.has_value()) { documents->closeDocument(modelId.value()); delete document; } } static void handleTabCloseButton(Ui_MainWindow* ui, DocumentManager* documents, int tabIndex) { if (tabIndex >= 0 and tabIndex < ui->tabs->count()) { EditorTabWidget* tab = qobject_cast<EditorTabWidget*>(ui->tabs->widget(tabIndex)); if (tab != nullptr) { closeDocument(documents, tab); } } } static std::optional<ModelId> findCurrentModelId(Ui_MainWindow* ui, DocumentManager* documents) { const EditorTabWidget* tab = currentTabWidget(ui); if (tab != nullptr) { return documents->findIdForModel(tab->model); } else { return {}; } } /** * @brief Updates the title of the main window so to contain the app's name * and version as well as the open document name. */ static QString title() { QString title = ::appName; title += " "; title += fullVersionString(); return title; } static ldraw::ColorTable loadColors(const LibraryManager* 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); } } static void updateRenderPreferences( Ui_MainWindow* ui, const gl::RenderPreferences* renderPreferences) { for (int i = 0; i < ui->tabs->count(); i += 1) { EditorTabWidget* tab = qobject_cast<EditorTabWidget*>(ui->tabs->widget(i)); if (tab != nullptr) { tab->canvas->setRenderPreferences(*renderPreferences); } } for (auto data : ::renderStyleButtons) { QAction* action = data.memberInstance(ui); action->setChecked(renderPreferences->style == data.payload); } ui->actionDrawAxes->setChecked(renderPreferences->drawAxes); }; static gl::RenderPreferences loadRenderPreferences(Configuration* settings) { return gl::RenderPreferences{ .style = static_cast<gl::RenderStyle>(settings->renderStyle()), .mainColor = settings->mainColor(), .backgroundColor = settings->backgroundColor(), .selectedColor = settings->selectedColor(), .lineThickness = settings->lineThickness(), .lineAntiAliasing = settings->lineAntiAliasing(), .drawAxes = settings->drawAxes(), }; } int main(int argc, char *argv[]) { doQtRegistrations(); QApplication app{argc, argv}; QMainWindow mainWindow; Ui_MainWindow ui; DocumentManager documents{&mainWindow}; QString currentLanguage = "en"; QTranslator translator{&mainWindow}; Configuration settings; LibraryManager libraries{&mainWindow}; QByteArray documentSplitterState; QStringList recentlyOpenedFiles; ldraw::ColorTable colorTable; gl::RenderPreferences renderPreferences; ui.setupUi(&mainWindow); const uiutilities::KeySequenceMap defaultKeyboardShortcuts = uiutilities::makeKeySequenceMap(uiutilities::collectActions(&mainWindow)); const auto saveSettings = [&]{ settings.setMainWindowGeometry(mainWindow.saveGeometry()); settings.setRecentFiles(recentlyOpenedFiles); settings.setMainSplitterState(documentSplitterState); settings.setRenderStyle(static_cast<int>(renderPreferences.style)); settings.setDrawAxes(renderPreferences.drawAxes); libraries.storeToSettings(&settings); }; 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); } ); } }; const auto restoreSettings = [&]{ recentlyOpenedFiles = settings.recentFiles(); documentSplitterState = settings.mainSplitterState(); renderPreferences = loadRenderPreferences(&settings); changeLanguage(settings.locale(), &translator); libraries.restoreFromSettings(&settings); updateRecentlyOpenedDocumentsMenu(); colorTable = loadColors(&libraries); updateRenderPreferences(&ui, &renderPreferences); ui.retranslateUi(&mainWindow); }; 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){ EditorTabWidget* document = new EditorTabWidget{ documents.getModelById(modelId), &documents, colorTable, }; document->canvas->setRenderPreferences(renderPreferences); QObject::connect( document, &EditorTabWidget::newStatusText, [&](const QString& newStatusText) { mainWindow.statusBar()->showMessage(newStatusText); }); const QFileInfo fileInfo{*documents.modelPath(modelId)}; ui.tabs->addTab(document, tabName(fileInfo)); ui.tabs->setCurrentWidget(document); document->restoreSplitterState(documentSplitterState); }; const auto newModel = [&openModelForEditing](DocumentManager* documents){ openModelForEditing(documents->newModel()); }; QObject::connect(ui.actionNew, &QAction::triggered, [&newModel, &documents]{ newModel(&documents); }); 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, [&]{ SettingsEditor settingsEditor{&settings, defaultKeyboardShortcuts, &mainWindow}; const int result = settingsEditor.exec(); if (result == QDialog::Accepted) { restoreSettings(); } }); QObject::connect(ui.actionQuit, &QAction::triggered, &mainWindow, &QMainWindow::close); QObject::connect(ui.actionAdjustGridToView, &QAction::triggered, [&ui]{ EditorTabWidget* tab = currentTabWidget(&ui); if (tab != nullptr) { adjustGridToView(tab->canvas); } }); QObject::connect(ui.actionClose, &QAction::triggered, [&ui, &documents]{ EditorTabWidget* tab = currentTabWidget(&ui); if (tab != nullptr) { closeDocument(&documents, tab); } }); 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, &documents); 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); ui.tabs->setTabText(ui.tabs->currentIndex(), QFileInfo{newPath}.fileName()); save(*modelId); } } }; QObject::connect(ui.actionSaveAs, &QAction::triggered, actionSaveAs); QObject::connect(ui.actionSave, &QAction::triggered, [&]{ if (currentTabWidget(&ui) != nullptr) { const std::optional<ModelId> modelId = findCurrentModelId(&ui, &documents); if (modelId.has_value()) { const QString* path = documents.modelPath(*modelId); if (path == nullptr or path->isEmpty()) { actionSaveAs(); } else { save(*modelId); } } } }); QObject::connect(ui.tabs, &QTabWidget::tabCloseRequested, [&](int index){ handleTabCloseButton(&ui, &documents, index); }); QObject::connect(ui.actionDrawAxes, &QAction::triggered, [&](bool drawAxes){ renderPreferences.drawAxes = drawAxes; saveSettings(); updateRenderPreferences(&ui, &renderPreferences); }); for (auto data : ::renderStyleButtons) { QAction* action = data.memberInstance(&ui); QObject::connect(action, &QAction::triggered, [&, data]{ renderPreferences.style = data.payload; saveSettings(); updateRenderPreferences(&ui, &renderPreferences); }); } mainWindow.setWindowTitle(title()); mainWindow.restoreGeometry(settings.mainWindowGeometry()); restoreSettings(); updateRenderPreferences(&ui, &renderPreferences); newModel(&documents); mainWindow.show(); const int result = app.exec(); saveSettings(); return result; }