diff --git a/kdevplatform/shell/problemmodel.h b/kdevplatform/shell/problemmodel.h --- a/kdevplatform/shell/problemmodel.h +++ b/kdevplatform/shell/problemmodel.h @@ -172,6 +172,10 @@ /// This signal emitted only when tooltip is really changed. void fullUpdateTooltipChanged(); + /// Emitted when the features are changed with setFeatures(). + /// This signal emitted only when features are really changed. + void featuresChanged(); + public Q_SLOTS: /// Show imports void setShowImports(bool showImports); diff --git a/kdevplatform/shell/problemmodel.cpp b/kdevplatform/shell/problemmodel.cpp --- a/kdevplatform/shell/problemmodel.cpp +++ b/kdevplatform/shell/problemmodel.cpp @@ -252,7 +252,12 @@ void ProblemModel::setFeatures(Features features) { + if (d->m_features == features) { + return; + } + d->m_features = features; + emit featuresChanged(); } QString ProblemModel::fullUpdateTooltip() const diff --git a/plugins/cppcheck/problemmodel.cpp b/plugins/cppcheck/problemmodel.cpp --- a/plugins/cppcheck/problemmodel.cpp +++ b/plugins/cppcheck/problemmodel.cpp @@ -123,6 +123,7 @@ void ProblemModel::setProblems() { setProblems(m_problems); + setFeatures(features() | CanDoFullUpdate); } void ProblemModel::reset() @@ -138,6 +139,8 @@ clearProblems(); m_problems.clear(); + setFeatures(features() & ~CanDoFullUpdate); + QString tooltip = i18nc("@info:tooltip", "Re-Run Last Cppcheck Analysis"); if (m_project) { tooltip += QStringLiteral(" (%1)").arg(prettyPathName(m_path)); diff --git a/plugins/problemreporter/CMakeLists.txt b/plugins/problemreporter/CMakeLists.txt --- a/plugins/problemreporter/CMakeLists.txt +++ b/plugins/problemreporter/CMakeLists.txt @@ -3,6 +3,7 @@ ########### next target ############### set(kdevproblemreporter_PART_SRCS + actionmenu.cpp problemreporterplugin.cpp problemtreeview.cpp problemhighlighter.cpp diff --git a/plugins/problemreporter/actionmenu.h b/plugins/problemreporter/actionmenu.h new file mode 100644 --- /dev/null +++ b/plugins/problemreporter/actionmenu.h @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Anton Anikin + * + * 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 ACTION_MENU_H +#define ACTION_MENU_H + +#include + +class QActionGroup; + +namespace KDevelop +{ + +class ActionMenu : public QWidgetAction +{ + Q_OBJECT + +public: + explicit ActionMenu(const QString& prefix, QObject* parent = nullptr); + ~ActionMenu() override; + + QAction* addAction(const QString& text, const QString& toolTip, int id); + + void selectAction(int id); + + QWidget* createWidget(QWidget* parent) override; + +Q_SIGNALS: + void actionSelected(int id); + +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + + QString m_prefix; + QActionGroup* m_group; + QHash m_mapper; +}; + +} + +#endif diff --git a/plugins/problemreporter/actionmenu.cpp b/plugins/problemreporter/actionmenu.cpp new file mode 100644 --- /dev/null +++ b/plugins/problemreporter/actionmenu.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2018 Anton Anikin + * + * 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 "actionmenu.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace KDevelop +{ + +class ActionMenuButton : public QToolButton +{ + Q_OBJECT + +public: + ActionMenuButton(const QString& prefix, QWidget* parent = nullptr) + : QToolButton(parent) + , m_prefix(prefix) + { + setPopupMode(QToolButton::InstantPopup); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + } + + ~ActionMenuButton() override + { + } + + QSize sizeHint() const override + { + auto fm = fontMetrics(); + int textHeight = 0; + int textWidth = 0; + int spaceWidth = fm.width(QLatin1Char(' ')); + + for (auto action : menu()->actions()) + { + auto textSize = fm.size( + Qt::TextShowMnemonic, + QStringLiteral("%1: %2").arg(m_prefix, action->text())); + + textHeight = qMax(textHeight, textSize.height()); + textWidth = qMax(textWidth, textSize.width()); + } + + QStyleOptionToolButton opt; + initStyleOption(&opt); + opt.rect.setHeight(textHeight); + opt.rect.setWidth(textWidth + 2 * spaceWidth); + + auto fullSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, opt.rect.size(), this). + expandedTo(QApplication::globalStrut()); + + m_margin = (fullSize.width() - opt.rect.width()) / 2 + spaceWidth; + + return fullSize; + } + + void paintEvent(QPaintEvent* event) override + { + Q_UNUSED(event); + + QStylePainter sp(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + + opt.text.clear(); + opt.icon = QIcon(); + sp.drawComplexControl(QStyle::CC_ToolButton, opt); + + opt.rect.adjust(m_margin, 0, 0, 0); + sp.drawText(opt.rect, Qt::AlignLeft | Qt::AlignVCenter, text()); + } + +protected: + QString m_prefix; + mutable int m_margin; +}; + +ActionMenu::ActionMenu(const QString& prefix, QObject* parent) + : QWidgetAction(parent) + , m_prefix(prefix) + , m_group(new QActionGroup(this)) +{ + setMenu(new QMenu); + menu()->installEventFilter(this); +} + +ActionMenu::~ActionMenu() +{ + delete menu(); +} + +QAction* ActionMenu::addAction(const QString& text, const QString& toolTip, int id) +{ + auto action = new QAction(text, this); + action->setToolTip(toolTip); + action->setCheckable(true); + + m_mapper[id] = action; + + connect(action, &QAction::triggered, + this, [this, id]() { + emit actionSelected(id); + }); + + menu()->addAction(action); + m_group->addAction(action); + + return action; +} + +void ActionMenu::selectAction(int id) +{ + if (auto action = m_mapper.value(id, nullptr)) { + action->setChecked(true); + setText(QStringLiteral("%1: %2").arg(m_prefix, action->text())); + } +} + +QWidget* ActionMenu::createWidget(QWidget* parent) +{ + auto button = new ActionMenuButton(m_prefix, parent); + button->setMenu(menu()); + button->setDefaultAction(this); + + return button; +} + +bool ActionMenu::eventFilter(QObject* obj, QEvent* event) +{ + if (obj == menu()) { + if (event->type() == QEvent::ToolTip && menu()->activeAction()) { + auto helpEvent = static_cast(event); + QToolTip::showText(helpEvent->globalPos(), menu()->activeAction()->toolTip()); + } else { + QToolTip::hideText(); + } + } + + return QObject::eventFilter(obj, event); +} + +} + +#include "actionmenu.moc" diff --git a/plugins/problemreporter/problemsview.h b/plugins/problemreporter/problemsview.h --- a/plugins/problemreporter/problemsview.h +++ b/plugins/problemreporter/problemsview.h @@ -26,7 +26,6 @@ class ProblemTreeView; -class KActionMenu; class KExpandableLineEdit; class QAction; @@ -39,6 +38,8 @@ struct ModelData; +class ActionMenu; + /** * @brief Provides a tabbed view for models in the ProblemModelSet. * @@ -87,6 +88,7 @@ void handleSeverityActionToggled(); void setScope(int scope); + void setGrouping(int grouping); /// Create a view for the model and add to the tabbed widget void addModel(const ModelData& data); @@ -96,12 +98,11 @@ QTabWidget* m_tabWidget; - KActionMenu* m_scopeMenu = nullptr; - KActionMenu* m_groupingMenu = nullptr; + ActionMenu* m_scopeMenu = nullptr; + ActionMenu* 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; diff --git a/plugins/problemreporter/problemsview.cpp b/plugins/problemreporter/problemsview.cpp --- a/plugins/problemreporter/problemsview.cpp +++ b/plugins/problemreporter/problemsview.cpp @@ -35,6 +35,7 @@ #include #include #include +#include "actionmenu.h" #include "problemtreeview.h" #include "problemmodel.h" @@ -55,71 +56,36 @@ } { - m_scopeMenu = new KActionMenu(this); - m_scopeMenu->setDelayed(false); + m_scopeMenu = new ActionMenu(i18n("Scope"), this); m_scopeMenu->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); - m_scopeMenu->setObjectName(QStringLiteral("scopeMenu")); + connect(m_scopeMenu, &ActionMenu::actionSelected, this, &ProblemsView::setScope); - QActionGroup* scopeActions = new QActionGroup(this); + m_scopeMenu->addAction( + i18n("Current Document"), + i18nc("@info:tooltip", "Display problems in current document"), + CurrentDocument); - m_currentDocumentAction = new QAction(this); - m_currentDocumentAction->setText(i18n("Current Document")); - m_currentDocumentAction->setToolTip(i18nc("@info:tooltip", "Display problems in current document")); + m_scopeMenu->addAction( + i18n("Open Documents"), + i18nc("@info:tooltip", "Display problems in all open documents"), + OpenDocuments); - QAction* openDocumentsAction = new QAction(this); - openDocumentsAction->setText(i18n("Open Documents")); - openDocumentsAction->setToolTip(i18nc("@info:tooltip", "Display problems in all open documents")); + m_scopeMenu->addAction( + i18n("Current Project"), + i18nc("@info:tooltip", "Display problems in current project"), + CurrentProject); - QAction* currentProjectAction = new QAction(this); - currentProjectAction->setText(i18n("Current Project")); - currentProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in current project")); + m_scopeMenu->addAction( + i18n("All Projects"), + i18nc("@info:tooltip", "Display problems in all projects"), + AllProjects); - QAction* allProjectAction = new QAction(this); - allProjectAction->setText(i18n("All Projects")); - allProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in all projects")); + m_showAllAction = m_scopeMenu->addAction( + i18n("Show All"), + i18nc("@info:tooltip", "Display ALL problems"), + BypassScopeFilter); - 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); - }); } { @@ -170,41 +136,25 @@ } { - m_groupingMenu = new KActionMenu(i18n("Grouping"), this); - m_groupingMenu->setDelayed(false); + m_groupingMenu = new ActionMenu(i18n("Grouping"), this); + connect(m_groupingMenu, &ActionMenu::actionSelected, this, &ProblemsView::setGrouping); - QActionGroup* groupingActions = new QActionGroup(this); + m_groupingMenu->addAction( + i18n("None"), + i18nc("@info:tooltip", "No grouping"), + NoGrouping); - QAction* noGroupingAction = new QAction(i18n("None"), this); - QAction* pathGroupingAction = new QAction(i18n("Path"), this); - QAction* severityGroupingAction = new QAction(i18n("Severity"), this); + m_groupingMenu->addAction( + i18n("Path"), + i18nc("@info:tooltip", "Group problems by path"), + PathGrouping); - 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); + m_groupingMenu->addAction( + i18n("Severity"), + i18nc("@info:tooltip", "Group problems by severity"), + SeverityGrouping); - 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); - }); + addAction(m_groupingMenu); } { @@ -237,29 +187,29 @@ auto problemModel = currentView()->model(); Q_ASSERT(problemModel); - m_fullUpdateAction->setVisible(problemModel->features().testFlag(ProblemModel::CanDoFullUpdate)); + m_fullUpdateAction->setEnabled(problemModel->features().testFlag(ProblemModel::CanDoFullUpdate)); m_fullUpdateAction->setToolTip(problemModel->fullUpdateTooltip()); - 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_showImportsAction->setEnabled(problemModel->features().testFlag(ProblemModel::CanShowImports)); + m_scopeMenu->setEnabled(problemModel->features().testFlag(ProblemModel::ScopeFilter)); + m_severityActions->setEnabled(problemModel->features().testFlag(ProblemModel::SeverityFilter)); + m_groupingMenu->setEnabled(problemModel->features().testFlag(ProblemModel::Grouping)); - m_showAllAction->setVisible(problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)); + m_showAllAction->setEnabled(problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)); m_showImportsAction->setChecked(false); problemModel->setShowImports(false); // 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); + setGrouping(NoGrouping); + setFocus(); // set focus to default widget (filterEdit) } @@ -436,6 +386,13 @@ } }); + connect(model, &ProblemModel::featuresChanged, + this, [this, model] { + if (currentView()->model() == model) { + updateActions(); + } + }); + int insertIdx = 0; if (newData.id != parserId) { for (insertIdx = 0; insertIdx < m_models.size(); ++insertIdx) { @@ -499,9 +456,14 @@ void ProblemsView::setScope(int scope) { - m_scopeMenu->setText(i18n("Scope: %1", m_scopeMenu->menu()->actions().at(scope)->text())); - currentView()->model()->setScope(scope); + m_scopeMenu->selectAction(scope); +} + +void ProblemsView::setGrouping(int grouping) +{ + currentView()->model()->setGrouping(grouping); + m_groupingMenu->selectAction(grouping); } void ProblemsView::setFilter(const QString& filterText) diff --git a/plugins/problemreporter/tests/CMakeLists.txt b/plugins/problemreporter/tests/CMakeLists.txt --- a/plugins/problemreporter/tests/CMakeLists.txt +++ b/plugins/problemreporter/tests/CMakeLists.txt @@ -1,5 +1,6 @@ set(TEST_PROBLEMSVIEW_SRC test_problemsview.cpp + ../actionmenu.cpp ../problemsview.cpp ../problemtreeview.cpp )