]> cloud.milkyroute.net Git - dolphin.git/blob - src/klistview.cpp
Let deselect items individually using Ctrl+Click
[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 #include "klistview.h"
22 #include "klistview_p.h"
23
24 #include <math.h> // trunc on C99 compliant systems
25 #include <kdefakes.h> // trunc for not C99 compliant systems
26
27 #include <QApplication>
28 #include <QPainter>
29 #include <QScrollBar>
30 #include <QPaintEvent>
31
32 #include <kdebug.h>
33 #include <kstyle.h>
34
35 #include "kitemcategorizer.h"
36 #include "ksortfilterproxymodel.h"
37
38 class LessThan
39 {
40 public:
41 enum Purpose
42 {
43 GeneralPurpose = 0,
44 CategoryPurpose
45 };
46
47 inline LessThan(const KSortFilterProxyModel *proxyModel,
48 Purpose purpose)
49 : proxyModel(proxyModel)
50 , purpose(purpose)
51 {
52 }
53
54 inline bool operator()(const QModelIndex &left,
55 const QModelIndex &right) const
56 {
57 if (purpose == GeneralPurpose)
58 {
59 return proxyModel->sortOrder() == Qt::AscendingOrder ?
60 proxyModel->lessThanGeneralPurpose(left, right) :
61 !proxyModel->lessThanGeneralPurpose(left, right);
62 }
63
64 return proxyModel->sortOrder() == Qt::AscendingOrder ?
65 proxyModel->lessThanCategoryPurpose(left, right) :
66 !proxyModel->lessThanCategoryPurpose(left, right);
67 }
68
69 private:
70 const KSortFilterProxyModel *proxyModel;
71 const Purpose purpose;
72 };
73
74
75 //==============================================================================
76
77
78 KListView::Private::Private(KListView *listView)
79 : listView(listView)
80 , itemCategorizer(0)
81 , mouseButtonPressed(false)
82 , isDragging(false)
83 , proxyModel(0)
84 , lastIndex(QModelIndex())
85 {
86 }
87
88 KListView::Private::~Private()
89 {
90 }
91
92 const QModelIndexList &KListView::Private::intersectionSet(const QRect &rect)
93 {
94 QModelIndex index;
95 QRect indexVisualRect;
96
97 intersectedIndexes.clear();
98
99 // Lets find out where we should start
100 int top = proxyModel->rowCount() - 1;
101 int bottom = 0;
102 int middle = (top + bottom) / 2;
103 while (bottom <= top)
104 {
105 middle = (top + bottom) / 2;
106
107 index = elementDictionary[proxyModel->index(middle, 0)];
108 indexVisualRect = visualRect(index);
109
110 if (qMax(indexVisualRect.topLeft().y(),
111 indexVisualRect.bottomRight().y()) < qMin(rect.topLeft().y(),
112 rect.bottomRight().y()))
113 {
114 bottom = middle + 1;
115 }
116 else
117 {
118 top = middle - 1;
119 }
120 }
121
122 int j = 0;
123 for (int i = middle; i < proxyModel->rowCount(); i++)
124 {
125 index = elementDictionary[proxyModel->index(i, 0)];
126 indexVisualRect = visualRect(index);
127
128 if (rect.intersects(indexVisualRect))
129 intersectedIndexes.append(index);
130
131 // If we passed next item, stop searching for hits
132 if (qMax(rect.bottomRight().y(), rect.topLeft().y()) <
133 indexVisualRect.topLeft().y())
134 break;
135
136 j++;
137 }
138
139 return intersectedIndexes;
140 }
141
142 QRect KListView::Private::visualRectInViewport(const QModelIndex &index) const
143 {
144 if (!index.isValid())
145 return QRect();
146
147 QString curCategory = elementsInfo[index].category;
148
149 QRect retRect(listView->spacing(), listView->spacing() * 2 +
150 30 /* categoryHeight */, 0, 0);
151
152 int viewportWidth = listView->viewport()->width() - listView->spacing();
153
154 // We really need all items to be of same size. Otherwise we cannot do this
155 // (ereslibre)
156 // QSize itemSize =
157 // listView->sizeHintForIndex(proxyModel->mapFromSource(index));
158 // int itemHeight = itemSize.height();
159 // int itemWidth = itemSize.width();*/
160 int itemHeight = 107;
161 int itemWidth = 130;
162 int itemWidthPlusSeparation = listView->spacing() + itemWidth;
163 int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
164 if (!elementsPerRow)
165 elementsPerRow++;
166
167 int column = elementsInfo[index].relativeOffsetToCategory % elementsPerRow;
168 int row = elementsInfo[index].relativeOffsetToCategory / elementsPerRow;
169
170 retRect.setLeft(retRect.left() + column * listView->spacing() +
171 column * itemWidth);
172
173 float rows;
174 int rowsInt;
175 foreach (const QString &category, categories)
176 {
177 if (category == curCategory)
178 break;
179
180 rows = (float) ((float) categoriesIndexes[category].count() /
181 (float) elementsPerRow);
182 rowsInt = categoriesIndexes[category].count() / elementsPerRow;
183
184 if (rows - trunc(rows)) rowsInt++;
185
186 retRect.setTop(retRect.top() +
187 (rowsInt * listView->spacing()) +
188 (rowsInt * itemHeight) +
189 30 /* categoryHeight */ +
190 listView->spacing() * 2);
191 }
192
193 retRect.setTop(retRect.top() + row * listView->spacing() +
194 row * itemHeight);
195
196 retRect.setWidth(itemWidth);
197 retRect.setHeight(itemHeight);
198
199 return retRect;
200 }
201
202 QRect KListView::Private::visualCategoryRectInViewport(const QString &category)
203 const
204 {
205 QRect retRect(listView->spacing(),
206 listView->spacing(),
207 listView->viewport()->width() - listView->spacing() * 2,
208 0);
209
210 if (!proxyModel->rowCount() || !categories.contains(category))
211 return QRect();
212
213 QModelIndex index = proxyModel->index(0, 0, QModelIndex());
214
215 int viewportWidth = listView->viewport()->width() - listView->spacing();
216
217 // We really need all items to be of same size. Otherwise we cannot do this
218 // (ereslibre)
219 // QSize itemSize = listView->sizeHintForIndex(index);
220 // int itemHeight = itemSize.height();
221 // int itemWidth = itemSize.width();
222 int itemHeight = 107;
223 int itemWidth = 130;
224 int itemWidthPlusSeparation = listView->spacing() + itemWidth;
225 int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
226
227 if (!elementsPerRow)
228 elementsPerRow++;
229
230 float rows;
231 int rowsInt;
232 foreach (const QString &itCategory, categories)
233 {
234 if (itCategory == category)
235 break;
236
237 rows = (float) ((float) categoriesIndexes[itCategory].count() /
238 (float) elementsPerRow);
239 rowsInt = categoriesIndexes[itCategory].count() / elementsPerRow;
240
241 if (rows - trunc(rows)) rowsInt++;
242
243 retRect.setTop(retRect.top() +
244 (rowsInt * listView->spacing()) +
245 (rowsInt * itemHeight) +
246 30 /* categoryHeight */ +
247 listView->spacing() * 2);
248 }
249
250 retRect.setHeight(30 /* categoryHeight */);
251
252 return retRect;
253 }
254
255 // We're sure elementsPosition doesn't contain index
256 const QRect &KListView::Private::cacheIndex(const QModelIndex &index)
257 {
258 QRect rect = visualRectInViewport(index);
259 elementsPosition[index] = rect;
260
261 return elementsPosition[index];
262 }
263
264 // We're sure categoriesPosition doesn't contain category
265 const QRect &KListView::Private::cacheCategory(const QString &category)
266 {
267 QRect rect = visualCategoryRectInViewport(category);
268 categoriesPosition[category] = rect;
269
270 return categoriesPosition[category];
271 }
272
273 const QRect &KListView::Private::cachedRectIndex(const QModelIndex &index)
274 {
275 if (elementsPosition.contains(index)) // If we have it cached
276 { // return it
277 return elementsPosition[index];
278 }
279 else // Otherwise, cache it
280 { // and return it
281 return cacheIndex(index);
282 }
283 }
284
285 const QRect &KListView::Private::cachedRectCategory(const QString &category)
286 {
287 if (categoriesPosition.contains(category)) // If we have it cached
288 { // return it
289 return categoriesPosition[category];
290 }
291 else // Otherwise, cache it and
292 { // return it
293 return cacheCategory(category);
294 }
295 }
296
297 QRect KListView::Private::visualRect(const QModelIndex &index)
298 {
299 QModelIndex mappedIndex = proxyModel->mapToSource(index);
300
301 QRect retRect = cachedRectIndex(mappedIndex);
302 int dx = -listView->horizontalOffset();
303 int dy = -listView->verticalOffset();
304 retRect.adjust(dx, dy, dx, dy);
305
306 return retRect;
307 }
308
309 QRect KListView::Private::categoryVisualRect(const QString &category)
310 {
311 QRect retRect = cachedRectCategory(category);
312 int dx = -listView->horizontalOffset();
313 int dy = -listView->verticalOffset();
314 retRect.adjust(dx, dy, dx, dy);
315
316 return retRect;
317 }
318
319 void KListView::Private::drawNewCategory(const QString &category,
320 const QStyleOption &option,
321 QPainter *painter)
322 {
323 QColor color = option.palette.color(QPalette::Text);
324
325 painter->save();
326 painter->setRenderHint(QPainter::Antialiasing);
327
328 QStyleOptionButton opt;
329
330 opt.rect = option.rect;
331 opt.palette = option.palette;
332 opt.direction = option.direction;
333 opt.text = category;
334
335 if (option.rect.contains(listView->viewport()->mapFromGlobal(QCursor::pos())) &&
336 !mouseButtonPressed)
337 {
338 const QPalette::ColorGroup group =
339 option.state & QStyle::State_Enabled ?
340 QPalette::Normal : QPalette::Disabled;
341
342 QLinearGradient gradient(option.rect.topLeft(),
343 option.rect.bottomRight());
344 gradient.setColorAt(0,
345 option.palette.color(group,
346 QPalette::Highlight).light());
347 gradient.setColorAt(1, Qt::transparent);
348
349 painter->fillRect(option.rect, gradient);
350 }
351
352 /*if (const KStyle *style = dynamic_cast<const KStyle*>(QApplication::style()))
353 {
354 style->drawControl(KStyle::CE_Category, &opt, painter, this);
355 }
356 else
357 {*/
358 QFont painterFont = painter->font();
359 painterFont.setWeight(QFont::Bold);
360 QFontMetrics metrics(painterFont);
361 painter->setFont(painterFont);
362
363 QPainterPath path;
364 path.addRect(option.rect.left(),
365 option.rect.bottom() - 2,
366 option.rect.width(),
367 2);
368
369 QLinearGradient gradient(option.rect.topLeft(),
370 option.rect.bottomRight());
371 gradient.setColorAt(0, color);
372 gradient.setColorAt(1, Qt::transparent);
373
374 painter->setBrush(gradient);
375 painter->fillPath(path, gradient);
376
377 painter->setPen(color);
378
379 painter->drawText(option.rect, Qt::AlignVCenter | Qt::AlignLeft,
380 metrics.elidedText(category, Qt::ElideRight, option.rect.width()));
381 //}
382 painter->restore();
383 }
384
385
386 void KListView::Private::updateScrollbars()
387 {
388 int lastItemBottom = cachedRectIndex(lastIndex).bottom() +
389 listView->spacing() - listView->viewport()->height();
390
391 listView->verticalScrollBar()->setSingleStep(listView->viewport()->height() / 10);
392 listView->verticalScrollBar()->setPageStep(listView->viewport()->height());
393 listView->verticalScrollBar()->setRange(0, lastItemBottom);
394 }
395
396 void KListView::Private::drawDraggedItems(QPainter *painter)
397 {
398 QStyleOptionViewItemV3 option = listView->viewOptions();
399 option.state &= ~QStyle::State_MouseOver;
400 foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
401 {
402 int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
403 int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
404
405 option.rect = visualRect(index);
406 option.rect.adjust(dx, dy, dx, dy);
407
408 listView->itemDelegate(index)->paint(painter, option, index);
409 }
410 }
411
412
413 //==============================================================================
414
415
416 KListView::KListView(QWidget *parent)
417 : QListView(parent)
418 , d(new Private(this))
419 {
420 }
421
422 KListView::~KListView()
423 {
424 delete d;
425 }
426
427 void KListView::setModel(QAbstractItemModel *model)
428 {
429 if (d->proxyModel)
430 {
431 QObject::disconnect(d->proxyModel,
432 SIGNAL(rowsRemoved(QModelIndex,int,int)),
433 this, SLOT(rowsRemoved(QModelIndex,int,int)));
434
435 QObject::disconnect(d->proxyModel,
436 SIGNAL(sortingRoleChanged()),
437 this, SLOT(slotSortingRoleChanged()));
438 }
439
440 QListView::setModel(model);
441
442 d->proxyModel = dynamic_cast<KSortFilterProxyModel*>(model);
443
444 if (d->proxyModel)
445 {
446 QObject::connect(d->proxyModel,
447 SIGNAL(rowsRemoved(QModelIndex,int,int)),
448 this, SLOT(rowsRemoved(QModelIndex,int,int)));
449
450 QObject::connect(d->proxyModel,
451 SIGNAL(sortingRoleChanged()),
452 this, SLOT(slotSortingRoleChanged()));
453 }
454 }
455
456 QRect KListView::visualRect(const QModelIndex &index) const
457 {
458 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
459 !d->itemCategorizer)
460 {
461 return QListView::visualRect(index);
462 }
463
464 if (!qobject_cast<const QSortFilterProxyModel*>(index.model()))
465 {
466 return d->visualRect(d->proxyModel->mapFromSource(index));
467 }
468
469 return d->visualRect(index);
470 }
471
472 KItemCategorizer *KListView::itemCategorizer() const
473 {
474 return d->itemCategorizer;
475 }
476
477 void KListView::setItemCategorizer(KItemCategorizer *itemCategorizer)
478 {
479 if (!itemCategorizer && d->proxyModel)
480 {
481 QObject::disconnect(d->proxyModel,
482 SIGNAL(rowsRemoved(QModelIndex,int,int)),
483 this, SLOT(rowsRemoved(QModelIndex,int,int)));
484
485 QObject::disconnect(d->proxyModel,
486 SIGNAL(sortingRoleChanged()),
487 this, SLOT(slotSortingRoleChanged()));
488 }
489 else if (itemCategorizer && d->proxyModel)
490 {
491 QObject::connect(d->proxyModel,
492 SIGNAL(rowsRemoved(QModelIndex,int,int)),
493 this, SLOT(rowsRemoved(QModelIndex,int,int)));
494
495 QObject::connect(d->proxyModel,
496 SIGNAL(sortingRoleChanged()),
497 this, SLOT(slotSortingRoleChanged()));
498 }
499
500 d->itemCategorizer = itemCategorizer;
501
502 if (itemCategorizer)
503 {
504 rowsInserted(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
505 }
506 else
507 {
508 updateGeometries();
509 }
510 }
511
512 QModelIndex KListView::indexAt(const QPoint &point) const
513 {
514 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
515 !d->itemCategorizer)
516 {
517 return QListView::indexAt(point);
518 }
519
520 QModelIndex index;
521
522 QModelIndexList item = d->intersectionSet(QRect(point, point));
523
524 if (item.count() == 1)
525 {
526 index = item[0];
527 }
528
529 d->hovered = index;
530
531 return index;
532 }
533
534 void KListView::reset()
535 {
536 QListView::reset();
537
538 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
539 !d->itemCategorizer)
540 {
541 return;
542 }
543
544 d->elementsInfo.clear();
545 d->elementsPosition.clear();
546 d->elementDictionary.clear();
547 d->categoriesIndexes.clear();
548 d->categoriesPosition.clear();
549 d->isIndexSelected.clear(); // selection cache
550 d->categories.clear();
551 d->intersectedIndexes.clear();
552 d->sourceModelIndexList.clear();
553 d->hovered = QModelIndex();
554 d->mouseButtonPressed = false;
555 }
556
557 void KListView::paintEvent(QPaintEvent *event)
558 {
559 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
560 !d->itemCategorizer)
561 {
562 QListView::paintEvent(event);
563 return;
564 }
565
566 QStyleOptionViewItemV3 option = viewOptions();
567 QPainter painter(viewport());
568 QRect area = event->rect();
569 const bool focus = (hasFocus() || viewport()->hasFocus()) &&
570 currentIndex().isValid();
571 const QStyle::State state = option.state;
572 const bool enabled = (state & QStyle::State_Enabled) != 0;
573
574 painter.save();
575
576 QModelIndexList dirtyIndexes = d->intersectionSet(area);
577 foreach (const QModelIndex &index, dirtyIndexes)
578 {
579 option.state = state;
580 option.rect = d->visualRect(index);
581
582 if (selectionModel() && selectionModel()->isSelected(index))
583 {
584 option.state |= QStyle::State_Selected;
585 }
586
587 if (enabled)
588 {
589 QPalette::ColorGroup cg;
590 if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0)
591 {
592 option.state &= ~QStyle::State_Enabled;
593 cg = QPalette::Disabled;
594 }
595 else
596 {
597 cg = QPalette::Normal;
598 }
599 option.palette.setCurrentColorGroup(cg);
600 }
601
602 if (focus && currentIndex() == index)
603 {
604 option.state |= QStyle::State_HasFocus;
605 if (this->state() == EditingState)
606 option.state |= QStyle::State_Editing;
607 }
608
609 if ((index == d->hovered) && !d->mouseButtonPressed)
610 option.state |= QStyle::State_MouseOver;
611 else
612 option.state &= ~QStyle::State_MouseOver;
613
614 itemDelegate(index)->paint(&painter, option, index);
615 }
616
617 // Redraw categories
618 QStyleOptionViewItem otherOption;
619 foreach (const QString &category, d->categories)
620 {
621 otherOption = option;
622 otherOption.rect = d->categoryVisualRect(category);
623
624 if (otherOption.rect.intersects(area))
625 {
626 d->drawNewCategory(category, otherOption, &painter);
627 }
628 }
629
630 if (d->mouseButtonPressed && !d->isDragging)
631 {
632 QPoint start, end, initialPressPosition;
633
634 initialPressPosition = d->initialPressPosition;
635
636 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
637 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
638
639 if (d->initialPressPosition.x() > d->mousePosition.x() ||
640 d->initialPressPosition.y() > d->mousePosition.y())
641 {
642 start = d->mousePosition;
643 end = initialPressPosition;
644 }
645 else
646 {
647 start = initialPressPosition;
648 end = d->mousePosition;
649 }
650
651 QStyleOptionRubberBand yetAnotherOption;
652 yetAnotherOption.initFrom(this);
653 yetAnotherOption.shape = QRubberBand::Rectangle;
654 yetAnotherOption.opaque = false;
655 yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
656 painter.save();
657 style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter);
658 painter.restore();
659 }
660
661 if (d->isDragging)
662 d->drawDraggedItems(&painter);
663
664 painter.restore();
665 }
666
667 void KListView::resizeEvent(QResizeEvent *event)
668 {
669 QListView::resizeEvent(event);
670
671 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
672 !d->itemCategorizer)
673 {
674 return;
675 }
676
677 // Clear the items positions cache
678 d->elementsPosition.clear();
679 d->categoriesPosition.clear();
680
681 d->updateScrollbars();
682 }
683
684 void KListView::setSelection(const QRect &rect,
685 QItemSelectionModel::SelectionFlags flags)
686 {
687 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
688 !d->itemCategorizer)
689 {
690 QListView::setSelection(rect, flags);
691 return;
692 }
693
694 if (flags & QItemSelectionModel::Clear)
695 {
696 selectionModel()->clear();
697 d->isIndexSelected.clear();
698 d->isTemporarySelected.clear();
699 }
700
701 QItemSelection selection;
702 QItemSelection deselect;
703 QModelIndexList dirtyIndexes = d->intersectionSet(rect);
704 foreach (const QModelIndex &index, dirtyIndexes)
705 {
706 if (!d->mouseButtonPressed && rect.intersects(visualRect(index)))
707 {
708 if (d->isIndexSelected.contains(index))
709 {
710 if (!d->isIndexSelected[index])
711 {
712 selection.select(index, index);
713 d->isIndexSelected[index] = true;
714 }
715 else
716 {
717 deselect.select(index, index);
718 d->isIndexSelected[index] = false;
719 }
720 }
721 else
722 {
723 d->isIndexSelected.insert(index, true);
724 selection.select(index, index);
725 }
726 }
727 else if (d->mouseButtonPressed) // selection cache
728 {
729 if (!d->isIndexSelected.contains(index) ||
730 (d->isIndexSelected.contains(index) && !d->isIndexSelected[index]))
731 {
732 if (d->isTemporarySelected.contains(index))
733 {
734 d->isTemporarySelected[index] = true;
735 }
736 else
737 {
738 d->isTemporarySelected.insert(index, true);
739 }
740 }
741
742 if (d->isIndexSelected.contains(index))
743 {
744 if (!d->isIndexSelected[index])
745 selection.select(index, index);
746
747 d->isIndexSelected[index] = true;
748 }
749 else
750 {
751 d->isIndexSelected.insert(index, true);
752 selection.select(index, index);
753 }
754 }
755 }
756
757 foreach (const QModelIndex &index, d->isIndexSelected.keys())
758 {
759 if (!rect.intersects(visualRect(index)))
760 {
761 if (d->isTemporarySelected.contains(index) &&
762 d->isTemporarySelected[index])
763 {
764 deselect.select(index, index);
765 d->isTemporarySelected[index] = false;
766 d->isIndexSelected[index] = false;
767 }
768 }
769 }
770
771 if (selection.count())
772 selectionModel()->select(selection, QItemSelectionModel::Select);
773
774 if (deselect.count())
775 selectionModel()->select(deselect, QItemSelectionModel::Deselect);
776 }
777
778 void KListView::mouseMoveEvent(QMouseEvent *event)
779 {
780 QListView::mouseMoveEvent(event);
781
782 d->mousePosition = event->pos();
783
784 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
785 !d->itemCategorizer)
786 {
787 return;
788 }
789
790 event->accept();
791
792 viewport()->update();
793 }
794
795 void KListView::mousePressEvent(QMouseEvent *event)
796 {
797 QListView::mousePressEvent(event);
798
799 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
800 !d->itemCategorizer)
801 {
802 return;
803 }
804
805 event->accept();
806
807 if (event->button() == Qt::LeftButton)
808 {
809 d->mouseButtonPressed = true;
810
811 d->initialPressPosition = event->pos();
812 d->initialPressPosition.setY(d->initialPressPosition.y() +
813 verticalOffset());
814 d->initialPressPosition.setX(d->initialPressPosition.x() +
815 horizontalOffset());
816 }
817
818 viewport()->update();
819 }
820
821 void KListView::mouseReleaseEvent(QMouseEvent *event)
822 {
823 QListView::mouseReleaseEvent(event);
824
825 d->mouseButtonPressed = false;
826
827 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
828 !d->itemCategorizer)
829 {
830 return;
831 }
832
833 event->accept();
834
835 d->isTemporarySelected.clear(); // selection cache
836
837 QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
838 initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
839 initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
840
841 if (initialPressPosition == d->initialPressPosition)
842 {
843 QItemSelection selection;
844 foreach(const QString &category, d->categories)
845 {
846 if (d->categoryVisualRect(category).contains(event->pos()))
847 {
848 QModelIndex index;
849 foreach (const QModelIndex &mappedIndex,
850 d->categoriesIndexes[category])
851 {
852 index = d->proxyModel->mapFromSource(mappedIndex);
853
854 if (d->isIndexSelected.contains(index))
855 {
856 if (!d->isIndexSelected[index])
857 selection.select(index, index);
858
859 d->isIndexSelected[index] = true;
860 }
861 else
862 {
863 d->isIndexSelected.insert(index, true);
864 selection.select(index, index);
865 }
866 }
867
868 selectionModel()->select(selection, QItemSelectionModel::Toggle);
869
870 break;
871 }
872 }
873 }
874
875 viewport()->update();
876 }
877
878 void KListView::leaveEvent(QEvent *event)
879 {
880 QListView::leaveEvent(event);
881
882 d->hovered = QModelIndex();
883
884 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
885 !d->itemCategorizer)
886 {
887 return;
888 }
889
890 event->accept();
891
892 viewport()->update();
893 }
894
895 void KListView::startDrag(Qt::DropActions supportedActions)
896 {
897 QListView::startDrag(supportedActions);
898
899 d->isDragging = false;
900 d->mouseButtonPressed = false;
901 }
902
903 void KListView::dragMoveEvent(QDragMoveEvent *event)
904 {
905 QListView::dragMoveEvent(event);
906
907 d->mousePosition = event->pos();
908
909 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
910 !d->itemCategorizer)
911 {
912 return;
913 }
914
915 if (d->mouseButtonPressed)
916 {
917 d->isDragging = true;
918 }
919 else
920 {
921 d->isDragging = false;
922 }
923
924 event->accept();
925
926 viewport()->update();
927 }
928
929 void KListView::rowsInserted(const QModelIndex &parent,
930 int start,
931 int end)
932 {
933 QListView::rowsInserted(parent, start, end);
934
935 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
936 !d->itemCategorizer)
937 {
938 return;
939 }
940
941 rowsInsertedArtifficial(parent, start, end);
942 }
943
944 void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
945 int start,
946 int end)
947 {
948 d->elementsInfo.clear();
949 d->elementsPosition.clear();
950 d->elementDictionary.clear();
951 d->categoriesIndexes.clear();
952 d->categoriesPosition.clear();
953 d->isIndexSelected.clear(); // selection cache
954 d->categories.clear();
955 d->intersectedIndexes.clear();
956 d->sourceModelIndexList.clear();
957 d->hovered = QModelIndex();
958 d->mouseButtonPressed = false;
959
960 if (start > end || end < 0 || start < 0 || !d->proxyModel->rowCount())
961 {
962 return;
963 }
964
965 // Add all elements mapped to the source model
966 for (int k = 0; k < d->proxyModel->rowCount(); k++)
967 {
968 d->sourceModelIndexList <<
969 d->proxyModel->mapToSource(d->proxyModel->index(k, 0));
970 }
971
972 // Sort them with the general purpose lessThan method
973 LessThan generalLessThan(d->proxyModel,
974 LessThan::GeneralPurpose);
975
976 qStableSort(d->sourceModelIndexList.begin(), d->sourceModelIndexList.end(),
977 generalLessThan);
978
979 // Explore categories
980 QString prevCategory =
981 d->itemCategorizer->categoryForItem(d->sourceModelIndexList[0],
982 d->proxyModel->sortRole());
983 QString lastCategory = prevCategory;
984 QModelIndexList modelIndexList;
985 struct Private::ElementInfo elementInfo;
986 foreach (const QModelIndex &index, d->sourceModelIndexList)
987 {
988 lastCategory = d->itemCategorizer->categoryForItem(index,
989 d->proxyModel->sortRole());
990
991 elementInfo.category = lastCategory;
992
993 if (prevCategory != lastCategory)
994 {
995 d->categoriesIndexes.insert(prevCategory, modelIndexList);
996 d->categories << prevCategory;
997 modelIndexList.clear();
998 }
999
1000 modelIndexList << index;
1001 prevCategory = lastCategory;
1002
1003 d->elementsInfo.insert(index, elementInfo);
1004 }
1005
1006 d->categoriesIndexes.insert(prevCategory, modelIndexList);
1007 d->categories << prevCategory;
1008
1009 // Sort items locally in their respective categories with the category
1010 // purpose lessThan
1011 LessThan categoryLessThan(d->proxyModel,
1012 LessThan::CategoryPurpose);
1013
1014 foreach (const QString &key, d->categories)
1015 {
1016 QModelIndexList &indexList = d->categoriesIndexes[key];
1017
1018 qStableSort(indexList.begin(), indexList.end(), categoryLessThan);
1019 }
1020
1021 d->lastIndex = d->categoriesIndexes[d->categories[d->categories.count() - 1]][d->categoriesIndexes[d->categories[d->categories.count() - 1]].count() - 1];
1022
1023 // Finally, fill data information of items situation. This will help when
1024 // trying to compute an item place in the viewport
1025 int i = 0; // position relative to the category beginning
1026 int j = 0; // number of elements before current
1027 foreach (const QString &key, d->categories)
1028 {
1029 foreach (const QModelIndex &index, d->categoriesIndexes[key])
1030 {
1031 struct Private::ElementInfo &elementInfo = d->elementsInfo[index];
1032
1033 elementInfo.relativeOffsetToCategory = i;
1034
1035 d->elementDictionary.insert(d->proxyModel->index(j, 0),
1036 d->proxyModel->mapFromSource(index));
1037
1038 i++;
1039 j++;
1040 }
1041
1042 i = 0;
1043 }
1044
1045 d->updateScrollbars();
1046 }
1047
1048 void KListView::rowsRemoved(const QModelIndex &parent,
1049 int start,
1050 int end)
1051 {
1052 if (d->proxyModel)
1053 {
1054 // Force the view to update all elements
1055 rowsInsertedArtifficial(parent, start, end);
1056 }
1057 }
1058
1059 void KListView::updateGeometries()
1060 {
1061 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
1062 !d->itemCategorizer)
1063 {
1064 QListView::updateGeometries();
1065 return;
1066 }
1067
1068 // Avoid QListView::updateGeometries(), since it will try to set another
1069 // range to our scroll bars, what we don't want (ereslibre)
1070 QAbstractItemView::updateGeometries();
1071 }
1072
1073 void KListView::slotSortingRoleChanged()
1074 {
1075 if ((viewMode() == KListView::IconMode) && d->proxyModel &&
1076 d->itemCategorizer)
1077 {
1078 // Force the view to update all elements
1079 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
1080 }
1081 }
1082
1083 #include "klistview.moc"