1 #include <QScrollBar>
2 #include <QPainter>
3 
4 #include "gridview.h"
5 #include "../ui_qt.h"
6 
7 /* http://www.informit.com/articles/article.aspx?p=1613548 */
8 
ThumbnailDelegate(const GridItem & gridItem,QObject * parent)9 ThumbnailDelegate::ThumbnailDelegate(const GridItem &gridItem, QObject* parent) :
10    QStyledItemDelegate(parent), m_style(gridItem)
11 {
12 }
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const13 void ThumbnailDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex& index) const
14 {
15    QStyleOptionViewItem opt = option;
16    const QWidget    *widget = opt.widget;
17    QStyle            *style = widget->style();
18    int              padding = m_style.padding;
19    int      text_top_margin = 4; /* Qt seemingly reports -4 the actual line height. */
20    int          text_height = painter->fontMetrics().height() + padding + padding;
21    QRect               rect = opt.rect;
22    QRect           adjusted = rect.adjusted(padding, padding, -padding, -text_height + text_top_margin);
23    QPixmap           pixmap = index.data(PlaylistModel::THUMBNAIL).value<QPixmap>();
24 
25    painter->save();
26 
27    initStyleOption(&opt, index);
28 
29    /* draw the background */
30    style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget);
31 
32    /* draw the image */
33    if (!pixmap.isNull())
34    {
35       QPixmap pixmapScaled = pixmap.scaled(adjusted.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
36       style->drawItemPixmap(painter, adjusted, Qt::AlignHCenter | m_style.thumbnailVerticalAlignmentFlag, pixmapScaled);
37    }
38 
39    /* draw the text */
40    if (!opt.text.isEmpty())
41    {
42       QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
43       QRect textRect          = QRect(rect.x() + padding, rect.y() + adjusted.height() - text_top_margin + padding, rect.width() - 2 * padding, text_height);
44       QString elidedText      = painter->fontMetrics().elidedText(opt.text, opt.textElideMode, textRect.width(), Qt::TextShowMnemonic);
45 
46       if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active))
47          cg = QPalette::Inactive;
48 
49       if (opt.state & QStyle::State_Selected)
50          painter->setPen(opt.palette.color(cg, QPalette::HighlightedText));
51       else
52          painter->setPen(opt.palette.color(cg, QPalette::Text));
53 
54       painter->setFont(opt.font);
55       painter->drawText(textRect, Qt::AlignCenter, elidedText);
56    }
57 
58    painter->restore();
59 }
60 
GridView(QWidget * parent)61 GridView::GridView(QWidget *parent) : QAbstractItemView(parent), m_idealHeight(0), m_hashIsDirty(false)
62 {
63    setFocusPolicy(Qt::WheelFocus);
64    horizontalScrollBar()->setRange(0, 0);
65    verticalScrollBar()->setRange(0, 0);
66 }
67 
setModel(QAbstractItemModel * newModel)68 void GridView::setModel(QAbstractItemModel *newModel)
69 {
70    if (model())
71       disconnect(model(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(rowsRemoved(QModelIndex, int, int)));
72 
73    QAbstractItemView::setModel(newModel);
74 
75    connect(newModel, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(rowsRemoved(QModelIndex, int, int)));
76 
77    m_hashIsDirty = true;
78 }
79 
setviewMode(ViewMode mode)80 void GridView::setviewMode(ViewMode mode)
81 {
82    m_viewMode = mode;
83 }
84 
calculateRectsIfNecessary() const85 void GridView::calculateRectsIfNecessary() const
86 {
87    int x, y;
88    int row, nextX;
89    if (!m_hashIsDirty)
90       return;
91 
92    x                  = m_spacing;
93    y                  = m_spacing;
94    const int maxWidth = viewport()->width();
95 
96    if (m_size + m_spacing * 2 > maxWidth)
97    {
98       m_rectForRow[0] = QRectF(x, y, m_size, m_size);
99 
100       for (row = 1; row < model()->rowCount(); ++row)
101       {
102          y                 += m_size + m_spacing;
103          m_rectForRow[row]  = QRectF(x, y, m_size, m_size);
104       }
105    }
106    else
107    {
108       switch (m_viewMode)
109       {
110          case Anchored:
111          {
112             int columns = (maxWidth - m_spacing) / (m_size + m_spacing);
113             if (columns > 0)
114             {
115                const int actualSpacing = (maxWidth - m_spacing -
116                      m_size - (columns - 1) * m_size) / columns;
117                for (row = 0; row < model()->rowCount(); ++row)
118                {
119                   nextX = x + m_size + actualSpacing;
120                   if (nextX > maxWidth)
121                   {
122                      x = m_spacing;
123                      y += m_size + m_spacing;
124                      nextX = x + m_size + actualSpacing;
125                   }
126                   m_rectForRow[row] = QRectF(x, y, m_size, m_size);
127                   x = nextX;
128                }
129             }
130             break;
131          }
132          case Centered:
133          {
134             int columns = (maxWidth - m_spacing) / (m_size + m_spacing);
135             if (columns > 0)
136             {
137                const int actualSpacing = (maxWidth - columns * m_size)
138                   / (columns + 1);
139                x                       = actualSpacing;
140 
141                for (row = 0; row < model()->rowCount(); ++row)
142                {
143                   nextX = x + m_size + actualSpacing;
144                   if (nextX > maxWidth)
145                   {
146                      x = actualSpacing;
147                      y += m_size + m_spacing;
148                      nextX = x + m_size + actualSpacing;
149                   }
150                   m_rectForRow[row] = QRectF(x, y, m_size, m_size);
151                   x = nextX;
152                }
153             }
154             break;
155          }
156          case Simple:
157             for (row = 0; row < model()->rowCount(); ++row)
158             {
159                nextX = x + m_size + m_spacing;
160                if (nextX > maxWidth)
161                {
162                   x = m_spacing;
163                   y += m_size + m_spacing;
164                   nextX = x + m_size + m_spacing;
165                }
166                m_rectForRow[row] = QRectF(x, y, m_size, m_size);
167                x = nextX;
168             }
169             break;
170          }
171    }
172 
173    m_idealHeight = y + m_size + m_spacing;
174    m_hashIsDirty = false;
175    viewport()->update();
176 }
177 
visualRect(const QModelIndex & index) const178 QRect GridView::visualRect(const QModelIndex &index) const
179 {
180    QRect rect;
181    if (index.isValid())
182       rect = viewportRectForRow(index.row()).toRect();
183    return rect;
184 }
185 
viewportRectForRow(int row) const186 QRectF GridView::viewportRectForRow(int row) const
187 {
188    QRectF rect;
189    calculateRectsIfNecessary();
190    rect = m_rectForRow.value(row).toRect();
191    if (!rect.isValid())
192       return rect;
193    return QRectF(rect.x() - horizontalScrollBar()->value(), rect.y() - verticalScrollBar()->value(), rect.width(), rect.height());
194 }
195 
scrollTo(const QModelIndex & index,QAbstractItemView::ScrollHint)196 void GridView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint)
197 {
198    QRect viewRect = viewport()->rect();
199    QRect itemRect = visualRect(index);
200 
201    if (itemRect.left() < viewRect.left())
202       horizontalScrollBar()->setValue(horizontalScrollBar()->value() + itemRect.left() - viewRect.left());
203    else if (itemRect.right() > viewRect.right())
204       horizontalScrollBar()->setValue(horizontalScrollBar()->value() + qMin(itemRect.right() - viewRect.right(), itemRect.left() - viewRect.left()));
205    if (itemRect.top() < viewRect.top())
206       verticalScrollBar()->setValue(verticalScrollBar()->value() + itemRect.top() - viewRect.top());
207    else if (itemRect.bottom() > viewRect.bottom())
208       verticalScrollBar()->setValue(verticalScrollBar()->value() + qMin(itemRect.bottom() - viewRect.bottom(), itemRect.top() - viewRect.top()));
209    viewport()->update();
210 }
211 
212 /* TODO: Make this more efficient by changing m_rectForRow for another data structure. Look at how Qt's own views do it. */
indexAt(const QPoint & point_) const213 QModelIndex GridView::indexAt(const QPoint &point_) const
214 {
215    QPoint point(point_);
216    QHash<int, QRectF>::const_iterator i;
217    point.rx() += horizontalScrollBar()->value();
218    point.ry() += verticalScrollBar()->value();
219 
220    calculateRectsIfNecessary();
221 
222    i = m_rectForRow.constBegin();
223 
224    while (i != m_rectForRow.constEnd())
225    {
226       if (i.value().contains(point))
227          return model()->index(i.key(), 0, rootIndex());
228       i++;
229    }
230    return QModelIndex();
231 }
232 
dataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight,const QVector<int> & roles)233 void GridView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
234 {
235    m_hashIsDirty = true;
236    QAbstractItemView::dataChanged(topLeft, bottomRight);
237 }
238 
refresh()239 void GridView::refresh()
240 {
241    m_hashIsDirty = true;
242    calculateRectsIfNecessary();
243    updateGeometries();
244 }
245 
rowsInserted(const QModelIndex & parent,int start,int end)246 void GridView::rowsInserted(const QModelIndex &parent, int start, int end)
247 {
248    QAbstractItemView::rowsInserted(parent, start, end);
249    refresh();
250 }
251 
rowsRemoved(const QModelIndex & parent,int start,int end)252 void GridView::rowsRemoved(const QModelIndex &parent, int start, int end)
253 {
254    refresh();
255 }
256 
setGridSize(const int newSize)257 void GridView::setGridSize(const int newSize)
258 {
259    if (newSize != m_size)
260    {
261       m_size = newSize;
262       refresh();
263    }
264 }
265 
resizeEvent(QResizeEvent *)266 void GridView::resizeEvent(QResizeEvent*)
267 {
268    refresh();
269 }
270 
reset()271 void GridView::reset()
272 {
273    m_visibleIndexes.clear();
274    QAbstractItemView::reset();
275    refresh();
276 }
277 
moveCursor(QAbstractItemView::CursorAction cursorAction,Qt::KeyboardModifiers)278 QModelIndex GridView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers)
279 {
280    QModelIndex index = currentIndex();
281    if (index.isValid())
282    {
283       if ((cursorAction == MoveLeft && index.row() > 0) || (cursorAction == MoveRight && index.row() + 1 < model()->rowCount()))
284       {
285          const int offset = (cursorAction == MoveLeft ? -1 : 1);
286          index = model()->index(index.row() + offset, index.column(), index.parent());
287       }
288       else if ((cursorAction == MoveUp && index.row() > 0) || (cursorAction == MoveDown && index.row() + 1 < model()->rowCount()))
289       {
290          const int offset = ((m_size + m_spacing) * (cursorAction == MoveUp ? -1 : 1));
291          QRect rect = viewportRectForRow(index.row()).toRect();
292          QPoint point(rect.center().x(), rect.center().y() + offset);
293          index = indexAt(point);
294       }
295    }
296    return index;
297 }
298 
horizontalOffset() const299 int GridView::horizontalOffset() const
300 {
301    return horizontalScrollBar()->value();
302 }
303 
verticalOffset() const304 int GridView::verticalOffset() const
305 {
306    return verticalScrollBar()->value();
307 }
308 
scrollContentsBy(int dx,int dy)309 void GridView::scrollContentsBy(int dx, int dy)
310 {
311    scrollDirtyRegion(dx, dy);
312    viewport()->scroll(dx, dy);
313    emit(visibleItemsChangedMaybe());
314 }
315 
316 /* TODO: Maybe add a way to get the previous/next visible indexes. */
visibleIndexes() const317 QVector<QModelIndex> GridView::visibleIndexes() const {
318    return m_visibleIndexes;
319 }
320 
setSelection(const QRect & rect,QFlags<QItemSelectionModel::SelectionFlag> flags)321 void GridView::setSelection(const QRect &rect, QFlags<QItemSelectionModel::SelectionFlag> flags)
322 {
323    QRect rectangle;
324    QHash<int, QRectF>::const_iterator i;
325    int firstRow = model()->rowCount();
326    int lastRow  = -1;
327 
328    calculateRectsIfNecessary();
329 
330    rectangle    = rect.translated(horizontalScrollBar()->value(),
331          verticalScrollBar()->value()).normalized();
332 
333    i            = m_rectForRow.constBegin();
334 
335    while (i != m_rectForRow.constEnd())
336    {
337       if (i.value().intersects(rectangle))
338       {
339          firstRow = firstRow < i.key() ? firstRow : i.key();
340          lastRow = lastRow > i.key() ? lastRow : i.key();
341       }
342       i++;
343    }
344    if (firstRow != model()->rowCount() && lastRow != -1)
345    {
346       QItemSelection selection(model()->index(
347                firstRow, 0, rootIndex()),
348             model()->index(lastRow, 0, rootIndex()));
349       selectionModel()->select(selection, flags);
350    }
351    else
352    {
353       QModelIndex invalid;
354       QItemSelection selection(invalid, invalid);
355       selectionModel()->select(selection, flags);
356    }
357 }
358 
visualRegionForSelection(const QItemSelection & selection) const359 QRegion GridView::visualRegionForSelection(const QItemSelection &selection) const
360 {
361    int i;
362    QRegion region;
363    QItemSelectionRange range;
364 
365    for (i = 0; i < selection.size(); i++)
366    {
367       range = selection.at(i);
368       int row;
369       for (row = range.top(); row <= range.bottom(); row++)
370       {
371          int column;
372          for (column = range.left(); column < range.right(); column++)
373          {
374             QModelIndex index = model()->index(row, column, rootIndex());
375             region += visualRect(index);
376          }
377       }
378    }
379 
380    return region;
381 }
382 
paintEvent(QPaintEvent *)383 void GridView::paintEvent(QPaintEvent*)
384 {
385    QPainter painter(viewport());
386    int row;
387 
388    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
389    m_visibleIndexes.clear();
390 
391    for (row = 0; row < model()->rowCount(rootIndex()); ++row)
392    {
393       QModelIndex index = model()->index(row, 0, rootIndex());
394       QRectF rect = viewportRectForRow(row);
395       QStyleOptionViewItem option = viewOptions();
396 
397       if (!rect.isValid() || rect.bottom() < 0 || rect.y() > viewport()->height())
398          continue;
399 
400       m_visibleIndexes.append(index);
401       option.rect = rect.toRect();
402 
403       if (selectionModel()->isSelected(index))
404          option.state |= QStyle::State_Selected;
405 
406       if (currentIndex() == index)
407          option.state |= QStyle::State_HasFocus;
408 
409       itemDelegate()->paint(&painter, option, index);
410    }
411 }
412 
updateGeometries()413 void GridView::updateGeometries()
414 {
415    const int RowHeight = m_size + m_spacing;
416 
417    QAbstractItemView::updateGeometries();
418 
419    verticalScrollBar()->setSingleStep(RowHeight);
420    verticalScrollBar()->setPageStep(viewport()->height());
421    verticalScrollBar()->setRange(0, qMax(0, m_idealHeight - viewport()->height()));
422 
423    horizontalScrollBar()->setPageStep(viewport()->width());
424    horizontalScrollBar()->setRange(0, qMax(0, RowHeight - viewport()->width()));
425 
426    emit(visibleItemsChangedMaybe());
427 }
428 
getLayout() const429 QString GridView::getLayout() const
430 {
431    switch (m_viewMode)
432    {
433       case Simple:
434          return "simple";
435       case Anchored:
436          return "anchored";
437       case Centered:
438       default:
439          break;
440    }
441 
442    return "centered";
443 }
444 
setLayout(QString layout)445 void GridView::setLayout(QString layout)
446 {
447    if (layout == "anchored")
448       m_viewMode = Anchored;
449    else if (layout == "centered")
450       m_viewMode = Centered;
451    else if (layout == "fixed")
452       m_viewMode = Simple;
453 }
454 
getSpacing() const455 int GridView::getSpacing() const
456 {
457    return m_spacing;
458 }
459 
setSpacing(const int spacing)460 void GridView::setSpacing(const int spacing)
461 {
462    m_spacing = spacing;
463 }
464 
GridItem(QWidget * parent)465 GridItem::GridItem(QWidget* parent) : QWidget(parent)
466 , thumbnailVerticalAlignmentFlag(Qt::AlignBottom)
467 , padding(11)
468 {
469 }
470 
getPadding() const471 int GridItem::getPadding() const
472 {
473    return padding;
474 }
475 
setPadding(const int value)476 void GridItem::setPadding(const int value)
477 {
478    padding = value;
479 }
480 
getThumbnailVerticalAlign() const481 QString GridItem::getThumbnailVerticalAlign() const
482 {
483    switch (thumbnailVerticalAlignmentFlag)
484    {
485       case Qt::AlignTop:
486          return "top";
487       case Qt::AlignVCenter:
488          return "center";
489       case Qt::AlignBottom:
490       default:
491          break;
492    }
493 
494    return "bottom";
495 }
496 
setThumbnailVerticalAlign(const QString valign)497 void GridItem::setThumbnailVerticalAlign(const QString valign)
498 {
499    if (valign == "top")
500       thumbnailVerticalAlignmentFlag = Qt::AlignTop;
501    else if (valign == "center")
502       thumbnailVerticalAlignmentFlag = Qt::AlignVCenter;
503    else if (valign == "bottom")
504       thumbnailVerticalAlignmentFlag = Qt::AlignBottom;
505 }
506