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