diff --git a/kdevplatform/shell/mainwindow.cpp b/kdevplatform/shell/mainwindow.cpp index 5f0640caf0..6da872723c 100644 --- a/kdevplatform/shell/mainwindow.cpp +++ b/kdevplatform/shell/mainwindow.cpp @@ -1,574 +1,574 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow.h" #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shellextension.h" #include "partcontroller.h" #include "plugincontroller.h" #include "projectcontroller.h" #include "uicontroller.h" #include "documentcontroller.h" #include "workingsetcontroller.h" #include "sessioncontroller.h" #include "sourceformattercontroller.h" #include "areadisplay.h" #include "project.h" #include "debug.h" #include "uiconfig.h" #include "ktexteditorpluginintegration.h" #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QColor defaultColor(const QPalette& palette) { return palette.windowText().color(); } QColor colorForDocument(const QUrl& url, const QPalette& palette, const QColor& defaultColor) { auto project = Core::self()->projectController()->findProjectForUrl(url); if (!project) return defaultColor; return WidgetColorizer::colorForId(qHash(project->path()), palette); } } void MainWindow::applyMainWindowSettings(const KConfigGroup& config) { Q_D(MainWindow); if(!d->changingActiveView()) KXmlGuiWindow::applyMainWindowSettings(config); } void MainWindow::createGUI(KParts::Part* part) { //TODO remove if-clause once KF5 >= 5.24 is required #if KPARTS_VERSION_MINOR >= 24 Sublime::MainWindow::setWindowTitleHandling(false); Sublime::MainWindow::createGUI(part); #else Sublime::MainWindow::createGUI(part); if (part) { // Don't let the Part control the main window caption -- we take care of that disconnect(part, SIGNAL(setWindowCaption(QString)), this, SLOT(setCaption(QString))); } #endif } void MainWindow::initializeCorners() { const KConfigGroup cg = KSharedConfig::openConfig()->group( "UiSettings" ); const int bottomleft = cg.readEntry( "BottomLeftCornerOwner", 0 ); const int bottomright = cg.readEntry( "BottomRightCornerOwner", 0 ); qCDebug(SHELL) << "Bottom Left:" << bottomleft; qCDebug(SHELL) << "Bottom Right:" << bottomright; // 0 means vertical dock (left, right), 1 means horizontal dock( top, bottom ) if( bottomleft == 0 ) setCorner( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea ); else if( bottomleft == 1 ) setCorner( Qt::BottomLeftCorner, Qt::BottomDockWidgetArea ); if( bottomright == 0 ) setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea ); else if( bottomright == 1 ) setCorner( Qt::BottomRightCorner, Qt::BottomDockWidgetArea ); } MainWindow::MainWindow( Sublime::Controller *parent, Qt::WindowFlags flags ) : Sublime::MainWindow( parent, flags ) { QDBusConnection::sessionBus().registerObject( QStringLiteral("/kdevelop/MainWindow"), this, QDBusConnection::ExportScriptableSlots ); setAcceptDrops( true ); initializeCorners(); setObjectName( QStringLiteral("MainWindow") ); d_ptr = new MainWindowPrivate(this); Q_D(MainWindow); setStandardToolBarMenuEnabled( true ); d->setupActions(); if( !ShellExtension::getInstance()->xmlFile().isEmpty() ) { setXMLFile( ShellExtension::getInstance() ->xmlFile() ); } menuBar()->setCornerWidget(new AreaDisplay(this), Qt::TopRightCorner); } MainWindow::~ MainWindow() { if (memberList().count() == 1) { // We're closing down... Core::self()->shutdown(); } delete d_ptr; } KTextEditorIntegration::MainWindow *MainWindow::kateWrapper() const { Q_D(const MainWindow); return d->kateWrapper(); } void MainWindow::split(Qt::Orientation orientation) { Q_D(MainWindow); d->split(orientation); } void MainWindow::ensureVisible() { if (isMinimized()) { if (isMaximized()) { showMaximized(); } else { showNormal(); } } KWindowSystem::forceActiveWindow(winId()); } QAction* MainWindow::createCustomElement(QWidget* parent, int index, const QDomElement& element) { QAction* before = nullptr; if (index > 0 && index < parent->actions().count()) before = parent->actions().at(index); //KDevelop needs to ensure that separators defined as //are always shown in the menubar. For those, we create special disabled actions //instead of calling QMenuBar::addSeparator() because menubar separators are ignored if (element.tagName().toLower() == QLatin1String("separator") && element.attribute(QStringLiteral("style")) == QLatin1String("visible")) { if ( auto* bar = qobject_cast( parent ) ) { QAction *separatorAction = new QAction(QStringLiteral("|"), this); bar->insertAction( before, separatorAction ); separatorAction->setDisabled(true); return separatorAction; } } return KXMLGUIBuilder::createCustomElement(parent, index, element); } bool KDevelop::MainWindow::event( QEvent* ev ) { if ( ev->type() == QEvent::PaletteChange ) updateAllTabColors(); return Sublime::MainWindow::event(ev); } void MainWindow::dragEnterEvent( QDragEnterEvent* ev ) { const QMimeData* mimeData = ev->mimeData(); if (mimeData->hasUrls()) { ev->acceptProposedAction(); } else if (mimeData->hasText()) { // also take text which contains a URL const QUrl url = QUrl::fromUserInput(mimeData->text()); if (url.isValid()) { ev->acceptProposedAction(); } } } void MainWindow::dropEvent( QDropEvent* ev ) { Sublime::View* dropToView = viewForPosition(mapToGlobal(ev->pos())); if(dropToView) activateView(dropToView); QList urls; const QMimeData* mimeData = ev->mimeData(); if (mimeData->hasUrls()) { urls = mimeData->urls(); } else if (mimeData->hasText()) { const QUrl url = QUrl::fromUserInput(mimeData->text()); if (url.isValid()) { urls << url; } } bool eventUsed = false; if (urls.size() == 1) { - eventUsed = Core::self()->projectControllerInternal()->fetchProjectFromUrl(urls.at(0)); + eventUsed = Core::self()->projectControllerInternal()->fetchProjectFromUrl(urls.at(0), ProjectController::NoFetchFlags); } if (!eventUsed) { for(const auto& url : qAsConst(urls)) { Core::self()->documentController()->openDocument(url); } } ev->acceptProposedAction(); } void MainWindow::loadSettings() { qCDebug(SHELL) << "Loading Settings"; initializeCorners(); updateAllTabColors(); Sublime::MainWindow::loadSettings(); } void MainWindow::configureShortcuts() { ///Workaround for a problem with the actions: Always start the shortcut-configuration in the first mainwindow, then propagate the updated ///settings into the other windows // We need to bring up the shortcut dialog ourself instead of // Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->configureShortcuts(); // so we can connect to the saved() signal to propagate changes in the editor shortcuts KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); const auto firstMainWindowClientsBefore = Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients(); for (KXMLGUIClient* client : firstMainWindowClientsBefore) { if(client && !client->xmlFile().isEmpty()) dlg.addCollection( client->actionCollection() ); } connect(&dlg, &KShortcutsDialog::saved, this, &MainWindow::shortcutsChanged); dlg.configure(true); QMap shortcuts; // querying again just in case something changed behind our back const auto firstMainWindowClientsAfter = Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients(); for (KXMLGUIClient* client : firstMainWindowClientsAfter) { const auto actions = client->actionCollection()->actions(); for (QAction* action : actions) { if(!action->objectName().isEmpty()) { shortcuts[action->objectName()] = action->shortcut(); } } } for(int a = 1; a < Core::self()->uiControllerInternal()->mainWindows().size(); ++a) { const auto clients = Core::self()->uiControllerInternal()->mainWindows()[a]->guiFactory()->clients(); for (KXMLGUIClient* client : clients) { const auto actions = client->actionCollection()->actions(); for (QAction* action : actions) { qCDebug(SHELL) << "transferring setting shortcut for" << action->objectName(); const auto shortcutIt = shortcuts.constFind(action->objectName()); if (shortcutIt != shortcuts.constEnd()) { action->setShortcut(*shortcutIt); } } } } } void MainWindow::shortcutsChanged() { KTextEditor::View *activeClient = Core::self()->documentController()->activeTextDocumentView(); if(!activeClient) return; const auto documents = Core::self()->documentController()->openDocuments(); for (IDocument* doc : documents) { KTextEditor::Document *textDocument = doc->textDocument(); if (textDocument) { const auto views = textDocument->views(); for (KTextEditor::View* client : views) { if (client != activeClient) { client->reloadXML(); } } } } } void MainWindow::initialize() { Q_D(MainWindow); KStandardAction::keyBindings(this, SLOT(configureShortcuts()), actionCollection()); setupGUI( KXmlGuiWindow::ToolBar | KXmlGuiWindow::Create | KXmlGuiWindow::Save ); Core::self()->partController()->addManagedTopLevelWidget(this); qCDebug(SHELL) << "Adding plugin-added connection"; connect( Core::self()->pluginController(), &IPluginController::pluginLoaded, d, &MainWindowPrivate::addPlugin); connect( Core::self()->pluginController(), &IPluginController::pluginUnloaded, d, &MainWindowPrivate::removePlugin); connect( Core::self()->partController(), &IPartController::activePartChanged, d, &MainWindowPrivate::activePartChanged); connect( this, &MainWindow::activeViewChanged, d, &MainWindowPrivate::changeActiveView); connect(Core::self()->sourceFormatterControllerInternal(), &SourceFormatterController::hasFormattersChanged, d, &MainWindowPrivate::updateSourceFormatterGuiClient); const auto plugins = Core::self()->pluginController()->loadedPlugins(); for (IPlugin* plugin : plugins) { d->addPlugin(plugin); } guiFactory()->addClient(Core::self()->sessionController()); if (Core::self()->sourceFormatterControllerInternal()->hasFormatters()) { guiFactory()->addClient(Core::self()->sourceFormatterControllerInternal()); } // Needed to re-plug the actions from the sessioncontroller as xmlguiclients don't // seem to remember which actions where plugged in. Core::self()->sessionController()->updateXmlGuiActionList(); d->setupGui(); qRegisterMetaType>(); //Queued so we process it with some delay, to make sure the rest of the UI has already adapted connect(Core::self()->documentController(), &IDocumentController::documentActivated, // Use a queued connection, because otherwise the view is not yet fully set up // but wrap the document in a smart pointer to guard against crashes when it // gets deleted in the meantime this, [this](IDocument *doc) { const auto textDocument = QPointer(doc->textDocument()); QMetaObject::invokeMethod(this, "documentActivated", Qt::QueuedConnection, Q_ARG(QPointer, textDocument)); }); connect(Core::self()->documentController(), &IDocumentController::documentClosed, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->sessionController()->activeSession(), &ISession::sessionUpdated, this, &MainWindow::updateCaption); connect(Core::self()->documentController(), &IDocumentController::documentOpened, this, &MainWindow::updateTabColor); connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateTabColor); connect(this, &Sublime::MainWindow::viewAdded, this, &MainWindow::updateAllTabColors); connect(Core::self()->projectController(), &ProjectController::projectOpened, this, &MainWindow::updateAllTabColors, Qt::QueuedConnection); updateCaption(); } void MainWindow::cleanup() { } void MainWindow::setVisible( bool visible ) { KXmlGuiWindow::setVisible( visible ); emit finishedLoading(); } bool MainWindow::queryClose() { if (!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(this, IDocument::Default)) return false; return Sublime::MainWindow::queryClose(); } void MainWindow::documentActivated(const QPointer& textDocument) { Q_D(MainWindow); updateCaption(); // update active document connection disconnect(d->activeDocumentReadWriteConnection); if (textDocument) { d->activeDocumentReadWriteConnection = connect(textDocument, &KTextEditor::Document::readWriteChanged, this, &MainWindow::updateCaption); } } void MainWindow::updateCaption() { const auto activeSession = Core::self()->sessionController()->activeSession(); QString title = activeSession ? activeSession->description() : QString(); QString localFilePath; if(area()->activeView()) { if(!title.isEmpty()) title += QLatin1String(" - [ "); Sublime::Document* doc = area()->activeView()->document(); auto* urlDoc = qobject_cast(doc); if(urlDoc) { if (urlDoc->url().isLocalFile()) { localFilePath = urlDoc->url().toLocalFile(); } title += Core::self()->projectController()->prettyFileName(urlDoc->url(), KDevelop::IProjectController::FormatPlain); } else title += doc->title(); auto activeDocument = Core::self()->documentController()->activeDocument(); if (activeDocument && activeDocument->textDocument() && !activeDocument->textDocument()->isReadWrite()) title += i18n(" (read only)"); title += QLatin1String(" ]"); } setWindowFilePath(localFilePath); setCaption(title); } void MainWindow::updateAllTabColors() { auto documentController = Core::self()->documentController(); if (!documentController) return; const auto defaultColor = ::defaultColor(palette()); if (UiConfig::colorizeByProject()) { QHash viewColors; const auto containers = this->containers(); for (auto* container : containers) { const auto views = container->views(); viewColors.reserve(views.size()); viewColors.clear(); for (auto view : views) { const auto urlDoc = qobject_cast(view->document()); if (urlDoc) { viewColors[view] = colorForDocument(urlDoc->url(), palette(), defaultColor); } } container->setTabColors(viewColors); } } else { const auto containers = this->containers(); for (auto* container : containers) { container->resetTabColors(defaultColor); } } } void MainWindow::updateTabColor(IDocument* doc) { if (!UiConfig::self()->colorizeByProject()) return; const auto color = colorForDocument(doc->url(), palette(), defaultColor(palette())); const auto containers = this->containers(); for (auto* container : containers) { const auto views = container->views(); for (auto* view : views) { const auto urlDoc = qobject_cast(view->document()); if (urlDoc && urlDoc->url() == doc->url()) { container->setTabColor(view, color); } } } } void MainWindow::registerStatus(QObject* status) { Q_D(MainWindow); d->registerStatus(status); } void MainWindow::initializeStatusBar() { Q_D(MainWindow); d->setupStatusBar(); } void MainWindow::showErrorMessage(const QString& message, int timeout) { Q_D(MainWindow); d->showErrorMessage(message, timeout); } void MainWindow::tabContextMenuRequested(Sublime::View* view, QMenu* menu) { Q_D(MainWindow); Sublime::MainWindow::tabContextMenuRequested(view, menu); d->tabContextMenuRequested(view, menu); } void MainWindow::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) { Q_D(MainWindow); d->tabToolTipRequested(view, container, tab); } void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position) { Q_D(MainWindow); d->dockBarContextMenuRequested(area, position); } void MainWindow::newTabRequested() { Q_D(MainWindow); Sublime::MainWindow::newTabRequested(); d->fileNew(); } diff --git a/kdevplatform/shell/projectcontroller.cpp b/kdevplatform/shell/projectcontroller.cpp index 3db4373c24..47f9d19367 100644 --- a/kdevplatform/shell/projectcontroller.cpp +++ b/kdevplatform/shell/projectcontroller.cpp @@ -1,1365 +1,1367 @@ /* 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 #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_recentProjectsAction; Core* const m_core; // IProject* m_currentProject; ProjectModel* const model; QPointer m_openProject; QPointer m_fetchProject; QPointer m_closeProject; QPointer m_openConfig; IProjectDialogProvider* dialog; QList m_currentlyOpening; // project-file urls that are being opened ProjectController* const 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 ProjectChangesModel* m_changesModel = nullptr; QHash< IProject*, QPointer > m_parseJobs; // parse jobs that add files from the project to the background parser. ProjectControllerPrivate(Core* core, ProjectController* p) : m_core(core) , model(new ProjectModel()) , dialog(nullptr) , q(p) , buildset(nullptr) , m_foundProjectFile(false) , m_cleaningUp(false) { } void unloadAllProjectPlugins() { if( m_projects.isEmpty() ) m_core->pluginControllerInternal()->unloadProjectPlugins(); } void projectConfig( QObject * obj ) { if( !obj ) return; auto* proj = qobject_cast(obj); if( !proj ) return; auto cfgDlg = new KDevelop::ConfigDialog(m_core->uiController()->activeMainWindow()); cfgDlg->setAttribute(Qt::WA_DeleteOnClose); cfgDlg->setModal(true); QVector configPages; ProjectConfigOptions options; options.developerFile = proj->developerFile(); options.developerTempFile = proj->developerTempFile(); options.projectTempFile = proj->projectTempFile(); options.project = proj; const auto plugins = findPluginsForProject(proj); for (IPlugin* plugin : plugins) { const int perProjectConfigPagesCount = plugin->perProjectConfigPages(); configPages.reserve(configPages.size() + perProjectConfigPagesCount); for (int i = 0; i < perProjectConfigPagesCount; ++i) { configPages.append(plugin->perProjectConfigPage(i, options, cfgDlg)); } } std::sort(configPages.begin(), configPages.end(), [](const ConfigPage* a, const ConfigPage* b) { return a->name() < b->name(); }); for (auto page : configPages) { cfgDlg->appendConfigPage(page); } QObject::connect(cfgDlg, &ConfigDialog::configSaved, cfgDlg, [this, proj](ConfigPage* page) { Q_UNUSED(page) Q_ASSERT_X(proj, Q_FUNC_INFO, "ConfigDialog signalled project config change, but no project set for configuring!"); emit q->projectConfigurationChanged(proj); }); cfgDlg->setWindowTitle(i18n("Configure Project %1", proj->name())); QObject::connect(cfgDlg, &KDevelop::ConfigDialog::finished, proj, [proj]() { proj->projectConfiguration()->sync(); }); cfgDlg->show(); } void saveListOfOpenedProjects() { auto activeSession = Core::self()->activeSession(); if (!activeSession) { return; } QList openProjects; openProjects.reserve( m_projects.size() ); for (IProject* project : qAsConst(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 ) { const QList auxBuilders = topBuilder->additionalBuilderPlugins(project); destination.append( auxBuilders ); for (IProjectBuilder* auxBuilder : auxBuilders ) { collectBuilders( destination, auxBuilder, project ); } } QVector findPluginsForProject( IProject* project ) const { const QList plugins = m_core->pluginController()->loadedPlugins(); const IBuildSystemManager* const buildSystemManager = project->buildSystemManager(); 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 (buildSystemManager) { buildersForKcm << buildSystemManager->builder(); collectBuilders( buildersForKcm, buildSystemManager->builder(), project ); } for (auto plugin : plugins) { auto info = m_core->pluginController()->pluginInfo(plugin); auto* manager = plugin->extension(); if( manager && manager != project->projectFileManager() ) { // current plugin is a manager but does not apply to given project, skip continue; } auto* builder = plugin->extension(); if ( builder && !buildersForKcm.contains( builder ) ) { continue; } // Do not show config pages for analyzer tools which need a buildSystemManager // TODO: turn into generic feature to disable plugin config pages which do not apply for a project if (!buildSystemManager) { const auto required = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-IRequired")); if (required.contains(QLatin1String("org.kdevelop.IBuildSystemManager"))) { continue; } } qCDebug(SHELL) << "Using plugin" << info.pluginId() << "for project" << project->name(); projectPlugins << plugin; } return projectPlugins; } void updateActionStates() { // if only one project loaded, this is always our target int itemCount = (m_projects.size() == 1) ? 1 : 0; if (itemCount == 0) { // otherwise base on selection auto* itemContext = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (itemContext) { itemCount = itemContext->items().count(); } } m_openConfig->setEnabled(itemCount == 1); m_closeProject->setEnabled(itemCount > 0); } QSet selectedProjects() { QSet projects; // if only one project loaded, this is our target if (m_projects.count() == 1) { projects.insert(m_projects.at(0)); } else { // otherwise base on selection auto* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (ctx) { const auto items = ctx->items(); for (ProjectBaseItem* item : items) { projects.insert(item->project()); } } } return projects; } void openProjectConfig() { auto projects = selectedProjects(); if (projects.count() == 1) { q->configureProject(*projects.constBegin()); } } void closeSelectedProjects() { const auto projects = selectedProjects(); for (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; } const auto projects = m_projects; for (IProject* project : 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(); auto* 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* p) : d(p) {} ProjectDialogProvider::~ProjectDialogProvider() {} bool writeNewProjectFile( const QString& localConfigFile, const QString& name, const QString& createdFrom, 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( "CreatedFrom", createdFrom ); grp.writeEntry( "Manager", manager ); cfg->sync(); return true; } bool writeProjectSettingsToConfigFile(const QUrl& projectFileUrl, OpenProjectDialog* dlg) { if ( !projectFileUrl.isLocalFile() ) { QTemporaryFile tmp; if ( !tmp.open() ) { return false; } if ( !writeNewProjectFile( tmp.fileName(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->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(); } // Here and above we take .filename() part of the selectedUrl() to make it relative to the project root, // thus keeping .kdev file relocatable return writeNewProjectFile( projectFileUrl.toLocalFile(), dlg->projectName(), dlg->selectedUrl().fileName(), dlg->projectManager() ); } bool projectFileExists( const QUrl& u ) { if( u.isLocalFile() ) { return QFileInfo::exists( u.toLocalFile() ); } else { auto statJob = KIO::stat(u, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); 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, const QUrl& repoUrl, IPlugin* vcsOrProviderPlugin) { Q_ASSERT(d); ScopedDialog dlg(fetch, startUrl, repoUrl, vcsOrProviderPlugin, Core::self()->uiController()->activeMainWindow()); if(dlg->exec() == QDialog::Rejected) { return QUrl(); } QUrl projectFileUrl = dlg->projectFileUrl(); qCDebug(SHELL) << "selected project:" << projectFileUrl << dlg->projectName() << dlg->projectManager(); if ( dlg->projectManager() == QLatin1String("") ) { return projectFileUrl; } // controls if existing project file should be saved bool writeProjectConfigToFile = true; if( projectFileExists( projectFileUrl ) ) { // check whether config is equal bool shouldAsk = true; if( projectFileUrl == dlg->selectedUrl() ) { 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) { Path projectConfigDir(projectFileUrl); projectConfigDir.setLastPathSegment(QStringLiteral(".kdev4")); auto delJob = KIO::del(projectConfigDir.toUrl()); delJob->exec(); if (!writeProjectSettingsToConfigFile(projectFileUrl, dlg)) { 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_D(ProjectController); Q_ASSERT(d->dialog); delete d->dialog; d->dialog = dialog; } ProjectController::ProjectController( Core* core ) : IProjectController(core) , d_ptr(new ProjectControllerPrivate(core, this)) { qRegisterMetaType>(); setObjectName(QStringLiteral("ProjectController")); //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() { Q_D(ProjectController); 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 one 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, [this] { Q_D(ProjectController); 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, [this] { Q_D(ProjectController); 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, [this] (Sublime::Area* area) { Q_D(ProjectController); 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_recentProjectsAction = KStandardAction::openRecent(this, SLOT(openProject(QUrl)), this); ac->addAction( QStringLiteral("project_open_recent"), d->m_recentProjectsAction ); d->m_recentProjectsAction->setText( i18n( "Open Recent Project" ) ); d->m_recentProjectsAction->setWhatsThis( i18nc( "@info:whatsthis", "Opens recently opened project." ) ); d->m_recentProjectsAction->loadEntries( KConfigGroup(config, "RecentProjects") ); auto* openProjectForFileAction = new QAction( this ); ac->addAction(QStringLiteral("project_open_for_file"), openProjectForFileAction); openProjectForFileAction->setText(i18n("Open Project for Current File")); openProjectForFileAction->setIcon(QIcon::fromTheme(QStringLiteral("project-open"))); connect( openProjectForFileAction, &QAction::triggered, this, &ProjectController::openProjectForUrlSlot); } ProjectController::~ProjectController() { Q_D(ProjectController); delete d->model; delete d->dialog; } void ProjectController::cleanup() { Q_D(ProjectController); if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } saveRecentProjectsActionEntries(); d->m_cleaningUp = true; if( buildSetModel() ) { buildSetModel()->storeToSession( Core::self()->activeSession() ); } closeAllProjects(); } void ProjectController::saveRecentProjectsActionEntries() { Q_D(ProjectController); if (!d->m_recentProjectsAction) return; auto config = KSharedConfig::openConfig(); KConfigGroup recentGroup = config->group("RecentProjects"); d->m_recentProjectsAction->saveEntries( recentGroup ); config->sync(); } void ProjectController::initialize() { Q_D(ProjectController); 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->m_changesModel = new ProjectChangesModel(this); 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" ); const auto projects = group.readEntry( "Open Projects", QList() ); connect( Core::self()->selectionController(), &ISelectionController::selectionChanged, this, [this]() { Q_D(ProjectController); d->updateActionStates(); } ); connect(this, &ProjectController::projectOpened, this, [this]() { Q_D(ProjectController); d->updateActionStates(); }); connect(this, &ProjectController::projectClosing, this, [this]() { Q_D(ProjectController); d->updateActionStates(); }); QTimer::singleShot(0, this, [this, projects](){ openProjects(projects); emit initialized(); }); } void ProjectController::openProjects(const QList& projects) { for (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 { Q_D(const ProjectController); return d->m_projects.count(); } IProject* ProjectController::projectAt( int num ) const { Q_D(const ProjectController); if( !d->m_projects.isEmpty() && num >= 0 && num < d->m_projects.count() ) return d->m_projects.at( num ); return nullptr; } QList ProjectController::projects() const { Q_D(const ProjectController); return d->m_projects; } void ProjectController::eventuallyOpenProjectFile(KIO::Job* _job, const KIO::UDSEntryList& entries) { Q_D(ProjectController); auto* job = qobject_cast(_job); Q_ASSERT(job); for (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_D(ProjectController); Q_ASSERT(!sourceUrl.isRelative()); QUrl dirUrl = sourceUrl; if (sourceUrl.isLocalFile() && !QFileInfo(sourceUrl.toLocalFile()).isDir()) { dirUrl = dirUrl.adjusted(QUrl::RemoveFilename); } QUrl testAt = dirUrl; d->m_foundProjectFile = false; while(!testAt.path().isEmpty()) { 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 ) { Q_D(ProjectController); 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)) { const auto sessions = Core::self()->sessionController()->sessions(); for (const Session* session : 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 const auto windows = Core::self()->uiController()->controller()->mainWindows(); for (Sublime::MainWindow* window : windows) { window->close(); } } #endif } } } if ( ! existingSessions.isEmpty() ) { ScopedDialog 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); for (const Session* session : qAsConst(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.data(), &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject); mainLayout->addWidget(buttonBox); if (!dialog->exec()) return; for (const QObject* obj : sessions.children()) { if ( const auto* 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); } } -bool ProjectController::fetchProjectFromUrl(const QUrl& repoUrl) +bool ProjectController::fetchProjectFromUrl(const QUrl& repoUrl, FetchFlags fetchFlags) { Q_D(ProjectController); IPlugin* vcsOrProviderPlugin = nullptr; // TODO: query also projectprovider plugins, and that before plain vcs plugins // e.g. KDE provider plugin could catch URLs from mirror or pickup kde:repo things auto* pluginController = d->m_core->pluginController(); const auto& vcsPlugins = pluginController->allPluginsForExtension(QStringLiteral("org.kdevelop.IBasicVersionControl")); for (auto* plugin : vcsPlugins) { auto* iface = plugin->extension(); if (iface->isValidRemoteRepositoryUrl(repoUrl)) { vcsOrProviderPlugin = plugin; break; } } if (!vcsOrProviderPlugin) { - KMessageBox::error(Core::self()->uiController()->activeMainWindow(), - i18n("No enabled plugin supports this repository URL: %1", repoUrl.toDisplayString())); + if (fetchFlags.testFlag(FetchShowErrorIfNotSupported)) { + KMessageBox::error(Core::self()->uiController()->activeMainWindow(), + i18n("No enabled plugin supports this repository URL: %1", repoUrl.toDisplayString())); + } return false; } const QUrl url = d->dialog->askProjectConfigLocation(true, QUrl(), repoUrl, vcsOrProviderPlugin); if (!url.isEmpty()) { d->importProject(url); } return true; } void ProjectController::fetchProject() { Q_D(ProjectController); QUrl url = d->dialog->askProjectConfigLocation(true); if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::projectImportingFinished( IProject* project ) { Q_D(ProjectController); if( !project ) { qCWarning(SHELL) << "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 ); if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } if (Core::self()->setupFlags() != Core::NoUi) { d->m_recentProjectsAction->addUrl( project->projectFile().toUrl() ); saveRecentProjectsActionEntries(); } 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) { Q_D(ProjectController); QList pluginsForProj = d->m_projectPlugins.value( proj ); d->m_projectPlugins.remove( proj ); QList otherProjectPlugins; for (const QList& _list : qAsConst(d->m_projectPlugins)) { otherProjectPlugins << _list; } QSet pluginsForProjSet = QSet::fromList( pluginsForProj ); QSet otherPrjPluginsSet = QSet::fromList( otherProjectPlugins ); // loaded - target = tobe unloaded. const QSet tobeRemoved = pluginsForProjSet.subtract( otherPrjPluginsSet ); for (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) { const auto documents = Core::self()->documentController()->openDocuments(); for (IDocument* doc : documents) { 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, [this] { Q_D(ProjectController); d->unloadAllProjectPlugins(); }); } void ProjectController::takeProject(IProject* proj) { Q_D(ProjectController); if (!proj) { return; } // loading might have failed 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(); if (d->m_projects.isEmpty()) { initializePluginCleanup(proj); } if(!d->m_cleaningUp) d->saveListOfOpenedProjects(); emit projectClosed(proj); } void ProjectController::closeProject(IProject* proj) { takeProject(proj); proj->deleteLater(); // be safe when deleting } void ProjectController::closeAllProjects() { Q_D(ProjectController); const auto projects = d->m_projects; for (auto* project : projects) { closeProject(project); } } void ProjectController::abortOpeningProject(IProject* proj) { Q_D(ProjectController); d->m_currentlyOpening.removeAll(proj->projectFile().toUrl()); emit projectOpeningAborted(proj); } ProjectModel* ProjectController::projectModel() { Q_D(ProjectController); return d->model; } IProject* ProjectController::findProjectForUrl( const QUrl& url ) const { Q_D(const ProjectController); if (d->m_projects.isEmpty()) { return nullptr; } ProjectBaseItem* item = d->model->itemForPath(IndexedString(url)); if (item) { return item->project(); } return nullptr; } IProject* ProjectController::findProjectByName( const QString& name ) { Q_D(ProjectController); auto it = std::find_if(d->m_projects.constBegin(), d->m_projects.constEnd(), [&](IProject* proj) { return (proj->name() == name); }); return (it != d->m_projects.constEnd()) ? *it : nullptr; } void ProjectController::configureProject( IProject* project ) { Q_D(ProjectController); d->projectConfig( project ); } void ProjectController::addProject(IProject* project) { Q_D(ProjectController); Q_ASSERT(project); if (d->m_projects.contains(project)) { qCWarning(SHELL) << "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); } bool ProjectController::isProjectNameUsed( const QString& name ) const { const auto projects = this->projects(); return std::any_of(projects.begin(), projects.end(), [&](IProject* p) { return (p->name() == name); }); } QUrl ProjectController::projectsBaseDirectory() const { KConfigGroup group = ICore::self()->activeSession()->config()->group( "Project Manager" ); return group.readEntry("Projects Base Directory", QUrl::fromLocalFile(QDir::homePath() + QLatin1String("/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 const auto projects = Core::self()->projectController()->projects(); auto it = std::find_if(projects.begin(), projects.end(), [&](IProject* candidateProject) { return (candidateProject->path().toUrl().isParentOf(url)); }); if (it != projects.end()) { project = *it; } } Path parent = Path(url).parent(); QString prefixText; if (project) { if (format == FormatHtml) { prefixText = QLatin1String("") + project->name() + QLatin1String("/"); } else { prefixText = project->name() + QLatin1Char(':'); } QString relativePath = project->path().relativePath(parent); if(relativePath.startsWith(QLatin1String("./"))) { relativePath.remove(0, 2); } if (!relativePath.isEmpty()) { prefixText += relativePath + QLatin1Char('/'); } } else { prefixText = parent.pathOrUrl() + QLatin1Char('/'); } 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 QLatin1String("") + project->name() + QLatin1String(""); } else { return project->name(); } } QString prefixText = prettyFilePath( url, format ); if (format == FormatHtml) { return prefixText + QLatin1String("") + url.fileName() + QLatin1String(""); } else { return prefixText + url.fileName(); } } ContextMenuExtension ProjectController::contextMenuExtension(Context* ctx, QWidget* parent) { Q_D(ProjectController); Q_UNUSED(parent); ContextMenuExtension ext; if ( ctx->type() != Context::ProjectItemContext) { return ext; } if (!static_cast(ctx)->items().isEmpty() ) { auto* action = new QAction(i18n("Reparse the Entire Project"), this); connect(action, &QAction::triggered, this, [this] { Q_D(ProjectController); const auto projects = d->selectedProjects(); for (auto* project : projects) { reparseProject(project, true, true); } }); ext.addAction(ContextMenuExtension::ProjectGroup, action); return ext; } ext.addAction(ContextMenuExtension::ProjectGroup, d->m_openProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_fetchProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_recentProjectsAction); return ext; } ProjectBuildSetModel* ProjectController::buildSetModel() { Q_D(ProjectController); return d->buildset; } ProjectChangesModel* ProjectController::changesModel() { Q_D(ProjectController); 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(); auto* 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) { ScopedDialog commitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } } } QString ProjectController::mapSourceBuild( const QString& path_, bool reverse, bool fallbackRoot ) const { Q_D(const ProjectController); Path path(path_); IProject* sourceDirProject = nullptr, *buildDirProject = nullptr; for (IProject* proj : qAsConst(d->m_projects)) { if(proj->path().isParentOf(path) || proj->path() == path) sourceDirProject = proj; if(proj->buildSystemManager()) { Path buildDir = proj->buildSystemManager()->buildDirectory(proj->projectItem()); if(buildDir.isValid() && (buildDir.isParentOf(path) || buildDir == 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 KDevelop::ProjectController::reparseProject(IProject *project, bool forceUpdate, bool forceAll) { Q_D(ProjectController); if (auto job = d->m_parseJobs.value(project)) { job->kill(); } d->m_parseJobs[project] = new KDevelop::ParseProjectJob(project, forceUpdate, forceAll); ICore::self()->runController()->registerJob(d->m_parseJobs[project]); } } diff --git a/kdevplatform/shell/projectcontroller.h b/kdevplatform/shell/projectcontroller.h index 50589ca2fa..1086ac8a66 100644 --- a/kdevplatform/shell/projectcontroller.h +++ b/kdevplatform/shell/projectcontroller.h @@ -1,193 +1,199 @@ /* 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 ProjectControllerPrivate; 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(), const QUrl& repoUrl = QUrl(), IPlugin* plugin = nullptr) = 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; IProject* findProjectByName( const QString& name ) override; IProject* findProjectForUrl( const QUrl& ) const override; 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, QWidget* parent); + enum FetchFlag { + NoFetchFlags = 0, + FetchShowErrorIfNotSupported = 1, + }; + Q_DECLARE_FLAGS(FetchFlags, FetchFlag) + /** * @param repoUrl url identifying the repo * @returns @c true if a plugin was found to handle the repo (also if user cancelled), @c false otherwise */ - bool fetchProjectFromUrl(const QUrl& repoUrl); + bool fetchProjectFromUrl(const QUrl& repoUrl, FetchFlags fetchFlags = FetchShowErrorIfNotSupported); public Q_SLOTS: Q_SCRIPTABLE void openProjectForUrl( const QString &sourceUrl ) { openProjectForUrl(QUrl(sourceUrl)); } void openProjectForUrl( const QUrl &sourceUrl ) override; void fetchProject(); void openProject( const QUrl &KDev4ProjectFile = QUrl() ) override; void abortOpeningProject( IProject* ); void projectImportingFinished( IProject* ); void closeProject( IProject* ) override; void closeAllProjects() override; void configureProject( IProject* ) override; void reparseProject( IProject* project, bool forceUpdate = false, bool forceAll = false ) override; void eventuallyOpenProjectFile(KIO::Job* job, const KIO::UDSEntryList& entries); 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. // This is used through DBus from within kdevplatform_shell_environment.sh // 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(); Q_SIGNALS: void initialized(); private: //FIXME Do not load all of this just for the project being opened... //void legacyLoading(); void setupActions(); void cleanup(); void saveRecentProjectsActionEntries(); // helper methods for closeProject() void unloadUnusedProjectPlugins(IProject* proj); void disableProjectCloseAction(); void closeAllOpenedFiles(IProject* proj); void initializePluginCleanup(IProject* proj); private: const QScopedPointer d_ptr; Q_DECLARE_PRIVATE(ProjectController) friend class ProjectControllerPrivate; }; class ProjectDialogProvider : public IProjectDialogProvider { Q_OBJECT public: explicit ProjectDialogProvider(ProjectControllerPrivate* p); ~ProjectDialogProvider() override; ProjectControllerPrivate* const d; public Q_SLOTS: QUrl askProjectConfigLocation(bool fetch, const QUrl& startUrl, const QUrl& repoUrl, IPlugin* plugin) override; bool userWantsReopen() override; }; } #endif diff --git a/plugins/okteta/oktetadocument.h b/plugins/okteta/oktetadocument.h index 12cafec956..4390f6bf68 100644 --- a/plugins/okteta/oktetadocument.h +++ b/plugins/okteta/oktetadocument.h @@ -1,98 +1,99 @@ /* This file is part of the KDevelop Okteta module, part of the KDE project. Copyright 2010 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef OKTETADOCUMENT_H #define OKTETADOCUMENT_H // Kasten core #include // KDevPlatform #include #include namespace Kasten { class ByteArrayDocument; class AbstractDocument; } namespace KDevelop { class ICore; class OktetaPlugin; class OktetaDocument : public Sublime::UrlDocument, public IDocument { Q_OBJECT + Q_INTERFACES(KDevelop::IDocument) public: OktetaDocument( const QUrl &url, ICore* core ); ~OktetaDocument() override; public: // KDevelop::IDocument API KTextEditor::Cursor cursorPosition() const override; bool isActive() const override; QMimeType mimeType() const override; KParts::Part* partForView( QWidget* widget ) const override; DocumentState state() const override; KTextEditor::Document* textDocument() const override; QUrl url() const override; void activate( Sublime::View* view, KParts::MainWindow* mainWindow ) override; bool close( IDocument::DocumentSaveMode = IDocument::Default ) override; void reload() override; bool save( IDocument::DocumentSaveMode = IDocument::Default ) override; void setCursorPosition( const KTextEditor::Cursor& cursor ) override; void setTextSelection( const KTextEditor::Range& range ) override; public: // Sublime::Document API bool closeDocument(bool silent) override; public: OktetaPlugin* plugin() const; Kasten::ByteArrayDocument* byteArrayDocument() const; public: void setPlugin( OktetaPlugin* plugin ); protected Q_SLOTS: // Sublime::Document API Sublime::View* newView( Sublime::Document* document ) override; protected Q_SLOTS: void onByteArrayDocumentChanged(); // Moc is too primitive to know about namespace aliase void onByteArrayDocumentLoaded( Kasten::AbstractDocument* document ); private: OktetaPlugin* mPlugin; Kasten::ByteArrayDocument* mByteArrayDocument; }; inline OktetaPlugin* OktetaDocument::plugin() const { return mPlugin; } inline Kasten::ByteArrayDocument* OktetaDocument::byteArrayDocument() const { return mByteArrayDocument; } } #endif