diff --git a/interfaces/iplugin.cpp b/interfaces/iplugin.cpp index 58cdcf2e1c..8bf8449a2b 100644 --- a/interfaces/iplugin.cpp +++ b/interfaces/iplugin.cpp @@ -1,204 +1,204 @@ /* This file is part of the KDE project Copyright 2002 Simon Hausmann Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Harald Fernengel Copyright 2002 Falk Brettschneider Copyright 2003 Julian Rockey Copyright 2003 Roberto Raggi Copyright 2003 Jens Dagerbo Copyright 2003 Mario Scalas Copyright 2003-2004,2007 Alexander Dymo 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 "iplugin.h" #include #include #include #include #include "icore.h" #include "iplugincontroller.h" #include "iprojectcontroller.h" #include "iproject.h" #include "contextmenuextension.h" namespace KDevelop { class IPluginPrivate { public: IPluginPrivate(IPlugin *q) : q(q) {} ~IPluginPrivate() { } void guiClientAdded(KXMLGUIClient *client) { if (client != q) return; q->initializeGuiState(); updateState(); } void updateState() { const int projectCount = ICore::self()->projectController()->projectCount(); IPlugin::ReverseStateChange reverse = IPlugin::StateNoReverse; if (! projectCount) reverse = IPlugin::StateReverse; q->stateChanged(QLatin1String("has_project"), reverse); } IPlugin *q; ICore *core; QStringList m_extensions; }; IPlugin::IPlugin( const QString &componentName, QObject *parent ) : KTextEditor::Plugin(parent), KXMLGUIClient(), d( new IPluginPrivate(this) ) { // The following cast is safe, there's no component in KDevPlatform that // creates plugins except the plugincontroller. The controller passes // Core::self() as parent to KServiceTypeTrader::createInstanceFromQuery // so we know the parent is always a Core* pointer. // This is the only way to pass the Core pointer to the plugin during its // creation so plugins have access to ICore during their creation. d->core = static_cast(parent); setComponentName(componentName, componentName); auto clientAdded = [=] (KXMLGUIClient* client) { d->guiClientAdded(client); }; foreach (KMainWindow* mw, KMainWindow::memberList()) { KXmlGuiWindow* guiWindow = qobject_cast(mw); if (! guiWindow) continue; connect(guiWindow->guiFactory(), &KXMLGUIFactory::clientAdded, this, clientAdded); } auto updateState = [=] { d->updateState(); }; connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, updateState); connect(ICore::self()->projectController(), &IProjectController::projectClosed, this, updateState); } IPlugin::~IPlugin() { delete d; } void IPlugin::unload() { } ICore *IPlugin::core() const { return d->core; } } QStringList KDevelop::IPlugin::extensions( ) const { return d->m_extensions; } void KDevelop::IPlugin::addExtension( const QString& ext ) { d->m_extensions << ext; } KDevelop::ContextMenuExtension KDevelop::IPlugin::contextMenuExtension( KDevelop::Context* ) { return KDevelop::ContextMenuExtension(); } void KDevelop::IPlugin::initializeGuiState() { } class CustomXmlGUIClient : public KXMLGUIClient { public: CustomXmlGUIClient(const QString& componentName) { // TODO KF5: Get rid off this setComponentName(componentName, componentName); } void setXmlFile(QString file) { KXMLGUIClient::setXMLFile(file); } }; KXMLGUIClient* KDevelop::IPlugin::createGUIForMainWindow(Sublime::MainWindow* window) { CustomXmlGUIClient* ret = new CustomXmlGUIClient(componentName()); QString file; createActionsForMainWindow(window, file, *ret->actionCollection()); if(!ret->actionCollection()->isEmpty()) { Q_ASSERT(!file.isEmpty()); //A file must have been set ret->setXmlFile(file); }else{ delete ret; ret = 0; } return ret; } void KDevelop::IPlugin::createActionsForMainWindow( Sublime::MainWindow* /*window*/, QString& /*xmlFile*/, KActionCollection& /*actions*/ ) { } bool KDevelop::IPlugin::hasError() const { return false; } QString KDevelop::IPlugin::errorDescription() const { return QString(); } int KDevelop::IPlugin::perProjectConfigPages() const { return 0; } -KTextEditor::ConfigPage* KDevelop::IPlugin::perProjectConfigPage(int, QWidget*) +KDevelop::ConfigPage* KDevelop::IPlugin::perProjectConfigPage(int, const ProjectConfigOptions&, QWidget*) { return nullptr; } -KDevelop::ConfigPage* KDevelop::IPlugin::configPage ( int number, QWidget* parent ) +KDevelop::ConfigPage* KDevelop::IPlugin::configPage (int, QWidget*) { return nullptr; } #include "moc_iplugin.cpp" diff --git a/interfaces/iplugin.h b/interfaces/iplugin.h index 22fb97594d..bdaf852c81 100644 --- a/interfaces/iplugin.h +++ b/interfaces/iplugin.h @@ -1,266 +1,269 @@ /* This file is part of the KDE project Copyright 1999-2001 Bernd Gehrmann Copyright 2004,2007 Alexander Dymo 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_IPLUGIN_H #define KDEVPLATFORM_IPLUGIN_H #include #include #include #include #include #include #include "interfacesexport.h" #include "configpage.h" class KComponentData; class QAction; namespace Sublime { class MainWindow; } /** * This macro adds an extension interface to register with the extension manager * Call this macro for all interfaces your plugin implements in its constructor */ #define KDEV_USE_EXTENSION_INTERFACE( Extension ) \ addExtension( qobject_interface_iid() ); namespace KDevelop { class ICore; class Context; class ContextMenuExtension; - +struct ProjectConfigOptions; /** * The base class for all KDevelop plugins. * * Plugin is a component which is loaded into KDevelop shell at startup or by * request. Each plugin should have corresponding .desktop file with a * description. The .desktop file template looks like: * @code * [Desktop Entry] * Type=Service * Exec=blubb * Name= * GenericName= * Comment= * Icon= * ServiceTypes=KDevelop/Plugin * X-KDE-Library= * X-KDE-PluginInfo-Name= * X-KDE-PluginInfo-Author= * X-KDE-PluginInfo-Version= * X-KDE-PluginInfo-License= * X-KDE-PluginInfo-Category= * X-KDevelop-Version= * X-KDevelop-Category= * X-KDevelop-Mode=GUI * X-KDevelop-LoadMode= * X-KDevelop-Language= * X-KDevelop-SupportedMimeTypes= * X-KDevelop-Interfaces= * X-KDevelop-IOptional= * X-KDevelop-IRequired= * @endcode * Description of parameters in .desktop file: * - Name is a translatable name of a plugin, it is used in the plugin * selection list (required); * - GenericName is a translatable generic name of a plugin, it should * describe in general what the plugin can do (required); * - Comment is a short description about the plugin (optional); * - Icon is a plugin icon (preferred); * X-KDE-librarythis is the name of the .so file to load for this plugin (required); * - X-KDE-PluginInfo-Name is a non-translatable user-readable plugin * identifier used in KTrader queries (required); * - X-KDE-PluginInfo-Author is a non-translateable name of the plugin * author (optional); * - X-KDE-PluginInfo-Version is version number of the plugin (optional); * - X-KDE-PluginInfo-License is a license (optional). can be: GPL, * LGPL, BSD, Artistic, QPL or Custom. If this property is not set, license is * considered as unknown; * - X-KDE-PluginInfo-Category is used to categorize plugins (optional). can be: * Core, Project Management, Version Control, Utilities, Documentation, * Language Support, Debugging, Other * If this property is not set, "Other" is assumed * - X-KDevelop-Version is the KDevPlatform API version this plugin * works with (required); * - X-KDevelop-Interfaces is a list of extension interfaces that this * plugin implements (optional); * - X-KDevelop-IRequired is a list of extension interfaces that this * plugin depends on (optional); A list entry can also be of the form @c interface@pluginname, * in which case a plugin of the given name is required which implements the interface. * - X-KDevelop-IOptional is a list of extension interfaces that this * plugin will use if they are available (optional); * - X-KDevelop-Language is the name of the language the plugin provides * support for (optional); * - X-KDevelop-SupportedMimeTypes is a list of mimetypes that the * language-parser in this plugin supports (optional); * - X-KDevelop-Mode is either GUI or NoGUI to indicate whether a plugin can run * with the GUI components loaded or not (required); * - X-KDevelop-Category is a scope of a plugin (see below for * explanation) (required); * - X-KDevelop-LoadMode can be set to AlwaysOn in which case the plugin will * never be unloaded even if requested via the API. (optional); * * Plugin scope can be either: * - Global * - Project * . * Global plugins are plugins which require only the shell to be loaded and do not operate on * the Project interface and/or do not use project wide information.\n * Core plugins are global plugins which offer some important "core" functionality and thus * are not selectable by user in plugin configuration pages.\n * Project plugins require a project to be loaded and are usually loaded/unloaded along with * the project. * If your plugin uses the Project interface and/or operates on project-related * information then this is a project plugin. * * * @sa Core class documentation for information about features available to * plugins from shell applications. */ class KDEVPLATFORMINTERFACES_EXPORT IPlugin: public KTextEditor::Plugin, public KXMLGUIClient { Q_OBJECT public: /**Constructs a plugin. * @param componentName The component name for this plugin. * @param parent The parent object for the plugin. */ IPlugin(const QString &componentName, QObject *parent); /**Destructs a plugin.*/ virtual ~IPlugin(); /** * Signal the plugin that it should cleanup since it will be unloaded soon. */ Q_SCRIPTABLE virtual void unload(); /** * Provides access to the ICore implementation */ Q_SCRIPTABLE ICore *core() const; Q_SCRIPTABLE QStringList extensions() const; template Extension* extension() { if( extensions().contains( qobject_interface_iid() ) ) { return qobject_cast( this ); } return 0; } /** * Ask the plugin for a ContextActionContainer, which contains actions * that will be merged into the context menu. * @param context the context describing where the context menu was requested * @returns a container describing which actions to merge into which context menu part */ virtual ContextMenuExtension contextMenuExtension( KDevelop::Context* context ); /** * Can create a new KXMLGUIClient, and set it up correctly with all the plugins per-window GUI actions. * * The caller owns the created object, including all contained actions. The object is destroyed as soon as * the mainwindow is closed. * * The default implementation calls the convenience function @ref createActionsForMainWindow and uses it to fill a custom KXMLGUIClient. * * Only override this version if you need more specific features of KXMLGUIClient, or other special per-window handling. * * @param window The main window the actions are created for */ virtual KXMLGUIClient* createGUIForMainWindow( Sublime::MainWindow* window ); /** * This function allows allows setting up plugin actions conveniently. Unless createGUIForMainWindow was overridden, * this is called for each new mainwindow, and the plugin should add its actions to @p actions, and write its KXMLGui xml file * into @p xmlFile. * * @param window The main window the actions are created for * @param xmlFile Change this to the xml file that needed for merging the actions into the GUI * @param actions Add your actions here. A new set of actions has to be created for each mainwindow. */ virtual void createActionsForMainWindow( Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions ); /** * This function is necessary because there is no proper way to signal errors from plugin constructor. * @returns True if the plugin has encountered an error, false otherwise. */ virtual bool hasError() const; /** * Description of the last encountered error, of an empty string if none. */ virtual QString errorDescription() const; /** * Get the number of available config pages for per project settings. * @return number of per project config pages, default implementation says 0 * @see perProjectConfigPage() */ virtual int perProjectConfigPages() const; /** * Get the per project config page with the \p number, config pages from 0 to * configPages()-1 are available if configPages() > 0. + * * @param number index of config page + * @param options The options used to initialize the ProjectConfigPage * @param parent parent widget for config page - * @return newly created config page or NULL, if the number is out of bounds, default implementation returns NULL - * @see perProjectConfigPages() + * @return newly created config page or NULL, if the number is out of bounds, default implementation returns NULL. + * This config page should inherit from ProjectConfigPage, but it is not a strict requirement. + * @see perProjectConfigPages(), ProjectConfigPage */ - virtual KTextEditor::ConfigPage* perProjectConfigPage(int number, QWidget *parent); + virtual ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent); /** * Make sure plugins return a KDevelop::ConfigPage and not just a KTextEditor::ConfigPage * @see KTextEditor::Plugin::configPage */ virtual KDevelop::ConfigPage* configPage(int number, QWidget *parent) override; /** This is implemented to do nothing, so that we can inherit from KTextEditor::Plugin */ virtual QObject* createView(KTextEditor::MainWindow*) override final { return nullptr; }; protected: void addExtension( const QString& ); /** * Initialize the XML GUI State. */ virtual void initializeGuiState(); private: friend class IPluginPrivate; class IPluginPrivate* const d; }; } #endif diff --git a/plugins/projectfilter/CMakeLists.txt b/plugins/projectfilter/CMakeLists.txt index 77e746b075..14f13f2e64 100644 --- a/plugins/projectfilter/CMakeLists.txt +++ b/plugins/projectfilter/CMakeLists.txt @@ -1,50 +1,27 @@ ############################## # PLUGIN ##################### ############################## set( projectfilter_SRCS projectfilterprovider.cpp projectfilter.cpp projectfilterdebug.cpp filter.cpp -) - -add_library( kdevprojectfilter MODULE ${projectfilter_SRCS} ) -target_link_libraries( kdevprojectfilter - KF5::KCMUtils - KDev::Project KDev::Util KDev::Interfaces ) - -install( TARGETS kdevprojectfilter DESTINATION ${PLUGIN_INSTALL_DIR}/kdevplatform/${KDEV_PLUGIN_VERSION} ) - -configure_file(kdevprojectfilter.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdevprojectfilter.desktop) -kcoreaddons_desktop_to_json(kdevprojectfilter ${CMAKE_CURRENT_BINARY_DIR}/kdevprojectfilter.desktop) - -############################## -# KCM ######################## -############################## - -set( projectfilterkcm_SRCS projectfilterkcm.cpp projectfilterdebug.cpp filter.cpp filtermodel.cpp comboboxdelegate.cpp ) -set( projectfilterkcm_UI - projectfiltersettings.ui -) +ki18n_wrap_ui(projectfilter_SRCS projectfiltersettings.ui) +kconfig_add_kcfg_files(projectfilter_SRCS projectfiltersettings.kcfgc) -ki18n_wrap_ui( projectfilterkcm_SRCS ${projectfilterkcm_UI} ) -kconfig_add_kcfg_files( projectfilterkcm_SRCS projectfiltersettings.kcfgc ) -add_library( kcm_kdevprojectfilter MODULE ${projectfilterkcm_SRCS} ) -target_link_libraries( kcm_kdevprojectfilter - KF5::KCMUtils - KDev::Project KDev::Util KDev::Interfaces) +add_library( kdevprojectfilter MODULE ${projectfilter_SRCS} ) +target_link_libraries(kdevprojectfilter KDev::Project KDev::Util KDev::Interfaces) +configure_file(kdevprojectfilter.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdevprojectfilter.desktop) +kcoreaddons_desktop_to_json(kdevprojectfilter ${CMAKE_CURRENT_BINARY_DIR}/kdevprojectfilter.desktop) -install( TARGETS kcm_kdevprojectfilter DESTINATION ${PLUGIN_INSTALL_DIR}/kdevplatform/${KDEV_PLUGIN_VERSION}/kcm ) -configure_file(kcm_kdevprojectfilter.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/kcm_kdevprojectfilter.desktop) -kcoreaddons_desktop_to_json(kcm_kdevprojectfilter ${CMAKE_CURRENT_BINARY_DIR}/kcm_kdevprojectfilter.desktop) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kcm_kdevprojectfilter.desktop DESTINATION ${SERVICES_INSTALL_DIR}) +install(TARGETS kdevprojectfilter DESTINATION ${PLUGIN_INSTALL_DIR}/kdevplatform/${KDEV_PLUGIN_VERSION}) add_subdirectory(tests) diff --git a/plugins/projectfilter/kcm_kdevprojectfilter.desktop.cmake b/plugins/projectfilter/kcm_kdevprojectfilter.desktop.cmake deleted file mode 100644 index 2d728b3fcb..0000000000 --- a/plugins/projectfilter/kcm_kdevprojectfilter.desktop.cmake +++ /dev/null @@ -1,69 +0,0 @@ -[Desktop Entry] -Icon=view-filter -Type=Service -ServiceTypes=KCModule - -X-KDE-ModuleType=Library -X-KDE-Library=kdevplatform/@KDEV_PLUGIN_VERSION@/kcm/kcm_kdevprojectfilter -X-KDE-FactoryName=kcm_kdevprojectfilter -X-KDE-ParentApp=kdevplatform -X-KDE-ParentComponents=KDevProjectFilter -X-KDE-CfgDlgHierarchy=GENERAL - -Name=Project Filter -Name[bs]=Filter projekta -Name[ca]=Filtre de projecte -Name[ca@valencia]=Filtre de projecte -Name[cs]=Filtr projektů -Name[da]=Projektfilter -Name[de]=Projektfilter -Name[en_GB]=Project Filter -Name[es]=Filtro del proyecto -Name[et]=Projektifilter -Name[fi]=Projektisuodatin -Name[fr]=Filtre de projet -Name[gl]=Filtro do proxecto -Name[hu]=Projektszűrő -Name[it]=Filtro progetto -Name[kk]=Жоба сүзгісі -Name[nb]=Prosjektfilter -Name[nl]=Projectfilter -Name[pl]=Filtr projektu -Name[pt]=Filtro de Projectos -Name[pt_BR]=Filtro de projetos -Name[ru]=Фильтр проекта -Name[sk]=Filter projektu -Name[sl]=Filter projektov -Name[sv]=Projektfilter -Name[tr]=Proje Süzgeci -Name[uk]=Фільтр проекту -Name[x-test]=xxProject Filterxx -Name[zh_TW]=專案過濾器 -Comment=Configure which files and folders inside the project folder should be included or excluded. -Comment[bs]=Konfiguriši koje datoteke i koji direktoriji unutar foldera projekta trebaju biti uključeni ili isključeni. -Comment[ca]=Configura quins fitxers i carpetes dins de la carpeta del projecte s'han d'incloure o excloure. -Comment[ca@valencia]=Configura quins fitxers i carpetes dins de la carpeta del projecte s'han d'incloure o excloure. -Comment[da]=Indstil hvilke filer og mapper i projektmappen der skal medtages eller undtages. -Comment[de]=Legt fest, welche Dateien und Ordner innerhalb des Projektordners ein- oder ausgeschlossen werden sollen. -Comment[en_GB]=Configure which files and folders inside the project folder should be included or excluded. -Comment[es]=Configura qué archivos y carpetas contenidos en la carpeta del proyecto se deben incluir o excluir. -Comment[et]=Määramine, millised projektikataloogi failid ja kataloogid kaasata või välja jätta. -Comment[fi]=Valitse, mitkä projektikansiossa olevat tiedostot ja kansiot pitäisi ottaa mukaan ja mitkä pitäisi jättää pois. -Comment[fr]=Configure quels fichiers et dossier au sein du projet doivent être inclus ou exclus. -Comment[gl]=Configurar cales ficheiros e cartafoles dentro do cartafol do proxecto deben incluírse ou excluírse. -Comment[hu]=Beállítja, hogy mely fájlokat és mappákat kell belefoglalni vagy kihagyni a projektmappában. -Comment[it]=Configura quali file e cartelle nella cartella del progetto devono essere incluse o escluse. -Comment[kk]=Жоба қапшығындағы қай файлдар мен қапшықтарды есепке кіргізу қайсын кіргізбеу керек. -Comment[nb]=Bestem hvilke filer og mapper inni prosjektmappa skal tas med eller utelates. -Comment[nl]=Stel in welke bestanden en mappen in de projectmap meegenomen of uitgesloten moeten worden. -Comment[pl]=Ustaw jakie pliki i katalogi wewnątrz katalogu projektu mają być uwzględniane albo wykluczane. -Comment[pt]=Configura os ficheiros e pastas, dentro da pasta do projecto, que devem ser incluídos ou excluídos. -Comment[pt_BR]=Configura os arquivos e pastas, dentro da pasta do projeto, que devem ser incluídos ou excluídos. -Comment[ru]=Определяет, какие файлы и папки внутри папки проекта будут включены или исключены из него. -Comment[sk]=Nastaviť, ktoré súbory a priečinky v priečinku projektu majú byť zahrnuté alebo vylúčené. -Comment[sl]=Nastavi katere datoteke in mape znotraj mape projekta naj bodo vključene ali izključene. -Comment[sv]=Anpassa vilka filer och kataloger inne i projektkatalogen som ska inkluderas eller exkluderas. -Comment[tr]=Proje klasörü içerisinde hangi dosya ve klasörlerin dahil edileceğini veya dışlanacağını yapılandır. -Comment[uk]=За допомогою цього модуля можна визначити, які файли і теки у теці проекту має бути включено або виключено з його складу. -Comment[x-test]=xxConfigure which files and folders inside the project folder should be included or excluded.xx -Comment[zh_TW]=設定專案資料夾中要包含或排除哪些檔案與資料夾。 diff --git a/plugins/projectfilter/projectfilterkcm.cpp b/plugins/projectfilter/projectfilterkcm.cpp index 4457066d6d..4a602e3933 100644 --- a/plugins/projectfilter/projectfilterkcm.cpp +++ b/plugins/projectfilter/projectfilterkcm.cpp @@ -1,206 +1,216 @@ /* This file is part of KDevelop Copyright 2008 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 "projectfilterkcm.h" #include #include #include -#include -#include -#include #include #include #include #include #include #include #include #include #include "ui_projectfiltersettings.h" #include "projectfilterdebug.h" #include "filtermodel.h" #include "comboboxdelegate.h" using namespace KDevelop; -K_PLUGIN_FACTORY_WITH_JSON(ProjectFilterKCMFactory, "kcm_kdevprojectfilter.json", registerPlugin();) - -ProjectFilterKCM::ProjectFilterKCM(QWidget* parent, const QVariantList& args) - : ProjectKCModule(KAboutData::pluginData("kcm_kdevprojectfilter"), parent, args) +ProjectFilterKCM::ProjectFilterKCM(const ProjectConfigOptions& options, QWidget* parent) + : ProjectConfigPage(options, parent) , m_model(new FilterModel(this)) , m_ui(new Ui::ProjectFilterSettings) { QVBoxLayout *l = new QVBoxLayout(this); QWidget *w = new QWidget; m_ui->setupUi(w); m_ui->filters->setSelectionMode(QAbstractItemView::SingleSelection); m_ui->filters->setModel(m_model); m_ui->filters->setRootIsDecorated(false); m_ui->filters->header()->setSectionResizeMode(FilterModel::Pattern, QHeaderView::Stretch); m_ui->filters->header()->setSectionResizeMode(FilterModel::Targets, QHeaderView::ResizeToContents); m_ui->filters->header()->setSectionResizeMode(FilterModel::Inclusive, QHeaderView::ResizeToContents); m_ui->filters->setItemDelegateForColumn(FilterModel::Targets, new ComboBoxDelegate(QVector() << ComboBoxDelegate::Item(i18n("Files"), static_cast(Filter::Files)) << ComboBoxDelegate::Item(i18n("Folders"), static_cast(Filter::Folders)) << ComboBoxDelegate::Item(i18n("Files and Folders"), static_cast(Filter::Folders | Filter::Files)) , this)); m_ui->filters->setItemDelegateForColumn(FilterModel::Inclusive, new ComboBoxDelegate(QVector() << ComboBoxDelegate::Item(i18n("Exclude"), false) << ComboBoxDelegate::Item(i18n("Include"), true) , this)); m_ui->filters->installEventFilter(this); m_ui->filters->setDragEnabled(true); m_ui->filters->setDragDropMode(QAbstractItemView::InternalMove); m_ui->filters->setAutoScroll(true); l->addWidget(w); - addConfig( ProjectFilterSettings::self(), w ); - load(); + reset(); selectionChanged(); connect(m_ui->filters->selectionModel(), &QItemSelectionModel::currentChanged, this, &ProjectFilterKCM::selectionChanged); connect(this, static_cast(&ProjectFilterKCM::changed), this, &ProjectFilterKCM::selectionChanged); connect(m_model, &FilterModel::dataChanged, this, &ProjectFilterKCM::emitChanged); connect(m_model, &FilterModel::rowsInserted, this, &ProjectFilterKCM::emitChanged); connect(m_model, &FilterModel::rowsRemoved, this, &ProjectFilterKCM::emitChanged); connect(m_model, &FilterModel::modelReset, this, &ProjectFilterKCM::emitChanged); connect(m_model, &FilterModel::rowsMoved, this, &ProjectFilterKCM::emitChanged); connect(m_ui->add, &QPushButton::clicked, this, &ProjectFilterKCM::add); connect(m_ui->remove, &QPushButton::clicked, this, &ProjectFilterKCM::remove); connect(m_ui->moveUp, &QPushButton::clicked, this, &ProjectFilterKCM::moveUp); connect(m_ui->moveDown, &QPushButton::clicked, this, &ProjectFilterKCM::moveDown); } ProjectFilterKCM::~ProjectFilterKCM() { } -void ProjectFilterKCM::save() +void ProjectFilterKCM::apply() { + ProjectConfigPage::apply(); writeFilters(m_model->filters(), project()->projectConfiguration()); - - KSettings::Dispatcher::reparseConfiguration("kdevprojectfilter"); } -void ProjectFilterKCM::load() +void ProjectFilterKCM::reset() { + ProjectConfigPage::reset(); m_model->setFilters(readFilters(project()->projectConfiguration())); } void ProjectFilterKCM::defaults() { + ProjectConfigPage::defaults(); m_model->setFilters(defaultFilters()); } bool ProjectFilterKCM::eventFilter(QObject* object, QEvent* event) { Q_ASSERT(object == m_ui->filters); Q_UNUSED(object); if (event->type() == QEvent::KeyRelease) { QKeyEvent* key = static_cast(event); if (key->key() == Qt::Key_Delete && key->modifiers() == Qt::NoModifier && m_ui->filters->currentIndex().isValid()) { // workaround https://bugs.kde.org/show_bug.cgi?id=324451 // there is no other way I see to figure out whether an editor is showing... QWidget* editor = m_ui->filters->viewport()->findChild(); if (editor && editor->isVisible()) { // editor is showing return false; } remove(); return true; } } return false; } void ProjectFilterKCM::selectionChanged() { bool hasSelection = m_ui->filters->currentIndex().isValid(); int row = -1; if (hasSelection) { row = m_ui->filters->currentIndex().row(); } m_ui->remove->setEnabled(hasSelection); m_ui->moveDown->setEnabled(hasSelection && row != m_model->rowCount() - 1); m_ui->moveUp->setEnabled(hasSelection && row != 0); } void ProjectFilterKCM::add() { m_model->insertRows(m_model->rowCount(), 1); const QModelIndex index = m_model->index(m_model->rowCount() - 1, FilterModel::Pattern, QModelIndex()); m_ui->filters->setCurrentIndex(index); m_ui->filters->edit(index); } void ProjectFilterKCM::remove() { Q_ASSERT(m_ui->filters->currentIndex().isValid()); m_model->removeRows(m_ui->filters->currentIndex().row(), 1); } void ProjectFilterKCM::moveUp() { Q_ASSERT(m_ui->filters->currentIndex().isValid()); m_model->moveFilterUp(m_ui->filters->currentIndex().row()); } void ProjectFilterKCM::moveDown() { Q_ASSERT(m_ui->filters->currentIndex().isValid()); m_model->moveFilterDown(m_ui->filters->currentIndex().row()); } static void addError(const QString& message, QWidget* parent) { KMessageWidget* widget = new KMessageWidget(parent); widget->setMessageType(KMessageWidget::Error); widget->setText(message); parent->layout()->addWidget(widget); } void ProjectFilterKCM::emitChanged() { qDeleteAll(m_ui->messages->findChildren()); foreach(const Filter& filter, m_model->filters()) { const QString &pattern = filter.pattern.pattern(); if (pattern.isEmpty()) { addError(i18n("A filter with an empty pattern will match all items. Use \"*\" to make this explicit."), m_ui->messages); } else if (pattern.endsWith('/') && filter.targets == Filter::Files) { addError(i18n("A filter ending on \"/\" can never match a file."), m_ui->messages); } } - emit changed(true); + emit changed(); +} + +QString ProjectFilterKCM::fullName() const +{ + return i18n("Configure which files and folders inside the project folder should be included or excluded."); +} + +QIcon ProjectFilterKCM::icon() const +{ + return QIcon::fromTheme(QStringLiteral("view-filter")); +} + +QString ProjectFilterKCM::name() const +{ + return i18n("Project Filter"); } #include "projectfilterkcm.moc" diff --git a/plugins/projectfilter/projectfilterkcm.h b/plugins/projectfilter/projectfilterkcm.h index 01d1434695..017e9607c1 100644 --- a/plugins/projectfilter/projectfilterkcm.h +++ b/plugins/projectfilter/projectfilterkcm.h @@ -1,65 +1,70 @@ /* This file is part of KDevelop Copyright 2013 Milian Wolff Copyright 2008 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. */ #ifndef KDEVPLATFORM_PLUGIN_PROJECTFILTERKCM_H #define KDEVPLATFORM_PLUGIN_PROJECTFILTERKCM_H -#include +#include #include "projectfiltersettings.h" namespace Ui { class ProjectFilterSettings; } namespace KDevelop { class FilterModel; -class ProjectFilterKCM : public ProjectKCModule +class ProjectFilterKCM : public ProjectConfigPage { Q_OBJECT public: - ProjectFilterKCM(QWidget* parent, const QVariantList& args); + ProjectFilterKCM(const KDevelop::ProjectConfigOptions& options, QWidget* parent); virtual ~ProjectFilterKCM(); - virtual void save(); - virtual void load(); - virtual void defaults(); + virtual QString name() const; + virtual QIcon icon() const; + virtual QString fullName() const; protected: virtual bool eventFilter(QObject* object, QEvent* event); private slots: void add(); void remove(); void moveUp(); void moveDown(); void selectionChanged(); void emitChanged(); +public Q_SLOTS: + virtual void apply() override; + virtual void reset() override; + virtual void defaults() override; + private: FilterModel *m_model; QScopedPointer m_ui; }; } #endif diff --git a/plugins/projectfilter/projectfilterprovider.cpp b/plugins/projectfilter/projectfilterprovider.cpp index 102a482de5..0d39fdd7f3 100644 --- a/plugins/projectfilter/projectfilterprovider.cpp +++ b/plugins/projectfilter/projectfilterprovider.cpp @@ -1,159 +1,167 @@ /* This file is part of KDevelop Copyright 2013 Milian Wolff 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 "projectfilterprovider.h" #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include "projectfilterdebug.h" +#include "projectfilterkcm.h" #include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(ProjectFilterProviderFactory, "kdevprojectfilter.json", registerPlugin();) ProjectFilterProvider::ProjectFilterProvider( QObject* parent, const QVariantList& /*args*/ ) : IPlugin( "kdevprojectfilter", parent ) { KDEV_USE_EXTENSION_INTERFACE( IProjectFilterProvider ) connect(core()->projectController(), &IProjectController::projectClosing, this, &ProjectFilterProvider::projectClosing); connect(core()->projectController(), &IProjectController::projectAboutToBeOpened, this, &ProjectFilterProvider::projectAboutToBeOpened); updateProjectFilters(); - - KSettings::Dispatcher::registerComponent(componentName(), this, "updateProjectFilters"); } QSharedPointer ProjectFilterProvider::createFilter(IProject* project) const { return QSharedPointer(new ProjectFilter(project, m_filters[project])); } ContextMenuExtension ProjectFilterProvider::contextMenuExtension(Context* context) { ContextMenuExtension ret; if (!context->hasType(Context::ProjectItemContext)) { return ret; } ProjectItemContext* ctx = static_cast( context ); QList items = ctx->items(); // filter out project roots and items in targets QList< ProjectBaseItem* >::iterator it = items.begin(); while (it != items.end()) { if ((*it)->isProjectRoot() || !(*it)->parent()->folder()) { it = items.erase(it); } else { ++it; } } if (items.isEmpty()) { return ret; } QAction* action = new QAction(QIcon::fromTheme("view-filter"), i18np("Exclude Item From Project", "Exclude Items From Project", items.size()), this); action->setData(QVariant::fromValue(items)); connect(action, &QAction::triggered, this, &ProjectFilterProvider::addFilterFromContextMenu); ret.addAction(ContextMenuExtension::FileGroup, action); return ret; } void ProjectFilterProvider::addFilterFromContextMenu() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); QList items = action->data().value >(); QHash changedProjectFilters; foreach(ProjectBaseItem* item, items) { if (!changedProjectFilters.contains(item->project())) { changedProjectFilters[item->project()] = readFilters(item->project()->projectConfiguration()); } SerializedFilters& filters = changedProjectFilters[item->project()]; Path path; if (item->target()) { path = Path(item->parent()->path(), item->text()); } else { path = item->path(); } filters << SerializedFilter('/' + item->project()->path().relativePath(path), item->folder() ? Filter::Folders : Filter::Files); } QHash< IProject*, SerializedFilters >::const_iterator it = changedProjectFilters.constBegin(); while (it != changedProjectFilters.constEnd()) { writeFilters(it.value(), it.key()->projectConfiguration()); m_filters[it.key()] = deserialize(it.value()); emit filterChanged(this, it.key()); ++it; } KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18np("A filter for the item was added. To undo, use the project filter settings.", "A filter for the items was added. To undo, use the project filter settings.", items.size()), i18n("Project Filter Added"), "projectfilter-addfromctxmenu"); } void ProjectFilterProvider::updateProjectFilters() { foreach(IProject* project, core()->projectController()->projects()) { Filters newFilters = deserialize(readFilters(project->projectConfiguration())); Filters& filters = m_filters[project]; if (filters != newFilters) { projectFilterDebug() << "project filter changed:" << project->name(); filters = newFilters; emit filterChanged(this, project); } } } void ProjectFilterProvider::projectAboutToBeOpened(IProject* project) { m_filters[project] = deserialize(readFilters(project->projectConfiguration())); } void ProjectFilterProvider::projectClosing(IProject* project) { m_filters.remove(project); } +int ProjectFilterProvider::perProjectConfigPages() const +{ + return 1; +} + +ConfigPage* ProjectFilterProvider::perProjectConfigPage(int i, const ProjectConfigOptions& options, QWidget* parent) +{ + return i == 0 ? new ProjectFilterKCM(options, parent) : nullptr; +} + #include "projectfilterprovider.moc" diff --git a/plugins/projectfilter/projectfilterprovider.h b/plugins/projectfilter/projectfilterprovider.h index cb1fa978e0..64da5524c0 100644 --- a/plugins/projectfilter/projectfilterprovider.h +++ b/plugins/projectfilter/projectfilterprovider.h @@ -1,62 +1,65 @@ /* This file is part of KDevelop Copyright 2013 Milian Wolff 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_PLUGIN_PROJECTFILTERPROVIDER_H #define KDEVPLATFORM_PLUGIN_PROJECTFILTERPROVIDER_H #include #include #include "projectfilter.h" #include namespace KDevelop { class ProjectFilterProvider: public IPlugin, public IProjectFilterProvider { Q_OBJECT Q_INTERFACES( KDevelop::IProjectFilterProvider ) public: explicit ProjectFilterProvider( QObject* parent = 0, const QVariantList& args = QVariantList() ); virtual QSharedPointer createFilter(IProject* project) const; virtual ContextMenuExtension contextMenuExtension(Context* context); + virtual int perProjectConfigPages() const override; + virtual ConfigPage* perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) override; + signals: void filterChanged(KDevelop::IProjectFilterProvider*, KDevelop::IProject*); private slots: void updateProjectFilters(); void projectClosing(KDevelop::IProject*); void projectAboutToBeOpened(KDevelop::IProject*); void addFilterFromContextMenu(); private: QHash > m_filters; }; } #endif // KDEVPLATFORM_PLUGIN_PROJECTFILTERPROVIDER_H diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt index 948e7ffd8a..08acf318f7 100644 --- a/project/CMakeLists.txt +++ b/project/CMakeLists.txt @@ -1,70 +1,70 @@ set(KDevPlatformProject_LIB_SRCS projectutils.cpp projectmodel.cpp projectconfigskeleton.cpp importprojectjob.cpp builderjob.cpp projectbuildsetmodel.cpp projectitemlineedit.cpp helper.cpp debug.cpp projectproxymodel.cpp abstractfilemanagerplugin.cpp filemanagerlistjob.cpp projectfiltermanager.cpp interfaces/iprojectbuilder.cpp interfaces/iprojectfilemanager.cpp interfaces/ibuildsystemmanager.cpp interfaces/iprojectfilter.cpp interfaces/iprojectfilterprovider.cpp ) add_library(KDevPlatformProject ${KDevPlatformProject_LIB_SRCS}) add_library(KDev::Project ALIAS KDevPlatformProject) generate_export_header(KDevPlatformProject EXPORT_FILE_NAME projectexport.h) # TODO: helper.cpp needs vcsexport.h and outputviewexport.h, but can't link to KDev::Vcs target_include_directories(KDevPlatformProject PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../vcs/") target_include_directories(KDevPlatformProject PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../outputview/") target_link_libraries(KDevPlatformProject LINK_PUBLIC KDev::Interfaces KDev::Util # util/path.h ) target_link_libraries(KDevPlatformProject LINK_PRIVATE KDev::Interfaces KDev::Serialization KF5::KIOWidgets Qt5::Concurrent ) set_target_properties(KDevPlatformProject PROPERTIES VERSION ${KDEVPLATFORM_LIB_VERSION} SOVERSION ${KDEVPLATFORM_LIB_SOVERSION} EXPORT_NAME Project) install(TARGETS KDevPlatformProject EXPORT KDevPlatformTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) add_subdirectory(tests) ########### install files ############### install(FILES interfaces/iprojectbuilder.h interfaces/iprojectfilemanager.h interfaces/ibuildsystemmanager.h interfaces/iprojectfilter.h interfaces/iprojectfilterprovider.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/project/interfaces COMPONENT Devel ) install(FILES projectutils.h importprojectjob.h projectconfigskeleton.h projectmodel.h - projectkcmodule.h + projectconfigpage.h projectitemlineedit.h projectbuildsetmodel.h builderjob.h helper.h abstractfilemanagerplugin.h projectfiltermanager.h ${CMAKE_CURRENT_BINARY_DIR}/projectexport.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevplatform/project COMPONENT Devel ) diff --git a/project/projectconfigpage.h b/project/projectconfigpage.h new file mode 100644 index 0000000000..2d5e2fa6cc --- /dev/null +++ b/project/projectconfigpage.h @@ -0,0 +1,85 @@ +/* KDevelop + * + * Copyright 2007 Andreas Pakulat + * + * 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) 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 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_PROJECTCONFIGPAGE_H +#define KDEVPLATFORM_PROJECTCONFIGPAGE_H + + +#include +#include +#include + +#include + +#include "projectconfigskeleton.h" + +class KComponentData; +class QWidget; +class QStringList; + +namespace KDevelop { + +/** This is needed because IProject does not expose these methods */ +struct ProjectConfigOptions { + QString developerTempFile; + Path developerFile; + QString projectTempFile; + Path projectFile; + QString projectName; +}; + +} + +/** + * @tparam T a class derived from KDevelop::ProjectConfigSkeleton. + */ +template +class ProjectConfigPage : public KDevelop::ConfigPage +{ + static_assert(std::is_base_of::value, "T must inherit from KDevelop::ProjectConfigSkeleton"); +public: + ProjectConfigPage(const KDevelop::ProjectConfigOptions& options, QWidget* parent) + : KDevelop::ConfigPage(initConfigSkeleton(options), parent), projectName(options.projectName) + { + KDevelop::ProjectConfigSkeleton* conf = T::self(); + conf->setDeveloperTempFile(options.developerTempFile); + conf->setDeveloperFile(options.developerFile); + conf->setProjectTempFile(options.projectTempFile); + conf->setProjectFile(options.projectFile); + } + + virtual ~ProjectConfigPage() {} + + KDevelop::IProject* project() const + { + return KDevelop::ICore::self()->projectController()->findProjectByName( projectName ); + } +private: + static inline KDevelop::ProjectConfigSkeleton* initConfigSkeleton(const KDevelop::ProjectConfigOptions& options) + { + T::instance(options.developerTempFile); + return T::self(); + } +private: + QString projectName; +}; + +#endif + diff --git a/project/projectkcmodule.h b/project/projectkcmodule.h deleted file mode 100644 index 8c413bbe18..0000000000 --- a/project/projectkcmodule.h +++ /dev/null @@ -1,59 +0,0 @@ -/* KDevelop - * - * Copyright 2007 Andreas Pakulat - * - * 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) 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 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_PROJECTKCMODULE_H -#define KDEVPLATFORM_PROJECTKCMODULE_H - -#include - -#include - -#include -#include - -#include - -class QWidget; -class QStringList; - -template class ProjectKCModule : public KCModule -{ - public: - ProjectKCModule( KAboutData* data, QWidget* parent, const QVariantList& args = QVariantList() ) - : KCModule( data, parent, args ) - { - Q_ASSERT( args.count() > 3 ); - T::instance( args.at(0).toString() ); - T::self()->setDeveloperTempFile( args.at(0).toString() ); - T::self()->setProjectTempFile( args.at(1).toString() ); - T::self()->setProjectFile( KDevelop::Path(args.at(2).toString()) ); - T::self()->setDeveloperFile( KDevelop::Path(args.at(3).toString()) ); - projectName = args.at(4).toString(); - } - virtual ~ProjectKCModule() {} - KDevelop::IProject* project() const { - return KDevelop::ICore::self()->projectController()->findProjectByName( projectName ); - } -private: - QString projectName; -}; - -#endif - diff --git a/shell/projectcontroller.cpp b/shell/projectcontroller.cpp index 14da967059..8ff625bb7a 100644 --- a/shell/projectcontroller.cpp +++ b/shell/projectcontroller.cpp @@ -1,1186 +1,1209 @@ /* 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 "core.h" #include "project.h" #include "mainwindow.h" #include "shellextension.h" #include "plugincontroller.h" #include "uicontroller.h" #include "documentcontroller.h" #include "openprojectdialog.h" #include #include #include #include #include "sessioncontroller.h" #include "session.h" #include "debug.h" #include #include #include #include #include #include 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; //@FIXME: Currently it is important to set a parentApp on the kcms //that's different from the component name of the application, else //the plugin will show up on all projects settings dialogs. - QStringList pluginsForPrj = findPluginsForProject( proj ); - qCDebug(SHELL) << "Using pluginlist:" << pluginsForPrj; - pluginsForPrj << "kdevplatformproject"; // for project-wide env settings. - KSettings::Dialog cfgDlg( pluginsForPrj, m_core->uiController()->activeMainWindow() ); - cfgDlg.setKCMArguments( QStringList() - << proj->developerTempFile() - << proj->projectTempFile() - << proj->projectFile().pathOrUrl() - << proj->developerFile().pathOrUrl() - << proj->name() ); - m_configuringProject = proj; - cfgDlg.setWindowTitle( i18n("Configure Project %1", proj->name()) ); +// QStringList pluginsForPrj = findPluginsForProject( proj ); +// kDebug() << "Using pluginlist:" << pluginsForPrj; +// pluginsForPrj << "kdevplatformproject"; // for project-wide env settings. +// KSettings::Dialog cfgDlg( pluginsForPrj, m_core->uiController()->activeMainWindow() ); +// cfgDlg.setKCMArguments( QStringList() +// << proj->developerTempFile() +// << proj->projectTempFile() +// << proj->projectFile().pathOrUrl() +// << proj->developerFile().pathOrUrl() +// << proj->name() ); +// m_configuringProject = proj; +// cfgDlg.setWindowTitle( i18n("Configure Project %1", proj->name()) ); +// cfgDlg.exec(); +// proj->projectConfiguration()->sync(); +// m_configuringProject = 0; + QList configPages; + auto mainWindow = m_core->uiController()->activeMainWindow(); + + ProjectConfigOptions options; + options.developerFile = proj->developerFile(); + options.developerTempFile = proj->developerTempFile(); + options.projectFile = proj->projectFile(); + options.projectTempFile = proj->projectTempFile(); + options.projectName = proj->name(); + + for (IPlugin* plugin : findPluginsForProject(proj)) { + for (int i = 0; i < plugin->perProjectConfigPages(); ++i) { + configPages.append(plugin->perProjectConfigPage(i, options, mainWindow)); + } + } + + KDevelop::ConfigDialog cfgDlg(configPages, mainWindow); + cfgDlg.setWindowTitle(i18n("Configure Project %1", proj->name())); cfgDlg.exec(); proj->projectConfiguration()->sync(); - m_configuringProject = 0; } 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 ); } } - QStringList findPluginsForProject( IProject* project ) + QList findPluginsForProject( IProject* project ) { QList plugins = m_core->pluginController()->loadedPlugins(); - QStringList pluginnames; + QList projectPlugins; QList< IProjectBuilder* > 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 ); } for( QList::iterator it = plugins.begin(); it != plugins.end(); ++it ) { IPlugin* plugin = *it; const KPluginInfo info = m_core->pluginController()->pluginInfo( plugin ); if (info.property("X-KDevelop-Category").toString() != "Project") { continue; } 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; } - pluginnames << info.pluginName(); + qCDebug(SHELL) << "Using plugin" << info.pluginName() << "for project" << project->name(); + projectPlugins << plugin; } - return pluginnames; + return projectPlugins; } void notifyProjectConfigurationChanged() { if( m_configuringProject ) { emit q->projectConfigurationChanged( m_configuringProject ); } } 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() ) { QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath(); if ( !path.isEmpty() ) url.setPath( 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->projectFileUrl() ) { 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("commit_current_project")->setEnabled(area->objectName() == "code"); ac->action("commit_current_project")->setVisible(area->objectName() == "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 ) ) { setObjectName("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( "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("project-open")); connect(action, &QAction::triggered, this, [&] { openProject(); }); d->m_fetchProject = action = ac->addAction( "project_fetch" ); action->setText(i18nc( "@action", "Fetch Project..." ) ); action->setIcon( QIcon::fromTheme( "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( "project_close" ); connect( action, &QAction::triggered, this, [&] { d->closeSelectedProjects(); } ); action->setText( i18nc( "@action", "Close Project(s)" ) ); action->setIcon( QIcon::fromTheme( "project-development-close" ) ); action->setToolTip( i18nc( "@info:tooltip", "Closes all currently selected projects" ) ); action->setEnabled( false ); d->m_openConfig = action = ac->addAction( "project_open_config" ); connect( action, &QAction::triggered, this, [&] { d->openProjectConfig(); } ); action->setText( i18n( "Open Configuration..." ) ); action->setIcon( QIcon::fromTheme("configure") ); action->setEnabled( false ); action = ac->addAction( "commit_current_project" ); connect( action, &QAction::triggered, this, &ProjectController::commitCurrentProject ); action->setText( i18n( "Commit Current Project..." ) ); action->setIconText( i18n( "Commit..." ) ); action->setIcon( QIcon::fromTheme("svn-commit") ); connect(d->m_core->uiControllerInternal()->defaultMainWindow(), &MainWindow::areaChanged, this, [&] (Sublime::Area* area) { d->areaChanged(area); }); d->m_core->uiControllerInternal()->area(0, "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( "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("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 ); } } 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); KSettings::Dispatcher::registerComponent( QStringLiteral("kdevplatformproject"), this, "notifyProjectConfigurationChanged" ); QDBusConnection::sessionBus().registerObject( "/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(".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, SIGNAL(accepted()), &dialog, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(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->projectFileUrl() ); KSharedConfig * config = KSharedConfig::openConfig().data(); KConfigGroup recentGroup = config->group("RecentProjects"); d->m_recentAction->saveEntries( recentGroup ); config->sync(); } Q_ASSERT(d->m_currentlyOpening.contains(project->projectFileUrl())); d->m_currentlyOpening.removeAll(project->projectFileUrl()); 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 ) { KPluginInfo _plugInfo = Core::self()->pluginController()->pluginInfo( _plugin ); if( _plugInfo.isValid() ) { QString _plugName = _plugInfo.pluginName(); 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_) { if (!proj_) { return; } // loading might have failed d->m_currentlyOpening.removeAll(proj_->projectFileUrl()); Project* proj = dynamic_cast( proj_ ); if( !proj ) { qWarning() << "Unknown Project subclass found!"; return; } 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::abortOpeningProject(IProject* proj) { d->m_currentlyOpening.removeAll(proj->projectFileUrl()); 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 ); } 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->folder().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("./")) { 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"