forked from qt-creator/qt-creator
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 <christian.stenger@qt.io>
This commit is contained in:
@@ -25,8 +25,6 @@
|
||||
|
||||
#include "copytaskhandler.h"
|
||||
|
||||
#include "task.h"
|
||||
|
||||
#include <coreplugin/coreconstants.h>
|
||||
|
||||
#include <QAction>
|
||||
@@ -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
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -26,6 +26,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "projectexplorer_export.h"
|
||||
#include "task.h"
|
||||
|
||||
#include <utils/id.h>
|
||||
|
||||
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
|
@@ -29,6 +29,7 @@
|
||||
#include "task.h"
|
||||
#include "taskhub.h"
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QFileInfo>
|
||||
@@ -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<Tasks>(indexes, [this](const QModelIndex &i) { return task(i); }),
|
||||
[](const Task &t) { return !t.isNull(); });
|
||||
}
|
||||
|
||||
QList<Utils::Id> TaskModel::categoryIds() const
|
||||
{
|
||||
QList<Utils::Id> 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;
|
||||
|
@@ -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<Utils::Id> categoryIds() const;
|
||||
QString categoryDisplayName(Utils::Id categoryId) const;
|
||||
@@ -142,6 +143,7 @@ public:
|
||||
void setFilteredCategories(const QList<Utils::Id> &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
|
||||
|
@@ -67,7 +67,7 @@ namespace ProjectExplorer {
|
||||
|
||||
static QList<ITaskHandler *> 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<TaskWindowPrivate>())
|
||||
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<TaskWindowPrivate>())
|
||||
|
||||
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<QWidget*> 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<const QAbstractItemView *>(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<TaskFilterModel *>(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<const QAbstractItemView *>(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<QTextLayout::FormatRange> formats = index.data(TaskModel::Task_t).value<Task>().formats;
|
||||
for (QTextLayout::FormatRange &format : formats)
|
||||
format.format.setForeground(opt.palette.highlightedText());
|
||||
format.format.setForeground(textColor);
|
||||
tl.setFormats(formats);
|
||||
tl.beginLayout();
|
||||
while (true) {
|
||||
|
@@ -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();
|
||||
|
||||
|
Reference in New Issue
Block a user