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