]> cloud.milkyroute.net Git - dolphin.git/blob - src/kcategorizedview.cpp
Fix crash when category view is enabled if there are no categories in KCategorizedVie...
[dolphin.git] / src / kcategorizedview.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 "kcategorizedview.h"
22 #include "kcategorizedview_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 KCategorizedView::Private::Private(KCategorizedView *listView)
79 : listView(listView)
80 , itemCategorizer(0)
81 , biggestItemSize(QSize(0, 0))
82 , mouseButtonPressed(false)
83 , isDragging(false)
84 , dragLeftViewport(false)
85 , proxyModel(0)
86 {
87 }
88
89 KCategorizedView::Private::~Private()
90 {
91 }
92
93 const QModelIndexList &KCategorizedView::Private::intersectionSet(const QRect &rect)
94 {
95 QModelIndex index;
96 QRect indexVisualRect;
97
98 intersectedIndexes.clear();
99
100 int itemHeight;
101
102 if (listView->gridSize().isEmpty())
103 {
104 itemHeight = biggestItemSize.height();
105 }
106 else
107 {
108 itemHeight = listView->gridSize().height();
109 }
110
111 // Lets find out where we should start
112 int top = proxyModel->rowCount() - 1;
113 int bottom = 0;
114 int middle = (top + bottom) / 2;
115 while (bottom <= top)
116 {
117 middle = (top + bottom) / 2;
118
119 index = elementDictionary[proxyModel->index(middle, 0)];
120 indexVisualRect = visualRect(index);
121 // We need the whole height (not only the visualRect). This will help us to update
122 // all needed indexes correctly (ereslibre)
123 indexVisualRect.setHeight(indexVisualRect.height() + (itemHeight - indexVisualRect.height()));
124
125 if (qMax(indexVisualRect.topLeft().y(),
126 indexVisualRect.bottomRight().y()) < qMin(rect.topLeft().y(),
127 rect.bottomRight().y()))
128 {
129 bottom = middle + 1;
130 }
131 else
132 {
133 top = middle - 1;
134 }
135 }
136
137 for (int i = middle; i < proxyModel->rowCount(); i++)
138 {
139 index = elementDictionary[proxyModel->index(i, 0)];
140 indexVisualRect = visualRect(index);
141
142 if (rect.intersects(indexVisualRect))
143 intersectedIndexes.append(index);
144
145 // If we passed next item, stop searching for hits
146 if (qMax(rect.bottomRight().y(), rect.topLeft().y()) <
147 qMin(indexVisualRect.topLeft().y(),
148 indexVisualRect.bottomRight().y()))
149 break;
150 }
151
152 return intersectedIndexes;
153 }
154
155 QRect KCategorizedView::Private::visualRectInViewport(const QModelIndex &index) const
156 {
157 if (!index.isValid())
158 return QRect();
159
160 QString curCategory = elementsInfo[index].category;
161
162 QRect retRect(listView->spacing(), listView->spacing() * 2 +
163 itemCategorizer->categoryHeight(listView->viewOptions()), 0, 0);
164
165 int viewportWidth = listView->viewport()->width() - listView->spacing();
166
167 int itemHeight;
168 int itemWidth;
169
170 if (listView->gridSize().isEmpty())
171 {
172 itemHeight = biggestItemSize.height();
173 itemWidth = biggestItemSize.width();
174 }
175 else
176 {
177 itemHeight = listView->gridSize().height();
178 itemWidth = listView->gridSize().width();
179 }
180
181 int itemWidthPlusSeparation = listView->spacing() + itemWidth;
182 int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
183 if (!elementsPerRow)
184 elementsPerRow++;
185
186 int column = elementsInfo[index].relativeOffsetToCategory % elementsPerRow;
187 int row = elementsInfo[index].relativeOffsetToCategory / elementsPerRow;
188
189 retRect.setLeft(retRect.left() + column * listView->spacing() +
190 column * itemWidth);
191
192 foreach (const QString &category, categories)
193 {
194 if (category == curCategory)
195 break;
196
197 float rows = (float) ((float) categoriesIndexes[category].count() /
198 (float) elementsPerRow);
199 int rowsInt = categoriesIndexes[category].count() / elementsPerRow;
200
201 if (rows - trunc(rows)) rowsInt++;
202
203 retRect.setTop(retRect.top() +
204 (rowsInt * itemHeight) +
205 itemCategorizer->categoryHeight(listView->viewOptions()) +
206 listView->spacing() * 2);
207
208 if (listView->gridSize().isEmpty())
209 {
210 retRect.setTop(retRect.top() +
211 (rowsInt * listView->spacing()));
212 }
213 }
214
215
216 if (listView->gridSize().isEmpty())
217 {
218 retRect.setTop(retRect.top() + row * listView->spacing() +
219 (row * itemHeight));
220 }
221 else
222 {
223 retRect.setTop(retRect.top() + (row * itemHeight));
224 }
225
226 retRect.setWidth(itemWidth);
227
228 if (listView->gridSize().isEmpty())
229 {
230 retRect.setHeight(listView->sizeHintForIndex(proxyModel->mapFromSource(index)).height());
231 }
232 else
233 {
234 retRect.setHeight(qMin(listView->sizeHintForIndex(proxyModel->mapFromSource(index)).height(),
235 listView->gridSize().height()));
236 }
237
238 return retRect;
239 }
240
241 QRect KCategorizedView::Private::visualCategoryRectInViewport(const QString &category)
242 const
243 {
244 QRect retRect(listView->spacing(),
245 listView->spacing(),
246 listView->viewport()->width() - listView->spacing() * 2,
247 0);
248
249 if (!proxyModel->rowCount() || !categories.contains(category))
250 return QRect();
251
252 QModelIndex index = proxyModel->index(0, 0, QModelIndex());
253
254 int viewportWidth = listView->viewport()->width() - listView->spacing();
255
256 int itemHeight;
257 int itemWidth;
258
259 if (listView->gridSize().isEmpty())
260 {
261 itemHeight = biggestItemSize.height();
262 itemWidth = biggestItemSize.width();
263 }
264 else
265 {
266 itemHeight = listView->gridSize().height();
267 itemWidth = listView->gridSize().width();
268 }
269
270 int itemWidthPlusSeparation = listView->spacing() + itemWidth;
271 int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
272
273 if (!elementsPerRow)
274 elementsPerRow++;
275
276 foreach (const QString &itCategory, categories)
277 {
278 if (itCategory == category)
279 break;
280
281 float rows = (float) ((float) categoriesIndexes[itCategory].count() /
282 (float) elementsPerRow);
283 int rowsInt = categoriesIndexes[itCategory].count() / elementsPerRow;
284
285 if (rows - trunc(rows)) rowsInt++;
286
287 retRect.setTop(retRect.top() +
288 (rowsInt * itemHeight) +
289 itemCategorizer->categoryHeight(listView->viewOptions()) +
290 listView->spacing() * 2);
291
292 if (listView->gridSize().isEmpty())
293 {
294 retRect.setTop(retRect.top() +
295 (rowsInt * listView->spacing()));
296 }
297 }
298
299 retRect.setHeight(itemCategorizer->categoryHeight(listView->viewOptions()));
300
301 return retRect;
302 }
303
304 // We're sure elementsPosition doesn't contain index
305 const QRect &KCategorizedView::Private::cacheIndex(const QModelIndex &index)
306 {
307 QRect rect = visualRectInViewport(index);
308 elementsPosition[index] = rect;
309
310 return elementsPosition[index];
311 }
312
313 // We're sure categoriesPosition doesn't contain category
314 const QRect &KCategorizedView::Private::cacheCategory(const QString &category)
315 {
316 QRect rect = visualCategoryRectInViewport(category);
317 categoriesPosition[category] = rect;
318
319 return categoriesPosition[category];
320 }
321
322 const QRect &KCategorizedView::Private::cachedRectIndex(const QModelIndex &index)
323 {
324 if (elementsPosition.contains(index)) // If we have it cached
325 { // return it
326 return elementsPosition[index];
327 }
328 else // Otherwise, cache it
329 { // and return it
330 return cacheIndex(index);
331 }
332 }
333
334 const QRect &KCategorizedView::Private::cachedRectCategory(const QString &category)
335 {
336 if (categoriesPosition.contains(category)) // If we have it cached
337 { // return it
338 return categoriesPosition[category];
339 }
340 else // Otherwise, cache it and
341 { // return it
342 return cacheCategory(category);
343 }
344 }
345
346 QRect KCategorizedView::Private::visualRect(const QModelIndex &index)
347 {
348 QModelIndex mappedIndex = proxyModel->mapToSource(index);
349
350 QRect retRect = cachedRectIndex(mappedIndex);
351 int dx = -listView->horizontalOffset();
352 int dy = -listView->verticalOffset();
353 retRect.adjust(dx, dy, dx, dy);
354
355 return retRect;
356 }
357
358 QRect KCategorizedView::Private::categoryVisualRect(const QString &category)
359 {
360 QRect retRect = cachedRectCategory(category);
361 int dx = -listView->horizontalOffset();
362 int dy = -listView->verticalOffset();
363 retRect.adjust(dx, dy, dx, dy);
364
365 return retRect;
366 }
367
368 void KCategorizedView::Private::drawNewCategory(const QModelIndex &index,
369 int sortRole,
370 const QStyleOption &option,
371 QPainter *painter)
372 {
373 QStyleOption optionCopy = option;
374 const QString category = itemCategorizer->categoryForItem(index, sortRole);
375
376 if ((category == hoveredCategory) && !mouseButtonPressed)
377 {
378 optionCopy.state |= QStyle::State_MouseOver;
379 }
380
381 itemCategorizer->drawCategory(index,
382 sortRole,
383 optionCopy,
384 painter);
385 }
386
387
388 void KCategorizedView::Private::updateScrollbars()
389 {
390 // find the last index in the last category
391 QModelIndex lastIndex = categoriesIndexes.isEmpty() ? QModelIndex() : categoriesIndexes[categories.last()].last();
392
393 int lastItemBottom = cachedRectIndex(lastIndex).top() +
394 listView->spacing() + (listView->gridSize().isEmpty() ? 0 : listView->gridSize().height()) - listView->viewport()->height();
395
396 listView->verticalScrollBar()->setSingleStep(listView->viewport()->height() / 10);
397 listView->verticalScrollBar()->setPageStep(listView->viewport()->height());
398 listView->verticalScrollBar()->setRange(0, lastItemBottom);
399 }
400
401 void KCategorizedView::Private::drawDraggedItems(QPainter *painter)
402 {
403 QStyleOptionViewItemV3 option = listView->viewOptions();
404 option.state &= ~QStyle::State_MouseOver;
405 foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
406 {
407 const int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
408 const int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
409
410 option.rect = visualRect(index);
411 option.rect.adjust(dx, dy, dx, dy);
412
413 if (option.rect.intersects(listView->viewport()->rect()))
414 {
415 listView->itemDelegate(index)->paint(painter, option, index);
416 }
417 }
418 }
419
420 void KCategorizedView::Private::drawDraggedItems()
421 {
422 QRect rectToUpdate;
423 QRect currentRect;
424 foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
425 {
426 int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
427 int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
428
429 currentRect = visualRect(index);
430 currentRect.adjust(dx, dy, dx, dy);
431
432 if (currentRect.intersects(listView->viewport()->rect()))
433 {
434 rectToUpdate = rectToUpdate.united(currentRect);
435 }
436 }
437
438 listView->viewport()->update(lastDraggedItemsRect.united(rectToUpdate));
439
440 lastDraggedItemsRect = rectToUpdate;
441 }
442
443
444 //==============================================================================
445
446
447 KCategorizedView::KCategorizedView(QWidget *parent)
448 : QListView(parent)
449 , d(new Private(this))
450 {
451 }
452
453 KCategorizedView::~KCategorizedView()
454 {
455 delete d;
456 }
457
458 void KCategorizedView::setModel(QAbstractItemModel *model)
459 {
460 d->lastSelection = QItemSelection();
461 d->currentViewIndex = QModelIndex();
462 d->forcedSelectionPosition = 0;
463 d->elementsInfo.clear();
464 d->elementsPosition.clear();
465 d->elementDictionary.clear();
466 d->invertedElementDictionary.clear();
467 d->categoriesIndexes.clear();
468 d->categoriesPosition.clear();
469 d->categories.clear();
470 d->intersectedIndexes.clear();
471 d->sourceModelIndexList.clear();
472 d->hovered = QModelIndex();
473 d->mouseButtonPressed = false;
474
475 if (d->proxyModel)
476 {
477 QObject::disconnect(d->proxyModel,
478 SIGNAL(rowsRemoved(QModelIndex,int,int)),
479 this, SLOT(rowsRemoved(QModelIndex,int,int)));
480
481 QObject::disconnect(d->proxyModel,
482 SIGNAL(sortingRoleChanged()),
483 this, SLOT(slotSortingRoleChanged()));
484 }
485
486 QListView::setModel(model);
487
488 d->proxyModel = dynamic_cast<KSortFilterProxyModel*>(model);
489
490 if (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
502 QRect KCategorizedView::visualRect(const QModelIndex &index) const
503 {
504 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
505 !d->itemCategorizer)
506 {
507 return QListView::visualRect(index);
508 }
509
510 if (!qobject_cast<const QSortFilterProxyModel*>(index.model()))
511 {
512 return d->visualRect(d->proxyModel->mapFromSource(index));
513 }
514
515 return d->visualRect(index);
516 }
517
518 KItemCategorizer *KCategorizedView::itemCategorizer() const
519 {
520 return d->itemCategorizer;
521 }
522
523 void KCategorizedView::setItemCategorizer(KItemCategorizer *itemCategorizer)
524 {
525 d->lastSelection = QItemSelection();
526 d->currentViewIndex = QModelIndex();
527 d->forcedSelectionPosition = 0;
528 d->elementsInfo.clear();
529 d->elementsPosition.clear();
530 d->elementDictionary.clear();
531 d->invertedElementDictionary.clear();
532 d->categoriesIndexes.clear();
533 d->categoriesPosition.clear();
534 d->categories.clear();
535 d->intersectedIndexes.clear();
536 d->sourceModelIndexList.clear();
537 d->hovered = QModelIndex();
538 d->mouseButtonPressed = false;
539
540 if (!itemCategorizer && d->proxyModel)
541 {
542 QObject::disconnect(d->proxyModel,
543 SIGNAL(rowsRemoved(QModelIndex,int,int)),
544 this, SLOT(rowsRemoved(QModelIndex,int,int)));
545
546 QObject::disconnect(d->proxyModel,
547 SIGNAL(sortingRoleChanged()),
548 this, SLOT(slotSortingRoleChanged()));
549 }
550 else if (itemCategorizer && d->proxyModel)
551 {
552 QObject::connect(d->proxyModel,
553 SIGNAL(rowsRemoved(QModelIndex,int,int)),
554 this, SLOT(rowsRemoved(QModelIndex,int,int)));
555
556 QObject::connect(d->proxyModel,
557 SIGNAL(sortingRoleChanged()),
558 this, SLOT(slotSortingRoleChanged()));
559 }
560
561 d->itemCategorizer = itemCategorizer;
562
563 if (itemCategorizer)
564 {
565 rowsInserted(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
566 }
567 else
568 {
569 updateGeometries();
570 }
571 }
572
573 QModelIndex KCategorizedView::indexAt(const QPoint &point) const
574 {
575 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
576 !d->itemCategorizer)
577 {
578 return QListView::indexAt(point);
579 }
580
581 QModelIndex index;
582
583 QModelIndexList item = d->intersectionSet(QRect(point, point));
584
585 if (item.count() == 1)
586 {
587 index = item[0];
588 }
589
590 d->hovered = index;
591
592 return index;
593 }
594
595 void KCategorizedView::reset()
596 {
597 QListView::reset();
598
599 d->lastSelection = QItemSelection();
600 d->currentViewIndex = QModelIndex();
601 d->forcedSelectionPosition = 0;
602 d->elementsInfo.clear();
603 d->elementsPosition.clear();
604 d->elementDictionary.clear();
605 d->invertedElementDictionary.clear();
606 d->categoriesIndexes.clear();
607 d->categoriesPosition.clear();
608 d->categories.clear();
609 d->intersectedIndexes.clear();
610 d->sourceModelIndexList.clear();
611 d->hovered = QModelIndex();
612 d->biggestItemSize = QSize(0, 0);
613 d->mouseButtonPressed = false;
614 }
615
616 void KCategorizedView::paintEvent(QPaintEvent *event)
617 {
618 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
619 !d->itemCategorizer)
620 {
621 QListView::paintEvent(event);
622 return;
623 }
624
625 QStyleOptionViewItemV3 option = viewOptions();
626 option.widget = this;
627 if (wordWrap())
628 {
629 option.features |= QStyleOptionViewItemV2::WrapText;
630 }
631
632 QPainter painter(viewport());
633 QRect area = event->rect();
634 const bool focus = (hasFocus() || viewport()->hasFocus()) &&
635 currentIndex().isValid();
636 const QStyle::State state = option.state;
637 const bool enabled = (state & QStyle::State_Enabled) != 0;
638
639 painter.save();
640
641 QModelIndexList dirtyIndexes = d->intersectionSet(area);
642 foreach (const QModelIndex &index, dirtyIndexes)
643 {
644 option.state = state;
645 option.rect = visualRect(index);
646
647 if (selectionModel() && selectionModel()->isSelected(index))
648 {
649 option.state |= QStyle::State_Selected;
650 }
651
652 if (enabled)
653 {
654 QPalette::ColorGroup cg;
655 if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0)
656 {
657 option.state &= ~QStyle::State_Enabled;
658 cg = QPalette::Disabled;
659 }
660 else
661 {
662 cg = QPalette::Normal;
663 }
664 option.palette.setCurrentColorGroup(cg);
665 }
666
667 if (focus && currentIndex() == index)
668 {
669 option.state |= QStyle::State_HasFocus;
670 if (this->state() == EditingState)
671 option.state |= QStyle::State_Editing;
672 }
673
674 if ((index == d->hovered) && !d->mouseButtonPressed)
675 option.state |= QStyle::State_MouseOver;
676 else
677 option.state &= ~QStyle::State_MouseOver;
678
679 itemDelegate(index)->paint(&painter, option, index);
680 }
681
682 // Redraw categories
683 QStyleOptionViewItem otherOption;
684 foreach (const QString &category, d->categories)
685 {
686 otherOption = option;
687 otherOption.rect = d->categoryVisualRect(category);
688 otherOption.state &= ~QStyle::State_MouseOver;
689
690 if (otherOption.rect.intersects(area))
691 {
692 d->drawNewCategory(d->categoriesIndexes[category][0],
693 d->proxyModel->sortRole(), otherOption, &painter);
694 }
695 }
696
697 if (d->mouseButtonPressed && !d->isDragging)
698 {
699 QPoint start, end, initialPressPosition;
700
701 initialPressPosition = d->initialPressPosition;
702
703 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
704 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
705
706 if (d->initialPressPosition.x() > d->mousePosition.x() ||
707 d->initialPressPosition.y() > d->mousePosition.y())
708 {
709 start = d->mousePosition;
710 end = initialPressPosition;
711 }
712 else
713 {
714 start = initialPressPosition;
715 end = d->mousePosition;
716 }
717
718 QStyleOptionRubberBand yetAnotherOption;
719 yetAnotherOption.initFrom(this);
720 yetAnotherOption.shape = QRubberBand::Rectangle;
721 yetAnotherOption.opaque = false;
722 yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
723 painter.save();
724 style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter);
725 painter.restore();
726 }
727
728 if (d->isDragging && !d->dragLeftViewport)
729 {
730 painter.setOpacity(0.5);
731 d->drawDraggedItems(&painter);
732 }
733
734 painter.restore();
735 }
736
737 void KCategorizedView::resizeEvent(QResizeEvent *event)
738 {
739 QListView::resizeEvent(event);
740
741 // Clear the items positions cache
742 d->elementsPosition.clear();
743 d->categoriesPosition.clear();
744 d->forcedSelectionPosition = 0;
745
746 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
747 !d->itemCategorizer)
748 {
749 return;
750 }
751
752 d->updateScrollbars();
753 }
754
755 void KCategorizedView::setSelection(const QRect &rect,
756 QItemSelectionModel::SelectionFlags flags)
757 {
758 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
759 !d->itemCategorizer)
760 {
761 QListView::setSelection(rect, flags);
762 return;
763 }
764
765 if (!flags)
766 return;
767
768 selectionModel()->clear();
769
770 if (flags & QItemSelectionModel::Clear)
771 {
772 d->lastSelection = QItemSelection();
773 }
774
775 QModelIndexList dirtyIndexes = d->intersectionSet(rect);
776
777 QItemSelection selection;
778
779 if (!dirtyIndexes.count())
780 {
781 if (d->lastSelection.count())
782 {
783 selectionModel()->select(d->lastSelection, flags);
784 }
785
786 return;
787 }
788
789 if (!d->mouseButtonPressed)
790 {
791 selection = QItemSelection(dirtyIndexes[0], dirtyIndexes[0]);
792 d->currentViewIndex = dirtyIndexes[0];
793 }
794 else
795 {
796 QModelIndex first = dirtyIndexes[0];
797 QModelIndex last;
798 foreach (const QModelIndex &index, dirtyIndexes)
799 {
800 if (last.isValid() && last.row() + 1 != index.row())
801 {
802 QItemSelectionRange range(first, last);
803
804 selection << range;
805
806 first = index;
807 }
808
809 last = index;
810 }
811
812 if (last.isValid())
813 selection << QItemSelectionRange(first, last);
814 }
815
816 if (d->lastSelection.count() && !d->mouseButtonPressed)
817 {
818 selection.merge(d->lastSelection, flags);
819 }
820 else if (d->lastSelection.count())
821 {
822 selection.merge(d->lastSelection, QItemSelectionModel::Select);
823 }
824
825 selectionModel()->select(selection, flags);
826 }
827
828 void KCategorizedView::mouseMoveEvent(QMouseEvent *event)
829 {
830 QListView::mouseMoveEvent(event);
831
832 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
833 !d->itemCategorizer)
834 {
835 return;
836 }
837
838 const QString previousHoveredCategory = d->hoveredCategory;
839
840 d->mousePosition = event->pos();
841 d->hoveredCategory = QString();
842
843 // Redraw categories
844 foreach (const QString &category, d->categories)
845 {
846 if (d->categoryVisualRect(category).intersects(QRect(event->pos(), event->pos())))
847 {
848 d->hoveredCategory = category;
849 viewport()->update(d->categoryVisualRect(category));
850 }
851 else if ((category == previousHoveredCategory) &&
852 (!d->categoryVisualRect(previousHoveredCategory).intersects(QRect(event->pos(), event->pos()))))
853 {
854 viewport()->update(d->categoryVisualRect(category));
855 }
856 }
857
858 QRect rect;
859 if (d->mouseButtonPressed && !d->isDragging)
860 {
861 QPoint start, end, initialPressPosition;
862
863 initialPressPosition = d->initialPressPosition;
864
865 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
866 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
867
868 if (d->initialPressPosition.x() > d->mousePosition.x() ||
869 d->initialPressPosition.y() > d->mousePosition.y())
870 {
871 start = d->mousePosition;
872 end = initialPressPosition;
873 }
874 else
875 {
876 start = initialPressPosition;
877 end = d->mousePosition;
878 }
879
880 rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
881
882 //viewport()->update(rect.united(d->lastSelectionRect));
883
884 d->lastSelectionRect = rect;
885 }
886 }
887
888 void KCategorizedView::mousePressEvent(QMouseEvent *event)
889 {
890 d->dragLeftViewport = false;
891
892 if (event->button() == Qt::LeftButton)
893 {
894 d->mouseButtonPressed = true;
895
896 d->initialPressPosition = event->pos();
897 d->initialPressPosition.setY(d->initialPressPosition.y() +
898 verticalOffset());
899 d->initialPressPosition.setX(d->initialPressPosition.x() +
900 horizontalOffset());
901 }
902
903 QListView::mousePressEvent(event);
904 }
905
906 void KCategorizedView::mouseReleaseEvent(QMouseEvent *event)
907 {
908 d->mouseButtonPressed = false;
909
910 QListView::mouseReleaseEvent(event);
911
912 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
913 !d->itemCategorizer)
914 {
915 return;
916 }
917
918 QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
919 initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
920 initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
921
922 QItemSelection selection;
923
924 if (initialPressPosition == d->initialPressPosition)
925 {
926 foreach(const QString &category, d->categories)
927 {
928 if (d->categoryVisualRect(category).contains(event->pos()))
929 {
930 foreach (const QModelIndex &index, d->categoriesIndexes[category])
931 {
932 selection << QItemSelectionRange(d->proxyModel->mapFromSource(index));
933 }
934
935 selectionModel()->select(selection, QItemSelectionModel::Select);
936
937 break;
938 }
939 }
940 }
941
942 d->lastSelection = selectionModel()->selection();
943
944 if (d->hovered.isValid())
945 viewport()->update(visualRect(d->hovered));
946 else if (!d->hoveredCategory.isEmpty())
947 viewport()->update(d->categoryVisualRect(d->hoveredCategory));
948 }
949
950 void KCategorizedView::leaveEvent(QEvent *event)
951 {
952 d->hovered = QModelIndex();
953 d->hoveredCategory = QString();
954
955 QListView::leaveEvent(event);
956 }
957
958 void KCategorizedView::startDrag(Qt::DropActions supportedActions)
959 {
960 QListView::startDrag(supportedActions);
961
962 d->isDragging = false;
963 d->mouseButtonPressed = false;
964
965 viewport()->update(d->lastDraggedItemsRect);
966 }
967
968 void KCategorizedView::dragMoveEvent(QDragMoveEvent *event)
969 {
970 d->mousePosition = event->pos();
971
972 if (d->mouseButtonPressed)
973 {
974 d->isDragging = true;
975 }
976 else
977 {
978 d->isDragging = false;
979 }
980
981 d->dragLeftViewport = false;
982
983 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
984 !d->itemCategorizer)
985 {
986 QListView::dragMoveEvent(event);
987 return;
988 }
989
990 d->drawDraggedItems();
991 }
992
993 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent *event)
994 {
995 d->dragLeftViewport = true;
996
997 QListView::dragLeaveEvent(event);
998 }
999
1000 QModelIndex KCategorizedView::moveCursor(CursorAction cursorAction,
1001 Qt::KeyboardModifiers modifiers)
1002 {
1003 if ( (viewMode() != KCategorizedView::IconMode) ||
1004 !d->proxyModel ||
1005 !d->itemCategorizer ||
1006 d->categories.isEmpty()
1007 )
1008 {
1009 return QListView::moveCursor(cursorAction, modifiers);
1010 }
1011
1012 const QModelIndex current = selectionModel()->currentIndex();
1013
1014 int viewportWidth = viewport()->width() - spacing();
1015 int itemWidth;
1016
1017 if (gridSize().isEmpty())
1018 {
1019 itemWidth = d->biggestItemSize.width();
1020 }
1021 else
1022 {
1023 itemWidth = gridSize().width();
1024 }
1025
1026 int itemWidthPlusSeparation = spacing() + itemWidth;
1027 int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
1028
1029 QString lastCategory = d->categories.first();
1030 QString theCategory = d->categories.first();
1031 QString afterCategory = d->categories.first();
1032
1033 bool hasToBreak = false;
1034 foreach (const QString &category, d->categories)
1035 {
1036 if (hasToBreak)
1037 {
1038 afterCategory = category;
1039
1040 break;
1041 }
1042
1043 if (category == d->elementsInfo[d->proxyModel->mapToSource(current)].category)
1044 {
1045 theCategory = category;
1046
1047 hasToBreak = true;
1048 }
1049
1050 if (!hasToBreak)
1051 {
1052 lastCategory = category;
1053 }
1054 }
1055
1056 switch (cursorAction)
1057 {
1058 case QAbstractItemView::MoveUp: {
1059 if (d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory >= elementsPerRow)
1060 {
1061 int indexToMove = d->invertedElementDictionary[current].row();
1062 indexToMove -= qMin(((d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory) + d->forcedSelectionPosition), elementsPerRow - d->forcedSelectionPosition + (d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory % elementsPerRow));
1063
1064 return d->elementDictionary[d->proxyModel->index(indexToMove, 0)];
1065 }
1066 else
1067 {
1068 int lastCategoryLastRow = (d->categoriesIndexes[lastCategory].count() - 1) % elementsPerRow;
1069 int indexToMove = d->invertedElementDictionary[current].row() - d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory;
1070
1071 if (d->forcedSelectionPosition >= lastCategoryLastRow)
1072 {
1073 indexToMove -= 1;
1074 }
1075 else
1076 {
1077 indexToMove -= qMin((lastCategoryLastRow - d->forcedSelectionPosition + 1), d->forcedSelectionPosition + elementsPerRow + 1);
1078 }
1079
1080 return d->elementDictionary[d->proxyModel->index(indexToMove, 0)];
1081 }
1082 }
1083
1084 case QAbstractItemView::MoveDown: {
1085 if (d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory < (d->categoriesIndexes[theCategory].count() - 1 - ((d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow)))
1086 {
1087 int indexToMove = d->invertedElementDictionary[current].row();
1088 indexToMove += qMin(elementsPerRow, d->categoriesIndexes[theCategory].count() - 1 - d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory);
1089
1090 return d->elementDictionary[d->proxyModel->index(indexToMove, 0)];
1091 }
1092 else
1093 {
1094 int afterCategoryLastRow = qMin(elementsPerRow, d->categoriesIndexes[afterCategory].count());
1095 int indexToMove = d->invertedElementDictionary[current].row() + (d->categoriesIndexes[theCategory].count() - d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory);
1096
1097 if (d->forcedSelectionPosition >= afterCategoryLastRow)
1098 {
1099 indexToMove += afterCategoryLastRow - 1;
1100 }
1101 else
1102 {
1103 indexToMove += qMin(d->forcedSelectionPosition, elementsPerRow);
1104 }
1105
1106 return d->elementDictionary[d->proxyModel->index(indexToMove, 0)];
1107 }
1108 }
1109
1110 case QAbstractItemView::MoveLeft:
1111 d->forcedSelectionPosition = d->elementsInfo[d->proxyModel->mapToSource(d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() - 1, 0)])].relativeOffsetToCategory % elementsPerRow;
1112
1113 if (d->forcedSelectionPosition < 0)
1114 d->forcedSelectionPosition = (d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow;
1115
1116 return d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() - 1, 0)];
1117
1118 case QAbstractItemView::MoveRight:
1119 d->forcedSelectionPosition = d->elementsInfo[d->proxyModel->mapToSource(d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() + 1, 0)])].relativeOffsetToCategory % elementsPerRow;
1120
1121 if (d->forcedSelectionPosition < 0)
1122 d->forcedSelectionPosition = (d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow;
1123
1124 return d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() + 1, 0)];
1125
1126 default:
1127 break;
1128 }
1129
1130 return QListView::moveCursor(cursorAction, modifiers);
1131 }
1132
1133 void KCategorizedView::rowsInserted(const QModelIndex &parent,
1134 int start,
1135 int end)
1136 {
1137 QListView::rowsInserted(parent, start, end);
1138
1139 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
1140 !d->itemCategorizer)
1141 {
1142 d->lastSelection = QItemSelection();
1143 d->currentViewIndex = QModelIndex();
1144 d->forcedSelectionPosition = 0;
1145 d->elementsInfo.clear();
1146 d->elementsPosition.clear();
1147 d->elementDictionary.clear();
1148 d->invertedElementDictionary.clear();
1149 d->categoriesIndexes.clear();
1150 d->categoriesPosition.clear();
1151 d->categories.clear();
1152 d->intersectedIndexes.clear();
1153 d->sourceModelIndexList.clear();
1154 d->hovered = QModelIndex();
1155 d->biggestItemSize = QSize(0, 0);
1156 d->mouseButtonPressed = false;
1157
1158 return;
1159 }
1160
1161 rowsInsertedArtifficial(parent, start, end);
1162 }
1163
1164 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex &parent,
1165 int start,
1166 int end)
1167 {
1168 Q_UNUSED(parent);
1169
1170 d->lastSelection = QItemSelection();
1171 d->currentViewIndex = QModelIndex();
1172 d->forcedSelectionPosition = 0;
1173 d->elementsInfo.clear();
1174 d->elementsPosition.clear();
1175 d->elementDictionary.clear();
1176 d->invertedElementDictionary.clear();
1177 d->categoriesIndexes.clear();
1178 d->categoriesPosition.clear();
1179 d->categories.clear();
1180 d->intersectedIndexes.clear();
1181 d->sourceModelIndexList.clear();
1182 d->hovered = QModelIndex();
1183 d->biggestItemSize = QSize(0, 0);
1184 d->mouseButtonPressed = false;
1185
1186 if (start > end || end < 0 || start < 0 || !d->proxyModel->rowCount())
1187 {
1188 return;
1189 }
1190
1191 // Add all elements mapped to the source model
1192 for (int k = 0; k < d->proxyModel->rowCount(); k++)
1193 {
1194 d->biggestItemSize = QSize(qMax(sizeHintForIndex(d->proxyModel->index(k, 0)).width(),
1195 d->biggestItemSize.width()),
1196 qMax(sizeHintForIndex(d->proxyModel->index(k, 0)).height(),
1197 d->biggestItemSize.height()));
1198
1199 d->sourceModelIndexList <<
1200 d->proxyModel->mapToSource(d->proxyModel->index(k, 0));
1201 }
1202
1203 // Sort them with the general purpose lessThan method
1204 LessThan generalLessThan(d->proxyModel,
1205 LessThan::GeneralPurpose);
1206
1207 qStableSort(d->sourceModelIndexList.begin(), d->sourceModelIndexList.end(),
1208 generalLessThan);
1209
1210 // Explore categories
1211 QString prevCategory =
1212 d->itemCategorizer->categoryForItem(d->sourceModelIndexList[0],
1213 d->proxyModel->sortRole());
1214 QString lastCategory = prevCategory;
1215 QModelIndexList modelIndexList;
1216 struct Private::ElementInfo elementInfo;
1217 foreach (const QModelIndex &index, d->sourceModelIndexList)
1218 {
1219 lastCategory = d->itemCategorizer->categoryForItem(index,
1220 d->proxyModel->sortRole());
1221
1222 elementInfo.category = lastCategory;
1223
1224 if (prevCategory != lastCategory)
1225 {
1226 d->categoriesIndexes.insert(prevCategory, modelIndexList);
1227 d->categories << prevCategory;
1228 modelIndexList.clear();
1229 }
1230
1231 modelIndexList << index;
1232 prevCategory = lastCategory;
1233
1234 d->elementsInfo.insert(index, elementInfo);
1235 }
1236
1237 d->categoriesIndexes.insert(prevCategory, modelIndexList);
1238 d->categories << prevCategory;
1239
1240 // Sort items locally in their respective categories with the category
1241 // purpose lessThan
1242 LessThan categoryLessThan(d->proxyModel,
1243 LessThan::CategoryPurpose);
1244
1245 foreach (const QString &key, d->categories)
1246 {
1247 QModelIndexList &indexList = d->categoriesIndexes[key];
1248
1249 qStableSort(indexList.begin(), indexList.end(), categoryLessThan);
1250 }
1251
1252 // Finally, fill data information of items situation. This will help when
1253 // trying to compute an item place in the viewport
1254 int i = 0; // position relative to the category beginning
1255 int j = 0; // number of elements before current
1256 foreach (const QString &key, d->categories)
1257 {
1258 foreach (const QModelIndex &index, d->categoriesIndexes[key])
1259 {
1260 struct Private::ElementInfo &elementInfo = d->elementsInfo[index];
1261
1262 elementInfo.relativeOffsetToCategory = i;
1263
1264 d->elementDictionary.insert(d->proxyModel->index(j, 0),
1265 d->proxyModel->mapFromSource(index));
1266
1267 d->invertedElementDictionary.insert(d->proxyModel->mapFromSource(index),
1268 d->proxyModel->index(j, 0));
1269
1270 i++;
1271 j++;
1272 }
1273
1274 i = 0;
1275 }
1276
1277 d->updateScrollbars();
1278 }
1279
1280 void KCategorizedView::rowsRemoved(const QModelIndex &parent,
1281 int start,
1282 int end)
1283 {
1284 if ((viewMode() == KCategorizedView::IconMode) && d->proxyModel &&
1285 d->itemCategorizer)
1286 {
1287 // Force the view to update all elements
1288 rowsInsertedArtifficial(parent, start, end);
1289 }
1290 }
1291
1292 void KCategorizedView::updateGeometries()
1293 {
1294 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
1295 !d->itemCategorizer)
1296 {
1297 QListView::updateGeometries();
1298 return;
1299 }
1300
1301 // Avoid QListView::updateGeometries(), since it will try to set another
1302 // range to our scroll bars, what we don't want (ereslibre)
1303 QAbstractItemView::updateGeometries();
1304 }
1305
1306 void KCategorizedView::slotSortingRoleChanged()
1307 {
1308 if ((viewMode() == KCategorizedView::IconMode) && d->proxyModel &&
1309 d->itemCategorizer)
1310 {
1311 // Force the view to update all elements
1312 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
1313 }
1314 }
1315
1316 #include "kcategorizedview.moc"