mirror of
https://github.com/libretro/RetroArch
synced 2025-01-31 15:32:59 +00:00
506 lines
14 KiB
C++
506 lines
14 KiB
C++
#include <QScrollBar>
|
|
#include <QPainter>
|
|
|
|
#include "gridview.h"
|
|
#include "../ui_qt.h"
|
|
|
|
/* http://www.informit.com/articles/article.aspx?p=1613548 */
|
|
|
|
ThumbnailDelegate::ThumbnailDelegate(const GridItem &gridItem, QObject* parent) :
|
|
QStyledItemDelegate(parent), m_style(gridItem)
|
|
{
|
|
}
|
|
void ThumbnailDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex& index) const
|
|
{
|
|
QStyleOptionViewItem opt = option;
|
|
const QWidget *widget = opt.widget;
|
|
QStyle *style = widget->style();
|
|
int padding = m_style.padding;
|
|
int text_top_margin = 4; /* Qt seemingly reports -4 the actual line height. */
|
|
int text_height = painter->fontMetrics().height() + padding + padding;
|
|
QRect rect = opt.rect;
|
|
QRect adjusted = rect.adjusted(padding, padding, -padding, -text_height + text_top_margin);
|
|
QPixmap pixmap = index.data(PlaylistModel::THUMBNAIL).value<QPixmap>();
|
|
|
|
painter->save();
|
|
|
|
initStyleOption(&opt, index);
|
|
|
|
/* draw the background */
|
|
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget);
|
|
|
|
/* draw the image */
|
|
if (!pixmap.isNull())
|
|
{
|
|
QPixmap pixmapScaled = pixmap.scaled(adjusted.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
style->drawItemPixmap(painter, adjusted, Qt::AlignHCenter | m_style.thumbnailVerticalAlignmentFlag, pixmapScaled);
|
|
}
|
|
|
|
/* draw the text */
|
|
if (!opt.text.isEmpty())
|
|
{
|
|
QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
|
|
QRect textRect = QRect(rect.x() + padding, rect.y() + adjusted.height() - text_top_margin + padding, rect.width() - 2 * padding, text_height);
|
|
QString elidedText = painter->fontMetrics().elidedText(opt.text, opt.textElideMode, textRect.width(), Qt::TextShowMnemonic);
|
|
|
|
if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active))
|
|
cg = QPalette::Inactive;
|
|
|
|
if (opt.state & QStyle::State_Selected)
|
|
painter->setPen(opt.palette.color(cg, QPalette::HighlightedText));
|
|
else
|
|
painter->setPen(opt.palette.color(cg, QPalette::Text));
|
|
|
|
painter->setFont(opt.font);
|
|
painter->drawText(textRect, Qt::AlignCenter, elidedText);
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
|
|
GridView::GridView(QWidget *parent) : QAbstractItemView(parent), m_idealHeight(0), m_hashIsDirty(false)
|
|
{
|
|
setFocusPolicy(Qt::WheelFocus);
|
|
horizontalScrollBar()->setRange(0, 0);
|
|
verticalScrollBar()->setRange(0, 0);
|
|
}
|
|
|
|
void GridView::setModel(QAbstractItemModel *newModel)
|
|
{
|
|
if (model())
|
|
disconnect(model(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(rowsRemoved(QModelIndex, int, int)));
|
|
|
|
QAbstractItemView::setModel(newModel);
|
|
|
|
connect(newModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(rowsRemoved(QModelIndex, int, int)));
|
|
|
|
m_hashIsDirty = true;
|
|
}
|
|
|
|
void GridView::setviewMode(ViewMode mode)
|
|
{
|
|
m_viewMode = mode;
|
|
}
|
|
|
|
void GridView::calculateRectsIfNecessary() const
|
|
{
|
|
int x, y;
|
|
int row, nextX;
|
|
if (!m_hashIsDirty)
|
|
return;
|
|
|
|
x = m_spacing;
|
|
y = m_spacing;
|
|
const int maxWidth = viewport()->width();
|
|
|
|
if (m_size + m_spacing * 2 > maxWidth)
|
|
{
|
|
m_rectForRow[0] = QRectF(x, y, m_size, m_size);
|
|
|
|
for (row = 1; row < model()->rowCount(); ++row)
|
|
{
|
|
y += m_size + m_spacing;
|
|
m_rectForRow[row] = QRectF(x, y, m_size, m_size);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (m_viewMode)
|
|
{
|
|
case Anchored:
|
|
{
|
|
int columns = (maxWidth - m_spacing) / (m_size + m_spacing);
|
|
if (columns > 0)
|
|
{
|
|
const int actualSpacing = (maxWidth - m_spacing -
|
|
m_size - (columns - 1) * m_size) / columns;
|
|
for (row = 0; row < model()->rowCount(); ++row)
|
|
{
|
|
nextX = x + m_size + actualSpacing;
|
|
if (nextX > maxWidth)
|
|
{
|
|
x = m_spacing;
|
|
y += m_size + m_spacing;
|
|
nextX = x + m_size + actualSpacing;
|
|
}
|
|
m_rectForRow[row] = QRectF(x, y, m_size, m_size);
|
|
x = nextX;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Centered:
|
|
{
|
|
int columns = (maxWidth - m_spacing) / (m_size + m_spacing);
|
|
if (columns > 0)
|
|
{
|
|
const int actualSpacing = (maxWidth - columns * m_size)
|
|
/ (columns + 1);
|
|
x = actualSpacing;
|
|
|
|
for (row = 0; row < model()->rowCount(); ++row)
|
|
{
|
|
nextX = x + m_size + actualSpacing;
|
|
if (nextX > maxWidth)
|
|
{
|
|
x = actualSpacing;
|
|
y += m_size + m_spacing;
|
|
nextX = x + m_size + actualSpacing;
|
|
}
|
|
m_rectForRow[row] = QRectF(x, y, m_size, m_size);
|
|
x = nextX;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Simple:
|
|
for (row = 0; row < model()->rowCount(); ++row)
|
|
{
|
|
nextX = x + m_size + m_spacing;
|
|
if (nextX > maxWidth)
|
|
{
|
|
x = m_spacing;
|
|
y += m_size + m_spacing;
|
|
nextX = x + m_size + m_spacing;
|
|
}
|
|
m_rectForRow[row] = QRectF(x, y, m_size, m_size);
|
|
x = nextX;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_idealHeight = y + m_size + m_spacing;
|
|
m_hashIsDirty = false;
|
|
viewport()->update();
|
|
}
|
|
|
|
QRect GridView::visualRect(const QModelIndex &index) const
|
|
{
|
|
QRect rect;
|
|
if (index.isValid())
|
|
rect = viewportRectForRow(index.row()).toRect();
|
|
return rect;
|
|
}
|
|
|
|
QRectF GridView::viewportRectForRow(int row) const
|
|
{
|
|
QRectF rect;
|
|
calculateRectsIfNecessary();
|
|
rect = m_rectForRow.value(row).toRect();
|
|
if (!rect.isValid())
|
|
return rect;
|
|
return QRectF(rect.x() - horizontalScrollBar()->value(), rect.y() - verticalScrollBar()->value(), rect.width(), rect.height());
|
|
}
|
|
|
|
void GridView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint)
|
|
{
|
|
QRect viewRect = viewport()->rect();
|
|
QRect itemRect = visualRect(index);
|
|
|
|
if (itemRect.left() < viewRect.left())
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + itemRect.left() - viewRect.left());
|
|
else if (itemRect.right() > viewRect.right())
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + qMin(itemRect.right() - viewRect.right(), itemRect.left() - viewRect.left()));
|
|
if (itemRect.top() < viewRect.top())
|
|
verticalScrollBar()->setValue(verticalScrollBar()->value() + itemRect.top() - viewRect.top());
|
|
else if (itemRect.bottom() > viewRect.bottom())
|
|
verticalScrollBar()->setValue(verticalScrollBar()->value() + qMin(itemRect.bottom() - viewRect.bottom(), itemRect.top() - viewRect.top()));
|
|
viewport()->update();
|
|
}
|
|
|
|
/* TODO: Make this more efficient by changing m_rectForRow for another data structure. Look at how Qt's own views do it. */
|
|
QModelIndex GridView::indexAt(const QPoint &point_) const
|
|
{
|
|
QPoint point(point_);
|
|
QHash<int, QRectF>::const_iterator i;
|
|
point.rx() += horizontalScrollBar()->value();
|
|
point.ry() += verticalScrollBar()->value();
|
|
|
|
calculateRectsIfNecessary();
|
|
|
|
i = m_rectForRow.constBegin();
|
|
|
|
while (i != m_rectForRow.constEnd())
|
|
{
|
|
if (i.value().contains(point))
|
|
return model()->index(i.key(), 0, rootIndex());
|
|
i++;
|
|
}
|
|
return QModelIndex();
|
|
}
|
|
|
|
void GridView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
|
|
{
|
|
m_hashIsDirty = true;
|
|
QAbstractItemView::dataChanged(topLeft, bottomRight);
|
|
}
|
|
|
|
void GridView::refresh()
|
|
{
|
|
m_hashIsDirty = true;
|
|
calculateRectsIfNecessary();
|
|
updateGeometries();
|
|
}
|
|
|
|
void GridView::rowsInserted(const QModelIndex &parent, int start, int end)
|
|
{
|
|
QAbstractItemView::rowsInserted(parent, start, end);
|
|
refresh();
|
|
}
|
|
|
|
void GridView::rowsRemoved(const QModelIndex &parent, int start, int end)
|
|
{
|
|
refresh();
|
|
}
|
|
|
|
void GridView::setGridSize(const int newSize)
|
|
{
|
|
if (newSize != m_size)
|
|
{
|
|
m_size = newSize;
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
void GridView::resizeEvent(QResizeEvent*)
|
|
{
|
|
refresh();
|
|
}
|
|
|
|
void GridView::reset()
|
|
{
|
|
m_visibleIndexes.clear();
|
|
QAbstractItemView::reset();
|
|
refresh();
|
|
}
|
|
|
|
QModelIndex GridView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers)
|
|
{
|
|
QModelIndex index = currentIndex();
|
|
if (index.isValid())
|
|
{
|
|
if ((cursorAction == MoveLeft && index.row() > 0) || (cursorAction == MoveRight && index.row() + 1 < model()->rowCount()))
|
|
{
|
|
const int offset = (cursorAction == MoveLeft ? -1 : 1);
|
|
index = model()->index(index.row() + offset, index.column(), index.parent());
|
|
}
|
|
else if ((cursorAction == MoveUp && index.row() > 0) || (cursorAction == MoveDown && index.row() + 1 < model()->rowCount()))
|
|
{
|
|
const int offset = ((m_size + m_spacing) * (cursorAction == MoveUp ? -1 : 1));
|
|
QRect rect = viewportRectForRow(index.row()).toRect();
|
|
QPoint point(rect.center().x(), rect.center().y() + offset);
|
|
index = indexAt(point);
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
int GridView::horizontalOffset() const
|
|
{
|
|
return horizontalScrollBar()->value();
|
|
}
|
|
|
|
int GridView::verticalOffset() const
|
|
{
|
|
return verticalScrollBar()->value();
|
|
}
|
|
|
|
void GridView::scrollContentsBy(int dx, int dy)
|
|
{
|
|
scrollDirtyRegion(dx, dy);
|
|
viewport()->scroll(dx, dy);
|
|
emit(visibleItemsChangedMaybe());
|
|
}
|
|
|
|
/* TODO: Maybe add a way to get the previous/next visible indexes. */
|
|
QVector<QModelIndex> GridView::visibleIndexes() const {
|
|
return m_visibleIndexes;
|
|
}
|
|
|
|
void GridView::setSelection(const QRect &rect, QFlags<QItemSelectionModel::SelectionFlag> flags)
|
|
{
|
|
QRect rectangle;
|
|
QHash<int, QRectF>::const_iterator i;
|
|
int firstRow = model()->rowCount();
|
|
int lastRow = -1;
|
|
|
|
calculateRectsIfNecessary();
|
|
|
|
rectangle = rect.translated(horizontalScrollBar()->value(),
|
|
verticalScrollBar()->value()).normalized();
|
|
|
|
i = m_rectForRow.constBegin();
|
|
|
|
while (i != m_rectForRow.constEnd())
|
|
{
|
|
if (i.value().intersects(rectangle))
|
|
{
|
|
firstRow = firstRow < i.key() ? firstRow : i.key();
|
|
lastRow = lastRow > i.key() ? lastRow : i.key();
|
|
}
|
|
i++;
|
|
}
|
|
if (firstRow != model()->rowCount() && lastRow != -1)
|
|
{
|
|
QItemSelection selection(model()->index(
|
|
firstRow, 0, rootIndex()),
|
|
model()->index(lastRow, 0, rootIndex()));
|
|
selectionModel()->select(selection, flags);
|
|
}
|
|
else
|
|
{
|
|
QModelIndex invalid;
|
|
QItemSelection selection(invalid, invalid);
|
|
selectionModel()->select(selection, flags);
|
|
}
|
|
}
|
|
|
|
QRegion GridView::visualRegionForSelection(const QItemSelection &selection) const
|
|
{
|
|
int i;
|
|
QRegion region;
|
|
QItemSelectionRange range;
|
|
|
|
for (i = 0; i < selection.size(); i++)
|
|
{
|
|
range = selection.at(i);
|
|
int row;
|
|
for (row = range.top(); row <= range.bottom(); row++)
|
|
{
|
|
int column;
|
|
for (column = range.left(); column < range.right(); column++)
|
|
{
|
|
QModelIndex index = model()->index(row, column, rootIndex());
|
|
region += visualRect(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
return region;
|
|
}
|
|
|
|
void GridView::paintEvent(QPaintEvent*)
|
|
{
|
|
QPainter painter(viewport());
|
|
int row;
|
|
|
|
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
|
|
m_visibleIndexes.clear();
|
|
|
|
for (row = 0; row < model()->rowCount(rootIndex()); ++row)
|
|
{
|
|
QModelIndex index = model()->index(row, 0, rootIndex());
|
|
QRectF rect = viewportRectForRow(row);
|
|
QStyleOptionViewItem option = viewOptions();
|
|
|
|
if (!rect.isValid() || rect.bottom() < 0 || rect.y() > viewport()->height())
|
|
continue;
|
|
|
|
m_visibleIndexes.append(index);
|
|
option.rect = rect.toRect();
|
|
|
|
if (selectionModel()->isSelected(index))
|
|
option.state |= QStyle::State_Selected;
|
|
|
|
if (currentIndex() == index)
|
|
option.state |= QStyle::State_HasFocus;
|
|
|
|
itemDelegate()->paint(&painter, option, index);
|
|
}
|
|
}
|
|
|
|
void GridView::updateGeometries()
|
|
{
|
|
const int RowHeight = m_size + m_spacing;
|
|
|
|
QAbstractItemView::updateGeometries();
|
|
|
|
verticalScrollBar()->setSingleStep(RowHeight);
|
|
verticalScrollBar()->setPageStep(viewport()->height());
|
|
verticalScrollBar()->setRange(0, qMax(0, m_idealHeight - viewport()->height()));
|
|
|
|
horizontalScrollBar()->setPageStep(viewport()->width());
|
|
horizontalScrollBar()->setRange(0, qMax(0, RowHeight - viewport()->width()));
|
|
|
|
emit(visibleItemsChangedMaybe());
|
|
}
|
|
|
|
QString GridView::getLayout() const
|
|
{
|
|
switch (m_viewMode)
|
|
{
|
|
case Simple:
|
|
return "simple";
|
|
case Anchored:
|
|
return "anchored";
|
|
case Centered:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return "centered";
|
|
}
|
|
|
|
void GridView::setLayout(QString layout)
|
|
{
|
|
if (layout == "anchored")
|
|
m_viewMode = Anchored;
|
|
else if (layout == "centered")
|
|
m_viewMode = Centered;
|
|
else if (layout == "fixed")
|
|
m_viewMode = Simple;
|
|
}
|
|
|
|
int GridView::getSpacing() const
|
|
{
|
|
return m_spacing;
|
|
}
|
|
|
|
void GridView::setSpacing(const int spacing)
|
|
{
|
|
m_spacing = spacing;
|
|
}
|
|
|
|
GridItem::GridItem(QWidget* parent) : QWidget(parent)
|
|
, thumbnailVerticalAlignmentFlag(Qt::AlignBottom)
|
|
, padding(11)
|
|
{
|
|
}
|
|
|
|
int GridItem::getPadding() const
|
|
{
|
|
return padding;
|
|
}
|
|
|
|
void GridItem::setPadding(const int value)
|
|
{
|
|
padding = value;
|
|
}
|
|
|
|
QString GridItem::getThumbnailVerticalAlign() const
|
|
{
|
|
switch (thumbnailVerticalAlignmentFlag)
|
|
{
|
|
case Qt::AlignTop:
|
|
return "top";
|
|
case Qt::AlignVCenter:
|
|
return "center";
|
|
case Qt::AlignBottom:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return "bottom";
|
|
}
|
|
|
|
void GridItem::setThumbnailVerticalAlign(const QString valign)
|
|
{
|
|
if (valign == "top")
|
|
thumbnailVerticalAlignmentFlag = Qt::AlignTop;
|
|
else if (valign == "center")
|
|
thumbnailVerticalAlignmentFlag = Qt::AlignVCenter;
|
|
else if (valign == "bottom")
|
|
thumbnailVerticalAlignmentFlag = Qt::AlignBottom;
|
|
}
|