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