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();
103 if (listView
->gridSize().isEmpty())
105 itemHeight
= biggestItemSize
.height();
109 itemHeight
= listView
->gridSize().height();
112 // Lets find out where we should start
113 int top
= proxyModel
->rowCount() - 1;
115 int middle
= (top
+ bottom
) / 2;
116 while (bottom
<= top
)
118 middle
= (top
+ bottom
) / 2;
120 index
= elementDictionary
[proxyModel
->index(middle
, 0)];
121 indexVisualRect
= visualRect(index
);
122 // We need the whole height (not only the visualRect). This will help us to update
123 // all needed indexes correctly (ereslibre)
124 indexVisualRect
.setHeight(indexVisualRect
.height() + (itemHeight
- indexVisualRect
.height()));
126 if (qMax(indexVisualRect
.topLeft().y(),
127 indexVisualRect
.bottomRight().y()) < qMin(rect
.topLeft().y(),
128 rect
.bottomRight().y()))
138 for (int i
= middle
; i
< proxyModel
->rowCount(); i
++)
140 index
= elementDictionary
[proxyModel
->index(i
, 0)];
141 indexVisualRect
= visualRect(index
);
143 if (rect
.intersects(indexVisualRect
))
144 intersectedIndexes
.append(index
);
146 // If we passed next item, stop searching for hits
147 if (qMax(rect
.bottomRight().y(), rect
.topLeft().y()) <
148 qMin(indexVisualRect
.topLeft().y(),
149 indexVisualRect
.bottomRight().y()))
153 return intersectedIndexes
;
156 QRect
KCategorizedView::Private::visualRectInViewport(const QModelIndex
&index
) const
158 if (!index
.isValid())
161 QString curCategory
= elementsInfo
[index
].category
;
163 QRect
retRect(listView
->spacing(), listView
->spacing() * 2 +
164 itemCategorizer
->categoryHeight(listView
->viewOptions()), 0, 0);
166 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
171 if (listView
->gridSize().isEmpty())
173 itemHeight
= biggestItemSize
.height();
174 itemWidth
= biggestItemSize
.width();
178 itemHeight
= listView
->gridSize().height();
179 itemWidth
= listView
->gridSize().width();
182 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
183 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
187 int column
= elementsInfo
[index
].relativeOffsetToCategory
% elementsPerRow
;
188 int row
= elementsInfo
[index
].relativeOffsetToCategory
/ elementsPerRow
;
190 retRect
.setLeft(retRect
.left() + column
* listView
->spacing() +
193 foreach (const QString
&category
, categories
)
195 if (category
== curCategory
)
198 float rows
= (float) ((float) categoriesIndexes
[category
].count() /
199 (float) elementsPerRow
);
200 int rowsInt
= categoriesIndexes
[category
].count() / elementsPerRow
;
202 if (rows
- trunc(rows
)) rowsInt
++;
204 retRect
.setTop(retRect
.top() +
205 (rowsInt
* listView
->spacing()) +
206 (rowsInt
* itemHeight
) +
207 itemCategorizer
->categoryHeight(listView
->viewOptions()) +
208 listView
->spacing() * 2);
211 retRect
.setTop(retRect
.top() + row
* listView
->spacing() +
214 retRect
.setWidth(itemWidth
);
216 if (listView
->gridSize().isEmpty())
218 retRect
.setHeight(listView
->sizeHintForIndex(proxyModel
->mapFromSource(index
)).height());
222 retRect
.setHeight(qMin(listView
->sizeHintForIndex(proxyModel
->mapFromSource(index
)).height(),
223 listView
->gridSize().height()));
229 QRect
KCategorizedView::Private::visualCategoryRectInViewport(const QString
&category
)
232 QRect
retRect(listView
->spacing(),
234 listView
->viewport()->width() - listView
->spacing() * 2,
237 if (!proxyModel
->rowCount() || !categories
.contains(category
))
240 QModelIndex index
= proxyModel
->index(0, 0, QModelIndex());
242 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
247 if (listView
->gridSize().isEmpty())
249 itemHeight
= biggestItemSize
.height();
250 itemWidth
= biggestItemSize
.width();
254 itemHeight
= listView
->gridSize().height();
255 itemWidth
= listView
->gridSize().width();
258 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
259 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
264 foreach (const QString
&itCategory
, categories
)
266 if (itCategory
== category
)
269 float rows
= (float) ((float) categoriesIndexes
[itCategory
].count() /
270 (float) elementsPerRow
);
271 int rowsInt
= categoriesIndexes
[itCategory
].count() / elementsPerRow
;
273 if (rows
- trunc(rows
)) rowsInt
++;
275 retRect
.setTop(retRect
.top() +
276 (rowsInt
* listView
->spacing()) +
277 (rowsInt
* itemHeight
) +
278 itemCategorizer
->categoryHeight(listView
->viewOptions()) +
279 listView
->spacing() * 2);
282 retRect
.setHeight(itemCategorizer
->categoryHeight(listView
->viewOptions()));
287 // We're sure elementsPosition doesn't contain index
288 const QRect
&KCategorizedView::Private::cacheIndex(const QModelIndex
&index
)
290 QRect rect
= visualRectInViewport(index
);
291 elementsPosition
[index
] = rect
;
293 return elementsPosition
[index
];
296 // We're sure categoriesPosition doesn't contain category
297 const QRect
&KCategorizedView::Private::cacheCategory(const QString
&category
)
299 QRect rect
= visualCategoryRectInViewport(category
);
300 categoriesPosition
[category
] = rect
;
302 return categoriesPosition
[category
];
305 const QRect
&KCategorizedView::Private::cachedRectIndex(const QModelIndex
&index
)
307 if (elementsPosition
.contains(index
)) // If we have it cached
309 return elementsPosition
[index
];
311 else // Otherwise, cache it
313 return cacheIndex(index
);
317 const QRect
&KCategorizedView::Private::cachedRectCategory(const QString
&category
)
319 if (categoriesPosition
.contains(category
)) // If we have it cached
321 return categoriesPosition
[category
];
323 else // Otherwise, cache it and
325 return cacheCategory(category
);
329 QRect
KCategorizedView::Private::visualRect(const QModelIndex
&index
)
331 QModelIndex mappedIndex
= proxyModel
->mapToSource(index
);
333 QRect retRect
= cachedRectIndex(mappedIndex
);
334 int dx
= -listView
->horizontalOffset();
335 int dy
= -listView
->verticalOffset();
336 retRect
.adjust(dx
, dy
, dx
, dy
);
341 QRect
KCategorizedView::Private::categoryVisualRect(const QString
&category
)
343 QRect retRect
= cachedRectCategory(category
);
344 int dx
= -listView
->horizontalOffset();
345 int dy
= -listView
->verticalOffset();
346 retRect
.adjust(dx
, dy
, dx
, dy
);
351 void KCategorizedView::Private::drawNewCategory(const QModelIndex
&index
,
353 const QStyleOption
&option
,
356 QStyleOption optionCopy
= option
;
357 const QString category
= itemCategorizer
->categoryForItem(index
, sortRole
);
359 if ((category
== hoveredCategory
) && !mouseButtonPressed
)
361 optionCopy
.state
|= QStyle::State_MouseOver
;
364 itemCategorizer
->drawCategory(index
,
371 void KCategorizedView::Private::updateScrollbars()
373 int lastItemBottom
= cachedRectIndex(lastIndex
).top() +
374 listView
->spacing() + (listView
->gridSize().isEmpty() ? 0 : listView
->gridSize().height()) - listView
->viewport()->height();
376 listView
->verticalScrollBar()->setSingleStep(listView
->viewport()->height() / 10);
377 listView
->verticalScrollBar()->setPageStep(listView
->viewport()->height());
378 listView
->verticalScrollBar()->setRange(0, lastItemBottom
);
381 void KCategorizedView::Private::drawDraggedItems(QPainter
*painter
)
383 QStyleOptionViewItemV3 option
= listView
->viewOptions();
384 option
.state
&= ~QStyle::State_MouseOver
;
385 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
387 const int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
388 const int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
390 option
.rect
= visualRect(index
);
391 option
.rect
.adjust(dx
, dy
, dx
, dy
);
393 if (option
.rect
.intersects(listView
->viewport()->rect()))
395 listView
->itemDelegate(index
)->paint(painter
, option
, index
);
400 void KCategorizedView::Private::drawDraggedItems()
404 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
406 int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
407 int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
409 currentRect
= visualRect(index
);
410 currentRect
.adjust(dx
, dy
, dx
, dy
);
412 if (currentRect
.intersects(listView
->viewport()->rect()))
414 rectToUpdate
= rectToUpdate
.united(currentRect
);
418 listView
->viewport()->update(lastDraggedItemsRect
.united(rectToUpdate
));
420 lastDraggedItemsRect
= rectToUpdate
;
424 //==============================================================================
427 KCategorizedView::KCategorizedView(QWidget
*parent
)
429 , d(new Private(this))
433 KCategorizedView::~KCategorizedView()
438 void KCategorizedView::setModel(QAbstractItemModel
*model
)
440 d
->lastSelection
= QItemSelection();
441 d
->currentViewIndex
= QModelIndex();
442 d
->forcedSelectionPosition
= 0;
443 d
->elementsInfo
.clear();
444 d
->elementsPosition
.clear();
445 d
->elementDictionary
.clear();
446 d
->invertedElementDictionary
.clear();
447 d
->categoriesIndexes
.clear();
448 d
->categoriesPosition
.clear();
449 d
->categories
.clear();
450 d
->intersectedIndexes
.clear();
451 d
->sourceModelIndexList
.clear();
452 d
->hovered
= QModelIndex();
453 d
->mouseButtonPressed
= false;
457 QObject::disconnect(d
->proxyModel
,
458 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
459 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
461 QObject::disconnect(d
->proxyModel
,
462 SIGNAL(sortingRoleChanged()),
463 this, SLOT(slotSortingRoleChanged()));
466 QListView::setModel(model
);
468 d
->proxyModel
= dynamic_cast<KSortFilterProxyModel
*>(model
);
472 QObject::connect(d
->proxyModel
,
473 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
474 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
476 QObject::connect(d
->proxyModel
,
477 SIGNAL(sortingRoleChanged()),
478 this, SLOT(slotSortingRoleChanged()));
482 QRect
KCategorizedView::visualRect(const QModelIndex
&index
) const
484 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
487 return QListView::visualRect(index
);
490 if (!qobject_cast
<const QSortFilterProxyModel
*>(index
.model()))
492 return d
->visualRect(d
->proxyModel
->mapFromSource(index
));
495 return d
->visualRect(index
);
498 KItemCategorizer
*KCategorizedView::itemCategorizer() const
500 return d
->itemCategorizer
;
503 void KCategorizedView::setItemCategorizer(KItemCategorizer
*itemCategorizer
)
505 d
->lastSelection
= QItemSelection();
506 d
->currentViewIndex
= QModelIndex();
507 d
->forcedSelectionPosition
= 0;
508 d
->elementsInfo
.clear();
509 d
->elementsPosition
.clear();
510 d
->elementDictionary
.clear();
511 d
->invertedElementDictionary
.clear();
512 d
->categoriesIndexes
.clear();
513 d
->categoriesPosition
.clear();
514 d
->categories
.clear();
515 d
->intersectedIndexes
.clear();
516 d
->sourceModelIndexList
.clear();
517 d
->hovered
= QModelIndex();
518 d
->mouseButtonPressed
= false;
520 if (!itemCategorizer
&& d
->proxyModel
)
522 QObject::disconnect(d
->proxyModel
,
523 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
524 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
526 QObject::disconnect(d
->proxyModel
,
527 SIGNAL(sortingRoleChanged()),
528 this, SLOT(slotSortingRoleChanged()));
530 else if (itemCategorizer
&& d
->proxyModel
)
532 QObject::connect(d
->proxyModel
,
533 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
534 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
536 QObject::connect(d
->proxyModel
,
537 SIGNAL(sortingRoleChanged()),
538 this, SLOT(slotSortingRoleChanged()));
541 d
->itemCategorizer
= itemCategorizer
;
545 rowsInserted(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
553 QModelIndex
KCategorizedView::indexAt(const QPoint
&point
) const
555 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
558 return QListView::indexAt(point
);
563 QModelIndexList item
= d
->intersectionSet(QRect(point
, point
));
565 if (item
.count() == 1)
575 void KCategorizedView::reset()
579 d
->lastSelection
= QItemSelection();
580 d
->currentViewIndex
= QModelIndex();
581 d
->forcedSelectionPosition
= 0;
582 d
->elementsInfo
.clear();
583 d
->elementsPosition
.clear();
584 d
->elementDictionary
.clear();
585 d
->invertedElementDictionary
.clear();
586 d
->categoriesIndexes
.clear();
587 d
->categoriesPosition
.clear();
588 d
->categories
.clear();
589 d
->intersectedIndexes
.clear();
590 d
->sourceModelIndexList
.clear();
591 d
->hovered
= QModelIndex();
592 d
->biggestItemSize
= QSize(0, 0);
593 d
->mouseButtonPressed
= false;
596 void KCategorizedView::paintEvent(QPaintEvent
*event
)
598 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
601 QListView::paintEvent(event
);
605 QStyleOptionViewItemV3 option
= viewOptions();
606 option
.widget
= this;
607 QPainter
painter(viewport());
608 QRect area
= event
->rect();
609 const bool focus
= (hasFocus() || viewport()->hasFocus()) &&
610 currentIndex().isValid();
611 const QStyle::State state
= option
.state
;
612 const bool enabled
= (state
& QStyle::State_Enabled
) != 0;
616 QModelIndexList dirtyIndexes
= d
->intersectionSet(area
);
617 foreach (const QModelIndex
&index
, dirtyIndexes
)
619 option
.state
= state
;
620 option
.rect
= d
->visualRect(index
);
622 if (selectionModel() && selectionModel()->isSelected(index
))
624 option
.state
|= QStyle::State_Selected
;
629 QPalette::ColorGroup cg
;
630 if ((d
->proxyModel
->flags(index
) & Qt::ItemIsEnabled
) == 0)
632 option
.state
&= ~QStyle::State_Enabled
;
633 cg
= QPalette::Disabled
;
637 cg
= QPalette::Normal
;
639 option
.palette
.setCurrentColorGroup(cg
);
642 if (focus
&& currentIndex() == index
)
644 option
.state
|= QStyle::State_HasFocus
;
645 if (this->state() == EditingState
)
646 option
.state
|= QStyle::State_Editing
;
649 if ((index
== d
->hovered
) && !d
->mouseButtonPressed
)
650 option
.state
|= QStyle::State_MouseOver
;
652 option
.state
&= ~QStyle::State_MouseOver
;
654 itemDelegate(index
)->paint(&painter
, option
, index
);
658 QStyleOptionViewItem otherOption
;
659 foreach (const QString
&category
, d
->categories
)
661 otherOption
= option
;
662 otherOption
.rect
= d
->categoryVisualRect(category
);
663 otherOption
.state
&= ~QStyle::State_MouseOver
;
665 if (otherOption
.rect
.intersects(area
))
667 d
->drawNewCategory(d
->categoriesIndexes
[category
][0],
668 d
->proxyModel
->sortRole(), otherOption
, &painter
);
672 if (d
->mouseButtonPressed
&& !d
->isDragging
)
674 QPoint start
, end
, initialPressPosition
;
676 initialPressPosition
= d
->initialPressPosition
;
678 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
679 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
681 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
682 d
->initialPressPosition
.y() > d
->mousePosition
.y())
684 start
= d
->mousePosition
;
685 end
= initialPressPosition
;
689 start
= initialPressPosition
;
690 end
= d
->mousePosition
;
693 QStyleOptionRubberBand yetAnotherOption
;
694 yetAnotherOption
.initFrom(this);
695 yetAnotherOption
.shape
= QRubberBand::Rectangle
;
696 yetAnotherOption
.opaque
= false;
697 yetAnotherOption
.rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
699 style()->drawControl(QStyle::CE_RubberBand
, &yetAnotherOption
, &painter
);
703 if (d
->isDragging
&& !d
->dragLeftViewport
)
705 painter
.setOpacity(0.5);
706 d
->drawDraggedItems(&painter
);
712 void KCategorizedView::resizeEvent(QResizeEvent
*event
)
714 QListView::resizeEvent(event
);
716 // Clear the items positions cache
717 d
->elementsPosition
.clear();
718 d
->categoriesPosition
.clear();
719 d
->forcedSelectionPosition
= 0;
721 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
727 d
->updateScrollbars();
730 void KCategorizedView::setSelection(const QRect
&rect
,
731 QItemSelectionModel::SelectionFlags flags
)
733 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
736 QListView::setSelection(rect
, flags
);
743 selectionModel()->clear();
745 if (flags
& QItemSelectionModel::Clear
)
747 d
->lastSelection
= QItemSelection();
750 QModelIndexList dirtyIndexes
= d
->intersectionSet(rect
);
752 QItemSelection selection
;
754 if (!dirtyIndexes
.count())
756 if (d
->lastSelection
.count())
758 selectionModel()->select(d
->lastSelection
, flags
);
764 if (!d
->mouseButtonPressed
)
766 selection
= QItemSelection(dirtyIndexes
[0], dirtyIndexes
[0]);
767 d
->currentViewIndex
= dirtyIndexes
[0];
771 QModelIndex first
= dirtyIndexes
[0];
773 foreach (const QModelIndex
&index
, dirtyIndexes
)
775 if (last
.isValid() && last
.row() + 1 != index
.row())
777 QItemSelectionRange
range(first
, last
);
788 selection
<< QItemSelectionRange(first
, last
);
791 if (d
->lastSelection
.count() && !d
->mouseButtonPressed
)
793 selection
.merge(d
->lastSelection
, flags
);
795 else if (d
->lastSelection
.count())
797 selection
.merge(d
->lastSelection
, QItemSelectionModel::Select
);
800 selectionModel()->select(selection
, flags
);
803 void KCategorizedView::mouseMoveEvent(QMouseEvent
*event
)
805 QListView::mouseMoveEvent(event
);
807 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
813 const QString previousHoveredCategory
= d
->hoveredCategory
;
815 d
->mousePosition
= event
->pos();
816 d
->hoveredCategory
= QString();
819 foreach (const QString
&category
, d
->categories
)
821 if (d
->categoryVisualRect(category
).intersects(QRect(event
->pos(), event
->pos())))
823 d
->hoveredCategory
= category
;
824 viewport()->update(d
->categoryVisualRect(category
));
826 else if ((category
== previousHoveredCategory
) &&
827 (!d
->categoryVisualRect(previousHoveredCategory
).intersects(QRect(event
->pos(), event
->pos()))))
829 viewport()->update(d
->categoryVisualRect(category
));
834 if (d
->mouseButtonPressed
&& !d
->isDragging
)
836 QPoint start
, end
, initialPressPosition
;
838 initialPressPosition
= d
->initialPressPosition
;
840 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
841 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
843 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
844 d
->initialPressPosition
.y() > d
->mousePosition
.y())
846 start
= d
->mousePosition
;
847 end
= initialPressPosition
;
851 start
= initialPressPosition
;
852 end
= d
->mousePosition
;
855 rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
857 //viewport()->update(rect.united(d->lastSelectionRect));
859 d
->lastSelectionRect
= rect
;
863 void KCategorizedView::mousePressEvent(QMouseEvent
*event
)
865 d
->dragLeftViewport
= false;
867 if (event
->button() == Qt::LeftButton
)
869 d
->mouseButtonPressed
= true;
871 d
->initialPressPosition
= event
->pos();
872 d
->initialPressPosition
.setY(d
->initialPressPosition
.y() +
874 d
->initialPressPosition
.setX(d
->initialPressPosition
.x() +
878 QListView::mousePressEvent(event
);
881 void KCategorizedView::mouseReleaseEvent(QMouseEvent
*event
)
883 d
->mouseButtonPressed
= false;
885 QListView::mouseReleaseEvent(event
);
887 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
893 QPoint initialPressPosition
= viewport()->mapFromGlobal(QCursor::pos());
894 initialPressPosition
.setY(initialPressPosition
.y() + verticalOffset());
895 initialPressPosition
.setX(initialPressPosition
.x() + horizontalOffset());
897 QItemSelection selection
;
899 if (initialPressPosition
== d
->initialPressPosition
)
901 foreach(const QString
&category
, d
->categories
)
903 if (d
->categoryVisualRect(category
).contains(event
->pos()))
905 foreach (const QModelIndex
&index
, d
->categoriesIndexes
[category
])
907 selection
<< QItemSelectionRange(d
->proxyModel
->mapFromSource(index
));
910 selectionModel()->select(selection
, QItemSelectionModel::Select
);
917 d
->lastSelection
= selectionModel()->selection();
919 if (d
->hovered
.isValid())
920 viewport()->update(d
->visualRect(d
->hovered
));
921 else if (!d
->hoveredCategory
.isEmpty())
922 viewport()->update(d
->categoryVisualRect(d
->hoveredCategory
));
925 void KCategorizedView::leaveEvent(QEvent
*event
)
927 d
->hovered
= QModelIndex();
928 d
->hoveredCategory
= QString();
930 QListView::leaveEvent(event
);
933 void KCategorizedView::startDrag(Qt::DropActions supportedActions
)
935 QListView::startDrag(supportedActions
);
937 d
->isDragging
= false;
938 d
->mouseButtonPressed
= false;
940 viewport()->update(d
->lastDraggedItemsRect
);
943 void KCategorizedView::dragMoveEvent(QDragMoveEvent
*event
)
945 d
->mousePosition
= event
->pos();
947 if (d
->mouseButtonPressed
)
949 d
->isDragging
= true;
953 d
->isDragging
= false;
956 d
->dragLeftViewport
= false;
958 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
961 QListView::dragMoveEvent(event
);
965 d
->drawDraggedItems();
968 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent
*event
)
970 d
->dragLeftViewport
= true;
972 QListView::dragLeaveEvent(event
);
975 QModelIndex
KCategorizedView::moveCursor(CursorAction cursorAction
,
976 Qt::KeyboardModifiers modifiers
)
978 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
981 return QListView::moveCursor(cursorAction
, modifiers
);
984 const QModelIndex current
= selectionModel()->currentIndex();
986 int viewportWidth
= viewport()->width() - spacing();
989 if (gridSize().isEmpty())
991 itemWidth
= d
->biggestItemSize
.width();
995 itemWidth
= gridSize().width();
998 int itemWidthPlusSeparation
= spacing() + itemWidth
;
999 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
1001 QString lastCategory
= d
->categories
[0];
1002 QString theCategory
= d
->categories
[0];
1003 QString afterCategory
= d
->categories
[0];
1004 bool hasToBreak
= false;
1005 foreach (const QString
&category
, d
->categories
)
1009 afterCategory
= category
;
1014 if (category
== d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].category
)
1016 theCategory
= category
;
1023 lastCategory
= category
;
1027 switch (cursorAction
)
1029 case QAbstractItemView::MoveUp
: {
1030 if (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
>= elementsPerRow
)
1032 int indexToMove
= d
->invertedElementDictionary
[current
].row();
1033 indexToMove
-= qMin(((d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
) + d
->forcedSelectionPosition
), elementsPerRow
- d
->forcedSelectionPosition
+ (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
% elementsPerRow
));
1035 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1039 int lastCategoryLastRow
= (d
->categoriesIndexes
[lastCategory
].count() - 1) % elementsPerRow
;
1040 int indexToMove
= d
->invertedElementDictionary
[current
].row() - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
;
1042 if (d
->forcedSelectionPosition
>= lastCategoryLastRow
)
1048 indexToMove
-= qMin((lastCategoryLastRow
- d
->forcedSelectionPosition
+ 1), d
->forcedSelectionPosition
+ elementsPerRow
+ 1);
1051 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1055 case QAbstractItemView::MoveDown
: {
1056 if (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
< (d
->categoriesIndexes
[theCategory
].count() - 1 - ((d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
)))
1058 int indexToMove
= d
->invertedElementDictionary
[current
].row();
1059 indexToMove
+= qMin(elementsPerRow
, d
->categoriesIndexes
[theCategory
].count() - 1 - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
);
1061 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1065 int afterCategoryLastRow
= qMin(elementsPerRow
, d
->categoriesIndexes
[afterCategory
].count());
1066 int indexToMove
= d
->invertedElementDictionary
[current
].row() + (d
->categoriesIndexes
[theCategory
].count() - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
);
1068 if (d
->forcedSelectionPosition
>= afterCategoryLastRow
)
1070 indexToMove
+= afterCategoryLastRow
- 1;
1074 indexToMove
+= qMin(d
->forcedSelectionPosition
, elementsPerRow
);
1077 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1081 case QAbstractItemView::MoveLeft
:
1082 d
->forcedSelectionPosition
= d
->elementsInfo
[d
->proxyModel
->mapToSource(d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() - 1, 0)])].relativeOffsetToCategory
% elementsPerRow
;
1084 if (d
->forcedSelectionPosition
< 0)
1085 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1087 return d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() - 1, 0)];
1089 case QAbstractItemView::MoveRight
:
1090 d
->forcedSelectionPosition
= d
->elementsInfo
[d
->proxyModel
->mapToSource(d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() + 1, 0)])].relativeOffsetToCategory
% elementsPerRow
;
1092 if (d
->forcedSelectionPosition
< 0)
1093 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1095 return d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() + 1, 0)];
1101 return QListView::moveCursor(cursorAction
, modifiers
);
1104 void KCategorizedView::rowsInserted(const QModelIndex
&parent
,
1108 QListView::rowsInserted(parent
, start
, end
);
1110 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1111 !d
->itemCategorizer
)
1113 d
->lastSelection
= QItemSelection();
1114 d
->currentViewIndex
= QModelIndex();
1115 d
->forcedSelectionPosition
= 0;
1116 d
->elementsInfo
.clear();
1117 d
->elementsPosition
.clear();
1118 d
->elementDictionary
.clear();
1119 d
->invertedElementDictionary
.clear();
1120 d
->categoriesIndexes
.clear();
1121 d
->categoriesPosition
.clear();
1122 d
->categories
.clear();
1123 d
->intersectedIndexes
.clear();
1124 d
->sourceModelIndexList
.clear();
1125 d
->hovered
= QModelIndex();
1126 d
->biggestItemSize
= QSize(0, 0);
1127 d
->mouseButtonPressed
= false;
1132 rowsInsertedArtifficial(parent
, start
, end
);
1135 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex
&parent
,
1141 d
->lastSelection
= QItemSelection();
1142 d
->currentViewIndex
= QModelIndex();
1143 d
->forcedSelectionPosition
= 0;
1144 d
->elementsInfo
.clear();
1145 d
->elementsPosition
.clear();
1146 d
->elementDictionary
.clear();
1147 d
->invertedElementDictionary
.clear();
1148 d
->categoriesIndexes
.clear();
1149 d
->categoriesPosition
.clear();
1150 d
->categories
.clear();
1151 d
->intersectedIndexes
.clear();
1152 d
->sourceModelIndexList
.clear();
1153 d
->hovered
= QModelIndex();
1154 d
->biggestItemSize
= QSize(0, 0);
1155 d
->mouseButtonPressed
= false;
1157 if (start
> end
|| end
< 0 || start
< 0 || !d
->proxyModel
->rowCount())
1162 // Add all elements mapped to the source model
1163 for (int k
= 0; k
< d
->proxyModel
->rowCount(); k
++)
1165 d
->biggestItemSize
= QSize(qMax(sizeHintForIndex(d
->proxyModel
->index(k
, 0)).width(),
1166 d
->biggestItemSize
.width()),
1167 qMax(sizeHintForIndex(d
->proxyModel
->index(k
, 0)).height(),
1168 d
->biggestItemSize
.height()));
1170 d
->sourceModelIndexList
<<
1171 d
->proxyModel
->mapToSource(d
->proxyModel
->index(k
, 0));
1174 // Sort them with the general purpose lessThan method
1175 LessThan
generalLessThan(d
->proxyModel
,
1176 LessThan::GeneralPurpose
);
1178 qStableSort(d
->sourceModelIndexList
.begin(), d
->sourceModelIndexList
.end(),
1181 // Explore categories
1182 QString prevCategory
=
1183 d
->itemCategorizer
->categoryForItem(d
->sourceModelIndexList
[0],
1184 d
->proxyModel
->sortRole());
1185 QString lastCategory
= prevCategory
;
1186 QModelIndexList modelIndexList
;
1187 struct Private::ElementInfo elementInfo
;
1188 foreach (const QModelIndex
&index
, d
->sourceModelIndexList
)
1190 lastCategory
= d
->itemCategorizer
->categoryForItem(index
,
1191 d
->proxyModel
->sortRole());
1193 elementInfo
.category
= lastCategory
;
1195 if (prevCategory
!= lastCategory
)
1197 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1198 d
->categories
<< prevCategory
;
1199 modelIndexList
.clear();
1202 modelIndexList
<< index
;
1203 prevCategory
= lastCategory
;
1205 d
->elementsInfo
.insert(index
, elementInfo
);
1208 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1209 d
->categories
<< prevCategory
;
1211 // Sort items locally in their respective categories with the category
1213 LessThan
categoryLessThan(d
->proxyModel
,
1214 LessThan::CategoryPurpose
);
1216 foreach (const QString
&key
, d
->categories
)
1218 QModelIndexList
&indexList
= d
->categoriesIndexes
[key
];
1220 qStableSort(indexList
.begin(), indexList
.end(), categoryLessThan
);
1223 d
->lastIndex
= d
->categoriesIndexes
[d
->categories
[d
->categories
.count() - 1]][d
->categoriesIndexes
[d
->categories
[d
->categories
.count() - 1]].count() - 1];
1225 // Finally, fill data information of items situation. This will help when
1226 // trying to compute an item place in the viewport
1227 int i
= 0; // position relative to the category beginning
1228 int j
= 0; // number of elements before current
1229 foreach (const QString
&key
, d
->categories
)
1231 foreach (const QModelIndex
&index
, d
->categoriesIndexes
[key
])
1233 struct Private::ElementInfo
&elementInfo
= d
->elementsInfo
[index
];
1235 elementInfo
.relativeOffsetToCategory
= i
;
1237 d
->elementDictionary
.insert(d
->proxyModel
->index(j
, 0),
1238 d
->proxyModel
->mapFromSource(index
));
1240 d
->invertedElementDictionary
.insert(d
->proxyModel
->mapFromSource(index
),
1241 d
->proxyModel
->index(j
, 0));
1250 d
->updateScrollbars();
1253 void KCategorizedView::rowsRemoved(const QModelIndex
&parent
,
1257 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1260 // Force the view to update all elements
1261 rowsInsertedArtifficial(parent
, start
, end
);
1265 void KCategorizedView::updateGeometries()
1267 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1268 !d
->itemCategorizer
)
1270 QListView::updateGeometries();
1274 // Avoid QListView::updateGeometries(), since it will try to set another
1275 // range to our scroll bars, what we don't want (ereslibre)
1276 QAbstractItemView::updateGeometries();
1279 void KCategorizedView::slotSortingRoleChanged()
1281 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1284 // Force the view to update all elements
1285 rowsInsertedArtifficial(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
1289 #include "kcategorizedview.moc"