From 1660d3301a3b806fc52eae4c8bac8713b89e462a Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Thu, 14 Mar 2019 15:59:02 +0100 Subject: [PATCH] Advanced Search: Add horizontal scroll bar Compute the actual size hint for the search results and make the column resize to its contents. To make the highlight of selected items still span the whole widget, we force the column to span at least the width of the tree view, by setting the minimum section size accordingly on resize events. Fixes: QTCREATORBUG-21099 Change-Id: Ia9e88fba8185429fede8a82402a62285afb1e0dd Reviewed-by: Mitch Curtis Reviewed-by: David Schulz --- .../find/searchresulttreeitemdelegate.cpp | 148 ++++++++++++------ .../find/searchresulttreeitemdelegate.h | 15 +- .../coreplugin/find/searchresulttreeview.cpp | 9 ++ .../coreplugin/find/searchresulttreeview.h | 1 + 4 files changed, 121 insertions(+), 52 deletions(-) diff --git a/src/plugins/coreplugin/find/searchresulttreeitemdelegate.cpp b/src/plugins/coreplugin/find/searchresulttreeitemdelegate.cpp index 5e1c8ed3490..9351387c5b8 100644 --- a/src/plugins/coreplugin/find/searchresulttreeitemdelegate.cpp +++ b/src/plugins/coreplugin/find/searchresulttreeitemdelegate.cpp @@ -40,59 +40,101 @@ SearchResultTreeItemDelegate::SearchResultTreeItemDelegate(int tabWidth, QObject setTabWidth(tabWidth); } -void SearchResultTreeItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +const int lineNumberAreaHorizontalPadding = 4; +const int minimumLineNumberDigits = 6; + +static std::pair lineNumberInfo(const QStyleOptionViewItem &option, + const QModelIndex &index) +{ + const int lineNumber = index.data(ItemDataRoles::ResultBeginLineNumberRole).toInt(); + if (lineNumber < 1) + return {0, {}}; + const QString lineNumberText = QString::number(lineNumber); + const int lineNumberDigits = qMax(minimumLineNumberDigits, lineNumberText.count()); + const int fontWidth = option.fontMetrics.horizontalAdvance(QString(lineNumberDigits, QLatin1Char('0'))); + const QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + return {lineNumberAreaHorizontalPadding + fontWidth + lineNumberAreaHorizontalPadding + + style->pixelMetric(QStyle::PM_FocusFrameHMargin), + lineNumberText}; +} + +static QString itemText(const QModelIndex &index) +{ + const QString text = index.data(Qt::DisplayRole).toString(); + // show number of subresults in displayString + if (index.model()->hasChildren(index)) { + return text + QLatin1String(" (") + QString::number(index.model()->rowCount(index)) + + QLatin1Char(')'); + } + return text; +} + +LayoutInfo SearchResultTreeItemDelegate::getLayoutInfo(const QStyleOptionViewItem &option, + const QModelIndex &index) const { static const int iconSize = 16; - painter->save(); - - QStyleOptionViewItem opt = setOptions(index, option); - painter->setFont(opt.font); - - QItemDelegate::drawBackground(painter, opt, index); - - // ---- do the layout - QRect checkRect; - QRect pixmapRect; - QRect textRect; + LayoutInfo info; + info.option = setOptions(index, option); // check mark - bool checkable = (index.model()->flags(index) & Qt::ItemIsUserCheckable); - Qt::CheckState checkState = Qt::Unchecked; + const bool checkable = (index.model()->flags(index) & Qt::ItemIsUserCheckable); + info.checkState = Qt::Unchecked; if (checkable) { QVariant checkStateData = index.data(Qt::CheckStateRole); - checkState = static_cast(checkStateData.toInt()); - checkRect = doCheck(opt, opt.rect, checkStateData); + info.checkState = static_cast(checkStateData.toInt()); + info.checkRect = doCheck(info.option, info.option.rect, checkStateData); } // icon - QIcon icon = index.model()->data(index, ItemDataRoles::ResultIconRole).value(); - if (!icon.isNull()) { - const QSize size = icon.actualSize(QSize(iconSize, iconSize)); - pixmapRect = QRect(0, 0, size.width(), size.height()); + info.icon = index.data(ItemDataRoles::ResultIconRole).value(); + if (!info.icon.isNull()) { + const QSize size = info.icon.actualSize(QSize(iconSize, iconSize)); + info.pixmapRect = QRect(0, 0, size.width(), size.height()); } // text - textRect = opt.rect.adjusted(0, 0, checkRect.width() + pixmapRect.width(), 0); + info.textRect = info.option.rect.adjusted(0, + 0, + info.checkRect.width() + info.pixmapRect.width(), + 0); + + // do basic layout + doLayout(info.option, &info.checkRect, &info.pixmapRect, &info.textRect, false); + + // adapt for line numbers + const int lineNumberWidth = lineNumberInfo(info.option, index).first; + info.lineNumberRect = info.textRect; + info.lineNumberRect.setWidth(lineNumberWidth); + info.textRect.adjust(lineNumberWidth, 0, 0, 0); + return info; +} + +void SearchResultTreeItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + painter->save(); + + const LayoutInfo info = getLayoutInfo(option, index); + + painter->setFont(info.option.font); + + QItemDelegate::drawBackground(painter, info.option, index); - // do layout - doLayout(opt, &checkRect, &pixmapRect, &textRect, false); // ---- draw the items // icon - if (!icon.isNull()) - icon.paint(painter, pixmapRect, option.decorationAlignment); + if (!info.icon.isNull()) + info.icon.paint(painter, info.pixmapRect, info.option.decorationAlignment); // line numbers - int lineNumberAreaWidth = drawLineNumber(painter, opt, textRect, index); - textRect.adjust(lineNumberAreaWidth, 0, 0, 0); + drawLineNumber(painter, info.option, info.lineNumberRect, index); // text and focus/selection - drawText(painter, opt, textRect, index); - QItemDelegate::drawFocus(painter, opt, opt.rect); + drawText(painter, info.option, info.textRect, index); + QItemDelegate::drawFocus(painter, info.option, info.option.rect); // check mark - if (checkable) - QItemDelegate::drawCheck(painter, opt, checkRect, checkState); + if (info.checkRect.isValid()) + QItemDelegate::drawCheck(painter, info.option, info.checkRect, info.checkState); painter->restore(); } @@ -102,22 +144,31 @@ void SearchResultTreeItemDelegate::setTabWidth(int width) m_tabString = QString(width, QLatin1Char(' ')); } +QSize SearchResultTreeItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + const LayoutInfo info = getLayoutInfo(option, index); + const int height = index.data(Qt::SizeHintRole).value().height(); + // get text width, see QItemDelegatePrivate::displayRect + const QString text = itemText(index).replace('\t', m_tabString); + const QRect textMaxRect(0, 0, INT_MAX / 256, height); + const QRect textLayoutRect = textRectangle(nullptr, textMaxRect, info.option.font, text); + const QRect textRect(info.textRect.x(), info.textRect.y(), textLayoutRect.width(), height); + const QRect layoutRect = info.checkRect | info.pixmapRect | info.lineNumberRect | textRect; + return QSize(layoutRect.x(), layoutRect.y()) + layoutRect.size(); +} + // returns the width of the line number area int SearchResultTreeItemDelegate::drawLineNumber(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QModelIndex &index) const { - static const int lineNumberAreaHorizontalPadding = 4; - int lineNumber = index.model()->data(index, ItemDataRoles::ResultBeginLineNumberRole).toInt(); - if (lineNumber < 1) - return 0; const bool isSelected = option.state & QStyle::State_Selected; - QString lineText = QString::number(lineNumber); - int minimumLineNumberDigits = qMax((int)m_minimumLineNumberDigits, lineText.count()); - int fontWidth = painter->fontMetrics().horizontalAdvance(QString(minimumLineNumberDigits, QLatin1Char('0'))); - int lineNumberAreaWidth = lineNumberAreaHorizontalPadding + fontWidth + lineNumberAreaHorizontalPadding; + const std::pair numberInfo = lineNumberInfo(option, index); + if (numberInfo.first == 0) + return 0; QRect lineNumberAreaRect(rect); - lineNumberAreaRect.setWidth(lineNumberAreaWidth); + lineNumberAreaRect.setWidth(numberInfo.first); QPalette::ColorGroup cg = QPalette::Normal; if (!(option.state & QStyle::State_Active)) @@ -137,9 +188,9 @@ int SearchResultTreeItemDelegate::drawLineNumber(QPainter *painter, const QStyle const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, nullptr) + 1; const QRect rowRect = lineNumberAreaRect.adjusted(-textMargin, 0, textMargin-lineNumberAreaHorizontalPadding, 0); - QItemDelegate::drawDisplay(painter, opt, rowRect, lineText); + QItemDelegate::drawDisplay(painter, opt, rowRect, numberInfo.second); - return lineNumberAreaWidth; + return numberInfo.first; } void SearchResultTreeItemDelegate::drawText(QPainter *painter, @@ -147,18 +198,15 @@ void SearchResultTreeItemDelegate::drawText(QPainter *painter, const QRect &rect, const QModelIndex &index) const { - QString text = index.model()->data(index, Qt::DisplayRole).toString(); - // show number of subresults in displayString - if (index.model()->hasChildren(index)) { - text += QLatin1String(" (") - + QString::number(index.model()->rowCount(index)) - + QLatin1Char(')'); - } + const QString text = itemText(index); const int searchTermStart = index.model()->data(index, ItemDataRoles::ResultBeginColumnNumberRole).toInt(); int searchTermLength = index.model()->data(index, ItemDataRoles::SearchTermLengthRole).toInt(); if (searchTermStart < 0 || searchTermStart >= text.length() || searchTermLength < 1) { - QItemDelegate::drawDisplay(painter, option, rect, text.replace(QLatin1Char('\t'), m_tabString)); + QItemDelegate::drawDisplay(painter, + option, + rect, + QString(text).replace(QLatin1Char('\t'), m_tabString)); return; } diff --git a/src/plugins/coreplugin/find/searchresulttreeitemdelegate.h b/src/plugins/coreplugin/find/searchresulttreeitemdelegate.h index 0551e4c8d73..edb13e98ede 100644 --- a/src/plugins/coreplugin/find/searchresulttreeitemdelegate.h +++ b/src/plugins/coreplugin/find/searchresulttreeitemdelegate.h @@ -30,20 +30,31 @@ namespace Core { namespace Internal { +struct LayoutInfo +{ + QRect checkRect; + QRect pixmapRect; + QRect textRect; + QRect lineNumberRect; + QIcon icon; + Qt::CheckState checkState; + QStyleOptionViewItem option; +}; + class SearchResultTreeItemDelegate: public QItemDelegate { public: SearchResultTreeItemDelegate(int tabWidth, QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setTabWidth(int width); - + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: + LayoutInfo getLayoutInfo(const QStyleOptionViewItem &option, const QModelIndex &index) const; int drawLineNumber(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QModelIndex &index) const; void drawText(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QModelIndex &index) const; QString m_tabString; - static const int m_minimumLineNumberDigits = 6; }; } // namespace Internal diff --git a/src/plugins/coreplugin/find/searchresulttreeview.cpp b/src/plugins/coreplugin/find/searchresulttreeview.cpp index 0ca09fcefd1..48d1845053e 100644 --- a/src/plugins/coreplugin/find/searchresulttreeview.cpp +++ b/src/plugins/coreplugin/find/searchresulttreeview.cpp @@ -44,6 +44,8 @@ SearchResultTreeView::SearchResultTreeView(QWidget *parent) setIndentation(14); setUniformRowHeights(true); setExpandsOnDoubleClick(true); + header()->setSectionResizeMode(QHeaderView::ResizeToContents); + header()->setStretchLastSection(false); header()->hide(); connect(this, &SearchResultTreeView::activated, @@ -93,6 +95,13 @@ void SearchResultTreeView::keyPressEvent(QKeyEvent *event) TreeView::keyPressEvent(event); } +bool SearchResultTreeView::event(QEvent *e) +{ + if (e->type() == QEvent::Resize) + header()->setMinimumSectionSize(width()); + return TreeView::event(e); +} + void SearchResultTreeView::emitJumpToSearchResult(const QModelIndex &index) { if (model()->data(index, ItemDataRoles::IsGeneratedRole).toBool()) diff --git a/src/plugins/coreplugin/find/searchresulttreeview.h b/src/plugins/coreplugin/find/searchresulttreeview.h index eb11d29a7d9..4809a6887ba 100644 --- a/src/plugins/coreplugin/find/searchresulttreeview.h +++ b/src/plugins/coreplugin/find/searchresulttreeview.h @@ -50,6 +50,7 @@ public: void addResults(const QList &items, SearchResult::AddMode mode); void keyPressEvent(QKeyEvent *event) override; + bool event(QEvent *e) override; signals: void jumpToSearchResult(const SearchResultItem &item);