Refactor RDTreeWidgetDelegate into RichTextViewDelegate that is reusable

* This allows us to add rich text support much more easily into other itemviews
  like RDTableView.
* We set it up for debug messages so that resource links in debug messages can
  be linked.
This commit is contained in:
baldurk
2018-12-17 12:47:07 +00:00
parent 58e2c88e76
commit ce88558a7c
11 changed files with 237 additions and 202 deletions
+124
View File
@@ -365,6 +365,130 @@ bool RichResourceTextMouseEvent(const QWidget *owner, const QVariant &var, QRect
return false;
}
RichTextViewDelegate::RichTextViewDelegate(QAbstractItemView *parent)
: m_widget(parent), ForwardingDelegate(parent)
{
}
RichTextViewDelegate::~RichTextViewDelegate()
{
}
void RichTextViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if(index.isValid())
{
QVariant v = index.data();
if(RichResourceTextCheck(v))
{
// draw the item without text, so we get the proper background/selection/etc.
// we'd like to be able to use the parent delegate's paint here, but either it calls to
// QStyledItemDelegate which will re-fetch the text (bleh), or it calls to the manual
// delegate which could do anything. So for this case we just use the style and skip the
// delegate and hope it works out.
QStyleOptionViewItem opt = option;
QStyledItemDelegate::initStyleOption(&opt, index);
opt.text.clear();
m_widget->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, m_widget);
painter->save();
QRect rect = option.rect;
if(!opt.icon.isNull())
{
QIcon::Mode mode;
if((opt.state & QStyle::State_Enabled) == 0)
mode = QIcon::Disabled;
else if(opt.state & QStyle::State_Selected)
mode = QIcon::Selected;
else
mode = QIcon::Normal;
QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off;
rect.setX(rect.x() + opt.icon.actualSize(opt.decorationSize, mode, state).width() + 4);
}
RichResourceTextPaint(m_widget, painter, rect, option.font, option.palette,
option.state & QStyle::State_MouseOver,
m_widget->viewport()->mapFromGlobal(QCursor::pos()), v);
painter->restore();
return;
}
}
return ForwardingDelegate::paint(painter, option, index);
}
QSize RichTextViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if(index.isValid())
{
QVariant v = index.data();
if(RichResourceTextCheck(v))
return QSize(RichResourceTextWidthHint(m_widget, v), option.fontMetrics.height());
}
return ForwardingDelegate::sizeHint(option, index);
}
bool RichTextViewDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option, const QModelIndex &index)
{
if(event->type() == QEvent::MouseButtonRelease && index.isValid())
{
QVariant v = index.data();
if(RichResourceTextCheck(v))
{
QRect rect = option.rect;
QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
if(!icon.isNull())
{
rect.setX(rect.x() +
icon.actualSize(option.decorationSize, QIcon::Normal, QIcon::On).width() + 4);
}
// ignore the return value, we always consume clicks on this cell
RichResourceTextMouseEvent(m_widget, v, rect, (QMouseEvent *)event);
return true;
}
}
return ForwardingDelegate::editorEvent(event, model, option, index);
}
bool RichTextViewDelegate::linkHover(QMouseEvent *e, const QModelIndex &index)
{
if(index.isValid())
{
QVariant v = index.data();
if(RichResourceTextCheck(v))
{
QRect rect = m_widget->visualRect(index);
QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
if(!icon.isNull())
{
rect.setX(
rect.x() +
icon.actualSize(QSize(rect.height(), rect.height()), QIcon::Normal, QIcon::On).width() +
4);
}
return RichResourceTextMouseEvent(m_widget, v, rect, e);
}
}
return false;
}
#include "renderdoc_tostr.inl"
QString ToQStr(const ResourceUsage usage, const GraphicsAPI apitype)
+22
View File
@@ -467,6 +467,28 @@ private:
QAbstractItemDelegate *m_delegate = NULL;
};
// delegate that will handle painting, hovering and clicking on rich text items.
// owning view needs to call linkHover, and adjust its cursor and repaint as necessary.
class RichTextViewDelegate : public ForwardingDelegate
{
Q_OBJECT
public:
explicit RichTextViewDelegate(QAbstractItemView *parent);
~RichTextViewDelegate();
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
const QModelIndex &index) override;
bool linkHover(QMouseEvent *e, const QModelIndex &index);
private:
QAbstractItemView *m_widget;
};
class QMenu;
// helper for doing a manual blocking invoke of a dialog
+46 -1
View File
@@ -39,10 +39,24 @@ RDTableView::RDTableView(QWidget *parent) : QTableView(parent)
m_horizontalHeader = new RDHeaderView(Qt::Horizontal, this);
setHorizontalHeader(m_horizontalHeader);
m_delegate = new RichTextViewDelegate(this);
QTableView::setItemDelegate(m_delegate);
QObject::connect(m_horizontalHeader, &QHeaderView::sectionResized,
[this](int, int, int) { viewport()->update(); });
}
void RDTableView::setItemDelegate(QAbstractItemDelegate *delegate)
{
m_userDelegate = delegate;
m_delegate->setForwardDelegate(m_userDelegate);
}
QAbstractItemDelegate *RDTableView::itemDelegate() const
{
return m_userDelegate;
}
int RDTableView::columnViewportPosition(int column) const
{
return horizontalHeader()->sectionViewportPosition(column);
@@ -306,9 +320,40 @@ void RDTableView::paintCell(QPainter *painter, const QModelIndex &index,
if(selectionModel() && selectionModel()->isSelected(index))
cellopt.state |= QStyle::State_Selected;
if(cellopt.rect.contains(viewport()->mapFromGlobal(QCursor::pos())))
cellopt.state |= QStyle::State_MouseOver;
if(index.row() == 1 && index.column() == 5)
qInfo() << cellopt.state;
// draw the background, then the cell
style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &cellopt, painter, this);
itemDelegate(index)->paint(painter, cellopt, index);
QTableView::itemDelegate(index)->paint(painter, cellopt, index);
}
void RDTableView::mouseMoveEvent(QMouseEvent *e)
{
QModelIndex oldHover = m_currentHoverIndex;
QTableView::mouseMoveEvent(e);
QModelIndex newHover = m_currentHoverIndex = indexAt(e->pos());
update(newHover);
if(m_delegate && m_delegate->linkHover(e, newHover))
{
setCursor(QCursor(Qt::PointingHandCursor));
}
else
{
unsetCursor();
}
if(oldHover == newHover)
return;
update(oldHover);
}
void RDTableView::scrollTo(const QModelIndex &index, ScrollHint hint)
+13
View File
@@ -27,6 +27,8 @@
#include <QTableView>
#include "RDHeaderView.h"
class RichTextViewDelegate;
class RDTableView : public QTableView
{
Q_OBJECT
@@ -44,6 +46,9 @@ public:
void resizeColumnsToContents();
void setCustomHeaderSizing(bool sizing) { m_horizontalHeader->setCustomSizing(sizing); }
void setItemDelegate(QAbstractItemDelegate *delegate);
QAbstractItemDelegate *itemDelegate() const;
// these ones we CAN override, so even though the implementation is identical to QTableView we
// reimplement so it can pick up the above functions
QRect visualRect(const QModelIndex &index) const override;
@@ -57,6 +62,7 @@ public:
void setPinnedColumns(int numColumns);
int pinnedColumns() const { return m_pinnedColumns; }
protected:
void mouseMoveEvent(QMouseEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void updateGeometries() override;
@@ -69,4 +75,11 @@ private:
int m_columnGroupRole = 0;
RDHeaderView *m_horizontalHeader;
QModelIndex m_currentHoverIndex;
QAbstractItemDelegate *m_userDelegate = NULL;
RichTextViewDelegate *m_delegate;
friend class RichTextViewDelegate;
};
+5 -18
View File
@@ -37,36 +37,23 @@ RDTreeViewDelegate::RDTreeViewDelegate(RDTreeView *view) : ForwardingDelegate(vi
QSize RDTreeViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
m_suppressIcon = m_View->ignoreIconSize();
QSize ret = ForwardingDelegate::sizeHint(option, index);
m_suppressIcon = false;
if(m_View->ignoreIconSize())
ret.setHeight(qMax(option.decorationSize.height() + 2, ret.height()));
// expand a pixel for the grid lines
if(m_View->visibleGridLines())
{
ret.setWidth(ret.width() + 1);
ret.setHeight(ret.height() + 1);
}
// ensure we have at least the margin on top of font size. If the style applied more, don't add to
// it.
ret.setHeight(qMax(ret.height(), option.fontMetrics.height() + m_View->verticalItemMargin()));
int minHeight = option.fontMetrics.height();
ret.setHeight(qMax(ret.height(), minHeight + m_View->verticalItemMargin()));
return ret;
}
void RDTreeViewDelegate::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const
{
QStyledItemDelegate::initStyleOption(option, index);
if(m_suppressIcon)
{
option->icon = QIcon();
option->features &= ~QStyleOptionViewItem::HasDecoration;
option->decorationSize = QSize();
}
}
RDTipLabel::RDTipLabel(QWidget *listener) : QLabel(NULL), mouseListener(listener)
{
int margin = style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, NULL, this);
-8
View File
@@ -38,19 +38,11 @@ class RDTreeViewDelegate : public ForwardingDelegate
private:
Q_OBJECT
// I hate the mutable keyword, but it's necessary to modify it inside sizeHint to access in
// initStyleOption. There's no other way to only remove the icon while in sizeHint, and not in
// other places that call initStyleOption. We don't want to try and manually init, since if
// there's a forwarded delegate it might do something different.
mutable bool m_suppressIcon = false;
RDTreeView *m_View;
public:
RDTreeViewDelegate(RDTreeView *view);
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
protected:
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override;
};
class RDTipLabel : public QLabel
+2 -152
View File
@@ -36,6 +36,7 @@
#include "Code/Interface/QRDInterface.h"
#include "Code/QRDUtils.h"
#include "Code/Resources.h"
#include "Widgets/Extended/RDTableView.h"
class RDTreeWidgetModel : public QAbstractItemModel
{
@@ -568,163 +569,12 @@ RDTreeWidgetItemIterator &RDTreeWidgetItemIterator::operator++()
return *this;
}
RDTreeWidgetDelegate::RDTreeWidgetDelegate(RDTreeWidget *parent)
: m_widget(parent), RDTreeViewDelegate(parent)
{
}
RDTreeWidgetDelegate::~RDTreeWidgetDelegate()
{
}
void RDTreeWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
RDTreeWidgetModel *model = m_widget->m_model;
if(index.isValid() && model == index.model())
{
RDTreeWidgetItem *item = model->itemForIndex(index);
if(index.column() < item->m_text.count())
{
QVariant v = item->m_text[index.column()];
if(RichResourceTextCheck(v))
{
// draw the item without text, so we get the proper background/selection/etc.
// we'd like to be able to use the parent delegate's paint here, but either it calls to
// QStyledItemDelegate which will re-fetch the text (bleh), or it calls to the manual
// delegate which could do anything. So for this case we just use the style and skip the
// delegate and hope it works out.
QStyleOptionViewItem opt = option;
QStyledItemDelegate::initStyleOption(&opt, index);
opt.text.clear();
m_widget->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, m_widget);
painter->save();
QRect rect = option.rect;
if(!opt.icon.isNull())
{
QIcon::Mode mode;
if((opt.state & QStyle::State_Enabled) == 0)
mode = QIcon::Disabled;
else if(opt.state & QStyle::State_Selected)
mode = QIcon::Selected;
else
mode = QIcon::Normal;
QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off;
rect.setX(rect.x() + opt.icon.actualSize(opt.decorationSize, mode, state).width());
}
RichResourceTextPaint(m_widget, painter, rect, option.font, option.palette,
option.state & QStyle::State_MouseOver,
m_widget->viewport()->mapFromGlobal(QCursor::pos()), v);
painter->restore();
return;
}
}
}
return RDTreeViewDelegate::paint(painter, option, index);
}
QSize RDTreeWidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
RDTreeWidgetModel *model = m_widget->m_model;
if(index.isValid() && model == index.model())
{
RDTreeWidgetItem *item = model->itemForIndex(index);
if(index.column() < item->m_text.count())
{
QVariant v = item->m_text[index.column()];
if(RichResourceTextCheck(v))
return QSize(RichResourceTextWidthHint(m_widget, v), option.fontMetrics.height());
}
}
return RDTreeViewDelegate::sizeHint(option, index);
}
bool RDTreeWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option, const QModelIndex &index)
{
RDTreeWidgetModel *rdmodel = m_widget->m_model;
if(event->type() == QEvent::MouseButtonRelease && index.isValid() && rdmodel == model)
{
RDTreeWidgetItem *item = rdmodel->itemForIndex(index);
if(index.column() < item->m_text.count())
{
QVariant v = item->m_text[index.column()];
if(RichResourceTextCheck(v))
{
QRect rect = option.rect;
if(!item->m_icons[index.column()].isNull())
{
QIcon::Mode mode;
if((option.state & QStyle::State_Enabled) == 0)
mode = QIcon::Disabled;
else if(option.state & QStyle::State_Selected)
mode = QIcon::Selected;
else
mode = QIcon::Normal;
QIcon::State state = option.state & QStyle::State_Open ? QIcon::On : QIcon::Off;
rect.setX(rect.x() + option.icon.actualSize(option.decorationSize, mode, state).width());
}
// ignore the return value, we always consume clicks on this cell
RichResourceTextMouseEvent(m_widget, v, rect, (QMouseEvent *)event);
return true;
}
}
}
return RDTreeViewDelegate::editorEvent(event, model, option, index);
}
bool RDTreeWidgetDelegate::linkHover(QMouseEvent *e, const QModelIndex &index)
{
if(index.isValid())
{
RDTreeWidgetItem *item = m_widget->m_model->itemForIndex(index);
if(index.column() < item->m_text.count())
{
QVariant v = item->m_text[index.column()];
if(RichResourceTextCheck(v))
{
QRect rect = m_widget->visualRect(index);
QIcon icon = item->m_icons[index.column()];
if(!icon.isNull())
{
rect.setX(
rect.x() +
icon.actualSize(QSize(rect.height(), rect.height()), QIcon::Normal, QIcon::On).width());
}
return RichResourceTextMouseEvent(m_widget, v, rect, e);
}
}
}
return false;
}
RDTreeWidget::RDTreeWidget(QWidget *parent) : RDTreeView(parent)
{
// we'll call this ourselves in drawBranches()
RDTreeView::enableBranchRectFill(false);
m_delegate = new RDTreeWidgetDelegate(this);
m_delegate = new RichTextViewDelegate(this);
RDTreeView::setItemDelegate(m_delegate);
header()->setSectionsMovable(false);
+2 -20
View File
@@ -198,25 +198,7 @@ private:
RDTreeWidgetItem *m_Current;
};
class RDTreeWidgetDelegate : public RDTreeViewDelegate
{
Q_OBJECT
public:
explicit RDTreeWidgetDelegate(RDTreeWidget *parent);
~RDTreeWidgetDelegate();
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
const QModelIndex &index) override;
bool linkHover(QMouseEvent *e, const QModelIndex &index);
private:
RDTreeWidget *m_widget;
};
class RichTextViewDelegate;
class RDTreeWidget : public RDTreeView
{
@@ -313,7 +295,7 @@ private:
RDTreeWidgetModel *m_model;
QAbstractItemDelegate *m_userDelegate = NULL;
RDTreeWidgetDelegate *m_delegate;
RichTextViewDelegate *m_delegate;
bool m_clearing = false;
+11 -1
View File
@@ -106,7 +106,12 @@ public:
case 2: return sort ? QVariant((uint32_t)msg.severity) : QVariant(ToQStr(msg.severity));
case 3: return ToQStr(msg.category);
case 4: return msg.messageID;
case 5: return msg.description;
case 5:
{
QVariant desc = msg.description;
RichResourceTextInitialise(desc);
return desc;
}
default: break;
}
}
@@ -208,6 +213,11 @@ DebugMessageView::DebugMessageView(ICaptureContext &ctx, QWidget *parent)
ui->messages->setSortingEnabled(true);
ui->messages->sortByColumn(0, Qt::AscendingOrder);
ui->messages->setMouseTracking(true);
ui->messages->setAutoScroll(false);
ui->messages->horizontalHeader()->setStretchLastSection(false);
ui->messages->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(ui->messages, &QWidget::customContextMenuRequested, this,
&DebugMessageView::messages_contextMenu);
+11 -1
View File
@@ -27,7 +27,10 @@
<number>3</number>
</property>
<item>
<widget class="QTableView" name="messages">
<widget class="RDTableView" name="messages">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
@@ -53,6 +56,13 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RDTableView</class>
<extends>QTableView</extends>
<header>Widgets/Extended/RDTableView.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+1 -1
View File
@@ -111,7 +111,7 @@ EventBrowser::EventBrowser(ICaptureContext &ctx, QWidget *parent)
ui->events->header()->setCascadingSectionResizes(false);
ui->events->setItemVerticalMargin(4);
ui->events->setItemVerticalMargin(0);
ui->events->setIgnoreIconSize(true);
// set up default section layout. This will be overridden in restoreState()