2 * This file is part of the KDE project
3 * Copyright (C) 2007 Rafael Fernández López <ereslibre@gmail.com>
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.
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.
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.
21 #include "kcategorizedview.h"
22 #include "kcategorizedview_p.h"
24 #include <math.h> // trunc on C99 compliant systems
25 #include <kdefakes.h> // trunc for not C99 compliant systems
27 #include <QApplication>
30 #include <QPaintEvent>
35 #include "kitemcategorizer.h"
36 #include "ksortfilterproxymodel.h"
47 inline LessThan(const KSortFilterProxyModel
*proxyModel
,
49 : proxyModel(proxyModel
)
54 inline bool operator()(const QModelIndex
&left
,
55 const QModelIndex
&right
) const
57 if (purpose
== GeneralPurpose
)
59 return proxyModel
->sortOrder() == Qt::AscendingOrder
?
60 proxyModel
->lessThanGeneralPurpose(left
, right
) :
61 !proxyModel
->lessThanGeneralPurpose(left
, right
);
64 return proxyModel
->sortOrder() == Qt::AscendingOrder
?
65 proxyModel
->lessThanCategoryPurpose(left
, right
) :
66 !proxyModel
->lessThanCategoryPurpose(left
, right
);
70 const KSortFilterProxyModel
*proxyModel
;
71 const Purpose purpose
;
75 //==============================================================================
78 KCategorizedView::Private::Private(KCategorizedView
*listView
)
81 , biggestItemSize(QSize(0, 0))
82 , mouseButtonPressed(false)
84 , dragLeftViewport(false)
86 , lastIndex(QModelIndex())
90 KCategorizedView::Private::~Private()
94 const QModelIndexList
&KCategorizedView::Private::intersectionSet(const QRect
&rect
)
97 QRect indexVisualRect
;
99 intersectedIndexes
.clear();
101 // Lets find out where we should start
102 int top
= proxyModel
->rowCount() - 1;
104 int middle
= (top
+ bottom
) / 2;
105 while (bottom
<= top
)
107 middle
= (top
+ bottom
) / 2;
109 index
= elementDictionary
[proxyModel
->index(middle
, 0)];
110 indexVisualRect
= visualRect(index
);
112 if (qMax(indexVisualRect
.topLeft().y(),
113 indexVisualRect
.bottomRight().y()) < qMin(rect
.topLeft().y(),
114 rect
.bottomRight().y()))
124 for (int i
= middle
; i
< proxyModel
->rowCount(); i
++)
126 index
= elementDictionary
[proxyModel
->index(i
, 0)];
127 indexVisualRect
= visualRect(index
);
129 if (rect
.intersects(indexVisualRect
))
130 intersectedIndexes
.append(index
);
132 // If we passed next item, stop searching for hits
133 if (qMax(rect
.bottomRight().y(), rect
.topLeft().y()) <
134 qMin(indexVisualRect
.topLeft().y(),
135 indexVisualRect
.bottomRight().y()))
139 return intersectedIndexes
;
142 QRect
KCategorizedView::Private::visualRectInViewport(const QModelIndex
&index
) const
144 if (!index
.isValid())
147 QString curCategory
= elementsInfo
[index
].category
;
149 QRect
retRect(listView
->spacing(), listView
->spacing() * 2 +
150 itemCategorizer
->categoryHeight(listView
->viewOptions()), 0, 0);
152 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
157 if (listView
->gridSize().isEmpty())
159 itemHeight
= biggestItemSize
.height();
160 itemWidth
= biggestItemSize
.width();
164 itemHeight
= listView
->gridSize().height();
165 itemWidth
= listView
->gridSize().width();
168 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
169 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
173 int column
= elementsInfo
[index
].relativeOffsetToCategory
% elementsPerRow
;
174 int row
= elementsInfo
[index
].relativeOffsetToCategory
/ elementsPerRow
;
176 retRect
.setLeft(retRect
.left() + column
* listView
->spacing() +
179 foreach (const QString
&category
, categories
)
181 if (category
== curCategory
)
184 float rows
= (float) ((float) categoriesIndexes
[category
].count() /
185 (float) elementsPerRow
);
186 int rowsInt
= categoriesIndexes
[category
].count() / elementsPerRow
;
188 if (rows
- trunc(rows
)) rowsInt
++;
190 retRect
.setTop(retRect
.top() +
191 (rowsInt
* listView
->spacing()) +
192 (rowsInt
* itemHeight
) +
193 itemCategorizer
->categoryHeight(listView
->viewOptions()) +
194 listView
->spacing() * 2);
197 retRect
.setTop(retRect
.top() + row
* listView
->spacing() +
200 retRect
.setWidth(itemWidth
);
202 if (listView
->gridSize().isEmpty())
204 retRect
.setHeight(listView
->sizeHintForIndex(proxyModel
->mapFromSource(index
)).height());
208 retRect
.setHeight(qMin(listView
->sizeHintForIndex(proxyModel
->mapFromSource(index
)).height(),
209 listView
->gridSize().height()));
215 QRect
KCategorizedView::Private::visualCategoryRectInViewport(const QString
&category
)
218 QRect
retRect(listView
->spacing(),
220 listView
->viewport()->width() - listView
->spacing() * 2,
223 if (!proxyModel
->rowCount() || !categories
.contains(category
))
226 QModelIndex index
= proxyModel
->index(0, 0, QModelIndex());
228 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
233 if (listView
->gridSize().isEmpty())
235 itemHeight
= biggestItemSize
.height();
236 itemWidth
= biggestItemSize
.width();
240 itemHeight
= listView
->gridSize().height();
241 itemWidth
= listView
->gridSize().width();
244 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
245 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
250 foreach (const QString
&itCategory
, categories
)
252 if (itCategory
== category
)
255 float rows
= (float) ((float) categoriesIndexes
[itCategory
].count() /
256 (float) elementsPerRow
);
257 int rowsInt
= categoriesIndexes
[itCategory
].count() / elementsPerRow
;
259 if (rows
- trunc(rows
)) rowsInt
++;
261 retRect
.setTop(retRect
.top() +
262 (rowsInt
* listView
->spacing()) +
263 (rowsInt
* itemHeight
) +
264 itemCategorizer
->categoryHeight(listView
->viewOptions()) +
265 listView
->spacing() * 2);
268 retRect
.setHeight(itemCategorizer
->categoryHeight(listView
->viewOptions()));
273 // We're sure elementsPosition doesn't contain index
274 const QRect
&KCategorizedView::Private::cacheIndex(const QModelIndex
&index
)
276 QRect rect
= visualRectInViewport(index
);
277 elementsPosition
[index
] = rect
;
279 return elementsPosition
[index
];
282 // We're sure categoriesPosition doesn't contain category
283 const QRect
&KCategorizedView::Private::cacheCategory(const QString
&category
)
285 QRect rect
= visualCategoryRectInViewport(category
);
286 categoriesPosition
[category
] = rect
;
288 return categoriesPosition
[category
];
291 const QRect
&KCategorizedView::Private::cachedRectIndex(const QModelIndex
&index
)
293 if (elementsPosition
.contains(index
)) // If we have it cached
295 return elementsPosition
[index
];
297 else // Otherwise, cache it
299 return cacheIndex(index
);
303 const QRect
&KCategorizedView::Private::cachedRectCategory(const QString
&category
)
305 if (categoriesPosition
.contains(category
)) // If we have it cached
307 return categoriesPosition
[category
];
309 else // Otherwise, cache it and
311 return cacheCategory(category
);
315 QRect
KCategorizedView::Private::visualRect(const QModelIndex
&index
)
317 QModelIndex mappedIndex
= proxyModel
->mapToSource(index
);
319 QRect retRect
= cachedRectIndex(mappedIndex
);
320 int dx
= -listView
->horizontalOffset();
321 int dy
= -listView
->verticalOffset();
322 retRect
.adjust(dx
, dy
, dx
, dy
);
327 QRect
KCategorizedView::Private::categoryVisualRect(const QString
&category
)
329 QRect retRect
= cachedRectCategory(category
);
330 int dx
= -listView
->horizontalOffset();
331 int dy
= -listView
->verticalOffset();
332 retRect
.adjust(dx
, dy
, dx
, dy
);
337 void KCategorizedView::Private::drawNewCategory(const QModelIndex
&index
,
339 const QStyleOption
&option
,
342 QStyleOption optionCopy
= option
;
343 const QString category
= itemCategorizer
->categoryForItem(index
, sortRole
);
345 if ((category
== hoveredCategory
) && !mouseButtonPressed
)
347 optionCopy
.state
|= QStyle::State_MouseOver
;
350 itemCategorizer
->drawCategory(index
,
357 void KCategorizedView::Private::updateScrollbars()
359 int lastItemBottom
= cachedRectIndex(lastIndex
).top() +
360 listView
->spacing() + (listView
->gridSize().isEmpty() ? 0 : listView
->gridSize().height()) - listView
->viewport()->height();
362 listView
->verticalScrollBar()->setSingleStep(listView
->viewport()->height() / 10);
363 listView
->verticalScrollBar()->setPageStep(listView
->viewport()->height());
364 listView
->verticalScrollBar()->setRange(0, lastItemBottom
);
367 void KCategorizedView::Private::drawDraggedItems(QPainter
*painter
)
369 QStyleOptionViewItemV3 option
= listView
->viewOptions();
370 option
.state
&= ~QStyle::State_MouseOver
;
371 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
373 const int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
374 const int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
376 option
.rect
= visualRect(index
);
377 option
.rect
.adjust(dx
, dy
, dx
, dy
);
379 if (option
.rect
.intersects(listView
->viewport()->rect()))
381 listView
->itemDelegate(index
)->paint(painter
, option
, index
);
386 void KCategorizedView::Private::drawDraggedItems()
390 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
392 int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
393 int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
395 currentRect
= visualRect(index
);
396 currentRect
.adjust(dx
, dy
, dx
, dy
);
398 if (currentRect
.intersects(listView
->viewport()->rect()))
400 rectToUpdate
= rectToUpdate
.united(currentRect
);
404 listView
->viewport()->update(lastDraggedItemsRect
.united(rectToUpdate
));
406 lastDraggedItemsRect
= rectToUpdate
;
410 //==============================================================================
413 KCategorizedView::KCategorizedView(QWidget
*parent
)
415 , d(new Private(this))
419 KCategorizedView::~KCategorizedView()
424 void KCategorizedView::setModel(QAbstractItemModel
*model
)
426 d
->lastSelection
= QItemSelection();
427 d
->currentViewIndex
= QModelIndex();
428 d
->forcedSelectionPosition
= 0;
429 d
->elementsInfo
.clear();
430 d
->elementsPosition
.clear();
431 d
->elementDictionary
.clear();
432 d
->invertedElementDictionary
.clear();
433 d
->categoriesIndexes
.clear();
434 d
->categoriesPosition
.clear();
435 d
->categories
.clear();
436 d
->intersectedIndexes
.clear();
437 d
->sourceModelIndexList
.clear();
438 d
->hovered
= QModelIndex();
439 d
->mouseButtonPressed
= false;
443 QObject::disconnect(d
->proxyModel
,
444 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
445 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
447 QObject::disconnect(d
->proxyModel
,
448 SIGNAL(sortingRoleChanged()),
449 this, SLOT(slotSortingRoleChanged()));
452 QListView::setModel(model
);
454 d
->proxyModel
= dynamic_cast<KSortFilterProxyModel
*>(model
);
458 QObject::connect(d
->proxyModel
,
459 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
460 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
462 QObject::connect(d
->proxyModel
,
463 SIGNAL(sortingRoleChanged()),
464 this, SLOT(slotSortingRoleChanged()));
468 QRect
KCategorizedView::visualRect(const QModelIndex
&index
) const
470 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
473 return QListView::visualRect(index
);
476 if (!qobject_cast
<const QSortFilterProxyModel
*>(index
.model()))
478 return d
->visualRect(d
->proxyModel
->mapFromSource(index
));
481 return d
->visualRect(index
);
484 KItemCategorizer
*KCategorizedView::itemCategorizer() const
486 return d
->itemCategorizer
;
489 void KCategorizedView::setItemCategorizer(KItemCategorizer
*itemCategorizer
)
491 d
->lastSelection
= QItemSelection();
492 d
->currentViewIndex
= QModelIndex();
493 d
->forcedSelectionPosition
= 0;
494 d
->elementsInfo
.clear();
495 d
->elementsPosition
.clear();
496 d
->elementDictionary
.clear();
497 d
->invertedElementDictionary
.clear();
498 d
->categoriesIndexes
.clear();
499 d
->categoriesPosition
.clear();
500 d
->categories
.clear();
501 d
->intersectedIndexes
.clear();
502 d
->sourceModelIndexList
.clear();
503 d
->hovered
= QModelIndex();
504 d
->mouseButtonPressed
= false;
506 if (!itemCategorizer
&& d
->proxyModel
)
508 QObject::disconnect(d
->proxyModel
,
509 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
510 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
512 QObject::disconnect(d
->proxyModel
,
513 SIGNAL(sortingRoleChanged()),
514 this, SLOT(slotSortingRoleChanged()));
516 else if (itemCategorizer
&& d
->proxyModel
)
518 QObject::connect(d
->proxyModel
,
519 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
520 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
522 QObject::connect(d
->proxyModel
,
523 SIGNAL(sortingRoleChanged()),
524 this, SLOT(slotSortingRoleChanged()));
527 d
->itemCategorizer
= itemCategorizer
;
531 rowsInserted(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
539 QModelIndex
KCategorizedView::indexAt(const QPoint
&point
) const
541 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
544 return QListView::indexAt(point
);
549 QModelIndexList item
= d
->intersectionSet(QRect(point
, point
));
551 if (item
.count() == 1)
561 void KCategorizedView::reset()
565 d
->lastSelection
= QItemSelection();
566 d
->currentViewIndex
= QModelIndex();
567 d
->forcedSelectionPosition
= 0;
568 d
->elementsInfo
.clear();
569 d
->elementsPosition
.clear();
570 d
->elementDictionary
.clear();
571 d
->invertedElementDictionary
.clear();
572 d
->categoriesIndexes
.clear();
573 d
->categoriesPosition
.clear();
574 d
->categories
.clear();
575 d
->intersectedIndexes
.clear();
576 d
->sourceModelIndexList
.clear();
577 d
->hovered
= QModelIndex();
578 d
->biggestItemSize
= QSize(0, 0);
579 d
->mouseButtonPressed
= false;
582 void KCategorizedView::paintEvent(QPaintEvent
*event
)
584 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
587 QListView::paintEvent(event
);
591 QStyleOptionViewItemV3 option
= viewOptions();
592 option
.widget
= this;
593 QPainter
painter(viewport());
594 QRect area
= event
->rect();
595 const bool focus
= (hasFocus() || viewport()->hasFocus()) &&
596 currentIndex().isValid();
597 const QStyle::State state
= option
.state
;
598 const bool enabled
= (state
& QStyle::State_Enabled
) != 0;
602 QModelIndexList dirtyIndexes
= d
->intersectionSet(area
);
603 foreach (const QModelIndex
&index
, dirtyIndexes
)
605 option
.state
= state
;
606 option
.rect
= d
->visualRect(index
);
608 if (selectionModel() && selectionModel()->isSelected(index
))
610 option
.state
|= QStyle::State_Selected
;
615 QPalette::ColorGroup cg
;
616 if ((d
->proxyModel
->flags(index
) & Qt::ItemIsEnabled
) == 0)
618 option
.state
&= ~QStyle::State_Enabled
;
619 cg
= QPalette::Disabled
;
623 cg
= QPalette::Normal
;
625 option
.palette
.setCurrentColorGroup(cg
);
628 if (focus
&& currentIndex() == index
)
630 option
.state
|= QStyle::State_HasFocus
;
631 if (this->state() == EditingState
)
632 option
.state
|= QStyle::State_Editing
;
635 if ((index
== d
->hovered
) && !d
->mouseButtonPressed
)
636 option
.state
|= QStyle::State_MouseOver
;
638 option
.state
&= ~QStyle::State_MouseOver
;
640 itemDelegate(index
)->paint(&painter
, option
, index
);
644 QStyleOptionViewItem otherOption
;
645 foreach (const QString
&category
, d
->categories
)
647 otherOption
= option
;
648 otherOption
.rect
= d
->categoryVisualRect(category
);
649 otherOption
.state
&= ~QStyle::State_MouseOver
;
651 if (otherOption
.rect
.intersects(area
))
653 d
->drawNewCategory(d
->categoriesIndexes
[category
][0],
654 d
->proxyModel
->sortRole(), otherOption
, &painter
);
658 if (d
->mouseButtonPressed
&& !d
->isDragging
)
660 QPoint start
, end
, initialPressPosition
;
662 initialPressPosition
= d
->initialPressPosition
;
664 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
665 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
667 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
668 d
->initialPressPosition
.y() > d
->mousePosition
.y())
670 start
= d
->mousePosition
;
671 end
= initialPressPosition
;
675 start
= initialPressPosition
;
676 end
= d
->mousePosition
;
679 QStyleOptionRubberBand yetAnotherOption
;
680 yetAnotherOption
.initFrom(this);
681 yetAnotherOption
.shape
= QRubberBand::Rectangle
;
682 yetAnotherOption
.opaque
= false;
683 yetAnotherOption
.rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
685 style()->drawControl(QStyle::CE_RubberBand
, &yetAnotherOption
, &painter
);
689 if (d
->isDragging
&& !d
->dragLeftViewport
)
691 painter
.setOpacity(0.5);
692 d
->drawDraggedItems(&painter
);
698 void KCategorizedView::resizeEvent(QResizeEvent
*event
)
700 QListView::resizeEvent(event
);
702 // Clear the items positions cache
703 d
->elementsPosition
.clear();
704 d
->categoriesPosition
.clear();
705 d
->forcedSelectionPosition
= 0;
707 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
713 d
->updateScrollbars();
716 void KCategorizedView::setSelection(const QRect
&rect
,
717 QItemSelectionModel::SelectionFlags flags
)
719 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
722 QListView::setSelection(rect
, flags
);
729 selectionModel()->clear();
731 if (flags
& QItemSelectionModel::Clear
)
733 d
->lastSelection
= QItemSelection();
736 QModelIndexList dirtyIndexes
= d
->intersectionSet(rect
);
738 QItemSelection selection
;
740 if (!dirtyIndexes
.count())
742 if (d
->lastSelection
.count())
744 selectionModel()->select(d
->lastSelection
, flags
);
750 if (!d
->mouseButtonPressed
)
752 selection
= QItemSelection(dirtyIndexes
[0], dirtyIndexes
[0]);
753 d
->currentViewIndex
= dirtyIndexes
[0];
757 QModelIndex first
= dirtyIndexes
[0];
759 foreach (const QModelIndex
&index
, dirtyIndexes
)
761 if (last
.isValid() && last
.row() + 1 != index
.row())
763 QItemSelectionRange
range(first
, last
);
774 selection
<< QItemSelectionRange(first
, last
);
777 if (d
->lastSelection
.count() && !d
->mouseButtonPressed
)
779 selection
.merge(d
->lastSelection
, flags
);
781 else if (d
->lastSelection
.count())
783 selection
.merge(d
->lastSelection
, QItemSelectionModel::Select
);
786 selectionModel()->select(selection
, flags
);
789 void KCategorizedView::mouseMoveEvent(QMouseEvent
*event
)
791 QListView::mouseMoveEvent(event
);
793 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
799 const QString previousHoveredCategory
= d
->hoveredCategory
;
801 d
->mousePosition
= event
->pos();
802 d
->hoveredCategory
= QString();
805 foreach (const QString
&category
, d
->categories
)
807 if (d
->categoryVisualRect(category
).intersects(QRect(event
->pos(), event
->pos())))
809 d
->hoveredCategory
= category
;
810 viewport()->update(d
->categoryVisualRect(category
));
812 else if ((category
== previousHoveredCategory
) &&
813 (!d
->categoryVisualRect(previousHoveredCategory
).intersects(QRect(event
->pos(), event
->pos()))))
815 viewport()->update(d
->categoryVisualRect(category
));
820 if (d
->mouseButtonPressed
&& !d
->isDragging
)
822 QPoint start
, end
, initialPressPosition
;
824 initialPressPosition
= d
->initialPressPosition
;
826 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
827 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
829 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
830 d
->initialPressPosition
.y() > d
->mousePosition
.y())
832 start
= d
->mousePosition
;
833 end
= initialPressPosition
;
837 start
= initialPressPosition
;
838 end
= d
->mousePosition
;
841 rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
843 //viewport()->update(rect.united(d->lastSelectionRect));
845 d
->lastSelectionRect
= rect
;
849 void KCategorizedView::mousePressEvent(QMouseEvent
*event
)
851 d
->dragLeftViewport
= false;
853 if (event
->button() == Qt::LeftButton
)
855 d
->mouseButtonPressed
= true;
857 d
->initialPressPosition
= event
->pos();
858 d
->initialPressPosition
.setY(d
->initialPressPosition
.y() +
860 d
->initialPressPosition
.setX(d
->initialPressPosition
.x() +
864 QListView::mousePressEvent(event
);
867 void KCategorizedView::mouseReleaseEvent(QMouseEvent
*event
)
869 d
->mouseButtonPressed
= false;
871 QListView::mouseReleaseEvent(event
);
873 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
879 QPoint initialPressPosition
= viewport()->mapFromGlobal(QCursor::pos());
880 initialPressPosition
.setY(initialPressPosition
.y() + verticalOffset());
881 initialPressPosition
.setX(initialPressPosition
.x() + horizontalOffset());
883 QItemSelection selection
;
885 if (initialPressPosition
== d
->initialPressPosition
)
887 foreach(const QString
&category
, d
->categories
)
889 if (d
->categoryVisualRect(category
).contains(event
->pos()))
891 foreach (const QModelIndex
&index
, d
->categoriesIndexes
[category
])
893 selection
<< QItemSelectionRange(d
->proxyModel
->mapFromSource(index
));
896 selectionModel()->select(selection
, QItemSelectionModel::Select
);
903 d
->lastSelection
= selectionModel()->selection();
905 if (d
->hovered
.isValid())
906 viewport()->update(d
->visualRect(d
->hovered
));
907 else if (!d
->hoveredCategory
.isEmpty())
908 viewport()->update(d
->categoryVisualRect(d
->hoveredCategory
));
911 void KCategorizedView::leaveEvent(QEvent
*event
)
913 d
->hovered
= QModelIndex();
914 d
->hoveredCategory
= QString();
916 QListView::leaveEvent(event
);
919 void KCategorizedView::startDrag(Qt::DropActions supportedActions
)
921 QListView::startDrag(supportedActions
);
923 d
->isDragging
= false;
924 d
->mouseButtonPressed
= false;
926 viewport()->update(d
->lastDraggedItemsRect
);
929 void KCategorizedView::dragMoveEvent(QDragMoveEvent
*event
)
931 d
->mousePosition
= event
->pos();
933 if (d
->mouseButtonPressed
)
935 d
->isDragging
= true;
939 d
->isDragging
= false;
942 d
->dragLeftViewport
= false;
944 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
947 QListView::dragMoveEvent(event
);
951 d
->drawDraggedItems();
954 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent
*event
)
956 d
->dragLeftViewport
= true;
958 QListView::dragLeaveEvent(event
);
961 QModelIndex
KCategorizedView::moveCursor(CursorAction cursorAction
,
962 Qt::KeyboardModifiers modifiers
)
964 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
967 return QListView::moveCursor(cursorAction
, modifiers
);
970 const QModelIndex current
= selectionModel()->currentIndex();
972 int viewportWidth
= viewport()->width() - spacing();
975 if (gridSize().isEmpty())
977 itemWidth
= d
->biggestItemSize
.width();
981 itemWidth
= gridSize().width();
984 int itemWidthPlusSeparation
= spacing() + itemWidth
;
985 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
987 QString lastCategory
= d
->categories
[0];
988 QString theCategory
= d
->categories
[0];
989 QString afterCategory
= d
->categories
[0];
990 bool hasToBreak
= false;
991 foreach (const QString
&category
, d
->categories
)
995 afterCategory
= category
;
1000 if (category
== d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].category
)
1002 theCategory
= category
;
1009 lastCategory
= category
;
1013 switch (cursorAction
)
1015 case QAbstractItemView::MoveUp
: {
1016 if (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
>= elementsPerRow
)
1018 int indexToMove
= d
->invertedElementDictionary
[current
].row();
1019 indexToMove
-= qMin(((d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
) + d
->forcedSelectionPosition
), elementsPerRow
- d
->forcedSelectionPosition
+ (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
% elementsPerRow
));
1021 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1025 int lastCategoryLastRow
= (d
->categoriesIndexes
[lastCategory
].count() - 1) % elementsPerRow
;
1026 int indexToMove
= d
->invertedElementDictionary
[current
].row() - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
;
1028 if (d
->forcedSelectionPosition
>= lastCategoryLastRow
)
1034 indexToMove
-= qMin((lastCategoryLastRow
- d
->forcedSelectionPosition
+ 1), d
->forcedSelectionPosition
+ elementsPerRow
+ 1);
1037 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1041 case QAbstractItemView::MoveDown
: {
1042 if (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
< (d
->categoriesIndexes
[theCategory
].count() - 1 - ((d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
)))
1044 int indexToMove
= d
->invertedElementDictionary
[current
].row();
1045 indexToMove
+= qMin(elementsPerRow
, d
->categoriesIndexes
[theCategory
].count() - 1 - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
);
1047 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1051 int afterCategoryLastRow
= qMin(elementsPerRow
, d
->categoriesIndexes
[afterCategory
].count());
1052 int indexToMove
= d
->invertedElementDictionary
[current
].row() + (d
->categoriesIndexes
[theCategory
].count() - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
);
1054 if (d
->forcedSelectionPosition
>= afterCategoryLastRow
)
1056 indexToMove
+= afterCategoryLastRow
- 1;
1060 indexToMove
+= qMin(d
->forcedSelectionPosition
, elementsPerRow
);
1063 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1067 case QAbstractItemView::MoveLeft
:
1068 d
->forcedSelectionPosition
= d
->elementsInfo
[d
->proxyModel
->mapToSource(d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() - 1, 0)])].relativeOffsetToCategory
% elementsPerRow
;
1070 if (d
->forcedSelectionPosition
< 0)
1071 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1073 return d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() - 1, 0)];
1075 case QAbstractItemView::MoveRight
:
1076 d
->forcedSelectionPosition
= d
->elementsInfo
[d
->proxyModel
->mapToSource(d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() + 1, 0)])].relativeOffsetToCategory
% elementsPerRow
;
1078 if (d
->forcedSelectionPosition
< 0)
1079 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1081 return d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() + 1, 0)];
1087 return QListView::moveCursor(cursorAction
, modifiers
);
1090 void KCategorizedView::rowsInserted(const QModelIndex
&parent
,
1094 QListView::rowsInserted(parent
, start
, end
);
1096 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1097 !d
->itemCategorizer
)
1099 d
->lastSelection
= QItemSelection();
1100 d
->currentViewIndex
= QModelIndex();
1101 d
->forcedSelectionPosition
= 0;
1102 d
->elementsInfo
.clear();
1103 d
->elementsPosition
.clear();
1104 d
->elementDictionary
.clear();
1105 d
->invertedElementDictionary
.clear();
1106 d
->categoriesIndexes
.clear();
1107 d
->categoriesPosition
.clear();
1108 d
->categories
.clear();
1109 d
->intersectedIndexes
.clear();
1110 d
->sourceModelIndexList
.clear();
1111 d
->hovered
= QModelIndex();
1112 d
->biggestItemSize
= QSize(0, 0);
1113 d
->mouseButtonPressed
= false;
1118 rowsInsertedArtifficial(parent
, start
, end
);
1121 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex
&parent
,
1127 d
->lastSelection
= QItemSelection();
1128 d
->currentViewIndex
= QModelIndex();
1129 d
->forcedSelectionPosition
= 0;
1130 d
->elementsInfo
.clear();
1131 d
->elementsPosition
.clear();
1132 d
->elementDictionary
.clear();
1133 d
->invertedElementDictionary
.clear();
1134 d
->categoriesIndexes
.clear();
1135 d
->categoriesPosition
.clear();
1136 d
->categories
.clear();
1137 d
->intersectedIndexes
.clear();
1138 d
->sourceModelIndexList
.clear();
1139 d
->hovered
= QModelIndex();
1140 d
->biggestItemSize
= QSize(0, 0);
1141 d
->mouseButtonPressed
= false;
1143 if (start
> end
|| end
< 0 || start
< 0 || !d
->proxyModel
->rowCount())
1148 // Add all elements mapped to the source model
1149 for (int k
= 0; k
< d
->proxyModel
->rowCount(); k
++)
1151 d
->biggestItemSize
= QSize(qMax(sizeHintForIndex(d
->proxyModel
->index(k
, 0)).width(),
1152 d
->biggestItemSize
.width()),
1153 qMax(sizeHintForIndex(d
->proxyModel
->index(k
, 0)).height(),
1154 d
->biggestItemSize
.height()));
1156 d
->sourceModelIndexList
<<
1157 d
->proxyModel
->mapToSource(d
->proxyModel
->index(k
, 0));
1160 // Sort them with the general purpose lessThan method
1161 LessThan
generalLessThan(d
->proxyModel
,
1162 LessThan::GeneralPurpose
);
1164 qStableSort(d
->sourceModelIndexList
.begin(), d
->sourceModelIndexList
.end(),
1167 // Explore categories
1168 QString prevCategory
=
1169 d
->itemCategorizer
->categoryForItem(d
->sourceModelIndexList
[0],
1170 d
->proxyModel
->sortRole());
1171 QString lastCategory
= prevCategory
;
1172 QModelIndexList modelIndexList
;
1173 struct Private::ElementInfo elementInfo
;
1174 foreach (const QModelIndex
&index
, d
->sourceModelIndexList
)
1176 lastCategory
= d
->itemCategorizer
->categoryForItem(index
,
1177 d
->proxyModel
->sortRole());
1179 elementInfo
.category
= lastCategory
;
1181 if (prevCategory
!= lastCategory
)
1183 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1184 d
->categories
<< prevCategory
;
1185 modelIndexList
.clear();
1188 modelIndexList
<< index
;
1189 prevCategory
= lastCategory
;
1191 d
->elementsInfo
.insert(index
, elementInfo
);
1194 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1195 d
->categories
<< prevCategory
;
1197 // Sort items locally in their respective categories with the category
1199 LessThan
categoryLessThan(d
->proxyModel
,
1200 LessThan::CategoryPurpose
);
1202 foreach (const QString
&key
, d
->categories
)
1204 QModelIndexList
&indexList
= d
->categoriesIndexes
[key
];
1206 qStableSort(indexList
.begin(), indexList
.end(), categoryLessThan
);
1209 d
->lastIndex
= d
->categoriesIndexes
[d
->categories
[d
->categories
.count() - 1]][d
->categoriesIndexes
[d
->categories
[d
->categories
.count() - 1]].count() - 1];
1211 // Finally, fill data information of items situation. This will help when
1212 // trying to compute an item place in the viewport
1213 int i
= 0; // position relative to the category beginning
1214 int j
= 0; // number of elements before current
1215 foreach (const QString
&key
, d
->categories
)
1217 foreach (const QModelIndex
&index
, d
->categoriesIndexes
[key
])
1219 struct Private::ElementInfo
&elementInfo
= d
->elementsInfo
[index
];
1221 elementInfo
.relativeOffsetToCategory
= i
;
1223 d
->elementDictionary
.insert(d
->proxyModel
->index(j
, 0),
1224 d
->proxyModel
->mapFromSource(index
));
1226 d
->invertedElementDictionary
.insert(d
->proxyModel
->mapFromSource(index
),
1227 d
->proxyModel
->index(j
, 0));
1236 d
->updateScrollbars();
1239 void KCategorizedView::rowsRemoved(const QModelIndex
&parent
,
1243 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1246 // Force the view to update all elements
1247 rowsInsertedArtifficial(parent
, start
, end
);
1251 void KCategorizedView::updateGeometries()
1253 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1254 !d
->itemCategorizer
)
1256 QListView::updateGeometries();
1260 // Avoid QListView::updateGeometries(), since it will try to set another
1261 // range to our scroll bars, what we don't want (ereslibre)
1262 QAbstractItemView::updateGeometries();
1265 void KCategorizedView::slotSortingRoleChanged()
1267 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1270 // Force the view to update all elements
1271 rowsInsertedArtifficial(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
1275 #include "kcategorizedview.moc"