diff --git a/documentation/documentationview.cpp b/documentation/documentationview.cpp index 796694f00..9d184a071 100644 --- a/documentation/documentationview.cpp +++ b/documentation/documentationview.cpp @@ -1,334 +1,347 @@ /* Copyright 2009 Aleix Pol Gonzalez Copyright 2010 Benjamin Port 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 "documentationview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "documentationfindwidget.h" #include "debug.h" Q_LOGGING_CATEGORY(DOCUMENTATION, "kdevplatform.documentation") using namespace KDevelop; -DocumentationView::DocumentationView(QWidget* parent, ProvidersModel* m) - : QWidget(parent), mProvidersModel(m) +DocumentationView::DocumentationView(QWidget* parent, ProvidersModel* model) + : QWidget(parent), mProvidersModel(model) { - setWindowIcon(QIcon::fromTheme("documentation")); + setWindowIcon(QIcon::fromTheme(QStringLiteral("documentation"))); setWindowTitle(i18n("Documentation")); setLayout(new QVBoxLayout(this)); layout()->setMargin(0); layout()->setSpacing(0); //TODO: clean this up, simply use addAction as that will create a toolbar automatically // use custom QAction's with createWidget for mProviders and mIdentifiers - mActions=new KToolBar(this); + mActions = new KToolBar(this); // set window title so the QAction from QToolBar::toggleViewAction gets a proper name set mActions->setWindowTitle(i18n("Documentation Tool Bar")); mActions->setToolButtonStyle(Qt::ToolButtonIconOnly); - int iconSize=style()->pixelMetric(QStyle::PM_SmallIconSize); + int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize); mActions->setIconSize(QSize(iconSize, iconSize)); mFindDoc = new DocumentationFindWidget; mFindDoc->hide(); - mBack=mActions->addAction(QIcon::fromTheme("go-previous"), i18n("Back")); - mForward=mActions->addAction(QIcon::fromTheme("go-next"), i18n("Forward")); - mFind=mActions->addAction(QIcon::fromTheme("edit-find"), i18n("Find"), mFindDoc, SLOT(startSearch())); + mBack = mActions->addAction(QIcon::fromTheme("go-previous"), i18n("Back")); + mForward = mActions->addAction(QIcon::fromTheme("go-next"), i18n("Forward")); + mFind = mActions->addAction(QIcon::fromTheme("edit-find"), i18n("Find"), mFindDoc, SLOT(startSearch())); mActions->addSeparator(); mActions->addAction(QIcon::fromTheme("go-home"), i18n("Home"), this, SLOT(showHome())); - mProviders=new QComboBox(mActions); + mProviders = new QComboBox(mActions); - mIdentifiers=new QLineEdit(mActions); + mIdentifiers = new QLineEdit(mActions); mIdentifiers->setClearButtonEnabled(true); mIdentifiers->setCompleter(new QCompleter(mIdentifiers)); // mIdentifiers->completer()->setCompletionMode(QCompleter::UnfilteredPopupCompletion); mIdentifiers->completer()->setCaseSensitivity(Qt::CaseInsensitive); /* vertical size policy should be left to the style. */ mIdentifiers->setSizePolicy(QSizePolicy::Expanding, mIdentifiers->sizePolicy().verticalPolicy()); connect(mIdentifiers, &QLineEdit::returnPressed, this, &DocumentationView::changedSelection); connect(mIdentifiers->completer(), static_cast(&QCompleter::activated), this, &DocumentationView::changeProvider); QWidget::setTabOrder(mProviders, mIdentifiers); mActions->addWidget(mProviders); mActions->addWidget(mIdentifiers); mBack->setEnabled(false); mForward->setEnabled(false); connect(mBack, &QAction::triggered, this, &DocumentationView::browseBack); connect(mForward, &QAction::triggered, this, &DocumentationView::browseForward); - mCurrent=mHistory.end(); + mCurrent = mHistory.end(); layout()->addWidget(mActions); layout()->addWidget(new QWidget(this)); layout()->addWidget(mFindDoc); QMetaObject::invokeMethod(this, "initialize", Qt::QueuedConnection); } void DocumentationView::initialize() { mProviders->setModel(mProvidersModel); connect(mProviders, static_cast(&QComboBox::activated), this, &DocumentationView::changedProvider); foreach (IDocumentationProvider* p, mProvidersModel->providers()) { // can't use new signal/slot syntax here, IDocumentation is not a QObject connect(dynamic_cast(p), SIGNAL(addHistory(KDevelop::IDocumentation::Ptr)), this, SLOT(addHistory(KDevelop::IDocumentation::Ptr))); } connect(mProvidersModel, &ProvidersModel::providersChanged, this, &DocumentationView::emptyHistory); - if(mProvidersModel->rowCount()>0) + if (mProvidersModel->rowCount() > 0) { changedProvider(0); + } } void DocumentationView::browseBack() { mCurrent--; - mBack->setEnabled(mCurrent!=mHistory.begin()); + mBack->setEnabled(mCurrent != mHistory.begin()); mForward->setEnabled(true); updateView(); } void DocumentationView::browseForward() { mCurrent++; - mForward->setEnabled(mCurrent+1!=mHistory.end()); + mForward->setEnabled(mCurrent+1 != mHistory.end()); mBack->setEnabled(true); updateView(); } void DocumentationView::showHome() { auto prov = mProvidersModel->provider(mProviders->currentIndex()); showDocumentation(prov->homePage()); } void DocumentationView::changedSelection() { changeProvider(mIdentifiers->completer()->currentIndex()); } void DocumentationView::changeProvider(const QModelIndex& idx) { if (idx.isValid()) { IDocumentationProvider* prov = mProvidersModel->provider(mProviders->currentIndex()); auto doc = prov->documentationForIndex(idx); if (doc) { showDocumentation(doc); } } } void DocumentationView::showDocumentation(const IDocumentation::Ptr& doc) { qCDebug(DOCUMENTATION) << "showing" << doc->name(); addHistory(doc); updateView(); } void DocumentationView::addHistory(const IDocumentation::Ptr& doc) { - mBack->setEnabled( !mHistory.isEmpty() ); + mBack->setEnabled(!mHistory.isEmpty()); mForward->setEnabled(false); // clear all history following the current item, unless we're already // at the end (otherwise this code crashes when history is empty, which // happens when addHistory is first called on startup to add the // homepage) if (mCurrent+1 < mHistory.end()) { mHistory.erase(mCurrent+1, mHistory.end()); } mHistory.append(doc); - mCurrent=mHistory.end()-1; + mCurrent = mHistory.end()-1; // NOTE: we assume an existing widget was used to navigate somewhere // but this history entry actually contains the new info for the // title... this is ugly and should be refactored somehow if (mIdentifiers->completer()->model() == (*mCurrent)->provider()->indexModel()) { mIdentifiers->setText((*mCurrent)->name()); } } void DocumentationView::emptyHistory() { mHistory.clear(); - mCurrent=mHistory.end(); + mCurrent = mHistory.end(); mBack->setEnabled(false); mForward->setEnabled(false); - if(mProviders->count() > 0) { + if (mProviders->count() > 0) { mProviders->setCurrentIndex(0); changedProvider(0); } } void DocumentationView::updateView() { mProviders->setCurrentIndex(mProvidersModel->rowForProvider((*mCurrent)->provider())); mIdentifiers->completer()->setModel((*mCurrent)->provider()->indexModel()); mIdentifiers->setText((*mCurrent)->name()); - QLayoutItem* lastview=layout()->takeAt(1); + QLayoutItem* lastview = layout()->takeAt(1); Q_ASSERT(lastview); - if(lastview->widget()->parent()==this) + if (lastview->widget()->parent() == this) { lastview->widget()->deleteLater(); + } delete lastview; mFindDoc->setEnabled(false); - QWidget* w=(*mCurrent)->documentationWidget(mFindDoc, this); + QWidget* w = (*mCurrent)->documentationWidget(mFindDoc, this); Q_ASSERT(w); QWidget::setTabOrder(mIdentifiers, w); mFind->setEnabled(mFindDoc->isEnabled()); - if(!mFindDoc->isEnabled()) + if (!mFindDoc->isEnabled()) { mFindDoc->hide(); + } - QLayoutItem* findW=layout()->takeAt(1); + QLayoutItem* findWidget = layout()->takeAt(1); layout()->addWidget(w); - layout()->addItem(findW); + layout()->addItem(findWidget); } void DocumentationView::changedProvider(int row) { mIdentifiers->completer()->setModel(mProvidersModel->provider(row)->indexModel()); mIdentifiers->clear(); showHome(); } ////////////// ProvidersModel ////////////////// ProvidersModel::ProvidersModel(QObject* parent) : QAbstractListModel(parent) , mProviders(ICore::self()->documentationController()->documentationProviders()) { connect(ICore::self()->pluginController(), &IPluginController::unloadingPlugin, this, &ProvidersModel::unloaded); connect(ICore::self()->pluginController(), &IPluginController::pluginLoaded, this, &ProvidersModel::loaded); connect(ICore::self()->documentationController(), &IDocumentationController::providersChanged, this, &ProvidersModel::reloadProviders); } void ProvidersModel::reloadProviders() { beginResetModel(); mProviders = ICore::self()->documentationController()->documentationProviders(); endResetModel(); emit providersChanged(); } QVariant ProvidersModel::data(const QModelIndex& index, int role) const { if (index.row() >= mProviders.count() || index.row() < 0) return QVariant(); QVariant ret; switch (role) { case Qt::DisplayRole: - ret=provider(index.row())->name(); + ret = provider(index.row())->name(); break; case Qt::DecorationRole: - ret=provider(index.row())->icon(); + ret = provider(index.row())->icon(); break; } return ret; } -void ProvidersModel::removeProviders(const QList &prov) +void ProvidersModel::removeProviders(const QList&prov) { if (prov.isEmpty()) return; int idx = mProviders.indexOf(prov.first()); - if (idx>=0) { - beginRemoveRows(QModelIndex(), idx, idx + prov.count()-1); - for(int i=0, c=prov.count(); i= 0) { + beginRemoveRows(QModelIndex(), idx, idx + prov.count() - 1); + for(int i = 0, c = prov.count(); i < c; ++i) mProviders.removeAt(idx); endRemoveRows(); } emit providersChanged(); } -void ProvidersModel::unloaded(IPlugin* p) +void ProvidersModel::unloaded(IPlugin* plugin) { - IDocumentationProvider* prov=p->extension(); - if (prov) - removeProviders(QList() << prov); + IDocumentationProvider* provider = plugin->extension(); + if (provider) + removeProviders({provider}); - IDocumentationProviderProvider* provProv=p->extension(); - if (provProv) { - removeProviders(provProv->providers()); + IDocumentationProviderProvider* providerProvider = plugin->extension(); + if (providerProvider) { + removeProviders(providerProvider->providers()); } } -void ProvidersModel::loaded(IPlugin* p) +void ProvidersModel::loaded(IPlugin* plugin) { - IDocumentationProvider* prov=p->extension(); - - if (prov && !mProviders.contains(prov)) { + IDocumentationProvider* provider = plugin->extension(); + if (provider && !mProviders.contains(provider)) { beginInsertRows(QModelIndex(), 0, 0); - mProviders.append(prov); + mProviders.append(provider); endInsertRows(); emit providersChanged(); } - IDocumentationProviderProvider* provProv=p->extension(); - if (provProv) { - beginInsertRows(QModelIndex(), 0, provProv->providers().count()-1); - mProviders.append(provProv->providers()); + IDocumentationProviderProvider* providerProvider = plugin->extension(); + if (providerProvider) { + beginInsertRows(QModelIndex(), 0, providerProvider->providers().count()-1); + mProviders.append(providerProvider->providers()); endInsertRows(); emit providersChanged(); } } int ProvidersModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : mProviders.count(); } -int ProvidersModel::rowForProvider(IDocumentationProvider* provider) { return mProviders.indexOf(provider); } -IDocumentationProvider* ProvidersModel::provider(int pos) const { return mProviders[pos]; } -QList< IDocumentationProvider* > ProvidersModel::providers() { return mProviders; } +int ProvidersModel::rowForProvider(IDocumentationProvider* provider) +{ + return mProviders.indexOf(provider); +} + +IDocumentationProvider* ProvidersModel::provider(int pos) const +{ + return mProviders[pos]; +} + +QList ProvidersModel::providers() +{ + return mProviders; +} diff --git a/interfaces/iproblem.h b/interfaces/iproblem.h index 0fea92465..b97aeda6d 100644 --- a/interfaces/iproblem.h +++ b/interfaces/iproblem.h @@ -1,118 +1,120 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef IPROBLEM_H #define IPROBLEM_H #include #include #include #include namespace KDevelop { class IAssistant; /// Interface for the Problem classes class IProblem : public QSharedData { public: typedef QExplicitlySharedDataPointer Ptr; /// The source of the problem. That is which tool / which part found this problem. enum Source { Unknown, Disk, Preprocessor, Lexer, Parser, DUChainBuilder, SemanticAnalysis, ToDo, Plugin /// The source is a problem checker plugin }; /// Severity of the problem. That is, how serious is the found problem. enum Severity { Error, Warning, Hint }; IProblem(){} virtual ~IProblem(){} /// Returns the source of the problem virtual Source source() const = 0; /// Sets the source of the problem virtual void setSource(Source source) = 0; /// Returns a string containing the source of the problem virtual QString sourceString() const = 0; /// Returns the location of the problem (path, line, column) virtual KDevelop::DocumentRange finalLocation() const = 0; /// Sets the location of the problem (path, line, column) virtual void setFinalLocation(const KDevelop::DocumentRange& location) = 0; /// Returns the short description of the problem. virtual QString description() const = 0; /// Sets the short description of the problem virtual void setDescription(const QString& description) = 0; /// Returns the detailed explanation of the problem. virtual QString explanation() const = 0; /// Sets the detailed explanation of the problem virtual void setExplanation(const QString& explanation) = 0; /// Returns the severity of the problem virtual Severity severity() const = 0; /// Sets the severity of the problem virtual void setSeverity(Severity severity) = 0; /// Returns a string containing the severity of the problem virtual QString severityString() const = 0; /// Returns the diagnostics of the problem. virtual QVector diagnostics() const = 0; /// Sets the diagnostics of the problem virtual void setDiagnostics(const QVector &diagnostics) = 0; /// Adds a diagnostic line to the problem virtual void addDiagnostic(const Ptr &diagnostic) = 0; /// Clears all diagnostics virtual void clearDiagnostics() = 0; /// Returns a solution assistant for the problem, if applicable that is. virtual QExplicitlySharedDataPointer solutionAssistant() const = 0; }; } +Q_DECLARE_METATYPE(KDevelop::IProblem::Ptr); + #endif diff --git a/language/classmodel/classmodel.h b/language/classmodel/classmodel.h index bd2a8a704..193b7e446 100644 --- a/language/classmodel/classmodel.h +++ b/language/classmodel/classmodel.h @@ -1,154 +1,154 @@ /* * KDevelop Class Browser * * Copyright 2007-2008 Hamish Rodda * Copyright 2009 Lior Mualem * * 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 General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_CLASSMODEL_H #define KDEVPLATFORM_CLASSMODEL_H #include #include "classmodelnode.h" #include class ClassBrowserPlugin; namespace KDevelop { class TopDUContext; class IDocument; class ParseJob; class DUContext; class IProject; class DUChainBase; class IndexedQualifiedIdentifier; } namespace ClassModelNodes { class Node; class FilteredAllClassesFolder; class FilteredProjectFolder; class FolderNode; class IdentifierNode; } /// The model interface accessible from the nodes. class NodesModelInterface { public: virtual ~NodesModelInterface(); public: enum Feature { AllProjectsClasses = 0x1, BaseAndDerivedClasses = 0x2, ClassInternals = 0x4 }; Q_DECLARE_FLAGS(Features, Feature) virtual void nodesLayoutAboutToBeChanged(ClassModelNodes::Node* a_parent) = 0; virtual void nodesLayoutChanged(ClassModelNodes::Node* a_parent) = 0; virtual void nodesRemoved(ClassModelNodes::Node* a_parent, int a_first, int a_last) = 0; virtual void nodesAboutToBeAdded(ClassModelNodes::Node* a_parent, int a_pos, int a_size) = 0; virtual void nodesAdded(ClassModelNodes::Node* a_parent) = 0; virtual Features features() const = 0; }; /** * @short A model that holds a convinient representation of the defined class in the project * * This model doesn't have much code in it, it mostly acts as a glue between the different * nodes and the tree view. * * The nodes are defined in the namespace @ref ClassModelNodes */ class KDEVPLATFORMLANGUAGE_EXPORT ClassModel : public QAbstractItemModel, public NodesModelInterface { Q_OBJECT public: ClassModel(); virtual ~ClassModel(); public: /// Retrieve the DU object related to the specified index. /// @note DUCHAINS READER LOCK MUST BE TAKEN! KDevelop::DUChainBase* duObjectForIndex(const QModelIndex& a_index); /// Call this to retrieve the index for the node associated with the specified id. QModelIndex getIndexForIdentifier(const KDevelop::IndexedQualifiedIdentifier& a_id); /// Return the model index associated with the given node. QModelIndex index(ClassModelNodes::Node* a_node) const; inline void setFeatures(NodesModelInterface::Features features); - virtual inline NodesModelInterface::Features features() const { return m_features; } + virtual inline NodesModelInterface::Features features() const override { return m_features; } public Q_SLOTS: /// Call this to update the filter string for the search results folder. void updateFilterString(QString a_newFilterString); /// removes the project-specific node void removeProjectNode(KDevelop::IProject* project); /// adds the project-specific node void addProjectNode(KDevelop::IProject* project); private: // NodesModelInterface overrides virtual void nodesLayoutAboutToBeChanged(ClassModelNodes::Node* a_parent) override; virtual void nodesLayoutChanged(ClassModelNodes::Node* a_parent) override; virtual void nodesRemoved(ClassModelNodes::Node* a_parent, int a_first, int a_last) override; virtual void nodesAboutToBeAdded(ClassModelNodes::Node* a_parent, int a_pos, int a_size) override; virtual void nodesAdded(ClassModelNodes::Node* a_parent) override; private: /// Main level node - it's usually invisible. ClassModelNodes::Node* m_topNode; ClassModelNodes::FilteredAllClassesFolder* m_allClassesNode; QMap m_projectNodes; NodesModelInterface::Features m_features; public Q_SLOTS: /// This slot needs to be attached to collapsed signal in the tree view. void collapsed(const QModelIndex& index); /// This slot needs to be attached to expanded signal in the tree view. void expanded(const QModelIndex& index); public: // QAbstractItemModel overrides virtual QFlags< Qt::ItemFlag > flags(const QModelIndex&) const override; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; virtual bool hasChildren(const QModelIndex& parent = QModelIndex()) const override; virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; virtual QModelIndex parent(const QModelIndex& child) const override; }; inline void ClassModel::setFeatures(Features features) { m_features = features; } Q_DECLARE_OPERATORS_FOR_FLAGS(NodesModelInterface::Features) #endif // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/language/codecompletion/codecompletion.cpp b/language/codecompletion/codecompletion.cpp index 22aaabfa3..c90da5c60 100644 --- a/language/codecompletion/codecompletion.cpp +++ b/language/codecompletion/codecompletion.cpp @@ -1,129 +1,133 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006 Hamish Rodda * * 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 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 "codecompletion.h" #include #include #include #include #include #include #include "../duchain/duchain.h" #include "../duchain/topducontext.h" #include "util/debug.h" #include "codecompletionmodel.h" #include using namespace KTextEditor; using namespace KDevelop; CodeCompletion::CodeCompletion(QObject *parent, KTextEditor::CodeCompletionModel* aModel, const QString& language) : QObject(parent), m_model(aModel), m_language(language) { KDevelop::CodeCompletionModel* kdevModel = dynamic_cast(aModel); if(kdevModel) kdevModel->initialize(); connect(KDevelop::ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &CodeCompletion::textDocumentCreated); connect( ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &CodeCompletion::documentUrlChanged ); aModel->setParent(this); // prevent deadlock QMetaObject::invokeMethod(this, "checkDocuments", Qt::QueuedConnection); } CodeCompletion::~CodeCompletion() { } void CodeCompletion::checkDocuments() { foreach( KDevelop::IDocument* doc, KDevelop::ICore::self()->documentController()->openDocuments() ) { if (doc->textDocument()) { checkDocument(doc->textDocument()); } } } void CodeCompletion::viewCreated(KTextEditor::Document * document, KTextEditor::View * view) { Q_UNUSED(document); if (CodeCompletionInterface* cc = dynamic_cast(view)) { cc->registerCompletionModel(m_model); qCDebug(LANGUAGE) << "Registered completion model"; + emit registeredToView(view); } } void CodeCompletion::documentUrlChanged(KDevelop::IDocument* document) { // The URL has changed (might have a different language now), so we re-register the document Document* textDocument = document->textDocument(); if(textDocument) { checkDocument(textDocument); } } void CodeCompletion::textDocumentCreated(KDevelop::IDocument* document) { Q_ASSERT(document->textDocument()); checkDocument(document->textDocument()); } void CodeCompletion::unregisterDocument(Document* textDocument) { - foreach (KTextEditor::View* view, textDocument->views()) - if (CodeCompletionInterface* cc = dynamic_cast(view)) + foreach (KTextEditor::View* view, textDocument->views()) { + if (CodeCompletionInterface* cc = dynamic_cast(view)) { cc->unregisterCompletionModel(m_model); + emit unregisteredFromView(view); + } + } disconnect(textDocument, &Document::viewCreated, this, &CodeCompletion::viewCreated); } void CodeCompletion::checkDocument(Document* textDocument) { unregisterDocument(textDocument); auto langs = ICore::self()->languageController()->languagesForUrl( textDocument->url() ); bool found = false; foreach(const auto& lang, langs) { if(m_language==lang->name()) { found=true; break; } } if(!found && !m_language.isEmpty()) return; foreach (KTextEditor::View* view, textDocument->views()) viewCreated(textDocument, view); connect(textDocument, &Document::viewCreated, this, &CodeCompletion::viewCreated); } diff --git a/language/codecompletion/codecompletion.h b/language/codecompletion/codecompletion.h index 5b0ab83fa..104be9c22 100644 --- a/language/codecompletion/codecompletion.h +++ b/language/codecompletion/codecompletion.h @@ -1,74 +1,79 @@ /* * KDevelop Generic Code Completion Support * * Copyright 2006 Hamish Rodda * * 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 General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_CODECOMPLETION_H #define KDEVPLATFORM_CODECOMPLETION_H #include #include namespace KParts { class Part; } namespace KTextEditor { class Document; class View; class CodeCompletionModel; } namespace KDevelop { class IDocument; class ILanguage; +// TODO: cleanup this class for 5.1 class KDEVPLATFORMLANGUAGE_EXPORT CodeCompletion : public QObject { Q_OBJECT public: /** CodeCompletion will be the @p aModel parent. * If @p language is empty, the completion model will work for all files, * otherwise only for ones that contain the selected language. */ CodeCompletion(QObject* parent, KTextEditor::CodeCompletionModel* aModel, const QString& language); virtual ~CodeCompletion(); private Q_SLOTS: void textDocumentCreated(KDevelop::IDocument*); void viewCreated(KTextEditor::Document *document, KTextEditor::View *view); void documentUrlChanged(KDevelop::IDocument*); /** * check already opened documents, * needs to be done via delayed call to prevent infinite loop in * checkDocument() -> load lang plugin -> register CodeCompletion -> checkDocument() -> ... */ void checkDocuments(); + signals: + void registeredToView(KTextEditor::View* view); + void unregisteredFromView(KTextEditor::View* view); + private: void unregisterDocument(KTextEditor::Document*); void checkDocument(KTextEditor::Document*); KTextEditor::CodeCompletionModel* m_model; QString m_language; }; } #endif diff --git a/language/duchain/navigation/abstractnavigationcontext.cpp b/language/duchain/navigation/abstractnavigationcontext.cpp index 43e5775e4..905ac97b0 100644 --- a/language/duchain/navigation/abstractnavigationcontext.cpp +++ b/language/duchain/navigation/abstractnavigationcontext.cpp @@ -1,488 +1,488 @@ /* 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 "util/debug.h" #include #include #include namespace KDevelop { void AbstractNavigationContext::setTopContext(KDevelop::TopDUContextPointer context) { m_topContext = context; } KDevelop::TopDUContextPointer AbstractNavigationContext::topContext() const { return m_topContext; } AbstractNavigationContext::AbstractNavigationContext( KDevelop::TopDUContextPointer topContext, AbstractNavigationContext* previousContext) : m_selectedLink(0), m_shorten(false), m_linkCount(-1), m_currentPositionLine(0), m_previousContext(previousContext), m_topContext(topContext) { } void AbstractNavigationContext::addExternalHtml( const QString& text ) { int lastPos = 0; int pos = 0; QString fileMark = "KDEV_FILE_LINK{"; while( pos < text.length() && (pos = text.indexOf( fileMark, pos)) != -1 ) { modifyHtml() += text.mid(lastPos, pos-lastPos); pos += fileMark.length(); if( pos != text.length() ) { int fileEnd = text.indexOf('}', pos); if( fileEnd != -1 ) { QString file = text.mid( pos, fileEnd - pos ); pos = fileEnd + 1; const QUrl url = QUrl::fromUserInput(file); makeLink( url.fileName(), file, NavigationAction( url, KTextEditor::Cursor() ) ); } } lastPos = pos; } modifyHtml() += text.mid(lastPos, text.length()-lastPos); } void AbstractNavigationContext::makeLink( const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType ) { NavigationAction action( declaration, actionType ); QString targetId = QString::number((quint64)declaration.data() * actionType); makeLink(name, targetId, action); } QString AbstractNavigationContext::createLink(const QString& name, QString targetId, const NavigationAction& action) { if(m_shorten) { //Do not create links in shortened mode, it's only for viewing return typeHighlight(name.toHtmlEscaped()); } m_links[ targetId ] = action; m_intLinks[ m_linkCount ] = action; m_linkLines[ m_linkCount ] = m_currentLine; if(m_currentPositionLine == m_currentLine) { m_currentPositionLine = -1; m_selectedLink = m_linkCount; } QString str = name.toHtmlEscaped(); if( m_linkCount == m_selectedLink ) str = "" + str + ""; QString ret = "" + str + ""; if( m_selectedLink == m_linkCount ) m_selectedLinkAction = action; ++m_linkCount; return ret; } void AbstractNavigationContext::makeLink( const QString& name, QString targetId, const NavigationAction& action) { modifyHtml() += createLink(name, targetId, action); } void AbstractNavigationContext::clear() { m_linkCount = 0; m_currentLine = 0; m_currentText.clear(); m_links.clear(); m_intLinks.clear(); m_linkLines.clear(); } NavigationContextPointer AbstractNavigationContext::executeLink (QString link) { if(!m_links.contains(link)) return NavigationContextPointer(this); return execute(m_links[link]); } NavigationContextPointer AbstractNavigationContext::executeKeyAction(QString key) { Q_UNUSED(key); return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::execute(const NavigationAction& action) { 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); } qRegisterMetaType("KTextEditor::Cursor"); 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: { AbstractDeclarationNavigationContext* ctx = dynamic_cast(m_previousContext); if( ctx && ctx->declaration() == action.decl ) return NavigationContextPointer( m_previousContext ); return AbstractNavigationContext::registerChild(action.decl); } break; case NavigationAction::NavigateUses: { IContextBrowser* browser = ICore::self()->pluginController()->extensionForPlugin(); if (browser) { browser->showUses(action.decl); return NavigationContextPointer(this); } // fall-through } 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()); ICore::self()->documentationController()->showDocumentation(doc); } break; } return NavigationContextPointer( this ); } void AbstractNavigationContext::setPreviousContext(KDevelop::AbstractNavigationContext* previous) { m_previousContext = previous; } NavigationContextPointer AbstractNavigationContext::registerChild( AbstractNavigationContext* context ) { m_children << NavigationContextPointer(context); return m_children.last(); } NavigationContextPointer AbstractNavigationContext::registerChild(DeclarationPointer declaration) { //We create a navigation-widget here, and steal its context.. evil ;) - QWidget* navigationWidget = declaration->context()->createNavigationWidget(declaration.data()); - NavigationContextPointer ret; - AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget); - if(abstractNavigationWidget) - ret = abstractNavigationWidget->context(); - delete navigationWidget; - ret->setPreviousContext(this); - m_children << ret; - return ret; + QScopedPointer navigationWidget(declaration->context()->createNavigationWidget(declaration.data())); + if (AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget.data()) ) { + NavigationContextPointer ret = abstractNavigationWidget->context(); + ret->setPreviousContext(this); + m_children << ret; + return ret; + } else { + return NavigationContextPointer(this); + } } const int lineJump = 3; void AbstractNavigationContext::down() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); int fromLine = m_currentPositionLine; if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { if(fromLine == -1) fromLine = m_linkLines[m_selectedLink]; for(int newSelectedLink = m_selectedLink+1; newSelectedLink < m_linkCount; ++newSelectedLink) { if(m_linkLines[newSelectedLink] > fromLine && m_linkLines[newSelectedLink] - fromLine <= lineJump) { m_selectedLink = newSelectedLink; m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = 0; m_currentPositionLine = fromLine + lineJump; if(m_currentPositionLine > m_currentLine) m_currentPositionLine = m_currentLine; } void AbstractNavigationContext::up() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); int fromLine = m_currentPositionLine; if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { if(fromLine == -1) fromLine = m_linkLines[m_selectedLink]; for(int newSelectedLink = m_selectedLink-1; newSelectedLink >= 0; --newSelectedLink) { if(m_linkLines[newSelectedLink] < fromLine && fromLine - m_linkLines[newSelectedLink] <= lineJump) { m_selectedLink = newSelectedLink; m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = m_currentLine; m_currentPositionLine = fromLine - lineJump; if(m_currentPositionLine < 0) m_currentPositionLine = 0; } void AbstractNavigationContext::nextLink() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); m_currentPositionLine = -1; if( m_linkCount > 0 ) m_selectedLink = (m_selectedLink+1) % m_linkCount; } void AbstractNavigationContext::previousLink() { //Make sure link-count is valid if( m_linkCount == -1 ) html(); m_currentPositionLine = -1; if( m_linkCount > 0 ) { --m_selectedLink; if( m_selectedLink < 0 ) m_selectedLink += m_linkCount; } Q_ASSERT(m_selectedLink >= 0); } void AbstractNavigationContext::setPrefixSuffix( const QString& prefix, const QString& suffix ) { m_prefix = prefix; m_suffix = suffix; } NavigationContextPointer AbstractNavigationContext::back() { if(m_previousContext) return NavigationContextPointer(m_previousContext); else return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept() { if( m_selectedLink >= 0 && m_selectedLink < m_linkCount ) { NavigationAction action = m_intLinks[m_selectedLink]; return execute(action); } return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept(IndexedDeclaration decl) { if(decl.data()) { NavigationAction action(DeclarationPointer(decl.data()), NavigationAction::NavigateDeclaration); return execute(action); }else{ return NavigationContextPointer(this); } } NavigationContextPointer AbstractNavigationContext::acceptLink(const QString& link) { if( !m_links.contains(link) ) { qCDebug(LANGUAGE) << "Executed unregistered link " << link << endl; return NavigationContextPointer(this); } return execute(m_links[link]); } NavigationAction AbstractNavigationContext::currentAction() const { return m_selectedLinkAction; } QString AbstractNavigationContext::declarationKind(DeclarationPointer decl) { const AbstractFunctionDeclaration* 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( NamespaceAliasDeclaration* 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) { m_shorten = shorten; return QString(); } bool AbstractNavigationContext::alreadyComputed() const { return !m_currentText.isEmpty(); } bool AbstractNavigationContext::isWidgetMaximized() const { return true; } QWidget* AbstractNavigationContext::widget() const { return 0; } ///Splits the string by the given regular expression, but keeps the split-matches at the end of each line static QStringList splitAndKeep(QString str, QRegExp regExp) { QStringList ret; int place = regExp.indexIn(str); while(place != -1) { ret << str.left(place + regExp.matchedLength()); str = str.mid(place + regExp.matchedLength()); place = regExp.indexIn(str); } ret << str; return ret; } void AbstractNavigationContext::addHtml(QString html) { QRegExp newLineRegExp("
|
"); foreach(const QString& line, splitAndKeep(html, newLineRegExp)) { m_currentText += line; if(line.indexOf(newLineRegExp) != -1) { ++m_currentLine; if(m_currentLine == m_currentPositionLine) { m_currentText += QStringLiteral(" <-> "); // ><-> is <-> } } } } QString AbstractNavigationContext::currentHtml() const { return m_currentText; } QString AbstractNavigationContext::fontSizePrefix(bool /*shorten*/) const { return QString(); } QString AbstractNavigationContext::fontSizeSuffix(bool /*shorten*/) const { return QString(); } QString Colorizer::operator() ( const QString& str ) const { QString ret = "" + str + ""; if( m_formatting & Fixed ) ret = ""+ret+""; if ( m_formatting & Bold ) ret = ""+ret+""; if ( m_formatting & Italic ) ret = ""+ret+""; return ret; } const Colorizer AbstractNavigationContext::typeHighlight("006000"); const Colorizer AbstractNavigationContext::errorHighlight("990000"); const Colorizer AbstractNavigationContext::labelHighlight("000000"); const Colorizer AbstractNavigationContext::codeHighlight("005000"); const Colorizer AbstractNavigationContext::propertyHighlight("009900"); const Colorizer AbstractNavigationContext::navigationHighlight("000099"); const Colorizer AbstractNavigationContext::importantHighlight("000000", Colorizer::Bold | Colorizer::Italic); const Colorizer AbstractNavigationContext::commentHighlight("303030"); const Colorizer AbstractNavigationContext::nameHighlight("000000", Colorizer::Bold); } diff --git a/outputview/outputfilteringstrategies.cpp b/outputview/outputfilteringstrategies.cpp index 3d4b4cacd..55abe30d2 100644 --- a/outputview/outputfilteringstrategies.cpp +++ b/outputview/outputfilteringstrategies.cpp @@ -1,429 +1,432 @@ /* This file is part of KDevelop Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "outputfilteringstrategies.h" #include "outputformats.h" #include "filtereditem.h" #include #include #include #include using namespace KDevelop; namespace { template FilteredItem match(const ErrorFormats& errorFormats, const QString& line) { FilteredItem item(line); for( const ErrorFormat& curErrFilter : errorFormats ) { const auto match = curErrFilter.expression.match(line); if( match.hasMatch() ) { item.url = QUrl::fromUserInput(match.captured( curErrFilter.fileGroup )); item.lineNo = match.captured( curErrFilter.lineGroup ).toInt() - 1; if(curErrFilter.columnGroup >= 0) { item.columnNo = match.captured( curErrFilter.columnGroup ).toInt() - 1; } else { item.columnNo = 0; } QString txt = match.captured(curErrFilter.textGroup); item.type = FilteredItem::ErrorItem; // Make the item clickable if it comes with the necessary file & line number information if (curErrFilter.fileGroup > 0 && curErrFilter.lineGroup > 0) { item.isActivatable = true; } break; } } return item; } } /// --- No filter strategy --- NoFilterStrategy::NoFilterStrategy() { } FilteredItem NoFilterStrategy::actionInLine(const QString& line) { return FilteredItem( line ); } FilteredItem NoFilterStrategy::errorInLine(const QString& line) { return FilteredItem( line ); } /// --- Compiler error filter strategy --- CompilerFilterStrategy::CompilerFilterStrategy(const QUrl& buildDir) : m_buildDir(buildDir) { } Path CompilerFilterStrategy::pathForFile(const QString& filename) const { QFileInfo fi( filename ); Path currentPath; if( fi.isRelative() ) { if( m_currentDirs.isEmpty() ) { return Path(m_buildDir, filename ); } auto it = m_currentDirs.constEnd() - 1; do { currentPath = Path(*it, filename); } while( (it-- != m_currentDirs.constBegin()) && !QFileInfo(currentPath.toLocalFile()).exists() ); return currentPath; } else { currentPath = Path(filename); } return currentPath; } bool CompilerFilterStrategy::isMultiLineCase(KDevelop::ErrorFormat curErrFilter) const { - if(curErrFilter.compiler == "gfortran" || curErrFilter.compiler == "cmake") { + if(curErrFilter.compiler == QLatin1String("gfortran") || curErrFilter.compiler == QLatin1String("cmake")) { return true; } return false; } void CompilerFilterStrategy::putDirAtEnd(const Path& pathToInsert) { auto it = m_positionInCurrentDirs.find( pathToInsert ); // Encountered new build directory? if (it == m_positionInCurrentDirs.end()) { m_currentDirs.push_back( pathToInsert ); m_positionInCurrentDirs.insert( pathToInsert, m_currentDirs.size() - 1 ); } else { // Build dir already in currentDirs, but move it to back of currentDirs list // (this gives us most-recently-used semantics in pathForFile) std::rotate(m_currentDirs.begin() + it.value(), m_currentDirs.begin() + it.value() + 1, m_currentDirs.end() ); it.value() = m_currentDirs.size() - 1; } } QVector CompilerFilterStrategy::getCurrentDirs() { QVector ret; ret.reserve(m_currentDirs.size()); for (const auto& path : m_currentDirs) { ret << path.pathOrUrl(); } return ret; } FilteredItem CompilerFilterStrategy::actionInLine(const QString& line) { // A list of filters for possible compiler, linker, and make actions static const ActionFormat ACTION_FILTERS[] = { ActionFormat( ki18nc("compiling a file: %1 file %2 compiler", "compiling %1 (%2)"), 1, 2, QStringLiteral("(?:^|[^=])\\b(gcc|CC|cc|distcc|c\\+\\+|g\\+\\+|clang(?:\\+\\+)|mpicc|icc|icpc)\\s+.*-c.*[/ '\\\\]+(\\w+\\.(?:cpp|CPP|c|C|cxx|CXX|cs|java|hpf|f|F|f90|F90|f95|F95))")), //moc and uic ActionFormat( ki18nc("generating a file: %1 file %2 generator tool", "generating %1 (%2)"), 1, 2, QStringLiteral("/(moc|uic)\\b.*\\s-o\\s([^\\s;]+)")), //libtool linking ActionFormat( ki18nc("Linking object files into a library or executable: %1 target, %2 linker", "linking %1 (%2)"), QStringLiteral("libtool"), QStringLiteral("/bin/sh\\s.*libtool.*--mode=link\\s.*\\s-o\\s([^\\s;]+)"), 1 ), //unsermake ActionFormat( ki18nc("compiling a file: %1 file %2 compiler", "compiling %1 (%2)"), 1, 1, QStringLiteral("^compiling (.*)") ), ActionFormat( ki18nc("generating a file: %1 file %2 generator tool", "generating %1 (%2)"), 1, 2, QStringLiteral("^generating (.*)") ), ActionFormat( ki18nc("Linking object files into a library or executable: %1 file, %2 linker", "linking %1 (%2)"), 1, 2, QStringLiteral("(gcc|cc|c\\+\\+|g\\+\\+|clang(?:\\+\\+)|mpicc|icc|icpc)\\S* (?:\\S* )*-o ([^\\s;]+)")), ActionFormat( ki18nc("Linking object files into a library or executable: %1 file, %2 compiler", "linking %1 (%2)"), 1, 2, QStringLiteral("^linking (.*)") ), //cmake ActionFormat( ki18nc("finished building a target: %1 target name", "built %1"), -1, 1, QStringLiteral("\\[.+%\\] Built target (.*)") ), ActionFormat( ki18nc("compiling a file: %1 file %2 compiler", "compiling %1 (%2)"), QStringLiteral("cmake"), - QStringLiteral("\\[.+%\\] Building .* object (.*)CMakeFiles/"), 1 ), + QStringLiteral("\\[.+%\\] Building .* object (.*)"), 1 ), ActionFormat( ki18nc("generating a file: %1 file %2 generator tool", "generating %1 (%2)"), -1, 1, QStringLiteral("\\[.+%\\] Generating (.*)") ), ActionFormat( ki18nc("Linking object files into a library or executable: %1 target", "linking %1"), -1, 1, QStringLiteral("^Linking (.*)") ), ActionFormat( ki18nc("configuring a project: %1 tool", "configuring (%1)"), QStringLiteral("cmake"), QStringLiteral("(-- Configuring (done|incomplete)|-- Found|-- Adding|-- Enabling)"), -1 ), ActionFormat( ki18nc("installing a file: %1 file", "installing %1"), -1, 1, QStringLiteral("-- Installing (.*)") ), //libtool install ActionFormat( ki18nc("creating a folder: %1 path", "creating %1"), {}, QStringLiteral("/(?:bin/sh\\s.*mkinstalldirs).*\\s([^\\s;]+)"), 1 ), ActionFormat( ki18nc("installing a file: %1 file", "installing %1"), {}, QStringLiteral("/(?:usr/bin/install|bin/sh\\s.*mkinstalldirs|bin/sh\\s.*libtool.*--mode=install).*\\s([^\\s;]+)"), 1 ), //dcop ActionFormat( ki18nc("generating a file: %1 file %2 generator tool", "generating %1 (%2)"), QStringLiteral("dcopidl"), QStringLiteral("dcopidl .* > ([^\\s;]+)"), 1 ), ActionFormat( ki18nc("compiling a file: %1 file %2 compiler", "compiling %1 (%2)"), QStringLiteral("dcopidl2cpp"), QStringLiteral("dcopidl2cpp (?:\\S* )*([^\\s;]+)"), 1 ), // match against Entering directory to update current build dir ActionFormat( ki18nc("change directory: %1: path", "entering %1 (%2)"), QStringLiteral("cd"), QStringLiteral("make\\[\\d+\\]: Entering directory (\\`|\\')(.+)'"), 2), // waf and scons use the same basic convention as make ActionFormat( ki18nc("change directory: %1: path", "entering %1 (%2)"), QStringLiteral("cd"), QStringLiteral("(Waf|scons): Entering directory (\\`|\\')(.+)'"), 3) }; FilteredItem item(line); for (const auto& curActFilter : ACTION_FILTERS) { const auto match = curActFilter.expression.match(line); if( match.hasMatch() ) { item.type = FilteredItem::ActionItem; KLocalizedString shortened = curActFilter.label; if (curActFilter.fileGroup != -1) { shortened = shortened.subs(match.captured(curActFilter.fileGroup)); } if (curActFilter.toolGroup != -1 ) { shortened = shortened.subs(match.captured(curActFilter.toolGroup)); } else if (!curActFilter.tool.isEmpty()) { shortened = shortened.subs(curActFilter.tool); } item.shortenedText = shortened.toString(); if( curActFilter.tool == "cd" ) { const Path path(match.captured(curActFilter.fileGroup)); m_currentDirs.push_back( path ); m_positionInCurrentDirs.insert( path , m_currentDirs.size() - 1 ); } // Special case for cmake: we parse the "Compiling " expression // and use it to find out about the build paths encountered during a build. // They are later searched by pathForFile to find source files corresponding to // compiler errors. + // Note: CMake objectfile has the format: "/path/to/four/CMakeFiles/file.o" if ( curActFilter.fileGroup != -1 && curActFilter.tool == "cmake" && line.contains("Building")) { - putDirAtEnd(Path(m_buildDir, match.captured(curActFilter.fileGroup).mid(1))); + const auto objectFile = match.captured(curActFilter.fileGroup); + const auto dir = objectFile.section(QStringLiteral("CMakeFiles/"), 0, 0); + putDirAtEnd(Path(m_buildDir, dir)); } break; } } return item; } FilteredItem CompilerFilterStrategy::errorInLine(const QString& line) { // All the possible string that indicate an error if we via Regex have been able to // extract file and linenumber from a given outputline // TODO: This seems clumsy -- and requires another scan of the line. // Merge this information into ErrorFormat? --Kevin using Indicator = QPair; static const Indicator INDICATORS[] = { // ld Indicator(QStringLiteral("undefined reference"), FilteredItem::ErrorItem), Indicator(QStringLiteral("undefined symbol"), FilteredItem::ErrorItem), Indicator(QStringLiteral("ld: cannot find"), FilteredItem::ErrorItem), Indicator(QStringLiteral("no such file"), FilteredItem::ErrorItem), // gcc Indicator(QStringLiteral("error"), FilteredItem::ErrorItem), // generic Indicator(QStringLiteral("warning"), FilteredItem::WarningItem), Indicator(QStringLiteral("info"), FilteredItem::InformationItem), Indicator(QStringLiteral("note"), FilteredItem::InformationItem), }; // A list of filters for possible compiler, linker, and make errors static const ErrorFormat ERROR_FILTERS[] = { // GCC - another case, eg. for #include "pixmap.xpm" which does not exists ErrorFormat( QStringLiteral("^([^:\t]+):([0-9]+):([0-9]+):([^0-9]+)"), 1, 2, 4, 3 ), // GCC ErrorFormat( QStringLiteral("^([^:\t]+):([0-9]+):([^0-9]+)"), 1, 2, 3 ), // GCC ErrorFormat( QStringLiteral("^(In file included from |[ ]+from )([^: \\t]+):([0-9]+)(:|,)(|[0-9]+)"), 2, 3, 5 ), // ICC ErrorFormat( QStringLiteral("^([^: \\t]+)\\(([0-9]+)\\):([^0-9]+)"), 1, 2, 3, QStringLiteral("intel") ), //libtool link ErrorFormat( QStringLiteral("^(libtool):( link):( warning): "), 0, 0, 0 ), // make ErrorFormat( QStringLiteral("No rule to make target"), 0, 0, 0 ), // cmake ErrorFormat( QStringLiteral("^([^: \\t]+):([0-9]+):"), 1, 2, 0, QStringLiteral("cmake") ), // cmake ErrorFormat( QStringLiteral("CMake (Error|Warning) (|\\([a-zA-Z]+\\) )(in|at) ([^:]+):($|[0-9]+)"), 4, 5, 1, QStringLiteral("cmake") ), // cmake/automoc // example: AUTOMOC: error: /foo/bar.cpp The file includes (...), // example: AUTOMOC: error: /foo/bar.cpp: The file includes (...) // note: ':' after file name isn't always appended, see http://cmake.org/gitweb?p=cmake.git;a=commitdiff;h=317d8498aa02c9f486bf5071963bb2034777cdd6 // example: AUTOGEN: error: /foo/bar.cpp: The file includes (...) // note: AUTOMOC got renamed to AUTOGEN at some point ErrorFormat( QStringLiteral("^(AUTOMOC|AUTOGEN): error: ([^:]+):? (The file .*)$"), 2, 0, 0 ), // via qt4_automoc // example: automoc4: The file "/foo/bar.cpp" includes the moc file "bar1.moc", but ... ErrorFormat( QStringLiteral("^automoc4: The file \"([^\"]+)\" includes the moc file"), 1, 0, 0 ), // Fortran ErrorFormat( QStringLiteral("\"(.*)\", line ([0-9]+):(.*)"), 1, 2, 3 ), // GFortran ErrorFormat( QStringLiteral("^(.*):([0-9]+)\\.([0-9]+):(.*)"), 1, 2, 4, QStringLiteral("gfortran"), 3 ), // Jade ErrorFormat( QStringLiteral("^[a-zA-Z]+:([^: \t]+):([0-9]+):[0-9]+:[a-zA-Z]:(.*)"), 1, 2, 3 ), // ifort ErrorFormat( QStringLiteral("^fortcom: (.*): (.*), line ([0-9]+):(.*)"), 2, 3, 1, QStringLiteral("intel") ), // PGI ErrorFormat( QStringLiteral("PGF9(.*)-(.*)-(.*)-(.*) \\((.*): ([0-9]+)\\)"), 5, 6, 4, QStringLiteral("pgi") ), // PGI (2) ErrorFormat( QStringLiteral("PGF9(.*)-(.*)-(.*)-Symbol, (.*) \\((.*)\\)"), 5, 5, 4, QStringLiteral("pgi") ), }; FilteredItem item(line); for (const auto& curErrFilter : ERROR_FILTERS) { const auto match = curErrFilter.expression.match(line); if( match.hasMatch() && !( line.contains( QLatin1String("Each undeclared identifier is reported only once") ) || line.contains( QLatin1String("for each function it appears in.") ) ) ) { if(curErrFilter.fileGroup > 0) { if( curErrFilter.compiler == "cmake" ) { // Unfortunately we cannot know if an error or an action comes first in cmake, and therefore we need to do this if( m_currentDirs.empty() ) { putDirAtEnd( m_buildDir.parent() ); } } item.url = pathForFile( match.captured( curErrFilter.fileGroup ) ).toUrl(); } item.lineNo = match.captured( curErrFilter.lineGroup ).toInt() - 1; if(curErrFilter.columnGroup >= 0) { item.columnNo = match.captured( curErrFilter.columnGroup ).toInt() - 1; } else { item.columnNo = 0; } QString txt = match.captured(curErrFilter.textGroup); // Find the indicator which happens most early. int earliestIndicatorIdx = txt.length(); for (const auto& curIndicator : INDICATORS) { int curIndicatorIdx = txt.indexOf(curIndicator.first, 0, Qt::CaseInsensitive); if((curIndicatorIdx >= 0) && (earliestIndicatorIdx > curIndicatorIdx)) { earliestIndicatorIdx = curIndicatorIdx; item.type = curIndicator.second; } } // Make the item clickable if it comes with the necessary file information if (item.url.isValid()) { item.isActivatable = true; if(item.type == FilteredItem::InvalidItem) { // If there are no error indicators in the line // maybe this is a multiline case if(isMultiLineCase(curErrFilter)) { item.type = FilteredItem::ErrorItem; } else { // Okay so we couldn't find anything to indicate an error, but we have file and lineGroup // Lets keep this item clickable and indicate this to the user. item.type = FilteredItem::InformationItem; } } } break; } } return item; } /// --- Script error filter strategy --- ScriptErrorFilterStrategy::ScriptErrorFilterStrategy() { } FilteredItem ScriptErrorFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem ScriptErrorFilterStrategy::errorInLine(const QString& line) { // A list of filters for possible Python and PHP errors static const ErrorFormat SCRIPT_ERROR_FILTERS[] = { ErrorFormat( QStringLiteral("^ File \"(.*)\", line ([0-9]+)(.*$|, in(.*)$)"), 1, 2, -1 ), ErrorFormat( QStringLiteral("^.*(/.*):([0-9]+).*$"), 1, 2, -1 ), ErrorFormat( QStringLiteral("^.* in (/.*) on line ([0-9]+).*$"), 1, 2, -1 ) }; return match(SCRIPT_ERROR_FILTERS, line); } /// --- Native application error filter strategy --- NativeAppErrorFilterStrategy::NativeAppErrorFilterStrategy() { } FilteredItem NativeAppErrorFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem NativeAppErrorFilterStrategy::errorInLine(const QString& line) { static const ErrorFormat QT_APPLICATION_ERROR_FILTERS[] = { // QObject::connect related errors, also see err_method_notfound() in qobject.cpp // QObject::connect: No such slot Foo::bar() in /foo/bar.cpp:313 ErrorFormat(QStringLiteral("^QObject::connect: (?:No such|Parentheses expected,) (?:slot|signal) [^ ]* in (.*):([0-9]+)$"), 1, 2, -1), // ASSERT: "errors().isEmpty()" in file /foo/bar.cpp, line 49 ErrorFormat(QStringLiteral("^ASSERT: \"(.*)\" in file (.*), line ([0-9]+)$"), 2, 3, -1), // QFATAL : FooTest::testBar() ASSERT: "index.isValid()" in file /foo/bar.cpp, line 32 ErrorFormat(QStringLiteral("^QFATAL : (.*) ASSERT: \"(.*)\" in file (.*), line ([0-9]+)$"), 3, 4, -1), // Catch: // FAIL! : FooTest::testBar() Compared pointers are not the same // Actual ... // Expected ... // Loc: [/foo/bar.cpp(33)] // // Do *not* catch: // ... // Loc: [Unknown file(0)] ErrorFormat(QStringLiteral("^ Loc: \\[(.*)\\(([1-9][0-9]*)\\)\\]$"), 1, 2, -1) }; return match(QT_APPLICATION_ERROR_FILTERS, line); } /// --- Static Analysis filter strategy --- StaticAnalysisFilterStrategy::StaticAnalysisFilterStrategy() { } FilteredItem StaticAnalysisFilterStrategy::actionInLine(const QString& line) { return FilteredItem(line); } FilteredItem StaticAnalysisFilterStrategy::errorInLine(const QString& line) { // A list of filters for static analysis tools (krazy2, cppcheck) static const ErrorFormat STATIC_ANALYSIS_FILTERS[] = { // CppCheck ErrorFormat( QStringLiteral("^\\[(.*):([0-9]+)\\]:(.*)"), 1, 2, 3 ), // krazy2 ErrorFormat( QStringLiteral("^\\t([^:]+).*line#([0-9]+).*"), 1, 2, -1 ), // krazy2 without line info ErrorFormat( QStringLiteral("^\\t(.*): missing license"), 1, -1, -1 ) }; return match(STATIC_ANALYSIS_FILTERS, line); } diff --git a/outputview/tests/test_filteringstrategy.cpp b/outputview/tests/test_filteringstrategy.cpp index ee80fdcb7..7462c0f94 100644 --- a/outputview/tests/test_filteringstrategy.cpp +++ b/outputview/tests/test_filteringstrategy.cpp @@ -1,485 +1,477 @@ /* This file is part of KDevelop Copyright 2012 Milian Wolff Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "test_filteringstrategy.h" #include "testlinebuilderfunctions.h" #include #include #include -QTEST_GUILESS_MAIN(KDevelop::TestFilteringStrategy) +using namespace KDevelop; + +QTEST_GUILESS_MAIN(TestFilteringStrategy) namespace QTest { template<> -inline char* toString(const KDevelop::FilteredItem::FilteredOutputItemType& type) +inline char* toString(const FilteredItem::FilteredOutputItemType& type) { switch (type) { - case KDevelop::FilteredItem::ActionItem: + case FilteredItem::ActionItem: return qstrdup("ActionItem"); - case KDevelop::FilteredItem::CustomItem: + case FilteredItem::CustomItem: return qstrdup("CustomItem"); - case KDevelop::FilteredItem::ErrorItem: + case FilteredItem::ErrorItem: return qstrdup("ErrorItem"); - case KDevelop::FilteredItem::InformationItem: + case FilteredItem::InformationItem: return qstrdup("InformationItem"); - case KDevelop::FilteredItem::InvalidItem: + case FilteredItem::InvalidItem: return qstrdup("InvalidItem"); - case KDevelop::FilteredItem::StandardItem: + case FilteredItem::StandardItem: return qstrdup("StandardItem"); - case KDevelop::FilteredItem::WarningItem: + case FilteredItem::WarningItem: return qstrdup("WarningItem"); } return qstrdup("unknown"); } } -namespace KDevelop -{ - void TestFilteringStrategy::testNoFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expected"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << buildCompilerActionLine() << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line") << buildCompilerInformationLine() << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem; } void TestFilteringStrategy::testNoFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expected); NoFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expected); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expected); } void TestFilteringStrategy::testCompilerFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line") << buildCompilerInformationLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line2") << buildInfileIncludedFromFirstLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("compiler-information-line3") << buildInfileIncludedFromSecondLine() << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-line1") << "CMake Error at CMakeLists.txt:2 (cmake_minimum_required):" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-multiline1") << "CMake Error: Error in cmake code at" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cmake-error-multiline2") << buildCmakeConfigureMultiLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-warning-line") << "CMake Warning (dev) in CMakeLists.txt:" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("cmake-automoc-error") << "AUTOMOC: error: /foo/bar.cpp The file includes the moc file \"moc_bar1.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("cmake-automoc4-error") << "automoc4: The file \"/foo/bar.cpp\" includes the moc file \"bar1.moc\"" << FilteredItem::InformationItem << FilteredItem::InvalidItem; QTest::newRow("cmake-autogen-error") << "AUTOGEN: error: /foo/bar.cpp The file includes the moc file \"moc_bar1.cpp\"" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("linker-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::ActionItem; QTest::newRow("linker-error-line") << buildLinkerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testCompilerFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testCompilerFilterstrategyMultipleKeywords_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("warning-containing-error-word") << "RingBuffer.cpp:64:6: warning: unused parameter ‘errorItem’ [-Wunused-parameter]" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("error-containing-info-word") << "NodeSet.hpp:89:27: error: ‘Info’ was not declared in this scope" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("warning-in-filename-containing-error-word") << "ErrorHandling.cpp:100:56: warning: unused parameter ‘item’ [-Wunused-parameter]" << FilteredItem::WarningItem << FilteredItem::InvalidItem; QTest::newRow("error-in-filename-containing-warning-word") << "WarningHandling.cpp:100:56: error: ‘Item’ was not declared in this scope" << FilteredItem::ErrorItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testCompilerFilterstrategyMultipleKeywords() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testCompilerFilterStrategyShortenedText_data() { QTest::addColumn("line"); QTest::addColumn("expectedShortenedText"); QTest::newRow("c++-compile") << "g++ -c main.cpp -o main.o" << "compiling main.cpp (g++)"; QTest::newRow("clang++-link") << "clang++ -c main.cpp -o main.o" << "compiling main.cpp (clang++)"; // see bug: https://bugs.kde.org/show_bug.cgi?id=240017 QTest::newRow("mpicc-link") << "/usr/bin/mpicc -c main.cpp -o main.o" << "compiling main.cpp (mpicc)"; QTest::newRow("c++-link") << "/usr/bin/g++ main.cpp -o main" << "linking main (g++)"; QTest::newRow("clang++-link") << "/usr/bin/clang++ main.cpp -o a.out" << "linking a.out (clang++)"; QTest::newRow("mpicc-link") << "mpicc main.cpp -o main" << "linking main (mpicc)"; } void TestFilteringStrategy::testCompilerFilterStrategyShortenedText() { QFETCH(QString, line); QFETCH(QString, expectedShortenedText); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); CompilerFilterStrategy testee(projecturl); FilteredItem item = testee.actionInLine(line); QCOMPARE(item.shortenedText, expectedShortenedText); } void TestFilteringStrategy::testScriptErrorFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testScriptErrorFilterStrategy() { QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); ScriptErrorFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testNativeAppErrorFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("file"); QTest::addColumn("lineNo"); QTest::addColumn("column"); QTest::addColumn("itemtype"); // TODO: qt-connect-* and friends shouldn't be error items but warnings items instead // this needs refactoring in outputfilteringstrategies, though... QTest::newRow("qt-connect-nosuch-slot") << "QObject::connect: No such slot Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-connect-nosuch-signal") << "QObject::connect: No such signal Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-connect-parentheses-slot") << "QObject::connect: Parentheses expected, slot Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-connect-parentheses-signal") << "QObject::connect: Parentheses expected, signal Foo::bar() in /foo/bar.cpp:313" << "/foo/bar.cpp" << 312 << 0 << FilteredItem::ErrorItem; QTest::newRow("qt-assert") << "ASSERT: \"errors().isEmpty()\" in file /tmp/foo/bar.cpp, line 49" << "/tmp/foo/bar.cpp" << 48 << 0 << FilteredItem::ErrorItem; QTest::newRow("qttest-assert") << "QFATAL : FooTest::testBar() ASSERT: \"index.isValid()\" in file /foo/bar.cpp, line 32" << "/foo/bar.cpp" << 31 << 0 << FilteredItem::ErrorItem; QTest::newRow("qttest-loc") << " Loc: [/foo/bar.cpp(33)]" << "/foo/bar.cpp" << 32 << 0 << FilteredItem::ErrorItem; QTest::newRow("qttest-loc-nocatch") << " Loc: [Unknown file(0)]" << "" << -1 << -1 << FilteredItem::InvalidItem; } void TestFilteringStrategy::testNativeAppErrorFilterStrategy() { QFETCH(QString, line); QFETCH(QString, file); QFETCH(int, lineNo); QFETCH(int, column); QFETCH(FilteredItem::FilteredOutputItemType, itemtype); NativeAppErrorFilterStrategy testee; FilteredItem item = testee.errorInLine(line); QCOMPARE(item.url.path(), file); QCOMPARE(item.lineNo , lineNo); QCOMPARE(item.columnNo , column); QCOMPARE(item.type , itemtype); } void TestFilteringStrategy::testStaticAnalysisFilterStrategy_data() { QTest::addColumn("line"); QTest::addColumn("expectedError"); QTest::addColumn("expectedAction"); QTest::newRow("cppcheck-info-line") << buildCppCheckInformationLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("cppcheck-error-line") << buildCppCheckErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line") << buildKrazyErrorLine() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-two-colons") << buildKrazyErrorLine2() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-error-description") << buildKrazyErrorLine3() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("krazy2-error-line-wo-line-info") << buildKrazyErrorLineNoLineInfo() << FilteredItem::ErrorItem << FilteredItem::InvalidItem; QTest::newRow("compiler-line") << buildCompilerLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-error-line") << buildCompilerErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("compiler-action-line") << "linking testCustombuild (g++)" << FilteredItem::InvalidItem << FilteredItem::InvalidItem; QTest::newRow("python-error-line") << buildPythonErrorLine() << FilteredItem::InvalidItem << FilteredItem::InvalidItem; } void TestFilteringStrategy::testStaticAnalysisFilterStrategy() { // Test that url's are extracted correctly as well QString referencePath = projectPath() + "main.cpp"; QFETCH(QString, line); QFETCH(FilteredItem::FilteredOutputItemType, expectedError); QFETCH(FilteredItem::FilteredOutputItemType, expectedAction); StaticAnalysisFilterStrategy testee; FilteredItem item1 = testee.errorInLine(line); QString extractedPath = item1.url.toLocalFile(); QVERIFY((item1.type != FilteredItem::ErrorItem) || ( extractedPath == referencePath)); QCOMPARE(item1.type, expectedError); item1 = testee.actionInLine(line); QCOMPARE(item1.type, expectedAction); } void TestFilteringStrategy::testCompilerFilterstrategyUrlFromAction_data() { QTest::addColumn("line"); QTest::addColumn("expectedLastDir"); QString basepath = projectPath(); QTest::newRow("cmake-line1") - << "[ 25%] Building CXX object /path/to/one/CMakeFiles/file.o" << QString( basepath + "/path/to/one" ); + << "[ 25%] Building CXX object path/to/one/CMakeFiles/file.o" << QString( basepath + "/path/to/one" ); QTest::newRow("cmake-line2") - << "[ 26%] Building CXX object /path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two"); + << "[ 26%] Building CXX object path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two"); QTest::newRow("cmake-line3") - << "[ 26%] Building CXX object /path/to/three/CMakeFiles/file.o" << QString( basepath + "/path/to/three"); + << "[ 26%] Building CXX object path/to/three/CMakeFiles/file.o" << QString( basepath + "/path/to/three"); QTest::newRow("cmake-line4") - << "[ 26%] Building CXX object /path/to/four/CMakeFiles/file.o" << QString( basepath + "/path/to/four"); + << "[ 26%] Building CXX object path/to/four/CMakeFiles/file.o" << QString( basepath + "/path/to/four"); QTest::newRow("cmake-line5") - << "[ 26%] Building CXX object /path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two"); + << "[ 26%] Building CXX object path/to/two/CMakeFiles/file.o" << QString( basepath + "/path/to/two"); QTest::newRow("cd-line6") << QString("make[4]: Entering directory '" + basepath + "/path/to/one/'") << QString( basepath + "/path/to/one"); QTest::newRow("waf-cd") << QString("Waf: Entering directory `" + basepath + "/path/to/two/'") << QString( basepath + "/path/to/two"); QTest::newRow("cmake-line7") << QStringLiteral("[ 50%] Building CXX object CMakeFiles/testdeque.dir/RingBuffer.cpp.o") << QString( basepath); } void TestFilteringStrategy::testCompilerFilterstrategyUrlFromAction() { QFETCH(QString, line); QFETCH(QString, expectedLastDir); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); static CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.actionInLine(line); QCOMPARE(testee.getCurrentDirs().last(), expectedLastDir); } void TestFilteringStrategy::benchMarkCompilerFilterAction() { QString projecturl = projectPath(); QStringList outputlines; const int numLines(10000); int j(0), k(0), l(0), m(0); do { ++j; ++k; ++l; QString tmp; if(m % 2 == 0) { tmp = QStringLiteral( "[ 26%] Building CXX object /this/is/the/path/to/the/files/%1/%2/%3/CMakeFiles/file.o").arg( j ).arg( k ).arg( l ); } else { tmp = QString( "make[4]: Entering directory '" + projecturl + "/this/is/the/path/to/the/files/%1/%2/%3/").arg( j ).arg( k ).arg( l ); } outputlines << tmp; if(j % 6 == 0) { j = 0; ++m; } if(k % 9 == 0) { k = 0; ++m; } if(l % 13 == 0) { l = 0; ++m; } } while(outputlines.size() < numLines ); // gives us numLines (-ish) QElapsedTimer totalTime; totalTime.start(); static CompilerFilterStrategy testee(QUrl::fromLocalFile(projecturl)); FilteredItem item1("dummyline", FilteredItem::InvalidItem); QBENCHMARK { for(int i = 0; i < outputlines.size(); ++i) { item1 = testee.actionInLine(outputlines.at(i)); } } const qint64 elapsed = totalTime.elapsed(); qDebug() << "ms elapsed to add directories: " << elapsed; qDebug() << "total number of directories: " << outputlines.count(); const double avgDirectoryInsertion = double(elapsed) / outputlines.count(); qDebug() << "average ms spend pr. dir: " << avgDirectoryInsertion; QVERIFY(avgDirectoryInsertion < 2); } void TestFilteringStrategy::testExtractionOfLineAndColumn_data() { QTest::addColumn("line"); QTest::addColumn("file"); QTest::addColumn("lineNr"); QTest::addColumn("column"); QTest::addColumn("itemtype"); QTest::newRow("gcc-with-col") << "/path/to/file.cpp:123:45: fatal error: ..." << "/path/to/file.cpp" << 122 << 44 << FilteredItem::ErrorItem; QTest::newRow("gcc-no-col") << "/path/to/file.cpp:123: error ..." << "/path/to/file.cpp" << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcom") << "fortcom: Error: Ogive8.f90, line 123: ..." << QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcomError") << "fortcom: Error: ./Ogive8.f90, line 123: ..." << QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::ErrorItem; QTest::newRow("fortcomWarning") << "fortcom: Warning: /path/Ogive8.f90, line 123: ..." << "/path/Ogive8.f90" << 122 << 0 << FilteredItem::WarningItem; QTest::newRow("fortcomInfo") << "fortcom: Info: Ogive8.f90, line 123: ..." << QString(projectPath() + "/Ogive8.f90") << 122 << 0 << FilteredItem::InformationItem; QTest::newRow("libtool") << "libtool: link: warning: ..." << "" << -1 << 0 << FilteredItem::WarningItem; QTest::newRow("gfortranError1") << "/path/to/file.f90:123.456:Error: ...." << "/path/to/file.f90" << 122 << 455 << FilteredItem::ErrorItem; QTest::newRow("gfortranError2") << "/path/flib.f90:3567.22:" << "/path/flib.f90" << 3566 << 21 << FilteredItem::ErrorItem; } void TestFilteringStrategy::testExtractionOfLineAndColumn() { QFETCH(QString, line); QFETCH(QString, file); QFETCH(int, lineNr); QFETCH(int, column); QFETCH(FilteredItem::FilteredOutputItemType, itemtype); QUrl projecturl = QUrl::fromLocalFile( projectPath() ); CompilerFilterStrategy testee(projecturl); FilteredItem item1 = testee.errorInLine(line); QCOMPARE(item1.type , itemtype); QCOMPARE(item1.url.path(), file); QCOMPARE(item1.lineNo , lineNr); QCOMPARE(item1.columnNo , column); } - - - - -} - - diff --git a/plugins/appwizard/projectselectionpage.cpp b/plugins/appwizard/projectselectionpage.cpp index 38d7da6b5..949dd048e 100644 --- a/plugins/appwizard/projectselectionpage.cpp +++ b/plugins/appwizard/projectselectionpage.cpp @@ -1,370 +1,363 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2011 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) any later version. * * * ***************************************************************************/ #include "projectselectionpage.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include "ui_projectselectionpage.h" #include "projecttemplatesmodel.h" using namespace KDevelop; ProjectSelectionPage::ProjectSelectionPage(ProjectTemplatesModel *templatesModel, AppWizardDialog *wizardDialog) : AppWizardPageWidget(wizardDialog), m_templatesModel(templatesModel) { ui = new Ui::ProjectSelectionPage(); ui->setupUi(this); setContentsMargins(0,0,0,0); ui->descriptionContent->setBackgroundRole(QPalette::Base); ui->descriptionContent->setForegroundRole(QPalette::Text); ui->locationUrl->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly ); ui->locationUrl->setUrl(KDevelop::ICore::self()->projectController()->projectsBaseDirectory()); - ui->locationValidLabel->setText(QStringLiteral(" ")); + ui->locationValidWidget->hide(); + ui->locationValidWidget->setMessageType(KMessageWidget::Error); + ui->locationValidWidget->setCloseButtonVisible(false); connect( ui->locationUrl->lineEdit(), &KLineEdit::textEdited, this, &ProjectSelectionPage::urlEdited); connect( ui->locationUrl, &KUrlRequester::urlSelected, this, &ProjectSelectionPage::urlEdited); connect( ui->appNameEdit, &QLineEdit::textEdited, this, &ProjectSelectionPage::nameChanged ); m_listView = new KDevelop::MultiLevelListView(this); m_listView->setLevels(2); m_listView->setHeaderLabels(QStringList() << i18n("Category") << i18n("Project Type")); m_listView->setModel(templatesModel); m_listView->setLastModelsFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); m_listView->setContentsMargins(0, 0, 0, 0); connect (m_listView, &MultiLevelListView::currentIndexChanged, this, &ProjectSelectionPage::typeChanged); ui->gridLayout->addWidget(m_listView, 0, 0, 1, 1); typeChanged(m_listView->currentIndex()); connect( ui->templateType, static_cast(&QComboBox::currentIndexChanged), this, &ProjectSelectionPage::templateChanged ); QPushButton* getMoreButton = new QPushButton(i18n("Get More Templates"), m_listView); getMoreButton->setIcon(QIcon::fromTheme("get-hot-new-stuff")); connect (getMoreButton, &QPushButton::clicked, this, &ProjectSelectionPage::moreTemplatesClicked); m_listView->addWidget(0, getMoreButton); QPushButton* loadButton = new QPushButton(m_listView); loadButton->setText(i18n("Load Template From File")); loadButton->setIcon(QIcon::fromTheme("application-x-archive")); connect (loadButton, &QPushButton::clicked, this, &ProjectSelectionPage::loadFileClicked); m_listView->addWidget(0, loadButton); m_wizardDialog = wizardDialog; } void ProjectSelectionPage::nameChanged() { validateData(); emit locationChanged( location() ); } ProjectSelectionPage::~ProjectSelectionPage() { delete ui; } void ProjectSelectionPage::typeChanged(const QModelIndex& idx) { if (!idx.model()) { qCDebug(PLUGIN_APPWIZARD) << "Index with no model"; return; } int children = idx.model()->rowCount(idx); ui->templateType->setVisible(children); ui->templateType->setEnabled(children > 1); if (children) { ui->templateType->setModel(m_templatesModel); ui->templateType->setRootModelIndex(idx); ui->templateType->setCurrentIndex(0); itemChanged(idx.child(0, 0)); } else { itemChanged(idx); } } void ProjectSelectionPage::templateChanged(int current) { QModelIndex idx=m_templatesModel->index(current, 0, ui->templateType->rootModelIndex()); itemChanged(idx); } void ProjectSelectionPage::itemChanged( const QModelIndex& current) { QString picPath = current.data( KDevelop::TemplatesModel::IconNameRole ).toString(); if( picPath.isEmpty() ) { QIcon icon("kdevelop"); ui->icon->setPixmap(icon.pixmap(128, 128)); ui->icon->setFixedHeight(128); } else { QPixmap pixmap( picPath ); ui->icon->setPixmap( pixmap ); ui->icon->setFixedHeight( pixmap.height() ); } // header name is either from this index directly or the parents if we show the combo box const QVariant headerData = ui->templateType->isVisible() ? current.parent().data() : current.data(); ui->header->setText(QStringLiteral("

%1

").arg(headerData.toString().trimmed())); ui->description->setText(current.data(KDevelop::TemplatesModel::CommentRole).toString()); validateData(); ui->propertiesBox->setEnabled(true); } QString ProjectSelectionPage::selectedTemplate() { QStandardItem *item = getCurrentItem(); if (item) return item->data().toString(); else return ""; } QUrl ProjectSelectionPage::location() { QUrl url = ui->locationUrl->url().adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + encodedAppName()); return url; } QString ProjectSelectionPage::appName() { return ui->appNameEdit->text(); } void ProjectSelectionPage::urlEdited() { validateData(); emit locationChanged( location() ); } -void setForeground(QLabel* label, KColorScheme::ForegroundRole role) -{ - QPalette p = label->palette(); - KColorScheme::adjustForeground(p, role, label->foregroundRole(), KColorScheme::Window); - label->setPalette(p); -} - void ProjectSelectionPage::validateData() { QUrl url = ui->locationUrl->url(); if( !url.isLocalFile() || url.isEmpty() ) { - ui->locationValidLabel->setText( i18n("Invalid location") ); - setForeground(ui->locationValidLabel, KColorScheme::NegativeText); + ui->locationValidWidget->setText( i18n("Invalid location") ); + ui->locationValidWidget->animatedShow(); emit invalid(); return; } if( appName().isEmpty() ) { - ui->locationValidLabel->setText( i18n("Empty project name") ); - setForeground(ui->locationValidLabel, KColorScheme::NegativeText); + ui->locationValidWidget->setText( i18n("Empty project name") ); + ui->locationValidWidget->animatedShow(); emit invalid(); return; } if( !appName().isEmpty() ) { QString appname = appName(); QString templatefile = m_wizardDialog->appInfo().appTemplate; // Read template file KConfig config(templatefile); KConfigGroup configgroup(&config, "General"); QString pattern = configgroup.readEntry( "ValidProjectName" , "^[a-zA-Z][a-zA-Z0-9_]+$" ); // Validation int pos = 0; QRegExp regex( pattern ); QRegExpValidator validator( regex ); if( validator.validate(appname, pos) == QValidator::Invalid ) { - ui->locationValidLabel->setText( i18n("Invalid project name") ); - setForeground(ui->locationValidLabel, KColorScheme::NegativeText); + ui->locationValidWidget->setText( i18n("Invalid project name") ); emit invalid(); return; } } QDir tDir(url.toLocalFile()); while (!tDir.exists() && !tDir.isRoot()) { if (!tDir.cdUp()) { break; } } if (tDir.exists()) { QFileInfo tFileInfo(tDir.absolutePath()); if (!tFileInfo.isWritable() || !tFileInfo.isExecutable()) { - ui->locationValidLabel->setText( i18n("Unable to create subdirectories, " + ui->locationValidWidget->setText( i18n("Unable to create subdirectories, " "missing permissions on: %1", tDir.absolutePath()) ); - setForeground(ui->locationValidLabel, KColorScheme::NegativeText); + ui->locationValidWidget->animatedShow(); emit invalid(); return; } } QStandardItem* item = getCurrentItem(); if( item && !item->hasChildren() ) { - ui->locationValidLabel->setText( QStringLiteral(" ") ); - setForeground(ui->locationValidLabel, KColorScheme::NormalText); + ui->locationValidWidget->animatedHide(); emit valid(); } else { - ui->locationValidLabel->setText( i18n("Invalid project template, please choose a leaf item") ); - setForeground(ui->locationValidLabel, KColorScheme::NegativeText); + ui->locationValidWidget->setText( i18n("Invalid project template, please choose a leaf item") ); + ui->locationValidWidget->animatedShow(); emit invalid(); return; } // Check for non-empty target directory. Not an error, but need to display a warning. url.setPath( url.path() + '/' + encodedAppName() ); QFileInfo fi( url.toLocalFile() ); if( fi.exists() && fi.isDir() ) { if( !QDir( fi.absoluteFilePath()).entryList( QDir::NoDotAndDotDot | QDir::AllEntries ).isEmpty() ) { - ui->locationValidLabel->setText( i18n("Path already exists and contains files. Open it as a project.") ); - setForeground(ui->locationValidLabel, KColorScheme::NegativeText); + ui->locationValidWidget->setText( i18n("Path already exists and contains files. Open it as a project.") ); + ui->locationValidWidget->animatedShow(); emit invalid(); return; } } } QByteArray ProjectSelectionPage::encodedAppName() { // : < > * ? / \ | " are invalid on windows QByteArray tEncodedName = appName().toUtf8(); for (int i = 0; i < tEncodedName.size(); ++i) { QChar tChar(tEncodedName.at( i )); if (tChar.isDigit() || tChar.isSpace() || tChar.isLetter() || tChar == '%') continue; QByteArray tReplace = QUrl::toPercentEncoding( tChar ); tEncodedName.replace( tEncodedName.at( i ) ,tReplace ); i = i + tReplace.size() - 1; } return tEncodedName; } QStandardItem* ProjectSelectionPage::getCurrentItem() const { QStandardItem* item = m_templatesModel->itemFromIndex( m_listView->currentIndex() ); if ( item && item->hasChildren() ) { const int currect = ui->templateType->currentIndex(); const QModelIndex idx = m_templatesModel->index( currect, 0, ui->templateType->rootModelIndex() ); item = m_templatesModel->itemFromIndex(idx); } return item; } bool ProjectSelectionPage::shouldContinue() { QFileInfo fi(location().toLocalFile()); if (fi.exists() && fi.isDir()) { if (!QDir(fi.absoluteFilePath()).entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()) { int res = KMessageBox::questionYesNo(this, i18n("The specified path already exists and contains files. " "Are you sure you want to proceed?")); return res == KMessageBox::Yes; } } return true; } void ProjectSelectionPage::loadFileClicked() { QString filter = "application/x-desktop application/x-bzip-compressed-tar application/zip"; const QString fileName = QFileDialog::getOpenFileName(this, i18n("Load Template From File"), QString(), filter); if (!fileName.isEmpty()) { QString destination = m_templatesModel->loadTemplateFile(fileName); QModelIndexList indexes = m_templatesModel->templateIndexes(destination); if (indexes.size() > 2) { m_listView->setCurrentIndex(indexes.at(1)); ui->templateType->setCurrentIndex(indexes.at(2).row()); } } } void ProjectSelectionPage::moreTemplatesClicked() { KNS3::DownloadDialog dialog("kdevappwizard.knsrc", this); dialog.exec(); auto entries = dialog.changedEntries(); if (entries.isEmpty()) { return; } m_templatesModel->refresh(); bool updated = false; foreach (const KNS3::Entry& entry, entries) { if (!entry.installedFiles().isEmpty()) { updated = true; setCurrentTemplate(entry.installedFiles().first()); break; } } if (!updated) { m_listView->setCurrentIndex(QModelIndex()); } } void ProjectSelectionPage::setCurrentTemplate (const QString& fileName) { QModelIndexList indexes = m_templatesModel->templateIndexes(fileName); if (indexes.size() > 1) { m_listView->setCurrentIndex(indexes.at(1)); } if (indexes.size() > 2) { ui->templateType->setCurrentIndex(indexes.at(2).row()); } } diff --git a/plugins/appwizard/projectselectionpage.ui b/plugins/appwizard/projectselectionpage.ui index 34bb56ebc..1e7365819 100644 --- a/plugins/appwizard/projectselectionpage.ui +++ b/plugins/appwizard/projectselectionpage.ui @@ -1,169 +1,175 @@ ProjectSelectionPage 0 0 807 598 false 0 0 Properties - + Application Name: Location: - - - KSqueezedTextLabel - - + 0 0 300 400 300 16777215 false QFrame::StyledPanel QFrame::Sunken 0 - + + 0 + + + 0 + + + 0 + + 0 true Qt::AlignHCenter|Qt::AlignTop true Qt::AlignHCenter|Qt::AlignTop 0 0 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true KUrlRequester QFrame
kurlrequester.h
1
- KSqueezedTextLabel - QLabel -
ksqueezedtextlabel.h
+ KMessageWidget + QFrame +
kmessagewidget.h
+ 1
appNameEdit
diff --git a/plugins/codeutils/codeutilsplugin.cpp b/plugins/codeutils/codeutilsplugin.cpp index 16ffee4c8..e5e065161 100644 --- a/plugins/codeutils/codeutilsplugin.cpp +++ b/plugins/codeutils/codeutilsplugin.cpp @@ -1,159 +1,160 @@ /* * This file is part of KDevelop * Copyright 2010 Milian Wolff * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU 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 "codeutilsplugin.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 Q_LOGGING_CATEGORY(PLUGIN_CODEUTILS, "kdevplatform.plugins.codeutils") using namespace KDevelop; using namespace KTextEditor; K_PLUGIN_FACTORY_WITH_JSON(CodeUtilsPluginFactory, "kdevcodeutils.json", registerPlugin(); ) CodeUtilsPlugin::CodeUtilsPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( "kdevcodeutils", parent ) { setXMLFile( "kdevcodeutils.rc" ); QAction* action = actionCollection()->addAction( "document_declaration" ); // i18n: action name; 'Document' is a verb action->setText( i18n( "Document Declaration" ) ); actionCollection()->setDefaultShortcut(action, i18n( "Alt+Shift+d" )); connect( action, SIGNAL(triggered(bool)), this, SLOT(documentDeclaration()) ); action->setToolTip( i18n( "Add Doxygen skeleton for declaration under cursor." ) ); // i18n: translate title same as the action name action->setWhatsThis( i18n( "Adds a basic Doxygen comment skeleton in front of " "the declaration under the cursor, e.g. with all the " "parameter of a function." ) ); action->setIcon( QIcon::fromTheme( "documentinfo" ) ); } void CodeUtilsPlugin::documentDeclaration() { View* view = ICore::self()->documentController()->activeTextDocumentView(); if ( !view ) { return; } DUChainReadLocker lock; TopDUContext* topCtx = DUChainUtils::standardContextForUrl(view->document()->url()); if ( !topCtx ) { return; } Declaration* dec = DUChainUtils::declarationInLine( KTextEditor::Cursor( view->cursorPosition() ), topCtx ); if ( !dec || dec->isForwardDeclaration() ) { return; } // finally - we found the declaration :) int line = dec->range().start.line; Cursor insertPos( line, 0 ); TemplateRenderer renderer; + renderer.setEmptyLinesPolicy(TemplateRenderer::TrimEmptyLines); renderer.addVariable("brief", i18n( "..." )); /* QString indentation = textDoc->line( insertPos.line() ); if (!indentation.isEmpty()) { int lastSpace = 0; while (indentation.at(lastSpace).isSpace()) { ++lastSpace; } indentation.truncate(lastSpace); } */ if (dec->isFunctionDeclaration()) { FunctionDescription description = FunctionDescription(DeclarationPointer(dec)); renderer.addVariable("function", QVariant::fromValue(description)); qCDebug(PLUGIN_CODEUTILS) << "Found function" << description.name << "with" << description.arguments.size() << "arguments"; } lock.unlock(); // TODO: Choose the template based on the language QString templateName = "doxygen_cpp"; auto languages = core()->languageController()->languagesForUrl(view->document()->url()); if (!languages.isEmpty()) { QString languageName = languages.first()->name(); if (languageName == "Php") { templateName = "phpdoc_php"; } else if (languageName == "Python") { templateName = "rest_python"; // Python docstrings appear inside functions and classes, not above them insertPos = Cursor(line+1, 0); } } QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevcodeutils/templates/" + templateName + ".txt"); if (fileName.isEmpty()) { qWarning() << "No suitable template found" << fileName; return; } const QString comment = renderer.renderFile(QUrl::fromLocalFile(fileName)); view->insertTemplate(insertPos, comment); } CodeUtilsPlugin::~CodeUtilsPlugin() { } #include "codeutilsplugin.moc" diff --git a/plugins/codeutils/doc_templates/doxygen_cpp.txt b/plugins/codeutils/doc_templates/doxygen_cpp.txt index 2c661d442..2f3725227 100644 --- a/plugins/codeutils/doc_templates/doxygen_cpp.txt +++ b/plugins/codeutils/doc_templates/doxygen_cpp.txt @@ -1,10 +1,10 @@ /** * @brief ${% templatetag openbrace %}{{ brief }}{% templatetag closebrace %} * {% for arg in function.arguments %} * @param {{ arg.name }} ${p_{{arg.name}}:...}{% if arg.value %} Defaults to {{ arg.value }}.{% endif %} {% endfor %} -{% if function.returnType %} +{% if function.returnType %}{% ifnotequal function.returnType "void" %} * @return {{ function.returnType }} -{% endif %} +{% endifnotequal %}{% endif %} */ diff --git a/plugins/openwith/openwithplugin.cpp b/plugins/openwith/openwithplugin.cpp index d55fa451e..f4b703ddb 100644 --- a/plugins/openwith/openwithplugin.cpp +++ b/plugins/openwith/openwithplugin.cpp @@ -1,278 +1,294 @@ /* * This file is part of KDevelop * Copyright 2009 Andreas Pakulat * * 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 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 "openwithplugin.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 using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(KDevOpenWithFactory, "kdevopenwith.json", registerPlugin();) namespace { bool sortActions(QAction* left, QAction* right) { return left->text() < right->text(); } bool isTextEditor(const KService::Ptr& service) { return service->serviceTypes().contains( "KTextEditor/Document" ); } QString defaultForMimeType(const QString& mimeType) { KConfigGroup config = KSharedConfig::openConfig()->group("Open With Defaults"); if (config.hasKey(mimeType)) { QString storageId = config.readEntry(mimeType, QString()); if (!storageId.isEmpty() && KService::serviceByStorageId(storageId)) { return storageId; } } return QString(); } bool canOpenDefault(const QString& mimeType) { if (defaultForMimeType(mimeType).isEmpty() && mimeType == "inode/directory") { // potentially happens in non-kde environments apparently, see https://git.reviewboard.kde.org/r/122373 return KMimeTypeTrader::self()->preferredService(mimeType); } else { return true; } } } OpenWithPlugin::OpenWithPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( "kdevopenwith", parent ), m_actionMap( 0 ) { KDEV_USE_EXTENSION_INTERFACE( IOpenWith ) } OpenWithPlugin::~OpenWithPlugin() { } KDevelop::ContextMenuExtension OpenWithPlugin::contextMenuExtension( KDevelop::Context* context ) { // do not recurse if (context->hasType(KDevelop::Context::OpenWithContext)) { return ContextMenuExtension(); } m_urls.clear(); m_actionMap.reset(); m_services.clear(); FileContext* filectx = dynamic_cast( context ); ProjectItemContext* projctx = dynamic_cast( context ); if ( filectx && filectx->urls().count() > 0 ) { m_urls = filectx->urls(); } else if ( projctx && projctx->items().count() > 0 ) { // For now, let's handle *either* files only *or* directories only const int wantedType = projctx->items().first()->type(); foreach( ProjectBaseItem* item, projctx->items() ) { if (wantedType == ProjectBaseItem::File && item->file()) { m_urls << item->file()->path().toUrl(); } else if ((wantedType == ProjectBaseItem::Folder || wantedType == ProjectBaseItem::BuildFolder) && item->folder()) { m_urls << item->folder()->path().toUrl(); } } } if (m_urls.isEmpty()) { return KDevelop::ContextMenuExtension(); } m_actionMap.reset(new QSignalMapper( this )); connect( m_actionMap.data(), static_cast(&QSignalMapper::mapped), this, &OpenWithPlugin::open ); // Ok, lets fetch the mimetype for the !!first!! url and the relevant services // TODO: Think about possible alternatives to using the mimetype of the first url. QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(m_urls.first()); m_mimeType = mimetype.name(); QList partActions = actionsForServiceType("KParts/ReadOnlyPart"); QList appActions = actionsForServiceType("Application"); OpenWithContext subContext(m_urls, mimetype); QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &subContext ); foreach( const ContextMenuExtension& ext, extensions ) { appActions += ext.actions(ContextMenuExtension::OpenExternalGroup); partActions += ext.actions(ContextMenuExtension::OpenEmbeddedGroup); } + { + auto other = new QAction(i18n("other..."), this); + connect(other, &QAction::triggered, this, [this] { + auto dialog = new KOpenWithDialog(m_urls, ICore::self()->uiController()->activeMainWindow()); + if (dialog->exec() == QDialog::Accepted && dialog->service()) { + openService(dialog->service()); + } + }); + appActions << other; + } + // Now setup a menu with actions for each part and app QMenu* menu = new QMenu( i18n("Open With" ) ); menu->setIcon( QIcon::fromTheme( "document-open" ) ); if (!partActions.isEmpty()) { menu->addSection(i18n("Embedded Editors")); menu->addActions( partActions ); } if (!appActions.isEmpty()) { menu->addSection(i18n("External Applications")); menu->addActions( appActions ); } KDevelop::ContextMenuExtension ext; if (canOpenDefault(m_mimeType)) { QAction* openAction = new QAction( i18n( "Open" ), this ); openAction->setIcon( QIcon::fromTheme( "document-open" ) ); connect( openAction, SIGNAL(triggered()), SLOT(openDefault()) ); ext.addAction( KDevelop::ContextMenuExtension::FileGroup, openAction ); } if (!menu->isEmpty()) { ext.addAction(KDevelop::ContextMenuExtension::FileGroup, menu->menuAction()); } else { delete menu; } return ext; } QList OpenWithPlugin::actionsForServiceType( const QString& serviceType ) { KService::List list = KMimeTypeTrader::self()->query( m_mimeType, serviceType ); KService::Ptr pref = KMimeTypeTrader::self()->preferredService( m_mimeType, serviceType ); m_services += list; QList actions; QAction* standardAction = 0; const QString defaultId = defaultForMimeType(m_mimeType); foreach( KService::Ptr svc, list ) { QAction* act = new QAction( isTextEditor(svc) ? i18n("Default Editor") : svc->name(), this ); act->setIcon( QIcon::fromTheme( svc->icon() ) ); if (svc->storageId() == defaultId || (defaultId.isEmpty() && isTextEditor(svc))) { QFont font = act->font(); font.setBold(true); act->setFont(font); } connect(act, &QAction::triggered, m_actionMap.data(), static_cast(&QSignalMapper::map)); m_actionMap->setMapping( act, svc->storageId() ); actions << act; if ( isTextEditor(svc) ) { standardAction = act; } else if ( svc->storageId() == pref->storageId() ) { standardAction = act; } } std::sort(actions.begin(), actions.end(), sortActions); if (standardAction) { actions.removeOne(standardAction); actions.prepend(standardAction); } return actions; } void OpenWithPlugin::openDefault() { // check preferred handler const QString defaultId = defaultForMimeType(m_mimeType); if (!defaultId.isEmpty()) { open(defaultId); return; } // default handlers if (m_mimeType == "inode/directory") { KService::Ptr service = KMimeTypeTrader::self()->preferredService(m_mimeType); KRun::runService(*service, m_urls, ICore::self()->uiController()->activeMainWindow()); } else { foreach( const QUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u ); } } } void OpenWithPlugin::open( const QString& storageid ) { - KService::Ptr svc = KService::serviceByStorageId( storageid ); - if( svc->isApplication() ) { - KRun::runService( *svc, m_urls, ICore::self()->uiController()->activeMainWindow() ); + openService(KService::serviceByStorageId( storageid )); +} + +void OpenWithPlugin::openService(const KService::Ptr& service) +{ + if (service->isApplication()) { + KRun::runService( *service, m_urls, ICore::self()->uiController()->activeMainWindow() ); } else { - QString prefName = svc->desktopEntryName(); - if ( isTextEditor(svc) ) { + QString prefName = service->desktopEntryName(); + if (isTextEditor(service)) { // If the user chose a KTE part, lets make sure we're creating a TextDocument instead of // a PartDocument by passing no preferredpart to the documentcontroller // TODO: Solve this rather inside DocumentController prefName = ""; } foreach( const QUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u, prefName ); } } KConfigGroup config = KSharedConfig::openConfig()->group("Open With Defaults"); - if (storageid != config.readEntry(m_mimeType, QString())) { + if (service->storageId() != config.readEntry(m_mimeType, QString())) { int setDefault = KMessageBox::questionYesNo( qApp->activeWindow(), i18nc("%1: mime type name, %2: app/part name", "Do you want to open all '%1' files by default with %2?", - m_mimeType, svc->name() ), + m_mimeType, service->name() ), i18n("Set as default?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("OpenWith-%1").arg(m_mimeType) ); if (setDefault == KMessageBox::Yes) { - config.writeEntry(m_mimeType, storageid); + config.writeEntry(m_mimeType, service->storageId()); } } } void OpenWithPlugin::openFilesInternal( const QList& files ) { if (files.isEmpty()) { return; } m_urls = files; m_mimeType = QMimeDatabase().mimeTypeForUrl(m_urls.first()).name(); openDefault(); } #include "openwithplugin.moc" diff --git a/plugins/openwith/openwithplugin.h b/plugins/openwith/openwithplugin.h index 0dbfc3ce9..78e79c1e7 100644 --- a/plugins/openwith/openwithplugin.h +++ b/plugins/openwith/openwithplugin.h @@ -1,63 +1,64 @@ /* * This file is part of KDevelop * Copyright 2009 Andreas Pakulat * * 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 General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_OPENWITHPLUGIN_H #define KDEVPLATFORM_PLUGIN_OPENWITHPLUGIN_H #include #include #include #include "iopenwith.h" class QSignalMapper; namespace KDevelop { class ContextMenuExtension; class Context; } class OpenWithPlugin : public KDevelop::IPlugin, public KDevelop::IOpenWith { Q_OBJECT Q_INTERFACES( KDevelop::IOpenWith ) public: OpenWithPlugin( QObject* parent, const QVariantList& args ); virtual ~OpenWithPlugin(); virtual KDevelop::ContextMenuExtension contextMenuExtension ( KDevelop::Context* context ) override; protected: virtual void openFilesInternal( const QList& files ) override; private slots: - void open( const QString& ); + void open( const QString& storageId ); + void openService( const KService::Ptr& service ); void openDefault(); private: QList actionsForServiceType( const QString& serviceType ); QScopedPointer m_actionMap; QList m_urls; QString m_mimeType; KService::List m_services; }; #endif // KDEVPLATFORM_PLUGIN_OPENWITHPLUGIN_H diff --git a/plugins/patchreview/patchreview.cpp b/plugins/patchreview/patchreview.cpp index c864d4ab4..0136f0af9 100644 --- a/plugins/patchreview/patchreview.cpp +++ b/plugins/patchreview/patchreview.cpp @@ -1,537 +1,540 @@ /*************************************************************************** Copyright 2006-2009 David Nolden ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "patchreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ///Whether arbitrary exceptions that occurred while diff-parsing within the library should be caught #define CATCHLIBDIFF /* Exclude this file from doublequote_chars check as krazy doesn't understand std::string*/ //krazy:excludeall=doublequote_chars #include #include #include #include #include #include "patchhighlighter.h" #include "patchreviewtoolview.h" #include "localpatchsource.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_PATCHREVIEW, "kdevplatform.plugins.patchreview") using namespace KDevelop; namespace { // Maximum number of files to open directly within a tab when the review is started const int maximumFilesToOpenDirectly = 15; } Q_DECLARE_METATYPE( const Diff2::DiffModel* ) void PatchReviewPlugin::seekHunk( bool forwards, const QUrl& fileName ) { try { qCDebug(PLUGIN_PATCHREVIEW) << forwards << fileName << fileName.isEmpty(); if ( !m_modelList ) throw "no model"; for ( int a = 0; a < m_modelList->modelCount(); ++a ) { const Diff2::DiffModel* model = m_modelList->modelAt( a ); if ( !model || !model->differences() ) continue; QUrl file = urlForFileModel( model ); if ( !fileName.isEmpty() && fileName != file ) continue; IDocument* doc = ICore::self()->documentController()->documentForUrl( file ); if ( doc && m_highlighters.contains( doc->url() ) && m_highlighters[doc->url()] ) { if ( doc->textDocument() ) { const QList ranges = m_highlighters[doc->url()]->ranges(); KTextEditor::View * v = doc->activeTextView(); int bestLine = -1; if ( v ) { KTextEditor::Cursor c = v->cursorPosition(); for ( QList::const_iterator it = ranges.begin(); it != ranges.end(); ++it ) { int line = ( *it )->start().line(); if ( forwards ) { if ( line > c.line() && ( bestLine == -1 || line < bestLine ) ) bestLine = line; } else { if ( line < c.line() && ( bestLine == -1 || line > bestLine ) ) bestLine = line; } } if ( bestLine != -1 ) { v->setCursorPosition( KTextEditor::Cursor( bestLine, 0 ) ); return; } else if(fileName.isEmpty()) { int next = qBound(0, forwards ? a+1 : a-1, m_modelList->modelCount()-1); ICore::self()->documentController()->openDocument(urlForFileModel(m_modelList->modelAt(next))); } } } } } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "seekHunk():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "seekHunk():" << str; } qCDebug(PLUGIN_PATCHREVIEW) << "no matching hunk found"; } void PatchReviewPlugin::addHighlighting( const QUrl& highlightFile, IDocument* document ) { try { if ( !modelList() ) throw "no model"; for ( int a = 0; a < modelList()->modelCount(); ++a ) { Diff2::DiffModel* model = modelList()->modelAt( a ); if ( !model ) continue; QUrl file = urlForFileModel( model ); if ( file != highlightFile ) continue; qCDebug(PLUGIN_PATCHREVIEW) << "highlighting" << file.toDisplayString(); IDocument* doc = document; if( !doc ) doc = ICore::self()->documentController()->documentForUrl( file ); qCDebug(PLUGIN_PATCHREVIEW) << "highlighting file" << file << "with doc" << doc; if ( !doc || !doc->textDocument() ) continue; removeHighlighting( file ); m_highlighters[file] = new PatchHighlighter( model, doc, this ); } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } } void PatchReviewPlugin::highlightPatch() { try { if ( !modelList() ) throw "no model"; for ( int a = 0; a < modelList()->modelCount(); ++a ) { const Diff2::DiffModel* model = modelList()->modelAt( a ); if ( !model ) continue; QUrl file = urlForFileModel( model ); addHighlighting( file ); } } catch ( const QString & str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } catch ( const char * str ) { qCDebug(PLUGIN_PATCHREVIEW) << "highlightFile():" << str; } } void PatchReviewPlugin::removeHighlighting( const QUrl& file ) { if ( file.isEmpty() ) { ///Remove all highlighting qDeleteAll( m_highlighters ); m_highlighters.clear(); } else { HighlightMap::iterator it = m_highlighters.find( file ); if ( it != m_highlighters.end() ) { delete *it; m_highlighters.erase( it ); } } } void PatchReviewPlugin::notifyPatchChanged() { - qCDebug(PLUGIN_PATCHREVIEW) << "notifying patch change: " << m_patch->file(); - m_updateKompareTimer->start( 500 ); + if (m_patch) { + qCDebug(PLUGIN_PATCHREVIEW) << "notifying patch change: " << m_patch->file(); + m_updateKompareTimer->start( 500 ); + } else { + m_updateKompareTimer->stop(); + } } void PatchReviewPlugin::forceUpdate() { if( m_patch ) { m_patch->update(); notifyPatchChanged(); } } void PatchReviewPlugin::updateKompareModel() { if ( !m_patch ) { ///TODO: this method should be cleaned up, it can be called by the timer and /// e.g. https://bugs.kde.org/show_bug.cgi?id=267187 shows how it could /// lead to asserts before... return; } qCDebug(PLUGIN_PATCHREVIEW) << "updating model"; removeHighlighting(); m_modelList.reset( nullptr ); delete m_diffSettings; { IDocument* patchDoc = ICore::self()->documentController()->documentForUrl( m_patch->file() ); if( patchDoc ) patchDoc->reload(); } QString patchFile; if( m_patch->file().isLocalFile() ) patchFile = m_patch->file().toLocalFile(); else if( m_patch->file().isValid() && !m_patch->file().isEmpty() ) { patchFile = QStandardPaths::writableLocation(QStandardPaths::TempLocation); bool ret = KIO::copy( m_patch->file(), QUrl::fromLocalFile(patchFile) )->exec(); if( !ret ) { qWarning() << "Problem while downloading: " << m_patch->file() << "to" << patchFile; patchFile.clear(); } } if (!patchFile.isEmpty()) //only try to construct the model if we have a patch to load try { m_diffSettings = new DiffSettings( 0 ); m_kompareInfo.reset( new Kompare::Info() ); m_kompareInfo->localDestination = patchFile; m_kompareInfo->localSource = m_patch->baseDir().toLocalFile(); m_kompareInfo->depth = m_patch->depth(); m_kompareInfo->applied = m_patch->isAlreadyApplied(); m_modelList.reset( new Diff2::KompareModelList( m_diffSettings.data(), new QWidget, this ) ); m_modelList->slotKompareInfo( m_kompareInfo.data() ); try { m_modelList->openDirAndDiff(); } catch ( const QString & str ) { throw; } catch ( ... ) { throw QStringLiteral( "lib/libdiff2 crashed, memory may be corrupted. Please restart kdevelop." ); } emit patchChanged(); for( int i = 0; i < m_modelList->modelCount(); i++ ) { const Diff2::DiffModel* model = m_modelList->modelAt( i ); for( int j = 0; j < model->differences()->count(); j++ ) { model->differences()->at( j )->apply( m_patch->isAlreadyApplied() ); } } highlightPatch(); return; } catch ( const QString & str ) { KMessageBox::error( 0, str, i18n( "Kompare Model Update" ) ); } catch ( const char * str ) { KMessageBox::error( 0, str, i18n( "Kompare Model Update" ) ); } removeHighlighting(); m_modelList.reset( nullptr ); m_kompareInfo.reset( nullptr ); delete m_diffSettings; emit patchChanged(); } K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevpatchreview.json", registerPlugin();) class PatchReviewToolViewFactory : public KDevelop::IToolViewFactory { public: PatchReviewToolViewFactory( PatchReviewPlugin *plugin ) : m_plugin( plugin ) {} virtual QWidget* create( QWidget *parent = 0 ) override { return m_plugin->createToolView( parent ); } virtual Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } virtual QString id() const override { return "org.kdevelop.PatchReview"; } private: PatchReviewPlugin *m_plugin; }; PatchReviewPlugin::~PatchReviewPlugin() { removeHighlighting(); // Tweak to work around a crash on OS X; see https://bugs.kde.org/show_bug.cgi?id=338829 // and http://qt-project.org/forums/viewthread/38406/#162801 - if (m_patch) { - m_patch->deleteLater(); - } + // modified tweak: use setPatch() and deleteLater in that method. + setPatch(0); } void PatchReviewPlugin::clearPatch( QObject* _patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "clearing patch" << _patch << "current:" << ( QObject* )m_patch; IPatchSource::Ptr patch( ( IPatchSource* )_patch ); if( patch == m_patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "is current patch"; setPatch( IPatchSource::Ptr( new LocalPatchSource ) ); } } void PatchReviewPlugin::closeReview() { if( m_patch ) { removeHighlighting(); m_modelList.reset( 0 ); if( !dynamic_cast( m_patch.data() ) ) { // make sure "show" button still openes the file dialog to open a custom patch file setPatch( new LocalPatchSource ); } else emit patchChanged(); Sublime::Area* area = ICore::self()->uiController()->activeArea(); if( area->objectName() == "review" ) { if( ICore::self()->documentController()->saveAllDocuments() ) ICore::self()->uiController()->switchToArea( "code", KDevelop::IUiController::ThisWindow ); } } } void PatchReviewPlugin::cancelReview() { if( m_patch ) { m_patch->cancelReview(); closeReview(); } } void PatchReviewPlugin::finishReview( QList selection ) { if( m_patch && m_patch->finishReview( selection ) ) { closeReview(); } } void PatchReviewPlugin::startReview( IPatchSource* patch, IPatchReview::ReviewMode mode ) { Q_UNUSED( mode ); emit startingNewReview(); setPatch( patch ); QMetaObject::invokeMethod( this, "updateReview", Qt::QueuedConnection ); } void PatchReviewPlugin::switchToEmptyReviewArea() { foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { if (area->objectName() == "review") { emit area->clearDocuments(); } } if ( ICore::self()->uiController()->activeArea()->objectName() != "review" ) ICore::self()->uiController()->switchToArea( "review", KDevelop::IUiController::ThisWindow ); } QUrl PatchReviewPlugin::urlForFileModel( const Diff2::DiffModel* model ) { QUrl file = m_patch->baseDir(); file.setPath(file.toLocalFile() + '/' + model->destinationPath() + '/' + model->destinationFile()); return file; } void PatchReviewPlugin::updateReview() { if( !m_patch ) return; m_updateKompareTimer->stop(); switchToEmptyReviewArea(); IDocument* futureActiveDoc = ICore::self()->documentController()->openDocument( m_patch->file() ); updateKompareModel(); if ( !m_modelList || !futureActiveDoc || !futureActiveDoc->textDocument() ) { // might happen if e.g. openDocument dialog was cancelled by user // or under the theoretic possibility of a non-text document getting opened return; } futureActiveDoc->textDocument()->setReadWrite( false ); futureActiveDoc->setPrettyName( i18n( "Overview" ) ); IDocument* buddyDoc = futureActiveDoc; KTextEditor::ModificationInterface* modif = dynamic_cast( futureActiveDoc->textDocument() ); modif->setModifiedOnDiskWarning( false ); if( m_modelList->modelCount() < maximumFilesToOpenDirectly ) { //Open all relates files for( int a = 0; a < m_modelList->modelCount(); ++a ) { QUrl absoluteUrl = urlForFileModel( m_modelList->modelAt( a ) ); if( QFileInfo( absoluteUrl.toLocalFile() ).exists() && absoluteUrl.toLocalFile() != "/dev/null" ) { buddyDoc = ICore::self()->documentController()->openDocument( absoluteUrl, KTextEditor::Range::invalid(), IDocumentController::DoNotActivate, "", buddyDoc ); seekHunk( true, absoluteUrl ); //Jump to the first changed position }else{ // Maybe the file was deleted qCDebug(PLUGIN_PATCHREVIEW) << "could not open" << absoluteUrl << "because it doesn't exist"; } } } Q_ASSERT( futureActiveDoc ); ICore::self()->documentController()->activateDocument( futureActiveDoc ); bool b = ICore::self()->uiController()->findToolView( i18n( "Patch Review" ), m_factory ); Q_ASSERT( b ); Q_UNUSED( b ); } void PatchReviewPlugin::setPatch( IPatchSource* patch ) { if ( patch == m_patch ) { return; } if( m_patch ) { disconnect( m_patch.data(), &IPatchSource::patchChanged, this, &PatchReviewPlugin::notifyPatchChanged ); if ( qobject_cast( m_patch ) ) { // make sure we don't leak this // TODO: what about other patch sources? - delete m_patch; + m_patch->deleteLater(); } } m_patch = patch; if( m_patch ) { qCDebug(PLUGIN_PATCHREVIEW) << "setting new patch" << patch->name() << "with file" << patch->file() << "basedir" << patch->baseDir(); connect( m_patch.data(), &IPatchSource::patchChanged, this, &PatchReviewPlugin::notifyPatchChanged ); } QString finishText = i18n( "Finish Review" ); if( m_patch && !m_patch->finishReviewCustomText().isEmpty() ) finishText = m_patch->finishReviewCustomText(); m_finishReview->setText( finishText ); notifyPatchChanged(); } PatchReviewPlugin::PatchReviewPlugin( QObject *parent, const QVariantList & ) : KDevelop::IPlugin( "kdevpatchreview", parent ), m_patch( 0 ), m_factory( new PatchReviewToolViewFactory( this ) ) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IPatchReview ) qRegisterMetaType( "const Diff2::DiffModel*" ); setXMLFile( "kdevpatchreview.rc" ); connect( ICore::self()->documentController(), &IDocumentController::documentClosed, this, &PatchReviewPlugin::documentClosed ); connect( ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &PatchReviewPlugin::textDocumentCreated ); connect( ICore::self()->documentController(), &IDocumentController::documentSaved, this, &PatchReviewPlugin::documentSaved ); m_updateKompareTimer = new QTimer( this ); m_updateKompareTimer->setSingleShot( true ); connect( m_updateKompareTimer, &QTimer::timeout, this, &PatchReviewPlugin::updateKompareModel ); m_finishReview = new QAction(i18n("Finish Review"), this); m_finishReview->setIcon( QIcon::fromTheme( "dialog-ok" ) ); actionCollection()->setDefaultShortcut( m_finishReview, Qt::CTRL|Qt::Key_Return ); actionCollection()->addAction("commit_or_finish_review", m_finishReview); foreach(Sublime::Area* area, ICore::self()->uiController()->allAreas()) { if (area->objectName() == "review") area->addAction(m_finishReview); } core()->uiController()->addToolView( i18n( "Patch Review" ), m_factory ); areaChanged(ICore::self()->uiController()->activeArea()); } void PatchReviewPlugin::documentClosed( IDocument* doc ) { removeHighlighting( doc->url() ); } void PatchReviewPlugin::documentSaved( IDocument* doc ) { // Only update if the url is not the patch-file, because our call to // the reload() KTextEditor function also causes this signal, // which would lead to an endless update loop. if( m_patch && doc->url() != m_patch->file() ) forceUpdate(); } void PatchReviewPlugin::textDocumentCreated( IDocument* doc ) { if (m_patch) { addHighlighting( doc->url(), doc ); } } void PatchReviewPlugin::unload() { core()->uiController()->removeToolView( m_factory ); KDevelop::IPlugin::unload(); } QWidget* PatchReviewPlugin::createToolView( QWidget* parent ) { return new PatchReviewToolView( parent, this ); } void PatchReviewPlugin::areaChanged(Sublime::Area* area) { bool reviewing = area->objectName() == "review"; m_finishReview->setEnabled(reviewing); if(!reviewing) { closeReview(); } } #include "patchreview.moc" // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on diff --git a/plugins/problemreporter/problemtreeview.cpp b/plugins/problemreporter/problemtreeview.cpp index 4d174a666..af7a2e82c 100644 --- a/plugins/problemreporter/problemtreeview.cpp +++ b/plugins/problemreporter/problemtreeview.cpp @@ -1,394 +1,381 @@ /* * KDevelop Problem Reporter * * Copyright (c) 2006-2007 Hamish Rodda * Copyright 2006 Adam Treat * * 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 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 "problemtreeview.h" #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include "problemreporterplugin.h" #include #include #include //#include "modeltest.h" using namespace KDevelop; namespace KDevelop { class ProblemTreeViewItemDelegate : public QItemDelegate { Q_OBJECT public: explicit ProblemTreeViewItemDelegate(QObject* parent = nullptr); void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; } ProblemTreeViewItemDelegate::ProblemTreeViewItemDelegate(QObject* parent) : QItemDelegate(parent) { } void ProblemTreeViewItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem newOption(option); newOption.textElideMode = index.column() == ProblemModel::File ? Qt::ElideMiddle : Qt::ElideRight; QItemDelegate::paint(painter, newOption, index); } ProblemTreeView::ProblemTreeView(QWidget* parent, QAbstractItemModel* itemModel) : QTreeView(parent) + , m_proxy(new QSortFilterProxyModel(this)) { - ProblemModel* problemModel = dynamic_cast(itemModel); - Q_ASSERT(problemModel); setObjectName("Problem Reporter Tree"); setWhatsThis(i18n("Problems")); setItemDelegate(new ProblemTreeViewItemDelegate); setSelectionBehavior(QAbstractItemView::SelectRows); + m_proxy->setSortRole(ProblemModel::SeverityRole); + m_proxy->setDynamicSortFilter(true); + m_proxy->sort(0, Qt::AscendingOrder); + + ProblemModel* problemModel = dynamic_cast(itemModel); + Q_ASSERT(problemModel); setModel(problemModel); + header()->setStretchLastSection(false); if (problemModel->features().testFlag(ProblemModel::CanDoFullUpdate)) { QAction* fullUpdateAction = new QAction(this); fullUpdateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); fullUpdateAction->setText(i18n("Force Full Update")); fullUpdateAction->setToolTip(i18nc("@info:tooltip", "Re-parse all watched documents")); fullUpdateAction->setIcon(QIcon::fromTheme("view-refresh")); connect(fullUpdateAction, &QAction::triggered, model(), &ProblemModel::forceFullUpdate); addAction(fullUpdateAction); } if (problemModel->features().testFlag(ProblemModel::CanShowImports)) { QAction* showImportsAction = new QAction(this); addAction(showImportsAction); showImportsAction->setCheckable(true); showImportsAction->setChecked(false); showImportsAction->setText(i18n("Show Imports")); showImportsAction->setToolTip(i18nc("@info:tooltip", "Display problems in imported files")); this->model()->setShowImports(false); connect(showImportsAction, &QAction::triggered, model(), &ProblemModel::setShowImports); } if (problemModel->features().testFlag(ProblemModel::ScopeFilter)) { KActionMenu* scopeMenu = new KActionMenu(this); scopeMenu->setDelayed(false); scopeMenu->setText(i18n("Scope")); scopeMenu->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); QActionGroup* scopeActions = new QActionGroup(this); QAction* currentDocumentAction = new QAction(this); currentDocumentAction->setText(i18n("Current Document")); currentDocumentAction->setToolTip(i18nc("@info:tooltip", "Display problems in current document")); QAction* openDocumentsAction = new QAction(this); openDocumentsAction->setText(i18n("Open Documents")); openDocumentsAction->setToolTip(i18nc("@info:tooltip", "Display problems in all open documents")); QAction* currentProjectAction = new QAction(this); currentProjectAction->setText(i18n("Current Project")); currentProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in current project")); QAction* allProjectAction = new QAction(this); allProjectAction->setText(i18n("All Projects")); allProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in all projects")); QVector actions; actions.push_back(currentDocumentAction); actions.push_back(openDocumentsAction); actions.push_back(currentProjectAction); actions.push_back(allProjectAction); if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { QAction* showAllAction = new QAction(this); showAllAction->setText(i18n("Show All")); showAllAction->setToolTip(i18nc("@info:tooltip", "Display ALL problems")); actions.push_back(showAllAction); } foreach (QAction* action, actions) { action->setCheckable(true); scopeActions->addAction(action); scopeMenu->addAction(action); } addAction(scopeMenu); // Show All should be default if it's supported. It helps with error messages that are otherwise invisible if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { actions.last()->setChecked(true); model()->setScope(BypassScopeFilter); } else { currentDocumentAction->setChecked(true); model()->setScope(CurrentDocument); } QSignalMapper* scopeMapper = new QSignalMapper(this); scopeMapper->setMapping(currentDocumentAction, CurrentDocument); scopeMapper->setMapping(openDocumentsAction, OpenDocuments); scopeMapper->setMapping(currentProjectAction, CurrentProject); scopeMapper->setMapping(allProjectAction, AllProjects); connect(currentDocumentAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(openDocumentsAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(currentProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(allProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { scopeMapper->setMapping(actions.last(), BypassScopeFilter); connect(actions.last(), &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); } connect(scopeMapper, static_cast(&QSignalMapper::mapped), model(), &ProblemModel::setScope); } if (problemModel->features().testFlag(ProblemModel::SeverityFilter)) { KActionMenu* severityMenu = new KActionMenu(i18n("Severity"), this); severityMenu->setDelayed(false); severityMenu->setToolTip(i18nc("@info:tooltip", "Select the lowest level of problem severity to be displayed")); QActionGroup* severityActions = new QActionGroup(this); auto errorSeverityAction = new QAction(i18n("Error"), this); errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display only errors")); errorSeverityAction->setIcon(QIcon::fromTheme("dialog-error")); auto warningSeverityAction = new QAction(i18n("Warning"), this); warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors and warnings")); warningSeverityAction->setIcon(QIcon::fromTheme("dialog-warning")); auto hintSeverityAction = new QAction(i18n("Hint"), this); hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors, warnings and hints")); hintSeverityAction->setIcon(QIcon::fromTheme("dialog-information")); QAction* severityActionArray[] = { errorSeverityAction, warningSeverityAction, hintSeverityAction }; for (int i = 0; i < 3; ++i) { severityActionArray[i]->setCheckable(true); severityActions->addAction(severityActionArray[i]); severityMenu->addAction(severityActionArray[i]); } addAction(severityMenu); hintSeverityAction->setChecked(true); model()->setSeverity(IProblem::Hint); QSignalMapper* severityMapper = new QSignalMapper(this); severityMapper->setMapping(errorSeverityAction, IProblem::Error); severityMapper->setMapping(warningSeverityAction, IProblem::Warning); severityMapper->setMapping(hintSeverityAction, IProblem::Hint); connect(errorSeverityAction, &QAction::triggered, severityMapper, static_cast(&QSignalMapper::map)); connect(warningSeverityAction, &QAction::triggered, severityMapper, static_cast(&QSignalMapper::map)); connect(hintSeverityAction, &QAction::triggered, severityMapper, static_cast(&QSignalMapper::map)); connect(severityMapper, static_cast(&QSignalMapper::mapped), model(), &ProblemModel::setSeverity); } if (problemModel->features().testFlag(ProblemModel::Grouping)) { KActionMenu* groupingMenu = new KActionMenu(i18n("Grouping"), this); groupingMenu->setDelayed(false); QActionGroup* groupingActions = new QActionGroup(this); QAction* noGroupingAction = new QAction(i18n("None"), this); QAction* pathGroupingAction = new QAction(i18n("Path"), this); QAction* severityGroupingAction = new QAction(i18n("Severity"), this); QAction* groupingActionArray[] = { noGroupingAction, pathGroupingAction, severityGroupingAction }; for (unsigned i = 0; i < sizeof(groupingActionArray) / sizeof(QAction*); ++i) { QAction* action = groupingActionArray[i]; action->setCheckable(true); groupingActions->addAction(action); groupingMenu->addAction(action); } addAction(groupingMenu); noGroupingAction->setChecked(true); QSignalMapper* groupingMapper = new QSignalMapper(this); groupingMapper->setMapping(noGroupingAction, NoGrouping); groupingMapper->setMapping(pathGroupingAction, PathGrouping); groupingMapper->setMapping(severityGroupingAction, SeverityGrouping); connect(noGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(pathGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(severityGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(groupingMapper, static_cast(&QSignalMapper::mapped), model(), &ProblemModel::setGrouping); } connect(this, &ProblemTreeView::clicked, this, &ProblemTreeView::itemActivated); connect(model(), &QAbstractItemModel::rowsInserted, this, &ProblemTreeView::changed); connect(model(), &QAbstractItemModel::rowsRemoved, this, &ProblemTreeView::changed); connect(model(), &QAbstractItemModel::modelReset, this, &ProblemTreeView::changed); } ProblemTreeView::~ProblemTreeView() { } void ProblemTreeView::openDocumentForCurrentProblem() { itemActivated(currentIndex()); } void ProblemTreeView::itemActivated(const QModelIndex& index) { if (!index.isValid()) return; KTextEditor::Cursor start; QUrl url; { // TODO: is this really necessary? DUChainReadLocker lock(DUChain::lock()); - IProblem::Ptr problem = model()->problemForIndex(index); + const auto problem = index.data(ProblemModel::ProblemRole).value(); if (!problem) return; url = problem->finalLocation().document.toUrl(); start = problem->finalLocation().start(); } ICore::self()->documentController()->openDocument(url, start); } void ProblemTreeView::resizeColumns() { - // Do actual resizing only if the widget is visible and there are not too many items - const int ResizeRowLimit = 15; - if (isVisible() && model()->rowCount() > 0 && model()->rowCount() < ResizeRowLimit) { - const int columnCount = model()->columnCount(); - QVector widthArray(columnCount); - int totalWidth = 0; - for (int i = 0; i < columnCount; ++i) { - widthArray[i] = columnWidth(i); - totalWidth += widthArray[i]; - } - for (int i = 0; i < columnCount; ++i) { - int columnWidthHint = qMax(sizeHintForColumn(i), header()->sectionSizeHint(i)); - if (columnWidthHint - widthArray[i] > 0) { - if (columnWidthHint - widthArray[i] < width() - totalWidth) { // enough space to resize - setColumnWidth(i, columnWidthHint); - totalWidth += (columnWidthHint - widthArray[i]); - } else { - setColumnWidth(i, widthArray[i] + width() - totalWidth); - break; - } - } - } - } + for (int i = 0; i < model()->columnCount(); ++i) + resizeColumnToContents(i); } void ProblemTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) { QTreeView::dataChanged(topLeft, bottomRight, roles); resizeColumns(); } void ProblemTreeView::reset() { QTreeView::reset(); resizeColumns(); } ProblemModel* ProblemTreeView::model() const { - return static_cast(QTreeView::model()); + return static_cast(m_proxy->sourceModel()); } void ProblemTreeView::setModel(QAbstractItemModel* model) { Q_ASSERT(qobject_cast(model)); - QTreeView::setModel(model); + m_proxy->setSourceModel(model); + QTreeView::setModel(m_proxy); } void ProblemTreeView::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if (index.isValid()) { - IProblem::Ptr problem = model()->problemForIndex(index); - if (problem) { - QExplicitlySharedDataPointer solution = problem->solutionAssistant(); - if (solution) { - QList actions; - foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) { - actions << action->toKAction(); - } - if (!actions.isEmpty()) { - QString title = solution->title(); - title = KDevelop::htmlToPlainText(title); - title.replace("'", "\'"); - - QPointer m = new QMenu(this); - m->addSection(title); - m->addActions(actions); - m->exec(event->globalPos()); - delete m; - } - } + const auto problem = index.data(ProblemModel::ProblemRole).value(); + if (!problem) { + return; + } + QExplicitlySharedDataPointer solution = problem->solutionAssistant(); + if (!solution) { + return; + } + QList actions; + foreach (KDevelop::IAssistantAction::Ptr action, solution->actions()) { + actions << action->toKAction(); + } + if (!actions.isEmpty()) { + QString title = solution->title(); + title = KDevelop::htmlToPlainText(title); + title.replace("'", "\'"); + + QPointer m = new QMenu(this); + m->addSection(title); + m->addActions(actions); + m->exec(event->globalPos()); + delete m; } } } void ProblemTreeView::showEvent(QShowEvent* event) { Q_UNUSED(event) - - for (int i = 0; i < model()->columnCount(); ++i) - resizeColumnToContents(i); + resizeColumns(); } #include "problemtreeview.moc" diff --git a/plugins/problemreporter/problemtreeview.h b/plugins/problemreporter/problemtreeview.h index 39c26f554..5ecaf8063 100644 --- a/plugins/problemreporter/problemtreeview.h +++ b/plugins/problemreporter/problemtreeview.h @@ -1,74 +1,75 @@ /* * KDevelop Problem Reporter * * Copyright (c) 2007 Hamish Rodda * * 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 General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_PROBLEMWIDGET_H #define KDEVPLATFORM_PLUGIN_PROBLEMWIDGET_H #include namespace KDevelop { class ParseJob; class TopDUContext; class IDocument; class ProblemModel; } class ProblemReporterPlugin; +class QSortFilterProxyModel; class ProblemTreeView : public QTreeView { Q_OBJECT public: ProblemTreeView(QWidget* parent, QAbstractItemModel* itemModel); virtual ~ProblemTreeView(); KDevelop::ProblemModel* model() const; virtual void setModel(QAbstractItemModel* model) override; virtual void contextMenuEvent(QContextMenuEvent*) override; virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles = QVector()) override; virtual void reset() override; public slots: void openDocumentForCurrentProblem(); signals: // Emitted when the model's rows change (added/removed/reset) void changed(); protected: virtual void showEvent(QShowEvent* event) override; private slots: void itemActivated(const QModelIndex& index); private: void resizeColumns(); ProblemReporterPlugin* m_plugin; - ; + QSortFilterProxyModel* m_proxy; }; #endif // kate: space-indent on; indent-width 2; tab-width: 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/welcomepage/qml/ProjectsDashboard.qml b/plugins/welcomepage/qml/ProjectsDashboard.qml index 0276a2745..4bc1a2b23 100644 --- a/plugins/welcomepage/qml/ProjectsDashboard.qml +++ b/plugins/welcomepage/qml/ProjectsDashboard.qml @@ -1,47 +1,50 @@ /* KDevelop * * Copyright 2011 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ import QtQuick 2.0 import QtQuick.Layouts 1.0 import QtQuick.Controls 1.2 import "plugins" StandardPage { ScrollView { - anchors.fill: parent + anchors { + fill: parent + margins: 30 + } GridView { id: grid width: parent.width cellWidth: grid.width/2 cellHeight: grid.height/3 model: [ "qrc:/qml/plugins/Branches.qml", "qrc:/qml/plugins/Projects.qml" ] // TODO: make this the plugin delegate: Loader { x: 5 y: 5 width: grid.cellWidth-5 height: grid.cellHeight-5 source: modelData } } } } diff --git a/plugins/welcomepage/qml/area_code.qml b/plugins/welcomepage/qml/area_code.qml index 756c654ee..30a901ba2 100644 --- a/plugins/welcomepage/qml/area_code.qml +++ b/plugins/welcomepage/qml/area_code.qml @@ -1,73 +1,73 @@ /* KDevelop * * Copyright 2011 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ import QtQuick 2.0 import org.kde.plasma.components 2.0 StandardBackground { id: root gradient: Gradient { GradientStop { position: 0.0; color: "#61B056" } GradientStop { position: 1.0; color: "#A3D69B" } } state: "develop" tools: ButtonColumn { spacing: 10 Link { iconSource: "applications-development" text: i18n("Develop") onClicked: root.state = "develop" } Link { iconSource: "project-development" text: i18n("Projects") onClicked: root.state = "projects" -// visible: projects.count != 0 + visible: false //FIXME: removed until it makes sense } Link { iconSource: "help-contents" text: i18n("Getting Started") onClicked: root.state = "gettingstarted" } } Loader { id: codeContents anchors { fill: parent leftMargin: root.marginLeft+root.margins margins: root.margins } } states: [ State { name: "gettingstarted" PropertyChanges { target: codeContents; source: "qrc:/qml/GettingStarted.qml"} }, State { name: "develop" PropertyChanges { target: codeContents; source: "qrc:/qml/Develop.qml"} }, State { name: "projects" PropertyChanges { target: codeContents; source: "qrc:/qml/ProjectsDashboard.qml"} } ] } diff --git a/plugins/welcomepage/welcomepageplugin.cpp b/plugins/welcomepage/welcomepageplugin.cpp index e43bfe919..ed5c323e8 100644 --- a/plugins/welcomepage/welcomepageplugin.cpp +++ b/plugins/welcomepage/welcomepageplugin.cpp @@ -1,70 +1,77 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol Gonzalez 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 "welcomepageplugin.h" +#include +#include "welcomepageview.h" #include #include #include #include #include +#include #include #include #include #include #include #include #include +#include #include #include "welcomepagedocument.h" K_PLUGIN_FACTORY_WITH_JSON(KDevWelcomePagePluginFactory, "kdevwelcomepage.json", registerPlugin();) using namespace KDevelop; class WelcomePageFactory : public KDevelop::IDocumentFactory { public: virtual IDocument* create(const QUrl& /*url*/, ICore*) override { return new WelcomePageDocument(); } }; KDevWelcomePagePlugin::KDevWelcomePagePlugin( QObject* parent, const QVariantList& ) : IPlugin("kdevwelcomepage", parent ) { - ICore::self()->documentController()->registerDocumentForMimetype("text/x-kdevelop-internal", new WelcomePageFactory); +// ICore::self()->documentController()->registerDocumentForMimetype("text/x-kdevelop-internal", new WelcomePageFactory); +// +// //FIXME: When and where to open the welcome page? +// //QTimer::singleShot(500, this, SLOT(openWelcomePage())); +// +// QAction* action = actionCollection()->addAction("show-welcomepage"); +// action->setText("Show Welcome Page"); +// action->setIcon(QIcon::fromTheme("meeting-organizer")); - //FIXME: When and where to open the welcome page? - //QTimer::singleShot(500, this, SLOT(openWelcomePage())); - - QAction* action = actionCollection()->addAction("show-welcomepage"); - action->setText("Show Welcome Page"); - action->setIcon(QIcon::fromTheme("meeting-organizer")); + Sublime::MainWindow* mw = qobject_cast(ICore::self()->uiController()->activeMainWindow()); + mw->setBackgroundCentralWidget(new WelcomePageWidget(QList(), mw)); } void KDevWelcomePagePlugin::openWelcomePage() { ICore::self()->documentController()->openDocument(WelcomePageDocument::welcomePageUrl()); } #include "welcomepageplugin.moc" diff --git a/shell/editorconfigpage.cpp b/shell/editorconfigpage.cpp index e49ed1ba7..337348fbd 100644 --- a/shell/editorconfigpage.cpp +++ b/shell/editorconfigpage.cpp @@ -1,123 +1,127 @@ /* * This file is part of KDevelop * Copyright 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "editorconfigpage.h" #include #include #include #include using namespace KDevelop; namespace { class KTextEditorConfigPageAdapter : public ConfigPage { Q_OBJECT public: explicit KTextEditorConfigPageAdapter(KTextEditor::ConfigPage* page, QWidget* parent = nullptr) : ConfigPage(nullptr, nullptr, parent), m_page(page) { page->setParent(this); + QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(page); setLayout(layout); + + connect(page, &KTextEditor::ConfigPage::changed, + this, &ConfigPage::changed); } virtual ~KTextEditorConfigPageAdapter() {} virtual QString name() const override { return m_page->name(); } virtual QIcon icon() const override { return m_page->icon(); } virtual QString fullName() const override { return m_page->fullName(); } public Q_SLOTS: virtual void apply() override { m_page->apply(); } virtual void defaults() override { m_page->defaults(); } virtual void reset() override { m_page->reset(); } private: KTextEditor::ConfigPage* m_page; }; } EditorConfigPage::EditorConfigPage(QWidget* parent) : ConfigPage(nullptr, nullptr, parent) { setObjectName("editorconfig"); } EditorConfigPage::~EditorConfigPage() {}; QString EditorConfigPage::name() const { return i18n("Editor"); } QIcon EditorConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("accessories-text-editor")); } QString EditorConfigPage::fullName() const { return i18n("Configure Text editor"); } int EditorConfigPage::childPages() const { return KTextEditor::Editor::instance()->configPages(); } ConfigPage* EditorConfigPage::childPage(int number) { auto page = KTextEditor::Editor::instance()->configPage(number, this); if (page) { return new KTextEditorConfigPageAdapter(page, this); } return nullptr; } #include "editorconfigpage.moc" diff --git a/shell/filteredproblemstore.h b/shell/filteredproblemstore.h index d5c259638..2ef541456 100644 --- a/shell/filteredproblemstore.h +++ b/shell/filteredproblemstore.h @@ -1,118 +1,118 @@ /* * Copyright 2015 Laszlo Kis-Adam * * 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 General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef FILTEREDPROBLEMSTORE_H #define FILTEREDPROBLEMSTORE_H #include "problemstore.h" #include "problemconstants.h" namespace KDevelop { struct FilteredProblemStorePrivate; /** * @brief ProblemStore subclass that can group by severity, and path, and filter by scope, and severity. * * Internally grouping is implemented using a tree structure. * When grouping is on, the top level nodes are the groups, and their children are the nodes containing the problems that belong into that node. * If the problems have diagnostics, then the diagnostics are added as children nodes as well. This was implemented so they can be browsed in a model/view architecture. * When grouping is off, the top level nodes are the problem nodes. * * Grouping can be set and queried using the following methods * \li setGrouping(); * \li grouping(); * * Valid grouping settings: * \li NoGrouping * \li PathGrouping * \li SeverityGrouping * * Severity filter can be set and queried using the following methods * \li setSeverity() * \li severity() * * Valid severity setting: * \li IProblem::Error * \li IProblem::Warning * \li IProblem::Hint * * When changing the grouping or filtering method the following signals are emitted in order: * \li beginRebuild() * \li endRebuild() * \li changed() * * Usage example: * @code * IProblem::Ptr problem(new DetectedProblem); * problem->setSeverity(IProblem::Error); * ... * FilteredProblemStore *store = new FilteredProblemStore(); * store->setGrouping(SeverityGrouping); * store->addProblem(problem); * ProblemStoreNode *label = store->findNode(0); // Label node with the label "Error" * ProblemNode *problemNode = dynamic_cast(label->child(0)); // the node with the problem * problemNode->problem(); // The problem we added * @endcode * */ class KDEVPLATFORMSHELL_EXPORT FilteredProblemStore : public ProblemStore { Q_OBJECT public: explicit FilteredProblemStore(QObject *parent = nullptr); ~FilteredProblemStore(); /// Adds a problem, which is then filtered and also added to the filtered problem list if it matches the filters void addProblem(const IProblem::Ptr &problem) override; /// Retrieves the specified node const ProblemStoreNode* findNode(int row, ProblemStoreNode *parent = nullptr) const override; /// Retrieves the number of filtered problems int count(ProblemStoreNode *parent = nullptr) const override; /// Clears the problems void clear() override; /// Rebuilds the filtered problem list void rebuild() override; /// Set the grouping method. See the GroupingMethod enum. void setGrouping(int grouping) override; /// Tells which grouping strategy is currently in use int grouping() const; /// Sets whether we should bypass the scope filter void setBypassScopeFilter(bool bypass) override; /// Tells whether the scope filter bypass is on bool bypassScopeFilter() const; private: - friend class FilteredProblemStorePrivate; + friend struct FilteredProblemStorePrivate; QScopedPointer d; }; } #endif diff --git a/shell/problemmodel.cpp b/shell/problemmodel.cpp index 851353cbe..5c785cfa7 100644 --- a/shell/problemmodel.cpp +++ b/shell/problemmodel.cpp @@ -1,322 +1,325 @@ /* * KDevelop Problem Reporter * * Copyright 2007 Hamish Rodda * * 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 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 "problemmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { QIcon iconForSeverity(KDevelop::IProblem::Severity severity) { switch (severity) { case KDevelop::IProblem::Hint: return QIcon::fromTheme(QStringLiteral("dialog-information")); case KDevelop::IProblem::Warning: return QIcon::fromTheme(QStringLiteral("dialog-warning")); case KDevelop::IProblem::Error: return QIcon::fromTheme(QStringLiteral("dialog-error")); } - return QIcon(); + return {}; } } struct ProblemModelPrivate { ProblemModelPrivate(KDevelop::ProblemStore *store) : m_problems(store) , m_features(KDevelop::ProblemModel::NoFeatures) { } QScopedPointer m_problems; KDevelop::ProblemModel::Features m_features; }; namespace KDevelop { ProblemModel::ProblemModel(QObject * parent, ProblemStore *store) : QAbstractItemModel(parent) , d(new ProblemModelPrivate(store)) { if (!d->m_problems) { d->m_problems.reset(new FilteredProblemStore()); d->m_features = ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter; } setScope(CurrentDocument); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProblemModel::setCurrentDocument); /// CompletionSettings include a list of todo markers we care for, so need to update connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, this, &ProblemModel::forceFullUpdate); if (ICore::self()->documentController()->activeDocument()) { setCurrentDocument(ICore::self()->documentController()->activeDocument()); } connect(d->m_problems.data(), &ProblemStore::beginRebuild, this, &ProblemModel::onBeginRebuild); connect(d->m_problems.data(), &ProblemStore::endRebuild, this, &ProblemModel::onEndRebuild); } ProblemModel::~ ProblemModel() { } -int ProblemModel::rowCount(const QModelIndex & parent) const +int ProblemModel::rowCount(const QModelIndex& parent) const { - if(!parent.isValid()) + if (!parent.isValid()) { return d->m_problems->count(); - else + } else { return d->m_problems->count(reinterpret_cast(parent.internalPointer())); + } } static QString displayUrl(const QUrl &url, const QUrl &base) { if (base.isParentOf(url)) { return url.toDisplayString(QUrl::PreferLocalFile).mid(base.toDisplayString(QUrl::PreferLocalFile).length()); } else { return ICore::self()->projectController()->prettyFileName(url, IProjectController::FormatPlain); } } -QVariant ProblemModel::data(const QModelIndex & index, int role) const +QVariant ProblemModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); QUrl baseDirectory = d->m_problems->currentDocument().toUrl().adjusted(QUrl::RemoveFilename); IProblem::Ptr p = problemForIndex(index); - if(p.constData() == NULL) - { - if((role == Qt::DisplayRole) && (index.column() == Error)) - { + if (!p.constData()) { + if (role == Qt::DisplayRole && index.column() == Error) { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); - if(node == NULL) - return QVariant(); - else + if (node) { return node->label(); + } } + return {}; + } - return QVariant(); + if (role == SeverityRole) { + return p->severity(); + } else if (role == ProblemRole) { + return QVariant::fromValue(p); } switch (role) { case Qt::DisplayRole: switch (index.column()) { case Source: return p->sourceString(); case Error: return p->description(); case File: return displayUrl(p->finalLocation().document.toUrl(), baseDirectory); case Line: - if(p->finalLocation().isValid()) + if (p->finalLocation().isValid()) { return QString::number(p->finalLocation().start().line() + 1); + } break; case Column: - if(p->finalLocation().isValid()) + if (p->finalLocation().isValid()) { return QString::number(p->finalLocation().start().column() + 1); + } break; } break; case Qt::DecorationRole: - if (index.column() == 0) { + if (index.column() == Error) { return iconForSeverity(p->severity()); } break; case Qt::ToolTipRole: return p->explanation(); default: break; } - return QVariant(); + return {}; } QModelIndex ProblemModel::parent(const QModelIndex& index) const { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); - if(node == NULL) - return QModelIndex(); + if (!node) { + return {}; + } ProblemStoreNode *parent = node->parent(); - if(parent == NULL) - return QModelIndex(); - - if(parent->isRoot()) - return QModelIndex(); + if (!parent || parent->isRoot()) { + return {}; + } int idx = parent->index(); - return createIndex(idx, 0, parent); - } -QModelIndex ProblemModel::index(int row, int column, const QModelIndex & parent) const +QModelIndex ProblemModel::index(int row, int column, const QModelIndex& parent) const { - if (row < 0 || row >= rowCount(parent) || column < 0 || column >= LastColumn) + if (row < 0 || row >= rowCount(parent) || column < 0 || column >= LastColumn) { return QModelIndex(); - + } ProblemStoreNode *parentNode = reinterpret_cast(parent.internalPointer()); const ProblemStoreNode *node = d->m_problems->findNode(row, parentNode); return createIndex(row, column, (void*)node); } -int ProblemModel::columnCount(const QModelIndex & parent) const +int ProblemModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return LastColumn; } -IProblem::Ptr ProblemModel::problemForIndex(const QModelIndex & index) const +IProblem::Ptr ProblemModel::problemForIndex(const QModelIndex& index) const { ProblemStoreNode *node = reinterpret_cast(index.internalPointer()); - if(node == NULL) - return IProblem::Ptr(NULL); - else + if (!node) { + return {}; + } else { return node->problem(); + } } ProblemModel::Features ProblemModel::features() const { return d->m_features; } void ProblemModel::setFeatures(Features features) { d->m_features = features; } void ProblemModel::addProblem(const IProblem::Ptr &problem) { int c = d->m_problems->count(); beginInsertRows(QModelIndex(), c, c + 1); d->m_problems->addProblem(problem); endInsertRows(); } void ProblemModel::setProblems(const QVector &problems) { beginResetModel(); d->m_problems->setProblems(problems); endResetModel(); } void ProblemModel::clearProblems() { beginResetModel(); d->m_problems->clear(); endResetModel(); } QVariant ProblemModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); if (role != Qt::DisplayRole) - return QVariant(); + return {}; switch (section) { case Source: return i18nc("@title:column source of problem", "Source"); case Error: return i18nc("@title:column problem description", "Problem"); case File: return i18nc("@title:column file where problem was found", "File"); case Line: return i18nc("@title:column line number with problem", "Line"); case Column: return i18nc("@title:column column number with problem", "Column"); } - return QVariant(); + return {}; } void ProblemModel::setCurrentDocument(IDocument* document) { Q_ASSERT(thread() == QThread::currentThread()); QUrl currentDocument = document->url(); /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setCurrentDocument(IndexedString(currentDocument)); } void ProblemModel::onBeginRebuild() { beginResetModel(); } void ProblemModel::onEndRebuild() { endResetModel(); } void ProblemModel::setScope(int scope) { Q_ASSERT(thread() == QThread::currentThread()); /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setScope(scope); } void ProblemModel::setSeverity(int severity) { Q_ASSERT(thread() == QThread::currentThread()); /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setSeverity(severity); } void ProblemModel::setGrouping(int grouping) { /// Will trigger signals beginRebuild(), endRebuild() if problems change and are rebuilt d->m_problems->setGrouping(grouping); } ProblemStore* ProblemModel::store() const { return d->m_problems.data(); } } diff --git a/shell/problemmodel.h b/shell/problemmodel.h index ff6898c1a..0933a8049 100644 --- a/shell/problemmodel.h +++ b/shell/problemmodel.h @@ -1,177 +1,182 @@ /* * KDevelop Problem Reporter * * Copyright 2007 Hamish Rodda * Copyright 2015 Laszlo Kis-Adam * * 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 General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PROBLEMMODEL_H #define PROBLEMMODEL_H #include #include #include #include struct ProblemModelPrivate; namespace KDevelop { class IDocument; class ProblemStore; /** * @brief Wraps a ProblemStore and adds the QAbstractItemModel interface, so the it can be used in a model/view architecture. * * By default ProblemModel instantiates a FilteredProblemStore, with the following features on: * \li ScopeFilter * \li SeverityFilter * \li Grouping * \li CanByPassScopeFilter * * Has to following columns: * \li Error * \li Source * \li File * \li Line * \li Column * \li LastColumn * * Possible ProblemModel features * \li NoFeatures * \li CanDoFullUpdate * \li CanShowImports * \li ScopeFilter * \li SeverityFilter * \li Grouping * \li CanByPassScopeFilter * * Scope, severity, grouping, imports can be set using the slots named after these features. * * Usage example: * @code * IProblem::Ptr problem(new DetectedProblem); * problem->setDescription(QStringLiteral("Problem")); * ProblemModel *model = new ProblemModel(nullptr); * model->addProblem(problem); * model->rowCount(); // returns 1 * QModelIndex idx = model->index(0, 0); * model->data(index); // "Problem" * @endcode * */ class KDEVPLATFORMSHELL_EXPORT ProblemModel : public QAbstractItemModel { Q_OBJECT public: /// List of supportable features enum FeatureCode { NoFeatures = 0, /// No features :( CanDoFullUpdate = 1, /// Reload/Reparse problems CanShowImports = 2, /// Show problems from imported files. E.g.: Header files in C/C++ ScopeFilter = 4, /// Filter problems by scope. E.g.: current document, open documents, etc SeverityFilter = 8, /// Filter problems by severity. E.g.: hint, warning, error, etc Grouping = 16, /// Can group problems CanByPassScopeFilter = 32 /// Can bypass scope filter }; Q_DECLARE_FLAGS(Features, FeatureCode) explicit ProblemModel(QObject *parent, ProblemStore *store = NULL); virtual ~ProblemModel(); enum Columns { Error, Source, File, Line, Column, LastColumn }; + enum Roles { + ProblemRole = Qt::UserRole + 1, + SeverityRole + }; + virtual int columnCount(const QModelIndex & parent = QModelIndex()) const override; virtual QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override; virtual QModelIndex parent(const QModelIndex & index) const override; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; virtual int rowCount(const QModelIndex & parent = QModelIndex()) const override; virtual QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override; IProblem::Ptr problemForIndex(const QModelIndex& index) const; /// Adds a new problem to the model void addProblem(const IProblem::Ptr &problem); /// Clears the problems, then adds a new set of them void setProblems(const QVector &problems); /// Clears the problems void clearProblems(); /// Retrieve the supported features Features features() const; /// Set the supported features void setFeatures(Features features); public slots: /// Show imports virtual void setShowImports(bool){} /// Sets the scope filter. Uses int to be able to use QSignalMapper virtual void setScope(int scope); /// Sets the severity filter. Uses int to be able to use QSignalMapper virtual void setSeverity(int severity); void setGrouping(int grouping); /** * Force a full problem update. * E.g.: Reparse the source code. * Obviously it doesn't make sense for run-time problem checkers. */ virtual void forceFullUpdate(){} protected slots: /// Triggered when problems change virtual void onProblemsChanged(){} private slots: /// Triggered when the current document changes virtual void setCurrentDocument(IDocument* doc); /// Triggered before the problems are rebuilt void onBeginRebuild(); /// Triggered once the problems have been rebuilt void onEndRebuild(); protected: ProblemStore *store() const; private: QScopedPointer d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProblemModel::Features) } #endif // PROBLEMMODEL_H diff --git a/shell/projectsourcepage.cpp b/shell/projectsourcepage.cpp index 62ffbb6de..100a5d7ed 100644 --- a/shell/projectsourcepage.cpp +++ b/shell/projectsourcepage.cpp @@ -1,269 +1,272 @@ /*************************************************************************** * Copyright (C) 2010 by 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) any later version. * * * ***************************************************************************/ #include "projectsourcepage.h" #include "ui_projectsourcepage.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; static const int FROM_FILESYSTEM_SOURCE_INDEX = 0; ProjectSourcePage::ProjectSourcePage(const QUrl& initial, QWidget* parent) : QWidget(parent) { m_ui = new Ui::ProjectSourcePage; m_ui->setupUi(this); + m_ui->status->setCloseButtonVisible(false); + m_ui->status->setMessageType(KMessageWidget::Error); + m_ui->workingDir->setUrl(initial); m_ui->workingDir->setMode(KFile::Directory); m_ui->remoteWidget->setLayout(new QVBoxLayout(m_ui->remoteWidget)); m_ui->sources->addItem(QIcon::fromTheme("folder"), i18n("From File System")); m_plugins.append(0); IPluginController* pluginManager = ICore::self()->pluginController(); foreach( IPlugin* p, pluginManager->allPluginsForExtension( "org.kdevelop.IBasicVersionControl" ) ) { m_plugins.append(p); m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension()->name()); } foreach( IPlugin* p, pluginManager->allPluginsForExtension( "org.kdevelop.IProjectProvider" ) ) { m_plugins.append(p); m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension()->name()); } connect(m_ui->workingDir, &KUrlRequester::textChanged, this, &ProjectSourcePage::reevaluateCorrection); connect(m_ui->sources, static_cast(&QComboBox::currentIndexChanged), this, &ProjectSourcePage::setSourceIndex); connect(m_ui->get, &QPushButton::clicked, this, &ProjectSourcePage::checkoutVcsProject); emit isCorrect(false); setSourceIndex(FROM_FILESYSTEM_SOURCE_INDEX); if(!m_plugins.isEmpty()) m_ui->sources->setCurrentIndex(1); } ProjectSourcePage::~ProjectSourcePage() { delete m_ui; } void ProjectSourcePage::setSourceIndex(int index) { m_locationWidget = 0; m_providerWidget = 0; QLayout* remoteWidgetLayout = m_ui->remoteWidget->layout(); QLayoutItem *child; while ((child = remoteWidgetLayout->takeAt(0)) != 0) { delete child->widget(); delete child; } IBasicVersionControl* vcIface = vcsPerIndex(index); IProjectProvider* providerIface; bool found=false; if(vcIface) { found=true; m_locationWidget=vcIface->vcsLocation(m_ui->sourceBox); connect(m_locationWidget, &VcsLocationWidget::changed, this, &ProjectSourcePage::locationChanged); remoteWidgetLayout->addWidget(m_locationWidget); } else { providerIface = providerPerIndex(index); if(providerIface) { found=true; m_providerWidget=providerIface->providerWidget(m_ui->sourceBox); connect(m_providerWidget, &IProjectProviderWidget::changed, this, &ProjectSourcePage::projectChanged); remoteWidgetLayout->addWidget(m_providerWidget); } } reevaluateCorrection(); m_ui->sourceBox->setVisible(found); } IBasicVersionControl* ProjectSourcePage::vcsPerIndex(int index) { IPlugin* p = m_plugins.value(index); if(!p) return 0; else return p->extension(); } IProjectProvider* ProjectSourcePage::providerPerIndex(int index) { IPlugin* p = m_plugins.value(index); if(!p) return 0; else return p->extension(); } VcsJob* ProjectSourcePage::jobPerCurrent() { QUrl url=m_ui->workingDir->url(); IPlugin* p=m_plugins[m_ui->sources->currentIndex()]; VcsJob* job=0; if(IBasicVersionControl* iface=p->extension()) { Q_ASSERT(iface && m_locationWidget); job=iface->createWorkingCopy(m_locationWidget->location(), url); } else if(m_providerWidget) { job=m_providerWidget->createWorkingCopy(url); } return job; } void ProjectSourcePage::checkoutVcsProject() { QUrl url=m_ui->workingDir->url(); QDir d(url.toLocalFile()); if(!url.isLocalFile() && !d.exists()) { bool corr = d.mkpath(d.path()); if(!corr) { KMessageBox::error(0, i18n("Could not create the directory: %1", d.path())); return; } } VcsJob* job=jobPerCurrent(); if (!job) { return; } m_ui->sources->setEnabled(false); m_ui->sourceBox->setEnabled(false); m_ui->workingDir->setEnabled(false); m_ui->get->setEnabled(false); m_ui->creationProgress->setValue(m_ui->creationProgress->minimum()); connect(job, &VcsJob::result, this, &ProjectSourcePage::projectReceived); // Can't use new signal-slot syntax, KJob::percent is private :/ connect(job, SIGNAL(percent(KJob*,ulong)), SLOT(progressChanged(KJob*,ulong))); connect(job, &VcsJob::infoMessage, this, &ProjectSourcePage::infoMessage); ICore::self()->runController()->registerJob(job); } void ProjectSourcePage::progressChanged(KJob*, unsigned long value) { m_ui->creationProgress->setValue(value); } void ProjectSourcePage::infoMessage(KJob* , const QString& text, const QString& /*rich*/) { m_ui->creationProgress->setFormat(i18nc("Format of the progress bar text. progress and info", "%1 : %p%", text)); } void ProjectSourcePage::projectReceived(KJob* job) { if (job->error()) { m_ui->creationProgress->setValue(0); } else { m_ui->creationProgress->setValue(m_ui->creationProgress->maximum()); } reevaluateCorrection(); m_ui->creationProgress->setFormat("%p%"); } void ProjectSourcePage::reevaluateCorrection() { //TODO: Probably we should just ignore remote URL's, I don't think we're ever going //to support checking out to remote directories const QUrl cwd = m_ui->workingDir->url(); const QDir dir = cwd.toLocalFile(); // case where we import a project from local file system if (m_ui->sources->currentIndex() == FROM_FILESYSTEM_SOURCE_INDEX) { emit isCorrect(dir.exists()); return; } // all other cases where remote locations need to be specified bool correct=!cwd.isRelative() && (!cwd.isLocalFile() || QDir(cwd.adjusted(QUrl::RemoveFilename).toLocalFile()).exists()); emit isCorrect(correct && m_ui->creationProgress->value() == m_ui->creationProgress->maximum()); bool validWidget = ((m_locationWidget && m_locationWidget->isCorrect()) || (m_providerWidget && m_providerWidget->isCorrect())); bool validToCheckout = correct && validWidget; //To checkout, if it exists, it should be an empty dir if (validToCheckout && cwd.isLocalFile() && dir.exists()) { validToCheckout = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty(); } m_ui->get->setEnabled(validToCheckout); m_ui->creationProgress->setEnabled(validToCheckout); if(!correct) setStatus(i18n("You need to specify a valid or nonexistent directory to check out a project")); else if(!m_ui->get->isEnabled() && m_ui->workingDir->isEnabled()) setStatus(i18n("You need to specify a valid project location")); else clearStatus(); } void ProjectSourcePage::locationChanged() { Q_ASSERT(m_locationWidget); if(m_locationWidget->isCorrect()) { QString currentUrl = m_ui->workingDir->text(); currentUrl = currentUrl.left(currentUrl.lastIndexOf('/')+1); QUrl current = QUrl::fromUserInput(currentUrl + "/" + m_locationWidget->projectName()); m_ui->workingDir->setUrl(current); } else reevaluateCorrection(); } void ProjectSourcePage::projectChanged(const QString& name) { Q_ASSERT(m_providerWidget); QString currentUrl = m_ui->workingDir->text(); currentUrl = currentUrl.left(currentUrl.lastIndexOf('/')+1); QUrl current = QUrl::fromUserInput(currentUrl + "/" + name); m_ui->workingDir->setUrl(current); } void ProjectSourcePage::setStatus(const QString& message) { - KColorScheme scheme(QPalette::Normal); - m_ui->status->setText(QStringLiteral("%2").arg(scheme.foreground(KColorScheme::NegativeText).color().name()).arg(message)); + m_ui->status->setText(message); + m_ui->status->animatedShow(); } void ProjectSourcePage::clearStatus() { - m_ui->status->clear(); + m_ui->status->animatedHide(); } QUrl ProjectSourcePage::workingDir() const { return m_ui->workingDir->url(); } diff --git a/shell/projectsourcepage.ui b/shell/projectsourcepage.ui index e17e1ac85..79699aa5b 100644 --- a/shell/projectsourcepage.ui +++ b/shell/projectsourcepage.ui @@ -1,126 +1,133 @@ ProjectSourcePage 0 0 420 353 0 0 Source: Destination Directory: - + Select the directory to use... 0 0 Source false Get false 0 - + Qt::Vertical 20 40 KUrlRequester QFrame
kurlrequester.h
+ 1 +
+ + KMessageWidget + QFrame +
kmessagewidget.h
+ 1
diff --git a/shell/textdocument.cpp b/shell/textdocument.cpp index 7febacf1a..501b592d3 100644 --- a/shell/textdocument.cpp +++ b/shell/textdocument.cpp @@ -1,790 +1,779 @@ /*************************************************************************** * 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 "textdocument.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 "core.h" #include "mainwindow.h" #include "uicontroller.h" #include "partcontroller.h" #include "plugincontroller.h" #include "documentcontroller.h" #include "debug.h" #include namespace KDevelop { const int MAX_DOC_SETTINGS = 20; // This sets cursor position and selection on the view to the given // range. Selection is set only for non-empty ranges // Factored into a function since its needed in 3 places already static void selectAndReveal( KTextEditor::View* view, const KTextEditor::Range& range ) { Q_ASSERT(view); if (range.isValid()) { view->setCursorPosition(range.start()); if (!range.isEmpty()) { view->setSelection(range); } } } struct TextDocumentPrivate { TextDocumentPrivate(TextDocument *textDocument) : document(nullptr), state(IDocument::Clean), encoding(), q(textDocument) , m_loaded(false), m_addedContextMenu(0) { } ~TextDocumentPrivate() { delete m_addedContextMenu; m_addedContextMenu = 0; saveSessionConfig(); delete document; } QPointer document; IDocument::DocumentState state; QString encoding; void setStatus(KTextEditor::Document* document, bool dirty) { QIcon statusIcon; if (document->isModified()) if (dirty) { state = IDocument::DirtyAndModified; statusIcon = QIcon::fromTheme("edit-delete"); } else { state = IDocument::Modified; statusIcon = QIcon::fromTheme("document-save"); } else if (dirty) { state = IDocument::Dirty; statusIcon = QIcon::fromTheme("document-revert"); } else { state = IDocument::Clean; } q->notifyStateChanged(); Core::self()->uiControllerInternal()->setStatusIcon(q, statusIcon); } inline KConfigGroup katePartSettingsGroup() const { return KSharedConfig::openConfig()->group("KatePart Settings"); } inline QString docConfigGroupName() const { return document->url().toDisplayString(QUrl::PreferLocalFile); } inline KConfigGroup docConfigGroup() const { return katePartSettingsGroup().group(docConfigGroupName()); } void saveSessionConfig() { if(document && document->url().isValid()) { // make sure only MAX_DOC_SETTINGS entries are stored KConfigGroup katePartSettings = katePartSettingsGroup(); // ordered list of documents QStringList documents = katePartSettings.readEntry("documents", QStringList()); // ensure this document is "new", i.e. at the end of the list documents.removeOne(docConfigGroupName()); documents.append(docConfigGroupName()); // remove "old" documents + their group while(documents.size() >= MAX_DOC_SETTINGS) { katePartSettings.group(documents.takeFirst()).deleteGroup(); } // update order katePartSettings.writeEntry("documents", documents); // actually save session config KConfigGroup group = docConfigGroup(); document->writeSessionConfig(group); } } void loadSessionConfig() { if (!document || !katePartSettingsGroup().hasGroup(docConfigGroupName())) { return; } document->readSessionConfig(docConfigGroup(), {"SkipUrl"}); } // Determines whether the current contents of this document in the editor // could be retrieved from the VCS if they were dismissed. void queryCanRecreateFromVcs(KTextEditor::Document* document) const { IProject* project = 0; // Find projects by checking which one contains the file's parent directory, // to avoid issues with the cmake manager temporarily removing files from a project // during reloading. KDevelop::Path path(document->url()); foreach ( KDevelop::IProject* current, Core::self()->projectController()->projects() ) { if ( current->path().isParentOf(path) ) { project = current; break; } } if (!project) { return; } IContentAwareVersionControl* iface; iface = qobject_cast< KDevelop::IContentAwareVersionControl* >(project->versionControlPlugin()); if (!iface) { return; } if ( !qobject_cast( document ) ) { return; } CheckInRepositoryJob* req = iface->isInRepository( document ); if ( !req ) { return; } QObject::connect(req, &CheckInRepositoryJob::finished, q, &TextDocument::repositoryCheckFinished); // Abort the request when the user edits the document QObject::connect(q->textDocument(), &KTextEditor::Document::textChanged, req, &CheckInRepositoryJob::abort); } void modifiedOnDisk(KTextEditor::Document *document, bool /*isModified*/, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { bool dirty = false; switch (reason) { case KTextEditor::ModificationInterface::OnDiskUnmodified: break; case KTextEditor::ModificationInterface::OnDiskModified: case KTextEditor::ModificationInterface::OnDiskCreated: case KTextEditor::ModificationInterface::OnDiskDeleted: dirty = true; break; } // In some cases, the VCS (e.g. git) can know whether the old contents are "valuable", i.e. // not retrieveable from the VCS. If that is not the case, then the document can safely be // reloaded without displaying a dialog asking the user. if ( dirty ) { queryCanRecreateFromVcs(document); } setStatus(document, dirty); } TextDocument * const q; bool m_loaded; // we want to remove the added stuff when the menu hides QMenu* m_addedContextMenu; }; struct TextViewPrivate { TextViewPrivate(TextView* q) : q(q) {} TextView* const q; QPointer view; KTextEditor::Range initialRange; }; TextDocument::TextDocument(const QUrl &url, ICore* core, const QString& encoding) :PartDocument(url, core), d(new TextDocumentPrivate(this)) { d->encoding = encoding; } TextDocument::~TextDocument() { delete d; } bool TextDocument::isTextDocument() const { if( !d->document ) { /// @todo Somehow it can happen that d->document is zero, which makes /// code relying on "isTextDocument() == (bool)textDocument()" crash qWarning() << "Broken text-document: " << url(); return false; } return true; } KTextEditor::Document *TextDocument::textDocument() const { return d->document; } QWidget *TextDocument::createViewWidget(QWidget *parent) { KTextEditor::View* view = 0L; if (!d->document) { d->document = Core::self()->partControllerInternal()->createTextPart(); // Connect to the first text changed signal, it occurs before the completed() signal connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::slotDocumentLoaded); // Also connect to the completed signal, sometimes the first text changed signal is missed because the part loads too quickly (? TODO - confirm this is necessary) connect(d->document.data(), static_cast(&KTextEditor::Document::completed), this, &TextDocument::slotDocumentLoaded); // force a reparse when a document gets reloaded connect(d->document.data(), &KTextEditor::Document::reloaded, this, [] (KTextEditor::Document* document) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(document->url()), (TopDUContext::Features) ( TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate ), BackgroundParser::BestPriority, 0); }); // Set encoding passed via constructor // Needs to be done before openUrl, else katepart won't use the encoding // @see KTextEditor::Document::setEncoding if (!d->encoding.isEmpty()) d->document->setEncoding(d->encoding); if (!url().isEmpty() && !DocumentController::isEmptyDocumentUrl(url())) d->document->openUrl( url() ); d->setStatus(d->document, false); /* It appears, that by default a part will be deleted the first view containing it is deleted. Since we do want to have several views, disable that behaviour. */ d->document->setAutoDeletePart(false); Core::self()->partController()->addPart(d->document, false); d->loadSessionConfig(); connect(d->document.data(), &KTextEditor::Document::modifiedChanged, this, &TextDocument::newDocumentStatus); connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::textChanged); connect(d->document.data(), &KTextEditor::Document::documentUrlChanged, this, &TextDocument::documentUrlChanged); connect(d->document.data(), &KTextEditor::Document::documentSavedOrUploaded, this, &TextDocument::documentSaved ); if (qobject_cast(d->document)) { // can't use new signal/slot syntax here, MarkInterface is not a QObject connect(d->document.data(), SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(saveSessionConfig())); } if (auto iface = qobject_cast(d->document)) { iface->setModifiedOnDiskWarning(true); // can't use new signal/slot syntax here, ModificationInterface is not a QObject connect(d->document.data(), SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } notifyTextDocumentCreated(); } view = d->document->createView(parent); // get rid of some actions regarding the config dialog, we merge that one into the kdevelop menu already delete view->actionCollection()->action(QStringLiteral("set_confdlg")); delete view->actionCollection()->action(QStringLiteral("editor_options")); view->setStatusBarEnabled(Core::self()->partControllerInternal()->showTextEditorStatusBar()); connect(view, &KTextEditor::View::contextMenuAboutToShow, this, &TextDocument::populateContextMenu); if (KTextEditor::CodeCompletionInterface* cc = dynamic_cast(view)) cc->setAutomaticInvocationEnabled(core()->languageController()->completionSettings()->automaticCompletionEnabled()); if (KTextEditor::ConfigInterface *config = qobject_cast(view)) { config->setConfigValue("allow-mark-menu", false); config->setConfigValue("default-mark-type", KTextEditor::MarkInterface::BreakpointActive); } return view; } KParts::Part *TextDocument::partForView(QWidget *view) const { if (d->document && d->document->views().contains((KTextEditor::View*)view)) return d->document; return 0; } // KDevelop::IDocument implementation void TextDocument::reload() { if (!d->document) return; KTextEditor::ModificationInterface* modif=0; if(d->state==Dirty) { modif = qobject_cast(d->document); modif->setModifiedOnDiskWarning(false); } d->document->documentReload(); if(modif) modif->setModifiedOnDiskWarning(true); } bool TextDocument::save(DocumentSaveMode mode) { if (!d->document) return true; if (mode & Discard) return true; switch (d->state) { case IDocument::Clean: return true; - case IDocument::Modified: break; - if (!(mode & Silent)) - { - int code = KMessageBox::warningYesNoCancel( - Core::self()->uiController()->activeMainWindow(), - i18n("The document \"%1\" has unsaved changes. Would you like to save them?", d->document->url().toLocalFile()), - i18nc("@title:window", "Close Document")); - if (code != KMessageBox::Yes) - return false; - } + case IDocument::Modified: break; case IDocument::Dirty: case IDocument::DirtyAndModified: if (!(mode & Silent)) { int code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The file \"%1\" is modified on disk.\n\nAre " "you sure you want to overwrite it? (External " "changes will be lost.)", d->document->url().toLocalFile()), i18nc("@title:window", "Document Externally Modified")); if (code != KMessageBox::Yes) return false; } break; } QUrl urlBeforeSave = d->document->url(); if (d->document->documentSave()) { if (d->document->url() != urlBeforeSave) notifyUrlChanged(); return true; } return false; } IDocument::DocumentState TextDocument::state() const { return d->state; } KTextEditor::Cursor KDevelop::TextDocument::cursorPosition() const { if (!d->document) { return KTextEditor::Cursor::invalid(); } KTextEditor::View *view = activeTextView(); if (view) return view->cursorPosition(); return KTextEditor::Cursor::invalid(); } void TextDocument::setCursorPosition(const KTextEditor::Cursor &cursor) { if (!cursor.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); // Rodda: Cursor must be accurate here, to the definition of accurate for KTextEditor::Cursor. // ie, starting from 0,0 if (view) view->setCursorPosition(cursor); } KTextEditor::Range TextDocument::textSelection() const { if (!d->document) { return KTextEditor::Range::invalid(); } KTextEditor::View *view = activeTextView(); if (view && view->selection()) { return view->selectionRange(); } return PartDocument::textSelection(); } QString TextDocument::textLine() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { return d->document->line( view->cursorPosition().line() ); } return PartDocument::textLine(); } QString TextDocument::textWord() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { KTextEditor::Cursor start = view->cursorPosition(); qCDebug(SHELL) << "got start position from view:" << start.line() << start.column(); QString linestr = textLine(); int startPos = qMax( qMin( start.column(), linestr.length() - 1 ), 0 ); int endPos = startPos; startPos --; while( startPos >= 0 && ( linestr[startPos].isLetterOrNumber() || linestr[startPos] == '_' || linestr[startPos] == '~' ) ) { --startPos; } while( endPos < linestr.length() && ( linestr[endPos].isLetterOrNumber() || linestr[endPos] == '_' || linestr[endPos] == '~' ) ) { ++endPos; } if( startPos != endPos ) { qCDebug(SHELL) << "found word" << startPos << endPos << linestr.mid( startPos+1, endPos - startPos - 1 ); return linestr.mid( startPos + 1, endPos - startPos - 1 ); } } return PartDocument::textWord(); } void TextDocument::setTextSelection(const KTextEditor::Range &range) { if (!range.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); if (view) { selectAndReveal(view, range); } } bool TextDocument::close(DocumentSaveMode mode) { if (!PartDocument::close(mode)) return false; if ( d->document ) { d->saveSessionConfig(); delete d->document; //We have to delete the document right now, to prevent random crashes in the event handler } return true; } Sublime::View* TextDocument::newView(Sublime::Document* doc) { Q_UNUSED(doc); - - emit viewNumberChanged(this); return new TextView(this); } } KDevelop::TextView::TextView(TextDocument * doc) : View(doc, View::TakeOwnership), d(new TextViewPrivate(this)) { } KDevelop::TextView::~TextView() { delete d; } QWidget * KDevelop::TextView::createWidget(QWidget * parent) { auto textDocument = qobject_cast(document()); Q_ASSERT(textDocument); QWidget* widget = textDocument->createViewWidget(parent); d->view = qobject_cast(widget); Q_ASSERT(d->view); connect(d->view.data(), &KTextEditor::View::cursorPositionChanged, this, &KDevelop::TextView::sendStatusChanged); return widget; } QString KDevelop::TextView::viewState() const { if (d->view) { if (d->view->selection()) { KTextEditor::Range selection = d->view->selectionRange(); return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } else { KTextEditor::Cursor cursor = d->view->cursorPosition(); return QStringLiteral("Cursor=%1,%2").arg(cursor.line()).arg(cursor.column()); } } else { KTextEditor::Range selection = d->initialRange; return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } } void KDevelop::TextView::setInitialRange(const KTextEditor::Range& range) { if (d->view) { selectAndReveal(d->view, range); } else { d->initialRange = range; } } KTextEditor::Range KDevelop::TextView::initialRange() const { return d->initialRange; } void KDevelop::TextView::setState(const QString & state) { static QRegExp reCursor("Cursor=([\\d]+),([\\d]+)"); static QRegExp reSelection("Selection=([\\d]+),([\\d]+),([\\d]+),([\\d]+)"); if (reCursor.exactMatch(state)) { setInitialRange(KTextEditor::Range(KTextEditor::Cursor(reCursor.cap(1).toInt(), reCursor.cap(2).toInt()), 0)); } else if (reSelection.exactMatch(state)) { KTextEditor::Range range(reSelection.cap(1).toInt(), reSelection.cap(2).toInt(), reSelection.cap(3).toInt(), reSelection.cap(4).toInt()); setInitialRange(range); } } QString KDevelop::TextDocument::documentType() const { return "Text"; } QIcon KDevelop::TextDocument::defaultIcon() const { if (d->document) { QMimeType mime = QMimeDatabase().mimeTypeForName(d->document->mimeType()); QIcon icon = QIcon::fromTheme(mime.iconName()); if (!icon.isNull()) { return icon; } } return PartDocument::defaultIcon(); } KTextEditor::View *KDevelop::TextView::textView() const { return d->view; } QString KDevelop::TextView::viewStatus() const { // only show status when KTextEditor's own status bar isn't already enabled const bool showStatus = !Core::self()->partControllerInternal()->showTextEditorStatusBar(); if (!showStatus) { return QString(); } const KTextEditor::Cursor pos = d->view ? d->view->cursorPosition() : KTextEditor::Cursor::invalid(); return i18n(" Line: %1 Col: %2 ", pos.line() + 1, pos.column() + 1); } void KDevelop::TextView::sendStatusChanged() { emit statusChanged(this); } KTextEditor::View* KDevelop::TextDocument::activeTextView() const { KTextEditor::View* fallback = nullptr; for (auto view : views()) { auto textView = qobject_cast(view)->textView(); if (!textView) { continue; } if (textView->hasFocus()) { return textView; } else if (textView->isVisible()) { fallback = textView; } else if (!fallback) { fallback = textView; } } return fallback; } void KDevelop::TextDocument::newDocumentStatus(KTextEditor::Document *document) { bool dirty = (d->state == IDocument::Dirty || d->state == IDocument::DirtyAndModified); d->setStatus(document, dirty); } void KDevelop::TextDocument::textChanged(KTextEditor::Document *document) { Q_UNUSED(document); notifyContentChanged(); } void KDevelop::TextDocument::populateContextMenu( KTextEditor::View* v, QMenu* menu ) { if (d->m_addedContextMenu) { foreach ( QAction* action, d->m_addedContextMenu->actions() ) { menu->removeAction(action); } delete d->m_addedContextMenu; } d->m_addedContextMenu = new QMenu(); Context* c = new EditorContext( v, v->cursorPosition() ); QList extensions = Core::self()->pluginController()->queryPluginsForContextMenuExtensions( c ); ContextMenuExtension::populateMenu(d->m_addedContextMenu, extensions); { QUrl url = v->document()->url(); QList< ProjectBaseItem* > items = Core::self()->projectController()->projectModel()->itemsForPath( IndexedString(url) ); if (!items.isEmpty()) { populateParentItemsMenu( items.front(), d->m_addedContextMenu ); } } foreach ( QAction* action, d->m_addedContextMenu->actions() ) { menu->addAction(action); } } void KDevelop::TextDocument::repositoryCheckFinished(bool canRecreate) { if ( d->state != IDocument::Dirty && d->state != IDocument::DirtyAndModified ) { // document is not dirty for whatever reason, nothing to do. return; } if ( ! canRecreate ) { return; } KTextEditor::ModificationInterface* modIface = qobject_cast( d->document ); Q_ASSERT(modIface); // Ok, all safe, we can clean up the document. Close it if the file is gone, // and reload if it's still there. d->setStatus(d->document, false); modIface->setModifiedOnDisk(KTextEditor::ModificationInterface::OnDiskUnmodified); if ( QFile::exists(d->document->url().path()) ) { reload(); } else { close(KDevelop::IDocument::Discard); } } void KDevelop::TextDocument::slotDocumentLoaded() { if (d->m_loaded) return; // Tell the editor integrator first d->m_loaded = true; notifyLoaded(); } void KDevelop::TextDocument::documentSaved(KTextEditor::Document* document, bool saveAs) { Q_UNUSED(document); Q_UNUSED(saveAs); notifySaved(); notifyStateChanged(); } void KDevelop::TextDocument::documentUrlChanged(KTextEditor::Document* document) { Q_UNUSED(document); if (url() != d->document->url()) setUrl(d->document->url()); } #include "moc_textdocument.cpp" diff --git a/sublime/document.cpp b/sublime/document.cpp index 94ebf770f..a64f5aa53 100644 --- a/sublime/document.cpp +++ b/sublime/document.cpp @@ -1,177 +1,175 @@ /*************************************************************************** * 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 "document.h" #include "view.h" #include "area.h" #include "controller.h" namespace Sublime { // struct DocumentPrivate struct DocumentPrivate { DocumentPrivate(Document *doc): m_document(doc) {} void removeView(Sublime::View* view) { views.removeAll(view); - emit m_document->viewNumberChanged(m_document); //no need to keep empty document - we need to remove it if (views.count() == 0) { emit m_document->aboutToDelete(m_document); m_document->deleteLater(); } } Controller *controller; QList views; QIcon statusIcon; QString documentToolTip; Document *m_document; }; //class Document Document::Document(const QString &title, Controller *controller) :QObject(controller), d( new DocumentPrivate(this) ) { setObjectName(title); d->controller = controller; d->controller->addDocument(this); // 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 connect(this, &Document::destroyed, d->controller, [controller] (QObject* obj) { controller->removeDocument(static_cast(obj)); }); } Document::~Document() { delete d; } Controller *Document::controller() const { return d->controller; } View *Document::createView() { View *view = newView(this); connect(view, &View::destroyed, this, [&] (QObject* obj) { d->removeView(static_cast(obj)); }); d->views.append(view); return view; } const QList &Document::views() const { return d->views; } QString Document::title(TitleType /*type*/) const { return objectName(); } QString Document::toolTip() const { return d->documentToolTip; } void Document::setTitle(const QString& newTitle) { setObjectName(newTitle); emit titleChanged(this); } void Document::setToolTip(const QString& newToolTip) { d->documentToolTip=newToolTip; } View *Document::newView(Document *doc) { //first create View, second emit the signal View *newView = new View(doc); - emit viewNumberChanged(this); return newView; } void Document::setStatusIcon(QIcon icon) { d->statusIcon = icon; emit statusIconChanged(this); } QIcon Document::statusIcon() const { return d->statusIcon; } void Document::closeViews() { foreach (Sublime::Area *area, controller()->allAreas()) { QList areaViews = area->views(); foreach (Sublime::View *view, areaViews) { if (views().contains(view)) { area->removeView(view); delete view; } } } Q_ASSERT(views().isEmpty()); } bool Document::askForCloseFeedback() { return true; } bool Document::closeDocument(bool silent) { if( !silent && !askForCloseFeedback() ) return false; closeViews(); deleteLater(); return true; } QIcon Document::icon() const { QIcon ret = statusIcon(); if (!ret.isNull()) { return ret; } return defaultIcon(); } QIcon Document::defaultIcon() const { return QIcon(); } } #include "moc_document.cpp" diff --git a/sublime/document.h b/sublime/document.h index 7d8ed9eb4..f28c8cde6 100644 --- a/sublime/document.h +++ b/sublime/document.h @@ -1,149 +1,146 @@ /*************************************************************************** * 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMEDOCUMENT_H #define KDEVPLATFORM_SUBLIMEDOCUMENT_H #include #include #include "sublimeexport.h" class QIcon; class QWidget; namespace Sublime { class Area; class View; class Controller; /** @short Abstract base class for all Sublime documents Subclass from Document and implement createViewWidget() method to return a new widget for a view. */ class KDEVPLATFORMSUBLIME_EXPORT Document: public QObject { Q_OBJECT public: /**Creates a document and adds it to a @p controller.*/ Document(const QString &title, Controller *controller); ~Document(); /**@return the new view for this document. @note it will not create a widget, just return a view object.*/ View *createView(); /**@return the list of all views in all areas for this document.*/ const QList &views() const; /**@return the controller for this document.*/ Controller *controller() const; /**@return the document title.*/ enum TitleType { Normal, Extended}; virtual QString title(TitleType type = Normal) const; /**Set the document title.*/ void setTitle(const QString& newTitle); void setToolTip(const QString& newToolTip); QString toolTip() const; /**@return the type of document which can be written to config.*/ virtual QString documentType() const = 0; /**@return the specifics of this document which can be written to config.*/ virtual QString documentSpecifier() const = 0; /** * If the document is in a state where data may be lost while closking, * asks the user whether he really wants to close the document. * * This function may also take actions like saving the document before closing * if the user desires so. * * @return true if the document is allowed to be closed, otherwise false. * * The default implementation always returns true. * * */ virtual bool askForCloseFeedback(); /**Should try closing the document, eventually asking the user for feedback. * *If closing is successful, all views should be deleted, and the document itself *be scheduled for deletion using deleteLater(). * * @param silent If this is true, the user must not be asked. * * Returns whether closing was successful (The user did not push 'Cancel') */ virtual bool closeDocument(bool silent = false); void setStatusIcon(QIcon icon); /** * @return The status icon of the document. */ QIcon statusIcon() const; /** * @return The status icon of the document, or, if none is present, an icon * that resembles the document, i.e. based on its mime type. * @see defaultIcon() */ QIcon icon() const; /** * Optionally override this to return a default icon when no status * icon is set for the document. The default returns an invalid icon. */ virtual QIcon defaultIcon() const; Q_SIGNALS: - /**Emitted when the view is added or deleted. Use Document::views to find out - which views and how many of them are still there.*/ - void viewNumberChanged(Sublime::Document *doc); /**Emitted when the document is about to be deleted but is still in valid state.*/ void aboutToDelete(Sublime::Document *doc); /**Emitted when the document's title is changed.*/ void titleChanged(Sublime::Document *doc); /**Emitted when the document status-icon has changed */ void statusIconChanged(Sublime::Document *doc); protected: /**Creates and returns the new view. Reimplement in subclasses to instantiate views of derived from Sublime::View classes.*/ virtual View *newView(Document *doc); /**Reimplement this to create and return the new widget to display this document in the view. This method is used by View class when it is asked for its widget.*/ virtual QWidget *createViewWidget(QWidget *parent = 0) = 0; /** Closes all views associated to this document */ virtual void closeViews(); private: struct DocumentPrivate *const d; friend struct DocumentPrivate; friend class View; }; } #endif diff --git a/tests/testproject.cpp b/tests/testproject.cpp index d619419af..8fe82bdfc 100644 --- a/tests/testproject.cpp +++ b/tests/testproject.cpp @@ -1,155 +1,162 @@ /*************************************************************************** * Copyright 2010 Niko Sams * * Copyright 2012 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "testproject.h" #include #include using namespace KDevelop; TestProject::TestProject(const Path& path, QObject* parent) : IProject(parent) , m_root(0) , m_projectConfiguration(KSharedConfig::openConfig()) { m_path = path.isValid() ? path : Path("/tmp/kdev-testproject/"); m_root = new ProjectFolderItem(this, m_path); ICore::self()->projectController()->projectModel()->appendRow( m_root ); } void TestProject::setPath(const Path& path) { m_path = path; if (m_root) { m_root->setPath(path); } } TestProject::~TestProject() { if (m_root) { delete m_root; } } ProjectFolderItem* TestProject::projectItem() const { return m_root; } void TestProject::setProjectItem(ProjectFolderItem* item) { if (m_root) { ICore::self()->projectController()->projectModel()->removeRow( m_root->row() ); m_root = 0; m_path.clear(); } if (item) { m_root = item; m_path = item->path(); ICore::self()->projectController()->projectModel()->appendRow( m_root ); } } Path TestProject::projectFile() const { return Path(m_path, m_path.lastPathSegment() + ".kdev4"); } Path TestProject::path() const { return m_path; } bool TestProject::inProject(const IndexedString& path) const { return m_path.isParentOf(Path(path.str())); } -void findFileItems(ProjectBaseItem* root, QList& items) +void findFileItems(ProjectBaseItem* root, QList& items, const Path& path = {}) { foreach(ProjectBaseItem* item, root->children()) { - if (item->file()) { + if (item->file() && (path.isEmpty() || item->path() == path)) { items << item->file(); } if (item->rowCount()) { - findFileItems(item, items); + findFileItems(item, items, path); } } } QList< ProjectFileItem* > TestProject::files() const { QList ret; findFileItems(m_root, ret); return ret; } +QList TestProject::filesForPath(const IndexedString& path) const +{ + QList ret; + findFileItems(m_root, ret, Path(path.toUrl())); + return ret; +} + void TestProject::addToFileSet(ProjectFileItem* file) { if (!m_fileSet.contains(file->indexedPath())) { m_fileSet.insert(file->indexedPath()); emit fileAddedToSet(file); } } void TestProject::removeFromFileSet(ProjectFileItem* file) { if (m_fileSet.remove(file->indexedPath())) { emit fileRemovedFromSet(file); } } void TestProjectController::addProject(IProject* p) { emit projectAboutToBeOpened(p); p->setParent(this); m_projects << p; emit projectOpened(p); } void TestProjectController::clearProjects() { foreach(IProject* p, m_projects) { closeProject(p); } } void TestProjectController::closeProject(IProject* p) { emit projectClosing(p); delete p; m_projects.removeOne(p); emit projectClosed(p); } void KDevelop::TestProjectController::takeProject(KDevelop::IProject* p) { emit projectClosing(p); m_projects.removeOne(p); emit projectClosed(p); } void TestProjectController::initialize() { } diff --git a/tests/testproject.h b/tests/testproject.h index 8eb981e1b..6b7b59a35 100644 --- a/tests/testproject.h +++ b/tests/testproject.h @@ -1,106 +1,106 @@ /*************************************************************************** * Copyright 2010 Niko Sams * * Copyright 2012 Milian Wolff * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_TEST_PROJECT_H #define KDEVPLATFORM_TEST_PROJECT_H #include #include #include #include #include "testsexport.h" #include namespace KDevelop { /** * Dummy Project than can be used for Unit Tests. * * Currently only FileSet methods are implemented. */ class KDEVPLATFORMTESTS_EXPORT TestProject : public IProject { Q_OBJECT public: /** * @p url Path to project directory. */ TestProject(const Path& url = Path(), QObject* parent = 0); virtual ~TestProject(); IProjectFileManager* projectFileManager() const override { return 0; } IBuildSystemManager* buildSystemManager() const override { return 0; } IPlugin* managerPlugin() const override { return 0; } IPlugin* versionControlPlugin() const override { return 0; } ProjectFolderItem* projectItem() const override; void setProjectItem(ProjectFolderItem* item); int fileCount() const { return 0; } ProjectFileItem* fileAt( int ) const { return 0; } QList files() const; virtual QList< ProjectBaseItem* > itemsForPath(const IndexedString&) const override { return QList< ProjectBaseItem* >(); } - virtual QList< ProjectFileItem* > filesForPath(const IndexedString&) const override { return QList(); } + virtual QList< ProjectFileItem* > filesForPath(const IndexedString&) const override; virtual QList< ProjectFolderItem* > foldersForPath(const IndexedString&) const override { return QList(); } void reloadModel() override { } Path projectFile() const override; KSharedConfigPtr projectConfiguration() const override { return m_projectConfiguration; } void addToFileSet( ProjectFileItem* file) override; void removeFromFileSet( ProjectFileItem* file) override; QSet fileSet() const override { return m_fileSet; } bool isReady() const override { return true; } void setPath(const Path& path); Path path() const override; QString name() const override { return "Test Project"; } virtual bool inProject(const IndexedString& path) const override; virtual void setReloadJob(KJob* ) override {} private: QSet m_fileSet; Path m_path; ProjectFolderItem* m_root; KSharedConfigPtr m_projectConfiguration; }; /** * ProjectController that can clear open projects. Useful in Unit Tests. */ class KDEVPLATFORMTESTS_EXPORT TestProjectController : public ProjectController { Q_OBJECT public: TestProjectController(Core* core) : ProjectController(core) {} IProject* projectAt( int i ) const override { return m_projects.at(i); } int projectCount() const override { return m_projects.count(); } QList projects() const override { return m_projects; } public: void addProject(IProject* p); void takeProject(IProject* p); void clearProjects(); virtual void closeProject(IProject* p) override; virtual void initialize() override; private: QList m_projects; }; } #endif