File indexing completed on 2024-05-19 05:41:56

0001 // mainwindow.cpp                                                    -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <kmessagewidget.h>
0021 #include <mainwindow.h>
0022 
0023 #include <QDragEnterEvent>
0024 #include <QDropEvent>
0025 #include <QFileDialog>
0026 #include <QFileInfo>
0027 #include <QFormLayout>
0028 #include <QJsonObject>
0029 #include <QKeyEvent>
0030 #include <QMessageBox>
0031 #include <QMimeData>
0032 #include <QModelIndex>
0033 #include <QPushButton>
0034 #include <QStandardPaths>
0035 #include <QStatusBar>
0036 
0037 #include <ct_lvtmdl_errorsmodel.h>
0038 #include <ct_lvtmdl_methodstablemodel.h>
0039 #include <ct_lvtmdl_modelhelpers.h>
0040 #include <ct_lvtmdl_namespacetreemodel.h>
0041 #include <ct_lvtmdl_packagetreemodel.h>
0042 #include <ct_lvtmdl_physicaltablemodels.h>
0043 #include <ct_lvtmdl_usesintheimpltablemodel.h>
0044 #include <ct_lvtmdl_usesintheinterfacetablemodel.h>
0045 
0046 #include <ct_lvtldr_nodestorage.h>
0047 #include <ct_lvtldr_packagenode.h>
0048 
0049 #include <ct_lvtshr_graphenums.h>
0050 
0051 #include <ct_lvtqtc_graphicsscene.h>
0052 #include <ct_lvtqtc_graphicsview.h>
0053 #include <ct_lvtqtc_lakosentitypluginutils.h>
0054 #include <ct_lvtqtc_pluginmanagerutils.h>
0055 #include <ct_lvtqtc_undo_manager.h>
0056 
0057 #include <ct_lvtqtd_packageviewdelegate.h>
0058 
0059 #include <ct_lvtqtw_configurationdialog.h>
0060 #include <ct_lvtqtw_exportmanager.h>
0061 #include <ct_lvtqtw_graphtabelement.h>
0062 #include <ct_lvtqtw_parse_codebase.h>
0063 #include <ct_lvtqtw_splitterview.h>
0064 #include <ct_lvtqtw_tabwidget.h>
0065 
0066 #include <ct_lvtcgn_app_adapter.h>
0067 
0068 #include <fstream>
0069 #include <preferences.h>
0070 #include <projectsettingsdialog.h>
0071 
0072 #include <QDesktopServices>
0073 #include <QInputDialog>
0074 #include <QLoggingCategory>
0075 #ifdef USE_WEB_ENGINE
0076 #include <QWebEngineView>
0077 #else
0078 #include <QTextBrowser>
0079 #endif
0080 
0081 #include <KActionCollection>
0082 #include <KLocalizedString>
0083 #include <KStandardAction>
0084 #include <kwidgetsaddons_version.h>
0085 
0086 // in a header
0087 Q_DECLARE_LOGGING_CATEGORY(LogWindow)
0088 
0089 // in one source file
0090 Q_LOGGING_CATEGORY(LogWindow, "log.window")
0091 
0092 using namespace Codethink::lvtqtc;
0093 using namespace Codethink::lvtldr;
0094 using namespace Codethink::lvtqtc;
0095 using namespace Codethink::lvtmdl;
0096 using namespace Codethink::lvtqtw;
0097 using namespace Codethink::lvtqtd;
0098 using namespace Codethink::lvtprj;
0099 using namespace Codethink::lvtplg;
0100 
0101 void MainWindow::initializeResource()
0102 {
0103     static auto initialized = false;
0104     if (!initialized) {
0105         Q_INIT_RESOURCE(desktopapp);
0106     }
0107 }
0108 
0109 MainWindow::MainWindow(NodeStorage& sharedNodeStorage,
0110                        PluginManager *pluginManager,
0111                        UndoManager *undoManager,
0112                        DebugModel *debugModel):
0113     ui(sharedNodeStorage, d_projectFile, pluginManager),
0114     sharedNodeStorage(sharedNodeStorage),
0115     namespaceModel(new Codethink::lvtmdl::NamespaceTreeModel()),
0116     packageModel(new Codethink::lvtmdl::PackageTreeModel(sharedNodeStorage)),
0117     d_errorModel_p(new Codethink::lvtmdl::ErrorsModel()),
0118     d_status_bar(new CodeVisStatusBar()),
0119     d_pluginManager_p(pluginManager),
0120     d_undoManager_p(undoManager),
0121     debugModel(debugModel),
0122     d_dockReports(new QDockWidget(this)),
0123     d_reportsTabWidget(new QTabWidget(d_dockReports))
0124 {
0125     using namespace Codethink::lvtqtw;
0126     using namespace Codethink::lvtmdl;
0127 
0128     ui.setupUi(this);
0129 
0130     fieldsModel = new FieldsTreeModel();
0131     auto *usesInTheImplTableModel = new UsesInTheImplTableModel();
0132     auto *usesInTheInterfaceTableModel = new UsesInTheInterfaceTableModel();
0133     auto *methodsTableModel = new MethodsTableModel();
0134     auto *providersTableModel = new PhysicalProvidersTableModel();
0135     auto *clientsTableModel = new PhysicalClientsTableModel();
0136 
0137     tableModels.append({usesInTheImplTableModel,
0138                         usesInTheInterfaceTableModel,
0139                         methodsTableModel,
0140                         providersTableModel,
0141                         clientsTableModel});
0142 
0143     ui.topMessageWidget->setVisible(false);
0144     ui.topMessageWidget->setWordWrap(true);
0145 #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
0146     ui.topMessageWidget->setPosition(KMessageWidget::Header);
0147 #endif
0148 
0149     connect(ui.mainSplitter, &SplitterView::currentTabChanged, this, &MainWindow::currentGraphSplitChanged);
0150     connect(ui.namespaceFilter, &QLineEdit::textChanged, ui.namespaceTree, &TreeView::setFilterText);
0151     connect(ui.packagesFilter, &QLineEdit::textChanged, ui.packagesTree, &TreeView::setFilterText);
0152 
0153     connect(ui.namespaceTree, &TreeView::leafSelected, this, &MainWindow::setCurrentGraph);
0154     connect(ui.namespaceTree,
0155             &TreeView::leafMiddleClicked,
0156             this,
0157             qOverload<const QModelIndex&>(&MainWindow::newTabRequested));
0158     connect(ui.namespaceTree, &TreeView::branchRightClicked, this, &MainWindow::requestMenuNamespaceView);
0159     connect(ui.namespaceTree, &TreeView::leafRightClicked, this, &MainWindow::requestMenuNamespaceView);
0160 
0161     connect(ui.packagesTree, &TreeView::leafSelected, this, &MainWindow::setCurrentGraph);
0162     connect(ui.packagesTree,
0163             &TreeView::leafMiddleClicked,
0164             this,
0165             qOverload<const QModelIndex&>(&MainWindow::newTabRequested));
0166     connect(ui.packagesTree, &TreeView::branchSelected, this, &MainWindow::setCurrentGraph);
0167     connect(ui.packagesTree,
0168             &TreeView::branchMiddleClicked,
0169             this,
0170             qOverload<const QModelIndex&>(&MainWindow::newTabRequested));
0171     connect(ui.packagesTree, &TreeView::branchRightClicked, this, &MainWindow::requestMenuPackageView);
0172     connect(ui.packagesTree, &TreeView::leafRightClicked, this, &MainWindow::requestMenuPackageView);
0173 
0174     ui.namespaceTree->setModel(namespaceModel);
0175     ui.packagesTree->setModel(packageModel);
0176     ui.packagesTree->setItemDelegateForColumn(0, new PackageViewDelegate());
0177 
0178     ui.fieldsTree->setModel(fieldsModel);
0179     ui.usesInTheImplTable->setModel(usesInTheImplTableModel);
0180     ui.usesInTheInterfaceTable->setModel(usesInTheInterfaceTableModel);
0181     ui.methodsTable->setModel(methodsTableModel);
0182     ui.providersTable->setModel(providersTableModel);
0183     ui.clientsTable->setModel(clientsTableModel);
0184     ui.errorView->setModel(d_errorModel_p);
0185 
0186     ui.namespaceFilter->setVisible(false);
0187     ui.packagesFilter->setVisible(false);
0188 
0189     ui.namespaceFilter->installEventFilter(this);
0190     ui.packagesFilter->installEventFilter(this);
0191     ui.mainSplitter->setUndoManager(d_undoManager_p);
0192     d_undoManager_p->createDock(this);
0193 
0194     configurePluginDocks();
0195 
0196 #ifdef Q_OS_MACOS
0197     setDocumentMode(true);
0198 #endif
0199 
0200     ui.packagesTree->setFocus();
0201 
0202     // Always open with the welcome page on. When the welcomePage triggers a signal, or a
0203     // signal happens, we hide it.
0204     showWelcomeScreen();
0205 
0206     connect(ui.welcomeWidget, &WelcomeScreen::requestNewProject, this, &MainWindow::newProject);
0207     connect(ui.welcomeWidget, &WelcomeScreen::requestParseProject, this, &MainWindow::newProjectFromSource);
0208     connect(ui.welcomeWidget, &WelcomeScreen::requestExistingProject, this, &MainWindow::openProjectAction);
0209 
0210     // NOLINTNEXTLINE
0211     currentGraphTab = qobject_cast<Codethink::lvtqtw::TabWidget *>(ui.mainSplitter->widget(0));
0212     // reason for the no-lint. cppcoreguidelines wants us to initialize everything on the initalization
0213     // list, but we can't have the value of ui.mainspliter->widget(0) there.
0214 
0215     changeCurrentGraphWidget(0);
0216 
0217     QObject::connect(&sharedNodeStorage, &NodeStorage::storageChanged, this, [this] {
0218         d_projectFile.requestAutosave(Preferences::autoSaveBackupIntervalMsecs());
0219         Preferences::setLastDocument(QString::fromStdString(d_projectFile.backupPath().string()));
0220         Preferences::self()->save();
0221     });
0222 
0223     setStatusBar(d_status_bar);
0224     connect(d_status_bar, &CodeVisStatusBar::mouseInteractionLabelClicked, this, [&]() {
0225         openPreferencesAt(tr("Mouse"));
0226     });
0227 
0228     ui.errorDock->setVisible(false);
0229 
0230     connect(&d_projectFile, &Codethink::lvtprj::ProjectFile::bookmarksChanged, this, &MainWindow::bookmarksChanged);
0231 
0232     d_reportsTabWidget->setTabsClosable(true);
0233     connect(d_reportsTabWidget->tabBar(),
0234             &QTabBar::tabCloseRequested,
0235             d_reportsTabWidget->tabBar(),
0236             &QTabBar::removeTab);
0237     d_dockReports->setWindowTitle("Reports");
0238     d_dockReports->setObjectName("Reports");
0239     addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, d_dockReports);
0240     d_dockReports->setWidget(d_reportsTabWidget);
0241 
0242     ui.pluginEditorView->setPluginManager(d_pluginManager_p);
0243 
0244     /* Setup default dock visibility */
0245     ui.objectHierarchyDoc->setVisible(true);
0246     ui.usesInTheInterfaceDock->setVisible(false);
0247     ui.methodsDock->setVisible(false);
0248     ui.usesInTheImplDock->setVisible(false);
0249     ui.fieldsDock->setVisible(false);
0250     ui.providersDock->setVisible(false);
0251     ui.clientsDock->setVisible(false);
0252     ui.pluginEditorDock->setVisible(false);
0253     d_dockReports->setVisible(false);
0254 
0255     setupActions();
0256     setProjectWidgetsEnabled(false);
0257     setAcceptDrops(true);
0258 }
0259 
0260 MainWindow::~MainWindow() noexcept = default;
0261 
0262 void MainWindow::dragEnterEvent(QDragEnterEvent *e)
0263 {
0264     if (e->mimeData()->hasUrls()) {
0265         e->acceptProposedAction();
0266     }
0267 }
0268 
0269 void MainWindow::dropEvent(QDropEvent *e)
0270 {
0271     const QUrl url = e->mimeData()->urls().first();
0272     const QString filename = url.toLocalFile();
0273     const bool success = openProjectFromPath(filename);
0274     if (!success) {
0275         showMessage(tr("Error loading project file %1").arg(filename), KMessageWidget::Error);
0276     }
0277 }
0278 
0279 void MainWindow::setupActions()
0280 {
0281     auto *action = new QAction(this);
0282     action->setText(tr("New from source"));
0283     action->setIcon(QIcon::fromTheme("document-new"));
0284     actionCollection()->addAction("new_project_from_source", action);
0285     actionCollection()->setDefaultShortcut(action,
0286                                            static_cast<QKeySequence>(static_cast<int>(Qt::CTRL)
0287                                                                      | static_cast<int>(Qt::SHIFT)
0288                                                                      | static_cast<int>(Qt::Key_N)));
0289     connect(action, &QAction::triggered, this, &MainWindow::newProjectFromSource);
0290 
0291     action = new QAction(this);
0292     action->setText(tr("Parse Aditional Source"));
0293     action->setIcon(QIcon::fromTheme("document-new"));
0294     actionCollection()->addAction("parse_aditional", action);
0295     actionCollection()->setDefaultShortcut(
0296         action,
0297         static_cast<QKeySequence>(static_cast<int>(Qt::CTRL) | static_cast<int>(Qt::Key_P)));
0298     connect(action, &QAction::triggered, this, &MainWindow::openGenerateDatabase);
0299 
0300     action = new QAction(this);
0301     action->setText(tr("Dump usage log"));
0302     actionCollection()->addAction("dump_usage_log", action);
0303     connect(action, &QAction::triggered, this, [this] {
0304         const QString fileName = QFileDialog::getSaveFileName();
0305         if (fileName.isEmpty()) {
0306             return;
0307         }
0308 
0309         const bool ret = this->debugModel->saveAs(fileName);
0310         if (!ret) {
0311             showMessage(tr("Could not save dump file"), KMessageWidget::MessageType::Error);
0312         }
0313     });
0314 
0315     action = new QAction(this);
0316     action->setText(tr("Reset usage log"));
0317     connect(action, &QAction::triggered, this, [this] {
0318         debugModel->clear();
0319     });
0320 
0321     action = new QAction(this);
0322     action->setText(tr("Generate Code"));
0323     action->setIcon(QIcon::fromTheme("document-new"));
0324     actionCollection()->addAction("generate_code", action);
0325     actionCollection()->setDefaultShortcut(
0326         action,
0327         static_cast<QKeySequence>(static_cast<int>(Qt::CTRL) | static_cast<int>(Qt::Key_G)));
0328     connect(action, &QAction::triggered, this, &MainWindow::openCodeGenerationWindow);
0329 
0330     action = new QAction(this);
0331     action->setText(tr("Svg"));
0332     action->setIcon(QIcon::fromTheme("document-new"));
0333     actionCollection()->addAction("export_svg", action);
0334     connect(action, &QAction::triggered, this, &MainWindow::exportSvg);
0335 
0336     action = new QAction(this);
0337     action->setCheckable(true);
0338     action->setText(tr("Toggle split view"));
0339     action->setIcon(QIcon::fromTheme("document-new"));
0340     actionCollection()->addAction("toggle_split_view", action);
0341     connect(action, &QAction::toggled, this, &MainWindow::toggleSplitView);
0342 
0343     action = new QAction(this);
0344     action->setText(tr("New Tab"));
0345     action->setIcon(QIcon::fromTheme("document-new"));
0346     actionCollection()->addAction("new_tab", action);
0347     actionCollection()->setDefaultShortcut(
0348         action,
0349         static_cast<QKeySequence>(static_cast<int>(Qt::CTRL) | static_cast<int>(Qt::Key_T)));
0350     connect(action, &QAction::triggered, this, &MainWindow::newTab);
0351 
0352     action = new QAction(this);
0353     action->setText(tr("Close current tab"));
0354     action->setIcon(QIcon::fromTheme("document-new"));
0355     actionCollection()->addAction("close_current_tab", action);
0356     actionCollection()->setDefaultShortcut(action,
0357                                            static_cast<QKeySequence>(static_cast<int>(Qt::CTRL)
0358                                                                      | static_cast<int>(Qt::SHIFT)
0359                                                                      | static_cast<int>(Qt::Key_W)));
0360     connect(action, &QAction::triggered, this, &MainWindow::closeCurrentTab);
0361 
0362     action = new QAction(this);
0363     action->setText(tr("Bookmark Current Tab"));
0364     actionCollection()->addAction("bookmark_current_tab", action);
0365     connect(action, &QAction::triggered, this, &MainWindow::bookmarkCurrentTab);
0366 
0367     // Common Set of Actions that most applications have. Those *do not* need to be
0368     // specified in the codevisui.rc
0369     KStandardAction::find(this, &MainWindow::requestSearch, actionCollection());
0370     KStandardAction::openNew(this, &MainWindow::newProject, actionCollection());
0371     KStandardAction::close(this, &MainWindow::closeProject, actionCollection());
0372     KStandardAction::undo(this, &MainWindow::triggerUndo, actionCollection());
0373     KStandardAction::redo(this, &MainWindow::triggerRedo, actionCollection());
0374     KStandardAction::preferences(this, &MainWindow::openPreferences, actionCollection());
0375     KStandardAction::save(this, &MainWindow::saveProject, actionCollection());
0376     KStandardAction::saveAs(this, &MainWindow::saveProjectAs, actionCollection());
0377     KStandardAction::open(this, &MainWindow::openProjectAction, actionCollection());
0378     KStandardAction::quit(qApp, &QCoreApplication::quit, actionCollection());
0379 
0380     setupGUI(Default, QStringLiteral(":/ui_files/codevisui.rc"));
0381 
0382     // Populate the "View" menu. (See codevisui.rc)
0383     // Note that we can't use the name "view" due to naming clash
0384     auto const MENUBAR_VIEW_MENU_ID = QString{"codevis_view_menu"};
0385     auto menuView = this->findChild<QMenu *>(MENUBAR_VIEW_MENU_ID);
0386     const auto dockWidgets = findChildren<QDockWidget *>();
0387     for (auto *dock : dockWidgets) {
0388         action = new QAction();
0389         action->setText(dock->windowTitle());
0390         action->setCheckable(true);
0391         action->setChecked(dock->isVisible());
0392         connect(action, &QAction::toggled, dock, &QDockWidget::setVisible);
0393         connect(dock, &QDockWidget::visibilityChanged, action, [dock, action](bool visible) {
0394             action->setChecked(dock->isVisible());
0395         });
0396         menuView->addAction(action);
0397     }
0398 }
0399 
0400 void MainWindow::closeEvent(QCloseEvent *ev)
0401 {
0402     if (d_projectFile.isOpen() && d_projectFile.isDirty()) {
0403         const auto choice = QMessageBox::warning(this,
0404                                                  tr("Save changes?"),
0405                                                  tr("Do you want to save the changes on the project?"),
0406                                                  QMessageBox::StandardButton::Save | QMessageBox::StandardButton::No);
0407         if (choice == QMessageBox::StandardButton::Save) {
0408             saveProject();
0409         }
0410     }
0411     QMainWindow::closeEvent(ev);
0412 }
0413 
0414 void MainWindow::bookmarkCurrentTab()
0415 {
0416     if (!currentGraphTab) {
0417         showErrorMessage(tr("Nothing to bookmark"));
0418     }
0419 
0420     currentGraphTab->saveBookmarkByTabIndex(currentGraphTab->currentIndex());
0421 }
0422 
0423 void MainWindow::setProjectWidgetsEnabled(bool enabled)
0424 {
0425     const auto dockWidgets = findChildren<QDockWidget *>();
0426     for (auto *docks : dockWidgets) {
0427         docks->setEnabled(enabled);
0428     }
0429 
0430     // Uncomment this if you want to see all names of configured actions.
0431     // for (const auto *action : actionCollection()->actions()) {
0432     //    std::cout << action->objectName().toStdString() << std::endl;
0433     //}
0434 
0435     actionCollection()->action("close_current_tab")->setEnabled(enabled);
0436     actionCollection()->action("file_close")->setEnabled(enabled);
0437     actionCollection()->action("generate_code")->setEnabled(enabled);
0438     actionCollection()->action("parse_aditional")->setEnabled(enabled);
0439     actionCollection()->action("export_svg")->setEnabled(enabled);
0440     actionCollection()->action("new_tab")->setEnabled(enabled);
0441     actionCollection()->action("file_save_as")->setEnabled(enabled);
0442     actionCollection()->action("file_save")->setEnabled(enabled);
0443     actionCollection()->action("edit_find")->setEnabled(enabled);
0444     actionCollection()->action("toggle_split_view")->setEnabled(enabled);
0445 }
0446 
0447 void MainWindow::closeProject()
0448 {
0449     setProjectWidgetsEnabled(false);
0450 
0451     sharedNodeStorage.closeDatabase();
0452     cpp::result<void, Codethink::lvtprj::ProjectFileError> closed = d_projectFile.close();
0453     if (closed.has_error()) {
0454         showErrorMessage(
0455             tr("Error closing the current project\n%1").arg(QString::fromStdString(closed.error().errorMessage)));
0456         return;
0457     }
0458 
0459     if (d_undoManager_p) {
0460         d_undoManager_p->clear();
0461     }
0462     sharedNodeStorage.clear();
0463     packageModel->clear();
0464     namespaceModel->clear();
0465     ui.mainSplitter->closeAllTabs();
0466     if (ui.mainSplitter->count() > 1) {
0467         ui.mainSplitter->toggle();
0468     }
0469     d_status_bar->reset();
0470     showWelcomeScreen();
0471     Preferences::setLastDocument(QString());
0472 }
0473 
0474 bool MainWindow::askCloseCurrentProject()
0475 {
0476     if (d_projectFile.isOpen()) {
0477         auto result = QMessageBox::question(this,
0478                                             tr("Really close project"),
0479                                             tr("Do you really want to close the project and create a new one?"),
0480                                             QMessageBox::Button::Yes,
0481                                             QMessageBox::Button::No);
0482         if (result == QMessageBox::Button::No) {
0483             return false;
0484         }
0485     }
0486 
0487     return true;
0488 }
0489 
0490 bool MainWindow::tryCreateEmptyProjectFile()
0491 {
0492     cpp::result<void, Codethink::lvtprj::ProjectFileError> created = d_projectFile.createEmpty();
0493     if (created.has_error()) {
0494         showErrorMessage(tr("Could not create empty project, check your permissions on the temporary folder.\n%1")
0495                              .arg(QString::fromStdString(created.error().errorMessage)));
0496         return false;
0497     }
0498     return true;
0499 }
0500 
0501 void MainWindow::newProjectFromSource()
0502 {
0503     if (newProject()) {
0504         openGenerateDatabase();
0505     }
0506 }
0507 
0508 bool MainWindow::newProject()
0509 {
0510     if (!askCloseCurrentProject()) {
0511         return false;
0512     }
0513     closeProject();
0514 
0515     const QString projectName = requestProjectName();
0516     if (projectName.isEmpty()) {
0517         return false;
0518     }
0519 
0520     if (!tryCreateEmptyProjectFile()) {
0521         return false;
0522     }
0523 
0524     d_projectFile.setProjectName(projectName.toStdString());
0525 
0526     updateSessionPtr();
0527     showProjectView();
0528     setWindowTitle(qApp->applicationName() + " Unsaved Document");
0529     return true;
0530 }
0531 
0532 QString MainWindow::requestProjectName()
0533 {
0534     bool ok = true;
0535     QString projectName =
0536         QInputDialog::getText(this, tr("Project Name"), tr("Project Name"), QLineEdit::Normal, tr("Untitled"), &ok);
0537     if (!ok) {
0538         return {};
0539     }
0540     return projectName;
0541 }
0542 
0543 void MainWindow::saveTabsOnProject()
0544 {
0545     auto *tabWidget = qobject_cast<Codethink::lvtqtw::TabWidget *>(ui.mainSplitter->widget(0));
0546     if (tabWidget) {
0547         tabWidget->saveTabsOnProject(ProjectFile::BookmarkType::LeftPane);
0548     }
0549 
0550     tabWidget = qobject_cast<Codethink::lvtqtw::TabWidget *>(ui.mainSplitter->widget(1));
0551     if (tabWidget) {
0552         tabWidget->saveTabsOnProject(ProjectFile::BookmarkType::RightPane);
0553     }
0554 }
0555 
0556 void MainWindow::saveProject()
0557 {
0558     if (d_projectFile.location().empty()) {
0559         saveProjectAs();
0560         return;
0561     }
0562 
0563     d_projectFile.prepareSave();
0564     saveTabsOnProject();
0565 
0566     cpp::result<void, Codethink::lvtprj::ProjectFileError> saved = d_projectFile.save();
0567     if (saved.has_error()) {
0568         showErrorMessage(tr("Error saving project: %1").arg(QString::fromStdString(saved.error().errorMessage)));
0569         return;
0570     }
0571 
0572     Preferences::setLastDocument(QString::fromStdString(d_projectFile.location().string()));
0573 }
0574 
0575 void MainWindow::saveProjectAs()
0576 {
0577     const QString saveProjectPath =
0578         QFileDialog::getSaveFileName(this,
0579                                      tr("CodeVis Project File"),
0580                                      QStandardPaths::writableLocation(QStandardPaths::HomeLocation),
0581                                      tr("CodeVis Project (*.lks)"));
0582 
0583     if (saveProjectPath.isEmpty()) {
0584         return;
0585     }
0586 
0587     d_projectFile.prepareSave();
0588     saveTabsOnProject();
0589 
0590     cpp::result<void, Codethink::lvtprj::ProjectFileError> saved =
0591         d_projectFile.saveAs(saveProjectPath.toStdString(),
0592                              Codethink::lvtprj::ProjectFile::BackupFileBehavior::Discard);
0593     if (saved.has_error()) {
0594         showErrorMessage(tr("Error saving project: %1").arg(QString::fromStdString(saved.error().errorMessage)));
0595         return;
0596     }
0597 
0598     Preferences::setLastDocument(QString::fromStdString(d_projectFile.location().string()));
0599     setWindowTitle(qApp->applicationName() + " " + QString::fromStdString(d_projectFile.location().string()));
0600 }
0601 
0602 void MainWindow::openCodeGenerationWindow()
0603 {
0604     using Codethink::lvtcgn::app::CodegenAppAdapter;
0605     CodegenAppAdapter::run(this, sharedNodeStorage);
0606 }
0607 
0608 void MainWindow::openProjectAction()
0609 {
0610     if (d_projectFile.isOpen()) {
0611         auto result = QMessageBox::question(this,
0612                                             tr("Really close project"),
0613                                             tr("Do you really want to close the project and open another?"),
0614                                             QMessageBox::Button::Yes,
0615                                             QMessageBox::Button::No);
0616         if (result == QMessageBox::Button::No) {
0617             return;
0618         }
0619         closeProject();
0620     }
0621 
0622     auto path = QFileDialog::getOpenFileName(this,
0623                                              tr("CodeVis Project File"),
0624                                              QStandardPaths::writableLocation(QStandardPaths::HomeLocation),
0625                                              tr("CodeVis Project (*.lks)"));
0626 
0627     if (path.isEmpty()) {
0628         // User hits "Cancel" - Nothing to be done.
0629         return;
0630     }
0631 
0632     bool opened = openProjectFromPath(path);
0633     (void) opened; // CPPCHECK
0634 }
0635 
0636 bool MainWindow::openProjectFromPath(const QString& path)
0637 {
0638     if (path.isEmpty()) {
0639         showErrorMessage(tr("Can't open an empty project."));
0640         showWelcomeScreen();
0641         return false;
0642     }
0643 
0644     cpp::result<void, Codethink::lvtprj::ProjectFileError> saved = d_projectFile.open(path.toStdString());
0645     if (saved.has_error()) {
0646         qDebug() << QString::fromStdString(saved.error().errorMessage);
0647         showErrorMessage(tr("Could not open project: %1").arg(QString::fromStdString(saved.error().errorMessage)));
0648         showWelcomeScreen();
0649         return false;
0650     }
0651 
0652     showProjectView();
0653     updateSessionPtr();
0654 
0655     const QString project = QString::fromStdString(d_projectFile.location().string());
0656     Preferences::setLastDocument(project);
0657     setWindowTitle(qApp->applicationName() + " " + project);
0658 
0659     loadTabsFromProject();
0660     bookmarksChanged();
0661     return true;
0662 }
0663 
0664 void MainWindow::loadTabsFromProject()
0665 {
0666     auto leftTabs = d_projectFile.leftPanelTab();
0667     auto rightTabs = d_projectFile.rightPanelTab();
0668 
0669     const auto loadTab = [this](int id, const std::vector<QJsonDocument>& tabs) {
0670         auto *tabWidget = qobject_cast<Codethink::lvtqtw::TabWidget *>(ui.mainSplitter->widget(id));
0671         int idx = 0;
0672         for (const auto& tab : tabs) {
0673             if (idx != 0) {
0674                 tabWidget->openNewGraphTab();
0675             }
0676             auto *currentTabElement = qobject_cast<Codethink::lvtqtw::GraphTabElement *>(tabWidget->widget(idx));
0677             auto *scene = qobject_cast<Codethink::lvtqtc::GraphicsScene *>(currentTabElement->graphicsView()->scene());
0678 
0679             QJsonObject obj = tab.object();
0680 
0681             tabWidget->setTabText(idx, obj["tabname"].toString());
0682             scene->fromJson(tab["scene"].toObject());
0683             idx += 1;
0684         }
0685     };
0686 
0687     loadTab(0, leftTabs);
0688     if (!rightTabs.empty()) {
0689         if (ui.mainSplitter->count() == 1) {
0690             toggleSplitView();
0691         }
0692         loadTab(1, rightTabs);
0693     }
0694 }
0695 
0696 void MainWindow::triggerUndo()
0697 {
0698     if (!d_undoManager_p) {
0699         return;
0700     }
0701 
0702     if (qobject_cast<GraphicsView *>(focusWidget())) {
0703         d_undoManager_p->undo();
0704     }
0705 }
0706 
0707 void MainWindow::triggerRedo()
0708 {
0709     if (!d_undoManager_p) {
0710         return;
0711     }
0712 
0713     if (qobject_cast<GraphicsView *>(focusWidget())) {
0714         d_undoManager_p->redo();
0715     }
0716 }
0717 
0718 void MainWindow::requestSearch()
0719 {
0720     const auto *f = focusWidget();
0721     const auto wdgPairs =
0722         std::initializer_list<std::pair<QLineEdit *, QWidget *>>{{ui.namespaceFilter, ui.namespaceTree},
0723                                                                  {ui.packagesFilter, ui.packagesTree}};
0724 
0725     bool isPanels = false;
0726     for (const auto& [filter, widget] : wdgPairs) {
0727         if (f == filter || f == widget) {
0728             isPanels = true;
0729             filter->setVisible(!filter->isVisible());
0730             if (!filter->isVisible()) {
0731                 filter->setText(QString());
0732             } else {
0733                 filter->setFocus();
0734             }
0735         }
0736     }
0737 
0738     if (!isPanels) {
0739         auto *elm = qobject_cast<Codethink::lvtqtw::GraphTabElement *>(currentGraphTab->currentWidget());
0740 
0741         elm->toggleFilterVisibility();
0742     }
0743 }
0744 
0745 bool MainWindow::eventFilter(QObject *obj, QEvent *event)
0746 {
0747     // Hide search boxes.
0748     if (event->type() == QEvent::KeyPress) {
0749         auto *keyEvent = static_cast<QKeyEvent *>(event); // NOLINT
0750         if (keyEvent->key() != Qt::Key_Escape) {
0751             return false;
0752         }
0753 
0754         auto *lineEdit = qobject_cast<QLineEdit *>(obj);
0755         if (!lineEdit) {
0756             return false;
0757         }
0758         lineEdit->setText(QString());
0759         lineEdit->setVisible(false);
0760         return true;
0761     }
0762     return false;
0763 }
0764 
0765 void MainWindow::openPreferences()
0766 {
0767     Codethink::lvtqtw::ConfigurationDialog confDialog(d_pluginManager_p, this);
0768     confDialog.exec();
0769 }
0770 
0771 void MainWindow::openPreferencesAt(std::optional<QString> preferredPage)
0772 {
0773     Codethink::lvtqtw::ConfigurationDialog confDialog(d_pluginManager_p, this);
0774     if (preferredPage) {
0775         confDialog.changeCurrentWidgetByString(*preferredPage);
0776     }
0777     confDialog.exec();
0778 }
0779 
0780 void MainWindow::closeCurrentTab()
0781 {
0782     if (currentGraphTab) {
0783         currentGraphTab->closeTab(currentGraphTab->currentIndex());
0784     }
0785 }
0786 
0787 void MainWindow::newTab()
0788 {
0789     if (currentGraphTab) {
0790         currentGraphTab->openNewGraphTab();
0791     }
0792 }
0793 
0794 void MainWindow::toggleSplitView() const
0795 {
0796     ui.mainSplitter->toggle();
0797 }
0798 
0799 void MainWindow::selectLeftSplitView() const
0800 {
0801     ui.mainSplitter->setCurrentIndex(0);
0802 }
0803 
0804 void MainWindow::selectRightSplitView() const
0805 {
0806     ui.mainSplitter->setCurrentIndex(1);
0807 }
0808 
0809 void MainWindow::setCurrentGraphFromString(Codethink::lvtmdl::NodeType::Enum type, const QString& qualifiedName)
0810 {
0811     const QModelIndex idx = packageModel->indexForData(std::vector<std::pair<QVariant, int>>({
0812         {qualifiedName, Codethink::lvtmdl::ModelRoles::e_QualifiedName},
0813         {type, Codethink::lvtmdl::ModelRoles::e_NodeType},
0814     }));
0815 
0816     if (!idx.isValid()) {
0817         qDebug() << "Could not find data for" << qualifiedName;
0818     }
0819     setCurrentGraph(idx);
0820 }
0821 
0822 void MainWindow::setCurrentGraph(const QModelIndex& idx)
0823 {
0824     // TODO: Fix This
0825 
0826     // QString qualifiedName = idx.data(ModelRoles::e_QualifiedName).toString();
0827     // NodeType::Enum type = static_cast<NodeType::Enum>(idx.data(ModelRoles::e_NodeType).toInt());
0828     // currentGraphTab->setCurrentGraphTab(TabWidget::GraphInfo{qualifiedName, type});
0829     d_projectFile.setDirty();
0830 }
0831 
0832 void MainWindow::newTabRequested(const QModelIndex& idx)
0833 {
0834     newTabRequested(QModelIndexList({idx}));
0835 }
0836 
0837 void MainWindow::newTabRequested(const QModelIndexList& idxList)
0838 {
0839     QSet<QString> qualifiedNames;
0840     for (const auto idx : idxList) {
0841         qualifiedNames.insert(idx.data(ModelRoles::e_QualifiedName).toString());
0842     }
0843     newTabRequested(qualifiedNames);
0844 }
0845 
0846 void MainWindow::newTabRequested(const QSet<QString> qualifiedNames)
0847 {
0848     currentGraphTab->openNewGraphTab(QSet<QString>({qualifiedNames}));
0849 }
0850 
0851 void MainWindow::exportSvg()
0852 {
0853     using GraphicsView = Codethink::lvtqtc::GraphicsView;
0854     using ExportManager = Codethink::lvtqtw::ExportManager;
0855 
0856     auto *view = qobject_cast<GraphicsView *>(ui.mainSplitter->graphicsView());
0857     assert(view);
0858 
0859     ExportManager exporter(view);
0860     auto res = exporter.exportSvg();
0861     if (res.has_error()) {
0862         showErrorMessage(QString::fromStdString(res.error().what));
0863     }
0864 }
0865 
0866 void MainWindow::changeCurrentGraphWidget(int graphTabIdx)
0867 {
0868     using Codethink::lvtmdl::BaseTableModel;
0869     using Codethink::lvtqtc::GraphicsScene;
0870     using Codethink::lvtqtc::GraphicsView;
0871     using Codethink::lvtqtw::GraphTabElement;
0872 
0873     auto *tab = qobject_cast<GraphTabElement *>(currentGraphTab->widget(graphTabIdx));
0874     if (!tab) {
0875         return;
0876     }
0877     connect(tab, &GraphTabElement::sendMessage, this, &MainWindow::showMessage, Qt::UniqueConnection);
0878 
0879     auto *graphWidget = tab->graphicsView();
0880     if (!graphWidget) {
0881         return;
0882     }
0883 
0884     // disconnect everything related to the old graph and the window.
0885     [&]() {
0886         if (!currentGraphWidget) {
0887             return;
0888         }
0889         disconnect(currentGraphWidget, nullptr, this, nullptr);
0890         disconnect(this, nullptr, currentGraphWidget, nullptr);
0891 
0892         auto *graphicsScene = qobject_cast<GraphicsScene *>(currentGraphWidget->scene());
0893         if (!graphicsScene) {
0894             return;
0895         }
0896         disconnect(graphicsScene, nullptr, this, nullptr);
0897     }();
0898 
0899     currentGraphWidget = graphWidget;
0900     if (!currentGraphWidget) {
0901         return;
0902     }
0903 
0904     // Update window title
0905     auto projectLocation = d_projectFile.location();
0906     auto projectName = projectLocation.empty() ? tr("Untitled") : QString::fromStdString(projectLocation.string());
0907     setWindowTitle(qApp->applicationName() + " " + projectName + " " + currentGraphTab->tabText(graphTabIdx));
0908 
0909     // connect everything related to the new graph widget and the window
0910     auto addGWdgConnection = [this](auto signal, auto slot) {
0911         connect(currentGraphWidget, signal, this, slot, Qt::UniqueConnection);
0912     };
0913     addGWdgConnection(&Codethink::lvtqtc::GraphicsView::graphLoadStarted, &MainWindow::graphLoadStarted);
0914     addGWdgConnection(&Codethink::lvtqtc::GraphicsView::graphLoadFinished, &MainWindow::graphLoadFinished);
0915     addGWdgConnection(&Codethink::lvtqtc::GraphicsView::errorMessage, &MainWindow::showErrorMessage);
0916     addGWdgConnection(&Codethink::lvtqtc::GraphicsView::newSelectionMade, &MainWindow::updateTableModels);
0917 
0918     // connect everything related to the new graph scene and the window
0919     auto *graphicsScene = qobject_cast<GraphicsScene *>(currentGraphWidget->scene());
0920     if (!graphicsScene) {
0921         return;
0922     }
0923     auto addGSConnection = [this, &graphicsScene](auto signal, auto slot) {
0924         connect(graphicsScene, signal, this, slot, Qt::UniqueConnection);
0925     };
0926     addGSConnection(&Codethink::lvtqtc::GraphicsScene::errorMessage, &MainWindow::showErrorMessage);
0927     addGSConnection(&Codethink::lvtqtc::GraphicsScene::requestEnableWindow, &MainWindow::enableWindow);
0928     addGSConnection(&Codethink::lvtqtc::GraphicsScene::requestDisableWindow, &MainWindow::disableWindow);
0929     addGSConnection(&Codethink::lvtqtc::GraphicsScene::createReportActionClicked, &MainWindow::createReport);
0930     addGSConnection(&Codethink::lvtqtc::GraphicsScene::requestNewTab,
0931                     qOverload<const QSet<QString>>(&MainWindow::newTabRequested));
0932 
0933     if (d_pluginManager_p) {
0934         auto getSceneName = [&graphicsScene]() {
0935             return graphicsScene->objectName().toStdString();
0936         };
0937         d_pluginManager_p->callHooksActiveSceneChanged(getSceneName);
0938     }
0939 
0940     addGSConnection(&GraphicsScene::graphLoadFinished, &MainWindow::updatePluginData);
0941 }
0942 
0943 void MainWindow::updatePluginData()
0944 {
0945     if (!d_pluginManager_p) {
0946         return;
0947     }
0948 
0949     auto *graphicsScene = qobject_cast<GraphicsScene *>(currentGraphWidget->scene());
0950 
0951     auto getSceneName = [&graphicsScene]() {
0952         return graphicsScene->objectName().toStdString();
0953     };
0954 
0955     auto getVisibleEntities = [&graphicsScene]() {
0956         auto entities = std::vector<Entity>{};
0957         for (auto&& e : graphicsScene->allEntities()) {
0958             entities.push_back(createWrappedEntityFromLakosEntity(e));
0959         }
0960         return entities;
0961     };
0962 
0963     auto getEdgeByQualifiedName = [graphicsScene](std::string const& fromQualifiedName,
0964                                                   std::string const& toQualifiedName) -> std::optional<Edge> {
0965         auto *fromEntity = graphicsScene->entityByQualifiedName(fromQualifiedName);
0966         if (!fromEntity) {
0967             return std::nullopt;
0968         }
0969         auto *toEntity = graphicsScene->entityByQualifiedName(toQualifiedName);
0970         if (!toEntity) {
0971             return std::nullopt;
0972         }
0973         return createWrappedEdgeFromLakosEntity(fromEntity, toEntity);
0974     };
0975 
0976     auto getProjectData = [this]() {
0977         auto getSourceCodePath = [this]() {
0978             return this->d_projectFile.sourceCodePath().string();
0979         };
0980         return ProjectData{getSourceCodePath};
0981     };
0982 
0983     d_pluginManager_p->callHooksGraphChanged(getSceneName, getVisibleEntities, getEdgeByQualifiedName, getProjectData);
0984 }
0985 
0986 void MainWindow::updateTableModels(std::deque<Codethink::lvtldr::LakosianNode *> selectedNodes)
0987 {
0988     fieldsModel->refreshData(selectedNodes);
0989 }
0990 
0991 void MainWindow::createReport(std::string const& title, std::string const& htmlContents)
0992 {
0993 #ifdef USE_WEB_ENGINE
0994     auto *htmlReportTab = new QWebEngineView(this);
0995 #else
0996     auto *htmlReportTab = new QTextBrowser(this);
0997 #endif
0998     htmlReportTab->setHtml(QString::fromStdString(htmlContents));
0999 
1000     auto idx = d_reportsTabWidget->addTab(htmlReportTab, QString::fromStdString(title));
1001     d_reportsTabWidget->setCurrentIndex(idx);
1002     d_dockReports->show();
1003 }
1004 
1005 void MainWindow::showWarningMessage(const QString& message)
1006 {
1007     showMessage(message, KMessageWidget::MessageType::Warning);
1008 }
1009 
1010 void MainWindow::showErrorMessage(const QString& message)
1011 {
1012     showMessage(message, KMessageWidget::MessageType::Error);
1013 }
1014 
1015 void MainWindow::showSuccessMessage(const QString& message)
1016 {
1017     showMessage(message, KMessageWidget::MessageType::Positive);
1018 }
1019 
1020 QString MainWindow::currentMessage() const
1021 {
1022     return ui.topMessageWidget->text();
1023 }
1024 
1025 void MainWindow::showMessage(const QString& message, KMessageWidget::MessageType type)
1026 {
1027     if (message.isEmpty()) {
1028         ui.topMessageWidget->animatedHide();
1029         return;
1030     }
1031 
1032     ui.topMessageWidget->setText(message);
1033     ui.topMessageWidget->setMessageType(type);
1034     ui.topMessageWidget->animatedShow();
1035 }
1036 
1037 void MainWindow::currentGraphSplitChanged(Codethink::lvtqtw::TabWidget *tabWidget)
1038 {
1039     if (currentGraphTab) {
1040         disconnect(currentGraphTab, &QTabWidget::currentChanged, this, &MainWindow::changeCurrentGraphWidget);
1041         disconnect(currentGraphTab,
1042                    &Codethink::lvtqtw::TabWidget::currentTabTextChanged,
1043                    this,
1044                    &MainWindow::focusedGraphChanged);
1045     }
1046 
1047     currentGraphTab = tabWidget;
1048     connect(tabWidget, &QTabWidget::currentChanged, this, &MainWindow::changeCurrentGraphWidget);
1049     connect(tabWidget, &Codethink::lvtqtw::TabWidget::currentTabTextChanged, this, &MainWindow::focusedGraphChanged);
1050     changeCurrentGraphWidget(tabWidget->currentIndex());
1051 }
1052 
1053 void MainWindow::graphLoadStarted()
1054 {
1055     // HACK: we are throwing two signals in sequence, hitting the assert.
1056     if (d_graphLoadRunning) {
1057         return;
1058     }
1059 
1060     disableWindow();
1061 
1062     QGuiApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
1063 
1064 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1065     ui.topMessageWidget->clearActions();
1066 #else
1067     const auto ourActions = ui.topMessageWidget->actions();
1068     for (auto *action : ourActions) {
1069         ui.topMessageWidget->removeAction(action);
1070     }
1071 #endif
1072 
1073     ui.topMessageWidget->animatedHide();
1074 }
1075 
1076 void MainWindow::graphLoadFinished()
1077 {
1078     QGuiApplication::restoreOverrideCursor();
1079 
1080     enableWindow();
1081 
1082     Q_EMIT databaseIdle();
1083 }
1084 
1085 void MainWindow::mousePressEvent(QMouseEvent *ev)
1086 {
1087     if (!isEnabled()) {
1088         ev->ignore();
1089         return;
1090     }
1091 
1092     QMainWindow::mousePressEvent(ev);
1093 }
1094 
1095 void MainWindow::mouseReleaseEvent(QMouseEvent *ev)
1096 {
1097     if (!isEnabled()) {
1098         ev->ignore();
1099         return;
1100     }
1101 
1102     QMainWindow::mousePressEvent(ev);
1103 }
1104 
1105 void MainWindow::enableWindow()
1106 {
1107     qApp->processEvents();
1108 
1109     for (QWidget *child : qAsConst(d_disabledWidgets)) {
1110         child->setEnabled(true);
1111     }
1112 
1113     d_disabledWidgets.clear();
1114 }
1115 
1116 void MainWindow::disableWindow()
1117 {
1118     // we don't want to disable thre graph load progress bar when we disable
1119     // widgets during a graph load. To achieve this we also have to not disable
1120     // its parent
1121     const QList<QWidget *> neverDisable{ui.centralarea};
1122     for (QWidget *child : findChildren<QWidget *>()) { // clazy:exclude=range-loop,range-loop-detach
1123         if (neverDisable.contains(child)) {
1124             continue;
1125         }
1126         if (child->isEnabled()) {
1127             d_disabledWidgets.append(child);
1128             child->setEnabled(false);
1129         }
1130     }
1131 
1132     qApp->processEvents();
1133 }
1134 
1135 void MainWindow::focusedGraphChanged(const QString& qualifiedName)
1136 {
1137     m_currentQualifiedName = qualifiedName;
1138 
1139     const QString projectName =
1140         d_projectFile.location().empty() ? "Untitled" : QString::fromStdString(d_projectFile.location().string());
1141 
1142     setWindowTitle(qApp->applicationName() + " " + projectName + " " + qualifiedName);
1143 }
1144 
1145 void MainWindow::openGenerateDatabase()
1146 {
1147     using ParseCodebaseDialog = Codethink::lvtqtw::ParseCodebaseDialog;
1148 
1149     if (d_parseCodebaseDialog_p && d_parseCodebaseDialog_p->isVisible()) {
1150         return;
1151     }
1152 
1153     if (!d_parseCodebaseDialog_p) {
1154         d_parseCodebaseDialog_p = std::make_unique<ParseCodebaseDialog>(this);
1155         connect(d_parseCodebaseDialog_p.get(),
1156                 &ParseCodebaseDialog::readyForDbUpdate,
1157                 this,
1158                 &MainWindow::generateDatabaseReadyForUpdate);
1159         connect(d_parseCodebaseDialog_p.get(),
1160                 &ParseCodebaseDialog::parseFinished,
1161                 this,
1162                 &MainWindow::generateCodeDatabaseFinished);
1163         d_status_bar->setParseCodebaseWindow(*d_parseCodebaseDialog_p);
1164 
1165         if (d_pluginManager_p) {
1166             d_parseCodebaseDialog_p->setPluginManager(*d_pluginManager_p);
1167         }
1168     }
1169 
1170     d_parseCodebaseDialog_p->setCodebasePath(QString::fromStdString(d_projectFile.openLocation().string()));
1171     d_parseCodebaseDialog_p->show();
1172 }
1173 
1174 void MainWindow::generateDatabaseReadyForUpdate()
1175 {
1176     assert(d_parseCodebaseDialog_p);
1177 
1178     if (d_graphLoadRunning) {
1179         // call back to the dialog once the database is idle
1180         connect(this, &MainWindow::databaseIdle, this, &MainWindow::prepareForCodeDatabaseUpdate, Qt::UniqueConnection);
1181     } else {
1182         // the database is already idle; call directly
1183         prepareForCodeDatabaseUpdate();
1184     }
1185 }
1186 
1187 void MainWindow::prepareForCodeDatabaseUpdate()
1188 // Database should now be idle
1189 {
1190     /*
1191         // close the database so that we can replace the file
1192         // TODO: we need a proper way to close the database
1193         d_codeDatabase->setPath(":memory:");
1194         if (!d_codeDatabase->open(Codethink::lvtcdb::BaseDb::OpenType::NewDatabase)) {
1195             showErrorMessage(
1196                 tr("Error preparing in-memory database for the current project file. Check the 'Error List' for
1197        details.")); return;
1198         }
1199     */
1200 
1201     // tell parseCodebaseDialog we are ready for it to do its thing
1202     d_parseCodebaseDialog_p->updateDatabase();
1203 }
1204 
1205 void MainWindow::generateCodeDatabaseFinished(Codethink::lvtqtw::ParseCodebaseDialog::State state)
1206 {
1207     disconnect(this, &MainWindow::databaseIdle, this, &MainWindow::prepareForCodeDatabaseUpdate);
1208 
1209     if (state == ParseCodebaseDialog::State::Killed) {
1210         return;
1211     }
1212 
1213     // As soon as you parsed the whole codebase, that means that we need to copy all the
1214     // data to the Cad database, to show on the package tree. We might already have
1215     // things in the cad database, this might clash with the unique keys, so I can't
1216     // just dump the data from one db to another.
1217     // So, for the time being, let's just nuke the CadDb and recreate it.
1218     sharedNodeStorage.closeDatabase();
1219     const auto res = d_projectFile.resetCadDatabaseFromCodeDatabase();
1220     if (res.has_error()) {
1221         showErrorMessage(QString::fromStdString(res.error().errorMessage));
1222         return;
1223     }
1224 
1225     updateSessionPtr();
1226     d_projectFile.setSourceCodePath(d_parseCodebaseDialog_p->sourcePath());
1227 }
1228 
1229 void MainWindow::updateSessionPtr()
1230 {
1231     sharedNodeStorage.setDatabaseSourcePath(d_projectFile.cadDatabasePath().string());
1232     packageModel->reload();
1233 
1234     // TODO: Properly populate GUI models from node storage
1235     //    for (auto *model : std::vector<Codethink::lvtmdl::BaseTreeModel *>{namespaceModel, packageModel}) {
1236     //        model->setDboSession(dboSessionPtr);
1237     //    }
1238     //
1239     //    for (Codethink::lvtmdl::BaseTableModel *model : qAsConst(tableModels)) {
1240     //        model->setDboSession(dboSessionPtr);
1241     //    }
1242     //
1243     //    d_errorModel_p->setDboSession(dboSessionPtr);
1244 }
1245 
1246 void MainWindow::showWelcomeScreen()
1247 {
1248     ui.stackedWidget->setCurrentWidget(ui.welcomePage);
1249     ui.welcomePage->setEnabled(true);
1250 }
1251 
1252 void MainWindow::showProjectView()
1253 {
1254     setProjectWidgetsEnabled(true);
1255     ui.stackedWidget->setCurrentWidget(ui.graphPage);
1256 }
1257 
1258 void MainWindow::openProjectSettings()
1259 {
1260     auto projectSettingsDialog = ProjectSettingsDialog{d_projectFile};
1261     projectSettingsDialog.show();
1262     projectSettingsDialog.exec();
1263 }
1264 
1265 Codethink::lvtprj::ProjectFile& MainWindow::projectFile()
1266 {
1267     return d_projectFile;
1268 }
1269 
1270 void MainWindow::requestMenuPackageView(const QModelIndexList& multiSelection,
1271                                         const QModelIndex& clickedOn,
1272                                         const QPoint& pos)
1273 {
1274     QMenu menu;
1275     QAction *act = menu.addAction(tr("Open in New Tab"));
1276     connect(act, &QAction::triggered, this, [this, multiSelection] {
1277         newTabRequested(multiSelection);
1278     });
1279 
1280     act = menu.addAction(tr("Load on Current Scene"));
1281     connect(act, &QAction::triggered, this, [this, multiSelection] {
1282         auto *scene = qobject_cast<Codethink::lvtqtc::GraphicsScene *>(currentGraphWidget->scene());
1283         for (const auto idx : multiSelection) {
1284             const QString qualName = idx.data(Codethink::lvtmdl::ModelRoles::e_QualifiedName).toString().toLocal8Bit();
1285             scene->loadEntityByQualifiedName(qualName, QPoint());
1286         }
1287         scene->reLayout();
1288     });
1289 
1290     const NodeType::Enum type = static_cast<NodeType::Enum>(clickedOn.data(ModelRoles::e_NodeType).toInt());
1291     if (type == NodeType::e_Package) {
1292         auto *node =
1293             sharedNodeStorage.findByQualifiedName(clickedOn.data(ModelRoles::e_QualifiedName).toString().toStdString());
1294         auto *pkgNode = dynamic_cast<Codethink::lvtldr::PackageNode *>(node);
1295         QString filePath = QString::fromStdString(pkgNode->dirPath());
1296         filePath.replace("${SOURCE_DIR}", QString::fromStdString(projectFile().sourceCodePath().string()));
1297         const QFileInfo fInfo(filePath);
1298 
1299         act = menu.addAction("Open Locally");
1300         if (!fInfo.exists()) {
1301             act->setToolTip(tr("Couldn't find folder for this package."));
1302             act->setEnabled(false);
1303         }
1304 
1305         connect(act, &QAction::triggered, this, [filePath] {
1306             const QUrl localFilePath = QUrl::fromLocalFile(filePath);
1307             QDesktopServices::openUrl(localFilePath);
1308         });
1309     }
1310 
1311     menu.exec(pos);
1312 }
1313 
1314 void MainWindow::requestMenuNamespaceView([[maybe_unused]] const QModelIndexList& multiSelection,
1315                                           const QModelIndex& clickedOn,
1316                                           const QPoint& pos)
1317 {
1318     const NodeType::Enum type = static_cast<NodeType::Enum>(clickedOn.data(ModelRoles::e_NodeType).toInt());
1319     if (type != NodeType::e_Class) {
1320         return;
1321     }
1322 
1323     QMenu menu;
1324     QAction *act = menu.addAction(tr("Load on Empty Scene"));
1325     connect(act, &QAction::triggered, this, [this, clickedOn] {
1326         setCurrentGraph(clickedOn);
1327     });
1328 
1329     act = menu.addAction(tr("Load on Current Scene"));
1330     connect(act, &QAction::triggered, this, [this, clickedOn] {
1331         const QString qualName =
1332             clickedOn.data(Codethink::lvtmdl::ModelRoles::e_QualifiedName).toString().toLocal8Bit();
1333         auto *scene = qobject_cast<Codethink::lvtqtc::GraphicsScene *>(currentGraphWidget->scene());
1334         scene->loadEntityByQualifiedName(qualName, QPoint());
1335         scene->reLayout();
1336     });
1337 
1338     menu.exec(pos);
1339 }
1340 
1341 void MainWindow::bookmarksChanged()
1342 {
1343     QList<QAction *> actions;
1344     for (const auto& bookmark : d_projectFile.bookmarks()) {
1345         auto *bookmarkAction = new QAction(bookmark);
1346         connect(bookmarkAction, &QAction::triggered, this, [this, bookmark] {
1347             QJsonDocument doc = d_projectFile.getBookmark(bookmark);
1348             currentGraphTab->loadBookmark(doc, Codethink::lvtshr::HistoryType::History);
1349         });
1350 
1351         actions.append(bookmarkAction);
1352     }
1353 
1354     unplugActionList("bookmark_actionlist");
1355     plugActionList("bookmark_actionlist", actions);
1356 }
1357 
1358 void MainWindow::configurePluginDocks()
1359 {
1360     if (!d_pluginManager_p) {
1361         return;
1362     }
1363 
1364     auto createPluginDock = [this](std::string const& dockId, std::string const& title) {
1365         using namespace Codethink::lvtqtc;
1366 
1367         auto *pluginDock = new QDockWidget(QString::fromStdString(title), this);
1368         pluginDock->setObjectName(QString::fromStdString(dockId));
1369         addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, pluginDock);
1370         auto *pluginDockWidget = new QWidget();
1371         pluginDockWidget->setLayout(new QFormLayout());
1372         pluginDock->setWidget(pluginDockWidget);
1373         d_pluginManager_p->registerPluginQObject(dockId, pluginDock);
1374         pluginDock->setVisible(false);
1375 
1376         return PluginManagerQtUtils::createPluginDockWidgetHandler(d_pluginManager_p, dockId);
1377     };
1378     d_pluginManager_p->callHooksSetupDockWidget(createPluginDock);
1379 }
1380 
1381 WrappedUiMainWindow::WrappedUiMainWindow(NodeStorage& sharedNodeStorage,
1382                                          ProjectFile& projectFile,
1383                                          PluginManager *pluginManager):
1384     sharedNodeStorage(sharedNodeStorage), projectFile(projectFile), pluginManager(pluginManager)
1385 {
1386 }
1387 
1388 void WrappedUiMainWindow::setupUi(QMainWindow *mw)
1389 {
1390     Ui::MainWindow::setupUi(mw);
1391 
1392     mainSplitter = new Codethink::lvtqtw::SplitterView(sharedNodeStorage, projectFile, pluginManager, graphPage);
1393     mainSplitter->setObjectName(QString::fromUtf8("mainSplitter"));
1394     verticalLayout_10->addWidget(mainSplitter);
1395 }