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