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
29 #include <QPaintEvent>
33 #include "kcategorydrawer.h"
34 #include "kcategorizedsortfilterproxymodel.h"
36 // By defining DOLPHIN_DRAGANDDROP the custom drag and drop implementation of
37 // KCategorizedView is bypassed to have a consistent drag and drop look for all
38 // views. Hopefully transparent pixmaps for drag objects will be supported in
39 // Qt 4.4, so that this workaround can be skipped.
40 #define DOLPHIN_DRAGANDDROP
42 KCategorizedView::Private::Private(KCategorizedView
*listView
)
45 , biggestItemSize(QSize(0, 0))
46 , mouseButtonPressed(false)
48 , dragLeftViewport(false)
53 KCategorizedView::Private::~Private()
57 const QModelIndexList
&KCategorizedView::Private::intersectionSet(const QRect
&rect
)
60 QRect indexVisualRect
;
62 intersectedIndexes
.clear();
66 if (listView
->gridSize().isEmpty())
68 itemHeight
= biggestItemSize
.height();
72 itemHeight
= listView
->gridSize().height();
75 // Lets find out where we should start
76 int top
= proxyModel
->rowCount() - 1;
78 int middle
= (top
+ bottom
) / 2;
81 middle
= (top
+ bottom
) / 2;
83 index
= proxyModel
->index(middle
, 0);
84 indexVisualRect
= visualRect(index
);
85 // We need the whole height (not only the visualRect). This will help us to update
86 // all needed indexes correctly (ereslibre)
87 indexVisualRect
.setHeight(indexVisualRect
.height() + (itemHeight
- indexVisualRect
.height()));
89 if (qMax(indexVisualRect
.topLeft().y(),
90 indexVisualRect
.bottomRight().y()) < qMin(rect
.topLeft().y(),
91 rect
.bottomRight().y()))
101 for (int i
= middle
; i
< proxyModel
->rowCount(); i
++)
103 index
= proxyModel
->index(i
, 0);
104 indexVisualRect
= visualRect(index
);
106 if (rect
.intersects(indexVisualRect
))
107 intersectedIndexes
.append(index
);
109 // If we passed next item, stop searching for hits
110 if (qMax(rect
.bottomRight().y(), rect
.topLeft().y()) <
111 qMin(indexVisualRect
.topLeft().y(),
112 indexVisualRect
.bottomRight().y()))
116 return intersectedIndexes
;
119 QRect
KCategorizedView::Private::visualRectInViewport(const QModelIndex
&index
) const
121 if (!index
.isValid())
124 QString curCategory
= elementsInfo
[index
.row()].category
;
128 if (listView
->layoutDirection() == Qt::LeftToRight
)
130 retRect
= QRect(listView
->spacing(), listView
->spacing() * 2 +
131 categoryDrawer
->categoryHeight(listView
->viewOptions()), 0, 0);
135 retRect
= QRect(listView
->viewport()->width() - listView
->spacing(), listView
->spacing() * 2 +
136 categoryDrawer
->categoryHeight(listView
->viewOptions()), 0, 0);
139 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
144 if (listView
->gridSize().isEmpty())
146 itemHeight
= biggestItemSize
.height();
147 itemWidth
= biggestItemSize
.width();
151 itemHeight
= listView
->gridSize().height();
152 itemWidth
= listView
->gridSize().width();
155 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
156 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
160 int column
= elementsInfo
[index
.row()].relativeOffsetToCategory
% elementsPerRow
;
161 int row
= elementsInfo
[index
.row()].relativeOffsetToCategory
/ elementsPerRow
;
163 if (listView
->layoutDirection() == Qt::LeftToRight
)
165 retRect
.setLeft(retRect
.left() + column
* listView
->spacing() +
170 retRect
.setLeft(retRect
.right() - column
* listView
->spacing() -
171 column
* itemWidth
- itemWidth
);
173 retRect
.setRight(retRect
.right() - column
* listView
->spacing() -
177 foreach (const QString
&category
, categories
)
179 if (category
== curCategory
)
182 float rows
= (float) ((float) categoriesIndexes
[category
].count() /
183 (float) elementsPerRow
);
185 int rowsInt
= categoriesIndexes
[category
].count() / elementsPerRow
;
187 if (rows
- trunc(rows
)) rowsInt
++;
189 retRect
.setTop(retRect
.top() +
190 (rowsInt
* itemHeight
) +
191 categoryDrawer
->categoryHeight(listView
->viewOptions()) +
192 listView
->spacing() * 2);
194 if (listView
->gridSize().isEmpty())
196 retRect
.setTop(retRect
.top() +
197 (rowsInt
* listView
->spacing()));
201 if (listView
->gridSize().isEmpty())
203 retRect
.setTop(retRect
.top() + row
* listView
->spacing() +
208 retRect
.setTop(retRect
.top() + (row
* itemHeight
));
211 retRect
.setWidth(itemWidth
);
213 QModelIndex heightIndex
= proxyModel
->index(index
.row(), 0);
214 if (listView
->gridSize().isEmpty())
216 retRect
.setHeight(listView
->sizeHintForIndex(heightIndex
).height());
220 retRect
.setHeight(qMin(listView
->sizeHintForIndex(heightIndex
).height(),
221 listView
->gridSize().height()));
227 QRect
KCategorizedView::Private::visualCategoryRectInViewport(const QString
&category
)
230 QRect
retRect(listView
->spacing(),
232 listView
->viewport()->width() - listView
->spacing() * 2,
235 if (!proxyModel
->rowCount() || !categories
.contains(category
))
238 QModelIndex index
= proxyModel
->index(0, 0, QModelIndex());
240 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
245 if (listView
->gridSize().isEmpty())
247 itemHeight
= biggestItemSize
.height();
248 itemWidth
= biggestItemSize
.width();
252 itemHeight
= listView
->gridSize().height();
253 itemWidth
= listView
->gridSize().width();
256 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
257 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
262 foreach (const QString
&itCategory
, categories
)
264 if (itCategory
== category
)
267 float rows
= (float) ((float) categoriesIndexes
[itCategory
].count() /
268 (float) elementsPerRow
);
269 int rowsInt
= categoriesIndexes
[itCategory
].count() / elementsPerRow
;
271 if (rows
- trunc(rows
)) rowsInt
++;
273 retRect
.setTop(retRect
.top() +
274 (rowsInt
* itemHeight
) +
275 categoryDrawer
->categoryHeight(listView
->viewOptions()) +
276 listView
->spacing() * 2);
278 if (listView
->gridSize().isEmpty())
280 retRect
.setTop(retRect
.top() +
281 (rowsInt
* listView
->spacing()));
285 retRect
.setHeight(categoryDrawer
->categoryHeight(listView
->viewOptions()));
290 // We're sure elementsPosition doesn't contain index
291 const QRect
&KCategorizedView::Private::cacheIndex(const QModelIndex
&index
)
293 QRect rect
= visualRectInViewport(index
);
294 elementsPosition
[index
.row()] = rect
;
296 return elementsPosition
[index
.row()];
299 // We're sure categoriesPosition doesn't contain category
300 const QRect
&KCategorizedView::Private::cacheCategory(const QString
&category
)
302 QRect rect
= visualCategoryRectInViewport(category
);
303 categoriesPosition
[category
] = rect
;
305 return categoriesPosition
[category
];
308 const QRect
&KCategorizedView::Private::cachedRectIndex(const QModelIndex
&index
)
310 if (elementsPosition
.contains(index
.row())) // If we have it cached
312 return elementsPosition
[index
.row()];
314 else // Otherwise, cache it
316 return cacheIndex(index
);
320 const QRect
&KCategorizedView::Private::cachedRectCategory(const QString
&category
)
322 if (categoriesPosition
.contains(category
)) // If we have it cached
324 return categoriesPosition
[category
];
326 else // Otherwise, cache it and
328 return cacheCategory(category
);
332 QRect
KCategorizedView::Private::visualRect(const QModelIndex
&index
)
334 QRect retRect
= cachedRectIndex(index
);
335 int dx
= -listView
->horizontalOffset();
336 int dy
= -listView
->verticalOffset();
337 retRect
.adjust(dx
, dy
, dx
, dy
);
342 QRect
KCategorizedView::Private::categoryVisualRect(const QString
&category
)
344 QRect retRect
= cachedRectCategory(category
);
345 int dx
= -listView
->horizontalOffset();
346 int dy
= -listView
->verticalOffset();
347 retRect
.adjust(dx
, dy
, dx
, dy
);
352 void KCategorizedView::Private::drawNewCategory(const QModelIndex
&index
,
354 const QStyleOption
&option
,
357 if (!index
.isValid())
362 QStyleOption optionCopy
= option
;
363 const QString category
= proxyModel
->data(index
, KCategorizedSortFilterProxyModel::CategoryDisplayRole
).toString();
365 optionCopy
.state
&= ~QStyle::State_Selected
;
367 if ((category
== hoveredCategory
) && !mouseButtonPressed
)
369 optionCopy
.state
|= QStyle::State_MouseOver
;
371 else if ((category
== hoveredCategory
) && mouseButtonPressed
)
373 QPoint initialPressPosition
= listView
->viewport()->mapFromGlobal(QCursor::pos());
374 initialPressPosition
.setY(initialPressPosition
.y() + listView
->verticalOffset());
375 initialPressPosition
.setX(initialPressPosition
.x() + listView
->horizontalOffset());
377 if (initialPressPosition
== this->initialPressPosition
)
379 optionCopy
.state
|= QStyle::State_Selected
;
383 categoryDrawer
->drawCategory(index
,
390 void KCategorizedView::Private::updateScrollbars()
392 // find the last index in the last category
393 QModelIndex lastIndex
= categoriesIndexes
.isEmpty() ? QModelIndex() : categoriesIndexes
[categories
.last()].last();
395 int lastItemBottom
= cachedRectIndex(lastIndex
).top() +
396 listView
->spacing() + (listView
->gridSize().isEmpty() ? 0 : listView
->gridSize().height()) - listView
->viewport()->height();
398 listView
->horizontalScrollBar()->setRange(0, 0);
400 listView
->verticalScrollBar()->setSingleStep(listView
->viewport()->height() / 10);
401 listView
->verticalScrollBar()->setPageStep(listView
->viewport()->height());
402 listView
->verticalScrollBar()->setRange(0, lastItemBottom
);
405 void KCategorizedView::Private::drawDraggedItems(QPainter
*painter
)
407 QStyleOptionViewItemV3 option
= listView
->viewOptions();
408 option
.state
&= ~QStyle::State_MouseOver
;
409 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
411 const int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
412 const int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
414 option
.rect
= visualRect(index
);
415 option
.rect
.adjust(dx
, dy
, dx
, dy
);
417 if (option
.rect
.intersects(listView
->viewport()->rect()))
419 listView
->itemDelegate(index
)->paint(painter
, option
, index
);
424 void KCategorizedView::Private::layoutChanged(bool forceItemReload
)
426 if ((listView
->viewMode() == KCategorizedView::IconMode
) && proxyModel
&&
427 categoryDrawer
&& proxyModel
->isCategorizedModel() &&
429 (modelSortRole
!= proxyModel
->sortRole()) ||
430 (modelSortColumn
!= proxyModel
->sortColumn()) ||
431 (modelSortOrder
!= proxyModel
->sortOrder()) ||
432 (modelLastRowCount
!= proxyModel
->rowCount()) ||
433 (modelCategorized
!= proxyModel
->isCategorizedModel()))))
435 // Force the view to update all elements
436 listView
->rowsInsertedArtifficial(QModelIndex(), 0, proxyModel
->rowCount() - 1);
438 if (!forceItemReload
)
440 modelSortRole
= proxyModel
->sortRole();
441 modelSortColumn
= proxyModel
->sortColumn();
442 modelSortOrder
= proxyModel
->sortOrder();
443 modelLastRowCount
= proxyModel
->rowCount();
444 modelCategorized
= proxyModel
->isCategorizedModel();
447 else if ((listView
->viewMode() == KCategorizedView::IconMode
) && proxyModel
&&
448 categoryDrawer
&& proxyModel
->isCategorizedModel())
454 void KCategorizedView::Private::drawDraggedItems()
458 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
460 int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
461 int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
463 currentRect
= visualRect(index
);
464 currentRect
.adjust(dx
, dy
, dx
, dy
);
466 if (currentRect
.intersects(listView
->viewport()->rect()))
468 rectToUpdate
= rectToUpdate
.united(currentRect
);
472 listView
->viewport()->update(lastDraggedItemsRect
.united(rectToUpdate
));
474 lastDraggedItemsRect
= rectToUpdate
;
478 //==============================================================================
481 KCategorizedView::KCategorizedView(QWidget
*parent
)
483 , d(new Private(this))
487 KCategorizedView::~KCategorizedView()
492 void KCategorizedView::setGridSize(const QSize
&size
)
494 QListView::setGridSize(size
);
496 d
->layoutChanged(true);
499 void KCategorizedView::setModel(QAbstractItemModel
*model
)
501 d
->lastSelection
= QItemSelection();
502 d
->currentViewIndex
= QModelIndex();
503 d
->forcedSelectionPosition
= 0;
504 d
->elementsInfo
.clear();
505 d
->elementsPosition
.clear();
506 d
->categoriesIndexes
.clear();
507 d
->categoriesPosition
.clear();
508 d
->categories
.clear();
509 d
->intersectedIndexes
.clear();
510 d
->modelIndexList
.clear();
511 d
->hovered
= QModelIndex();
512 d
->mouseButtonPressed
= false;
516 QObject::disconnect(d
->proxyModel
,
517 SIGNAL(layoutChanged()),
518 this, SLOT(slotLayoutChanged()));
520 QObject::disconnect(d
->proxyModel
,
521 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
522 this, SLOT(slotLayoutChanged()));
524 QObject::disconnect(d
->proxyModel
,
525 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
526 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
529 QListView::setModel(model
);
531 d
->proxyModel
= dynamic_cast<KCategorizedSortFilterProxyModel
*>(model
);
535 d
->modelSortRole
= d
->proxyModel
->sortRole();
536 d
->modelSortColumn
= d
->proxyModel
->sortColumn();
537 d
->modelSortOrder
= d
->proxyModel
->sortOrder();
538 d
->modelLastRowCount
= d
->proxyModel
->rowCount();
539 d
->modelCategorized
= d
->proxyModel
->isCategorizedModel();
541 QObject::connect(d
->proxyModel
,
542 SIGNAL(layoutChanged()),
543 this, SLOT(slotLayoutChanged()));
545 QObject::connect(d
->proxyModel
,
546 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
547 this, SLOT(slotLayoutChanged()));
549 QObject::connect(d
->proxyModel
,
550 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
551 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
553 if (d
->proxyModel
->rowCount())
555 d
->layoutChanged(true);
560 d
->modelCategorized
= false;
564 QRect
KCategorizedView::visualRect(const QModelIndex
&index
) const
566 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
567 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
569 return QListView::visualRect(index
);
572 if (!qobject_cast
<const QSortFilterProxyModel
*>(index
.model()))
574 return d
->visualRect(d
->proxyModel
->mapFromSource(index
));
577 return d
->visualRect(index
);
580 KCategoryDrawer
*KCategorizedView::categoryDrawer() const
582 return d
->categoryDrawer
;
585 void KCategorizedView::setCategoryDrawer(KCategoryDrawer
*categoryDrawer
)
587 d
->lastSelection
= QItemSelection();
588 d
->currentViewIndex
= QModelIndex();
589 d
->forcedSelectionPosition
= 0;
590 d
->elementsInfo
.clear();
591 d
->elementsPosition
.clear();
592 d
->categoriesIndexes
.clear();
593 d
->categoriesPosition
.clear();
594 d
->categories
.clear();
595 d
->intersectedIndexes
.clear();
596 d
->modelIndexList
.clear();
597 d
->hovered
= QModelIndex();
598 d
->mouseButtonPressed
= false;
600 if (!categoryDrawer
&& d
->proxyModel
)
602 QObject::disconnect(d
->proxyModel
,
603 SIGNAL(layoutChanged()),
604 this, SLOT(slotLayoutChanged()));
606 QObject::disconnect(d
->proxyModel
,
607 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
608 this, SLOT(slotLayoutChanged()));
610 QObject::disconnect(d
->proxyModel
,
611 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
612 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
614 else if (categoryDrawer
&& d
->proxyModel
)
616 QObject::connect(d
->proxyModel
,
617 SIGNAL(layoutChanged()),
618 this, SLOT(slotLayoutChanged()));
620 QObject::connect(d
->proxyModel
,
621 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
622 this, SLOT(slotLayoutChanged()));
624 QObject::connect(d
->proxyModel
,
625 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
626 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
629 d
->categoryDrawer
= categoryDrawer
;
635 if (d
->proxyModel
->rowCount())
637 d
->layoutChanged(true);
647 QModelIndex
KCategorizedView::indexAt(const QPoint
&point
) const
649 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
650 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
652 return QListView::indexAt(point
);
657 QModelIndexList item
= d
->intersectionSet(QRect(point
, point
));
659 if (item
.count() == 1)
669 void KCategorizedView::reset()
673 d
->lastSelection
= QItemSelection();
674 d
->currentViewIndex
= QModelIndex();
675 d
->forcedSelectionPosition
= 0;
676 d
->elementsInfo
.clear();
677 d
->elementsPosition
.clear();
678 d
->categoriesIndexes
.clear();
679 d
->categoriesPosition
.clear();
680 d
->categories
.clear();
681 d
->intersectedIndexes
.clear();
682 d
->modelIndexList
.clear();
683 d
->hovered
= QModelIndex();
684 d
->biggestItemSize
= QSize(0, 0);
685 d
->mouseButtonPressed
= false;
688 void KCategorizedView::paintEvent(QPaintEvent
*event
)
690 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
691 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
693 QListView::paintEvent(event
);
697 QStyleOptionViewItemV3 option
= viewOptions();
698 option
.widget
= this;
701 option
.features
|= QStyleOptionViewItemV2::WrapText
;
704 QPainter
painter(viewport());
705 QRect area
= event
->rect();
706 const bool focus
= (hasFocus() || viewport()->hasFocus()) &&
707 currentIndex().isValid();
708 const QStyle::State state
= option
.state
;
709 const bool enabled
= (state
& QStyle::State_Enabled
) != 0;
713 QModelIndexList dirtyIndexes
= d
->intersectionSet(area
);
714 foreach (const QModelIndex
&index
, dirtyIndexes
)
716 option
.state
= state
;
717 option
.rect
= visualRect(index
);
719 if (selectionModel() && selectionModel()->isSelected(index
))
721 option
.state
|= QStyle::State_Selected
;
726 QPalette::ColorGroup cg
;
727 if ((d
->proxyModel
->flags(index
) & Qt::ItemIsEnabled
) == 0)
729 option
.state
&= ~QStyle::State_Enabled
;
730 cg
= QPalette::Disabled
;
734 cg
= QPalette::Normal
;
736 option
.palette
.setCurrentColorGroup(cg
);
739 if (focus
&& currentIndex() == index
)
741 option
.state
|= QStyle::State_HasFocus
;
742 if (this->state() == EditingState
)
743 option
.state
|= QStyle::State_Editing
;
746 if ((index
== d
->hovered
) && !d
->mouseButtonPressed
)
747 option
.state
|= QStyle::State_MouseOver
;
749 option
.state
&= ~QStyle::State_MouseOver
;
751 itemDelegate(index
)->paint(&painter
, option
, index
);
755 QStyleOptionViewItem otherOption
;
756 bool intersectedInThePast
= false;
757 foreach (const QString
&category
, d
->categories
)
759 otherOption
= option
;
760 otherOption
.rect
= d
->categoryVisualRect(category
);
761 otherOption
.state
&= ~QStyle::State_MouseOver
;
763 if (otherOption
.rect
.intersects(area
))
765 intersectedInThePast
= true;
767 QModelIndex indexToDraw
= d
->proxyModel
->index(d
->categoriesIndexes
[category
][0].row(), d
->proxyModel
->sortColumn());
769 d
->drawNewCategory(indexToDraw
,
770 d
->proxyModel
->sortRole(), otherOption
, &painter
);
772 else if (intersectedInThePast
)
774 break; // the visible area has been finished, we don't need to keep asking, the rest won't intersect
775 // this is doable because we know that categories are correctly ordered on the list
779 if (d
->mouseButtonPressed
&& !d
->isDragging
)
781 QPoint start
, end
, initialPressPosition
;
783 initialPressPosition
= d
->initialPressPosition
;
785 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
786 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
788 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
789 d
->initialPressPosition
.y() > d
->mousePosition
.y())
791 start
= d
->mousePosition
;
792 end
= initialPressPosition
;
796 start
= initialPressPosition
;
797 end
= d
->mousePosition
;
800 QStyleOptionRubberBand yetAnotherOption
;
801 yetAnotherOption
.initFrom(this);
802 yetAnotherOption
.shape
= QRubberBand::Rectangle
;
803 yetAnotherOption
.opaque
= false;
804 yetAnotherOption
.rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
806 style()->drawControl(QStyle::CE_RubberBand
, &yetAnotherOption
, &painter
);
810 if (d
->isDragging
&& !d
->dragLeftViewport
)
812 painter
.setOpacity(0.5);
813 d
->drawDraggedItems(&painter
);
819 void KCategorizedView::resizeEvent(QResizeEvent
*event
)
821 QListView::resizeEvent(event
);
823 // Clear the items positions cache
824 d
->elementsPosition
.clear();
825 d
->categoriesPosition
.clear();
826 d
->forcedSelectionPosition
= 0;
828 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
829 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
834 d
->updateScrollbars();
837 void KCategorizedView::setSelection(const QRect
&rect
,
838 QItemSelectionModel::SelectionFlags flags
)
840 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
841 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
843 QListView::setSelection(rect
, flags
);
850 if (flags
& QItemSelectionModel::Clear
)
852 selectionModel()->clear();
855 QModelIndexList dirtyIndexes
= d
->intersectionSet(rect
);
857 // no items affected, just leave
858 if (!dirtyIndexes
.count())
860 selectionModel()->select(d
->lastSelection
, flags
);
862 d
->lastSelection
.clear();
867 d
->lastSelection
.clear();
869 if (!(flags
& QItemSelectionModel::Current
))
871 Q_ASSERT(dirtyIndexes
.count() == 1);
873 selectionModel()->select(dirtyIndexes
[0], flags
);
879 QModelIndex bottomRight
;
881 if (d
->mouseButtonPressed
) // selection with click + drag
883 QModelIndex prev
= dirtyIndexes
[0];
884 QModelIndex first
= prev
;
885 foreach (const QModelIndex
&index
, dirtyIndexes
)
887 if ((index
.row() - prev
.row()) > 1) {
888 d
->lastSelection
<< QItemSelectionRange(first
, prev
);
896 d
->lastSelection
<< QItemSelectionRange(first
, prev
);
898 selectionModel()->select(d
->lastSelection
, flags
);
900 else // selection with click + keyboard keys
902 QModelIndex topLeftIndex
= indexAt(QPoint(rect
.topLeft().x(),
903 rect
.topLeft().y()));
904 QModelIndex bottomRightIndex
= indexAt(QPoint(rect
.bottomRight().x(),
905 rect
.bottomRight().y()));
907 // keyboard selection comes "upside down". Let's normalize it
908 if (topLeftIndex
.row() > bottomRightIndex
.row())
910 QModelIndex auxIndex
= topLeftIndex
;
911 topLeftIndex
= bottomRightIndex
;
912 bottomRightIndex
= auxIndex
;
915 int viewportWidth
= viewport()->width() - spacing();
918 if (gridSize().isEmpty())
920 itemWidth
= d
->biggestItemSize
.width();
924 itemWidth
= gridSize().width();
927 int itemWidthPlusSeparation
= spacing() + itemWidth
;
928 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
932 QModelIndexList
theoricDirty(dirtyIndexes
);
933 dirtyIndexes
.clear();
934 int first
= model()->rowCount();
937 foreach (const QModelIndex
&index
, theoricDirty
)
939 if ((index
.row() < first
) &&
940 ((((topLeftIndex
.row() / elementsPerRow
) == (index
.row() / elementsPerRow
)) &&
941 ((topLeftIndex
.row() % elementsPerRow
) <= (index
.row() % elementsPerRow
))) ||
942 (topLeftIndex
.row() / elementsPerRow
) != (index
.row() / elementsPerRow
)))
948 if ((index
.row() > last
) &&
949 ((((bottomRightIndex
.row() / elementsPerRow
) == (index
.row() / elementsPerRow
)) &&
950 ((bottomRightIndex
.row() % elementsPerRow
) >= (index
.row() % elementsPerRow
))) ||
951 (bottomRightIndex
.row() / elementsPerRow
) != (index
.row() / elementsPerRow
)))
958 for (int i
= first
; i
<= last
; i
++)
960 dirtyIndexes
<< model()->index(i
, theoricDirty
[0].column(), theoricDirty
[0].parent());
963 // our current selection will result modified
964 d
->lastSelection
= QItemSelection(topLeft
, bottomRight
);
966 selectionModel()->select(d
->lastSelection
, flags
);
970 void KCategorizedView::mouseMoveEvent(QMouseEvent
*event
)
972 QListView::mouseMoveEvent(event
);
974 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
975 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
980 const QString previousHoveredCategory
= d
->hoveredCategory
;
982 d
->mousePosition
= event
->pos();
983 d
->hoveredCategory
= QString();
986 foreach (const QString
&category
, d
->categories
)
988 if (d
->categoryVisualRect(category
).intersects(QRect(event
->pos(), event
->pos())))
990 d
->hoveredCategory
= category
;
991 viewport()->update(d
->categoryVisualRect(category
));
993 else if ((category
== previousHoveredCategory
) &&
994 (!d
->categoryVisualRect(previousHoveredCategory
).intersects(QRect(event
->pos(), event
->pos()))))
996 viewport()->update(d
->categoryVisualRect(category
));
1001 if (d
->mouseButtonPressed
&& !d
->isDragging
)
1003 QPoint start
, end
, initialPressPosition
;
1005 initialPressPosition
= d
->initialPressPosition
;
1007 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
1008 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
1010 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
1011 d
->initialPressPosition
.y() > d
->mousePosition
.y())
1013 start
= d
->mousePosition
;
1014 end
= initialPressPosition
;
1018 start
= initialPressPosition
;
1019 end
= d
->mousePosition
;
1022 rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
1026 void KCategorizedView::mousePressEvent(QMouseEvent
*event
)
1028 d
->dragLeftViewport
= false;
1030 if (event
->button() == Qt::LeftButton
)
1032 d
->mouseButtonPressed
= true;
1034 d
->initialPressPosition
= event
->pos();
1035 d
->initialPressPosition
.setY(d
->initialPressPosition
.y() +
1037 d
->initialPressPosition
.setX(d
->initialPressPosition
.x() +
1038 horizontalOffset());
1041 QListView::mousePressEvent(event
);
1043 viewport()->update(d
->categoryVisualRect(d
->hoveredCategory
));
1046 void KCategorizedView::mouseReleaseEvent(QMouseEvent
*event
)
1048 d
->mouseButtonPressed
= false;
1050 QListView::mouseReleaseEvent(event
);
1052 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1053 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1058 QPoint initialPressPosition
= viewport()->mapFromGlobal(QCursor::pos());
1059 initialPressPosition
.setY(initialPressPosition
.y() + verticalOffset());
1060 initialPressPosition
.setX(initialPressPosition
.x() + horizontalOffset());
1062 if (initialPressPosition
== d
->initialPressPosition
)
1064 foreach(const QString
&category
, d
->categories
)
1066 if (d
->categoryVisualRect(category
).contains(event
->pos()))
1068 QItemSelection selection
;
1069 QModelIndexList indexList
= d
->categoriesIndexes
[category
];
1071 foreach (const QModelIndex
&index
, indexList
)
1073 QModelIndex selectIndex
= index
.model()->index(index
.row(), 0);
1075 selection
<< QItemSelectionRange(selectIndex
);
1078 selectionModel()->select(selection
, QItemSelectionModel::SelectCurrent
);
1085 if (d
->hovered
.isValid())
1086 viewport()->update(visualRect(d
->hovered
));
1087 else if (!d
->hoveredCategory
.isEmpty())
1088 viewport()->update(d
->categoryVisualRect(d
->hoveredCategory
));
1091 void KCategorizedView::leaveEvent(QEvent
*event
)
1093 d
->hovered
= QModelIndex();
1094 d
->hoveredCategory
= QString();
1096 QListView::leaveEvent(event
);
1099 void KCategorizedView::startDrag(Qt::DropActions supportedActions
)
1101 // FIXME: QAbstractItemView does far better here since it sets the
1102 // pixmap of selected icons to the dragging cursor, but it sets a non
1103 // ARGB window so it is no transparent. Use QAbstractItemView when
1104 // this is fixed on Qt.
1105 // QAbstractItemView::startDrag(supportedActions);
1106 #if !defined(DOLPHIN_DRAGANDDROP)
1107 QListView::startDrag(supportedActions
);
1110 d
->isDragging
= false;
1111 d
->mouseButtonPressed
= false;
1113 viewport()->update(d
->lastDraggedItemsRect
);
1116 void KCategorizedView::dragMoveEvent(QDragMoveEvent
*event
)
1118 d
->mousePosition
= event
->pos();
1120 if (d
->mouseButtonPressed
)
1122 d
->isDragging
= true;
1126 d
->isDragging
= false;
1129 d
->dragLeftViewport
= false;
1131 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1132 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1134 #if defined(DOLPHIN_DRAGANDDROP)
1135 QAbstractItemView::dragMoveEvent(event
);
1137 QListView::dragMoveEvent(event
);
1142 d
->drawDraggedItems();
1145 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent
*event
)
1147 d
->dragLeftViewport
= true;
1149 #if defined(DOLPHIN_DRAGANDDROP)
1150 QAbstractItemView::dragLeaveEvent(event
);
1152 QListView::dragLeaveEvent(event
);
1156 void KCategorizedView::dropEvent(QDropEvent
*event
)
1158 #if defined(DOLPHIN_DRAGANDDROP)
1159 QAbstractItemView::dropEvent(event
);
1161 QListView::dropEvent(event
);
1165 QModelIndex
KCategorizedView::moveCursor(CursorAction cursorAction
,
1166 Qt::KeyboardModifiers modifiers
)
1168 if ((viewMode() != KCategorizedView::IconMode
) ||
1170 !d
->categoryDrawer
||
1171 d
->categories
.isEmpty() ||
1172 !d
->proxyModel
->isCategorizedModel()
1175 return QListView::moveCursor(cursorAction
, modifiers
);
1178 QModelIndex current
= selectionModel()->currentIndex();
1180 if (!current
.isValid())
1182 current
= model()->index(0, 0, QModelIndex());
1183 setCurrentIndex(current
);
1184 d
->forcedSelectionPosition
= 0;
1189 int viewportWidth
= viewport()->width() - spacing();
1192 if (gridSize().isEmpty())
1194 itemWidth
= d
->biggestItemSize
.width();
1198 itemWidth
= gridSize().width();
1201 int itemWidthPlusSeparation
= spacing() + itemWidth
;
1202 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
1203 if (!elementsPerRow
)
1206 QString lastCategory
= d
->categories
.first();
1207 QString theCategory
= d
->categories
.first();
1208 QString afterCategory
= d
->categories
.first();
1210 bool hasToBreak
= false;
1211 foreach (const QString
&category
, d
->categories
)
1215 afterCategory
= category
;
1220 if (category
== d
->elementsInfo
[current
.row()].category
)
1222 theCategory
= category
;
1229 lastCategory
= category
;
1233 switch (cursorAction
)
1235 case QAbstractItemView::MoveUp
: {
1236 if (d
->elementsInfo
[current
.row()].relativeOffsetToCategory
>= elementsPerRow
)
1238 int indexToMove
= current
.row();
1239 indexToMove
-= qMin(((d
->elementsInfo
[current
.row()].relativeOffsetToCategory
) + d
->forcedSelectionPosition
), elementsPerRow
- d
->forcedSelectionPosition
+ (d
->elementsInfo
[current
.row()].relativeOffsetToCategory
% elementsPerRow
));
1241 return d
->proxyModel
->index(indexToMove
, 0);
1245 int lastCategoryLastRow
= (d
->categoriesIndexes
[lastCategory
].count() - 1) % elementsPerRow
;
1246 int indexToMove
= current
.row() - d
->elementsInfo
[current
.row()].relativeOffsetToCategory
;
1248 if (d
->forcedSelectionPosition
>= lastCategoryLastRow
)
1254 indexToMove
-= qMin((lastCategoryLastRow
- d
->forcedSelectionPosition
+ 1), d
->forcedSelectionPosition
+ elementsPerRow
+ 1);
1257 return d
->proxyModel
->index(indexToMove
, 0);
1261 case QAbstractItemView::MoveDown
: {
1262 if (d
->elementsInfo
[current
.row()].relativeOffsetToCategory
< (d
->categoriesIndexes
[theCategory
].count() - 1 - ((d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
)))
1264 int indexToMove
= current
.row();
1265 indexToMove
+= qMin(elementsPerRow
, d
->categoriesIndexes
[theCategory
].count() - 1 - d
->elementsInfo
[current
.row()].relativeOffsetToCategory
);
1267 return d
->proxyModel
->index(indexToMove
, 0);
1271 int afterCategoryLastRow
= qMin(elementsPerRow
, d
->categoriesIndexes
[afterCategory
].count());
1272 int indexToMove
= current
.row() + (d
->categoriesIndexes
[theCategory
].count() - d
->elementsInfo
[current
.row()].relativeOffsetToCategory
);
1274 if (d
->forcedSelectionPosition
>= afterCategoryLastRow
)
1276 indexToMove
+= afterCategoryLastRow
- 1;
1280 indexToMove
+= qMin(d
->forcedSelectionPosition
, elementsPerRow
);
1283 return d
->proxyModel
->index(indexToMove
, 0);
1287 case QAbstractItemView::MoveLeft
:
1288 if (layoutDirection() == Qt::RightToLeft
)
1290 if (!(d
->elementsInfo
[current
.row() + 1].relativeOffsetToCategory
% elementsPerRow
))
1293 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() + 1].relativeOffsetToCategory
% elementsPerRow
;
1295 #if 0 //follow qt view behavior. lateral movements won't change visual row
1296 if (d
->forcedSelectionPosition
< 0)
1297 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1300 return d
->proxyModel
->index(current
.row() + 1, 0);
1303 if (!(d
->elementsInfo
[current
.row()].relativeOffsetToCategory
% elementsPerRow
))
1306 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() - 1].relativeOffsetToCategory
% elementsPerRow
;
1308 #if 0 //follow qt view behavior. lateral movements won't change visual row
1309 if (d
->forcedSelectionPosition
< 0)
1310 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1313 return d
->proxyModel
->index(current
.row() - 1, 0);
1315 case QAbstractItemView::MoveRight
:
1316 if (layoutDirection() == Qt::RightToLeft
)
1318 if (!(d
->elementsInfo
[current
.row()].relativeOffsetToCategory
% elementsPerRow
))
1321 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() - 1].relativeOffsetToCategory
% elementsPerRow
;
1323 #if 0 //follow qt view behavior. lateral movements won't change visual row
1324 if (d
->forcedSelectionPosition
< 0)
1325 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1328 return d
->proxyModel
->index(current
.row() - 1, 0);
1331 if (!(d
->elementsInfo
[current
.row() + 1].relativeOffsetToCategory
% elementsPerRow
))
1334 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() + 1].relativeOffsetToCategory
% elementsPerRow
;
1336 #if 0 //follow qt view behavior. lateral movements won't change visual row
1337 if (d
->forcedSelectionPosition
< 0)
1338 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1341 return d
->proxyModel
->index(current
.row() + 1, 0);
1347 return QListView::moveCursor(cursorAction
, modifiers
);
1350 void KCategorizedView::rowsInserted(const QModelIndex
&parent
,
1354 QListView::rowsInserted(parent
, start
, end
);
1356 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1357 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1359 d
->lastSelection
= QItemSelection();
1360 d
->currentViewIndex
= QModelIndex();
1361 d
->forcedSelectionPosition
= 0;
1362 d
->elementsInfo
.clear();
1363 d
->elementsPosition
.clear();
1364 d
->categoriesIndexes
.clear();
1365 d
->categoriesPosition
.clear();
1366 d
->categories
.clear();
1367 d
->intersectedIndexes
.clear();
1368 d
->modelIndexList
.clear();
1369 d
->hovered
= QModelIndex();
1370 d
->biggestItemSize
= QSize(0, 0);
1371 d
->mouseButtonPressed
= false;
1376 rowsInsertedArtifficial(parent
, start
, end
);
1379 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex
&parent
,
1385 d
->lastSelection
= QItemSelection();
1386 d
->currentViewIndex
= QModelIndex();
1387 d
->forcedSelectionPosition
= 0;
1388 d
->elementsInfo
.clear();
1389 d
->elementsPosition
.clear();
1390 d
->categoriesIndexes
.clear();
1391 d
->categoriesPosition
.clear();
1392 d
->categories
.clear();
1393 d
->intersectedIndexes
.clear();
1394 d
->modelIndexList
.clear();
1395 d
->hovered
= QModelIndex();
1396 d
->biggestItemSize
= QSize(0, 0);
1397 d
->mouseButtonPressed
= false;
1399 if (start
> end
|| end
< 0 || start
< 0 || !d
->proxyModel
->rowCount())
1404 // Add all elements mapped to the source model and explore categories
1405 QString prevCategory
= d
->proxyModel
->data(d
->proxyModel
->index(0, d
->proxyModel
->sortColumn()), KCategorizedSortFilterProxyModel::CategoryDisplayRole
).toString();
1406 QString lastCategory
= prevCategory
;
1407 QModelIndexList modelIndexList
;
1408 struct Private::ElementInfo elementInfo
;
1410 for (int k
= 0; k
< d
->proxyModel
->rowCount(); ++k
)
1412 QModelIndex index
= d
->proxyModel
->index(k
, d
->proxyModel
->sortColumn());
1413 QModelIndex indexSize
= d
->proxyModel
->index(k
, 0);
1415 d
->biggestItemSize
= QSize(qMax(sizeHintForIndex(indexSize
).width(),
1416 d
->biggestItemSize
.width()),
1417 qMax(sizeHintForIndex(indexSize
).height(),
1418 d
->biggestItemSize
.height()));
1420 d
->modelIndexList
<< index
;
1422 lastCategory
= d
->proxyModel
->data(index
, KCategorizedSortFilterProxyModel::CategoryDisplayRole
).toString();
1424 elementInfo
.category
= lastCategory
;
1426 if (prevCategory
!= lastCategory
)
1429 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1430 d
->categories
<< prevCategory
;
1431 modelIndexList
.clear();
1438 elementInfo
.relativeOffsetToCategory
= offset
;
1440 modelIndexList
<< index
;
1441 prevCategory
= lastCategory
;
1443 d
->elementsInfo
.insert(index
.row(), elementInfo
);
1446 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1447 d
->categories
<< prevCategory
;
1449 d
->updateScrollbars();
1452 void KCategorizedView::rowsRemoved(const QModelIndex
&parent
,
1456 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1457 d
->categoryDrawer
&& d
->proxyModel
->isCategorizedModel())
1459 // Force the view to update all elements
1460 rowsInsertedArtifficial(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
1464 void KCategorizedView::updateGeometries()
1466 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1467 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1469 QListView::updateGeometries();
1473 // Avoid QListView::updateGeometries(), since it will try to set another
1474 // range to our scroll bars, what we don't want (ereslibre)
1475 QAbstractItemView::updateGeometries();
1478 void KCategorizedView::slotLayoutChanged()
1483 #include "kcategorizedview.moc"