]> cloud.milkyroute.net Git - dolphin.git/blob - src/klistview.cpp
Added Rafael López's item categorizer into Dolphin (it's currently deactivated in...
[dolphin.git] / src / klistview.cpp
1 /**
2 * This file is part of the KDE project
3 * Copyright (C) 2007 Rafael Fernández López <ereslibre@gmail.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 // NOTE: rectForIndex() not virtual on QListView !! relevant ?
22
23 #include <QPainter>
24 #include <QScrollBar>
25 #include <QPaintEvent>
26 #include <QSortFilterProxyModel>
27
28 #include <kdebug.h>
29
30 #include "klistview.h"
31 #include "klistview_p.h"
32 #include "kitemcategorizer.h"
33
34 KListView::Private::Private(KListView *listView)
35 : listView(listView)
36 , modelSortCapable(false)
37 , itemCategorizer(0)
38 , numCategories(0)
39 , proxyModel(0)
40 {
41 }
42
43 KListView::Private::~Private()
44 {
45 }
46
47 QModelIndexList KListView::Private::intersectionSet(const QRect &rect) const
48 {
49 // FIXME: boost me, I suck (ereslibre)
50
51 QModelIndexList modelIndexList;
52
53 QModelIndex index;
54 for (int i = 0; i < listView->model()->rowCount(); i++)
55 {
56 index = listView->model()->index(i, 0);
57
58 if (rect.intersects(listView->visualRect(index)))
59 modelIndexList.append(index);
60 }
61
62 return modelIndexList;
63 }
64
65 KListView::KListView(QWidget *parent)
66 : QListView(parent)
67 , d(new Private(this))
68 {
69 }
70
71 KListView::~KListView()
72 {
73 if (d->proxyModel)
74 {
75 QObject::disconnect(this->model(), SIGNAL(layoutChanged()),
76 this , SLOT(itemsLayoutChanged()));
77 }
78
79 delete d;
80 }
81
82 void KListView::setModel(QAbstractItemModel *model)
83 {
84 QSortFilterProxyModel *proxyModel =
85 qobject_cast<QSortFilterProxyModel*>(model);
86
87 if (this->model() && this->model()->rowCount())
88 {
89 QObject::disconnect(this->model(), SIGNAL(layoutChanged()),
90 this , SLOT(itemsLayoutChanged()));
91
92 rowsAboutToBeRemovedArtifficial(QModelIndex(), 0,
93 this->model()->rowCount() - 1);
94 }
95
96 d->modelSortCapable = (proxyModel != 0);
97 d->proxyModel = proxyModel;
98
99 // If the model was initialized before applying to the view, we update
100 // internal data structure of the view with the model information
101 if (model->rowCount())
102 {
103 rowsInsertedArtifficial(QModelIndex(), 0, model->rowCount() - 1);
104 }
105
106 QListView::setModel(model);
107
108 QObject::connect(model, SIGNAL(layoutChanged()),
109 this , SLOT(itemsLayoutChanged()));
110 }
111
112 QRect KListView::visualRect(const QModelIndex &index) const
113 {
114 // FIXME: right to left languages (ereslibre)
115 // FIXME: drag & drop support (ereslibre)
116 // FIXME: do not forget to remove itemWidth's hard-coded values that were
117 // only for testing purposes. We might like to calculate the best
118 // width, but what we would really like for sure is that all items
119 // have the same width, as well as the same height (ereslibre)
120
121 if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
122 !d->itemCategorizer)
123 {
124 return QListView::visualRect(index);
125 }
126
127 QRect retRect(spacing(), spacing(), 0, 0);
128 int viewportWidth = viewport()->width() - spacing();
129 int dx = -horizontalOffset();
130 int dy = -verticalOffset();
131
132 if (verticalScrollBar() && !verticalScrollBar()->isHidden())
133 viewportWidth -= verticalScrollBar()->width();
134
135 int itemHeight = sizeHintForIndex(index).height();
136 int itemWidth = 130; // NOTE: ghosts in here !
137 int itemWidthPlusSeparation = spacing() + itemWidth;
138 int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
139 if (!elementsPerRow)
140 elementsPerRow++;
141 QModelIndex currentIndex = d->proxyModel->index(index.row(), 0);
142 QString itemCategory = d->itemCategorizer->categoryForItem(currentIndex,
143 d->proxyModel->sortRole());
144 int naturalRow = index.row() / elementsPerRow;
145 int naturalTop = naturalRow * itemHeight + naturalRow * spacing();
146
147 int rowsForCategory;
148 int lastIndexShown = -1;
149 foreach (QString category, d->categories)
150 {
151 retRect.setTop(retRect.top() + spacing());
152
153 if (category == itemCategory)
154 {
155 break;
156 }
157
158 rowsForCategory = (d->elementsPerCategory[category] / elementsPerRow);
159
160 if ((d->elementsPerCategory[category] % elementsPerRow) ||
161 !rowsForCategory)
162 {
163 rowsForCategory++;
164 }
165
166 lastIndexShown += d->elementsPerCategory[category];
167
168 retRect.setTop(retRect.top() + categoryHeight(viewOptions()) +
169 (rowsForCategory * spacing() * 2) +
170 (rowsForCategory * itemHeight));
171 }
172
173 int rowToPosition = (index.row() - (lastIndexShown + 1)) / elementsPerRow;
174 int columnToPosition = (index.row() - (lastIndexShown + 1)) %
175 elementsPerRow;
176
177 retRect.setTop(retRect.top() + (rowToPosition * spacing() * 2) +
178 (rowToPosition * itemHeight));
179
180 retRect.setLeft(retRect.left() + (columnToPosition * spacing()) +
181 (columnToPosition * itemWidth));
182
183 retRect.setWidth(130); // NOTE: ghosts in here !
184 retRect.setHeight(itemHeight);
185
186 retRect.adjust(dx, dy, dx, dy);
187
188 return retRect;
189 }
190
191 KItemCategorizer *KListView::itemCategorizer() const
192 {
193 return d->itemCategorizer;
194 }
195
196 void KListView::setItemCategorizer(KItemCategorizer *itemCategorizer)
197 {
198 d->itemCategorizer = itemCategorizer;
199
200 if (itemCategorizer)
201 itemsLayoutChanged();
202 }
203
204 QModelIndex KListView::indexAt(const QPoint &point) const
205 {
206 QModelIndex index;
207
208 if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
209 !d->itemCategorizer)
210 {
211 return QListView::indexAt(point);
212 }
213
214 QModelIndexList item = d->intersectionSet(QRect(point, point));
215
216 if (item.count() == 1)
217 index = item[0];
218
219 d->hovered = index;
220
221 return index;
222 }
223
224 int KListView::sizeHintForRow(int row) const
225 {
226 if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
227 !d->itemCategorizer)
228 {
229 return QListView::sizeHintForRow(row);
230 }
231
232 QModelIndex index = d->proxyModel->index(0, 0);
233
234 if (!index.isValid())
235 return 0;
236
237 return sizeHintForIndex(index).height() + categoryHeight(viewOptions()) +
238 spacing();
239 }
240
241 void KListView::drawNewCategory(const QString &category,
242 const QStyleOptionViewItem &option,
243 QPainter *painter)
244 {
245 painter->drawText(option.rect.topLeft(), category);
246 }
247
248 int KListView::categoryHeight(const QStyleOptionViewItem &option) const
249 {
250 return option.fontMetrics.height();
251 }
252
253 void KListView::paintEvent(QPaintEvent *event)
254 {
255 if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
256 !d->itemCategorizer)
257 {
258 QListView::paintEvent(event);
259 return;
260 }
261
262 if (!itemDelegate())
263 return;
264
265 QStyleOptionViewItemV3 option = viewOptions();
266 QPainter painter(viewport());
267 QRect area = event->rect();
268 const bool focus = (hasFocus() || viewport()->hasFocus()) &&
269 currentIndex().isValid();
270 const QStyle::State state = option.state;
271 const bool enabled = (state & QStyle::State_Enabled) != 0;
272
273 int totalHeight = 0;
274 QModelIndex index;
275 QString prevCategory;
276 QModelIndexList dirtyIndexes = d->intersectionSet(area);
277 foreach (const QModelIndex &index, dirtyIndexes)
278 {
279 option.state = state;
280 option.rect = visualRect(index);
281 if (selectionModel() && selectionModel()->isSelected(index))
282 option.state |= QStyle::State_Selected;
283 if (enabled)
284 {
285 QPalette::ColorGroup cg;
286 if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0)
287 {
288 option.state &= ~QStyle::State_Enabled;
289 cg = QPalette::Disabled;
290 }
291 else
292 {
293 cg = QPalette::Normal;
294 }
295 option.palette.setCurrentColorGroup(cg);
296 }
297 if (focus && currentIndex() == index)
298 {
299 option.state |= QStyle::State_HasFocus;
300 if (this->state() == EditingState)
301 option.state |= QStyle::State_Editing;
302 }
303
304 if (index == d->hovered)
305 option.state |= QStyle::State_MouseOver;
306 else
307 option.state &= ~QStyle::State_MouseOver;
308
309 if (prevCategory != d->itemCategorizer->categoryForItem(index,
310 d->proxyModel->sortRole()))
311 {
312 prevCategory = d->itemCategorizer->categoryForItem(index,
313 d->proxyModel->sortRole());
314 drawNewCategory(prevCategory, option, &painter);
315 }
316 itemDelegate(index)->paint(&painter, option, index);
317 }
318 }
319
320 void KListView::setSelection(const QRect &rect,
321 QItemSelectionModel::SelectionFlags flags)
322 {
323 // TODO: implement me
324
325 QListView::setSelection(rect, flags);
326
327 /*if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
328 !d->itemCategorizer)
329 {
330 QListView::setSelection(rect, flags);
331 return;
332 }
333
334 QModelIndex index;
335 for (int i = 0; i < d->proxyModel->rowCount(); i++)
336 {
337 index = d->proxyModel->index(i, 0);
338 if (rect.intersects(visualRect(index)))
339 {
340 selectionModel()->select(index, QItemSelectionModel::Select);
341 }
342 else
343 {
344 selectionModel()->select(index, QItemSelectionModel::Deselect);
345 }
346 }*/
347
348 //selectionModel()->select(selection, flags);
349 }
350
351 void KListView::timerEvent(QTimerEvent *event)
352 {
353 QListView::timerEvent(event);
354
355 if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
356 !d->itemCategorizer)
357 {
358 return;
359 }
360 }
361
362 void KListView::rowsInserted(const QModelIndex &parent,
363 int start,
364 int end)
365 {
366 QListView::rowsInserted(parent, start, end);
367 rowsInsertedArtifficial(parent, start, end);
368 }
369
370 void KListView::rowsAboutToBeRemoved(const QModelIndex &parent,
371 int start,
372 int end)
373 {
374 QListView::rowsAboutToBeRemoved(parent, start, end);
375 rowsAboutToBeRemovedArtifficial(parent, start, end);
376 }
377
378 void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
379 int start,
380 int end)
381 {
382 if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
383 !d->itemCategorizer)
384 {
385 return;
386 }
387
388 QString category;
389 QModelIndex index;
390 for (int i = start; i <= end; i++)
391 {
392 index = d->proxyModel->index(i, 0, parent);
393 category = d->itemCategorizer->categoryForItem(index,
394 d->proxyModel->sortRole());
395
396 if (d->elementsPerCategory.contains(category))
397 d->elementsPerCategory[category]++;
398 else
399 {
400 d->elementsPerCategory.insert(category, 1);
401 d->categories.append(category);
402 }
403 }
404 }
405
406 void KListView::rowsAboutToBeRemovedArtifficial(const QModelIndex &parent,
407 int start,
408 int end)
409 {
410 if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
411 !d->itemCategorizer)
412 {
413 return;
414 }
415
416 d->hovered = QModelIndex();
417
418 QString category;
419 QModelIndex index;
420 for (int i = start; i <= end; i++)
421 {
422 index = d->proxyModel->index(i, 0, parent);
423 category = d->itemCategorizer->categoryForItem(index,
424 d->proxyModel->sortRole());
425
426 if (d->elementsPerCategory.contains(category))
427 {
428 d->elementsPerCategory[category]--;
429
430 if (!d->elementsPerCategory[category])
431 {
432 d->elementsPerCategory.remove(category);
433 d->categories.removeAll(category);
434 }
435 }
436 }
437 }
438
439 void KListView::itemsLayoutChanged()
440 {
441 d->elementsPerCategory.clear();
442 d->categories.clear();
443
444 if (d->proxyModel && d->proxyModel->rowCount())
445 rowsInsertedArtifficial(QModelIndex(), 0,
446 d->proxyModel->rowCount() - 1);
447 }
448
449 #include "klistview.moc"