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