]> cloud.milkyroute.net Git - dolphin.git/blob - src/klistview.cpp
Better drawing when big selection rect is drawn.
[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 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 ((category == hoveredCategory) && !mouseButtonPressed)
335 {
336 const QPalette::ColorGroup group =
337 option.state & QStyle::State_Enabled ?
338 QPalette::Normal : QPalette::Disabled;
339
340 QLinearGradient gradient(option.rect.topLeft(),
341 option.rect.bottomRight());
342 gradient.setColorAt(0,
343 option.palette.color(group,
344 QPalette::Highlight).light());
345 gradient.setColorAt(1, Qt::transparent);
346
347 painter->fillRect(option.rect, gradient);
348 }
349
350 /*if (const KStyle *style = dynamic_cast<const KStyle*>(QApplication::style()))
351 {
352 style->drawControl(KStyle::CE_Category, &opt, painter, this);
353 }
354 else
355 {*/
356 QFont painterFont = painter->font();
357 painterFont.setWeight(QFont::Bold);
358 QFontMetrics metrics(painterFont);
359 painter->setFont(painterFont);
360
361 QPainterPath path;
362 path.addRect(option.rect.left(),
363 option.rect.bottom() - 2,
364 option.rect.width(),
365 2);
366
367 QLinearGradient gradient(option.rect.topLeft(),
368 option.rect.bottomRight());
369 gradient.setColorAt(0, color);
370 gradient.setColorAt(1, Qt::transparent);
371
372 painter->setBrush(gradient);
373 painter->fillPath(path, gradient);
374
375 painter->setPen(color);
376
377 painter->drawText(option.rect, Qt::AlignVCenter | Qt::AlignLeft,
378 metrics.elidedText(category, Qt::ElideRight, option.rect.width()));
379 //}
380 painter->restore();
381 }
382
383
384 void KListView::Private::updateScrollbars()
385 {
386 int lastItemBottom = cachedRectIndex(lastIndex).bottom() +
387 listView->spacing() - listView->viewport()->height();
388
389 listView->verticalScrollBar()->setSingleStep(listView->viewport()->height() / 10);
390 listView->verticalScrollBar()->setPageStep(listView->viewport()->height());
391 listView->verticalScrollBar()->setRange(0, lastItemBottom);
392 }
393
394 void KListView::Private::drawDraggedItems(QPainter *painter)
395 {
396 QStyleOptionViewItemV3 option = listView->viewOptions();
397 option.state &= ~QStyle::State_MouseOver;
398 int dx;
399 int dy;
400 foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
401 {
402 dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
403 dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
404
405 option.rect = visualRect(index);
406 option.rect.adjust(dx, dy, dx, dy);
407
408 listView->itemDelegate(index)->paint(painter, option, index);
409 }
410 }
411
412 void KListView::Private::drawDraggedItems()
413 {
414 int dx;
415 int dy;
416 QRect rectToUpdate;
417 QRect currentRect;
418 foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
419 {
420 dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
421 dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
422
423 currentRect = visualRect(index);
424 currentRect.adjust(dx, dy, dx, dy);
425
426 rectToUpdate = rectToUpdate.united(currentRect);
427 }
428
429 listView->viewport()->update(lastDraggedItemsRect);
430
431 lastDraggedItemsRect = rectToUpdate;
432
433 listView->viewport()->update(rectToUpdate);
434 }
435
436
437 //==============================================================================
438
439
440 KListView::KListView(QWidget *parent)
441 : QListView(parent)
442 , d(new Private(this))
443 {
444 }
445
446 KListView::~KListView()
447 {
448 delete d;
449 }
450
451 void KListView::setModel(QAbstractItemModel *model)
452 {
453 if (d->proxyModel)
454 {
455 QObject::disconnect(d->proxyModel,
456 SIGNAL(rowsRemoved(QModelIndex,int,int)),
457 this, SLOT(rowsRemoved(QModelIndex,int,int)));
458
459 QObject::disconnect(d->proxyModel,
460 SIGNAL(sortingRoleChanged()),
461 this, SLOT(slotSortingRoleChanged()));
462 }
463
464 QListView::setModel(model);
465
466 d->proxyModel = dynamic_cast<KSortFilterProxyModel*>(model);
467
468 if (d->proxyModel)
469 {
470 QObject::connect(d->proxyModel,
471 SIGNAL(rowsRemoved(QModelIndex,int,int)),
472 this, SLOT(rowsRemoved(QModelIndex,int,int)));
473
474 QObject::connect(d->proxyModel,
475 SIGNAL(sortingRoleChanged()),
476 this, SLOT(slotSortingRoleChanged()));
477 }
478 }
479
480 QRect KListView::visualRect(const QModelIndex &index) const
481 {
482 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
483 !d->itemCategorizer)
484 {
485 return QListView::visualRect(index);
486 }
487
488 if (!qobject_cast<const QSortFilterProxyModel*>(index.model()))
489 {
490 return d->visualRect(d->proxyModel->mapFromSource(index));
491 }
492
493 return d->visualRect(index);
494 }
495
496 KItemCategorizer *KListView::itemCategorizer() const
497 {
498 return d->itemCategorizer;
499 }
500
501 void KListView::setItemCategorizer(KItemCategorizer *itemCategorizer)
502 {
503 if (!itemCategorizer && d->proxyModel)
504 {
505 QObject::disconnect(d->proxyModel,
506 SIGNAL(rowsRemoved(QModelIndex,int,int)),
507 this, SLOT(rowsRemoved(QModelIndex,int,int)));
508
509 QObject::disconnect(d->proxyModel,
510 SIGNAL(sortingRoleChanged()),
511 this, SLOT(slotSortingRoleChanged()));
512 }
513 else if (itemCategorizer && d->proxyModel)
514 {
515 QObject::connect(d->proxyModel,
516 SIGNAL(rowsRemoved(QModelIndex,int,int)),
517 this, SLOT(rowsRemoved(QModelIndex,int,int)));
518
519 QObject::connect(d->proxyModel,
520 SIGNAL(sortingRoleChanged()),
521 this, SLOT(slotSortingRoleChanged()));
522 }
523
524 d->itemCategorizer = itemCategorizer;
525
526 if (itemCategorizer)
527 {
528 rowsInserted(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
529 }
530 else
531 {
532 updateGeometries();
533 }
534 }
535
536 QModelIndex KListView::indexAt(const QPoint &point) const
537 {
538 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
539 !d->itemCategorizer)
540 {
541 return QListView::indexAt(point);
542 }
543
544 QModelIndex index;
545
546 QModelIndexList item = d->intersectionSet(QRect(point, point));
547
548 if (item.count() == 1)
549 {
550 index = item[0];
551 }
552
553 d->hovered = index;
554
555 return index;
556 }
557
558 void KListView::reset()
559 {
560 QListView::reset();
561
562 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
563 !d->itemCategorizer)
564 {
565 return;
566 }
567
568 d->elementsInfo.clear();
569 d->elementsPosition.clear();
570 d->elementDictionary.clear();
571 d->categoriesIndexes.clear();
572 d->categoriesPosition.clear();
573 d->categories.clear();
574 d->intersectedIndexes.clear();
575 d->sourceModelIndexList.clear();
576 d->hovered = QModelIndex();
577 d->mouseButtonPressed = false;
578 }
579
580 void KListView::paintEvent(QPaintEvent *event)
581 {
582 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
583 !d->itemCategorizer)
584 {
585 QListView::paintEvent(event);
586 return;
587 }
588
589 QStyleOptionViewItemV3 option = viewOptions();
590 QPainter painter(viewport());
591 QRect area = event->rect();
592 const bool focus = (hasFocus() || viewport()->hasFocus()) &&
593 currentIndex().isValid();
594 const QStyle::State state = option.state;
595 const bool enabled = (state & QStyle::State_Enabled) != 0;
596
597 painter.save();
598
599 QModelIndexList dirtyIndexes = d->intersectionSet(area);
600 foreach (const QModelIndex &index, dirtyIndexes)
601 {
602 option.state = state;
603 option.rect = d->visualRect(index);
604
605 if (selectionModel() && selectionModel()->isSelected(index))
606 {
607 option.state |= QStyle::State_Selected;
608 }
609
610 if (enabled)
611 {
612 QPalette::ColorGroup cg;
613 if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0)
614 {
615 option.state &= ~QStyle::State_Enabled;
616 cg = QPalette::Disabled;
617 }
618 else
619 {
620 cg = QPalette::Normal;
621 }
622 option.palette.setCurrentColorGroup(cg);
623 }
624
625 if (focus && currentIndex() == index)
626 {
627 option.state |= QStyle::State_HasFocus;
628 if (this->state() == EditingState)
629 option.state |= QStyle::State_Editing;
630 }
631
632 if ((index == d->hovered) && !d->mouseButtonPressed)
633 option.state |= QStyle::State_MouseOver;
634 else
635 option.state &= ~QStyle::State_MouseOver;
636
637 itemDelegate(index)->paint(&painter, option, index);
638 }
639
640 if (d->mouseButtonPressed && !d->isDragging)
641 {
642 QPoint start, end, initialPressPosition;
643
644 initialPressPosition = d->initialPressPosition;
645
646 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
647 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
648
649 if (d->initialPressPosition.x() > d->mousePosition.x() ||
650 d->initialPressPosition.y() > d->mousePosition.y())
651 {
652 start = d->mousePosition;
653 end = initialPressPosition;
654 }
655 else
656 {
657 start = initialPressPosition;
658 end = d->mousePosition;
659 }
660
661 QStyleOptionRubberBand yetAnotherOption;
662 yetAnotherOption.initFrom(this);
663 yetAnotherOption.shape = QRubberBand::Rectangle;
664 yetAnotherOption.opaque = false;
665 yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
666 painter.save();
667 style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter);
668 painter.restore();
669 }
670
671 // Redraw categories
672 QStyleOptionViewItem otherOption;
673 foreach (const QString &category, d->categories)
674 {
675 otherOption = option;
676 otherOption.rect = d->categoryVisualRect(category);
677
678 if (otherOption.rect.intersects(area))
679 {
680 d->drawNewCategory(category, otherOption, &painter);
681 }
682 }
683
684 if (d->isDragging && !d->dragLeftViewport)
685 {
686 painter.setOpacity(0.5);
687 d->drawDraggedItems(&painter);
688 }
689
690 painter.restore();
691 }
692
693 void KListView::resizeEvent(QResizeEvent *event)
694 {
695 QListView::resizeEvent(event);
696
697 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
698 !d->itemCategorizer)
699 {
700 return;
701 }
702
703 // Clear the items positions cache
704 d->elementsPosition.clear();
705 d->categoriesPosition.clear();
706
707 d->updateScrollbars();
708 }
709
710 void KListView::setSelection(const QRect &rect,
711 QItemSelectionModel::SelectionFlags flags)
712 {
713 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
714 !d->itemCategorizer)
715 {
716 QListView::setSelection(rect, flags);
717 return;
718 }
719
720 if (!flags)
721 return;
722
723 selectionModel()->clear();
724
725 if (flags & QItemSelectionModel::Clear)
726 {
727 d->lastSelection = QItemSelection();
728 }
729
730 QModelIndexList dirtyIndexes = d->intersectionSet(rect);
731 QItemSelection selection;
732
733 if (!dirtyIndexes.count())
734 {
735 if (d->lastSelection.count())
736 {
737 selectionModel()->select(d->lastSelection, flags);
738 }
739
740 return;
741 }
742
743 if (!d->mouseButtonPressed)
744 {
745 selection = QItemSelection(dirtyIndexes[0], dirtyIndexes[0]);
746 }
747 else
748 {
749 QModelIndex first = dirtyIndexes[0];
750 QModelIndex last;
751 foreach (const QModelIndex &index, dirtyIndexes)
752 {
753 if (last.isValid() && last.row() + 1 != index.row())
754 {
755 QItemSelectionRange range(first, last);
756
757 selection << range;
758
759 first = index;
760 }
761
762 last = index;
763 }
764
765 if (last.isValid())
766 selection << QItemSelectionRange(first, last);
767 }
768
769 if (d->lastSelection.count() && !d->mouseButtonPressed)
770 {
771 selection.merge(d->lastSelection, flags);
772 }
773 else if (d->lastSelection.count())
774 {
775 selection.merge(d->lastSelection, QItemSelectionModel::Select);
776 }
777
778 selectionModel()->select(selection, flags);
779 }
780
781 void KListView::mouseMoveEvent(QMouseEvent *event)
782 {
783 QListView::mouseMoveEvent(event);
784
785 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
786 !d->itemCategorizer)
787 {
788 return;
789 }
790
791 d->mousePosition = event->pos();
792 d->hoveredCategory = QString();
793
794 // Redraw categories
795 foreach (const QString &category, d->categories)
796 {
797 if (d->categoryVisualRect(category).intersects(QRect(event->pos(), event->pos())))
798 {
799 d->hoveredCategory = category;
800 }
801
802 viewport()->update(d->categoryVisualRect(category));
803 }
804
805 QRect rect;
806 if (d->mouseButtonPressed && !d->isDragging)
807 {
808 QPoint start, end, initialPressPosition;
809
810 initialPressPosition = d->initialPressPosition;
811
812 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
813 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
814
815 if (d->initialPressPosition.x() > d->mousePosition.x() ||
816 d->initialPressPosition.y() > d->mousePosition.y())
817 {
818 start = d->mousePosition;
819 end = initialPressPosition;
820 }
821 else
822 {
823 start = initialPressPosition;
824 end = d->mousePosition;
825 }
826
827 viewport()->update(d->lastSelectionRect);
828
829 rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
830
831 viewport()->update(rect);
832
833 d->lastSelectionRect = rect;
834 }
835 }
836
837 void KListView::mousePressEvent(QMouseEvent *event)
838 {
839 QListView::mousePressEvent(event);
840
841 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
842 !d->itemCategorizer)
843 {
844 return;
845 }
846
847 d->dragLeftViewport = false;
848
849 if (event->button() == Qt::LeftButton)
850 {
851 d->mouseButtonPressed = true;
852
853 d->initialPressPosition = event->pos();
854 d->initialPressPosition.setY(d->initialPressPosition.y() +
855 verticalOffset());
856 d->initialPressPosition.setX(d->initialPressPosition.x() +
857 horizontalOffset());
858 }
859 }
860
861 void KListView::mouseReleaseEvent(QMouseEvent *event)
862 {
863 QListView::mouseReleaseEvent(event);
864
865 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
866 !d->itemCategorizer)
867 {
868 return;
869 }
870
871 d->mouseButtonPressed = false;
872
873 QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
874 initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
875 initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
876
877 QItemSelection selection;
878
879 if (initialPressPosition == d->initialPressPosition)
880 {
881 foreach(const QString &category, d->categories)
882 {
883 if (d->categoryVisualRect(category).contains(event->pos()))
884 {
885 QItemSelectionRange selectionRange(d->proxyModel->mapFromSource(d->categoriesIndexes[category][0]),
886 d->proxyModel->mapFromSource(d->categoriesIndexes[category][d->categoriesIndexes[category].count() - 1]));
887
888 selection << selectionRange;
889
890 selectionModel()->select(selection, QItemSelectionModel::Select);
891
892 break;
893 }
894 }
895 }
896
897 d->lastSelection = selectionModel()->selection();
898
899 if (d->hovered.isValid())
900 viewport()->update(d->visualRect(d->hovered));
901 else if (!d->hoveredCategory.isEmpty())
902 viewport()->update(d->categoryVisualRect(d->hoveredCategory));
903 }
904
905 void KListView::leaveEvent(QEvent *event)
906 {
907 QListView::leaveEvent(event);
908
909 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
910 !d->itemCategorizer)
911 {
912 return;
913 }
914
915 d->hovered = QModelIndex();
916 d->hoveredCategory = QString();
917 }
918
919 void KListView::startDrag(Qt::DropActions supportedActions)
920 {
921 QListView::startDrag(supportedActions);
922
923 d->isDragging = false;
924 d->mouseButtonPressed = false;
925 }
926
927 void KListView::dragMoveEvent(QDragMoveEvent *event)
928 {
929 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
930 !d->itemCategorizer)
931 {
932 QListView::dragMoveEvent(event);
933 return;
934 }
935
936 d->mousePosition = event->pos();
937
938 if (d->mouseButtonPressed)
939 {
940 d->isDragging = true;
941 }
942 else
943 {
944 d->isDragging = false;
945 }
946
947 d->dragLeftViewport = false;
948
949 d->drawDraggedItems();
950 }
951
952 void KListView::dragLeaveEvent(QDragLeaveEvent *event)
953 {
954 QListView::dragLeaveEvent(event);
955
956 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
957 !d->itemCategorizer)
958 {
959 return;
960 }
961
962 d->dragLeftViewport = true;
963 }
964
965 QModelIndex KListView::moveCursor(CursorAction cursorAction,
966 Qt::KeyboardModifiers modifiers)
967 {
968 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
969 !d->itemCategorizer)
970 {
971 return QListView::moveCursor(cursorAction, modifiers);
972 }
973
974 return QListView::moveCursor(cursorAction, modifiers);
975 }
976
977 void KListView::rowsInserted(const QModelIndex &parent,
978 int start,
979 int end)
980 {
981 QListView::rowsInserted(parent, start, end);
982
983 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
984 !d->itemCategorizer)
985 {
986 return;
987 }
988
989 rowsInsertedArtifficial(parent, start, end);
990 }
991
992 void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
993 int start,
994 int end)
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"