2 * This file is part of the KDE project
3 * Copyright (C) 2007 Rafael Fernández López <ereslibre@kde.org>
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>
34 #include "kcategorydrawer.h"
35 #include "kcategorizedsortfilterproxymodel.h"
37 KCategorizedView::Private::Private(KCategorizedView
*listView
)
40 , biggestItemSize(QSize(0, 0))
41 , mouseButtonPressed(false)
43 , dragLeftViewport(false)
48 KCategorizedView::Private::~Private()
52 const QModelIndexList
&KCategorizedView::Private::intersectionSet(const QRect
&rect
)
55 QRect indexVisualRect
;
57 intersectedIndexes
.clear();
61 if (listView
->gridSize().isEmpty())
63 itemHeight
= biggestItemSize
.height();
67 itemHeight
= listView
->gridSize().height();
70 // Lets find out where we should start
71 int top
= proxyModel
->rowCount() - 1;
73 int middle
= (top
+ bottom
) / 2;
76 middle
= (top
+ bottom
) / 2;
78 index
= proxyModel
->index(middle
, 0);
79 indexVisualRect
= visualRect(index
);
80 // We need the whole height (not only the visualRect). This will help us to update
81 // all needed indexes correctly (ereslibre)
82 indexVisualRect
.setHeight(indexVisualRect
.height() + (itemHeight
- indexVisualRect
.height()));
84 if (qMax(indexVisualRect
.topLeft().y(),
85 indexVisualRect
.bottomRight().y()) < qMin(rect
.topLeft().y(),
86 rect
.bottomRight().y()))
96 for (int i
= middle
; i
< proxyModel
->rowCount(); i
++)
98 index
= proxyModel
->index(i
, 0);
99 indexVisualRect
= visualRect(index
);
101 if (rect
.intersects(indexVisualRect
))
102 intersectedIndexes
.append(index
);
104 // If we passed next item, stop searching for hits
105 if (qMax(rect
.bottomRight().y(), rect
.topLeft().y()) <
106 qMin(indexVisualRect
.topLeft().y(),
107 indexVisualRect
.bottomRight().y()))
111 return intersectedIndexes
;
114 QRect
KCategorizedView::Private::visualRectInViewport(const QModelIndex
&index
) const
116 if (!index
.isValid())
119 QString curCategory
= elementsInfo
[index
.row()].category
;
123 if (listView
->layoutDirection() == Qt::LeftToRight
)
125 retRect
= QRect(listView
->spacing(), listView
->spacing() * 2 +
126 categoryDrawer
->categoryHeight(listView
->viewOptions()), 0, 0);
130 retRect
= QRect(listView
->viewport()->width() - listView
->spacing(), listView
->spacing() * 2 +
131 categoryDrawer
->categoryHeight(listView
->viewOptions()), 0, 0);
134 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
139 if (listView
->gridSize().isEmpty())
141 itemHeight
= biggestItemSize
.height();
142 itemWidth
= biggestItemSize
.width();
146 itemHeight
= listView
->gridSize().height();
147 itemWidth
= listView
->gridSize().width();
150 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
151 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
155 int column
= elementsInfo
[index
.row()].relativeOffsetToCategory
% elementsPerRow
;
156 int row
= elementsInfo
[index
.row()].relativeOffsetToCategory
/ elementsPerRow
;
158 if (listView
->layoutDirection() == Qt::LeftToRight
)
160 retRect
.setLeft(retRect
.left() + column
* listView
->spacing() +
165 retRect
.setLeft(retRect
.right() - column
* listView
->spacing() -
166 column
* itemWidth
- itemWidth
);
168 retRect
.setRight(retRect
.right() - column
* listView
->spacing() -
172 foreach (const QString
&category
, categories
)
174 if (category
== curCategory
)
177 float rows
= (float) ((float) categoriesIndexes
[category
].count() /
178 (float) elementsPerRow
);
180 int rowsInt
= categoriesIndexes
[category
].count() / elementsPerRow
;
182 if (rows
- trunc(rows
)) rowsInt
++;
184 retRect
.setTop(retRect
.top() +
185 (rowsInt
* itemHeight
) +
186 categoryDrawer
->categoryHeight(listView
->viewOptions()) +
187 listView
->spacing() * 2);
189 if (listView
->gridSize().isEmpty())
191 retRect
.setTop(retRect
.top() +
192 (rowsInt
* listView
->spacing()));
196 if (listView
->gridSize().isEmpty())
198 retRect
.setTop(retRect
.top() + row
* listView
->spacing() +
203 retRect
.setTop(retRect
.top() + (row
* itemHeight
));
206 retRect
.setWidth(itemWidth
);
208 QModelIndex heightIndex
= proxyModel
->index(index
.row(), 0);
209 if (listView
->gridSize().isEmpty())
211 retRect
.setHeight(listView
->sizeHintForIndex(heightIndex
).height());
215 retRect
.setHeight(qMin(listView
->sizeHintForIndex(heightIndex
).height(),
216 listView
->gridSize().height()));
222 QRect
KCategorizedView::Private::visualCategoryRectInViewport(const QString
&category
)
225 QRect
retRect(listView
->spacing(),
227 listView
->viewport()->width() - listView
->spacing() * 2,
230 if (!proxyModel
->rowCount() || !categories
.contains(category
))
233 QModelIndex index
= proxyModel
->index(0, 0, QModelIndex());
235 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
240 if (listView
->gridSize().isEmpty())
242 itemHeight
= biggestItemSize
.height();
243 itemWidth
= biggestItemSize
.width();
247 itemHeight
= listView
->gridSize().height();
248 itemWidth
= listView
->gridSize().width();
251 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
252 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
257 foreach (const QString
&itCategory
, categories
)
259 if (itCategory
== category
)
262 float rows
= (float) ((float) categoriesIndexes
[itCategory
].count() /
263 (float) elementsPerRow
);
264 int rowsInt
= categoriesIndexes
[itCategory
].count() / elementsPerRow
;
266 if (rows
- trunc(rows
)) rowsInt
++;
268 retRect
.setTop(retRect
.top() +
269 (rowsInt
* itemHeight
) +
270 categoryDrawer
->categoryHeight(listView
->viewOptions()) +
271 listView
->spacing() * 2);
273 if (listView
->gridSize().isEmpty())
275 retRect
.setTop(retRect
.top() +
276 (rowsInt
* listView
->spacing()));
280 retRect
.setHeight(categoryDrawer
->categoryHeight(listView
->viewOptions()));
285 // We're sure elementsPosition doesn't contain index
286 const QRect
&KCategorizedView::Private::cacheIndex(const QModelIndex
&index
)
288 QRect rect
= visualRectInViewport(index
);
289 elementsPosition
[index
.row()] = rect
;
291 return elementsPosition
[index
.row()];
294 // We're sure categoriesPosition doesn't contain category
295 const QRect
&KCategorizedView::Private::cacheCategory(const QString
&category
)
297 QRect rect
= visualCategoryRectInViewport(category
);
298 categoriesPosition
[category
] = rect
;
300 return categoriesPosition
[category
];
303 const QRect
&KCategorizedView::Private::cachedRectIndex(const QModelIndex
&index
)
305 if (elementsPosition
.contains(index
.row())) // If we have it cached
307 return elementsPosition
[index
.row()];
309 else // Otherwise, cache it
311 return cacheIndex(index
);
315 const QRect
&KCategorizedView::Private::cachedRectCategory(const QString
&category
)
317 if (categoriesPosition
.contains(category
)) // If we have it cached
319 return categoriesPosition
[category
];
321 else // Otherwise, cache it and
323 return cacheCategory(category
);
327 QRect
KCategorizedView::Private::visualRect(const QModelIndex
&index
)
329 QRect retRect
= cachedRectIndex(index
);
330 int dx
= -listView
->horizontalOffset();
331 int dy
= -listView
->verticalOffset();
332 retRect
.adjust(dx
, dy
, dx
, dy
);
337 QRect
KCategorizedView::Private::categoryVisualRect(const QString
&category
)
339 QRect retRect
= cachedRectCategory(category
);
340 int dx
= -listView
->horizontalOffset();
341 int dy
= -listView
->verticalOffset();
342 retRect
.adjust(dx
, dy
, dx
, dy
);
347 void KCategorizedView::Private::drawNewCategory(const QModelIndex
&index
,
349 const QStyleOption
&option
,
352 if (!index
.isValid())
357 QStyleOption optionCopy
= option
;
358 const QString category
= proxyModel
->data(index
, KCategorizedSortFilterProxyModel::CategoryRole
).toString();
360 optionCopy
.state
&= ~QStyle::State_Selected
;
362 if ((category
== hoveredCategory
) && !mouseButtonPressed
)
364 optionCopy
.state
|= QStyle::State_MouseOver
;
366 else if ((category
== hoveredCategory
) && mouseButtonPressed
)
368 QPoint initialPressPosition
= listView
->viewport()->mapFromGlobal(QCursor::pos());
369 initialPressPosition
.setY(initialPressPosition
.y() + listView
->verticalOffset());
370 initialPressPosition
.setX(initialPressPosition
.x() + listView
->horizontalOffset());
372 if (initialPressPosition
== this->initialPressPosition
)
374 optionCopy
.state
|= QStyle::State_Selected
;
378 categoryDrawer
->drawCategory(index
,
385 void KCategorizedView::Private::updateScrollbars()
387 // find the last index in the last category
388 QModelIndex lastIndex
= categoriesIndexes
.isEmpty() ? QModelIndex() : categoriesIndexes
[categories
.last()].last();
390 int lastItemBottom
= cachedRectIndex(lastIndex
).top() +
391 listView
->spacing() + (listView
->gridSize().isEmpty() ? 0 : listView
->gridSize().height()) - listView
->viewport()->height();
393 listView
->horizontalScrollBar()->setRange(0, 0);
395 listView
->verticalScrollBar()->setSingleStep(listView
->viewport()->height() / 10);
396 listView
->verticalScrollBar()->setPageStep(listView
->viewport()->height());
397 listView
->verticalScrollBar()->setRange(0, lastItemBottom
);
400 void KCategorizedView::Private::drawDraggedItems(QPainter
*painter
)
402 QStyleOptionViewItemV3 option
= listView
->viewOptions();
403 option
.state
&= ~QStyle::State_MouseOver
;
404 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
406 const int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
407 const int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
409 option
.rect
= visualRect(index
);
410 option
.rect
.adjust(dx
, dy
, dx
, dy
);
412 if (option
.rect
.intersects(listView
->viewport()->rect()))
414 listView
->itemDelegate(index
)->paint(painter
, option
, index
);
419 void KCategorizedView::Private::layoutChanged(bool forceItemReload
)
421 if ((listView
->viewMode() == KCategorizedView::IconMode
) && proxyModel
&&
422 categoryDrawer
&& proxyModel
->isCategorizedModel() &&
424 (modelSortRole
!= proxyModel
->sortRole()) ||
425 (modelSortColumn
!= proxyModel
->sortColumn()) ||
426 (modelSortOrder
!= proxyModel
->sortOrder()) ||
427 (modelLastRowCount
!= proxyModel
->rowCount()) ||
428 (modelCategorized
!= proxyModel
->isCategorizedModel()))))
430 // Force the view to update all elements
431 listView
->rowsInsertedArtifficial(QModelIndex(), 0, proxyModel
->rowCount() - 1);
433 if (!forceItemReload
)
435 modelSortRole
= proxyModel
->sortRole();
436 modelSortColumn
= proxyModel
->sortColumn();
437 modelSortOrder
= proxyModel
->sortOrder();
438 modelLastRowCount
= proxyModel
->rowCount();
439 modelCategorized
= proxyModel
->isCategorizedModel();
442 else if ((listView
->viewMode() == KCategorizedView::IconMode
) && proxyModel
&&
443 categoryDrawer
&& proxyModel
->isCategorizedModel())
449 void KCategorizedView::Private::drawDraggedItems()
453 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
455 int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
456 int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
458 currentRect
= visualRect(index
);
459 currentRect
.adjust(dx
, dy
, dx
, dy
);
461 if (currentRect
.intersects(listView
->viewport()->rect()))
463 rectToUpdate
= rectToUpdate
.united(currentRect
);
467 listView
->viewport()->update(lastDraggedItemsRect
.united(rectToUpdate
));
469 lastDraggedItemsRect
= rectToUpdate
;
473 //==============================================================================
476 KCategorizedView::KCategorizedView(QWidget
*parent
)
478 , d(new Private(this))
482 KCategorizedView::~KCategorizedView()
487 void KCategorizedView::setGridSize(const QSize
&size
)
489 QListView::setGridSize(size
);
491 d
->layoutChanged(true);
494 void KCategorizedView::setModel(QAbstractItemModel
*model
)
496 d
->lastSelection
= QItemSelection();
497 d
->currentViewIndex
= QModelIndex();
498 d
->forcedSelectionPosition
= 0;
499 d
->elementsInfo
.clear();
500 d
->elementsPosition
.clear();
501 d
->categoriesIndexes
.clear();
502 d
->categoriesPosition
.clear();
503 d
->categories
.clear();
504 d
->intersectedIndexes
.clear();
505 d
->modelIndexList
.clear();
506 d
->hovered
= QModelIndex();
507 d
->mouseButtonPressed
= false;
511 QObject::disconnect(d
->proxyModel
,
512 SIGNAL(layoutChanged()),
513 this, SLOT(slotLayoutChanged()));
515 QObject::disconnect(d
->proxyModel
,
516 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
517 this, SLOT(slotLayoutChanged()));
519 QObject::disconnect(d
->proxyModel
,
520 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
521 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
524 QListView::setModel(model
);
526 d
->proxyModel
= dynamic_cast<KCategorizedSortFilterProxyModel
*>(model
);
530 d
->modelSortRole
= d
->proxyModel
->sortRole();
531 d
->modelSortColumn
= d
->proxyModel
->sortColumn();
532 d
->modelSortOrder
= d
->proxyModel
->sortOrder();
533 d
->modelLastRowCount
= d
->proxyModel
->rowCount();
534 d
->modelCategorized
= d
->proxyModel
->isCategorizedModel();
536 QObject::connect(d
->proxyModel
,
537 SIGNAL(layoutChanged()),
538 this, SLOT(slotLayoutChanged()));
540 QObject::connect(d
->proxyModel
,
541 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
542 this, SLOT(slotLayoutChanged()));
544 QObject::connect(d
->proxyModel
,
545 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
546 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
548 if (d
->proxyModel
->rowCount())
550 d
->layoutChanged(true);
555 d
->modelCategorized
= false;
559 QRect
KCategorizedView::visualRect(const QModelIndex
&index
) const
561 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
562 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
564 return QListView::visualRect(index
);
567 if (!qobject_cast
<const QSortFilterProxyModel
*>(index
.model()))
569 return d
->visualRect(d
->proxyModel
->mapFromSource(index
));
572 return d
->visualRect(index
);
575 KCategoryDrawer
*KCategorizedView::categoryDrawer() const
577 return d
->categoryDrawer
;
580 void KCategorizedView::setCategoryDrawer(KCategoryDrawer
*categoryDrawer
)
582 d
->lastSelection
= QItemSelection();
583 d
->currentViewIndex
= QModelIndex();
584 d
->forcedSelectionPosition
= 0;
585 d
->elementsInfo
.clear();
586 d
->elementsPosition
.clear();
587 d
->categoriesIndexes
.clear();
588 d
->categoriesPosition
.clear();
589 d
->categories
.clear();
590 d
->intersectedIndexes
.clear();
591 d
->modelIndexList
.clear();
592 d
->hovered
= QModelIndex();
593 d
->mouseButtonPressed
= false;
595 if (!categoryDrawer
&& d
->proxyModel
)
597 QObject::disconnect(d
->proxyModel
,
598 SIGNAL(layoutChanged()),
599 this, SLOT(slotLayoutChanged()));
601 QObject::disconnect(d
->proxyModel
,
602 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
603 this, SLOT(slotLayoutChanged()));
605 QObject::disconnect(d
->proxyModel
,
606 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
607 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
609 else if (categoryDrawer
&& d
->proxyModel
)
611 QObject::connect(d
->proxyModel
,
612 SIGNAL(layoutChanged()),
613 this, SLOT(slotLayoutChanged()));
615 QObject::connect(d
->proxyModel
,
616 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
617 this, SLOT(slotLayoutChanged()));
619 QObject::connect(d
->proxyModel
,
620 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
621 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
624 d
->categoryDrawer
= categoryDrawer
;
630 if (d
->proxyModel
->rowCount())
632 d
->layoutChanged(true);
642 QModelIndex
KCategorizedView::indexAt(const QPoint
&point
) const
644 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
645 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
647 return QListView::indexAt(point
);
652 QModelIndexList item
= d
->intersectionSet(QRect(point
, point
));
654 if (item
.count() == 1)
664 void KCategorizedView::reset()
668 d
->lastSelection
= QItemSelection();
669 d
->currentViewIndex
= QModelIndex();
670 d
->forcedSelectionPosition
= 0;
671 d
->elementsInfo
.clear();
672 d
->elementsPosition
.clear();
673 d
->categoriesIndexes
.clear();
674 d
->categoriesPosition
.clear();
675 d
->categories
.clear();
676 d
->intersectedIndexes
.clear();
677 d
->modelIndexList
.clear();
678 d
->hovered
= QModelIndex();
679 d
->biggestItemSize
= QSize(0, 0);
680 d
->mouseButtonPressed
= false;
683 void KCategorizedView::paintEvent(QPaintEvent
*event
)
685 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
686 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
688 QListView::paintEvent(event
);
692 QStyleOptionViewItemV3 option
= viewOptions();
693 option
.widget
= this;
696 option
.features
|= QStyleOptionViewItemV2::WrapText
;
699 QPainter
painter(viewport());
700 QRect area
= event
->rect();
701 const bool focus
= (hasFocus() || viewport()->hasFocus()) &&
702 currentIndex().isValid();
703 const QStyle::State state
= option
.state
;
704 const bool enabled
= (state
& QStyle::State_Enabled
) != 0;
708 QModelIndexList dirtyIndexes
= d
->intersectionSet(area
);
709 foreach (const QModelIndex
&index
, dirtyIndexes
)
711 option
.state
= state
;
712 option
.rect
= visualRect(index
);
714 if (selectionModel() && selectionModel()->isSelected(index
))
716 option
.state
|= QStyle::State_Selected
;
721 QPalette::ColorGroup cg
;
722 if ((d
->proxyModel
->flags(index
) & Qt::ItemIsEnabled
) == 0)
724 option
.state
&= ~QStyle::State_Enabled
;
725 cg
= QPalette::Disabled
;
729 cg
= QPalette::Normal
;
731 option
.palette
.setCurrentColorGroup(cg
);
734 if (focus
&& currentIndex() == index
)
736 option
.state
|= QStyle::State_HasFocus
;
737 if (this->state() == EditingState
)
738 option
.state
|= QStyle::State_Editing
;
741 if ((index
== d
->hovered
) && !d
->mouseButtonPressed
)
742 option
.state
|= QStyle::State_MouseOver
;
744 option
.state
&= ~QStyle::State_MouseOver
;
746 itemDelegate(index
)->paint(&painter
, option
, index
);
750 QStyleOptionViewItem otherOption
;
751 bool intersectedInThePast
= false;
752 foreach (const QString
&category
, d
->categories
)
754 otherOption
= option
;
755 otherOption
.rect
= d
->categoryVisualRect(category
);
756 otherOption
.state
&= ~QStyle::State_MouseOver
;
758 if (otherOption
.rect
.intersects(area
))
760 intersectedInThePast
= true;
762 QModelIndex indexToDraw
= d
->proxyModel
->index(d
->categoriesIndexes
[category
][0].row(), d
->proxyModel
->sortColumn());
764 d
->drawNewCategory(indexToDraw
,
765 d
->proxyModel
->sortRole(), otherOption
, &painter
);
767 else if (intersectedInThePast
)
769 break; // the visible area has been finished, we don't need to keep asking, the rest won't intersect
770 // this is doable because we know that categories are correctly ordered on the list
774 if (d
->mouseButtonPressed
&& !d
->isDragging
)
776 QPoint start
, end
, initialPressPosition
;
778 initialPressPosition
= d
->initialPressPosition
;
780 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
781 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
783 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
784 d
->initialPressPosition
.y() > d
->mousePosition
.y())
786 start
= d
->mousePosition
;
787 end
= initialPressPosition
;
791 start
= initialPressPosition
;
792 end
= d
->mousePosition
;
795 QStyleOptionRubberBand yetAnotherOption
;
796 yetAnotherOption
.initFrom(this);
797 yetAnotherOption
.shape
= QRubberBand::Rectangle
;
798 yetAnotherOption
.opaque
= false;
799 yetAnotherOption
.rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
801 style()->drawControl(QStyle::CE_RubberBand
, &yetAnotherOption
, &painter
);
805 if (d
->isDragging
&& !d
->dragLeftViewport
)
807 painter
.setOpacity(0.5);
808 d
->drawDraggedItems(&painter
);
814 void KCategorizedView::resizeEvent(QResizeEvent
*event
)
816 QListView::resizeEvent(event
);
818 // Clear the items positions cache
819 d
->elementsPosition
.clear();
820 d
->categoriesPosition
.clear();
821 d
->forcedSelectionPosition
= 0;
823 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
824 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
829 d
->updateScrollbars();
832 void KCategorizedView::setSelection(const QRect
&rect
,
833 QItemSelectionModel::SelectionFlags flags
)
835 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
836 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
838 QListView::setSelection(rect
, flags
);
845 if (flags
& QItemSelectionModel::Clear
)
847 if (!rect
.intersects(d
->categoryVisualRect(d
->hoveredCategory
)))
849 d
->lastSelection
= QItemSelection();
853 selectionModel()->clear();
855 QModelIndexList dirtyIndexes
= d
->intersectionSet(rect
);
857 if (!dirtyIndexes
.count())
859 if (!d
->lastSelection
.isEmpty() &&
860 (rect
.intersects(d
->categoryVisualRect(d
->hoveredCategory
)) || d
->mouseButtonPressed
))
862 selectionModel()->select(d
->lastSelection
, flags
);
868 QItemSelection selection
;
870 if (!d
->mouseButtonPressed
)
872 selection
= QItemSelection(dirtyIndexes
[0], dirtyIndexes
[0]);
873 d
->currentViewIndex
= dirtyIndexes
[0];
877 QModelIndex first
= dirtyIndexes
[0];
879 foreach (const QModelIndex
&index
, dirtyIndexes
)
881 if (last
.isValid() && last
.row() + 1 != index
.row())
883 QItemSelectionRange
range(first
, last
);
894 selection
<< QItemSelectionRange(first
, last
);
897 if (d
->lastSelection
.count())
899 if ((selection
.count() == 1) && (selection
[0].indexes().count() == 1))
900 selection
.merge(d
->lastSelection
, flags
);
902 selection
.merge(d
->lastSelection
, QItemSelectionModel::Select
);
905 selectionModel()->select(selection
, flags
);
908 void KCategorizedView::mouseMoveEvent(QMouseEvent
*event
)
910 QListView::mouseMoveEvent(event
);
912 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
913 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
918 const QString previousHoveredCategory
= d
->hoveredCategory
;
920 d
->mousePosition
= event
->pos();
921 d
->hoveredCategory
= QString();
924 foreach (const QString
&category
, d
->categories
)
926 if (d
->categoryVisualRect(category
).intersects(QRect(event
->pos(), event
->pos())))
928 d
->hoveredCategory
= category
;
929 viewport()->update(d
->categoryVisualRect(category
));
931 else if ((category
== previousHoveredCategory
) &&
932 (!d
->categoryVisualRect(previousHoveredCategory
).intersects(QRect(event
->pos(), event
->pos()))))
934 viewport()->update(d
->categoryVisualRect(category
));
939 if (d
->mouseButtonPressed
&& !d
->isDragging
)
941 QPoint start
, end
, initialPressPosition
;
943 initialPressPosition
= d
->initialPressPosition
;
945 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
946 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
948 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
949 d
->initialPressPosition
.y() > d
->mousePosition
.y())
951 start
= d
->mousePosition
;
952 end
= initialPressPosition
;
956 start
= initialPressPosition
;
957 end
= d
->mousePosition
;
960 rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
962 //viewport()->update(rect.united(d->lastSelectionRect));
964 d
->lastSelectionRect
= rect
;
968 void KCategorizedView::mousePressEvent(QMouseEvent
*event
)
970 d
->dragLeftViewport
= false;
972 if (event
->button() == Qt::LeftButton
)
974 d
->mouseButtonPressed
= true;
976 d
->initialPressPosition
= event
->pos();
977 d
->initialPressPosition
.setY(d
->initialPressPosition
.y() +
979 d
->initialPressPosition
.setX(d
->initialPressPosition
.x() +
983 QListView::mousePressEvent(event
);
985 viewport()->update(d
->categoryVisualRect(d
->hoveredCategory
));
988 void KCategorizedView::mouseReleaseEvent(QMouseEvent
*event
)
990 d
->mouseButtonPressed
= false;
992 QListView::mouseReleaseEvent(event
);
994 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
995 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1000 QPoint initialPressPosition
= viewport()->mapFromGlobal(QCursor::pos());
1001 initialPressPosition
.setY(initialPressPosition
.y() + verticalOffset());
1002 initialPressPosition
.setX(initialPressPosition
.x() + horizontalOffset());
1004 QItemSelection selection
;
1005 QItemSelection deselection
;
1007 if (initialPressPosition
== d
->initialPressPosition
)
1009 foreach(const QString
&category
, d
->categories
)
1011 if (d
->categoryVisualRect(category
).contains(event
->pos()))
1013 foreach (const QModelIndex
&index
, d
->categoriesIndexes
[category
])
1015 QModelIndex selectIndex
= index
.model()->index(index
.row(), 0);
1017 if (!d
->lastSelection
.contains(selectIndex
))
1019 selection
<< QItemSelectionRange(selectIndex
);
1023 deselection
<< QItemSelectionRange(selectIndex
);
1027 selectionModel()->select(selection
, QItemSelectionModel::Select
);
1028 selectionModel()->select(deselection
, QItemSelectionModel::Deselect
);
1035 d
->lastSelection
= selectionModel()->selection();
1037 if (d
->hovered
.isValid())
1038 viewport()->update(visualRect(d
->hovered
));
1039 else if (!d
->hoveredCategory
.isEmpty())
1040 viewport()->update(d
->categoryVisualRect(d
->hoveredCategory
));
1043 void KCategorizedView::leaveEvent(QEvent
*event
)
1045 d
->hovered
= QModelIndex();
1046 d
->hoveredCategory
= QString();
1048 QListView::leaveEvent(event
);
1051 void KCategorizedView::startDrag(Qt::DropActions supportedActions
)
1053 // FIXME: QAbstractItemView does far better here since it sets the
1054 // pixmap of selected icons to the dragging cursor, but it sets a non
1055 // ARGB window so it is no transparent. Use QAbstractItemView when
1056 // this is fixed on Qt.
1057 // QAbstractItemView::startDrag(supportedActions);
1058 QListView::startDrag(supportedActions
);
1060 d
->isDragging
= false;
1061 d
->mouseButtonPressed
= false;
1063 viewport()->update(d
->lastDraggedItemsRect
);
1066 void KCategorizedView::dragMoveEvent(QDragMoveEvent
*event
)
1068 d
->mousePosition
= event
->pos();
1070 if (d
->mouseButtonPressed
)
1072 d
->isDragging
= true;
1076 d
->isDragging
= false;
1079 d
->dragLeftViewport
= false;
1081 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1082 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1084 QListView::dragMoveEvent(event
);
1088 d
->drawDraggedItems();
1091 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent
*event
)
1093 d
->dragLeftViewport
= true;
1095 QListView::dragLeaveEvent(event
);
1098 QModelIndex
KCategorizedView::moveCursor(CursorAction cursorAction
,
1099 Qt::KeyboardModifiers modifiers
)
1101 if ((viewMode() != KCategorizedView::IconMode
) ||
1103 !d
->categoryDrawer
||
1104 d
->categories
.isEmpty() ||
1105 !d
->proxyModel
->isCategorizedModel()
1108 return QListView::moveCursor(cursorAction
, modifiers
);
1111 const QModelIndex current
= selectionModel()->currentIndex();
1113 int viewportWidth
= viewport()->width() - spacing();
1116 if (gridSize().isEmpty())
1118 itemWidth
= d
->biggestItemSize
.width();
1122 itemWidth
= gridSize().width();
1125 int itemWidthPlusSeparation
= spacing() + itemWidth
;
1126 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
1128 QString lastCategory
= d
->categories
.first();
1129 QString theCategory
= d
->categories
.first();
1130 QString afterCategory
= d
->categories
.first();
1132 bool hasToBreak
= false;
1133 foreach (const QString
&category
, d
->categories
)
1137 afterCategory
= category
;
1142 if (category
== d
->elementsInfo
[current
.row()].category
)
1144 theCategory
= category
;
1151 lastCategory
= category
;
1155 switch (cursorAction
)
1157 case QAbstractItemView::MoveUp
: {
1158 if (d
->elementsInfo
[current
.row()].relativeOffsetToCategory
>= elementsPerRow
)
1160 int indexToMove
= current
.row();
1161 indexToMove
-= qMin(((d
->elementsInfo
[current
.row()].relativeOffsetToCategory
) + d
->forcedSelectionPosition
), elementsPerRow
- d
->forcedSelectionPosition
+ (d
->elementsInfo
[current
.row()].relativeOffsetToCategory
% elementsPerRow
));
1163 return d
->proxyModel
->index(indexToMove
, 0);
1167 int lastCategoryLastRow
= (d
->categoriesIndexes
[lastCategory
].count() - 1) % elementsPerRow
;
1168 int indexToMove
= current
.row() - d
->elementsInfo
[current
.row()].relativeOffsetToCategory
;
1170 if (d
->forcedSelectionPosition
>= lastCategoryLastRow
)
1176 indexToMove
-= qMin((lastCategoryLastRow
- d
->forcedSelectionPosition
+ 1), d
->forcedSelectionPosition
+ elementsPerRow
+ 1);
1179 return d
->proxyModel
->index(indexToMove
, 0);
1183 case QAbstractItemView::MoveDown
: {
1184 if (d
->elementsInfo
[current
.row()].relativeOffsetToCategory
< (d
->categoriesIndexes
[theCategory
].count() - 1 - ((d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
)))
1186 int indexToMove
= current
.row();
1187 indexToMove
+= qMin(elementsPerRow
, d
->categoriesIndexes
[theCategory
].count() - 1 - d
->elementsInfo
[current
.row()].relativeOffsetToCategory
);
1189 return d
->proxyModel
->index(indexToMove
, 0);
1193 int afterCategoryLastRow
= qMin(elementsPerRow
, d
->categoriesIndexes
[afterCategory
].count());
1194 int indexToMove
= current
.row() + (d
->categoriesIndexes
[theCategory
].count() - d
->elementsInfo
[current
.row()].relativeOffsetToCategory
);
1196 if (d
->forcedSelectionPosition
>= afterCategoryLastRow
)
1198 indexToMove
+= afterCategoryLastRow
- 1;
1202 indexToMove
+= qMin(d
->forcedSelectionPosition
, elementsPerRow
);
1205 return d
->proxyModel
->index(indexToMove
, 0);
1209 case QAbstractItemView::MoveLeft
:
1210 if (layoutDirection() == Qt::RightToLeft
)
1212 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() + 1].relativeOffsetToCategory
% elementsPerRow
;
1214 if (d
->forcedSelectionPosition
< 0)
1215 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1217 return d
->proxyModel
->index(current
.row() + 1, 0);
1220 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() - 1].relativeOffsetToCategory
% elementsPerRow
;
1222 if (d
->forcedSelectionPosition
< 0)
1223 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1225 return d
->proxyModel
->index(current
.row() - 1, 0);
1227 case QAbstractItemView::MoveRight
:
1228 if (layoutDirection() == Qt::RightToLeft
)
1230 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() - 1].relativeOffsetToCategory
% elementsPerRow
;
1232 if (d
->forcedSelectionPosition
< 0)
1233 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1235 return d
->proxyModel
->index(current
.row() - 1, 0);
1238 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() + 1].relativeOffsetToCategory
% elementsPerRow
;
1240 if (d
->forcedSelectionPosition
< 0)
1241 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1243 return d
->proxyModel
->index(current
.row() + 1, 0);
1249 return QListView::moveCursor(cursorAction
, modifiers
);
1252 void KCategorizedView::rowsInserted(const QModelIndex
&parent
,
1256 QListView::rowsInserted(parent
, start
, end
);
1258 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1259 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1261 d
->lastSelection
= QItemSelection();
1262 d
->currentViewIndex
= QModelIndex();
1263 d
->forcedSelectionPosition
= 0;
1264 d
->elementsInfo
.clear();
1265 d
->elementsPosition
.clear();
1266 d
->categoriesIndexes
.clear();
1267 d
->categoriesPosition
.clear();
1268 d
->categories
.clear();
1269 d
->intersectedIndexes
.clear();
1270 d
->modelIndexList
.clear();
1271 d
->hovered
= QModelIndex();
1272 d
->biggestItemSize
= QSize(0, 0);
1273 d
->mouseButtonPressed
= false;
1278 rowsInsertedArtifficial(parent
, start
, end
);
1281 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex
&parent
,
1287 d
->lastSelection
= QItemSelection();
1288 d
->currentViewIndex
= QModelIndex();
1289 d
->forcedSelectionPosition
= 0;
1290 d
->elementsInfo
.clear();
1291 d
->elementsPosition
.clear();
1292 d
->categoriesIndexes
.clear();
1293 d
->categoriesPosition
.clear();
1294 d
->categories
.clear();
1295 d
->intersectedIndexes
.clear();
1296 d
->modelIndexList
.clear();
1297 d
->hovered
= QModelIndex();
1298 d
->biggestItemSize
= QSize(0, 0);
1299 d
->mouseButtonPressed
= false;
1301 if (start
> end
|| end
< 0 || start
< 0 || !d
->proxyModel
->rowCount())
1306 // Add all elements mapped to the source model and explore categories
1307 QString prevCategory
= d
->proxyModel
->data(d
->proxyModel
->index(0, d
->proxyModel
->sortColumn()), KCategorizedSortFilterProxyModel::CategoryRole
).toString();
1308 QString lastCategory
= prevCategory
;
1309 QModelIndexList modelIndexList
;
1310 struct Private::ElementInfo elementInfo
;
1312 for (int k
= 0; k
< d
->proxyModel
->rowCount(); ++k
)
1314 QModelIndex index
= d
->proxyModel
->index(k
, d
->proxyModel
->sortColumn());
1315 QModelIndex indexSize
= d
->proxyModel
->index(k
, 0);
1317 d
->biggestItemSize
= QSize(qMax(sizeHintForIndex(indexSize
).width(),
1318 d
->biggestItemSize
.width()),
1319 qMax(sizeHintForIndex(indexSize
).height(),
1320 d
->biggestItemSize
.height()));
1322 d
->modelIndexList
<< index
;
1324 lastCategory
= d
->proxyModel
->data(index
, KCategorizedSortFilterProxyModel::CategoryRole
).toString();
1326 elementInfo
.category
= lastCategory
;
1328 if (prevCategory
!= lastCategory
)
1331 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1332 d
->categories
<< prevCategory
;
1333 modelIndexList
.clear();
1340 elementInfo
.relativeOffsetToCategory
= offset
;
1342 modelIndexList
<< index
;
1343 prevCategory
= lastCategory
;
1345 d
->elementsInfo
.insert(index
.row(), elementInfo
);
1348 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1349 d
->categories
<< prevCategory
;
1351 d
->updateScrollbars();
1354 void KCategorizedView::rowsRemoved(const QModelIndex
&parent
,
1358 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1359 d
->categoryDrawer
&& d
->proxyModel
->isCategorizedModel())
1361 // Force the view to update all elements
1362 rowsInsertedArtifficial(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
1366 void KCategorizedView::updateGeometries()
1368 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1369 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1371 QListView::updateGeometries();
1375 // Avoid QListView::updateGeometries(), since it will try to set another
1376 // range to our scroll bars, what we don't want (ereslibre)
1377 QAbstractItemView::updateGeometries();
1380 void KCategorizedView::slotLayoutChanged()
1385 #include "kcategorizedview.moc"