From 5207374d5d230d6307fa3a839212c24ae627c635 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Mon, 13 Dec 2021 12:06:11 +0100 Subject: [PATCH] ProjectExplorer: Allow multi-selection in issues pane Users should be able to copy or remove several issues at once. Fixes: QTCREATORBUG-25547 Fixes: QTCREATORBUG-26720 Change-Id: I1ac75a3445c37200b6a01dd8c5d79d2b69c54a3c Reviewed-by: Christian Stenger --- .../projectexplorer/copytaskhandler.cpp | 38 +++++----- src/plugins/projectexplorer/copytaskhandler.h | 5 +- src/plugins/projectexplorer/itaskhandler.h | 14 +++- .../projectexplorer/removetaskhandler.cpp | 5 +- .../projectexplorer/removetaskhandler.h | 5 +- src/plugins/projectexplorer/taskmodel.cpp | 14 ++++ src/plugins/projectexplorer/taskmodel.h | 2 + src/plugins/projectexplorer/taskwindow.cpp | 75 +++++++++++-------- src/plugins/projectexplorer/taskwindow.h | 1 - 9 files changed, 99 insertions(+), 60 deletions(-) diff --git a/src/plugins/projectexplorer/copytaskhandler.cpp b/src/plugins/projectexplorer/copytaskhandler.cpp index 6a12c5251dd..4f9257cb01d 100644 --- a/src/plugins/projectexplorer/copytaskhandler.cpp +++ b/src/plugins/projectexplorer/copytaskhandler.cpp @@ -25,8 +25,6 @@ #include "copytaskhandler.h" -#include "task.h" - #include #include @@ -36,25 +34,27 @@ using namespace ProjectExplorer; using namespace ProjectExplorer::Internal; -void CopyTaskHandler::handle(const Task &task) +void CopyTaskHandler::handle(const Tasks &tasks) { - QString type; - switch (task.type) { - case Task::Error: - //: Task is of type: error - type = tr("error:") + QLatin1Char(' '); - break; - case Task::Warning: - //: Task is of type: warning - type = tr("warning:") + QLatin1Char(' '); - break; - default: - break; + QStringList lines; + for (const Task &task : tasks) { + QString type; + switch (task.type) { + case Task::Error: + //: Task is of type: error + type = tr("error:") + QLatin1Char(' '); + break; + case Task::Warning: + //: Task is of type: warning + type = tr("warning:") + QLatin1Char(' '); + break; + default: + break; + } + lines << task.file.toUserOutput() + ':' + QString::number(task.line) + + ": " + type + task.description(); } - - QApplication::clipboard()->setText(task.file.toUserOutput() + QLatin1Char(':') + - QString::number(task.line) + QLatin1String(": ") - + type + task.description()); + QApplication::clipboard()->setText(lines.join('\n')); } Utils::Id CopyTaskHandler::actionManagerId() const diff --git a/src/plugins/projectexplorer/copytaskhandler.h b/src/plugins/projectexplorer/copytaskhandler.h index da95a413c4b..8c4b4dc1da2 100644 --- a/src/plugins/projectexplorer/copytaskhandler.h +++ b/src/plugins/projectexplorer/copytaskhandler.h @@ -35,8 +35,9 @@ class CopyTaskHandler : public ITaskHandler Q_OBJECT public: - bool canHandle(const Task &) const override { return true; } - void handle(const Task &task) override; + CopyTaskHandler() : ITaskHandler(true) {} + + void handle(const Tasks &tasks) override; Utils::Id actionManagerId() const override; QAction *createAction(QObject *parent) const override; }; diff --git a/src/plugins/projectexplorer/itaskhandler.h b/src/plugins/projectexplorer/itaskhandler.h index 199e243988c..20a85ed5751 100644 --- a/src/plugins/projectexplorer/itaskhandler.h +++ b/src/plugins/projectexplorer/itaskhandler.h @@ -26,6 +26,7 @@ #pragma once #include "projectexplorer_export.h" +#include "task.h" #include @@ -37,21 +38,26 @@ class QAction; QT_END_NAMESPACE namespace ProjectExplorer { -class Task; class PROJECTEXPLORER_EXPORT ITaskHandler : public QObject { Q_OBJECT public: - ITaskHandler(); + explicit ITaskHandler(bool isMultiHandler = false); ~ITaskHandler() override; virtual bool isDefaultHandler() const { return false; } - virtual bool canHandle(const Task &) const = 0; - virtual void handle(const Task &) = 0; + virtual bool canHandle(const Task &) const { return m_isMultiHandler; } + virtual void handle(const Task &); // Non-multi-handlers should implement this. + virtual void handle(const Tasks &tasks); // Multi-handlers should implement this. virtual Utils::Id actionManagerId() const { return Utils::Id(); } virtual QAction *createAction(QObject *parent) const = 0; + + bool canHandle(const Tasks &tasks) const; + +private: + const bool m_isMultiHandler; }; } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/removetaskhandler.cpp b/src/plugins/projectexplorer/removetaskhandler.cpp index b8abc22e11d..7c8813b79f7 100644 --- a/src/plugins/projectexplorer/removetaskhandler.cpp +++ b/src/plugins/projectexplorer/removetaskhandler.cpp @@ -33,9 +33,10 @@ namespace ProjectExplorer { namespace Internal { -void RemoveTaskHandler::handle(const Task &task) +void RemoveTaskHandler::handle(const Tasks &tasks) { - TaskHub::removeTask(task); + for (const Task &task : tasks) + TaskHub::removeTask(task); } QAction *RemoveTaskHandler::createAction(QObject *parent) const diff --git a/src/plugins/projectexplorer/removetaskhandler.h b/src/plugins/projectexplorer/removetaskhandler.h index ae320d029de..ff624bc5073 100644 --- a/src/plugins/projectexplorer/removetaskhandler.h +++ b/src/plugins/projectexplorer/removetaskhandler.h @@ -35,8 +35,9 @@ class RemoveTaskHandler : public ITaskHandler Q_OBJECT public: - bool canHandle(const Task &) const override { return true; } - void handle(const Task &task) override; + RemoveTaskHandler() : ITaskHandler(true) {} + + void handle(const Tasks &tasks) override; QAction *createAction(QObject *parent) const override; }; diff --git a/src/plugins/projectexplorer/taskmodel.cpp b/src/plugins/projectexplorer/taskmodel.cpp index b9b74bb340c..36b6c942a1f 100644 --- a/src/plugins/projectexplorer/taskmodel.cpp +++ b/src/plugins/projectexplorer/taskmodel.cpp @@ -29,6 +29,7 @@ #include "task.h" #include "taskhub.h" +#include #include #include @@ -286,6 +287,13 @@ Task TaskModel::task(const QModelIndex &index) const return m_tasks.at(row); } +Tasks TaskModel::tasks(const QModelIndexList &indexes) const +{ + return Utils::filtered( + Utils::transform(indexes, [this](const QModelIndex &i) { return task(i); }), + [](const Task &t) { return !t.isNull(); }); +} + QList TaskModel::categoryIds() const { QList categories = m_categories.keys(); @@ -360,6 +368,12 @@ void TaskFilterModel::setFilterIncludesWarnings(bool b) invalidateFilter(); } +Tasks TaskFilterModel::tasks(const QModelIndexList &indexes) const +{ + return taskModel()->tasks(Utils::transform(indexes, [this](const QModelIndex &i) { + return mapToSource(i); })); +} + int TaskFilterModel::issuesCount(int startRow, int endRow) const { int count = 0; diff --git a/src/plugins/projectexplorer/taskmodel.h b/src/plugins/projectexplorer/taskmodel.h index 43e2d1d18c9..a0f23710194 100644 --- a/src/plugins/projectexplorer/taskmodel.h +++ b/src/plugins/projectexplorer/taskmodel.h @@ -49,6 +49,7 @@ public: int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Task task(const QModelIndex &index) const; + Tasks tasks(const QModelIndexList &indexes) const; QList categoryIds() const; QString categoryDisplayName(Utils::Id categoryId) const; @@ -142,6 +143,7 @@ public: void setFilteredCategories(const QList &categoryIds) { m_categoryIds = categoryIds; invalidateFilter(); } Task task(const QModelIndex &index) const { return taskModel()->task(mapToSource(index)); } + Tasks tasks(const QModelIndexList &indexes) const; int issuesCount(int startRow, int endRow) const; bool hasFile(const QModelIndex &index) const diff --git a/src/plugins/projectexplorer/taskwindow.cpp b/src/plugins/projectexplorer/taskwindow.cpp index 7b197a7ce18..5e446246b15 100644 --- a/src/plugins/projectexplorer/taskwindow.cpp +++ b/src/plugins/projectexplorer/taskwindow.cpp @@ -67,7 +67,7 @@ namespace ProjectExplorer { static QList g_taskHandlers; -ITaskHandler::ITaskHandler() +ITaskHandler::ITaskHandler(bool isMultiHandler) : m_isMultiHandler(isMultiHandler) { g_taskHandlers.append(this); } @@ -77,6 +77,30 @@ ITaskHandler::~ITaskHandler() g_taskHandlers.removeOne(this); } +void ITaskHandler::handle(const Task &task) +{ + QTC_ASSERT(m_isMultiHandler, return); + handle(Tasks{task}); +} + +void ITaskHandler::handle(const Tasks &tasks) +{ + QTC_ASSERT(canHandle(tasks), return); + QTC_ASSERT(!m_isMultiHandler, return); + handle(tasks.first()); +} + +bool ITaskHandler::canHandle(const Tasks &tasks) const +{ + if (tasks.isEmpty()) + return false; + if (m_isMultiHandler) + return true; + if (tasks.size() > 1) + return false; + return canHandle(tasks.first()); +} + namespace Internal { class TaskView : public Utils::ListView @@ -322,7 +346,7 @@ TaskWindow::TaskWindow() : d(std::make_unique()) d->m_listview->setModel(d->m_filter); d->m_listview->setFrameStyle(QFrame::NoFrame); d->m_listview->setWindowTitle(displayName()); - d->m_listview->setSelectionMode(QAbstractItemView::SingleSelection); + d->m_listview->setSelectionMode(QAbstractItemView::ExtendedSelection); auto *tld = new Internal::TaskDelegate(this); d->m_listview->setItemDelegate(tld); d->m_listview->setWindowIcon(Icons::WINDOW.icon()); @@ -336,11 +360,18 @@ TaskWindow::TaskWindow() : d(std::make_unique()) connect(d->m_listview->selectionModel(), &QItemSelectionModel::currentChanged, tld, &TaskDelegate::currentChanged); - connect(d->m_listview->selectionModel(), &QItemSelectionModel::currentChanged, - this, &TaskWindow::currentChanged); + this, [this](const QModelIndex &index) { d->m_listview->scrollTo(index); }); connect(d->m_listview, &QAbstractItemView::activated, this, &TaskWindow::triggerDefaultHandler); + connect(d->m_listview->selectionModel(), &QItemSelectionModel::selectionChanged, + this, [this] { + const Tasks tasks = d->m_filter->tasks(d->m_listview->selectionModel()->selectedIndexes()); + for (QAction * const action : qAsConst(d->m_actions)) { + ITaskHandler * const h = d->handler(action); + action->setEnabled(h && h->canHandle(tasks)); + } + }); d->m_contextMenu = new QMenu(d->m_listview); @@ -417,6 +448,7 @@ void TaskWindow::delayedInitialization() d->m_defaultHandler = h; QAction *action = h->createAction(this); + action->setEnabled(false); QTC_ASSERT(action, continue); d->m_actionToHandlerMap.insert(action, h); connect(action, &QAction::triggered, this, &TaskWindow::actionTriggered); @@ -430,9 +462,6 @@ void TaskWindow::delayedInitialization() } d->m_listview->addAction(action); } - - // Disable everything for now: - currentChanged(QModelIndex()); } QList TaskWindow::toolBarWidgets() const @@ -468,16 +497,6 @@ void TaskWindow::setCategoryVisibility(Utils::Id categoryId, bool visible) d->m_filter->setFilteredCategories(categories); } -void TaskWindow::currentChanged(const QModelIndex &index) -{ - const Task task = index.isValid() ? d->m_filter->task(index) : Task(); - foreach (QAction *action, d->m_actions) { - ITaskHandler *h = d->handler(action); - action->setEnabled((task.isNull() || !h) ? false : h->canHandle(task)); - } - d->m_listview->scrollTo(index); -} - void TaskWindow::saveSettings() { QStringList categories = Utils::transform(d->m_filter->filteredCategories(), &Utils::Id::toString); @@ -605,12 +624,7 @@ void TaskWindow::actionTriggered() if (!h) return; - QModelIndex index = d->m_listview->selectionModel()->currentIndex(); - Task task = d->m_filter->task(index); - if (task.isNull()) - return; - - h->handle(task); + h->handle(d->m_filter->tasks(d->m_listview->selectionModel()->selectedIndexes())); } void TaskWindow::setShowWarnings(bool show) @@ -774,11 +788,11 @@ QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInd initStyleOption(&opt, index); auto view = qobject_cast(opt.widget); - const bool selected = (view->selectionModel()->currentIndex() == index); + const bool current = view->selectionModel()->currentIndex() == index; QSize s; s.setWidth(option.rect.width()); - if (!selected && option.font == m_cachedFont && m_cachedHeight > 0) { + if (!current && option.font == m_cachedFont && m_cachedHeight > 0) { s.setHeight(m_cachedHeight); return s; } @@ -790,7 +804,7 @@ QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInd auto model = static_cast(view->model())->taskModel(); Positions positions(option, model); - if (selected) { + if (current) { QString description = index.data(TaskModel::Description).toString(); // Layout the description int leading = fontLeading; @@ -817,7 +831,7 @@ QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInd if (s.height() < Positions::minimumHeight()) s.setHeight(Positions::minimumHeight()); - if (!selected) { + if (!current) { m_cachedHeight = s.height(); m_cachedFont = option.font; } @@ -856,7 +870,8 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, QColor textColor; auto view = qobject_cast(opt.widget); - bool selected = view->selectionModel()->currentIndex() == index; + const bool selected = view->selectionModel()->isSelected(index); + const bool current = view->selectionModel()->currentIndex() == index; if (selected) { painter->setBrush(opt.palette.highlight().color()); @@ -885,7 +900,7 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, icon.pixmap(Positions::taskIconWidth(), Positions::taskIconHeight())); // Paint TextArea: - if (!selected) { + if (!current) { // in small mode we lay out differently QString bottom = index.data(TaskModel::Description).toString().split(QLatin1Char('\n')).first(); painter->setClipRect(positions.textArea()); @@ -908,7 +923,7 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, QTextLayout tl(description); QVector formats = index.data(TaskModel::Task_t).value().formats; for (QTextLayout::FormatRange &format : formats) - format.format.setForeground(opt.palette.highlightedText()); + format.format.setForeground(textColor); tl.setFormats(formats); tl.beginLayout(); while (true) { diff --git a/src/plugins/projectexplorer/taskwindow.h b/src/plugins/projectexplorer/taskwindow.h index e3287a92bd5..30f75ed09d6 100644 --- a/src/plugins/projectexplorer/taskwindow.h +++ b/src/plugins/projectexplorer/taskwindow.h @@ -91,7 +91,6 @@ private: void openTask(const Task &task); void clearTasks(Utils::Id categoryId); void setCategoryVisibility(Utils::Id categoryId, bool visible); - void currentChanged(const QModelIndex &index); void saveSettings(); void loadSettings();