]> cloud.milkyroute.net Git - dolphin.git/blob - src/kcategorizedview.cpp
If the categorizing has been enabled/disabled we need to update the scrollbar in...
[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@kde.org>
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 optionCopy.state &= ~QStyle::State_Selected;
361
362 if ((category == hoveredCategory) && !mouseButtonPressed)
363 {
364 optionCopy.state |= QStyle::State_MouseOver;
365 }
366 else if ((category == hoveredCategory) && mouseButtonPressed)
367 {
368 QPoint initialPressPosition = listView->viewport()->mapFromGlobal(QCursor::pos());
369 initialPressPosition.setY(initialPressPosition.y() + listView->verticalOffset());
370 initialPressPosition.setX(initialPressPosition.x() + listView->horizontalOffset());
371
372 if (initialPressPosition == this->initialPressPosition)
373 {
374 optionCopy.state |= QStyle::State_Selected;
375 }
376 }
377
378 categoryDrawer->drawCategory(index,
379 sortRole,
380 optionCopy,
381 painter);
382 }
383
384
385 void KCategorizedView::Private::updateScrollbars()
386 {
387 // find the last index in the last category
388 QModelIndex lastIndex = categoriesIndexes.isEmpty() ? QModelIndex() : categoriesIndexes[categories.last()].last();
389
390 int lastItemBottom = cachedRectIndex(lastIndex).top() +
391 listView->spacing() + (listView->gridSize().isEmpty() ? 0 : listView->gridSize().height()) - listView->viewport()->height();
392
393 listView->verticalScrollBar()->setSingleStep(listView->viewport()->height() / 10);
394 listView->verticalScrollBar()->setPageStep(listView->viewport()->height());
395 listView->verticalScrollBar()->setRange(0, lastItemBottom);
396 }
397
398 void KCategorizedView::Private::drawDraggedItems(QPainter *painter)
399 {
400 QStyleOptionViewItemV3 option = listView->viewOptions();
401 option.state &= ~QStyle::State_MouseOver;
402 foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
403 {
404 const int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
405 const int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
406
407 option.rect = visualRect(index);
408 option.rect.adjust(dx, dy, dx, dy);
409
410 if (option.rect.intersects(listView->viewport()->rect()))
411 {
412 listView->itemDelegate(index)->paint(painter, option, index);
413 }
414 }
415 }
416
417 void KCategorizedView::Private::drawDraggedItems()
418 {
419 QRect rectToUpdate;
420 QRect currentRect;
421 foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
422 {
423 int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
424 int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
425
426 currentRect = visualRect(index);
427 currentRect.adjust(dx, dy, dx, dy);
428
429 if (currentRect.intersects(listView->viewport()->rect()))
430 {
431 rectToUpdate = rectToUpdate.united(currentRect);
432 }
433 }
434
435 listView->viewport()->update(lastDraggedItemsRect.united(rectToUpdate));
436
437 lastDraggedItemsRect = rectToUpdate;
438 }
439
440
441 //==============================================================================
442
443
444 KCategorizedView::KCategorizedView(QWidget *parent)
445 : QListView(parent)
446 , d(new Private(this))
447 {
448 }
449
450 KCategorizedView::~KCategorizedView()
451 {
452 delete d;
453 }
454
455 void KCategorizedView::setGridSize(const QSize &size)
456 {
457 QListView::setGridSize(size);
458
459 slotLayoutChanged();
460 }
461
462 void KCategorizedView::setModel(QAbstractItemModel *model)
463 {
464 d->lastSelection = QItemSelection();
465 d->currentViewIndex = QModelIndex();
466 d->forcedSelectionPosition = 0;
467 d->elementsInfo.clear();
468 d->elementsPosition.clear();
469 d->categoriesIndexes.clear();
470 d->categoriesPosition.clear();
471 d->categories.clear();
472 d->intersectedIndexes.clear();
473 d->modelIndexList.clear();
474 d->hovered = QModelIndex();
475 d->mouseButtonPressed = false;
476
477 if (d->proxyModel)
478 {
479 QObject::disconnect(d->proxyModel,
480 SIGNAL(layoutChanged()),
481 this, SLOT(slotLayoutChanged()));
482
483 QObject::disconnect(d->proxyModel,
484 SIGNAL(dataChanged(QModelIndex,QModelIndex)),
485 this, SLOT(slotLayoutChanged()));
486
487 QObject::disconnect(d->proxyModel,
488 SIGNAL(rowsRemoved(QModelIndex,int,int)),
489 this, SLOT(rowsRemoved(QModelIndex,int,int)));
490 }
491
492 QListView::setModel(model);
493
494 d->proxyModel = dynamic_cast<KCategorizedSortFilterProxyModel*>(model);
495
496 if (d->proxyModel)
497 {
498 d->modelSortRole = d->proxyModel->sortRole();
499 d->modelSortColumn = d->proxyModel->sortColumn();
500 d->modelCategorized = true;
501 d->modelSortOrder = d->proxyModel->sortOrder();
502
503 QObject::connect(d->proxyModel,
504 SIGNAL(layoutChanged()),
505 this, SLOT(slotLayoutChanged()));
506
507 QObject::connect(d->proxyModel,
508 SIGNAL(dataChanged(QModelIndex,QModelIndex)),
509 this, SLOT(slotLayoutChanged()));
510
511 QObject::connect(d->proxyModel,
512 SIGNAL(rowsRemoved(QModelIndex,int,int)),
513 this, SLOT(rowsRemoved(QModelIndex,int,int)));
514
515 if (d->proxyModel->rowCount())
516 {
517 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
518 }
519 }
520 else
521 {
522 d->modelCategorized = false;
523 }
524 }
525
526 QRect KCategorizedView::visualRect(const QModelIndex &index) const
527 {
528 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
529 !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
530 {
531 return QListView::visualRect(index);
532 }
533
534 if (!qobject_cast<const QSortFilterProxyModel*>(index.model()))
535 {
536 return d->visualRect(d->proxyModel->mapFromSource(index));
537 }
538
539 return d->visualRect(index);
540 }
541
542 KCategoryDrawer *KCategorizedView::categoryDrawer() const
543 {
544 return d->categoryDrawer;
545 }
546
547 void KCategorizedView::setCategoryDrawer(KCategoryDrawer *categoryDrawer)
548 {
549 d->lastSelection = QItemSelection();
550 d->currentViewIndex = QModelIndex();
551 d->forcedSelectionPosition = 0;
552 d->elementsInfo.clear();
553 d->elementsPosition.clear();
554 d->categoriesIndexes.clear();
555 d->categoriesPosition.clear();
556 d->categories.clear();
557 d->intersectedIndexes.clear();
558 d->modelIndexList.clear();
559 d->hovered = QModelIndex();
560 d->mouseButtonPressed = false;
561
562 if (!categoryDrawer && d->proxyModel)
563 {
564 QObject::disconnect(d->proxyModel,
565 SIGNAL(layoutChanged()),
566 this, SLOT(slotLayoutChanged()));
567
568 QObject::disconnect(d->proxyModel,
569 SIGNAL(dataChanged(QModelIndex,QModelIndex)),
570 this, SLOT(slotLayoutChanged()));
571
572 QObject::disconnect(d->proxyModel,
573 SIGNAL(rowsRemoved(QModelIndex,int,int)),
574 this, SLOT(rowsRemoved(QModelIndex,int,int)));
575 }
576 else if (categoryDrawer && d->proxyModel)
577 {
578 QObject::connect(d->proxyModel,
579 SIGNAL(layoutChanged()),
580 this, SLOT(slotLayoutChanged()));
581
582 QObject::connect(d->proxyModel,
583 SIGNAL(dataChanged(QModelIndex,QModelIndex)),
584 this, SLOT(slotLayoutChanged()));
585
586 QObject::connect(d->proxyModel,
587 SIGNAL(rowsRemoved(QModelIndex,int,int)),
588 this, SLOT(rowsRemoved(QModelIndex,int,int)));
589 }
590
591 d->categoryDrawer = categoryDrawer;
592
593 if (categoryDrawer)
594 {
595 if (d->proxyModel)
596 {
597 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
598 }
599 }
600 else
601 {
602 updateGeometries();
603 }
604 }
605
606 QModelIndex KCategorizedView::indexAt(const QPoint &point) const
607 {
608 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
609 !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
610 {
611 return QListView::indexAt(point);
612 }
613
614 QModelIndex index;
615
616 QModelIndexList item = d->intersectionSet(QRect(point, point));
617
618 if (item.count() == 1)
619 {
620 index = item[0];
621 }
622
623 d->hovered = index;
624
625 return index;
626 }
627
628 void KCategorizedView::reset()
629 {
630 QListView::reset();
631
632 d->lastSelection = QItemSelection();
633 d->currentViewIndex = QModelIndex();
634 d->forcedSelectionPosition = 0;
635 d->elementsInfo.clear();
636 d->elementsPosition.clear();
637 d->categoriesIndexes.clear();
638 d->categoriesPosition.clear();
639 d->categories.clear();
640 d->intersectedIndexes.clear();
641 d->modelIndexList.clear();
642 d->hovered = QModelIndex();
643 d->biggestItemSize = QSize(0, 0);
644 d->mouseButtonPressed = false;
645 }
646
647 void KCategorizedView::paintEvent(QPaintEvent *event)
648 {
649 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
650 !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
651 {
652 QListView::paintEvent(event);
653 return;
654 }
655
656 QStyleOptionViewItemV3 option = viewOptions();
657 option.widget = this;
658 if (wordWrap())
659 {
660 option.features |= QStyleOptionViewItemV2::WrapText;
661 }
662
663 QPainter painter(viewport());
664 QRect area = event->rect();
665 const bool focus = (hasFocus() || viewport()->hasFocus()) &&
666 currentIndex().isValid();
667 const QStyle::State state = option.state;
668 const bool enabled = (state & QStyle::State_Enabled) != 0;
669
670 painter.save();
671
672 QModelIndexList dirtyIndexes = d->intersectionSet(area);
673 foreach (const QModelIndex &index, dirtyIndexes)
674 {
675 option.state = state;
676 option.rect = visualRect(index);
677
678 if (selectionModel() && selectionModel()->isSelected(index))
679 {
680 option.state |= QStyle::State_Selected;
681 }
682
683 if (enabled)
684 {
685 QPalette::ColorGroup cg;
686 if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0)
687 {
688 option.state &= ~QStyle::State_Enabled;
689 cg = QPalette::Disabled;
690 }
691 else
692 {
693 cg = QPalette::Normal;
694 }
695 option.palette.setCurrentColorGroup(cg);
696 }
697
698 if (focus && currentIndex() == index)
699 {
700 option.state |= QStyle::State_HasFocus;
701 if (this->state() == EditingState)
702 option.state |= QStyle::State_Editing;
703 }
704
705 if ((index == d->hovered) && !d->mouseButtonPressed)
706 option.state |= QStyle::State_MouseOver;
707 else
708 option.state &= ~QStyle::State_MouseOver;
709
710 itemDelegate(index)->paint(&painter, option, index);
711 }
712
713 // Redraw categories
714 QStyleOptionViewItem otherOption;
715 foreach (const QString &category, d->categories)
716 {
717 otherOption = option;
718 otherOption.rect = d->categoryVisualRect(category);
719 otherOption.state &= ~QStyle::State_MouseOver;
720
721 if (otherOption.rect.intersects(area))
722 {
723 QModelIndex indexToDraw = d->proxyModel->index(d->categoriesIndexes[category][0].row(), d->proxyModel->sortColumn());
724
725 d->drawNewCategory(indexToDraw,
726 d->proxyModel->sortRole(), otherOption, &painter);
727 }
728 }
729
730 if (d->mouseButtonPressed && !d->isDragging)
731 {
732 QPoint start, end, initialPressPosition;
733
734 initialPressPosition = d->initialPressPosition;
735
736 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
737 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
738
739 if (d->initialPressPosition.x() > d->mousePosition.x() ||
740 d->initialPressPosition.y() > d->mousePosition.y())
741 {
742 start = d->mousePosition;
743 end = initialPressPosition;
744 }
745 else
746 {
747 start = initialPressPosition;
748 end = d->mousePosition;
749 }
750
751 QStyleOptionRubberBand yetAnotherOption;
752 yetAnotherOption.initFrom(this);
753 yetAnotherOption.shape = QRubberBand::Rectangle;
754 yetAnotherOption.opaque = false;
755 yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
756 painter.save();
757 style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter);
758 painter.restore();
759 }
760
761 if (d->isDragging && !d->dragLeftViewport)
762 {
763 painter.setOpacity(0.5);
764 d->drawDraggedItems(&painter);
765 }
766
767 painter.restore();
768 }
769
770 void KCategorizedView::resizeEvent(QResizeEvent *event)
771 {
772 QListView::resizeEvent(event);
773
774 // Clear the items positions cache
775 d->elementsPosition.clear();
776 d->categoriesPosition.clear();
777 d->forcedSelectionPosition = 0;
778
779 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
780 !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
781 {
782 return;
783 }
784
785 d->updateScrollbars();
786 }
787
788 void KCategorizedView::setSelection(const QRect &rect,
789 QItemSelectionModel::SelectionFlags flags)
790 {
791 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
792 !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
793 {
794 QListView::setSelection(rect, flags);
795 return;
796 }
797
798 if (!flags)
799 return;
800
801 if (flags & QItemSelectionModel::Clear)
802 {
803 if (!rect.intersects(d->categoryVisualRect(d->hoveredCategory)))
804 {
805 d->lastSelection = QItemSelection();
806 }
807 }
808
809 selectionModel()->clear();
810
811 QModelIndexList dirtyIndexes = d->intersectionSet(rect);
812
813 if (!dirtyIndexes.count())
814 {
815 if (!d->lastSelection.isEmpty() &&
816 (rect.intersects(d->categoryVisualRect(d->hoveredCategory)) || d->mouseButtonPressed))
817 {
818 selectionModel()->select(d->lastSelection, flags);
819 }
820
821 return;
822 }
823
824 QItemSelection selection;
825
826 if (!d->mouseButtonPressed)
827 {
828 selection = QItemSelection(dirtyIndexes[0], dirtyIndexes[0]);
829 d->currentViewIndex = dirtyIndexes[0];
830 }
831 else
832 {
833 QModelIndex first = dirtyIndexes[0];
834 QModelIndex last;
835 foreach (const QModelIndex &index, dirtyIndexes)
836 {
837 if (last.isValid() && last.row() + 1 != index.row())
838 {
839 QItemSelectionRange range(first, last);
840
841 selection << range;
842
843 first = index;
844 }
845
846 last = index;
847 }
848
849 if (last.isValid())
850 selection << QItemSelectionRange(first, last);
851 }
852
853 if (d->lastSelection.count())
854 {
855 if ((selection.count() == 1) && (selection[0].indexes().count() == 1))
856 selection.merge(d->lastSelection, flags);
857 else
858 selection.merge(d->lastSelection, QItemSelectionModel::Select);
859 }
860
861 selectionModel()->select(selection, flags);
862 }
863
864 void KCategorizedView::mouseMoveEvent(QMouseEvent *event)
865 {
866 QListView::mouseMoveEvent(event);
867
868 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
869 !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
870 {
871 return;
872 }
873
874 const QString previousHoveredCategory = d->hoveredCategory;
875
876 d->mousePosition = event->pos();
877 d->hoveredCategory = QString();
878
879 // Redraw categories
880 foreach (const QString &category, d->categories)
881 {
882 if (d->categoryVisualRect(category).intersects(QRect(event->pos(), event->pos())))
883 {
884 d->hoveredCategory = category;
885 viewport()->update(d->categoryVisualRect(category));
886 }
887 else if ((category == previousHoveredCategory) &&
888 (!d->categoryVisualRect(previousHoveredCategory).intersects(QRect(event->pos(), event->pos()))))
889 {
890 viewport()->update(d->categoryVisualRect(category));
891 }
892 }
893
894 QRect rect;
895 if (d->mouseButtonPressed && !d->isDragging)
896 {
897 QPoint start, end, initialPressPosition;
898
899 initialPressPosition = d->initialPressPosition;
900
901 initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
902 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
903
904 if (d->initialPressPosition.x() > d->mousePosition.x() ||
905 d->initialPressPosition.y() > d->mousePosition.y())
906 {
907 start = d->mousePosition;
908 end = initialPressPosition;
909 }
910 else
911 {
912 start = initialPressPosition;
913 end = d->mousePosition;
914 }
915
916 rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
917
918 //viewport()->update(rect.united(d->lastSelectionRect));
919
920 d->lastSelectionRect = rect;
921 }
922 }
923
924 void KCategorizedView::mousePressEvent(QMouseEvent *event)
925 {
926 d->dragLeftViewport = false;
927
928 if (event->button() == Qt::LeftButton)
929 {
930 d->mouseButtonPressed = true;
931
932 d->initialPressPosition = event->pos();
933 d->initialPressPosition.setY(d->initialPressPosition.y() +
934 verticalOffset());
935 d->initialPressPosition.setX(d->initialPressPosition.x() +
936 horizontalOffset());
937 }
938
939 QListView::mousePressEvent(event);
940
941 viewport()->update(d->categoryVisualRect(d->hoveredCategory));
942 }
943
944 void KCategorizedView::mouseReleaseEvent(QMouseEvent *event)
945 {
946 d->mouseButtonPressed = false;
947
948 QListView::mouseReleaseEvent(event);
949
950 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
951 !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
952 {
953 return;
954 }
955
956 QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
957 initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
958 initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
959
960 QItemSelection selection;
961 QItemSelection deselection;
962
963 if (initialPressPosition == d->initialPressPosition)
964 {
965 foreach(const QString &category, d->categories)
966 {
967 if (d->categoryVisualRect(category).contains(event->pos()))
968 {
969 foreach (const QModelIndex &index, d->categoriesIndexes[category])
970 {
971 QModelIndex selectIndex = index.model()->index(index.row(), 0);
972
973 if (!d->lastSelection.contains(selectIndex))
974 {
975 selection << QItemSelectionRange(selectIndex);
976 }
977 else
978 {
979 deselection << QItemSelectionRange(selectIndex);
980 }
981 }
982
983 selectionModel()->select(selection, QItemSelectionModel::Select);
984 selectionModel()->select(deselection, QItemSelectionModel::Deselect);
985
986 break;
987 }
988 }
989 }
990
991 d->lastSelection = selectionModel()->selection();
992
993 if (d->hovered.isValid())
994 viewport()->update(visualRect(d->hovered));
995 else if (!d->hoveredCategory.isEmpty())
996 viewport()->update(d->categoryVisualRect(d->hoveredCategory));
997 }
998
999 void KCategorizedView::leaveEvent(QEvent *event)
1000 {
1001 d->hovered = QModelIndex();
1002 d->hoveredCategory = QString();
1003
1004 QListView::leaveEvent(event);
1005 }
1006
1007 void KCategorizedView::startDrag(Qt::DropActions supportedActions)
1008 {
1009 // FIXME: QAbstractItemView does far better here since it sets the
1010 // pixmap of selected icons to the dragging cursor, but it sets a non
1011 // ARGB window so it is no transparent. Use QAbstractItemView when
1012 // this is fixed on Qt.
1013 // QAbstractItemView::startDrag(supportedActions);
1014 QListView::startDrag(supportedActions);
1015
1016 d->isDragging = false;
1017 d->mouseButtonPressed = false;
1018
1019 viewport()->update(d->lastDraggedItemsRect);
1020 }
1021
1022 void KCategorizedView::dragMoveEvent(QDragMoveEvent *event)
1023 {
1024 d->mousePosition = event->pos();
1025
1026 if (d->mouseButtonPressed)
1027 {
1028 d->isDragging = true;
1029 }
1030 else
1031 {
1032 d->isDragging = false;
1033 }
1034
1035 d->dragLeftViewport = false;
1036
1037 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
1038 !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
1039 {
1040 QListView::dragMoveEvent(event);
1041 return;
1042 }
1043
1044 d->drawDraggedItems();
1045 }
1046
1047 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent *event)
1048 {
1049 d->dragLeftViewport = true;
1050
1051 QListView::dragLeaveEvent(event);
1052 }
1053
1054 QModelIndex KCategorizedView::moveCursor(CursorAction cursorAction,
1055 Qt::KeyboardModifiers modifiers)
1056 {
1057 if ((viewMode() != KCategorizedView::IconMode) ||
1058 !d->proxyModel ||
1059 !d->categoryDrawer ||
1060 d->categories.isEmpty() ||
1061 !d->proxyModel->isCategorizedModel()
1062 )
1063 {
1064 return QListView::moveCursor(cursorAction, modifiers);
1065 }
1066
1067 const QModelIndex current = selectionModel()->currentIndex();
1068
1069 int viewportWidth = viewport()->width() - spacing();
1070 int itemWidth;
1071
1072 if (gridSize().isEmpty())
1073 {
1074 itemWidth = d->biggestItemSize.width();
1075 }
1076 else
1077 {
1078 itemWidth = gridSize().width();
1079 }
1080
1081 int itemWidthPlusSeparation = spacing() + itemWidth;
1082 int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
1083
1084 QString lastCategory = d->categories.first();
1085 QString theCategory = d->categories.first();
1086 QString afterCategory = d->categories.first();
1087
1088 bool hasToBreak = false;
1089 foreach (const QString &category, d->categories)
1090 {
1091 if (hasToBreak)
1092 {
1093 afterCategory = category;
1094
1095 break;
1096 }
1097
1098 if (category == d->elementsInfo[current.row()].category)
1099 {
1100 theCategory = category;
1101
1102 hasToBreak = true;
1103 }
1104
1105 if (!hasToBreak)
1106 {
1107 lastCategory = category;
1108 }
1109 }
1110
1111 switch (cursorAction)
1112 {
1113 case QAbstractItemView::MoveUp: {
1114 if (d->elementsInfo[current.row()].relativeOffsetToCategory >= elementsPerRow)
1115 {
1116 int indexToMove = current.row();
1117 indexToMove -= qMin(((d->elementsInfo[current.row()].relativeOffsetToCategory) + d->forcedSelectionPosition), elementsPerRow - d->forcedSelectionPosition + (d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow));
1118
1119 return d->proxyModel->index(indexToMove, 0);
1120 }
1121 else
1122 {
1123 int lastCategoryLastRow = (d->categoriesIndexes[lastCategory].count() - 1) % elementsPerRow;
1124 int indexToMove = current.row() - d->elementsInfo[current.row()].relativeOffsetToCategory;
1125
1126 if (d->forcedSelectionPosition >= lastCategoryLastRow)
1127 {
1128 indexToMove -= 1;
1129 }
1130 else
1131 {
1132 indexToMove -= qMin((lastCategoryLastRow - d->forcedSelectionPosition + 1), d->forcedSelectionPosition + elementsPerRow + 1);
1133 }
1134
1135 return d->proxyModel->index(indexToMove, 0);
1136 }
1137 }
1138
1139 case QAbstractItemView::MoveDown: {
1140 if (d->elementsInfo[current.row()].relativeOffsetToCategory < (d->categoriesIndexes[theCategory].count() - 1 - ((d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow)))
1141 {
1142 int indexToMove = current.row();
1143 indexToMove += qMin(elementsPerRow, d->categoriesIndexes[theCategory].count() - 1 - d->elementsInfo[current.row()].relativeOffsetToCategory);
1144
1145 return d->proxyModel->index(indexToMove, 0);
1146 }
1147 else
1148 {
1149 int afterCategoryLastRow = qMin(elementsPerRow, d->categoriesIndexes[afterCategory].count());
1150 int indexToMove = current.row() + (d->categoriesIndexes[theCategory].count() - d->elementsInfo[current.row()].relativeOffsetToCategory);
1151
1152 if (d->forcedSelectionPosition >= afterCategoryLastRow)
1153 {
1154 indexToMove += afterCategoryLastRow - 1;
1155 }
1156 else
1157 {
1158 indexToMove += qMin(d->forcedSelectionPosition, elementsPerRow);
1159 }
1160
1161 return d->proxyModel->index(indexToMove, 0);
1162 }
1163 }
1164
1165 case QAbstractItemView::MoveLeft:
1166 if (layoutDirection() == Qt::RightToLeft)
1167 {
1168 d->forcedSelectionPosition = d->elementsInfo[current.row() + 1].relativeOffsetToCategory % elementsPerRow;
1169
1170 if (d->forcedSelectionPosition < 0)
1171 d->forcedSelectionPosition = (d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow;
1172
1173 return d->proxyModel->index(current.row() + 1, 0);
1174 }
1175
1176 d->forcedSelectionPosition = d->elementsInfo[current.row() - 1].relativeOffsetToCategory % elementsPerRow;
1177
1178 if (d->forcedSelectionPosition < 0)
1179 d->forcedSelectionPosition = (d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow;
1180
1181 return d->proxyModel->index(current.row() - 1, 0);
1182
1183 case QAbstractItemView::MoveRight:
1184 if (layoutDirection() == Qt::RightToLeft)
1185 {
1186 d->forcedSelectionPosition = d->elementsInfo[current.row() - 1].relativeOffsetToCategory % elementsPerRow;
1187
1188 if (d->forcedSelectionPosition < 0)
1189 d->forcedSelectionPosition = (d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow;
1190
1191 return d->proxyModel->index(current.row() - 1, 0);
1192 }
1193
1194 d->forcedSelectionPosition = d->elementsInfo[current.row() + 1].relativeOffsetToCategory % elementsPerRow;
1195
1196 if (d->forcedSelectionPosition < 0)
1197 d->forcedSelectionPosition = (d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow;
1198
1199 return d->proxyModel->index(current.row() + 1, 0);
1200
1201 default:
1202 break;
1203 }
1204
1205 return QListView::moveCursor(cursorAction, modifiers);
1206 }
1207
1208 void KCategorizedView::rowsInserted(const QModelIndex &parent,
1209 int start,
1210 int end)
1211 {
1212 QListView::rowsInserted(parent, start, end);
1213
1214 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
1215 !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
1216 {
1217 d->lastSelection = QItemSelection();
1218 d->currentViewIndex = QModelIndex();
1219 d->forcedSelectionPosition = 0;
1220 d->elementsInfo.clear();
1221 d->elementsPosition.clear();
1222 d->categoriesIndexes.clear();
1223 d->categoriesPosition.clear();
1224 d->categories.clear();
1225 d->intersectedIndexes.clear();
1226 d->modelIndexList.clear();
1227 d->hovered = QModelIndex();
1228 d->biggestItemSize = QSize(0, 0);
1229 d->mouseButtonPressed = false;
1230
1231 return;
1232 }
1233
1234 rowsInsertedArtifficial(parent, start, end);
1235 }
1236
1237 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex &parent,
1238 int start,
1239 int end)
1240 {
1241 Q_UNUSED(parent);
1242
1243 d->lastSelection = QItemSelection();
1244 d->currentViewIndex = QModelIndex();
1245 d->forcedSelectionPosition = 0;
1246 d->elementsInfo.clear();
1247 d->elementsPosition.clear();
1248 d->categoriesIndexes.clear();
1249 d->categoriesPosition.clear();
1250 d->categories.clear();
1251 d->intersectedIndexes.clear();
1252 d->modelIndexList.clear();
1253 d->hovered = QModelIndex();
1254 d->biggestItemSize = QSize(0, 0);
1255 d->mouseButtonPressed = false;
1256
1257 if (start > end || end < 0 || start < 0 || !d->proxyModel->rowCount())
1258 {
1259 return;
1260 }
1261
1262 // Add all elements mapped to the source model and explore categories
1263 QString prevCategory = d->proxyModel->data(d->proxyModel->index(0, d->proxyModel->sortColumn()), KCategorizedSortFilterProxyModel::CategoryRole).toString();
1264 QString lastCategory = prevCategory;
1265 QModelIndexList modelIndexList;
1266 struct Private::ElementInfo elementInfo;
1267 int offset = -1;
1268 for (int k = 0; k < d->proxyModel->rowCount(); ++k)
1269 {
1270 QModelIndex index = d->proxyModel->index(k, d->proxyModel->sortColumn());
1271 QModelIndex indexSize = d->proxyModel->index(k, 0);
1272
1273 d->biggestItemSize = QSize(qMax(sizeHintForIndex(indexSize).width(),
1274 d->biggestItemSize.width()),
1275 qMax(sizeHintForIndex(indexSize).height(),
1276 d->biggestItemSize.height()));
1277
1278 d->modelIndexList << index;
1279
1280 lastCategory = d->proxyModel->data(index, KCategorizedSortFilterProxyModel::CategoryRole).toString();
1281
1282 elementInfo.category = lastCategory;
1283
1284 if (prevCategory != lastCategory)
1285 {
1286 offset = 0;
1287 d->categoriesIndexes.insert(prevCategory, modelIndexList);
1288 d->categories << prevCategory;
1289 modelIndexList.clear();
1290 }
1291 else
1292 {
1293 offset++;
1294 }
1295
1296 elementInfo.relativeOffsetToCategory = offset;
1297
1298 modelIndexList << index;
1299 prevCategory = lastCategory;
1300
1301 d->elementsInfo.insert(index.row(), elementInfo);
1302 }
1303
1304 d->categoriesIndexes.insert(prevCategory, modelIndexList);
1305 d->categories << prevCategory;
1306
1307 d->updateScrollbars();
1308 }
1309
1310 void KCategorizedView::rowsRemoved(const QModelIndex &parent,
1311 int start,
1312 int end)
1313 {
1314 if ((viewMode() == KCategorizedView::IconMode) && d->proxyModel &&
1315 d->categoryDrawer && d->proxyModel->isCategorizedModel())
1316 {
1317 // Force the view to update all elements
1318 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
1319 }
1320 }
1321
1322 void KCategorizedView::updateGeometries()
1323 {
1324 if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel ||
1325 !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
1326 {
1327 QListView::updateGeometries();
1328 return;
1329 }
1330
1331 // Avoid QListView::updateGeometries(), since it will try to set another
1332 // range to our scroll bars, what we don't want (ereslibre)
1333 QAbstractItemView::updateGeometries();
1334 }
1335
1336 void KCategorizedView::slotLayoutChanged()
1337 {
1338 if ((viewMode() == KCategorizedView::IconMode) && d->proxyModel &&
1339 d->categoryDrawer && d->proxyModel->isCategorizedModel() &&
1340 ((d->modelSortRole != d->proxyModel->sortRole()) ||
1341 (d->modelSortColumn != d->proxyModel->sortColumn()) ||
1342 (d->modelSortOrder != d->proxyModel->sortOrder()) ||
1343 (d->modelCategorized != d->proxyModel->isCategorizedModel())))
1344 {
1345 // Force the view to update all elements
1346 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
1347
1348 d->modelSortRole = d->proxyModel->sortRole();
1349 d->modelSortColumn = d->proxyModel->sortColumn();
1350 d->modelCategorized = d->proxyModel->isCategorizedModel();
1351 d->modelSortOrder = d->proxyModel->sortOrder();
1352 }
1353 else if ((viewMode() == KCategorizedView::IconMode) && d->proxyModel &&
1354 d->categoryDrawer && d->proxyModel->isCategorizedModel())
1355 {
1356 d->updateScrollbars();
1357 }
1358 }
1359
1360 #include "kcategorizedview.moc"