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