mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-04 17:10:47 +00:00
986 lines
27 KiB
C++
986 lines
27 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2019-2021 Baldur Karlsson
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#include "RDHeaderView.h"
|
|
#include <QAbstractScrollArea>
|
|
#include <QLabel>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QPixmap>
|
|
#include <QPointer>
|
|
#include <QScrollBar>
|
|
#include <QTreeView>
|
|
#include "Code/QRDUtils.h"
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// this file contains a few hardcoded assumptions for my use case, especially
|
|
// with the 'custom sizing' mode that allows merging sections and pinning sections
|
|
// and so on.
|
|
//
|
|
// * No handling for moving/rearranging/hiding sections with the custom sizing
|
|
// mode. Just needs more careful handling and distinguishing between logical
|
|
// and visual indices.
|
|
// * Probably a few places vertical orientation isn't handled right, but that
|
|
// shouldn't be too bad.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
RDHeaderView::RDHeaderView(Qt::Orientation orient, QWidget *parent) : QHeaderView(orient, parent)
|
|
{
|
|
m_sectionPreview = new QLabel(this);
|
|
|
|
setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
|
|
|
QTreeView *treeView = qobject_cast<QTreeView *>(parent);
|
|
if(treeView)
|
|
{
|
|
QObject::connect(treeView, &QTreeView::expanded,
|
|
[this](const QModelIndex &) { rowsChanged(QModelIndex(), 0, 0); });
|
|
QObject::connect(treeView, &QTreeView::collapsed,
|
|
[this](const QModelIndex &) { rowsChanged(QModelIndex(), 0, 0); });
|
|
}
|
|
}
|
|
|
|
RDHeaderView::~RDHeaderView()
|
|
{
|
|
}
|
|
|
|
QSize RDHeaderView::sizeHint() const
|
|
{
|
|
if(!m_customSizing)
|
|
return QHeaderView::sizeHint();
|
|
return m_sizeHint;
|
|
}
|
|
|
|
void RDHeaderView::setModel(QAbstractItemModel *model)
|
|
{
|
|
QAbstractItemModel *m = this->model();
|
|
|
|
if(m)
|
|
{
|
|
QObject::disconnect(m, &QAbstractItemModel::headerDataChanged, this,
|
|
&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);
|
|
|
|
if(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);
|
|
QObject::connect(model, &QAbstractItemModel::dataChanged,
|
|
[this](const QModelIndex &topLeft, const QModelIndex &bottomRight,
|
|
const QVector<int> &roles) {
|
|
if(roles.contains(Qt::DisplayRole))
|
|
rowsChanged(QModelIndex(), 0, 0);
|
|
});
|
|
}
|
|
}
|
|
|
|
void RDHeaderView::reset()
|
|
{
|
|
if(m_customSizing)
|
|
cacheSections();
|
|
|
|
QHeaderView::reset();
|
|
}
|
|
|
|
void RDHeaderView::cacheSections()
|
|
{
|
|
if(m_suppressSectionCache)
|
|
return;
|
|
|
|
QAbstractItemModel *m = this->model();
|
|
|
|
int oldCount = m_sections.count();
|
|
m_sections.resize(m->columnCount());
|
|
|
|
// give new sections a default minimum size
|
|
for(int col = oldCount; col < m_sections.count(); col++)
|
|
m_sections[col].size = 10;
|
|
|
|
for(int col = 0; col < m_sections.count(); col++)
|
|
{
|
|
if(m_columnGroupRole > 0)
|
|
{
|
|
QVariant v = m->data(m->index(0, col), m_columnGroupRole);
|
|
if(v.isValid())
|
|
m_sections[col].group = v.toInt();
|
|
else
|
|
m_sections[col].group = -m_columnGroupRole - col;
|
|
|
|
if(col > 0)
|
|
{
|
|
m_sections[col - 1].groupGap =
|
|
(m_sections[col].group != m_sections[col - 1].group) && m_sections[col].group >= 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_sections[col].group = col;
|
|
m_sections[col].groupGap = true;
|
|
}
|
|
}
|
|
|
|
int accum = 0;
|
|
|
|
for(int col = 0; col < m_sections.count(); col++)
|
|
{
|
|
if(col == m_pinnedColumns)
|
|
m_pinnedWidth = accum;
|
|
|
|
m_sections[col].offset = accum;
|
|
accum += m_sections[col].size;
|
|
|
|
if(hasGroupGap(col))
|
|
accum += groupGapSize();
|
|
}
|
|
|
|
if(m_pinnedColumns >= m_sections.count())
|
|
m_pinnedWidth = m_pinnedColumns;
|
|
|
|
QHeaderView::initializeSections();
|
|
|
|
QStyleOptionHeader opt;
|
|
initStyleOption(&opt);
|
|
|
|
QFont f = font();
|
|
f.setBold(true);
|
|
|
|
opt.section = 0;
|
|
opt.fontMetrics = QFontMetrics(f);
|
|
opt.text = m->headerData(0, orientation(), Qt::DisplayRole).toString();
|
|
|
|
m_sizeHint = style()->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), this);
|
|
m_sizeHint.setWidth(accum);
|
|
|
|
viewport()->update(viewport()->rect());
|
|
}
|
|
|
|
int RDHeaderView::sectionSize(int logicalIndex) const
|
|
{
|
|
if(m_customSizing)
|
|
{
|
|
if(logicalIndex < 0 || logicalIndex >= m_sections.count())
|
|
return 0;
|
|
|
|
return m_sections[logicalIndex].size;
|
|
}
|
|
|
|
return QHeaderView::sectionSize(logicalIndex);
|
|
}
|
|
|
|
int RDHeaderView::sectionViewportPosition(int logicalIndex) const
|
|
{
|
|
if(m_customSizing)
|
|
{
|
|
if(logicalIndex < 0 || logicalIndex >= m_sections.count())
|
|
return -1;
|
|
|
|
int offs = m_sections[logicalIndex].offset;
|
|
|
|
if(logicalIndex >= m_pinnedColumns)
|
|
offs -= offset();
|
|
|
|
return offs;
|
|
}
|
|
|
|
return QHeaderView::sectionViewportPosition(logicalIndex);
|
|
}
|
|
|
|
int RDHeaderView::visualIndexAt(int position) const
|
|
{
|
|
if(m_customSizing)
|
|
{
|
|
if(m_sections.isEmpty())
|
|
return -1;
|
|
|
|
if(position >= m_pinnedWidth)
|
|
position += offset();
|
|
|
|
SectionData search;
|
|
search.offset = position;
|
|
auto it = std::lower_bound(
|
|
m_sections.begin(), m_sections.end(), search,
|
|
[](const SectionData &a, const SectionData &b) { return a.offset <= b.offset; });
|
|
|
|
if(it != m_sections.begin())
|
|
--it;
|
|
|
|
if(it->offset <= position &&
|
|
position < (it->offset + it->size + (it->groupGap ? groupGapSize() : 0)))
|
|
return (it - m_sections.begin());
|
|
|
|
return -1;
|
|
}
|
|
|
|
return QHeaderView::visualIndexAt(position);
|
|
}
|
|
|
|
int RDHeaderView::logicalIndexAt(int position) const
|
|
{
|
|
if(m_customSizing)
|
|
return visualIndexAt(position);
|
|
|
|
return QHeaderView::logicalIndexAt(position);
|
|
}
|
|
|
|
int RDHeaderView::count() const
|
|
{
|
|
if(m_customSizing)
|
|
return m_sections.count();
|
|
|
|
return QHeaderView::count();
|
|
}
|
|
|
|
void RDHeaderView::resizeSection(int logicalIndex, int size)
|
|
{
|
|
if(!m_customSizing)
|
|
return QHeaderView::resizeSection(logicalIndex, size);
|
|
|
|
if(logicalIndex >= 0 && logicalIndex < m_sections.count())
|
|
{
|
|
int oldSize = m_sections[logicalIndex].size;
|
|
m_sections[logicalIndex].size = size;
|
|
|
|
emit sectionResized(logicalIndex, oldSize, size);
|
|
}
|
|
|
|
cacheSections();
|
|
}
|
|
|
|
void RDHeaderView::resizeSections(QHeaderView::ResizeMode mode)
|
|
{
|
|
if(!m_sectionStretchHints.isEmpty())
|
|
{
|
|
resizeSectionsWithHints();
|
|
return;
|
|
}
|
|
|
|
if(!m_customSizing)
|
|
return QHeaderView::resizeSections(mode);
|
|
|
|
if(mode != ResizeToContents)
|
|
return;
|
|
|
|
QAbstractItemModel *m = this->model();
|
|
|
|
int rowCount = m->rowCount();
|
|
|
|
for(int col = 0; col < m_sections.count(); col++)
|
|
{
|
|
QSize sz;
|
|
|
|
for(int row = 0; row < rowCount; row++)
|
|
{
|
|
QVariant v = m->data(m->index(row, col), Qt::SizeHintRole);
|
|
if(v.isValid() && v.canConvert<QSize>())
|
|
sz = sz.expandedTo(v.value<QSize>());
|
|
}
|
|
|
|
int oldSize = m_sections[col].size;
|
|
|
|
m_sections[col].size = sz.width();
|
|
|
|
emit sectionResized(col, oldSize, sz.width());
|
|
}
|
|
}
|
|
|
|
void RDHeaderView::resizeSections(const QList<int> &sizes)
|
|
{
|
|
if(!m_customSizing)
|
|
{
|
|
for(int i = 0; i < qMin(sizes.count(), QHeaderView::count()); i++)
|
|
{
|
|
QHeaderView::resizeSection(i, sizes[i]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
for(int i = 0; i < qMin(sizes.count(), m_sections.count()); i++)
|
|
{
|
|
int oldSize = m_sections[i].size;
|
|
|
|
m_sections[i].size = sizes[i];
|
|
|
|
emit sectionResized(i, oldSize, sizes[i]);
|
|
}
|
|
|
|
cacheSections();
|
|
}
|
|
|
|
bool RDHeaderView::hasGroupGap(int columnIndex) const
|
|
{
|
|
if(columnIndex >= 0 && columnIndex < m_sections.count())
|
|
return m_sections[columnIndex].groupGap;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool RDHeaderView::hasGroupTitle(int columnIndex) const
|
|
{
|
|
if(columnIndex == m_sections.count() - 1)
|
|
return true;
|
|
|
|
if(columnIndex >= 0 && columnIndex < m_sections.count())
|
|
return m_sections[columnIndex].groupGap || m_sections[columnIndex].group < 0;
|
|
|
|
return false;
|
|
}
|
|
|
|
void RDHeaderView::cacheSectionMinSizes()
|
|
{
|
|
m_sectionMinSizes.resize(count());
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void RDHeaderView::resizeSectionsWithHints()
|
|
{
|
|
if(m_sectionMinSizes.isEmpty() || m_sectionStretchHints.isEmpty())
|
|
return;
|
|
|
|
QVector<int> sizes = m_sectionMinSizes;
|
|
int minsizesTotal = 0;
|
|
int stretchHintTotal = 0;
|
|
|
|
for(int i = 0; i < count() && i < sizes.count() && i < m_sectionStretchHints.count(); i++)
|
|
{
|
|
if(isSectionHidden(i))
|
|
sizes[i] = 0;
|
|
else if(m_sectionStretchHints[i] >= 0)
|
|
stretchHintTotal += m_sectionStretchHints[i];
|
|
|
|
minsizesTotal += sizes[i];
|
|
}
|
|
|
|
if(stretchHintTotal == 0)
|
|
return;
|
|
|
|
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 > minsizesTotal)
|
|
{
|
|
// this is how much space we can allocate to stretch sections
|
|
available -= minsizesTotal;
|
|
|
|
// distribute the available space between the sections. Dividing by the total stretch tells us
|
|
// how many 'whole' multiples we can allocate:
|
|
int wholeMultiples = available / stretchHintTotal;
|
|
|
|
if(wholeMultiples > 0)
|
|
{
|
|
for(int i = 0; i < sizes.count() && i < m_sectionStretchHints.count(); i++)
|
|
{
|
|
int hint = m_sectionStretchHints[i];
|
|
if(hint > 0 && !isSectionHidden(i))
|
|
sizes[i] += wholeMultiples * hint;
|
|
}
|
|
}
|
|
|
|
available -= wholeMultiples * stretchHintTotal;
|
|
|
|
// we now have a small amount (less than stretchHintTotal) 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, or
|
|
// hidden sections
|
|
if(i >= m_sectionStretchHints.count() || m_sectionStretchHints[i] <= 0 || isSectionHidden(i))
|
|
{
|
|
increment[i] = 0.0f;
|
|
continue;
|
|
}
|
|
|
|
increment[i] = float(m_sectionStretchHints[i]) / float(stretchHintTotal);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// 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::setPinnedColumns(int numColumns, QAbstractScrollArea *scroll)
|
|
{
|
|
m_pinnedColumns = numColumns;
|
|
QObject::connect(scroll->horizontalScrollBar(), &QScrollBar::valueChanged,
|
|
[this]() { viewport()->update(); });
|
|
}
|
|
|
|
void RDHeaderView::setRootIndex(const QModelIndex &index)
|
|
{
|
|
QHeaderView::setRootIndex(index);
|
|
|
|
// need to enqueue this after the root index is actually processed (this function is called
|
|
// *before* the root index changes).
|
|
if(!m_sectionStretchHints.isEmpty())
|
|
{
|
|
QPointer<RDHeaderView> ptr = this;
|
|
GUIInvoke::defer(this, [this]() {
|
|
cacheSectionMinSizes();
|
|
resizeSectionsWithHints();
|
|
});
|
|
}
|
|
}
|
|
|
|
void RDHeaderView::headerDataChanged(Qt::Orientation orientation, int logicalFirst, int logicalLast)
|
|
{
|
|
if(m_customSizing)
|
|
cacheSections();
|
|
}
|
|
|
|
void RDHeaderView::columnsInserted(const QModelIndex &parent, int first, int last)
|
|
{
|
|
if(m_customSizing)
|
|
cacheSections();
|
|
}
|
|
|
|
void RDHeaderView::rowsChanged(const QModelIndex &, int, int)
|
|
{
|
|
if(!m_sectionStretchHints.isEmpty())
|
|
{
|
|
cacheSectionMinSizes();
|
|
resizeSectionsWithHints();
|
|
}
|
|
}
|
|
|
|
void RDHeaderView::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
int mousePos = event->x();
|
|
int idx = logicalIndexAt(mousePos);
|
|
|
|
if(sectionsMovable() && idx >= 0 && event->buttons() == Qt::LeftButton)
|
|
{
|
|
int secSize = sectionSize(idx);
|
|
int secPos = sectionViewportPosition(idx);
|
|
|
|
int handleWidth = style()->pixelMetric(QStyle::PM_HeaderGripMargin, 0, this);
|
|
|
|
if(secPos >= 0 && secSize > 0 && mousePos >= secPos + handleWidth &&
|
|
mousePos <= secPos + secSize - handleWidth)
|
|
{
|
|
m_movingSection = idx;
|
|
|
|
m_sectionPreview->resize(secSize, height());
|
|
|
|
QPixmap preview(m_sectionPreview->size());
|
|
preview.fill(QColor::fromRgba(qRgba(0, 0, 0, 100)));
|
|
|
|
QPainter painter(&preview);
|
|
painter.setOpacity(0.75f);
|
|
paintSection(&painter, QRect(QPoint(0, 0), m_sectionPreview->size()), idx);
|
|
painter.end();
|
|
|
|
m_cursorPos = QCursor::pos().x();
|
|
|
|
m_sectionPreview->setPixmap(preview);
|
|
|
|
m_sectionPreviewOffset = mousePos - secPos;
|
|
|
|
m_sectionPreview->move(mousePos - m_sectionPreviewOffset, 0);
|
|
m_sectionPreview->show();
|
|
|
|
QHeaderView::mousePressEvent(event);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(m_customSizing)
|
|
{
|
|
m_resizeState = checkResizing(event);
|
|
m_cursorPos = QCursor::pos().x();
|
|
|
|
return QAbstractItemView::mousePressEvent(event);
|
|
}
|
|
|
|
QHeaderView::mousePressEvent(event);
|
|
}
|
|
|
|
void RDHeaderView::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
if(m_movingSection >= 0)
|
|
{
|
|
m_sectionPreview->move(event->x() - m_sectionPreviewOffset, 0);
|
|
return;
|
|
}
|
|
|
|
if(m_customSizing)
|
|
{
|
|
if(m_resizeState.first == NoResize || m_resizeState.second < 0 ||
|
|
m_resizeState.second >= m_sections.count())
|
|
{
|
|
auto res = checkResizing(event);
|
|
|
|
bool hasCursor = testAttribute(Qt::WA_SetCursor);
|
|
|
|
if(res.first != NoResize)
|
|
{
|
|
if(!hasCursor)
|
|
setCursor(Qt::SplitHCursor);
|
|
}
|
|
else if(hasCursor)
|
|
{
|
|
unsetCursor();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int curX = QCursor::pos().x();
|
|
int delta = curX - m_cursorPos;
|
|
|
|
int idx = m_resizeState.second;
|
|
|
|
if(m_resizeState.first == LeftResize && idx > 0)
|
|
idx--;
|
|
|
|
// batch the cache update
|
|
m_suppressSectionCache = true;
|
|
|
|
int firstCol = idx;
|
|
int lastCol = idx;
|
|
|
|
// idx is the last in a group, so search backwards to see if there are neighbour sections we
|
|
// should share the resize with
|
|
while(firstCol > 0 && m_sections[firstCol - 1].group == m_sections[lastCol].group)
|
|
firstCol--;
|
|
|
|
// how much space could we lose on the columns, in total
|
|
int freeSpace = 0;
|
|
for(int col = firstCol; col <= lastCol; col++)
|
|
freeSpace += m_sections[col].size - minimumSectionSize();
|
|
|
|
int numCols = lastCol - firstCol + 1;
|
|
|
|
// spread the delta amonst the colummns
|
|
int perSectionDelta = delta / numCols;
|
|
|
|
// call resizeSection to emit the sectionResized signal but we set m_suppressSectionCache so
|
|
// we won't cache sections.
|
|
for(int col = firstCol; col <= lastCol; col++)
|
|
resizeSection(col, qMax(minimumSectionSize(), m_sections[col].size + perSectionDelta));
|
|
|
|
// if there was an uneven spread, a few pixels will remain
|
|
int remainder = delta - perSectionDelta * numCols;
|
|
|
|
// loop around for the remainder pixels, assigning them one by one to the smallest/largest
|
|
// column.
|
|
// this is inefficient but remainder is very small - at most 3.
|
|
int step = remainder < 0 ? -1 : 1;
|
|
for(int i = 0; i < qAbs(remainder); i++)
|
|
{
|
|
int chosenCol = firstCol;
|
|
for(int col = firstCol; col <= lastCol; col++)
|
|
{
|
|
if(step > 0 && m_sections[col].size < m_sections[chosenCol].size)
|
|
chosenCol = col;
|
|
else if(step < 0 && m_sections[col].size > m_sections[chosenCol].size)
|
|
chosenCol = col;
|
|
}
|
|
|
|
resizeSection(chosenCol, qMax(minimumSectionSize(), m_sections[chosenCol].size + step));
|
|
}
|
|
|
|
// only updating the cursor when the section is moving means that it becomes 'sticky'. If we
|
|
// try to size down below the minimum size and keep going then it doesn't start resizing up
|
|
// until it passes the divider again.
|
|
int appliedDelta = delta;
|
|
|
|
// if we were resizing down, at best we removed the remaining free space
|
|
if(delta < 0)
|
|
appliedDelta = qMax(delta, -freeSpace);
|
|
|
|
m_cursorPos += appliedDelta;
|
|
|
|
m_suppressSectionCache = false;
|
|
|
|
cacheSections();
|
|
}
|
|
|
|
return QAbstractItemView::mouseMoveEvent(event);
|
|
}
|
|
|
|
QHeaderView::mouseMoveEvent(event);
|
|
}
|
|
|
|
QPair<RDHeaderView::ResizeType, int> RDHeaderView::checkResizing(QMouseEvent *event)
|
|
{
|
|
int mousePos = event->x();
|
|
int idx = logicalIndexAt(mousePos);
|
|
|
|
bool hasCursor = testAttribute(Qt::WA_SetCursor);
|
|
bool cursorSet = false;
|
|
|
|
bool leftResize = idx > 0 && (m_sections[idx - 1].group != m_sections[idx].group);
|
|
bool rightResize = idx >= 0 && hasGroupTitle(idx);
|
|
|
|
if(leftResize || rightResize)
|
|
{
|
|
int secSize = sectionSize(idx);
|
|
int secPos = sectionViewportPosition(idx);
|
|
|
|
int handleWidth = style()->pixelMetric(QStyle::PM_HeaderGripMargin, 0, this);
|
|
|
|
int gapWidth = 0;
|
|
if(hasGroupGap(idx))
|
|
gapWidth = groupGapSize();
|
|
|
|
if(leftResize && secPos >= 0 && secSize > 0 && mousePos < secPos + handleWidth)
|
|
{
|
|
return qMakePair(LeftResize, idx);
|
|
}
|
|
if(rightResize && secPos >= 0 && secSize > 0 &&
|
|
mousePos > secPos + secSize - handleWidth - gapWidth)
|
|
{
|
|
return qMakePair(RightResize, idx);
|
|
}
|
|
}
|
|
|
|
return qMakePair(NoResize, -1);
|
|
}
|
|
|
|
void RDHeaderView::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
if(m_movingSection >= 0)
|
|
{
|
|
int mousePos = event->x();
|
|
int idx = logicalIndexAt(mousePos);
|
|
|
|
m_sectionPreview->hide();
|
|
|
|
if(idx >= 0)
|
|
{
|
|
int secSize = sectionSize(idx);
|
|
int secPos = sectionPosition(idx);
|
|
|
|
int srcSection = visualIndex(m_movingSection);
|
|
int dstSection = visualIndex(idx);
|
|
|
|
if(srcSection >= 0 && dstSection >= 0 && srcSection != dstSection)
|
|
{
|
|
// the half-way point of the section decides whether we're dropping to the left
|
|
// or the right of it.
|
|
if(mousePos < secPos + secSize / 2)
|
|
{
|
|
// if we're moving from the left, place it to the left of dstSection
|
|
if(srcSection < dstSection)
|
|
moveSection(srcSection, dstSection - 1);
|
|
else
|
|
moveSection(srcSection, dstSection);
|
|
}
|
|
else
|
|
{
|
|
// if we're moving it from the right, place it to the right of dstSection
|
|
if(srcSection > dstSection)
|
|
moveSection(srcSection, dstSection + 1);
|
|
else
|
|
moveSection(srcSection, dstSection);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_movingSection = -1;
|
|
|
|
if(m_customSizing)
|
|
m_resizeState = qMakePair(NoResize, -1);
|
|
|
|
QHeaderView::mouseReleaseEvent(event);
|
|
}
|
|
|
|
void RDHeaderView::paintEvent(QPaintEvent *e)
|
|
{
|
|
if(!m_customSizing)
|
|
return QHeaderView::paintEvent(e);
|
|
|
|
if(count() == 0)
|
|
return;
|
|
|
|
QPainter painter(viewport());
|
|
|
|
int start = qMax(visualIndexAt(e->rect().left()), 0);
|
|
int end = visualIndexAt(e->rect().right());
|
|
|
|
if(end == -1)
|
|
end = count() - 1;
|
|
|
|
// make sure we always paint the whole header for any merged headers
|
|
while(start > 0 && !hasGroupTitle(start - 1))
|
|
start--;
|
|
while(end < m_sections.count() && !hasGroupTitle(end))
|
|
end++;
|
|
|
|
QRect accumRect;
|
|
for(int i = start; i <= end; ++i)
|
|
{
|
|
int pos = sectionViewportPosition(i);
|
|
int size = sectionSize(i);
|
|
|
|
if(!hasGroupGap(i) && pos < 0)
|
|
{
|
|
size += pos;
|
|
pos = 0;
|
|
}
|
|
|
|
// either set or accumulate this section's rect
|
|
if(accumRect.isEmpty())
|
|
accumRect.setRect(pos, 0, size, viewport()->height());
|
|
else
|
|
accumRect.setWidth(accumRect.width() + size);
|
|
|
|
if(hasGroupTitle(i))
|
|
{
|
|
painter.save();
|
|
|
|
if(accumRect.left() < m_pinnedWidth && i >= m_pinnedColumns)
|
|
accumRect.setLeft(m_pinnedWidth);
|
|
|
|
paintSection(&painter, accumRect, i);
|
|
painter.restore();
|
|
|
|
// if we have more sections to go, reset so we can accumulate the next group
|
|
if(i < end)
|
|
accumRect = QRect();
|
|
}
|
|
}
|
|
|
|
// clear the remainder of the header if there's a gap
|
|
if(accumRect.right() < e->rect().right())
|
|
{
|
|
QStyleOption opt;
|
|
opt.init(this);
|
|
opt.state |= QStyle::State_Horizontal;
|
|
opt.rect =
|
|
QRect(accumRect.right() + 1, 0, e->rect().right() - accumRect.right(), viewport()->height());
|
|
style()->drawControl(QStyle::CE_HeaderEmptyArea, &opt, &painter, this);
|
|
}
|
|
}
|
|
|
|
void RDHeaderView::paintSection(QPainter *painter, const QRect &rect, int section) const
|
|
{
|
|
if(!m_customSizing)
|
|
return QHeaderView::paintSection(painter, rect, section);
|
|
|
|
if(!rect.isValid())
|
|
return;
|
|
|
|
QStyleOptionHeader opt;
|
|
initStyleOption(&opt);
|
|
|
|
QAbstractItemModel *m = this->model();
|
|
|
|
if(hasFocus())
|
|
opt.state |= (QStyle::State_Active | QStyle::State_HasFocus);
|
|
else
|
|
opt.state &= ~(QStyle::State_Active | QStyle::State_HasFocus);
|
|
|
|
opt.rect = rect;
|
|
opt.section = section;
|
|
opt.textAlignment = defaultAlignment();
|
|
opt.iconAlignment = Qt::AlignVCenter;
|
|
|
|
QVariant variant;
|
|
|
|
if(m_columnGroupRole)
|
|
{
|
|
variant = m->headerData(section, orientation(), m_columnGroupRole);
|
|
if(variant.isValid() && variant.canConvert<QString>())
|
|
opt.text = variant.toString();
|
|
}
|
|
|
|
if(opt.text.isEmpty())
|
|
opt.text = m->headerData(section, orientation(), Qt::DisplayRole).toString();
|
|
|
|
int margin = 2 * style()->pixelMetric(QStyle::PM_HeaderMargin, 0, this);
|
|
|
|
if(textElideMode() != Qt::ElideNone)
|
|
opt.text = opt.fontMetrics.elidedText(opt.text, textElideMode(), rect.width() - margin);
|
|
|
|
if(section == 0 && section == m_sections.count() - 1)
|
|
opt.position = QStyleOptionHeader::OnlyOneSection;
|
|
else if(section == 0)
|
|
opt.position = QStyleOptionHeader::Beginning;
|
|
else if(section == m_sections.count() - 1)
|
|
opt.position = QStyleOptionHeader::End;
|
|
else
|
|
opt.position = QStyleOptionHeader::Middle;
|
|
|
|
opt.orientation = orientation();
|
|
|
|
bool prevSel = section > 0 && selectionModel()->isColumnSelected(section - 1, QModelIndex());
|
|
bool nextSel = section + 1 < m_sections.count() &&
|
|
selectionModel()->isColumnSelected(section + 1, QModelIndex());
|
|
|
|
if(prevSel && nextSel)
|
|
opt.selectedPosition = QStyleOptionHeader::NextAndPreviousAreSelected;
|
|
else if(prevSel)
|
|
opt.selectedPosition = QStyleOptionHeader::PreviousIsSelected;
|
|
else if(nextSel)
|
|
opt.selectedPosition = QStyleOptionHeader::NextIsSelected;
|
|
else
|
|
opt.selectedPosition = QStyleOptionHeader::NotAdjacent;
|
|
|
|
style()->drawControl(QStyle::CE_Header, &opt, painter, this);
|
|
}
|
|
|
|
void RDHeaderView::currentChanged(const QModelIndex ¤t, const QModelIndex &old)
|
|
{
|
|
if(!m_customSizing)
|
|
return QHeaderView::currentChanged(current, old);
|
|
|
|
// not optimal at all
|
|
if(current != old)
|
|
{
|
|
QRect r = viewport()->rect();
|
|
|
|
if(old.isValid())
|
|
{
|
|
QRect rect = r;
|
|
|
|
if(orientation() == Qt::Horizontal)
|
|
{
|
|
rect.setLeft(sectionViewportPosition(old.column()));
|
|
rect.setWidth(sectionSize(old.column()));
|
|
}
|
|
else
|
|
{
|
|
rect.setTop(sectionViewportPosition(old.column()));
|
|
rect.setHeight(sectionSize(old.column()));
|
|
}
|
|
|
|
viewport()->update(rect);
|
|
}
|
|
|
|
if(current.isValid())
|
|
{
|
|
QRect rect = r;
|
|
|
|
if(orientation() == Qt::Horizontal)
|
|
{
|
|
rect.setLeft(sectionViewportPosition(current.column()));
|
|
rect.setWidth(sectionSize(current.column()));
|
|
}
|
|
else
|
|
{
|
|
rect.setTop(sectionViewportPosition(current.column()));
|
|
rect.setHeight(sectionSize(current.column()));
|
|
}
|
|
|
|
viewport()->update(rect);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RDHeaderView::updateGeometries()
|
|
{
|
|
if(!m_sectionStretchHints.isEmpty())
|
|
resizeSectionsWithHints();
|
|
|
|
QHeaderView::updateGeometries();
|
|
}
|