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 <mitch.curtis@qt.io>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Eike Ziller
2019-03-14 15:59:02 +01:00
parent 5e749c6d09
commit 1660d3301a
4 changed files with 121 additions and 52 deletions

View File

@@ -40,59 +40,101 @@ SearchResultTreeItemDelegate::SearchResultTreeItemDelegate(int tabWidth, QObject
setTabWidth(tabWidth); 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<int, QString> 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; static const int iconSize = 16;
painter->save(); LayoutInfo info;
info.option = setOptions(index, option);
QStyleOptionViewItem opt = setOptions(index, option);
painter->setFont(opt.font);
QItemDelegate::drawBackground(painter, opt, index);
// ---- do the layout
QRect checkRect;
QRect pixmapRect;
QRect textRect;
// check mark // check mark
bool checkable = (index.model()->flags(index) & Qt::ItemIsUserCheckable); const bool checkable = (index.model()->flags(index) & Qt::ItemIsUserCheckable);
Qt::CheckState checkState = Qt::Unchecked; info.checkState = Qt::Unchecked;
if (checkable) { if (checkable) {
QVariant checkStateData = index.data(Qt::CheckStateRole); QVariant checkStateData = index.data(Qt::CheckStateRole);
checkState = static_cast<Qt::CheckState>(checkStateData.toInt()); info.checkState = static_cast<Qt::CheckState>(checkStateData.toInt());
checkRect = doCheck(opt, opt.rect, checkStateData); info.checkRect = doCheck(info.option, info.option.rect, checkStateData);
} }
// icon // icon
QIcon icon = index.model()->data(index, ItemDataRoles::ResultIconRole).value<QIcon>(); info.icon = index.data(ItemDataRoles::ResultIconRole).value<QIcon>();
if (!icon.isNull()) { if (!info.icon.isNull()) {
const QSize size = icon.actualSize(QSize(iconSize, iconSize)); const QSize size = info.icon.actualSize(QSize(iconSize, iconSize));
pixmapRect = QRect(0, 0, size.width(), size.height()); info.pixmapRect = QRect(0, 0, size.width(), size.height());
} }
// text // 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 // ---- draw the items
// icon // icon
if (!icon.isNull()) if (!info.icon.isNull())
icon.paint(painter, pixmapRect, option.decorationAlignment); info.icon.paint(painter, info.pixmapRect, info.option.decorationAlignment);
// line numbers // line numbers
int lineNumberAreaWidth = drawLineNumber(painter, opt, textRect, index); drawLineNumber(painter, info.option, info.lineNumberRect, index);
textRect.adjust(lineNumberAreaWidth, 0, 0, 0);
// text and focus/selection // text and focus/selection
drawText(painter, opt, textRect, index); drawText(painter, info.option, info.textRect, index);
QItemDelegate::drawFocus(painter, opt, opt.rect); QItemDelegate::drawFocus(painter, info.option, info.option.rect);
// check mark // check mark
if (checkable) if (info.checkRect.isValid())
QItemDelegate::drawCheck(painter, opt, checkRect, checkState); QItemDelegate::drawCheck(painter, info.option, info.checkRect, info.checkState);
painter->restore(); painter->restore();
} }
@@ -102,22 +144,31 @@ void SearchResultTreeItemDelegate::setTabWidth(int width)
m_tabString = QString(width, QLatin1Char(' ')); 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<QSize>().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 // returns the width of the line number area
int SearchResultTreeItemDelegate::drawLineNumber(QPainter *painter, const QStyleOptionViewItem &option, int SearchResultTreeItemDelegate::drawLineNumber(QPainter *painter, const QStyleOptionViewItem &option,
const QRect &rect, const QRect &rect,
const QModelIndex &index) const 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; const bool isSelected = option.state & QStyle::State_Selected;
QString lineText = QString::number(lineNumber); const std::pair<int, QString> numberInfo = lineNumberInfo(option, index);
int minimumLineNumberDigits = qMax((int)m_minimumLineNumberDigits, lineText.count()); if (numberInfo.first == 0)
int fontWidth = painter->fontMetrics().horizontalAdvance(QString(minimumLineNumberDigits, QLatin1Char('0'))); return 0;
int lineNumberAreaWidth = lineNumberAreaHorizontalPadding + fontWidth + lineNumberAreaHorizontalPadding;
QRect lineNumberAreaRect(rect); QRect lineNumberAreaRect(rect);
lineNumberAreaRect.setWidth(lineNumberAreaWidth); lineNumberAreaRect.setWidth(numberInfo.first);
QPalette::ColorGroup cg = QPalette::Normal; QPalette::ColorGroup cg = QPalette::Normal;
if (!(option.state & QStyle::State_Active)) 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 int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, nullptr) + 1;
const QRect rowRect = lineNumberAreaRect.adjusted(-textMargin, 0, textMargin-lineNumberAreaHorizontalPadding, 0); 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, void SearchResultTreeItemDelegate::drawText(QPainter *painter,
@@ -147,18 +198,15 @@ void SearchResultTreeItemDelegate::drawText(QPainter *painter,
const QRect &rect, const QRect &rect,
const QModelIndex &index) const const QModelIndex &index) const
{ {
QString text = index.model()->data(index, Qt::DisplayRole).toString(); const QString text = itemText(index);
// show number of subresults in displayString
if (index.model()->hasChildren(index)) {
text += QLatin1String(" (")
+ QString::number(index.model()->rowCount(index))
+ QLatin1Char(')');
}
const int searchTermStart = index.model()->data(index, ItemDataRoles::ResultBeginColumnNumberRole).toInt(); const int searchTermStart = index.model()->data(index, ItemDataRoles::ResultBeginColumnNumberRole).toInt();
int searchTermLength = index.model()->data(index, ItemDataRoles::SearchTermLengthRole).toInt(); int searchTermLength = index.model()->data(index, ItemDataRoles::SearchTermLengthRole).toInt();
if (searchTermStart < 0 || searchTermStart >= text.length() || searchTermLength < 1) { 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; return;
} }

View File

@@ -30,20 +30,31 @@
namespace Core { namespace Core {
namespace Internal { namespace Internal {
struct LayoutInfo
{
QRect checkRect;
QRect pixmapRect;
QRect textRect;
QRect lineNumberRect;
QIcon icon;
Qt::CheckState checkState;
QStyleOptionViewItem option;
};
class SearchResultTreeItemDelegate: public QItemDelegate class SearchResultTreeItemDelegate: public QItemDelegate
{ {
public: public:
SearchResultTreeItemDelegate(int tabWidth, QObject *parent = nullptr); SearchResultTreeItemDelegate(int tabWidth, QObject *parent = nullptr);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setTabWidth(int width); void setTabWidth(int width);
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
private: private:
LayoutInfo getLayoutInfo(const QStyleOptionViewItem &option, const QModelIndex &index) const;
int drawLineNumber(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, 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, void drawText(QPainter *painter, const QStyleOptionViewItem &option,
const QRect &rect, const QModelIndex &index) const; const QRect &rect, const QModelIndex &index) const;
QString m_tabString; QString m_tabString;
static const int m_minimumLineNumberDigits = 6;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -44,6 +44,8 @@ SearchResultTreeView::SearchResultTreeView(QWidget *parent)
setIndentation(14); setIndentation(14);
setUniformRowHeights(true); setUniformRowHeights(true);
setExpandsOnDoubleClick(true); setExpandsOnDoubleClick(true);
header()->setSectionResizeMode(QHeaderView::ResizeToContents);
header()->setStretchLastSection(false);
header()->hide(); header()->hide();
connect(this, &SearchResultTreeView::activated, connect(this, &SearchResultTreeView::activated,
@@ -93,6 +95,13 @@ void SearchResultTreeView::keyPressEvent(QKeyEvent *event)
TreeView::keyPressEvent(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) void SearchResultTreeView::emitJumpToSearchResult(const QModelIndex &index)
{ {
if (model()->data(index, ItemDataRoles::IsGeneratedRole).toBool()) if (model()->data(index, ItemDataRoles::IsGeneratedRole).toBool())

View File

@@ -50,6 +50,7 @@ public:
void addResults(const QList<SearchResultItem> &items, SearchResult::AddMode mode); void addResults(const QList<SearchResultItem> &items, SearchResult::AddMode mode);
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;
bool event(QEvent *e) override;
signals: signals:
void jumpToSearchResult(const SearchResultItem &item); void jumpToSearchResult(const SearchResultItem &item);