Tue, 28 Sep 2021 22:14:00 +0300
Fix memory corruption involving document tools.
I don't think that the metaobject-initialization had anything to do with this
but it is a lot simpler without it anyway.
/* * 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 <http://www.gnu.org/licenses/>. */ #include <QLabel> #include <QVBoxLayout> #include <QCloseEvent> #include <QFileDialog> #include <QMessageBox> #include "mainwindow.h" #include "ui_mainwindow.h" #include "settingseditor/settingseditor.h" #include "version.h" #include "document.h" #include "uiutilities.h" #include "widgets/colorselectdialog.h" 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 }, }; class A : public QSettings { using QSettings::QSettings; }; MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}, ui{std::make_unique<Ui_MainWindow>()}, documents{this}, settings{}, libraries{this} { this->ui->setupUi(this); defaultKeyboardShortcuts = uiutilities::makeKeySequenceMap(uiutilities::collectActions(this)); connect(ui->actionNew, &QAction::triggered, this, &MainWindow::newModel); connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::openModel); connect(ui->actionQuit, &QAction::triggered, this, &QMainWindow::close); connect(ui->actionSettingsEditor, &QAction::triggered, this, &MainWindow::runSettingsEditor); connect(ui->actionAdjustGridToView, &QAction::triggered, [&]() { if (this->currentDocument() != nullptr) { this->currentDocument()->adjustGridToView(); } }); connect(this->ui->actionSave, &QAction::triggered, this, &MainWindow::actionSave); connect(this->ui->actionSaveAs, &QAction::triggered, this, &MainWindow::actionSaveAs); for (auto data : ::renderStyleButtons) { QAction* action = data.memberInstance(this->ui.get()); connect(action, &QAction::triggered, [this, data]() { this->setRenderStyle(data.payload); }); } this->updateTitle(); this->restoreStartupSettings(); this->restoreSettings(); this->updateRenderPreferences(); this->newModel(); } // MainWindow needs a destructor even if it is empty because otherwise the destructor of the // std::unique_ptr is resolved in the header file, where it will complain about Ui_MainWindow // being incomplete. MainWindow::~MainWindow() { } void MainWindow::newModel() { this->openModelForEditing(documents.newModel()); } void MainWindow::openModel() { const QString path = QFileDialog::getOpenFileName( this, tr("Open model"), "", tr("LDraw models (*.ldr *.dat)")); if (not path.isEmpty()) { this->openModelFromPath(path); } } void MainWindow::openModelFromPath(const QString& path) { QString errorString; QTextStream errorStream{&errorString}; QString modelName = this->documents.openModel(path, errorStream); if (not modelName.isEmpty()) { this->documents.loadDependenciesForModel(modelName, path, this->libraries, errorStream); if (not errorString.isEmpty()) { QMessageBox::warning( this, tr("Problem loading references"), errorString); } this->openModelForEditing(modelName); this->addRecentlyOpenedFile(path); } else { QMessageBox::critical( this, tr("Problem opening file"), utility::format( tr("Could not open %1: %2"), path, errorString)); } } /** * @brief Changes the application language to the specified language * @param localeCode Code of the locale to translate to */ void MainWindow::changeLanguage(QString localeCode) { if (not localeCode.isEmpty() and localeCode != this->currentLanguage) { this->currentLanguage = localeCode; if (localeCode == "system") { localeCode = QLocale::system().name(); } QLocale::setDefault({localeCode}); qApp->removeTranslator(&this->translator); const bool loadSuccessful = this->translator.load(pathToTranslation(localeCode)); if (loadSuccessful) { qApp->installTranslator(&this->translator); } } } void MainWindow::addRecentlyOpenedFile(const QString& path) { this->recentlyOpenedFiles.removeAll(path); this->recentlyOpenedFiles.insert(0, path); while (this->recentlyOpenedFiles.size() > maxRecentlyOpenedFiles) { this->recentlyOpenedFiles.removeLast(); } this->saveSettings(); this->updateRecentlyOpenedDocumentsMenu(); } void MainWindow::openModelForEditing(const QString& modelName) { Document* document = new Document{this->documents.findModelByName(modelName), &this->documents, this->colorTable}; document->setRenderPreferences(this->renderPreferences); connect(document, &Document::newStatusText, [&](const QString& newStatusText) { this->statusBar()->showMessage(newStatusText); }); this->ui->tabs->addTab(document, modelName); this->ui->tabs->setCurrentWidget(document); document->restoreSplitterState(this->documentSplitterState); } void MainWindow::runSettingsEditor() { SettingsEditor settingsEditor{&this->settings, this->defaultKeyboardShortcuts, this}; const int result = settingsEditor.exec(); if (result == QDialog::Accepted) { this->restoreSettings(); } } Document* MainWindow::currentDocument() { return qobject_cast<Document*>(this->ui->tabs->currentWidget()); } void MainWindow::handleDocumentSplitterChange() { Document* currentDocument = this->currentDocument(); if (currentDocument != nullptr) { this->documentSplitterState = currentDocument->saveSplitterState(); for (int i = 0; i < this->ui->tabs->count(); i += 1) { Document* document = qobject_cast<Document*>(this->ui->tabs->widget(i)); if (document != nullptr and document != currentDocument) { document->restoreSplitterState(this->documentSplitterState); } } this->settings.setMainSplitterState(this->documentSplitterState); } } void MainWindow::updateRecentlyOpenedDocumentsMenu() { this->ui->menuRecentFiles->clear(); for (const QString& path : this->recentlyOpenedFiles) { QAction* action = new QAction{path, this}; action->setData(path); this->ui->menuRecentFiles->addAction(action); connect(action, &QAction::triggered, this, &MainWindow::openRecentFile); } } void MainWindow::openRecentFile() { QAction* action = qobject_cast<QAction*>(this->sender()); if (action != nullptr) { const QString path = action->data().toString(); this->openModelFromPath(path); } } void MainWindow::setRenderStyle(gl::RenderStyle renderStyle) { this->renderPreferences.style = renderStyle; this->saveSettings(); this->updateRenderPreferences(); } /** * @brief Handles the "Save" (Ctrl+S) action */ void MainWindow::actionSave() { if (this->currentDocument() != nullptr) { if (this->currentDocument()->modelPath().isEmpty()) { this->actionSaveAs(); } else { QString error; QTextStream errorStream{&error}; const bool succeeded = this->currentDocument()->save(errorStream); if (not succeeded) { QMessageBox::critical(this, tr("Save error"), error); } else { this->addRecentlyOpenedFile(this->currentDocument()->modelPath()); } } } } /** * @brief Handles the "Save as…" (Ctrl+Shift+S) action */ void MainWindow::actionSaveAs() { if (this->currentDocument() != nullptr) { const QString dir = QFileInfo{this->currentDocument()->modelPath()}.absoluteDir().path(); const QString newPath = QFileDialog::getSaveFileName( this, tr("Save as…"), dir, tr("LDraw files (*.ldr *dat);;All files (*)") ); if (not newPath.isEmpty()) { this->currentDocument()->setModelPath(newPath); this->ui->tabs->setTabText(this->ui->tabs->currentIndex(), QFileInfo{newPath}.fileName()); this->actionSave(); } } } void MainWindow::changeEvent(QEvent* event) { if (event != nullptr) { switch (event->type()) { case QEvent::LanguageChange: this->ui->retranslateUi(this); break; default: break; } } QMainWindow::changeEvent(event); } /** * @brief Handles closing the main window * @param event Event information */ void MainWindow::closeEvent(QCloseEvent* event) { saveSettings(); event->accept(); } /** * @brief Updates the title of the main window so to contain the app's name * and version as well as the open document name. */ void MainWindow::updateTitle() { QString title = ::appName; title += " "; title += fullVersionString(); setWindowTitle(title); } void MainWindow::updateRenderPreferences() { for (int i = 0; i < this->ui->tabs->count(); i += 1) { Document* document = qobject_cast<Document*>(this->ui->tabs->widget(i)); if (document != nullptr) { document->setRenderPreferences(this->renderPreferences); } } for (auto data : ::renderStyleButtons) { QAction* action = data.memberInstance(this->ui.get()); action->setChecked(this->renderPreferences.style == data.payload); } } /** * @brief Stores the settings of the main window, storing geometry, etc */ void MainWindow::saveSettings() { this->settings.setMainWindowGeometry(this->saveGeometry()); this->settings.setRecentFiles(this->recentlyOpenedFiles); this->settings.setMainSplitterState(this->documentSplitterState); this->settings.setRenderStyle(static_cast<int>(this->renderPreferences.style)); this->libraries.storeToSettings(&this->settings); } void MainWindow::restoreStartupSettings() { this->restoreGeometry(this->settings.mainWindowGeometry()); } /** * @brief Restores saved settings relating to the main window */ void MainWindow::restoreSettings() { this->recentlyOpenedFiles = this->settings.recentFiles(); this->documentSplitterState = this->settings.mainSplitterState(); this->renderPreferences.style = static_cast<gl::RenderStyle>(this->settings.renderStyle()); this->renderPreferences.mainColor = this->settings.mainColor(); this->renderPreferences.backgroundColor = this->settings.backgroundColor(); this->renderPreferences.lineThickness = this->settings.lineThickness(); this->renderPreferences.lineAntiAliasing = this->settings.lineAntiAliasing(); this->renderPreferences.selectedColor = this->settings.selectedColor(); const QString systemLocale = QLocale::system().name(); const QVariant defaultLocale = this->settings.locale(); this->changeLanguage(defaultLocale.toString()); this->libraries.restoreFromSettings(&this->settings); this->updateRecentlyOpenedDocumentsMenu(); this->loadColors(); this->updateRenderPreferences(); } QString MainWindow::pathToTranslation(const QString& localeCode) { QDir dir {":/locale"}; return dir.filePath(localeCode + ".qm"); } void MainWindow::loadColors() { QTextStream errors; this->colorTable = this->libraries.loadColorTable(errors); } void MainWindow::keyReleaseEvent(QKeyEvent* event) { Document* document = this->currentDocument(); if (document != nullptr) { document->handleKeyPress(event); } }