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