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