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.
This commit is contained in:
baldurk
2017-07-12 19:50:29 +01:00
parent 1e840356a1
commit 24e1a8fc42
2 changed files with 198 additions and 1 deletions
+186 -1
View File
@@ -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<int> &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<QAbstractItemView *>(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<int> 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<float> 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<int> &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 &current, const QModelIndex
}
}
}
void RDHeaderView::updateGeometries()
{
if(!m_sectionStretchHints.isEmpty())
resizeSectionsWithHints();
QHeaderView::updateGeometries();
}
@@ -58,6 +58,8 @@ public:
bool hasGroupGap(int columnIndex) const;
bool hasGroupTitle(int columnIndex) const;
void setColumnStretchHints(const QList<int> &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 &current, 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<int> m_sectionStretchHints;
int m_sectionStretchHintTotal = 0;
QVector<int> m_sectionMinSizes;
int m_sectionMinSizesTotal = 0;
int m_columnGroupRole = 0;
int m_pinnedColumns = 0;