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