~ubuntu-branches/ubuntu/saucy/clementine/saucy

« back to all changes in this revision

Viewing changes to src/widgets/groupediconview.cpp

  • Committer: Package Import Robot
  • Author(s): Thomas PIERSON
  • Date: 2012-01-01 20:43:39 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20120101204339-lsb6nndwhfy05sde
Tags: 1.0.1+dfsg-1
New upstream release. (Closes: #653926, #651611, #657391)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* This file is part of Clementine.
 
2
   Copyright 2010, David Sansome <me@davidsansome.com>
 
3
 
 
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.
 
8
 
 
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.
 
13
 
 
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/>.
 
16
*/
 
17
 
 
18
#include "groupediconview.h"
 
19
#include "core/multisortfilterproxy.h"
 
20
 
 
21
#include <QPainter>
 
22
#include <QPaintEvent>
 
23
#include <QScrollBar>
 
24
#include <QSortFilterProxyModel>
 
25
#include <QtDebug>
 
26
 
 
27
const int GroupedIconView::kBarThickness = 2;
 
28
const int GroupedIconView::kBarMarginTop = 3;
 
29
 
 
30
 
 
31
GroupedIconView::GroupedIconView(QWidget* parent)
 
32
  : QListView(parent),
 
33
    proxy_model_(new MultiSortFilterProxy(this)),
 
34
    default_header_height_(fontMetrics().height() +
 
35
      kBarMarginTop + kBarThickness),
 
36
    header_spacing_(10),
 
37
    header_indent_(5),
 
38
    item_indent_(10),
 
39
    header_text_("%1")
 
40
{
 
41
  setFlow(LeftToRight);
 
42
  setViewMode(IconMode);
 
43
  setResizeMode(Adjust);
 
44
  setWordWrap(true);
 
45
  setDragEnabled(false);
 
46
 
 
47
  proxy_model_->AddSortSpec(Role_Group);
 
48
  proxy_model_->setDynamicSortFilter(true);
 
49
 
 
50
  connect(proxy_model_, SIGNAL(modelReset()), SLOT(LayoutItems()));
 
51
}
 
52
 
 
53
void GroupedIconView::AddSortSpec(int role, Qt::SortOrder order) {
 
54
  proxy_model_->AddSortSpec(role, order);
 
55
}
 
56
 
 
57
void GroupedIconView::setModel(QAbstractItemModel* model) {
 
58
  proxy_model_->setSourceModel(model);
 
59
  proxy_model_->sort(0);
 
60
 
 
61
  QListView::setModel(proxy_model_);
 
62
  LayoutItems();
 
63
}
 
64
 
 
65
int GroupedIconView::header_height() const {
 
66
  return default_header_height_;
 
67
}
 
68
 
 
69
void GroupedIconView::DrawHeader(QPainter* painter, const QRect& rect,
 
70
                                 const QFont& font, const QPalette& palette,
 
71
                                 const QString& text) {
 
72
  painter->save();
 
73
 
 
74
  // Bold font
 
75
  QFont bold_font(font);
 
76
  bold_font.setBold(true);
 
77
  QFontMetrics metrics(bold_font);
 
78
 
 
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);
 
84
 
 
85
  // Draw text
 
86
  painter->setFont(bold_font);
 
87
  painter->drawText(text_rect, text);
 
88
 
 
89
  // Draw a line underneath
 
90
  const QPoint start(rect.left(), text_rect.bottom() + kBarMarginTop);
 
91
  const QPoint end(rect.right(), start.y());
 
92
 
 
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);
 
98
 
 
99
  painter->restore();
 
100
}
 
101
 
 
102
void GroupedIconView::resizeEvent(QResizeEvent* e) {
 
103
  QListView::resizeEvent(e);
 
104
  LayoutItems();
 
105
}
 
106
 
 
107
void GroupedIconView::rowsInserted(const QModelIndex& parent, int start, int end) {
 
108
  QListView::rowsInserted(parent, start, end);
 
109
  LayoutItems();
 
110
}
 
111
 
 
112
void GroupedIconView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) {
 
113
  QListView::dataChanged(topLeft, bottomRight);
 
114
  LayoutItems();
 
115
}
 
116
 
 
117
void GroupedIconView::LayoutItems() {
 
118
  if (!model())
 
119
    return;
 
120
 
 
121
  const int count = model()->rowCount();
 
122
 
 
123
  QString last_group;
 
124
  QPoint next_position(0, 0);
 
125
  int max_row_height = 0;
 
126
 
 
127
  visual_rects_.clear();
 
128
  visual_rects_.reserve(count);
 
129
  headers_.clear();
 
130
 
 
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());
 
135
 
 
136
    // Is this the first item in a new group?
 
137
    if (group != last_group) {
 
138
      // Add the group header.
 
139
      Header header;
 
140
      header.y = next_position.y() + max_row_height + header_indent_;
 
141
      header.first_row = i;
 
142
      header.text = group;
 
143
 
 
144
      if (!last_group.isNull()) {
 
145
        header.y += header_spacing_;
 
146
      }
 
147
 
 
148
      headers_ << header;
 
149
 
 
150
      // Remember this group so we don't add it again.
 
151
      last_group = group;
 
152
 
 
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_);
 
156
      max_row_height = 0;
 
157
    }
 
158
 
 
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_);
 
163
    } else {
 
164
      this_position.setX(this_position.x() + spacing());
 
165
    }
 
166
 
 
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_);
 
173
 
 
174
      max_row_height = 0;
 
175
    }
 
176
 
 
177
    // Set this item's geometry
 
178
    visual_rects_.append(QRect(this_position, size));
 
179
 
 
180
    // Update next index
 
181
    next_position.setX(this_position.x() + size.width());
 
182
    max_row_height = qMax(max_row_height, size.height());
 
183
  }
 
184
 
 
185
  verticalScrollBar()->setRange(0, next_position.y() + max_row_height - viewport()->height());
 
186
  update();
 
187
}
 
188
 
 
189
QRect GroupedIconView::visualRect(const QModelIndex& index) const {
 
190
  if (index.row() < 0 || index.row() >= visual_rects_.count())
 
191
    return QRect();
 
192
  return visual_rects_[index.row()].translated(-horizontalOffset(), -verticalOffset());
 
193
}
 
194
 
 
195
QModelIndex GroupedIconView::indexAt(const QPoint& p) const {
 
196
  const QPoint viewport_p = p + QPoint(horizontalOffset(), verticalOffset());
 
197
 
 
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);
 
202
    }
 
203
  }
 
204
  return QModelIndex();
 
205
}
 
206
 
 
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.
 
210
 
 
211
  QStyleOptionViewItemV4 option(viewOptions());
 
212
  if (isWrapping())
 
213
    option.features = QStyleOptionViewItemV2::WrapText;
 
214
  option.locale = locale();
 
215
  option.locale.setNumberOptions(QLocale::OmitGroupSeparator);
 
216
  option.widget = this;
 
217
 
 
218
  QPainter painter(viewport());
 
219
 
 
220
  const QRect viewport_rect(e->rect().translated(horizontalOffset(), verticalOffset()));
 
221
  QVector<QModelIndex> toBeRendered = IntersectingItems(viewport_rect);
 
222
 
 
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;
 
230
 
 
231
  int maxSize = (flow() == TopToBottom)
 
232
      ? viewport()->size().width() - 2 * spacing()
 
233
      : viewport()->size().height() - 2 * spacing();
 
234
 
 
235
  QVector<QModelIndex>::const_iterator end = toBeRendered.constEnd();
 
236
  for (QVector<QModelIndex>::const_iterator it = toBeRendered.constBegin(); it != end; ++it) {
 
237
    if (!it->isValid()) {
 
238
      continue;
 
239
    }
 
240
 
 
241
    option.rect = visualRect(*it);
 
242
 
 
243
    if (flow() == TopToBottom)
 
244
      option.rect.setWidth(qMin(maxSize, option.rect.width()));
 
245
    else
 
246
      option.rect.setHeight(qMin(maxSize, option.rect.height()));
 
247
 
 
248
    option.state = state;
 
249
    if (selections && selections->isSelected(*it))
 
250
      option.state |= QStyle::State_Selected;
 
251
    if (enabled) {
 
252
      QPalette::ColorGroup cg;
 
253
      if ((itemModel->flags(*it) & Qt::ItemIsEnabled) == 0) {
 
254
        option.state &= ~QStyle::State_Enabled;
 
255
        cg = QPalette::Disabled;
 
256
      } else {
 
257
        cg = QPalette::Normal;
 
258
      }
 
259
      option.palette.setCurrentColorGroup(cg);
 
260
    }
 
261
    if (focus && current == *it) {
 
262
      option.state |= QStyle::State_HasFocus;
 
263
      if (viewState == EditingState)
 
264
        option.state |= QStyle::State_Editing;
 
265
    }
 
266
 
 
267
    itemDelegate()->paint(&painter, option, *it);
 
268
  }
 
269
 
 
270
  // Draw headers
 
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());
 
275
 
 
276
    // Is this header contained in the area we're drawing?
 
277
    if (!header_rect.intersects(viewport_rect)) {
 
278
      continue;
 
279
    }
 
280
 
 
281
    // Draw the header
 
282
    DrawHeader(&painter,
 
283
               header_rect.translated(-horizontalOffset(), -verticalOffset()),
 
284
               font(),
 
285
               palette(),
 
286
               model()->index(header.first_row, 0).data(Role_Group).toString());
 
287
  }
 
288
}
 
289
 
 
290
void GroupedIconView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command) {
 
291
  QVector<QModelIndex> indexes(IntersectingItems(rect.translated(horizontalOffset(), verticalOffset())));
 
292
  QItemSelection selection;
 
293
 
 
294
  foreach (const QModelIndex& index, indexes) {
 
295
    selection << QItemSelectionRange(index);
 
296
  }
 
297
 
 
298
  selectionModel()->select(selection, command);
 
299
}
 
300
 
 
301
QVector<QModelIndex> GroupedIconView::IntersectingItems(const QRect& rect) const {
 
302
  QVector<QModelIndex> ret;
 
303
 
 
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));
 
308
    }
 
309
  }
 
310
 
 
311
  return ret;
 
312
}
 
313
 
 
314
QRegion GroupedIconView::visualRegionForSelection(const QItemSelection& selection) const {
 
315
  QRegion ret;
 
316
  foreach (const QModelIndex& index, selection.indexes()) {
 
317
    ret += visual_rects_[index.row()];
 
318
  }
 
319
  return ret;
 
320
}
 
321
 
 
322
QModelIndex GroupedIconView::moveCursor(CursorAction action, Qt::KeyboardModifiers) {
 
323
  if (model()->rowCount() == 0) {
 
324
    return QModelIndex();
 
325
  }
 
326
 
 
327
  int ret = currentIndex().row();
 
328
  if (ret == -1) {
 
329
    ret = 0;
 
330
  }
 
331
 
 
332
  switch (action) {
 
333
    case MoveUp:    ret = IndexAboveOrBelow(ret, -1); break;
 
334
    case MovePrevious:
 
335
    case MoveLeft:  ret --; break;
 
336
    case MoveDown:  ret = IndexAboveOrBelow(ret, +1); break;
 
337
    case MoveNext:
 
338
    case MoveRight: ret ++; break;
 
339
    case MovePageUp:
 
340
    case MoveHome:  ret = 0; break;
 
341
    case MovePageDown:
 
342
    case MoveEnd:   ret = model()->rowCount() - 1; break;
 
343
  }
 
344
 
 
345
  return model()->index(qBound(0, ret, model()->rowCount()), 0);
 
346
}
 
347
 
 
348
int GroupedIconView::IndexAboveOrBelow(int index, int d) const {
 
349
  const QRect orig_rect(visual_rects_[index]);
 
350
 
 
351
  while (index >= 0 && index < visual_rects_.count()) {
 
352
    const QRect rect(visual_rects_[index]);
 
353
    const QPoint center(rect.center());
 
354
 
 
355
    if ((center.y() <= orig_rect.top() || center.y() >= orig_rect.bottom()) &&
 
356
        center.x() >= orig_rect.left() &&
 
357
        center.x() <= orig_rect.right()) {
 
358
      return index;
 
359
    }
 
360
 
 
361
    index += d;
 
362
  }
 
363
 
 
364
  return index;
 
365
}