mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-05 01:20:42 +00:00
538 lines
16 KiB
C++
538 lines
16 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2016-2019 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 "RDTableView.h"
|
|
#include <QAbstractButton>
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QHeaderView>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QPen>
|
|
#include <QScrollBar>
|
|
#include "Code/QRDUtils.h"
|
|
#include "RDHeaderView.h"
|
|
|
|
RDTableView::RDTableView(QWidget *parent) : QTableView(parent)
|
|
{
|
|
m_horizontalHeader = new RDHeaderView(Qt::Horizontal, this);
|
|
setHorizontalHeader(m_horizontalHeader);
|
|
|
|
m_delegate = new RichTextViewDelegate(this);
|
|
QTableView::setItemDelegate(m_delegate);
|
|
|
|
QObject::connect(m_horizontalHeader, &QHeaderView::sectionResized,
|
|
[this](int, int, int) { viewport()->update(); });
|
|
}
|
|
|
|
void RDTableView::setItemDelegate(QAbstractItemDelegate *delegate)
|
|
{
|
|
m_userDelegate = delegate;
|
|
m_delegate->setForwardDelegate(m_userDelegate);
|
|
}
|
|
|
|
QAbstractItemDelegate *RDTableView::itemDelegate() const
|
|
{
|
|
return m_userDelegate;
|
|
}
|
|
|
|
int RDTableView::columnViewportPosition(int column) const
|
|
{
|
|
return horizontalHeader()->sectionViewportPosition(column);
|
|
}
|
|
|
|
int RDTableView::columnAt(int x) const
|
|
{
|
|
return horizontalHeader()->visualIndexAt(x);
|
|
}
|
|
|
|
int RDTableView::columnWidth(int column) const
|
|
{
|
|
return horizontalHeader()->sectionSize(column);
|
|
}
|
|
|
|
void RDTableView::setColumnWidth(int column, int width)
|
|
{
|
|
horizontalHeader()->resizeSection(column, width);
|
|
|
|
updateGeometries();
|
|
}
|
|
|
|
void RDTableView::setColumnWidths(const QList<int> &widths)
|
|
{
|
|
horizontalHeader()->resizeSections(widths);
|
|
|
|
updateGeometries();
|
|
}
|
|
|
|
void RDTableView::resizeColumnsToContents()
|
|
{
|
|
horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
|
|
|
|
updateGeometries();
|
|
}
|
|
|
|
QRect RDTableView::visualRect(const QModelIndex &index) const
|
|
{
|
|
if(!index.isValid())
|
|
return QRect();
|
|
|
|
const int row = index.row();
|
|
const int col = index.column();
|
|
|
|
const int gridWidth = showGrid() ? 1 : 0;
|
|
|
|
return QRect(columnViewportPosition(col), rowViewportPosition(row), columnWidth(col) - gridWidth,
|
|
rowHeight(row) - gridWidth);
|
|
}
|
|
|
|
QRegion RDTableView::visualRegionForSelection(const QItemSelection &selection) const
|
|
{
|
|
QRegion selectionRegion;
|
|
const QRect viewRect = viewport()->rect();
|
|
|
|
QAbstractItemModel *m = model();
|
|
|
|
for(const QItemSelectionRange &selRange : selection)
|
|
{
|
|
for(int row = selRange.top(); row <= selRange.bottom(); row++)
|
|
{
|
|
for(int col = selRange.left(); col <= selRange.right(); col++)
|
|
{
|
|
const QRect &rangeRect = visualRect(m->index(row, col));
|
|
if(viewRect.intersects(rangeRect))
|
|
selectionRegion += rangeRect;
|
|
}
|
|
}
|
|
}
|
|
|
|
return selectionRegion;
|
|
}
|
|
|
|
QModelIndex RDTableView::indexAt(const QPoint &p) const
|
|
{
|
|
int row = rowAt(p.y());
|
|
int col = columnAt(p.x());
|
|
|
|
if(row < 0 || col < 0)
|
|
return QModelIndex();
|
|
|
|
return model()->index(row, col);
|
|
}
|
|
|
|
void RDTableView::setColumnGroupRole(int role)
|
|
{
|
|
m_columnGroupRole = role;
|
|
m_horizontalHeader->setColumnGroupRole(role);
|
|
}
|
|
|
|
void RDTableView::setPinnedColumns(int numColumns)
|
|
{
|
|
m_pinnedColumns = numColumns;
|
|
m_horizontalHeader->setPinnedColumns(numColumns);
|
|
}
|
|
|
|
void RDTableView::keyPressEvent(QKeyEvent *e)
|
|
{
|
|
if(e == QKeySequence::Copy)
|
|
{
|
|
QModelIndexList sel = selectionModel()->selectedIndexes();
|
|
|
|
if(!sel.isEmpty())
|
|
{
|
|
int stackWidths[16];
|
|
int *heapWidths = NULL;
|
|
|
|
int colCount = model()->columnCount();
|
|
|
|
if(colCount >= 16)
|
|
heapWidths = new int[colCount];
|
|
|
|
int *widths = heapWidths ? heapWidths : stackWidths;
|
|
|
|
for(int i = 0; i < colCount; i++)
|
|
widths[i] = 0;
|
|
|
|
int top = sel[0].row(), bottom = sel[0].row();
|
|
int left = sel[0].column(), right = sel[0].column();
|
|
|
|
// align the copied data so that each column is the same width
|
|
for(QModelIndex idx : sel)
|
|
{
|
|
QString text = model()->data(idx).toString();
|
|
widths[idx.column()] = qMax(widths[idx.column()], text.count());
|
|
|
|
top = qMin(top, idx.row());
|
|
bottom = qMax(bottom, idx.row());
|
|
left = qMin(left, idx.column());
|
|
right = qMax(right, idx.column());
|
|
}
|
|
|
|
// only align up to 50 characters so one really long item doesn't mess up the whole thing
|
|
for(int i = 0; i < colCount; i++)
|
|
widths[i] = qMin(50, widths[i]);
|
|
|
|
QString clipData;
|
|
for(int row = top; row <= bottom; row++)
|
|
{
|
|
for(int col = left; col <= right; col++)
|
|
{
|
|
QString format = col == left ? lit("%1") : lit(" %1");
|
|
QString text = model()->data(model()->index(row, col)).toString();
|
|
|
|
clipData += format.arg(text, -widths[col]);
|
|
}
|
|
|
|
clipData += lit("\n");
|
|
}
|
|
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
clipboard->setText(clipData.trimmed());
|
|
|
|
delete[] heapWidths;
|
|
}
|
|
|
|
e->accept();
|
|
return;
|
|
}
|
|
|
|
return QTableView::keyPressEvent(e);
|
|
}
|
|
|
|
void RDTableView::paintEvent(QPaintEvent *e)
|
|
{
|
|
const int gridWidth = showGrid() ? 1 : 0;
|
|
QStyleOptionViewItem opt = viewOptions();
|
|
|
|
QPainter painter(viewport());
|
|
|
|
if(model()->rowCount() == 0 || model()->columnCount() == 0)
|
|
return;
|
|
|
|
int firstRow = qMax(verticalHeader()->visualIndexAt(0), 0);
|
|
int lastRow = verticalHeader()->visualIndexAt(viewport()->height());
|
|
if(lastRow < 0)
|
|
{
|
|
// if lastRow is negative, display as many will fit. This is a reasonable upper bound without
|
|
// displaying all rows which could be massive
|
|
lastRow = firstRow + (viewport()->height() / qMax(1, rowHeight(firstRow))) + 1;
|
|
}
|
|
lastRow = qMin(lastRow, verticalHeader()->count() - 1);
|
|
|
|
int firstCol = qMax(horizontalHeader()->visualIndexAt(horizontalHeader()->pinnedWidth() + 1), 0);
|
|
int lastCol = horizontalHeader()->visualIndexAt(viewport()->width());
|
|
if(lastCol < 0)
|
|
lastCol = horizontalHeader()->count() - 1;
|
|
lastCol = qMin(lastCol, horizontalHeader()->count() - 1);
|
|
|
|
firstCol = qMax(m_pinnedColumns, firstCol);
|
|
|
|
for(int row = firstRow; row <= lastRow; row++)
|
|
{
|
|
for(int col = firstCol; col <= lastCol; col++)
|
|
{
|
|
const QModelIndex index = model()->index(row, col);
|
|
if(index.isValid())
|
|
paintCell(&painter, index, opt);
|
|
}
|
|
for(int col = 0; col < m_pinnedColumns; col++)
|
|
{
|
|
const QModelIndex index = model()->index(row, col);
|
|
if(index.isValid())
|
|
paintCell(&painter, index, opt);
|
|
}
|
|
}
|
|
|
|
if(gridWidth)
|
|
{
|
|
QPen prevPen = painter.pen();
|
|
QBrush prevBrush = painter.brush();
|
|
|
|
QColor gridCol(QRgb(style()->styleHint(QStyle::SH_Table_GridLineColor, &opt, this)));
|
|
|
|
painter.setPen(QPen(gridCol, 0, gridStyle()));
|
|
painter.setBrush(QBrush(gridCol));
|
|
|
|
// draw bottom line of each row
|
|
for(int row = firstRow; row <= lastRow; row++)
|
|
{
|
|
int y = rowViewportPosition(row) + rowHeight(row) - gridWidth;
|
|
painter.drawLine(viewport()->rect().left(), y, viewport()->rect().right(), y);
|
|
}
|
|
|
|
int gapSize = m_horizontalHeader->groupGapSize();
|
|
|
|
// draw lines for each column, and group gaps
|
|
for(int col = firstCol; col <= lastCol; col++)
|
|
{
|
|
int x = columnViewportPosition(col) + columnWidth(col) - gridWidth;
|
|
|
|
if(m_horizontalHeader->hasGroupGap(col))
|
|
painter.drawRect(x, viewport()->rect().top(), gapSize, viewport()->rect().height());
|
|
else
|
|
painter.drawLine(x, viewport()->rect().top(), x, viewport()->rect().bottom());
|
|
}
|
|
for(int col = 0; col < m_pinnedColumns; col++)
|
|
{
|
|
int x = columnViewportPosition(col) + columnWidth(col) - gridWidth;
|
|
|
|
if(m_horizontalHeader->hasGroupGap(col))
|
|
painter.drawRect(x, viewport()->rect().top(), gapSize, viewport()->rect().height());
|
|
else
|
|
painter.drawLine(x, viewport()->rect().top(), x, viewport()->rect().bottom());
|
|
}
|
|
|
|
painter.setPen(prevPen);
|
|
painter.setBrush(prevBrush);
|
|
}
|
|
}
|
|
|
|
void RDTableView::paintCell(QPainter *painter, const QModelIndex &index,
|
|
const QStyleOptionViewItem &opt)
|
|
{
|
|
QStyleOptionViewItem cellopt = opt;
|
|
|
|
cellopt.rect = QRect(columnViewportPosition(index.column()), rowViewportPosition(index.row()),
|
|
columnWidth(index.column()), rowHeight(index.row()));
|
|
|
|
// erase the rect here since we need to draw over any overlapping non-pinned cells and
|
|
// there's no way to just clip the above painting :(
|
|
if(index.column() < m_pinnedColumns)
|
|
painter->eraseRect(cellopt.rect);
|
|
|
|
if(selectionModel() && selectionModel()->isSelected(index))
|
|
cellopt.state |= QStyle::State_Selected;
|
|
|
|
if(cellopt.rect.contains(viewport()->mapFromGlobal(QCursor::pos())))
|
|
cellopt.state |= QStyle::State_MouseOver;
|
|
|
|
// draw the background, then the cell
|
|
style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &cellopt, painter, this);
|
|
QTableView::itemDelegate(index)->paint(painter, cellopt, index);
|
|
}
|
|
|
|
void RDTableView::mouseMoveEvent(QMouseEvent *e)
|
|
{
|
|
QModelIndex oldHover = m_currentHoverIndex;
|
|
|
|
QTableView::mouseMoveEvent(e);
|
|
|
|
QModelIndex newHover = m_currentHoverIndex = indexAt(e->pos());
|
|
|
|
update(newHover);
|
|
|
|
if(m_delegate && m_delegate->linkHover(e, newHover))
|
|
{
|
|
setCursor(QCursor(Qt::PointingHandCursor));
|
|
}
|
|
else
|
|
{
|
|
unsetCursor();
|
|
}
|
|
|
|
if(oldHover == newHover)
|
|
return;
|
|
|
|
update(oldHover);
|
|
}
|
|
|
|
void RDTableView::scrollTo(const QModelIndex &index, ScrollHint hint)
|
|
{
|
|
if(!index.isValid())
|
|
return;
|
|
|
|
QRect cellRect = QRect(columnViewportPosition(index.column()), rowViewportPosition(index.row()),
|
|
columnWidth(index.column()), rowHeight(index.row()));
|
|
|
|
QRect dataRect = viewport()->rect();
|
|
dataRect.setLeft(horizontalHeader()->pinnedWidth());
|
|
|
|
// if it's already visible then just bail, common case
|
|
if(dataRect.contains(cellRect) && hint == QAbstractItemView::EnsureVisible)
|
|
return;
|
|
|
|
// assume per-item vertical scrolling and per-pixel horizontal scrolling
|
|
|
|
// for any hint except position at center, we just ensure it's visible horizontally
|
|
if(hint != QAbstractItemView::PositionAtCenter)
|
|
{
|
|
// scroll into view from the left
|
|
if(dataRect.left() > cellRect.left())
|
|
{
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() -
|
|
(dataRect.left() - cellRect.left()));
|
|
}
|
|
|
|
// scroll into view from the right
|
|
if(dataRect.right() < cellRect.right())
|
|
{
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() +
|
|
(cellRect.right() - dataRect.right()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// center it horizontally from the left
|
|
QPoint dataCenter = dataRect.center();
|
|
QPoint cellCenter = cellRect.center();
|
|
|
|
if(dataCenter.x() > cellCenter.x())
|
|
{
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() -
|
|
(dataCenter.x() - cellCenter.x()));
|
|
}
|
|
|
|
// center it horizontally from the right
|
|
if(dataCenter.x() < cellCenter.x())
|
|
{
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() +
|
|
(cellCenter.x() - dataCenter.x()));
|
|
}
|
|
}
|
|
|
|
// collapse EnsureVisible to either PositionAtTop or PositionAtBottom depending on which side it's
|
|
// on, or just return if we only had to make it visible horizontally
|
|
if(hint == QAbstractItemView::EnsureVisible)
|
|
{
|
|
if(dataRect.bottom() < cellRect.bottom())
|
|
hint = QAbstractItemView::PositionAtBottom;
|
|
else if(dataRect.top() > cellRect.top())
|
|
hint = QAbstractItemView::PositionAtTop;
|
|
else
|
|
return;
|
|
}
|
|
|
|
int firstRow = qMax(verticalHeader()->visualIndexAt(0), 0);
|
|
int lastRow = verticalHeader()->visualIndexAt(viewport()->height());
|
|
if(lastRow == -1)
|
|
lastRow = verticalHeader()->count();
|
|
|
|
int visibleRows = lastRow - firstRow + 1;
|
|
|
|
// a partially displayed row doesn't count
|
|
if(verticalHeader()->sectionViewportPosition(lastRow) + verticalHeader()->sectionSize(lastRow) >
|
|
viewport()->height())
|
|
visibleRows--;
|
|
|
|
if(hint == QAbstractItemView::PositionAtTop)
|
|
{
|
|
verticalScrollBar()->setValue(index.row());
|
|
}
|
|
else if(hint == QAbstractItemView::PositionAtBottom)
|
|
{
|
|
verticalScrollBar()->setValue(index.row() - visibleRows + 1);
|
|
}
|
|
else if(hint == QAbstractItemView::PositionAtCenter)
|
|
{
|
|
verticalScrollBar()->setValue(index.row() - (visibleRows + 1) / 2);
|
|
}
|
|
|
|
update(index);
|
|
}
|
|
|
|
void RDTableView::updateGeometries()
|
|
{
|
|
if(!m_horizontalHeader->customSizing())
|
|
return QTableView::updateGeometries();
|
|
|
|
static bool recurse = false;
|
|
if(recurse)
|
|
return;
|
|
recurse = true;
|
|
|
|
QAbstractButton *cornerButton = findChild<QAbstractButton *>();
|
|
cornerButton->setVisible(false);
|
|
|
|
QRect geom = viewport()->geometry();
|
|
|
|
// assume no vertical header
|
|
|
|
int horizHeight =
|
|
qBound(horizontalHeader()->minimumHeight(), horizontalHeader()->sizeHint().height(),
|
|
horizontalHeader()->maximumHeight());
|
|
|
|
setViewportMargins(0, horizHeight, 0, 0);
|
|
|
|
horizontalHeader()->setGeometry(geom.left(), geom.top() - horizHeight, geom.width(), horizHeight);
|
|
|
|
// even though it's not visible we need to set the geometry right so that it looks up rows by
|
|
// position properly.
|
|
verticalHeader()->setGeometry(0, horizHeight, 0, geom.height());
|
|
|
|
// if the headers are hidden nothing else will update their geometries and some things like
|
|
// scrolling etc depend on it being up to date, so hackily call the protected slot. Yuk!
|
|
if(verticalHeader()->isHidden())
|
|
QMetaObject::invokeMethod(verticalHeader(), "updateGeometries");
|
|
if(horizontalHeader()->isHidden())
|
|
QMetaObject::invokeMethod(horizontalHeader(), "updateGeometries");
|
|
|
|
// assume per-item vertical scrolling and per-pixel horizontal scrolling
|
|
|
|
// vertical scroll bar
|
|
{
|
|
int firstRow = qMax(verticalHeader()->visualIndexAt(0), 0);
|
|
int lastRow = verticalHeader()->visualIndexAt(viewport()->height());
|
|
bool last = false;
|
|
if(lastRow == -1)
|
|
{
|
|
last = true;
|
|
lastRow = verticalHeader()->count();
|
|
}
|
|
|
|
int visibleRows = lastRow - firstRow + 1;
|
|
|
|
// a partially displayed row doesn't count
|
|
if(verticalHeader()->sectionViewportPosition(lastRow) + verticalHeader()->sectionSize(lastRow) >
|
|
viewport()->height())
|
|
visibleRows--;
|
|
|
|
verticalScrollBar()->setRange(0, verticalHeader()->count() - visibleRows);
|
|
verticalScrollBar()->setSingleStep(1);
|
|
verticalScrollBar()->setPageStep(visibleRows);
|
|
if(visibleRows >= verticalHeader()->count())
|
|
verticalHeader()->setOffset(0);
|
|
else if(last)
|
|
verticalHeader()->setOffsetToLastSection();
|
|
}
|
|
|
|
// horizontal scroll bar
|
|
{
|
|
int totalWidth = horizontalHeader()->sizeHint().width();
|
|
|
|
horizontalScrollBar()->setPageStep(viewport()->width() - horizontalHeader()->pinnedWidth());
|
|
horizontalScrollBar()->setRange(0, totalWidth - viewport()->width());
|
|
horizontalScrollBar()->setSingleStep(qMax(totalWidth / (horizontalHeader()->count() + 1), 2));
|
|
}
|
|
|
|
recurse = false;
|
|
QAbstractItemView::updateGeometries();
|
|
}
|
|
|
|
void RDTableView::scrollContentsBy(int dx, int dy)
|
|
{
|
|
QTableView::scrollContentsBy(dx, dy);
|
|
|
|
viewport()->update();
|
|
}
|