diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.cpp b/qrenderdoc/Widgets/Extended/RDTreeView.cpp index cef17ef06..321c1eab2 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.cpp +++ b/qrenderdoc/Widgets/Extended/RDTreeView.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +36,6 @@ #include #include #include -#include #include #include "Code/QRDUtils.h" #include "Code/Resources.h" @@ -132,6 +132,19 @@ RDTipLabel::RDTipLabel(QWidget *listener) : QLabel(NULL), mouseListener(listener setWindowOpacity(opacity / 255.0); } +QSize RDTipLabel::getSizeForTip(QString text) +{ + setText(text); + return sizeHint(); +} + +void RDTipLabel::showTip(QPoint pos, QString text) +{ + move(pos); + setText(text); + show(); +} + void RDTipLabel::paintEvent(QPaintEvent *ev) { QStylePainter p(this); @@ -187,23 +200,72 @@ RDTreeView::RDTreeView(QWidget *parent) : QTreeView(parent) m_delegate = new RDTreeViewDelegate(this); QTreeView::setItemDelegate(m_delegate); - m_ElidedTooltip = new RDTipLabel(viewport()); - m_ElidedTooltip->hide(); + m_TooltipLabel = new RDTipLabel(viewport()); + m_TooltipLabel->hide(); + m_CurrentTooltipElided = false; + + m_Tooltip = m_TooltipLabel; } RDTreeView::~RDTreeView() { setModel(NULL); - delete m_ElidedTooltip; + delete m_TooltipLabel; } void RDTreeView::mouseMoveEvent(QMouseEvent *e) { - if(m_ElidedTooltip->isVisible() && !m_ElidedTooltip->geometry().contains(QCursor::pos())) - m_ElidedTooltip->hide(); + QModelIndex oldHoverIndex = m_currentHoverIndex; + + if(m_CurrentTooltipElided && m_TooltipLabel->isVisible() && + !m_TooltipLabel->geometry().contains(QCursor::pos())) + m_Tooltip->hideTip(); m_currentHoverIndex = indexAt(e->pos()); + + if(oldHoverIndex != m_currentHoverIndex) + { + if(m_instantTooltips) + { + m_Tooltip->hideTip(); + + if(m_currentHoverIndex.isValid()) + { + QString tooltip = m_currentHoverIndex.data(Qt::ToolTipRole).toString(); + + if(!tooltip.isEmpty()) + { + // We don't use QToolTip since we have a custom tooltip for showing elided results, and we + // use that for consistency. This also makes it easier to slot in a custom tooltip widget + // externally. + QPoint p = QCursor::pos(); + + // estimate, as this is not easily queryable + const QPoint cursorSize(16, 16); + const QRect screenAvailGeom = QApplication::desktop()->availableGeometry(p); + + // start with the tooltip placed bottom-right of the cursor, as the default + QRect tooltipRect; + tooltipRect.setTopLeft(p + cursorSize); + tooltipRect.setSize(m_Tooltip->getSizeForTip(tooltip)); + + // clip by the available geometry in x + if(tooltipRect.right() > screenAvailGeom.right()) + tooltipRect.moveRight(screenAvailGeom.right()); + + // if we'd go out of bounds in y, place the tooltip above the cursor. Don't just clip like + // in x, because that could place the tooltip over the cursor. + if(tooltipRect.bottom() > screenAvailGeom.bottom()) + tooltipRect.moveBottom(p.y() - cursorSize.y()); + + m_Tooltip->showTip(tooltipRect.topLeft(), tooltip); + m_CurrentTooltipElided = false; + } + } + } + } + QTreeView::mouseMoveEvent(e); } @@ -215,8 +277,15 @@ void RDTreeView::wheelEvent(QWheelEvent *e) void RDTreeView::leaveEvent(QEvent *e) { - if(m_ElidedTooltip->isVisible() && !m_ElidedTooltip->geometry().contains(QCursor::pos())) - m_ElidedTooltip->hide(); + if(m_CurrentTooltipElided) + { + if(m_TooltipLabel->isVisible() && !m_TooltipLabel->geometry().contains(QCursor::pos())) + m_Tooltip->hideTip(); + } + else + { + m_Tooltip->hideTip(); + } m_currentHoverIndex = QModelIndex(); @@ -306,40 +375,48 @@ void RDTreeView::collapseAll(QModelIndex index) bool RDTreeView::viewportEvent(QEvent *event) { - if(m_TooltipElidedItems && event->type() == QEvent::ToolTip) + if(event->type() == QEvent::ToolTip) { - QHelpEvent *he = (QHelpEvent *)event; - QModelIndex index = indexAt(he->pos()); + // if we're doing instant tooltips this is all handled in the mousemove handler, don't do + // anything here + if(m_instantTooltips) + return true; - QAbstractItemDelegate *delegate = m_userDelegate; - - if(!delegate) - delegate = QTreeView::itemDelegate(index); - - if(delegate) + if(m_TooltipElidedItems) { - QStyleOptionViewItem option; - option.initFrom(this); - option.rect = visualRect(index); + QHelpEvent *he = (QHelpEvent *)event; + QModelIndex index = indexAt(he->pos()); - // delegates get first dibs at processing the event - bool ret = delegate->helpEvent(he, this, option, index); + QAbstractItemDelegate *delegate = m_userDelegate; - if(ret) - return true; + if(!delegate) + delegate = QTreeView::itemDelegate(index); - QSize desiredSize = delegate->sizeHint(option, index); - - if(desiredSize.width() > option.rect.width()) + if(delegate) { - const QString fullText = index.data(Qt::DisplayRole).toString(); - if(!fullText.isEmpty()) + QStyleOptionViewItem option; + option.initFrom(this); + option.rect = visualRect(index); + + // delegates get first dibs at processing the event + bool ret = delegate->helpEvent(he, this, option, index); + + if(ret) + return true; + + QSize desiredSize = delegate->sizeHint(option, index); + + if(desiredSize.width() > option.rect.width()) { - // need to use a custom label tooltip since the QToolTip freaks out as we're placing it - // underneath the cursor instead of next to it (so that the tooltip lines up over the row) - m_ElidedTooltip->move(viewport()->mapToGlobal(option.rect.topLeft())); - m_ElidedTooltip->setText(fullText); - m_ElidedTooltip->show(); + const QString fullText = index.data(Qt::DisplayRole).toString(); + if(!fullText.isEmpty()) + { + // need to use a custom label tooltip since the QToolTip freaks out as we're placing it + // underneath the cursor instead of next to it (so that the tooltip lines up over the + // row) + m_Tooltip->showTip(viewport()->mapToGlobal(option.rect.topLeft()), fullText); + m_CurrentTooltipElided = true; + } } } } diff --git a/qrenderdoc/Widgets/Extended/RDTreeView.h b/qrenderdoc/Widgets/Extended/RDTreeView.h index 662ebcdec..e36fd2d95 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeView.h +++ b/qrenderdoc/Widgets/Extended/RDTreeView.h @@ -47,18 +47,28 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; -class RDTipLabel : public QLabel +struct ITreeViewTipDisplay +{ +public: + virtual void hideTip() = 0; + virtual QSize getSizeForTip(QString text) = 0; + virtual void showTip(QPoint pos, QString text) = 0; +}; + +class RDTipLabel : public QLabel, public ITreeViewTipDisplay { private: Q_OBJECT - int m_TooltipMargin = 0; QWidget *mouseListener; public: explicit RDTipLabel(QWidget *listener = NULL); - int tipMargin() { return m_TooltipMargin; } + void hideTip() { hide(); } + QSize getSizeForTip(QString text); + void showTip(QPoint pos, QString text); + protected: void paintEvent(QPaintEvent *); void mousePressEvent(QMouseEvent *); @@ -76,6 +86,7 @@ class RDTreeView : public QTreeView Q_OBJECT Q_PROPERTY(bool customCopyPasteHandler READ customCopyPasteHandler WRITE setCustomCopyPasteHandler) + Q_PROPERTY(bool instantTooltips READ instantTooltips WRITE setInstantTooltips) public: explicit RDTreeView(QWidget *parent = 0); virtual ~RDTreeView(); @@ -97,10 +108,17 @@ public: bool ignoreIconSize() { return m_IgnoreIconSize; } bool customCopyPasteHandler() { return m_customCopyPaste; } void setCustomCopyPasteHandler(bool custom) { m_customCopyPaste = custom; } + bool instantTooltips() { return m_instantTooltips; } + void setInstantTooltips(bool instant) { m_instantTooltips = instant; } QModelIndex currentHoverIndex() const { return m_currentHoverIndex; } void setItemDelegate(QAbstractItemDelegate *delegate); QAbstractItemDelegate *itemDelegate() const; + void setCustomTooltip(ITreeViewTipDisplay *tip) + { + m_Tooltip = tip; + m_TooltipElidedItems = false; + } void setModel(QAbstractItemModel *model) override; // state is the storage to save the expansion state into @@ -168,6 +186,7 @@ private: bool m_VisibleGridLines = true; bool m_TooltipElidedItems = true; bool m_customCopyPaste = false; + bool m_instantTooltips = false; QMap m_Expansions; @@ -179,7 +198,9 @@ private: QAbstractItemDelegate *m_userDelegate = NULL; RDTreeViewDelegate *m_delegate; - RDTipLabel *m_ElidedTooltip; + RDTipLabel *m_TooltipLabel; + ITreeViewTipDisplay *m_Tooltip; + bool m_CurrentTooltipElided = false; int m_VertMargin = 6; bool m_IgnoreIconSize = false; diff --git a/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp b/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp index bfc1315a5..d41b2c10e 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp +++ b/qrenderdoc/Widgets/Extended/RDTreeWidget.cpp @@ -31,7 +31,6 @@ #include #include #include -#include #include "Code/Interface/QRDInterface.h" #include "Code/QRDUtils.h" #include "Code/Resources.h" @@ -259,7 +258,7 @@ public: item->m_fore = value.value(); ret = true; } - else if(role == Qt::ToolTipRole && !widget->m_instantTooltips) + else if(role == Qt::ToolTipRole) { item->m_tooltip = value.toString(); ret = true; @@ -359,7 +358,7 @@ QVariant RDTreeWidgetItem::data(int column, int role) const return QVariant(); } - else if(role == Qt::ToolTipRole && !m_widget->m_instantTooltips) + else if(role == Qt::ToolTipRole) { if(!m_tooltip.isEmpty()) return m_tooltip; @@ -784,27 +783,6 @@ void RDTreeWidget::mouseMoveEvent(QMouseEvent *e) m_model->itemChanged(oldHover, roles); m_model->itemChanged(newHover, roles); - if(m_instantTooltips) - { - QToolTip::hideText(); - - if(newHover && !newHover->m_tooltip.isEmpty()) - { - // the documentation says: - // - // "If text is empty the tool tip is hidden. If the text is the same as the currently shown - // tooltip, the tip will not move. You can force moving by first hiding the tip with an - // empty - // text, and then showing the new tip at the new position." - // - // However the actual implementation has some kind of 'fading' check, so if you hide then - // immediately show, it will try to reuse the tooltip and end up not moving it at all if the - // text hasn't changed. - QToolTip::showText(QCursor::pos(), lit(" "), this); - QToolTip::showText(QCursor::pos(), newHover->m_tooltip, this); - } - } - emit hoverItemChanged(newHover); emit mouseMove(e); @@ -831,8 +809,6 @@ void RDTreeWidget::leaveEvent(QEvent *e) if(m_currentHoverIndex.isValid()) { RDTreeWidgetItem *item = m_model->itemForIndex(m_currentHoverIndex); - if(!item->m_tooltip.isEmpty() && m_instantTooltips) - QToolTip::hideText(); m_model->itemChanged(item, {Qt::DecorationRole, Qt::BackgroundRole, Qt::ForegroundRole}); } diff --git a/qrenderdoc/Widgets/Extended/RDTreeWidget.h b/qrenderdoc/Widgets/Extended/RDTreeWidget.h index 426456d92..b5a188e72 100644 --- a/qrenderdoc/Widgets/Extended/RDTreeWidget.h +++ b/qrenderdoc/Widgets/Extended/RDTreeWidget.h @@ -210,7 +210,6 @@ class RDTreeWidget : public RDTreeView { Q_OBJECT - Q_PROPERTY(bool instantTooltips READ instantTooltips WRITE setInstantTooltips) public: explicit RDTreeWidget(QWidget *parent = 0); ~RDTreeWidget(); @@ -226,8 +225,6 @@ public: void setHoverHandCursor(bool hand) { m_hoverHandCursor = hand; } void setHoverClickActivate(bool click) { m_activateOnClick = click; } void setClearSelectionOnFocusLoss(bool clear) { m_clearSelectionOnFocusLoss = clear; } - bool instantTooltips() { return m_instantTooltips; } - void setInstantTooltips(bool instant) { m_instantTooltips = instant; } RDTreeWidgetItem *invisibleRootItem() { return m_root; } void addTopLevelItem(RDTreeWidgetItem *item) { m_root->addChild(item); } RDTreeWidgetItem *topLevelItem(int index) const { return m_root->child(index); } @@ -314,7 +311,6 @@ private: QVector m_alignments; - bool m_instantTooltips = false; int m_hoverColumn = -1; QIcon m_normalHoverIcon; QIcon m_activeHoverIcon;