33 #include "kcategorizedview_p.h"
40 #include <QPaintEvent>
47 struct KCategorizedView::Private::Item
59 struct KCategorizedView::Private::Block
64 , firstIndex(QModelIndex())
65 , quarantineStart(QModelIndex())
67 , outOfQuarantine(false)
75 return firstIndex != rhs.firstIndex;
78 static bool lessThan(
const Block &left,
const Block &right)
80 Q_ASSERT(left.firstIndex.isValid());
81 Q_ASSERT(right.firstIndex.isValid());
82 return left.firstIndex.row() < right.firstIndex.row();
87 QPersistentModelIndex firstIndex;
94 QPersistentModelIndex quarantineStart;
100 bool outOfQuarantine;
111 , categoryDrawerV2(0)
112 , categoryDrawerV3(0)
114 , alternatingBlockColors(false)
115 , collapsibleBlocks(false)
116 , hoveredBlock(new Block())
117 , hoveredIndex(QModelIndex())
118 , pressedPosition(
QPoint())
119 , rubberBandRect(
QRect())
123 KCategorizedView::Private::~Private()
128 bool KCategorizedView::Private::isCategorized()
const
130 return proxyModel && categoryDrawer && proxyModel->isCategorizedModel();
133 QStyleOptionViewItemV4 KCategorizedView::Private::blockRect(
const QModelIndex &representative)
135 QStyleOptionViewItemV4 option(q->viewOptions());
136 const int height = categoryDrawer->categoryHeight(representative, option);
138 QPoint pos = blockPosition(categoryDisplay);
140 option.rect.setTopLeft(pos);
141 option.rect.setWidth(viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin());
142 option.rect.setHeight(height + blockHeight(categoryDisplay));
143 option.rect = mapToViewport(option.rect);
150 const int rowCount = proxyModel->rowCount();
152 const QRect rect = _rect.normalized();
156 int top = rowCount - 1;
157 while (bottom <= top) {
158 const int middle = (bottom + top) / 2;
159 const QModelIndex index = proxyModel->index(middle, q->modelColumn(), q->rootIndex());
160 QRect itemRect = q->visualRect(index);
161 const int verticalOff = q->verticalOffset();
162 const int horizontalOff = q->horizontalOffset();
163 itemRect.topLeft().ry() += verticalOff;
164 itemRect.topLeft().rx() += horizontalOff;
165 itemRect.bottomRight().ry() += verticalOff;
166 itemRect.bottomRight().rx() += horizontalOff;
167 if (itemRect.bottomRight().y() <= rect.topLeft().y()) {
174 const QModelIndex bottomIndex = proxyModel->index(bottom, q->modelColumn(), q->rootIndex());
179 while (bottom <= top) {
180 const int middle = (bottom + top) / 2;
181 const QModelIndex index = proxyModel->index(middle, q->modelColumn(), q->rootIndex());
182 QRect itemRect = q->visualRect(index);
183 const int verticalOff = q->verticalOffset();
184 const int horizontalOff = q->horizontalOffset();
185 itemRect.topLeft().ry() += verticalOff;
186 itemRect.topLeft().rx() += horizontalOff;
187 itemRect.bottomRight().ry() += verticalOff;
188 itemRect.bottomRight().rx() += horizontalOff;
189 if (itemRect.topLeft().y() <= rect.bottomRight().y()) {
196 const QModelIndex topIndex = proxyModel->index(top, q->modelColumn(), q->rootIndex());
198 return qMakePair(bottomIndex, topIndex);
201 QPoint KCategorizedView::Private::blockPosition(
const QString &category)
203 Block &block = blocks[category];
205 if (block.outOfQuarantine && !block.topLeft.isNull()) {
206 return block.topLeft;
209 QPoint res(categorySpacing, 0);
211 const QModelIndex index = block.firstIndex;
215 const QModelIndex categoryIndex = block.firstIndex;
216 if (index.row() < categoryIndex.row()) {
219 res.ry() += categoryDrawer->categoryHeight(categoryIndex, q->viewOptions()) + categorySpacing;
220 if (index.row() == categoryIndex.row()) {
223 res.ry() += blockHeight(it.key());
226 block.outOfQuarantine =
true;
232 int KCategorizedView::Private::blockHeight(
const QString &category)
234 Block &block = blocks[category];
236 if (block.collapsed) {
240 if (block.height > -1) {
244 const QModelIndex firstIndex = block.firstIndex;
245 const QModelIndex lastIndex = proxyModel->index(firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex());
246 const QRect topLeft = q->visualRect(firstIndex);
247 QRect bottomRight = q->visualRect(lastIndex);
250 bottomRight.setHeight(qMax(bottomRight.height(), q->gridSize().height()));
252 if (!q->uniformItemSizes()) {
253 bottomRight.setHeight(highestElementInLastRow(block) + q->spacing() * 2);
257 const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1;
258 block.height = height;
263 int KCategorizedView::Private::viewportWidth()
const
265 return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin();
268 void KCategorizedView::Private::regenerateAllElements()
272 block.outOfQuarantine =
false;
273 block.quarantineStart = block.firstIndex;
278 void KCategorizedView::Private::rowsInserted(
const QModelIndex &parent,
int start,
int end)
280 if (!isCategorized()) {
284 for (
int i = start; i <=
end; ++i) {
285 const QModelIndex index = proxyModel->index(i, q->modelColumn(), parent);
287 Q_ASSERT(index.isValid());
289 const QString category = categoryForIndex(index);
291 Block &block = blocks[category];
297 const QModelIndex firstIndex = block.firstIndex;
298 if (!firstIndex.isValid() || index.row() < firstIndex.row()) {
299 block.firstIndex = index;
303 Q_ASSERT(block.firstIndex.isValid());
305 const int firstIndexRow = block.firstIndex.row();
307 block.items.insert(index.row() - firstIndexRow, Private::Item());
310 q->visualRect(index);
311 q->viewport()->update();
316 const QModelIndex lastIndex = proxyModel->index(end, q->modelColumn(), parent);
317 const QString category = categoryForIndex(lastIndex);
318 Private::Block &block = blocks[category];
319 block.quarantineStart = block.firstIndex;
325 const QModelIndex firstIndex = proxyModel->index(start, q->modelColumn(), parent);
326 const QString category = categoryForIndex(firstIndex);
327 const QModelIndex firstAffectedCategory = blocks[category].firstIndex;
332 foreach (
const Block &block, blockList) {
333 firstIndexesRows << block.firstIndex.row();
337 Private::Block &block = *it;
338 if (block.firstIndex.row() > firstAffectedCategory.row()) {
339 block.outOfQuarantine =
false;
340 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
341 }
else if (block.firstIndex.row() == firstAffectedCategory.row()) {
342 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
349 QRect KCategorizedView::Private::mapToViewport(
const QRect &rect)
const
351 const int dx = -q->horizontalOffset();
352 const int dy = -q->verticalOffset();
353 return rect.adjusted(dx, dy, dx, dy);
356 QRect KCategorizedView::Private::mapFromViewport(
const QRect &rect)
const
358 const int dx = q->horizontalOffset();
359 const int dy = q->verticalOffset();
360 return rect.adjusted(dx, dy, dx, dy);
363 int KCategorizedView::Private::highestElementInLastRow(
const Block &block)
const
366 const QModelIndex lastIndex = proxyModel->index(block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex());
367 const QRect prevRect = q->visualRect(lastIndex);
368 int res = prevRect.height();
369 QModelIndex prevIndex = proxyModel->index(lastIndex.row() - 1, q->modelColumn(), q->rootIndex());
370 if (!prevIndex.isValid()) {
374 const QRect tempRect = q->visualRect(prevIndex);
375 if (tempRect.topLeft().y() < prevRect.topLeft().y()) {
378 res = qMax(res, tempRect.height());
379 if (prevIndex == block.firstIndex) {
382 prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex());
388 bool KCategorizedView::Private::hasGrid()
const
390 const QSize gridSize = q->gridSize();
391 return gridSize.isValid() && !gridSize.isNull();
394 QString KCategorizedView::Private::categoryForIndex(
const QModelIndex &index)
const
396 const QModelIndex categoryIndex = index.model()->index(index.row(), proxyModel->sortColumn(), index.parent());
400 void KCategorizedView::Private::leftToRightVisualRect(
const QModelIndex &index, Item &item,
401 const Block &block,
const QPoint &blockPos)
const
403 const int firstIndexRow = block.firstIndex.row();
406 const int relativeRow = index.row() - firstIndexRow;
407 const int maxItemsPerRow = qMax(viewportWidth() / q->gridSize().width(), 1);
408 if (q->layoutDirection() == Qt::LeftToRight) {
409 item.topLeft.rx() = (relativeRow % maxItemsPerRow) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin();
411 item.topLeft.rx() = viewportWidth() - ((relativeRow % maxItemsPerRow) + 1) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing;
413 item.topLeft.ry() = (relativeRow / maxItemsPerRow) * q->gridSize().height();
415 if (q->uniformItemSizes()) {
416 const int relativeRow = index.row() - firstIndexRow;
417 const QSize itemSize = q->sizeHintForIndex(index);
418 const int maxItemsPerRow = qMax((viewportWidth() - q->spacing()) / (itemSize.width() + q->spacing()), 1);
419 if (q->layoutDirection() == Qt::LeftToRight) {
420 item.topLeft.rx() = (relativeRow % maxItemsPerRow) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin();
422 item.topLeft.rx() = viewportWidth() - (relativeRow % maxItemsPerRow) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing;
424 item.topLeft.ry() = (relativeRow / maxItemsPerRow) * itemSize.height();
426 const QSize currSize = q->sizeHintForIndex(index);
427 if (index != block.firstIndex) {
428 const int viewportW = viewportWidth() - q->spacing();
429 QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex());
430 QRect prevRect = q->visualRect(prevIndex);
431 prevRect = mapFromViewport(prevRect);
432 if ((prevRect.bottomRight().x() + 1) + currSize.width() - blockPos.x() + q->spacing() > viewportW) {
436 prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex());
437 const QRect tempRect = q->visualRect(prevIndex);
438 if (tempRect.topLeft().y() < prevRect.topLeft().y()) {
441 if (tempRect.bottomRight().y() > prevRect.bottomRight().y()) {
444 if (prevIndex == block.firstIndex) {
448 if (q->layoutDirection() == Qt::LeftToRight) {
449 item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing();
451 item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
453 item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y();
455 if (q->layoutDirection() == Qt::LeftToRight) {
456 item.topLeft.rx() = (prevRect.bottomRight().x() + 1) + q->spacing();
458 item.topLeft.rx() = (prevRect.bottomLeft().x() - 1) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing;
460 item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y();
463 if (q->layoutDirection() == Qt::LeftToRight) {
464 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
466 item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
468 item.topLeft.ry() = q->spacing();
472 item.size = q->sizeHintForIndex(index);
475 void KCategorizedView::Private::topToBottomVisualRect(
const QModelIndex &index, Item &item,
476 const Block &block,
const QPoint &blockPos)
const
478 const int firstIndexRow = block.firstIndex.row();
481 const int relativeRow = index.row() - firstIndexRow;
482 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
483 item.topLeft.ry() = relativeRow * q->gridSize().height();
485 if (q->uniformItemSizes()) {
486 const int relativeRow = index.row() - firstIndexRow;
487 const QSize itemSize = q->sizeHintForIndex(index);
488 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
489 item.topLeft.ry() = relativeRow * itemSize.height();
491 if (index != block.firstIndex) {
492 QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex());
493 QRect prevRect = q->visualRect(prevIndex);
494 prevRect = mapFromViewport(prevRect);
495 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
496 item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y();
498 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
499 item.topLeft.ry() = q->spacing();
503 item.size = q->sizeHintForIndex(index);
504 item.size.setWidth(viewportWidth());
507 void KCategorizedView::Private::_k_slotCollapseOrExpandClicked(QModelIndex)
517 , d(new Private(this))
528 if (d->proxyModel == model) {
535 disconnect(d->proxyModel, SIGNAL(layoutChanged()),
this, SLOT(
slotLayoutChanged()));
541 connect(d->proxyModel, SIGNAL(layoutChanged()),
this, SLOT(
slotLayoutChanged()));
544 QListView::setModel(model);
547 if (model->rowCount()) {
559 d->regenerateAllElements();
560 QListView::setGridSize(size);
565 if (!d->isCategorized()) {
566 return QListView::visualRect(index);
569 if (!index.isValid()) {
573 const QString category = d->categoryForIndex(index);
575 if (!d->blocks.contains(category)) {
579 Private::Block &block = d->blocks[category];
580 const int firstIndexRow = block.firstIndex.row();
582 Q_ASSERT(block.firstIndex.isValid());
584 if (index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count()) {
588 const QPoint blockPos = d->blockPosition(category);
590 Private::Item &ritem = block.items[index.row() - firstIndexRow];
592 if (ritem.topLeft.isNull() || (block.quarantineStart.isValid() &&
593 index.row() >= block.quarantineStart.row())) {
594 if (flow() == LeftToRight) {
595 d->leftToRightVisualRect(index, ritem, block, blockPos);
597 d->topToBottomVisualRect(index, ritem, block, blockPos);
601 const bool wasLastIndex = (index.row() == (block.firstIndex.row() + block.items.count() - 1));
602 if (index.row() == block.quarantineStart.row()) {
604 block.quarantineStart = QModelIndex();
606 const QModelIndex nextIndex = d->proxyModel->index(index.row() + 1, modelColumn(), rootIndex());
607 block.quarantineStart = nextIndex;
615 Private::Item item(ritem);
616 item.topLeft.ry() += blockPos.y();
618 const QSize sizeHint = item.size;
621 const QSize sizeGrid = gridSize();
622 const QSize resultingSize = sizeHint.boundedTo(sizeGrid);
623 QRect res(item.topLeft.x() + ((sizeGrid.width() - resultingSize.width()) / 2),
624 item.topLeft.y(), resultingSize.width(), resultingSize.height());
625 if (block.collapsed) {
628 res.setLeft(-resultingSize.width());
631 return d->mapToViewport(res);
634 QRect res(item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height());
635 if (block.collapsed) {
638 res.setLeft(-sizeHint.width());
641 return d->mapToViewport(res);
646 return d->categoryDrawer;
651 if (d->categoryDrawerV2) {
652 disconnect(d->categoryDrawerV2, SIGNAL(collapseOrExpandClicked(QModelIndex)),
653 this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex)));
660 if (d->categoryDrawerV2) {
661 connect(d->categoryDrawerV2, SIGNAL(collapseOrExpandClicked(QModelIndex)),
662 this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex)));
668 return d->categorySpacing;
673 if (d->categorySpacing == categorySpacing) {
680 Private::Block &block = *it;
681 block.outOfQuarantine =
false;
687 return d->alternatingBlockColors;
692 d->alternatingBlockColors = enable;
697 return d->collapsibleBlocks;
702 d->collapsibleBlocks = enable;
708 const Private::Block &block = d->blocks[category];
709 if (block.height == -1) {
712 QModelIndex current = block.firstIndex;
713 const int first = current.row();
714 for (
int i = 1; i <= block.items.count(); ++i) {
715 if (current.isValid()) {
718 current = d->proxyModel->index(first + i, modelColumn(), rootIndex());
730 if (!d->isCategorized()) {
731 return QListView::indexAt(point);
734 const int rowCount = d->proxyModel->rowCount();
736 return QModelIndex();
741 int top = rowCount - 1;
742 while (bottom <= top) {
743 const int middle = (bottom + top) / 2;
744 const QModelIndex index = d->proxyModel->index(middle, modelColumn(), rootIndex());
746 const int verticalOff = verticalOffset();
747 int horizontalOff = horizontalOffset();
748 if (layoutDirection() == Qt::RightToLeft) {
751 rect.topLeft().ry() += verticalOff;
752 rect.topLeft().rx() += horizontalOff;
753 rect.bottomRight().ry() += verticalOff;
754 rect.bottomRight().rx() += horizontalOff;
755 if (rect.contains(point)) {
756 if (index.model()->flags(index) & Qt::ItemIsEnabled) {
759 return QModelIndex();
761 bool directionCondition;
762 if (layoutDirection() == Qt::LeftToRight) {
763 directionCondition = point.x() > rect.bottomRight().x();
765 directionCondition = point.x() < rect.bottomLeft().x();
767 if (point.y() > rect.bottomRight().y() ||
768 (point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && directionCondition)) {
774 return QModelIndex();
785 if (!d->isCategorized()) {
786 QListView::paintEvent(event);
792 QPainter p(viewport());
795 Q_ASSERT(selectionModel()->model() == d->proxyModel);
799 while (it != d->blocks.constEnd()) {
800 const Private::Block &block = *it;
801 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
802 QStyleOptionViewItemV4 option(viewOptions());
803 option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate
804 : QStyleOptionViewItemV4::None;
805 option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open
806 : QStyle::State_None;
807 const int height = d->categoryDrawer->categoryHeight(categoryIndex, option);
808 QPoint pos = d->blockPosition(it.key());
810 option.rect.setTopLeft(pos);
811 option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin());
812 option.rect.setHeight(height + d->blockHeight(it.key()));
813 option.rect = d->mapToViewport(option.rect);
814 if (!option.rect.intersects(viewport()->rect())) {
818 d->categoryDrawer->drawCategory(categoryIndex, d->proxyModel->sortRole(), option, &p);
823 if (intersecting.first.isValid() && intersecting.second.isValid()) {
825 int i = intersecting.first.row();
826 int indexToCheckIfBlockCollapsed = i;
827 QModelIndex categoryIndex;
829 Private::Block *block = 0;
830 while (i <= intersecting.second.row()) {
832 if (i == indexToCheckIfBlockCollapsed) {
833 categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex());
835 block = &d->blocks[category];
836 indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count();
837 if (block->collapsed) {
838 i = indexToCheckIfBlockCollapsed;
846 const bool alternateItem = (i - block->firstIndex.row()) % 2;
848 const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex());
849 const Qt::ItemFlags flags = d->proxyModel->flags(index);
850 QStyleOptionViewItemV4 option(viewOptions());
852 option.widget =
this;
853 option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText
854 : QStyleOptionViewItemV2::None;
855 option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate
856 : QStyleOptionViewItemV4::None;
857 if (flags & Qt::ItemIsSelectable) {
858 option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected
859 : QStyle::State_None;
861 option.state &= ~QStyle::State_Selected;
863 option.state |= (index == currentIndex()) ? QStyle::State_HasFocus
864 : QStyle::State_None;
865 if (!(flags & Qt::ItemIsEnabled)) {
866 option.state &= ~QStyle::State_Enabled;
868 option.state |= (index == d->hoveredIndex) ? QStyle::State_MouseOver
869 : QStyle::State_None;
872 itemDelegate(index)->paint(&p, option, index);
879 if (isSelectionRectVisible() && d->rubberBandRect.isValid()) {
880 QStyleOptionRubberBand opt;
882 opt.shape = QRubberBand::Rectangle;
884 opt.rect = d->mapToViewport(d->rubberBandRect).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
886 style()->drawControl(QStyle::CE_RubberBand, &opt, &p);
896 d->regenerateAllElements();
897 QListView::resizeEvent(event);
901 QItemSelectionModel::SelectionFlags flags)
903 if (!d->isCategorized()) {
904 QListView::setSelection(rect, flags);
908 if (rect.topLeft() == rect.bottomRight()) {
909 const QModelIndex index =
indexAt(rect.topLeft());
910 selectionModel()->select(index, flags);
916 QItemSelection selection;
919 QModelIndex firstIndex;
920 QModelIndex lastIndex;
921 for (
int i = intersecting.first.row(); i <= intersecting.second.row(); ++i) {
922 const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex());
923 const bool visualRectIntersects =
visualRect(index).intersects(rect);
924 if (firstIndex.isValid()) {
925 if (visualRectIntersects) {
928 selection << QItemSelectionRange(firstIndex, lastIndex);
929 firstIndex = QModelIndex();
931 }
else if (visualRectIntersects) {
937 if (firstIndex.isValid()) {
938 selection << QItemSelectionRange(firstIndex, lastIndex);
941 selectionModel()->select(selection, flags);
946 QListView::mouseMoveEvent(event);
947 d->hoveredIndex =
indexAt(event->pos());
948 const SelectionMode itemViewSelectionMode = selectionMode();
949 if (state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection
950 && itemViewSelectionMode != NoSelection) {
951 QRect rect(d->pressedPosition, event->pos() +
QPoint(horizontalOffset(), verticalOffset()));
952 rect = rect.normalized();
953 update(rect.united(d->rubberBandRect));
954 d->rubberBandRect = rect;
956 if (!d->categoryDrawerV2) {
960 while (it != d->blocks.constEnd()) {
961 const Private::Block &block = *it;
962 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
963 QStyleOptionViewItemV4 option(viewOptions());
964 const int height = d->categoryDrawer->categoryHeight(categoryIndex, option);
965 QPoint pos = d->blockPosition(it.key());
967 option.rect.setTopLeft(pos);
968 option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin());
969 option.rect.setHeight(height + d->blockHeight(it.key()));
970 option.rect = d->mapToViewport(option.rect);
971 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
972 if (option.rect.contains(mousePos)) {
973 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1 && *d->hoveredBlock != block) {
974 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
975 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
976 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect);
977 *d->hoveredBlock =
block;
978 d->hoveredCategory = it.key();
979 viewport()->update(option.rect);
980 }
else if (d->hoveredBlock->height == -1) {
981 *d->hoveredBlock =
block;
982 d->hoveredCategory = it.key();
983 }
else if (d->categoryDrawerV3) {
984 d->categoryDrawerV3->mouseMoved(categoryIndex, option.rect, event);
986 d->categoryDrawerV2->mouseButtonMoved(categoryIndex, event);
988 viewport()->update(option.rect);
993 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1) {
994 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
995 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
996 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect);
997 *d->hoveredBlock = Private::Block();
998 d->hoveredCategory =
QString();
999 viewport()->update(option.rect);
1005 if (event->button() == Qt::LeftButton) {
1006 d->pressedPosition =
event->pos();
1007 d->pressedPosition.rx() += horizontalOffset();
1008 d->pressedPosition.ry() += verticalOffset();
1010 if (!d->categoryDrawerV2) {
1011 QListView::mousePressEvent(event);
1015 while (it != d->blocks.constEnd()) {
1016 const Private::Block &block = *it;
1017 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
1018 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
1019 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
1020 if (option.rect.contains(mousePos)) {
1021 if (d->categoryDrawerV3) {
1022 d->categoryDrawerV3->mouseButtonPressed(categoryIndex, option.rect, event);
1024 d->categoryDrawerV2->mouseButtonPressed(categoryIndex, event);
1026 viewport()->update(option.rect);
1027 if (!event->isAccepted()) {
1028 QListView::mousePressEvent(event);
1034 QListView::mousePressEvent(event);
1039 d->pressedPosition =
QPoint();
1040 d->rubberBandRect =
QRect();
1041 if (!d->categoryDrawerV2) {
1042 QListView::mouseReleaseEvent(event);
1046 while (it != d->blocks.constEnd()) {
1047 const Private::Block &block = *it;
1048 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
1049 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
1050 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
1051 if (option.rect.contains(mousePos)) {
1052 if (d->categoryDrawerV3) {
1053 d->categoryDrawerV3->mouseButtonReleased(categoryIndex, option.rect, event);
1055 d->categoryDrawerV2->mouseButtonReleased(categoryIndex, event);
1057 viewport()->update(option.rect);
1058 if (!event->isAccepted()) {
1059 QListView::mouseReleaseEvent(event);
1065 QListView::mouseReleaseEvent(event);
1070 QListView::leaveEvent(event);
1071 if (d->hoveredIndex.isValid()) {
1072 viewport()->update(
visualRect(d->hoveredIndex));
1073 d->hoveredIndex = QModelIndex();
1075 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1) {
1076 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
1077 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
1078 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect);
1079 *d->hoveredBlock = Private::Block();
1080 d->hoveredCategory =
QString();
1081 viewport()->update(option.rect);
1087 QListView::startDrag(supportedActions);
1092 QListView::dragMoveEvent(event);
1093 d->hoveredIndex =
indexAt(event->pos());
1098 QListView::dragEnterEvent(event);
1103 QListView::dragLeaveEvent(event);
1108 QListView::dropEvent(event);
1114 Qt::KeyboardModifiers modifiers)
1116 if (!d->isCategorized()) {
1117 return QListView::moveCursor(cursorAction, modifiers);
1120 const QModelIndex current = currentIndex();
1122 if (!current.isValid()) {
1123 const int rowCount = d->proxyModel->rowCount(rootIndex());
1125 return QModelIndex();
1127 return d->proxyModel->index(0, modelColumn(), rootIndex());
1130 switch (cursorAction) {
1132 if (!current.row()) {
1133 return QModelIndex();
1135 const QModelIndex previous = d->proxyModel->index(current.row() - 1, modelColumn(), rootIndex());
1137 if (previousRect.top() == currentRect.top()) {
1141 return QModelIndex();
1144 if (current.row() == d->proxyModel->rowCount() - 1) {
1145 return QModelIndex();
1147 const QModelIndex
next = d->proxyModel->index(current.row() + 1, modelColumn(), rootIndex());
1149 if (nextRect.top() == currentRect.top()) {
1153 return QModelIndex();
1156 if (d->hasGrid() || uniformItemSizes()) {
1157 const QModelIndex current = currentIndex();
1158 const QSize itemSize = d->hasGrid() ? gridSize()
1159 : sizeHintForIndex(current);
1160 const Private::Block &block = d->blocks[d->categoryForIndex(current)];
1161 const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1);
1162 const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() +
1163 block.items.count();
1166 return d->proxyModel->index(current.row() + maxItemsPerRow, modelColumn(), rootIndex());
1169 const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow;
1170 const QModelIndex nextIndex = d->proxyModel->index(block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex());
1172 if (!nextIndex.isValid()) {
1173 return QModelIndex();
1176 const Private::Block &nextBlock = d->blocks[d->categoryForIndex(nextIndex)];
1178 if (nextBlock.items.count() <= currentRelativePos) {
1179 return QModelIndex();
1182 if (currentRelativePos < (block.items.count() % maxItemsPerRow)) {
1183 return d->proxyModel->index(nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex());
1186 return QModelIndex();
1190 if (d->hasGrid() || uniformItemSizes()) {
1191 const QModelIndex current = currentIndex();
1192 const QSize itemSize = d->hasGrid() ? gridSize()
1193 : sizeHintForIndex(current);
1194 const Private::Block &block = d->blocks[d->categoryForIndex(current)];
1195 const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1);
1196 const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row();
1199 return d->proxyModel->index(current.row() - maxItemsPerRow, modelColumn(), rootIndex());
1202 const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow;
1203 const QModelIndex prevIndex = d->proxyModel->index(block.firstIndex.row() - 1, modelColumn(), rootIndex());
1205 if (!prevIndex.isValid()) {
1206 return QModelIndex();
1209 const Private::Block &prevBlock = d->blocks[d->categoryForIndex(prevIndex)];
1211 if (prevBlock.items.count() <= currentRelativePos) {
1212 return QModelIndex();
1215 const int remainder = prevBlock.items.count() % maxItemsPerRow;
1216 if (currentRelativePos < remainder) {
1217 return d->proxyModel->index(prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex());
1220 return QModelIndex();
1227 return QModelIndex();
1234 if (!d->isCategorized()) {
1235 QListView::rowsAboutToBeRemoved(parent, start, end);
1239 *d->hoveredBlock = Private::Block();
1240 d->hoveredCategory =
QString();
1242 if (end - start + 1 == d->proxyModel->rowCount()) {
1244 QListView::rowsAboutToBeRemoved(parent, start, end);
1285 int alreadyRemoved = 0;
1286 for (
int i = start; i <=
end; ++i) {
1287 const QModelIndex index = d->proxyModel->index(i, modelColumn(), parent);
1289 Q_ASSERT(index.isValid());
1291 const QString category = d->categoryForIndex(index);
1293 if (lastCategory != category) {
1294 lastCategory = category;
1298 Private::Block &block = d->blocks[category];
1299 block.items.removeAt(i - block.firstIndex.row() - alreadyRemoved);
1302 if (!block.items.count()) {
1303 listOfCategoriesMarkedForRemoval << category;
1308 viewport()->update();
1313 const QModelIndex lastIndex = d->proxyModel->index(end, modelColumn(), parent);
1314 const QString category = d->categoryForIndex(lastIndex);
1315 Private::Block &block = d->blocks[category];
1316 if (block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row()) {
1317 block.firstIndex = d->proxyModel->index(end + 1, modelColumn(), parent);
1319 block.quarantineStart = block.firstIndex;
1323 Q_FOREACH (
const QString &category, listOfCategoriesMarkedForRemoval) {
1324 d->blocks.remove(category);
1333 foreach (
const Private::Block &block, blockList) {
1334 firstIndexesRows << block.firstIndex.row();
1338 Private::Block &block = *it;
1339 if (block.firstIndex.row() > start) {
1340 block.outOfQuarantine =
false;
1341 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
1342 }
else if (block.firstIndex.row() == start) {
1343 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
1349 QListView::rowsAboutToBeRemoved(parent, start, end);
1354 const int oldVerticalOffset = verticalOffset();
1355 const Qt::ScrollBarPolicy verticalP = verticalScrollBarPolicy(), horizontalP = horizontalScrollBarPolicy();
1371 if (d->isCategorized()) {
1372 setVerticalScrollBarPolicy((verticalP == Qt::ScrollBarAlwaysOn || verticalScrollBar()->isVisibleTo(
this)) ?
1373 Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
1374 setHorizontalScrollBarPolicy((horizontalP == Qt::ScrollBarAlwaysOn || horizontalScrollBar()->isVisibleTo(
this)) ?
1375 Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
1379 QListView::updateGeometries();
1381 if (!d->isCategorized()) {
1385 const int rowCount = d->proxyModel->rowCount();
1387 verticalScrollBar()->setRange(0, 0);
1389 horizontalScrollBar()->setRange(0, 0);
1393 const QModelIndex lastIndex = d->proxyModel->index(rowCount - 1, modelColumn(), rootIndex());
1394 Q_ASSERT(lastIndex.isValid());
1398 lastItemRect.setSize(lastItemRect.size().expandedTo(gridSize()));
1400 if (uniformItemSizes()) {
1401 QSize itemSize = sizeHintForIndex(lastIndex);
1402 itemSize.setHeight(itemSize.height() + spacing());
1403 lastItemRect.setSize(itemSize);
1405 QSize itemSize = sizeHintForIndex(lastIndex);
1406 const QString category = d->categoryForIndex(lastIndex);
1407 itemSize.setHeight(d->highestElementInLastRow(d->blocks[category]) + spacing());
1408 lastItemRect.setSize(itemSize);
1412 const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height();
1414 if (verticalScrollMode() == ScrollPerItem) {
1415 verticalScrollBar()->setSingleStep(lastItemRect.height());
1416 const int rowsPerPage = qMax(viewport()->height() / lastItemRect.height(), 1);
1417 verticalScrollBar()->setPageStep(rowsPerPage * lastItemRect.height());
1420 verticalScrollBar()->setRange(0, bottomRange);
1421 verticalScrollBar()->setValue(oldVerticalOffset);
1427 horizontalScrollBar()->setRange(0, 0);
1431 setVerticalScrollBarPolicy(verticalP);
1432 setHorizontalScrollBarPolicy(horizontalP);
1434 bool validRange = verticalScrollBar()->maximum() != verticalScrollBar()->minimum();
1435 if (verticalP == Qt::ScrollBarAsNeeded && (verticalScrollBar()->isVisibleTo(
this) != validRange))
1436 verticalScrollBar()->setVisible(validRange);
1437 validRange = horizontalScrollBar()->maximum() > horizontalScrollBar()->minimum();
1438 if (horizontalP == Qt::ScrollBarAsNeeded && (horizontalScrollBar()->isVisibleTo(
this) != validRange))
1439 horizontalScrollBar()->setVisible(validRange);
1444 const QModelIndex &previous)
1446 QListView::currentChanged(current, previous);
1450 const QModelIndex &bottomRight)
1452 QListView::dataChanged(topLeft, bottomRight);
1453 if (!d->isCategorized()) {
1457 *d->hoveredBlock = Private::Block();
1458 d->hoveredCategory =
QString();
1461 int i = topLeft.row();
1462 int indexToCheck = i;
1463 QModelIndex categoryIndex;
1465 Private::Block *
block;
1466 while (i <= bottomRight.row()) {
1467 const QModelIndex currIndex = d->proxyModel->index(i, modelColumn(), rootIndex());
1468 if (i == indexToCheck) {
1469 categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex());
1471 block = &d->blocks[category];
1472 block->quarantineStart = currIndex;
1473 indexToCheck = block->firstIndex.row() + block->items.count();
1485 QListView::rowsInserted(parent, start, end);
1486 if (!d->isCategorized()) {
1490 *d->hoveredBlock = Private::Block();
1491 d->hoveredCategory =
QString();
1492 d->rowsInserted(parent, start, end);
1495 #ifndef KDE_NO_DEPRECATED
1506 #ifndef KDE_NO_DEPRECATED
1519 if (!d->isCategorized()) {
1524 *d->hoveredBlock = Private::Block();
1525 d->hoveredCategory =
QString();
1526 if (d->proxyModel->rowCount()) {
1527 d->rowsInserted(rootIndex(), 0, d->proxyModel->rowCount() - 1);
1533 #include "kcategorizedview.moc"