diff --git a/plugins/projectmanagerview/projectmanagerviewplugin.cpp b/plugins/projectmanagerview/projectmanagerviewplugin.cpp index 04af3a453d..b01bd66b36 100644 --- a/plugins/projectmanagerview/projectmanagerviewplugin.cpp +++ b/plugins/projectmanagerview/projectmanagerviewplugin.cpp @@ -1,714 +1,715 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2007 Andreas Pakulat 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 "projectmanagerviewplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "projectmanagerview.h" #include "debug.h" using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_PROJECTMANAGERVIEW, "kdevplatform.plugins.projectmanagerview") K_PLUGIN_FACTORY_WITH_JSON(ProjectManagerFactory, "kdevprojectmanagerview.json", registerPlugin();) class KDevProjectManagerViewFactory: public KDevelop::IToolViewFactory { public: KDevProjectManagerViewFactory( ProjectManagerViewPlugin *plugin ): mplugin( plugin ) {} virtual QWidget* create( QWidget *parent = 0 ) override { return new ProjectManagerView( mplugin, parent ); } virtual Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } virtual QString id() const override { return "org.kdevelop.ProjectsView"; } private: ProjectManagerViewPlugin *mplugin; }; class ProjectManagerViewPluginPrivate { public: ProjectManagerViewPluginPrivate() {} KDevProjectManagerViewFactory *factory; QList ctxProjectItemList; QAction* m_buildAll; QAction* m_build; QAction* m_install; QAction* m_clean; QAction* m_configure; QAction* m_prune; }; static QList itemsFromIndexes(const QList& indexes) { QList items; ProjectModel* model = ICore::self()->projectController()->projectModel(); foreach(const QModelIndex& index, indexes) { items += model->itemFromIndex(index); } return items; } ProjectManagerViewPlugin::ProjectManagerViewPlugin( QObject *parent, const QVariantList& ) : IPlugin( "kdevprojectmanagerview", parent ), d(new ProjectManagerViewPluginPrivate) { d->m_buildAll = new QAction( i18n("Build all Projects"), this ); d->m_buildAll->setIcon(QIcon::fromTheme("run-build")); connect( d->m_buildAll, &QAction::triggered, this, &ProjectManagerViewPlugin::buildAllProjects ); actionCollection()->addAction( "project_buildall", d->m_buildAll ); d->m_build = new QAction( i18n("Build Selection"), this ); d->m_build->setIconText( i18n("Build") ); actionCollection()->setDefaultShortcut( d->m_build, Qt::Key_F8 ); d->m_build->setIcon(QIcon::fromTheme("run-build")); d->m_build->setEnabled( false ); connect( d->m_build, &QAction::triggered, this, &ProjectManagerViewPlugin::buildProjectItems ); actionCollection()->addAction( "project_build", d->m_build ); d->m_install = new QAction( i18n("Install Selection"), this ); d->m_install->setIconText( i18n("Install") ); d->m_install->setIcon(QIcon::fromTheme("run-build-install")); actionCollection()->setDefaultShortcut( d->m_install, Qt::SHIFT + Qt::Key_F8 ); d->m_install->setEnabled( false ); connect( d->m_install, &QAction::triggered, this, &ProjectManagerViewPlugin::installProjectItems ); actionCollection()->addAction( "project_install", d->m_install ); d->m_clean = new QAction( i18n("Clean Selection"), this ); d->m_clean->setIconText( i18n("Clean") ); d->m_clean->setIcon(QIcon::fromTheme("run-build-clean")); d->m_clean->setEnabled( false ); connect( d->m_clean, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanProjectItems ); actionCollection()->addAction( "project_clean", d->m_clean ); d->m_configure = new QAction( i18n("Configure Selection"), this ); + d->m_configure->setMenuRole( QAction::NoRole ); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item d->m_configure->setIconText( i18n("Configure") ); d->m_configure->setIcon(QIcon::fromTheme("run-build-configure")); d->m_configure->setEnabled( false ); connect( d->m_configure, &QAction::triggered, this, &ProjectManagerViewPlugin::configureProjectItems ); actionCollection()->addAction( "project_configure", d->m_configure ); d->m_prune = new QAction( i18n("Prune Selection"), this ); d->m_prune->setIconText( i18n("Prune") ); d->m_prune->setIcon(QIcon::fromTheme("run-build-prune")); d->m_prune->setEnabled( false ); connect( d->m_prune, &QAction::triggered, this, &ProjectManagerViewPlugin::pruneProjectItems ); actionCollection()->addAction( "project_prune", d->m_prune ); // only add the action so that its known in the actionCollection // and so that it's shortcut etc. pp. is restored // apparently that is not possible to be done in the view itself *sigh* actionCollection()->addAction( "locate_document" ); setXMLFile( "kdevprojectmanagerview.rc" ); d->factory = new KDevProjectManagerViewFactory( this ); core()->uiController()->addToolView( i18n("Projects"), d->factory ); connect(core()->selectionController(), &ISelectionController::selectionChanged, this, &ProjectManagerViewPlugin::updateActionState); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsInserted, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsRemoved, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::modelReset, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); } void ProjectManagerViewPlugin::updateFromBuildSetChange() { updateActionState( core()->selectionController()->currentSelection() ); } void ProjectManagerViewPlugin::updateActionState( KDevelop::Context* ctx ) { bool isEmpty = ICore::self()->projectController()->buildSetModel()->items().isEmpty(); if( isEmpty ) { isEmpty = !ctx || ctx->type() != Context::ProjectItemContext || dynamic_cast( ctx )->items().isEmpty(); } d->m_build->setEnabled( !isEmpty ); d->m_install->setEnabled( !isEmpty ); d->m_clean->setEnabled( !isEmpty ); d->m_configure->setEnabled( !isEmpty ); d->m_prune->setEnabled( !isEmpty ); } ProjectManagerViewPlugin::~ProjectManagerViewPlugin() { delete d; } void ProjectManagerViewPlugin::unload() { qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "unloading manager view"; core()->uiController()->removeToolView(d->factory); } ContextMenuExtension ProjectManagerViewPlugin::contextMenuExtension( KDevelop::Context* context ) { if( context->type() != KDevelop::Context::ProjectItemContext ) return IPlugin::contextMenuExtension( context ); KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); d->ctxProjectItemList.clear(); if( items.isEmpty() ) return IPlugin::contextMenuExtension( context ); //TODO: also needs: removeTarget, removeFileFromTarget, runTargetsFromContextMenu ContextMenuExtension menuExt; bool needsCreateFile = true; bool needsCreateFolder = true; bool needsCloseProjects = true; bool needsBuildItems = true; bool needsFolderItems = true; bool needsRemoveAndRename = true; bool needsRemoveTargetFiles = true; bool needsPaste = true; //needsCreateFile if there is one item and it's a folder or target needsCreateFile &= (items.count() == 1) && (items.first()->folder() || items.first()->target()); //needsCreateFolder if there is one item and it's a folder needsCreateFolder &= (items.count() == 1) && (items.first()->folder()); needsPaste = needsCreateFolder; foreach( ProjectBaseItem* item, items ) { d->ctxProjectItemList << item->index(); //needsBuildItems if items are limited to targets and buildfolders needsBuildItems &= item->target() || item->type() == ProjectBaseItem::BuildFolder; //needsCloseProjects if items are limited to top level folders (Project Folders) needsCloseProjects &= item->folder() && !item->folder()->parent(); //needsFolderItems if items are limited to folders needsFolderItems &= (bool)item->folder(); //needsRemove if items are limited to non-top-level folders or files that don't belong to targets needsRemoveAndRename &= (item->folder() && item->parent()) || (item->file() && !item->parent()->target()); //needsRemoveTargets if items are limited to file items with target parents needsRemoveTargetFiles &= (item->file() && item->parent()->target()); } if ( needsCreateFile ) { QAction* action = new QAction( i18n( "Create File" ), this ); action->setIcon(QIcon::fromTheme("document-new")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFileFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsCreateFolder ) { QAction* action = new QAction( i18n( "Create Folder" ), this ); action->setIcon(QIcon::fromTheme("folder-new")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFolderFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsBuildItems ) { QAction* action = new QAction( i18nc( "@action", "Build" ), this ); action->setIcon(QIcon::fromTheme("run-build")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::buildItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "Install" ), this ); action->setIcon(QIcon::fromTheme("run-install")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::installItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "Clean" ), this ); action->setIcon(QIcon::fromTheme("run-clean")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18n( "Add to Build Set" ), this ); action->setIcon(QIcon::fromTheme("list-add")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); } if ( needsCloseProjects ) { QAction* close = new QAction( i18np( "Close Project", "Close Projects", items.count() ), this ); close->setIcon(QIcon::fromTheme("project-development-close")); connect( close, &QAction::triggered, this, &ProjectManagerViewPlugin::closeProjects ); menuExt.addAction( ContextMenuExtension::ProjectGroup, close ); } if ( needsFolderItems ) { QAction* action = new QAction( i18n( "Reload" ), this ); action->setIcon(QIcon::fromTheme("view-refresh")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::reloadFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsRemoveAndRename ) { QAction* remove = new QAction( i18n( "Remove" ), this ); remove->setIcon(QIcon::fromTheme("user-trash")); connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); QAction* rename = new QAction( i18n( "Rename" ), this ); rename->setIcon(QIcon::fromTheme("edit-rename")); connect( rename, &QAction::triggered, this, &ProjectManagerViewPlugin::renameItemFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, rename ); } if ( needsRemoveTargetFiles ) { QAction* remove = new QAction( i18n( "Remove From Target" ), this ); remove->setIcon(QIcon::fromTheme("user-trash")); connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeTargetFilesFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); } { QAction* copy = KStandardAction::copy(this, SLOT(copyFromContextMenu()), this); copy->setShortcutContext(Qt::WidgetShortcut); menuExt.addAction( ContextMenuExtension::FileGroup, copy ); } if (needsPaste) { QAction* paste = KStandardAction::paste(this, SLOT(pasteFromContextMenu()), this); paste->setShortcutContext(Qt::WidgetShortcut); menuExt.addAction( ContextMenuExtension::FileGroup, paste ); } return menuExt; } void ProjectManagerViewPlugin::closeProjects() { QList projectsToClose; ProjectModel* model = ICore::self()->projectController()->projectModel(); foreach( const QModelIndex& index, d->ctxProjectItemList ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex(index); if( !projectsToClose.contains( item->project() ) ) { projectsToClose << item->project(); } } d->ctxProjectItemList.clear(); foreach( KDevelop::IProject* proj, projectsToClose ) { core()->projectController()->closeProject( proj ); } } void ProjectManagerViewPlugin::installItemsFromContextMenu() { runBuilderJob( BuilderJob::Install, itemsFromIndexes(d->ctxProjectItemList) ); d->ctxProjectItemList.clear(); } void ProjectManagerViewPlugin::cleanItemsFromContextMenu() { runBuilderJob( BuilderJob::Clean, itemsFromIndexes( d->ctxProjectItemList ) ); d->ctxProjectItemList.clear(); } void ProjectManagerViewPlugin::buildItemsFromContextMenu() { runBuilderJob( BuilderJob::Build, itemsFromIndexes( d->ctxProjectItemList ) ); d->ctxProjectItemList.clear(); } QList ProjectManagerViewPlugin::collectAllProjects() { QList items; foreach( KDevelop::IProject* project, core()->projectController()->projects() ) { items << project->projectItem(); } return items; } void ProjectManagerViewPlugin::buildAllProjects() { runBuilderJob( BuilderJob::Build, collectAllProjects() ); } QList ProjectManagerViewPlugin::collectItems() { QList items; QList buildItems = ICore::self()->projectController()->buildSetModel()->items(); if( !buildItems.isEmpty() ) { foreach( const BuildItem& buildItem, buildItems ) { if( ProjectBaseItem* item = buildItem.findItem() ) { items << item; } } } else { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); items = ctx->items(); } return items; } void ProjectManagerViewPlugin::runBuilderJob( BuilderJob::BuildType type, QList items ) { BuilderJob* builder = new BuilderJob; builder->addItems( type, items ); builder->updateJobName(); ICore::self()->uiController()->registerStatus(new JobStatus(builder)); ICore::self()->runController()->registerJob( builder ); } void ProjectManagerViewPlugin::installProjectItems() { runBuilderJob( KDevelop::BuilderJob::Install, collectItems() ); } void ProjectManagerViewPlugin::pruneProjectItems() { runBuilderJob( KDevelop::BuilderJob::Prune, collectItems() ); } void ProjectManagerViewPlugin::configureProjectItems() { runBuilderJob( KDevelop::BuilderJob::Configure, collectItems() ); } void ProjectManagerViewPlugin::cleanProjectItems() { runBuilderJob( KDevelop::BuilderJob::Clean, collectItems() ); } void ProjectManagerViewPlugin::buildProjectItems() { runBuilderJob( KDevelop::BuilderJob::Build, collectItems() ); } void ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { ICore::self()->projectController()->buildSetModel()->addProjectItem( item ); } } void ProjectManagerViewPlugin::runTargetsFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { KDevelop::ProjectExecutableTargetItem* t=item->executable(); if(t) { qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "Running target: " << t->text() << t->builtUrl(); } } } void ProjectManagerViewPlugin::projectConfiguration( ) { if( !d->ctxProjectItemList.isEmpty() ) { ProjectModel* model = ICore::self()->projectController()->projectModel(); core()->projectController()->configureProject( model->itemFromIndex(d->ctxProjectItemList.at( 0 ))->project() ); } } void ProjectManagerViewPlugin::reloadFromContextMenu( ) { QList< KDevelop::ProjectFolderItem* > folders; foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { // since reloading should be recursive, only pass the upper-most items bool found = false; foreach ( KDevelop::ProjectFolderItem* existing, folders ) { if ( existing->path().isParentOf(item->folder()->path()) ) { // simply skip this child found = true; break; } else if ( item->folder()->path().isParentOf(existing->path()) ) { // remove the child in the list and add the current item instead folders.removeOne(existing); // continue since there could be more than one existing child } } if ( !found ) { folders << item->folder(); } } } foreach( KDevelop::ProjectFolderItem* folder, folders ) { folder->project()->projectFileManager()->reload(folder); } } void ProjectManagerViewPlugin::createFolderFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { if ( item->folder() ) { QWidget* window(ICore::self()->uiController()->activeMainWindow()->window()); QString name = QInputDialog::getText ( window, i18n ( "Create Folder in %1", item->folder()->path().pathOrUrl() ), i18n ( "Folder Name" ) ); if (!name.isEmpty()) { item->project()->projectFileManager()->addFolder( Path(item->path(), name), item->folder() ); } } } } void ProjectManagerViewPlugin::removeFromContextMenu() { removeItems(itemsFromIndexes( d->ctxProjectItemList )); } void ProjectManagerViewPlugin::removeItems(const QList< ProjectBaseItem* >& items) { if (items.isEmpty()) { return; } //copy the list of selected items and sort it to guarantee parents will come before children QList sortedItems = items; std::sort(sortedItems.begin(), sortedItems.end(), ProjectBaseItem::pathLessThan); Path lastFolder; QMap< IProjectFileManager*, QList > filteredItems; QStringList itemPaths; foreach( KDevelop::ProjectBaseItem* item, sortedItems ) { if (item->isProjectRoot()) { continue; } else if (item->folder() || item->file()) { //make sure no children of folders that will be deleted are listed if (lastFolder.isParentOf(item->path())) { continue; } else if (item->folder()) { lastFolder = item->path(); } IProjectFileManager* manager = item->project()->projectFileManager(); if (manager) { filteredItems[manager] << item; itemPaths << item->path().pathOrUrl(); } } } if (filteredItems.isEmpty()) { return; } if (KMessageBox::warningYesNoList( QApplication::activeWindow(), i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", itemPaths.size()), itemPaths, i18n("Delete Files"), KStandardGuiItem::del(), KStandardGuiItem::cancel() ) == KMessageBox::No) { return; } //Go though projectmanagers, have them remove the files and folders that they own QMap< IProjectFileManager*, QList >::iterator it; for (it = filteredItems.begin(); it != filteredItems.end(); ++it) { Q_ASSERT(it.key()); it.key()->removeFilesAndFolders(it.value()); } } void ProjectManagerViewPlugin::removeTargetFilesFromContextMenu() { QList items = itemsFromIndexes( d->ctxProjectItemList ); QMap< IBuildSystemManager*, QList > itemsByBuildSystem; foreach(ProjectBaseItem *item, items) itemsByBuildSystem[item->project()->buildSystemManager()].append(item->file()); QMap< IBuildSystemManager*, QList >::iterator it; for (it = itemsByBuildSystem.begin(); it != itemsByBuildSystem.end(); ++it) it.key()->removeFilesFromTargets(it.value()); } void ProjectManagerViewPlugin::renameItemFromContextMenu() { renameItems(itemsFromIndexes( d->ctxProjectItemList )); } void ProjectManagerViewPlugin::renameItems(const QList< ProjectBaseItem* >& items) { if (items.isEmpty()) { return; } QWidget* window = ICore::self()->uiController()->activeMainWindow()->window(); foreach( KDevelop::ProjectBaseItem* item, items ) { if ((item->type()!=ProjectBaseItem::BuildFolder && item->type()!=ProjectBaseItem::Folder && item->type()!=ProjectBaseItem::File) || !item->parent()) { continue; } const QString src = item->text(); //Change QInputDialog->KFileSaveDialog? QString name = QInputDialog::getText( window, i18n("Rename..."), i18n("New name for '%1':", item->text()), QLineEdit::Normal, item->text() ); if (!name.isEmpty() && name != src) { ProjectBaseItem::RenameStatus status = item->rename( name ); switch(status) { case ProjectBaseItem::RenameOk: break; case ProjectBaseItem::ExistingItemSameName: KMessageBox::error(window, i18n("There is already a file named '%1'", name)); break; case ProjectBaseItem::ProjectManagerRenameFailed: KMessageBox::error(window, i18n("Could not rename '%1'", name)); break; case ProjectBaseItem::InvalidNewName: KMessageBox::error(window, i18n("'%1' is not a valid file name", name)); break; } } } } ProjectFileItem* createFile(const ProjectFolderItem* item) { QWidget* window = ICore::self()->uiController()->activeMainWindow()->window(); QString name = QInputDialog::getText(window, i18n("Create File in %1", item->path().pathOrUrl()), i18n("File name:")); if(name.isEmpty()) return 0; ProjectFileItem* ret = item->project()->projectFileManager()->addFile( Path(item->path(), name), item->folder() ); if (ret) { ICore::self()->documentController()->openDocument( ret->path().toUrl() ); } return ret; } void ProjectManagerViewPlugin::createFileFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { createFile(item->folder()); } else if ( item->target() ) { ProjectFolderItem* folder=dynamic_cast(item->parent()); if(folder) { ProjectFileItem* f=createFile(folder); if(f) item->project()->buildSystemManager()->addFilesToTarget(QList() << f, item->target()); } } } } void ProjectManagerViewPlugin::copyFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); QList urls; foreach (ProjectBaseItem* item, ctx->items()) { if (item->folder() || item->file()) { urls << item->path().toUrl(); } } qCDebug(PLUGIN_PROJECTMANAGERVIEW) << urls; if (!urls.isEmpty()) { QMimeData* data = new QMimeData; data->setUrls(urls); qApp->clipboard()->setMimeData(data); } } void ProjectManagerViewPlugin::pasteFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (ctx->items().count() != 1) return; //do nothing if multiple or none items are selected ProjectBaseItem* destItem = ctx->items().first(); if (!destItem->folder()) return; //do nothing if the target is not a directory const QMimeData* data = qApp->clipboard()->mimeData(); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << data->urls(); const Path::List paths = toPathList(data->urls()); bool success = destItem->project()->projectFileManager()->copyFilesAndFolders(paths, destItem->folder()); if (success) { ProjectManagerViewItemContext* viewCtx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (viewCtx) { //expand target folder viewCtx->view()->expandItem(destItem); //and select new items QList newItems; foreach (const Path &path, paths) { const Path targetPath(destItem->path(), path.lastPathSegment()); foreach (ProjectBaseItem *item, destItem->children()) { if (item->path() == targetPath) { newItems << item; } } } viewCtx->view()->selectItems(newItems); } } } #include "projectmanagerviewplugin.moc" diff --git a/shell/mainwindow_p.cpp b/shell/mainwindow_p.cpp index 1e0e3614f0..ac3d471ee5 100644 --- a/shell/mainwindow_p.cpp +++ b/shell/mainwindow_p.cpp @@ -1,452 +1,453 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo 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 "mainwindow_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "partdocument.h" #include "partcontroller.h" #include "uicontroller.h" #include "statusbar.h" #include "mainwindow.h" #include "textdocument.h" #include "sessioncontroller.h" #include "debug.h" #include "ktexteditorpluginintegration.h" #include #include #include #include #include namespace KDevelop { MainWindowPrivate::MainWindowPrivate(MainWindow *mainWindow) : QObject(mainWindow) , m_mainWindow(mainWindow) , m_statusBar(0) , lastXMLGUIClientView(0) , m_changingActiveView(false) , m_kateWrapper(new KTextEditorIntegration::MainWindow(mainWindow)) { } void MainWindowPrivate::setupGui() { m_statusBar = new KDevelop::StatusBar(m_mainWindow); setupStatusBar(); } void MainWindowPrivate::setupStatusBar() { QWidget *location = m_mainWindow->statusBarLocation(); if (m_statusBar) location->layout()->addWidget(m_statusBar); } void MainWindowPrivate::addPlugin( IPlugin *plugin ) { qCDebug(SHELL) << "add plugin" << plugin << plugin->componentName(); Q_ASSERT( plugin ); //The direct plugin client can only be added to the first mainwindow if(m_mainWindow == Core::self()->uiControllerInternal()->mainWindows()[0]) m_mainWindow->guiFactory()->addClient( plugin ); Q_ASSERT(!m_pluginCustomClients.contains(plugin)); KXMLGUIClient* ownClient = plugin->createGUIForMainWindow(m_mainWindow); if(ownClient) { m_pluginCustomClients[plugin] = ownClient; connect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed); m_mainWindow->guiFactory()->addClient(ownClient); } } void MainWindowPrivate::pluginDestroyed(QObject* pluginObj) { IPlugin* plugin = static_cast(pluginObj); KXMLGUIClient* p = m_pluginCustomClients.take(plugin); m_mainWindow->guiFactory()->removeClient( p ); delete p; } MainWindowPrivate::~MainWindowPrivate() { qDeleteAll(m_pluginCustomClients); } void MainWindowPrivate::removePlugin( IPlugin *plugin ) { Q_ASSERT( plugin ); pluginDestroyed(plugin); disconnect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed); m_mainWindow->guiFactory()->removeClient( plugin ); } void MainWindowPrivate::activePartChanged(KParts::Part *part) { if ( Core::self()->uiController()->activeMainWindow() == m_mainWindow) m_mainWindow->createGUI(part); } void MainWindowPrivate::changeActiveView(Sublime::View *view) { //disable updates on a window to avoid toolbar flickering on xmlgui client change Sublime::HoldUpdates s(m_mainWindow); mergeView(view); if(!view) return; IDocument *doc = dynamic_cast(view->document()); if (doc) { doc->activate(view, m_mainWindow); } else { //activated view is not a part document so we need to remove active part gui ///@todo adymo: only this window needs to remove GUI // KParts::Part *activePart = Core::self()->partController()->activePart(); // if (activePart) // guiFactory()->removeClient(activePart); } } void MainWindowPrivate::mergeView(Sublime::View* view) { PushPositiveValue block(m_changingActiveView, true); // If the previous view was KXMLGUIClient, remove its actions // In the case that that view was removed, lastActiveView // will auto-reset, and xmlguifactory will disconnect that // client, I think. if (lastXMLGUIClientView) { qCDebug(SHELL) << "clearing last XML GUI client" << lastXMLGUIClientView; m_mainWindow->guiFactory()->removeClient(dynamic_cast(lastXMLGUIClientView)); disconnect (lastXMLGUIClientView, &QWidget::destroyed, this, 0); lastXMLGUIClientView = NULL; } if (!view) return; QWidget* viewWidget = view->widget(); Q_ASSERT(viewWidget); qCDebug(SHELL) << "changing active view to" << view << "doc" << view->document() << "mw" << m_mainWindow; // If the new view is KXMLGUIClient, add it. if (KXMLGUIClient* c = dynamic_cast(viewWidget)) { qCDebug(SHELL) << "setting new XMLGUI client" << viewWidget; lastXMLGUIClientView = viewWidget; m_mainWindow->guiFactory()->addClient(c); connect(viewWidget, &QWidget::destroyed, this, &MainWindowPrivate::xmlguiclientDestroyed); } } void MainWindowPrivate::xmlguiclientDestroyed(QObject* obj) { /* We're informed the QWidget for the active view that is also KXMLGUIclient is dying. KXMLGUIFactory will not like deleted clients, really. Unfortunately, there's nothing we can do at this point. For example, KateView derives from QWidget and KXMLGUIClient. The destroyed() signal is emitted by ~QWidget. At this point, event attempt to cross-cast to KXMLGUIClient is undefined behaviour. We hope to catch view deletion a bit later, but if we fail, we better report it now, rather than get a weird crash a bit later. */ Q_ASSERT(obj == lastXMLGUIClientView); Q_ASSERT(false && "xmlgui clients management is messed up"); Q_UNUSED(obj); } void MainWindowPrivate::setupActions() { connect(Core::self()->sessionController(), &SessionController::quitSession, this, &MainWindowPrivate::quitAll); QAction* action; const QString app = qApp->applicationName(); action = KStandardAction::preferences( this, SLOT(settingsDialog()), actionCollection()); action->setToolTip( i18nc( "%1 = application name", "Configure %1", app ) ); action->setWhatsThis( i18n( "Lets you customize %1.", app ) ); action = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); action->setText( i18n("Configure Notifications...") ); action->setToolTip( i18nc("@info:tooltip", "Configure notifications") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog that lets you configure notifications." ) ); action = actionCollection()->addAction( "about_platform", this, SLOT(showAboutPlatform()) ); action->setText( i18n("About KDevelop Platform") ); + action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'About KDevelop' action->setStatusTip( i18n("Show Information about KDevelop Platform") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about KDevelop Platform." ) ); action = actionCollection()->addAction( "loaded_plugins", this, SLOT(showLoadedPlugins()) ); action->setText( i18n("Loaded Plugins") ); action->setStatusTip( i18n("Show a list of all loaded plugins") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about all loaded plugins." ) ); action = actionCollection()->addAction( "view_next_window" ); action->setText( i18n( "&Next Window" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextWindow ); actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::SHIFT + Qt::Key_Right ); action->setToolTip( i18nc( "@info:tooltip", "Next window" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next window." ) ); action->setIcon(QIcon::fromTheme("go-next")); action = actionCollection()->addAction( "view_previous_window" ); action->setText( i18n( "&Previous Window" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousWindow ); actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::SHIFT + Qt::Key_Left ); action->setToolTip( i18nc( "@info:tooltip", "Previous window" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous window." ) ); action->setIcon(QIcon::fromTheme("go-previous")); action = actionCollection()->addAction("next_error"); action->setText(i18n("Jump to Next Outputmark")); actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::Key_F4) ); action->setIcon(QIcon::fromTheme("arrow-right")); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextItem); action = actionCollection()->addAction("prev_error"); action->setText(i18n("Jump to Previous Outputmark")); actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::SHIFT | Qt::Key_F4) ); action->setIcon(QIcon::fromTheme("arrow-left")); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPrevItem); action = actionCollection()->addAction( "split_horizontal" ); action->setIcon(QIcon::fromTheme( "view-split-top-bottom" )); action->setText( i18n( "Split View &Top/Bottom" ) ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_T ); connect( action, &QAction::triggered, this, &MainWindowPrivate::splitHorizontal ); action->setToolTip( i18nc( "@info:tooltip", "Split horizontal" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view horizontally." ) ); action = actionCollection()->addAction( "split_vertical" ); action->setIcon(QIcon::fromTheme( "view-split-left-right" )); action->setText( i18n( "Split View &Left/Right" ) ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_L ); connect( action, &QAction::triggered, this, &MainWindowPrivate::splitVertical ); action->setToolTip( i18nc( "@info:tooltip", "Split vertical" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view vertically." ) ); action = actionCollection()->addAction( "view_next_split" ); action->setText( i18n( "&Next Split View" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextSplit ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_N ); action->setToolTip( i18nc( "@info:tooltip", "Next split view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next split view." ) ); action->setIcon(QIcon::fromTheme("go-next")); action = actionCollection()->addAction( "view_previous_split" ); action->setText( i18n( "&Previous Split View" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousSplit ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_P ); action->setToolTip( i18nc( "@info:tooltip", "Previous split view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous split view." ) ); action->setIcon(QIcon::fromTheme("go-previous")); action = KStandardAction::fullScreen( this, SLOT(toggleFullScreen(bool)), m_mainWindow, actionCollection() ); action = actionCollection()->addAction( "file_new" ); action->setIcon(QIcon::fromTheme("document-new")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_N ); action->setText( i18n( "&New" ) ); action->setIconText( i18nc( "Shorter Text for 'New File' shown in the toolbar", "New") ); connect( action, &QAction::triggered, this, &MainWindowPrivate::fileNew ); action->setToolTip( i18nc( "@info:tooltip", "New file" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Creates an empty file." ) ); action = actionCollection()->addAction( "add_toolview" ); action->setIcon(QIcon::fromTheme("window-new")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_V ); action->setText( i18n( "&Add Tool View..." ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::viewAddNewToolView ); action->setToolTip( i18nc( "@info:tooltip", "Add tool view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Adds a new tool view to this window." ) ); } void MainWindowPrivate::toggleArea(bool b) { if (!b) return; QAction* action = qobject_cast(sender()); if (!action) return; m_mainWindow->controller()->showArea(action->data().toString(), m_mainWindow); } KActionCollection * MainWindowPrivate::actionCollection() { return m_mainWindow->actionCollection(); } void MainWindowPrivate::registerStatus(QObject* status) { m_statusBar->registerStatus(status); } void MainWindowPrivate::showErrorMessage(QString message, int timeout) { m_statusBar->showErrorMessage(message, timeout); } void MainWindowPrivate::tabContextMenuRequested(Sublime::View* view, QMenu* menu) { m_tabView = view; QAction* action; action = menu->addAction(QIcon::fromTheme("view-split-top-bottom"), i18n("Split View Top/Bottom")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitHorizontal); action = menu->addAction(QIcon::fromTheme("view-split-left-right"), i18n("Split View Left/Right")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitVertical); menu->addSeparator(); action = menu->addAction(QIcon::fromTheme("document-new"), i18n("New File")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuFileNew); if (view) { if (TextDocument* doc = dynamic_cast(view->document())) { action = menu->addAction(QIcon::fromTheme("view-refresh"), i18n("Reload")); connect(action, &QAction::triggered, doc, &TextDocument::reload); action = menu->addAction(QIcon::fromTheme("view-refresh"), i18n("Reload All")); connect(action, &QAction::triggered, this, &MainWindowPrivate::reloadAll); } } } void MainWindowPrivate::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) { if (m_tabTooltip.second) { if (m_tabTooltip.first == view) { // tooltip already shown, don't do anything. prevents flicker when moving mouse over same tab return; } else { m_tabTooltip.second.data()->close(); } } Sublime::UrlDocument* urlDoc = dynamic_cast(view->document()); if (urlDoc) { DUChainReadLocker lock; TopDUContext* top = DUChainUtils::standardContextForUrl(urlDoc->url()); if (top) { if ( QWidget* navigationWidget = top->createNavigationWidget() ) { NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(m_mainWindow, QCursor::pos() + QPoint(20, 20), navigationWidget); tooltip->resize(navigationWidget->sizeHint() + QSize(10, 10)); tooltip->setHandleRect(container->tabRect(tab)); m_tabTooltip.first = view; m_tabTooltip.second = tooltip; ActiveToolTip::showToolTip(m_tabTooltip.second.data()); } } } } void MainWindowPrivate::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position) { QMenu menu; menu.addSection(QIcon::fromTheme("window-new"), i18n("Add Tool View")); QMap factories = Core::self()->uiControllerInternal()->factoryDocuments(); QMap actionToFactory; if ( !factories.isEmpty() ) { // sorted actions QMap actionMap; for (QMap::const_iterator it = factories.constBegin(); it != factories.constEnd(); ++it) { QAction* action = new QAction(it.value()->statusIcon(), it.value()->title(), &menu); action->setIcon(it.value()->statusIcon()); if (!it.key()->allowMultiple() && Core::self()->uiControllerInternal()->toolViewPresent(it.value(), m_mainWindow->area())) { action->setDisabled(true); } actionToFactory.insert(action, it.key()); actionMap[action->text()] = action; } menu.addActions(actionMap.values()); } QAction* triggered = menu.exec(position); if ( !triggered ) { return; } Core::self()->uiControllerInternal()->addToolViewToDockArea( actionToFactory[triggered], area ); } bool MainWindowPrivate::changingActiveView() const { return m_changingActiveView; } KTextEditorIntegration::MainWindow *MainWindowPrivate::kateWrapper() const { return m_kateWrapper; } } #include "mainwindow_actions.cpp" diff --git a/shell/runcontroller.cpp b/shell/runcontroller.cpp index f9b109fce8..f11e9c2c41 100644 --- a/shell/runcontroller.cpp +++ b/shell/runcontroller.cpp @@ -1,1021 +1,1022 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 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 "runcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "plugincontroller.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include "debug.h" #include #include #include #include using namespace KDevelop; namespace { namespace Strings { QString LaunchConfigurationsGroup() { return QStringLiteral("Launch"); } QString LaunchConfigurationsListEntry() { return QStringLiteral("Launch Configurations"); } QString CurrentLaunchConfigProjectEntry() { return QStringLiteral("Current Launch Config Project"); } QString CurrentLaunchConfigNameEntry() { return QStringLiteral("Current Launch Config GroupName"); } QString ConfiguredFromProjectItemEntry() { return QStringLiteral("Configured from ProjectItem"); } } } typedef QPair Target; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} virtual QIcon icon() const override { return QIcon::fromTheme("tools-report-bug"); } virtual QString id() const override { return "debug"; } virtual QString name() const override { return i18n("Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} virtual QIcon icon() const override { return QIcon::fromTheme("office-chart-area"); } virtual QString id() const override { return "profile"; } virtual QString name() const override { return i18n("Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} virtual QIcon icon() const override { return QIcon::fromTheme("system-run"); } virtual QString id() const override { return "execute"; } virtual QString name() const override { return i18n("Execute"); } }; class RunController::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; QAction* stopAction; KActionMenu* stopJobsMenu; QAction* runAction; QAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QSignalMapper* launchChangeMapper; QSignalMapper* launchAsMapper; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() ); grp.writeEntry( Strings::CurrentLaunchConfigProjectEntry(), l->project() ? l->project()->name() : "" ); grp.writeEntry( Strings::CurrentLaunchConfigNameEntry(), l->configGroupName() ); grp.sync(); } } void configureLaunches() { LaunchConfigurationDialog dlg; dlg.exec(); } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QStringLiteral("%1 : %2").arg( l->project()->name()).arg(l->name()); } else { label = l->name(); } return label; } void launchAs( int id ) { //qCDebug(SHELL) << "Launching id:" << id; QPair info = launchAsInfo[id]; //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { ILauncher* launcher = 0; foreach (ILauncher *l, type->launchers()) { //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } if (launcher) { QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); ILaunchConfiguration* ilaunch = 0; foreach (LaunchConfiguration *l, launchConfigurations) { QStringList path = l->config().readEntry(Strings::ConfiguredFromProjectItemEntry(), QStringList()); if (l->type() == type && path == itemPath) { qCDebug(SHELL) << "already generated ilaunch" << path; ilaunch = l; break; } } if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); LaunchConfiguration* launch = dynamic_cast( ilaunch ); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(Strings::ConfiguredFromProjectItemEntry(), itemPath); //qCDebug(SHELL) << "created config, launching"; } else { //qCDebug(SHELL) << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); QString currentLaunchProject = launchGrp.readEntry( Strings::CurrentLaunchConfigProjectEntry(), "" ); QString currentLaunchName = launchGrp.readEntry( Strings::CurrentLaunchConfigNameEntry(), "" ); LaunchConfiguration* l = 0; if( currentTargetAction->currentAction() ) { l = static_cast( currentTargetAction->currentAction()->data().value() ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { foreach( QAction* a, currentTargetAction->actions() ) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { qCDebug(SHELL) << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().first()->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; QAction* action = currentTargetAction->addAction(launchActionText( l )); action->setData(qVariantFromValue(l)); } void readLaunchConfigs( KSharedConfigPtr cfg, IProject* prj ) { KConfigGroup group(cfg, Strings::LaunchConfigurationsGroup()); QStringList configs = group.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); foreach( const QString& cfg, configs ) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry(), "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { qWarning() << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } return 0; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d(new RunControllerPrivate) { setObjectName("RunController"); // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed d->currentTargetAction = 0; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->launchChangeMapper = new QSignalMapper( this ); d->launchAsMapper = 0; d->contextItem = 0; d->executeMode = 0; d->debugMode = 0; d->profileMode = 0; if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() { delete d; } void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { delete d->executeMode; d->executeMode = 0; delete d->profileMode; d->profileMode = 0; delete d->debugMode; d->debugMode = 0; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); d->readLaunchConfigs( Core::self()->activeSession()->config(), 0 ); foreach (IProject* project, Core::self()->projectController()->projects()) { slotProjectOpened(project); } connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &RunController::slotProjectOpened); connect(Core::self()->projectController(), &IProjectController::projectClosing, this, &RunController::slotProjectClosing); connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &RunController::slotRefreshProject); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { qCDebug(SHELL) << "execute called without launch config!"; return 0; } LaunchConfiguration *run = dynamic_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} qCDebug(SHELL) << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); qCDebug(SHELL) << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { KMessageBox::error( qApp->activeWindow(), i18n("The current launch configuration does not support the '%1' mode.", runMode), ""); return 0; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { QAction* action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); action = new QAction(i18n("Configure Launches..."), this); ac->addAction("configure_launches", action); + action->setMenuRole(QAction::NoRole); // OSX: Be explicit about role, prevent hiding due to conflict with "Preferences..." menu item action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, &QAction::triggered, this, [&] { d->configureLaunches(); }); d->runAction = new QAction( QIcon::fromTheme("system-run"), i18n("Execute Launch"), this); d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction("run_execute", d->runAction); connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute); d->dbgAction = new QAction( QIcon::fromTheme("debug-run"), i18n("Debug Launch"), this); ac->setDefaultShortcut( d->dbgAction, Qt::Key_F9); d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction("run_debug", d->dbgAction); connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug); Core::self()->uiControllerInternal()->area(0, "code")->addAction(d->dbgAction); // TODO: at least get a profile target, it's sad to have the menu entry without a profiler // QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this); // profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); // profileAction->setStatusTip(i18n("Profile current launch")); // profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); // ac->addAction("run_profile", profileAction); // connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); action = d->stopAction = new QAction( QIcon::fromTheme("process-stop"), i18n("Stop All Jobs"), this); action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but thats taken by the ksysguard desktop shortcut ac->setDefaultShortcut( action, QKeySequence("Ctrl+Shift+Escape")); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction("run_stop_all", action); connect(action, &QAction::triggered, this, &RunController::stopAllProcesses); Core::self()->uiControllerInternal()->area(0, "debug")->addAction(action); action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme("process-stop"), i18n("Stop"), this); action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction("run_stop_menu", action); d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction("run_default_target", d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { if (!d->currentTargetAction) return; foreach (QAction* action, d->currentTargetAction->actions()) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().first()->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "debug" ); } void RunController::slotProfile() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "profile" ); } void RunController::slotExecute() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "execute" ); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); return 0; } void KDevelop::RunController::registerJob(KJob * job) { if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 qWarning() << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { QAction* stopJobAction = 0; if (Core::self()->setupFlags() != Core::NoUi) { stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", job->staticMetaObject.className()) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, &KJob::finished, this, &RunController::finished ); connect( job, &KJob::destroyed, this, &RunController::jobDestroyed ); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { bool running = false; foreach (KJob* job, d->jobs.keys()) { if (!job->isSuspended()) { running = true; break; } } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 // foreach already iterates over a copy foreach (KJob* job, d->jobs.keys()) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { qWarning() << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { QAction* action = dynamic_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { ///WARNING: do *not* use a nested event loop here, it might cause /// random crashes later on, see e.g.: /// https://bugs.kde.org/show_bug.cgi?id=309811 auto dialog = new QDialog(qApp->activeWindow()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Process Error")); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, dialog); KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Warning, job->errorString(), QStringList(), QString(), 0, KMessageBox::NoExec); dialog->show(); } } } void RunController::jobDestroyed(QObject* job) { KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { qWarning() << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; foreach (LaunchConfiguration *config, launchConfigurationsInternal()) configs << config; return configs; } QList RunController::launchConfigurationsInternal() const { return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { foreach( LaunchConfiguration* l, d->launchConfigurations ) { if( l->type() == type ) { d->launchConfigurations.removeAll( l ); delete l; } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { QMap::iterator it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } return 0; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().first()->setChecked( true ); } } connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launcherGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launcherGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launcherGroup.sync(); foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().first()->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { auto dl = defaultLaunch(); if( !dl ) { qWarning() << "no default launch!"; return; } execute( runMode, dl ); } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { foreach(ILaunchConfiguration* config, Core::self()->runControllerInternal()->launchConfigurations()) { if(config->name()==name) return true; } return false; } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( Strings::LaunchConfigurationsGroup() ); } else { launchGroup = Core::self()->activeSession()->config()->group( Strings::LaunchConfigurationsGroup() ); } QStringList configs = launchGroup.readEntry( Strings::LaunchConfigurationsListEntry(), QStringList() ); uint num = 0; QString baseName = "Launch Configuration"; while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry(), cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry(), type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( Strings::LaunchConfigurationsListEntry(), configs ); launchGroup.sync(); LaunchConfiguration* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { return d->delegate; } ContextMenuExtension RunController::contextMenuExtension ( Context* ctx ) { delete d->launchAsMapper; d->launchAsMapper = new QSignalMapper( this ); connect( d->launchAsMapper, static_cast(&QSignalMapper::mapped), this, [&] (int id) { d->launchAs(id); } ); d->launchAsInfo.clear(); d->contextItem = 0; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { KDevelop::ProjectItemContext* prjctx = dynamic_cast( ctx ); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; foreach( ILaunchMode* mode, d->launchModes.values() ) { KActionMenu* menu = new KActionMenu( i18n("%1 As...", mode->name() ), this ); foreach( LaunchConfigurationType* type, launchConfigurationTypes() ) { bool hasLauncher = false; foreach( ILauncher* launcher, type->launchers() ) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); QAction* act = new QAction( d->launchAsMapper ); act->setText( type->name() ); qCDebug(SHELL) << "Setting up mapping for:" << i << "for action" << act->text() << "in mode" << mode->name(); d->launchAsMapper->setMapping( act, i ); connect( act, &QAction::triggered, d->launchAsMapper, static_cast(&QSignalMapper::map) ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; QVariant status = index.data(Qt::UserRole+1); // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "moc_runcontroller.cpp" diff --git a/shell/sessioncontroller.cpp b/shell/sessioncontroller.cpp index 843c5a5278..c8002ae3d9 100644 --- a/shell/sessioncontroller.cpp +++ b/shell/sessioncontroller.cpp @@ -1,683 +1,684 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 David Nolden 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 "sessioncontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session.h" #include "core.h" #include "uicontroller.h" #include "sessiondialog.h" #include "shellextension.h" #include "sessionlock.h" #include "sessionchooserdialog.h" #include "debug.h" #include #include #include #include #include namespace KDevelop { namespace { int argc = 0; char** argv = 0; }; void SessionController::setArguments(int _argc, char** _argv) { argc = _argc; argv = _argv; } static QStringList standardArguments() { QStringList ret; for(int a = 0; a < argc; ++a) { QString arg = QString::fromLocal8Bit(argv[a]); if(arg.startsWith("-graphicssystem") || arg.startsWith("-style")) { ret << '-' + arg; if(a+1 < argc) ret << QString::fromLocal8Bit(argv[a+1]); } } return ret; } class SessionControllerPrivate : public QObject { Q_OBJECT public: SessionControllerPrivate( SessionController* s ) : q(s) , activeSession(0) , grp(0) { } ~SessionControllerPrivate() { } Session* findSessionForName( const QString& name ) const { foreach( Session* s, sessionActions.keys() ) { if( s->name() == name ) return s; } return 0; } Session* findSessionForId(QString idString) { QUuid id(idString); foreach( Session* s, sessionActions.keys() ) { if( s->id() == id) return s; } return 0; } void newSession() { qsrand(QDateTime::currentDateTime().toTime_t()); Session* session = new Session( QUuid::createUuid().toString() ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << "-s" << session->id().toString() << standardArguments()); delete session; #if 0 //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); #endif } void configureSessions() { SessionDialog dlg(ICore::self()->uiController()-> activeMainWindow()); dlg.exec(); } void deleteCurrentSession() { int choice = KMessageBox::warningContinueCancel(Core::self()->uiController()->activeMainWindow(), i18n("The current session and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?")); if(choice == KMessageBox::Continue) { q->deleteSessionFromDisk(sessionLock); q->emitQuitSession(); } } void renameSession() { QDialog dialog; dialog.setWindowTitle(i18n("Rename Session")); auto mainLayout = new QVBoxLayout(&dialog); QGroupBox box; QHBoxLayout layout(&box); box.setTitle(i18n("New Session Name")); QLineEdit edit; layout.addWidget(&edit); mainLayout->addWidget(&box); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); mainLayout->addWidget(buttonBox); edit.setText(q->activeSession()->name()); edit.setFocus(); if(dialog.exec() == QDialog::Accepted) { static_cast(q->activeSession())->setName(edit.text()); } } bool loadSessionExternally( Session* s ) { Q_ASSERT( s ); KProcess::startDetached(ShellExtension::getInstance()->binaryPath(), QStringList() << "-s" << s->id().toString() << standardArguments()); return true; } TryLockSessionResult activateSession( Session* s ) { Q_ASSERT( s ); activeSession = s; TryLockSessionResult result = SessionController::tryLockSession( s->id().toString()); if( !result.lock ) { activeSession = 0; return result; } Q_ASSERT(s->id().toString() == result.lock->id()); sessionLock = result.lock; KConfigGroup grp = KSharedConfig::openConfig()->group( SessionController::cfgSessionGroup() ); grp.writeEntry( SessionController::cfgActiveSessionEntry(), s->id().toString() ); grp.sync(); if (Core::self()->setupFlags() & Core::NoUi) return result; QHash::iterator it = sessionActions.find(s); Q_ASSERT( it != sessionActions.end() ); (*it)->setCheckable(true); (*it)->setChecked(true); for(it = sessionActions.begin(); it != sessionActions.end(); ++it) { if(it.key() != s) (*it)->setCheckable(false); } return result; } void loadSessionFromAction( QAction* a ) { foreach( Session* s, sessionActions.keys() ) { if( s->id() == QUuid( a->data().toString() ) && s != activeSession ) { loadSessionExternally( s ); break; } } } void addSession( Session* s ) { if (Core::self()->setupFlags() & Core::NoUi) { sessionActions[s] = 0; return; } QAction* a = new QAction( grp ); a->setText( s->description() ); a->setCheckable( false ); a->setData( s->id().toString() ); sessionActions[s] = a; q->actionCollection()->addAction( "session_"+s->id().toString(), a ); q->unplugActionList( "available_sessions" ); q->plugActionList( "available_sessions", grp->actions() ); connect( s, &Session::sessionUpdated, this, &SessionControllerPrivate::sessionUpdated ); sessionUpdated( s ); } SessionController* q; QHash sessionActions; ISession* activeSession; QActionGroup* grp; ISessionLock::Ptr sessionLock; static QString sessionBaseDirectory() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +'/'+ qApp->applicationName() + "/sessions/"; } QString ownSessionDirectory() const { Q_ASSERT(activeSession); return q->sessionDirectory( activeSession->id().toString() ); } private slots: void sessionUpdated( KDevelop::ISession* s ) { sessionActions[static_cast( s )]->setText( KStringHandler::rsqueeze(s->description()) ); } }; SessionController::SessionController( QObject *parent ) : QObject( parent ), d(new SessionControllerPrivate(this)) { setObjectName("SessionController"); setComponentName(QStringLiteral("kdevsession"), QStringLiteral("KDevSession")); setXMLFile("kdevsessionui.rc"); QDBusConnection::sessionBus().registerObject( "/kdevelop/SessionController", this, QDBusConnection::ExportScriptableSlots ); if (Core::self()->setupFlags() & Core::NoUi) return; QAction* action = actionCollection()->addAction( "new_session", this, SLOT(newSession()) ); action->setText( i18nc("@action:inmenu", "Start New Session") ); action->setToolTip( i18nc("@info:tooltip", "Start a new KDevelop instance with an empty session") ); action->setIcon(QIcon::fromTheme("window-new")); action = actionCollection()->addAction( "rename_session", this, SLOT(renameSession()) ); action->setText( i18n("Rename Current Session...") ); action->setIcon(QIcon::fromTheme("edit-rename")); action = actionCollection()->addAction( "delete_session", this, SLOT(deleteCurrentSession()) ); action->setText( i18n("Delete Current Session...") ); action->setIcon(QIcon::fromTheme("edit-delete")); action = actionCollection()->addAction( "quit", this, SIGNAL(quitSession()) ); action->setText( i18n("Quit") ); + action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'Quit KDevelop...' actionCollection()->setDefaultShortcut( action, Qt::CTRL | Qt::Key_Q ); action->setIcon(QIcon::fromTheme("application-exit")); #if 0 action = actionCollection()->addAction( "configure_sessions", this, SLOT(configureSessions()) ); action->setText( i18n("Configure Sessions...") ); action->setToolTip( i18n("Create/Delete/Activate Sessions") ); action->setWhatsThis( i18n( "Shows a dialog to Create/Delete Sessions and set a new active session." ) ); #endif d->grp = new QActionGroup( this ); connect( d->grp, &QActionGroup::triggered, this, [&] (QAction* a) { d->loadSessionFromAction(a); } ); } SessionController::~SessionController() { delete d; } void SessionController::startNewSession() { d->newSession(); } void SessionController::cleanup() { if (d->activeSession) { Q_ASSERT(d->activeSession->id().toString() == d->sessionLock->id()); if (d->activeSession->isTemporary()) { deleteSessionFromDisk(d->sessionLock); } d->activeSession = 0; } d->sessionLock.clear(); qDeleteAll(d->sessionActions); d->sessionActions.clear(); } void SessionController::initialize( const QString& session ) { QDir sessiondir( SessionControllerPrivate::sessionBaseDirectory() ); foreach( const QString& s, sessiondir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot ) ) { QUuid id( s ); if( id.isNull() ) continue; // Only create sessions for directories that represent proper uuid's Session* ses = new Session( id.toString(), this ); //Delete sessions that have no name and are empty if( ses->containedProjects().isEmpty() && ses->name().isEmpty() && (session.isEmpty() || (ses->id().toString() != session && ses->name() != session)) ) { TryLockSessionResult result = tryLockSession(s); if (result.lock) { deleteSessionFromDisk(result.lock); } delete ses; } else { d->addSession( ses ); } } loadDefaultSession( session ); } ISession* SessionController::activeSession() const { return d->activeSession; } ISessionLock::Ptr SessionController::activeSessionLock() const { return d->sessionLock; } void SessionController::loadSession( const QString& nameOrId ) { d->loadSessionExternally( session( nameOrId ) ); } QList SessionController::sessionNames() const { QStringList l; foreach( const Session* s, d->sessionActions.keys() ) { l << s->name(); } return l; } QList< const KDevelop::Session* > SessionController::sessions() const { QList< const KDevelop::Session* > ret; foreach( const Session* s, d->sessionActions.keys() ) { ret << s; } return ret; } Session* SessionController::createSession( const QString& name ) { Session* s; if(name.startsWith('{')) { s = new Session( QUuid(name).toString() ); }else{ qsrand(QDateTime::currentDateTime().toTime_t()); s = new Session( QUuid::createUuid().toString() ); s->setName( name ); } d->addSession( s ); return s; } void SessionController::deleteSession( const ISessionLock::Ptr& lock ) { Session* s = session(lock->id()); QHash::iterator it = d->sessionActions.find(s); Q_ASSERT( it != d->sessionActions.end() ); unplugActionList( "available_sessions" ); actionCollection()->removeAction(*it); if (d->grp) { // happens in unit tests d->grp->removeAction(*it); plugActionList( "available_sessions", d->grp->actions() ); } if (s == d->activeSession) { d->activeSession = nullptr; } deleteSessionFromDisk(lock); emit sessionDeleted( s->id().toString() ); d->sessionActions.remove(s); delete s; } void SessionController::deleteSessionFromDisk( const ISessionLock::Ptr& lock ) { qCDebug(SHELL) << "Deleting session:" << lock->id(); QDir(sessionDirectory(lock->id())).removeRecursively(); ItemRepositoryRegistry::deleteRepositoryFromDisk( lock ); } void SessionController::loadDefaultSession( const QString& session ) { QString load = session; if (load.isEmpty()) { KConfigGroup grp = KSharedConfig::openConfig()->group( cfgSessionGroup() ); load = grp.readEntry( cfgActiveSessionEntry(), "default" ); } // Iteratively try to load the session, asking user what to do in case of failure // If showForceOpenDialog() returns empty string, stop trying Session* s = 0; do { s = this->session( load ); if( !s ) { s = createSession( load ); } TryLockSessionResult result = d->activateSession( s ); if( result.lock ) { Q_ASSERT(d->activeSession == s); Q_ASSERT(d->sessionLock = result.lock); break; } load = handleLockedSession( s->name(), s->id().toString(), result.runInfo ); } while( !load.isEmpty() ); } Session* SessionController::session( const QString& nameOrId ) const { Session* ret = d->findSessionForName( nameOrId ); if(ret) return ret; return d->findSessionForId( nameOrId ); } QString SessionController::cloneSession( const QString& nameOrid ) { Session* origSession = session( nameOrid ); qsrand(QDateTime::currentDateTime().toTime_t()); QUuid id = QUuid::createUuid(); auto copyJob = KIO::copy(QUrl::fromLocalFile(sessionDirectory(origSession->id().toString())), QUrl::fromLocalFile(sessionDirectory( id.toString()))); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); copyJob->exec(); Session* newSession = new Session( id.toString() ); newSession->setName( i18n( "Copy of %1", origSession->name() ) ); d->addSession(newSession); return newSession->name(); } void SessionController::plugActions() { unplugActionList( "available_sessions" ); plugActionList( "available_sessions", d->grp->actions() ); } QString SessionController::cfgSessionGroup() { return "Sessions"; } QString SessionController::cfgActiveSessionEntry() { return "Active Session ID"; } QList< SessionInfo > SessionController::availableSessionInfo() { QList< SessionInfo > available; foreach( const QString& sessionId, QDir( SessionControllerPrivate::sessionBaseDirectory() ).entryList( QDir::AllDirs ) ) { if( !QUuid( sessionId ).isNull() ) { available << Session::parse( sessionId ); } } return available; } QString SessionController::sessionDirectory(const QString& sessionId) { return SessionControllerPrivate::sessionBaseDirectory() + sessionId; } TryLockSessionResult SessionController::tryLockSession(const QString& id) { return SessionLock::tryLockSession(id, true); } bool SessionController::isSessionRunning(const QString& id) { return sessionRunInfo(id).isRunning; } SessionRunInfo SessionController::sessionRunInfo(const QString& id) { return SessionLock::tryLockSession(id, false).runInfo; } QString SessionController::showSessionChooserDialog(QString headerText, bool onlyRunning) { ///FIXME: move this code into sessiondialog.cpp QListView* view = new QListView; QLineEdit* filter = new QLineEdit; filter->setClearButtonEnabled( true ); filter->setPlaceholderText(i18n("Search")); QStandardItemModel* model = new QStandardItemModel(view); QSortFilterProxyModel *proxy = new QSortFilterProxyModel(model); proxy->setSourceModel(model); proxy->setFilterKeyColumn( 1 ); proxy->setFilterCaseSensitivity( Qt::CaseInsensitive ); connect(filter, &QLineEdit::textChanged, proxy, &QSortFilterProxyModel::setFilterFixedString); SessionChooserDialog dialog(view, proxy, filter); view->setEditTriggers(QAbstractItemView::NoEditTriggers); QVBoxLayout layout(dialog.mainWidget()); if(!headerText.isEmpty()) { QLabel* heading = new QLabel(headerText); QFont font = heading->font(); font.setBold(true); heading->setFont(font); layout.addWidget(heading); } model->setColumnCount(3); model->setHeaderData(0, Qt::Horizontal,i18n("Identity")); model->setHeaderData(1, Qt::Horizontal, i18n("Contents")); model->setHeaderData(2, Qt::Horizontal,i18n("State")); view->setModel(proxy); view->setModelColumn(1); QHBoxLayout* filterLayout = new QHBoxLayout(); filterLayout->addWidget(new QLabel(i18n("Filter:"))); filterLayout->addWidget(filter); layout.addLayout(filterLayout); layout.addWidget(view); filter->setFocus(); int row = 0; QString defaultSession = KSharedConfig::openConfig()->group( cfgSessionGroup() ).readEntry( cfgActiveSessionEntry(), "default" ); foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfo()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } bool running = KDevelop::SessionController::isSessionRunning(si.uuid.toString()); if(onlyRunning && !running) continue; model->setItem(row, 0, new QStandardItem(si.uuid.toString())); model->setItem(row, 1, new QStandardItem(si.description)); model->setItem(row, 2, new QStandardItem); ++row; } model->sort(1); if(!onlyRunning) { model->setItem(row, 0, new QStandardItem); model->setItem(row, 1, new QStandardItem(QIcon::fromTheme("window-new"), i18n("Create New Session"))); } dialog.updateState(); dialog.mainWidget()->layout()->setContentsMargins(0,0,0,0); const QModelIndex defaultSessionIndex = model->match(model->index(0, 0), Qt::DisplayRole, defaultSession, 1, Qt::MatchExactly).value(0); view->selectionModel()->setCurrentIndex(proxy->mapFromSource(defaultSessionIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ///@todo We need a way to get a proper size-hint from the view, but unfortunately, that only seems possible after the view was shown. dialog.resize(QSize(900, 600)); if(dialog.exec() != QDialog::Accepted) { return QString(); } QModelIndex selected = view->selectionModel()->currentIndex(); if (!selected.isValid()) return QString(); const QString selectedSessionId = selected.sibling(selected.row(), 0).data().toString(); if (selectedSessionId.isEmpty()) { // "Create New Session" item selected, return a fresh UUID qsrand(QDateTime::currentDateTime().toTime_t()); return QUuid::createUuid().toString(); } return selectedSessionId; } QString SessionController::handleLockedSession( const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo ) { return SessionLock::handleLockedSession(sessionName, sessionId, runInfo); } QString SessionController::sessionDir() { if( !activeSession() ) return QString(); return d->ownSessionDirectory(); } QString SessionController::sessionName() { if(!activeSession()) return QString(); return activeSession()->description(); } } #include "sessioncontroller.moc" #include "moc_sessioncontroller.cpp"