]> cloud.milkyroute.net Git - dolphin.git/blob - src/klistview.cpp
Fix the scrollbar issue. We can live with the setSingleStep nasty workaround for...
[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 }
490
491 QModelIndex KListView::indexAt(const QPoint &point) const
492 {
493 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
494 !d->itemCategorizer)
495 {
496 return QListView::indexAt(point);
497 }
498
499 QModelIndex index;
500
501 QModelIndexList item = d->intersectionSet(QRect(point, point));
502
503 if (item.count() == 1)
504 {
505 index = item[0];
506 }
507
508 d->hovered = index;
509
510 return index;
511 }
512
513 void KListView::reset()
514 {
515 QListView::reset();
516
517 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
518 !d->itemCategorizer)
519 {
520 return;
521 }
522
523 d->elementsInfo.clear();
524 d->elementsPosition.clear();
525 d->elementDictionary.clear();
526 d->categoriesIndexes.clear();
527 d->categoriesPosition.clear();
528 d->categories.clear();
529 d->intersectedIndexes.clear();
530 d->sourceModelIndexList.clear();
531 d->hovered = QModelIndex();
532 d->mouseButtonPressed = false;
533 }
534
535 void KListView::paintEvent(QPaintEvent *event)
536 {
537 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
538 !d->itemCategorizer)
539 {
540 QListView::paintEvent(event);
541 return;
542 }
543
544 QStyleOptionViewItemV3 option = viewOptions();
545 QPainter painter(viewport());
546 QRect area = event->rect();
547 const bool focus = (hasFocus() || viewport()->hasFocus()) &&
548 currentIndex().isValid();
549 const QStyle::State state = option.state;
550 const bool enabled = (state & QStyle::State_Enabled) != 0;
551
552 painter.save();
553
554 QModelIndexList dirtyIndexes = d->intersectionSet(area);
555 foreach (const QModelIndex &index, dirtyIndexes)
556 {
557 option.state = state;
558 option.rect = d->visualRect(index);
559
560 if (selectionModel() && selectionModel()->isSelected(index))
561 {
562 option.state |= QStyle::State_Selected;
563 }
564
565 if (enabled)
566 {
567 QPalette::ColorGroup cg;
568 if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0)
569 {
570 option.state &= ~QStyle::State_Enabled;
571 cg = QPalette::Disabled;
572 }
573 else
574 {
575 cg = QPalette::Normal;
576 }
577 option.palette.setCurrentColorGroup(cg);
578 }
579
580 if (focus && currentIndex() == index)
581 {
582 option.state |= QStyle::State_HasFocus;
583 if (this->state() == EditingState)
584 option.state |= QStyle::State_Editing;
585 }
586
587 if ((index == d->hovered) && !d->mouseButtonPressed)
588 option.state |= QStyle::State_MouseOver;
589 else
590 option.state &= ~QStyle::State_MouseOver;
591
592 itemDelegate(index)->paint(&painter, option, index);
593 }
594
595 // Redraw categories
596 QStyleOptionViewItem otherOption;
597 foreach (const QString &category, d->categories)
598 {
599 otherOption = option;
600 otherOption.rect = d->categoryVisualRect(category);
601
602 if (otherOption.rect.intersects(area))
603 {
604 d->drawNewCategory(category, otherOption, &painter);
605 }
606 }
607
608 if (d->mouseButtonPressed)
609 {
610 QPoint start, end, initialPressPosition;
611
612 initialPressPosition = d->initialPressPosition;
613
614 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
615 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
616
617 if (d->initialPressPosition.x() > d->mousePosition.x() ||
618 d->initialPressPosition.y() > d->mousePosition.y())
619 {
620 start = d->mousePosition;
621 end = initialPressPosition;
622 }
623 else
624 {
625 start = initialPressPosition;
626 end = d->mousePosition;
627 }
628
629 QStyleOptionRubberBand yetAnotherOption;
630 yetAnotherOption.initFrom(this);
631 yetAnotherOption.shape = QRubberBand::Rectangle;
632 yetAnotherOption.opaque = false;
633 yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
634 painter.save();
635 style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter);
636 painter.restore();
637 }
638
639 painter.restore();
640 }
641
642 void KListView::resizeEvent(QResizeEvent *event)
643 {
644 QListView::resizeEvent(event);
645
646 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
647 !d->itemCategorizer)
648 {
649 return;
650 }
651
652 // Clear the items positions cache
653 d->elementsPosition.clear();
654 d->categoriesPosition.clear();
655
656 d->updateScrollbars();
657 }
658
659 void KListView::setSelection(const QRect &rect,
660 QItemSelectionModel::SelectionFlags flags)
661 {
662 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
663 !d->itemCategorizer)
664 {
665 QListView::setSelection(rect, flags);
666 return;
667 }
668
669 // FIXME: I have to rethink and rewrite this method (ereslibre)
670
671 QModelIndexList dirtyIndexes = d->intersectionSet(rect);
672 foreach (const QModelIndex &index, dirtyIndexes)
673 {
674 if (!d->mouseButtonPressed && rect.intersects(visualRect(index)))
675 {
676 selectionModel()->select(index, flags);
677 }
678 else
679 {
680 selectionModel()->select(index, QItemSelectionModel::Select);
681
682 if (d->mouseButtonPressed)
683 d->tempSelected.append(index);
684 }
685 }
686
687 if (d->mouseButtonPressed)
688 {
689 foreach (const QModelIndex &index, selectionModel()->selectedIndexes())
690 {
691 if (!rect.intersects(visualRect(index)))
692 {
693 selectionModel()->select(index, QItemSelectionModel::Deselect);
694
695 if (d->mouseButtonPressed)
696 {
697 d->tempSelected.removeAll(index);
698 }
699 }
700 }
701 }
702 }
703
704 void KListView::mouseMoveEvent(QMouseEvent *event)
705 {
706 QListView::mouseMoveEvent(event);
707
708 d->mousePosition = event->pos();
709
710 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
711 !d->itemCategorizer)
712 {
713 return;
714 }
715
716 event->accept();
717
718 viewport()->update();
719 }
720
721 void KListView::mousePressEvent(QMouseEvent *event)
722 {
723 QListView::mousePressEvent(event);
724
725 d->tempSelected.clear();
726
727 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
728 !d->itemCategorizer)
729 {
730 return;
731 }
732
733 event->accept();
734
735 if (event->button() == Qt::LeftButton)
736 {
737 d->mouseButtonPressed = true;
738
739 d->initialPressPosition = event->pos();
740 d->initialPressPosition.setY(d->initialPressPosition.y() +
741 verticalOffset());
742 d->initialPressPosition.setX(d->initialPressPosition.x() +
743 horizontalOffset());
744 }
745
746 viewport()->update();
747 }
748
749 void KListView::mouseReleaseEvent(QMouseEvent *event)
750 {
751 QListView::mouseReleaseEvent(event);
752
753 d->mouseButtonPressed = false;
754
755 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
756 !d->itemCategorizer)
757 {
758 return;
759 }
760
761 event->accept();
762
763 // FIXME: I have to rethink and rewrite this method (ereslibre)
764
765 QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
766 initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
767 initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
768
769 if (initialPressPosition == d->initialPressPosition)
770 {
771 foreach(const QString &category, d->categories)
772 {
773 if (d->categoryVisualRect(category).contains(event->pos()))
774 {
775 QModelIndex index;
776 QItemSelectionModel::SelectionFlag flag;
777 foreach (const QModelIndex &mappedIndex,
778 d->categoriesIndexes[category])
779 {
780 index = d->proxyModel->mapFromSource(mappedIndex);
781
782 if (selectionModel()->selectedIndexes().contains(index))
783 {
784 flag = QItemSelectionModel::Deselect;
785 }
786 else
787 {
788 flag = QItemSelectionModel::Select;
789 }
790
791 selectionModel()->select(index, flag);
792 }
793 }
794 }
795 }
796
797 viewport()->update();
798 }
799
800 void KListView::leaveEvent(QEvent *event)
801 {
802 QListView::leaveEvent(event);
803
804 d->hovered = QModelIndex();
805
806 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
807 !d->itemCategorizer)
808 {
809 return;
810 }
811
812 event->accept();
813
814 viewport()->update();
815 }
816
817 void KListView::rowsInserted(const QModelIndex &parent,
818 int start,
819 int end)
820 {
821 QListView::rowsInserted(parent, start, end);
822
823 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
824 !d->itemCategorizer)
825 {
826 return;
827 }
828
829 rowsInsertedArtifficial(parent, start, end);
830 }
831
832 void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
833 int start,
834 int end)
835 {
836 d->elementsInfo.clear();
837 d->elementsPosition.clear();
838 d->elementDictionary.clear();
839 d->categoriesIndexes.clear();
840 d->categoriesPosition.clear();
841 d->categories.clear();
842 d->intersectedIndexes.clear();
843 d->sourceModelIndexList.clear();
844 d->hovered = QModelIndex();
845 d->mouseButtonPressed = false;
846
847 if (start > end || end < 0 || start < 0 || !d->proxyModel->rowCount())
848 {
849 return;
850 }
851
852 // Add all elements mapped to the source model
853 for (int k = 0; k < d->proxyModel->rowCount(); k++)
854 {
855 d->sourceModelIndexList <<
856 d->proxyModel->mapToSource(d->proxyModel->index(k, 0));
857 }
858
859 // Sort them with the general purpose lessThan method
860 LessThan generalLessThan(d->proxyModel,
861 LessThan::GeneralPurpose);
862
863 qStableSort(d->sourceModelIndexList.begin(), d->sourceModelIndexList.end(),
864 generalLessThan);
865
866 // Explore categories
867 QString prevCategory =
868 d->itemCategorizer->categoryForItem(d->sourceModelIndexList[0],
869 d->proxyModel->sortRole());
870 QString lastCategory = prevCategory;
871 QModelIndexList modelIndexList;
872 struct Private::ElementInfo elementInfo;
873 foreach (const QModelIndex &index, d->sourceModelIndexList)
874 {
875 lastCategory = d->itemCategorizer->categoryForItem(index,
876 d->proxyModel->sortRole());
877
878 elementInfo.category = lastCategory;
879
880 if (prevCategory != lastCategory)
881 {
882 d->categoriesIndexes.insert(prevCategory, modelIndexList);
883 d->categories << prevCategory;
884 modelIndexList.clear();
885 }
886
887 modelIndexList << index;
888 prevCategory = lastCategory;
889
890 d->elementsInfo.insert(index, elementInfo);
891 }
892
893 d->categoriesIndexes.insert(prevCategory, modelIndexList);
894 d->categories << prevCategory;
895
896 // Sort items locally in their respective categories with the category
897 // purpose lessThan
898 LessThan categoryLessThan(d->proxyModel,
899 LessThan::CategoryPurpose);
900
901 foreach (const QString &key, d->categories)
902 {
903 QModelIndexList &indexList = d->categoriesIndexes[key];
904
905 qStableSort(indexList.begin(), indexList.end(), categoryLessThan);
906 }
907
908 d->lastIndex = d->categoriesIndexes[d->categories[d->categories.count() - 1]][d->categoriesIndexes[d->categories[d->categories.count() - 1]].count() - 1];
909
910 // Finally, fill data information of items situation. This will help when
911 // trying to compute an item place in the viewport
912 int i = 0; // position relative to the category beginning
913 int j = 0; // number of elements before current
914 foreach (const QString &key, d->categories)
915 {
916 foreach (const QModelIndex &index, d->categoriesIndexes[key])
917 {
918 struct Private::ElementInfo &elementInfo = d->elementsInfo[index];
919
920 elementInfo.relativeOffsetToCategory = i;
921
922 d->elementDictionary.insert(d->proxyModel->index(j, 0),
923 d->proxyModel->mapFromSource(index));
924
925 i++;
926 j++;
927 }
928
929 i = 0;
930 }
931
932 d->updateScrollbars();
933 }
934
935 void KListView::rowsRemoved(const QModelIndex &parent,
936 int start,
937 int end)
938 {
939 if (d->proxyModel)
940 {
941 // Force the view to update all elements
942 rowsInsertedArtifficial(parent, start, end);
943 }
944 }
945
946 void KListView::updateGeometries()
947 {
948 if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
949 !d->itemCategorizer)
950 {
951 QListView::updateGeometries();
952 return;
953 }
954
955 // Avoid QListView::updateGeometries(), since it will try to set another
956 // range to our scroll bars, what we don't want (ereslibre)
957 QAbstractItemView::updateGeometries();
958 }
959
960 void KListView::slotSortingRoleChanged()
961 {
962 if ((viewMode() == KListView::IconMode) && d->proxyModel &&
963 d->itemCategorizer)
964 {
965 // Force the view to update all elements
966 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
967 }
968 }
969
970 #include "klistview.moc"