File indexing completed on 2024-04-28 05:49:09
0001 /* This file is part of the Kate project. 0002 * 0003 * SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kateprojectpluginview.h" 0009 #include "branchcheckoutdialog.h" 0010 #include "currentgitbranchbutton.h" 0011 #include "fileutil.h" 0012 #include "gitprocess.h" 0013 #include "gitwidget.h" 0014 #include "kateproject.h" 0015 #include "kateprojectinfoview.h" 0016 #include "kateprojectinfoviewindex.h" 0017 #include "kateprojectplugin.h" 0018 #include "kateprojectview.h" 0019 #include "ktexteditor_utils.h" 0020 0021 #include <KTextEditor/Command> 0022 #include <ktexteditor/application.h> 0023 #include <ktexteditor/document.h> 0024 #include <ktexteditor/editor.h> 0025 #include <ktexteditor/view.h> 0026 0027 #include <KActionCollection> 0028 #include <KActionMenu> 0029 #include <KLocalizedString> 0030 #include <KPluginFactory> 0031 #include <KStandardAction> 0032 #include <KStringHandler> 0033 #include <KXMLGUIFactory> 0034 #include <KXmlGuiWindow> 0035 0036 #include <QAction> 0037 #include <QFileDialog> 0038 #include <QHBoxLayout> 0039 #include <QKeyEvent> 0040 0041 #define PROJECTCLOSEICON "window-close" 0042 0043 K_PLUGIN_FACTORY_WITH_JSON(KateProjectPluginFactory, "kateprojectplugin.json", registerPlugin<KateProjectPlugin>();) 0044 0045 KateProjectPluginView::KateProjectPluginView(KateProjectPlugin *plugin, KTextEditor::MainWindow *mainWin) 0046 : QObject(mainWin) 0047 , m_plugin(plugin) 0048 , m_mainWindow(mainWin) 0049 , m_toolView(nullptr) 0050 , m_toolInfoView(nullptr) 0051 , m_toolMultiView(nullptr) 0052 , m_lookupAction(nullptr) 0053 , m_gotoSymbolAction(nullptr) 0054 , m_gotoSymbolActionAppMenu(nullptr) 0055 { 0056 KXMLGUIClient::setComponentName(QStringLiteral("kateproject"), i18n("Project Manager")); 0057 setXMLFile(QStringLiteral("ui.rc")); 0058 0059 /** 0060 * create toolviews 0061 */ 0062 m_toolView = m_mainWindow->createToolView(m_plugin, 0063 QStringLiteral("kateproject"), 0064 KTextEditor::MainWindow::Left, 0065 QIcon::fromTheme(QStringLiteral("project-open")), 0066 i18n("Projects")); 0067 m_gitToolView.reset(m_mainWindow->createToolView(m_plugin, QStringLiteral("kateprojectgit"), KTextEditor::MainWindow::Left, gitIcon(), i18n("Git"))); 0068 m_toolInfoView = m_mainWindow->createToolView(m_plugin, 0069 QStringLiteral("kateprojectinfo"), 0070 KTextEditor::MainWindow::Bottom, 0071 QIcon::fromTheme(QStringLiteral("view-choose")), 0072 i18n("Project")); 0073 0074 /** 0075 * create the combo + buttons for the toolViews + stacked widgets 0076 */ 0077 m_projectsCombo = new QComboBox(m_toolView); 0078 m_projectsCombo->setToolTip(i18n("Open projects list")); 0079 m_projectsCombo->setFrame(false); 0080 m_reloadButton = new QToolButton(m_toolView); 0081 m_reloadButton->setAutoRaise(true); 0082 m_reloadButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); 0083 m_reloadButton->setToolTip(i18n("Reload project")); 0084 m_closeProjectButton = new QToolButton(m_toolView); 0085 m_closeProjectButton->setAutoRaise(true); 0086 m_closeProjectButton->setToolTip(i18n("Close project")); 0087 m_closeProjectButton->setIcon(QIcon::fromTheme(QStringLiteral(PROJECTCLOSEICON))); 0088 QHBoxLayout *layout = new QHBoxLayout(); 0089 layout->setSpacing(0); 0090 layout->addWidget(m_projectsCombo); 0091 layout->addWidget(m_reloadButton); 0092 layout->addWidget(m_closeProjectButton); 0093 m_toolView->layout()->addItem(layout); 0094 m_toolView->layout()->setSpacing(0); 0095 0096 auto separator = new QFrame(m_toolView); 0097 separator->setFrameShape(QFrame::HLine); 0098 separator->setEnabled(false); 0099 m_toolView->layout()->addWidget(separator); 0100 0101 m_projectsComboGit = new QComboBox(m_gitToolView.get()); 0102 m_projectsComboGit->setFrame(false); 0103 m_gitStatusRefreshButton = new QToolButton(m_gitToolView.get()); 0104 m_gitStatusRefreshButton->setAutoRaise(true); 0105 m_gitStatusRefreshButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); 0106 m_gitStatusRefreshButton->setToolTip(i18n("Refresh git status")); 0107 layout = new QHBoxLayout(); 0108 layout->setSpacing(0); 0109 layout->addWidget(m_projectsComboGit); 0110 layout->addWidget(m_gitStatusRefreshButton); 0111 m_gitToolView->layout()->addItem(layout); 0112 m_gitToolView->layout()->setSpacing(0); 0113 0114 m_stackedProjectViews = new QStackedWidget(m_toolView); 0115 m_stackedProjectInfoViews = new QStackedWidget(m_toolInfoView); 0116 m_stackedGitViews = new QStackedWidget(m_gitToolView.get()); 0117 0118 connect(m_projectsCombo, 0119 static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), 0120 m_projectsComboGit, 0121 static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged)); 0122 connect(m_projectsComboGit, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this](int index) { 0123 m_projectsCombo->setCurrentIndex(index); 0124 }); 0125 connect(m_projectsCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KateProjectPluginView::slotCurrentChanged); 0126 connect(m_reloadButton, &QToolButton::clicked, this, &KateProjectPluginView::slotProjectReload); 0127 0128 connect(m_closeProjectButton, &QToolButton::clicked, this, &KateProjectPluginView::slotCloseProject); 0129 connect(m_plugin, &KateProjectPlugin::pluginViewProjectClosing, this, &KateProjectPluginView::slotHandleProjectClosing); 0130 0131 connect(m_gitStatusRefreshButton, &QToolButton::clicked, this, [this] { 0132 if (auto widget = m_stackedGitViews->currentWidget()) { 0133 qobject_cast<GitWidget *>(widget)->updateStatus(); 0134 } 0135 }); 0136 0137 connect(&m_plugin->fileWatcher(), &QFileSystemWatcher::fileChanged, this, [this](const QString &path) { 0138 if (m_gitChangedWatcherFile == path) { 0139 slotUpdateStatus(true); 0140 } 0141 }); 0142 0143 /** 0144 * create views for all already existing projects 0145 * will create toolviews on demand! 0146 */ 0147 const auto projectList = m_plugin->projects(); 0148 for (KateProject *project : projectList) { 0149 viewForProject(project); 0150 } 0151 // If the list of projects is empty we do not want to restore the tool view from the last session, BUG: 432296 0152 if (projectList.isEmpty()) { 0153 // We have to call this in the next iteration of the event loop, after the session is restored 0154 QTimer::singleShot(0, this, [this]() { 0155 m_mainWindow->hideToolView(m_toolView); 0156 m_mainWindow->hideToolView(m_gitToolView.get()); 0157 m_mainWindow->hideToolView(m_toolInfoView); 0158 if (m_toolMultiView) { 0159 m_mainWindow->hideToolView(m_toolMultiView); 0160 } 0161 }); 0162 } 0163 0164 /** 0165 * connect to important signals, e.g. for auto project view creation 0166 */ 0167 connect(m_plugin, &KateProjectPlugin::projectCreated, this, &KateProjectPluginView::viewForProject); 0168 connect(m_plugin, &KateProjectPlugin::configUpdated, this, &KateProjectPluginView::slotConfigUpdated); 0169 connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateProjectPluginView::slotViewChanged); 0170 connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &KateProjectPluginView::slotViewCreated); 0171 0172 /** 0173 * connect for all already existing views 0174 */ 0175 const auto views = m_mainWindow->views(); 0176 for (KTextEditor::View *view : views) { 0177 slotViewCreated(view); 0178 } 0179 0180 /** 0181 * back + forward 0182 */ 0183 auto a = actionCollection()->addAction(QStringLiteral("projects_open_project"), this, SLOT(openDirectoryOrProject())); 0184 a->setText(i18n("Open Folder...")); 0185 a->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder"))); 0186 actionCollection()->setDefaultShortcut(a, QKeySequence(QKeySequence(QStringLiteral("Ctrl+T, O"), QKeySequence::PortableText))); 0187 0188 m_projectTodosAction = a = actionCollection()->addAction(QStringLiteral("projects_todos"), this, SLOT(showProjectTodos())); 0189 a->setText(i18n("Project TODOs")); 0190 a->setIcon(QIcon::fromTheme(QStringLiteral("korg-todo"))); 0191 0192 m_projectPrevAction = a = actionCollection()->addAction(QStringLiteral("projects_prev_project"), this, SLOT(slotProjectPrev())); 0193 a->setText(i18n("Activate Previous Project")); 0194 a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); 0195 actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Left)); 0196 0197 m_projectNextAction = a = actionCollection()->addAction(QStringLiteral("projects_next_project"), this, SLOT(slotProjectNext())); 0198 a->setText(i18n("Activate Next Project")); 0199 a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); 0200 actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Right)); 0201 0202 m_projectGotoIndexAction = a = actionCollection()->addAction(QStringLiteral("projects_goto_index"), this, SLOT(slotProjectIndex())); 0203 a->setText(i18n("Lookup")); 0204 actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_1)); 0205 0206 m_projectCloseAction = a = actionCollection()->addAction(QStringLiteral("projects_close"), this, SLOT(slotCloseProject())); 0207 a->setText(i18n("Close Project")); 0208 a->setIcon(QIcon::fromTheme(QStringLiteral(PROJECTCLOSEICON))); 0209 0210 m_projectCloseAllAction = a = actionCollection()->addAction(QStringLiteral("projects_close_all"), this, SLOT(slotCloseAllProjects())); 0211 a->setText(i18n("Close All Projects")); 0212 a->setIcon(QIcon::fromTheme(QStringLiteral(PROJECTCLOSEICON))); 0213 0214 m_projectCloseWithoutDocumentsAction = a = 0215 actionCollection()->addAction(QStringLiteral("projects_close_without_open_documents"), this, SLOT(slotCloseAllProjectsWithoutDocuments())); 0216 a->setText(i18n("Close Orphaned Projects")); 0217 a->setIcon(QIcon::fromTheme(QStringLiteral(PROJECTCLOSEICON))); 0218 0219 m_projectReloadAction = a = actionCollection()->addAction(QStringLiteral("project_reload"), this, SLOT(slotProjectReload())); 0220 a->setText(i18n("Reload Project")); 0221 a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); 0222 0223 m_gotoSymbolActionAppMenu = a = actionCollection()->addAction(KStandardAction::Goto, QStringLiteral("projects_goto_symbol"), this, SLOT(slotGotoSymbol())); 0224 0225 auto chckbrAct = actionCollection()->addAction(QStringLiteral("checkout_branch"), this, [this] { 0226 BranchCheckoutDialog bd(mainWindow()->window(), projectBaseDir()); 0227 bd.openDialog(); 0228 }); 0229 chckbrAct->setIcon(QIcon::fromTheme(QStringLiteral("vcs-branch"))); 0230 chckbrAct->setText(i18n("Checkout Git Branch")); 0231 0232 // popup menu 0233 auto popup = new KActionMenu(i18n("Project"), this); 0234 actionCollection()->addAction(QStringLiteral("popup_project"), popup); 0235 0236 m_lookupAction = popup->menu()->addAction(i18n("Lookup: %1", QString()), this, &KateProjectPluginView::slotProjectIndex); 0237 m_gotoSymbolAction = popup->menu()->addAction(i18n("Goto: %1", QString()), this, &KateProjectPluginView::slotGotoSymbol); 0238 0239 connect(popup->menu(), &QMenu::aboutToShow, this, &KateProjectPluginView::slotContextMenuAboutToShow); 0240 0241 connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KateProjectPluginView::handleEsc); 0242 0243 // update git status on toolview visibility change 0244 connect(m_gitToolView.get(), SIGNAL(toolVisibleChanged(bool)), this, SLOT(slotUpdateStatus(bool))); 0245 0246 /** 0247 * add us to gui 0248 */ 0249 m_mainWindow->guiFactory()->addClient(this); 0250 0251 /** 0252 * align to current config 0253 */ 0254 slotConfigUpdated(); 0255 0256 /** 0257 * trigger once view change, to highlight right document 0258 */ 0259 slotViewChanged(); 0260 0261 /** 0262 * ensure proper action update, to enable/disable stuff 0263 */ 0264 connect(this, &KateProjectPluginView::projectMapChanged, this, &KateProjectPluginView::updateActions); 0265 updateActions(); 0266 } 0267 0268 KateProjectPluginView::~KateProjectPluginView() 0269 { 0270 /** 0271 * cleanup for all views 0272 */ 0273 for (QObject *view : qAsConst(m_textViews)) { 0274 KTextEditor::View *v = qobject_cast<KTextEditor::View *>(view); 0275 if (v) { 0276 v->unregisterCompletionModel(m_plugin->completion()); 0277 } 0278 } 0279 0280 /** 0281 * cu toolviews 0282 */ 0283 delete m_toolView; 0284 m_toolView = nullptr; 0285 delete m_toolInfoView; 0286 m_toolInfoView = nullptr; 0287 delete m_toolMultiView; 0288 m_toolMultiView = nullptr; 0289 0290 /** 0291 * cu gui client 0292 */ 0293 m_mainWindow->guiFactory()->removeClient(this); 0294 0295 // Don't watch what nobody use, the old project... 0296 if (!m_gitChangedWatcherFile.isEmpty()) { 0297 m_plugin->fileWatcher().removePath(m_gitChangedWatcherFile); 0298 } 0299 } 0300 0301 void KateProjectPluginView::slotConfigUpdated() 0302 { 0303 if (!m_plugin->multiProjectGoto()) { 0304 delete m_toolMultiView; 0305 m_toolMultiView = nullptr; 0306 } else if (!m_toolMultiView) { 0307 m_toolMultiView = m_mainWindow->createToolView(m_plugin, 0308 QStringLiteral("kateprojectmulti"), 0309 KTextEditor::MainWindow::Bottom, 0310 QIcon::fromTheme(QStringLiteral("view-choose")), 0311 i18n("Projects Index")); 0312 auto gotoindex = new KateProjectInfoViewIndex(this, nullptr, m_toolMultiView); 0313 m_toolMultiView->layout()->addWidget(gotoindex); 0314 } 0315 0316 // update action state 0317 updateActions(); 0318 } 0319 0320 QPair<KateProjectView *, KateProjectInfoView *> KateProjectPluginView::viewForProject(KateProject *project) 0321 { 0322 /** 0323 * needs valid project 0324 */ 0325 Q_ASSERT(project); 0326 0327 /** 0328 * existing view? 0329 */ 0330 if (m_project2View.contains(project)) { 0331 return m_project2View.value(project); 0332 } 0333 0334 /** 0335 * create new views 0336 */ 0337 KateProjectView *view = new KateProjectView(this, project); 0338 KateProjectInfoView *infoView = new KateProjectInfoView(this, project); 0339 GitWidget *gitView = new GitWidget(project, m_mainWindow, this); 0340 0341 /** 0342 * attach to toolboxes 0343 * first the views, then the combo, that triggers signals 0344 */ 0345 m_stackedProjectViews->addWidget(view); 0346 m_stackedProjectInfoViews->addWidget(infoView); 0347 m_stackedGitViews->addWidget(gitView); 0348 m_projectsCombo->addItem(QIcon::fromTheme(QStringLiteral("project-open")), project->name(), project->fileName()); 0349 m_projectsComboGit->addItem(QIcon::fromTheme(QStringLiteral("project-open")), project->name(), project->fileName()); 0350 connect(project, &KateProject::projectMapChanged, this, [this] { 0351 auto widget = m_stackedProjectViews->currentWidget(); 0352 auto project = static_cast<KateProjectView *>(widget)->project(); 0353 if (widget && project == sender()) { 0354 Q_EMIT projectMapChanged(); 0355 0356 int index = m_projectsCombo->findData(project->fileName()); 0357 Q_ASSERT(index == m_projectsCombo->currentIndex()); 0358 if (index != -1) { 0359 m_projectsCombo->setItemText(index, project->name()); 0360 } 0361 } 0362 }); 0363 0364 /* 0365 * inform onward 0366 */ 0367 Q_EMIT pluginProjectAdded(project->baseDir(), project->name()); 0368 0369 /** 0370 * remember and return it 0371 */ 0372 return (m_project2View[project] = QPair<KateProjectView *, KateProjectInfoView *>(view, infoView)); 0373 } 0374 0375 QString KateProjectPluginView::projectFileName() const 0376 { 0377 QWidget *active = m_stackedProjectViews->currentWidget(); 0378 if (!active) { 0379 return QString(); 0380 } 0381 0382 return static_cast<KateProjectView *>(active)->project()->fileName(); 0383 } 0384 0385 QString KateProjectPluginView::projectName() const 0386 { 0387 QWidget *active = m_stackedProjectViews->currentWidget(); 0388 if (!active) { 0389 return QString(); 0390 } 0391 0392 return static_cast<KateProjectView *>(active)->project()->name(); 0393 } 0394 0395 QString KateProjectPluginView::projectBaseDir() const 0396 { 0397 QWidget *active = m_stackedProjectViews->currentWidget(); 0398 if (!active) { 0399 return QString(); 0400 } 0401 0402 return static_cast<KateProjectView *>(active)->project()->baseDir(); 0403 } 0404 0405 QVariantMap KateProjectPluginView::projectMap() const 0406 { 0407 QWidget *active = m_stackedProjectViews->currentWidget(); 0408 if (!active) { 0409 return QVariantMap(); 0410 } 0411 0412 return static_cast<KateProjectView *>(active)->project()->projectMap(); 0413 } 0414 0415 QStringList KateProjectPluginView::projectFiles() const 0416 { 0417 KateProjectView *active = static_cast<KateProjectView *>(m_stackedProjectViews->currentWidget()); 0418 if (!active) { 0419 return QStringList(); 0420 } 0421 0422 return active->project()->files(); 0423 } 0424 0425 QString KateProjectPluginView::allProjectsCommonBaseDir() const 0426 { 0427 auto projects = m_plugin->projects(); 0428 0429 if (projects.empty()) { 0430 return QString(); 0431 } 0432 0433 if (projects.size() == 1) { 0434 return projects[0]->baseDir(); 0435 } 0436 0437 QString commonParent1 = FileUtil::commonParent(projects[0]->baseDir(), projects[1]->baseDir()); 0438 0439 for (int i = 2; i < projects.size(); i++) { 0440 commonParent1 = FileUtil::commonParent(commonParent1, projects[i]->baseDir()); 0441 } 0442 0443 return commonParent1; 0444 } 0445 0446 QStringList KateProjectPluginView::allProjectsFiles() const 0447 { 0448 QStringList fileList; 0449 0450 const auto projectList = m_plugin->projects(); 0451 for (auto project : projectList) { 0452 fileList.append(project->files()); 0453 } 0454 0455 return fileList; 0456 } 0457 0458 QMap<QString, QString> KateProjectPluginView::allProjects() const 0459 { 0460 QMap<QString, QString> projectMap; 0461 0462 const auto projectList = m_plugin->projects(); 0463 for (auto project : projectList) { 0464 projectMap[project->baseDir()] = project->name(); 0465 } 0466 return projectMap; 0467 } 0468 0469 void KateProjectPluginView::slotViewChanged() 0470 { 0471 /** 0472 * get active view 0473 */ 0474 KTextEditor::View *activeView = m_mainWindow->activeView(); 0475 0476 /** 0477 * update pointer, maybe disconnect before 0478 */ 0479 if (m_activeTextEditorView) { 0480 // but only url changed 0481 disconnect(m_activeTextEditorView->document(), &KTextEditor::Document::documentUrlChanged, this, &KateProjectPluginView::slotDocumentUrlChanged); 0482 } 0483 m_activeTextEditorView = activeView; 0484 0485 /** 0486 * no current active view, return 0487 */ 0488 if (!m_activeTextEditorView) { 0489 return; 0490 } 0491 0492 /** 0493 * connect to url changed, for auto load 0494 */ 0495 connect(m_activeTextEditorView->document(), &KTextEditor::Document::documentUrlChanged, this, &KateProjectPluginView::slotDocumentUrlChanged); 0496 0497 /** 0498 * Watch any document, as long as we live, if it's saved 0499 */ 0500 connect(m_activeTextEditorView->document(), 0501 &KTextEditor::Document::documentSavedOrUploaded, 0502 this, 0503 &KateProjectPluginView::slotDocumentSaved, 0504 Qt::UniqueConnection); 0505 0506 /** 0507 * trigger slot once 0508 */ 0509 slotDocumentUrlChanged(m_activeTextEditorView->document()); 0510 } 0511 0512 void KateProjectPluginView::slotDocumentSaved() 0513 { 0514 slotUpdateStatus(true); 0515 } 0516 0517 void KateProjectPluginView::slotCurrentChanged(int index) 0518 { 0519 // trigger change of stacked widgets 0520 m_stackedProjectViews->setCurrentIndex(index); 0521 m_stackedProjectInfoViews->setCurrentIndex(index); 0522 m_stackedGitViews->setCurrentIndex(index); 0523 0524 { 0525 const QSignalBlocker blocker(m_projectsComboGit); 0526 m_projectsComboGit->setCurrentIndex(index); 0527 } 0528 0529 // update focus proxy + open currently selected document 0530 if (QWidget *current = m_stackedProjectViews->currentWidget()) { 0531 m_stackedProjectViews->setFocusProxy(current); 0532 static_cast<KateProjectView *>(current)->openSelectedDocument(); 0533 } 0534 0535 // update focus proxy 0536 if (QWidget *current = m_stackedProjectInfoViews->currentWidget()) { 0537 m_stackedProjectInfoViews->setFocusProxy(current); 0538 } 0539 0540 // update git focus proxy + update status 0541 if (QWidget *current = m_stackedGitViews->currentWidget()) { 0542 m_stackedGitViews->setFocusProxy(current); 0543 } 0544 0545 // Don't watch what nobody use, the old project... 0546 if (!m_gitChangedWatcherFile.isEmpty()) { 0547 m_plugin->fileWatcher().removePath(m_gitChangedWatcherFile); 0548 m_gitChangedWatcherFile.clear(); 0549 } 0550 0551 // ...and start watching the new one 0552 slotUpdateStatus(true); 0553 0554 // project file name might have changed 0555 Q_EMIT projectFileNameChanged(); 0556 Q_EMIT projectMapChanged(); 0557 } 0558 0559 void KateProjectPluginView::slotDocumentUrlChanged(KTextEditor::Document *document) 0560 { 0561 /** 0562 * abort if empty url or no local path 0563 */ 0564 if (document->url().isEmpty() || !document->url().isLocalFile()) { 0565 return; 0566 } 0567 0568 /** 0569 * search matching project 0570 */ 0571 KateProject *project = m_plugin->projectForUrl(document->url()); 0572 if (!project) { 0573 return; 0574 } 0575 0576 /** 0577 * select the file FIRST 0578 */ 0579 m_project2View.value(project).first->selectFile(document->url().toLocalFile()); 0580 0581 /** 0582 * get active project view and switch it, if it is for a different project 0583 * do this AFTER file selection 0584 */ 0585 KateProjectView *active = static_cast<KateProjectView *>(m_stackedProjectViews->currentWidget()); 0586 if (active != m_project2View.value(project).first) { 0587 int index = m_projectsCombo->findData(project->fileName()); 0588 if (index >= 0) { 0589 m_projectsCombo->setCurrentIndex(index); 0590 } 0591 } 0592 } 0593 0594 void KateProjectPluginView::switchToProject(const QDir &dir) 0595 { 0596 /** 0597 * search matching project 0598 */ 0599 KateProject *project = m_plugin->projectForDir(dir); 0600 if (!project) { 0601 return; 0602 } 0603 0604 /** 0605 * get active project view and switch it, if it is for a different project 0606 * do this AFTER file selection 0607 */ 0608 KateProjectView *active = static_cast<KateProjectView *>(m_stackedProjectViews->currentWidget()); 0609 if (active != m_project2View.value(project).first) { 0610 int index = m_projectsCombo->findData(project->fileName()); 0611 if (index >= 0) { 0612 m_projectsCombo->setCurrentIndex(index); 0613 } 0614 } 0615 } 0616 0617 void KateProjectPluginView::runCmdInTerminal(const QString &workingDir, const QString &cmd) 0618 { 0619 m_mainWindow->showToolView(m_toolInfoView); 0620 auto widget = static_cast<KateProjectInfoView *>(m_stackedProjectInfoViews->currentWidget()); 0621 if (!widget) { 0622 return; 0623 } 0624 widget->runCmdInTerminal(workingDir, cmd); 0625 } 0626 0627 void KateProjectPluginView::slotViewCreated(KTextEditor::View *view) 0628 { 0629 /** 0630 * connect to destroyed 0631 */ 0632 connect(view, &KTextEditor::View::destroyed, this, &KateProjectPluginView::slotViewDestroyed); 0633 0634 /** 0635 * add completion model if possible 0636 */ 0637 view->registerCompletionModel(m_plugin->completion()); 0638 0639 /** 0640 * remember for this view we need to cleanup! 0641 */ 0642 m_textViews.insert(view); 0643 } 0644 0645 void KateProjectPluginView::slotViewDestroyed(QObject *view) 0646 { 0647 /** 0648 * remove remembered views for which we need to cleanup on exit! 0649 */ 0650 m_textViews.remove(view); 0651 } 0652 0653 void KateProjectPluginView::slotProjectPrev() 0654 { 0655 if (!m_projectsCombo->count()) { 0656 return; 0657 } 0658 0659 if (m_projectsCombo->currentIndex() == 0) { 0660 m_projectsCombo->setCurrentIndex(m_projectsCombo->count() - 1); 0661 } else { 0662 m_projectsCombo->setCurrentIndex(m_projectsCombo->currentIndex() - 1); 0663 } 0664 } 0665 0666 void KateProjectPluginView::slotProjectNext() 0667 { 0668 if (!m_projectsCombo->count()) { 0669 return; 0670 } 0671 0672 if (m_projectsCombo->currentIndex() + 1 == m_projectsCombo->count()) { 0673 m_projectsCombo->setCurrentIndex(0); 0674 } else { 0675 m_projectsCombo->setCurrentIndex(m_projectsCombo->currentIndex() + 1); 0676 } 0677 } 0678 0679 void KateProjectPluginView::slotProjectReload() 0680 { 0681 /** 0682 * force reload if any active project 0683 */ 0684 if (QWidget *current = m_stackedProjectViews->currentWidget()) { 0685 static_cast<KateProjectView *>(current)->project()->reload(true); 0686 } 0687 /** 0688 * Refresh git status 0689 */ 0690 if (auto widget = m_stackedGitViews->currentWidget()) { 0691 qobject_cast<GitWidget *>(widget)->updateStatus(); 0692 } 0693 } 0694 0695 void KateProjectPluginView::slotCloseProject() 0696 { 0697 if (QWidget *current = m_stackedProjectViews->currentWidget()) { 0698 m_plugin->closeProject(static_cast<KateProjectView *>(current)->project()); 0699 } 0700 } 0701 0702 void KateProjectPluginView::slotCloseAllProjects() 0703 { 0704 // we must close project after project 0705 // project closing might activate a different one and then would load the files of that project again 0706 const auto copiedProjects = m_plugin->projects(); 0707 for (auto project : copiedProjects) { 0708 m_plugin->closeProject(project); 0709 } 0710 } 0711 0712 void KateProjectPluginView::slotCloseAllProjectsWithoutDocuments() 0713 { 0714 // we must close project after project 0715 // project closing might activate a different one and then would load the files of that project again 0716 const auto copiedProjects = m_plugin->projects(); 0717 for (auto project : copiedProjects) { 0718 if (!m_plugin->projectHasOpenDocuments(project)) { 0719 m_plugin->closeProject(project); 0720 } 0721 } 0722 } 0723 0724 void KateProjectPluginView::slotHandleProjectClosing(KateProject *project) 0725 { 0726 const int index = m_plugin->projects().indexOf(project); 0727 m_project2View.erase(m_project2View.find(project)); 0728 0729 QWidget *stackedProjectViewsWidget = m_stackedProjectViews->widget(index); 0730 m_stackedProjectViews->removeWidget(stackedProjectViewsWidget); 0731 delete stackedProjectViewsWidget; 0732 0733 QWidget *stackedProjectInfoViewsWidget = m_stackedProjectInfoViews->widget(index); 0734 m_stackedProjectInfoViews->removeWidget(stackedProjectInfoViewsWidget); 0735 delete stackedProjectInfoViewsWidget; 0736 0737 QWidget *stackedgitViewsWidget = m_stackedGitViews->widget(index); 0738 m_stackedGitViews->removeWidget(stackedgitViewsWidget); 0739 delete stackedgitViewsWidget; 0740 0741 m_projectsCombo->removeItem(index); 0742 m_projectsComboGit->removeItem(index); 0743 0744 // Stop watching what no one is interesting anymore 0745 if (!m_gitChangedWatcherFile.isEmpty()) { 0746 m_plugin->fileWatcher().removePath(m_gitChangedWatcherFile); 0747 m_gitChangedWatcherFile.clear(); 0748 } 0749 0750 // inform onward 0751 Q_EMIT pluginProjectRemoved(project->baseDir(), project->name()); 0752 0753 // update actions, e.g. the close all stuff needs this 0754 updateActions(); 0755 } 0756 0757 QString KateProjectPluginView::currentWord() const 0758 { 0759 KTextEditor::View *kv = m_activeTextEditorView; 0760 if (!kv) { 0761 return QString(); 0762 } 0763 0764 if (kv->selection() && kv->selectionRange().onSingleLine()) { 0765 return kv->selectionText(); 0766 } 0767 0768 return kv->document()->wordAt(kv->cursorPosition()); 0769 } 0770 0771 void KateProjectPluginView::slotProjectIndex() 0772 { 0773 const QString word = currentWord(); 0774 if (!word.isEmpty()) { 0775 auto tabView = qobject_cast<QTabWidget *>(m_stackedProjectInfoViews->currentWidget()); 0776 if (tabView) { 0777 if (auto codeIndex = tabView->findChild<KateProjectInfoViewIndex *>()) { 0778 tabView->setCurrentWidget(codeIndex); 0779 } 0780 } 0781 m_mainWindow->showToolView(m_toolInfoView); 0782 Q_EMIT projectLookupWord(word); 0783 } 0784 } 0785 0786 void KateProjectPluginView::slotGotoSymbol() 0787 { 0788 if (!m_toolMultiView) { 0789 return; 0790 } 0791 0792 const QString word = currentWord(); 0793 if (!word.isEmpty()) { 0794 int results = 0; 0795 Q_EMIT gotoSymbol(word, results); 0796 if (results > 1) { 0797 m_mainWindow->showToolView(m_toolMultiView); 0798 } 0799 } 0800 } 0801 0802 void KateProjectPluginView::slotContextMenuAboutToShow() 0803 { 0804 const QString word = currentWord(); 0805 if (word.isEmpty()) { 0806 return; 0807 } 0808 0809 const QString squeezed = KStringHandler::csqueeze(word, 30); 0810 m_lookupAction->setText(i18n("Lookup: %1", squeezed)); 0811 m_gotoSymbolAction->setText(i18n("Goto: %1", squeezed)); 0812 } 0813 0814 void KateProjectPluginView::handleEsc(QEvent *e) 0815 { 0816 if (!m_mainWindow) { 0817 return; 0818 } 0819 0820 QKeyEvent *k = static_cast<QKeyEvent *>(e); 0821 if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { 0822 const auto infoView = qobject_cast<const KateProjectInfoView *>(m_stackedProjectInfoViews->currentWidget()); 0823 if (m_toolInfoView->isVisible() && (!infoView || !infoView->ignoreEsc())) { 0824 m_mainWindow->hideToolView(m_toolInfoView); 0825 } 0826 } 0827 } 0828 0829 void KateProjectPluginView::slotUpdateStatus(bool visible) 0830 { 0831 if (!visible) { 0832 return; 0833 } 0834 0835 if (auto widget = static_cast<GitWidget *>(m_stackedGitViews->currentWidget())) { 0836 // To support separate-git-dir always use dotGitPath 0837 // We need to add the path every time again because it's always a different file 0838 if (!m_gitChangedWatcherFile.isEmpty()) { 0839 m_plugin->fileWatcher().removePath(m_gitChangedWatcherFile); 0840 } 0841 m_gitChangedWatcherFile = widget->indexPath(); 0842 if (!m_gitChangedWatcherFile.isEmpty()) { 0843 m_plugin->fileWatcher().addPath(m_gitChangedWatcherFile); 0844 } 0845 widget->updateStatus(); 0846 } 0847 } 0848 0849 void KateProjectPluginView::openDirectoryOrProject() 0850 { 0851 // get dir or do nothing 0852 QFileDialog::Options opts; 0853 opts.setFlag(QFileDialog::ShowDirsOnly); 0854 opts.setFlag(QFileDialog::ReadOnly); 0855 const QString dir = QFileDialog::getExistingDirectory(m_mainWindow->window(), i18n("Choose a directory"), QDir::currentPath(), opts); 0856 if (dir.isEmpty()) { 0857 return; 0858 } 0859 0860 openDirectoryOrProject(dir); 0861 } 0862 0863 void KateProjectPluginView::openDirectoryOrProject(const QDir &dir) 0864 { 0865 // switch to this project if there 0866 if (auto project = m_plugin->projectForDir(dir, true)) { 0867 openProject(project); 0868 } 0869 } 0870 0871 void KateProjectPluginView::openProject(KateProject *project) 0872 { 0873 // just activate the right plugin in the toolview 0874 slotActivateProject(project); 0875 0876 // this is a user action, ensure the toolview is visible 0877 mainWindow()->showToolView(m_toolView); 0878 0879 // add the project to the recently opened items list 0880 if (auto *parentClient = qobject_cast<KXmlGuiWindow *>(m_mainWindow->window())) { 0881 if (auto *openRecentAction = parentClient->action(KStandardAction::name(KStandardAction::StandardAction::OpenRecent))) { 0882 if (auto *recentFilesAction = qobject_cast<KRecentFilesAction *>(openRecentAction)) { 0883 recentFilesAction->addUrl(QUrl::fromLocalFile(project->baseDir())); 0884 } 0885 } 0886 } 0887 } 0888 0889 void KateProjectPluginView::showProjectTodos() 0890 { 0891 KTextEditor::Command *pgrep = KTextEditor::Editor::instance()->queryCommand(QStringLiteral("pgrep")); 0892 if (!pgrep) { 0893 return; 0894 } 0895 QString msg; 0896 pgrep->exec(nullptr, QStringLiteral("preg (TODO|FIXME)\\b"), msg); 0897 } 0898 0899 void KateProjectPluginView::openTerminal(const QString &dirPath, KateProject *project) 0900 { 0901 m_mainWindow->showToolView(m_toolInfoView); 0902 0903 if (m_project2View.contains(project)) { 0904 m_project2View.find(project)->second->resetTerminal(dirPath); 0905 } 0906 } 0907 0908 void KateProjectPluginView::updateActions() 0909 { 0910 const bool hasMultipleProjects = m_projectsCombo->count() > 1; 0911 // currently some project active? 0912 const bool projectActive = !projectBaseDir().isEmpty(); 0913 m_projectsCombo->setEnabled(projectActive); 0914 m_projectsComboGit->setEnabled(projectActive); 0915 m_reloadButton->setEnabled(projectActive); 0916 m_closeProjectButton->setEnabled(projectActive); 0917 m_gitStatusRefreshButton->setEnabled(projectActive); 0918 m_projectTodosAction->setEnabled(projectActive); 0919 m_projectPrevAction->setEnabled(projectActive && hasMultipleProjects); 0920 m_projectNextAction->setEnabled(projectActive && hasMultipleProjects); 0921 m_projectCloseAction->setEnabled(projectActive); 0922 m_projectCloseAllAction->setEnabled(hasMultipleProjects); 0923 m_projectCloseWithoutDocumentsAction->setEnabled(m_projectsCombo->count() > 0); 0924 0925 const bool hasIndex = projectActive && m_plugin->getIndexEnabled(); 0926 m_lookupAction->setVisible(hasIndex); 0927 m_gotoSymbolAction->setVisible(hasIndex); 0928 m_projectGotoIndexAction->setVisible(hasIndex); 0929 m_gotoSymbolActionAppMenu->setVisible(hasIndex); 0930 actionCollection()->action(QStringLiteral("popup_project"))->setVisible(hasIndex); 0931 } 0932 0933 void KateProjectPluginView::slotActivateProject(KateProject *project) 0934 { 0935 const int index = m_projectsCombo->findData(project->fileName()); 0936 if (index >= 0) { 0937 m_projectsCombo->setCurrentIndex(index); 0938 } 0939 } 0940 0941 void KateProjectPluginView::updateGitBranchButton(KateProject *project) 0942 { 0943 if (!m_branchBtn) { 0944 m_branchBtn.reset(new CurrentGitBranchButton(mainWindow(), nullptr)); 0945 auto a = actionCollection()->action(QStringLiteral("checkout_branch")); 0946 Q_ASSERT(a); 0947 m_branchBtn->setDefaultAction(a); 0948 Utils::insertWidgetInStatusbar(m_branchBtn.get(), mainWindow()); 0949 } 0950 0951 if (!project || project->baseDir() != projectBaseDir()) { 0952 return; 0953 } 0954 0955 static_cast<CurrentGitBranchButton *>(m_branchBtn.get())->refresh(); 0956 } 0957 0958 #include "kateprojectpluginview.moc" 0959 #include "moc_kateprojectpluginview.cpp"