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