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