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 , mouseButtonPressed(false)
83 , dragLeftViewport(false)
85 , lastIndex(QModelIndex())
89 KCategorizedView::Private::~Private()
93 const QModelIndexList
&KCategorizedView::Private::intersectionSet(const QRect
&rect
)
96 QRect indexVisualRect
;
98 intersectedIndexes
.clear();
100 // Lets find out where we should start
101 int top
= proxyModel
->rowCount() - 1;
103 int middle
= (top
+ bottom
) / 2;
104 while (bottom
<= top
)
106 middle
= (top
+ bottom
) / 2;
108 index
= elementDictionary
[proxyModel
->index(middle
, 0)];
109 indexVisualRect
= visualRect(index
);
111 if (qMax(indexVisualRect
.topLeft().y(),
112 indexVisualRect
.bottomRight().y()) < qMin(rect
.topLeft().y(),
113 rect
.bottomRight().y()))
123 for (int i
= middle
; i
< proxyModel
->rowCount(); i
++)
125 index
= elementDictionary
[proxyModel
->index(i
, 0)];
126 indexVisualRect
= visualRect(index
);
128 if (rect
.intersects(indexVisualRect
))
129 intersectedIndexes
.append(index
);
131 // If we passed next item, stop searching for hits
132 if (qMax(rect
.bottomRight().y(), rect
.topLeft().y()) <
133 qMin(indexVisualRect
.topLeft().y(),
134 indexVisualRect
.bottomRight().y()))
138 return intersectedIndexes
;
141 QRect
KCategorizedView::Private::visualRectInViewport(const QModelIndex
&index
) const
143 if (!index
.isValid())
146 QString curCategory
= elementsInfo
[index
].category
;
148 QRect
retRect(listView
->spacing(), listView
->spacing() * 2 +
149 itemCategorizer
->categoryHeight(listView
->viewOptions()), 0, 0);
151 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
153 // We really need all items to be of same size. Otherwise we cannot do this
156 // listView->sizeHintForIndex(proxyModel->mapFromSource(index));
157 // int itemHeight = itemSize.height();
158 // int itemWidth = itemSize.width();*/
159 int itemHeight
= 107;
161 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
162 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
166 int column
= elementsInfo
[index
].relativeOffsetToCategory
% elementsPerRow
;
167 int row
= elementsInfo
[index
].relativeOffsetToCategory
/ elementsPerRow
;
169 retRect
.setLeft(retRect
.left() + column
* listView
->spacing() +
172 foreach (const QString
&category
, categories
)
174 if (category
== curCategory
)
177 float rows
= (float) ((float) categoriesIndexes
[category
].count() /
178 (float) elementsPerRow
);
179 int rowsInt
= categoriesIndexes
[category
].count() / elementsPerRow
;
181 if (rows
- trunc(rows
)) rowsInt
++;
183 retRect
.setTop(retRect
.top() +
184 (rowsInt
* listView
->spacing()) +
185 (rowsInt
* itemHeight
) +
186 itemCategorizer
->categoryHeight(listView
->viewOptions()) +
187 listView
->spacing() * 2);
190 retRect
.setTop(retRect
.top() + row
* listView
->spacing() +
193 retRect
.setWidth(itemWidth
);
194 retRect
.setHeight(itemHeight
);
199 QRect
KCategorizedView::Private::visualCategoryRectInViewport(const QString
&category
)
202 QRect
retRect(listView
->spacing(),
204 listView
->viewport()->width() - listView
->spacing() * 2,
207 if (!proxyModel
->rowCount() || !categories
.contains(category
))
210 QModelIndex index
= proxyModel
->index(0, 0, QModelIndex());
212 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
214 // We really need all items to be of same size. Otherwise we cannot do this
216 // QSize itemSize = listView->sizeHintForIndex(index);
217 // int itemHeight = itemSize.height();
218 // int itemWidth = itemSize.width();
219 int itemHeight
= 107;
221 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
222 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
227 foreach (const QString
&itCategory
, categories
)
229 if (itCategory
== category
)
232 float rows
= (float) ((float) categoriesIndexes
[itCategory
].count() /
233 (float) elementsPerRow
);
234 int rowsInt
= categoriesIndexes
[itCategory
].count() / elementsPerRow
;
236 if (rows
- trunc(rows
)) rowsInt
++;
238 retRect
.setTop(retRect
.top() +
239 (rowsInt
* listView
->spacing()) +
240 (rowsInt
* itemHeight
) +
241 itemCategorizer
->categoryHeight(listView
->viewOptions()) +
242 listView
->spacing() * 2);
245 retRect
.setHeight(itemCategorizer
->categoryHeight(listView
->viewOptions()));
250 // We're sure elementsPosition doesn't contain index
251 const QRect
&KCategorizedView::Private::cacheIndex(const QModelIndex
&index
)
253 QRect rect
= visualRectInViewport(index
);
254 elementsPosition
[index
] = rect
;
256 return elementsPosition
[index
];
259 // We're sure categoriesPosition doesn't contain category
260 const QRect
&KCategorizedView::Private::cacheCategory(const QString
&category
)
262 QRect rect
= visualCategoryRectInViewport(category
);
263 categoriesPosition
[category
] = rect
;
265 return categoriesPosition
[category
];
268 const QRect
&KCategorizedView::Private::cachedRectIndex(const QModelIndex
&index
)
270 if (elementsPosition
.contains(index
)) // If we have it cached
272 return elementsPosition
[index
];
274 else // Otherwise, cache it
276 return cacheIndex(index
);
280 const QRect
&KCategorizedView::Private::cachedRectCategory(const QString
&category
)
282 if (categoriesPosition
.contains(category
)) // If we have it cached
284 return categoriesPosition
[category
];
286 else // Otherwise, cache it and
288 return cacheCategory(category
);
292 QRect
KCategorizedView::Private::visualRect(const QModelIndex
&index
)
294 QModelIndex mappedIndex
= proxyModel
->mapToSource(index
);
296 QRect retRect
= cachedRectIndex(mappedIndex
);
297 int dx
= -listView
->horizontalOffset();
298 int dy
= -listView
->verticalOffset();
299 retRect
.adjust(dx
, dy
, dx
, dy
);
304 QRect
KCategorizedView::Private::categoryVisualRect(const QString
&category
)
306 QRect retRect
= cachedRectCategory(category
);
307 int dx
= -listView
->horizontalOffset();
308 int dy
= -listView
->verticalOffset();
309 retRect
.adjust(dx
, dy
, dx
, dy
);
314 void KCategorizedView::Private::drawNewCategory(const QModelIndex
&index
,
316 const QStyleOption
&option
,
319 QStyleOption optionCopy
= option
;
320 const QString category
= itemCategorizer
->categoryForItem(index
, sortRole
);
322 if ((category
== hoveredCategory
) && !mouseButtonPressed
)
324 optionCopy
.state
|= QStyle::State_MouseOver
;
327 itemCategorizer
->drawCategory(index
,
334 void KCategorizedView::Private::updateScrollbars()
336 int lastItemBottom
= cachedRectIndex(lastIndex
).bottom() +
337 listView
->spacing() - listView
->viewport()->height();
339 listView
->verticalScrollBar()->setSingleStep(listView
->viewport()->height() / 10);
340 listView
->verticalScrollBar()->setPageStep(listView
->viewport()->height());
341 listView
->verticalScrollBar()->setRange(0, lastItemBottom
);
344 void KCategorizedView::Private::drawDraggedItems(QPainter
*painter
)
346 QStyleOptionViewItemV3 option
= listView
->viewOptions();
347 option
.state
&= ~QStyle::State_MouseOver
;
348 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
350 const int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
351 const int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
353 option
.rect
= visualRect(index
);
354 option
.rect
.adjust(dx
, dy
, dx
, dy
);
356 if (option
.rect
.intersects(listView
->viewport()->rect()))
358 listView
->itemDelegate(index
)->paint(painter
, option
, index
);
363 void KCategorizedView::Private::drawDraggedItems()
367 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
369 int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
370 int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
372 currentRect
= visualRect(index
);
373 currentRect
.adjust(dx
, dy
, dx
, dy
);
375 if (currentRect
.intersects(listView
->viewport()->rect()))
377 rectToUpdate
= rectToUpdate
.united(currentRect
);
381 listView
->viewport()->update(lastDraggedItemsRect
.united(rectToUpdate
));
383 lastDraggedItemsRect
= rectToUpdate
;
387 //==============================================================================
390 KCategorizedView::KCategorizedView(QWidget
*parent
)
392 , d(new Private(this))
396 KCategorizedView::~KCategorizedView()
401 void KCategorizedView::setModel(QAbstractItemModel
*model
)
403 d
->lastSelection
= QItemSelection();
404 d
->currentViewIndex
= QModelIndex();
405 d
->forcedSelectionPosition
= 0;
406 d
->elementsInfo
.clear();
407 d
->elementsPosition
.clear();
408 d
->elementDictionary
.clear();
409 d
->invertedElementDictionary
.clear();
410 d
->categoriesIndexes
.clear();
411 d
->categoriesPosition
.clear();
412 d
->categories
.clear();
413 d
->intersectedIndexes
.clear();
414 d
->sourceModelIndexList
.clear();
415 d
->hovered
= QModelIndex();
416 d
->mouseButtonPressed
= false;
420 QObject::disconnect(d
->proxyModel
,
421 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
422 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
424 QObject::disconnect(d
->proxyModel
,
425 SIGNAL(sortingRoleChanged()),
426 this, SLOT(slotSortingRoleChanged()));
429 QListView::setModel(model
);
431 d
->proxyModel
= dynamic_cast<KSortFilterProxyModel
*>(model
);
435 QObject::connect(d
->proxyModel
,
436 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
437 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
439 QObject::connect(d
->proxyModel
,
440 SIGNAL(sortingRoleChanged()),
441 this, SLOT(slotSortingRoleChanged()));
445 QRect
KCategorizedView::visualRect(const QModelIndex
&index
) const
447 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
450 return QListView::visualRect(index
);
453 if (!qobject_cast
<const QSortFilterProxyModel
*>(index
.model()))
455 return d
->visualRect(d
->proxyModel
->mapFromSource(index
));
458 return d
->visualRect(index
);
461 KItemCategorizer
*KCategorizedView::itemCategorizer() const
463 return d
->itemCategorizer
;
466 void KCategorizedView::setItemCategorizer(KItemCategorizer
*itemCategorizer
)
468 d
->lastSelection
= QItemSelection();
469 d
->currentViewIndex
= QModelIndex();
470 d
->forcedSelectionPosition
= 0;
471 d
->elementsInfo
.clear();
472 d
->elementsPosition
.clear();
473 d
->elementDictionary
.clear();
474 d
->invertedElementDictionary
.clear();
475 d
->categoriesIndexes
.clear();
476 d
->categoriesPosition
.clear();
477 d
->categories
.clear();
478 d
->intersectedIndexes
.clear();
479 d
->sourceModelIndexList
.clear();
480 d
->hovered
= QModelIndex();
481 d
->mouseButtonPressed
= false;
483 if (!itemCategorizer
&& d
->proxyModel
)
485 QObject::disconnect(d
->proxyModel
,
486 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
487 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
489 QObject::disconnect(d
->proxyModel
,
490 SIGNAL(sortingRoleChanged()),
491 this, SLOT(slotSortingRoleChanged()));
493 else if (itemCategorizer
&& d
->proxyModel
)
495 QObject::connect(d
->proxyModel
,
496 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
497 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
499 QObject::connect(d
->proxyModel
,
500 SIGNAL(sortingRoleChanged()),
501 this, SLOT(slotSortingRoleChanged()));
504 d
->itemCategorizer
= itemCategorizer
;
508 rowsInserted(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
516 QModelIndex
KCategorizedView::indexAt(const QPoint
&point
) const
518 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
521 return QListView::indexAt(point
);
526 QModelIndexList item
= d
->intersectionSet(QRect(point
, point
));
528 if (item
.count() == 1)
538 void KCategorizedView::reset()
542 d
->lastSelection
= QItemSelection();
543 d
->currentViewIndex
= QModelIndex();
544 d
->forcedSelectionPosition
= 0;
545 d
->elementsInfo
.clear();
546 d
->elementsPosition
.clear();
547 d
->elementDictionary
.clear();
548 d
->invertedElementDictionary
.clear();
549 d
->categoriesIndexes
.clear();
550 d
->categoriesPosition
.clear();
551 d
->categories
.clear();
552 d
->intersectedIndexes
.clear();
553 d
->sourceModelIndexList
.clear();
554 d
->hovered
= QModelIndex();
555 d
->mouseButtonPressed
= false;
558 void KCategorizedView::paintEvent(QPaintEvent
*event
)
560 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
563 QListView::paintEvent(event
);
567 QStyleOptionViewItemV3 option
= viewOptions();
568 option
.widget
= this;
569 QPainter
painter(viewport());
570 QRect area
= event
->rect();
571 const bool focus
= (hasFocus() || viewport()->hasFocus()) &&
572 currentIndex().isValid();
573 const QStyle::State state
= option
.state
;
574 const bool enabled
= (state
& QStyle::State_Enabled
) != 0;
578 QModelIndexList dirtyIndexes
= d
->intersectionSet(area
);
579 foreach (const QModelIndex
&index
, dirtyIndexes
)
581 option
.state
= state
;
582 option
.rect
= d
->visualRect(index
);
584 if (selectionModel() && selectionModel()->isSelected(index
))
586 option
.state
|= QStyle::State_Selected
;
591 QPalette::ColorGroup cg
;
592 if ((d
->proxyModel
->flags(index
) & Qt::ItemIsEnabled
) == 0)
594 option
.state
&= ~QStyle::State_Enabled
;
595 cg
= QPalette::Disabled
;
599 cg
= QPalette::Normal
;
601 option
.palette
.setCurrentColorGroup(cg
);
604 if (focus
&& currentIndex() == index
)
606 option
.state
|= QStyle::State_HasFocus
;
607 if (this->state() == EditingState
)
608 option
.state
|= QStyle::State_Editing
;
611 if ((index
== d
->hovered
) && !d
->mouseButtonPressed
)
612 option
.state
|= QStyle::State_MouseOver
;
614 option
.state
&= ~QStyle::State_MouseOver
;
616 itemDelegate(index
)->paint(&painter
, option
, index
);
621 QStyleOptionViewItem otherOption
;
622 foreach (const QString
&category
, d
->categories
)
624 otherOption
= option
;
625 otherOption
.rect
= d
->categoryVisualRect(category
);
626 otherOption
.state
&= ~QStyle::State_MouseOver
;
628 if (otherOption
.rect
.intersects(area
))
630 d
->drawNewCategory(d
->categoriesIndexes
[category
][0],
631 d
->proxyModel
->sortRole(), otherOption
, &painter
);
635 if (d
->mouseButtonPressed
&& !d
->isDragging
)
637 QPoint start
, end
, initialPressPosition
;
639 initialPressPosition
= d
->initialPressPosition
;
641 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
642 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
644 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
645 d
->initialPressPosition
.y() > d
->mousePosition
.y())
647 start
= d
->mousePosition
;
648 end
= initialPressPosition
;
652 start
= initialPressPosition
;
653 end
= d
->mousePosition
;
656 QStyleOptionRubberBand yetAnotherOption
;
657 yetAnotherOption
.initFrom(this);
658 yetAnotherOption
.shape
= QRubberBand::Rectangle
;
659 yetAnotherOption
.opaque
= false;
660 yetAnotherOption
.rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
662 style()->drawControl(QStyle::CE_RubberBand
, &yetAnotherOption
, &painter
);
666 if (d
->isDragging
&& !d
->dragLeftViewport
)
668 painter
.setOpacity(0.5);
669 d
->drawDraggedItems(&painter
);
675 void KCategorizedView::resizeEvent(QResizeEvent
*event
)
677 QListView::resizeEvent(event
);
679 // Clear the items positions cache
680 d
->elementsPosition
.clear();
681 d
->categoriesPosition
.clear();
682 d
->forcedSelectionPosition
= 0;
684 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
690 d
->updateScrollbars();
693 void KCategorizedView::setSelection(const QRect
&rect
,
694 QItemSelectionModel::SelectionFlags flags
)
696 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
699 QListView::setSelection(rect
, flags
);
706 selectionModel()->clear();
708 if (flags
& QItemSelectionModel::Clear
)
710 d
->lastSelection
= QItemSelection();
713 QModelIndexList dirtyIndexes
= d
->intersectionSet(rect
);
715 QItemSelection selection
;
717 if (!dirtyIndexes
.count())
719 if (d
->lastSelection
.count())
721 selectionModel()->select(d
->lastSelection
, flags
);
727 if (!d
->mouseButtonPressed
)
729 selection
= QItemSelection(dirtyIndexes
[0], dirtyIndexes
[0]);
730 d
->currentViewIndex
= dirtyIndexes
[0];
734 QModelIndex first
= dirtyIndexes
[0];
736 foreach (const QModelIndex
&index
, dirtyIndexes
)
738 if (last
.isValid() && last
.row() + 1 != index
.row())
740 QItemSelectionRange
range(first
, last
);
751 selection
<< QItemSelectionRange(first
, last
);
754 if (d
->lastSelection
.count() && !d
->mouseButtonPressed
)
756 selection
.merge(d
->lastSelection
, flags
);
758 else if (d
->lastSelection
.count())
760 selection
.merge(d
->lastSelection
, QItemSelectionModel::Select
);
763 selectionModel()->select(selection
, flags
);
766 void KCategorizedView::mouseMoveEvent(QMouseEvent
*event
)
768 QListView::mouseMoveEvent(event
);
770 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
776 const QString previousHoveredCategory
= d
->hoveredCategory
;
778 d
->mousePosition
= event
->pos();
779 d
->hoveredCategory
= QString();
782 foreach (const QString
&category
, d
->categories
)
784 if (d
->categoryVisualRect(category
).intersects(QRect(event
->pos(), event
->pos())))
786 d
->hoveredCategory
= category
;
787 viewport()->update(d
->categoryVisualRect(category
));
789 else if ((category
== previousHoveredCategory
) &&
790 (!d
->categoryVisualRect(previousHoveredCategory
).intersects(QRect(event
->pos(), event
->pos()))))
792 viewport()->update(d
->categoryVisualRect(category
));
797 if (d
->mouseButtonPressed
&& !d
->isDragging
)
799 QPoint start
, end
, initialPressPosition
;
801 initialPressPosition
= d
->initialPressPosition
;
803 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
804 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
806 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
807 d
->initialPressPosition
.y() > d
->mousePosition
.y())
809 start
= d
->mousePosition
;
810 end
= initialPressPosition
;
814 start
= initialPressPosition
;
815 end
= d
->mousePosition
;
818 rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
820 //viewport()->update(rect.united(d->lastSelectionRect));
822 d
->lastSelectionRect
= rect
;
826 void KCategorizedView::mousePressEvent(QMouseEvent
*event
)
828 d
->dragLeftViewport
= false;
830 if (event
->button() == Qt::LeftButton
)
832 d
->mouseButtonPressed
= true;
834 d
->initialPressPosition
= event
->pos();
835 d
->initialPressPosition
.setY(d
->initialPressPosition
.y() +
837 d
->initialPressPosition
.setX(d
->initialPressPosition
.x() +
841 QListView::mousePressEvent(event
);
844 void KCategorizedView::mouseReleaseEvent(QMouseEvent
*event
)
846 d
->mouseButtonPressed
= false;
848 QListView::mouseReleaseEvent(event
);
850 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
856 QPoint initialPressPosition
= viewport()->mapFromGlobal(QCursor::pos());
857 initialPressPosition
.setY(initialPressPosition
.y() + verticalOffset());
858 initialPressPosition
.setX(initialPressPosition
.x() + horizontalOffset());
860 QItemSelection selection
;
862 if (initialPressPosition
== d
->initialPressPosition
)
864 foreach(const QString
&category
, d
->categories
)
866 if (d
->categoryVisualRect(category
).contains(event
->pos()))
868 foreach (const QModelIndex
&index
, d
->categoriesIndexes
[category
])
870 selection
<< QItemSelectionRange(d
->proxyModel
->mapFromSource(index
));
873 selectionModel()->select(selection
, QItemSelectionModel::Select
);
880 d
->lastSelection
= selectionModel()->selection();
882 if (d
->hovered
.isValid())
883 viewport()->update(d
->visualRect(d
->hovered
));
884 else if (!d
->hoveredCategory
.isEmpty())
885 viewport()->update(d
->categoryVisualRect(d
->hoveredCategory
));
888 void KCategorizedView::leaveEvent(QEvent
*event
)
890 d
->hovered
= QModelIndex();
891 d
->hoveredCategory
= QString();
893 QListView::leaveEvent(event
);
896 void KCategorizedView::startDrag(Qt::DropActions supportedActions
)
898 QListView::startDrag(supportedActions
);
900 d
->isDragging
= false;
901 d
->mouseButtonPressed
= false;
903 viewport()->update(d
->lastDraggedItemsRect
);
906 void KCategorizedView::dragMoveEvent(QDragMoveEvent
*event
)
908 d
->mousePosition
= event
->pos();
910 if (d
->mouseButtonPressed
)
912 d
->isDragging
= true;
916 d
->isDragging
= false;
919 d
->dragLeftViewport
= false;
921 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
924 QListView::dragMoveEvent(event
);
928 d
->drawDraggedItems();
931 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent
*event
)
933 d
->dragLeftViewport
= true;
935 QListView::dragLeaveEvent(event
);
938 QModelIndex
KCategorizedView::moveCursor(CursorAction cursorAction
,
939 Qt::KeyboardModifiers modifiers
)
941 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
944 return QListView::moveCursor(cursorAction
, modifiers
);
947 const QModelIndex current
= selectionModel()->currentIndex();
949 int viewportWidth
= viewport()->width() - spacing();
950 // We really need all items to be of same size. Otherwise we cannot do this
952 // QSize itemSize = listView->sizeHintForIndex(index);
953 // int itemHeight = itemSize.height();
954 // int itemWidth = itemSize.width();
955 int itemHeight
= 107;
957 int itemWidthPlusSeparation
= spacing() + itemWidth
;
958 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
960 QString lastCategory
= d
->categories
[0];
961 QString theCategory
= d
->categories
[0];
962 QString afterCategory
= d
->categories
[0];
963 bool hasToBreak
= false;
964 foreach (const QString
&category
, d
->categories
)
968 afterCategory
= category
;
973 if (category
== d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].category
)
975 theCategory
= category
;
982 lastCategory
= category
;
986 switch (cursorAction
)
988 case QAbstractItemView::MoveUp
: {
989 if (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
>= elementsPerRow
)
991 int indexToMove
= d
->invertedElementDictionary
[current
].row();
992 indexToMove
-= qMin(((d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
) + d
->forcedSelectionPosition
), elementsPerRow
- d
->forcedSelectionPosition
+ (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
% elementsPerRow
));
994 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
998 int lastCategoryLastRow
= (d
->categoriesIndexes
[lastCategory
].count() - 1) % elementsPerRow
;
999 int indexToMove
= d
->invertedElementDictionary
[current
].row() - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
;
1001 if (d
->forcedSelectionPosition
>= lastCategoryLastRow
)
1007 indexToMove
-= qMin((lastCategoryLastRow
- d
->forcedSelectionPosition
+ 1), d
->forcedSelectionPosition
+ elementsPerRow
+ 1);
1010 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1014 case QAbstractItemView::MoveDown
: {
1015 if (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
< (d
->categoriesIndexes
[theCategory
].count() - 1 - ((d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
)))
1017 int indexToMove
= d
->invertedElementDictionary
[current
].row();
1018 indexToMove
+= qMin(elementsPerRow
, d
->categoriesIndexes
[theCategory
].count() - 1 - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
);
1020 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1024 int afterCategoryLastRow
= qMin(elementsPerRow
, d
->categoriesIndexes
[afterCategory
].count());
1025 int indexToMove
= d
->invertedElementDictionary
[current
].row() + (d
->categoriesIndexes
[theCategory
].count() - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
);
1027 if (d
->forcedSelectionPosition
>= afterCategoryLastRow
)
1029 indexToMove
+= afterCategoryLastRow
- 1;
1033 indexToMove
+= qMin(d
->forcedSelectionPosition
, elementsPerRow
);
1036 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1040 case QAbstractItemView::MoveLeft
:
1041 d
->forcedSelectionPosition
= d
->elementsInfo
[d
->proxyModel
->mapToSource(d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() - 1, 0)])].relativeOffsetToCategory
% elementsPerRow
;
1043 if (d
->forcedSelectionPosition
< 0)
1044 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1046 return d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() - 1, 0)];
1048 case QAbstractItemView::MoveRight
:
1049 d
->forcedSelectionPosition
= d
->elementsInfo
[d
->proxyModel
->mapToSource(d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() + 1, 0)])].relativeOffsetToCategory
% elementsPerRow
;
1051 if (d
->forcedSelectionPosition
< 0)
1052 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1054 return d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() + 1, 0)];
1060 return QListView::moveCursor(cursorAction
, modifiers
);
1063 void KCategorizedView::rowsInserted(const QModelIndex
&parent
,
1067 QListView::rowsInserted(parent
, start
, end
);
1069 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1070 !d
->itemCategorizer
)
1072 d
->lastSelection
= QItemSelection();
1073 d
->currentViewIndex
= QModelIndex();
1074 d
->forcedSelectionPosition
= 0;
1075 d
->elementsInfo
.clear();
1076 d
->elementsPosition
.clear();
1077 d
->elementDictionary
.clear();
1078 d
->invertedElementDictionary
.clear();
1079 d
->categoriesIndexes
.clear();
1080 d
->categoriesPosition
.clear();
1081 d
->categories
.clear();
1082 d
->intersectedIndexes
.clear();
1083 d
->sourceModelIndexList
.clear();
1084 d
->hovered
= QModelIndex();
1085 d
->mouseButtonPressed
= false;
1090 rowsInsertedArtifficial(parent
, start
, end
);
1093 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex
&parent
,
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
->mouseButtonPressed
= false;
1114 if (start
> end
|| end
< 0 || start
< 0 || !d
->proxyModel
->rowCount())
1119 // Add all elements mapped to the source model
1120 for (int k
= 0; k
< d
->proxyModel
->rowCount(); k
++)
1122 d
->sourceModelIndexList
<<
1123 d
->proxyModel
->mapToSource(d
->proxyModel
->index(k
, 0));
1126 // Sort them with the general purpose lessThan method
1127 LessThan
generalLessThan(d
->proxyModel
,
1128 LessThan::GeneralPurpose
);
1130 qStableSort(d
->sourceModelIndexList
.begin(), d
->sourceModelIndexList
.end(),
1133 // Explore categories
1134 QString prevCategory
=
1135 d
->itemCategorizer
->categoryForItem(d
->sourceModelIndexList
[0],
1136 d
->proxyModel
->sortRole());
1137 QString lastCategory
= prevCategory
;
1138 QModelIndexList modelIndexList
;
1139 struct Private::ElementInfo elementInfo
;
1140 foreach (const QModelIndex
&index
, d
->sourceModelIndexList
)
1142 lastCategory
= d
->itemCategorizer
->categoryForItem(index
,
1143 d
->proxyModel
->sortRole());
1145 elementInfo
.category
= lastCategory
;
1147 if (prevCategory
!= lastCategory
)
1149 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1150 d
->categories
<< prevCategory
;
1151 modelIndexList
.clear();
1154 modelIndexList
<< index
;
1155 prevCategory
= lastCategory
;
1157 d
->elementsInfo
.insert(index
, elementInfo
);
1160 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1161 d
->categories
<< prevCategory
;
1163 // Sort items locally in their respective categories with the category
1165 LessThan
categoryLessThan(d
->proxyModel
,
1166 LessThan::CategoryPurpose
);
1168 foreach (const QString
&key
, d
->categories
)
1170 QModelIndexList
&indexList
= d
->categoriesIndexes
[key
];
1172 qStableSort(indexList
.begin(), indexList
.end(), categoryLessThan
);
1175 d
->lastIndex
= d
->categoriesIndexes
[d
->categories
[d
->categories
.count() - 1]][d
->categoriesIndexes
[d
->categories
[d
->categories
.count() - 1]].count() - 1];
1177 // Finally, fill data information of items situation. This will help when
1178 // trying to compute an item place in the viewport
1179 int i
= 0; // position relative to the category beginning
1180 int j
= 0; // number of elements before current
1181 foreach (const QString
&key
, d
->categories
)
1183 foreach (const QModelIndex
&index
, d
->categoriesIndexes
[key
])
1185 struct Private::ElementInfo
&elementInfo
= d
->elementsInfo
[index
];
1187 elementInfo
.relativeOffsetToCategory
= i
;
1189 d
->elementDictionary
.insert(d
->proxyModel
->index(j
, 0),
1190 d
->proxyModel
->mapFromSource(index
));
1192 d
->invertedElementDictionary
.insert(d
->proxyModel
->mapFromSource(index
),
1193 d
->proxyModel
->index(j
, 0));
1202 d
->updateScrollbars();
1205 void KCategorizedView::rowsRemoved(const QModelIndex
&parent
,
1209 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1212 // Force the view to update all elements
1213 rowsInsertedArtifficial(parent
, start
, end
);
1217 void KCategorizedView::updateGeometries()
1219 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1220 !d
->itemCategorizer
)
1222 QListView::updateGeometries();
1226 // Avoid QListView::updateGeometries(), since it will try to set another
1227 // range to our scroll bars, what we don't want (ereslibre)
1228 QAbstractItemView::updateGeometries();
1231 void KCategorizedView::slotSortingRoleChanged()
1233 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1236 // Force the view to update all elements
1237 rowsInsertedArtifficial(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
1241 #include "kcategorizedview.moc"