]> cloud.milkyroute.net Git - dolphin.git/blob - src/klistview.cpp
Add needed method for keyboard navigation
[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->categories.clear();
551 d->intersectedIndexes.clear();
552 d->sourceModelIndexList.clear();
553 d->hovered = QModelIndex();
554 d->mouseButtonPressed = false;
555 }
556
557 void KListView::paintEvent(QPaintEvent *event)
558 {
559 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
560 !d->itemCategorizer)
561 {
562 QListView::paintEvent(event);
563 return;
564 }
565
566 QStyleOptionViewItemV3 option = viewOptions();
567 QPainter painter(viewport());
568 QRect area = event->rect();
569 const bool focus = (hasFocus() || viewport()->hasFocus()) &&
570 currentIndex().isValid();
571 const QStyle::State state = option.state;
572 const bool enabled = (state & QStyle::State_Enabled) != 0;
573
574 painter.save();
575
576 QModelIndexList dirtyIndexes = d->intersectionSet(area);
577 foreach (const QModelIndex &index, dirtyIndexes)
578 {
579 option.state = state;
580 option.rect = d->visualRect(index);
581
582 if (selectionModel() && selectionModel()->isSelected(index))
583 {
584 option.state |= QStyle::State_Selected;
585 }
586
587 if (enabled)
588 {
589 QPalette::ColorGroup cg;
590 if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0)
591 {
592 option.state &= ~QStyle::State_Enabled;
593 cg = QPalette::Disabled;
594 }
595 else
596 {
597 cg = QPalette::Normal;
598 }
599 option.palette.setCurrentColorGroup(cg);
600 }
601
602 if (focus && currentIndex() == index)
603 {
604 option.state |= QStyle::State_HasFocus;
605 if (this->state() == EditingState)
606 option.state |= QStyle::State_Editing;
607 }
608
609 if ((index == d->hovered) && !d->mouseButtonPressed)
610 option.state |= QStyle::State_MouseOver;
611 else
612 option.state &= ~QStyle::State_MouseOver;
613
614 itemDelegate(index)->paint(&painter, option, index);
615 }
616
617 // Redraw categories
618 QStyleOptionViewItem otherOption;
619 foreach (const QString &category, d->categories)
620 {
621 otherOption = option;
622 otherOption.rect = d->categoryVisualRect(category);
623
624 if (otherOption.rect.intersects(area))
625 {
626 d->drawNewCategory(category, otherOption, &painter);
627 }
628 }
629
630 if (d->mouseButtonPressed && !d->isDragging)
631 {
632 QPoint start, end, initialPressPosition;
633
634 initialPressPosition = d->initialPressPosition;
635
636 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
637 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
638
639 if (d->initialPressPosition.x() > d->mousePosition.x() ||
640 d->initialPressPosition.y() > d->mousePosition.y())
641 {
642 start = d->mousePosition;
643 end = initialPressPosition;
644 }
645 else
646 {
647 start = initialPressPosition;
648 end = d->mousePosition;
649 }
650
651 QStyleOptionRubberBand yetAnotherOption;
652 yetAnotherOption.initFrom(this);
653 yetAnotherOption.shape = QRubberBand::Rectangle;
654 yetAnotherOption.opaque = false;
655 yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
656 painter.save();
657 style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter);
658 painter.restore();
659 }
660
661 if (d->isDragging && !d->dragLeftViewport)
662 {
663 painter.setOpacity(0.5);
664 d->drawDraggedItems(&painter);
665 }
666
667 painter.restore();
668 }
669
670 void KListView::resizeEvent(QResizeEvent *event)
671 {
672 QListView::resizeEvent(event);
673
674 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
675 !d->itemCategorizer)
676 {
677 return;
678 }
679
680 // Clear the items positions cache
681 d->elementsPosition.clear();
682 d->categoriesPosition.clear();
683
684 d->updateScrollbars();
685 }
686
687 void KListView::setSelection(const QRect &rect,
688 QItemSelectionModel::SelectionFlags flags)
689 {
690 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
691 !d->itemCategorizer)
692 {
693 QListView::setSelection(rect, flags);
694 return;
695 }
696
697 if (!flags)
698 return;
699
700 selectionModel()->clear();
701
702 if (flags & QItemSelectionModel::Clear)
703 {
704 d->lastSelection = QItemSelection();
705 }
706
707 QModelIndexList dirtyIndexes = d->intersectionSet(rect);
708 QItemSelection selection;
709
710 if (!dirtyIndexes.count())
711 {
712 if (d->lastSelection.count())
713 {
714 selectionModel()->select(d->lastSelection, flags);
715 }
716
717 return;
718 }
719
720 if (!d->mouseButtonPressed)
721 {
722 selection = QItemSelection(dirtyIndexes[0], dirtyIndexes[0]);
723 }
724 else
725 {
726 QModelIndex first = dirtyIndexes[0];
727 QModelIndex last;
728 foreach (const QModelIndex &index, dirtyIndexes)
729 {
730 if (last.isValid() && last.row() + 1 != index.row())
731 {
732 QItemSelectionRange range(first, last);
733
734 selection << range;
735
736 first = index;
737 }
738
739 last = index;
740 }
741
742 if (last.isValid())
743 selection << QItemSelectionRange(first, last);
744 }
745
746 if (d->lastSelection.count() && !d->mouseButtonPressed)
747 {
748 selection.merge(d->lastSelection, flags);
749 }
750 else if (d->lastSelection.count())
751 {
752 selection.merge(d->lastSelection, QItemSelectionModel::Select);
753 }
754
755 selectionModel()->select(selection, flags);
756
757 viewport()->update();
758 }
759
760 void KListView::mouseMoveEvent(QMouseEvent *event)
761 {
762 d->mousePosition = event->pos();
763
764 QListView::mouseMoveEvent(event);
765
766 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
767 !d->itemCategorizer)
768 {
769 return;
770 }
771
772 event->accept();
773
774 viewport()->update();
775 }
776
777 void KListView::mousePressEvent(QMouseEvent *event)
778 {
779 QListView::mousePressEvent(event);
780
781 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
782 !d->itemCategorizer)
783 {
784 return;
785 }
786
787 event->accept();
788
789 d->dragLeftViewport = false;
790
791 if (event->button() == Qt::LeftButton)
792 {
793 d->mouseButtonPressed = true;
794
795 d->initialPressPosition = event->pos();
796 d->initialPressPosition.setY(d->initialPressPosition.y() +
797 verticalOffset());
798 d->initialPressPosition.setX(d->initialPressPosition.x() +
799 horizontalOffset());
800 }
801
802 viewport()->update();
803 }
804
805 void KListView::mouseReleaseEvent(QMouseEvent *event)
806 {
807 QListView::mouseReleaseEvent(event);
808
809 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
810 !d->itemCategorizer)
811 {
812 return;
813 }
814
815 event->accept();
816
817 d->mouseButtonPressed = false;
818
819 QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
820 initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
821 initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
822
823 QItemSelection selection;
824
825 if (initialPressPosition == d->initialPressPosition)
826 {
827 foreach(const QString &category, d->categories)
828 {
829 if (d->categoryVisualRect(category).contains(event->pos()))
830 {
831 QItemSelectionRange selectionRange(d->proxyModel->mapFromSource(d->categoriesIndexes[category][0]),
832 d->proxyModel->mapFromSource(d->categoriesIndexes[category][d->categoriesIndexes[category].count() - 1]));
833
834 selection << selectionRange;
835
836 selectionModel()->select(selection, QItemSelectionModel::Select);
837
838 break;
839 }
840 }
841 }
842
843 d->lastSelection = selectionModel()->selection();
844
845 viewport()->update();
846 }
847
848 void KListView::leaveEvent(QEvent *event)
849 {
850 QListView::leaveEvent(event);
851
852 d->hovered = QModelIndex();
853
854 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
855 !d->itemCategorizer)
856 {
857 return;
858 }
859
860 event->accept();
861
862 viewport()->update();
863 }
864
865 void KListView::startDrag(Qt::DropActions supportedActions)
866 {
867 QListView::startDrag(supportedActions);
868
869 d->isDragging = false;
870 d->mouseButtonPressed = false;
871 }
872
873 void KListView::dragMoveEvent(QDragMoveEvent *event)
874 {
875 QListView::dragMoveEvent(event);
876
877 d->mousePosition = event->pos();
878
879 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
880 !d->itemCategorizer)
881 {
882 return;
883 }
884
885 if (d->mouseButtonPressed)
886 {
887 d->isDragging = true;
888 }
889 else
890 {
891 d->isDragging = false;
892 }
893
894 d->dragLeftViewport = false;
895
896 event->accept();
897
898 viewport()->update();
899 }
900
901 void KListView::dragLeaveEvent(QDragLeaveEvent *event)
902 {
903 QListView::dragLeaveEvent(event);
904
905 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
906 !d->itemCategorizer)
907 {
908 return;
909 }
910
911 d->dragLeftViewport = true;
912
913 event->accept();
914
915 viewport()->update();
916 }
917
918 QModelIndex KListView::moveCursor(CursorAction cursorAction,
919 Qt::KeyboardModifiers modifiers)
920 {
921 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
922 !d->itemCategorizer)
923 {
924 return QListView::moveCursor(cursorAction, modifiers);
925 }
926
927 return QListView::moveCursor(cursorAction, modifiers);
928 }
929
930 void KListView::rowsInserted(const QModelIndex &parent,
931 int start,
932 int end)
933 {
934 QListView::rowsInserted(parent, start, end);
935
936 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
937 !d->itemCategorizer)
938 {
939 return;
940 }
941
942 rowsInsertedArtifficial(parent, start, end);
943 }
944
945 void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
946 int start,
947 int end)
948 {
949 d->lastSelection = QItemSelection();
950 d->elementsInfo.clear();
951 d->elementsPosition.clear();
952 d->elementDictionary.clear();
953 d->categoriesIndexes.clear();
954 d->categoriesPosition.clear();
955 d->categories.clear();
956 d->intersectedIndexes.clear();
957 d->sourceModelIndexList.clear();
958 d->hovered = QModelIndex();
959 d->mouseButtonPressed = false;
960
961 if (start > end || end < 0 || start < 0 || !d->proxyModel->rowCount())
962 {
963 return;
964 }
965
966 // Add all elements mapped to the source model
967 for (int k = 0; k < d->proxyModel->rowCount(); k++)
968 {
969 d->sourceModelIndexList <<
970 d->proxyModel->mapToSource(d->proxyModel->index(k, 0));
971 }
972
973 // Sort them with the general purpose lessThan method
974 LessThan generalLessThan(d->proxyModel,
975 LessThan::GeneralPurpose);
976
977 qStableSort(d->sourceModelIndexList.begin(), d->sourceModelIndexList.end(),
978 generalLessThan);
979
980 // Explore categories
981 QString prevCategory =
982 d->itemCategorizer->categoryForItem(d->sourceModelIndexList[0],
983 d->proxyModel->sortRole());
984 QString lastCategory = prevCategory;
985 QModelIndexList modelIndexList;
986 struct Private::ElementInfo elementInfo;
987 foreach (const QModelIndex &index, d->sourceModelIndexList)
988 {
989 lastCategory = d->itemCategorizer->categoryForItem(index,
990 d->proxyModel->sortRole());
991
992 elementInfo.category = lastCategory;
993
994 if (prevCategory != lastCategory)
995 {
996 d->categoriesIndexes.insert(prevCategory, modelIndexList);
997 d->categories << prevCategory;
998 modelIndexList.clear();
999 }
1000
1001 modelIndexList << index;
1002 prevCategory = lastCategory;
1003
1004 d->elementsInfo.insert(index, elementInfo);
1005 }
1006
1007 d->categoriesIndexes.insert(prevCategory, modelIndexList);
1008 d->categories << prevCategory;
1009
1010 // Sort items locally in their respective categories with the category
1011 // purpose lessThan
1012 LessThan categoryLessThan(d->proxyModel,
1013 LessThan::CategoryPurpose);
1014
1015 foreach (const QString &key, d->categories)
1016 {
1017 QModelIndexList &indexList = d->categoriesIndexes[key];
1018
1019 qStableSort(indexList.begin(), indexList.end(), categoryLessThan);
1020 }
1021
1022 d->lastIndex = d->categoriesIndexes[d->categories[d->categories.count() - 1]][d->categoriesIndexes[d->categories[d->categories.count() - 1]].count() - 1];
1023
1024 // Finally, fill data information of items situation. This will help when
1025 // trying to compute an item place in the viewport
1026 int i = 0; // position relative to the category beginning
1027 int j = 0; // number of elements before current
1028 foreach (const QString &key, d->categories)
1029 {
1030 foreach (const QModelIndex &index, d->categoriesIndexes[key])
1031 {
1032 struct Private::ElementInfo &elementInfo = d->elementsInfo[index];
1033
1034 elementInfo.relativeOffsetToCategory = i;
1035
1036 d->elementDictionary.insert(d->proxyModel->index(j, 0),
1037 d->proxyModel->mapFromSource(index));
1038
1039 i++;
1040 j++;
1041 }
1042
1043 i = 0;
1044 }
1045
1046 d->updateScrollbars();
1047 }
1048
1049 void KListView::rowsRemoved(const QModelIndex &parent,
1050 int start,
1051 int end)
1052 {
1053 if ((viewMode() == KListView::IconMode) && d->proxyModel &&
1054 d->itemCategorizer)
1055 {
1056 // Force the view to update all elements
1057 rowsInsertedArtifficial(parent, start, end);
1058 }
1059 }
1060
1061 void KListView::updateGeometries()
1062 {
1063 if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
1064 !d->itemCategorizer)
1065 {
1066 QListView::updateGeometries();
1067 return;
1068 }
1069
1070 // Avoid QListView::updateGeometries(), since it will try to set another
1071 // range to our scroll bars, what we don't want (ereslibre)
1072 QAbstractItemView::updateGeometries();
1073 }
1074
1075 void KListView::slotSortingRoleChanged()
1076 {
1077 if ((viewMode() == KListView::IconMode) && d->proxyModel &&
1078 d->itemCategorizer)
1079 {
1080 // Force the view to update all elements
1081 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
1082 }
1083 }
1084
1085 #include "klistview.moc"