1 // This file is part of Heimer.
2 // Copyright (C) 2018 Jussi Lind <jussi.lind@iki.fi>
3 //
4 // Heimer is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 // Heimer is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Heimer. If not, see <http://www.gnu.org/licenses/>.
15 
16 #include "main_window.hpp"
17 
18 #include "about_dlg.hpp"
19 #include "constants.hpp"
20 #include "mediator.hpp"
21 #include "node_action.hpp"
22 #include "recent_files_manager.hpp"
23 #include "recent_files_menu.hpp"
24 #include "settings.hpp"
25 #include "settings_dialog.hpp"
26 #include "simple_logger.hpp"
27 #include "whats_new_dlg.hpp"
28 #include "widget_factory.hpp"
29 
30 #include <QAction>
31 #include <QApplication>
32 #include <QCheckBox>
33 #include <QDoubleSpinBox>
34 #include <QFontDialog>
35 #include <QHBoxLayout>
36 #include <QLabel>
37 #include <QLineEdit>
38 #include <QMenu>
39 #include <QMenuBar>
40 #include <QMessageBox>
41 #include <QPushButton>
42 #include <QScreen>
43 #include <QSpinBox>
44 #include <QToolBar>
45 #include <QVBoxLayout>
46 #include <QWidgetAction>
47 
48 #include <cassert>
49 
50 MainWindow * MainWindow::m_instance = nullptr;
51 
52 namespace {
53 static const auto threeDots = "...";
54 }
55 
MainWindow()56 MainWindow::MainWindow()
57   : m_aboutDlg(new AboutDlg(this))
58   , m_settingsDlg(new SettingsDialog(this))
59   , m_whatsNewDlg(new WhatsNewDlg(this))
60   , m_connectSelectedNodesAction(new QAction(tr("Connect selected nodes"), this))
61   , m_disconnectSelectedNodesAction(new QAction(tr("Disconnect selected nodes"), this))
62   , m_saveAction(new QAction(tr("&Save"), this))
63   , m_saveAsAction(new QAction(tr("&Save as") + threeDots, this))
64   , m_undoAction(new QAction(tr("Undo"), this))
65   , m_redoAction(new QAction(tr("Redo"), this))
66   , m_edgeWidthSpinBox(new QDoubleSpinBox(this))
67   , m_cornerRadiusSpinBox(new QSpinBox(this))
68   , m_fontButton(new QPushButton(this))
69   , m_gridSizeSpinBox(new QSpinBox(this))
70   , m_textSizeSpinBox(new QSpinBox(this))
71   , m_copyOnDragCheckBox(new QCheckBox(tr("Copy on drag"), this))
72   , m_showGridCheckBox(new QCheckBox(tr("Show grid"), this))
73   , m_searchLineEdit(new QLineEdit(this))
74 {
75     if (!m_instance) {
76         m_instance = this;
77     } else {
78         qFatal("MainWindow already instantiated!");
79     }
80 }
81 
addConnectSelectedNodesAction(QMenu & menu)82 void MainWindow::addConnectSelectedNodesAction(QMenu & menu)
83 {
84     m_connectSelectedNodesAction->setShortcut(QKeySequence("Ctrl+Shift+C"));
85     connect(m_connectSelectedNodesAction, &QAction::triggered, [this] {
86         juzzlin::L().debug() << "Connect selected triggered";
87         m_mediator->performNodeAction({ NodeAction::Type::ConnectSelected });
88     });
89     menu.addAction(m_connectSelectedNodesAction);
90     connect(&menu, &QMenu::aboutToShow, [=] {
91         m_connectSelectedNodesAction->setEnabled(m_mediator->areSelectedNodesConnectable());
92     });
93 }
94 
addDisconnectSelectedNodesAction(QMenu & menu)95 void MainWindow::addDisconnectSelectedNodesAction(QMenu & menu)
96 {
97     m_disconnectSelectedNodesAction->setShortcut(QKeySequence("Ctrl+Shift+D"));
98     connect(m_disconnectSelectedNodesAction, &QAction::triggered, [this] {
99         juzzlin::L().debug() << "Disconnect selected triggered";
100         m_mediator->performNodeAction({ NodeAction::Type::DisconnectSelected });
101     });
102     menu.addAction(m_disconnectSelectedNodesAction);
103     connect(&menu, &QMenu::aboutToShow, [=] {
104         m_disconnectSelectedNodesAction->setEnabled(m_mediator->areSelectedNodesDisconnectable());
105     });
106 }
107 
addRedoAction(QMenu & menu)108 void MainWindow::addRedoAction(QMenu & menu)
109 {
110     m_redoAction->setShortcut(QKeySequence(QKeySequence::Redo));
111 
112     connect(m_redoAction, &QAction::triggered, [=] {
113         m_mediator->redo();
114     });
115 
116     m_redoAction->setEnabled(false);
117 
118     menu.addAction(m_redoAction);
119 }
120 
addUndoAction(QMenu & menu)121 void MainWindow::addUndoAction(QMenu & menu)
122 {
123     m_undoAction->setShortcut(QKeySequence(QKeySequence::Undo));
124 
125     connect(m_undoAction, &QAction::triggered, [=] {
126         m_mediator->undo();
127     });
128 
129     m_undoAction->setEnabled(false);
130 
131     menu.addAction(m_undoAction);
132 }
133 
createEditMenu()134 void MainWindow::createEditMenu()
135 {
136     const auto editMenu = menuBar()->addMenu(tr("&Edit"));
137 
138     addUndoAction(*editMenu);
139 
140     addRedoAction(*editMenu);
141 
142     editMenu->addSeparator();
143 
144     addConnectSelectedNodesAction(*editMenu);
145 
146     addDisconnectSelectedNodesAction(*editMenu);
147 
148     editMenu->addSeparator();
149 
150     const auto colorMenu = new QMenu;
151     const auto colorMenuAction = editMenu->addMenu(colorMenu);
152     colorMenuAction->setText(tr("General &colors"));
153 
154     const auto backgroundColorAction = new QAction(tr("Set background color") + threeDots, this);
155     backgroundColorAction->setShortcut(QKeySequence("Ctrl+B"));
156 
157     connect(backgroundColorAction, &QAction::triggered, [=] {
158         emit actionTriggered(StateMachine::Action::BackgroundColorChangeRequested);
159     });
160 
161     colorMenu->addAction(backgroundColorAction);
162 
163     colorMenu->addSeparator();
164 
165     const auto edgeColorAction = new QAction(tr("Set edge color") + threeDots, this);
166     edgeColorAction->setShortcut(QKeySequence("Ctrl+E"));
167 
168     connect(edgeColorAction, &QAction::triggered, [=] {
169         emit actionTriggered(StateMachine::Action::EdgeColorChangeRequested);
170     });
171 
172     colorMenu->addAction(edgeColorAction);
173 
174     colorMenu->addSeparator();
175 
176     const auto gridColorAction = new QAction(tr("Set grid color") + threeDots, this);
177     gridColorAction->setShortcut(QKeySequence("Ctrl+G"));
178 
179     connect(gridColorAction, &QAction::triggered, [=] {
180         emit actionTriggered(StateMachine::Action::GridColorChangeRequested);
181     });
182 
183     colorMenu->addAction(gridColorAction);
184 
185     editMenu->addSeparator();
186 
187     auto optimizeLayoutAction = new QAction(tr("Optimize layout") + threeDots, this);
188     optimizeLayoutAction->setShortcut(QKeySequence("Ctrl+Shift+O"));
189 
190     connect(optimizeLayoutAction, &QAction::triggered, [=] {
191         emit actionTriggered(StateMachine::Action::LayoutOptimizationRequested);
192     });
193 
194     editMenu->addAction(optimizeLayoutAction);
195 }
196 
createCornerRadiusAction()197 QWidgetAction * MainWindow::createCornerRadiusAction()
198 {
199     m_cornerRadiusSpinBox->setMinimum(0);
200     m_cornerRadiusSpinBox->setMaximum(Constants::Node::MAX_CORNER_RADIUS);
201     m_cornerRadiusSpinBox->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
202 
203 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
204     connect(m_cornerRadiusSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &MainWindow::cornerRadiusChanged);
205 #else
206     connect(m_cornerRadiusSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MainWindow::cornerRadiusChanged);
207 #endif
208     return WidgetFactory::buildToolBarWidgetActionWithLabel(tr("Corner radius:"), *m_cornerRadiusSpinBox, *this).second;
209 }
210 
createEdgeWidthAction()211 QWidgetAction * MainWindow::createEdgeWidthAction()
212 {
213     m_edgeWidthSpinBox->setSingleStep(Constants::Edge::STEP);
214     m_edgeWidthSpinBox->setMinimum(Constants::Edge::MIN_SIZE);
215     m_edgeWidthSpinBox->setMaximum(Constants::Edge::MAX_SIZE);
216     m_edgeWidthSpinBox->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
217 
218 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
219     connect(m_edgeWidthSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &MainWindow::edgeWidthChanged);
220 #else
221     connect(m_edgeWidthSpinBox, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, &MainWindow::edgeWidthChanged);
222 #endif
223     return WidgetFactory::buildToolBarWidgetActionWithLabel(tr("Edge width:"), *m_edgeWidthSpinBox, *this).second;
224 }
225 
createTextSizeAction()226 QWidgetAction * MainWindow::createTextSizeAction()
227 {
228     m_textSizeSpinBox->setMinimum(Constants::Text::MIN_SIZE);
229     m_textSizeSpinBox->setMaximum(Constants::Text::MAX_SIZE);
230     m_textSizeSpinBox->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
231 
232 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
233     connect(m_textSizeSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &MainWindow::textSizeChanged);
234 #else
235     connect(m_textSizeSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MainWindow::textSizeChanged);
236 #endif
237     return WidgetFactory::buildToolBarWidgetActionWithLabel(tr("Text size:"), *m_textSizeSpinBox, *this).second;
238 }
239 
createFontAction()240 QWidgetAction * MainWindow::createFontAction()
241 {
242     m_fontButton->setText(tr("Font") + threeDots);
243     connect(m_fontButton, &QPushButton::clicked, [=] {
244         bool ok;
245         QFont defaultFont = m_fontButton->font();
246         defaultFont.setPointSize(m_textSizeSpinBox->value());
247         const auto font = QFontDialog::getFont(&ok, defaultFont, this);
248         if (ok) {
249             // Note: Support for multiple families implemented in Qt 5.13 =>
250             juzzlin::L().debug() << "Font family selected: '" << font.family().toStdString() << "'";
251             juzzlin::L().debug() << "Font weight selected: " << font.weight();
252             updateFontButtonFont(font);
253             m_textSizeSpinBox->setValue(font.pointSize());
254             emit textSizeChanged(font.pointSize());
255             emit fontChanged(font);
256         }
257     });
258     return WidgetFactory::buildToolBarWidgetAction(*m_fontButton, *this).second;
259 }
260 
createGridSizeAction()261 QWidgetAction * MainWindow::createGridSizeAction()
262 {
263     m_gridSizeSpinBox->setMinimum(Constants::Grid::MIN_SIZE);
264     m_gridSizeSpinBox->setMaximum(Constants::Grid::MAX_SIZE);
265     m_gridSizeSpinBox->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
266 
267 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
268     const auto signal = QOverload<int>::of(&QSpinBox::valueChanged);
269 #else
270     const auto signal = static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged);
271 #endif
272     connect(m_gridSizeSpinBox, signal, this, &MainWindow::gridSizeChanged);
273     connect(m_gridSizeSpinBox, signal, Settings::saveGridSize);
274 
275     m_gridSizeSpinBox->setValue(Settings::loadGridSize());
276 
277     return WidgetFactory::buildToolBarWidgetActionWithLabel(tr("Grid size:"), *m_gridSizeSpinBox, *this).second;
278 }
279 
createSearchAction()280 QWidgetAction * MainWindow::createSearchAction()
281 {
282     m_searchTimer.setSingleShot(true);
283     connect(&m_searchTimer, &QTimer::timeout, [this, searchLineEdit = m_searchLineEdit]() {
284         const auto text = searchLineEdit->text();
285         juzzlin::L().debug() << "Search text changed: " << text.toStdString();
286         emit searchTextChanged(text);
287     });
288     connect(m_searchLineEdit, &QLineEdit::textChanged, [searchTimer = &m_searchTimer](const QString & text) {
289         if (text.isEmpty()) {
290             searchTimer->start(0);
291         } else {
292             searchTimer->start(Constants::View::TEXT_SEARCH_DELAY_MS);
293         }
294     });
295     connect(m_searchLineEdit, &QLineEdit::returnPressed, [searchTimer = &m_searchTimer] {
296         searchTimer->start(0);
297     });
298 
299     return WidgetFactory::buildToolBarWidgetActionWithLabel(tr("Search:"), *m_searchLineEdit, *this).second;
300 }
301 
createExportSubMenu(QMenu & fileMenu)302 void MainWindow::createExportSubMenu(QMenu & fileMenu)
303 {
304     const auto exportMenu = new QMenu;
305     const auto exportMenuAction = fileMenu.addMenu(exportMenu);
306     exportMenuAction->setText(tr("&Export"));
307 
308     // Add "export to PNG image"-action
309     const auto exportToPngAction = new QAction(tr("&PNG"), this);
310     exportMenu->addAction(exportToPngAction);
311     connect(exportToPngAction, &QAction::triggered, [=] {
312         emit actionTriggered(StateMachine::Action::PngExportSelected);
313     });
314 
315     exportMenu->addSeparator();
316 
317     // Add "export to SVG file"-action
318     const auto exportToSvgAction = new QAction(tr("&SVG"), this);
319     exportMenu->addAction(exportToSvgAction);
320     connect(exportToSvgAction, &QAction::triggered, [=] {
321         emit actionTriggered(StateMachine::Action::SvgExportSelected);
322     });
323 
324     connect(&fileMenu, &QMenu::aboutToShow, [=] {
325         exportToPngAction->setEnabled(m_mediator->hasNodes());
326         exportToSvgAction->setEnabled(m_mediator->hasNodes());
327     });
328 }
329 
createFileMenu()330 void MainWindow::createFileMenu()
331 {
332     const auto fileMenu = menuBar()->addMenu(tr("&File"));
333 
334     // Add "new"-action
335     const auto newAct = new QAction(tr("&New") + threeDots, this);
336     newAct->setShortcut(QKeySequence(QKeySequence::New));
337     fileMenu->addAction(newAct);
338     connect(newAct, &QAction::triggered, [=] {
339         emit actionTriggered(StateMachine::Action::NewSelected);
340     });
341 
342     // Add "open"-action
343     const auto openAct = new QAction(tr("&Open") + threeDots, this);
344     openAct->setShortcut(QKeySequence(QKeySequence::Open));
345     fileMenu->addAction(openAct);
346     connect(openAct, &QAction::triggered, [=] {
347         emit actionTriggered(StateMachine::Action::OpenSelected);
348     });
349 
350     // Add "Recent Files"-menu
351     const auto recentFilesMenu = new RecentFilesMenu;
352     const auto recentFilesMenuAction = fileMenu->addMenu(recentFilesMenu);
353     recentFilesMenuAction->setText(tr("Recent &Files"));
354     connect(recentFilesMenu, &RecentFilesMenu::fileSelected, [=] {
355         emit actionTriggered(StateMachine::Action::RecentFileSelected);
356     });
357 
358     fileMenu->addSeparator();
359 
360     // Add "save"-action
361     m_saveAction->setShortcut(QKeySequence(QKeySequence::Save));
362     m_saveAction->setEnabled(false);
363     fileMenu->addAction(m_saveAction);
364     connect(m_saveAction, &QAction::triggered, [=] {
365         emit actionTriggered(StateMachine::Action::SaveSelected);
366     });
367 
368     // Add "save as"-action
369     m_saveAsAction->setShortcut(QKeySequence(QKeySequence::SaveAs));
370     m_saveAsAction->setEnabled(false);
371     fileMenu->addAction(m_saveAsAction);
372     connect(m_saveAsAction, &QAction::triggered, [=] {
373         emit actionTriggered(StateMachine::Action::SaveAsSelected);
374     });
375 
376     fileMenu->addSeparator();
377 
378     createExportSubMenu(*fileMenu);
379 
380     fileMenu->addSeparator();
381 
382     // Add "settings"-action
383     const auto settingsAct = new QAction(tr("Settings") + threeDots, this);
384     connect(settingsAct, &QAction::triggered, m_settingsDlg, &SettingsDialog::exec);
385     fileMenu->addAction(settingsAct);
386 
387     fileMenu->addSeparator();
388 
389     // Add "quit"-action
390     const auto quitAct = new QAction(tr("&Quit"), this);
391     quitAct->setShortcut(QKeySequence(QKeySequence::Quit));
392     fileMenu->addAction(quitAct);
393     connect(quitAct, &QAction::triggered, [=] {
394         emit actionTriggered(StateMachine::Action::QuitSelected);
395     });
396 
397     connect(fileMenu, &QMenu::aboutToShow, [=] {
398         recentFilesMenuAction->setEnabled(RecentFilesManager::instance().hasRecentFiles());
399     });
400 }
401 
createHelpMenu()402 void MainWindow::createHelpMenu()
403 {
404     const auto helpMenu = menuBar()->addMenu(tr("&Help"));
405 
406     // Add "about"-action
407     const auto aboutAct = new QAction(tr("&About"), this);
408     helpMenu->addAction(aboutAct);
409     connect(aboutAct, &QAction::triggered, [=] {
410         m_aboutDlg->exec();
411     });
412 
413     // Add "about Qt"-action
414     const auto aboutQtAct = new QAction(tr("About &Qt"), this);
415     helpMenu->addAction(aboutQtAct);
416     connect(aboutQtAct, &QAction::triggered, [=] {
417         QMessageBox::aboutQt(this, tr("About Qt"));
418     });
419 
420     helpMenu->addSeparator();
421 
422     // Add "What's new"-action
423     const auto whatsNewAct = new QAction(tr("What's New"), this);
424     helpMenu->addAction(whatsNewAct);
425     connect(whatsNewAct, &QAction::triggered, [=] {
426         m_whatsNewDlg->resize(3 * width() / 5, 3 * height() / 5);
427         m_whatsNewDlg->exec();
428     });
429 }
430 
createToolBar()431 void MainWindow::createToolBar()
432 {
433     const auto toolBar = new QToolBar(this);
434     addToolBar(Qt::BottomToolBarArea, toolBar);
435     toolBar->addAction(createEdgeWidthAction());
436     toolBar->addSeparator();
437     toolBar->addAction(createTextSizeAction());
438     toolBar->addSeparator();
439     toolBar->addAction(createFontAction());
440     toolBar->addSeparator();
441     toolBar->addAction(createCornerRadiusAction());
442     toolBar->addSeparator();
443     toolBar->addAction(createGridSizeAction());
444 
445     const auto spacer = new QWidget;
446     spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
447     toolBar->addWidget(spacer);
448     toolBar->addAction(createSearchAction());
449     toolBar->addSeparator();
450     toolBar->addWidget(m_showGridCheckBox);
451     toolBar->addWidget(m_copyOnDragCheckBox);
452 
453     connect(m_showGridCheckBox, &QCheckBox::stateChanged, this, &MainWindow::gridVisibleChanged);
454     connect(m_showGridCheckBox, &QCheckBox::stateChanged, Settings::saveGridVisibleState);
455 
456     m_showGridCheckBox->setCheckState(Settings::loadGridVisibleState());
457 }
458 
createViewMenu()459 void MainWindow::createViewMenu()
460 {
461     const auto viewMenu = menuBar()->addMenu(tr("&View"));
462 
463     // Add "fullScreen"-action
464     m_fullScreenAction = new QAction(tr("Full Screen"), this);
465     m_fullScreenAction->setCheckable(true);
466     m_fullScreenAction->setChecked(false);
467     m_fullScreenAction->setShortcut(QKeySequence("F11"));
468     viewMenu->addAction(m_fullScreenAction);
469     connect(m_fullScreenAction, &QAction::triggered, [=](bool checked) {
470         if (checked) {
471             Settings::saveFullScreen(true);
472             showFullScreen();
473         } else {
474             Settings::saveFullScreen(false);
475             showNormal();
476             resize(Settings::loadWindowSize(calculateDefaultWindowSize().second));
477         }
478     });
479 
480     viewMenu->addSeparator();
481 
482     // Add "zoom in"-action
483     const auto zoomIn = new QAction(tr("Zoom In"), this);
484     viewMenu->addAction(zoomIn);
485     zoomIn->setShortcut(QKeySequence(QKeySequence::ZoomIn));
486     connect(zoomIn, &QAction::triggered, this, &MainWindow::zoomInTriggered);
487 
488     // Add "zoom out"-action
489     const auto zoomOut = new QAction(tr("Zoom Out"), this);
490     viewMenu->addAction(zoomOut);
491     connect(zoomOut, &QAction::triggered, this, &MainWindow::zoomOutTriggered);
492     zoomOut->setShortcut(QKeySequence(QKeySequence::ZoomOut));
493 
494     viewMenu->addSeparator();
495 
496     // Add "zoom to fit"-action
497     const auto zoomToFit = new QAction(tr("&Zoom To Fit"), this);
498     zoomToFit->setShortcut(QKeySequence("Ctrl+0"));
499     viewMenu->addAction(zoomToFit);
500     connect(zoomToFit, &QAction::triggered, this, &MainWindow::zoomToFitTriggered);
501 
502     connect(viewMenu, &QMenu::aboutToShow, [=] {
503         zoomToFit->setEnabled(m_mediator->hasNodes());
504     });
505 }
506 
calculateDefaultWindowSize() const507 std::pair<QSize, QSize> MainWindow::calculateDefaultWindowSize() const
508 {
509     // Detect screen dimensions
510     const auto screen = QGuiApplication::primaryScreen();
511     const auto screenGeometry = screen->geometry();
512     const int height = screenGeometry.height();
513     const int width = screenGeometry.width();
514     const double defaultScale = 0.8;
515     return { QSize(width, height), QSize(width, height) * defaultScale };
516 }
517 
initialize()518 void MainWindow::initialize()
519 {
520     // Read dialog size data
521     const auto screenAndWindow = calculateDefaultWindowSize();
522     const auto lastSize = Settings::loadWindowSize(screenAndWindow.second);
523     if (screenAndWindow.first.width() < lastSize.width()
524         || screenAndWindow.first.height() < lastSize.height()) {
525         resize(screenAndWindow.second);
526         Settings::saveWindowSize(size());
527     } else {
528         resize(lastSize);
529     }
530 
531     // Try to center the window.
532     move(screenAndWindow.first.width() / 2 - this->width() / 2, screenAndWindow.first.height() / 2 - this->height() / 2);
533 
534     populateMenuBar();
535 
536     createToolBar();
537 
538     setWindowIcon(QIcon(":/heimer.png"));
539 
540     emit actionTriggered(StateMachine::Action::MainWindowInitialized);
541 }
542 
setMediator(std::shared_ptr<Mediator> mediator)543 void MainWindow::setMediator(std::shared_ptr<Mediator> mediator)
544 {
545     m_mediator = mediator;
546 }
547 
setTitle()548 void MainWindow::setTitle()
549 {
550     const auto appInfo = QString(Constants::Application::APPLICATION_NAME) + " " + Constants::Application::APPLICATION_VERSION;
551     const auto displayFileName = m_mediator->fileName().isEmpty() ? tr("New File") : m_mediator->fileName();
552     if (m_mediator->isModified()) {
553         setWindowTitle(appInfo + " - " + displayFileName + " - " + tr("Not Saved"));
554     } else {
555         setWindowTitle(appInfo + " - " + displayFileName);
556     }
557 }
558 
instance()559 MainWindow * MainWindow::instance()
560 {
561     return MainWindow::m_instance;
562 }
563 
closeEvent(QCloseEvent * event)564 void MainWindow::closeEvent(QCloseEvent * event)
565 {
566     event->ignore();
567     emit actionTriggered(StateMachine::Action::QuitSelected);
568 }
569 
populateMenuBar()570 void MainWindow::populateMenuBar()
571 {
572     createFileMenu();
573 
574     createEditMenu();
575 
576     createViewMenu();
577 
578     createHelpMenu();
579 }
580 
updateFontButtonFont(const QFont & font)581 void MainWindow::updateFontButtonFont(const QFont & font)
582 {
583     QFont newFont(font);
584     newFont.setPointSize(m_fontButton->font().pointSize());
585     m_fontButton->setFont(newFont);
586 }
587 
appear()588 void MainWindow::appear()
589 {
590     if (Settings::loadFullScreen()) {
591         m_fullScreenAction->trigger();
592     } else {
593         show();
594     }
595 }
596 
copyOnDragEnabled() const597 bool MainWindow::copyOnDragEnabled() const
598 {
599     return m_copyOnDragCheckBox->isChecked();
600 }
601 
disableUndoAndRedo()602 void MainWindow::disableUndoAndRedo()
603 {
604     m_undoAction->setEnabled(false);
605     m_redoAction->setEnabled(false);
606 }
607 
enableConnectSelectedNodesAction(bool enable)608 void MainWindow::enableConnectSelectedNodesAction(bool enable)
609 {
610     m_connectSelectedNodesAction->setEnabled(enable);
611 }
612 
enableDisconnectSelectedNodesAction(bool enable)613 void MainWindow::enableDisconnectSelectedNodesAction(bool enable)
614 {
615     m_disconnectSelectedNodesAction->setEnabled(enable);
616 }
617 
enableWidgetSignals(bool enable)618 void MainWindow::enableWidgetSignals(bool enable)
619 {
620     m_cornerRadiusSpinBox->blockSignals(!enable);
621     m_edgeWidthSpinBox->blockSignals(!enable);
622     m_textSizeSpinBox->blockSignals(!enable);
623     m_gridSizeSpinBox->blockSignals(!enable);
624 }
625 
setCornerRadius(int value)626 void MainWindow::setCornerRadius(int value)
627 {
628     if (m_cornerRadiusSpinBox->value() != value) {
629         m_cornerRadiusSpinBox->setValue(value);
630     }
631 }
632 
setEdgeWidth(double value)633 void MainWindow::setEdgeWidth(double value)
634 {
635     if (!qFuzzyCompare(m_edgeWidthSpinBox->value(), value)) {
636         m_edgeWidthSpinBox->setValue(value);
637     }
638 }
639 
setFont(const QFont & font)640 void MainWindow::setFont(const QFont & font)
641 {
642     updateFontButtonFont(font);
643 }
644 
setTextSize(int textSize)645 void MainWindow::setTextSize(int textSize)
646 {
647     if (m_textSizeSpinBox->value() != textSize) {
648         m_textSizeSpinBox->setValue(textSize);
649     }
650 }
651 
enableUndo(bool enable)652 void MainWindow::enableUndo(bool enable)
653 {
654     m_undoAction->setEnabled(enable);
655 }
656 
enableRedo(bool enable)657 void MainWindow::enableRedo(bool enable)
658 {
659     m_redoAction->setEnabled(enable);
660 }
661 
enableSave(bool enable)662 void MainWindow::enableSave(bool enable)
663 {
664     setTitle();
665 
666     m_saveAction->setEnabled(enable);
667 }
668 
saveWindowSize()669 void MainWindow::saveWindowSize()
670 {
671     if (!m_fullScreenAction->isChecked()) {
672         Settings::saveWindowSize(size());
673     }
674 }
675 
showErrorDialog(QString message)676 void MainWindow::showErrorDialog(QString message)
677 {
678     QMessageBox::critical(this,
679                           Constants::Application::APPLICATION_NAME,
680                           message,
681                           "");
682 }
683 
initializeNewMindMap()684 void MainWindow::initializeNewMindMap()
685 {
686     disableUndoAndRedo();
687     setSaveActionStatesOnNewMindMap();
688 
689     emit actionTriggered(StateMachine::Action::NewMindMapInitialized);
690 }
691 
setSaveActionStatesOnNewMindMap()692 void MainWindow::setSaveActionStatesOnNewMindMap()
693 {
694     m_saveAction->setEnabled(true);
695     m_saveAsAction->setEnabled(true);
696 }
697 
setSaveActionStatesOnOpenedMindMap()698 void MainWindow::setSaveActionStatesOnOpenedMindMap()
699 {
700     m_saveAction->setEnabled(false);
701     m_saveAsAction->setEnabled(true);
702 }
703 
704 MainWindow::~MainWindow() = default;
705