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