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