1 #include <QApplication> |
1 #include <QApplication> |
2 #include <QFileDialog> |
2 #include <QFileDialog> |
3 #include <QMessageBox> |
3 #include <QMessageBox> |
4 #include <QMdiSubWindow> |
4 #include <QMdiSubWindow> |
5 #include <QStackedWidget> |
5 #include <QStackedWidget> |
|
6 #include <QCloseEvent> |
6 #include "mainwindow.h" |
7 #include "mainwindow.h" |
7 #include "ui_mainwindow.h" |
8 #include "ui_mainwindow.h" |
8 #include "version.h" |
9 #include "version.h" |
|
10 #include "ui/canvas.h" |
9 #include "document.h" |
11 #include "document.h" |
10 #include "settingseditor/settingseditor.h" |
12 #include "settingseditor/settingseditor.h" |
11 #include "widgets/colorselectdialog.h" |
13 #include "widgets/colorselectdialog.h" |
12 |
14 |
13 static const QDir LOCALE_DIR {":/locale"}; |
15 static const QDir LOCALE_DIR {":/locale"}; |
|
16 |
|
17 class ModelSubWindow : public QMdiSubWindow |
|
18 { |
|
19 Q_OBJECT |
|
20 public: |
|
21 const ModelId modelId; |
|
22 ModelSubWindow(ModelId modelId, QWidget* widget) : |
|
23 QMdiSubWindow{widget}, |
|
24 modelId{modelId} |
|
25 { |
|
26 } |
|
27 protected: |
|
28 void closeEvent(QCloseEvent* event) override |
|
29 { |
|
30 event->ignore(); |
|
31 } |
|
32 }; |
14 |
33 |
15 static void doQtRegistrations() |
34 static void doQtRegistrations() |
16 { |
35 { |
17 QCoreApplication::setApplicationName(::appName); |
36 QCoreApplication::setApplicationName(::appName); |
18 QCoreApplication::setOrganizationName("hecknology.net"); |
37 QCoreApplication::setOrganizationName("hecknology.net"); |
29 constexpr MemberType memberInstance(BaseType* instance) const |
48 constexpr MemberType memberInstance(BaseType* instance) const |
30 { |
49 { |
31 return *reinterpret_cast<MemberType*>(reinterpret_cast<char*>(instance) + this->member); |
50 return *reinterpret_cast<MemberType*>(reinterpret_cast<char*>(instance) + this->member); |
32 } |
51 } |
33 }; |
52 }; |
|
53 |
|
54 class ModelData : public QObject |
|
55 { |
|
56 Q_OBJECT |
|
57 public: |
|
58 ModelData(QObject* parent) : QObject {parent} {} |
|
59 std::unique_ptr<Canvas> canvas; |
|
60 std::unique_ptr<QItemSelectionModel> itemSelectionModel; |
|
61 std::unique_ptr<EditTools> tools; |
|
62 Model* model; |
|
63 }; |
|
64 #include "main.moc" |
34 |
65 |
35 static constexpr MemberData<Ui_MainWindow, QAction*, gl::RenderStyle> renderStyleButtons[] = { |
66 static constexpr MemberData<Ui_MainWindow, QAction*, gl::RenderStyle> renderStyleButtons[] = { |
36 { offsetof(Ui_MainWindow, actionRenderStyleNormal), gl::RenderStyle::Normal }, |
67 { offsetof(Ui_MainWindow, actionRenderStyleNormal), gl::RenderStyle::Normal }, |
37 { offsetof(Ui_MainWindow, actionRenderStyleBfc), gl::RenderStyle::BfcRedGreen }, |
68 { offsetof(Ui_MainWindow, actionRenderStyleBfc), gl::RenderStyle::BfcRedGreen }, |
38 { offsetof(Ui_MainWindow, actionRenderStyleRandom), gl::RenderStyle::RandomColors }, |
69 { offsetof(Ui_MainWindow, actionRenderStyleRandom), gl::RenderStyle::RandomColors }, |
105 qApp->installTranslator(translator); |
136 qApp->installTranslator(translator); |
106 } |
137 } |
107 } |
138 } |
108 } |
139 } |
109 |
140 |
110 /* |
141 ModelData* findModelData(DocumentManager* documents, ModelId modelId) |
111 void MainWindow::handleDocumentSplitterChange() |
142 { |
112 { |
143 return documents->findPayload<ModelData>(modelId); |
113 EditorTabWidget* currentDocument = this->currentDocument(); |
144 } |
114 if (currentDocument != nullptr) |
145 |
115 { |
146 static ModelSubWindow* currentModelSubWindow(Ui_MainWindow* ui) |
116 this->documentSplitterState = currentDocument->saveSplitterState(); |
147 { |
117 for (int i = 0; i < this->ui->tabs->count(); i += 1) |
148 return qobject_cast<ModelSubWindow*>(ui->mdiArea->activeSubWindow()); |
118 { |
149 } |
119 EditorTabWidget* document = qobject_cast<EditorTabWidget*>(this->ui->tabs->widget(i)); |
150 |
120 if (document != nullptr and document != currentDocument) |
151 static ModelData* currentModelData(Ui_MainWindow* ui, DocumentManager* documents) |
121 { |
152 { |
122 document->restoreSplitterState(this->documentSplitterState); |
153 if (auto* const activeSubWindow = currentModelSubWindow(ui)) { |
123 } |
154 return findModelData(documents, activeSubWindow->modelId); |
124 } |
155 } |
125 this->settings.setMainSplitterState(this->documentSplitterState); |
156 else { |
126 } |
|
127 } |
|
128 */ |
|
129 |
|
130 static EditorTabWidget* currentTabWidget(Ui_MainWindow* ui) |
|
131 { |
|
132 QMdiSubWindow* activeSubWindow = ui->mdiArea->activeSubWindow(); |
|
133 if (activeSubWindow == nullptr) { |
|
134 return nullptr; |
157 return nullptr; |
135 } |
158 } |
|
159 } |
|
160 |
|
161 static Model* currentModelBody(Ui_MainWindow* ui, DocumentManager* documents) |
|
162 { |
|
163 if (auto* const activeSubWindow = currentModelSubWindow(ui)) { |
|
164 return documents->getModelById(activeSubWindow->modelId); |
|
165 } |
136 else { |
166 else { |
137 return qobject_cast<EditorTabWidget*>(activeSubWindow->widget()); |
167 return nullptr; |
138 } |
168 } |
139 }; |
169 } |
140 |
170 |
141 |
171 static std::optional<ModelId> findCurrentModelId(Ui_MainWindow* ui) |
142 static void closeDocument(DocumentManager* documents, EditorTabWidget *document) |
172 { |
143 { |
173 ModelSubWindow* activeSubWindow = qobject_cast<ModelSubWindow*>(ui->mdiArea->activeSubWindow()); |
144 std::optional<ModelId> modelId = documents->findIdForModel(document->model); |
174 if (activeSubWindow != nullptr) { |
145 if (modelId.has_value()) { |
175 return activeSubWindow->modelId; |
146 documents->closeDocument(modelId.value()); |
|
147 delete document; |
|
148 } |
|
149 } |
|
150 |
|
151 static std::optional<ModelId> findCurrentModelId(Ui_MainWindow* ui, DocumentManager* documents) |
|
152 { |
|
153 const EditorTabWidget* tab = currentTabWidget(ui); |
|
154 if (tab != nullptr) { |
|
155 return documents->findIdForModel(tab->model); |
|
156 } |
176 } |
157 else { |
177 else { |
158 return {}; |
178 return {}; |
159 } |
179 } |
160 } |
180 } |
194 action->setData(path); |
214 action->setData(path); |
195 menu->addAction(action); |
215 menu->addAction(action); |
196 } |
216 } |
197 } |
217 } |
198 |
218 |
|
219 template<typename Fn> |
|
220 static void forModel(const DocumentManager* documents, Fn&& fn) |
|
221 { |
|
222 forValueInMap(*documents, [&fn](const DocumentManager::ModelInfo& info) |
|
223 { |
|
224 ModelData* modelSpecificData = qobject_cast<ModelData*>(info.payload); |
|
225 if (modelSpecificData != nullptr) { |
|
226 fn(&info, modelSpecificData); |
|
227 } |
|
228 }); |
|
229 } |
|
230 |
199 static void updateRenderPreferences( |
231 static void updateRenderPreferences( |
200 Ui_MainWindow* ui, |
232 Ui_MainWindow* ui, |
201 const gl::RenderPreferences* renderPreferences) |
233 const gl::RenderPreferences* renderPreferences, |
202 { |
234 const DocumentManager* documents) |
203 for (QMdiSubWindow* subWindow : ui->mdiArea->subWindowList()) { |
235 { |
204 EditorTabWidget* tab = qobject_cast<EditorTabWidget*>(subWindow->widget()); |
236 forModel(documents, [&renderPreferences](const void*, const ModelData* data){ |
205 if (tab != nullptr) { |
237 if (data->canvas != nullptr) { |
206 tab->canvas->setRenderPreferences(*renderPreferences); |
238 data->canvas->setRenderPreferences(*renderPreferences); |
207 } |
239 } |
208 } |
240 }); |
209 for (auto data : ::renderStyleButtons) { |
241 for (auto data : ::renderStyleButtons) { |
210 QAction* action = data.memberInstance(ui); |
242 QAction* action = data.memberInstance(ui); |
211 action->setChecked(renderPreferences->style == data.payload); |
243 action->setChecked(renderPreferences->style == data.payload); |
212 } |
244 } |
213 ui->actionDrawAxes->setChecked(renderPreferences->drawAxes); |
245 ui->actionDrawAxes->setChecked(renderPreferences->drawAxes); |
338 renderPreferences = loadRenderPreferences(&settings); |
369 renderPreferences = loadRenderPreferences(&settings); |
339 changeLanguage(settings.locale(), &translator); |
370 changeLanguage(settings.locale(), &translator); |
340 libraries.restoreFromSettings(&settings); |
371 libraries.restoreFromSettings(&settings); |
341 updateRecentlyOpenedDocumentsMenu(); |
372 updateRecentlyOpenedDocumentsMenu(); |
342 colorTable = loadColors(&libraries); |
373 colorTable = loadColors(&libraries); |
343 updateRenderPreferences(&ui, &renderPreferences); |
374 updateRenderPreferences(&ui, &renderPreferences, &documents); |
344 ui.mdiArea->setViewMode(static_cast<QMdiArea::ViewMode>(settings.viewMode())); |
375 ui.mdiArea->setViewMode(static_cast<QMdiArea::ViewMode>(settings.viewMode())); |
345 ui.retranslateUi(&mainWindow); |
376 ui.retranslateUi(&mainWindow); |
346 }; |
377 }; |
347 const auto addRecentlyOpenedFile = [&](const QString& path){ |
378 const auto addRecentlyOpenedFile = [&](const QString& path){ |
348 constexpr int maxRecentlyOpenedFiles = 10; |
379 constexpr int maxRecentlyOpenedFiles = 10; |
355 saveSettings(); |
386 saveSettings(); |
356 updateRecentlyOpenedDocumentsMenu(); |
387 updateRecentlyOpenedDocumentsMenu(); |
357 }; |
388 }; |
358 const auto openModelForEditing = [&](const ModelId modelId){ |
389 const auto openModelForEditing = [&](const ModelId modelId){ |
359 Model* model = documents.getModelById(modelId); |
390 Model* model = documents.getModelById(modelId); |
360 EditorTabWidget* document = new EditorTabWidget{model, &documents, colorTable}; |
391 if (model != nullptr) { |
361 QObject::connect( |
392 ModelData* data = new ModelData(&documents); |
362 document, |
393 data->tools = std::make_unique<EditTools>(model, colorTable); |
363 &EditorTabWidget::modelAction, |
394 data->canvas = std::make_unique<Canvas>(model, data->tools.get(), &documents, colorTable); |
364 std::bind(executeAction, model, std::placeholders::_1)); |
395 data->itemSelectionModel = std::make_unique<QItemSelectionModel>(model); |
365 QItemSelectionModel* selectionModel = new QItemSelectionModel{model}; |
396 data->model = model; |
366 itemSelectionModels[model] = selectionModel; |
397 QObject::connect( |
367 QObject::connect(selectionModel, &QItemSelectionModel::selectionChanged, |
398 data->tools.get(), |
368 [model, document](const QItemSelection& selected, const QItemSelection& deselected) |
399 &EditTools::modelAction, |
369 { |
400 std::bind(executeAction, model, std::placeholders::_1)); |
370 auto resolveIndex = [&model](const QModelIndex& index){ |
401 QObject::connect(data->itemSelectionModel.get(), &QItemSelectionModel::selectionChanged, |
371 return model->idAt(index.row()); |
402 [modelId, &documents]( |
372 }; |
403 const QItemSelection& selected, |
373 auto resolve = [&resolveIndex](const QItemSelection& selection) |
404 const QItemSelection& deselected) |
374 { |
405 { |
375 return fn::map<QSet<ModelId>>(selection.indexes(), resolveIndex); |
406 ModelData* data = findModelData(&documents, modelId); |
376 }; |
407 if (data != nullptr) { |
377 document->canvas->handleSelectionChange(resolve(selected), resolve(deselected)); |
408 auto resolveIndex = [&data](const QModelIndex& index){ |
378 }); |
409 return data->model->idAt(index.row()); |
379 document->canvas->setRenderPreferences(renderPreferences); |
410 }; |
380 QObject::connect( |
411 auto resolve = [&resolveIndex](const QItemSelection& selection) |
381 document, |
412 { |
382 &EditorTabWidget::newStatusText, |
413 return fn::map<QSet<ModelId>>(selection.indexes(), resolveIndex); |
383 [&](const QString& newStatusText) { |
414 }; |
384 mainWindow.statusBar()->showMessage(newStatusText); |
415 data->canvas->handleSelectionChange(resolve(selected), resolve(deselected)); |
|
416 } |
385 }); |
417 }); |
386 const QFileInfo fileInfo{*documents.modelPath(modelId)}; |
418 data->canvas->setRenderPreferences(renderPreferences); |
387 QMdiSubWindow* subWindow = ui.mdiArea->addSubWindow(document); |
419 QObject::connect( |
388 subWindow->setWindowTitle(tabName(fileInfo)); |
420 data->tools.get(), |
389 subWindow->show(); |
421 &EditTools::newStatusText, |
|
422 [&](const QString& newStatusText) { |
|
423 mainWindow.statusBar()->showMessage(newStatusText); |
|
424 }); |
|
425 const QFileInfo fileInfo{*documents.modelPath(modelId)}; |
|
426 QMdiSubWindow* subWindow = ui.mdiArea->addSubWindow(data->canvas.get()); |
|
427 subWindow->setWindowTitle(tabName(fileInfo)); |
|
428 subWindow->show(); |
|
429 } |
390 }; |
430 }; |
391 QObject::connect(ui.actionNew, &QAction::triggered, [&]{ |
431 QObject::connect(ui.actionNew, &QAction::triggered, [&]{ |
392 openModelForEditing(documents.newModel()); |
432 openModelForEditing(documents.newModel()); |
393 }); |
433 }); |
394 QObject::connect(ui.actionOpen, &QAction::triggered, [&]{ |
434 QObject::connect(ui.actionOpen, &QAction::triggered, [&]{ |
409 { |
449 { |
410 restoreSettings(); |
450 restoreSettings(); |
411 } |
451 } |
412 }); |
452 }); |
413 QObject::connect(ui.actionQuit, &QAction::triggered, &mainWindow, &QMainWindow::close); |
453 QObject::connect(ui.actionQuit, &QAction::triggered, &mainWindow, &QMainWindow::close); |
414 QObject::connect(ui.actionAdjustGridToView, &QAction::triggered, [&ui]{ |
454 QObject::connect(ui.actionAdjustGridToView, &QAction::triggered, [&]{ |
415 EditorTabWidget* tab = currentTabWidget(&ui); |
455 if (ModelData* data = currentModelData(&ui, &documents)) { |
416 if (tab != nullptr) |
456 adjustGridToView(data->canvas.get()); |
417 { |
|
418 adjustGridToView(tab->canvas); |
|
419 } |
457 } |
420 }); |
458 }); |
421 QObject::connect(ui.actionClose, &QAction::triggered, [&ui, &documents]{ |
459 QObject::connect(ui.actionClose, &QAction::triggered, [&ui, &documents]{ |
422 EditorTabWidget* tab = currentTabWidget(&ui); |
460 if (ModelData* data = currentModelData(&ui, &documents)) { |
423 if (tab != nullptr) |
461 // TODO |
424 { |
|
425 closeDocument(&documents, tab); |
|
426 } |
462 } |
427 }); |
463 }); |
428 const auto save = [&](ModelId modelId){ |
464 const auto save = [&](ModelId modelId){ |
429 QString error; |
465 QString error; |
430 QTextStream errorStream{&error}; |
466 QTextStream errorStream{&error}; |
465 } |
501 } |
466 } |
502 } |
467 }; |
503 }; |
468 QObject::connect(ui.actionSaveAs, &QAction::triggered, actionSaveAs); |
504 QObject::connect(ui.actionSaveAs, &QAction::triggered, actionSaveAs); |
469 QObject::connect(ui.actionSave, &QAction::triggered, [&]{ |
505 QObject::connect(ui.actionSave, &QAction::triggered, [&]{ |
470 if (currentTabWidget(&ui) != nullptr) { |
506 const std::optional<ModelId> modelId = findCurrentModelId(&ui); |
471 const std::optional<ModelId> modelId = findCurrentModelId(&ui, &documents); |
507 if (modelId.has_value()) { |
472 if (modelId.has_value()) { |
508 const QString* path = documents.modelPath(*modelId); |
473 const QString* path = documents.modelPath(*modelId); |
509 if (path == nullptr or path->isEmpty()) { |
474 if (path == nullptr or path->isEmpty()) { |
510 actionSaveAs(); |
475 actionSaveAs(); |
511 } |
476 } |
512 else { |
477 else { |
513 save(*modelId); |
478 save(*modelId); |
|
479 } |
|
480 } |
514 } |
481 } |
515 } |
482 }); |
516 }); |
483 QObject::connect(ui.actionDelete, &QAction::triggered, [&]{ |
517 QObject::connect(ui.actionDelete, &QAction::triggered, [&]{ |
484 EditorTabWidget* tab = currentTabWidget(&ui); |
518 if (Model* model = currentModelBody(&ui, &documents)) { |
485 if (tab != nullptr) { |
|
486 std::vector<int> selectedRows = rows(ui.modelListView->selectionModel()->selectedRows()); |
519 std::vector<int> selectedRows = rows(ui.modelListView->selectionModel()->selectedRows()); |
487 std::sort(selectedRows.begin(), selectedRows.end(), std::greater<int>{}); |
520 std::sort(selectedRows.begin(), selectedRows.end(), std::greater<int>{}); |
488 for (int row : selectedRows) { |
521 for (int row : selectedRows) { |
489 executeAction(tab->model, DeleteFromModel{.position = row}); |
522 executeAction(model, DeleteFromModel{.position = row}); |
490 } |
523 } |
491 } |
524 } |
492 }); |
525 }); |
493 QObject::connect(ui.actionDrawAxes, &QAction::triggered, [&](bool drawAxes){ |
526 QObject::connect(ui.actionDrawAxes, &QAction::triggered, [&](bool drawAxes){ |
494 renderPreferences.drawAxes = drawAxes; |
527 renderPreferences.drawAxes = drawAxes; |
495 saveSettings(); |
528 saveSettings(); |
496 updateRenderPreferences(&ui, &renderPreferences); |
529 updateRenderPreferences(&ui, &renderPreferences, &documents); |
497 }); |
530 }); |
498 for (auto data : ::renderStyleButtons) { |
531 for (auto data : ::renderStyleButtons) { |
499 QAction* action = data.memberInstance(&ui); |
532 QAction* action = data.memberInstance(&ui); |
500 QObject::connect(action, &QAction::triggered, [&, data]{ |
533 QObject::connect(action, &QAction::triggered, [&, data]{ |
501 renderPreferences.style = data.payload; |
534 renderPreferences.style = data.payload; |
502 saveSettings(); |
535 saveSettings(); |
503 updateRenderPreferences(&ui, &renderPreferences); |
536 updateRenderPreferences(&ui, &renderPreferences, &documents); |
504 }); |
537 }); |
505 } |
538 } |
506 const auto checkEditingModeAction = [&ui](EditingMode mode) { |
539 const auto checkEditingModeAction = [&ui](EditingMode mode) { |
507 for (QAction* action : ui.editingModesToolBar->actions()) { |
540 for (QAction* action : ui.editingModesToolBar->actions()) { |
508 action->setChecked(action->data().value<EditingMode>() == mode); |
541 action->setChecked(action->data().value<EditingMode>() == mode); |
509 } |
542 } |
510 }; |
543 }; |
511 initializeTools(&ui, &mainWindow); |
544 initializeTools(&ui, &mainWindow); |
512 for (QAction* action : ui.editingModesToolBar->actions()) { |
545 for (QAction* action : ui.editingModesToolBar->actions()) { |
513 QObject::connect(action, &QAction::triggered, [&, action]{ |
546 QObject::connect(action, &QAction::triggered, [&, action]{ |
514 EditorTabWidget* tab = currentTabWidget(&ui); |
547 if (ModelData* data = currentModelData(&ui, &documents)) { |
515 if (tab != nullptr) { |
|
516 const EditingMode mode = action->data().value<EditingMode>(); |
548 const EditingMode mode = action->data().value<EditingMode>(); |
517 tab->setEditMode(mode); |
549 data->tools->setEditMode(mode); |
518 checkEditingModeAction(mode); |
550 checkEditingModeAction(mode); |
519 } |
551 } |
520 }); |
552 }); |
521 } |
553 } |
522 QObject::connect(ui.mdiArea, &QMdiArea::subWindowActivated, |
554 QObject::connect(ui.mdiArea, &QMdiArea::subWindowActivated, |
523 [&](QMdiSubWindow* subWindow) { |
555 [&](QMdiSubWindow* subWindow) { |
524 if (subWindow != nullptr) { |
556 ModelSubWindow* modelSubWindow = qobject_cast<ModelSubWindow*>(subWindow); |
525 EditorTabWidget* tab = qobject_cast<EditorTabWidget*>(subWindow->widget()); |
557 if (modelSubWindow != nullptr) { |
526 if (tab != nullptr) { |
558 if (ModelData* data = documents.findPayload<ModelData>(modelSubWindow->modelId)) { |
527 checkEditingModeAction(tab->currentEditingMode()); |
559 checkEditingModeAction(data->tools->currentEditingMode()); |
528 QItemSelectionModel* selectionModel = itemSelectionModels.value(tab->model); |
560 if (data->itemSelectionModel != nullptr) { |
529 if (selectionModel != nullptr) { |
561 ui.modelListView->setModel(data->model); |
530 ui.modelListView->setModel(tab->model); |
562 ui.modelListView->setSelectionModel(data->itemSelectionModel.get()); |
531 ui.modelListView->setSelectionModel(selectionModel); |
|
532 } |
563 } |
533 } |
564 } |
534 } |
565 } |
535 }); |
566 }); |
536 mainWindow.setWindowTitle(title()); |
567 mainWindow.setWindowTitle(title()); |
537 mainWindow.restoreGeometry(settings.mainWindowGeometry()); |
568 mainWindow.restoreGeometry(settings.mainWindowGeometry()); |
538 restoreSettings(); |
569 restoreSettings(); |
539 updateRenderPreferences(&ui, &renderPreferences); |
570 updateRenderPreferences(&ui, &renderPreferences, &documents); |
540 mainWindow.show(); |
571 mainWindow.show(); |
541 const int result = app.exec(); |
572 const int result = app.exec(); |
542 saveSettings(); |
573 saveSettings(); |
543 return result; |
574 return result; |
544 } |
575 } |