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