diff --git a/kdevplatform/language/codegen/templateclassgenerator.cpp b/kdevplatform/language/codegen/templateclassgenerator.cpp index b4ef75be6a..61debb8fa9 100644 --- a/kdevplatform/language/codegen/templateclassgenerator.cpp +++ b/kdevplatform/language/codegen/templateclassgenerator.cpp @@ -1,386 +1,384 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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 "templateclassgenerator.h" #include "archivetemplateloader.h" #include #include #include "language/codegen/documentchangeset.h" #include "codedescription.h" #include "templaterenderer.h" #include "sourcefiletemplate.h" #include #include #include #include #include #include using namespace KDevelop; /// @param base String such as 'public QObject' or 'QObject' InheritanceDescription descriptionFromString(const QString& base) { QStringList splitBase = base.split(QLatin1Char(' ')); QString identifier = splitBase.takeLast(); QString inheritanceMode = splitBase.join(QLatin1Char(' ')); InheritanceDescription desc; desc.baseType = identifier; desc.inheritanceMode = inheritanceMode; return desc; } class KDevelop::TemplateClassGeneratorPrivate { public: SourceFileTemplate fileTemplate; QUrl baseUrl; // changes state when rendering mutable TemplateRenderer renderer; QString name; QString identifier; QStringList namespaces; QString license; // lazily estimated mutable QHash fileUrls; QHash filePositions; ClassDescription description; QList directBaseClasses; QList allBaseClasses; void fetchSuperClasses(const DeclarationPointer& declaration); }; void TemplateClassGeneratorPrivate::fetchSuperClasses(const DeclarationPointer& declaration) { DUChainReadLocker lock; //Prevent duplicity if (allBaseClasses.contains(declaration)) { return; } allBaseClasses << declaration; DUContext* context = declaration->internalContext(); if (context) { const auto importedParentContexts = context->importedParentContexts(); for (const DUContext::Import& import : importedParentContexts) { if (DUContext* parentContext = import.context(context->topContext())) { if (parentContext->type() == DUContext::Class) { fetchSuperClasses(DeclarationPointer(parentContext->owner())); } } } } } TemplateClassGenerator::TemplateClassGenerator(const QUrl& baseUrl) : d_ptr(new TemplateClassGeneratorPrivate) { Q_D(TemplateClassGenerator); Q_ASSERT(QFileInfo(baseUrl.toLocalFile()).isDir()); // assume folder d->baseUrl = baseUrl; d->renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); } TemplateClassGenerator::~TemplateClassGenerator() = default; void TemplateClassGenerator::setTemplateDescription(const SourceFileTemplate& fileTemplate) { Q_D(TemplateClassGenerator); d->fileTemplate = fileTemplate; Q_ASSERT(fileTemplate.isValid()); } DocumentChangeSet TemplateClassGenerator::generate() { Q_D(TemplateClassGenerator); return d->renderer.renderFileTemplate(d->fileTemplate, d->baseUrl, fileUrls()); } QHash TemplateClassGenerator::fileLabels() const { Q_D(const TemplateClassGenerator); Q_ASSERT(d->fileTemplate.isValid()); QHash labels; const auto outputFiles = d->fileTemplate.outputFiles(); labels.reserve(outputFiles.size()); for (const SourceFileTemplate::OutputFile& outputFile : outputFiles) { labels.insert(outputFile.identifier, outputFile.label); } return labels; } TemplateClassGenerator::UrlHash TemplateClassGenerator::fileUrls() const { Q_D(const TemplateClassGenerator); if (d->fileUrls.isEmpty()) { const auto outputFiles = d->fileTemplate.outputFiles(); for (const SourceFileTemplate::OutputFile& outputFile : outputFiles) { QString outputName = d->renderer.render(outputFile.outputName, outputFile.identifier); QUrl url = d->baseUrl.resolved(QUrl(outputName)); d->fileUrls.insert(outputFile.identifier, url); } } return d->fileUrls; } QUrl TemplateClassGenerator::baseUrl() const { Q_D(const TemplateClassGenerator); return d->baseUrl; } QUrl TemplateClassGenerator::fileUrl(const QString& outputFile) const { return fileUrls().value(outputFile); } void TemplateClassGenerator::setFileUrl(const QString& outputFile, const QUrl& url) { Q_D(TemplateClassGenerator); d->fileUrls.insert(outputFile, url); d->renderer.addVariable(QLatin1String("output_file_") + outputFile.toLower(), QDir( d->baseUrl.path()).relativeFilePath(url.path())); d->renderer.addVariable(QLatin1String("output_file_") + outputFile.toLower() + QLatin1String( "_absolute"), url.toLocalFile()); } KTextEditor::Cursor TemplateClassGenerator::filePosition(const QString& outputFile) const { Q_D(const TemplateClassGenerator); return d->filePositions.value(outputFile); } void TemplateClassGenerator::setFilePosition(const QString& outputFile, const KTextEditor::Cursor& position) { Q_D(TemplateClassGenerator); d->filePositions.insert(outputFile, position); } void TemplateClassGenerator::addVariables(const QVariantHash& variables) { Q_D(TemplateClassGenerator); d->renderer.addVariables(variables); } QString TemplateClassGenerator::renderString(const QString& text) const { Q_D(const TemplateClassGenerator); return d->renderer.render(text); } SourceFileTemplate TemplateClassGenerator::sourceFileTemplate() const { Q_D(const TemplateClassGenerator); return d->fileTemplate; } TemplateRenderer* TemplateClassGenerator::renderer() const { Q_D(const TemplateClassGenerator); return &(d->renderer); } QString TemplateClassGenerator::name() const { Q_D(const TemplateClassGenerator); return d->name; } void TemplateClassGenerator::setName(const QString& newName) { Q_D(TemplateClassGenerator); d->name = newName; d->renderer.addVariable(QStringLiteral("name"), newName); } QString TemplateClassGenerator::identifier() const { - Q_D(const TemplateClassGenerator); - return name(); } void TemplateClassGenerator::setIdentifier(const QString& identifier) { Q_D(TemplateClassGenerator); d->renderer.addVariable(QStringLiteral("identifier"), identifier); const QStringList separators{ QStringLiteral("::"), QStringLiteral("."), QStringLiteral(":"), QStringLiteral("\\"), QStringLiteral("/"), }; QStringList ns; for (const QString& separator : separators) { ns = identifier.split(separator); if (ns.size() > 1) { break; } } setName(ns.takeLast()); setNamespaces(ns); } QStringList TemplateClassGenerator::namespaces() const { Q_D(const TemplateClassGenerator); return d->namespaces; } void TemplateClassGenerator::setNamespaces(const QStringList& namespaces) { Q_D(TemplateClassGenerator); d->namespaces = namespaces; d->renderer.addVariable(QStringLiteral("namespaces"), namespaces); } /// Specify license for this class void TemplateClassGenerator::setLicense(const QString& license) { Q_D(TemplateClassGenerator); qCDebug(LANGUAGE) << "New Class: " << d->name << "Set license: " << d->license; d->license = license; d->renderer.addVariable(QStringLiteral("license"), license); } /// Get the license specified for this classes QString TemplateClassGenerator::license() const { Q_D(const TemplateClassGenerator); return d->license; } void TemplateClassGenerator::setDescription(const ClassDescription& description) { Q_D(TemplateClassGenerator); d->description = description; QVariantHash variables; variables[QStringLiteral("description")] = QVariant::fromValue(description); variables[QStringLiteral("members")] = CodeDescription::toVariantList(description.members); variables[QStringLiteral("functions")] = CodeDescription::toVariantList(description.methods); variables[QStringLiteral("base_classes")] = CodeDescription::toVariantList(description.baseClasses); d->renderer.addVariables(variables); } ClassDescription TemplateClassGenerator::description() const { Q_D(const TemplateClassGenerator); return d->description; } void TemplateClassGenerator::addBaseClass(const QString& base) { Q_D(TemplateClassGenerator); const InheritanceDescription desc = descriptionFromString(base); ClassDescription cd = description(); cd.baseClasses << desc; setDescription(cd); DUChainReadLocker lock; PersistentSymbolTable::Declarations decl = PersistentSymbolTable::self().declarations(IndexedQualifiedIdentifier(QualifiedIdentifier(desc.baseType))); //Search for all super classes for (PersistentSymbolTable::Declarations::Iterator it = decl.iterator(); it; ++it) { DeclarationPointer declaration = DeclarationPointer(it->declaration()); if (declaration->isForwardDeclaration()) { continue; } // Check if it's a class/struct/etc if (declaration->type()) { d->fetchSuperClasses(declaration); d->directBaseClasses << declaration; break; } } } void TemplateClassGenerator::setBaseClasses(const QList& bases) { Q_D(TemplateClassGenerator); // clear ClassDescription cd = description(); cd.baseClasses.clear(); setDescription(cd); d->directBaseClasses.clear(); d->allBaseClasses.clear(); // add all bases for (const QString& base : bases) { addBaseClass(base); } } QList TemplateClassGenerator::directBaseClasses() const { Q_D(const TemplateClassGenerator); return d->directBaseClasses; } QList TemplateClassGenerator::allBaseClasses() const { Q_D(const TemplateClassGenerator); return d->allBaseClasses; } diff --git a/kdevplatform/language/duchain/navigation/abstractnavigationcontext.cpp b/kdevplatform/language/duchain/navigation/abstractnavigationcontext.cpp index 39dbbbb878..838d1db087 100644 --- a/kdevplatform/language/duchain/navigation/abstractnavigationcontext.cpp +++ b/kdevplatform/language/duchain/navigation/abstractnavigationcontext.cpp @@ -1,622 +1,620 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "abstractnavigationcontext.h" #include #include #include "abstractdeclarationnavigationcontext.h" #include "abstractnavigationwidget.h" #include "usesnavigationcontext.h" #include "../../../interfaces/icore.h" #include "../../../interfaces/idocumentcontroller.h" #include "../functiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../types/functiontype.h" #include "../types/structuretype.h" #include #include #include #include namespace KDevelop { class AbstractNavigationContextPrivate { public: QVector m_children; //Used to keep alive all children until this is deleted int m_selectedLink = 0; //The link currently selected NavigationAction m_selectedLinkAction; //Target of the currently selected link bool m_shorten = false; //A counter used while building the html-code to count the used links. int m_linkCount = -1; //Something else than -1 if the current position is represented by a line-number, not a link. int m_currentLine = 0; int m_currentPositionLine = 0; QMap m_links; QMap m_linkLines; //Holds the line for each link QMap m_intLinks; AbstractNavigationContext* m_previousContext; TopDUContextPointer m_topContext; QString m_currentText; //Here the text is built }; void AbstractNavigationContext::setTopContext(const TopDUContextPointer& context) { Q_D(AbstractNavigationContext); d->m_topContext = context; } TopDUContextPointer AbstractNavigationContext::topContext() const { Q_D(const AbstractNavigationContext); return d->m_topContext; } AbstractNavigationContext::AbstractNavigationContext(const TopDUContextPointer& topContext, AbstractNavigationContext* previousContext) : d_ptr(new AbstractNavigationContextPrivate) { Q_D(AbstractNavigationContext); d->m_previousContext = previousContext; d->m_topContext = topContext; qRegisterMetaType("KTextEditor::Cursor"); qRegisterMetaType("IDocumentation::Ptr"); } AbstractNavigationContext::~AbstractNavigationContext() { } void AbstractNavigationContext::makeLink(const QString& name, const DeclarationPointer& declaration, NavigationAction::Type actionType) { NavigationAction action(declaration, actionType); makeLink(name, QString(), action); } QString AbstractNavigationContext::createLink(const QString& name, const QString&, const NavigationAction& action) { Q_D(AbstractNavigationContext); if (d->m_shorten) { //Do not create links in shortened mode, it's only for viewing return typeHighlight(name.toHtmlEscaped()); } // NOTE: Since the by definition in the HTML standard some uri components // are case-insensitive, we define a new lowercase link-id for each // link. Otherwise Qt 5 seems to mess up the casing and the link // cannot be matched when it's executed. QString hrefId = QStringLiteral("link_%1").arg(d->m_links.count()); d->m_links[hrefId] = action; d->m_intLinks[d->m_linkCount] = action; d->m_linkLines[d->m_linkCount] = d->m_currentLine; if (d->m_currentPositionLine == d->m_currentLine) { d->m_currentPositionLine = -1; d->m_selectedLink = d->m_linkCount; } QString str = name.toHtmlEscaped(); if (d->m_linkCount == d->m_selectedLink) str = QLatin1String("") + str + QLatin1String( ""); QString ret = QLatin1String("m_linkCount == d->m_selectedLink && d->m_currentPositionLine == -1) ? QStringLiteral(" name = \"currentPosition\"") : QString()) + QLatin1Char('>') + str + QLatin1String(""); if (d->m_selectedLink == d->m_linkCount) d->m_selectedLinkAction = action; ++d->m_linkCount; return ret; } void AbstractNavigationContext::makeLink(const QString& name, const QString& targetId, const NavigationAction& action) { modifyHtml() += createLink(name, targetId, action); } void AbstractNavigationContext::clear() { Q_D(AbstractNavigationContext); d->m_linkCount = 0; d->m_currentLine = 0; d->m_currentText.clear(); d->m_links.clear(); d->m_intLinks.clear(); d->m_linkLines.clear(); } void AbstractNavigationContext::executeLink(const QString& link) { Q_D(AbstractNavigationContext); const auto actionIt = d->m_links.constFind(link); if (actionIt == d->m_links.constEnd()) return; execute(*actionIt); } NavigationContextPointer AbstractNavigationContext::executeKeyAction(const QString& key) { Q_UNUSED(key); return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::execute(const NavigationAction& action) { Q_D(AbstractNavigationContext); if (action.targetContext) return NavigationContextPointer(action.targetContext); if (action.type == NavigationAction::ExecuteKey) return executeKeyAction(action.key); if (!action.decl && (action.type != NavigationAction::JumpToSource || action.document.isEmpty())) { qCDebug(LANGUAGE) << "Navigation-action has invalid declaration" << endl; return NavigationContextPointer(this); } switch (action.type) { case NavigationAction::ExecuteKey: break; case NavigationAction::None: qCDebug(LANGUAGE) << "Tried to execute an invalid action in navigation-widget" << endl; break; case NavigationAction::NavigateDeclaration: { auto ctx = dynamic_cast(d->m_previousContext); if (ctx && ctx->declaration() == action.decl) return NavigationContextPointer(d->m_previousContext); return registerChild(action.decl); } case NavigationAction::NavigateUses: { auto* browser = ICore::self()->pluginController()->extensionForPlugin(); if (browser) { browser->showUses(action.decl); return NavigationContextPointer(this); } Q_FALLTHROUGH(); } case NavigationAction::ShowUses: { return registerChild(new UsesNavigationContext(action.decl.data(), this)); } case NavigationAction::JumpToSource: { QUrl doc = action.document; KTextEditor::Cursor cursor = action.cursor; { DUChainReadLocker lock(DUChain::lock()); if (action.decl) { if (doc.isEmpty()) { doc = action.decl->url().toUrl(); /* if(action.decl->internalContext()) cursor = action.decl->internalContext()->range().start() + KTextEditor::Cursor(0, 1); else*/ cursor = action.decl->rangeInCurrentRevision().start(); } action.decl->activateSpecialization(); } } //This is used to execute the slot delayed in the event-loop, so crashes are avoided QMetaObject::invokeMethod(ICore::self()->documentController(), "openDocument", Qt::QueuedConnection, Q_ARG(QUrl, doc), Q_ARG(KTextEditor::Cursor, cursor)); break; } case NavigationAction::ShowDocumentation: { auto doc = ICore::self()->documentationController()->documentationForDeclaration(action.decl.data()); // This is used to execute the slot delayed in the event-loop, so crashes are avoided // which can happen e.g. due to focus change events resulting in tooltip destruction and thus this object QMetaObject::invokeMethod( ICore::self()->documentationController(), "showDocumentation", Qt::QueuedConnection, Q_ARG(IDocumentation::Ptr, doc)); } break; } return NavigationContextPointer(this); } AbstractNavigationContext* AbstractNavigationContext::previousContext() const { Q_D(const AbstractNavigationContext); return d->m_previousContext; } void AbstractNavigationContext::setPreviousContext(AbstractNavigationContext* previous) { Q_D(AbstractNavigationContext); d->m_previousContext = previous; } NavigationContextPointer AbstractNavigationContext::registerChild(AbstractNavigationContext* context) { Q_D(AbstractNavigationContext); d->m_children << NavigationContextPointer(context); return d->m_children.last(); } NavigationContextPointer AbstractNavigationContext::registerChild(const DeclarationPointer& declaration) { Q_D(AbstractNavigationContext); //We create a navigation-widget here, and steal its context.. evil ;) QScopedPointer navigationWidget( declaration->context()->createNavigationWidget(declaration.data())); if (navigationWidget) { NavigationContextPointer ret = navigationWidget->context(); ret->setPreviousContext(this); d->m_children << ret; return ret; } else { return NavigationContextPointer(this); } } const int lineJump = 3; bool AbstractNavigationContext::down() { Q_D(AbstractNavigationContext); //Make sure link-count is valid if (d->m_linkCount == -1) { DUChainReadLocker lock; html(); } // select first link when we enter via down if (d->m_selectedLink == -1 && d->m_linkCount) { d->m_selectedLink = 0; d->m_currentPositionLine = -1; return true; } int fromLine = d->m_currentPositionLine; // try to select the next link within our lineJump distance if (d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { if (fromLine == -1) fromLine = d->m_linkLines[d->m_selectedLink]; for (int newSelectedLink = d->m_selectedLink + 1; newSelectedLink < d->m_linkCount; ++newSelectedLink) { if (d->m_linkLines[newSelectedLink] > fromLine && d->m_linkLines[newSelectedLink] - fromLine <= lineJump) { d->m_selectedLink = newSelectedLink; d->m_currentPositionLine = -1; return true; } } } if (fromLine == d->m_currentLine - 1) // nothing to do, we are at the end of the document return false; // scroll down by applying the lineJump if (fromLine == -1) fromLine = 0; d->m_currentPositionLine = fromLine + lineJump; if (d->m_currentPositionLine >= d->m_currentLine) { d->m_currentPositionLine = d->m_currentLine - 1; } return fromLine != d->m_currentPositionLine; } bool AbstractNavigationContext::up() { Q_D(AbstractNavigationContext); //Make sure link-count is valid if (d->m_linkCount == -1) { DUChainReadLocker lock; html(); } // select last link when we enter via up if (d->m_selectedLink == -1 && d->m_linkCount) { d->m_selectedLink = d->m_linkCount - 1; d->m_currentPositionLine = -1; return true; } int fromLine = d->m_currentPositionLine; if (d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { if (fromLine == -1) fromLine = d->m_linkLines[d->m_selectedLink]; for (int newSelectedLink = d->m_selectedLink - 1; newSelectedLink >= 0; --newSelectedLink) { if (d->m_linkLines[newSelectedLink] < fromLine && fromLine - d->m_linkLines[newSelectedLink] <= lineJump) { d->m_selectedLink = newSelectedLink; d->m_currentPositionLine = -1; return true; } } } if (fromLine == -1) fromLine = d->m_currentLine - 1; d->m_currentPositionLine = fromLine - lineJump; if (d->m_currentPositionLine < 0) d->m_currentPositionLine = 0; return fromLine || d->m_currentPositionLine; } bool AbstractNavigationContext::nextLink() { Q_D(AbstractNavigationContext); //Make sure link-count is valid if (d->m_linkCount == -1) { DUChainReadLocker lock; html(); } if (!d->m_linkCount) return false; d->m_currentPositionLine = -1; d->m_selectedLink++; if (d->m_selectedLink >= d->m_linkCount) { d->m_selectedLink = 0; return false; } return true; } bool AbstractNavigationContext::previousLink() { Q_D(AbstractNavigationContext); //Make sure link-count is valid if (d->m_linkCount == -1) { DUChainReadLocker lock; html(); } if (!d->m_linkCount) return false; d->m_currentPositionLine = -1; d->m_selectedLink--; if (d->m_selectedLink < 0) { d->m_selectedLink = d->m_linkCount - 1; return false; } return true; } int AbstractNavigationContext::linkCount() const { Q_D(const AbstractNavigationContext); return d->m_linkCount; } void AbstractNavigationContext::resetNavigation() { Q_D(AbstractNavigationContext); d->m_currentPositionLine = -1; d->m_selectedLink = -1; d->m_selectedLinkAction = {}; } NavigationContextPointer AbstractNavigationContext::back() { Q_D(AbstractNavigationContext); if (d->m_previousContext) return NavigationContextPointer(d->m_previousContext); else return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept() { Q_D(AbstractNavigationContext); if (d->m_selectedLink >= 0 && d->m_selectedLink < d->m_linkCount) { NavigationAction action = d->m_intLinks[d->m_selectedLink]; return execute(action); } return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept(IndexedDeclaration decl) { - Q_D(AbstractNavigationContext); - if (decl.data()) { NavigationAction action(DeclarationPointer(decl.data()), NavigationAction::NavigateDeclaration); return execute(action); } else { return NavigationContextPointer(this); } } NavigationContextPointer AbstractNavigationContext::acceptLink(const QString& link) { Q_D(AbstractNavigationContext); const auto actionIt = d->m_links.constFind(link); if (actionIt == d->m_links.constEnd()) { qCDebug(LANGUAGE) << "Executed unregistered link " << link << endl; return NavigationContextPointer(this); } return execute(*actionIt); } NavigationAction AbstractNavigationContext::currentAction() const { Q_D(const AbstractNavigationContext); return d->m_selectedLinkAction; } QString AbstractNavigationContext::declarationKind(const DeclarationPointer& decl) { const auto* function = dynamic_cast(decl.data()); QString kind; if (decl->isTypeAlias()) kind = i18n("Typedef"); else if (decl->kind() == Declaration::Type) { if (decl->type()) kind = i18n("Class"); } else if (decl->kind() == Declaration::Instance) { kind = i18n("Variable"); } else if (decl->kind() == Declaration::Namespace) { kind = i18n("Namespace"); } if (auto* alias = dynamic_cast(decl.data())) { if (alias->identifier().isEmpty()) kind = i18n("Namespace import"); else kind = i18n("Namespace alias"); } if (function) kind = i18n("Function"); if (decl->isForwardDeclaration()) kind = i18n("Forward Declaration"); return kind; } QString AbstractNavigationContext::html(bool shorten) { Q_D(AbstractNavigationContext); d->m_shorten = shorten; return QString(); } bool AbstractNavigationContext::alreadyComputed() const { Q_D(const AbstractNavigationContext); return !d->m_currentText.isEmpty(); } bool AbstractNavigationContext::isWidgetMaximized() const { return true; } QWidget* AbstractNavigationContext::widget() const { return nullptr; } ///Splits the string by the given regular expression, but keeps the split-matches at the end of each line static QStringList splitAndKeep(QString str, const QRegExp& regExp) { QStringList ret; int place = regExp.indexIn(str); while (place != -1) { ret << str.left(place + regExp.matchedLength()); str.remove(0, place + regExp.matchedLength()); place = regExp.indexIn(str); } ret << str; return ret; } void AbstractNavigationContext::addHtml(const QString& html) { Q_D(AbstractNavigationContext); QRegExp newLineRegExp(QStringLiteral("
|
|

")); const auto lines = splitAndKeep(html, newLineRegExp); for (const QString& line : lines) { d->m_currentText += line; if (line.indexOf(newLineRegExp) != -1) { ++d->m_currentLine; if (d->m_currentLine == d->m_currentPositionLine) { d->m_currentText += QStringLiteral( " <-> "); // ><-> is <-> } } } } QString AbstractNavigationContext::currentHtml() const { Q_D(const AbstractNavigationContext); return d->m_currentText; } QString Colorizer::operator()(const QString& str) const { QString ret = QLatin1String("") + str + QLatin1String(""); if (m_formatting & Fixed) ret = QLatin1String("") + ret + QLatin1String(""); if (m_formatting & Bold) ret = QLatin1String("") + ret + QLatin1String(""); if (m_formatting & Italic) ret = QLatin1String("") + ret + QLatin1String(""); return ret; } const Colorizer AbstractNavigationContext::typeHighlight(QStringLiteral("006000")); const Colorizer AbstractNavigationContext::errorHighlight(QStringLiteral("990000")); const Colorizer AbstractNavigationContext::labelHighlight(QStringLiteral("000000")); const Colorizer AbstractNavigationContext::codeHighlight(QStringLiteral("005000")); const Colorizer AbstractNavigationContext::propertyHighlight(QStringLiteral("009900")); const Colorizer AbstractNavigationContext::navigationHighlight(QStringLiteral("000099")); const Colorizer AbstractNavigationContext::importantHighlight(QStringLiteral( "000000"), Colorizer::Bold | Colorizer::Italic); const Colorizer AbstractNavigationContext::commentHighlight(QStringLiteral("303030")); const Colorizer AbstractNavigationContext::nameHighlight(QStringLiteral("000000"), Colorizer::Bold); } diff --git a/kdevplatform/shell/documentcontroller.cpp b/kdevplatform/shell/documentcontroller.cpp index cdd65f7c4f..a69828da39 100644 --- a/kdevplatform/shell/documentcontroller.cpp +++ b/kdevplatform/shell/documentcontroller.cpp @@ -1,1277 +1,1275 @@ /* This file is part of the KDE project Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Bernd Gehrmann Copyright 2003 Roberto Raggi Copyright 2003-2008 Hamish Rodda Copyright 2003 Harald Fernengel Copyright 2003 Jens Dagerbo Copyright 2005 Adam Treat Copyright 2004-2007 Alexander Dymo 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 "documentcontroller.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 "core.h" #include "mainwindow.h" #include "textdocument.h" #include "uicontroller.h" #include "partcontroller.h" #include "savedialog.h" #include "debug.h" #include #include #define EMPTY_DOCUMENT_URL i18n("Untitled") using namespace KDevelop; class KDevelop::DocumentControllerPrivate { public: struct OpenFileResult { QList urls; QString encoding; }; explicit DocumentControllerPrivate(DocumentController* c) : controller(c) , fileOpenRecent(nullptr) , globalTextEditorInstance(nullptr) { } ~DocumentControllerPrivate() = default; // used to map urls to open docs QHash< QUrl, IDocument* > documents; QHash< QString, IDocumentFactory* > factories; struct HistoryEntry { HistoryEntry() {} HistoryEntry( const QUrl & u, const KTextEditor::Cursor& cursor ); QUrl url; KTextEditor::Cursor cursor; int id; }; void removeDocument(Sublime::Document *doc) { const QList urlsForDoc = documents.keys(dynamic_cast(doc)); for (const QUrl& url : urlsForDoc) { qCDebug(SHELL) << "destroying document" << doc; documents.remove(url); } } OpenFileResult showOpenFile() const { QUrl dir; if ( controller->activeDocument() ) { dir = controller->activeDocument()->url().adjusted(QUrl::RemoveFilename); } else { const auto cfg = KSharedConfig::openConfig()->group("Open File"); dir = cfg.readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() ); } const auto caption = i18n("Open File"); const auto filter = i18n("*|Text File\n"); auto parent = Core::self()->uiControllerInternal()->defaultMainWindow(); // use special dialogs in a KDE session, native dialogs elsewhere if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { const auto result = KEncodingFileDialog::getOpenUrlsAndEncoding(QString(), dir, filter, parent, caption); return {result.URLs, result.encoding}; } // note: can't just filter on text files using the native dialog, just display all files // see https://phabricator.kde.org/D622#11679 const auto urls = QFileDialog::getOpenFileUrls(parent, caption, dir); return {urls, QString()}; } void chooseDocument() { const auto res = showOpenFile(); if( !res.urls.isEmpty() ) { QString encoding = res.encoding; for (const QUrl& u : res.urls) { openDocumentInternal(u, QString(), KTextEditor::Range::invalid(), encoding ); } } } void changeDocumentUrl(KDevelop::IDocument* document) { QMutableHashIterator it = documents; while (it.hasNext()) { if (it.next().value() == document) { const auto documentIt = documents.constFind(document->url()); if (documentIt != documents.constEnd()) { // Weird situation (saving as a file that is already open) IDocument* origDoc = *documentIt; if (origDoc->state() & IDocument::Modified) { // given that the file has been saved, close the saved file as the other instance will become conflicted on disk document->close(); controller->activateDocument( origDoc ); break; } // Otherwise close the original document origDoc->close(); } else { // Remove the original document it.remove(); } documents.insert(document->url(), document); if (!controller->isEmptyDocumentUrl(document->url())) { fileOpenRecent->addUrl(document->url()); } break; } } } KDevelop::IDocument* findBuddyDocument(const QUrl &url, IBuddyDocumentFinder* finder) { const QList allDocs = controller->openDocuments(); for (KDevelop::IDocument* doc : allDocs) { if(finder->areBuddies(url, doc->url())) { return doc; } } return nullptr; } static bool fileExists(const QUrl& url) { if (url.isLocalFile()) { return QFile::exists(url.toLocalFile()); } else { auto job = KIO::stat(url, KIO::StatJob::SourceSide, 0, KIO::HideProgressInfo); KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); return job->exec(); } }; IDocument* openDocumentInternal( const QUrl & inputUrl, const QString& prefName = QString(), const KTextEditor::Range& range = KTextEditor::Range::invalid(), const QString& encoding = QString(), DocumentController::DocumentActivationParams activationParams = {}, IDocument* buddy = nullptr) { Q_ASSERT(!inputUrl.isRelative()); Q_ASSERT(!inputUrl.fileName().isEmpty() || !inputUrl.isLocalFile()); QString _encoding = encoding; QUrl url = inputUrl; if ( url.isEmpty() && (!activationParams.testFlag(IDocumentController::DoNotCreateView)) ) { const auto res = showOpenFile(); if( !res.urls.isEmpty() ) url = res.urls.first(); _encoding = res.encoding; if ( url.isEmpty() ) //still no url return nullptr; } KSharedConfig::openConfig()->group("Open File").writeEntry( "Last Open File Directory", url.adjusted(QUrl::RemoveFilename) ); // clean it and resolve possible symlink url = url.adjusted( QUrl::NormalizePathSegments ); if ( url.isLocalFile() ) { QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath(); if ( !path.isEmpty() ) url = QUrl::fromLocalFile( path ); } //get a part document IDocument* doc = documents.value(url); if (!doc) { QMimeType mimeType; if (DocumentController::isEmptyDocumentUrl(url)) { mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } else if (!url.isValid()) { // Exit if the url is invalid (should not happen) // If the url is valid and the file does not already exist, // kate creates the file and gives a message saying so qCDebug(SHELL) << "invalid URL:" << url.url(); return nullptr; } else if (KProtocolInfo::isKnownProtocol(url.scheme()) && !fileExists(url)) { //Don't create a new file if we are not in the code mode. if (ICore::self()->uiController()->activeArea()->objectName() != QLatin1String("code")) { return nullptr; } // enfore text mime type in order to create a kate part editor which then can be used to create the file // otherwise we could end up opening e.g. okteta which then crashes, see: https://bugs.kde.org/id=326434 mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } else { mimeType = QMimeDatabase().mimeTypeForUrl(url); if(!url.isLocalFile() && mimeType.isDefault()) { // fall back to text/plain, for remote files without extension, i.e. COPYING, LICENSE, ... // using a synchronous KIO::MimetypeJob is hazardous and may lead to repeated calls to // this function without it having returned in the first place // and this function is *not* reentrant, see assert below: // Q_ASSERT(!documents.contains(url) || documents[url]==doc); mimeType = QMimeDatabase().mimeTypeForName(QStringLiteral("text/plain")); } } // is the URL pointing to a directory? if (mimeType.inherits(QStringLiteral("inode/directory"))) { qCDebug(SHELL) << "cannot open directory:" << url.url(); return nullptr; } if( prefName.isEmpty() ) { // Try to find a plugin that handles this mimetype QVariantMap constraints; constraints.insert(QStringLiteral("X-KDevelop-SupportedMimeTypes"), mimeType.name()); Core::self()->pluginController()->pluginForExtension(QString(), QString(), constraints); } if( IDocumentFactory* factory = factories.value(mimeType.name())) { doc = factory->create(url, Core::self()); } if(!doc) { if( !prefName.isEmpty() ) { doc = new PartDocument(url, Core::self(), prefName); } else if ( Core::self()->partControllerInternal()->isTextType(mimeType)) { doc = new TextDocument(url, Core::self(), _encoding); } else if( Core::self()->partControllerInternal()->canCreatePart(url) ) { doc = new PartDocument(url, Core::self()); } else { int openAsText = KMessageBox::questionYesNo(nullptr, i18n("KDevelop could not find the editor for file '%1' of type %2.\nDo you want to open it as plain text?", url.fileName(), mimeType.name()), i18nc("@title:window", "Could Not Find Editor"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("AskOpenWithTextEditor")); if (openAsText == KMessageBox::Yes) doc = new TextDocument(url, Core::self(), _encoding); else return nullptr; } } } // The url in the document must equal the current url, else the housekeeping will get broken Q_ASSERT(!doc || doc->url() == url); if(doc && openDocumentInternal(doc, range, activationParams, buddy)) return doc; else return nullptr; } bool openDocumentInternal(IDocument* doc, const KTextEditor::Range& range, DocumentController::DocumentActivationParams activationParams, IDocument* buddy = nullptr) { IDocument* previousActiveDocument = controller->activeDocument(); KTextEditor::View* previousActiveTextView = ICore::self()->documentController()->activeTextDocumentView(); KTextEditor::Cursor previousActivePosition; if(previousActiveTextView) previousActivePosition = previousActiveTextView->cursorPosition(); QUrl url=doc->url(); UiController *uiController = Core::self()->uiControllerInternal(); Sublime::Area *area = uiController->activeArea(); //We can't have the same url in many documents //so we check it's already the same if it exists //contains=>it's the same Q_ASSERT(!documents.contains(url) || documents[url]==doc); auto *sdoc = dynamic_cast(doc); if( !sdoc ) { documents.remove(url); delete doc; return false; } //react on document deletion - we need to cleanup controller structures QObject::connect(sdoc, &Sublime::Document::aboutToDelete, controller, &DocumentController::notifyDocumentClosed); //We check if it was already opened before bool emitOpened = !documents.contains(url); if(emitOpened) documents[url]=doc; if (!activationParams.testFlag(IDocumentController::DoNotCreateView)) { //find a view if there's one already opened in this area Sublime::AreaIndex* activeViewIdx = area->indexOf(uiController->activeSublimeWindow()->activeView()); const auto& views = sdoc->views(); auto it = std::find_if(views.begin(), views.end(), [&](Sublime::View* view) { Sublime::AreaIndex* areaIdx = area->indexOf(view); return (areaIdx && areaIdx == activeViewIdx); }); Sublime::View* partView = (it != views.end()) ? *it : nullptr; bool addView = false; if (!partView) { //no view currently shown for this url partView = sdoc->createView(); addView = true; } if(addView) { // This code is never executed when restoring session on startup, // only when opening a file manually Sublime::View* buddyView = nullptr; bool placeAfterBuddy = true; if(Core::self()->uiControllerInternal()->arrangeBuddies() && !buddy && doc->mimeType().isValid()) { // If buddy is not set, look for a (usually) plugin which handles this URL's mimetype // and use its IBuddyDocumentFinder, if available, to find a buddy document QString mime = doc->mimeType().name(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); if(buddyFinder) { buddy = findBuddyDocument(url, buddyFinder); if(buddy) { placeAfterBuddy = buddyFinder->buddyOrder(buddy->url(), doc->url()); } } } if(buddy) { auto* sublimeDocBuddy = dynamic_cast(buddy); if(sublimeDocBuddy) { Sublime::AreaIndex *pActiveViewIndex = area->indexOf(uiController->activeSublimeWindow()->activeView()); if(pActiveViewIndex) { // try to find existing View of buddy document in current active view's tab const auto& activeAreaViews = pActiveViewIndex->views(); const auto& buddyViews = sublimeDocBuddy->views(); auto it = std::find_if(activeAreaViews.begin(), activeAreaViews.end(), [&](Sublime::View* view) { return buddyViews.contains(view); }); if (it != activeAreaViews.end()) { buddyView = *it; } } } } // add view to the area if(buddyView && area->indexOf(buddyView)) { if(placeAfterBuddy) { // Adding new view after buddy view, simple case area->addView(partView, area->indexOf(buddyView), buddyView); } else { // First new view, then buddy view area->addView(partView, area->indexOf(buddyView), buddyView); // move buddyView tab after the new document area->removeView(buddyView); area->addView(buddyView, area->indexOf(partView), partView); } } else { // no buddy found for new document / plugin does not support buddies / buddy feature disabled Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); Sublime::UrlDocument *activeDoc = nullptr; IBuddyDocumentFinder *buddyFinder = nullptr; if(activeView) activeDoc = qobject_cast(activeView->document()); if(activeDoc && Core::self()->uiControllerInternal()->arrangeBuddies()) { QString mime = QMimeDatabase().mimeTypeForUrl(activeDoc->url()).name(); buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); } if(Core::self()->uiControllerInternal()->openAfterCurrent() && Core::self()->uiControllerInternal()->arrangeBuddies() && buddyFinder) { // Check if active document's buddy is directly next to it. // For example, we have the already-open tabs | *foo.h* | foo.cpp | , foo.h is active. // When we open a new document here (and the buddy feature is enabled), // we do not want to separate foo.h and foo.cpp, so we take care and avoid this. Sublime::AreaIndex *activeAreaIndex = area->indexOf(activeView); int pos = activeAreaIndex->views().indexOf(activeView); Sublime::View *afterActiveView = activeAreaIndex->views().value(pos+1, nullptr); Sublime::UrlDocument *activeDoc = nullptr, *afterActiveDoc = nullptr; if(activeView && afterActiveView) { activeDoc = qobject_cast(activeView->document()); afterActiveDoc = qobject_cast(afterActiveView->document()); } if(activeDoc && afterActiveDoc && buddyFinder->areBuddies(activeDoc->url(), afterActiveDoc->url())) { // don't insert in between of two buddies, but after them area->addView(partView, activeAreaIndex, afterActiveView); } else { // The active document's buddy is not directly after it // => no problem, insert after active document area->addView(partView, activeView); } } else { // Opening as last tab won't disturb our buddies // Same, if buddies are disabled, we needn't care about them. // this method places the tab according to openAfterCurrent() area->addView(partView, activeView); } } } if (!activationParams.testFlag(IDocumentController::DoNotActivate)) { uiController->activeSublimeWindow()->activateView( partView, !activationParams.testFlag(IDocumentController::DoNotFocus)); } if (!activationParams.testFlag(IDocumentController::DoNotAddToRecentOpen) && !controller->isEmptyDocumentUrl(url)) { fileOpenRecent->addUrl( url ); } if( range.isValid() ) { if (range.isEmpty()) doc->setCursorPosition( range.start() ); else doc->setTextSelection( range ); } } // Deferred signals, wait until it's all ready first if( emitOpened ) { emit controller->documentOpened( doc ); } if (!activationParams.testFlag(IDocumentController::DoNotActivate) && doc != controller->activeDocument()) emit controller->documentActivated( doc ); saveAll->setEnabled(true); revertAll->setEnabled(true); close->setEnabled(true); closeAll->setEnabled(true); closeAllOthers->setEnabled(true); KTextEditor::Cursor activePosition; if(range.isValid()) activePosition = range.start(); else if(KTextEditor::View* v = doc->activeTextView()) activePosition = v->cursorPosition(); if (doc != previousActiveDocument || activePosition != previousActivePosition) emit controller->documentJumpPerformed(doc, activePosition, previousActiveDocument, previousActivePosition); return true; } DocumentController* const controller; QPointer saveAll; QPointer revertAll; QPointer close; QPointer closeAll; QPointer closeAllOthers; KRecentFilesAction* fileOpenRecent; KTextEditor::Document* globalTextEditorInstance; }; Q_DECLARE_TYPEINFO(KDevelop::DocumentControllerPrivate::HistoryEntry, Q_MOVABLE_TYPE); DocumentController::DocumentController( QObject *parent ) : IDocumentController( parent ) , d_ptr(new DocumentControllerPrivate(this)) { - Q_D(DocumentController); - setObjectName(QStringLiteral("DocumentController")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/DocumentController"), this, QDBusConnection::ExportScriptableSlots ); connect(this, &DocumentController::documentUrlChanged, this, [this] (IDocument* document) { Q_D(DocumentController); d->changeDocumentUrl(document); }); if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); } void DocumentController::initialize() { } void DocumentController::cleanup() { Q_D(DocumentController); if (d->fileOpenRecent) d->fileOpenRecent->saveEntries( KConfigGroup(KSharedConfig::openConfig(), "Recent Files" ) ); // Close all documents without checking if they should be saved. // This is because the user gets a chance to save them during MainWindow::queryClose. const auto documents = openDocuments(); for (IDocument* doc : documents) { doc->close(IDocument::Discard); } } DocumentController::~DocumentController() = default; void DocumentController::setupActions() { Q_D(DocumentController); KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction* action; action = ac->addAction( QStringLiteral("file_open") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_O ); action->setText(i18n( "&Open..." ) ); connect(action, &QAction::triggered, this, [this] { Q_D(DocumentController); d->chooseDocument(); } ); action->setToolTip( i18n( "Open file" ) ); action->setWhatsThis( i18n( "Opens a file for editing." ) ); d->fileOpenRecent = KStandardAction::openRecent(this, SLOT(slotOpenDocument(QUrl)), ac); d->fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); d->fileOpenRecent->loadEntries( KConfigGroup(KSharedConfig::openConfig(), "Recent Files" ) ); action = d->saveAll = ac->addAction( QStringLiteral("file_save_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); action->setText(i18n( "Save Al&l" ) ); connect( action, &QAction::triggered, this, &DocumentController::slotSaveAllDocuments ); action->setToolTip( i18n( "Save all open documents" ) ); action->setWhatsThis( i18n( "Save all open documents, prompting for additional information when necessary." ) ); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L) ); action->setEnabled(false); action = d->revertAll = ac->addAction( QStringLiteral("file_revert_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-revert"))); action->setText(i18n( "Reload All" ) ); connect( action, &QAction::triggered, this, &DocumentController::reloadAllDocuments ); action->setToolTip( i18n( "Revert all open documents" ) ); action->setWhatsThis( i18n( "Revert all open documents, returning to the previously saved state." ) ); action->setEnabled(false); action = d->close = ac->addAction( QStringLiteral("file_close") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_W ); action->setText( i18n( "&Close" ) ); connect( action, &QAction::triggered, this, &DocumentController::fileClose ); action->setToolTip( i18n( "Close file" ) ); action->setWhatsThis( i18n( "Closes current file." ) ); action->setEnabled(false); action = d->closeAll = ac->addAction( QStringLiteral("file_close_all") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); action->setText(i18n( "Clos&e All" ) ); connect( action, &QAction::triggered, this, &DocumentController::closeAllDocuments ); action->setToolTip( i18n( "Close all open documents" ) ); action->setWhatsThis( i18n( "Close all open documents, prompting for additional information when necessary." ) ); action->setEnabled(false); action = d->closeAllOthers = ac->addAction( QStringLiteral("file_closeother") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-close"))); ac->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_W ); action->setText(i18n( "Close All Ot&hers" ) ); connect( action, &QAction::triggered, this, &DocumentController::closeAllOtherDocuments ); action->setToolTip( i18n( "Close all other documents" ) ); action->setWhatsThis( i18n( "Close all open documents, with the exception of the currently active document." ) ); action->setEnabled(false); action = ac->addAction( QStringLiteral("vcsannotate_current_document") ); connect( action, &QAction::triggered, this, &DocumentController::vcsAnnotateCurrentDocument ); action->setText( i18n( "Show Annotate on current document") ); action->setIconText( i18n( "Annotate" ) ); action->setIcon( QIcon::fromTheme(QStringLiteral("user-properties")) ); } void DocumentController::slotOpenDocument(const QUrl &url) { openDocument(url); } IDocument* DocumentController::openDocumentFromText( const QString& data ) { IDocument* d = openDocument(nextEmptyDocumentUrl()); Q_ASSERT(d->textDocument()); d->textDocument()->setText( data ); return d; } bool DocumentController::openDocumentFromTextSimple( QString text ) { return (bool)openDocumentFromText( text ); } bool DocumentController::openDocumentSimple( QString url, int line, int column ) { return (bool)openDocument( QUrl::fromUserInput(url), KTextEditor::Cursor( line, column ) ); } IDocument* DocumentController::openDocument( const QUrl& inputUrl, const QString& prefName ) { Q_D(DocumentController); return d->openDocumentInternal( inputUrl, prefName ); } IDocument* DocumentController::openDocument( const QUrl & inputUrl, const KTextEditor::Range& range, DocumentActivationParams activationParams, const QString& encoding, IDocument* buddy) { Q_D(DocumentController); return d->openDocumentInternal(inputUrl, QString(), range, encoding, activationParams, buddy); } bool DocumentController::openDocument(IDocument* doc, const KTextEditor::Range& range, DocumentActivationParams activationParams, IDocument* buddy) { Q_D(DocumentController); return d->openDocumentInternal( doc, range, activationParams, buddy); } void DocumentController::fileClose() { IDocument *activeDoc = activeDocument(); if (activeDoc) { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); uiController->activeArea()->closeView(activeView); } } bool DocumentController::closeDocument( const QUrl &url ) { Q_D(DocumentController); const auto documentIt = d->documents.constFind(url); if (documentIt == d->documents.constEnd()) return false; //this will remove all views and after the last view is removed, the //document will be self-destructed and removeDocument() slot will catch that //and clean up internal data structures (*documentIt)->close(); return true; } void DocumentController::notifyDocumentClosed(Sublime::Document* doc_) { Q_D(DocumentController); auto* doc = dynamic_cast(doc_); Q_ASSERT(doc); d->removeDocument(doc_); if (d->documents.isEmpty()) { if (d->saveAll) d->saveAll->setEnabled(false); if (d->revertAll) d->revertAll->setEnabled(false); if (d->close) d->close->setEnabled(false); if (d->closeAll) d->closeAll->setEnabled(false); if (d->closeAllOthers) d->closeAllOthers->setEnabled(false); } emit documentClosed(doc); } IDocument * DocumentController::documentForUrl( const QUrl & dirtyUrl ) const { Q_D(const DocumentController); if (dirtyUrl.isEmpty()) { return nullptr; } Q_ASSERT(!dirtyUrl.isRelative()); Q_ASSERT(!dirtyUrl.fileName().isEmpty() || !dirtyUrl.isLocalFile()); //Fix urls that might not be normalized return d->documents.value( dirtyUrl.adjusted( QUrl::NormalizePathSegments ), nullptr ); } QList DocumentController::openDocuments() const { Q_D(const DocumentController); QList opened; for (IDocument* doc : qAsConst(d->documents)) { auto *sdoc = dynamic_cast(doc); if( !sdoc ) { continue; } if (!sdoc->views().isEmpty()) opened << doc; } return opened; } void DocumentController::activateDocument( IDocument * document, const KTextEditor::Range& range ) { // TODO avoid some code in openDocument? Q_ASSERT(document); openDocument(document->url(), range, IDocumentController::DoNotAddToRecentOpen); } void DocumentController::slotSaveAllDocuments() { saveAllDocuments(IDocument::Silent); } bool DocumentController::saveAllDocuments(IDocument::DocumentSaveMode mode) { return saveSomeDocuments(openDocuments(), mode); } bool KDevelop::DocumentController::saveSomeDocuments(const QList< IDocument * > & list, IDocument::DocumentSaveMode mode) { if (mode & IDocument::Silent) { const auto documents = modifiedDocuments(list); for (IDocument* doc : documents) { if( !DocumentController::isEmptyDocumentUrl(doc->url()) && !doc->save(mode) ) { if( doc ) qCWarning(SHELL) << "!! Could not save document:" << doc->url(); else qCWarning(SHELL) << "!! Could not save document as its NULL"; } // TODO if (!ret) showErrorDialog() ? } } else { // Ask the user which documents to save QList checkSave = modifiedDocuments(list); if (!checkSave.isEmpty()) { ScopedDialog dialog(checkSave, qApp->activeWindow()); return dialog->exec(); } } return true; } QList< IDocument * > KDevelop::DocumentController::visibleDocumentsInWindow(MainWindow * mw) const { // Gather a list of all documents which do have a view in the given main window // Does not find documents which are open in inactive areas QList list; const auto documents = openDocuments(); for (IDocument* doc : documents) { if (auto* sdoc = dynamic_cast(doc)) { const auto views = sdoc->views(); auto hasViewInWindow = std::any_of(views.begin(), views.end(), [&](Sublime::View* view) { return (view->hasWidget() && view->widget()->window() == mw); }); if (hasViewInWindow) { list.append(doc); } } } return list; } QList< IDocument * > KDevelop::DocumentController::documentsExclusivelyInWindow(MainWindow * mw, bool currentAreaOnly) const { // Gather a list of all documents which have views only in the given main window QList checkSave; const auto documents = openDocuments(); for (IDocument* doc : documents) { if (auto* sdoc = dynamic_cast(doc)) { bool inOtherWindow = false; const auto views = sdoc->views(); for (Sublime::View* view : views) { const auto windows = Core::self()->uiControllerInternal()->mainWindows(); for (Sublime::MainWindow* window : windows) { if(window->containsView(view) && (window != mw || (currentAreaOnly && window == mw && !mw->area()->views().contains(view)))) { inOtherWindow = true; break; } } if (inOtherWindow) { break; } } if (!inOtherWindow) checkSave.append(doc); } } return checkSave; } QList< IDocument * > KDevelop::DocumentController::modifiedDocuments(const QList< IDocument * > & list) const { QList< IDocument * > ret; for (IDocument* doc : list) { if (doc->state() == IDocument::Modified || doc->state() == IDocument::DirtyAndModified) ret.append(doc); } return ret; } bool DocumentController::saveAllDocumentsForWindow(KParts::MainWindow* mw, KDevelop::IDocument::DocumentSaveMode mode, bool currentAreaOnly) { QList checkSave = documentsExclusivelyInWindow(qobject_cast(mw), currentAreaOnly); return saveSomeDocuments(checkSave, mode); } void DocumentController::reloadAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { const QList views = visibleDocumentsInWindow(qobject_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; for (IDocument* doc : views) { if(!isEmptyDocumentUrl(doc->url())) doc->reload(); } } } bool DocumentController::closeAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { const QList views = visibleDocumentsInWindow(qobject_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return false; for (IDocument* doc : views) { doc->close(IDocument::Discard); } } return true; } void DocumentController::closeAllOtherDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { Sublime::View* activeView = mw->activeView(); if (!activeView) { qCWarning(SHELL) << "Shouldn't there always be an active view when this function is called?"; return; } // Deal with saving unsaved solo views QList soloViews = documentsExclusivelyInWindow(qobject_cast(mw)); soloViews.removeAll(dynamic_cast(activeView->document())); if (!saveSomeDocuments(soloViews, IDocument::Default)) // User cancelled or other error return; const auto views = mw->area()->views(); for (Sublime::View* view : views) { if (view != activeView) mw->area()->closeView(view); } activeView->widget()->setFocus(); } } IDocument* DocumentController::activeDocument() const { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::MainWindow* mw = uiController->activeSublimeWindow(); if( !mw || !mw->activeView() ) return nullptr; return dynamic_cast(mw->activeView()->document()); } KTextEditor::View* DocumentController::activeTextDocumentView() const { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::MainWindow* mw = uiController->activeSublimeWindow(); if( !mw || !mw->activeView() ) return nullptr; auto* view = qobject_cast(mw->activeView()); if(!view) return nullptr; return view->textView(); } QString DocumentController::activeDocumentPath( const QString& target ) const { if(!target.isEmpty()) { const auto projects = Core::self()->projectController()->projects(); for (IProject* project : projects) { if(project->name().startsWith(target, Qt::CaseInsensitive)) { return project->path().pathOrUrl() + QLatin1String("/."); } } } IDocument* doc = activeDocument(); if(!doc || target == QStringLiteral("[selection]")) { Context* selection = ICore::self()->selectionController()->currentSelection(); if(selection && selection->type() == Context::ProjectItemContext && !static_cast(selection)->items().isEmpty()) { QString ret = static_cast(selection)->items().at(0)->path().pathOrUrl(); if(static_cast(selection)->items().at(0)->folder()) ret += QStringLiteral("/."); return ret; } return QString(); } return doc->url().toString(); } QStringList DocumentController::activeDocumentPaths() const { UiController *uiController = Core::self()->uiControllerInternal(); if( !uiController->activeSublimeWindow() ) return QStringList(); QSet documents; const auto views = uiController->activeSublimeWindow()->area()->views(); for (Sublime::View* view : views) { documents.insert(view->document()->documentSpecifier()); } return documents.toList(); } void DocumentController::registerDocumentForMimetype( const QString& mimetype, KDevelop::IDocumentFactory* factory ) { Q_D(DocumentController); if( !d->factories.contains( mimetype ) ) d->factories[mimetype] = factory; } QStringList DocumentController::documentTypes() const { return QStringList() << QStringLiteral("Text"); } static const QRegularExpression& emptyDocumentPattern() { static const QRegularExpression pattern(QStringLiteral("^/%1(?:\\s\\((\\d+)\\))?$").arg(EMPTY_DOCUMENT_URL)); return pattern; } bool DocumentController::isEmptyDocumentUrl(const QUrl &url) { return emptyDocumentPattern().match(url.toDisplayString(QUrl::PreferLocalFile)).hasMatch(); } QUrl DocumentController::nextEmptyDocumentUrl() { int nextEmptyDocNumber = 0; const auto& pattern = emptyDocumentPattern(); const auto openDocuments = Core::self()->documentControllerInternal()->openDocuments(); for (IDocument* doc : openDocuments) { if (DocumentController::isEmptyDocumentUrl(doc->url())) { const auto match = pattern.match(doc->url().toDisplayString(QUrl::PreferLocalFile)); if (match.hasMatch()) { const int num = match.captured(1).toInt(); nextEmptyDocNumber = qMax(nextEmptyDocNumber, num + 1); } else { nextEmptyDocNumber = qMax(nextEmptyDocNumber, 1); } } } QUrl url; if (nextEmptyDocNumber > 0) url = QUrl::fromLocalFile(QStringLiteral("/%1 (%2)").arg(EMPTY_DOCUMENT_URL).arg(nextEmptyDocNumber)); else url = QUrl::fromLocalFile(QLatin1Char('/') + EMPTY_DOCUMENT_URL); return url; } IDocumentFactory* DocumentController::factory(const QString& mime) const { Q_D(const DocumentController); return d->factories.value(mime); } KTextEditor::Document* DocumentController::globalTextEditorInstance() { Q_D(DocumentController); if(!d->globalTextEditorInstance) d->globalTextEditorInstance = Core::self()->partControllerInternal()->createTextPart(); return d->globalTextEditorInstance; } bool DocumentController::openDocumentsSimple( QStringList urls ) { Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); Sublime::AreaIndex* areaIndex = area->rootIndex(); QList topViews = static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->topViews(); if(Sublime::View* activeView = Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()) areaIndex = area->indexOf(activeView); qCDebug(SHELL) << "opening " << urls << " to area " << area << " index " << areaIndex << " with children " << areaIndex->first() << " " << areaIndex->second(); bool isFirstView = true; bool ret = openDocumentsWithSplitSeparators( areaIndex, urls, isFirstView ); qCDebug(SHELL) << "area arch. after opening: " << areaIndex->print(); // Required because sublime sometimes doesn't update correctly when the area-index contents has been changed // (especially when views have been moved to other indices, through unsplit, split, etc.) static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->reconstructViews(topViews); return ret; } bool DocumentController::openDocumentsWithSplitSeparators( Sublime::AreaIndex* index, QStringList urlsWithSeparators, bool& isFirstView ) { qCDebug(SHELL) << "opening " << urlsWithSeparators << " index " << index << " with children " << index->first() << " " << index->second() << " view-count " << index->viewCount(); if(urlsWithSeparators.isEmpty()) return true; Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); QList topLevelSeparators; // Indices of the top-level separators (with groups skipped) const QStringList separators {QStringLiteral("/"), QStringLiteral("-")}; QList groups; bool ret = true; { int parenDepth = 0; int groupStart = 0; for(int pos = 0; pos < urlsWithSeparators.size(); ++pos) { QString item = urlsWithSeparators[pos]; if(separators.contains(item)) { if(parenDepth == 0) topLevelSeparators << pos; }else if(item == QLatin1String("[")) { if(parenDepth == 0) groupStart = pos+1; ++parenDepth; } else if(item == QLatin1String("]")) { if(parenDepth > 0) { --parenDepth; if(parenDepth == 0) groups << urlsWithSeparators.mid(groupStart, pos-groupStart); } else{ qCDebug(SHELL) << "syntax error in " << urlsWithSeparators << ": parens do not match"; ret = false; } }else if(parenDepth == 0) { groups << (QStringList() << item); } } } if(topLevelSeparators.isEmpty()) { if(urlsWithSeparators.size() > 1) { for (const QStringList& group : qAsConst(groups)) { ret &= openDocumentsWithSplitSeparators( index, group, isFirstView ); } }else{ while(index->isSplit()) index = index->first(); // Simply open the document into the area index IDocument* doc = Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(urlsWithSeparators.front()), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); auto *sublimeDoc = dynamic_cast(doc); if (sublimeDoc) { Sublime::View* view = sublimeDoc->createView(); area->addView(view, index); if(isFirstView) { static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->activateView(view); isFirstView = false; } }else{ ret = false; } } return ret; } // Pick a separator in the middle int pickSeparator = topLevelSeparators[topLevelSeparators.size()/2]; bool activeViewToSecondChild = false; if(pickSeparator == urlsWithSeparators.size()-1) { // There is no right child group, so the right side should be filled with the currently active views activeViewToSecondChild = true; }else{ QStringList separatorsAndParens = separators; separatorsAndParens << QStringLiteral("[") << QStringLiteral("]"); // Check if the second child-set contains an unterminated separator, which means that the active views should end up there for(int pos = pickSeparator+1; pos < urlsWithSeparators.size(); ++pos) if( separators.contains(urlsWithSeparators[pos]) && (pos == urlsWithSeparators.size()-1 || separatorsAndParens.contains(urlsWithSeparators[pos-1])) ) activeViewToSecondChild = true; } Qt::Orientation orientation = urlsWithSeparators[pickSeparator] == QLatin1String("/") ? Qt::Horizontal : Qt::Vertical; if(!index->isSplit()) { qCDebug(SHELL) << "splitting " << index << "orientation" << orientation << "to second" << activeViewToSecondChild; index->split(orientation, activeViewToSecondChild); }else{ index->setOrientation(orientation); qCDebug(SHELL) << "WARNING: Area is already split (shouldn't be)" << urlsWithSeparators; } openDocumentsWithSplitSeparators( index->first(), urlsWithSeparators.mid(0, pickSeparator) , isFirstView ); if(pickSeparator != urlsWithSeparators.size() - 1) openDocumentsWithSplitSeparators( index->second(), urlsWithSeparators.mid(pickSeparator+1, urlsWithSeparators.size() - (pickSeparator+1) ), isFirstView ); // Clean up the child-indices, because document-loading may fail if(!index->first()->viewCount() && !index->first()->isSplit()) { qCDebug(SHELL) << "unsplitting first"; index->unsplit(index->first()); } else if(!index->second()->viewCount() && !index->second()->isSplit()) { qCDebug(SHELL) << "unsplitting second"; index->unsplit(index->second()); } return ret; } void DocumentController::vcsAnnotateCurrentDocument() { IDocument* doc = activeDocument(); if (!doc) return; QUrl url = doc->url(); IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { auto* iface = project->versionControlPlugin()->extension(); auto helper = new VcsPluginHelper(project->versionControlPlugin(), iface); connect(doc->textDocument(), &KTextEditor::Document::aboutToClose, helper, QOverload::of(&VcsPluginHelper::disposeEventually)); Q_ASSERT(qobject_cast(doc->activeTextView())); // can't use new signal slot syntax here, AnnotationViewInterface is not a QObject connect(doc->activeTextView(), SIGNAL(annotationBorderVisibilityChanged(View*,bool)), helper, SLOT(disposeEventually(View*,bool))); helper->addContextDocument(url); helper->annotation(); } else { KMessageBox::error(nullptr, i18n("Could not annotate the document because it is not " "part of a version-controlled project.")); } } #include "moc_documentcontroller.cpp" diff --git a/kdevplatform/shell/environmentconfigurebutton.cpp b/kdevplatform/shell/environmentconfigurebutton.cpp index 5877abdcda..525b8e1cde 100644 --- a/kdevplatform/shell/environmentconfigurebutton.cpp +++ b/kdevplatform/shell/environmentconfigurebutton.cpp @@ -1,110 +1,108 @@ /* This file is part of KDevelop Copyright 2010 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 "environmentconfigurebutton.h" #include #include "settings/environmentpreferences.h" #include #include #include #include #include #include #include namespace KDevelop { class EnvironmentConfigureButtonPrivate { public: explicit EnvironmentConfigureButtonPrivate(EnvironmentConfigureButton* _q) : q(_q), selectionWidget(nullptr) { } void showDialog() { ScopedDialog dlg(qApp->activeWindow()); QString selected; if (selectionWidget) { selected = selectionWidget->effectiveProfileName(); } auto prefs = new EnvironmentPreferences(selected, q); // TODO: This should be implicit when constructing EnvironmentPreferences prefs->initConfigManager(); prefs->reset(); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QObject::connect(buttonBox, &QDialogButtonBox::accepted, dlg.data(), &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, dlg.data(), &QDialog::reject); auto layout = new QVBoxLayout; layout->addWidget(prefs); layout->addWidget(buttonBox); dlg->setLayout(layout); dlg->setWindowTitle(prefs->fullName()); dlg->setWindowIcon(prefs->icon()); dlg->resize(800, 600); if (dlg->exec() == QDialog::Accepted) { prefs->apply(); emit q->environmentConfigured(); } } EnvironmentConfigureButton* const q; EnvironmentSelectionWidget *selectionWidget; }; EnvironmentConfigureButton::EnvironmentConfigureButton(QWidget* parent) : QPushButton(parent), d_ptr(new EnvironmentConfigureButtonPrivate(this)) { - Q_D(EnvironmentConfigureButton); - setText(QString()); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); setIcon(QIcon::fromTheme(QStringLiteral("configure"))); setToolTip(i18n("Configure environment variables")); connect(this, &EnvironmentConfigureButton::clicked, this, [this] { Q_D(EnvironmentConfigureButton); d->showDialog(); }); } EnvironmentConfigureButton::~EnvironmentConfigureButton() = default; void EnvironmentConfigureButton::setSelectionWidget(EnvironmentSelectionWidget* widget) { Q_D(EnvironmentConfigureButton); connect(this, &EnvironmentConfigureButton::environmentConfigured, widget, &EnvironmentSelectionWidget::reconfigure); d->selectionWidget = widget; } } #include "moc_environmentconfigurebutton.cpp" diff --git a/kdevplatform/shell/projectcontroller.cpp b/kdevplatform/shell/projectcontroller.cpp index 983a954060..3db4373c24 100644 --- a/kdevplatform/shell/projectcontroller.cpp +++ b/kdevplatform/shell/projectcontroller.cpp @@ -1,1367 +1,1365 @@ /* 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) { 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())); 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) { - Q_D(ProjectController); - // 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/sessioncontroller.cpp b/kdevplatform/shell/sessioncontroller.cpp index 354ef60103..84bd07df6f 100644 --- a/kdevplatform/shell/sessioncontroller.cpp +++ b/kdevplatform/shell/sessioncontroller.cpp @@ -1,699 +1,697 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessioncontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "session.h" #include "core.h" #include "uicontroller.h" #include "shellextension.h" #include "sessionlock.h" #include "sessionchooserdialog.h" #include "debug.h" #include #include #include namespace KDevelop { namespace { int argc = 0; char** argv = nullptr; } void SessionController::setArguments(int _argc, char** _argv) { argc = _argc; argv = _argv; } static QStringList standardArguments() { QStringList ret; for(int a = 0; a < argc; ++a) { QString arg = QString::fromLocal8Bit(argv[a]); if(arg.startsWith(QLatin1String("-graphicssystem")) || arg.startsWith(QLatin1String("-style"))) { ret << QLatin1Char('-') + arg; if(a+1 < argc) ret << QString::fromLocal8Bit(argv[a+1]); } } return ret; } class SessionControllerPrivate : public QObject { Q_OBJECT public: explicit SessionControllerPrivate( SessionController* s ) : q(s) , activeSession(nullptr) , grp(nullptr) { } ~SessionControllerPrivate() override { } Session* findSessionForName( const QString& name ) const { for (auto it = sessionActions.begin(), end = sessionActions.end(); it != end; ++it) { Session* s = it.key(); if( s->name() == name ) return s; } return nullptr; } Session* findSessionForId(const QString& idString) const { QUuid id(idString); for (auto it = sessionActions.begin(), end = sessionActions.end(); it != end; ++it) { Session* s = it.key(); if( s->id() == id) return s; } return nullptr; } void newSession() { qsrand(QDateTime::currentDateTimeUtc().toTime_t()); Session* session = new Session( QUuid::createUuid().toString() ); KProcess::startDetached(ShellExtension::getInstance()->executableFilePath(), QStringList() << QStringLiteral("-s") << session->id().toString() << standardArguments()); delete session; #if 0 //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 } void deleteCurrentSession() { int choice = KMessageBox::warningContinueCancel(Core::self()->uiController()->activeMainWindow(), i18n("The current session and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?")); if(choice == KMessageBox::Continue) { q->deleteSessionFromDisk(sessionLock); q->emitQuitSession(); } } void renameSession() { bool ok; auto newSessionName = QInputDialog::getText(Core::self()->uiController()->activeMainWindow(), i18n("Rename Session"), i18n("New Session Name:"), QLineEdit::Normal, q->activeSession()->name(), &ok); if (ok) { static_cast(q->activeSession())->setName(newSessionName); } q->updateXmlGuiActionList(); // resort } bool loadSessionExternally( Session* s ) { Q_ASSERT( s ); KProcess::startDetached(ShellExtension::getInstance()->executableFilePath(), QStringList() << QStringLiteral("-s") << s->id().toString() << standardArguments()); return true; } TryLockSessionResult activateSession( Session* s ) { Q_ASSERT( s ); activeSession = s; TryLockSessionResult result = SessionController::tryLockSession( s->id().toString()); if( !result.lock ) { activeSession = nullptr; return result; } Q_ASSERT(s->id().toString() == result.lock->id()); sessionLock = result.lock; KConfigGroup grp = KSharedConfig::openConfig()->group( SessionController::cfgSessionGroup() ); grp.writeEntry( SessionController::cfgActiveSessionEntry(), s->id().toString() ); grp.sync(); if (Core::self()->setupFlags() & Core::NoUi) return result; QHash::iterator it = sessionActions.find(s); Q_ASSERT( it != sessionActions.end() ); (*it)->setCheckable(true); (*it)->setChecked(true); for(it = sessionActions.begin(); it != sessionActions.end(); ++it) { if(it.key() != s) (*it)->setCheckable(false); } return result; } void loadSessionFromAction(QAction* action) { auto session = action->data().value(); loadSessionExternally(session); } void addSession( Session* s ) { if (Core::self()->setupFlags() & Core::NoUi) { sessionActions[s] = nullptr; return; } auto* a = new QAction( grp ); a->setText( s->description() ); a->setCheckable( false ); a->setData(QVariant::fromValue(s)); sessionActions[s] = a; q->actionCollection()->addAction(QLatin1String("session_") + s->id().toString(), a); connect( s, &Session::sessionUpdated, this, &SessionControllerPrivate::sessionUpdated ); sessionUpdated( s ); } SessionController* const q; QHash sessionActions; ISession* activeSession; QActionGroup* grp; ISessionLock::Ptr sessionLock; static QString sessionBaseDirectory() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + qApp->applicationName() + QLatin1String("/sessions/"); } QString ownSessionDirectory() const { Q_ASSERT(activeSession); return q->sessionDirectory( activeSession->id().toString() ); } private Q_SLOTS: void sessionUpdated( KDevelop::ISession* s ) { sessionActions[static_cast( s )]->setText( KStringHandler::rsqueeze(s->description()) ); } }; SessionController::SessionController( QObject *parent ) : QObject(parent) , d_ptr(new SessionControllerPrivate(this)) { Q_D(SessionController); setObjectName(QStringLiteral("SessionController")); setComponentName(QStringLiteral("kdevsession"), i18n("Session Manager")); setXMLFile(QStringLiteral("kdevsessionui.rc")); QDBusConnection::sessionBus().registerObject( QStringLiteral("/org/kdevelop/SessionController"), this, QDBusConnection::ExportScriptableSlots ); if (Core::self()->setupFlags() & Core::NoUi) return; QAction* action = actionCollection()->addAction(QStringLiteral("new_session")); connect(action, &QAction::triggered, this, [this] { Q_D(SessionController); d->newSession(); }); action->setText( i18nc("@action:inmenu", "Start New Session") ); action->setToolTip( i18nc("@info:tooltip", "Start a new KDevelop instance with an empty session") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); action = actionCollection()->addAction(QStringLiteral("rename_session")); connect(action, &QAction::triggered, this, [this] { Q_D(SessionController); d->renameSession(); }); action->setText( i18n("Rename Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); action = actionCollection()->addAction(QStringLiteral("delete_session")); connect(action, &QAction::triggered, this, [this] { Q_D(SessionController); d->deleteCurrentSession(); }); action->setText( i18n("Delete Current Session...") ); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action = actionCollection()->addAction( QStringLiteral("quit"), this, SIGNAL(quitSession()) ); action->setText( i18n("Quit") ); action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'Quit KDevelop...' actionCollection()->setDefaultShortcut( action, Qt::CTRL | Qt::Key_Q ); action->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); d->grp = new QActionGroup( this ); connect(d->grp, &QActionGroup::triggered, this, [this] (QAction* a) { Q_D(SessionController); d->loadSessionFromAction(a); } ); } SessionController::~SessionController() = default; void SessionController::startNewSession() { Q_D(SessionController); d->newSession(); } void SessionController::cleanup() { Q_D(SessionController); if (d->activeSession) { Q_ASSERT(d->activeSession->id().toString() == d->sessionLock->id()); if (d->activeSession->isTemporary()) { deleteSessionFromDisk(d->sessionLock); } d->activeSession = nullptr; } d->sessionLock.clear(); qDeleteAll(d->sessionActions); d->sessionActions.clear(); } void SessionController::initialize( const QString& session ) { Q_D(SessionController); QDir sessiondir( SessionControllerPrivate::sessionBaseDirectory() ); const auto sessionDirs = sessiondir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); for (const QString& s : sessionDirs) { QUuid id( s ); if( id.isNull() ) continue; // Only create sessions for directories that represent proper uuid's Session* ses = new Session( id.toString(), this ); //Delete sessions that have no name and are empty if( ses->containedProjects().isEmpty() && ses->name().isEmpty() && (session.isEmpty() || (ses->id().toString() != session && ses->name() != session)) ) { TryLockSessionResult result = tryLockSession(s); if (result.lock) { deleteSessionFromDisk(result.lock); } delete ses; } else { d->addSession( ses ); } } loadDefaultSession( session ); updateXmlGuiActionList(); } ISession* SessionController::activeSession() const { Q_D(const SessionController); return d->activeSession; } ISessionLock::Ptr SessionController::activeSessionLock() const { Q_D(const SessionController); return d->sessionLock; } void SessionController::loadSession( const QString& nameOrId ) { Q_D(SessionController); d->loadSessionExternally( session( nameOrId ) ); } QList SessionController::sessionNames() const { Q_D(const SessionController); QList l; const auto sessions = d->sessionActions.keys(); l.reserve(sessions.size()); for(const auto* s : sessions) { l << s->name(); } return l; } QList< const KDevelop::Session* > SessionController::sessions() const { Q_D(const SessionController); QList< const KDevelop::Session* > ret; const auto sessions = d->sessionActions.keys(); ret.reserve(sessions.size()); // turn to const pointers for (const auto* s : sessions) { ret << s; } return ret; } Session* SessionController::createSession( const QString& name ) { Q_D(SessionController); Session* s; if(name.startsWith(QLatin1Char('{'))) { s = new Session( QUuid(name).toString(), this ); }else{ qsrand(QDateTime::currentDateTimeUtc().toTime_t()); s = new Session( QUuid::createUuid().toString(), this ); s->setName( name ); } d->addSession( s ); updateXmlGuiActionList(); return s; } void SessionController::deleteSession( const ISessionLock::Ptr& lock ) { Q_D(SessionController); Session* s = session(lock->id()); QHash::iterator it = d->sessionActions.find(s); Q_ASSERT( it != d->sessionActions.end() ); unplugActionList( QStringLiteral("available_sessions") ); actionCollection()->removeAction(*it); if (d->grp) { // happens in unit tests d->grp->removeAction(*it); plugActionList( QStringLiteral("available_sessions"), d->grp->actions() ); } if (s == d->activeSession) { d->activeSession = nullptr; } deleteSessionFromDisk(lock); emit sessionDeleted( s->id().toString() ); d->sessionActions.remove(s); delete s; } void SessionController::deleteSessionFromDisk( const ISessionLock::Ptr& lock ) { qCDebug(SHELL) << "Deleting session:" << lock->id(); static_cast(lock.data())->removeFromDisk(); ItemRepositoryRegistry::deleteRepositoryFromDisk(DUChain::repositoryPathForSession(lock)); } void SessionController::loadDefaultSession( const QString& session ) { Q_D(SessionController); QString load = session; if (load.isEmpty()) { KConfigGroup grp = KSharedConfig::openConfig()->group( cfgSessionGroup() ); load = grp.readEntry( cfgActiveSessionEntry(), "default" ); } // Iteratively try to load the session, asking user what to do in case of failure // If showForceOpenDialog() returns empty string, stop trying do { Session* s = this->session(load); if( !s ) { s = createSession( load ); } TryLockSessionResult result = d->activateSession( s ); if( result.lock ) { Q_ASSERT(d->activeSession == s); Q_ASSERT(d->sessionLock = result.lock); break; } load = handleLockedSession( s->name(), s->id().toString(), result.runInfo ); } while( !load.isEmpty() ); } Session* SessionController::session( const QString& nameOrId ) const { Q_D(const SessionController); Session* ret = d->findSessionForName( nameOrId ); if(ret) return ret; return d->findSessionForId( nameOrId ); } QString SessionController::cloneSession( const QString& nameOrid ) { Q_D(SessionController); Session* origSession = session( nameOrid ); qsrand(QDateTime::currentDateTimeUtc().toTime_t()); QUuid id = QUuid::createUuid(); auto copyJob = KIO::copy(QUrl::fromLocalFile(sessionDirectory(origSession->id().toString())), QUrl::fromLocalFile(sessionDirectory( id.toString()))); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); copyJob->exec(); Session* newSession = new Session( id.toString() ); newSession->setName( i18n( "Copy of %1", origSession->name() ) ); d->addSession(newSession); updateXmlGuiActionList(); return newSession->name(); } void SessionController::updateXmlGuiActionList() { Q_D(SessionController); unplugActionList( QStringLiteral("available_sessions") ); if (d->grp) { auto actions = d->grp->actions(); std::sort(actions.begin(), actions.end(), [](const QAction* lhs, const QAction* rhs) { auto s1 = lhs->data().value(); auto s2 = rhs->data().value(); return QString::localeAwareCompare(s1->description(), s2->description()) < 0; }); plugActionList(QStringLiteral("available_sessions"), actions); } } QString SessionController::cfgSessionGroup() { return QStringLiteral("Sessions"); } QString SessionController::cfgActiveSessionEntry() { return QStringLiteral("Active Session ID"); } SessionInfos SessionController::availableSessionInfos() { SessionInfos sessionInfos; const auto sessionDirs = QDir(SessionControllerPrivate::sessionBaseDirectory()).entryList(QDir::AllDirs); sessionInfos.reserve(sessionDirs.size()); for (const QString& sessionId : sessionDirs) { if( !QUuid( sessionId ).isNull() ) { sessionInfos << Session::parse( sessionId ); } } sessionInfos.squeeze(); return sessionInfos; } QString SessionController::sessionDirectory(const QString& sessionId) { return SessionControllerPrivate::sessionBaseDirectory() + sessionId; } TryLockSessionResult SessionController::tryLockSession(const QString& id, bool doLocking) { return SessionLock::tryLockSession(id, doLocking); } bool SessionController::isSessionRunning(const QString& id) { return sessionRunInfo(id).isRunning; } SessionRunInfo SessionController::sessionRunInfo(const QString& id) { return SessionLock::tryLockSession(id, false).runInfo; } QString SessionController::showSessionChooserDialog(const QString& headerText, bool onlyRunning) { ///FIXME: move this code into sessiondialog.cpp auto* view = new QListView; auto* filter = new QLineEdit; filter->setClearButtonEnabled( true ); filter->setPlaceholderText(i18n("Search")); auto* model = new QStandardItemModel(view); auto *proxy = new QSortFilterProxyModel(model); proxy->setSourceModel(model); proxy->setFilterKeyColumn( 1 ); proxy->setFilterCaseSensitivity( Qt::CaseInsensitive ); connect(filter, &QLineEdit::textChanged, proxy, &QSortFilterProxyModel::setFilterFixedString); SessionChooserDialog dialog(view, proxy, filter); view->setEditTriggers(QAbstractItemView::NoEditTriggers); QVBoxLayout layout(dialog.mainWidget()); if(!headerText.isEmpty()) { QLabel* heading = new QLabel(headerText); QFont font = heading->font(); font.setBold(true); heading->setFont(font); layout.addWidget(heading); } model->setColumnCount(3); model->setHeaderData(0, Qt::Horizontal,i18n("Identity")); model->setHeaderData(1, Qt::Horizontal, i18n("Contents")); model->setHeaderData(2, Qt::Horizontal,i18n("State")); view->setModel(proxy); view->setModelColumn(1); auto* filterLayout = new QHBoxLayout(); filterLayout->addWidget(new QLabel(i18n("Filter:"))); filterLayout->addWidget(filter); layout.addLayout(filterLayout); layout.addWidget(view); filter->setFocus(); int row = 0; QString defaultSession = KSharedConfig::openConfig()->group( cfgSessionGroup() ).readEntry( cfgActiveSessionEntry(), "default" ); const auto availableSessionInfos = KDevelop::SessionController::availableSessionInfos(); for (const KDevelop::SessionInfo& si : availableSessionInfos) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } bool running = KDevelop::SessionController::isSessionRunning(si.uuid.toString()); if(onlyRunning && !running) continue; model->setItem(row, 0, new QStandardItem(si.uuid.toString())); model->setItem(row, 1, new QStandardItem(si.description)); model->setItem(row, 2, new QStandardItem); ++row; } model->sort(1); if(!onlyRunning) { model->setItem(row, 0, new QStandardItem); model->setItem(row, 1, new QStandardItem(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Create New Session"))); } dialog.updateState(); dialog.mainWidget()->layout()->setContentsMargins(0,0,0,0); const QModelIndex defaultSessionIndex = model->match(model->index(0, 0), Qt::DisplayRole, defaultSession, 1, Qt::MatchExactly).value(0); view->selectionModel()->setCurrentIndex(proxy->mapFromSource(defaultSessionIndex), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); ///@todo We need a way to get a proper size-hint from the view, but unfortunately, that only seems possible after the view was shown. dialog.resize(QSize(900, 600)); if(dialog.exec() != QDialog::Accepted) // krazy:exclude=crashy { return QString(); } QModelIndex selected = view->selectionModel()->currentIndex(); if (!selected.isValid()) return QString(); const QString selectedSessionId = selected.sibling(selected.row(), 0).data().toString(); if (selectedSessionId.isEmpty()) { // "Create New Session" item selected, return a fresh UUID qsrand(QDateTime::currentDateTimeUtc().toTime_t()); return QUuid::createUuid().toString(); } return selectedSessionId; } QString SessionController::handleLockedSession( const QString& sessionName, const QString& sessionId, const SessionRunInfo& runInfo ) { return SessionLock::handleLockedSession(sessionName, sessionId, runInfo); } QString SessionController::sessionDir() { Q_D(SessionController); if( !activeSession() ) return QString(); return d->ownSessionDirectory(); } QString SessionController::sessionName() { - Q_D(SessionController); - if(!activeSession()) return QString(); return activeSession()->description(); } } #include "sessioncontroller.moc" #include "moc_sessioncontroller.cpp" diff --git a/kdevplatform/shell/uicontroller.cpp b/kdevplatform/shell/uicontroller.cpp index 5390250655..76dba8cc94 100644 --- a/kdevplatform/shell/uicontroller.cpp +++ b/kdevplatform/shell/uicontroller.cpp @@ -1,792 +1,790 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "uicontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "configpage.h" #include "configdialog.h" #include "debug.h" #include "editorconfigpage.h" #include "shellextension.h" #include "plugincontroller.h" #include "mainwindow.h" #include "workingsetcontroller.h" #include "workingsets/workingset.h" #include "settings/bgpreferences.h" #include "settings/languagepreferences.h" #include "settings/environmentpreferences.h" #include "settings/pluginpreferences.h" #include "settings/projectpreferences.h" #include "settings/sourceformattersettings.h" #include "settings/uipreferences.h" #include "settings/templateconfig.h" #include "settings/analyzerspreferences.h" #include "settings/documentationpreferences.h" #include "settings/runtimespreferences.h" namespace KDevelop { class UiControllerPrivate { public: UiControllerPrivate(Core* core, UiController* controller) : core(core) , areasRestored(false) , m_controller(controller) { if (Core::self()->workingSetControllerInternal()) Core::self()->workingSetControllerInternal()->initializeController(m_controller); m_controller->connect(m_controller, &Sublime::Controller::mainWindowAdded, m_controller, &UiController::mainWindowAdded); QMap desired; desired[QStringLiteral("org.kdevelop.ClassBrowserView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.DocumentsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.FileManagerView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProblemReporterView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.OutputView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.ContextBrowser")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.SnippetView")] = Sublime::Right; desired[QStringLiteral("org.kdevelop.ExternalScriptView")] = Sublime::Right; Sublime::Area* a = new Sublime::Area(m_controller, QStringLiteral("code"), i18n("Code")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("document-edit")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.debugger.VariablesView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.debugger.BreakpointsView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.StackView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.ConsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; a = new Sublime::Area(m_controller, QStringLiteral("debug"), i18n("Debug")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("debug-run")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.PatchReview")] = Sublime::Bottom; a = new Sublime::Area(m_controller, QStringLiteral("review"), i18n("Review")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("text-x-patch")); m_controller->addDefaultArea(a); if(!(Core::self()->setupFlags() & Core::NoUi)) { defaultMainWindow = new MainWindow(m_controller); m_controller->addMainWindow(defaultMainWindow); activeSublimeWindow = defaultMainWindow; } else { activeSublimeWindow = defaultMainWindow = nullptr; } m_assistantTimer.setSingleShot(true); m_assistantTimer.setInterval(100); } void widgetChanged(QWidget*, QWidget* now) { if (now) { auto* win = qobject_cast(now->window()); if( win ) { activeSublimeWindow = win; } } } Core* const core; QPointer defaultMainWindow; QHash factoryDocuments; QPointer activeSublimeWindow; bool areasRestored; /// QWidget implementing IToolViewActionListener interface, or null QPointer activeActionListener; QTimer m_assistantTimer; private: UiController *m_controller; }; class UiToolViewFactory: public Sublime::ToolFactory { public: explicit UiToolViewFactory(IToolViewFactory *factory): m_factory(factory) {} ~UiToolViewFactory() override { delete m_factory; } QWidget* create(Sublime::ToolDocument *doc, QWidget *parent = nullptr) override { Q_UNUSED( doc ); return m_factory->create(parent); } QList< QAction* > contextMenuActions(QWidget* viewWidget) const override { return m_factory->contextMenuActions( viewWidget ); } QList toolBarActions( QWidget* viewWidget ) const override { return m_factory->toolBarActions( viewWidget ); } QString id() const override { return m_factory->id(); } private: IToolViewFactory* const m_factory; }; class ViewSelectorItem: public QListWidgetItem { public: explicit ViewSelectorItem(const QString& text, IToolViewFactory* factory, QListWidget* parent = nullptr, int type = Type) : QListWidgetItem(text, parent, type) , factory(factory) {} IToolViewFactory* const factory; }; class NewToolViewListWidget: public QListWidget { Q_OBJECT public: explicit NewToolViewListWidget(MainWindow *mw, QWidget* parent = nullptr) :QListWidget(parent), m_mw(mw) { connect(this, &NewToolViewListWidget::doubleClicked, this, &NewToolViewListWidget::addNewToolViewByDoubleClick); } Q_SIGNALS: void addNewToolView(MainWindow *mw, QListWidgetItem *item); private Q_SLOTS: void addNewToolViewByDoubleClick(const QModelIndex& index) { QListWidgetItem *item = itemFromIndex(index); // Disable item so that the tool view can not be added again. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); emit addNewToolView(m_mw, item); } private: MainWindow* const m_mw; }; UiController::UiController(Core *core) : Sublime::Controller(nullptr), IUiController() , d_ptr(new UiControllerPrivate(core, this)) { - Q_D(UiController); - setObjectName(QStringLiteral("UiController")); if (!defaultMainWindow() || (Core::self()->setupFlags() & Core::NoUi)) return; connect(qApp, &QApplication::focusChanged, this, [this] (QWidget* old, QWidget* now) { Q_D(UiController); d->widgetChanged(old, now); } ); setupActions(); } UiController::~UiController() = default; void UiController::setupActions() { } void UiController::mainWindowAdded(Sublime::MainWindow* mainWindow) { connect(mainWindow, &MainWindow::activeToolViewChanged, this, &UiController::slotActiveToolViewChanged); connect(mainWindow, &MainWindow::areaChanged, this, &UiController::slotAreaChanged); // also check after area reconstruction } // FIXME: currently, this always create new window. Probably, // should just rename it. void UiController::switchToArea(const QString &areaName, SwitchMode switchMode) { if (switchMode == ThisWindow) { showArea(areaName, activeSublimeWindow()); return; } auto *main = new MainWindow(this); addMainWindow(main); showArea(areaName, main); main->initialize(); // WTF? First, enabling this code causes crashes since we // try to disconnect some already-deleted action, or something. // Second, this code will disconnection the clients from guiFactory // of the previous main window. Ick! #if 0 //we need to add all existing guiclients to the new mainwindow //@todo adymo: add only ones that belong to the area (when the area code is there) const auto clients = oldMain->guiFactory()->clients(); for (KXMLGUIClient *client : clients) { main->guiFactory()->addClient(client); } #endif main->show(); } QWidget* UiController::findToolView(const QString& name, IToolViewFactory *factory, FindFlags flags) { Q_D(UiController); if(!d->areasRestored || !activeArea()) return nullptr; const QList views = activeArea()->toolViews(); for (Sublime::View* view : views) { auto* doc = qobject_cast(view->document()); if(doc && doc->title() == name && view->widget()) { if(flags & Raise) view->requestRaise(); return view->widget(); } } QWidget* ret = nullptr; if(flags & Create) { Sublime::ToolDocument* doc = d->factoryDocuments.value(factory); if(!doc) { doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments.insert(factory, doc); } Sublime::View* view = addToolViewToArea(factory, doc, activeArea()); if(view) ret = view->widget(); if(flags & Raise) findToolView(name, factory, Raise); } return ret; } void UiController::raiseToolView(QWidget* toolViewWidget) { Q_D(UiController); if(!d->areasRestored) return; const QList views = activeArea()->toolViews(); for (Sublime::View* view : views) { if(view->widget() == toolViewWidget) { view->requestRaise(); return; } } } void UiController::addToolView(const QString & name, IToolViewFactory *factory, FindFlags state) { Q_D(UiController); if (!factory) return; qCDebug(SHELL) ; auto *doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments[factory] = doc; /* Until areas are restored, we don't know which views should be really added, and which not, so we just record view availability. */ if (d->areasRestored && state != None) { const auto areas = allAreas(); for (Sublime::Area* area : areas) { addToolViewToArea(factory, doc, area); } } } void KDevelop::UiController::raiseToolView(Sublime::View * view) { const auto areas = allAreas(); for (Sublime::Area* area : areas) { if( area->toolViews().contains( view ) ) area->raiseToolView( view ); } slotActiveToolViewChanged(view); } void UiController::slotAreaChanged(Sublime::Area*) { // this slot gets call if an area in *any* MainWindow changed // so let's first get the "active area" const auto area = activeSublimeWindow()->area(); if (area) { // walk through shown tool views and maku sure the const auto shownIds = area->shownToolViews(Sublime::AllPositions); for (Sublime::View* toolView : qAsConst(area->toolViews())) { if (shownIds.contains(toolView->document()->documentSpecifier())) { slotActiveToolViewChanged(toolView); } } } } void UiController::slotActiveToolViewChanged(Sublime::View* view) { Q_D(UiController); if (!view) { return; } // record the last active tool view action listener if (qobject_cast(view->widget())) { d->activeActionListener = view->widget(); } } void KDevelop::UiController::removeToolView(IToolViewFactory *factory) { Q_D(UiController); if (!factory) return; qCDebug(SHELL) ; //delete the tooldocument Sublime::ToolDocument *doc = d->factoryDocuments.value(factory); ///@todo adymo: on document deletion all its views shall be also deleted for (Sublime::View* view : doc->views()) { const auto areas = allAreas(); for (Sublime::Area *area : areas) { if (area->removeToolView(view)) view->deleteLater(); } } d->factoryDocuments.remove(factory); delete doc; } Sublime::Area *UiController::activeArea() { Sublime::MainWindow *m = activeSublimeWindow(); if (m) return activeSublimeWindow()->area(); return nullptr; } Sublime::MainWindow *UiController::activeSublimeWindow() { Q_D(UiController); return d->activeSublimeWindow; } MainWindow *UiController::defaultMainWindow() { Q_D(UiController); return d->defaultMainWindow; } void UiController::initialize() { defaultMainWindow()->initialize(); } void UiController::cleanup() { for (Sublime::MainWindow* w : mainWindows()) { w->saveSettings(); } saveAllAreas(KSharedConfig::openConfig()); } void UiController::selectNewToolViewToAdd(MainWindow *mw) { Q_D(UiController); if (!mw || !mw->area()) return; ScopedDialog dia(mw); dia->setWindowTitle(i18n("Select Tool View to Add")); auto mainLayout = new QVBoxLayout(dia); auto *list = new NewToolViewListWidget(mw, dia); list->setSelectionMode(QAbstractItemView::ExtendedSelection); list->setSortingEnabled(true); for (QHash::const_iterator it = d->factoryDocuments.constBegin(); it != d->factoryDocuments.constEnd(); ++it) { ViewSelectorItem *item = new ViewSelectorItem(it.value()->title(), it.key(), list); if (!item->factory->allowMultiple() && toolViewPresent(it.value(), mw->area())) { // Disable item if the tool view is already present. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); } list->addItem(item); } list->setFocus(); connect(list, &NewToolViewListWidget::addNewToolView, this, &UiController::addNewToolView); mainLayout->addWidget(list); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dia->connect(buttonBox, &QDialogButtonBox::accepted, dia.data(), &QDialog::accept); dia->connect(buttonBox, &QDialogButtonBox::rejected, dia.data(), &QDialog::reject); mainLayout->addWidget(buttonBox); if (dia->exec() == QDialog::Accepted) { const auto items = list->selectedItems(); for (QListWidgetItem* item : items) { addNewToolView(mw, item); } } } void UiController::addNewToolView(MainWindow *mw, QListWidgetItem* item) { Q_D(UiController); auto *current = static_cast(item); Sublime::ToolDocument *doc = d->factoryDocuments[current->factory]; Sublime::View *view = doc->createView(); mw->area()->addToolView(view, Sublime::dockAreaToPosition(current->factory->defaultPosition())); current->factory->viewCreated(view); } void UiController::showSettingsDialog() { ConfigDialog cfgDlg(activeMainWindow()); auto editorConfigPage = new EditorConfigPage(&cfgDlg); auto languageConfigPage = new LanguagePreferences(&cfgDlg); auto analyzersPreferences = new AnalyzersPreferences(&cfgDlg); auto documentationPreferences = new DocumentationPreferences(&cfgDlg); auto runtimesPreferences = new RuntimesPreferences(&cfgDlg); auto templateConfig = new TemplateConfig(&cfgDlg); const auto configPages = QVector { new UiPreferences(&cfgDlg), new PluginPreferences(&cfgDlg), new SourceFormatterSettings(&cfgDlg), new ProjectPreferences(&cfgDlg), new EnvironmentPreferences(QString(), &cfgDlg), templateConfig, documentationPreferences, analyzersPreferences, runtimesPreferences, languageConfigPage, editorConfigPage }; for (auto page : configPages) { cfgDlg.appendConfigPage(page); } cfgDlg.appendSubConfigPage(languageConfigPage, new BGPreferences(&cfgDlg)); auto addPluginPages = [&](IPlugin* plugin) { for (int i = 0, numPages = plugin->configPages(); i < numPages; ++i) { auto page = plugin->configPage(i, &cfgDlg); if (!page) continue; if (page->configPageType() == ConfigPage::LanguageConfigPage) { cfgDlg.appendSubConfigPage(languageConfigPage, page); } else if (page->configPageType() == ConfigPage::AnalyzerConfigPage) { cfgDlg.appendSubConfigPage(analyzersPreferences, page); } else if (page->configPageType() == ConfigPage::RuntimeConfigPage) { cfgDlg.appendSubConfigPage(runtimesPreferences, page); } else if (page->configPageType() == ConfigPage::DocumentationConfigPage) { cfgDlg.appendSubConfigPage(documentationPreferences, page); } else { cfgDlg.insertConfigPage(editorConfigPage, page); } } }; auto plugins = ICore::self()->pluginController()->loadedPlugins(); std::sort(plugins.begin(), plugins.end()); for (IPlugin* plugin : qAsConst(plugins)) { addPluginPages(plugin); } // TODO: only load settings if a UI related page was changed? connect(&cfgDlg, &ConfigDialog::configSaved, activeSublimeWindow(), &Sublime::MainWindow::loadSettings); // make sure that pages get added whenever a new plugin is loaded (probably from the plugin selection dialog) // removal on plugin unload is already handled in ConfigDialog connect(ICore::self()->pluginController(), &IPluginController::pluginLoaded, &cfgDlg, addPluginPages); cfgDlg.exec(); } Sublime::Controller* UiController::controller() { return this; } KParts::MainWindow *UiController::activeMainWindow() { return activeSublimeWindow(); } void UiController::saveArea(Sublime::Area * area, KConfigGroup & group) { area->save(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->workingSet(area->workingSet()); set->saveFromArea(area, area->rootIndex()); } } void UiController::loadArea(Sublime::Area * area, const KConfigGroup & group) { area->load(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->workingSet(area->workingSet()); Q_ASSERT(set->isConnected(area)); Q_UNUSED(set); } } void UiController::saveAllAreas(const KSharedConfigPtr& config) { KConfigGroup uiConfig(config, "User Interface"); int wc = mainWindows().size(); uiConfig.writeEntry("Main Windows Count", wc); for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); for (Sublime::Area* defaultArea : defaultAreas()) { // FIXME: using object name seems ugly. QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, QLatin1String("Area ") + type); areaConfig.deleteGroup(); areaConfig.writeEntry("id", type); saveArea(area, areaConfig); areaConfig.sync(); } } uiConfig.sync(); } void UiController::loadAllAreas(const KSharedConfigPtr& config) { Q_D(UiController); KConfigGroup uiConfig(config, "User Interface"); int wc = uiConfig.readEntry("Main Windows Count", 1); /* It is expected the main windows are restored before restoring areas. */ if (wc > mainWindows().size()) wc = mainWindows().size(); /* Offer all tool views to the default areas. */ for (Sublime::Area* area : defaultAreas()) { QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } /* Restore per-windows areas. */ for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); Sublime::MainWindow *mw = mainWindows()[w]; /* We loop over default areas. This means that if the config file has an area of some type that is not in default set, we'd just ignore it. I think it's fine -- the model were a given mainwindow can has it's own area types not represented in the default set is way too complex. */ for (Sublime::Area* defaultArea : defaultAreas()) { QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, QLatin1String("Area ") + type); qCDebug(SHELL) << "Trying to restore area " << type; /* This is just an easy check that a group exists, to avoid "restoring" area from empty config group, wiping away programmatically installed defaults. */ if (areaConfig.readEntry("id", "") == type) { qCDebug(SHELL) << "Restoring area " << type; loadArea(area, areaConfig); } // At this point we know which tool views the area wants. // Tender all tool views we have. QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } // Force reload of the changes. showAreaInternal(mw->area(), mw); mw->enableAreaSettingsSave(); } d->areasRestored = true; } void UiController::addToolViewToDockArea(IToolViewFactory* factory, Qt::DockWidgetArea area) { Q_D(UiController); addToolViewToArea(factory, d->factoryDocuments.value(factory), activeArea(), Sublime::dockAreaToPosition(area)); } bool UiController::toolViewPresent(Sublime::ToolDocument* doc, Sublime::Area* area) { for (Sublime::View *view : doc->views()) { if( area->toolViews().contains( view ) ) return true; } return false; } void UiController::addToolViewIfWanted(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area) { if (area->wantToolView(factory->id())) { addToolViewToArea(factory, doc, area); } } Sublime::View* UiController::addToolViewToArea(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area, Sublime::Position p) { Sublime::View* view = doc->createView(); area->addToolView( view, p == Sublime::AllPositions ? Sublime::dockAreaToPosition(factory->defaultPosition()) : p); connect(view, &Sublime::View::raise, this, QOverload::of(&UiController::raiseToolView)); factory->viewCreated(view); return view; } void UiController::registerStatus(QObject* status) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; auto* mw = qobject_cast(w); if (!mw) return; mw->registerStatus(status); } void UiController::showErrorMessage(const QString& message, int timeout) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; auto* mw = qobject_cast(w); if (!mw) return; QMetaObject::invokeMethod(mw, "showErrorMessage", Q_ARG(QString, message), Q_ARG(int, timeout)); } const QHash< IToolViewFactory*, Sublime::ToolDocument* >& UiController::factoryDocuments() const { Q_D(const UiController); return d->factoryDocuments; } QWidget* UiController::activeToolViewActionListener() const { Q_D(const UiController); return d->activeActionListener; } QList UiController::allAreas() const { return Sublime::Controller::allAreas(); } } #include "uicontroller.moc" #include "moc_uicontroller.cpp" diff --git a/kdevplatform/sublime/area.cpp b/kdevplatform/sublime/area.cpp index 77cd762ee0..a35df70e09 100644 --- a/kdevplatform/sublime/area.cpp +++ b/kdevplatform/sublime/area.cpp @@ -1,547 +1,545 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "area.h" #include #include #include #include #include #include "view.h" #include "document.h" #include "areaindex.h" #include "controller.h" #include namespace Sublime { // class AreaPrivate class AreaPrivate { public: AreaPrivate() : rootIndex(new RootAreaIndex) , currentIndex(rootIndex.data()) { } AreaPrivate(const AreaPrivate &p) : title(p.title) , rootIndex(new RootAreaIndex(*(p.rootIndex))) , currentIndex(rootIndex.data()) , controller(p.controller) , toolViewPositions() , desiredToolViews(p.desiredToolViews) , shownToolViews(p.shownToolViews) , iconName(p.iconName) , workingSet(p.workingSet) , m_actions(p.m_actions) { } ~AreaPrivate() { } struct ViewFinder { explicit ViewFinder(View *_view): view(_view), index(nullptr) {} Area::WalkerMode operator() (AreaIndex *idx) { if (idx->hasView(view)) { index = idx; return Area::StopWalker; } return Area::ContinueWalker; } View *view; AreaIndex *index; }; struct ViewLister { Area::WalkerMode operator()(AreaIndex *idx) { views += idx->views(); return Area::ContinueWalker; } QList views; }; QString title; QScopedPointer rootIndex; AreaIndex *currentIndex; Controller *controller = nullptr; QList toolViews; QMap toolViewPositions; QMap desiredToolViews; QMap shownToolViews; QString iconName; QString workingSet; QPointer activeView; QList m_actions; }; // class Area Area::Area(Controller *controller, const QString &name, const QString &title) : QObject(controller) , d_ptr(new AreaPrivate()) { Q_D(Area); // FIXME: using objectName seems fishy. Introduce areaType method, // or some such. setObjectName(name); d->title = title; d->controller = controller; d->iconName = QStringLiteral("kdevelop"); d->workingSet.clear(); qCDebug(SUBLIME) << "initial working-set:" << d->workingSet; initialize(); } Area::Area(const Area &area) : QObject(area.controller()) , d_ptr(new AreaPrivate(*(area.d_ptr))) { Q_D(Area); setObjectName(area.objectName()); //clone tool views d->toolViews.clear(); for (View* view : qAsConst(area.toolViews())) { addToolView(view->document()->createView(), area.toolViewPosition(view)); } initialize(); } void Area::initialize() { Q_D(Area); connect(this, &Area::viewAdded, d->controller, &Controller::notifyViewAdded); connect(this, &Area::aboutToRemoveView, d->controller, &Controller::notifyViewRemoved); connect(this, &Area::toolViewAdded, d->controller, &Controller::notifyToolViewAdded); connect(this, &Area::aboutToRemoveToolView, d->controller, &Controller::notifyToolViewRemoved); connect(this, &Area::toolViewMoved, d->controller, &Controller::toolViewMoved); /* In theory, ownership is passed to us, so should not bother detecting deletion outside. */ // Functor will be called after destructor has run -> capture controller pointer by value // otherwise we crash because we access the already freed pointer this->d auto controller = d->controller; connect(this, &Area::destroyed, controller, [controller] (QObject* obj) { controller->removeArea(static_cast(obj)); }); } Area::~Area() = default; View* Area::activeView() { Q_D(Area); return d->activeView.data(); } void Area::setActiveView(View* view) { Q_D(Area); d->activeView = view; } void Area::addView(View *view, AreaIndex *index, View *after) { - Q_D(Area); - //View *after = 0; if (!after && controller()->openAfterCurrent()) { after = activeView(); } index->add(view, after); connect(view, &View::positionChanged, this, &Area::positionChanged); qCDebug(SUBLIME) << "view added in" << this; connect(this, &Area::destroyed, view, &View::deleteLater); emit viewAdded(index, view); } void Area::addView(View *view, View *after) { Q_D(Area); AreaIndex *index = d->currentIndex; if (after) { AreaIndex *i = indexOf(after); if (i) index = i; } addView(view, index); } void Area::addView(View *view, View *viewToSplit, Qt::Orientation orientation) { AreaIndex *indexToSplit = indexOf(viewToSplit); addView(view, indexToSplit, orientation); } void Area::addView(View* view, AreaIndex* indexToSplit, Qt::Orientation orientation) { indexToSplit->split(view, orientation); emit viewAdded(indexToSplit, view); connect(this, &Area::destroyed, view, &View::deleteLater); } View* Area::removeView(View *view) { AreaIndex *index = indexOf(view); if (!index) return nullptr; emit aboutToRemoveView(index, view); index->remove(view); emit viewRemoved(index, view); return view; } AreaIndex *Area::indexOf(View *view) { Q_D(Area); AreaPrivate::ViewFinder f(view); walkViews(f, d->rootIndex.data()); return f.index; } RootAreaIndex *Area::rootIndex() const { Q_D(const Area); return d->rootIndex.data(); } void Area::addToolView(View *view, Position defaultPosition) { Q_D(Area); d->toolViews.append(view); const QString id = view->document()->documentSpecifier(); const Position position = d->desiredToolViews.value(id, defaultPosition); d->desiredToolViews[id] = position; d->toolViewPositions[view] = position; emit toolViewAdded(view, position); } void Sublime::Area::raiseToolView(View * toolView) { emit requestToolViewRaise(toolView); } View* Area::removeToolView(View *view) { Q_D(Area); if (!d->toolViews.contains(view)) return nullptr; emit aboutToRemoveToolView(view, d->toolViewPositions[view]); QString id = view->document()->documentSpecifier(); qCDebug(SUBLIME) << this << "removed tool view " << id; d->desiredToolViews.remove(id); d->toolViews.removeAll(view); d->toolViewPositions.remove(view); return view; } void Area::moveToolView(View *toolView, Position newPosition) { Q_D(Area); if (!d->toolViews.contains(toolView)) return; QString id = toolView->document()->documentSpecifier(); d->desiredToolViews[id] = newPosition; d->toolViewPositions[toolView] = newPosition; emit toolViewMoved(toolView, newPosition); } const QList &Area::toolViews() const { Q_D(const Area); return d->toolViews; } Position Area::toolViewPosition(View *toolView) const { Q_D(const Area); return d->toolViewPositions[toolView]; } Controller *Area::controller() const { Q_D(const Area); return d->controller; } QList Sublime::Area::views() { Q_D(Area); AreaPrivate::ViewLister lister; walkViews(lister, d->rootIndex.data()); return lister.views; } QString Area::title() const { Q_D(const Area); return d->title; } void Area::setTitle(const QString &title) { Q_D(Area); d->title = title; } void Area::save(KConfigGroup& group) const { Q_D(const Area); QStringList desired; desired.reserve(d->desiredToolViews.size()); for (auto i = d->desiredToolViews.begin(), e = d->desiredToolViews.end(); i != e; ++i) { desired << i.key() + QLatin1Char(':') + QString::number(static_cast(i.value())); } group.writeEntry("desired views", desired); qCDebug(SUBLIME) << "save " << this << "wrote" << group.readEntry("desired views", ""); group.writeEntry("view on left", shownToolViews(Sublime::Left)); group.writeEntry("view on right", shownToolViews(Sublime::Right)); group.writeEntry("view on top", shownToolViews(Sublime::Top)); group.writeEntry("view on bottom", shownToolViews(Sublime::Bottom)); group.writeEntry("working set", d->workingSet); } void Area::load(const KConfigGroup& group) { Q_D(Area); qCDebug(SUBLIME) << "loading areas config"; d->desiredToolViews.clear(); const QStringList desired = group.readEntry("desired views", QStringList()); for (const QString& s : desired) { int i = s.indexOf(QLatin1Char(':')); if (i != -1) { QString id = s.left(i); int pos_i = s.midRef(i+1).toInt(); auto pos = static_cast(pos_i); if (pos != Sublime::Left && pos != Sublime::Right && pos != Sublime::Top && pos != Sublime::Bottom) { pos = Sublime::Bottom; } d->desiredToolViews[id] = pos; } } setShownToolViews(Sublime::Left, group.readEntry("view on left", QStringList())); setShownToolViews(Sublime::Right, group.readEntry("view on right", QStringList())); setShownToolViews(Sublime::Top, group.readEntry("view on top", QStringList())); setShownToolViews(Sublime::Bottom, group.readEntry("view on bottom", QStringList())); setWorkingSet(group.readEntry("working set", d->workingSet)); } bool Area::wantToolView(const QString& id) { Q_D(Area); return (d->desiredToolViews.contains(id)); } void Area::setShownToolViews(Sublime::Position pos, const QStringList& ids) { Q_D(Area); d->shownToolViews[pos] = ids; } QStringList Area::shownToolViews(Sublime::Position pos) const { Q_D(const Area); if (pos == Sublime::AllPositions) { QStringList allIds; allIds.reserve(d->shownToolViews.size()); std::for_each(d->shownToolViews.constBegin(), d->shownToolViews.constEnd(), [&](const QStringList& ids) { allIds << ids; }); return allIds; } return d->shownToolViews[pos]; } void Area::setDesiredToolViews( const QMap& desiredToolViews) { Q_D(Area); d->desiredToolViews = desiredToolViews; } QString Area::iconName() const { Q_D(const Area); return d->iconName; } void Area::setIconName(const QString& iconName) { Q_D(Area); d->iconName = iconName; } void Area::positionChanged(View *view, int newPos) { qCDebug(SUBLIME) << view << newPos; AreaIndex *index = indexOf(view); index->moveViewPosition(view, newPos); } QString Area::workingSet() const { Q_D(const Area); return d->workingSet; } void Area::setWorkingSet(const QString& name) { Q_D(Area); if(name != d->workingSet) { qCDebug(SUBLIME) << this << "setting new working-set" << name; QString oldName = d->workingSet; emit changingWorkingSet(this, oldName, name); d->workingSet = name; emit changedWorkingSet(this, oldName, name); } } bool Area::closeView(View* view, bool silent) { QPointer doc = view->document(); // We don't just delete the view, because if silent is false, we might need to ask the user. if(doc && !silent) { // Do some counting to check whether we need to ask the user for feedback qCDebug(SUBLIME) << "Closing view for" << view->document()->documentSpecifier() << "views" << view->document()->views().size() << "in area" << this; int viewsInCurrentArea = 0; // Number of views for the same document in the current area int viewsInOtherAreas = 0; // Number of views for the same document in other areas int viewsInOtherWorkingSets = 0; // Number of views for the same document in areas with different working-set for (View* otherView : qAsConst(doc.data()->views())) { Area* area = controller()->areaForView(otherView); if(area == this) viewsInCurrentArea += 1; if(!area || (area != this)) viewsInOtherAreas += 1; if(area && area != this && area->workingSet() != workingSet()) viewsInOtherWorkingSets += 1; } if(viewsInCurrentArea == 1 && (viewsInOtherAreas == 0 || viewsInOtherWorkingSets == 0)) { // Time to ask the user for feedback, because the document will be completely closed // due to working-set synchronization if( !doc.data()->askForCloseFeedback() ) return false; } } // otherwise we can silently close the view, // the document will still have an opened view somewhere delete removeView(view); return true; } void Area::clearViews(bool silent) { const auto views = this->views(); for (Sublime::View* view : views) { closeView(view, silent); } } void Area::clearDocuments() { if (views().isEmpty()) emit clearWorkingSet(this); else clearViews(true); } QList Area::actions() const { Q_D(const Area); return d->m_actions; } void Area::addAction(QAction* action) { Q_D(Area); Q_ASSERT(!d->m_actions.contains(action)); connect(action, &QAction::destroyed, this, &Area::actionDestroyed); d->m_actions.append(action); } void Area::actionDestroyed(QObject* action) { Q_D(Area); d->m_actions.removeAll(qobject_cast(action)); } } diff --git a/kdevplatform/vcs/dvcs/dvcsjob.cpp b/kdevplatform/vcs/dvcs/dvcsjob.cpp index fc935ca7b7..d0e0b0befb 100644 --- a/kdevplatform/vcs/dvcs/dvcsjob.cpp +++ b/kdevplatform/vcs/dvcs/dvcsjob.cpp @@ -1,384 +1,382 @@ /*************************************************************************** * This file was partly taken from KDevelop's cvs plugin * * Copyright 2002-2003 Christian Loose * * Copyright 2007 Robert Gruber * * * * Adapted for DVCS * * Copyright 2008 Evgeniy Ivanov * * Copyright Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "dvcsjob.h" #include "debug.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; class KDevelop::DVcsJobPrivate { public: DVcsJobPrivate() : childproc(new KProcess) {} ~DVcsJobPrivate() { delete childproc; } KProcess* childproc; VcsJob::JobStatus status; QByteArray output; QByteArray errorOutput; IPlugin* vcsplugin = nullptr; QVariant results; OutputModel* model; bool ignoreError = false; }; DVcsJob::DVcsJob(const QDir& workingDir, IPlugin* parent, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity) , d_ptr(new DVcsJobPrivate) { Q_D(DVcsJob); Q_ASSERT(workingDir.exists()); d->status = JobNotStarted; d->vcsplugin = parent; d->childproc->setWorkingDirectory(workingDir.absolutePath()); d->model = new OutputModel; d->ignoreError = false; setModel(d->model); setCapabilities(Killable); connect(d->childproc, QOverload::of(&QProcess::finished), this, &DVcsJob::slotProcessExited); connect(d->childproc, &QProcess::errorOccurred, this, &DVcsJob::slotProcessError); connect(d->childproc, &KProcess::readyReadStandardOutput, this, &DVcsJob::slotReceivedStdout); } DVcsJob::~DVcsJob() = default; QDir DVcsJob::directory() const { Q_D(const DVcsJob); return QDir(d->childproc->workingDirectory()); } DVcsJob& DVcsJob::operator<<(const QString& arg) { Q_D(DVcsJob); *d->childproc << arg; return *this; } DVcsJob& DVcsJob::operator<<(const char* arg) { Q_D(DVcsJob); *d->childproc << QString::fromUtf8(arg); return *this; } DVcsJob& DVcsJob::operator<<(const QStringList& args) { Q_D(DVcsJob); *d->childproc << args; return *this; } QStringList DVcsJob::dvcsCommand() const { Q_D(const DVcsJob); return d->childproc->program(); } QString DVcsJob::output() const { Q_D(const DVcsJob); QByteArray stdoutbuf = rawOutput(); int endpos = stdoutbuf.size(); if (d->status==JobRunning) { // We may have received only part of a code-point. apol: ASSERT? endpos = stdoutbuf.lastIndexOf('\n')+1; // Include the final newline or become 0, when there is no newline } return QString::fromLocal8Bit(stdoutbuf.constData(), endpos); } QByteArray DVcsJob::rawOutput() const { Q_D(const DVcsJob); return d->output; } QByteArray DVcsJob::errorOutput() const { Q_D(const DVcsJob); return d->errorOutput; } void DVcsJob::setIgnoreError(bool ignore) { Q_D(DVcsJob); d->ignoreError = ignore; } void DVcsJob::setResults(const QVariant &res) { Q_D(DVcsJob); d->results = res; } QVariant DVcsJob::fetchResults() { Q_D(DVcsJob); return d->results; } void DVcsJob::start() { Q_D(DVcsJob); Q_ASSERT_X(d->status != JobRunning, "DVCSjob::start", "Another process was started using this job class"); const QDir& workingdir = directory(); if( !workingdir.exists() ) { QString error = i18n( "Working Directory does not exist: %1", d->childproc->workingDirectory() ); d->model->appendLine(error); setError( 255 ); setErrorText(error); d->status = JobFailed; emitResult(); return; } if( !workingdir.isAbsolute() ) { QString error = i18n( "Working Directory is not absolute: %1", d->childproc->workingDirectory() ); d->model->appendLine(error); setError( 255 ); setErrorText(error); d->status = JobFailed; emitResult(); return; } QString commandDisplay = KShell::joinArgs(dvcsCommand()); qCDebug(VCS) << "Execute dvcs command:" << commandDisplay; QString service; if(d->vcsplugin) service = d->vcsplugin->objectName(); setObjectName(service + QLatin1String(": ") + commandDisplay); d->status = JobRunning; d->childproc->setOutputChannelMode(KProcess::SeparateChannels); //the started() and error() signals may be delayed! It causes crash with deferred deletion!!! d->childproc->start(); d->model->appendLine(directory().path() + QLatin1String("> ") + commandDisplay); } void DVcsJob::setCommunicationMode(KProcess::OutputChannelMode comm) { Q_D(DVcsJob); d->childproc->setOutputChannelMode(comm); } void DVcsJob::cancel() { Q_D(DVcsJob); d->childproc->kill(); } void DVcsJob::slotProcessError( QProcess::ProcessError err ) { Q_D(DVcsJob); d->status = JobFailed; setError(OutputJob::FailedShownError); //we don't want to trigger a message box d->errorOutput = d->childproc->readAllStandardError(); QString displayCommand = KShell::joinArgs(dvcsCommand()); QString completeErrorText = i18n("Process '%1' exited with status %2\n%3", displayCommand, d->childproc->exitCode(), QString::fromLocal8Bit(d->errorOutput) ); setErrorText( completeErrorText ); QString errorValue; //if trolls add Q_ENUMS for QProcess, then we can use better solution than switch: //QMetaObject::indexOfEnumerator(char*), QQStringLiteral(QMetaEnum::valueToKey())... switch (err) { case QProcess::FailedToStart: errorValue = QStringLiteral("FailedToStart"); break; case QProcess::Crashed: errorValue = QStringLiteral("Crashed"); break; case QProcess::Timedout: errorValue = QStringLiteral("Timedout"); break; case QProcess::WriteError: errorValue = QStringLiteral("WriteError"); break; case QProcess::ReadError: errorValue = QStringLiteral("ReadError"); break; case QProcess::UnknownError: errorValue = QStringLiteral("UnknownError"); break; } qCDebug(VCS) << "Found an error while running" << displayCommand << ":" << errorValue << "Exit code is:" << d->childproc->exitCode(); qCDebug(VCS) << "Error:" << completeErrorText; displayOutput(QString::fromLocal8Bit(d->errorOutput)); d->model->appendLine(i18n("Command finished with error %1.", errorValue)); if(verbosity()==Silent) { setVerbosity(Verbose); startOutput(); } emitResult(); } void DVcsJob::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { Q_D(DVcsJob); d->status = JobSucceeded; d->model->appendLine(i18n("Command exited with value %1.", exitCode)); if (exitStatus == QProcess::CrashExit) slotProcessError(QProcess::Crashed); else if (exitCode != 0 && !d->ignoreError) slotProcessError(QProcess::UnknownError); else jobIsReady(); } void DVcsJob::displayOutput(const QString& data) { Q_D(DVcsJob); d->model->appendLines(data.split(QLatin1Char('\n'))); } void DVcsJob::slotReceivedStdout() { Q_D(DVcsJob); QByteArray output = d->childproc->readAllStandardOutput(); // accumulate output d->output.append(output); displayOutput(QString::fromLocal8Bit(output)); } VcsJob::JobStatus DVcsJob::status() const { Q_D(const DVcsJob); return d->status; } IPlugin* DVcsJob::vcsPlugin() const { Q_D(const DVcsJob); return d->vcsplugin; } DVcsJob& DVcsJob::operator<<(const QUrl& url) { Q_D(DVcsJob); *d->childproc << url.toLocalFile(); return *this; } DVcsJob& DVcsJob::operator<<(const QList< QUrl >& urls) { - Q_D(DVcsJob); - for (const QUrl& url : urls) { operator<<(url); } return *this; } bool DVcsJob::doKill() { Q_D(DVcsJob); if (d->childproc->state() == QProcess::NotRunning) { return true; } static const int terminateKillTimeout = 1000; // ms d->childproc->terminate(); bool terminated = d->childproc->waitForFinished( terminateKillTimeout ); if( !terminated ) { d->childproc->kill(); terminated = d->childproc->waitForFinished( terminateKillTimeout ); } return terminated; } void DVcsJob::jobIsReady() { emit readyForParsing(this); //let parsers to set status emitResult(); //KJob emit resultsReady(this); //VcsJob } KProcess* DVcsJob::process() const { Q_D(const DVcsJob); return d->childproc; }