diff --git a/plugins/problemreporter/problemsview.cpp b/plugins/problemreporter/problemsview.cpp index f0b373dbcd..dc8ff4c2d8 100644 --- a/plugins/problemreporter/problemsview.cpp +++ b/plugins/problemreporter/problemsview.cpp @@ -1,239 +1,444 @@ /* * 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. */ #include "problemsview.h" +#include #include -#include +#include +#include +#include #include +#include #include #include +#include #include #include "problemtreeview.h" #include "problemmodel.h" namespace KDevelop { +void ProblemsView::setupActions() +{ + { + m_fullUpdateAction = new QAction(this); + m_fullUpdateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); + m_fullUpdateAction->setText(i18n("Force Full Update")); + m_fullUpdateAction->setToolTip(i18nc("@info:tooltip", "Re-parse all watched documents")); + m_fullUpdateAction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); + connect(m_fullUpdateAction, &QAction::triggered, this, [this]() { + currentView()->model()->forceFullUpdate(); + }); + addAction(m_fullUpdateAction); + } + + { + m_showImportsAction = new QAction(this); + addAction(m_showImportsAction); + m_showImportsAction->setCheckable(true); + m_showImportsAction->setChecked(false); + m_showImportsAction->setText(i18n("Show Imports")); + m_showImportsAction->setToolTip(i18nc("@info:tooltip", "Display problems in imported files")); + connect(m_showImportsAction, &QAction::triggered, this, [this](bool checked) { + currentView()->model()->setShowImports(checked); + }); + } + + { + m_scopeMenu = new KActionMenu(this); + m_scopeMenu->setDelayed(false); + m_scopeMenu->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); + m_scopeMenu->setObjectName(QStringLiteral("scopeMenu")); + + QActionGroup* scopeActions = new QActionGroup(this); + + m_currentDocumentAction = new QAction(this); + m_currentDocumentAction->setText(i18n("Current Document")); + m_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(m_currentDocumentAction); + actions.push_back(openDocumentsAction); + actions.push_back(currentProjectAction); + actions.push_back(allProjectAction); + + m_showAllAction = new QAction(this); + m_showAllAction->setText(i18n("Show All")); + m_showAllAction->setToolTip(i18nc("@info:tooltip", "Display ALL problems")); + actions.push_back(m_showAllAction); + + foreach (QAction* action, actions) { + action->setCheckable(true); + scopeActions->addAction(action); + m_scopeMenu->addAction(action); + } + addAction(m_scopeMenu); + + QSignalMapper* scopeMapper = new QSignalMapper(this); + scopeMapper->setMapping(m_currentDocumentAction, CurrentDocument); + scopeMapper->setMapping(openDocumentsAction, OpenDocuments); + scopeMapper->setMapping(currentProjectAction, CurrentProject); + scopeMapper->setMapping(allProjectAction, AllProjects); + connect(m_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)); + + { + scopeMapper->setMapping(actions.last(), BypassScopeFilter); + connect(actions.last(), &QAction::triggered, scopeMapper, + static_cast(&QSignalMapper::map)); + } + + connect(scopeMapper, static_cast(&QSignalMapper::mapped), + this, [this](int index) { + setScope(index); + }); + } + + { + m_severityActions = new QActionGroup(this); + + m_errorSeverityAction = new QAction(this); + m_errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors")); + m_errorSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); + + m_warningSeverityAction = new QAction(this); + m_warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display warnings")); + m_warningSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); + + m_hintSeverityAction = new QAction(this); + m_hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display hints")); + m_hintSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); + + QAction* severityActionArray[] = { m_errorSeverityAction, m_warningSeverityAction, m_hintSeverityAction }; + for (int i = 0; i < 3; ++i) { + severityActionArray[i]->setCheckable(true); + m_severityActions->addAction(severityActionArray[i]); + addAction(severityActionArray[i]); + } + m_severityActions->setExclusive(false); + + m_hintSeverityAction->setChecked(true); + m_warningSeverityAction->setChecked(true); + m_errorSeverityAction->setChecked(true); + + connect(m_errorSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); + connect(m_warningSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); + connect(m_hintSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); + } + + { + m_groupingMenu = new KActionMenu(i18n("Grouping"), this); + m_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); + m_groupingMenu->addAction(action); + } + addAction(m_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), + this, [this](int index) { + currentView()->model()->setGrouping(index); + }); + } +} + +void ProblemsView::updateActions() +{ + auto problemModel = currentView()->model(); + Q_ASSERT(problemModel); + + m_fullUpdateAction->setVisible(problemModel->features().testFlag(ProblemModel::CanDoFullUpdate)); + m_showImportsAction->setVisible(problemModel->features().testFlag(ProblemModel::CanShowImports)); + m_scopeMenu->setVisible(problemModel->features().testFlag(ProblemModel::ScopeFilter)); + m_severityActions->setVisible(problemModel->features().testFlag(ProblemModel::SeverityFilter)); + m_groupingMenu->setVisible(problemModel->features().testFlag(ProblemModel::Grouping)); + + m_showAllAction->setVisible(problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)); + + problemModel->setShowImports(false); + setScope(CurrentDocument); + + // 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); + setScope(BypassScopeFilter); + } else { + m_currentDocumentAction->setChecked(true); + setScope(CurrentDocument); + } + + problemModel->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); +} + /// TODO: Move to util? /// Note: Support for recursing into child indices would be nice class ItemViewWalker { public: ItemViewWalker(QItemSelectionModel* itemView); void selectNextIndex(); void selectPreviousIndex(); enum Direction { NextIndex, PreviousIndex }; void selectIndex(Direction direction); private: QItemSelectionModel* m_selectionModel; }; ItemViewWalker::ItemViewWalker(QItemSelectionModel* itemView) : m_selectionModel(itemView) { } void ItemViewWalker::selectNextIndex() { selectIndex(NextIndex); } void ItemViewWalker::selectPreviousIndex() { selectIndex(PreviousIndex); } void ItemViewWalker::selectIndex(Direction direction) { if (!m_selectionModel) { return; } const QModelIndexList list = m_selectionModel->selectedRows(); const QModelIndex currentIndex = list.value(0); if (!currentIndex.isValid()) { /// no selection yet, just select the first const QModelIndex firstIndex = m_selectionModel->model()->index(0, 0); m_selectionModel->setCurrentIndex(firstIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); return; } const int nextRow = currentIndex.row() + (direction == NextIndex ? 1 : -1); const QModelIndex nextIndex = currentIndex.sibling(nextRow, 0); if (!nextIndex.isValid()) { return; /// never invalidate the selection } m_selectionModel->setCurrentIndex(nextIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } ProblemsView::ProblemsView(QWidget* parent) - : QMainWindow(parent) + : QWidget(parent) { setWindowTitle(i18n("Problems")); setWindowIcon(QIcon::fromTheme(QStringLiteral("script-error"), windowIcon())); - m_toolBar = new QToolBar(this); - m_toolBar->setMovable(false); - m_toolBar->setFloatable(false); - addToolBar(m_toolBar); + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); m_tabWidget = new QTabWidget(this); m_tabWidget->setTabPosition(QTabWidget::South); - setCentralWidget(m_tabWidget); + layout->addWidget(m_tabWidget); + + setupActions(); } ProblemsView::~ProblemsView() { } void ProblemsView::load() { m_tabWidget->clear(); KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); QVector v = pms->models(); QVectorIterator itr(v); while (itr.hasNext()) { const KDevelop::ModelData& data = itr.next(); addModel(data); } connect(pms, &ProblemModelSet::added, this, &ProblemsView::onModelAdded); connect(pms, &ProblemModelSet::removed, this, &ProblemsView::onModelRemoved); connect(m_tabWidget, &QTabWidget::currentChanged, this, &ProblemsView::onCurrentChanged); if (m_tabWidget->currentIndex() == 0) { - updateToolBar(); + updateActions(); return; } m_tabWidget->setCurrentIndex(0); } void ProblemsView::onModelAdded(const ModelData& data) { addModel(data); } /** * @brief Returns the name part of the label * * E.g.: Test (666) => Test */ QString nameFromLabel(const QString& label) { QString txt = label; int i = txt.lastIndexOf('('); if (i != -1) txt = txt.left(i - 1); /// ignore whitespace before '(' return txt; } void ProblemsView::onModelRemoved(const QString& name) { int c = m_tabWidget->count(); int idx = 0; for (idx = 0; idx < c; ++idx) { if (nameFromLabel(m_tabWidget->tabText(idx)) == name) break; } if (idx < c) { QWidget* w = m_tabWidget->widget(idx); m_tabWidget->removeTab(idx); delete w; } } void ProblemsView::onCurrentChanged(int idx) { - m_toolBar->clear(); - if (idx == -1) return; - updateToolBar(); + updateActions(); } void ProblemsView::onViewChanged() { ProblemTreeView* view = static_cast(sender()); int idx = m_tabWidget->indexOf(view); int rows = view->model()->rowCount(); updateTab(idx, rows); } void ProblemsView::addModel(const ModelData& data) { ProblemTreeView* view = new ProblemTreeView(NULL, data.model); connect(view, &ProblemTreeView::changed, this, &ProblemsView::onViewChanged); int idx = m_tabWidget->addTab(view, data.name); int rows = view->model()->rowCount(); updateTab(idx, rows); } -void ProblemsView::updateToolBar() -{ - QWidget* w = m_tabWidget->currentWidget(); - m_toolBar->addActions(w->actions()); -} - void ProblemsView::updateTab(int idx, int rows) { const QString name = nameFromLabel(m_tabWidget->tabText(idx)); const QString tabText = i18nc("%1: tab name, %2: number of problems", "%1 (%2)", name, rows); m_tabWidget->setTabText(idx, tabText); } ProblemTreeView* ProblemsView::currentView() const { return qobject_cast(m_tabWidget->currentWidget()); } void ProblemsView::selectNextItem() { auto view = currentView(); if (view) { ItemViewWalker walker(view->selectionModel()); walker.selectNextIndex(); view->openDocumentForCurrentProblem(); } } void ProblemsView::selectPreviousItem() { auto view = currentView(); if (view) { ItemViewWalker walker(view->selectionModel()); walker.selectPreviousIndex(); view->openDocumentForCurrentProblem(); } } + +void ProblemsView::handleSeverityActionToggled() +{ + currentView()->model()->setSeverities( (m_errorSeverityAction->isChecked() ? IProblem::Error : IProblem::Severities()) | + (m_warningSeverityAction->isChecked() ? IProblem::Warning : IProblem::Severities()) | + (m_hintSeverityAction->isChecked() ? IProblem::Hint : IProblem::Severities()) ); +} + +void ProblemsView::setScope(int scope) +{ + m_scopeMenu->setText(i18n("Scope: %1", m_scopeMenu->menu()->actions().at(scope)->text())); + + currentView()->model()->setScope(scope); +} + } diff --git a/plugins/problemreporter/problemsview.h b/plugins/problemreporter/problemsview.h index 0602f0a6e9..d0e808ae4d 100644 --- a/plugins/problemreporter/problemsview.h +++ b/plugins/problemreporter/problemsview.h @@ -1,90 +1,107 @@ /* * 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 PROBLEMSVIEW_H #define PROBLEMSVIEW_H #include -#include +#include class ProblemTreeView; +class KActionMenu; + +class QAction; +class QActionGroup; +class QMenu; +class QTabWidget; + namespace KDevelop { struct ModelData; /** * @brief Provides a tabbed view for models in the ProblemModelSet. * + * * Also provides a toolbar for actions for the models and shows the number of messages in each tab's text. * When the load() method is called it looks up the models in the ProblemModelSet. * For each model it creates a treeview, which is then added to the tabbed view and a new tab. * The tab's text will be the name of the model + the number of items in the treeview. - * - * TODO: According to apol this should NOT be a QMainWindow, - * because updating the widget's actions should be sufficient to update the - * toolbar of the toolviiew */ -class ProblemsView : public QMainWindow, public IToolViewActionListener +class ProblemsView : public QWidget, public IToolViewActionListener { Q_OBJECT Q_INTERFACES(KDevelop::IToolViewActionListener) public: explicit ProblemsView(QWidget* parent = nullptr); ~ProblemsView() override; /// Load all the current models and create tabs for them void load(); public Q_SLOTS: /// Triggered when a new model is added to the ModelSet void onModelAdded(const ModelData& data); /// Triggered when a model is removed from the ModelSet void onModelRemoved(const QString& name); /// Triggered when the user (or program) selects a new tab void onCurrentChanged(int idx); /// Triggered when a view changes (happens when the model data changes) void onViewChanged(); void selectNextItem() override; void selectPreviousItem() override; private: ProblemTreeView* currentView() const; + void setupActions(); + void updateActions(); + + void handleSeverityActionToggled(); + void setScope(int scope); + /// Create a view for the model and add to the tabbed widget void addModel(const ModelData& data); - /// Update the toolbar with the widget's actions - void updateToolBar(); - /// Update the tab's text (name + number of problems in that tab) void updateTab(int idx, int rows); - QToolBar* m_toolBar; QTabWidget* m_tabWidget; + + KActionMenu* m_scopeMenu = nullptr; + KActionMenu* m_groupingMenu = nullptr; + QAction* m_fullUpdateAction = nullptr; + QAction* m_showImportsAction = nullptr; + QActionGroup* m_severityActions = nullptr; + QAction* m_currentDocumentAction = nullptr; + QAction* m_showAllAction = nullptr; + QAction* m_errorSeverityAction = nullptr; + QAction* m_warningSeverityAction = nullptr; + QAction* m_hintSeverityAction = nullptr; }; } #endif diff --git a/plugins/problemreporter/problemtreeview.cpp b/plugins/problemreporter/problemtreeview.cpp index 94dc0e3930..2918cc5966 100644 --- a/plugins/problemreporter/problemtreeview.cpp +++ b/plugins/problemreporter/problemtreeview.cpp @@ -1,393 +1,202 @@ /* * 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)) - , m_errorSeverityAction(nullptr) - , m_warningSeverityAction(nullptr) - , m_hintSeverityAction(nullptr) { setObjectName(QStringLiteral("Problem Reporter Tree")); setWhatsThis(i18n("Problems")); setItemDelegate(new ProblemTreeViewItemDelegate(this)); 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(QStringLiteral("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->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); - scopeMenu->setObjectName(QStringLiteral("scopeMenu")); - - 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); - setScope(CurrentDocument); - - // 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), this, - &ProblemTreeView::setScope); - } - - if (problemModel->features().testFlag(ProblemModel::SeverityFilter)) { - QActionGroup* severityActions = new QActionGroup(this); - - m_errorSeverityAction = new QAction(this); - m_errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors")); - m_errorSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); - - m_warningSeverityAction = new QAction(this); - m_warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display warnings")); - m_warningSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); - - m_hintSeverityAction = new QAction(this); - m_hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display hints")); - m_hintSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); - - QAction* severityActionArray[] = { m_errorSeverityAction, m_warningSeverityAction, m_hintSeverityAction }; - for (int i = 0; i < 3; ++i) { - severityActionArray[i]->setCheckable(true); - severityActions->addAction(severityActionArray[i]); - addAction(severityActionArray[i]); - } - severityActions->setExclusive(false); - - m_hintSeverityAction->setChecked(true); - m_warningSeverityAction->setChecked(true); - m_errorSeverityAction->setChecked(true); - - model()->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); - connect(m_errorSeverityAction, &QAction::toggled, this, &ProblemTreeView::handleSeverityActionToggled); - connect(m_warningSeverityAction, &QAction::toggled, this, &ProblemTreeView::handleSeverityActionToggled); - connect(m_hintSeverityAction, &QAction::toggled, this, &ProblemTreeView::handleSeverityActionToggled); - } - - 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()); 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::handleSeverityActionToggled() -{ - model()->setSeverities( (m_errorSeverityAction->isChecked() ? IProblem::Error : IProblem::Severities()) | - (m_warningSeverityAction->isChecked() ? IProblem::Warning : IProblem::Severities()) | - (m_hintSeverityAction->isChecked() ? IProblem::Hint : IProblem::Severities()) ); -} - -void ProblemTreeView::setScope(int scope) -{ - foreach (auto action, actions()) { - if (action->objectName() == QLatin1String("scopeMenu")) { - action->setText(i18n("Scope: %1", action->menu()->actions().at(scope)->text())); - } - } - - model()->setScope(scope); -} - void ProblemTreeView::resizeColumns() { 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(m_proxy->sourceModel()); } void ProblemTreeView::setModel(QAbstractItemModel* model) { Q_ASSERT(qobject_cast(model)); m_proxy->setSourceModel(model); QTreeView::setModel(m_proxy); } void ProblemTreeView::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if (index.isValid()) { 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(QLatin1String("'"), QLatin1String("\'")); 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) resizeColumns(); } #include "problemtreeview.moc" diff --git a/plugins/problemreporter/problemtreeview.h b/plugins/problemreporter/problemtreeview.h index 5f4945fa9c..d6f137a7f9 100644 --- a/plugins/problemreporter/problemtreeview.h +++ b/plugins/problemreporter/problemtreeview.h @@ -1,80 +1,74 @@ /* * 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 TopDUContext; class IDocument; class ProblemModel; } class ProblemReporterPlugin; class QSortFilterProxyModel; class ProblemTreeView : public QTreeView { Q_OBJECT public: ProblemTreeView(QWidget* parent, QAbstractItemModel* itemModel); ~ProblemTreeView() override; KDevelop::ProblemModel* model() const; void setModel(QAbstractItemModel* model) override; void contextMenuEvent(QContextMenuEvent*) override; void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles = QVector()) override; void reset() override; public slots: void openDocumentForCurrentProblem(); signals: // Emitted when the model's rows change (added/removed/reset) void changed(); protected: void showEvent(QShowEvent* event) override; private slots: void itemActivated(const QModelIndex& index); - void handleSeverityActionToggled(); - - void setScope(int scope); private: void resizeColumns(); ProblemReporterPlugin* m_plugin; QSortFilterProxyModel* m_proxy; - QAction* m_errorSeverityAction; - QAction* m_warningSeverityAction; - QAction* m_hintSeverityAction; }; #endif // kate: space-indent on; indent-width 2; tab-width: 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/problemreporter/tests/test_problemsview.cpp b/plugins/problemreporter/tests/test_problemsview.cpp index b5a3b5c2ac..fd5de0bcf4 100644 --- a/plugins/problemreporter/tests/test_problemsview.cpp +++ b/plugins/problemreporter/tests/test_problemsview.cpp @@ -1,227 +1,229 @@ /* * 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. */ #include +#include #include +#include #include #include "../problemsview.h" #include #include #include #include #include #include using namespace KDevelop; class TestProblemsView : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testLoad(); void testAddModel(); void testSwitchTab(); void testRemoveModel(); void testAddRemoveProblems(); void testSetProblems(); private: QTabWidget* tabWidget(); - QToolBar* toolBar(); bool compareActions(QWidget* w, QToolBar* tb); QScopedPointer m_view; }; void TestProblemsView::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); ProblemModelSet* pms = ICore::self()->languageController()->problemModelSet(); ProblemModel* model = new ProblemModel(pms); IProblem::Ptr p(new DetectedProblem()); model->addProblem(p); pms->addModel(QStringLiteral("MODEL1"), model); m_view.reset(new ProblemsView()); } void TestProblemsView::cleanupTestCase() { TestCore::shutdown(); } void TestProblemsView::testLoad() { m_view->load(); // Check that the inital model's tab shows up QTabWidget* tab = tabWidget(); QVERIFY(tab != nullptr); QCOMPARE(tab->count(), 1); QCOMPARE(tab->tabText(0), QStringLiteral("MODEL1 (1)")); } void TestProblemsView::testAddModel() { ProblemModelSet* pms = ICore::self()->languageController()->problemModelSet(); pms->addModel(QStringLiteral("MODEL2"), new ProblemModel(pms)); QTabWidget* tab = tabWidget(); QVERIFY(tab != nullptr); QCOMPARE(tab->count(), 2); QCOMPARE(tab->tabText(0), QStringLiteral("MODEL1 (1)")); QCOMPARE(tab->tabText(1), QStringLiteral("MODEL2 (0)")); } +QVector visibilites(const QList actions) +{ + QVector visibilites; + foreach (auto action, actions) { + visibilites << action->isVisible(); + } + return visibilites; +} + void TestProblemsView::testSwitchTab() { QTabWidget* tab = tabWidget(); QVERIFY(tab != nullptr); - QToolBar* tb = toolBar(); - QVERIFY(tb != nullptr); - // Check that the current widget's actions are in the toolbar QWidget* oldWidget = tab->currentWidget(); QVERIFY(oldWidget != nullptr); - QVERIFY(compareActions(oldWidget, tb)); + const auto oldVisibilites = visibilites(m_view->actions()); tab->setCurrentIndex(1); // Check that the new widget's actions are in the toolbar QWidget* newWidget = tab->currentWidget(); QVERIFY(newWidget != nullptr); QVERIFY(newWidget != oldWidget); - QVERIFY(compareActions(newWidget, tb)); + const auto newVisibilites = visibilites(m_view->actions()); + QCOMPARE(oldVisibilites, newVisibilites); } void TestProblemsView::testRemoveModel() { // Remove the model ProblemModelSet* pms = ICore::self()->languageController()->problemModelSet(); ProblemModel* model = pms->findModel(QStringLiteral("MODEL1")); QVERIFY(model != nullptr); pms->removeModel(QStringLiteral("MODEL1")); delete model; model = nullptr; // Now let's see if the view has been updated! QTabWidget* tab = tabWidget(); QVERIFY(tab != nullptr); QCOMPARE(tab->count(), 1); QCOMPARE(tab->tabText(0), QStringLiteral("MODEL2 (0)")); } void TestProblemsView::testAddRemoveProblems() { ProblemModelSet* pms = ICore::self()->languageController()->problemModelSet(); ProblemModel* model = pms->findModel(QStringLiteral("MODEL2")); QVERIFY(model != nullptr); QTabWidget* tab = tabWidget(); QVERIFY(tab != nullptr); // Make sure there are no problems right now model->clearProblems(); QCOMPARE(tab->tabText(0), QStringLiteral("MODEL2 (0)")); // Let's add some problems int c = 0; for (int i = 0; i < 3; i++) { IProblem::Ptr p(new DetectedProblem()); model->addProblem(p); c++; // Check if the view has noticed the addition QString label = QStringLiteral("MODEL2 (%1)").arg(c); QCOMPARE(tab->tabText(0), label); } // Clear the problems model->clearProblems(); // Check if the view has noticed the clear QCOMPARE(tab->tabText(0), QStringLiteral("MODEL2 (0)")); } void TestProblemsView::testSetProblems() { ProblemModelSet* pms = ICore::self()->languageController()->problemModelSet(); ProblemModel* model = pms->findModel(QStringLiteral("MODEL2")); QVERIFY(model != nullptr); QTabWidget* tab = tabWidget(); QVERIFY(tab != nullptr); // Make sure there are no problems right now model->clearProblems(); QCOMPARE(tab->tabText(0), QStringLiteral("MODEL2 (0)")); // Build a problem vector and set the problems QVector problems; for (int i = 0; i < 3; i++) { IProblem::Ptr p(new DetectedProblem()); problems.push_back(p); } model->setProblems(problems); // Check if the view has noticed QCOMPARE(tab->tabText(0), QStringLiteral("MODEL2 (3)")); } ////////////////////////////////////////////////////////////////////////////////////////////////////////// QTabWidget* TestProblemsView::tabWidget() { QTabWidget* tab = m_view->findChild(); return tab; } -QToolBar* TestProblemsView::toolBar() -{ - QToolBar* tb = m_view->findChild(); - return tb; -} - bool TestProblemsView::compareActions(QWidget* w, QToolBar* tb) { // Check that they have the same number of actions if (w->actions().count() != tb->actions().count()) return false; // Check that the actions are the same for (int i = 0; i < w->actions().count(); i++) { if (w->actions().at(i) != tb->actions().at(i)) return false; } return true; } QTEST_MAIN(TestProblemsView) #include "test_problemsview.moc"