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 "copytaskhandler.h"
|
||||||
|
|
||||||
#include "task.h"
|
|
||||||
|
|
||||||
#include <coreplugin/coreconstants.h>
|
#include <coreplugin/coreconstants.h>
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
@@ -36,25 +34,27 @@
|
|||||||
using namespace ProjectExplorer;
|
using namespace ProjectExplorer;
|
||||||
using namespace ProjectExplorer::Internal;
|
using namespace ProjectExplorer::Internal;
|
||||||
|
|
||||||
void CopyTaskHandler::handle(const Task &task)
|
void CopyTaskHandler::handle(const Tasks &tasks)
|
||||||
{
|
{
|
||||||
QString type;
|
QStringList lines;
|
||||||
switch (task.type) {
|
for (const Task &task : tasks) {
|
||||||
case Task::Error:
|
QString type;
|
||||||
//: Task is of type: error
|
switch (task.type) {
|
||||||
type = tr("error:") + QLatin1Char(' ');
|
case Task::Error:
|
||||||
break;
|
//: Task is of type: error
|
||||||
case Task::Warning:
|
type = tr("error:") + QLatin1Char(' ');
|
||||||
//: Task is of type: warning
|
break;
|
||||||
type = tr("warning:") + QLatin1Char(' ');
|
case Task::Warning:
|
||||||
break;
|
//: Task is of type: warning
|
||||||
default:
|
type = tr("warning:") + QLatin1Char(' ');
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lines << task.file.toUserOutput() + ':' + QString::number(task.line)
|
||||||
|
+ ": " + type + task.description();
|
||||||
}
|
}
|
||||||
|
QApplication::clipboard()->setText(lines.join('\n'));
|
||||||
QApplication::clipboard()->setText(task.file.toUserOutput() + QLatin1Char(':') +
|
|
||||||
QString::number(task.line) + QLatin1String(": ")
|
|
||||||
+ type + task.description());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::Id CopyTaskHandler::actionManagerId() const
|
Utils::Id CopyTaskHandler::actionManagerId() const
|
||||||
|
@@ -35,8 +35,9 @@ class CopyTaskHandler : public ITaskHandler
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool canHandle(const Task &) const override { return true; }
|
CopyTaskHandler() : ITaskHandler(true) {}
|
||||||
void handle(const Task &task) override;
|
|
||||||
|
void handle(const Tasks &tasks) override;
|
||||||
Utils::Id actionManagerId() const override;
|
Utils::Id actionManagerId() const override;
|
||||||
QAction *createAction(QObject *parent) const override;
|
QAction *createAction(QObject *parent) const override;
|
||||||
};
|
};
|
||||||
|
@@ -26,6 +26,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "projectexplorer_export.h"
|
#include "projectexplorer_export.h"
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
#include <utils/id.h>
|
#include <utils/id.h>
|
||||||
|
|
||||||
@@ -37,21 +38,26 @@ class QAction;
|
|||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
namespace ProjectExplorer {
|
namespace ProjectExplorer {
|
||||||
class Task;
|
|
||||||
|
|
||||||
class PROJECTEXPLORER_EXPORT ITaskHandler : public QObject
|
class PROJECTEXPLORER_EXPORT ITaskHandler : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ITaskHandler();
|
explicit ITaskHandler(bool isMultiHandler = false);
|
||||||
~ITaskHandler() override;
|
~ITaskHandler() override;
|
||||||
|
|
||||||
virtual bool isDefaultHandler() const { return false; }
|
virtual bool isDefaultHandler() const { return false; }
|
||||||
virtual bool canHandle(const Task &) const = 0;
|
virtual bool canHandle(const Task &) const { return m_isMultiHandler; }
|
||||||
virtual void handle(const Task &) = 0;
|
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 Utils::Id actionManagerId() const { return Utils::Id(); }
|
||||||
virtual QAction *createAction(QObject *parent) const = 0;
|
virtual QAction *createAction(QObject *parent) const = 0;
|
||||||
|
|
||||||
|
bool canHandle(const Tasks &tasks) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const bool m_isMultiHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ProjectExplorer
|
} // namespace ProjectExplorer
|
||||||
|
@@ -33,9 +33,10 @@
|
|||||||
namespace ProjectExplorer {
|
namespace ProjectExplorer {
|
||||||
namespace Internal {
|
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
|
QAction *RemoveTaskHandler::createAction(QObject *parent) const
|
||||||
|
@@ -35,8 +35,9 @@ class RemoveTaskHandler : public ITaskHandler
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool canHandle(const Task &) const override { return true; }
|
RemoveTaskHandler() : ITaskHandler(true) {}
|
||||||
void handle(const Task &task) override;
|
|
||||||
|
void handle(const Tasks &tasks) override;
|
||||||
QAction *createAction(QObject *parent) const override;
|
QAction *createAction(QObject *parent) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -29,6 +29,7 @@
|
|||||||
#include "task.h"
|
#include "task.h"
|
||||||
#include "taskhub.h"
|
#include "taskhub.h"
|
||||||
|
|
||||||
|
#include <utils/algorithm.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
@@ -286,6 +287,13 @@ Task TaskModel::task(const QModelIndex &index) const
|
|||||||
return m_tasks.at(row);
|
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> TaskModel::categoryIds() const
|
||||||
{
|
{
|
||||||
QList<Utils::Id> categories = m_categories.keys();
|
QList<Utils::Id> categories = m_categories.keys();
|
||||||
@@ -360,6 +368,12 @@ void TaskFilterModel::setFilterIncludesWarnings(bool b)
|
|||||||
invalidateFilter();
|
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 TaskFilterModel::issuesCount(int startRow, int endRow) const
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
@@ -49,6 +49,7 @@ public:
|
|||||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
Task task(const QModelIndex &index) const;
|
Task task(const QModelIndex &index) const;
|
||||||
|
Tasks tasks(const QModelIndexList &indexes) const;
|
||||||
|
|
||||||
QList<Utils::Id> categoryIds() const;
|
QList<Utils::Id> categoryIds() const;
|
||||||
QString categoryDisplayName(Utils::Id categoryId) const;
|
QString categoryDisplayName(Utils::Id categoryId) const;
|
||||||
@@ -142,6 +143,7 @@ public:
|
|||||||
void setFilteredCategories(const QList<Utils::Id> &categoryIds) { m_categoryIds = categoryIds; invalidateFilter(); }
|
void setFilteredCategories(const QList<Utils::Id> &categoryIds) { m_categoryIds = categoryIds; invalidateFilter(); }
|
||||||
|
|
||||||
Task task(const QModelIndex &index) const { return taskModel()->task(mapToSource(index)); }
|
Task task(const QModelIndex &index) const { return taskModel()->task(mapToSource(index)); }
|
||||||
|
Tasks tasks(const QModelIndexList &indexes) const;
|
||||||
int issuesCount(int startRow, int endRow) const;
|
int issuesCount(int startRow, int endRow) const;
|
||||||
|
|
||||||
bool hasFile(const QModelIndex &index) const
|
bool hasFile(const QModelIndex &index) const
|
||||||
|
@@ -67,7 +67,7 @@ namespace ProjectExplorer {
|
|||||||
|
|
||||||
static QList<ITaskHandler *> g_taskHandlers;
|
static QList<ITaskHandler *> g_taskHandlers;
|
||||||
|
|
||||||
ITaskHandler::ITaskHandler()
|
ITaskHandler::ITaskHandler(bool isMultiHandler) : m_isMultiHandler(isMultiHandler)
|
||||||
{
|
{
|
||||||
g_taskHandlers.append(this);
|
g_taskHandlers.append(this);
|
||||||
}
|
}
|
||||||
@@ -77,6 +77,30 @@ ITaskHandler::~ITaskHandler()
|
|||||||
g_taskHandlers.removeOne(this);
|
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 {
|
namespace Internal {
|
||||||
|
|
||||||
class TaskView : public Utils::ListView
|
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->setModel(d->m_filter);
|
||||||
d->m_listview->setFrameStyle(QFrame::NoFrame);
|
d->m_listview->setFrameStyle(QFrame::NoFrame);
|
||||||
d->m_listview->setWindowTitle(displayName());
|
d->m_listview->setWindowTitle(displayName());
|
||||||
d->m_listview->setSelectionMode(QAbstractItemView::SingleSelection);
|
d->m_listview->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
auto *tld = new Internal::TaskDelegate(this);
|
auto *tld = new Internal::TaskDelegate(this);
|
||||||
d->m_listview->setItemDelegate(tld);
|
d->m_listview->setItemDelegate(tld);
|
||||||
d->m_listview->setWindowIcon(Icons::WINDOW.icon());
|
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,
|
connect(d->m_listview->selectionModel(), &QItemSelectionModel::currentChanged,
|
||||||
tld, &TaskDelegate::currentChanged);
|
tld, &TaskDelegate::currentChanged);
|
||||||
|
|
||||||
connect(d->m_listview->selectionModel(), &QItemSelectionModel::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,
|
connect(d->m_listview, &QAbstractItemView::activated,
|
||||||
this, &TaskWindow::triggerDefaultHandler);
|
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);
|
d->m_contextMenu = new QMenu(d->m_listview);
|
||||||
|
|
||||||
@@ -417,6 +448,7 @@ void TaskWindow::delayedInitialization()
|
|||||||
d->m_defaultHandler = h;
|
d->m_defaultHandler = h;
|
||||||
|
|
||||||
QAction *action = h->createAction(this);
|
QAction *action = h->createAction(this);
|
||||||
|
action->setEnabled(false);
|
||||||
QTC_ASSERT(action, continue);
|
QTC_ASSERT(action, continue);
|
||||||
d->m_actionToHandlerMap.insert(action, h);
|
d->m_actionToHandlerMap.insert(action, h);
|
||||||
connect(action, &QAction::triggered, this, &TaskWindow::actionTriggered);
|
connect(action, &QAction::triggered, this, &TaskWindow::actionTriggered);
|
||||||
@@ -430,9 +462,6 @@ void TaskWindow::delayedInitialization()
|
|||||||
}
|
}
|
||||||
d->m_listview->addAction(action);
|
d->m_listview->addAction(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable everything for now:
|
|
||||||
currentChanged(QModelIndex());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QWidget*> TaskWindow::toolBarWidgets() const
|
QList<QWidget*> TaskWindow::toolBarWidgets() const
|
||||||
@@ -468,16 +497,6 @@ void TaskWindow::setCategoryVisibility(Utils::Id categoryId, bool visible)
|
|||||||
d->m_filter->setFilteredCategories(categories);
|
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()
|
void TaskWindow::saveSettings()
|
||||||
{
|
{
|
||||||
QStringList categories = Utils::transform(d->m_filter->filteredCategories(), &Utils::Id::toString);
|
QStringList categories = Utils::transform(d->m_filter->filteredCategories(), &Utils::Id::toString);
|
||||||
@@ -605,12 +624,7 @@ void TaskWindow::actionTriggered()
|
|||||||
if (!h)
|
if (!h)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QModelIndex index = d->m_listview->selectionModel()->currentIndex();
|
h->handle(d->m_filter->tasks(d->m_listview->selectionModel()->selectedIndexes()));
|
||||||
Task task = d->m_filter->task(index);
|
|
||||||
if (task.isNull())
|
|
||||||
return;
|
|
||||||
|
|
||||||
h->handle(task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TaskWindow::setShowWarnings(bool show)
|
void TaskWindow::setShowWarnings(bool show)
|
||||||
@@ -774,11 +788,11 @@ QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInd
|
|||||||
initStyleOption(&opt, index);
|
initStyleOption(&opt, index);
|
||||||
|
|
||||||
auto view = qobject_cast<const QAbstractItemView *>(opt.widget);
|
auto view = qobject_cast<const QAbstractItemView *>(opt.widget);
|
||||||
const bool selected = (view->selectionModel()->currentIndex() == index);
|
const bool current = view->selectionModel()->currentIndex() == index;
|
||||||
QSize s;
|
QSize s;
|
||||||
s.setWidth(option.rect.width());
|
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);
|
s.setHeight(m_cachedHeight);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@@ -790,7 +804,7 @@ QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInd
|
|||||||
auto model = static_cast<TaskFilterModel *>(view->model())->taskModel();
|
auto model = static_cast<TaskFilterModel *>(view->model())->taskModel();
|
||||||
Positions positions(option, model);
|
Positions positions(option, model);
|
||||||
|
|
||||||
if (selected) {
|
if (current) {
|
||||||
QString description = index.data(TaskModel::Description).toString();
|
QString description = index.data(TaskModel::Description).toString();
|
||||||
// Layout the description
|
// Layout the description
|
||||||
int leading = fontLeading;
|
int leading = fontLeading;
|
||||||
@@ -817,7 +831,7 @@ QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInd
|
|||||||
if (s.height() < Positions::minimumHeight())
|
if (s.height() < Positions::minimumHeight())
|
||||||
s.setHeight(Positions::minimumHeight());
|
s.setHeight(Positions::minimumHeight());
|
||||||
|
|
||||||
if (!selected) {
|
if (!current) {
|
||||||
m_cachedHeight = s.height();
|
m_cachedHeight = s.height();
|
||||||
m_cachedFont = option.font;
|
m_cachedFont = option.font;
|
||||||
}
|
}
|
||||||
@@ -856,7 +870,8 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|||||||
QColor textColor;
|
QColor textColor;
|
||||||
|
|
||||||
auto view = qobject_cast<const QAbstractItemView *>(opt.widget);
|
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) {
|
if (selected) {
|
||||||
painter->setBrush(opt.palette.highlight().color());
|
painter->setBrush(opt.palette.highlight().color());
|
||||||
@@ -885,7 +900,7 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|||||||
icon.pixmap(Positions::taskIconWidth(), Positions::taskIconHeight()));
|
icon.pixmap(Positions::taskIconWidth(), Positions::taskIconHeight()));
|
||||||
|
|
||||||
// Paint TextArea:
|
// Paint TextArea:
|
||||||
if (!selected) {
|
if (!current) {
|
||||||
// in small mode we lay out differently
|
// in small mode we lay out differently
|
||||||
QString bottom = index.data(TaskModel::Description).toString().split(QLatin1Char('\n')).first();
|
QString bottom = index.data(TaskModel::Description).toString().split(QLatin1Char('\n')).first();
|
||||||
painter->setClipRect(positions.textArea());
|
painter->setClipRect(positions.textArea());
|
||||||
@@ -908,7 +923,7 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|||||||
QTextLayout tl(description);
|
QTextLayout tl(description);
|
||||||
QVector<QTextLayout::FormatRange> formats = index.data(TaskModel::Task_t).value<Task>().formats;
|
QVector<QTextLayout::FormatRange> formats = index.data(TaskModel::Task_t).value<Task>().formats;
|
||||||
for (QTextLayout::FormatRange &format : formats)
|
for (QTextLayout::FormatRange &format : formats)
|
||||||
format.format.setForeground(opt.palette.highlightedText());
|
format.format.setForeground(textColor);
|
||||||
tl.setFormats(formats);
|
tl.setFormats(formats);
|
||||||
tl.beginLayout();
|
tl.beginLayout();
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@@ -91,7 +91,6 @@ private:
|
|||||||
void openTask(const Task &task);
|
void openTask(const Task &task);
|
||||||
void clearTasks(Utils::Id categoryId);
|
void clearTasks(Utils::Id categoryId);
|
||||||
void setCategoryVisibility(Utils::Id categoryId, bool visible);
|
void setCategoryVisibility(Utils::Id categoryId, bool visible);
|
||||||
void currentChanged(const QModelIndex &index);
|
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user