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