File indexing completed on 2024-04-28 04:39:09
0001 /* 0002 SPDX-FileCopyrightText: 2005 Roberto Raggi <roberto@kdevelop.org> 0003 SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de> 0004 SPDX-FileCopyrightText: 2008 Aleix Pol <aleixpol@gmail.com> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "projectmanagerview.h" 0010 0011 #include <QAction> 0012 #include <QHeaderView> 0013 #include <QKeyEvent> 0014 #include <QUrl> 0015 0016 #include <KActionCollection> 0017 #include <KActionMenu> 0018 #include <KLocalizedString> 0019 0020 #include <interfaces/iselectioncontroller.h> 0021 #include <interfaces/context.h> 0022 #include <interfaces/icore.h> 0023 #include <interfaces/isession.h> 0024 #include <interfaces/iprojectcontroller.h> 0025 #include <interfaces/iuicontroller.h> 0026 #include <interfaces/idocumentcontroller.h> 0027 #include <interfaces/iproject.h> 0028 #include <project/projectproxymodel.h> 0029 #include <project/projectmodel.h> 0030 #include <serialization/indexedstring.h> 0031 #include <util/path.h> 0032 0033 #include "../openwith/iopenwith.h" 0034 0035 #include <sublime/mainwindow.h> 0036 #include <sublime/area.h> 0037 0038 #include "projectmanagerviewplugin.h" 0039 #include "vcsoverlayproxymodel.h" 0040 #include "ui_projectmanagerview.h" 0041 #include "debug.h" 0042 0043 0044 using namespace KDevelop; 0045 0046 ProjectManagerViewItemContext::ProjectManagerViewItemContext(const QList< ProjectBaseItem* >& items, ProjectManagerView* view) 0047 : ProjectItemContextImpl(items), m_view(view) 0048 { 0049 } 0050 0051 ProjectManagerView *ProjectManagerViewItemContext::view() const 0052 { 0053 return m_view; 0054 } 0055 0056 0057 static const char sessionConfigGroup[] = "ProjectManagerView"; 0058 static const char splitterStateConfigKey[] = "splitterState"; 0059 static const char syncCurrentDocumentKey[] = "syncCurrentDocument"; 0060 static const char targetsVisibleConfigKey[] = "targetsVisible"; 0061 static const int projectTreeViewStrechFactor = 75; // % 0062 static const int projectBuildSetStrechFactor = 25; // % 0063 0064 ProjectManagerView::ProjectManagerView( ProjectManagerViewPlugin* plugin, QWidget *parent ) 0065 : QWidget( parent ), m_ui(new Ui::ProjectManagerView), m_plugin(plugin) 0066 { 0067 m_ui->setupUi( this ); 0068 setFocusProxy(m_ui->projectTreeView); 0069 0070 m_ui->projectTreeView->installEventFilter(this); 0071 0072 setWindowIcon( QIcon::fromTheme( QStringLiteral("project-development"), windowIcon() ) ); 0073 setWindowTitle(i18nc("@title:window", "Projects")); 0074 0075 KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); 0076 if (pmviewConfig.hasKey(splitterStateConfigKey)) { 0077 QByteArray geometry = pmviewConfig.readEntry<QByteArray>(splitterStateConfigKey, QByteArray()); 0078 m_ui->splitter->restoreState(geometry); 0079 } else { 0080 m_ui->splitter->setStretchFactor(0, projectTreeViewStrechFactor); 0081 m_ui->splitter->setStretchFactor(1, projectBuildSetStrechFactor); 0082 } 0083 0084 // keep the project tree view from collapsing (would confuse users) 0085 m_ui->splitter->setCollapsible(0, false); 0086 0087 auto* const syncActionMenu = new KActionMenu(this); 0088 auto* const syncSubAction = plugin->actionCollection()->action(QStringLiteral("locate_document")); 0089 Q_ASSERT(syncSubAction); 0090 for (QAction* action : {static_cast<QAction*>(syncActionMenu), syncSubAction}) { 0091 action->setText(i18nc("@action", "Locate Current Document")); 0092 action->setToolTip(i18nc("@info:tooltip", "Locates the current document in the project tree and selects it.")); 0093 action->setIcon(QIcon::fromTheme(QStringLiteral("dirsync"))); 0094 connect(action, &QAction::triggered, this, &ProjectManagerView::raiseAndLocateCurrentDocument); 0095 } 0096 syncActionMenu->addAction(syncSubAction); 0097 0098 auto* const autoSyncSubAction = new QAction(i18nc("@action", "Auto-Select Current Document"), this); 0099 autoSyncSubAction->setToolTip(i18nc("@info:tooltip", "Automatically select the current document in the project tree.")); 0100 autoSyncSubAction->setCheckable(true); 0101 autoSyncSubAction->setChecked(pmviewConfig.readEntry<bool>(syncCurrentDocumentKey, true)); 0102 connect(autoSyncSubAction, &QAction::triggered, this, &ProjectManagerView::toggleSyncCurrentDocument); 0103 connect(ICore::self()->documentController(), &KDevelop::IDocumentController::documentActivated, this, [autoSyncSubAction, this] { 0104 if (autoSyncSubAction->isChecked()) { 0105 locateCurrentDocument(); 0106 } 0107 }); 0108 // TODO: the above lambda should be connected to IDocumentController::documentUrlChanged as well. However, this 0109 // works incorrectly, because a renamed file is included into its project with a delay. This issue also affects 0110 // other slots connected to documentUrlChanged (see a similar TODO in CompileAnalyzer::CompileAnalyzer()). 0111 syncActionMenu->addAction(autoSyncSubAction); 0112 0113 const auto updateSyncAction = [syncActionMenu, syncSubAction, autoSyncSubAction] { 0114 const bool enable = KDevelop::ICore::self()->documentController()->activeDocument(); 0115 syncActionMenu->setEnabled(enable); 0116 syncSubAction->setEnabled(enable); 0117 autoSyncSubAction->setEnabled(enable); 0118 }; 0119 addAction(syncActionMenu); 0120 0121 m_toggleTargetsAction = new QAction(i18nc("@action", "Show Build Targets"), this); 0122 m_toggleTargetsAction->setCheckable(true); 0123 m_toggleTargetsAction->setChecked(pmviewConfig.readEntry<bool>(targetsVisibleConfigKey, true)); 0124 m_toggleTargetsAction->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); 0125 connect(m_toggleTargetsAction, &QAction::triggered, this, &ProjectManagerView::toggleHideTargets); 0126 addAction(m_toggleTargetsAction); 0127 0128 addAction(plugin->actionCollection()->action(QStringLiteral("project_build"))); 0129 addAction(plugin->actionCollection()->action(QStringLiteral("project_install"))); 0130 addAction(plugin->actionCollection()->action(QStringLiteral("project_clean"))); 0131 0132 connect(m_ui->projectTreeView, &ProjectTreeView::activate, this, &ProjectManagerView::open); 0133 0134 m_ui->buildSetView->setProjectView( this ); 0135 0136 m_modelFilter = new ProjectProxyModel( this ); 0137 m_modelFilter->showTargets(m_toggleTargetsAction->isChecked()); 0138 m_modelFilter->setSourceModel(ICore::self()->projectController()->projectModel()); 0139 m_overlayProxy = new VcsOverlayProxyModel( this ); 0140 m_overlayProxy->setSourceModel(m_modelFilter); 0141 0142 m_ui->projectTreeView->setModel( m_overlayProxy ); 0143 0144 connect( m_ui->projectTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, 0145 this, &ProjectManagerView::selectionChanged ); 0146 connect( KDevelop::ICore::self()->documentController(), &IDocumentController::documentClosed, 0147 this, updateSyncAction); 0148 connect( KDevelop::ICore::self()->documentController(), &IDocumentController::documentActivated, 0149 this, updateSyncAction); 0150 connect( qobject_cast<Sublime::MainWindow*>(KDevelop::ICore::self()->uiController()->activeMainWindow()), &Sublime::MainWindow::areaChanged, 0151 this, updateSyncAction); 0152 selectionChanged(); 0153 0154 updateSyncAction(); 0155 //Update the "sync" button after the initialization has completed, to see whether there already is some open documents 0156 QMetaObject::invokeMethod(this, updateSyncAction, Qt::QueuedConnection); 0157 0158 // Need to set this to get horizontal scrollbar. Also needs to be done after 0159 // the setModel call 0160 m_ui->projectTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents ); 0161 } 0162 0163 bool ProjectManagerView::eventFilter(QObject* obj, QEvent* event) 0164 { 0165 if (obj == m_ui->projectTreeView) { 0166 if (event->type() == QEvent::KeyRelease) { 0167 auto* keyEvent = static_cast<QKeyEvent*>(event); 0168 if (keyEvent->key() == Qt::Key_Delete && keyEvent->modifiers() == Qt::NoModifier) { 0169 m_plugin->removeItems(selectedItems()); 0170 return true; 0171 } else if (keyEvent->key() == Qt::Key_F2 && keyEvent->modifiers() == Qt::NoModifier) { 0172 m_plugin->renameItems(selectedItems()); 0173 return true; 0174 } else if (keyEvent->key() == Qt::Key_C && keyEvent->modifiers() == Qt::ControlModifier) { 0175 m_plugin->copyFromContextMenu(); 0176 return true; 0177 } else if (keyEvent->key() == Qt::Key_V && keyEvent->modifiers() == Qt::ControlModifier) { 0178 m_plugin->pasteFromContextMenu(); 0179 return true; 0180 } 0181 } 0182 } 0183 return QObject::eventFilter(obj, event); 0184 } 0185 0186 void ProjectManagerView::selectionChanged() 0187 { 0188 m_ui->buildSetView->selectionChanged(); 0189 QList<ProjectBaseItem*> selected; 0190 const auto selectedRows = m_ui->projectTreeView->selectionModel()->selectedRows(); 0191 selected.reserve(selectedRows.size()); 0192 for (const auto& idx : selectedRows) { 0193 selected << ICore::self()->projectController()->projectModel()->itemFromIndex(indexFromView( idx )); 0194 } 0195 selected.removeAll(nullptr); 0196 KDevelop::ICore::self()->selectionController()->updateSelection( new ProjectManagerViewItemContext( selected, this ) ); 0197 } 0198 0199 ProjectManagerView::~ProjectManagerView() 0200 { 0201 KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); 0202 pmviewConfig.writeEntry(splitterStateConfigKey, m_ui->splitter->saveState()); 0203 pmviewConfig.sync(); 0204 0205 delete m_ui; 0206 } 0207 0208 QList<KDevelop::ProjectBaseItem*> ProjectManagerView::selectedItems() const 0209 { 0210 QList<KDevelop::ProjectBaseItem*> items; 0211 const auto selectedIndexes = m_ui->projectTreeView->selectionModel()->selectedIndexes(); 0212 for (const QModelIndex& idx : selectedIndexes) { 0213 KDevelop::ProjectBaseItem* item = ICore::self()->projectController()->projectModel()->itemFromIndex(indexFromView(idx)); 0214 if( item ) 0215 items << item; 0216 else 0217 qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "adding an unknown item"; 0218 } 0219 return items; 0220 } 0221 0222 void ProjectManagerView::selectItems(const QList< ProjectBaseItem* >& items) 0223 { 0224 QItemSelection selection; 0225 selection.reserve(items.size()); 0226 for (ProjectBaseItem *item : items) { 0227 QModelIndex indx = indexToView(item->index()); 0228 selection.append(QItemSelectionRange(indx, indx)); 0229 m_ui->projectTreeView->setCurrentIndex(indx); 0230 } 0231 m_ui->projectTreeView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); 0232 } 0233 0234 void ProjectManagerView::expandItem(ProjectBaseItem* item) 0235 { 0236 m_ui->projectTreeView->expand( indexToView(item->index())); 0237 } 0238 0239 void ProjectManagerView::toggleHideTargets(bool visible) 0240 { 0241 KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); 0242 pmviewConfig.writeEntry<bool>(targetsVisibleConfigKey, visible); 0243 m_modelFilter->showTargets(visible); 0244 } 0245 0246 void ProjectManagerView::toggleSyncCurrentDocument(bool sync) 0247 { 0248 KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); 0249 pmviewConfig.writeEntry<bool>(syncCurrentDocumentKey, sync); 0250 if (sync) { 0251 raiseAndLocateCurrentDocument(); 0252 } 0253 } 0254 0255 void ProjectManagerView::raiseAndLocateCurrentDocument() 0256 { 0257 ICore::self()->uiController()->raiseToolView(this); 0258 locateCurrentDocument(); 0259 } 0260 0261 void ProjectManagerView::locateCurrentDocument() 0262 { 0263 KDevelop::IDocument *doc = ICore::self()->documentController()->activeDocument(); 0264 0265 if (!doc) { 0266 // in theory we should never get a null pointer as the action is only enabled 0267 // when there is an active document. 0268 // but: in practice it can happen that you close the last document and press 0269 // the shortcut to locate a doc or vice versa... so just do the failsafe thing here... 0270 return; 0271 } 0272 0273 QModelIndex bestMatch; 0274 const auto projects = ICore::self()->projectController()->projects(); 0275 for (IProject* proj : projects) { 0276 const auto files = proj->filesForPath(IndexedString(doc->url())); 0277 for (KDevelop::ProjectFileItem* item : files) { 0278 QModelIndex index = indexToView(item->index()); 0279 if (index.isValid()) { 0280 if (!bestMatch.isValid()) { 0281 bestMatch = index; 0282 } else if (KDevelop::ProjectBaseItem* parent = item->parent()) { 0283 // prefer files in their real folders over the 'copies' in the target folders 0284 if (!parent->target()) { 0285 bestMatch = index; 0286 break; 0287 } 0288 } 0289 } 0290 } 0291 } 0292 if (bestMatch.isValid()) { 0293 m_ui->projectTreeView->clearSelection(); 0294 m_ui->projectTreeView->setCurrentIndex(bestMatch); 0295 m_ui->projectTreeView->expand(bestMatch); 0296 m_ui->projectTreeView->scrollTo(bestMatch); 0297 } 0298 } 0299 0300 void ProjectManagerView::open( const Path& path ) 0301 { 0302 IOpenWith::openFiles(QList<QUrl>() << path.toUrl()); 0303 } 0304 0305 QModelIndex ProjectManagerView::indexFromView(const QModelIndex& index) const 0306 { 0307 return m_modelFilter->mapToSource( m_overlayProxy->mapToSource(index) ); 0308 } 0309 0310 QModelIndex ProjectManagerView::indexToView(const QModelIndex& index) const 0311 { 0312 return m_overlayProxy->mapFromSource( m_modelFilter->mapFromSource(index) ); 0313 } 0314 0315 #include "moc_projectmanagerview.cpp"