src/main.cpp

changeset 362
e1d646a4cbd8
parent 361
c5e8b68e34f8
child 363
16f5dcfa7c9f
--- a/src/main.cpp	Sun Apr 09 13:28:36 2023 +0300
+++ b/src/main.cpp	Sun Apr 09 15:59:08 2023 +0300
@@ -23,6 +23,7 @@
 #include "src/parser.h"
 #include "src/ldrawsyntaxhighlighter.h"
 #include <GL/glew.h>
+#include "src/main.h"
 
 static const QDir LOCALE_DIR {":/locale"};
 
@@ -145,26 +146,6 @@
 	}
 }
 
-static QString title(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;
@@ -328,13 +309,8 @@
 	return font;
 }
 
-constexpr glm::mat4 DEFAULT_GRID_MATRIX = {{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}};
-
-int main(int argc, char *argv[])
+struct MainState
 {
-	doQtRegistrations();
-	QApplication app{argc, argv};
-	QApplication::setWindowIcon(QIcon{":/icons/appicon.png"});
 	MainWindow mainWindow;
 	DocumentManager documents;
 	QString currentLanguage = "en";
@@ -344,194 +320,234 @@
 	ColorTable colorTable;
 	gl::RenderPreferences renderPreferences;
 	MessageLog messageLog;
-	Signal settingsChanged;
 	ToolWidgets toolWidgets{
 		.circleToolOptions = new CircleToolOptionsWidget{&mainWindow},
 	};
-	const auto updateTitle = [&mainWindow]{
-		mainWindow.setWindowTitle(title(&mainWindow));
-	};
-	const uiutilities::KeySequenceMap defaultKeyboardShortcuts =
-		uiutilities::makeKeySequenceMap(uiutilities::collectActions(&mainWindow));
-	const auto saveSettings = [
-		&libraries,
-		&mainWindow,
-		&recentlyOpenedFiles,
-		&renderPreferences,
-		&settingsChanged]
-	{
-		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 openModelForEditing = [
-		&colorTable,
-		&documents,
-		&mainWindow,
-		&messageLog,
-		&renderPreferences,
-		&settingsChanged]
-		(const ModelId modelId)
-	{
-		QTextDocument* 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->axesLayer = std::make_unique<AxesLayer>();
-			data->gridLayer = std::make_unique<GridLayer>();
-			data->gridLayer->setGridMatrix(DEFAULT_GRID_MATRIX);
-			data->tools->setGridMatrix(DEFAULT_GRID_MATRIX);
-			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());
-			new LDrawSyntaxHighlighter{model};
-			data->textcursor = std::make_unique<QTextCursor>(model);
-			documents.setModelPayload(modelId, data);
-			QObject::connect(
-				data->tools.get(),
-				&EditTools::modelAction,
-				std::bind(executeAction, model, std::placeholders::_1));
-			data->canvas->setRenderPreferences(renderPreferences);
-			QObject::connect(
-				data->tools.get(),
-				&EditTools::newStatusText,
-				[&mainWindow](const QString& newStatusText) {
-					mainWindow.statusBar()->showMessage(newStatusText);
-				});
+};
+
+static void openModelForEditing(MainState* state, const ModelId modelId)
+{
+	QTextDocument* model = state->documents.getModelById(modelId);
+	if (model != nullptr) {
+		ModelData* data = new ModelData(&state->documents);
+		data->tools = std::make_unique<EditTools>();
+		data->canvas = std::make_unique<PartRenderer>(model, &state->documents, state->colorTable);
+		data->axesLayer = std::make_unique<AxesLayer>();
+		data->gridLayer = std::make_unique<GridLayer>();
+		data->gridLayer->setGridMatrix(DEFAULT_GRID_MATRIX);
+		data->tools->setGridMatrix(DEFAULT_GRID_MATRIX);
+		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());
+		new LDrawSyntaxHighlighter{model};
+		data->textcursor = std::make_unique<QTextCursor>(model);
+		state->documents.setModelPayload(modelId, data);
+		QObject::connect(
+			data->tools.get(),
+			&EditTools::modelAction,
+			std::bind(executeAction, model, std::placeholders::_1));
+		data->canvas->setRenderPreferences(state->renderPreferences);
+		QObject::connect(
+			data->tools.get(),
+			&EditTools::newStatusText,
+			[&state](const QString& newStatusText) {
+				state->mainWindow.statusBar()->showMessage(newStatusText);
+			});
 #if 0
-			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->textcursor->clearSelection();
-						}
-						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(
+			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->textcursor->clearSelection();
+					}
+					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);
 						}
 					}
-				});
-#endif
-			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>(mainWindow.mdiArea, modelId);
-			subWindow->setMinimumSize({96, 96});
-			subWindow->resize({320, 200});
-			subWindow->setWidget(data->canvas.get());
-			subWindow->setWindowTitle(tabName(fileInfo));
-			subWindow->show();
-		}
-	};
-	const auto updateRecentlyOpenedDocumentsMenu = [
-		&mainWindow,
-		&recentlyOpenedFiles]
-	{
-		mainWindow.rebuildRecentFilesMenu(recentlyOpenedFiles);
-	};
-	const auto restoreSettings = [
-		&colorTable,
-		&documents,
-		&libraries,
-		&mainWindow,
-		&recentlyOpenedFiles,
-		&renderPreferences,
-		&settingsChanged,
-		&updateRecentlyOpenedDocumentsMenu]
+#endif
+#if 0
+		QObject::connect(this, &Main::settingsChanged, [modelId, this]{
+			ModelData* data = findModelData(&state.documents, modelId);
+			if (data != nullptr) {
+				data->gridLayer->settingsChanged();
+			}
+		});
+#endif
+		QObject::connect(data->canvas.get(), &PartRenderer::message, &state->messageLog, &MessageLog::addMessage);
+		QObject::connect(
+			data->tools.get(),
+			&EditTools::suggestCursor,
+			data->canvas.get(),
+			&QWidget::setCursor);
+		data->tools->setEditMode(SelectMode);
+		const QFileInfo fileInfo{*state->documents.modelPath(modelId)};
+		auto* const subWindow = createSubWindow<ModelSubWindow>(state->mainWindow.mdiArea, modelId);
+		subWindow->setMinimumSize({96, 96});
+		subWindow->resize({320, 200});
+		subWindow->setWidget(data->canvas.get());
+		subWindow->setWindowTitle(tabName(fileInfo));
+		subWindow->show();
+	}
+}
+
+static void updateRecentlyOpenedDocumentsMenu(MainState* state)
+{
+	state->mainWindow.rebuildRecentFilesMenu(state->recentlyOpenedFiles);
+}
+
+static void restoreSettings(MainState* state)
+{
+	state->recentlyOpenedFiles = setting<Setting::RecentFiles>();
+	state->renderPreferences = loadRenderPreferences();
+	state->libraries.restoreFromSettings();
+	updateRecentlyOpenedDocumentsMenu(state);
+	state->colorTable = loadColors(&state->libraries);
+	updateRenderPreferences(&state->mainWindow, &state->renderPreferences, &state->documents);
+	state->mainWindow.mdiArea->setViewMode(setting<Setting::ViewMode>());
+	state->mainWindow.retranslateUi(&state->mainWindow);
+	state->mainWindow.setToolButtonStyle(setting<Setting::ToolButtonStyle>());
+}
+
+static void saveSettings(MainState* state)
+{
+	setSetting<Setting::MainWindowGeometry>(state->mainWindow.saveGeometry());
+	setSetting<Setting::MainWindowState>(state->mainWindow.saveState());
+	setSetting<Setting::RecentFiles>(state->recentlyOpenedFiles);
+	setSetting<Setting::RenderStyle>(state->renderPreferences.style);
+	setSetting<Setting::DrawAxes>(state->renderPreferences.drawAxes);
+	setSetting<Setting::Wireframe>(state->renderPreferences.wireframe);
+	state->libraries.storeToSettings();
+}
+
+static void addRecentlyOpenedFile(MainState* state, const QString& path)
+{
+	constexpr int maxRecentlyOpenedFiles = 10;
+	state->recentlyOpenedFiles.removeAll(path);
+	state->recentlyOpenedFiles.insert(0, path);
+	while (state->recentlyOpenedFiles.size() > maxRecentlyOpenedFiles)
 	{
-		recentlyOpenedFiles = setting<Setting::RecentFiles>();
-		renderPreferences = loadRenderPreferences();
-		libraries.restoreFromSettings();
-		updateRecentlyOpenedDocumentsMenu();
-		colorTable = loadColors(&libraries);
-		updateRenderPreferences(&mainWindow, &renderPreferences, &documents);
-		mainWindow.mdiArea->setViewMode(setting<Setting::ViewMode>());
-		mainWindow.retranslateUi(&mainWindow);
-		mainWindow.setToolButtonStyle(setting<Setting::ToolButtonStyle>());
-		settingsChanged.emit();
-	};
-	const auto addRecentlyOpenedFile = [
-		&recentlyOpenedFiles,
-		&saveSettings,
-		&updateRecentlyOpenedDocumentsMenu]
-		(const QString& path)
+		state->recentlyOpenedFiles.removeLast();
+	}
+	saveSettings(state);
+	updateRecentlyOpenedDocumentsMenu(state);
+}
+
+static void saveCurrentModel(MainState* state, ModelId modelId)
+{
+	QString error;
+	QTextStream errorStream{&error};
+	const bool succeeded = state->documents.saveModel(modelId, errorStream);
+	if (not succeeded)
+	{
+		QMessageBox::critical(&state->mainWindow, QObject::tr("Save error"), error);
+	}
+	else
+	{
+		const QString* pathPtr = state->documents.modelPath(modelId);
+		if (pathPtr != nullptr) {
+			addRecentlyOpenedFile(state, *pathPtr);
+		}
+	}
+}
+
+static void saveCurrentModelAs(MainState* state)
+{
+	const std::optional<ModelId> modelId = findCurrentModelId(&state->mainWindow);
+	if (modelId.has_value())
 	{
-		constexpr int maxRecentlyOpenedFiles = 10;
-		recentlyOpenedFiles.removeAll(path);
-		recentlyOpenedFiles.insert(0, path);
-		while (recentlyOpenedFiles.size() > maxRecentlyOpenedFiles)
-		{
-			recentlyOpenedFiles.removeLast();
+		const QString* pathPtr = state->documents.modelPath(*modelId);
+		QString defaultPath = (pathPtr != nullptr) ? *pathPtr : "";
+		const QString newPath = QFileDialog::getSaveFileName(
+			&state->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};
+			state->documents.setModelPath(*modelId, newPath, state->libraries, errorStream);
+			QMdiSubWindow* const subWindow = state->mainWindow.mdiArea->currentSubWindow();
+			if (subWindow != nullptr) {
+				subWindow->setWindowTitle(tabName(QFileInfo{newPath}));
+			}
+			saveCurrentModel(state, *modelId);
 		}
-		saveSettings();
-		updateRecentlyOpenedDocumentsMenu();
-	};
+	}
+}
+
+static void checkEditingModeAction(MainState* state, const EditingMode mode)
+{
+	const bool hasDocument = currentModelData(&state->mainWindow, &state->documents) != nullptr;
+	for (QAction* action : state->mainWindow.editingModesToolBar->actions())
+	{
+		action->setEnabled(hasDocument);
+		action->setChecked(hasDocument and action->data().value<EditingMode>() == mode);
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	doQtRegistrations();
+	QApplication app{argc, argv};
+	QApplication::setWindowIcon(QIcon{":/icons/appicon.png"});
+	MainState state;
 	QObject::connect(
-		&mainWindow,
+		&state.mainWindow,
 		&MainWindow::recentFileSelected,
-		[&libraries, &documents, &mainWindow, &openModelForEditing, &addRecentlyOpenedFile](const QString& path) {
-			const auto id = openModelFromPath(path, &libraries, &documents, &mainWindow);
+		[&state](const QString& path) {
+			const auto id = openModelFromPath(path, &state.libraries, &state.documents, &state.mainWindow);
 			if (id.has_value())
 			{
-				openModelForEditing(id.value());
-				addRecentlyOpenedFile(path);
+				openModelForEditing(&state, id.value());
+				addRecentlyOpenedFile(&state, path);
 			}
 		}
 	);
-	QObject::connect(mainWindow.actionNew, &QAction::triggered,
-		[&documents, &openModelForEditing]{
-			openModelForEditing(documents.newModel());
+	QObject::connect(state.mainWindow.actionNew, &QAction::triggered,
+		[&state]{
+			openModelForEditing(&state, state.documents.newModel());
 		}
 	);
-	QObject::connect(mainWindow.actionOpen, &QAction::triggered,
-		[&addRecentlyOpenedFile, &documents, &libraries, &mainWindow, &openModelForEditing]
+	QObject::connect(state.mainWindow.actionOpen, &QAction::triggered,
+		[&state]
 		{
-			const QString path = getOpenModelPath(&mainWindow);
+			const QString path = getOpenModelPath(&state.mainWindow);
 			if (not path.isEmpty())
 			{
-				const std::optional<ModelId> id = openModelFromPath(path, &libraries, &documents, &mainWindow);
+				const std::optional<ModelId> id = openModelFromPath(path, &state.libraries, &state.documents, &state.mainWindow);
 				if (id.has_value()) {
-					openModelForEditing(id.value());
-					addRecentlyOpenedFile(path);
+					openModelForEditing(&state, id.value());
+					addRecentlyOpenedFile(&state, path);
 				}
 			}
 		}
 	);
-	QObject::connect(mainWindow.actionSettingsEditor, &QAction::triggered, [&defaultKeyboardShortcuts, &restoreSettings, &settingsChanged, &mainWindow]{
-		if (mainWindow.mdiArea->findChildren<SettingsEditor*>().isEmpty()) {
-			auto* const settingsEditor = createSubWindow<SettingsEditor>(mainWindow.mdiArea, defaultKeyboardShortcuts);
-			QObject::connect(&settingsChanged, &Signal::triggered, settingsEditor, &SettingsEditor::loadSettings);
-			QObject::connect(settingsEditor, &SettingsEditor::settingsChanged, restoreSettings);
+	QObject::connect(state.mainWindow.actionSettingsEditor, &QAction::triggered, [
+		&state,
+		defaultKeyboardShortcuts = uiutilities::makeKeySequenceMap(uiutilities::collectActions(&state.mainWindow))]
+	{
+		if (state.mainWindow.mdiArea->findChildren<SettingsEditor*>().isEmpty())
+		{
+			auto* const settingsEditor = createSubWindow<SettingsEditor>(state.mainWindow.mdiArea, defaultKeyboardShortcuts);
+			QObject::connect(settingsEditor, &SettingsEditor::settingsChanged, [&]{
+				restoreSettings(&state);
+			});
 			settingsEditor->setAttribute(Qt::WA_DeleteOnClose);
 			settingsEditor->show();
 		}
 	});
-	QObject::connect(mainWindow.actionQuit, &QAction::triggered, &mainWindow, &QMainWindow::close);
+	QObject::connect(state.mainWindow.actionQuit, &QAction::triggered, &state.mainWindow, &QMainWindow::close);
 #if 0
 	QObject::connect(ui.actionAdjustGridToView, &QAction::triggered, [&]{
 		if (ModelData* data = currentModelData(&ui, &documents)) {
@@ -539,193 +555,127 @@
 		}
 	});
 #endif
-	QObject::connect(mainWindow.actionClose, &QAction::triggered, [&mainWindow, &documents]{
-		if (ModelData* data = currentModelData(&mainWindow, &documents)) {
+	QObject::connect(state.mainWindow.actionClose, &QAction::triggered, [&state]{
+		if (ModelData* data = currentModelData(&state.mainWindow, &state.documents)) {
 			// TODO
 		}
 	});
-	const auto save = [&addRecentlyOpenedFile, &mainWindow](DocumentManager* documents, 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 = [&documents, &libraries, &mainWindow, &save]{
-		const std::optional<ModelId> modelId = findCurrentModelId(&mainWindow);
-		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 = mainWindow.mdiArea->currentSubWindow();
-				if (subWindow != nullptr) {
-					subWindow->setWindowTitle(tabName(QFileInfo{newPath}));
-				}
-				save(&documents, *modelId);
-			}
-		}
-	};
-	QObject::connect(mainWindow.actionSaveAs, &QAction::triggered, actionSaveAs);
-	QObject::connect(mainWindow.actionSave, &QAction::triggered, [
-		&actionSaveAs,
-		&documents,
-		&save,
-		&mainWindow]
+	QObject::connect(state.mainWindow.actionSaveAs, &QAction::triggered, [&state]{
+		saveCurrentModelAs(&state);
+	});
+	QObject::connect(state.mainWindow.actionSave, &QAction::triggered, [&state]
 	{
-		const std::optional<ModelId> modelId = findCurrentModelId(&mainWindow);
+		const std::optional<ModelId> modelId = findCurrentModelId(&state.mainWindow);
 		if (modelId.has_value()) {
-			const QString* path = documents.modelPath(*modelId);
+			const QString* path = state.documents.modelPath(*modelId);
 			if (path == nullptr or path->isEmpty()) {
-				actionSaveAs();
+				saveCurrentModelAs(&state);
 			}
 			else {
-				save(&documents, *modelId);
+				saveCurrentModel(&state, *modelId);
 			}
 		}
 	});
-	QObject::connect(mainWindow.actionDrawAxes, &QAction::triggered, [
-		&documents,
-		&renderPreferences,
-		&saveSettings,
-		&mainWindow]
+	QObject::connect(state.mainWindow.actionDrawAxes, &QAction::triggered, [&state]
 		(bool drawAxes)
 	{
-		renderPreferences.drawAxes = drawAxes;
-		saveSettings();
-		updateRenderPreferences(&mainWindow, &renderPreferences, &documents);
+		state.renderPreferences.drawAxes = drawAxes;
+		saveSettings(&state);
+		updateRenderPreferences(&state.mainWindow, &state.renderPreferences, &state.documents);
 	});
-	QObject::connect(mainWindow.actionWireframe, &QAction::triggered, [
-		&documents,
-		&renderPreferences,
-		&saveSettings,
-		&mainWindow]
+	QObject::connect(state.mainWindow.actionWireframe, &QAction::triggered, [&state]
 		(bool enabled)
 	{
-		renderPreferences.wireframe = enabled;
-		saveSettings();
-		updateRenderPreferences(&mainWindow, &renderPreferences, &documents);
+		state.renderPreferences.wireframe = enabled;
+		saveSettings(&state);
+		updateRenderPreferences(&state.mainWindow, &state.renderPreferences, &state.documents);
 	});
-	QObject::connect(&mainWindow, &MainWindow::renderStyleSelected, [
-		&documents,
-		&mainWindow,
-		&renderPreferences,
-		&saveSettings]
+	QObject::connect(&state.mainWindow, &MainWindow::renderStyleSelected, [&state]
 		(gl::RenderStyle newStyle)
 	{
-		renderPreferences.style = newStyle;
-		saveSettings();
-		updateRenderPreferences(&mainWindow, &renderPreferences, &documents);
+		state.renderPreferences.style = newStyle;
+		saveSettings(&state);
+		updateRenderPreferences(&state.mainWindow, &state.renderPreferences, &state.documents);
 	});
-	const auto checkEditingModeAction = [&mainWindow, &documents](EditingMode mode) {
-		const bool hasDocument = currentModelData(&mainWindow, &documents) != nullptr;
-		for (QAction* action : mainWindow.editingModesToolBar->actions()) {
-			action->setEnabled(hasDocument);
-			action->setChecked(hasDocument and action->data().value<EditingMode>() == mode);
-		}
-	};
-	initializeTools(&mainWindow, &toolWidgets, &mainWindow);
-	for (QAction* action : mainWindow.editingModesToolBar->actions()) {
-		QObject::connect(action, &QAction::triggered, [
-			action,
-			&checkEditingModeAction,
-			&documents,
-			&mainWindow]
+	initializeTools(&state.mainWindow, &state.toolWidgets, &state.mainWindow);
+	for (QAction* action : state.mainWindow.editingModesToolBar->actions()) {
+		QObject::connect(action, &QAction::triggered, [action, &state]
 		{
-			if (ModelData* data = currentModelData(&mainWindow, &documents)) {
+			if (ModelData* data = currentModelData(&state.mainWindow, &state.documents))
+			{
 				const EditingMode mode = action->data().value<EditingMode>();
 				data->tools->setEditMode(mode);
-				checkEditingModeAction(mode);
+				checkEditingModeAction(&state, mode);
 			}
 		});
 	}
-	QObject::connect(mainWindow.mdiArea, &QMdiArea::subWindowActivated, [
-		&checkEditingModeAction,
-		&documents,
-		&mainWindow,
-		&updateTitle]
-		(QMdiSubWindow* subWindow)
+	QObject::connect(state.mainWindow.mdiArea, &QMdiArea::subWindowActivated,
+		[&state](QMdiSubWindow* subWindow)
 	{
 		ModelSubWindow* modelSubWindow = qobject_cast<ModelSubWindow*>(subWindow);
-		if (modelSubWindow != nullptr) {
-			if (ModelData* data = documents.findPayload<ModelData>(modelSubWindow->modelId)) {
-				checkEditingModeAction(data->tools->currentEditingMode());
-				mainWindow.modelEdit->setDocument(data->model);
-				mainWindow.modelEdit->setTextCursor(*data->textcursor);
-				mainWindow.modelEdit->setFont(codeEditorFontFromSettings());
+		if (modelSubWindow != nullptr)
+		{
+			if (ModelData* data = state.documents.findPayload<ModelData>(modelSubWindow->modelId))
+			{
+				checkEditingModeAction(&state, data->tools->currentEditingMode());
+				state.mainWindow.modelEdit->setDocument(data->model);
+				state.mainWindow.modelEdit->setTextCursor(*data->textcursor);
+				state.mainWindow.modelEdit->setFont(codeEditorFontFromSettings());
 			}
 		}
-		else {
-			checkEditingModeAction(EditingMode::SelectMode);
+		else
+		{
+			checkEditingModeAction(&state, EditingMode::SelectMode);
 		}
-		mainWindow.modelEdit->setEnabled(modelSubWindow != nullptr);
-		updateTitle();
+		state.mainWindow.modelEdit->setEnabled(modelSubWindow != nullptr);
 	});
-	mainWindow.messageLog->setModel(&messageLog);
-	QObject::connect(&documents, &DocumentManager::message, &messageLog, &MessageLog::addMessage);
-	QObject::connect(&messageLog, &MessageLog::rowsAboutToBeInserted, [&mainWindow]{
-		const auto bar = mainWindow.messageLog->verticalScrollBar();
-		mainWindow.messageLog->setProperty("shouldAutoScroll", bar->value() == bar->maximum());
+	state.mainWindow.messageLog->setModel(&state.messageLog);
+	QObject::connect(&state.documents, &DocumentManager::message, &state.messageLog, &MessageLog::addMessage);
+	QObject::connect(&state.messageLog, &MessageLog::rowsAboutToBeInserted, [&state]{
+		const auto bar = state.mainWindow.messageLog->verticalScrollBar();
+		state.mainWindow.messageLog->setProperty("shouldAutoScroll", bar->value() == bar->maximum());
 	});
-	QObject::connect(&messageLog, &MessageLog::rowsInserted, [&mainWindow]{
-		mainWindow.messageLog->resizeRowsToContents();
-		if (mainWindow.messageLog->property("shouldAutoScroll").toBool()) {
-			mainWindow.messageLog->scrollToBottom();
+	QObject::connect(&state.messageLog, &MessageLog::rowsInserted, [&state]{
+		state.mainWindow.messageLog->resizeRowsToContents();
+		if (state.mainWindow.messageLog->property("shouldAutoScroll").toBool()) {
+			state.mainWindow.messageLog->scrollToBottom();
 		}
 	});
 	QObject::connect(
-		toolWidgets.circleToolOptions,
+		state.toolWidgets.circleToolOptions,
 		&CircleToolOptionsWidget::optionsChanged,
-		[&mainWindow, &documents](const CircleToolOptions& options) {
-			if (ModelData* data = currentModelData(&mainWindow, &documents)) {
+		[&state](const CircleToolOptions& options) {
+			if (ModelData* data = currentModelData(&state.mainWindow, &state.documents)) {
 				data->tools->setCircleToolOptions(options);
 			}
 		});
 	QObject::connect(
-		mainWindow.actionMakeUnofficial,
+		state.mainWindow.actionMakeUnofficial,
 		&QAction::triggered,
-		[&documents, &mainWindow]{
-			if (ModelData* data = currentModelData(&mainWindow, &documents)) {
+		[&state]{
+			if (ModelData* data = currentModelData(&state.mainWindow, &state.documents)) {
 				QTextDocument* const model = data->model;
 				for (const ModelAction& action : ldraw::makeUnofficial(model)) {
 					executeAction(model, action);
 				}
 			}
 		});
-	QObject::connect(mainWindow.actionAboutQt, &QAction::triggered, &app, &QApplication::aboutQt);
+	QObject::connect(state.mainWindow.actionAboutQt, &QAction::triggered, &QApplication::aboutQt);
 	QObject::connect(
-		mainWindow.modelEdit,
+		state.mainWindow.modelEdit,
 		&QPlainTextEdit::textChanged,
-		[&documents, &libraries, &mainWindow]{
-			if (ModelData* data = currentModelData(&mainWindow, &documents)) {
-				documents.loadDependenciesForAllModels(libraries);
+		[&state]{
+			if (ModelData* data = currentModelData(&state.mainWindow, &state.documents)) {
+				state.documents.loadDependenciesForAllModels(state.libraries);
 				data->canvas->update();
 			}
 		});
 	QObject::connect(
-		mainWindow.gridMatrix,
+		state.mainWindow.gridMatrix,
 		&MatrixEditor::valueChanged,
 		[&](const glm::mat4& newGridMatrix)
 		{
-			forEachModel(&documents, [&](const void*, const ModelData* data)
+			forEachModel(&state.documents, [&](const void*, const ModelData* data)
 			{
 				if (data->gridLayer != nullptr and data->tools != nullptr and data->canvas != nullptr)
 				{
@@ -737,21 +687,9 @@
 			});
 		}
 	);
-	mainWindow.gridMatrix->setValue(DEFAULT_GRID_MATRIX);
-	mainWindow.tabifyDockWidget(mainWindow.messageLogDock, mainWindow.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(&mainWindow, &renderPreferences, &documents);
-	mainWindow.actionAbout->setText(mainWindow.actionAbout->text().arg(CMAKE_PROJECT_NAME));
-	updateTitle();
-	mainWindow.show();
+	restoreSettings(&state);
+	updateRenderPreferences(&state.mainWindow, &state.renderPreferences, &state.documents);
 	const int result = app.exec();
-	saveSettings();
+	saveSettings(&state);
 	return result;
 }

mercurial