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 // By defining KDE_WORKAROUND_FOR_QT_VIEW_BUG we save the selection being held
43 // before mousePressEvent happens. On Qt there is something clearing the last
44 // selection made, what make it impossible to save our very last selection.
45 #define KDE_WORKAROUND_FOR_QT_VIEW_BUG
47 KCategorizedView::Private::Private(KCategorizedView
*listView
)
50 , biggestItemSize(QSize(0, 0))
51 , mouseButtonPressed(false)
53 , dragLeftViewport(false)
58 KCategorizedView::Private::~Private()
62 const QModelIndexList
&KCategorizedView::Private::intersectionSet(const QRect
&rect
)
65 QRect indexVisualRect
;
67 intersectedIndexes
.clear();
71 if (listView
->gridSize().isEmpty())
73 itemHeight
= biggestItemSize
.height();
77 itemHeight
= listView
->gridSize().height();
80 // Lets find out where we should start
81 int top
= proxyModel
->rowCount() - 1;
83 int middle
= (top
+ bottom
) / 2;
86 middle
= (top
+ bottom
) / 2;
88 index
= proxyModel
->index(middle
, 0);
89 indexVisualRect
= visualRect(index
);
90 // We need the whole height (not only the visualRect). This will help us to update
91 // all needed indexes correctly (ereslibre)
92 indexVisualRect
.setHeight(indexVisualRect
.height() + (itemHeight
- indexVisualRect
.height()));
94 if (qMax(indexVisualRect
.topLeft().y(),
95 indexVisualRect
.bottomRight().y()) < qMin(rect
.topLeft().y(),
96 rect
.bottomRight().y()))
106 for (int i
= middle
; i
< proxyModel
->rowCount(); i
++)
108 index
= proxyModel
->index(i
, 0);
109 indexVisualRect
= visualRect(index
);
111 if (rect
.intersects(indexVisualRect
))
112 intersectedIndexes
.append(index
);
114 // If we passed next item, stop searching for hits
115 if (qMax(rect
.bottomRight().y(), rect
.topLeft().y()) <
116 qMin(indexVisualRect
.topLeft().y(),
117 indexVisualRect
.bottomRight().y()))
121 return intersectedIndexes
;
124 QRect
KCategorizedView::Private::visualRectInViewport(const QModelIndex
&index
) const
126 if (!index
.isValid())
129 QString curCategory
= elementsInfo
[index
.row()].category
;
133 if (listView
->layoutDirection() == Qt::LeftToRight
)
135 retRect
= QRect(listView
->spacing(), listView
->spacing() * 2 +
136 categoryDrawer
->categoryHeight(listView
->viewOptions()), 0, 0);
140 retRect
= QRect(listView
->viewport()->width() - listView
->spacing(), listView
->spacing() * 2 +
141 categoryDrawer
->categoryHeight(listView
->viewOptions()), 0, 0);
144 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
149 if (listView
->gridSize().isEmpty())
151 itemHeight
= biggestItemSize
.height();
152 itemWidth
= biggestItemSize
.width();
156 itemHeight
= listView
->gridSize().height();
157 itemWidth
= listView
->gridSize().width();
160 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
161 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
165 int column
= elementsInfo
[index
.row()].relativeOffsetToCategory
% elementsPerRow
;
166 int row
= elementsInfo
[index
.row()].relativeOffsetToCategory
/ elementsPerRow
;
168 if (listView
->layoutDirection() == Qt::LeftToRight
)
170 retRect
.setLeft(retRect
.left() + column
* listView
->spacing() +
175 retRect
.setLeft(retRect
.right() - column
* listView
->spacing() -
176 column
* itemWidth
- itemWidth
);
178 retRect
.setRight(retRect
.right() - column
* listView
->spacing() -
182 foreach (const QString
&category
, categories
)
184 if (category
== curCategory
)
187 float rows
= (float) ((float) categoriesIndexes
[category
].count() /
188 (float) elementsPerRow
);
190 int rowsInt
= categoriesIndexes
[category
].count() / elementsPerRow
;
192 if (rows
- trunc(rows
)) rowsInt
++;
194 retRect
.setTop(retRect
.top() +
195 (rowsInt
* itemHeight
) +
196 categoryDrawer
->categoryHeight(listView
->viewOptions()) +
197 listView
->spacing() * 2);
199 if (listView
->gridSize().isEmpty())
201 retRect
.setTop(retRect
.top() +
202 (rowsInt
* listView
->spacing()));
206 if (listView
->gridSize().isEmpty())
208 retRect
.setTop(retRect
.top() + row
* listView
->spacing() +
213 retRect
.setTop(retRect
.top() + (row
* itemHeight
));
216 retRect
.setWidth(itemWidth
);
218 QModelIndex heightIndex
= proxyModel
->index(index
.row(), 0);
219 if (listView
->gridSize().isEmpty())
221 retRect
.setHeight(listView
->sizeHintForIndex(heightIndex
).height());
225 retRect
.setHeight(qMin(listView
->sizeHintForIndex(heightIndex
).height(),
226 listView
->gridSize().height()));
232 QRect
KCategorizedView::Private::visualCategoryRectInViewport(const QString
&category
)
235 QRect
retRect(listView
->spacing(),
237 listView
->viewport()->width() - listView
->spacing() * 2,
240 if (!proxyModel
->rowCount() || !categories
.contains(category
))
243 QModelIndex index
= proxyModel
->index(0, 0, QModelIndex());
245 int viewportWidth
= listView
->viewport()->width() - listView
->spacing();
250 if (listView
->gridSize().isEmpty())
252 itemHeight
= biggestItemSize
.height();
253 itemWidth
= biggestItemSize
.width();
257 itemHeight
= listView
->gridSize().height();
258 itemWidth
= listView
->gridSize().width();
261 int itemWidthPlusSeparation
= listView
->spacing() + itemWidth
;
262 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
267 foreach (const QString
&itCategory
, categories
)
269 if (itCategory
== category
)
272 float rows
= (float) ((float) categoriesIndexes
[itCategory
].count() /
273 (float) elementsPerRow
);
274 int rowsInt
= categoriesIndexes
[itCategory
].count() / elementsPerRow
;
276 if (rows
- trunc(rows
)) rowsInt
++;
278 retRect
.setTop(retRect
.top() +
279 (rowsInt
* itemHeight
) +
280 categoryDrawer
->categoryHeight(listView
->viewOptions()) +
281 listView
->spacing() * 2);
283 if (listView
->gridSize().isEmpty())
285 retRect
.setTop(retRect
.top() +
286 (rowsInt
* listView
->spacing()));
290 retRect
.setHeight(categoryDrawer
->categoryHeight(listView
->viewOptions()));
295 // We're sure elementsPosition doesn't contain index
296 const QRect
&KCategorizedView::Private::cacheIndex(const QModelIndex
&index
)
298 QRect rect
= visualRectInViewport(index
);
299 elementsPosition
[index
.row()] = rect
;
301 return elementsPosition
[index
.row()];
304 // We're sure categoriesPosition doesn't contain category
305 const QRect
&KCategorizedView::Private::cacheCategory(const QString
&category
)
307 QRect rect
= visualCategoryRectInViewport(category
);
308 categoriesPosition
[category
] = rect
;
310 return categoriesPosition
[category
];
313 const QRect
&KCategorizedView::Private::cachedRectIndex(const QModelIndex
&index
)
315 if (elementsPosition
.contains(index
.row())) // If we have it cached
317 return elementsPosition
[index
.row()];
319 else // Otherwise, cache it
321 return cacheIndex(index
);
325 const QRect
&KCategorizedView::Private::cachedRectCategory(const QString
&category
)
327 if (categoriesPosition
.contains(category
)) // If we have it cached
329 return categoriesPosition
[category
];
331 else // Otherwise, cache it and
333 return cacheCategory(category
);
337 QRect
KCategorizedView::Private::visualRect(const QModelIndex
&index
)
339 QRect retRect
= cachedRectIndex(index
);
340 int dx
= -listView
->horizontalOffset();
341 int dy
= -listView
->verticalOffset();
342 retRect
.adjust(dx
, dy
, dx
, dy
);
347 QRect
KCategorizedView::Private::categoryVisualRect(const QString
&category
)
349 QRect retRect
= cachedRectCategory(category
);
350 int dx
= -listView
->horizontalOffset();
351 int dy
= -listView
->verticalOffset();
352 retRect
.adjust(dx
, dy
, dx
, dy
);
357 void KCategorizedView::Private::drawNewCategory(const QModelIndex
&index
,
359 const QStyleOption
&option
,
362 if (!index
.isValid())
367 QStyleOption optionCopy
= option
;
368 const QString category
= proxyModel
->data(index
, KCategorizedSortFilterProxyModel::CategoryDisplayRole
).toString();
370 optionCopy
.state
&= ~QStyle::State_Selected
;
372 if ((category
== hoveredCategory
) && !mouseButtonPressed
)
374 optionCopy
.state
|= QStyle::State_MouseOver
;
376 else if ((category
== hoveredCategory
) && mouseButtonPressed
)
378 QPoint initialPressPosition
= listView
->viewport()->mapFromGlobal(QCursor::pos());
379 initialPressPosition
.setY(initialPressPosition
.y() + listView
->verticalOffset());
380 initialPressPosition
.setX(initialPressPosition
.x() + listView
->horizontalOffset());
382 if (initialPressPosition
== this->initialPressPosition
)
384 optionCopy
.state
|= QStyle::State_Selected
;
388 categoryDrawer
->drawCategory(index
,
395 void KCategorizedView::Private::updateScrollbars()
397 // find the last index in the last category
398 QModelIndex lastIndex
= categoriesIndexes
.isEmpty() ? QModelIndex() : categoriesIndexes
[categories
.last()].last();
400 int lastItemBottom
= cachedRectIndex(lastIndex
).top() +
401 listView
->spacing() + (listView
->gridSize().isEmpty() ? 0 : listView
->gridSize().height()) - listView
->viewport()->height();
403 listView
->horizontalScrollBar()->setRange(0, 0);
405 listView
->verticalScrollBar()->setSingleStep(listView
->viewport()->height() / 10);
406 listView
->verticalScrollBar()->setPageStep(listView
->viewport()->height());
407 listView
->verticalScrollBar()->setRange(0, lastItemBottom
);
410 void KCategorizedView::Private::drawDraggedItems(QPainter
*painter
)
412 QStyleOptionViewItemV3 option
= listView
->viewOptions();
413 option
.state
&= ~QStyle::State_MouseOver
;
414 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
416 const int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
417 const int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
419 option
.rect
= visualRect(index
);
420 option
.rect
.adjust(dx
, dy
, dx
, dy
);
422 if (option
.rect
.intersects(listView
->viewport()->rect()))
424 listView
->itemDelegate(index
)->paint(painter
, option
, index
);
429 void KCategorizedView::Private::layoutChanged(bool forceItemReload
)
431 if ((listView
->viewMode() == KCategorizedView::IconMode
) && proxyModel
&&
432 categoryDrawer
&& proxyModel
->isCategorizedModel() &&
434 (modelSortRole
!= proxyModel
->sortRole()) ||
435 (modelSortColumn
!= proxyModel
->sortColumn()) ||
436 (modelSortOrder
!= proxyModel
->sortOrder()) ||
437 (modelLastRowCount
!= proxyModel
->rowCount()) ||
438 (modelCategorized
!= proxyModel
->isCategorizedModel()))))
440 // Force the view to update all elements
441 listView
->rowsInsertedArtifficial(QModelIndex(), 0, proxyModel
->rowCount() - 1);
443 if (!forceItemReload
)
445 modelSortRole
= proxyModel
->sortRole();
446 modelSortColumn
= proxyModel
->sortColumn();
447 modelSortOrder
= proxyModel
->sortOrder();
448 modelLastRowCount
= proxyModel
->rowCount();
449 modelCategorized
= proxyModel
->isCategorizedModel();
452 else if ((listView
->viewMode() == KCategorizedView::IconMode
) && proxyModel
&&
453 categoryDrawer
&& proxyModel
->isCategorizedModel())
459 void KCategorizedView::Private::drawDraggedItems()
463 foreach (const QModelIndex
&index
, listView
->selectionModel()->selectedIndexes())
465 int dx
= mousePosition
.x() - initialPressPosition
.x() + listView
->horizontalOffset();
466 int dy
= mousePosition
.y() - initialPressPosition
.y() + listView
->verticalOffset();
468 currentRect
= visualRect(index
);
469 currentRect
.adjust(dx
, dy
, dx
, dy
);
471 if (currentRect
.intersects(listView
->viewport()->rect()))
473 rectToUpdate
= rectToUpdate
.united(currentRect
);
477 listView
->viewport()->update(lastDraggedItemsRect
.united(rectToUpdate
));
479 lastDraggedItemsRect
= rectToUpdate
;
483 //==============================================================================
486 KCategorizedView::KCategorizedView(QWidget
*parent
)
488 , d(new Private(this))
492 KCategorizedView::~KCategorizedView()
497 void KCategorizedView::setGridSize(const QSize
&size
)
499 QListView::setGridSize(size
);
501 d
->layoutChanged(true);
504 void KCategorizedView::setModel(QAbstractItemModel
*model
)
506 d
->lastSelection
= QItemSelection();
507 d
->currentViewIndex
= QModelIndex();
508 d
->forcedSelectionPosition
= 0;
509 d
->elementsInfo
.clear();
510 d
->elementsPosition
.clear();
511 d
->categoriesIndexes
.clear();
512 d
->categoriesPosition
.clear();
513 d
->categories
.clear();
514 d
->intersectedIndexes
.clear();
515 d
->modelIndexList
.clear();
516 d
->hovered
= QModelIndex();
517 d
->mouseButtonPressed
= false;
521 QObject::disconnect(d
->proxyModel
,
522 SIGNAL(layoutChanged()),
523 this, SLOT(slotLayoutChanged()));
525 QObject::disconnect(d
->proxyModel
,
526 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
527 this, SLOT(slotLayoutChanged()));
529 QObject::disconnect(d
->proxyModel
,
530 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
531 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
534 QListView::setModel(model
);
536 d
->proxyModel
= dynamic_cast<KCategorizedSortFilterProxyModel
*>(model
);
540 d
->modelSortRole
= d
->proxyModel
->sortRole();
541 d
->modelSortColumn
= d
->proxyModel
->sortColumn();
542 d
->modelSortOrder
= d
->proxyModel
->sortOrder();
543 d
->modelLastRowCount
= d
->proxyModel
->rowCount();
544 d
->modelCategorized
= d
->proxyModel
->isCategorizedModel();
546 QObject::connect(d
->proxyModel
,
547 SIGNAL(layoutChanged()),
548 this, SLOT(slotLayoutChanged()));
550 QObject::connect(d
->proxyModel
,
551 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
552 this, SLOT(slotLayoutChanged()));
554 QObject::connect(d
->proxyModel
,
555 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
556 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
558 if (d
->proxyModel
->rowCount())
560 d
->layoutChanged(true);
565 d
->modelCategorized
= false;
569 QRect
KCategorizedView::visualRect(const QModelIndex
&index
) const
571 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
572 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
574 return QListView::visualRect(index
);
577 if (!qobject_cast
<const QSortFilterProxyModel
*>(index
.model()))
579 return d
->visualRect(d
->proxyModel
->mapFromSource(index
));
582 return d
->visualRect(index
);
585 KCategoryDrawer
*KCategorizedView::categoryDrawer() const
587 return d
->categoryDrawer
;
590 void KCategorizedView::setCategoryDrawer(KCategoryDrawer
*categoryDrawer
)
592 d
->lastSelection
= QItemSelection();
593 d
->currentViewIndex
= QModelIndex();
594 d
->forcedSelectionPosition
= 0;
595 d
->elementsInfo
.clear();
596 d
->elementsPosition
.clear();
597 d
->categoriesIndexes
.clear();
598 d
->categoriesPosition
.clear();
599 d
->categories
.clear();
600 d
->intersectedIndexes
.clear();
601 d
->modelIndexList
.clear();
602 d
->hovered
= QModelIndex();
603 d
->mouseButtonPressed
= false;
605 if (!categoryDrawer
&& d
->proxyModel
)
607 QObject::disconnect(d
->proxyModel
,
608 SIGNAL(layoutChanged()),
609 this, SLOT(slotLayoutChanged()));
611 QObject::disconnect(d
->proxyModel
,
612 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
613 this, SLOT(slotLayoutChanged()));
615 QObject::disconnect(d
->proxyModel
,
616 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
617 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
619 else if (categoryDrawer
&& d
->proxyModel
)
621 QObject::connect(d
->proxyModel
,
622 SIGNAL(layoutChanged()),
623 this, SLOT(slotLayoutChanged()));
625 QObject::connect(d
->proxyModel
,
626 SIGNAL(dataChanged(QModelIndex
,QModelIndex
)),
627 this, SLOT(slotLayoutChanged()));
629 QObject::connect(d
->proxyModel
,
630 SIGNAL(rowsRemoved(QModelIndex
,int,int)),
631 this, SLOT(rowsRemoved(QModelIndex
,int,int)));
634 d
->categoryDrawer
= categoryDrawer
;
640 if (d
->proxyModel
->rowCount())
642 d
->layoutChanged(true);
652 QModelIndex
KCategorizedView::indexAt(const QPoint
&point
) const
654 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
655 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
657 return QListView::indexAt(point
);
662 QModelIndexList item
= d
->intersectionSet(QRect(point
, point
));
664 if (item
.count() == 1)
674 void KCategorizedView::reset()
678 d
->lastSelection
= QItemSelection();
679 d
->currentViewIndex
= QModelIndex();
680 d
->forcedSelectionPosition
= 0;
681 d
->elementsInfo
.clear();
682 d
->elementsPosition
.clear();
683 d
->categoriesIndexes
.clear();
684 d
->categoriesPosition
.clear();
685 d
->categories
.clear();
686 d
->intersectedIndexes
.clear();
687 d
->modelIndexList
.clear();
688 d
->hovered
= QModelIndex();
689 d
->biggestItemSize
= QSize(0, 0);
690 d
->mouseButtonPressed
= false;
693 void KCategorizedView::paintEvent(QPaintEvent
*event
)
695 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
696 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
698 QListView::paintEvent(event
);
702 QStyleOptionViewItemV3 option
= viewOptions();
703 option
.widget
= this;
706 option
.features
|= QStyleOptionViewItemV2::WrapText
;
709 QPainter
painter(viewport());
710 QRect area
= event
->rect();
711 const bool focus
= (hasFocus() || viewport()->hasFocus()) &&
712 currentIndex().isValid();
713 const QStyle::State state
= option
.state
;
714 const bool enabled
= (state
& QStyle::State_Enabled
) != 0;
718 QModelIndexList dirtyIndexes
= d
->intersectionSet(area
);
719 foreach (const QModelIndex
&index
, dirtyIndexes
)
721 option
.state
= state
;
722 option
.rect
= visualRect(index
);
724 if (selectionModel() && selectionModel()->isSelected(index
))
726 option
.state
|= QStyle::State_Selected
;
731 QPalette::ColorGroup cg
;
732 if ((d
->proxyModel
->flags(index
) & Qt::ItemIsEnabled
) == 0)
734 option
.state
&= ~QStyle::State_Enabled
;
735 cg
= QPalette::Disabled
;
739 cg
= QPalette::Normal
;
741 option
.palette
.setCurrentColorGroup(cg
);
744 if (focus
&& currentIndex() == index
)
746 option
.state
|= QStyle::State_HasFocus
;
747 if (this->state() == EditingState
)
748 option
.state
|= QStyle::State_Editing
;
751 if ((index
== d
->hovered
) && !d
->mouseButtonPressed
)
752 option
.state
|= QStyle::State_MouseOver
;
754 option
.state
&= ~QStyle::State_MouseOver
;
756 itemDelegate(index
)->paint(&painter
, option
, index
);
760 QStyleOptionViewItem otherOption
;
761 bool intersectedInThePast
= false;
762 foreach (const QString
&category
, d
->categories
)
764 otherOption
= option
;
765 otherOption
.rect
= d
->categoryVisualRect(category
);
766 otherOption
.state
&= ~QStyle::State_MouseOver
;
768 if (otherOption
.rect
.intersects(area
))
770 intersectedInThePast
= true;
772 QModelIndex indexToDraw
= d
->proxyModel
->index(d
->categoriesIndexes
[category
][0].row(), d
->proxyModel
->sortColumn());
774 d
->drawNewCategory(indexToDraw
,
775 d
->proxyModel
->sortRole(), otherOption
, &painter
);
777 else if (intersectedInThePast
)
779 break; // the visible area has been finished, we don't need to keep asking, the rest won't intersect
780 // this is doable because we know that categories are correctly ordered on the list
784 if (d
->mouseButtonPressed
&& !d
->isDragging
)
786 QPoint start
, end
, initialPressPosition
;
788 initialPressPosition
= d
->initialPressPosition
;
790 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
791 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
793 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
794 d
->initialPressPosition
.y() > d
->mousePosition
.y())
796 start
= d
->mousePosition
;
797 end
= initialPressPosition
;
801 start
= initialPressPosition
;
802 end
= d
->mousePosition
;
805 QStyleOptionRubberBand yetAnotherOption
;
806 yetAnotherOption
.initFrom(this);
807 yetAnotherOption
.shape
= QRubberBand::Rectangle
;
808 yetAnotherOption
.opaque
= false;
809 yetAnotherOption
.rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
811 style()->drawControl(QStyle::CE_RubberBand
, &yetAnotherOption
, &painter
);
815 if (d
->isDragging
&& !d
->dragLeftViewport
)
817 painter
.setOpacity(0.5);
818 d
->drawDraggedItems(&painter
);
824 void KCategorizedView::resizeEvent(QResizeEvent
*event
)
826 QListView::resizeEvent(event
);
828 // Clear the items positions cache
829 d
->elementsPosition
.clear();
830 d
->categoriesPosition
.clear();
831 d
->forcedSelectionPosition
= 0;
833 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
834 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
839 d
->updateScrollbars();
842 void KCategorizedView::setSelection(const QRect
&rect
,
843 QItemSelectionModel::SelectionFlags flags
)
845 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
846 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
848 QListView::setSelection(rect
, flags
);
855 if (flags
& QItemSelectionModel::Clear
)
857 selectionModel()->clear();
860 QModelIndexList dirtyIndexes
= d
->intersectionSet(rect
);
862 // no items affected, just leave
863 if (!dirtyIndexes
.count())
865 selectionModel()->select(d
->lastSelection
, flags
);
867 d
->lastSelection
.clear();
872 d
->lastSelection
.clear();
874 if (!(flags
& QItemSelectionModel::Current
))
876 Q_ASSERT(dirtyIndexes
.count() == 1);
878 selectionModel()->select(dirtyIndexes
[0], flags
);
884 QModelIndex bottomRight
;
886 if (d
->mouseButtonPressed
) // selection with click + drag
888 QModelIndex prev
= dirtyIndexes
[0];
889 QModelIndex first
= prev
;
890 foreach (const QModelIndex
&index
, dirtyIndexes
)
892 if ((index
.row() - prev
.row()) > 1) {
893 d
->lastSelection
<< QItemSelectionRange(first
, prev
);
901 d
->lastSelection
<< QItemSelectionRange(first
, prev
);
903 selectionModel()->select(d
->lastSelection
, flags
);
905 else // selection with click + keyboard keys
907 QModelIndex topLeftIndex
= indexAt(QPoint(rect
.topLeft().x(),
908 rect
.topLeft().y()));
909 QModelIndex bottomRightIndex
= indexAt(QPoint(rect
.bottomRight().x(),
910 rect
.bottomRight().y()));
912 // keyboard selection comes "upside down". Let's normalize it
913 if (topLeftIndex
.row() > bottomRightIndex
.row())
915 QModelIndex auxIndex
= topLeftIndex
;
916 topLeftIndex
= bottomRightIndex
;
917 bottomRightIndex
= auxIndex
;
920 int viewportWidth
= viewport()->width() - spacing();
923 if (gridSize().isEmpty())
925 itemWidth
= d
->biggestItemSize
.width();
929 itemWidth
= gridSize().width();
932 int itemWidthPlusSeparation
= spacing() + itemWidth
;
933 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
937 QModelIndexList
theoricDirty(dirtyIndexes
);
938 dirtyIndexes
.clear();
939 int first
= model()->rowCount();
942 foreach (const QModelIndex
&index
, theoricDirty
)
944 if ((index
.row() < first
) &&
945 ((((topLeftIndex
.row() / elementsPerRow
) == (index
.row() / elementsPerRow
)) &&
946 ((topLeftIndex
.row() % elementsPerRow
) <= (index
.row() % elementsPerRow
))) ||
947 (topLeftIndex
.row() / elementsPerRow
) != (index
.row() / elementsPerRow
)))
953 if ((index
.row() > last
) &&
954 ((((bottomRightIndex
.row() / elementsPerRow
) == (index
.row() / elementsPerRow
)) &&
955 ((bottomRightIndex
.row() % elementsPerRow
) >= (index
.row() % elementsPerRow
))) ||
956 (bottomRightIndex
.row() / elementsPerRow
) != (index
.row() / elementsPerRow
)))
963 for (int i
= first
; i
<= last
; i
++)
965 dirtyIndexes
<< model()->index(i
, theoricDirty
[0].column(), theoricDirty
[0].parent());
968 // our current selection will result modified
969 d
->lastSelection
= QItemSelection(topLeft
, bottomRight
);
971 selectionModel()->select(d
->lastSelection
, flags
);
975 void KCategorizedView::mouseMoveEvent(QMouseEvent
*event
)
977 QListView::mouseMoveEvent(event
);
979 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
980 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
985 const QString previousHoveredCategory
= d
->hoveredCategory
;
987 d
->mousePosition
= event
->pos();
988 d
->hoveredCategory
= QString();
991 foreach (const QString
&category
, d
->categories
)
993 if (d
->categoryVisualRect(category
).intersects(QRect(event
->pos(), event
->pos())))
995 d
->hoveredCategory
= category
;
996 viewport()->update(d
->categoryVisualRect(category
));
998 else if ((category
== previousHoveredCategory
) &&
999 (!d
->categoryVisualRect(previousHoveredCategory
).intersects(QRect(event
->pos(), event
->pos()))))
1001 viewport()->update(d
->categoryVisualRect(category
));
1006 if (d
->mouseButtonPressed
&& !d
->isDragging
)
1008 QPoint start
, end
, initialPressPosition
;
1010 initialPressPosition
= d
->initialPressPosition
;
1012 initialPressPosition
.setY(initialPressPosition
.y() - verticalOffset());
1013 initialPressPosition
.setX(initialPressPosition
.x() - horizontalOffset());
1015 if (d
->initialPressPosition
.x() > d
->mousePosition
.x() ||
1016 d
->initialPressPosition
.y() > d
->mousePosition
.y())
1018 start
= d
->mousePosition
;
1019 end
= initialPressPosition
;
1023 start
= initialPressPosition
;
1024 end
= d
->mousePosition
;
1027 rect
= QRect(start
, end
).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
1031 void KCategorizedView::mousePressEvent(QMouseEvent
*event
)
1033 d
->dragLeftViewport
= false;
1035 if (event
->button() == Qt::LeftButton
)
1037 d
->mouseButtonPressed
= true;
1039 d
->initialPressPosition
= event
->pos();
1040 d
->initialPressPosition
.setY(d
->initialPressPosition
.y() +
1042 d
->initialPressPosition
.setX(d
->initialPressPosition
.x() +
1043 horizontalOffset());
1046 #ifdef KDE_WORKAROUND_FOR_QT_VIEW_BUG
1047 QItemSelection prevSelection
= selectionModel()->selection();
1049 QListView::mousePressEvent(event
);
1050 #ifdef KDE_WORKAROUND_FOR_QT_VIEW_BUG
1051 selectionModel()->select(prevSelection
, QItemSelectionModel::Select
);
1054 viewport()->update(d
->categoryVisualRect(d
->hoveredCategory
));
1057 void KCategorizedView::mouseReleaseEvent(QMouseEvent
*event
)
1059 d
->mouseButtonPressed
= false;
1061 QListView::mouseReleaseEvent(event
);
1063 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1064 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1069 QPoint initialPressPosition
= viewport()->mapFromGlobal(QCursor::pos());
1070 initialPressPosition
.setY(initialPressPosition
.y() + verticalOffset());
1071 initialPressPosition
.setX(initialPressPosition
.x() + horizontalOffset());
1073 if (initialPressPosition
== d
->initialPressPosition
)
1075 foreach(const QString
&category
, d
->categories
)
1077 if (d
->categoryVisualRect(category
).contains(event
->pos()))
1079 QItemSelection selection
;
1080 QModelIndexList indexList
= d
->categoriesIndexes
[category
];
1082 foreach (const QModelIndex
&index
, indexList
)
1084 QModelIndex selectIndex
= index
.model()->index(index
.row(), 0);
1086 selection
<< QItemSelectionRange(selectIndex
);
1089 selectionModel()->select(selection
, QItemSelectionModel::SelectCurrent
);
1096 if (d
->hovered
.isValid())
1097 viewport()->update(visualRect(d
->hovered
));
1098 else if (!d
->hoveredCategory
.isEmpty())
1099 viewport()->update(d
->categoryVisualRect(d
->hoveredCategory
));
1102 void KCategorizedView::leaveEvent(QEvent
*event
)
1104 d
->hovered
= QModelIndex();
1105 d
->hoveredCategory
= QString();
1107 QListView::leaveEvent(event
);
1110 void KCategorizedView::startDrag(Qt::DropActions supportedActions
)
1112 // FIXME: QAbstractItemView does far better here since it sets the
1113 // pixmap of selected icons to the dragging cursor, but it sets a non
1114 // ARGB window so it is no transparent. Use QAbstractItemView when
1115 // this is fixed on Qt.
1116 // QAbstractItemView::startDrag(supportedActions);
1117 #if !defined(DOLPHIN_DRAGANDDROP)
1118 QListView::startDrag(supportedActions
);
1121 d
->isDragging
= false;
1122 d
->mouseButtonPressed
= false;
1124 viewport()->update(d
->lastDraggedItemsRect
);
1127 void KCategorizedView::dragMoveEvent(QDragMoveEvent
*event
)
1129 d
->mousePosition
= event
->pos();
1131 if (d
->mouseButtonPressed
)
1133 d
->isDragging
= true;
1137 d
->isDragging
= false;
1140 d
->dragLeftViewport
= false;
1142 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1143 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1145 #if defined(DOLPHIN_DRAGANDDROP)
1146 QAbstractItemView::dragMoveEvent(event
);
1148 QListView::dragMoveEvent(event
);
1153 d
->drawDraggedItems();
1156 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent
*event
)
1158 d
->dragLeftViewport
= true;
1160 #if defined(DOLPHIN_DRAGANDDROP)
1161 QAbstractItemView::dragLeaveEvent(event
);
1163 QListView::dragLeaveEvent(event
);
1167 void KCategorizedView::dropEvent(QDropEvent
*event
)
1169 #if defined(DOLPHIN_DRAGANDDROP)
1170 QAbstractItemView::dropEvent(event
);
1172 QListView::dropEvent(event
);
1176 QModelIndex
KCategorizedView::moveCursor(CursorAction cursorAction
,
1177 Qt::KeyboardModifiers modifiers
)
1179 if ((viewMode() != KCategorizedView::IconMode
) ||
1181 !d
->categoryDrawer
||
1182 d
->categories
.isEmpty() ||
1183 !d
->proxyModel
->isCategorizedModel()
1186 return QListView::moveCursor(cursorAction
, modifiers
);
1189 QModelIndex current
= selectionModel()->currentIndex();
1191 if (!current
.isValid())
1193 current
= model()->index(0, 0, QModelIndex());
1194 setCurrentIndex(current
);
1195 d
->forcedSelectionPosition
= 0;
1200 int viewportWidth
= viewport()->width() - spacing();
1203 if (gridSize().isEmpty())
1205 itemWidth
= d
->biggestItemSize
.width();
1209 itemWidth
= gridSize().width();
1212 int itemWidthPlusSeparation
= spacing() + itemWidth
;
1213 int elementsPerRow
= viewportWidth
/ itemWidthPlusSeparation
;
1214 if (!elementsPerRow
)
1217 QString lastCategory
= d
->categories
.first();
1218 QString theCategory
= d
->categories
.first();
1219 QString afterCategory
= d
->categories
.first();
1221 bool hasToBreak
= false;
1222 foreach (const QString
&category
, d
->categories
)
1226 afterCategory
= category
;
1231 if (category
== d
->elementsInfo
[current
.row()].category
)
1233 theCategory
= category
;
1240 lastCategory
= category
;
1244 switch (cursorAction
)
1246 case QAbstractItemView::MoveUp
: {
1247 if (d
->elementsInfo
[current
.row()].relativeOffsetToCategory
>= elementsPerRow
)
1249 int indexToMove
= current
.row();
1250 indexToMove
-= qMin(((d
->elementsInfo
[current
.row()].relativeOffsetToCategory
) + d
->forcedSelectionPosition
), elementsPerRow
- d
->forcedSelectionPosition
+ (d
->elementsInfo
[current
.row()].relativeOffsetToCategory
% elementsPerRow
));
1252 return d
->proxyModel
->index(indexToMove
, 0);
1256 int lastCategoryLastRow
= (d
->categoriesIndexes
[lastCategory
].count() - 1) % elementsPerRow
;
1257 int indexToMove
= current
.row() - d
->elementsInfo
[current
.row()].relativeOffsetToCategory
;
1259 if (d
->forcedSelectionPosition
>= lastCategoryLastRow
)
1265 indexToMove
-= qMin((lastCategoryLastRow
- d
->forcedSelectionPosition
+ 1), d
->forcedSelectionPosition
+ elementsPerRow
+ 1);
1268 return d
->proxyModel
->index(indexToMove
, 0);
1272 case QAbstractItemView::MoveDown
: {
1273 if (d
->elementsInfo
[current
.row()].relativeOffsetToCategory
< (d
->categoriesIndexes
[theCategory
].count() - 1 - ((d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
)))
1275 int indexToMove
= current
.row();
1276 indexToMove
+= qMin(elementsPerRow
, d
->categoriesIndexes
[theCategory
].count() - 1 - d
->elementsInfo
[current
.row()].relativeOffsetToCategory
);
1278 return d
->proxyModel
->index(indexToMove
, 0);
1282 int afterCategoryLastRow
= qMin(elementsPerRow
, d
->categoriesIndexes
[afterCategory
].count());
1283 int indexToMove
= current
.row() + (d
->categoriesIndexes
[theCategory
].count() - d
->elementsInfo
[current
.row()].relativeOffsetToCategory
);
1285 if (d
->forcedSelectionPosition
>= afterCategoryLastRow
)
1287 indexToMove
+= afterCategoryLastRow
- 1;
1291 indexToMove
+= qMin(d
->forcedSelectionPosition
, elementsPerRow
);
1294 return d
->proxyModel
->index(indexToMove
, 0);
1298 case QAbstractItemView::MoveLeft
:
1299 if (layoutDirection() == Qt::RightToLeft
)
1301 if (!(d
->elementsInfo
[current
.row() + 1].relativeOffsetToCategory
% elementsPerRow
))
1304 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() + 1].relativeOffsetToCategory
% elementsPerRow
;
1306 #if 0 //follow qt view behavior. lateral movements won't change visual row
1307 if (d
->forcedSelectionPosition
< 0)
1308 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1311 return d
->proxyModel
->index(current
.row() + 1, 0);
1314 if (!(d
->elementsInfo
[current
.row()].relativeOffsetToCategory
% elementsPerRow
))
1317 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() - 1].relativeOffsetToCategory
% elementsPerRow
;
1319 #if 0 //follow qt view behavior. lateral movements won't change visual row
1320 if (d
->forcedSelectionPosition
< 0)
1321 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1324 return d
->proxyModel
->index(current
.row() - 1, 0);
1326 case QAbstractItemView::MoveRight
:
1327 if (layoutDirection() == Qt::RightToLeft
)
1329 if (!(d
->elementsInfo
[current
.row()].relativeOffsetToCategory
% elementsPerRow
))
1332 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() - 1].relativeOffsetToCategory
% elementsPerRow
;
1334 #if 0 //follow qt view behavior. lateral movements won't change visual row
1335 if (d
->forcedSelectionPosition
< 0)
1336 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1339 return d
->proxyModel
->index(current
.row() - 1, 0);
1342 if (!(d
->elementsInfo
[current
.row() + 1].relativeOffsetToCategory
% elementsPerRow
))
1345 d
->forcedSelectionPosition
= d
->elementsInfo
[current
.row() + 1].relativeOffsetToCategory
% elementsPerRow
;
1347 #if 0 //follow qt view behavior. lateral movements won't change visual row
1348 if (d
->forcedSelectionPosition
< 0)
1349 d
->forcedSelectionPosition
= (d
->categoriesIndexes
[theCategory
].count() - 1) % elementsPerRow
;
1352 return d
->proxyModel
->index(current
.row() + 1, 0);
1358 return QListView::moveCursor(cursorAction
, modifiers
);
1361 void KCategorizedView::rowsInserted(const QModelIndex
&parent
,
1365 QListView::rowsInserted(parent
, start
, end
);
1367 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1368 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1370 d
->lastSelection
= QItemSelection();
1371 d
->currentViewIndex
= QModelIndex();
1372 d
->forcedSelectionPosition
= 0;
1373 d
->elementsInfo
.clear();
1374 d
->elementsPosition
.clear();
1375 d
->categoriesIndexes
.clear();
1376 d
->categoriesPosition
.clear();
1377 d
->categories
.clear();
1378 d
->intersectedIndexes
.clear();
1379 d
->modelIndexList
.clear();
1380 d
->hovered
= QModelIndex();
1381 d
->biggestItemSize
= QSize(0, 0);
1382 d
->mouseButtonPressed
= false;
1387 rowsInsertedArtifficial(parent
, start
, end
);
1390 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex
&parent
,
1396 d
->lastSelection
= QItemSelection();
1397 d
->currentViewIndex
= QModelIndex();
1398 d
->forcedSelectionPosition
= 0;
1399 d
->elementsInfo
.clear();
1400 d
->elementsPosition
.clear();
1401 d
->categoriesIndexes
.clear();
1402 d
->categoriesPosition
.clear();
1403 d
->categories
.clear();
1404 d
->intersectedIndexes
.clear();
1405 d
->modelIndexList
.clear();
1406 d
->hovered
= QModelIndex();
1407 d
->biggestItemSize
= QSize(0, 0);
1408 d
->mouseButtonPressed
= false;
1410 if (start
> end
|| end
< 0 || start
< 0 || !d
->proxyModel
->rowCount())
1415 // Add all elements mapped to the source model and explore categories
1416 QString prevCategory
= d
->proxyModel
->data(d
->proxyModel
->index(0, d
->proxyModel
->sortColumn()), KCategorizedSortFilterProxyModel::CategoryDisplayRole
).toString();
1417 QString lastCategory
= prevCategory
;
1418 QModelIndexList modelIndexList
;
1419 struct Private::ElementInfo elementInfo
;
1421 for (int k
= 0; k
< d
->proxyModel
->rowCount(); ++k
)
1423 QModelIndex index
= d
->proxyModel
->index(k
, d
->proxyModel
->sortColumn());
1424 QModelIndex indexSize
= d
->proxyModel
->index(k
, 0);
1426 d
->biggestItemSize
= QSize(qMax(sizeHintForIndex(indexSize
).width(),
1427 d
->biggestItemSize
.width()),
1428 qMax(sizeHintForIndex(indexSize
).height(),
1429 d
->biggestItemSize
.height()));
1431 d
->modelIndexList
<< index
;
1433 lastCategory
= d
->proxyModel
->data(index
, KCategorizedSortFilterProxyModel::CategoryDisplayRole
).toString();
1435 elementInfo
.category
= lastCategory
;
1437 if (prevCategory
!= lastCategory
)
1440 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1441 d
->categories
<< prevCategory
;
1442 modelIndexList
.clear();
1449 elementInfo
.relativeOffsetToCategory
= offset
;
1451 modelIndexList
<< index
;
1452 prevCategory
= lastCategory
;
1454 d
->elementsInfo
.insert(index
.row(), elementInfo
);
1457 d
->categoriesIndexes
.insert(prevCategory
, modelIndexList
);
1458 d
->categories
<< prevCategory
;
1460 d
->updateScrollbars();
1463 void KCategorizedView::rowsRemoved(const QModelIndex
&parent
,
1467 if ((viewMode() == KCategorizedView::IconMode
) && d
->proxyModel
&&
1468 d
->categoryDrawer
&& d
->proxyModel
->isCategorizedModel())
1470 // Force the view to update all elements
1471 rowsInsertedArtifficial(QModelIndex(), 0, d
->proxyModel
->rowCount() - 1);
1475 void KCategorizedView::updateGeometries()
1477 if ((viewMode() != KCategorizedView::IconMode
) || !d
->proxyModel
||
1478 !d
->categoryDrawer
|| !d
->proxyModel
->isCategorizedModel())
1480 QListView::updateGeometries();
1484 // Avoid QListView::updateGeometries(), since it will try to set another
1485 // range to our scroll bars, what we don't want (ereslibre)
1486 QAbstractItemView::updateGeometries();
1489 void KCategorizedView::slotLayoutChanged()
1494 #include "kcategorizedview.moc"