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 }