1
/* This file is part of Clementine.
2
Copyright 2010, David Sansome <me@davidsansome.com>
4
Clementine is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
9
Clementine is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
GNU General Public License for more details.
14
You should have received a copy of the GNU General Public License
15
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
18
#include "groupediconview.h"
19
#include "core/multisortfilterproxy.h"
22
#include <QPaintEvent>
24
#include <QSortFilterProxyModel>
27
const int GroupedIconView::kBarThickness = 2;
28
const int GroupedIconView::kBarMarginTop = 3;
31
GroupedIconView::GroupedIconView(QWidget* parent)
33
proxy_model_(new MultiSortFilterProxy(this)),
34
default_header_height_(fontMetrics().height() +
35
kBarMarginTop + kBarThickness),
42
setViewMode(IconMode);
43
setResizeMode(Adjust);
45
setDragEnabled(false);
47
proxy_model_->AddSortSpec(Role_Group);
48
proxy_model_->setDynamicSortFilter(true);
50
connect(proxy_model_, SIGNAL(modelReset()), SLOT(LayoutItems()));
53
void GroupedIconView::AddSortSpec(int role, Qt::SortOrder order) {
54
proxy_model_->AddSortSpec(role, order);
57
void GroupedIconView::setModel(QAbstractItemModel* model) {
58
proxy_model_->setSourceModel(model);
59
proxy_model_->sort(0);
61
QListView::setModel(proxy_model_);
65
int GroupedIconView::header_height() const {
66
return default_header_height_;
69
void GroupedIconView::DrawHeader(QPainter* painter, const QRect& rect,
70
const QFont& font, const QPalette& palette,
71
const QString& text) {
75
QFont bold_font(font);
76
bold_font.setBold(true);
77
QFontMetrics metrics(bold_font);
79
QRect text_rect(rect);
80
text_rect.setHeight(metrics.height());
81
text_rect.moveTop(rect.top() + (rect.height() - text_rect.height() -
82
kBarThickness - kBarMarginTop) / 2);
83
text_rect.setLeft(text_rect.left() + 3);
86
painter->setFont(bold_font);
87
painter->drawText(text_rect, text);
89
// Draw a line underneath
90
const QPoint start(rect.left(), text_rect.bottom() + kBarMarginTop);
91
const QPoint end(rect.right(), start.y());
93
painter->setRenderHint(QPainter::Antialiasing, true);
94
painter->setPen(QPen(palette.color(QPalette::Disabled, QPalette::Text),
95
kBarThickness, Qt::SolidLine, Qt::RoundCap));
96
painter->setOpacity(0.5);
97
painter->drawLine(start, end);
102
void GroupedIconView::resizeEvent(QResizeEvent* e) {
103
QListView::resizeEvent(e);
107
void GroupedIconView::rowsInserted(const QModelIndex& parent, int start, int end) {
108
QListView::rowsInserted(parent, start, end);
112
void GroupedIconView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) {
113
QListView::dataChanged(topLeft, bottomRight);
117
void GroupedIconView::LayoutItems() {
121
const int count = model()->rowCount();
124
QPoint next_position(0, 0);
125
int max_row_height = 0;
127
visual_rects_.clear();
128
visual_rects_.reserve(count);
131
for (int i=0 ; i<count ; ++i) {
132
const QModelIndex index(model()->index(i, 0));
133
const QString group = index.data(Role_Group).toString();
134
const QSize size(rectForIndex(index).size());
136
// Is this the first item in a new group?
137
if (group != last_group) {
138
// Add the group header.
140
header.y = next_position.y() + max_row_height + header_indent_;
141
header.first_row = i;
144
if (!last_group.isNull()) {
145
header.y += header_spacing_;
150
// Remember this group so we don't add it again.
153
// Move the next item immediately below the header.
154
next_position.setX(0);
155
next_position.setY(header.y + header_height() + header_indent_ + header_spacing_);
159
// Take into account padding and spacing
160
QPoint this_position(next_position);
161
if (this_position.x() == 0) {
162
this_position.setX(this_position.x() + item_indent_);
164
this_position.setX(this_position.x() + spacing());
167
// Should this item wrap?
168
if (next_position.x() != 0 && this_position.x() + size.width() >= viewport()->width()) {
169
next_position.setX(0);
170
next_position.setY(next_position.y() + max_row_height);
171
this_position = next_position;
172
this_position.setX(this_position.x() + item_indent_);
177
// Set this item's geometry
178
visual_rects_.append(QRect(this_position, size));
181
next_position.setX(this_position.x() + size.width());
182
max_row_height = qMax(max_row_height, size.height());
185
verticalScrollBar()->setRange(0, next_position.y() + max_row_height - viewport()->height());
189
QRect GroupedIconView::visualRect(const QModelIndex& index) const {
190
if (index.row() < 0 || index.row() >= visual_rects_.count())
192
return visual_rects_[index.row()].translated(-horizontalOffset(), -verticalOffset());
195
QModelIndex GroupedIconView::indexAt(const QPoint& p) const {
196
const QPoint viewport_p = p + QPoint(horizontalOffset(), verticalOffset());
198
const int count = visual_rects_.count();
199
for (int i=0 ; i<count ; ++i) {
200
if (visual_rects_[i].contains(viewport_p)) {
201
return model()->index(i, 0);
204
return QModelIndex();
207
void GroupedIconView::paintEvent(QPaintEvent* e) {
208
// This code was adapted from QListView::paintEvent(), changed to use the
209
// visualRect() of items, and to draw headers.
211
QStyleOptionViewItemV4 option(viewOptions());
213
option.features = QStyleOptionViewItemV2::WrapText;
214
option.locale = locale();
215
option.locale.setNumberOptions(QLocale::OmitGroupSeparator);
216
option.widget = this;
218
QPainter painter(viewport());
220
const QRect viewport_rect(e->rect().translated(horizontalOffset(), verticalOffset()));
221
QVector<QModelIndex> toBeRendered = IntersectingItems(viewport_rect);
223
const QModelIndex current = currentIndex();
224
const QAbstractItemModel *itemModel = model();
225
const QItemSelectionModel *selections = selectionModel();
226
const bool focus = (hasFocus() || viewport()->hasFocus()) && current.isValid();
227
const QStyle::State state = option.state;
228
const QAbstractItemView::State viewState = this->state();
229
const bool enabled = (state & QStyle::State_Enabled) != 0;
231
int maxSize = (flow() == TopToBottom)
232
? viewport()->size().width() - 2 * spacing()
233
: viewport()->size().height() - 2 * spacing();
235
QVector<QModelIndex>::const_iterator end = toBeRendered.constEnd();
236
for (QVector<QModelIndex>::const_iterator it = toBeRendered.constBegin(); it != end; ++it) {
237
if (!it->isValid()) {
241
option.rect = visualRect(*it);
243
if (flow() == TopToBottom)
244
option.rect.setWidth(qMin(maxSize, option.rect.width()));
246
option.rect.setHeight(qMin(maxSize, option.rect.height()));
248
option.state = state;
249
if (selections && selections->isSelected(*it))
250
option.state |= QStyle::State_Selected;
252
QPalette::ColorGroup cg;
253
if ((itemModel->flags(*it) & Qt::ItemIsEnabled) == 0) {
254
option.state &= ~QStyle::State_Enabled;
255
cg = QPalette::Disabled;
257
cg = QPalette::Normal;
259
option.palette.setCurrentColorGroup(cg);
261
if (focus && current == *it) {
262
option.state |= QStyle::State_HasFocus;
263
if (viewState == EditingState)
264
option.state |= QStyle::State_Editing;
267
itemDelegate()->paint(&painter, option, *it);
271
foreach (const Header& header, headers_) {
272
const QRect header_rect = QRect(
273
header_indent_, header.y,
274
viewport()->width() - header_indent_ * 2, header_height());
276
// Is this header contained in the area we're drawing?
277
if (!header_rect.intersects(viewport_rect)) {
283
header_rect.translated(-horizontalOffset(), -verticalOffset()),
286
model()->index(header.first_row, 0).data(Role_Group).toString());
290
void GroupedIconView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command) {
291
QVector<QModelIndex> indexes(IntersectingItems(rect.translated(horizontalOffset(), verticalOffset())));
292
QItemSelection selection;
294
foreach (const QModelIndex& index, indexes) {
295
selection << QItemSelectionRange(index);
298
selectionModel()->select(selection, command);
301
QVector<QModelIndex> GroupedIconView::IntersectingItems(const QRect& rect) const {
302
QVector<QModelIndex> ret;
304
const int count = visual_rects_.count();
305
for (int i=0 ; i<count ; ++i) {
306
if (rect.intersects(visual_rects_[i])) {
307
ret.append(model()->index(i, 0));
314
QRegion GroupedIconView::visualRegionForSelection(const QItemSelection& selection) const {
316
foreach (const QModelIndex& index, selection.indexes()) {
317
ret += visual_rects_[index.row()];
322
QModelIndex GroupedIconView::moveCursor(CursorAction action, Qt::KeyboardModifiers) {
323
if (model()->rowCount() == 0) {
324
return QModelIndex();
327
int ret = currentIndex().row();
333
case MoveUp: ret = IndexAboveOrBelow(ret, -1); break;
335
case MoveLeft: ret --; break;
336
case MoveDown: ret = IndexAboveOrBelow(ret, +1); break;
338
case MoveRight: ret ++; break;
340
case MoveHome: ret = 0; break;
342
case MoveEnd: ret = model()->rowCount() - 1; break;
345
return model()->index(qBound(0, ret, model()->rowCount()), 0);
348
int GroupedIconView::IndexAboveOrBelow(int index, int d) const {
349
const QRect orig_rect(visual_rects_[index]);
351
while (index >= 0 && index < visual_rects_.count()) {
352
const QRect rect(visual_rects_[index]);
353
const QPoint center(rect.center());
355
if ((center.y() <= orig_rect.top() || center.y() >= orig_rect.bottom()) &&
356
center.x() >= orig_rect.left() &&
357
center.x() <= orig_rect.right()) {