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)
89 KCategorizedView::Private::~Private()
93 const QModelIndexList
&KCategorizedView::Private::intersectionSet(const QRect
&rect
)
96 QRect indexVisualRect
;
98 intersectedIndexes
.clear();
102 if (listView
->gridSize().isEmpty())
104 itemHeight
= biggestItemSize
.height();
108 itemHeight
= listView
->gridSize().height();
111 // Lets find out where we should start
112 int top
= proxyModel
->rowCount() - 1;
114 int middle
= (top
+ bottom
) / 2;
115 while (bottom
<= top
)
117 middle
= (top
+ bottom
) / 2;
119 index
= elementDictionary
[proxyModel
->index(middle
, 0)];
120 indexVisualRect
= visualRect(index
);
121 // We need the whole height (not only the visualRect). This will help us to update
122 // all needed indexes correctly (ereslibre)
123 indexVisualRect
.setHeight(indexVisualRect
.height() + (itemHeight
- indexVisualRect
.height()));
125 if (qMax(indexVisualRect
.topLeft().y(),
126 indexVisualRect
.bottomRight().y()) < qMin(rect
.topLeft().y(),
127 rect
.bottomRight().y()))
137 for (int i
= middle
; i
< proxyModel
->rowCount(); i
++)
139 index
= elementDictionary
[proxyModel
->index(i
, 0)];
140 indexVisualRect
= visualRect(index
);
142 if (rect
.intersects(indexVisualRect
))
143 intersectedIndexes
.append(index
);
145 // If we passed next item, stop searching for hits
146 if (qMax(rect
.bottomRight().y(), rect
.topLeft().y()) <
147 qMin(indexVisualRect
.topLeft().y(),
148 indexVisualRect
.bottomRight().y()))
152 return intersectedIndexes
;
155 QRect
KCategorizedView::Private::visualRectInViewport(const QModelIndex
&index
) const
157 if (!index
.isValid())
160 QString curCategory
= elementsInfo
[index
].category
;
162 QRect
retRect(listView
->spacing(), listView
->spacing() * 2 +
163 itemCategorizer
->categoryHeight(listView
->viewOptions()), 0, 0);
165 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
170 if (listView
->gridSize().isEmpty())
172 itemHeight
= biggestItemSize
.height();
173 itemWidth
= biggestItemSize
.width();
177 itemHeight
= listView
->gridSize().height();
178 itemWidth
= listView
->gridSize().width();
181 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
182 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
186 int column
= elementsInfo
[index
].relativeOffsetToCategory
% elementsPerRow
;
187 int row
= elementsInfo
[index
].relativeOffsetToCategory
/ elementsPerRow
;
189 retRect
.setLeft(retRect
.left() + column
* listView
->spacing() +
192 foreach (const QString
&category
, categories
)
194 if (category
== curCategory
)
197 float rows
= (float) ((float) categoriesIndexes
[category
].count() /
198 (float) elementsPerRow
);
199 int rowsInt
= categoriesIndexes
[category
].count() / elementsPerRow
;
201 if (rows
- trunc(rows
)) rowsInt
++;
203 retRect
.setTop(retRect
.top() +
204 (rowsInt
* itemHeight
) +
205 itemCategorizer
->categoryHeight(listView
->viewOptions()) +
206 listView
->spacing() * 2);
208 if (listView
->gridSize().isEmpty())
210 retRect
.setTop(retRect
.top() +
211 (rowsInt
* listView
->spacing()));
216 if (listView
->gridSize().isEmpty())
218 retRect
.setTop(retRect
.top() + row
* listView
->spacing() +
223 retRect
.setTop(retRect
.top() + (row
* itemHeight
));
226 retRect
.setWidth(itemWidth
);
228 if (listView
->gridSize().isEmpty())
230 retRect
.setHeight(listView
->sizeHintForIndex(proxyModel
->mapFromSource(index
)).height());
234 retRect
.setHeight(qMin(listView
->sizeHintForIndex(proxyModel
->mapFromSource(index
)).height(),
235 listView
->gridSize().height()));
241 QRect
KCategorizedView::Private::visualCategoryRectInViewport(const QString
&category
)
244 QRect
retRect(listView
->spacing(),
246 listView
->viewport()->width() - listView
->spacing() * 2,
249 if (!proxyModel
->rowCount() || !categories
.contains(category
))
252 QModelIndex index
= proxyModel
->index(0, 0, QModelIndex());
254 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
259 if (listView
->gridSize().isEmpty())
261 itemHeight
= biggestItemSize
.height();
262 itemWidth
= biggestItemSize
.width();
266 itemHeight
= listView
->gridSize().height();
267 itemWidth
= listView
->gridSize().width();
270 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
271 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
276 foreach (const QString
&itCategory
, categories
)
278 if (itCategory
== category
)
281 float rows
= (float) ((float) categoriesIndexes
[itCategory
].count() /
282 (float) elementsPerRow
);
283 int rowsInt
= categoriesIndexes
[itCategory
].count() / elementsPerRow
;
285 if (rows
- trunc(rows
)) rowsInt
++;
287 retRect
.setTop(retRect
.top() +
288 (rowsInt
* itemHeight
) +
289 itemCategorizer
->categoryHeight(listView
->viewOptions()) +
290 listView
->spacing() * 2);
292 if (listView
->gridSize().isEmpty())
294 retRect
.setTop(retRect
.top() +
295 (rowsInt
* listView
->spacing()));
299 retRect
.setHeight(itemCategorizer
->categoryHeight(listView
->viewOptions()));
304 // We're sure elementsPosition doesn't contain index
305 const QRect
&KCategorizedView::Private::cacheIndex(const QModelIndex
&index
)
307 QRect rect
= visualRectInViewport(index
);
308 elementsPosition
[index
] = rect
;
310 return elementsPosition
[index
];
313 // We're sure categoriesPosition doesn't contain category
314 const QRect
&KCategorizedView::Private::cacheCategory(const QString
&category
)
316 QRect rect
= visualCategoryRectInViewport(category
);
317 categoriesPosition
[category
] = rect
;
319 return categoriesPosition
[category
];
322 const QRect
&KCategorizedView::Private::cachedRectIndex(const QModelIndex
&index
)
324 if (elementsPosition
.contains(index
)) // If we have it cached
326 return elementsPosition
[index
];
328 else // Otherwise, cache it
330 return cacheIndex(index
);
334 const QRect
&KCategorizedView::Private::cachedRectCategory(const QString
&category
)
336 if (categoriesPosition
.contains(category
)) // If we have it cached
338 return categoriesPosition
[category
];
340 else // Otherwise, cache it and
342 return cacheCategory(category
);
346 QRect
KCategorizedView::Private::visualRect(const QModelIndex
&index
)
348 QModelIndex mappedIndex
= proxyModel
->mapToSource(index
);
350 QRect retRect
= cachedRectIndex(mappedIndex
);
351 int dx
= -listView
->horizontalOffset();
352 int dy
= -listView
->verticalOffset();
353 retRect
.adjust(dx
, dy
, dx
, dy
);
358 QRect
KCategorizedView::Private::categoryVisualRect(const QString
&category
)
360 QRect retRect
= cachedRectCategory(category
);
361 int dx
= -listView
->horizontalOffset();
362 int dy
= -listView
->verticalOffset();
363 retRect
.adjust(dx
, dy
, dx
, dy
);
368 void KCategorizedView::Private::drawNewCategory(const QModelIndex
&index
,
370 const QStyleOption
&option
,
373 QStyleOption optionCopy
= option
;
374 const QString category
= itemCategorizer
->categoryForItem(index
, sortRole
);
376 if ((category
== hoveredCategory
) && !mouseButtonPressed
)
378 optionCopy
.state
|= QStyle::State_MouseOver
;
381 itemCategorizer
->drawCategory(index
,
388 void KCategorizedView::Private::updateScrollbars()
390 // find the last index in the last category
391 QModelIndex lastIndex
= categoriesIndexes
.isEmpty() ? QModelIndex() : categoriesIndexes
[categories
.last()].last();
393 int lastItemBottom
= cachedRectIndex(lastIndex
).top() +
394 listView
->spacing() + (listView
->gridSize().isEmpty() ? 0 : listView
->gridSize().height()) - listView
->viewport()->height();
396 listView
->verticalScrollBar()->setSingleStep(listView
->viewport()->height() / 10);
397 listView
->verticalScrollBar()->setPageStep(listView
->viewport()->height());
398 listView
->verticalScrollBar()->setRange(0, lastItemBottom
);
401 void KCategorizedView::Private::drawDraggedItems(QPainter
*painter
)
403 QStyleOptionViewItemV3 option
= listView
->viewOptions();
404 option
.state
&= ~QStyle::State_MouseOver
;
405 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
407 const int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
408 const int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
410 option
.rect
= visualRect(index
);
411 option
.rect
.adjust(dx
, dy
, dx
, dy
);
413 if (option
.rect
.intersects(listView
->viewport()->rect()))
415 listView
->itemDelegate(index
)->paint(painter
, option
, index
);
420 void KCategorizedView::Private::drawDraggedItems()
424 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
426 int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
427 int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
429 currentRect
= visualRect(index
);
430 currentRect
.adjust(dx
, dy
, dx
, dy
);
432 if (currentRect
.intersects(listView
->viewport()->rect()))
434 rectToUpdate
= rectToUpdate
.united(currentRect
);
438 listView
->viewport()->update(lastDraggedItemsRect
.united(rectToUpdate
));
440 lastDraggedItemsRect
= rectToUpdate
;
444 //==============================================================================
447 KCategorizedView::KCategorizedView(QWidget
*parent
)
449 , d(new Private(this))
453 KCategorizedView::~KCategorizedView()
458 void KCategorizedView::setModel(QAbstractItemModel
*model
)
460 d
->lastSelection
= QItemSelection();
461 d
->currentViewIndex
= QModelIndex();
462 d
->forcedSelectionPosition
= 0;
463 d
->elementsInfo
.clear();
464 d
->elementsPosition
.clear();
465 d
->elementDictionary
.clear();
466 d
->invertedElementDictionary
.clear();
467 d
->categoriesIndexes
.clear();
468 d
->categoriesPosition
.clear();
469 d
->categories
.clear();
470 d
->intersectedIndexes
.clear();
471 d
->sourceModelIndexList
.clear();
472 d
->hovered
= QModelIndex();
473 d
->mouseButtonPressed
= false;
477 QObject::disconnect(d
->proxyModel
,
478 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
479 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
481 QObject::disconnect(d
->proxyModel
,
482 SIGNAL(sortingRoleChanged()),
483 this, SLOT(slotSortingRoleChanged()));
486 QListView::setModel(model
);
488 d
->proxyModel
= dynamic_cast<KSortFilterProxyModel
*>(model
);
492 QObject::connect(d
->proxyModel
,
493 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
494 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
496 QObject::connect(d
->proxyModel
,
497 SIGNAL(sortingRoleChanged()),
498 this, SLOT(slotSortingRoleChanged()));
502 QRect
KCategorizedView::visualRect(const QModelIndex
&index
) const
504 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
507 return QListView::visualRect(index
);
510 if (!qobject_cast
<const QSortFilterProxyModel
*>(index
.model()))
512 return d
->visualRect(d
->proxyModel
->mapFromSource(index
));
515 return d
->visualRect(index
);
518 KItemCategorizer
*KCategorizedView::itemCategorizer() const
520 return d
->itemCategorizer
;
523 void KCategorizedView::setItemCategorizer(KItemCategorizer
*itemCategorizer
)
525 d
->lastSelection
= QItemSelection();
526 d
->currentViewIndex
= QModelIndex();
527 d
->forcedSelectionPosition
= 0;
528 d
->elementsInfo
.clear();
529 d
->elementsPosition
.clear();
530 d
->elementDictionary
.clear();
531 d
->invertedElementDictionary
.clear();
532 d
->categoriesIndexes
.clear();
533 d
->categoriesPosition
.clear();
534 d
->categories
.clear();
535 d
->intersectedIndexes
.clear();
536 d
->sourceModelIndexList
.clear();
537 d
->hovered
= QModelIndex();
538 d
->mouseButtonPressed
= false;
540 if (!itemCategorizer
&& d
->proxyModel
)
542 QObject::disconnect(d
->proxyModel
,
543 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
544 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
546 QObject::disconnect(d
->proxyModel
,
547 SIGNAL(sortingRoleChanged()),
548 this, SLOT(slotSortingRoleChanged()));
550 else if (itemCategorizer
&& d
->proxyModel
)
552 QObject::connect(d
->proxyModel
,
553 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
554 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
556 QObject::connect(d
->proxyModel
,
557 SIGNAL(sortingRoleChanged()),
558 this, SLOT(slotSortingRoleChanged()));
561 d
->itemCategorizer
= itemCategorizer
;
565 rowsInserted(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
573 QModelIndex
KCategorizedView::indexAt(const QPoint
&point
) const
575 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
578 return QListView::indexAt(point
);
583 QModelIndexList item
= d
->intersectionSet(QRect(point
, point
));
585 if (item
.count() == 1)
595 void KCategorizedView::reset()
599 d
->lastSelection
= QItemSelection();
600 d
->currentViewIndex
= QModelIndex();
601 d
->forcedSelectionPosition
= 0;
602 d
->elementsInfo
.clear();
603 d
->elementsPosition
.clear();
604 d
->elementDictionary
.clear();
605 d
->invertedElementDictionary
.clear();
606 d
->categoriesIndexes
.clear();
607 d
->categoriesPosition
.clear();
608 d
->categories
.clear();
609 d
->intersectedIndexes
.clear();
610 d
->sourceModelIndexList
.clear();
611 d
->hovered
= QModelIndex();
612 d
->biggestItemSize
= QSize(0, 0);
613 d
->mouseButtonPressed
= false;
616 void KCategorizedView::paintEvent(QPaintEvent
*event
)
618 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
621 QListView::paintEvent(event
);
625 QStyleOptionViewItemV3 option
= viewOptions();
626 option
.widget
= this;
629 option
.features
|= QStyleOptionViewItemV2::WrapText
;
632 QPainter
painter(viewport());
633 QRect area
= event
->rect();
634 const bool focus
= (hasFocus() || viewport()->hasFocus()) &&
635 currentIndex().isValid();
636 const QStyle::State state
= option
.state
;
637 const bool enabled
= (state
& QStyle::State_Enabled
) != 0;
641 QModelIndexList dirtyIndexes
= d
->intersectionSet(area
);
642 foreach (const QModelIndex
&index
, dirtyIndexes
)
644 option
.state
= state
;
645 option
.rect
= visualRect(index
);
647 if (selectionModel() && selectionModel()->isSelected(index
))
649 option
.state
|= QStyle::State_Selected
;
654 QPalette::ColorGroup cg
;
655 if ((d
->proxyModel
->flags(index
) & Qt::ItemIsEnabled
) == 0)
657 option
.state
&= ~QStyle::State_Enabled
;
658 cg
= QPalette::Disabled
;
662 cg
= QPalette::Normal
;
664 option
.palette
.setCurrentColorGroup(cg
);
667 if (focus
&& currentIndex() == index
)
669 option
.state
|= QStyle::State_HasFocus
;
670 if (this->state() == EditingState
)
671 option
.state
|= QStyle::State_Editing
;
674 if ((index
== d
->hovered
) && !d
->mouseButtonPressed
)
675 option
.state
|= QStyle::State_MouseOver
;
677 option
.state
&= ~QStyle::State_MouseOver
;
679 itemDelegate(index
)->paint(&painter
, option
, index
);
683 QStyleOptionViewItem otherOption
;
684 foreach (const QString
&category
, d
->categories
)
686 otherOption
= option
;
687 otherOption
.rect
= d
->categoryVisualRect(category
);
688 otherOption
.state
&= ~QStyle::State_MouseOver
;
690 if (otherOption
.rect
.intersects(area
))
692 d
->drawNewCategory(d
->categoriesIndexes
[category
][0],
693 d
->proxyModel
->sortRole(), otherOption
, &painter
);
697 if (d
->mouseButtonPressed
&& !d
->isDragging
)
699 QPoint start
, end
, initialPressPosition
;
701 initialPressPosition
= d
->initialPressPosition
;
703 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
704 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
706 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
707 d
->initialPressPosition
.y() > d
->mousePosition
.y())
709 start
= d
->mousePosition
;
710 end
= initialPressPosition
;
714 start
= initialPressPosition
;
715 end
= d
->mousePosition
;
718 QStyleOptionRubberBand yetAnotherOption
;
719 yetAnotherOption
.initFrom(this);
720 yetAnotherOption
.shape
= QRubberBand::Rectangle
;
721 yetAnotherOption
.opaque
= false;
722 yetAnotherOption
.rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
724 style()->drawControl(QStyle::CE_RubberBand
, &yetAnotherOption
, &painter
);
728 if (d
->isDragging
&& !d
->dragLeftViewport
)
730 painter
.setOpacity(0.5);
731 d
->drawDraggedItems(&painter
);
737 void KCategorizedView::resizeEvent(QResizeEvent
*event
)
739 QListView::resizeEvent(event
);
741 // Clear the items positions cache
742 d
->elementsPosition
.clear();
743 d
->categoriesPosition
.clear();
744 d
->forcedSelectionPosition
= 0;
746 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
752 d
->updateScrollbars();
755 void KCategorizedView::setSelection(const QRect
&rect
,
756 QItemSelectionModel::SelectionFlags flags
)
758 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
761 QListView::setSelection(rect
, flags
);
768 selectionModel()->clear();
770 if (flags
& QItemSelectionModel::Clear
)
772 d
->lastSelection
= QItemSelection();
775 QModelIndexList dirtyIndexes
= d
->intersectionSet(rect
);
777 QItemSelection selection
;
779 if (!dirtyIndexes
.count())
781 if (d
->lastSelection
.count())
783 selectionModel()->select(d
->lastSelection
, flags
);
789 if (!d
->mouseButtonPressed
)
791 selection
= QItemSelection(dirtyIndexes
[0], dirtyIndexes
[0]);
792 d
->currentViewIndex
= dirtyIndexes
[0];
796 QModelIndex first
= dirtyIndexes
[0];
798 foreach (const QModelIndex
&index
, dirtyIndexes
)
800 if (last
.isValid() && last
.row() + 1 != index
.row())
802 QItemSelectionRange
range(first
, last
);
813 selection
<< QItemSelectionRange(first
, last
);
816 if (d
->lastSelection
.count() && !d
->mouseButtonPressed
)
818 selection
.merge(d
->lastSelection
, flags
);
820 else if (d
->lastSelection
.count())
822 selection
.merge(d
->lastSelection
, QItemSelectionModel::Select
);
825 selectionModel()->select(selection
, flags
);
828 void KCategorizedView::mouseMoveEvent(QMouseEvent
*event
)
830 QListView::mouseMoveEvent(event
);
832 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
838 const QString previousHoveredCategory
= d
->hoveredCategory
;
840 d
->mousePosition
= event
->pos();
841 d
->hoveredCategory
= QString();
844 foreach (const QString
&category
, d
->categories
)
846 if (d
->categoryVisualRect(category
).intersects(QRect(event
->pos(), event
->pos())))
848 d
->hoveredCategory
= category
;
849 viewport()->update(d
->categoryVisualRect(category
));
851 else if ((category
== previousHoveredCategory
) &&
852 (!d
->categoryVisualRect(previousHoveredCategory
).intersects(QRect(event
->pos(), event
->pos()))))
854 viewport()->update(d
->categoryVisualRect(category
));
859 if (d
->mouseButtonPressed
&& !d
->isDragging
)
861 QPoint start
, end
, initialPressPosition
;
863 initialPressPosition
= d
->initialPressPosition
;
865 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
866 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
868 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
869 d
->initialPressPosition
.y() > d
->mousePosition
.y())
871 start
= d
->mousePosition
;
872 end
= initialPressPosition
;
876 start
= initialPressPosition
;
877 end
= d
->mousePosition
;
880 rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
882 //viewport()->update(rect.united(d->lastSelectionRect));
884 d
->lastSelectionRect
= rect
;
888 void KCategorizedView::mousePressEvent(QMouseEvent
*event
)
890 d
->dragLeftViewport
= false;
892 if (event
->button() == Qt::LeftButton
)
894 d
->mouseButtonPressed
= true;
896 d
->initialPressPosition
= event
->pos();
897 d
->initialPressPosition
.setY(d
->initialPressPosition
.y() +
899 d
->initialPressPosition
.setX(d
->initialPressPosition
.x() +
903 QListView::mousePressEvent(event
);
906 void KCategorizedView::mouseReleaseEvent(QMouseEvent
*event
)
908 d
->mouseButtonPressed
= false;
910 QListView::mouseReleaseEvent(event
);
912 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
918 QPoint initialPressPosition
= viewport()->mapFromGlobal(QCursor::pos());
919 initialPressPosition
.setY(initialPressPosition
.y() + verticalOffset());
920 initialPressPosition
.setX(initialPressPosition
.x() + horizontalOffset());
922 QItemSelection selection
;
924 if (initialPressPosition
== d
->initialPressPosition
)
926 foreach(const QString
&category
, d
->categories
)
928 if (d
->categoryVisualRect(category
).contains(event
->pos()))
930 foreach (const QModelIndex
&index
, d
->categoriesIndexes
[category
])
932 selection
<< QItemSelectionRange(d
->proxyModel
->mapFromSource(index
));
935 selectionModel()->select(selection
, QItemSelectionModel::Select
);
942 d
->lastSelection
= selectionModel()->selection();
944 if (d
->hovered
.isValid())
945 viewport()->update(visualRect(d
->hovered
));
946 else if (!d
->hoveredCategory
.isEmpty())
947 viewport()->update(d
->categoryVisualRect(d
->hoveredCategory
));
950 void KCategorizedView::leaveEvent(QEvent
*event
)
952 d
->hovered
= QModelIndex();
953 d
->hoveredCategory
= QString();
955 QListView::leaveEvent(event
);
958 void KCategorizedView::startDrag(Qt::DropActions supportedActions
)
960 QListView::startDrag(supportedActions
);
962 d
->isDragging
= false;
963 d
->mouseButtonPressed
= false;
965 viewport()->update(d
->lastDraggedItemsRect
);
968 void KCategorizedView::dragMoveEvent(QDragMoveEvent
*event
)
970 d
->mousePosition
= event
->pos();
972 if (d
->mouseButtonPressed
)
974 d
->isDragging
= true;
978 d
->isDragging
= false;
981 d
->dragLeftViewport
= false;
983 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
986 QListView::dragMoveEvent(event
);
990 d
->drawDraggedItems();
993 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent
*event
)
995 d
->dragLeftViewport
= true;
997 QListView::dragLeaveEvent(event
);
1000 QModelIndex
KCategorizedView::moveCursor(CursorAction cursorAction
,
1001 Qt::KeyboardModifiers modifiers
)
1003 if ( (viewMode() != KCategorizedView::IconMode
) ||
1005 !d
->itemCategorizer
||
1006 d
->categories
.isEmpty()
1009 return QListView::moveCursor(cursorAction
, modifiers
);
1012 const QModelIndex current
= selectionModel()->currentIndex();
1014 int viewportWidth
= viewport()->width() - spacing();
1017 if (gridSize().isEmpty())
1019 itemWidth
= d
->biggestItemSize
.width();
1023 itemWidth
= gridSize().width();
1026 int itemWidthPlusSeparation
= spacing() + itemWidth
;
1027 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
1029 QString lastCategory
= d
->categories
.first();
1030 QString theCategory
= d
->categories
.first();
1031 QString afterCategory
= d
->categories
.first();
1033 bool hasToBreak
= false;
1034 foreach (const QString
&category
, d
->categories
)
1038 afterCategory
= category
;
1043 if (category
== d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].category
)
1045 theCategory
= category
;
1052 lastCategory
= category
;
1056 switch (cursorAction
)
1058 case QAbstractItemView::MoveUp
: {
1059 if (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
>= elementsPerRow
)
1061 int indexToMove
= d
->invertedElementDictionary
[current
].row();
1062 indexToMove
-= qMin(((d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
) + d
->forcedSelectionPosition
), elementsPerRow
- d
->forcedSelectionPosition
+ (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
% elementsPerRow
));
1064 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1068 int lastCategoryLastRow
= (d
->categoriesIndexes
[lastCategory
].count() - 1) % elementsPerRow
;
1069 int indexToMove
= d
->invertedElementDictionary
[current
].row() - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
;
1071 if (d
->forcedSelectionPosition
>= lastCategoryLastRow
)
1077 indexToMove
-= qMin((lastCategoryLastRow
- d
->forcedSelectionPosition
+ 1), d
->forcedSelectionPosition
+ elementsPerRow
+ 1);
1080 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1084 case QAbstractItemView::MoveDown
: {
1085 if (d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
< (d
->categoriesIndexes
[theCategory
].count() - 1 - ((d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
)))
1087 int indexToMove
= d
->invertedElementDictionary
[current
].row();
1088 indexToMove
+= qMin(elementsPerRow
, d
->categoriesIndexes
[theCategory
].count() - 1 - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
);
1090 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1094 int afterCategoryLastRow
= qMin(elementsPerRow
, d
->categoriesIndexes
[afterCategory
].count());
1095 int indexToMove
= d
->invertedElementDictionary
[current
].row() + (d
->categoriesIndexes
[theCategory
].count() - d
->elementsInfo
[d
->proxyModel
->mapToSource(current
)].relativeOffsetToCategory
);
1097 if (d
->forcedSelectionPosition
>= afterCategoryLastRow
)
1099 indexToMove
+= afterCategoryLastRow
- 1;
1103 indexToMove
+= qMin(d
->forcedSelectionPosition
, elementsPerRow
);
1106 return d
->elementDictionary
[d
->proxyModel
->index(indexToMove
, 0)];
1110 case QAbstractItemView::MoveLeft
:
1111 d
->forcedSelectionPosition
= d
->elementsInfo
[d
->proxyModel
->mapToSource(d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() - 1, 0)])].relativeOffsetToCategory
% elementsPerRow
;
1113 if (d
->forcedSelectionPosition
< 0)
1114 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1116 return d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() - 1, 0)];
1118 case QAbstractItemView::MoveRight
:
1119 d
->forcedSelectionPosition
= d
->elementsInfo
[d
->proxyModel
->mapToSource(d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() + 1, 0)])].relativeOffsetToCategory
% elementsPerRow
;
1121 if (d
->forcedSelectionPosition
< 0)
1122 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1124 return d
->elementDictionary
[d
->proxyModel
->index(d
->invertedElementDictionary
[current
].row() + 1, 0)];
1130 return QListView::moveCursor(cursorAction
, modifiers
);
1133 void KCategorizedView::rowsInserted(const QModelIndex
&parent
,
1137 QListView::rowsInserted(parent
, start
, end
);
1139 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1140 !d
->itemCategorizer
)
1142 d
->lastSelection
= QItemSelection();
1143 d
->currentViewIndex
= QModelIndex();
1144 d
->forcedSelectionPosition
= 0;
1145 d
->elementsInfo
.clear();
1146 d
->elementsPosition
.clear();
1147 d
->elementDictionary
.clear();
1148 d
->invertedElementDictionary
.clear();
1149 d
->categoriesIndexes
.clear();
1150 d
->categoriesPosition
.clear();
1151 d
->categories
.clear();
1152 d
->intersectedIndexes
.clear();
1153 d
->sourceModelIndexList
.clear();
1154 d
->hovered
= QModelIndex();
1155 d
->biggestItemSize
= QSize(0, 0);
1156 d
->mouseButtonPressed
= false;
1161 rowsInsertedArtifficial(parent
, start
, end
);
1164 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex
&parent
,
1170 d
->lastSelection
= QItemSelection();
1171 d
->currentViewIndex
= QModelIndex();
1172 d
->forcedSelectionPosition
= 0;
1173 d
->elementsInfo
.clear();
1174 d
->elementsPosition
.clear();
1175 d
->elementDictionary
.clear();
1176 d
->invertedElementDictionary
.clear();
1177 d
->categoriesIndexes
.clear();
1178 d
->categoriesPosition
.clear();
1179 d
->categories
.clear();
1180 d
->intersectedIndexes
.clear();
1181 d
->sourceModelIndexList
.clear();
1182 d
->hovered
= QModelIndex();
1183 d
->biggestItemSize
= QSize(0, 0);
1184 d
->mouseButtonPressed
= false;
1186 if (start
> end
|| end
< 0 || start
< 0 || !d
->proxyModel
->rowCount())
1191 // Add all elements mapped to the source model
1192 for (int k
= 0; k
< d
->proxyModel
->rowCount(); k
++)
1194 d
->biggestItemSize
= QSize(qMax(sizeHintForIndex(d
->proxyModel
->index(k
, 0)).width(),
1195 d
->biggestItemSize
.width()),
1196 qMax(sizeHintForIndex(d
->proxyModel
->index(k
, 0)).height(),
1197 d
->biggestItemSize
.height()));
1199 d
->sourceModelIndexList
<<
1200 d
->proxyModel
->mapToSource(d
->proxyModel
->index(k
, 0));
1203 // Sort them with the general purpose lessThan method
1204 LessThan
generalLessThan(d
->proxyModel
,
1205 LessThan::GeneralPurpose
);
1207 qStableSort(d
->sourceModelIndexList
.begin(), d
->sourceModelIndexList
.end(),
1210 // Explore categories
1211 QString prevCategory
=
1212 d
->itemCategorizer
->categoryForItem(d
->sourceModelIndexList
[0],
1213 d
->proxyModel
->sortRole());
1214 QString lastCategory
= prevCategory
;
1215 QModelIndexList modelIndexList
;
1216 struct Private::ElementInfo elementInfo
;
1217 foreach (const QModelIndex
&index
, d
->sourceModelIndexList
)
1219 lastCategory
= d
->itemCategorizer
->categoryForItem(index
,
1220 d
->proxyModel
->sortRole());
1222 elementInfo
.category
= lastCategory
;
1224 if (prevCategory
!= lastCategory
)
1226 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1227 d
->categories
<< prevCategory
;
1228 modelIndexList
.clear();
1231 modelIndexList
<< index
;
1232 prevCategory
= lastCategory
;
1234 d
->elementsInfo
.insert(index
, elementInfo
);
1237 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1238 d
->categories
<< prevCategory
;
1240 // Sort items locally in their respective categories with the category
1242 LessThan
categoryLessThan(d
->proxyModel
,
1243 LessThan::CategoryPurpose
);
1245 foreach (const QString
&key
, d
->categories
)
1247 QModelIndexList
&indexList
= d
->categoriesIndexes
[key
];
1249 qStableSort(indexList
.begin(), indexList
.end(), categoryLessThan
);
1252 // Finally, fill data information of items situation. This will help when
1253 // trying to compute an item place in the viewport
1254 int i
= 0; // position relative to the category beginning
1255 int j
= 0; // number of elements before current
1256 foreach (const QString
&key
, d
->categories
)
1258 foreach (const QModelIndex
&index
, d
->categoriesIndexes
[key
])
1260 struct Private::ElementInfo
&elementInfo
= d
->elementsInfo
[index
];
1262 elementInfo
.relativeOffsetToCategory
= i
;
1264 d
->elementDictionary
.insert(d
->proxyModel
->index(j
, 0),
1265 d
->proxyModel
->mapFromSource(index
));
1267 d
->invertedElementDictionary
.insert(d
->proxyModel
->mapFromSource(index
),
1268 d
->proxyModel
->index(j
, 0));
1277 d
->updateScrollbars();
1280 void KCategorizedView::rowsRemoved(const QModelIndex
&parent
,
1284 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1287 // Force the view to update all elements
1288 rowsInsertedArtifficial(parent
, start
, end
);
1292 void KCategorizedView::updateGeometries()
1294 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1295 !d
->itemCategorizer
)
1297 QListView::updateGeometries();
1301 // Avoid QListView::updateGeometries(), since it will try to set another
1302 // range to our scroll bars, what we don't want (ereslibre)
1303 QAbstractItemView::updateGeometries();
1306 void KCategorizedView::slotSortingRoleChanged()
1308 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1311 // Force the view to update all elements
1312 rowsInsertedArtifficial(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
1316 #include "kcategorizedview.moc"