diff --git a/kdevplatform/util/CMakeLists.txt b/kdevplatform/util/CMakeLists.txt index 5fecb40d2b..a4ec912ca0 100644 --- a/kdevplatform/util/CMakeLists.txt +++ b/kdevplatform/util/CMakeLists.txt @@ -1,102 +1,103 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevplatform\") ########### next target ############### set(KDevPlatformUtil_LIB_SRCS autoorientedsplitter.cpp foregroundlock.cpp formattinghelpers.cpp zoomcontroller.cpp kdevstringhandler.cpp focusedtreeview.cpp processlinemaker.cpp commandexecutor.cpp environmentselectionwidget.cpp environmentselectionmodel.cpp environmentprofilelist.cpp jobstatus.cpp activetooltip.cpp executecompositejob.cpp shellutils.cpp multilevellistview.cpp objectlist.cpp placeholderitemproxymodel.cpp projecttestjob.cpp widgetcolorizer.cpp path.cpp texteditorhelpers.cpp stack.cpp + expandablelineedit.cpp ) set (KDevPlatformUtil_LIB_UI runoptions.ui ) if(NOT WIN32) add_subdirectory(dbus_socket_transformer) endif() if(BUILD_TESTING) add_subdirectory(duchainify) # needs KDev::Tests endif() if(BUILD_TESTING) add_subdirectory(tests) endif() ecm_qt_declare_logging_category(KDevPlatformUtil_LIB_SRCS HEADER debug.h IDENTIFIER UTIL CATEGORY_NAME "kdevplatform.util" ) ki18n_wrap_ui(KDevPlatformUtil_LIB_SRCS ${KDevPlatformUtil_LIB_US}) kdevplatform_add_library(KDevPlatformUtil SOURCES ${KDevPlatformUtil_LIB_SRCS}) target_link_libraries(KDevPlatformUtil PUBLIC KDev::Interfaces PRIVATE KF5::ItemModels KF5::GuiAddons ) install( FILES kdevplatform_shell_environment.sh DESTINATION bin PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ) add_executable(kdev_format_source kdevformatsource.cpp kdevformatfile.cpp) ecm_mark_nongui_executable(kdev_format_source) target_link_libraries(kdev_format_source Qt5::Core) install(TARGETS kdev_format_source DESTINATION ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES .zshrc PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ DESTINATION ${KDE_INSTALL_DATAROOTDIR}/kdevplatform/shellutils/) ########### install files ############### install( FILES autoorientedsplitter.h foregroundlock.h formattinghelpers.h zoomcontroller.h kdevstringhandler.h ksharedobject.h focusedtreeview.h activetooltip.h processlinemaker.h commandexecutor.h environmentselectionwidget.h environmentprofilelist.h jobstatus.h pushvalue.h kdevvarlengtharray.h embeddedfreetree.h executecompositejob.h convenientfreelist.h multilevellistview.h objectlist.h placeholderitemproxymodel.h projecttestjob.h widgetcolorizer.h path.h stack.h texteditorhelpers.h ${CMAKE_CURRENT_BINARY_DIR}/utilexport.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/kdevplatform/util COMPONENT Devel) diff --git a/kdevplatform/util/expandablelineedit.cpp b/kdevplatform/util/expandablelineedit.cpp new file mode 100644 index 0000000000..91824f0d9c --- /dev/null +++ b/kdevplatform/util/expandablelineedit.cpp @@ -0,0 +1,59 @@ +/* + * 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 "expandablelineedit.h" + +KExpandableLineEdit::KExpandableLineEdit(QWidget* parent) + : QLineEdit(parent) + , m_preferredWidth(200) +{ + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + connect(this, &KExpandableLineEdit::textChanged, this, &KExpandableLineEdit::updateGeometry); +} + +KExpandableLineEdit::~KExpandableLineEdit() +{ +} + +int KExpandableLineEdit::preferredWidth() const +{ + return m_preferredWidth; +} + +void KExpandableLineEdit::setPreferredWidth(int width) +{ + if (m_preferredWidth != width) { + m_preferredWidth = width; + updateGeometry(); + } +} + +QSize KExpandableLineEdit::sizeHint() const +{ + auto idealSize = QLineEdit::sizeHint(); + + int idealWidth = fontMetrics().width(text()); + if (isClearButtonEnabled()) { + idealWidth += 2 * idealSize.height(); + } + idealSize.setWidth(qMax(idealWidth, m_preferredWidth)); + + return idealSize; +} diff --git a/kdevplatform/util/expandablelineedit.h b/kdevplatform/util/expandablelineedit.h new file mode 100644 index 0000000000..69b597fc61 --- /dev/null +++ b/kdevplatform/util/expandablelineedit.h @@ -0,0 +1,50 @@ +/* + * 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 EXPANDABLE_LINE_EDIT_H +#define EXPANDABLE_LINE_EDIT_H + +#include "utilexport.h" +#include + +/** This class implements a line edit widget which tries to expand its + * width to fit typed text. When no text typed or text is too short + * the widget's width is defined by the preferredWidth() value + * (default is 200 px). + */ + +class KDEVPLATFORMUTIL_EXPORT KExpandableLineEdit : public QLineEdit +{ + Q_OBJECT + +public: + explicit KExpandableLineEdit(QWidget* parent = nullptr); + + ~KExpandableLineEdit() override; + + int preferredWidth() const; + void setPreferredWidth(int width); + + QSize sizeHint() const override; + +protected: + int m_preferredWidth; +}; + +#endif diff --git a/plugins/problemreporter/problemsview.cpp b/plugins/problemreporter/problemsview.cpp index 2f832a2e52..be95927bad 100644 --- a/plugins/problemreporter/problemsview.cpp +++ b/plugins/problemreporter/problemsview.cpp @@ -1,531 +1,528 @@ /* * 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 +#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->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(m_fullUpdateAction, &QAction::triggered, this, [this]() { currentView()->model()->forceFullUpdate(); }); addAction(m_fullUpdateAction); } { 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_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_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_errorSeverityAction->setIconText(i18n("Show Errors")); m_warningSeverityAction = new QAction(this); m_warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display warnings")); m_warningSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_warningSeverityAction->setIconText(i18n("Show Warnings")); m_hintSeverityAction = new QAction(this); m_hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display hints")); m_hintSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); m_hintSeverityAction->setIconText(i18n("Show Hints")); 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); }); } { QTimer* filterTimer = new QTimer(this); filterTimer->setSingleShot(true); connect(filterTimer, &QTimer::timeout, this, [this]() { setFilter(m_filterEdit->text()); }); - m_filterEdit = new QLineEdit(this); + m_filterEdit = new KExpandableLineEdit(this); m_filterEdit->setClearButtonEnabled(true); m_filterEdit->setPlaceholderText(i18n("Search...")); - QSizePolicy p(m_filterEdit->sizePolicy()); - p.setHorizontalPolicy(QSizePolicy::Fixed); - m_filterEdit->setSizePolicy(p); - connect(m_filterEdit, &QLineEdit::textChanged, this, [filterTimer](const QString&) { filterTimer->start(500); }); QWidgetAction* filterAction = new QWidgetAction(this); filterAction->setDefaultWidget(m_filterEdit); addAction(filterAction); m_prevTabIdx = -1; setFocusProxy(m_filterEdit); } } void ProblemsView::updateActions() { auto problemModel = currentView()->model(); Q_ASSERT(problemModel); m_fullUpdateAction->setVisible(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_showAllAction->setVisible(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); setFocus(); // set focus to default widget (filterEdit) } /// TODO: Move to util? /// Note: Support for recursing into child indices would be nice class ItemViewWalker { public: explicit 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) : QWidget(parent) { setWindowTitle(i18n("Problems")); setWindowIcon(QIcon::fromTheme(QStringLiteral("script-error"), windowIcon())); auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); m_tabWidget = new QTabWidget(this); m_tabWidget->setTabPosition(QTabWidget::South); m_tabWidget->setDocumentMode(true); 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) { updateActions(); return; } m_tabWidget->setCurrentIndex(0); } void ProblemsView::onModelAdded(const ModelData& data) { addModel(data); } void ProblemsView::showModel(const QString& id) { for (int i = 0; i < m_models.size(); ++i) { if (m_models[i].id == id) { m_tabWidget->setCurrentIndex(i); return; } } } void ProblemsView::onModelRemoved(const QString& id) { for (int i = 0; i < m_models.size(); ++i) { if (m_models[i].id == id) { m_models.remove(i); QWidget* w = m_tabWidget->widget(i); m_tabWidget->removeTab(i); delete w; return; } } } void ProblemsView::onCurrentChanged(int idx) { if (idx == -1) return; setFilter(QStringLiteral(""), m_prevTabIdx); setFilter(QStringLiteral("")); m_prevTabIdx = idx; 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& newData) { // We implement follows tabs order: // // 1) First tab always used by "Parser" model due to it's the most important // problem listing, it should be at the front (K.Funk idea at #kdevelop IRC channel). // // 2) Other tabs are alphabetically ordered. static const QString parserId = QStringLiteral("Parser"); auto model = newData.model; auto view = new ProblemTreeView(nullptr, model); connect(view, &ProblemTreeView::changed, this, &ProblemsView::onViewChanged); connect(model, &ProblemModel::fullUpdateTooltipChanged, this, [this, model]() { if (currentView()->model() == model) { m_fullUpdateAction->setToolTip(model->fullUpdateTooltip()); } }); int insertIdx = 0; if (newData.id != parserId) { for (insertIdx = 0; insertIdx < m_models.size(); ++insertIdx) { const ModelData& currentData = m_models[insertIdx]; // Skip first element if it's already occupied by "Parser" model if (insertIdx == 0 && currentData.id == parserId) continue; if (currentData.name.localeAwareCompare(newData.name) > 0) break; } } m_tabWidget->insertTab(insertIdx, view, newData.name); m_models.insert(insertIdx, newData); updateTab(insertIdx, model->rowCount()); } void ProblemsView::updateTab(int idx, int rows) { if (idx < 0 || idx >= m_models.size()) return; const QString name = m_models[idx].name; 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); } void ProblemsView::setFilter(const QString& filterText) { setFilter(filterText, m_tabWidget->currentIndex()); } void ProblemsView::setFilter(const QString& filterText, int tabIdx) { if (tabIdx < 0 || tabIdx >= m_tabWidget->count()) return; ProblemTreeView* view = static_cast(m_tabWidget->widget(tabIdx)); int rows = view->setFilter(filterText); updateTab(tabIdx, rows); if (tabIdx == m_tabWidget->currentIndex()) { QSignalBlocker blocker(m_filterEdit); m_filterEdit->setText(filterText); } } } diff --git a/plugins/problemreporter/problemsview.h b/plugins/problemreporter/problemsview.h index 5d84f7b640..5f9e5d0656 100644 --- a/plugins/problemreporter/problemsview.h +++ b/plugins/problemreporter/problemsview.h @@ -1,117 +1,119 @@ /* * 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 class ProblemTreeView; class KActionMenu; +class KExpandableLineEdit; class QAction; class QActionGroup; class QLineEdit; 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. */ 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& id); /// 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(); /// Open tab for selected model void showModel(const QString& id); 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 tab's text (name + number of problems in that tab) void updateTab(int idx, int rows); 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; void setFilter(const QString& filterText); void setFilter(const QString& filterText, int tabIdx); - QLineEdit* m_filterEdit; + KExpandableLineEdit* m_filterEdit; int m_prevTabIdx; QVector m_models; }; } #endif diff --git a/plugins/problemreporter/tests/CMakeLists.txt b/plugins/problemreporter/tests/CMakeLists.txt index 17eb9dcf69..3af2f2d844 100644 --- a/plugins/problemreporter/tests/CMakeLists.txt +++ b/plugins/problemreporter/tests/CMakeLists.txt @@ -1,9 +1,9 @@ set(TEST_PROBLEMSVIEW_SRC test_problemsview.cpp ../problemsview.cpp ../problemtreeview.cpp ) ecm_add_test(${TEST_PROBLEMSVIEW_SRC} TEST_NAME test_problemsview - LINK_LIBRARIES Qt5::Test KDev::Tests KDev::Shell + LINK_LIBRARIES Qt5::Test KDev::Tests KDev::Shell KDev::Util ) diff --git a/plugins/standardoutputview/outputwidget.cpp b/plugins/standardoutputview/outputwidget.cpp index 741470f21a..a1ba08a1b2 100644 --- a/plugins/standardoutputview/outputwidget.cpp +++ b/plugins/standardoutputview/outputwidget.cpp @@ -1,687 +1,686 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Dukju Ahn * * 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. */ #include "outputwidget.h" #include "standardoutputview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "outputmodel.h" #include "toolviewdata.h" #include Q_DECLARE_METATYPE(QTreeView*) OutputWidget::OutputWidget(QWidget* parent, const ToolViewData* tvdata) : QWidget( parent ) , m_tabwidget(nullptr) , m_stackwidget(nullptr) , data(tvdata) , m_closeButton(nullptr) , m_closeOthersAction(nullptr) , m_nextAction(nullptr) , m_previousAction(nullptr) , m_activateOnSelect(nullptr) , m_focusOnSelect(nullptr) , m_filterInput(nullptr) , m_filterAction(nullptr) { setWindowTitle(i18n("Output View")); setWindowIcon(tvdata->icon); QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); if( data->type & KDevelop::IOutputView::MultipleView ) { m_tabwidget = new QTabWidget(this); layout->addWidget( m_tabwidget ); m_closeButton = new QToolButton( this ); connect( m_closeButton, &QToolButton::clicked, this, &OutputWidget::closeActiveView ); m_closeButton->setIcon( QIcon::fromTheme( QStringLiteral( "tab-close") ) ); m_closeButton->setToolTip( i18n( "Close the currently active output view") ); m_closeButton->setAutoRaise(true); m_closeOthersAction = new QAction( this ); connect(m_closeOthersAction, &QAction::triggered, this, &OutputWidget::closeOtherViews); m_closeOthersAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other"))); m_closeOthersAction->setToolTip( i18n( "Close all other output views" ) ); m_closeOthersAction->setText( m_closeOthersAction->toolTip() ); addAction(m_closeOthersAction); m_tabwidget->setCornerWidget(m_closeButton, Qt::TopRightCorner); m_tabwidget->setDocumentMode(true); } else if ( data->type == KDevelop::IOutputView::HistoryView ) { m_stackwidget = new QStackedWidget( this ); layout->addWidget( m_stackwidget ); m_previousAction = new QAction( QIcon::fromTheme( QStringLiteral( "arrow-left" ) ), i18n("Previous Output"), this ); connect(m_previousAction, &QAction::triggered, this, &OutputWidget::previousOutput); addAction(m_previousAction); m_nextAction = new QAction( QIcon::fromTheme( QStringLiteral( "arrow-right" ) ), i18n("Next Output"), this ); connect(m_nextAction, &QAction::triggered, this, &OutputWidget::nextOutput); addAction(m_nextAction); } addAction(dynamic_cast(data->plugin->actionCollection()->action(QStringLiteral("prev_error")))); addAction(dynamic_cast(data->plugin->actionCollection()->action(QStringLiteral("next_error")))); m_activateOnSelect = new KToggleAction( QIcon(), i18n("Select activated Item"), this ); m_activateOnSelect->setChecked( true ); m_focusOnSelect = new KToggleAction( QIcon(), i18n("Focus when selecting Item"), this ); m_focusOnSelect->setChecked( false ); if( data->option & KDevelop::IOutputView::ShowItemsButton ) { addAction(m_activateOnSelect); addAction(m_focusOnSelect); } QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); QAction* action; action = new QAction(QIcon::fromTheme(QStringLiteral("go-first")), i18n("First Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectFirstItem); addAction(action); action = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Previous Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectPreviousItem); addAction(action); action = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Next Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectNextItem); addAction(action); action = new QAction(QIcon::fromTheme(QStringLiteral("go-last")), i18n("Last Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectLastItem); addAction(action); QAction* selectAllAction = KStandardAction::selectAll(this, SLOT(selectAll()), this); selectAllAction->setShortcut(QKeySequence()); //FIXME: why does CTRL-A conflict with Katepart (while CTRL-Cbelow doesn't) ? selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(selectAllAction); QAction* copyAction = KStandardAction::copy(this, SLOT(copySelection()), this); copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(copyAction); QAction *clearAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-list")), i18n("Clear"), this); connect(clearAction, &QAction::triggered, this, &OutputWidget::clearModel); addAction(clearAction); if( data->option & KDevelop::IOutputView::AddFilterAction ) { QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); - m_filterInput = new QLineEdit(); - m_filterInput->setMaximumWidth(150); - m_filterInput->setMinimumWidth(100); + m_filterInput = new KExpandableLineEdit(this); m_filterInput->setPlaceholderText(i18n("Search...")); m_filterInput->setClearButtonEnabled(true); m_filterInput->setToolTip(i18n("Enter a wild card string to filter the output view")); m_filterAction = new QWidgetAction(this); m_filterAction->setText(m_filterInput->placeholderText()); connect(m_filterAction, &QAction::triggered, this, [this]() {m_filterInput->setFocus();}); m_filterAction->setDefaultWidget(m_filterInput); addAction(m_filterAction); connect(m_filterInput, &QLineEdit::textEdited, this, &OutputWidget::outputFilter ); if( data->type & KDevelop::IOutputView::MultipleView ) { connect(m_tabwidget, &QTabWidget::currentChanged, this, &OutputWidget::updateFilter); } else if ( data->type == KDevelop::IOutputView::HistoryView ) { connect(m_stackwidget, &QStackedWidget::currentChanged, this, &OutputWidget::updateFilter); } } addActions(data->actionList); connect( data, &ToolViewData::outputAdded, this, &OutputWidget::addOutput ); connect( this, &OutputWidget::outputRemoved, data->plugin, &StandardOutputView::outputRemoved ); foreach( int id, data->outputdata.keys() ) { changeModel( id ); changeDelegate( id ); } enableActions(); } void OutputWidget::clearModel() { auto view = qobject_cast(currentWidget()); if( !view || !view->isVisible()) return; KDevelop::OutputModel *outputModel = nullptr; if (auto proxy = qobject_cast(view->model())) { outputModel = qobject_cast(proxy->sourceModel()); } else { outputModel = qobject_cast(view->model()); } outputModel->clear(); } void OutputWidget::addOutput( int id ) { QTreeView* listview = createListView(id); setCurrentWidget( listview ); connect( data->outputdata.value(id), &OutputData::modelChanged, this, &OutputWidget::changeModel); connect( data->outputdata.value(id), &OutputData::delegateChanged, this, &OutputWidget::changeDelegate); enableActions(); } void OutputWidget::setCurrentWidget( QTreeView* view ) { if( data->type & KDevelop::IOutputView::MultipleView ) { m_tabwidget->setCurrentWidget( view ); } else if( data->type & KDevelop::IOutputView::HistoryView ) { m_stackwidget->setCurrentWidget( view ); } } void OutputWidget::changeDelegate( int id ) { if( data->outputdata.contains( id ) && m_views.contains( id ) ) { m_views.value(id)->setItemDelegate(data->outputdata.value(id)->delegate); } else { addOutput(id); } } void OutputWidget::changeModel( int id ) { if( data->outputdata.contains( id ) && m_views.contains( id ) ) { OutputData* od = data->outputdata.value(id); m_views.value( id )->setModel(od->model); } else { addOutput( id ); } } void OutputWidget::removeOutput( int id ) { if( data->outputdata.contains( id ) && m_views.contains( id ) ) { QTreeView* view = m_views.value(id); if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) { if( data->type & KDevelop::IOutputView::MultipleView ) { int idx = m_tabwidget->indexOf( view ); if( idx != -1 ) { m_tabwidget->removeTab( idx ); if( m_proxyModels.contains( idx ) ) { delete m_proxyModels.take( idx ); m_filters.remove( idx ); } } } else { int idx = m_stackwidget->indexOf( view ); if( idx != -1 && m_proxyModels.contains( idx ) ) { delete m_proxyModels.take( idx ); m_filters.remove( idx ); } m_stackwidget->removeWidget( view ); } delete view; } else { m_views.value( id )->setModel( nullptr ); m_views.value( id )->setItemDelegate( nullptr ); if( m_proxyModels.contains( 0 ) ) { delete m_proxyModels.take( 0 ); m_filters.remove( 0 ); } } m_views.remove( id ); emit outputRemoved( data->toolViewId, id ); } enableActions(); } void OutputWidget::closeActiveView() { QWidget* widget = m_tabwidget->currentWidget(); if( !widget ) return; foreach( int id, m_views.keys() ) { if( m_views.value(id) == widget ) { OutputData* od = data->outputdata.value(id); if( od->behaviour & KDevelop::IOutputView::AllowUserClose ) { data->plugin->removeOutput( id ); } } } enableActions(); } void OutputWidget::closeOtherViews() { QWidget* widget = m_tabwidget->currentWidget(); if (!widget) return; foreach (int id, m_views.keys()) { if (m_views.value(id) == widget) { continue; // leave the active view open } OutputData* od = data->outputdata.value(id); if (od->behaviour & KDevelop::IOutputView::AllowUserClose) { data->plugin->removeOutput( id ); } } enableActions(); } QWidget* OutputWidget::currentWidget() const { QWidget* widget; if( data->type & KDevelop::IOutputView::MultipleView ) { widget = m_tabwidget->currentWidget(); } else if( data->type & KDevelop::IOutputView::HistoryView ) { widget = m_stackwidget->currentWidget(); } else { widget = m_views.begin().value(); } return widget; } KDevelop::IOutputViewModel *OutputWidget::outputViewModel() const { auto view = qobject_cast(currentWidget()); if( !view || !view->isVisible()) return nullptr; QAbstractItemModel *absmodel = view->model(); KDevelop::IOutputViewModel *iface = dynamic_cast(absmodel); if ( ! iface ) { // try if it's a proxy model? if ( QAbstractProxyModel* proxy = qobject_cast(absmodel) ) { iface = dynamic_cast(proxy->sourceModel()); } } return iface; } void OutputWidget::eventuallyDoFocus() { QWidget* widget = currentWidget(); if( m_focusOnSelect->isChecked() && !widget->hasFocus() ) { widget->setFocus( Qt::OtherFocusReason ); } } QAbstractItemView *OutputWidget::outputView() const { return qobject_cast(currentWidget()); } void OutputWidget::activateIndex(const QModelIndex &index, QAbstractItemView *view, KDevelop::IOutputViewModel *iface) { if( ! index.isValid() ) return; int tabIndex = currentOutputIndex(); QModelIndex sourceIndex = index; QModelIndex viewIndex = index; if( QAbstractProxyModel* proxy = m_proxyModels.value(tabIndex) ) { if ( index.model() == proxy ) { // index is from the proxy, map it to the source sourceIndex = proxy->mapToSource(index); } else if (proxy == view->model()) { // index is from the source, map it to the proxy viewIndex = proxy->mapFromSource(index); } } view->setCurrentIndex( viewIndex ); view->scrollTo( viewIndex ); if( m_activateOnSelect->isChecked() ) { iface->activate( sourceIndex ); } } void OutputWidget::selectFirstItem() { selectItem(First); } void OutputWidget::selectNextItem() { selectItem(Next); } void OutputWidget::selectPreviousItem() { selectItem(Previous); } void OutputWidget::selectLastItem() { selectItem(Last); } void OutputWidget::selectItem(SelectionMode selectionMode) { auto view = outputView(); auto iface = outputViewModel(); if ( ! view || ! iface ) return; eventuallyDoFocus(); auto index = view->currentIndex(); if (QAbstractProxyModel* proxy = m_proxyModels.value(currentOutputIndex())) { if ( index.model() == proxy ) { // index is from the proxy, map it to the source index = proxy->mapToSource(index); } } QModelIndex newIndex; switch (selectionMode) { case First: newIndex = iface->firstHighlightIndex(); break; case Next: newIndex = iface->nextHighlightIndex( index ); break; case Previous: newIndex = iface->previousHighlightIndex( index ); break; case Last: newIndex = iface->lastHighlightIndex(); break; } qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "old:" << index << "- new:" << newIndex; activateIndex(newIndex, view, iface); } void OutputWidget::activate(const QModelIndex& index) { auto iface = outputViewModel(); auto view = outputView(); if( ! view || ! iface ) return; activateIndex(index, view, iface); } QTreeView* OutputWidget::createListView(int id) { auto createHelper = [&]() -> QTreeView* { KDevelop::FocusedTreeView* listview = new KDevelop::FocusedTreeView(this); listview->setEditTriggers( QAbstractItemView::NoEditTriggers ); listview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); //Always enable the scrollbar, so it doesn't flash around listview->setHeaderHidden(true); listview->setUniformRowHeights(true); listview->setRootIsDecorated(false); listview->setSelectionMode( QAbstractItemView::ContiguousSelection ); if (data->outputdata.value(id)->behaviour & KDevelop::IOutputView::AutoScroll) { listview->setAutoScrollAtEnd(true); } connect(listview, &QTreeView::activated, this, &OutputWidget::activate); connect(listview, &QTreeView::clicked, this, &OutputWidget::activate); return listview; }; QTreeView* listview = nullptr; if( !m_views.contains(id) ) { bool newView = true; if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) { qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "creating listview"; listview = createHelper(); if( data->type & KDevelop::IOutputView::MultipleView ) { m_tabwidget->addTab( listview, data->outputdata.value(id)->title ); } else { m_stackwidget->addWidget( listview ); m_stackwidget->setCurrentWidget( listview ); } } else { if( m_views.isEmpty() ) { listview = createHelper(); layout()->addWidget( listview ); } else { listview = m_views.begin().value(); newView = false; } } m_views[id] = listview; changeModel( id ); changeDelegate( id ); if (newView) listview->scrollToBottom(); } else { listview = m_views.value(id); } enableActions(); return listview; } void OutputWidget::raiseOutput(int id) { if( m_views.contains(id) ) { if( data->type & KDevelop::IOutputView::MultipleView ) { int idx = m_tabwidget->indexOf( m_views.value(id) ); if( idx >= 0 ) { m_tabwidget->setCurrentIndex( idx ); } } else if( data->type & KDevelop::IOutputView::HistoryView ) { int idx = m_stackwidget->indexOf( m_views.value(id) ); if( idx >= 0 ) { m_stackwidget->setCurrentIndex( idx ); } } } enableActions(); } void OutputWidget::nextOutput() { if( m_stackwidget && m_stackwidget->currentIndex() < m_stackwidget->count()-1 ) { m_stackwidget->setCurrentIndex( m_stackwidget->currentIndex()+1 ); } enableActions(); } void OutputWidget::previousOutput() { if( m_stackwidget && m_stackwidget->currentIndex() > 0 ) { m_stackwidget->setCurrentIndex( m_stackwidget->currentIndex()-1 ); } enableActions(); } void OutputWidget::enableActions() { if( data->type == KDevelop::IOutputView::HistoryView ) { Q_ASSERT(m_stackwidget); Q_ASSERT(m_nextAction); Q_ASSERT(m_previousAction); m_previousAction->setEnabled( ( m_stackwidget->currentIndex() > 0 ) ); m_nextAction->setEnabled( ( m_stackwidget->currentIndex() < m_stackwidget->count() - 1 ) ); } } void OutputWidget::scrollToIndex( const QModelIndex& idx ) { QWidget* w = currentWidget(); if( !w ) return; QAbstractItemView *view = static_cast(w); view->scrollTo( idx ); } void OutputWidget::copySelection() { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; QClipboard *cb = QApplication::clipboard(); QModelIndexList indexes = view->selectionModel()->selectedRows(); QString content; Q_FOREACH( const QModelIndex& index, indexes) { content += index.data().toString() + '\n'; } content.chop(1); cb->setText(content); } void OutputWidget::selectAll() { if (QAbstractItemView *view = qobject_cast(currentWidget())) view->selectAll(); } int OutputWidget::currentOutputIndex() { int index = 0; if( data->type & KDevelop::IOutputView::MultipleView ) { index = m_tabwidget->currentIndex(); } else if( data->type & KDevelop::IOutputView::HistoryView ) { index = m_stackwidget->currentIndex(); } return index; } void OutputWidget::outputFilter(const QString& filter) { QAbstractItemView *view = qobject_cast(currentWidget()); if( !view ) return; int index = currentOutputIndex(); auto proxyModel = qobject_cast(view->model()); if( !proxyModel ) { proxyModel = new QSortFilterProxyModel(view->model()); proxyModel->setDynamicSortFilter(true); proxyModel->setSourceModel(view->model()); m_proxyModels.insert(index, proxyModel); view->setModel(proxyModel); } QRegExp regExp(filter, Qt::CaseInsensitive); proxyModel->setFilterRegExp(regExp); m_filters[index] = filter; } void OutputWidget::updateFilter(int index) { if(m_filters.contains(index)) { m_filterInput->setText(m_filters[index]); } else { m_filterInput->clear(); } } void OutputWidget::setTitle(int outputId, const QString& title) { QTreeView* view = m_views.value(outputId, nullptr); if(view && (data->type & KDevelop::IOutputView::MultipleView)) { int idx = m_tabwidget->indexOf(view); if (idx >= 0) { m_tabwidget->setTabText(idx, title); } } } diff --git a/plugins/standardoutputview/outputwidget.h b/plugins/standardoutputview/outputwidget.h index 2bd324dd74..918d3b85d6 100644 --- a/plugins/standardoutputview/outputwidget.h +++ b/plugins/standardoutputview/outputwidget.h @@ -1,120 +1,121 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Dukju Ahn * * 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. */ #ifndef KDEVPLATFORM_PLUGIN_OUTPUTWIDGET_H #define KDEVPLATFORM_PLUGIN_OUTPUTWIDGET_H #include #include #include #include #include +class KExpandableLineEdit; class KToggleAction; class StandardOutputViewTest; class QAction; class QAbstractItemView; class QLineEdit; class QModelIndex; class QSortFilterProxyModel; class QStackedWidget; class QString; class QTabWidget; class QToolButton; class QTreeView; class QWidgetAction; class ToolViewData; class OutputWidget : public QWidget, public KDevelop::IToolViewActionListener { Q_OBJECT Q_INTERFACES(KDevelop::IToolViewActionListener) friend class StandardOutputViewTest; public: OutputWidget(QWidget* parent, const ToolViewData* data); void removeOutput( int id ); void raiseOutput( int id ); public Q_SLOTS: void addOutput( int id ); void changeModel( int id ); void changeDelegate( int id ); void closeActiveView(); void closeOtherViews(); void selectFirstItem(); void selectNextItem() override; void selectPreviousItem() override; void selectLastItem(); void activate(const QModelIndex&); void scrollToIndex( const QModelIndex& ); void setTitle(int outputId, const QString& title); Q_SIGNALS: void outputRemoved( int, int ); private Q_SLOTS: void nextOutput(); void previousOutput(); void copySelection(); void selectAll(); void outputFilter(const QString& filter); void updateFilter(int index); void clearModel(); private: enum SelectionMode { Last, Next, Previous, First }; void selectItem(SelectionMode selectionMode); QTreeView* createListView(int id); void setCurrentWidget( QTreeView* view ); QWidget* currentWidget() const; void enableActions(); KDevelop::IOutputViewModel* outputViewModel() const; QAbstractItemView* outputView() const; void activateIndex(const QModelIndex& index, QAbstractItemView* view, KDevelop::IOutputViewModel* iface); void eventuallyDoFocus(); int currentOutputIndex(); QMap m_views; QMap m_proxyModels; QMap m_filters; QTabWidget* m_tabwidget; QStackedWidget* m_stackwidget; const ToolViewData* data; QToolButton* m_closeButton; QAction* m_closeOthersAction; QAction* m_nextAction; QAction* m_previousAction; KToggleAction* m_activateOnSelect; KToggleAction* m_focusOnSelect; - QLineEdit * m_filterInput; + KExpandableLineEdit* m_filterInput; QWidgetAction* m_filterAction; }; #endif