diff --git a/interfaces/contextmenuextension.cpp b/interfaces/contextmenuextension.cpp index 8db7f9d2fc..8dc9a11355 100644 --- a/interfaces/contextmenuextension.cpp +++ b/interfaces/contextmenuextension.cpp @@ -1,189 +1,208 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "contextmenuextension.h" #include #include #include #include namespace KDevelop { const QString ContextMenuExtension::FileGroup = QStringLiteral("FileGroup"); const QString ContextMenuExtension::RefactorGroup = QStringLiteral("RefactorGroup"); const QString ContextMenuExtension::BuildGroup = QStringLiteral("BuildGroup"); const QString ContextMenuExtension::RunGroup = QStringLiteral("RunGroup"); const QString ContextMenuExtension::DebugGroup = QStringLiteral("DebugGroup"); const QString ContextMenuExtension::EditGroup = QStringLiteral("EditGroup"); const QString ContextMenuExtension::VcsGroup = QStringLiteral("VcsGroup"); const QString ContextMenuExtension::ProjectGroup = QStringLiteral("ProjectGroup"); const QString ContextMenuExtension::OpenEmbeddedGroup = QStringLiteral("OpenEmbeddedGroup"); const QString ContextMenuExtension::OpenExternalGroup = QStringLiteral("OpenExternalGroup"); +const QString ContextMenuExtension::AnalyzeGroup = QStringLiteral("AnalyzeGroup"); const QString ContextMenuExtension::ExtensionGroup = QStringLiteral("ExtensionGroup"); class ContextMenuExtensionPrivate { public: QMap > extensions; }; ContextMenuExtension::ContextMenuExtension() : d(new ContextMenuExtensionPrivate) { } ContextMenuExtension::~ContextMenuExtension() { delete d; } ContextMenuExtension::ContextMenuExtension( const ContextMenuExtension& rhs ) : d( new ContextMenuExtensionPrivate ) { d->extensions = rhs.d->extensions; } ContextMenuExtension& ContextMenuExtension::operator=( const ContextMenuExtension& rhs ) { if( this == &rhs ) return *this; d->extensions = rhs.d->extensions; return *this; } QList ContextMenuExtension::actions( const QString& group ) const { return d->extensions.value( group, QList() ); } void ContextMenuExtension::addAction( const QString& group, QAction* action ) { if( !d->extensions.contains( group ) ) { d->extensions.insert( group, QList() << action ); } else { d->extensions[group].append( action ); } } void ContextMenuExtension::populateMenu(QMenu* menu, const QList& extensions) { QList buildActions; QList vcsActions; QList extActions; QList refactorActions; QList debugActions; + QList analyzeActions; foreach( const ContextMenuExtension &ext, extensions ) { foreach( QAction* act, ext.actions( ContextMenuExtension::BuildGroup ) ) { buildActions << act; } foreach( QAction* act, ext.actions( ContextMenuExtension::VcsGroup ) ) { vcsActions << act; } + foreach( QAction* act, ext.actions( ContextMenuExtension::AnalyzeGroup ) ) + { + analyzeActions << act; + } + foreach( QAction* act, ext.actions( ContextMenuExtension::ExtensionGroup ) ) { extActions << act; } foreach( QAction* act, ext.actions( ContextMenuExtension::RefactorGroup ) ) { refactorActions << act; } foreach( QAction* act, ext.actions( ContextMenuExtension::DebugGroup ) ) { debugActions << act; } } if(!buildActions.isEmpty()) { foreach(QAction* action, buildActions) menu->addAction(action); menu->addSeparator(); } foreach( const ContextMenuExtension &ext, extensions ) { foreach( QAction* act, ext.actions( ContextMenuExtension::FileGroup ) ) { menu->addAction( act ); } menu->addSeparator(); foreach( QAction* act, ext.actions( ContextMenuExtension::EditGroup ) ) { menu->addAction( act ); } } QMenu* debugmenu = menu; if( debugActions.count() > 1 ) { debugmenu = menu->addMenu( i18n("Debug") ); } foreach( QAction* act, debugActions ) { debugmenu->addAction( act ); } menu->addSeparator(); QMenu* refactormenu = menu; if( refactorActions.count() > 1 ) { refactormenu = menu->addMenu( i18n("Refactor") ); } foreach( QAction* act, refactorActions ) { refactormenu->addAction( act ); } menu->addSeparator(); QMenu* vcsmenu = menu; if( vcsActions.count() > 1 ) { vcsmenu = menu->addMenu( i18n("Version Control") ); } foreach( QAction* act, vcsActions ) { vcsmenu->addAction( act ); } + menu->addSeparator(); + QMenu* analyzeMenu = menu; + if( analyzeActions.count() > 1 ) + { + analyzeMenu = menu->addMenu( i18n("Analyze With") ); + analyzeMenu->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok"))); + } + foreach( QAction* act, analyzeActions ) + { + analyzeMenu->addAction( act ); + } menu->addSeparator(); + foreach( QAction* act, extActions ) { menu->addAction( act ); } } } diff --git a/interfaces/contextmenuextension.h b/interfaces/contextmenuextension.h index b17f73cd48..57309c295b 100644 --- a/interfaces/contextmenuextension.h +++ b/interfaces/contextmenuextension.h @@ -1,104 +1,106 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_CONTEXTMENUEXTENSION_H #define KDEVPLATFORM_CONTEXTMENUEXTENSION_H #include #include #include "interfacesexport.h" class QAction; class QMenu; template class QList; namespace KDevelop { /** * For documentation on how to use this class, @see interfaces/context.h */ class KDEVPLATFORMINTERFACES_EXPORT ContextMenuExtension { public: /** The menu group containing file actions */ static const QString FileGroup; /** menu group containing refactoring actions */ static const QString RefactorGroup; /** menu group to contain build support actions */ static const QString BuildGroup; /** menu group to contain run actions */ static const QString RunGroup; /** menu group to contain debug actions */ static const QString DebugGroup; /** menu group to contain editing actions */ static const QString EditGroup; /** menu group to contain version control actions */ static const QString VcsGroup; /** menu group to contain project actions */ static const QString ProjectGroup; /** menu group to contain open in embedded editor actions */ static const QString OpenEmbeddedGroup; /** menu group to contain open with external application actions */ static const QString OpenExternalGroup; + /** menu group to contain analyze actions */ + static const QString AnalyzeGroup; /** menu group that can contain any extension menu. * Actions for this extension will always be at the end * of the menu. Plugins using this should think about * providing a submenu, so the context menu doesn't get cluttered. */ static const QString ExtensionGroup; /** * create new context menu extension object */ ContextMenuExtension(); ~ContextMenuExtension(); ContextMenuExtension( const ContextMenuExtension& rhs ); ContextMenuExtension& operator=( const ContextMenuExtension& rhs ); /** * Add an action to the given menu group * @param group the menu group to which the action should be added * @param action the action to add to the menu group */ void addAction( const QString& group, QAction* action ); /** * Return all actions that are in the menu group * @param group the menu group from which to get the actions * @returns a list of actions for that menu group */ QList actions( const QString& group ) const; /** * Populate a QMenu with the actions in the given context menu extensions. */ static void populateMenu(QMenu* menu, const QList& extensions); private: class ContextMenuExtensionPrivate* const d; }; } #endif diff --git a/plugins/projectmanagerview/projecttreeview.cpp b/plugins/projectmanagerview/projecttreeview.cpp index 3d479be1db..8d128ad984 100644 --- a/plugins/projectmanagerview/projecttreeview.cpp +++ b/plugins/projectmanagerview/projecttreeview.cpp @@ -1,459 +1,473 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2009 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projecttreeview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "projectmanagerviewplugin.h" #include "projectmodelsaver.h" #include "projectmodelitemdelegate.h" #include "debug.h" #include #include #include #include #include using namespace KDevelop; namespace { const char settingsConfigGroup[] = "ProjectTreeView"; QList fileItemsWithin(const QList& items) { QList fileItems; fileItems.reserve(items.size()); foreach(ProjectBaseItem* item, items) { if (ProjectFileItem *file = item->file()) fileItems.append(file); else if (item->folder()) fileItems.append(fileItemsWithin(item->children())); } return fileItems; } QList topLevelItemsWithin(QList items) { std::sort(items.begin(), items.end(), ProjectBaseItem::pathLessThan); Path lastFolder; for (int i = items.size() - 1; i >= 0; --i) { if (lastFolder.isParentOf(items[i]->path())) items.removeAt(i); else if (items[i]->folder()) lastFolder = items[i]->path(); } return items; } template void filterDroppedItems(QList &items, ProjectBaseItem* dest) { for (int i = items.size() - 1; i >= 0; --i) { //No drag and drop from and to same location if (items[i]->parent() == dest) items.removeAt(i); //No moving between projects (technically feasible if the projectmanager is the same though...) else if (items[i]->project() != dest->project()) items.removeAt(i); } } //TODO test whether this could be replaced by projectbuildsetwidget.cpp::showContextMenu_appendActions void popupContextMenu_appendActions(QMenu& menu, const QList& actions) { menu.addActions(actions); menu.addSeparator(); } } ProjectTreeView::ProjectTreeView( QWidget *parent ) : QTreeView( parent ), m_ctxProject( 0 ) { header()->hide(); setEditTriggers( QAbstractItemView::EditKeyPressed ); setContextMenuPolicy( Qt::CustomContextMenu ); setSelectionMode( QAbstractItemView::ExtendedSelection ); setIndentation(10); setDragEnabled(true); setDragDropMode(QAbstractItemView::InternalMove); setAutoScroll(true); setAutoExpandDelay(300); setItemDelegate(new ProjectModelItemDelegate(this)); connect( this, &ProjectTreeView::customContextMenuRequested, this, &ProjectTreeView::popupContextMenu ); connect( this, &ProjectTreeView::activated, this, &ProjectTreeView::slotActivated ); connect( ICore::self(), &ICore::aboutToShutdown, this, &ProjectTreeView::aboutToShutdown); connect( ICore::self()->projectController(), &IProjectController::projectOpened, this, &ProjectTreeView::restoreState ); connect( ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectTreeView::saveState ); restoreState(); } ProjectTreeView::~ProjectTreeView() { } ProjectBaseItem* ProjectTreeView::itemAtPos(QPoint pos) { return indexAt(pos).data(ProjectModel::ProjectItemRole).value(); } void ProjectTreeView::dropEvent(QDropEvent* event) { ProjectItemContext* selectionCtxt = static_cast(KDevelop::ICore::self()->selectionController()->currentSelection()); ProjectBaseItem* destItem = itemAtPos(event->pos()); if (destItem && (dropIndicatorPosition() == AboveItem || dropIndicatorPosition() == BelowItem)) destItem = destItem->parent(); if (selectionCtxt && destItem) { if (ProjectFolderItem *folder = destItem->folder()) { QMenu dropMenu(this); QString seq = QKeySequence( Qt::ShiftModifier ).toString(); seq.chop(1); // chop superfluous '+' QAction* move = new QAction(i18n( "&Move Here" ) + '\t' + seq, &dropMenu); move->setIcon(QIcon::fromTheme(QStringLiteral("go-jump"))); dropMenu.addAction(move); seq = QKeySequence( Qt::ControlModifier ).toString(); seq.chop(1); QAction* copy = new QAction(i18n( "&Copy Here" ) + '\t' + seq, &dropMenu); copy->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); dropMenu.addAction(copy); dropMenu.addSeparator(); QAction* cancel = new QAction(i18n( "C&ancel" ) + '\t' + QKeySequence( Qt::Key_Escape ).toString(), &dropMenu); cancel->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); dropMenu.addAction(cancel); QAction *executedAction = 0; Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); if (modifiers == Qt::ControlModifier) { executedAction = copy; } else if (modifiers == Qt::ShiftModifier) { executedAction = move; } else { executedAction = dropMenu.exec(this->mapToGlobal(event->pos())); } QList usefulItems = topLevelItemsWithin(selectionCtxt->items()); filterDroppedItems(usefulItems, destItem); Path::List paths; foreach (ProjectBaseItem* i, usefulItems) { paths << i->path(); } bool success = false; if (executedAction == copy) { success = destItem->project()->projectFileManager()->copyFilesAndFolders(paths, folder); } else if (executedAction == move) { success = destItem->project()->projectFileManager()->moveFilesAndFolders(usefulItems, folder); } if (success) { //expand target folder expand( mapFromItem(folder)); //and select new items QItemSelection selection; foreach (const Path &path, paths) { const Path targetPath(folder->path(), path.lastPathSegment()); foreach (ProjectBaseItem *item, folder->children()) { if (item->path() == targetPath) { QModelIndex indx = mapFromItem( item ); selection.append(QItemSelectionRange(indx, indx)); setCurrentIndex(indx); } } } selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } } else if (destItem->target() && destItem->project()->buildSystemManager()) { QMenu dropMenu(this); QString seq = QKeySequence( Qt::ControlModifier ).toString(); seq.chop(1); QAction* addToTarget = new QAction(i18n( "&Add to Target" ) + '\t' + seq, &dropMenu); addToTarget->setIcon(QIcon::fromTheme(QStringLiteral("edit-link"))); dropMenu.addAction(addToTarget); dropMenu.addSeparator(); QAction* cancel = new QAction(i18n( "C&ancel" ) + '\t' + QKeySequence( Qt::Key_Escape ).toString(), &dropMenu); cancel->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); dropMenu.addAction(cancel); QAction *executedAction = 0; Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); if (modifiers == Qt::ControlModifier) { executedAction = addToTarget; } else { executedAction = dropMenu.exec(this->mapToGlobal(event->pos())); } if (executedAction == addToTarget) { QList usefulItems = fileItemsWithin(selectionCtxt->items()); filterDroppedItems(usefulItems, destItem); destItem->project()->buildSystemManager()->addFilesToTarget(usefulItems, destItem->target()); } } } event->accept(); } QModelIndex ProjectTreeView::mapFromSource(const QAbstractProxyModel* proxy, const QModelIndex& sourceIdx) { const QAbstractItemModel* next = proxy->sourceModel(); Q_ASSERT(next == sourceIdx.model() || qobject_cast(next)); if(next == sourceIdx.model()) return proxy->mapFromSource(sourceIdx); else { const QAbstractProxyModel* nextProxy = qobject_cast(next); QModelIndex idx = mapFromSource(nextProxy, sourceIdx); Q_ASSERT(idx.model() == nextProxy); return proxy->mapFromSource(idx); } } QModelIndex ProjectTreeView::mapFromItem(const ProjectBaseItem* item) { QModelIndex ret = mapFromSource(qobject_cast(model()), item->index()); Q_ASSERT(ret.model() == model()); return ret; } void ProjectTreeView::slotActivated( const QModelIndex &index ) { if ( QApplication::keyboardModifiers() & Qt::CTRL || QApplication::keyboardModifiers() & Qt::SHIFT ) { // Do not open file when Ctrl or Shift is pressed; that's for selection return; } KDevelop::ProjectBaseItem *item = index.data(ProjectModel::ProjectItemRole).value(); if ( item && item->file() ) { emit activate( item->file()->path() ); } } void ProjectTreeView::popupContextMenu( const QPoint &pos ) { QList itemlist; if ( indexAt(pos).isValid() ) { QModelIndexList indexes = selectionModel()->selectedRows(); foreach( const QModelIndex& index, indexes ) { if ( KDevelop::ProjectBaseItem *item = index.data(ProjectModel::ProjectItemRole).value() ) itemlist << item; } } if( !itemlist.isEmpty() ) { m_ctxProject = itemlist.at(0)->project(); } else { m_ctxProject = 0; } QMenu menu( this ); KDevelop::ProjectItemContextImpl context(itemlist); QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); QList buildActions; QList vcsActions; + QList analyzeActions; QList extActions; QList projectActions; QList fileActions; QList runActions; foreach( const ContextMenuExtension& ext, extensions ) { buildActions += ext.actions(ContextMenuExtension::BuildGroup); fileActions += ext.actions(ContextMenuExtension::FileGroup); projectActions += ext.actions(ContextMenuExtension::ProjectGroup); vcsActions += ext.actions(ContextMenuExtension::VcsGroup); + analyzeActions += ext.actions(ContextMenuExtension::AnalyzeGroup); extActions += ext.actions(ContextMenuExtension::ExtensionGroup); runActions += ext.actions(ContextMenuExtension::RunGroup); } + if ( analyzeActions.count() ) + { + QMenu* analyzeMenu = new QMenu( i18n("Analyze With"), this ); + analyzeMenu->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok"))); + foreach( QAction* act, analyzeActions ) + { + analyzeMenu->addAction( act ); + } + analyzeActions = {analyzeMenu->menuAction()}; + } + popupContextMenu_appendActions(menu, buildActions); popupContextMenu_appendActions(menu, runActions ); popupContextMenu_appendActions(menu, fileActions); popupContextMenu_appendActions(menu, vcsActions); + popupContextMenu_appendActions(menu, analyzeActions); popupContextMenu_appendActions(menu, extActions); if ( !itemlist.isEmpty() && itemlist.size() == 1 && itemlist[0]->folder() && !itemlist[0]->folder()->parent() ) { QAction* projectConfig = new QAction(i18n("Open Configuration..."), this); projectConfig->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect( projectConfig, &QAction::triggered, this, &ProjectTreeView::openProjectConfig ); projectActions << projectConfig; } popupContextMenu_appendActions(menu, projectActions); if(!itemlist.isEmpty()) KDevelop::populateParentItemsMenu(itemlist.front(), &menu); if ( !menu.isEmpty() ) { menu.exec( mapToGlobal( pos ) ); } } void ProjectTreeView::openProjectConfig() { if( m_ctxProject ) { IProjectController* ip = ICore::self()->projectController(); ip->configureProject( m_ctxProject ); } } void ProjectTreeView::saveState() { KConfigGroup configGroup( ICore::self()->activeSession()->config(), settingsConfigGroup ); ProjectModelSaver saver; saver.setView( this ); saver.saveState( configGroup ); } void ProjectTreeView::restoreState(IProject* project) { KConfigGroup configGroup( ICore::self()->activeSession()->config(), settingsConfigGroup ); ProjectModelSaver saver; saver.setProject( project ); saver.setView( this ); saver.restoreState( configGroup ); } void ProjectTreeView::aboutToShutdown() { // save all projects, not just the last one that is closed disconnect( ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectTreeView::saveState ); saveState(); } bool ProjectTreeView::event(QEvent* event) { if(event->type()==QEvent::ToolTip) { QPoint p = mapFromGlobal(QCursor::pos()); QModelIndex idxView = indexAt(p); ProjectBaseItem* it = idxView.data(ProjectModel::ProjectItemRole).value(); QModelIndex idx; if(it) idx = it->index(); if((m_idx!=idx || !m_tooltip) && it && it->file()) { m_idx=idx; ProjectFileItem* file=it->file(); KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); TopDUContext* top= DUChainUtils::standardContextForUrl(file->path().toUrl()); if(m_tooltip) m_tooltip->close(); if(top) { QWidget* navigationWidget = top->createNavigationWidget(); if( navigationWidget ) { m_tooltip = new KDevelop::NavigationToolTip(this, mapToGlobal(p) + QPoint(40, 0), navigationWidget); m_tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "tooltip size" << m_tooltip->size(); ActiveToolTip::showToolTip(m_tooltip); return true; } } } } return QAbstractItemView::event(event); } void ProjectTreeView::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Return && currentIndex().isValid() && state()!=QAbstractItemView::EditingState) { event->accept(); slotActivated(currentIndex()); } else QTreeView::keyPressEvent(event); } void ProjectTreeView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const { if (WidgetColorizer::colorizeByProject()) { const auto projectPath = index.data(ProjectModel::ProjectRole).value()->path(); const QColor color = WidgetColorizer::colorForId(qHash(projectPath), palette(), true); WidgetColorizer::drawBranches(this, painter, rect, index, color); } QTreeView::drawBranches(painter, rect, index); }