diff -r ca23936b455b -r 5d201ee4a9c3 src/main.cpp
--- a/src/main.cpp Mon Jun 06 22:01:22 2022 +0300
+++ b/src/main.cpp Tue Jun 07 01:37:26 2022 +0300
@@ -1,24 +1,14 @@
-/*
- * LDForge: LDraw parts authoring CAD
- * Copyright (C) 2013 - 2020 Teemu Piippo
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program 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
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
+#include
+#include
+#include
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include "version.h"
+#include "document.h"
+#include "settingseditor/settingseditor.h"
+#include "widgets/colorselectdialog.h"
-#include
-#include "mainwindow.h"
-#include "version.h"
+static const QDir LOCALE_DIR {":/locale"};
static void doQtRegistrations()
{
@@ -29,11 +19,408 @@
qRegisterMetaTypeStreamOperators("Libraries");
}
+template
+struct MemberData
+{
+ std::size_t member;
+ DataType payload;
+ constexpr MemberType memberInstance(BaseType* instance) const
+ {
+ return *reinterpret_cast(reinterpret_cast(instance) + this->member);
+ }
+};
+
+static constexpr MemberData 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 openModelFromPath(
+ const QString& path,
+ const LibraryManager* libraries,
+ DocumentManager* documents,
+ QWidget* parent)
+{
+ QString errorString;
+ QTextStream errorStream{&errorString};
+ const std::optional 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(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(ui->tabs->currentWidget());
+};
+
+
+static void closeDocument(DocumentManager* documents, EditorTabWidget *document)
+{
+ std::optional 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(ui->tabs->widget(tabIndex));
+ if (tab != nullptr) {
+ closeDocument(documents, tab);
+ }
+ }
+}
+
+static std::optional 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("");
+ }
+ 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(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(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};
- MainWindow mainwindow;
- mainwindow.show();
- return app.exec();
+ 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(renderPreferences.style));
+ settings.setDrawAxes(renderPreferences.drawAxes);
+ libraries.storeToSettings(&settings);
+ };
+ const auto updateRecentlyOpenedDocumentsMenu = [&]{
+ rebuildRecentFilesMenu(ui.menuRecentFiles, recentlyOpenedFiles, &mainWindow);
+ for (QAction* action : ui.menuRecentFiles->findChildren()) {
+ 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 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 = 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 = 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;
}