diff --git a/interfaces/iproject.h b/interfaces/iproject.h index 54d9fbeae5..0cc853b195 100644 --- a/interfaces/iproject.h +++ b/interfaces/iproject.h @@ -1,180 +1,183 @@ /* This file is part of the KDE project Copyright 2001 Matthias Hoelzer-Kluepfel Copyright 2001-2002 Bernd Gehrmann Copyright 2002-2003 Roberto Raggi Copyright 2002 Simon Hausmann Copyright 2003 Jens Dagerbo Copyright 2003 Mario Scalas Copyright 2003-2004 Alexander Dymo Copyright 2006 Matt Rogers 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. */ #ifndef KDEVPLATFORM_IPROJECT_H #define KDEVPLATFORM_IPROJECT_H #include #include #include #include "interfacesexport.h" class KJob; template class QList; template class QSet; namespace KDevelop { class IPlugin; class IProjectFileManager; class IBuildSystemManager; class Path; class ProjectBaseItem; class ProjectFileItem; class ProjectFolderItem; class IndexedString; /** * \brief Object which represents a KDevelop project * * Provide better descriptions */ class KDEVPLATFORMINTERFACES_EXPORT IProject : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kdevelop.Project") Q_PROPERTY(QString projectName READ name CONSTANT) public: /** * Constructs a project. * * @param parent The parent object for the plugin. */ explicit IProject(QObject *parent = 0); /// Destructor. ~IProject() override; /** * Get the file manager for the project * * @return the file manager for the project, if one exists; otherwise null */ Q_SCRIPTABLE virtual IProjectFileManager* projectFileManager() const = 0; /** * Get the build system manager for the project * * @return the build system manager for the project, if one exists; otherwise null */ Q_SCRIPTABLE virtual IBuildSystemManager* buildSystemManager() const = 0; /** * Get the plugin that manages the project * This can be used to get other interfaces like IBuildSystemManager */ Q_SCRIPTABLE virtual IPlugin* managerPlugin() const = 0; /** * Get the version control plugin for this project * This may return 0 if the project is not under version control * or version control has been disabled for this project */ Q_SCRIPTABLE virtual IPlugin* versionControlPlugin() const = 0; /** * With this the top-level project item can be retrieved */ Q_SCRIPTABLE virtual ProjectFolderItem* projectItem() const = 0; /** * @return all items with the corresponding @p path */ Q_SCRIPTABLE virtual QList itemsForPath( const IndexedString& path ) const = 0; /** * @return all file items with the corresponding @p file path */ Q_SCRIPTABLE virtual QList filesForPath( const IndexedString& file ) const = 0; /** * @return all folder items with the corresponding @p folder path */ Q_SCRIPTABLE virtual QList foldersForPath( const IndexedString& folder ) const = 0; /** * @return the path to the project file */ virtual Path projectFile() const = 0; /** Get the url of the project file.*/ virtual KSharedConfigPtr projectConfiguration() const = 0; virtual void addToFileSet( ProjectFileItem* item ) = 0; virtual void removeFromFileSet( ProjectFileItem* item ) = 0; virtual QSet fileSet() const = 0; /** Returns whether the project is ready to be used or not. A project won't be ready for use when it's being reloaded or still loading */ virtual bool isReady() const=0; /** * @brief Get the project path * @return The canonical absolute directory of the project. */ virtual Path path() const = 0; /** Returns the name of the project. */ virtual Q_SCRIPTABLE QString name() const = 0; /** * @brief Check if the project contains an item with the given @p path. * * @param path the path to check * * @return true if the path @a path is a part of the project. */ virtual Q_SCRIPTABLE bool inProject(const IndexedString &path) const = 0; /** * @brief Tells the project what job is reloading it * * It's useful so that we can tell whether the project manager is busy or not. */ virtual void setReloadJob(KJob* job) = 0; Q_SIGNALS: /** * Gets emitted whenever a file was added to the project. */ void fileAddedToSet( KDevelop::ProjectFileItem* item ); /** * Gets emitted whenever a file was removed from the project. */ void fileRemovedFromSet( KDevelop::ProjectFileItem* item ); public Q_SLOTS: /** Make the model to reload */ virtual void reloadModel() = 0; + + /** This method is invoked when the project needs to be closed. */ + virtual void close() = 0; }; } #endif diff --git a/interfaces/iprojectcontroller.h b/interfaces/iprojectcontroller.h index ab2ac2db1f..0f0df2cf45 100644 --- a/interfaces/iprojectcontroller.h +++ b/interfaces/iprojectcontroller.h @@ -1,199 +1,208 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat 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. */ #ifndef KDEVPLATFORM_IPROJECTCONTROLLER_H #define KDEVPLATFORM_IPROJECTCONTROLLER_H #include #include #include #include "interfacesexport.h" class QItemSelectionModel; namespace KDevelop { class IProject; class ProjectBuildSetModel; class ProjectModel; class ProjectBaseItem; class ProjectChangesModel; /** * @class IProjectController */ class KDEVPLATFORMINTERFACES_EXPORT IProjectController : public QObject { Q_OBJECT public: explicit IProjectController( QObject *parent = 0 ); ~IProjectController() override; Q_SCRIPTABLE virtual KDevelop::IProject* projectAt( int ) const = 0; Q_SCRIPTABLE virtual int projectCount() const = 0; Q_SCRIPTABLE virtual QList projects() const = 0; /** * Provides access to the model representing the open projects * @returns the model containing the projects and their items */ Q_SCRIPTABLE virtual ProjectModel* projectModel() = 0; /** * @returns an instance to the model that keeps track of the state * of the files per project. */ Q_SCRIPTABLE virtual ProjectChangesModel* changesModel() = 0; Q_SCRIPTABLE virtual ProjectBuildSetModel* buildSetModel() = 0; /** * Find an open project using the name of the project * @param name the name of the project to be found * @returns the project or null if no project with that name is open */ Q_SCRIPTABLE virtual KDevelop::IProject* findProjectByName( const QString& name ) = 0; /** * Finding an open project for a given file or folder in the project * @param url the url of a file/folder belonging to an open project * @returns the first open project containing the url or null if no such * project can be found */ Q_SCRIPTABLE virtual IProject* findProjectForUrl( const QUrl& url ) const = 0; /** * Checks whether the given project name is used already or not. The project * controller supports only 1 project with a given name to be open at any time * @param name the name of the project to be opened or created * @returns whether the name is already used for an open project */ Q_SCRIPTABLE virtual bool isProjectNameUsed( const QString& name ) const = 0; virtual QUrl projectsBaseDirectory() const = 0; enum FormattingOptions { FormatHtml, FormatPlain }; /** * Returns a pretty short representation of the base path of the url, considering the currently loaded projects: * When the file is part of a currently loaded project, that projects name is shown as prefix instead of the * the full file path. * The returned path always has a training slash. * @param format formatting used for the string */ Q_SCRIPTABLE virtual QString prettyFilePath(const QUrl& url, FormattingOptions format = FormatHtml) const = 0; /** * Returns a pretty short representation of the given url, considering the currently loaded projects: * When the file is part of a currently loaded project, that projects name is shown as prefix instead of the * the full file path. * @param format formatting used for the string */ Q_SCRIPTABLE virtual QString prettyFileName(const QUrl& url, FormattingOptions format = FormatHtml) const = 0; /** * @returns whether project files should be parsed or not */ static bool parseAllProjectSources(); public Q_SLOTS: /** * Tries finding a project-file for the given source-url and opens it. * If no .kdev4 project file is found, the user is asked to import a project. */ virtual void openProjectForUrl( const QUrl &sourceUrl ) = 0; /** * open the project from the given kdev4 project file. This only reads * the file and starts creating the project model from it. The opening process * is finished once @ref projectOpened signal is emitted. * @param url a kdev4 project file top open * @returns true if the given project could be opened, false otherwise */ virtual void openProject( const QUrl & url = QUrl() ) = 0; /** * close the given project. Closing the project is done in steps and * the @ref projectClosing and @ref projectClosed signals are emitted. Only when * the latter signal is emitted it is guaranteed that the project has been closed. * The @ref IProject object will be deleted after the closing has finished. * @returns true if the project could be closed, false otherwise */ virtual void closeProject( IProject* ) = 0; + + /** + * Close all projects + * + * This usually calls closeProject() on all controlled projects + * @sa closeProject() + */ + virtual void closeAllProjects() = 0; + virtual void configureProject( IProject* ) = 0; /// Schedules all files of the @p project for reparsing by @see BackgroundParser virtual void reparseProject( IProject* project, bool ForceUpdate = false ) = 0; // virtual void changeCurrentProject( KDevelop::ProjectBaseItem* ) = 0; Q_SIGNALS: /** * Emitted right before a project is started to be loaded. * * At this point all sanity checks have been done, so the project * is really going to be loaded. Will be followed by @ref projectOpened signal * when loading completes or by @ref projectOpeningAborted if there are errors during loading * or it is aborted. * * @param project the project that is about to be opened. */ void projectAboutToBeOpened( KDevelop::IProject* project ); /** * emitted after a project is completely opened and the project model * has been populated. * @param project the project that has been opened. */ void projectOpened( KDevelop::IProject* project ); /** * emitted when starting to close a project that has been completely loaded before * (the @ref projectOpened signal has been emitted). * @param project the project that is going to be closed. */ void projectClosing( KDevelop::IProject* project ); /** * emitted when a project has been closed completely. * The project object is still valid, the deletion will be done * delayed during the next run of the event loop. * @param project the project that has been closed. */ void projectClosed( KDevelop::IProject* project ); /** * emitted when a project could not be loaded correctly or loading was aborted. * @ref project contents may not be initialized properly. * @param project the project which loading has been aborted. */ void projectOpeningAborted( KDevelop::IProject* project ); /** * emitted whenever the project configuration dialog accepted * changes * @param project the project whose configuration has changed */ void projectConfigurationChanged( KDevelop::IProject* project ); }; } #endif diff --git a/plugins/quickopen/tests/bench_quickopen.cpp b/plugins/quickopen/tests/bench_quickopen.cpp index 5a9d96a421..dd90e4dd2a 100644 --- a/plugins/quickopen/tests/bench_quickopen.cpp +++ b/plugins/quickopen/tests/bench_quickopen.cpp @@ -1,158 +1,161 @@ /* * Copyright Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 General Public License * along with this program. If not, see . */ #include "bench_quickopen.h" +#include +#include + #include #include QTEST_MAIN(BenchQuickOpen); using namespace KDevelop; BenchQuickOpen::BenchQuickOpen(QObject* parent) : QuickOpenTestBase(Core::NoUi, parent) { } void BenchQuickOpen::getData() { QTest::addColumn("files"); QTest::addColumn("filter"); QTest::newRow("0100-___") << 100 << ""; QTest::newRow("0500-___") << 500 << ""; QTest::newRow("0100-bar") << 100 << "bar"; QTest::newRow("0500-bar") << 500 << "bar"; QTest::newRow("0100-1__") << 100 << "1"; QTest::newRow("0500-1__") << 500 << "1"; QTest::newRow("0100-f/b") << 100 << "f/b"; QTest::newRow("0500-f/b") << 500 << "f/b"; } void BenchQuickOpen::benchProjectFileFilter_addRemoveProject() { QFETCH(int, files); QFETCH(QString, filter); ProjectFileDataProvider provider; QScopedPointer project(getProjectWithFiles(files)); QBENCHMARK { projectController->addProject(project.data()); projectController->takeProject(project.data()); } } void BenchQuickOpen::benchProjectFileFilter_addRemoveProject_data() { getData(); } void BenchQuickOpen::benchProjectFileFilter_reset() { QFETCH(int, files); QFETCH(QString, filter); ProjectFileDataProvider provider; TestProject* project = getProjectWithFiles(files); provider.setFilterText(filter); projectController->addProject(project); QBENCHMARK { provider.reset(); } } void BenchQuickOpen::benchProjectFileFilter_reset_data() { getData(); } void BenchQuickOpen::benchProjectFileFilter_setFilter() { QFETCH(int, files); QFETCH(QString, filter); ProjectFileDataProvider provider; TestProject* project = getProjectWithFiles(files); projectController->addProject(project); provider.reset(); QBENCHMARK { provider.setFilterText(filter); provider.setFilterText(QString()); } } void BenchQuickOpen::benchProjectFileFilter_setFilter_data() { getData(); } void BenchQuickOpen::benchProjectFileFilter_providerData() { QFETCH(int, files); QFETCH(QString, filter); ProjectFileDataProvider provider; TestProject* project = getProjectWithFiles(files); projectController->addProject(project); provider.reset(); QCOMPARE(provider.itemCount(), uint(files)); provider.setFilterText(filter); QVERIFY(provider.itemCount()); const int itemIdx = provider.itemCount() - 1; QBENCHMARK { QuickOpenDataPointer data = provider.data(itemIdx); data->text(); } } void BenchQuickOpen::benchProjectFileFilter_providerData_data() { getData(); } void BenchQuickOpen::benchProjectFileFilter_providerDataIcon() { QFETCH(int, files); QFETCH(QString, filter); ProjectFileDataProvider provider; TestProject* project = getProjectWithFiles(files); projectController->addProject(project); provider.reset(); QCOMPARE(provider.itemCount(), uint(files)); provider.setFilterText(filter); QVERIFY(provider.itemCount()); const int itemIdx = provider.itemCount() - 1; QBENCHMARK { QuickOpenDataPointer data = provider.data(itemIdx); data->icon(); } } void BenchQuickOpen::benchProjectFileFilter_providerDataIcon_data() { getData(); } diff --git a/plugins/quickopen/tests/quickopentestbase.cpp b/plugins/quickopen/tests/quickopentestbase.cpp index 34c660c1a5..0b0565ce62 100644 --- a/plugins/quickopen/tests/quickopentestbase.cpp +++ b/plugins/quickopen/tests/quickopentestbase.cpp @@ -1,82 +1,82 @@ /* * Copyright Milian Wolff * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 General Public License * along with this program. If not, see . */ #include "quickopentestbase.h" #include #include #include #include #include #include using namespace KDevelop; QuickOpenTestBase::QuickOpenTestBase(Core::Setup setup_, QObject* parent) : QObject(parent) , core(0) , setup(setup_) , projectController(0) { qRegisterMetaType("QModelIndex"); } void QuickOpenTestBase::initTestCase() { AutoTestShell::init(); core = new TestCore; TestPluginController* pluginController = new TestPluginController(core); core->setPluginController(pluginController); TestCore::initialize(setup); projectController = new TestProjectController(core); delete core->projectController(); core->setProjectController(projectController); } void QuickOpenTestBase::cleanupTestCase() { TestCore::shutdown(); } void QuickOpenTestBase::cleanup() { - projectController->clearProjects(); + projectController->closeAllProjects(); core->documentController()->closeAllDocuments(); } TestProject* getProjectWithFiles(int files) { TestProject* project = new TestProject; ProjectFolderItem* foo = createChild(project->projectItem(), "foo"); ProjectFolderItem* bar = createChild(foo, "bar"); for(int i = 0; i < files; ++i) { createChild(bar, QString::number(i) + QLatin1String(".txt")); } return project; } QStringList items(const ProjectFileDataProvider& provider) { QStringList list; for(uint i = 0; i < provider.itemCount(); ++i) { list << provider.data(i)->text(); } return list; } diff --git a/shell/project.h b/shell/project.h index 184041734c..41a2c92b4d 100644 --- a/shell/project.h +++ b/shell/project.h @@ -1,143 +1,142 @@ /* This file is part of the KDE project Copyright 2001 Matthias Hoelzer-Kluepfel Copyright 2001-2002 Bernd Gehrmann Copyright 2002-2003 Roberto Raggi Copyright 2002 Simon Hausmann Copyright 2003 Jens Dagerbo Copyright 2003 Mario Scalas Copyright 2003-2004 Alexander Dymo Copyright 2006 Matt Rogers 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. */ #ifndef KDEVPLATFORM_SHELLPROJECT_H #define KDEVPLATFORM_SHELLPROJECT_H #include #include #include "shellexport.h" template class QList; class KJob; namespace KDevelop { class IProjectFileManager; class IBuildSystemManager; class ProjectFileItem; class PersistentHash; /** * \brief Object which represents a KDevelop project * * Provide better descriptions */ class KDEVPLATFORMSHELL_EXPORT Project : public IProject { Q_OBJECT public: /** * Constructs a project. * * @param parent The parent object for the plugin. */ explicit Project(QObject *parent = 0); ~Project() override; QList< ProjectBaseItem* > itemsForPath(const IndexedString& path) const override; QList< ProjectFileItem* > filesForPath(const IndexedString& file) const override; QList< ProjectFolderItem* > foldersForPath(const IndexedString& folder) const override; QString projectTempFile() const; QString developerTempFile() const; Path developerFile() const; void reloadModel() override; Path projectFile() const override; KSharedConfigPtr projectConfiguration() const override; void addToFileSet( ProjectFileItem* file ) override; void removeFromFileSet( ProjectFileItem* file ) override; QSet fileSet() const override; bool isReady() const override; Path path() const override; Q_SCRIPTABLE QString name() const override; public Q_SLOTS: /** * @brief Open a project * * This method opens a project and starts the process of loading the * data for the project from disk. * * @param projectFile The path pointing to the location of the project * file to load * * The project name is taken from the Name key in the project file in * the 'General' group */ bool open(const Path &projectFile); - /** This method is invoked when the project needs to be closed. */ - void close(); + void close() override; IProjectFileManager* projectFileManager() const override; IBuildSystemManager* buildSystemManager() const override; IPlugin* versionControlPlugin() const override; IPlugin* managerPlugin() const override; /** * Set the manager plugin for the project. */ void setManagerPlugin( IPlugin* manager ); ProjectFolderItem* projectItem() const override; /** * Check if the url specified by @a url is part of the project. * @a url can be either a relative url (to the project directory) or * an absolute url. * * @param url the url to check * * @return true if the url @a url is a part of the project. */ bool inProject(const IndexedString &url) const override; void setReloadJob(KJob* job) override; signals: /** * Internal signal to make IProjectController::projectAboutToOpen useful. */ void aboutToOpen(KDevelop::IProject*); private: Q_PRIVATE_SLOT(d, void importDone(KJob*)) Q_PRIVATE_SLOT(d, void reloadDone(KJob*)) class ProjectPrivate* const d; }; } // namespace KDevelop #endif diff --git a/shell/projectcontroller.cpp b/shell/projectcontroller.cpp index 736ec25345..56ba189c8a 100644 --- a/shell/projectcontroller.cpp +++ b/shell/projectcontroller.cpp @@ -1,1178 +1,1190 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat 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 "projectcontroller.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" +// TODO: Should get rid off this include (should depend on IProject only) #include "project.h" #include "mainwindow.h" #include "shellextension.h" #include "plugincontroller.h" #include "configdialog.h" #include "uicontroller.h" #include "documentcontroller.h" #include "openprojectdialog.h" #include "sessioncontroller.h" #include "session.h" #include "debug.h" namespace KDevelop { class ProjectControllerPrivate { public: QList m_projects; QMap< IProject*, QList > m_projectPlugins; QPointer m_recentAction; Core* m_core; // IProject* m_currentProject; ProjectModel* model; QItemSelectionModel* selectionModel; QPointer m_openProject; QPointer m_fetchProject; QPointer m_closeProject; QPointer m_openConfig; IProjectDialogProvider* dialog; QList m_currentlyOpening; // project-file urls that are being opened IProject* m_configuringProject; ProjectController* q; ProjectBuildSetModel* buildset; bool m_foundProjectFile; //Temporary flag used while searching the hierarchy for a project file bool m_cleaningUp; //Temporary flag enabled while destroying the project-controller QPointer m_changesModel; QHash< IProject*, QPointer > m_parseJobs; // parse jobs that add files from the project to the background parser. ProjectControllerPrivate( ProjectController* p ) : m_core(0), model(0), selectionModel(0), dialog(0), m_configuringProject(0), q(p), buildset(0), m_foundProjectFile(false), m_cleaningUp(false) { } void unloadAllProjectPlugins() { if( m_projects.isEmpty() ) m_core->pluginControllerInternal()->unloadProjectPlugins(); } void projectConfig( QObject * obj ) { if( !obj ) return; Project* proj = qobject_cast(obj); if( !proj ) return; QVector configPages; auto mainWindow = m_core->uiController()->activeMainWindow(); ProjectConfigOptions options; options.developerFile = proj->developerFile(); options.developerTempFile = proj->developerTempFile(); options.projectTempFile = proj->projectTempFile(); options.project = proj; foreach (IPlugin* plugin, findPluginsForProject(proj)) { for (int i = 0; i < plugin->perProjectConfigPages(); ++i) { configPages.append(plugin->perProjectConfigPage(i, options, mainWindow)); } } Q_ASSERT(!m_configuringProject); m_configuringProject = proj; KDevelop::ConfigDialog cfgDlg(configPages, mainWindow); QObject::connect(&cfgDlg, &ConfigDialog::configSaved, &cfgDlg, [this](ConfigPage* page) { Q_UNUSED(page) Q_ASSERT_X(m_configuringProject, Q_FUNC_INFO, "ConfigDialog signalled project config change, but no project set for configuring!"); emit q->projectConfigurationChanged(m_configuringProject); }); cfgDlg.setWindowTitle(i18n("Configure Project %1", proj->name())); cfgDlg.exec(); proj->projectConfiguration()->sync(); m_configuringProject = nullptr; } void saveListOfOpenedProjects() { auto activeSession = Core::self()->activeSession(); if (!activeSession) { return; } QList openProjects; openProjects.reserve( m_projects.size() ); foreach( IProject* project, m_projects ) { openProjects.append(project->projectFile().toUrl()); } activeSession->setContainedProjects( openProjects ); } // Recursively collects builder dependencies for a project. static void collectBuilders( QList< IProjectBuilder* >& destination, IProjectBuilder* topBuilder, IProject* project ) { QList< IProjectBuilder* > auxBuilders = topBuilder->additionalBuilderPlugins( project ); destination.append( auxBuilders ); foreach( IProjectBuilder* auxBuilder, auxBuilders ) { collectBuilders( destination, auxBuilder, project ); } } QVector findPluginsForProject( IProject* project ) { QList plugins = m_core->pluginController()->loadedPlugins(); QVector projectPlugins; QList buildersForKcm; // Important to also include the "top" builder for the project, so // projects with only one such builder are kept working. Otherwise the project config // dialog is empty for such cases. if( IBuildSystemManager* buildSystemManager = project->buildSystemManager() ) { buildersForKcm << buildSystemManager->builder(); collectBuilders( buildersForKcm, buildSystemManager->builder(), project ); } foreach(auto plugin, plugins) { auto info = m_core->pluginController()->pluginInfo(plugin); IProjectFileManager* manager = plugin->extension(); if( manager && manager != project->projectFileManager() ) { // current plugin is a manager but does not apply to given project, skip continue; } IProjectBuilder* builder = plugin->extension(); if ( builder && !buildersForKcm.contains( builder ) ) { continue; } qCDebug(SHELL) << "Using plugin" << info.pluginId() << "for project" << project->name(); projectPlugins << plugin; } return projectPlugins; } void updateActionStates( Context* ctx ) { ProjectItemContext* itemctx = dynamic_cast(ctx); m_openConfig->setEnabled( itemctx && itemctx->items().count() == 1 ); m_closeProject->setEnabled( itemctx && itemctx->items().count() > 0 ); } void openProjectConfig() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() == 1 ) { q->configureProject( ctx->items().at(0)->project() ); } } void closeSelectedProjects() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() > 0 ) { QSet projects; foreach( ProjectBaseItem* item, ctx->items() ) { projects.insert( item->project() ); } foreach( IProject* project, projects ) { q->closeProject( project ); } } } void importProject(const QUrl& url_) { QUrl url(url_); if (url.isLocalFile()) { const QString path = QFileInfo(url.toLocalFile()).canonicalFilePath(); if (!path.isEmpty()) { url = QUrl::fromLocalFile(path); } } if ( !url.isValid() ) { KMessageBox::error(Core::self()->uiControllerInternal()->activeMainWindow(), i18n("Invalid Location: %1", url.toDisplayString(QUrl::PreferLocalFile))); return; } if ( m_currentlyOpening.contains(url)) { qCDebug(SHELL) << "Already opening " << url << ". Aborting."; KPassivePopup::message( i18n( "Project already being opened"), i18n( "Already opening %1, not opening again", url.toDisplayString(QUrl::PreferLocalFile) ), m_core->uiController()->activeMainWindow() ); return; } foreach( IProject* project, m_projects ) { if ( url == project->projectFile().toUrl() ) { if ( dialog->userWantsReopen() ) { // close first, then open again by falling through q->closeProject(project); } else { // abort return; } } } m_currentlyOpening += url; m_core->pluginControllerInternal()->loadProjectPlugins(); Project* project = new Project(); QObject::connect(project, &Project::aboutToOpen, q, &ProjectController::projectAboutToBeOpened); if ( !project->open( Path(url) ) ) { m_currentlyOpening.removeAll(url); q->abortOpeningProject(project); project->deleteLater(); } } void areaChanged(Sublime::Area* area) { KActionCollection* ac = m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); ac->action(QStringLiteral("commit_current_project"))->setEnabled(area->objectName() == QLatin1String("code")); ac->action(QStringLiteral("commit_current_project"))->setVisible(area->objectName() == QLatin1String("code")); } }; IProjectDialogProvider::IProjectDialogProvider() {} IProjectDialogProvider::~IProjectDialogProvider() {} ProjectDialogProvider::ProjectDialogProvider(ProjectControllerPrivate* const p) : d(p) {} ProjectDialogProvider::~ProjectDialogProvider() {} bool writeNewProjectFile( const QString& localConfigFile, const QString& name, const QString& manager ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( localConfigFile, KConfig::SimpleConfig ); if (!cfg->isConfigWritable(true)) { qCDebug(SHELL) << "can't write to configfile"; return false; } KConfigGroup grp = cfg->group( "Project" ); grp.writeEntry( "Name", name ); grp.writeEntry( "Manager", manager ); cfg->sync(); return true; } bool writeProjectSettingsToConfigFile(const QUrl& projectFileUrl, const QString& projectName, const QString& projectManager) { if ( !projectFileUrl.isLocalFile() ) { QTemporaryFile tmp; if ( !tmp.open() ) { return false; } if ( !writeNewProjectFile( tmp.fileName(), projectName, projectManager ) ) { return false; } // explicitly close file before uploading it, see also: https://bugs.kde.org/show_bug.cgi?id=254519 tmp.close(); auto uploadJob = KIO::file_copy(QUrl::fromLocalFile(tmp.fileName()), projectFileUrl); KJobWidgets::setWindow(uploadJob, Core::self()->uiControllerInternal()->defaultMainWindow()); return uploadJob->exec(); } return writeNewProjectFile( projectFileUrl.toLocalFile(),projectName, projectManager ); } bool projectFileExists( const QUrl& u ) { if( u.isLocalFile() ) { return QFileInfo( u.toLocalFile() ).exists(); } else { auto statJob = KIO::stat(u, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(statJob, Core::self()->uiControllerInternal()->activeMainWindow()); return statJob->exec(); } } bool equalProjectFile( const QString& configPath, OpenProjectDialog* dlg ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( configPath, KConfig::SimpleConfig ); KConfigGroup grp = cfg->group( "Project" ); QString defaultName = dlg->projectFileUrl().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).fileName(); return (grp.readEntry( "Name", QString() ) == dlg->projectName() || dlg->projectName() == defaultName) && grp.readEntry( "Manager", QString() ) == dlg->projectManager(); } QUrl ProjectDialogProvider::askProjectConfigLocation(bool fetch, const QUrl& startUrl) { Q_ASSERT(d); OpenProjectDialog dlg( fetch, startUrl, Core::self()->uiController()->activeMainWindow() ); if(dlg.exec() == QDialog::Rejected) return QUrl(); QUrl projectFileUrl = dlg.projectFileUrl(); qCDebug(SHELL) << "selected project:" << projectFileUrl << dlg.projectName() << dlg.projectManager(); // controls if existing project file should be saved bool writeProjectConfigToFile = true; if( projectFileExists( projectFileUrl ) ) { // check whether config is equal bool shouldAsk = true; if( projectFileUrl.isLocalFile() ) { shouldAsk = !equalProjectFile( projectFileUrl.toLocalFile(), &dlg ); } else { shouldAsk = false; QTemporaryFile tmpFile; if (tmpFile.open()) { auto downloadJob = KIO::file_copy(projectFileUrl, QUrl::fromLocalFile(tmpFile.fileName())); KJobWidgets::setWindow(downloadJob, qApp->activeWindow()); if (downloadJob->exec()) { shouldAsk = !equalProjectFile(tmpFile.fileName(), &dlg); } } } if ( shouldAsk ) { KGuiItem yes = KStandardGuiItem::yes(); yes.setText(i18n("Override")); yes.setToolTip(i18nc("@info:tooltip", "Continue to open the project and use the just provided project configuration.")); yes.setIcon(QIcon()); KGuiItem no = KStandardGuiItem::no(); no.setText(i18n("Open Existing File")); no.setToolTip(i18nc("@info:tooltip", "Continue to open the project but use the existing project configuration.")); no.setIcon(QIcon()); KGuiItem cancel = KStandardGuiItem::cancel(); cancel.setToolTip(i18nc("@info:tooltip", "Cancel and do not open the project.")); int ret = KMessageBox::questionYesNoCancel(qApp->activeWindow(), i18n("There already exists a project configuration file at %1.\n" "Do you want to override it or open the existing file?", projectFileUrl.toDisplayString(QUrl::PreferLocalFile)), i18n("Override existing project configuration"), yes, no, cancel ); if ( ret == KMessageBox::No ) { writeProjectConfigToFile = false; } else if ( ret == KMessageBox::Cancel ) { return QUrl(); } // else fall through and write new file } else { writeProjectConfigToFile = false; } } if (writeProjectConfigToFile) { if (!writeProjectSettingsToConfigFile(projectFileUrl, dlg.projectName(), dlg.projectManager())) { KMessageBox::error(d->m_core->uiControllerInternal()->defaultMainWindow(), i18n("Unable to create configuration file %1", projectFileUrl.url())); return QUrl(); } } return projectFileUrl; } bool ProjectDialogProvider::userWantsReopen() { Q_ASSERT(d); return (KMessageBox::questionYesNo( d->m_core->uiControllerInternal()->defaultMainWindow(), i18n( "Reopen the current project?" ) ) == KMessageBox::No) ? false : true; } void ProjectController::setDialogProvider(IProjectDialogProvider* dialog) { Q_ASSERT(d->dialog); delete d->dialog; d->dialog = dialog; } ProjectController::ProjectController( Core* core ) : IProjectController( core ), d( new ProjectControllerPrivate( this ) ) { qRegisterMetaType>(); setObjectName(QStringLiteral("ProjectController")); d->m_core = core; d->model = new ProjectModel(); //NOTE: this is required to be called here, such that the // actions are available when the UI controller gets // initialized *before* the project controller if (Core::self()->setupFlags() != Core::NoUi) { setupActions(); } } void ProjectController::setupActions() { KActionCollection * ac = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction*action; d->m_openProject = action = ac->addAction( QStringLiteral("project_open") ); action->setText(i18nc( "@action", "Open / Import Project..." ) ); action->setToolTip( i18nc( "@info:tooltip", "Open or import project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Open an existing KDevelop 4 project or import " "an existing Project into KDevelop 4. This entry " "allows to select a KDevelop4 project file or an " "existing directory to open it in KDevelop. " "When opening an existing directory that does " "not yet have a KDevelop4 project file, the file " "will be created." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("project-open"))); connect(action, &QAction::triggered, this, [&] { openProject(); }); d->m_fetchProject = action = ac->addAction( QStringLiteral("project_fetch") ); action->setText(i18nc( "@action", "Fetch Project..." ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("edit-download") ) ); action->setToolTip( i18nc( "@info:tooltip", "Fetch project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Guides the user through the project fetch " "and then imports it into KDevelop 4." ) ); // action->setIcon(QIcon::fromTheme("project-open")); connect( action, &QAction::triggered, this, &ProjectController::fetchProject ); // action = ac->addAction( "project_close" ); // action->setText( i18n( "C&lose Project" ) ); // connect( action, SIGNAL(triggered(bool)), SLOT(closeProject()) ); // action->setToolTip( i18n( "Close project" ) ); // action->setWhatsThis( i18n( "Closes the current project." ) ); // action->setEnabled( false ); d->m_closeProject = action = ac->addAction( QStringLiteral("project_close") ); connect( action, &QAction::triggered, this, [&] { d->closeSelectedProjects(); } ); action->setText( i18nc( "@action", "Close Project(s)" ) ); action->setIcon( QIcon::fromTheme( QStringLiteral("project-development-close") ) ); action->setToolTip( i18nc( "@info:tooltip", "Closes all currently selected projects" ) ); action->setEnabled( false ); d->m_openConfig = action = ac->addAction( QStringLiteral("project_open_config") ); connect( action, &QAction::triggered, this, [&] { d->openProjectConfig(); } ); action->setText( i18n( "Open Configuration..." ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("configure")) ); action->setEnabled( false ); action = ac->addAction( QStringLiteral("commit_current_project") ); connect( action, &QAction::triggered, this, &ProjectController::commitCurrentProject ); action->setText( i18n( "Commit Current Project..." ) ); action->setIconText( i18n( "Commit..." ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("svn-commit")) ); connect(d->m_core->uiControllerInternal()->defaultMainWindow(), &MainWindow::areaChanged, this, [&] (Sublime::Area* area) { d->areaChanged(area); }); d->m_core->uiControllerInternal()->area(0, QStringLiteral("code"))->addAction(action); KSharedConfig * config = KSharedConfig::openConfig().data(); // KConfigGroup group = config->group( "General Options" ); d->m_recentAction = KStandardAction::openRecent(this, SLOT(openProject(QUrl)), this); ac->addAction( QStringLiteral("project_open_recent"), d->m_recentAction ); d->m_recentAction->setText( i18n( "Open Recent Project" ) ); d->m_recentAction->setWhatsThis( i18nc( "@info:whatsthis", "Opens recently opened project." ) ); d->m_recentAction->loadEntries( KConfigGroup(config, "RecentProjects") ); QAction* openProjectForFileAction = new QAction( this ); ac->addAction(QStringLiteral("project_open_for_file"), openProjectForFileAction); openProjectForFileAction->setText(i18n("Open Project for Current File")); connect( openProjectForFileAction, &QAction::triggered, this, &ProjectController::openProjectForUrlSlot); } ProjectController::~ProjectController() { delete d->model; delete d->dialog; delete d; } void ProjectController::cleanup() { if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } d->m_cleaningUp = true; if( buildSetModel() ) { buildSetModel()->storeToSession( Core::self()->activeSession() ); } - foreach( IProject* project, d->m_projects ) { - closeProject( project ); - } + closeAllProjects(); } void ProjectController::initialize() { d->buildset = new ProjectBuildSetModel( this ); buildSetModel()->loadFromSession( Core::self()->activeSession() ); connect( this, &ProjectController::projectOpened, d->buildset, &ProjectBuildSetModel::loadFromProject ); connect( this, &ProjectController::projectClosing, d->buildset, &ProjectBuildSetModel::saveToProject ); connect( this, &ProjectController::projectClosed, d->buildset, &ProjectBuildSetModel::projectClosed ); d->selectionModel = new QItemSelectionModel(d->model); loadSettings(false); d->dialog = new ProjectDialogProvider(d); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/ProjectController"), this, QDBusConnection::ExportScriptableSlots ); KSharedConfigPtr config = Core::self()->activeSession()->config(); KConfigGroup group = config->group( "General Options" ); QList openProjects = group.readEntry( "Open Projects", QList() ); QMetaObject::invokeMethod(this, "openProjects", Qt::QueuedConnection, Q_ARG(QList, openProjects)); connect( Core::self()->selectionController(), &ISelectionController::selectionChanged, this, [&] (Context* ctx) { d->updateActionStates(ctx); } ); } void ProjectController::openProjects(const QList& projects) { foreach (const QUrl& url, projects) openProject(url); } void ProjectController::loadSettings( bool projectIsLoaded ) { Q_UNUSED(projectIsLoaded) } void ProjectController::saveSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); } int ProjectController::projectCount() const { return d->m_projects.count(); } IProject* ProjectController::projectAt( int num ) const { if( !d->m_projects.isEmpty() && num >= 0 && num < d->m_projects.count() ) return d->m_projects.at( num ); return 0; } QList ProjectController::projects() const { return d->m_projects; } void ProjectController::eventuallyOpenProjectFile(KIO::Job* _job, KIO::UDSEntryList entries ) { KIO::SimpleJob* job(dynamic_cast(_job)); Q_ASSERT(job); foreach(const KIO::UDSEntry& entry, entries) { if(d->m_foundProjectFile) break; if(!entry.isDir()) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if(name.endsWith(QLatin1String(".kdev4"))) { //We have found a project-file, open it openProject(Path(Path(job->url()), name).toUrl()); d->m_foundProjectFile = true; } } } } void ProjectController::openProjectForUrlSlot(bool) { if(ICore::self()->documentController()->activeDocument()) { QUrl url = ICore::self()->documentController()->activeDocument()->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(!project) { openProjectForUrl(url); }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("Project already open: %1", project->name())); } }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("No active document")); } } void ProjectController::openProjectForUrl(const QUrl& sourceUrl) { Q_ASSERT(!sourceUrl.isRelative()); QUrl dirUrl = sourceUrl.adjusted(QUrl::RemoveFilename); QUrl testAt = dirUrl; d->m_foundProjectFile = false; while(!testAt.path().isEmpty()) { QUrl testProjectFile(testAt); KIO::ListJob* job = KIO::listDir(testAt); connect(job, &KIO::ListJob::entries, this, &ProjectController::eventuallyOpenProjectFile); KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); job->exec(); if(d->m_foundProjectFile) { //Fine! We have directly opened the project d->m_foundProjectFile = false; return; } QUrl oldTest = testAt.adjusted(QUrl::RemoveFilename); if(oldTest == testAt) break; } QUrl askForOpen = d->dialog->askProjectConfigLocation(false, dirUrl); if(askForOpen.isValid()) openProject(askForOpen); } void ProjectController::openProject( const QUrl &projectFile ) { QUrl url = projectFile; if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); if ( url.isEmpty() ) { return; } } Q_ASSERT(!url.isRelative()); QList existingSessions; if(!Core::self()->sessionController()->activeSession()->containedProjects().contains(url)) { foreach( const Session* session, Core::self()->sessionController()->sessions()) { if(session->containedProjects().contains(url)) { existingSessions << session; #if 0 ///@todo Think about this! Problem: The session might already contain files, the debugger might be active, etc. //If this session is empty, close it if(Core::self()->sessionController()->activeSession()->description().isEmpty()) { //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); } #endif } } } if ( ! existingSessions.isEmpty() ) { QDialog dialog(Core::self()->uiControllerInternal()->activeMainWindow()); dialog.setWindowTitle(i18n("Project Already Open")); auto mainLayout = new QVBoxLayout(&dialog); mainLayout->addWidget(new QLabel(i18n("The project you're trying to open is already open in at least one " "other session.
What do you want to do?"))); QGroupBox sessions; sessions.setLayout(new QVBoxLayout); QRadioButton* newSession = new QRadioButton(i18n("Add project to current session")); sessions.layout()->addWidget(newSession); newSession->setChecked(true); foreach ( const Session* session, existingSessions ) { QRadioButton* button = new QRadioButton(i18n("Open session %1", session->description())); button->setProperty("sessionid", session->id().toString()); sessions.layout()->addWidget(button); } sessions.layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); mainLayout->addWidget(&sessions); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Abort); 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); bool success = dialog.exec(); if (!success) return; foreach ( const QObject* obj, sessions.children() ) { if ( const QRadioButton* button = qobject_cast(obj) ) { QString sessionid = button->property("sessionid").toString(); if ( button->isChecked() && ! sessionid.isEmpty() ) { Core::self()->sessionController()->loadSession(sessionid); return; } } } } if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); } if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::fetchProject() { QUrl url = d->dialog->askProjectConfigLocation(true); if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::projectImportingFinished( IProject* project ) { if( !project ) { qWarning() << "OOOPS: 0-pointer project"; return; } IPlugin *managerPlugin = project->managerPlugin(); QList pluglist; pluglist.append( managerPlugin ); d->m_projectPlugins.insert( project, pluglist ); d->m_projects.append( project ); d->saveListOfOpenedProjects(); if (Core::self()->setupFlags() != Core::NoUi) { d->m_recentAction->addUrl( project->projectFile().toUrl() ); KSharedConfig * config = KSharedConfig::openConfig().data(); KConfigGroup recentGroup = config->group("RecentProjects"); d->m_recentAction->saveEntries( recentGroup ); config->sync(); } Q_ASSERT(d->m_currentlyOpening.contains(project->projectFile().toUrl())); d->m_currentlyOpening.removeAll(project->projectFile().toUrl()); emit projectOpened( project ); reparseProject(project); } // helper method for closeProject() void ProjectController::unloadUnusedProjectPlugins(IProject* proj) { QList pluginsForProj = d->m_projectPlugins.value( proj ); d->m_projectPlugins.remove( proj ); QList otherProjectPlugins; Q_FOREACH( const QList& _list, d->m_projectPlugins ) { otherProjectPlugins << _list; } QSet pluginsForProjSet = QSet::fromList( pluginsForProj ); QSet otherPrjPluginsSet = QSet::fromList( otherProjectPlugins ); // loaded - target = tobe unloaded. QSet tobeRemoved = pluginsForProjSet.subtract( otherPrjPluginsSet ); Q_FOREACH( IPlugin* _plugin, tobeRemoved ) { KPluginMetaData _plugInfo = Core::self()->pluginController()->pluginInfo( _plugin ); if( _plugInfo.isValid() ) { QString _plugName = _plugInfo.pluginId(); qCDebug(SHELL) << "about to unloading :" << _plugName; Core::self()->pluginController()->unloadPlugin( _plugName ); } } } // helper method for closeProject() void ProjectController::closeAllOpenedFiles(IProject* proj) { foreach(IDocument* doc, Core::self()->documentController()->openDocuments()) { if (proj->inProject(IndexedString(doc->url()))) { doc->close(); } } } // helper method for closeProject() void ProjectController::initializePluginCleanup(IProject* proj) { // Unloading (and thus deleting) these plugins is not a good idea just yet // as we're being called by the view part and it gets deleted when we unload the plugin(s) // TODO: find a better place to unload connect(proj, &IProject::destroyed, this, [&] { d->unloadAllProjectPlugins(); }); } -void ProjectController::closeProject(IProject* proj_) +void ProjectController::takeProject(IProject* proj) { - if (!proj_) - { + if (!proj) { return; } // loading might have failed - d->m_currentlyOpening.removeAll(proj_->projectFile().toUrl()); - - Project* proj = dynamic_cast( proj_ ); - if( !proj ) - { - qWarning() << "Unknown Project subclass found!"; - return; - } + d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); d->m_projects.removeAll(proj); emit projectClosing(proj); //Core::self()->saveSettings(); // The project file is being closed. // Now we can save settings for all of the Core // objects including this one!! unloadUnusedProjectPlugins(proj); closeAllOpenedFiles(proj); proj->close(); - proj->deleteLater(); //be safe when deleting if (d->m_projects.isEmpty()) { initializePluginCleanup(proj); } if(!d->m_cleaningUp) d->saveListOfOpenedProjects(); emit projectClosed(proj); - return; +} + +void ProjectController::closeProject(IProject* proj) +{ + takeProject(proj); + proj->deleteLater(); // be safe when deleting +} + +void ProjectController::closeAllProjects() +{ + foreach (auto project, d->m_projects) { + closeProject(project); + } } void ProjectController::abortOpeningProject(IProject* proj) { d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); emit projectOpeningAborted(proj); } ProjectModel* ProjectController::projectModel() { return d->model; } IProject* ProjectController::findProjectForUrl( const QUrl& url ) const { if (d->m_projects.isEmpty()) { return 0; } ProjectBaseItem* item = d->model->itemForPath(IndexedString(url)); if (item) { return item->project(); } return 0; } IProject* ProjectController::findProjectByName( const QString& name ) { Q_FOREACH( IProject* proj, d->m_projects ) { if( proj->name() == name ) { return proj; } } return 0; } void ProjectController::configureProject( IProject* project ) { d->projectConfig( project ); } void ProjectController::addProject(IProject* project) { - d->m_projects.append( project ); + Q_ASSERT(project); + if (d->m_projects.contains(project)) { + qWarning() << "Project already tracked by this project controller:" << project; + return; + } + + // fake-emit signals so listeners are aware of a new project being added + emit projectAboutToBeOpened(project); + project->setParent(this); + d->m_projects.append(project); + emit projectOpened(project); } QItemSelectionModel* ProjectController::projectSelectionModel() { return d->selectionModel; } bool ProjectController::isProjectNameUsed( const QString& name ) const { foreach( IProject* p, projects() ) { if( p->name() == name ) { return true; } } return false; } QUrl ProjectController::projectsBaseDirectory() const { KConfigGroup group = ICore::self()->activeSession()->config()->group( "Project Manager" ); return group.readEntry( "Projects Base Directory", QUrl::fromLocalFile( QDir::homePath() + "/projects" ) ); } QString ProjectController::prettyFilePath(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(!project) { // Find a project with the correct base directory at least foreach(IProject* candidateProject, Core::self()->projectController()->projects()) { if(candidateProject->path().toUrl().isParentOf(url)) { project = candidateProject; break; } } } Path parent = Path(url).parent(); QString prefixText; if (project) { if (format == FormatHtml) { prefixText = "" + project->name() + "/"; } else { prefixText = project->name() + ':'; } QString relativePath = project->path().relativePath(parent); if(relativePath.startsWith(QLatin1String("./"))) { relativePath = relativePath.mid(2); } if (!relativePath.isEmpty()) { prefixText += relativePath + '/'; } } else { prefixText = parent.pathOrUrl() + '/'; } return prefixText; } QString ProjectController::prettyFileName(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(project && project->path() == Path(url)) { if (format == FormatHtml) { return "" + project->name() + ""; } else { return project->name(); } } QString prefixText = prettyFilePath( url, format ); if (format == FormatHtml) { return prefixText + "" + url.fileName() + ""; } else { return prefixText + url.fileName(); } } ContextMenuExtension ProjectController::contextMenuExtension ( Context* ctx ) { ContextMenuExtension ext; if ( ctx->type() != Context::ProjectItemContext || !static_cast(ctx)->items().isEmpty() ) { return ext; } ext.addAction(ContextMenuExtension::ProjectGroup, d->m_openProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_fetchProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_recentAction); return ext; } ProjectBuildSetModel* ProjectController::buildSetModel() { return d->buildset; } ProjectChangesModel* ProjectController::changesModel() { if(!d->m_changesModel) d->m_changesModel=new ProjectChangesModel(this); return d->m_changesModel; } void ProjectController::commitCurrentProject() { IDocument* doc=ICore::self()->documentController()->activeDocument(); if(!doc) return; QUrl url=doc->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { IPlugin* plugin = project->versionControlPlugin(); IBasicVersionControl* vcs=plugin->extension(); if(vcs) { ICore::self()->documentController()->saveAllDocuments(KDevelop::IDocument::Silent); const Path basePath = project->path(); VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(vcs, basePath.toUrl())); bool ret = showVcsDiff(patchSource); if(!ret) { VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } } } QString ProjectController::mapSourceBuild( const QString& path_, bool reverse, bool fallbackRoot ) const { Path path(path_); IProject* sourceDirProject = 0, *buildDirProject = 0; Q_FOREACH(IProject* proj, d->m_projects) { if(proj->path().isParentOf(path)) sourceDirProject = proj; if(proj->buildSystemManager()) { Path buildDir = proj->buildSystemManager()->buildDirectory(proj->projectItem()); if(buildDir.isValid() && buildDir.isParentOf(path)) buildDirProject = proj; } } if(!reverse) { // Map-target is the build directory if(sourceDirProject && sourceDirProject->buildSystemManager()) { // We're in the source, map into the build directory QString relativePath = sourceDirProject->path().relativePath(path); Path build = sourceDirProject->buildSystemManager()->buildDirectory(sourceDirProject->projectItem()); build.addPath(relativePath); while(!QFile::exists(build.path())) build = build.parent(); return build.pathOrUrl(); }else if(buildDirProject && fallbackRoot) { // We're in the build directory, map to the build directory root return buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()).pathOrUrl(); } }else{ // Map-target is the source directory if(buildDirProject) { Path build = buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()); // We're in the source, map into the build directory QString relativePath = build.relativePath(path); Path source = buildDirProject->path(); source.addPath(relativePath); while(!QFile::exists(source.path())) source = source.parent(); return source.pathOrUrl(); }else if(sourceDirProject && fallbackRoot) { // We're in the source directory, map to the root return sourceDirProject->path().pathOrUrl(); } } return QString(); } void ProjectController::reparseProject( IProject* project, bool forceUpdate ) { if (auto job = d->m_parseJobs.value(project)) { job->kill(); } d->m_parseJobs[project] = new KDevelop::ParseProjectJob(project, forceUpdate); ICore::self()->runController()->registerJob(d->m_parseJobs[project]); } } #include "moc_projectcontroller.cpp" diff --git a/shell/projectcontroller.h b/shell/projectcontroller.h index 25fb3a6bd9..f31e9b430f 100644 --- a/shell/projectcontroller.h +++ b/shell/projectcontroller.h @@ -1,169 +1,184 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat 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. */ #ifndef KDEVPLATFORM_PROJECTCONTROLLER_H #define KDEVPLATFORM_PROJECTCONTROLLER_H #include #include #include #include "shellexport.h" namespace Sublime { class Area; } namespace KIO { class Job; } namespace KDevelop { class IProject; class Core; class Context; class ContextMenuExtension; class IPlugin; class KDEVPLATFORMSHELL_EXPORT IProjectDialogProvider : public QObject { Q_OBJECT public: IProjectDialogProvider(); ~IProjectDialogProvider() override; public Q_SLOTS: /** * Displays some UI to ask the user for the project location. * * @param fetch will tell the UI that the user might want to fetch the project first * @param startUrl tells where to look first */ virtual QUrl askProjectConfigLocation(bool fetch, const QUrl& startUrl = QUrl()) = 0; virtual bool userWantsReopen() = 0; }; class KDEVPLATFORMSHELL_EXPORT ProjectController : public IProjectController { Q_OBJECT Q_CLASSINFO( "D-Bus Interface", "org.kdevelop.ProjectController" ) friend class Core; friend class CorePrivate; friend class ProjectPreferences; public: explicit ProjectController( Core* core ); ~ProjectController() override; IProject* projectAt( int ) const override; int projectCount() const override; QList projects() const override; ProjectBuildSetModel* buildSetModel() override; ProjectModel* projectModel() override; ProjectChangesModel* changesModel() override; virtual QItemSelectionModel* projectSelectionModel(); IProject* findProjectByName( const QString& name ) override; IProject* findProjectForUrl( const QUrl& ) const override; - void addProject(IProject*); -// IProject* currentProject() const; bool isProjectNameUsed( const QString& name ) const override; void setDialogProvider(IProjectDialogProvider*); QUrl projectsBaseDirectory() const override; QString prettyFileName(const QUrl& url, FormattingOptions format = FormatHtml) const override; QString prettyFilePath(const QUrl& url, FormattingOptions format = FormatHtml) const override; ContextMenuExtension contextMenuExtension( KDevelop::Context* ctx ); public Q_SLOTS: void openProjectForUrl( const QUrl &sourceUrl ) override; virtual void fetchProject(); void openProject( const QUrl &KDev4ProjectFile = QUrl() ) override; virtual void abortOpeningProject( IProject* ); void projectImportingFinished( IProject* ); void closeProject( IProject* ) override; + void closeAllProjects() override; void configureProject( IProject* ) override; void reparseProject( IProject* project, bool forceUpdate = false ) override; void eventuallyOpenProjectFile(KIO::Job*,KIO::UDSEntryList); void openProjectForUrlSlot(bool); // void changeCurrentProject( ProjectBaseItem* ); void openProjects(const QList& projects); void commitCurrentProject(); // Maps the given path from the source to the equivalent path within the build directory // of the corresponding project. If the path is already in the build directory and fallbackRoot is true, // then it is mapped to the root of the build directory. // If reverse is true, maps the opposite direction, from build to source. [ Used in kdevplatform_shell_environment.sh ] Q_SCRIPTABLE QString mapSourceBuild( const QString& path, bool reverse = false, bool fallbackRoot = true ) const; protected: + /** + * Add the existing project @p project to the controller + * + * @note Method is used for testing objectives, consider using openProject() instead! + * @note takes ownership over the project + * + * @sa openProject() + */ + void addProject(IProject* proj); + /** + * Remove the project @p project from the controller + * + * @note Ownership is passed on to the caller + */ + void takeProject(IProject* proj); + virtual void loadSettings( bool projectIsLoaded ); virtual void saveSettings( bool projectIsLoaded ); virtual void initialize(); private: //FIXME Do not load all of this just for the project being opened... //void legacyLoading(); void setupActions(); void cleanup(); // helper methods for closeProject() void unloadUnusedProjectPlugins(IProject* proj); void disableProjectCloseAction(); void closeAllOpenedFiles(IProject* proj); void initializePluginCleanup(IProject* proj); private: Q_PRIVATE_SLOT(d, void projectConfig( QObject* ) ) Q_PRIVATE_SLOT(d, void unloadAllProjectPlugins() ) Q_PRIVATE_SLOT(d, void updateActionStates( KDevelop::Context* ) ) Q_PRIVATE_SLOT(d, void closeSelectedProjects() ) Q_PRIVATE_SLOT(d, void openProjectConfig() ) Q_PRIVATE_SLOT(d, void areaChanged(Sublime::Area*) ) class ProjectControllerPrivate* const d; friend class ProjectControllerPrivate; }; class ProjectDialogProvider : public IProjectDialogProvider { Q_OBJECT public: explicit ProjectDialogProvider(ProjectControllerPrivate* const p); ~ProjectDialogProvider() override; ProjectControllerPrivate* const d; public Q_SLOTS: QUrl askProjectConfigLocation(bool fetch, const QUrl& sta) override; bool userWantsReopen() override; }; } #endif diff --git a/tests/testproject.cpp b/tests/testproject.cpp index 8fe82bdfc6..40321105b2 100644 --- a/tests/testproject.cpp +++ b/tests/testproject.cpp @@ -1,162 +1,132 @@ /*************************************************************************** * Copyright 2010 Niko Sams * * Copyright 2012 Milian Wolff * * * * 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 "testproject.h" #include #include using namespace KDevelop; TestProject::TestProject(const Path& path, QObject* parent) : IProject(parent) , m_root(0) , m_projectConfiguration(KSharedConfig::openConfig()) { m_path = path.isValid() ? path : Path("/tmp/kdev-testproject/"); m_root = new ProjectFolderItem(this, m_path); ICore::self()->projectController()->projectModel()->appendRow( m_root ); } void TestProject::setPath(const Path& path) { m_path = path; if (m_root) { m_root->setPath(path); } } TestProject::~TestProject() { if (m_root) { delete m_root; } } ProjectFolderItem* TestProject::projectItem() const { return m_root; } void TestProject::setProjectItem(ProjectFolderItem* item) { if (m_root) { ICore::self()->projectController()->projectModel()->removeRow( m_root->row() ); m_root = 0; m_path.clear(); } if (item) { m_root = item; m_path = item->path(); ICore::self()->projectController()->projectModel()->appendRow( m_root ); } } Path TestProject::projectFile() const { return Path(m_path, m_path.lastPathSegment() + ".kdev4"); } Path TestProject::path() const { return m_path; } bool TestProject::inProject(const IndexedString& path) const { return m_path.isParentOf(Path(path.str())); } void findFileItems(ProjectBaseItem* root, QList& items, const Path& path = {}) { foreach(ProjectBaseItem* item, root->children()) { if (item->file() && (path.isEmpty() || item->path() == path)) { items << item->file(); } if (item->rowCount()) { findFileItems(item, items, path); } } } QList< ProjectFileItem* > TestProject::files() const { QList ret; findFileItems(m_root, ret); return ret; } QList TestProject::filesForPath(const IndexedString& path) const { QList ret; findFileItems(m_root, ret, Path(path.toUrl())); return ret; } void TestProject::addToFileSet(ProjectFileItem* file) { if (!m_fileSet.contains(file->indexedPath())) { m_fileSet.insert(file->indexedPath()); emit fileAddedToSet(file); } } void TestProject::removeFromFileSet(ProjectFileItem* file) { if (m_fileSet.remove(file->indexedPath())) { emit fileRemovedFromSet(file); } } -void TestProjectController::addProject(IProject* p) -{ - emit projectAboutToBeOpened(p); - p->setParent(this); - m_projects << p; - emit projectOpened(p); -} - -void TestProjectController::clearProjects() -{ - foreach(IProject* p, m_projects) { - closeProject(p); - } -} - -void TestProjectController::closeProject(IProject* p) -{ - emit projectClosing(p); - delete p; - m_projects.removeOne(p); - emit projectClosed(p); -} - -void KDevelop::TestProjectController::takeProject(KDevelop::IProject* p) -{ - emit projectClosing(p); - m_projects.removeOne(p); - emit projectClosed(p); -} - void TestProjectController::initialize() { } diff --git a/tests/testproject.h b/tests/testproject.h index a18c645f9f..7f37619967 100644 --- a/tests/testproject.h +++ b/tests/testproject.h @@ -1,106 +1,108 @@ /*************************************************************************** * Copyright 2010 Niko Sams * * Copyright 2012 Milian Wolff * * * * 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_TEST_PROJECT_H #define KDEVPLATFORM_TEST_PROJECT_H #include #include #include #include #include "testsexport.h" #include namespace KDevelop { /** * Dummy Project than can be used for Unit Tests. * * Currently only FileSet methods are implemented. */ class KDEVPLATFORMTESTS_EXPORT TestProject : public IProject { Q_OBJECT public: /** * @p url Path to project directory. */ TestProject(const Path& url = Path(), QObject* parent = 0); ~TestProject() override; IProjectFileManager* projectFileManager() const override { return 0; } IBuildSystemManager* buildSystemManager() const override { return 0; } IPlugin* managerPlugin() const override { return 0; } IPlugin* versionControlPlugin() const override { return 0; } ProjectFolderItem* projectItem() const override; void setProjectItem(ProjectFolderItem* item); int fileCount() const { return 0; } ProjectFileItem* fileAt( int ) const { return 0; } QList files() const; QList< ProjectBaseItem* > itemsForPath(const IndexedString&) const override { return QList< ProjectBaseItem* >(); } QList< ProjectFileItem* > filesForPath(const IndexedString&) const override; QList< ProjectFolderItem* > foldersForPath(const IndexedString&) const override { return QList(); } void reloadModel() override { } + void close() override {} Path projectFile() const override; KSharedConfigPtr projectConfiguration() const override { return m_projectConfiguration; } void addToFileSet( ProjectFileItem* file) override; void removeFromFileSet( ProjectFileItem* file) override; QSet fileSet() const override { return m_fileSet; } bool isReady() const override { return true; } void setPath(const Path& path); Path path() const override; QString name() const override { return "Test Project"; } bool inProject(const IndexedString& path) const override; void setReloadJob(KJob* ) override {} private: QSet m_fileSet; Path m_path; ProjectFolderItem* m_root; KSharedConfigPtr m_projectConfiguration; }; /** * ProjectController that can clear open projects. Useful in Unit Tests. */ class KDEVPLATFORMTESTS_EXPORT TestProjectController : public ProjectController { Q_OBJECT + public: TestProjectController(Core* core) : ProjectController(core) {} IProject* projectAt( int i ) const override { return m_projects.at(i); } int projectCount() const override { return m_projects.count(); } QList projects() const override { return m_projects; } + public: - void addProject(IProject* p); - void takeProject(IProject* p); - void clearProjects(); - void closeProject(IProject* p) override; + using ProjectController::addProject; + using ProjectController::takeProject; void initialize() override; + private: QList m_projects; }; } #endif