| 372 SubWindow* subWindow = new SubWindow{args..., nullptr}; |
372 SubWindow* subWindow = new SubWindow{args..., nullptr}; |
| 373 mdiArea->addSubWindow(subWindow); |
373 mdiArea->addSubWindow(subWindow); |
| 374 return subWindow; |
374 return subWindow; |
| 375 } |
375 } |
| 376 |
376 |
| |
377 static void executeAction(QTextDocument* model, const ModelAction& action) |
| |
378 { |
| |
379 std::visit(overloaded{ |
| |
380 [model](const AppendToModel& action){ |
| |
381 QTextCursor cursor{model}; |
| |
382 cursor.movePosition(QTextCursor::End); |
| |
383 const QString newText = modelElementToString(action.newElement); |
| |
384 // Make sure we have an empty line |
| |
385 if (not model->lastBlock().text().isEmpty()) { |
| |
386 cursor.insertBlock(); |
| |
387 } |
| |
388 cursor.insertText(newText); |
| |
389 }, |
| |
390 [](const DeleteFromModel&){}, |
| |
391 [model](const ModifyModel& action){ |
| |
392 QTextBlock block = model->findBlockByLineNumber((int) action.position); |
| |
393 if (block.isValid()) { |
| |
394 QTextCursor cursor{block}; |
| |
395 cursor.select(QTextCursor::LineUnderCursor); |
| |
396 cursor.insertText(modelElementToString(action.newElement)); |
| |
397 } |
| |
398 //model->assignAt(action.position, action.newElement); |
| |
399 }, |
| |
400 }, action); |
| |
401 } |
| |
402 |
| 377 int main(int argc, char *argv[]) |
403 int main(int argc, char *argv[]) |
| 378 { |
404 { |
| 379 doQtRegistrations(); |
405 doQtRegistrations(); |
| 380 QApplication app{argc, argv}; |
406 QApplication app{argc, argv}; |
| 381 QApplication::setWindowIcon(QIcon{":/icons/appicon.png"}); |
407 QApplication::setWindowIcon(QIcon{":/icons/appicon.png"}); |
| 397 const auto updateTitle = [&ui, &mainWindow]{ |
423 const auto updateTitle = [&ui, &mainWindow]{ |
| 398 mainWindow.setWindowTitle(title(&ui)); |
424 mainWindow.setWindowTitle(title(&ui)); |
| 399 }; |
425 }; |
| 400 const uiutilities::KeySequenceMap defaultKeyboardShortcuts = |
426 const uiutilities::KeySequenceMap defaultKeyboardShortcuts = |
| 401 uiutilities::makeKeySequenceMap(uiutilities::collectActions(&mainWindow)); |
427 uiutilities::makeKeySequenceMap(uiutilities::collectActions(&mainWindow)); |
| 402 const auto saveSettings = [&]{ |
428 const auto saveSettings = [ |
| |
429 &libraries, |
| |
430 &mainWindow, |
| |
431 &recentlyOpenedFiles, |
| |
432 &renderPreferences, |
| |
433 &settingsChanged] |
| |
434 { |
| 403 setSetting<Setting::MainWindowGeometry>(mainWindow.saveGeometry()); |
435 setSetting<Setting::MainWindowGeometry>(mainWindow.saveGeometry()); |
| 404 setSetting<Setting::MainWindowState>(mainWindow.saveState()); |
436 setSetting<Setting::MainWindowState>(mainWindow.saveState()); |
| 405 setSetting<Setting::RecentFiles>(recentlyOpenedFiles); |
437 setSetting<Setting::RecentFiles>(recentlyOpenedFiles); |
| 406 setSetting<Setting::RenderStyle>(renderPreferences.style); |
438 setSetting<Setting::RenderStyle>(renderPreferences.style); |
| 407 setSetting<Setting::DrawAxes>(renderPreferences.drawAxes); |
439 setSetting<Setting::DrawAxes>(renderPreferences.drawAxes); |
| 408 setSetting<Setting::Wireframe>(renderPreferences.wireframe); |
440 setSetting<Setting::Wireframe>(renderPreferences.wireframe); |
| 409 libraries.storeToSettings(); |
441 libraries.storeToSettings(); |
| 410 settingsChanged.emit(); |
442 settingsChanged.emit(); |
| 411 }; |
443 }; |
| 412 const auto executeAction = [&]( |
444 const auto openModelForEditing = [ |
| 413 QTextDocument* model, const ModelAction& action |
445 &colorTable, |
| 414 ) { |
446 &documents, |
| 415 std::visit(overloaded{ |
447 &mainWindow, |
| 416 [model](const AppendToModel& action){ |
448 &messageLog, |
| 417 QTextCursor cursor{model}; |
449 &renderPreferences, |
| 418 cursor.movePosition(QTextCursor::End); |
450 &settingsChanged, |
| 419 const QString newText = modelElementToString(action.newElement); |
451 &ui] |
| 420 // Make sure we have an empty line |
452 (const ModelId modelId) |
| 421 if (not model->lastBlock().text().isEmpty()) { |
453 { |
| 422 cursor.insertBlock(); |
|
| 423 } |
|
| 424 cursor.insertText(newText); |
|
| 425 }, |
|
| 426 [](const DeleteFromModel&){}, |
|
| 427 [model](const ModifyModel& action){ |
|
| 428 QTextBlock block = model->findBlockByLineNumber((int) action.position); |
|
| 429 if (block.isValid()) { |
|
| 430 QTextCursor cursor{block}; |
|
| 431 cursor.select(QTextCursor::LineUnderCursor); |
|
| 432 cursor.insertText(modelElementToString(action.newElement)); |
|
| 433 } |
|
| 434 //model->assignAt(action.position, actio邉󠄌n.newElement); |
|
| 435 }, |
|
| 436 }, action); |
|
| 437 |
|
| 438 }; |
|
| 439 const auto openModelForEditing = [&](const ModelId modelId){ |
|
| 440 QTextDocument* model = documents.getModelById(modelId); |
454 QTextDocument* model = documents.getModelById(modelId); |
| 441 if (model != nullptr) { |
455 if (model != nullptr) { |
| 442 ModelData* data = new ModelData(&documents); |
456 ModelData* data = new ModelData(&documents); |
| 443 data->tools = std::make_unique<EditTools>(); |
457 data->tools = std::make_unique<EditTools>(); |
| 444 data->canvas = std::make_unique<PartRenderer>(model, &documents, colorTable); |
458 data->canvas = std::make_unique<PartRenderer>(model, &documents, colorTable); |
| 506 subWindow->setWidget(data->canvas.get()); |
520 subWindow->setWidget(data->canvas.get()); |
| 507 subWindow->setWindowTitle(tabName(fileInfo)); |
521 subWindow->setWindowTitle(tabName(fileInfo)); |
| 508 subWindow->show(); |
522 subWindow->show(); |
| 509 } |
523 } |
| 510 }; |
524 }; |
| 511 const auto updateRecentlyOpenedDocumentsMenu = [&]{ |
525 const auto updateRecentlyOpenedDocumentsMenu = [ |
| |
526 &documents, |
| |
527 &libraries, |
| |
528 &mainWindow, |
| |
529 &openModelForEditing, |
| |
530 &recentlyOpenedFiles, |
| |
531 &ui] |
| |
532 { |
| 512 rebuildRecentFilesMenu(ui.menuRecentFiles, recentlyOpenedFiles, &mainWindow); |
533 rebuildRecentFilesMenu(ui.menuRecentFiles, recentlyOpenedFiles, &mainWindow); |
| 513 for (QAction* action : ui.menuRecentFiles->actions()) { |
534 for (QAction* action : ui.menuRecentFiles->actions()) { |
| 514 QString path = action->data().toString(); |
535 QString path = action->data().toString(); |
| 515 QObject::connect( |
536 QObject::connect( |
| 516 action, |
537 action, |
| 535 ui.mdiArea->setViewMode(setting<Setting::ViewMode>()); |
566 ui.mdiArea->setViewMode(setting<Setting::ViewMode>()); |
| 536 ui.retranslateUi(&mainWindow); |
567 ui.retranslateUi(&mainWindow); |
| 537 mainWindow.setToolButtonStyle(setting<Setting::ToolButtonStyle>()); |
568 mainWindow.setToolButtonStyle(setting<Setting::ToolButtonStyle>()); |
| 538 settingsChanged.emit(); |
569 settingsChanged.emit(); |
| 539 }; |
570 }; |
| 540 const auto addRecentlyOpenedFile = [&](const QString& path){ |
571 const auto addRecentlyOpenedFile = [ |
| |
572 &recentlyOpenedFiles, |
| |
573 &saveSettings, |
| |
574 &updateRecentlyOpenedDocumentsMenu] |
| |
575 (const QString& path) |
| |
576 { |
| 541 constexpr int maxRecentlyOpenedFiles = 10; |
577 constexpr int maxRecentlyOpenedFiles = 10; |
| 542 recentlyOpenedFiles.removeAll(path); |
578 recentlyOpenedFiles.removeAll(path); |
| 543 recentlyOpenedFiles.insert(0, path); |
579 recentlyOpenedFiles.insert(0, path); |
| 544 while (recentlyOpenedFiles.size() > maxRecentlyOpenedFiles) |
580 while (recentlyOpenedFiles.size() > maxRecentlyOpenedFiles) |
| 545 { |
581 { |
| 546 recentlyOpenedFiles.removeLast(); |
582 recentlyOpenedFiles.removeLast(); |
| 547 } |
583 } |
| 548 saveSettings(); |
584 saveSettings(); |
| 549 updateRecentlyOpenedDocumentsMenu(); |
585 updateRecentlyOpenedDocumentsMenu(); |
| 550 }; |
586 }; |
| 551 QObject::connect(ui.actionNew, &QAction::triggered, [&]{ |
587 QObject::connect(ui.actionNew, &QAction::triggered, |
| 552 openModelForEditing(documents.newModel()); |
588 [&documents, &openModelForEditing]{ |
| 553 }); |
589 openModelForEditing(documents.newModel()); |
| 554 QObject::connect(ui.actionOpen, &QAction::triggered, [&]{ |
590 } |
| 555 const QString path = getOpenModelPath(&mainWindow); |
591 ); |
| 556 if (not path.isEmpty()) |
592 QObject::connect(ui.actionOpen, &QAction::triggered, |
| |
593 [&addRecentlyOpenedFile, &documents, &libraries, &mainWindow, &openModelForEditing] |
| 557 { |
594 { |
| 558 const std::optional<ModelId> id = openModelFromPath(path, &libraries, &documents, &mainWindow); |
595 const QString path = getOpenModelPath(&mainWindow); |
| 559 if (id.has_value()) { |
596 if (not path.isEmpty()) |
| 560 openModelForEditing(id.value()); |
597 { |
| 561 addRecentlyOpenedFile(path); |
598 const std::optional<ModelId> id = openModelFromPath(path, &libraries, &documents, &mainWindow); |
| 562 } |
599 if (id.has_value()) { |
| 563 } |
600 openModelForEditing(id.value()); |
| 564 }); |
601 addRecentlyOpenedFile(path); |
| 565 QObject::connect(ui.actionSettingsEditor, &QAction::triggered, [&]{ |
602 } |
| |
603 } |
| |
604 } |
| |
605 ); |
| |
606 QObject::connect(ui.actionSettingsEditor, &QAction::triggered, [&defaultKeyboardShortcuts, &restoreSettings, &settingsChanged, &ui]{ |
| 566 if (ui.mdiArea->findChildren<SettingsEditor*>().isEmpty()) { |
607 if (ui.mdiArea->findChildren<SettingsEditor*>().isEmpty()) { |
| 567 auto* const settingsEditor = createSubWindow<SettingsEditor>(ui.mdiArea, defaultKeyboardShortcuts); |
608 auto* const settingsEditor = createSubWindow<SettingsEditor>(ui.mdiArea, defaultKeyboardShortcuts); |
| 568 QObject::connect(&settingsChanged, &Signal::triggered, settingsEditor, &SettingsEditor::loadSettings); |
609 QObject::connect(&settingsChanged, &Signal::triggered, settingsEditor, &SettingsEditor::loadSettings); |
| 569 QObject::connect(settingsEditor, &SettingsEditor::settingsChanged, restoreSettings); |
610 QObject::connect(settingsEditor, &SettingsEditor::settingsChanged, restoreSettings); |
| 570 settingsEditor->setAttribute(Qt::WA_DeleteOnClose); |
611 settingsEditor->setAttribute(Qt::WA_DeleteOnClose); |
| 582 QObject::connect(ui.actionClose, &QAction::triggered, [&ui, &documents]{ |
623 QObject::connect(ui.actionClose, &QAction::triggered, [&ui, &documents]{ |
| 583 if (ModelData* data = currentModelData(&ui, &documents)) { |
624 if (ModelData* data = currentModelData(&ui, &documents)) { |
| 584 // TODO |
625 // TODO |
| 585 } |
626 } |
| 586 }); |
627 }); |
| 587 const auto save = [&](ModelId modelId){ |
628 const auto save = [&addRecentlyOpenedFile, &documents, &mainWindow](ModelId modelId){ |
| 588 QString error; |
629 QString error; |
| 589 QTextStream errorStream{&error}; |
630 QTextStream errorStream{&error}; |
| 590 const bool succeeded = documents.saveModel(modelId, errorStream); |
631 const bool succeeded = documents.saveModel(modelId, errorStream); |
| 591 if (not succeeded) |
632 if (not succeeded) |
| 592 { |
633 { |
| 598 if (pathPtr != nullptr) { |
639 if (pathPtr != nullptr) { |
| 599 addRecentlyOpenedFile(*pathPtr); |
640 addRecentlyOpenedFile(*pathPtr); |
| 600 } |
641 } |
| 601 } |
642 } |
| 602 };; |
643 };; |
| 603 const auto actionSaveAs = [&]{ |
644 const auto actionSaveAs = [&documents, &libraries, &mainWindow, &save, &ui]{ |
| 604 const std::optional<ModelId> modelId = findCurrentModelId(&ui); |
645 const std::optional<ModelId> modelId = findCurrentModelId(&ui); |
| 605 if (modelId.has_value()) |
646 if (modelId.has_value()) |
| 606 { |
647 { |
| 607 const QString* pathPtr = documents.modelPath(*modelId); |
648 const QString* pathPtr = documents.modelPath(*modelId); |
| 608 QString defaultPath = (pathPtr != nullptr) ? *pathPtr : ""; |
649 QString defaultPath = (pathPtr != nullptr) ? *pathPtr : ""; |
| 623 save(*modelId); |
664 save(*modelId); |
| 624 } |
665 } |
| 625 } |
666 } |
| 626 }; |
667 }; |
| 627 QObject::connect(ui.actionSaveAs, &QAction::triggered, actionSaveAs); |
668 QObject::connect(ui.actionSaveAs, &QAction::triggered, actionSaveAs); |
| 628 QObject::connect(ui.actionSave, &QAction::triggered, [&]{ |
669 QObject::connect(ui.actionSave, &QAction::triggered, [ |
| |
670 &actionSaveAs, |
| |
671 &documents, |
| |
672 &save, |
| |
673 &ui] |
| |
674 { |
| 629 const std::optional<ModelId> modelId = findCurrentModelId(&ui); |
675 const std::optional<ModelId> modelId = findCurrentModelId(&ui); |
| 630 if (modelId.has_value()) { |
676 if (modelId.has_value()) { |
| 631 const QString* path = documents.modelPath(*modelId); |
677 const QString* path = documents.modelPath(*modelId); |
| 632 if (path == nullptr or path->isEmpty()) { |
678 if (path == nullptr or path->isEmpty()) { |
| 633 actionSaveAs(); |
679 actionSaveAs(); |
| 635 else { |
681 else { |
| 636 save(*modelId); |
682 save(*modelId); |
| 637 } |
683 } |
| 638 } |
684 } |
| 639 }); |
685 }); |
| 640 QObject::connect(ui.actionDrawAxes, &QAction::triggered, [&](bool drawAxes){ |
686 QObject::connect(ui.actionDrawAxes, &QAction::triggered, [ |
| |
687 &documents, |
| |
688 &renderPreferences, |
| |
689 &saveSettings, |
| |
690 &ui] |
| |
691 (bool drawAxes) |
| |
692 { |
| 641 renderPreferences.drawAxes = drawAxes; |
693 renderPreferences.drawAxes = drawAxes; |
| 642 saveSettings(); |
694 saveSettings(); |
| 643 updateRenderPreferences(&ui, &renderPreferences, &documents); |
695 updateRenderPreferences(&ui, &renderPreferences, &documents); |
| 644 }); |
696 }); |
| 645 QObject::connect(ui.actionWireframe, &QAction::triggered, [&](bool enabled){ |
697 QObject::connect(ui.actionWireframe, &QAction::triggered, [ |
| |
698 &documents, |
| |
699 &renderPreferences, |
| |
700 &saveSettings, |
| |
701 &ui] |
| |
702 (bool enabled) |
| |
703 { |
| 646 renderPreferences.wireframe = enabled; |
704 renderPreferences.wireframe = enabled; |
| 647 saveSettings(); |
705 saveSettings(); |
| 648 updateRenderPreferences(&ui, &renderPreferences, &documents); |
706 updateRenderPreferences(&ui, &renderPreferences, &documents); |
| 649 }); |
707 }); |
| 650 for (auto data : ::renderStyleButtons) { |
708 for (auto data : ::renderStyleButtons) { |
| 662 action->setChecked(hasDocument and action->data().value<EditingMode>() == mode); |
720 action->setChecked(hasDocument and action->data().value<EditingMode>() == mode); |
| 663 } |
721 } |
| 664 }; |
722 }; |
| 665 initializeTools(&ui, &toolWidgets, &mainWindow); |
723 initializeTools(&ui, &toolWidgets, &mainWindow); |
| 666 for (QAction* action : ui.editingModesToolBar->actions()) { |
724 for (QAction* action : ui.editingModesToolBar->actions()) { |
| 667 QObject::connect(action, &QAction::triggered, [&, action]{ |
725 QObject::connect(action, &QAction::triggered, [ |
| |
726 action, |
| |
727 &checkEditingModeAction, |
| |
728 &documents, |
| |
729 &ui] |
| |
730 { |
| 668 if (ModelData* data = currentModelData(&ui, &documents)) { |
731 if (ModelData* data = currentModelData(&ui, &documents)) { |
| 669 const EditingMode mode = action->data().value<EditingMode>(); |
732 const EditingMode mode = action->data().value<EditingMode>(); |
| 670 data->tools->setEditMode(mode); |
733 data->tools->setEditMode(mode); |
| 671 checkEditingModeAction(mode); |
734 checkEditingModeAction(mode); |
| 672 } |
735 } |
| 673 }); |
736 }); |
| 674 } |
737 } |
| 675 QObject::connect(ui.mdiArea, &QMdiArea::subWindowActivated, |
738 QObject::connect(ui.mdiArea, &QMdiArea::subWindowActivated, [ |
| 676 [&](QMdiSubWindow* subWindow) { |
739 &checkEditingModeAction, |
| |
740 &documents, |
| |
741 &ui, |
| |
742 &updateTitle] |
| |
743 (QMdiSubWindow* subWindow) |
| |
744 { |
| 677 ModelSubWindow* modelSubWindow = qobject_cast<ModelSubWindow*>(subWindow); |
745 ModelSubWindow* modelSubWindow = qobject_cast<ModelSubWindow*>(subWindow); |
| 678 if (modelSubWindow != nullptr) { |
746 if (modelSubWindow != nullptr) { |
| 679 if (ModelData* data = documents.findPayload<ModelData>(modelSubWindow->modelId)) { |
747 if (ModelData* data = documents.findPayload<ModelData>(modelSubWindow->modelId)) { |
| 680 checkEditingModeAction(data->tools->currentEditingMode()); |
748 checkEditingModeAction(data->tools->currentEditingMode()); |
| 681 ui.modelEdit->setDocument(data->model); |
749 ui.modelEdit->setDocument(data->model); |
| 689 updateTitle(); |
757 updateTitle(); |
| 690 }); |
758 }); |
| 691 ui.messageLog->setModel(&messageLog); |
759 ui.messageLog->setModel(&messageLog); |
| 692 QObject::connect(ui.actionAboutQt, &QAction::triggered, &app, &QApplication::aboutQt); |
760 QObject::connect(ui.actionAboutQt, &QAction::triggered, &app, &QApplication::aboutQt); |
| 693 QObject::connect(&documents, &DocumentManager::message, &messageLog, &MessageLog::addMessage); |
761 QObject::connect(&documents, &DocumentManager::message, &messageLog, &MessageLog::addMessage); |
| 694 QObject::connect(&messageLog, &MessageLog::rowsAboutToBeInserted, [&]{ |
762 QObject::connect(&messageLog, &MessageLog::rowsAboutToBeInserted, [&ui]{ |
| 695 const auto bar = ui.messageLog->verticalScrollBar(); |
763 const auto bar = ui.messageLog->verticalScrollBar(); |
| 696 ui.messageLog->setProperty("shouldAutoScroll", bar->value() == bar->maximum()); |
764 ui.messageLog->setProperty("shouldAutoScroll", bar->value() == bar->maximum()); |
| 697 }); |
765 }); |
| 698 QObject::connect(&messageLog, &MessageLog::rowsInserted, [&]{ |
766 QObject::connect(&messageLog, &MessageLog::rowsInserted, [&ui]{ |
| 699 ui.messageLog->resizeRowsToContents(); |
767 ui.messageLog->resizeRowsToContents(); |
| 700 if (ui.messageLog->property("shouldAutoScroll").toBool()) { |
768 if (ui.messageLog->property("shouldAutoScroll").toBool()) { |
| 701 ui.messageLog->scrollToBottom(); |
769 ui.messageLog->scrollToBottom(); |
| 702 } |
770 } |
| 703 }); |
771 }); |