forked from qt-creator/qt-creator
ProjectExplorer: Redo the TaskView
- Make it a tree view. - Use the default delegate for top-level items. - If the task has details, show them in a child item. Fixes: QTCREATORBUG-28850 Change-Id: Iafd65496f916b9e382ef2c6ade0f9b3a323f63ed Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
@@ -8,8 +8,8 @@
|
|||||||
#include "projectexplorertr.h"
|
#include "projectexplorertr.h"
|
||||||
|
|
||||||
#include <app/app_version.h>
|
#include <app/app_version.h>
|
||||||
|
#include <texteditor/fontsettings.h>
|
||||||
#include <texteditor/textmark.h>
|
#include <texteditor/textmark.h>
|
||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
#include <utils/utilsicons.h>
|
#include <utils/utilsicons.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
@@ -105,11 +105,16 @@ void Task::setFile(const Utils::FilePath &file_)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Task::description() const
|
QString Task::description(DescriptionTags tags) const
|
||||||
{
|
{
|
||||||
QString desc = summary;
|
QString desc;
|
||||||
if (!details.isEmpty())
|
if (tags & WithSummary)
|
||||||
desc.append('\n').append(details.join('\n'));
|
desc = summary;
|
||||||
|
if (!details.isEmpty()) {
|
||||||
|
if (!desc.isEmpty())
|
||||||
|
desc.append('\n');
|
||||||
|
desc.append(details.join('\n'));
|
||||||
|
}
|
||||||
return desc;
|
return desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,32 +125,37 @@ QIcon Task::icon() const
|
|||||||
return m_icon;
|
return m_icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Task::toolTip(const QString &extraHeading) const
|
QString Task::formattedDescription(DescriptionTags tags, const QString &extraHeading) const
|
||||||
{
|
{
|
||||||
if (isNull())
|
if (isNull())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
QString text = description();
|
QString text = description(tags);
|
||||||
|
const int offset = (tags & WithSummary) ? 0 : summary.size() + 1;
|
||||||
static const QString linkTagStartPlaceholder("__QTC_LINK_TAG_START__");
|
static const QString linkTagStartPlaceholder("__QTC_LINK_TAG_START__");
|
||||||
static const QString linkTagEndPlaceholder("__QTC_LINK_TAG_END__");
|
static const QString linkTagEndPlaceholder("__QTC_LINK_TAG_END__");
|
||||||
static const QString linkEndPlaceholder("__QTC_LINK_END__");
|
static const QString linkEndPlaceholder("__QTC_LINK_END__");
|
||||||
for (auto formatRange = formats.crbegin(); formatRange != formats.crend(); ++formatRange) {
|
if (tags & WithLinks) {
|
||||||
if (!formatRange->format.isAnchor())
|
for (auto formatRange = formats.crbegin(); formatRange != formats.crend(); ++formatRange) {
|
||||||
continue;
|
if (!formatRange->format.isAnchor())
|
||||||
text.insert(formatRange->start + formatRange->length, linkEndPlaceholder);
|
continue;
|
||||||
text.insert(formatRange->start, QString::fromLatin1("%1%2%3").arg(
|
text.insert(formatRange->start - offset + formatRange->length, linkEndPlaceholder);
|
||||||
|
text.insert(formatRange->start - offset, QString::fromLatin1("%1%2%3").arg(
|
||||||
linkTagStartPlaceholder, formatRange->format.anchorHref(), linkTagEndPlaceholder));
|
linkTagStartPlaceholder, formatRange->format.anchorHref(), linkTagEndPlaceholder));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
text = text.toHtmlEscaped();
|
text = text.toHtmlEscaped();
|
||||||
text.replace(linkEndPlaceholder, "</a>");
|
if (tags & WithLinks) {
|
||||||
text.replace(linkTagStartPlaceholder, "<a href=\"");
|
text.replace(linkEndPlaceholder, "</a>");
|
||||||
text.replace(linkTagEndPlaceholder, "\">");
|
text.replace(linkTagStartPlaceholder, "<a href=\"");
|
||||||
|
text.replace(linkTagEndPlaceholder, "\">");
|
||||||
|
}
|
||||||
const QString htmlExtraHeading = extraHeading.isEmpty()
|
const QString htmlExtraHeading = extraHeading.isEmpty()
|
||||||
? QString()
|
? QString()
|
||||||
: QString::fromUtf8("<b>%1</b><br/>").arg(extraHeading);
|
: QString::fromUtf8("<b>%1</b><br/>").arg(extraHeading);
|
||||||
return QString::fromUtf8("<html><body>%1<code style=\"white-space:pre;font-family:monospace\">"
|
return QString::fromUtf8("<html><body>%1<code style=\"white-space:pre;font-family:%2\">"
|
||||||
"%2</code></body></html>")
|
"%3</code></body></html>")
|
||||||
.arg(htmlExtraHeading, text);
|
.arg(htmlExtraHeading, TextEditor::FontSettings::defaultFixedFontFamily(), text);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@@ -38,6 +38,9 @@ public:
|
|||||||
};
|
};
|
||||||
using Options = char;
|
using Options = char;
|
||||||
|
|
||||||
|
enum DescriptionTag { WithSummary = 1, WithLinks = 2 };
|
||||||
|
using DescriptionTags = QFlags<DescriptionTag>;
|
||||||
|
|
||||||
Task() = default;
|
Task() = default;
|
||||||
Task(TaskType type, const QString &description,
|
Task(TaskType type, const QString &description,
|
||||||
const Utils::FilePath &file, int line, Utils::Id category,
|
const Utils::FilePath &file, int line, Utils::Id category,
|
||||||
@@ -49,9 +52,9 @@ public:
|
|||||||
bool isNull() const;
|
bool isNull() const;
|
||||||
void clear();
|
void clear();
|
||||||
void setFile(const Utils::FilePath &file);
|
void setFile(const Utils::FilePath &file);
|
||||||
QString description() const;
|
QString description(DescriptionTags tags = WithSummary) const;
|
||||||
QIcon icon() const;
|
QIcon icon() const;
|
||||||
QString toolTip(const QString &extraHeading = {}) const;
|
QString formattedDescription(DescriptionTags tags, const QString &extraHeading = {}) const;
|
||||||
|
|
||||||
friend PROJECTEXPLORER_EXPORT bool operator==(const Task &t1, const Task &t2);
|
friend PROJECTEXPLORER_EXPORT bool operator==(const Task &t1, const Task &t2);
|
||||||
friend PROJECTEXPLORER_EXPORT bool operator<(const Task &a, const Task &b);
|
friend PROJECTEXPLORER_EXPORT bool operator<(const Task &a, const Task &b);
|
||||||
|
@@ -51,8 +51,9 @@ public:
|
|||||||
: Tr::tr("Warning"));
|
: Tr::tr("Warning"));
|
||||||
setPriority(task.type == Task::Error ? TextEditor::TextMark::NormalPriority
|
setPriority(task.type == Task::Error ? TextEditor::TextMark::NormalPriority
|
||||||
: TextEditor::TextMark::LowPriority);
|
: TextEditor::TextMark::LowPriority);
|
||||||
setToolTip(task.toolTip(task.category == Constants::TASK_CATEGORY_COMPILE
|
setToolTip(task.formattedDescription({Task::WithSummary | Task::WithLinks},
|
||||||
? Tr::tr("Build Issue") : QString()));
|
task.category == Constants::TASK_CATEGORY_COMPILE
|
||||||
|
? Tr::tr("Build Issue") : QString()));
|
||||||
setIcon(task.icon());
|
setIcon(task.icon());
|
||||||
setVisible(!task.icon().isNull());
|
setVisible(!task.icon().isNull());
|
||||||
}
|
}
|
||||||
|
@@ -210,58 +210,80 @@ void TaskModel::clearTasks(Utils::Id categoryId)
|
|||||||
QModelIndex TaskModel::index(int row, int column, const QModelIndex &parent) const
|
QModelIndex TaskModel::index(int row, int column, const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
if (parent.isValid())
|
if (parent.isValid())
|
||||||
return QModelIndex();
|
return createIndex(row, column, quintptr(parent.row() + 1));
|
||||||
return createIndex(row, column);
|
return createIndex(row, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex TaskModel::parent(const QModelIndex &child) const
|
QModelIndex TaskModel::parent(const QModelIndex &child) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(child)
|
if (child.internalId())
|
||||||
return QModelIndex();
|
return index(child.internalId() - 1, 0);
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int TaskModel::rowCount(const QModelIndex &parent) const
|
int TaskModel::rowCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
return parent.isValid() ? 0 : m_tasks.count();
|
if (!parent.isValid())
|
||||||
|
return m_tasks.count();
|
||||||
|
if (parent.column() != 0)
|
||||||
|
return 0;
|
||||||
|
return task(parent).details.isEmpty() ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TaskModel::columnCount(const QModelIndex &parent) const
|
int TaskModel::columnCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
return parent.isValid() ? 0 : 1;
|
return parent.isValid() ? 1 : 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant TaskModel::data(const QModelIndex &index, int role) const
|
QVariant TaskModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
int row = index.row();
|
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent())
|
||||||
if (!index.isValid() || row < 0 || row >= m_tasks.count() || index.column() != 0)
|
|| index.column() >= columnCount(index.parent())) {
|
||||||
return QVariant();
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
if (role == TaskModel::File)
|
if (index.internalId()) {
|
||||||
return m_tasks.at(index.row()).file.toString();
|
const Task &task = m_tasks.at(index.internalId() - 1);
|
||||||
else if (role == TaskModel::Line)
|
if (role != Qt::DisplayRole)
|
||||||
return m_tasks.at(index.row()).line;
|
return {};
|
||||||
else if (role == TaskModel::MovedLine)
|
return task.formattedDescription(Task::WithLinks);
|
||||||
return m_tasks.at(index.row()).movedLine;
|
}
|
||||||
else if (role == TaskModel::Description)
|
|
||||||
return m_tasks.at(index.row()).description();
|
static const auto lineString = [](const Task &task) {
|
||||||
else if (role == TaskModel::FileNotFound)
|
QString file = task.file.fileName();
|
||||||
return m_fileNotFound.value(m_tasks.at(index.row()).file.toString());
|
const int line = task.movedLine > 0 ? task.movedLine : task.line;
|
||||||
else if (role == TaskModel::Type)
|
if (line > 0)
|
||||||
return (int)m_tasks.at(index.row()).type;
|
file.append(':').append(QString::number(line));
|
||||||
else if (role == TaskModel::Category)
|
return file;
|
||||||
return m_tasks.at(index.row()).category.uniqueIdentifier();
|
};
|
||||||
else if (role == TaskModel::Icon)
|
|
||||||
return m_tasks.at(index.row()).icon();
|
const Task &task = m_tasks.at(index.row());
|
||||||
else if (role == TaskModel::Task_t)
|
if (index.column() == 1) {
|
||||||
return QVariant::fromValue(task(index));
|
if (role == Qt::DisplayRole)
|
||||||
return QVariant();
|
return lineString(task);
|
||||||
|
if (role == Qt::ToolTipRole)
|
||||||
|
return task.file.toUserOutput();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DecorationRole:
|
||||||
|
return task.icon();
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
return task.summary;
|
||||||
|
case TaskModel::Description:
|
||||||
|
return task.description();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Task TaskModel::task(const QModelIndex &index) const
|
Task TaskModel::task(const QModelIndex &index) const
|
||||||
{
|
{
|
||||||
int row = index.row();
|
int row = index.row();
|
||||||
if (!index.isValid() || row < 0 || row >= m_tasks.count())
|
if (!index.isValid() || row < 0 || row >= m_tasks.count() || index.internalId()
|
||||||
|
|| index.column() > 0) {
|
||||||
return Task();
|
return Task();
|
||||||
|
}
|
||||||
return m_tasks.at(row);
|
return m_tasks.at(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -44,7 +44,7 @@ public:
|
|||||||
int sizeOfLineNumber(const QFont &font);
|
int sizeOfLineNumber(const QFont &font);
|
||||||
void setFileNotFound(const QModelIndex &index, bool b);
|
void setFileNotFound(const QModelIndex &index, bool b);
|
||||||
|
|
||||||
enum Roles { File = Qt::UserRole, Line, MovedLine, Description, FileNotFound, Type, Category, Icon, Task_t };
|
enum Roles { Description = Qt::UserRole, };
|
||||||
|
|
||||||
int taskCount(Utils::Id categoryId);
|
int taskCount(Utils::Id categoryId);
|
||||||
int errorTaskCount(Utils::Id categoryId);
|
int errorTaskCount(Utils::Id categoryId);
|
||||||
|
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
#include <utils/fileinprojectfinder.h>
|
#include <utils/fileinprojectfinder.h>
|
||||||
|
#include <utils/hostosinfo.h>
|
||||||
#include <utils/itemviews.h>
|
#include <utils/itemviews.h>
|
||||||
#include <utils/outputformatter.h>
|
#include <utils/outputformatter.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
@@ -30,22 +31,22 @@
|
|||||||
#include <utils/tooltip/tooltip.h>
|
#include <utils/tooltip/tooltip.h>
|
||||||
#include <utils/utilsicons.h>
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
|
#include <QAbstractTextDocumentLayout>
|
||||||
|
#include <QApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
#include <QStyledItemDelegate>
|
#include <QStyledItemDelegate>
|
||||||
|
#include <QTextDocument>
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
namespace {
|
|
||||||
const int ELLIPSIS_GRADIENT_WIDTH = 16;
|
|
||||||
const char SESSION_FILTER_CATEGORIES[] = "TaskWindow.Categories";
|
const char SESSION_FILTER_CATEGORIES[] = "TaskWindow.Categories";
|
||||||
const char SESSION_FILTER_WARNINGS[] = "TaskWindow.IncludeWarnings";
|
const char SESSION_FILTER_WARNINGS[] = "TaskWindow.IncludeWarnings";
|
||||||
}
|
|
||||||
|
|
||||||
namespace ProjectExplorer {
|
namespace ProjectExplorer {
|
||||||
|
|
||||||
@@ -87,175 +88,43 @@ bool ITaskHandler::canHandle(const Tasks &tasks) const
|
|||||||
|
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
class TaskView : public ListView
|
class TaskView : public TreeView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TaskView(QWidget *parent = nullptr);
|
TaskView() { setMouseTracking(true); }
|
||||||
~TaskView() override;
|
void resizeColumns();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
void keyReleaseEvent(QKeyEvent *e) override;
|
void keyReleaseEvent(QKeyEvent *e) override;
|
||||||
bool event(QEvent *e) override;
|
bool event(QEvent *e) override;
|
||||||
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
|
QString anchorAt(const QPoint &pos);
|
||||||
void showToolTip(const Task &task, const QPoint &pos);
|
void showToolTip(const Task &task, const QPoint &pos);
|
||||||
|
|
||||||
|
QString m_hoverAnchor;
|
||||||
|
QString m_clickAnchor;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TaskDelegate : public QStyledItemDelegate
|
class TaskDelegate : public QStyledItemDelegate
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
friend class TaskView; // for using Positions::minimumSize()
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TaskDelegate(QObject * parent = nullptr);
|
using QStyledItemDelegate::QStyledItemDelegate;
|
||||||
~TaskDelegate() override;
|
|
||||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
QTextDocument &doc() { return m_doc; }
|
||||||
|
private:
|
||||||
|
void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||||
|
const QModelIndex &index) const override;
|
||||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
|
||||||
// TaskView uses this method if the size of the taskview changes
|
bool needsSpecialHandling(const QModelIndex &index) const;
|
||||||
void emitSizeHintChanged(const QModelIndex &index);
|
|
||||||
|
|
||||||
private:
|
mutable QTextDocument m_doc;
|
||||||
void generateGradientPixmap(int width, int height, QColor color, bool selected) const;
|
|
||||||
|
|
||||||
mutable int m_cachedHeight = 0;
|
|
||||||
mutable QFont m_cachedFont;
|
|
||||||
|
|
||||||
/*
|
|
||||||
+------------------------------------------------------------------------------------------+
|
|
||||||
| TASKICONAREA TEXTAREA FILEAREA LINEAREA |
|
|
||||||
+------------------------------------------------------------------------------------------+
|
|
||||||
*/
|
|
||||||
class Positions
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Positions(const QStyleOptionViewItem &options, TaskModel *model) :
|
|
||||||
m_totalWidth(options.rect.width()),
|
|
||||||
m_maxFileLength(model->sizeOfFile(options.font)),
|
|
||||||
m_maxLineLength(model->sizeOfLineNumber(options.font)),
|
|
||||||
m_realFileLength(m_maxFileLength),
|
|
||||||
m_top(options.rect.top()),
|
|
||||||
m_bottom(options.rect.bottom())
|
|
||||||
{
|
|
||||||
int flexibleArea = lineAreaLeft() - textAreaLeft() - ITEM_SPACING;
|
|
||||||
if (m_maxFileLength > flexibleArea / 2)
|
|
||||||
m_realFileLength = flexibleArea / 2;
|
|
||||||
m_fontHeight = QFontMetrics(options.font).height();
|
|
||||||
}
|
|
||||||
|
|
||||||
int top() const { return m_top + ITEM_MARGIN; }
|
|
||||||
int left() const { return ITEM_MARGIN; }
|
|
||||||
int right() const { return m_totalWidth - ITEM_MARGIN; }
|
|
||||||
int bottom() const { return m_bottom; }
|
|
||||||
int firstLineHeight() const { return m_fontHeight + 1; }
|
|
||||||
static int minimumHeight() { return taskIconHeight() + 2 * ITEM_MARGIN; }
|
|
||||||
|
|
||||||
int taskIconLeft() const { return left(); }
|
|
||||||
static int taskIconWidth() { return TASK_ICON_SIZE; }
|
|
||||||
static int taskIconHeight() { return TASK_ICON_SIZE; }
|
|
||||||
int taskIconRight() const { return taskIconLeft() + taskIconWidth(); }
|
|
||||||
QRect taskIcon() const { return QRect(taskIconLeft(), top(), taskIconWidth(), taskIconHeight()); }
|
|
||||||
|
|
||||||
int textAreaLeft() const { return taskIconRight() + ITEM_SPACING; }
|
|
||||||
int textAreaWidth() const { return textAreaRight() - textAreaLeft(); }
|
|
||||||
int textAreaRight() const { return fileAreaLeft() - ITEM_SPACING; }
|
|
||||||
QRect textArea() const { return QRect(textAreaLeft(), top(), textAreaWidth(), firstLineHeight()); }
|
|
||||||
|
|
||||||
int fileAreaLeft() const { return fileAreaRight() - fileAreaWidth(); }
|
|
||||||
int fileAreaWidth() const { return m_realFileLength; }
|
|
||||||
int fileAreaRight() const { return lineAreaLeft() - ITEM_SPACING; }
|
|
||||||
QRect fileArea() const { return QRect(fileAreaLeft(), top(), fileAreaWidth(), firstLineHeight()); }
|
|
||||||
|
|
||||||
int lineAreaLeft() const { return lineAreaRight() - lineAreaWidth(); }
|
|
||||||
int lineAreaWidth() const { return m_maxLineLength; }
|
|
||||||
int lineAreaRight() const { return right(); }
|
|
||||||
QRect lineArea() const { return QRect(lineAreaLeft(), top(), lineAreaWidth(), firstLineHeight()); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
int m_totalWidth;
|
|
||||||
int m_maxFileLength;
|
|
||||||
int m_maxLineLength;
|
|
||||||
int m_realFileLength;
|
|
||||||
int m_top;
|
|
||||||
int m_bottom;
|
|
||||||
int m_fontHeight;
|
|
||||||
|
|
||||||
static const int TASK_ICON_SIZE = 16;
|
|
||||||
static const int ITEM_MARGIN = 2;
|
|
||||||
static const int ITEM_SPACING = 2 * ITEM_MARGIN;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TaskView::TaskView(QWidget *parent)
|
|
||||||
: ListView(parent)
|
|
||||||
{
|
|
||||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
||||||
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
|
||||||
setAutoScroll(false); // QTCREATORBUG-25101
|
|
||||||
setUniformItemSizes(true);
|
|
||||||
|
|
||||||
QFontMetrics fm(font());
|
|
||||||
int vStepSize = fm.height() + 3;
|
|
||||||
if (vStepSize < TaskDelegate::Positions::minimumHeight())
|
|
||||||
vStepSize = TaskDelegate::Positions::minimumHeight();
|
|
||||||
|
|
||||||
verticalScrollBar()->setSingleStep(vStepSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
TaskView::~TaskView() = default;
|
|
||||||
|
|
||||||
void TaskView::resizeEvent(QResizeEvent *e)
|
|
||||||
{
|
|
||||||
Q_UNUSED(e)
|
|
||||||
static_cast<TaskDelegate *>(itemDelegate())->emitSizeHintChanged(selectionModel()->currentIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskView::keyReleaseEvent(QKeyEvent *e)
|
|
||||||
{
|
|
||||||
ListView::keyReleaseEvent(e);
|
|
||||||
if (e->key() == Qt::Key_Space) {
|
|
||||||
const Task task = static_cast<TaskFilterModel *>(model())->task(currentIndex());
|
|
||||||
if (!task.isNull()) {
|
|
||||||
const QPoint toolTipPos = mapToGlobal(visualRect(currentIndex()).topLeft());
|
|
||||||
QMetaObject::invokeMethod(this, [this, task, toolTipPos] {
|
|
||||||
showToolTip(task, toolTipPos); }, Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TaskView::event(QEvent *e)
|
|
||||||
{
|
|
||||||
if (e->type() != QEvent::ToolTip)
|
|
||||||
return QListView::event(e);
|
|
||||||
|
|
||||||
const auto helpEvent = static_cast<QHelpEvent*>(e);
|
|
||||||
const Task task = static_cast<TaskFilterModel *>(model())->task(indexAt(helpEvent->pos()));
|
|
||||||
if (task.isNull())
|
|
||||||
return QListView::event(e);
|
|
||||||
showToolTip(task, helpEvent->globalPos());
|
|
||||||
e->accept();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TaskView::showToolTip(const Task &task, const QPoint &pos)
|
|
||||||
{
|
|
||||||
const QString toolTip = task.toolTip();
|
|
||||||
if (!toolTip.isEmpty()) {
|
|
||||||
const auto label = new QLabel(toolTip);
|
|
||||||
connect(label, &QLabel::linkActivated, [](const QString &link) {
|
|
||||||
Core::EditorManager::openEditorAt(OutputLineParser::parseLinkTarget(link), {},
|
|
||||||
Core::EditorManager::SwitchSplitIfAlreadyVisible);
|
|
||||||
});
|
|
||||||
const auto layout = new QVBoxLayout;
|
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
layout->addWidget(label);
|
|
||||||
ToolTip::show(pos, layout);
|
|
||||||
} else {
|
|
||||||
ToolTip::hideImmediately();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/////
|
/////
|
||||||
// TaskWindow
|
// TaskWindow
|
||||||
/////
|
/////
|
||||||
@@ -271,7 +140,7 @@ public:
|
|||||||
|
|
||||||
Internal::TaskModel *m_model;
|
Internal::TaskModel *m_model;
|
||||||
Internal::TaskFilterModel *m_filter;
|
Internal::TaskFilterModel *m_filter;
|
||||||
Internal::TaskView *m_listview;
|
TaskView m_treeView;
|
||||||
Core::IContext *m_taskWindowContext;
|
Core::IContext *m_taskWindowContext;
|
||||||
QMenu *m_contextMenu;
|
QMenu *m_contextMenu;
|
||||||
QMap<const QAction *, ITaskHandler *> m_actionToHandlerMap;
|
QMap<const QAction *, ITaskHandler *> m_actionToHandlerMap;
|
||||||
@@ -300,43 +169,46 @@ TaskWindow::TaskWindow() : d(std::make_unique<TaskWindowPrivate>())
|
|||||||
{
|
{
|
||||||
d->m_model = new Internal::TaskModel(this);
|
d->m_model = new Internal::TaskModel(this);
|
||||||
d->m_filter = new Internal::TaskFilterModel(d->m_model);
|
d->m_filter = new Internal::TaskFilterModel(d->m_model);
|
||||||
d->m_listview = new Internal::TaskView;
|
|
||||||
|
|
||||||
auto agg = new Aggregation::Aggregate;
|
auto agg = new Aggregation::Aggregate;
|
||||||
agg->add(d->m_listview);
|
agg->add(&d->m_treeView);
|
||||||
agg->add(new Core::ItemViewFind(d->m_listview, TaskModel::Description));
|
agg->add(new Core::ItemViewFind(&d->m_treeView, TaskModel::Description));
|
||||||
|
|
||||||
d->m_listview->setModel(d->m_filter);
|
d->m_treeView.setHeaderHidden(true);
|
||||||
d->m_listview->setFrameStyle(QFrame::NoFrame);
|
d->m_treeView.setExpandsOnDoubleClick(false);
|
||||||
d->m_listview->setWindowTitle(displayName());
|
d->m_treeView.setAlternatingRowColors(true);
|
||||||
d->m_listview->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
d->m_treeView.setTextElideMode(Qt::ElideMiddle);
|
||||||
auto *tld = new Internal::TaskDelegate(this);
|
d->m_treeView.setItemDelegate(new TaskDelegate(this));
|
||||||
d->m_listview->setItemDelegate(tld);
|
d->m_treeView.setModel(d->m_filter);
|
||||||
d->m_listview->setWindowIcon(Icons::WINDOW.icon());
|
d->m_treeView.setFrameStyle(QFrame::NoFrame);
|
||||||
d->m_listview->setContextMenuPolicy(Qt::ActionsContextMenu);
|
d->m_treeView.setWindowTitle(displayName());
|
||||||
d->m_listview->setAttribute(Qt::WA_MacShowFocusRect, false);
|
d->m_treeView.setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
d->m_treeView.setWindowIcon(Icons::WINDOW.icon());
|
||||||
|
d->m_treeView.setContextMenuPolicy(Qt::ActionsContextMenu);
|
||||||
|
d->m_treeView.setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||||
|
d->m_treeView.resizeColumns();
|
||||||
|
|
||||||
d->m_taskWindowContext = new Core::IContext(d->m_listview);
|
d->m_taskWindowContext = new Core::IContext(&d->m_treeView);
|
||||||
d->m_taskWindowContext->setWidget(d->m_listview);
|
d->m_taskWindowContext->setWidget(&d->m_treeView);
|
||||||
d->m_taskWindowContext->setContext(Core::Context(Core::Constants::C_PROBLEM_PANE));
|
d->m_taskWindowContext->setContext(Core::Context(Core::Constants::C_PROBLEM_PANE));
|
||||||
Core::ICore::addContextObject(d->m_taskWindowContext);
|
Core::ICore::addContextObject(d->m_taskWindowContext);
|
||||||
|
|
||||||
connect(d->m_listview->selectionModel(), &QItemSelectionModel::currentChanged,
|
connect(d->m_treeView.selectionModel(), &QItemSelectionModel::currentChanged,
|
||||||
this, [this](const QModelIndex &index) { d->m_listview->scrollTo(index); });
|
this, [this](const QModelIndex &index) { d->m_treeView.scrollTo(index); });
|
||||||
connect(d->m_listview, &QAbstractItemView::activated,
|
connect(&d->m_treeView, &QAbstractItemView::activated,
|
||||||
this, &TaskWindow::triggerDefaultHandler);
|
this, &TaskWindow::triggerDefaultHandler);
|
||||||
connect(d->m_listview->selectionModel(), &QItemSelectionModel::selectionChanged,
|
connect(d->m_treeView.selectionModel(), &QItemSelectionModel::selectionChanged,
|
||||||
this, [this] {
|
this, [this] {
|
||||||
const Tasks tasks = d->m_filter->tasks(d->m_listview->selectionModel()->selectedIndexes());
|
const Tasks tasks = d->m_filter->tasks(d->m_treeView.selectionModel()->selectedIndexes());
|
||||||
for (QAction * const action : std::as_const(d->m_actions)) {
|
for (QAction * const action : std::as_const(d->m_actions)) {
|
||||||
ITaskHandler * const h = d->handler(action);
|
ITaskHandler * const h = d->handler(action);
|
||||||
action->setEnabled(h && h->canHandle(tasks));
|
action->setEnabled(h && h->canHandle(tasks));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
d->m_contextMenu = new QMenu(d->m_listview);
|
d->m_contextMenu = new QMenu(&d->m_treeView);
|
||||||
|
|
||||||
d->m_listview->setContextMenuPolicy(Qt::ActionsContextMenu);
|
d->m_treeView.setContextMenuPolicy(Qt::ActionsContextMenu);
|
||||||
|
|
||||||
d->m_filterWarningsButton = createFilterButton(
|
d->m_filterWarningsButton = createFilterButton(
|
||||||
Utils::Icons::WARNING_TOOLBAR.icon(),
|
Utils::Icons::WARNING_TOOLBAR.icon(),
|
||||||
@@ -391,7 +263,6 @@ TaskWindow::TaskWindow() : d(std::make_unique<TaskWindowPrivate>())
|
|||||||
TaskWindow::~TaskWindow()
|
TaskWindow::~TaskWindow()
|
||||||
{
|
{
|
||||||
delete d->m_filterWarningsButton;
|
delete d->m_filterWarningsButton;
|
||||||
delete d->m_listview;
|
|
||||||
delete d->m_filter;
|
delete d->m_filter;
|
||||||
delete d->m_model;
|
delete d->m_model;
|
||||||
}
|
}
|
||||||
@@ -415,7 +286,7 @@ void TaskWindow::delayedInitialization()
|
|||||||
connect(action, &QAction::triggered, this, [this, action] {
|
connect(action, &QAction::triggered, this, [this, action] {
|
||||||
ITaskHandler *h = d->handler(action);
|
ITaskHandler *h = d->handler(action);
|
||||||
if (h)
|
if (h)
|
||||||
h->handle(d->m_filter->tasks(d->m_listview->selectionModel()->selectedIndexes()));
|
h->handle(d->m_filter->tasks(d->m_treeView.selectionModel()->selectedIndexes()));
|
||||||
});
|
});
|
||||||
d->m_actions << action;
|
d->m_actions << action;
|
||||||
|
|
||||||
@@ -425,7 +296,7 @@ void TaskWindow::delayedInitialization()
|
|||||||
Core::ActionManager::registerAction(action, id, d->m_taskWindowContext->context(), true);
|
Core::ActionManager::registerAction(action, id, d->m_taskWindowContext->context(), true);
|
||||||
action = cmd->action();
|
action = cmd->action();
|
||||||
}
|
}
|
||||||
d->m_listview->addAction(action);
|
d->m_treeView.addAction(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,7 +312,7 @@ QString TaskWindow::displayName() const
|
|||||||
|
|
||||||
QWidget *TaskWindow::outputWidget(QWidget *)
|
QWidget *TaskWindow::outputWidget(QWidget *)
|
||||||
{
|
{
|
||||||
return d->m_listview;
|
return &d->m_treeView;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TaskWindow::clearTasks(Id categoryId)
|
void TaskWindow::clearTasks(Id categoryId)
|
||||||
@@ -545,7 +416,7 @@ void TaskWindow::showTask(const Task &task)
|
|||||||
int sourceRow = d->m_model->rowForTask(task);
|
int sourceRow = d->m_model->rowForTask(task);
|
||||||
QModelIndex sourceIdx = d->m_model->index(sourceRow, 0);
|
QModelIndex sourceIdx = d->m_model->index(sourceRow, 0);
|
||||||
QModelIndex filterIdx = d->m_filter->mapFromSource(sourceIdx);
|
QModelIndex filterIdx = d->m_filter->mapFromSource(sourceIdx);
|
||||||
d->m_listview->setCurrentIndex(filterIdx);
|
d->m_treeView.setCurrentIndex(filterIdx);
|
||||||
popup(Core::IOutputPane::ModeSwitch);
|
popup(Core::IOutputPane::ModeSwitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,7 +433,10 @@ void TaskWindow::triggerDefaultHandler(const QModelIndex &index)
|
|||||||
if (!index.isValid() || !d->m_defaultHandler)
|
if (!index.isValid() || !d->m_defaultHandler)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Task task(d->m_filter->task(index));
|
QModelIndex taskIndex = index;
|
||||||
|
if (index.parent().isValid())
|
||||||
|
taskIndex = index.parent();
|
||||||
|
Task task(d->m_filter->task(taskIndex));
|
||||||
if (task.isNull())
|
if (task.isNull())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -579,7 +453,7 @@ void TaskWindow::triggerDefaultHandler(const QModelIndex &index)
|
|||||||
d->m_defaultHandler->handle(task);
|
d->m_defaultHandler->handle(task);
|
||||||
} else {
|
} else {
|
||||||
if (!task.file.exists())
|
if (!task.file.exists())
|
||||||
d->m_model->setFileNotFound(index, true);
|
d->m_model->setFileNotFound(taskIndex, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +519,7 @@ void TaskWindow::clearContents()
|
|||||||
|
|
||||||
bool TaskWindow::hasFocus() const
|
bool TaskWindow::hasFocus() const
|
||||||
{
|
{
|
||||||
return d->m_listview->window()->focusWidget() == d->m_listview;
|
return d->m_treeView.window()->focusWidget() == &d->m_treeView;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TaskWindow::canFocus() const
|
bool TaskWindow::canFocus() const
|
||||||
@@ -656,9 +530,13 @@ bool TaskWindow::canFocus() const
|
|||||||
void TaskWindow::setFocus()
|
void TaskWindow::setFocus()
|
||||||
{
|
{
|
||||||
if (d->m_filter->rowCount()) {
|
if (d->m_filter->rowCount()) {
|
||||||
d->m_listview->setFocus();
|
d->m_treeView.setFocus();
|
||||||
if (d->m_listview->currentIndex() == QModelIndex())
|
if (!d->m_treeView.currentIndex().isValid())
|
||||||
d->m_listview->setCurrentIndex(d->m_filter->index(0,0, QModelIndex()));
|
d->m_treeView.setCurrentIndex(d->m_filter->index(0,0, QModelIndex()));
|
||||||
|
if (d->m_treeView.selectionModel()->selection().isEmpty()) {
|
||||||
|
d->m_treeView.selectionModel()->setCurrentIndex(d->m_treeView.currentIndex(),
|
||||||
|
QItemSelectionModel::Select);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +554,7 @@ void TaskWindow::goToNext()
|
|||||||
{
|
{
|
||||||
if (!canNext())
|
if (!canNext())
|
||||||
return;
|
return;
|
||||||
QModelIndex startIndex = d->m_listview->currentIndex();
|
QModelIndex startIndex = d->m_treeView.currentIndex();
|
||||||
QModelIndex currentIndex = startIndex;
|
QModelIndex currentIndex = startIndex;
|
||||||
|
|
||||||
if (startIndex.isValid()) {
|
if (startIndex.isValid()) {
|
||||||
@@ -691,7 +569,7 @@ void TaskWindow::goToNext()
|
|||||||
} else {
|
} else {
|
||||||
currentIndex = d->m_filter->index(0, 0);
|
currentIndex = d->m_filter->index(0, 0);
|
||||||
}
|
}
|
||||||
d->m_listview->setCurrentIndex(currentIndex);
|
d->m_treeView.setCurrentIndex(currentIndex);
|
||||||
triggerDefaultHandler(currentIndex);
|
triggerDefaultHandler(currentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,7 +577,7 @@ void TaskWindow::goToPrev()
|
|||||||
{
|
{
|
||||||
if (!canPrevious())
|
if (!canPrevious())
|
||||||
return;
|
return;
|
||||||
QModelIndex startIndex = d->m_listview->currentIndex();
|
QModelIndex startIndex = d->m_treeView.currentIndex();
|
||||||
QModelIndex currentIndex = startIndex;
|
QModelIndex currentIndex = startIndex;
|
||||||
|
|
||||||
if (startIndex.isValid()) {
|
if (startIndex.isValid()) {
|
||||||
@@ -714,7 +592,7 @@ void TaskWindow::goToPrev()
|
|||||||
} else {
|
} else {
|
||||||
currentIndex = d->m_filter->index(0, 0);
|
currentIndex = d->m_filter->index(0, 0);
|
||||||
}
|
}
|
||||||
d->m_listview->setCurrentIndex(currentIndex);
|
d->m_treeView.setCurrentIndex(currentIndex);
|
||||||
triggerDefaultHandler(currentIndex);
|
triggerDefaultHandler(currentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -729,149 +607,161 @@ bool TaskWindow::canNavigate() const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/////
|
void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||||
// Delegate
|
const QModelIndex &index) const
|
||||||
/////
|
{
|
||||||
|
if (!needsSpecialHandling(index)) {
|
||||||
|
QStyledItemDelegate::paint(painter, option, index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TaskDelegate::TaskDelegate(QObject *parent) :
|
QStyleOptionViewItem options = option;
|
||||||
QStyledItemDelegate(parent)
|
initStyleOption(&options, index);
|
||||||
{ }
|
|
||||||
|
|
||||||
TaskDelegate::~TaskDelegate() = default;
|
painter->save();
|
||||||
|
m_doc.setHtml(options.text);
|
||||||
|
options.text = "";
|
||||||
|
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);
|
||||||
|
painter->translate(options.rect.left(), options.rect.top());
|
||||||
|
QRect clip(0, 0, options.rect.width(), options.rect.height());
|
||||||
|
QAbstractTextDocumentLayout::PaintContext paintContext;
|
||||||
|
paintContext.palette = options.palette;
|
||||||
|
painter->setClipRect(clip);
|
||||||
|
paintContext.clip = clip;
|
||||||
|
if (qobject_cast<const QAbstractItemView *>(options.widget)
|
||||||
|
->selectionModel()->isSelected(index)) {
|
||||||
|
QAbstractTextDocumentLayout::Selection selection;
|
||||||
|
selection.cursor = QTextCursor(&m_doc);
|
||||||
|
selection.cursor.select(QTextCursor::Document);
|
||||||
|
selection.format.setBackground(options.palette.brush(QPalette::Highlight));
|
||||||
|
selection.format.setForeground(options.palette.brush(QPalette::HighlightedText));
|
||||||
|
paintContext.selections << selection;
|
||||||
|
}
|
||||||
|
m_doc.documentLayout()->draw(painter, paintContext);
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||||
{
|
{
|
||||||
QStyleOptionViewItem opt = option;
|
if (!needsSpecialHandling(index))
|
||||||
initStyleOption(&opt, index);
|
return QStyledItemDelegate::sizeHint(option, index);
|
||||||
|
|
||||||
QSize s;
|
QStyleOptionViewItem options = option;
|
||||||
s.setWidth(option.rect.width());
|
initStyleOption(&options, index);
|
||||||
|
m_doc.setHtml(options.text);
|
||||||
if (option.font == m_cachedFont && m_cachedHeight > 0) {
|
m_doc.setTextWidth(options.rect.width());
|
||||||
s.setHeight(m_cachedHeight);
|
return QSize(m_doc.idealWidth(), m_doc.size().height());
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
s.setHeight(option.fontMetrics.height() + 3);
|
|
||||||
if (s.height() < Positions::minimumHeight())
|
|
||||||
s.setHeight(Positions::minimumHeight());
|
|
||||||
m_cachedHeight = s.height();
|
|
||||||
m_cachedFont = option.font;
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TaskDelegate::emitSizeHintChanged(const QModelIndex &index)
|
bool TaskDelegate::needsSpecialHandling(const QModelIndex &index) const
|
||||||
{
|
{
|
||||||
emit sizeHintChanged(index);
|
QModelIndex sourceIndex = index;
|
||||||
|
if (const auto proxyModel = qobject_cast<const QAbstractProxyModel *>(index.model()))
|
||||||
|
sourceIndex = proxyModel->mapToSource(index);
|
||||||
|
return sourceIndex.internalId();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
void TaskView::resizeColumns()
|
||||||
{
|
{
|
||||||
QStyleOptionViewItem opt = option;
|
setColumnWidth(0, width() * 0.85);
|
||||||
initStyleOption(&opt, index);
|
setColumnWidth(1, width() * 0.15);
|
||||||
painter->save();
|
}
|
||||||
|
|
||||||
QFontMetrics fm(opt.font);
|
void TaskView::resizeEvent(QResizeEvent *e)
|
||||||
QColor backgroundColor;
|
{
|
||||||
QColor textColor;
|
TreeView::resizeEvent(e);
|
||||||
|
resizeColumns();
|
||||||
|
}
|
||||||
|
|
||||||
auto view = qobject_cast<const QAbstractItemView *>(opt.widget);
|
void TaskView::mousePressEvent(QMouseEvent *e)
|
||||||
const bool selected = view->selectionModel()->isSelected(index);
|
{
|
||||||
|
m_clickAnchor = anchorAt(e->pos());
|
||||||
|
if (m_clickAnchor.isEmpty())
|
||||||
|
TreeView::mousePressEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
if (selected) {
|
void TaskView::mouseMoveEvent(QMouseEvent *e)
|
||||||
painter->setBrush(opt.palette.highlight().color());
|
{
|
||||||
backgroundColor = opt.palette.highlight().color();
|
const QString anchor = anchorAt(e->pos());
|
||||||
} else {
|
if (m_clickAnchor != anchor)
|
||||||
painter->setBrush(opt.palette.window().color());
|
m_clickAnchor.clear();
|
||||||
backgroundColor = opt.palette.window().color();
|
if (m_hoverAnchor != anchor) {
|
||||||
|
m_hoverAnchor = anchor;
|
||||||
|
if (!m_hoverAnchor.isEmpty())
|
||||||
|
QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
|
||||||
|
else
|
||||||
|
QApplication::restoreOverrideCursor();
|
||||||
}
|
}
|
||||||
painter->setPen(Qt::NoPen);
|
}
|
||||||
painter->drawRect(opt.rect);
|
|
||||||
|
|
||||||
// Set Text Color
|
void TaskView::mouseReleaseEvent(QMouseEvent *e)
|
||||||
if (selected)
|
{
|
||||||
textColor = opt.palette.highlightedText().color();
|
if (m_clickAnchor.isEmpty()) {
|
||||||
else
|
TreeView::mouseReleaseEvent(e);
|
||||||
textColor = opt.palette.text().color();
|
return;
|
||||||
|
|
||||||
painter->setPen(textColor);
|
|
||||||
|
|
||||||
auto model = static_cast<TaskFilterModel *>(view->model())->taskModel();
|
|
||||||
Positions positions(opt, model);
|
|
||||||
|
|
||||||
// Paint TaskIconArea:
|
|
||||||
QIcon icon = index.data(TaskModel::Icon).value<QIcon>();
|
|
||||||
painter->drawPixmap(positions.left(), positions.top(),
|
|
||||||
icon.pixmap(Positions::taskIconWidth(), Positions::taskIconHeight()));
|
|
||||||
|
|
||||||
// Paint TextArea:
|
|
||||||
QString bottom = index.data(TaskModel::Description).toString().split(QLatin1Char('\n')).first();
|
|
||||||
painter->setClipRect(positions.textArea());
|
|
||||||
painter->drawText(positions.textAreaLeft(), positions.top() + fm.ascent(), bottom);
|
|
||||||
if (fm.horizontalAdvance(bottom) > positions.textAreaWidth()) {
|
|
||||||
// draw a gradient to mask the text
|
|
||||||
int gradientStart = positions.textAreaRight() - ELLIPSIS_GRADIENT_WIDTH + 1;
|
|
||||||
QLinearGradient lg(gradientStart, 0, gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0);
|
|
||||||
lg.setColorAt(0, Qt::transparent);
|
|
||||||
lg.setColorAt(1, backgroundColor);
|
|
||||||
painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint FileArea
|
const QString anchor = anchorAt(e->pos());
|
||||||
QString file = index.data(TaskModel::File).toString();
|
if (anchor == m_clickAnchor) {
|
||||||
const int pos = file.lastIndexOf(QLatin1Char('/'));
|
Core::EditorManager::openEditorAt(OutputLineParser::parseLinkTarget(m_clickAnchor), {},
|
||||||
if (pos != -1)
|
Core::EditorManager::SwitchSplitIfAlreadyVisible);
|
||||||
file = file.mid(pos +1);
|
}
|
||||||
const int realFileWidth = fm.horizontalAdvance(file);
|
m_clickAnchor.clear();
|
||||||
painter->setClipRect(positions.fileArea());
|
}
|
||||||
painter->drawText(qMin(positions.fileAreaLeft(), positions.fileAreaRight() - realFileWidth),
|
|
||||||
positions.top() + fm.ascent(), file);
|
void TaskView::keyReleaseEvent(QKeyEvent *e)
|
||||||
if (realFileWidth > positions.fileAreaWidth()) {
|
{
|
||||||
// draw a gradient to mask the text
|
TreeView::keyReleaseEvent(e);
|
||||||
int gradientStart = positions.fileAreaLeft() - 1;
|
if (e->key() == Qt::Key_Space) {
|
||||||
QLinearGradient lg(gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0, gradientStart, 0);
|
const Task task = static_cast<TaskFilterModel *>(model())->task(currentIndex());
|
||||||
lg.setColorAt(0, Qt::transparent);
|
if (!task.isNull()) {
|
||||||
lg.setColorAt(1, backgroundColor);
|
const QPoint toolTipPos = mapToGlobal(visualRect(currentIndex()).topLeft());
|
||||||
painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg);
|
QMetaObject::invokeMethod(this, [this, task, toolTipPos] {
|
||||||
|
showToolTip(task, toolTipPos); }, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TaskView::event(QEvent *e)
|
||||||
|
{
|
||||||
|
if (e->type() != QEvent::ToolTip)
|
||||||
|
return TreeView::event(e);
|
||||||
|
|
||||||
|
const auto helpEvent = static_cast<QHelpEvent*>(e);
|
||||||
|
const Task task = static_cast<TaskFilterModel *>(model())->task(indexAt(helpEvent->pos()));
|
||||||
|
if (task.isNull())
|
||||||
|
return TreeView::event(e);
|
||||||
|
showToolTip(task, helpEvent->globalPos());
|
||||||
|
e->accept();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TaskView::showToolTip(const Task &task, const QPoint &pos)
|
||||||
|
{
|
||||||
|
if (task.details.isEmpty()) {
|
||||||
|
ToolTip::hideImmediately();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint LineArea
|
const auto layout = new QVBoxLayout;
|
||||||
int line = index.data(TaskModel::Line).toInt();
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
int movedLine = index.data(TaskModel::MovedLine).toInt();
|
layout->addWidget(new QLabel(task.formattedDescription({})));
|
||||||
QString lineText;
|
ToolTip::show(pos, layout);
|
||||||
|
}
|
||||||
|
|
||||||
if (line == -1) {
|
QString TaskView::anchorAt(const QPoint &pos)
|
||||||
// No line information at all
|
{
|
||||||
} else if (movedLine == -1) {
|
const QModelIndex index = indexAt(pos);
|
||||||
// removed the line, but we had line information, show the line in ()
|
if (!index.isValid() || !index.internalId())
|
||||||
QFont f = painter->font();
|
return {};
|
||||||
f.setItalic(true);
|
|
||||||
painter->setFont(f);
|
|
||||||
lineText = QLatin1Char('(') + QString::number(line) + QLatin1Char(')');
|
|
||||||
} else if (movedLine != line) {
|
|
||||||
// The line was moved
|
|
||||||
QFont f = painter->font();
|
|
||||||
f.setItalic(true);
|
|
||||||
painter->setFont(f);
|
|
||||||
lineText = QString::number(movedLine);
|
|
||||||
} else {
|
|
||||||
lineText = QString::number(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
painter->setClipRect(positions.lineArea());
|
const QRect itemRect = visualRect(index);
|
||||||
const int realLineWidth = fm.horizontalAdvance(lineText);
|
QTextDocument &doc = static_cast<TaskDelegate *>(itemDelegate())->doc();
|
||||||
painter->drawText(positions.lineAreaRight() - realLineWidth, positions.top() + fm.ascent(), lineText);
|
doc.setHtml(model()->data(index, Qt::DisplayRole).toString());
|
||||||
painter->setClipRect(opt.rect);
|
const QAbstractTextDocumentLayout * const textLayout = doc.documentLayout();
|
||||||
|
QTC_ASSERT(textLayout, return {});
|
||||||
// Separator lines
|
return textLayout->anchorAt(pos - itemRect.topLeft());
|
||||||
painter->setPen(QColor::fromRgb(150,150,150));
|
|
||||||
const QRectF borderRect = QRectF(opt.rect).adjusted(0.5, 0.5, -0.5, -0.5);
|
|
||||||
painter->drawLine(borderRect.bottomLeft(), borderRect.bottomRight());
|
|
||||||
painter->restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace ProjectExplorer
|
} // namespace ProjectExplorer
|
||||||
|
|
||||||
#include "taskwindow.moc"
|
|
||||||
|
Reference in New Issue
Block a user