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"