|
|
|
@@ -22,6 +22,7 @@
|
|
|
|
|
|
|
|
|
|
#include <utils/algorithm.h>
|
|
|
|
|
#include <utils/fileinprojectfinder.h>
|
|
|
|
|
#include <utils/hostosinfo.h>
|
|
|
|
|
#include <utils/itemviews.h>
|
|
|
|
|
#include <utils/outputformatter.h>
|
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
@@ -30,22 +31,22 @@
|
|
|
|
|
#include <utils/tooltip/tooltip.h>
|
|
|
|
|
#include <utils/utilsicons.h>
|
|
|
|
|
|
|
|
|
|
#include <QAbstractTextDocumentLayout>
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QLabel>
|
|
|
|
|
#include <QMenu>
|
|
|
|
|
#include <QPainter>
|
|
|
|
|
#include <QScrollBar>
|
|
|
|
|
#include <QStyledItemDelegate>
|
|
|
|
|
#include <QTextDocument>
|
|
|
|
|
#include <QToolButton>
|
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
const int ELLIPSIS_GRADIENT_WIDTH = 16;
|
|
|
|
|
const char SESSION_FILTER_CATEGORIES[] = "TaskWindow.Categories";
|
|
|
|
|
const char SESSION_FILTER_WARNINGS[] = "TaskWindow.IncludeWarnings";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace ProjectExplorer {
|
|
|
|
|
|
|
|
|
@@ -87,175 +88,43 @@ bool ITaskHandler::canHandle(const Tasks &tasks) const
|
|
|
|
|
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
|
|
class TaskView : public ListView
|
|
|
|
|
class TaskView : public TreeView
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
TaskView(QWidget *parent = nullptr);
|
|
|
|
|
~TaskView() override;
|
|
|
|
|
TaskView() { setMouseTracking(true); }
|
|
|
|
|
void resizeColumns();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void resizeEvent(QResizeEvent *e) override;
|
|
|
|
|
void keyReleaseEvent(QKeyEvent *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);
|
|
|
|
|
|
|
|
|
|
QString m_hoverAnchor;
|
|
|
|
|
QString m_clickAnchor;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class TaskDelegate : public QStyledItemDelegate
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
friend class TaskView; // for using Positions::minimumSize()
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
TaskDelegate(QObject * parent = nullptr);
|
|
|
|
|
~TaskDelegate() override;
|
|
|
|
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
|
|
|
|
using QStyledItemDelegate::QStyledItemDelegate;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// TaskView uses this method if the size of the taskview changes
|
|
|
|
|
void emitSizeHintChanged(const QModelIndex &index);
|
|
|
|
|
bool needsSpecialHandling(const QModelIndex &index) const;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
mutable QTextDocument m_doc;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
/////
|
|
|
|
@@ -271,7 +140,7 @@ public:
|
|
|
|
|
|
|
|
|
|
Internal::TaskModel *m_model;
|
|
|
|
|
Internal::TaskFilterModel *m_filter;
|
|
|
|
|
Internal::TaskView *m_listview;
|
|
|
|
|
TaskView m_treeView;
|
|
|
|
|
Core::IContext *m_taskWindowContext;
|
|
|
|
|
QMenu *m_contextMenu;
|
|
|
|
|
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_filter = new Internal::TaskFilterModel(d->m_model);
|
|
|
|
|
d->m_listview = new Internal::TaskView;
|
|
|
|
|
|
|
|
|
|
auto agg = new Aggregation::Aggregate;
|
|
|
|
|
agg->add(d->m_listview);
|
|
|
|
|
agg->add(new Core::ItemViewFind(d->m_listview, TaskModel::Description));
|
|
|
|
|
agg->add(&d->m_treeView);
|
|
|
|
|
agg->add(new Core::ItemViewFind(&d->m_treeView, TaskModel::Description));
|
|
|
|
|
|
|
|
|
|
d->m_listview->setModel(d->m_filter);
|
|
|
|
|
d->m_listview->setFrameStyle(QFrame::NoFrame);
|
|
|
|
|
d->m_listview->setWindowTitle(displayName());
|
|
|
|
|
d->m_listview->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
|
|
|
auto *tld = new Internal::TaskDelegate(this);
|
|
|
|
|
d->m_listview->setItemDelegate(tld);
|
|
|
|
|
d->m_listview->setWindowIcon(Icons::WINDOW.icon());
|
|
|
|
|
d->m_listview->setContextMenuPolicy(Qt::ActionsContextMenu);
|
|
|
|
|
d->m_listview->setAttribute(Qt::WA_MacShowFocusRect, false);
|
|
|
|
|
d->m_treeView.setHeaderHidden(true);
|
|
|
|
|
d->m_treeView.setExpandsOnDoubleClick(false);
|
|
|
|
|
d->m_treeView.setAlternatingRowColors(true);
|
|
|
|
|
d->m_treeView.setTextElideMode(Qt::ElideMiddle);
|
|
|
|
|
d->m_treeView.setItemDelegate(new TaskDelegate(this));
|
|
|
|
|
d->m_treeView.setModel(d->m_filter);
|
|
|
|
|
d->m_treeView.setFrameStyle(QFrame::NoFrame);
|
|
|
|
|
d->m_treeView.setWindowTitle(displayName());
|
|
|
|
|
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->setWidget(d->m_listview);
|
|
|
|
|
d->m_taskWindowContext = new Core::IContext(&d->m_treeView);
|
|
|
|
|
d->m_taskWindowContext->setWidget(&d->m_treeView);
|
|
|
|
|
d->m_taskWindowContext->setContext(Core::Context(Core::Constants::C_PROBLEM_PANE));
|
|
|
|
|
Core::ICore::addContextObject(d->m_taskWindowContext);
|
|
|
|
|
|
|
|
|
|
connect(d->m_listview->selectionModel(), &QItemSelectionModel::currentChanged,
|
|
|
|
|
this, [this](const QModelIndex &index) { d->m_listview->scrollTo(index); });
|
|
|
|
|
connect(d->m_listview, &QAbstractItemView::activated,
|
|
|
|
|
connect(d->m_treeView.selectionModel(), &QItemSelectionModel::currentChanged,
|
|
|
|
|
this, [this](const QModelIndex &index) { d->m_treeView.scrollTo(index); });
|
|
|
|
|
connect(&d->m_treeView, &QAbstractItemView::activated,
|
|
|
|
|
this, &TaskWindow::triggerDefaultHandler);
|
|
|
|
|
connect(d->m_listview->selectionModel(), &QItemSelectionModel::selectionChanged,
|
|
|
|
|
connect(d->m_treeView.selectionModel(), &QItemSelectionModel::selectionChanged,
|
|
|
|
|
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)) {
|
|
|
|
|
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_treeView);
|
|
|
|
|
|
|
|
|
|
d->m_listview->setContextMenuPolicy(Qt::ActionsContextMenu);
|
|
|
|
|
d->m_treeView.setContextMenuPolicy(Qt::ActionsContextMenu);
|
|
|
|
|
|
|
|
|
|
d->m_filterWarningsButton = createFilterButton(
|
|
|
|
|
Utils::Icons::WARNING_TOOLBAR.icon(),
|
|
|
|
@@ -391,7 +263,6 @@ TaskWindow::TaskWindow() : d(std::make_unique<TaskWindowPrivate>())
|
|
|
|
|
TaskWindow::~TaskWindow()
|
|
|
|
|
{
|
|
|
|
|
delete d->m_filterWarningsButton;
|
|
|
|
|
delete d->m_listview;
|
|
|
|
|
delete d->m_filter;
|
|
|
|
|
delete d->m_model;
|
|
|
|
|
}
|
|
|
|
@@ -415,7 +286,7 @@ void TaskWindow::delayedInitialization()
|
|
|
|
|
connect(action, &QAction::triggered, this, [this, action] {
|
|
|
|
|
ITaskHandler *h = d->handler(action);
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
@@ -425,7 +296,7 @@ void TaskWindow::delayedInitialization()
|
|
|
|
|
Core::ActionManager::registerAction(action, id, d->m_taskWindowContext->context(), true);
|
|
|
|
|
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 *)
|
|
|
|
|
{
|
|
|
|
|
return d->m_listview;
|
|
|
|
|
return &d->m_treeView;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskWindow::clearTasks(Id categoryId)
|
|
|
|
@@ -545,7 +416,7 @@ void TaskWindow::showTask(const Task &task)
|
|
|
|
|
int sourceRow = d->m_model->rowForTask(task);
|
|
|
|
|
QModelIndex sourceIdx = d->m_model->index(sourceRow, 0);
|
|
|
|
|
QModelIndex filterIdx = d->m_filter->mapFromSource(sourceIdx);
|
|
|
|
|
d->m_listview->setCurrentIndex(filterIdx);
|
|
|
|
|
d->m_treeView.setCurrentIndex(filterIdx);
|
|
|
|
|
popup(Core::IOutputPane::ModeSwitch);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -562,7 +433,10 @@ void TaskWindow::triggerDefaultHandler(const QModelIndex &index)
|
|
|
|
|
if (!index.isValid() || !d->m_defaultHandler)
|
|
|
|
|
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())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
@@ -579,7 +453,7 @@ void TaskWindow::triggerDefaultHandler(const QModelIndex &index)
|
|
|
|
|
d->m_defaultHandler->handle(task);
|
|
|
|
|
} else {
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
return d->m_listview->window()->focusWidget() == d->m_listview;
|
|
|
|
|
return d->m_treeView.window()->focusWidget() == &d->m_treeView;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TaskWindow::canFocus() const
|
|
|
|
@@ -656,9 +530,13 @@ bool TaskWindow::canFocus() const
|
|
|
|
|
void TaskWindow::setFocus()
|
|
|
|
|
{
|
|
|
|
|
if (d->m_filter->rowCount()) {
|
|
|
|
|
d->m_listview->setFocus();
|
|
|
|
|
if (d->m_listview->currentIndex() == QModelIndex())
|
|
|
|
|
d->m_listview->setCurrentIndex(d->m_filter->index(0,0, QModelIndex()));
|
|
|
|
|
d->m_treeView.setFocus();
|
|
|
|
|
if (!d->m_treeView.currentIndex().isValid())
|
|
|
|
|
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())
|
|
|
|
|
return;
|
|
|
|
|
QModelIndex startIndex = d->m_listview->currentIndex();
|
|
|
|
|
QModelIndex startIndex = d->m_treeView.currentIndex();
|
|
|
|
|
QModelIndex currentIndex = startIndex;
|
|
|
|
|
|
|
|
|
|
if (startIndex.isValid()) {
|
|
|
|
@@ -691,7 +569,7 @@ void TaskWindow::goToNext()
|
|
|
|
|
} else {
|
|
|
|
|
currentIndex = d->m_filter->index(0, 0);
|
|
|
|
|
}
|
|
|
|
|
d->m_listview->setCurrentIndex(currentIndex);
|
|
|
|
|
d->m_treeView.setCurrentIndex(currentIndex);
|
|
|
|
|
triggerDefaultHandler(currentIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -699,7 +577,7 @@ void TaskWindow::goToPrev()
|
|
|
|
|
{
|
|
|
|
|
if (!canPrevious())
|
|
|
|
|
return;
|
|
|
|
|
QModelIndex startIndex = d->m_listview->currentIndex();
|
|
|
|
|
QModelIndex startIndex = d->m_treeView.currentIndex();
|
|
|
|
|
QModelIndex currentIndex = startIndex;
|
|
|
|
|
|
|
|
|
|
if (startIndex.isValid()) {
|
|
|
|
@@ -714,7 +592,7 @@ void TaskWindow::goToPrev()
|
|
|
|
|
} else {
|
|
|
|
|
currentIndex = d->m_filter->index(0, 0);
|
|
|
|
|
}
|
|
|
|
|
d->m_listview->setCurrentIndex(currentIndex);
|
|
|
|
|
d->m_treeView.setCurrentIndex(currentIndex);
|
|
|
|
|
triggerDefaultHandler(currentIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -729,149 +607,161 @@ bool TaskWindow::canNavigate() const
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/////
|
|
|
|
|
// Delegate
|
|
|
|
|
/////
|
|
|
|
|
void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|
|
|
|
const QModelIndex &index) const
|
|
|
|
|
{
|
|
|
|
|
if (!needsSpecialHandling(index)) {
|
|
|
|
|
QStyledItemDelegate::paint(painter, option, index);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TaskDelegate::TaskDelegate(QObject *parent) :
|
|
|
|
|
QStyledItemDelegate(parent)
|
|
|
|
|
{ }
|
|
|
|
|
QStyleOptionViewItem options = option;
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
QStyleOptionViewItem opt = option;
|
|
|
|
|
initStyleOption(&opt, index);
|
|
|
|
|
if (!needsSpecialHandling(index))
|
|
|
|
|
return QStyledItemDelegate::sizeHint(option, index);
|
|
|
|
|
|
|
|
|
|
QSize s;
|
|
|
|
|
s.setWidth(option.rect.width());
|
|
|
|
|
|
|
|
|
|
if (option.font == m_cachedFont && m_cachedHeight > 0) {
|
|
|
|
|
s.setHeight(m_cachedHeight);
|
|
|
|
|
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;
|
|
|
|
|
QStyleOptionViewItem options = option;
|
|
|
|
|
initStyleOption(&options, index);
|
|
|
|
|
m_doc.setHtml(options.text);
|
|
|
|
|
m_doc.setTextWidth(options.rect.width());
|
|
|
|
|
return QSize(m_doc.idealWidth(), m_doc.size().height());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
initStyleOption(&opt, index);
|
|
|
|
|
painter->save();
|
|
|
|
|
setColumnWidth(0, width() * 0.85);
|
|
|
|
|
setColumnWidth(1, width() * 0.15);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QFontMetrics fm(opt.font);
|
|
|
|
|
QColor backgroundColor;
|
|
|
|
|
QColor textColor;
|
|
|
|
|
void TaskView::resizeEvent(QResizeEvent *e)
|
|
|
|
|
{
|
|
|
|
|
TreeView::resizeEvent(e);
|
|
|
|
|
resizeColumns();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto view = qobject_cast<const QAbstractItemView *>(opt.widget);
|
|
|
|
|
const bool selected = view->selectionModel()->isSelected(index);
|
|
|
|
|
void TaskView::mousePressEvent(QMouseEvent *e)
|
|
|
|
|
{
|
|
|
|
|
m_clickAnchor = anchorAt(e->pos());
|
|
|
|
|
if (m_clickAnchor.isEmpty())
|
|
|
|
|
TreeView::mousePressEvent(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selected) {
|
|
|
|
|
painter->setBrush(opt.palette.highlight().color());
|
|
|
|
|
backgroundColor = opt.palette.highlight().color();
|
|
|
|
|
} else {
|
|
|
|
|
painter->setBrush(opt.palette.window().color());
|
|
|
|
|
backgroundColor = opt.palette.window().color();
|
|
|
|
|
void TaskView::mouseMoveEvent(QMouseEvent *e)
|
|
|
|
|
{
|
|
|
|
|
const QString anchor = anchorAt(e->pos());
|
|
|
|
|
if (m_clickAnchor != anchor)
|
|
|
|
|
m_clickAnchor.clear();
|
|
|
|
|
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
|
|
|
|
|
if (selected)
|
|
|
|
|
textColor = opt.palette.highlightedText().color();
|
|
|
|
|
else
|
|
|
|
|
textColor = opt.palette.text().color();
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
void TaskView::mouseReleaseEvent(QMouseEvent *e)
|
|
|
|
|
{
|
|
|
|
|
if (m_clickAnchor.isEmpty()) {
|
|
|
|
|
TreeView::mouseReleaseEvent(e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Paint FileArea
|
|
|
|
|
QString file = index.data(TaskModel::File).toString();
|
|
|
|
|
const int pos = file.lastIndexOf(QLatin1Char('/'));
|
|
|
|
|
if (pos != -1)
|
|
|
|
|
file = file.mid(pos +1);
|
|
|
|
|
const int realFileWidth = fm.horizontalAdvance(file);
|
|
|
|
|
painter->setClipRect(positions.fileArea());
|
|
|
|
|
painter->drawText(qMin(positions.fileAreaLeft(), positions.fileAreaRight() - realFileWidth),
|
|
|
|
|
positions.top() + fm.ascent(), file);
|
|
|
|
|
if (realFileWidth > positions.fileAreaWidth()) {
|
|
|
|
|
// draw a gradient to mask the text
|
|
|
|
|
int gradientStart = positions.fileAreaLeft() - 1;
|
|
|
|
|
QLinearGradient lg(gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0, gradientStart, 0);
|
|
|
|
|
lg.setColorAt(0, Qt::transparent);
|
|
|
|
|
lg.setColorAt(1, backgroundColor);
|
|
|
|
|
painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg);
|
|
|
|
|
const QString anchor = anchorAt(e->pos());
|
|
|
|
|
if (anchor == m_clickAnchor) {
|
|
|
|
|
Core::EditorManager::openEditorAt(OutputLineParser::parseLinkTarget(m_clickAnchor), {},
|
|
|
|
|
Core::EditorManager::SwitchSplitIfAlreadyVisible);
|
|
|
|
|
}
|
|
|
|
|
m_clickAnchor.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskView::keyReleaseEvent(QKeyEvent *e)
|
|
|
|
|
{
|
|
|
|
|
TreeView::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 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
|
|
|
|
|
int line = index.data(TaskModel::Line).toInt();
|
|
|
|
|
int movedLine = index.data(TaskModel::MovedLine).toInt();
|
|
|
|
|
QString lineText;
|
|
|
|
|
const auto layout = new QVBoxLayout;
|
|
|
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
layout->addWidget(new QLabel(task.formattedDescription({})));
|
|
|
|
|
ToolTip::show(pos, layout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (line == -1) {
|
|
|
|
|
// No line information at all
|
|
|
|
|
} else if (movedLine == -1) {
|
|
|
|
|
// removed the line, but we had line information, show the line in ()
|
|
|
|
|
QFont f = painter->font();
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
QString TaskView::anchorAt(const QPoint &pos)
|
|
|
|
|
{
|
|
|
|
|
const QModelIndex index = indexAt(pos);
|
|
|
|
|
if (!index.isValid() || !index.internalId())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
painter->setClipRect(positions.lineArea());
|
|
|
|
|
const int realLineWidth = fm.horizontalAdvance(lineText);
|
|
|
|
|
painter->drawText(positions.lineAreaRight() - realLineWidth, positions.top() + fm.ascent(), lineText);
|
|
|
|
|
painter->setClipRect(opt.rect);
|
|
|
|
|
|
|
|
|
|
// Separator lines
|
|
|
|
|
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();
|
|
|
|
|
const QRect itemRect = visualRect(index);
|
|
|
|
|
QTextDocument &doc = static_cast<TaskDelegate *>(itemDelegate())->doc();
|
|
|
|
|
doc.setHtml(model()->data(index, Qt::DisplayRole).toString());
|
|
|
|
|
const QAbstractTextDocumentLayout * const textLayout = doc.documentLayout();
|
|
|
|
|
QTC_ASSERT(textLayout, return {});
|
|
|
|
|
return textLayout->anchorAt(pos - itemRect.topLeft());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace ProjectExplorer
|
|
|
|
|
|
|
|
|
|
#include "taskwindow.moc"
|
|
|
|
|