From 24e1a8fc4232d8947b88dca07c2421d73fc9363d Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 12 Jul 2017 19:50:29 +0100 Subject: [PATCH] Add a new 'stretchy size hint' mode for RDHeaderView * The basic idea here is to have a reasonable middleground between ResizeToContents and Stretch. We want to show at *least* enough for the contents, but the remaining space should be shared between the columns according to some proportions. * That way you don't end up with one huge column and several tiny ones that are just big enough but no more, but all data is still visible. --- qrenderdoc/Widgets/Extended/RDHeaderView.cpp | 187 ++++++++++++++++++- qrenderdoc/Widgets/Extended/RDHeaderView.h | 12 ++ 2 files changed, 198 insertions(+), 1 deletion(-) diff --git a/qrenderdoc/Widgets/Extended/RDHeaderView.cpp b/qrenderdoc/Widgets/Extended/RDHeaderView.cpp index df2e7f016..ce490512a 100644 --- a/qrenderdoc/Widgets/Extended/RDHeaderView.cpp +++ b/qrenderdoc/Widgets/Extended/RDHeaderView.cpp @@ -45,6 +45,8 @@ RDHeaderView::RDHeaderView(Qt::Orientation orient, QWidget *parent) : QHeaderView(orient, parent) { m_sectionPreview = new QLabel(this); + + setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); } RDHeaderView::~RDHeaderView() @@ -68,6 +70,8 @@ void RDHeaderView::setModel(QAbstractItemModel *model) &RDHeaderView::headerDataChanged); QObject::disconnect(m, &QAbstractItemModel::columnsInserted, this, &RDHeaderView::columnsInserted); + QObject::disconnect(m, &QAbstractItemModel::rowsInserted, this, &RDHeaderView::rowsChanged); + QObject::disconnect(m, &QAbstractItemModel::rowsRemoved, this, &RDHeaderView::rowsChanged); } QHeaderView::setModel(model); @@ -75,6 +79,8 @@ void RDHeaderView::setModel(QAbstractItemModel *model) QObject::connect(model, &QAbstractItemModel::headerDataChanged, this, &RDHeaderView::headerDataChanged); QObject::connect(model, &QAbstractItemModel::columnsInserted, this, &RDHeaderView::columnsInserted); + QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &RDHeaderView::rowsChanged); + QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &RDHeaderView::rowsChanged); } void RDHeaderView::reset() @@ -242,6 +248,12 @@ void RDHeaderView::resizeSection(int logicalIndex, int size) void RDHeaderView::resizeSections(QHeaderView::ResizeMode mode) { + if(!m_sectionStretchHints.isEmpty()) + { + resizeSectionsWithHints(); + return; + } + if(!m_customSizing) return resizeSections(mode); @@ -279,6 +291,7 @@ void RDHeaderView::resizeSections(const QList &sizes) { QHeaderView::resizeSection(i, sizes[i]); } + return; } for(int i = 0; i < qMin(sizes.count(), m_sections.count()); i++) @@ -312,6 +325,161 @@ bool RDHeaderView::hasGroupTitle(int columnIndex) const return false; } +void RDHeaderView::cacheSectionMinSizes() +{ + m_sectionMinSizes.resize(count()); + m_sectionMinSizesTotal = 0; + + for(int i = 0; i < m_sectionMinSizes.count(); i++) + { + int sz = 0; + + // see if we can fetch the column/row size hint from the item view + QAbstractItemView *view = qobject_cast(parent()); + if(view) + { + if(orientation() == Qt::Horizontal) + sz = view->sizeHintForColumn(i); + else + sz = view->sizeHintForRow(i); + } + + // also include the size for the header as another minimum + if(orientation() == Qt::Horizontal) + sz = qMax(sz, sectionSizeFromContents(i).width()); + else + sz = qMax(sz, sectionSizeFromContents(i).height()); + + // finally respect the minimum section size specified + sz = qMax(sz, minimumSectionSize()); + + // update the minimum size for this section and count the total which we'll need + m_sectionMinSizes[i] = sz; + m_sectionMinSizesTotal += m_sectionMinSizes[i]; + } +} + +void RDHeaderView::resizeSectionsWithHints() +{ + if(m_sectionMinSizes.count() == 0) + return; + + QVector sizes = m_sectionMinSizes; + + int available = 0; + + if(orientation() == Qt::Horizontal) + available = rect().width(); + else + available = rect().height(); + + // see if we even have any extra space to allocate + if(available > m_sectionMinSizesTotal) + { + // this is how much space we can allocate to stretch sections + available -= m_sectionMinSizesTotal; + + // distribute the available space between the sections. Dividing by the total stretch tells us + // how many 'whole' multiples we can allocate: + int wholeMultiples = available / m_sectionStretchHintTotal; + + if(wholeMultiples > 0) + { + for(int i = 0; i < sizes.count() && i < m_sectionStretchHints.count(); i++) + { + int hint = m_sectionStretchHints[i]; + if(hint > 0) + sizes[i] += wholeMultiples * hint; + } + } + + available -= wholeMultiples * m_sectionStretchHintTotal; + + // we now have a small amount (less than m_sectionStretchHintTotal) of extra space to allocate. + // we still want to assign this leftover proportional to the hints, otherwise we'd end up with a + // stair-stepping effect. + // To do this we calculate hint/total for each section then loop around adding on fractional + // components to the sizes until one is above 1, then it gets a pixel, and we keep going until + // all the remainder is allocated + QVector fractions, increment; + fractions.resize(sizes.count()); + increment.resize(sizes.count()); + + // set up increments + for(int i = 0; i < sizes.count(); i++) + { + // don't assign any space to sections with negative hints, or sections without hints + if(i >= m_sectionStretchHints.count() || m_sectionStretchHints[i] <= 0) + { + increment[i] = 0.0f; + continue; + } + + increment[i] = float(m_sectionStretchHints[i]) / float(m_sectionStretchHintTotal); + } + + while(available > 0) + { + // loop along each section incrementing it. + for(int i = 0; i < fractions.count(); i++) + { + fractions[i] += increment[i]; + + // if we have a whole pixel now, assign it + if(fractions[i] > 1.0f) + { + fractions[i] -= 1.0f; + sizes[i]++; + available--; + + // if we've assigned all pixels, stop + if(available == 0) + break; + } + } + } + + for(int pix = 0; pix < available; pix++) + { + int minSection = 0; + for(int i = 1; i < sizes.count(); i++) + { + // don't assign any space to sections with negative hints + if(i < m_sectionStretchHints.count() && m_sectionStretchHints[i] <= 0) + continue; + + if(sizes[i] < sizes[minSection]) + minSection = i; + } + + sizes[minSection]++; + } + } + + resizeSections(sizes.toList()); +} + +void RDHeaderView::setColumnStretchHints(const QList &hints) +{ + if(hints.count() != count()) + qCritical() << "Got" << hints.count() << "hints, but have" << count() << "columns"; + m_sectionStretchHints = hints; + + m_sectionStretchHintTotal = 0; + for(int h : m_sectionStretchHints) + { + if(h > 0) + m_sectionStretchHintTotal += h; + } + + // we take control of the sizing, we don't currently support custom resizing AND stretchy size + // hints. + QHeaderView::setSectionResizeMode(QHeaderView::Fixed); + + cacheSectionMinSizes(); + resizeSectionsWithHints(); +} + void RDHeaderView::headerDataChanged(Qt::Orientation orientation, int logicalFirst, int logicalLast) { if(m_customSizing) @@ -324,6 +492,15 @@ void RDHeaderView::columnsInserted(const QModelIndex &parent, int first, int las cacheSections(); } +void RDHeaderView::rowsChanged(const QModelIndex &parent, int first, int last) +{ + if(!m_sectionStretchHints.isEmpty()) + { + cacheSectionMinSizes(); + resizeSectionsWithHints(); + } +} + void RDHeaderView::mousePressEvent(QMouseEvent *event) { int mousePos = event->x(); @@ -658,7 +835,7 @@ void RDHeaderView::paintSection(QPainter *painter, const QRect &rect, int sectio QVariant textAlignment = m->headerData(section, orientation(), Qt::TextAlignmentRole); opt.rect = rect; opt.section = section; - opt.textAlignment = Qt::AlignLeft | Qt::AlignVCenter; + opt.textAlignment = defaultAlignment(); opt.iconAlignment = Qt::AlignVCenter; QVariant variant; @@ -752,3 +929,11 @@ void RDHeaderView::currentChanged(const QModelIndex ¤t, const QModelIndex } } } + +void RDHeaderView::updateGeometries() +{ + if(!m_sectionStretchHints.isEmpty()) + resizeSectionsWithHints(); + + QHeaderView::updateGeometries(); +} diff --git a/qrenderdoc/Widgets/Extended/RDHeaderView.h b/qrenderdoc/Widgets/Extended/RDHeaderView.h index dc490edca..609d6fd65 100644 --- a/qrenderdoc/Widgets/Extended/RDHeaderView.h +++ b/qrenderdoc/Widgets/Extended/RDHeaderView.h @@ -58,6 +58,8 @@ public: bool hasGroupGap(int columnIndex) const; bool hasGroupTitle(int columnIndex) const; + void setColumnStretchHints(const QList &hints); + void setColumnGroupRole(int role) { m_columnGroupRole = role; } int columnGroupRole() const { return m_columnGroupRole; } void setPinnedColumns(int numColumns) { m_pinnedColumns = numColumns; } @@ -67,6 +69,7 @@ public: public slots: void headerDataChanged(Qt::Orientation orientation, int logicalFirst, int logicalLast); void columnsInserted(const QModelIndex &parent, int first, int last); + void rowsChanged(const QModelIndex &parent, int first, int last); protected: void mousePressEvent(QMouseEvent *event) override; @@ -77,6 +80,8 @@ protected: void paintSection(QPainter *painter, const QRect &rect, int section) const override; void currentChanged(const QModelIndex ¤t, const QModelIndex &old) override; + void updateGeometries() override; + enum ResizeType { NoResize, @@ -89,6 +94,8 @@ protected: int m_cursorPos; void cacheSections(); + void resizeSectionsWithHints(); + void cacheSectionMinSizes(); struct SectionData { @@ -105,6 +112,11 @@ protected: bool m_suppressSectionCache = false; bool m_customSizing = false; + QList m_sectionStretchHints; + int m_sectionStretchHintTotal = 0; + QVector m_sectionMinSizes; + int m_sectionMinSizesTotal = 0; + int m_columnGroupRole = 0; int m_pinnedColumns = 0;