File indexing completed on 2025-04-27 03:58:27
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-01-16 0007 * Description : Item view for listing items in a categorized fashion optionally 0008 * 0009 * SPDX-FileCopyrightText: 2007 by Rafael Fernández López <ereslibre at kde dot org> 0010 * SPDX-FileCopyrightText: 2009-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0011 * SPDX-FileCopyrightText: 2011-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "dcategorizedview_p.h" 0018 0019 namespace Digikam 0020 { 0021 0022 DCategorizedView::DCategorizedView(QWidget* const parent) 0023 : QListView(parent), 0024 d (new Private(this)) 0025 { 0026 } 0027 0028 DCategorizedView::~DCategorizedView() 0029 { 0030 delete d; 0031 } 0032 0033 void DCategorizedView::setGridSize(const QSize& size) 0034 { 0035 QListView::setGridSize(size); 0036 0037 slotLayoutChanged(); 0038 } 0039 0040 void DCategorizedView::setModel(QAbstractItemModel* model) 0041 { 0042 d->lastSelection = QItemSelection(); 0043 d->forcedSelectionPosition = 0; 0044 d->hovered = QModelIndex(); 0045 d->mouseButtonPressed = false; 0046 d->rightMouseButtonPressed = false; 0047 d->elementsInfo.clear(); 0048 d->elementsPosition.clear(); 0049 d->categoriesIndexes.clear(); 0050 d->categoriesPosition.clear(); 0051 d->categories.clear(); 0052 d->intersectedIndexes.clear(); 0053 0054 if (d->proxyModel) 0055 { 0056 QObject::disconnect(d->proxyModel, SIGNAL(layoutChanged()), 0057 this, SLOT(slotLayoutChanged())); 0058 0059 QObject::disconnect(d->proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), 0060 this, SLOT(rowsRemoved(QModelIndex,int,int))); 0061 } 0062 0063 QListView::setModel(model); 0064 0065 d->proxyModel = dynamic_cast<DCategorizedSortFilterProxyModel*>(model); 0066 0067 if (d->proxyModel) 0068 { 0069 QObject::connect(d->proxyModel, SIGNAL(layoutChanged()), 0070 this, SLOT(slotLayoutChanged())); 0071 0072 QObject::connect(d->proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), 0073 this, SLOT(rowsRemoved(QModelIndex,int,int))); 0074 0075 if (d->proxyModel->rowCount()) 0076 { 0077 slotLayoutChanged(); 0078 } 0079 } 0080 } 0081 0082 QRect DCategorizedView::visualRect(const QModelIndex& index) const 0083 { 0084 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0085 { 0086 return QListView::visualRect(index); 0087 } 0088 0089 if (!qobject_cast<const QSortFilterProxyModel*>(index.model())) 0090 { 0091 return d->visualRect(d->proxyModel->mapFromSource(index)); 0092 } 0093 0094 return d->visualRect(index); 0095 } 0096 0097 QRect DCategorizedView::categoryVisualRect(const QModelIndex& index) const 0098 { 0099 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0100 { 0101 return QRect(); 0102 } 0103 0104 if (!index.isValid()) 0105 { 0106 return QRect(); 0107 } 0108 0109 QString category = d->elementsInfo[index.row()].category; 0110 0111 return d->categoryVisualRect(category); 0112 } 0113 0114 QModelIndex DCategorizedView::categoryAt(const QPoint& point) const 0115 { 0116 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0117 { 0118 return QModelIndex(); 0119 } 0120 0121 // We traverse the categories and find the first where point.y() is below the visualRect 0122 0123 int y = 0, lastY = 0; 0124 QString lastCategory; 0125 0126 Q_FOREACH (const QString& category, d->categories) 0127 { 0128 y = d->categoryVisualRect(category).top(); 0129 0130 if ((point.y() >= lastY) && (point.y() < y)) 0131 { 0132 break; 0133 } 0134 0135 lastY = y; 0136 y = 0; 0137 lastCategory = category; 0138 } 0139 0140 // if lastCategory is the last one in the list y will be 0 0141 0142 if (!lastCategory.isNull() && (point.y() >= lastY) && ((point.y() < y) || !y)) 0143 { 0144 return d->proxyModel->index(d->categoriesIndexes[lastCategory][0], d->proxyModel->sortColumn()); 0145 } 0146 0147 return QModelIndex(); 0148 } 0149 0150 QItemSelectionRange DCategorizedView::categoryRange(const QModelIndex& index) const 0151 { 0152 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0153 { 0154 return QItemSelectionRange(); 0155 } 0156 0157 if (!index.isValid()) 0158 { 0159 return QItemSelectionRange(); 0160 } 0161 0162 QString category = d->elementsInfo[index.row()].category; 0163 QModelIndex first = d->proxyModel->index(d->categoriesIndexes[category].first(), d->proxyModel->sortColumn()); 0164 QModelIndex last = d->proxyModel->index(d->categoriesIndexes[category].last(), d->proxyModel->sortColumn()); 0165 0166 return QItemSelectionRange(first, last); 0167 } 0168 0169 DCategoryDrawer* DCategorizedView::categoryDrawer() const 0170 { 0171 return d->categoryDrawer; 0172 } 0173 0174 void DCategorizedView::setCategoryDrawer(DCategoryDrawer* categoryDrawer) 0175 { 0176 d->lastSelection = QItemSelection(); 0177 d->forcedSelectionPosition = 0; 0178 d->hovered = QModelIndex(); 0179 d->mouseButtonPressed = false; 0180 d->rightMouseButtonPressed = false; 0181 d->elementsInfo.clear(); 0182 d->elementsPosition.clear(); 0183 d->categoriesIndexes.clear(); 0184 d->categoriesPosition.clear(); 0185 d->categories.clear(); 0186 d->intersectedIndexes.clear(); 0187 d->categoryDrawer = categoryDrawer; 0188 0189 if (categoryDrawer) 0190 { 0191 if (d->proxyModel) 0192 { 0193 if (d->proxyModel->rowCount()) 0194 { 0195 slotLayoutChanged(); 0196 } 0197 } 0198 } 0199 else 0200 { 0201 updateGeometries(); 0202 } 0203 } 0204 0205 void DCategorizedView::setDrawDraggedItems(bool drawDraggedItems) 0206 { 0207 d->drawItemsWhileDragging = drawDraggedItems; 0208 } 0209 0210 QModelIndex DCategorizedView::indexAt(const QPoint& point) const 0211 { 0212 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0213 { 0214 return QListView::indexAt(point); 0215 } 0216 0217 QModelIndex index; 0218 0219 const QModelIndexList item = d->intersectionSet(QRect(point, point)); 0220 0221 if (item.count() == 1) 0222 { 0223 index = item[0]; 0224 } 0225 0226 return index; 0227 } 0228 0229 QModelIndexList DCategorizedView::categorizedIndexesIn(const QRect& rect) const 0230 { 0231 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0232 { 0233 return QModelIndexList(); 0234 } 0235 0236 return d->intersectionSet(rect); 0237 } 0238 0239 void DCategorizedView::reset() 0240 { 0241 QListView::reset(); 0242 0243 d->lastSelection = QItemSelection(); 0244 d->forcedSelectionPosition = 0; 0245 d->hovered = QModelIndex(); 0246 d->biggestItemSize = QSize(0, 0); 0247 d->mouseButtonPressed = false; 0248 d->rightMouseButtonPressed = false; 0249 d->elementsInfo.clear(); 0250 d->elementsPosition.clear(); 0251 d->categoriesIndexes.clear(); 0252 d->categoriesPosition.clear(); 0253 d->categories.clear(); 0254 d->intersectedIndexes.clear(); 0255 } 0256 0257 void DCategorizedView::paintEvent(QPaintEvent* event) 0258 { 0259 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0260 { 0261 QListView::paintEvent(event); 0262 return; 0263 } 0264 0265 bool alternatingRows = alternatingRowColors(); 0266 QStyleOptionViewItem option; 0267 0268 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0269 0270 initViewItemOption(&option); 0271 0272 #else 0273 0274 option = viewOptions(); 0275 0276 #endif 0277 0278 option.widget = this; 0279 0280 if (wordWrap()) 0281 { 0282 option.features |= QStyleOptionViewItem::WrapText; 0283 } 0284 0285 QPainter painter(viewport()); 0286 QRect area = event->rect(); 0287 const bool focus = (hasFocus() || viewport()->hasFocus()) && currentIndex().isValid(); 0288 const QStyle::State state = option.state; 0289 const bool enabled = (state & QStyle::State_Enabled) != 0; 0290 0291 painter.save(); 0292 0293 QModelIndexList dirtyIndexes = d->intersectionSet(area); 0294 bool alternate = false; 0295 0296 if (dirtyIndexes.count()) 0297 { 0298 alternate = dirtyIndexes[0].row() % 2; 0299 } 0300 0301 Q_FOREACH (const QModelIndex& index, dirtyIndexes) 0302 { 0303 if (alternatingRows && alternate) 0304 { 0305 option.features |= QStyleOptionViewItem::Alternate; 0306 alternate = false; 0307 } 0308 else if (alternatingRows) 0309 { 0310 option.features &= ~QStyleOptionViewItem::Alternate; 0311 alternate = true; 0312 } 0313 0314 option.state = state; 0315 option.rect = visualRect(index); 0316 0317 if (selectionModel() && selectionModel()->isSelected(index)) 0318 { 0319 option.state |= QStyle::State_Selected; 0320 } 0321 0322 if (enabled) 0323 { 0324 QPalette::ColorGroup cg; 0325 0326 if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0) 0327 { 0328 option.state &= ~QStyle::State_Enabled; 0329 cg = QPalette::Disabled; 0330 } 0331 else 0332 { 0333 cg = QPalette::Normal; 0334 } 0335 0336 option.palette.setCurrentColorGroup(cg); 0337 } 0338 0339 if (focus && (currentIndex() == index)) 0340 { 0341 option.state |= QStyle::State_HasFocus; 0342 0343 if (this->state() == EditingState) 0344 { 0345 option.state |= QStyle::State_Editing; 0346 } 0347 } 0348 0349 if (index == d->hovered) 0350 { 0351 option.state |= QStyle::State_MouseOver; 0352 } 0353 else 0354 { 0355 option.state &= ~QStyle::State_MouseOver; 0356 } 0357 0358 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0359 0360 itemDelegateForIndex(index)->paint(&painter, option, index); 0361 0362 #else 0363 0364 itemDelegate(index)->paint(&painter, option, index); 0365 0366 #endif 0367 0368 } 0369 0370 // Redraw categories 0371 0372 QStyleOptionViewItem otherOption; 0373 bool intersectedInThePast = false; 0374 0375 Q_FOREACH (const QString& category, d->categories) 0376 { 0377 otherOption = option; 0378 otherOption.rect = d->categoryVisualRect(category); 0379 otherOption.state &= ~QStyle::State_MouseOver; 0380 0381 if (otherOption.rect.intersects(area)) 0382 { 0383 intersectedInThePast = true; 0384 QModelIndex indexToDraw = d->proxyModel->index(d->categoriesIndexes[category][0], 0385 d->proxyModel->sortColumn()); 0386 0387 d->drawNewCategory(indexToDraw, d->proxyModel->sortRole(), otherOption, &painter); 0388 } 0389 0390 // cppcheck-suppress knownConditionTrueFalse 0391 else if (intersectedInThePast) 0392 { 0393 // the visible area has been finished, we don't need to keep asking, the rest won't intersect 0394 // this is doable because we know that categories are correctly ordered on the list. 0395 0396 break; 0397 } 0398 } 0399 0400 if ((selectionMode() != SingleSelection) && (selectionMode() != NoSelection)) 0401 { 0402 if (d->mouseButtonPressed && (QListView::state() != DraggingState)) 0403 { 0404 QPoint start, end, initialPressPosition; 0405 0406 initialPressPosition = d->initialPressPosition; 0407 0408 initialPressPosition.setY(initialPressPosition.y() - verticalOffset()); 0409 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset()); 0410 0411 if ((d->initialPressPosition.x() > d->mousePosition.x()) || 0412 (d->initialPressPosition.y() > d->mousePosition.y())) 0413 { 0414 start = d->mousePosition; 0415 end = initialPressPosition; 0416 } 0417 else 0418 { 0419 start = initialPressPosition; 0420 end = d->mousePosition; 0421 } 0422 0423 QStyleOptionRubberBand yetAnotherOption; 0424 yetAnotherOption.initFrom(this); 0425 yetAnotherOption.shape = QRubberBand::Rectangle; 0426 yetAnotherOption.opaque = false; 0427 yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16)); 0428 painter.save(); 0429 style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter); 0430 painter.restore(); 0431 } 0432 } 0433 0434 if (d->drawItemsWhileDragging && (QListView::state() == DraggingState) && !d->dragLeftViewport) 0435 { 0436 painter.setOpacity(0.5); 0437 d->drawDraggedItems(&painter); 0438 } 0439 0440 painter.restore(); 0441 } 0442 0443 void DCategorizedView::resizeEvent(QResizeEvent* event) 0444 { 0445 QListView::resizeEvent(event); 0446 0447 // Clear the items positions cache 0448 0449 d->elementsPosition.clear(); 0450 d->categoriesPosition.clear(); 0451 d->forcedSelectionPosition = 0; 0452 0453 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0454 { 0455 return; 0456 } 0457 0458 d->updateScrollbars(); 0459 } 0460 0461 QItemSelection DCategorizedView::Private::selectionForRect(const QRect& rect) 0462 { 0463 QItemSelection selection; 0464 QModelIndex tl, br; 0465 QModelIndexList intersectedIndexes = intersectionSet(rect); 0466 QList<QModelIndex>::const_iterator it = intersectedIndexes.constBegin(); 0467 0468 for ( ; it != intersectedIndexes.constEnd() ; ++it) 0469 { 0470 if (!tl.isValid() && !br.isValid()) 0471 { 0472 tl = br = *it; 0473 } 0474 else if ((*it).row() == (tl.row() - 1)) 0475 { 0476 tl = *it; // expand current range 0477 } 0478 else if ((*it).row() == (br.row() + 1)) 0479 { 0480 br = (*it); // expand current range 0481 } 0482 else 0483 { 0484 selection.select(tl, br); // select current range 0485 tl = br = *it; // start new range 0486 } 0487 } 0488 0489 if (tl.isValid() && br.isValid()) 0490 { 0491 selection.select(tl, br); 0492 } 0493 else if (tl.isValid()) 0494 { 0495 selection.select(tl, tl); 0496 } 0497 else if (br.isValid()) 0498 { 0499 selection.select(br, br); 0500 } 0501 0502 return selection; 0503 } 0504 0505 void DCategorizedView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command) 0506 { 0507 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0508 { 0509 QListView::setSelection(rect, command); 0510 return; 0511 } 0512 0513 QItemSelection selection; 0514 /* 0515 QRect contentsRect = rect.translated(horizontalOffset(), verticalOffset()); 0516 */ 0517 QModelIndexList intersectedIndexes = d->intersectionSet(rect); 0518 0519 if ((rect.width() == 1) && (rect.height() == 1)) 0520 { 0521 QModelIndex tl; 0522 0523 if (!intersectedIndexes.isEmpty()) 0524 { 0525 tl = intersectedIndexes.last(); // special case for mouse press; only select the top item 0526 } 0527 0528 if (tl.isValid() && (tl.flags() & Qt::ItemIsEnabled)) 0529 { 0530 selection.select(tl, tl); 0531 } 0532 } 0533 else 0534 { 0535 if (state() == DragSelectingState) 0536 { 0537 // visual selection mode (rubberband selection) 0538 0539 selection = d->selectionForRect(rect); 0540 } 0541 else 0542 { 0543 // logical selection mode (key and mouse click selection) 0544 0545 QModelIndex tl, br; 0546 0547 // get the first item 0548 0549 const QRect topLeft(rect.left(), rect.top(), 1, 1); 0550 intersectedIndexes = d->intersectionSet(topLeft); 0551 0552 if (!intersectedIndexes.isEmpty()) 0553 { 0554 tl = intersectedIndexes.last(); 0555 } 0556 0557 // get the last item 0558 0559 const QRect bottomRight(rect.right(), rect.bottom(), 1, 1); 0560 intersectedIndexes = d->intersectionSet(bottomRight); 0561 0562 if (!intersectedIndexes.isEmpty()) 0563 { 0564 br = intersectedIndexes.last(); 0565 } 0566 0567 // get the ranges 0568 0569 if (tl.isValid() && 0570 br.isValid() && 0571 (tl.flags() & Qt::ItemIsEnabled) && 0572 (br.flags() & Qt::ItemIsEnabled)) 0573 { 0574 // first, middle, last in content coordinates 0575 0576 QRect middle; 0577 QRect first = d->cachedRectIndex(tl); 0578 QRect last = d->cachedRectIndex(br); 0579 QSize fullSize = d->contentsSize(); 0580 0581 if (flow() == LeftToRight) 0582 { 0583 QRect& top = first; 0584 QRect& bottom = last; 0585 0586 // if bottom is above top, swap them 0587 0588 if (top.center().y() > bottom.center().y()) 0589 { 0590 QRect tmp = top; 0591 top = bottom; 0592 bottom = tmp; 0593 } 0594 0595 // if the rect are on different lines, expand 0596 0597 if (top.top() != bottom.top()) 0598 { 0599 // top rectangle 0600 0601 if (isRightToLeft()) 0602 { 0603 top.setLeft(0); 0604 } 0605 else 0606 { 0607 top.setRight(fullSize.width()); 0608 } 0609 0610 // bottom rectangle 0611 0612 if (isRightToLeft()) 0613 { 0614 bottom.setRight(fullSize.width()); 0615 } 0616 else 0617 { 0618 bottom.setLeft(0); 0619 } 0620 } 0621 else if (top.left() > bottom.right()) 0622 { 0623 if (isRightToLeft()) 0624 { 0625 bottom.setLeft(top.right()); 0626 } 0627 else 0628 { 0629 bottom.setRight(top.left()); 0630 } 0631 } 0632 else 0633 { 0634 if (isRightToLeft()) 0635 { 0636 top.setLeft(bottom.right()); 0637 } 0638 else 0639 { 0640 top.setRight(bottom.left()); 0641 } 0642 } 0643 0644 // middle rectangle 0645 0646 if (top.bottom() < bottom.top()) 0647 { 0648 middle.setTop(top.bottom() + 1); 0649 middle.setLeft(qMin(top.left(), bottom.left())); 0650 middle.setBottom(bottom.top() - 1); 0651 middle.setRight(qMax(top.right(), bottom.right())); 0652 } 0653 } 0654 else 0655 { 0656 // TopToBottom 0657 0658 QRect& left = first; 0659 QRect& right = last; 0660 0661 if (left.center().x() > right.center().x()) 0662 { 0663 std::swap(left, right); 0664 } 0665 0666 int ch = fullSize.height(); 0667 0668 if (left.left() != right.left()) 0669 { 0670 // left rectangle 0671 0672 if (isRightToLeft()) 0673 { 0674 left.setTop(0); 0675 } 0676 else 0677 { 0678 left.setBottom(ch); 0679 } 0680 0681 // top rectangle 0682 0683 if (isRightToLeft()) 0684 { 0685 right.setBottom(ch); 0686 } 0687 else 0688 { 0689 right.setTop(0); 0690 } 0691 0692 // only set middle if the 0693 0694 middle.setTop(0); 0695 middle.setBottom(ch); 0696 middle.setLeft(left.right() + 1); 0697 middle.setRight(right.left() - 1); 0698 } 0699 else if (left.bottom() < right.top()) 0700 { 0701 left.setBottom(right.top() - 1); 0702 } 0703 else 0704 { 0705 right.setBottom(left.top() - 1); 0706 } 0707 } 0708 0709 // get viewport coordinates 0710 0711 first = first.translated( - horizontalOffset(), - verticalOffset()); 0712 middle = middle.translated( - horizontalOffset(), - verticalOffset()); 0713 last = last.translated( - horizontalOffset(), - verticalOffset()); 0714 0715 // do the selections 0716 0717 QItemSelection topSelection = d->selectionForRect(first); 0718 QItemSelection middleSelection = d->selectionForRect(middle); 0719 QItemSelection bottomSelection = d->selectionForRect(last); 0720 0721 // merge 0722 0723 selection.merge(topSelection, QItemSelectionModel::Select); 0724 selection.merge(middleSelection, QItemSelectionModel::Select); 0725 selection.merge(bottomSelection, QItemSelectionModel::Select); 0726 } 0727 } 0728 } 0729 0730 selectionModel()->select(selection, command); 0731 } 0732 0733 void DCategorizedView::mouseMoveEvent(QMouseEvent* event) 0734 { 0735 QListView::mouseMoveEvent(event); 0736 0737 // was a dragging started? 0738 0739 if (state() == DraggingState) 0740 { 0741 d->mouseButtonPressed = false; 0742 d->rightMouseButtonPressed = false; 0743 0744 if (d->drawItemsWhileDragging) 0745 { 0746 viewport()->update(d->lastDraggedItemsRect); 0747 } 0748 } 0749 0750 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0751 { 0752 return; 0753 } 0754 0755 const QModelIndexList item = d->intersectionSet(QRect(event->pos(), event->pos())); 0756 0757 if (item.count() == 1) 0758 { 0759 d->hovered = item[0]; 0760 } 0761 else 0762 { 0763 d->hovered = QModelIndex(); 0764 } 0765 0766 const QString previousHoveredCategory = d->hoveredCategory; 0767 0768 d->mousePosition = event->pos(); 0769 0770 d->hoveredCategory.clear(); 0771 0772 // Redraw categories 0773 0774 Q_FOREACH (const QString& category, d->categories) 0775 { 0776 if (d->categoryVisualRect(category).intersects(QRect(event->pos(), event->pos()))) 0777 { 0778 d->hoveredCategory = category; 0779 viewport()->update(d->categoryVisualRect(category)); 0780 } 0781 else if ((category == previousHoveredCategory) && 0782 (!d->categoryVisualRect(previousHoveredCategory).intersects(QRect(event->pos(), event->pos())))) 0783 { 0784 viewport()->update(d->categoryVisualRect(category)); 0785 } 0786 } 0787 0788 QRect rect; 0789 0790 if (d->mouseButtonPressed && QListView::state() != DraggingState) 0791 { 0792 QPoint start, end, initialPressPosition; 0793 0794 initialPressPosition = d->initialPressPosition; 0795 0796 initialPressPosition.setY(initialPressPosition.y() - verticalOffset()); 0797 initialPressPosition.setX(initialPressPosition.x() - horizontalOffset()); 0798 0799 if (d->initialPressPosition.x() > d->mousePosition.x() || 0800 d->initialPressPosition.y() > d->mousePosition.y()) 0801 { 0802 start = d->mousePosition; 0803 end = initialPressPosition; 0804 } 0805 else 0806 { 0807 start = initialPressPosition; 0808 end = d->mousePosition; 0809 } 0810 0811 rect = QRect(start, end).adjusted(-16, -16, 16, 16); 0812 rect = rect.united(QRect(start, end).adjusted(16, 16, -16, -16)).intersected(viewport()->rect()); 0813 0814 viewport()->update(rect); 0815 } 0816 } 0817 0818 void DCategorizedView::mousePressEvent(QMouseEvent* event) 0819 { 0820 d->dragLeftViewport = false; 0821 0822 QListView::mousePressEvent(event); 0823 0824 if (event->button() == Qt::LeftButton) 0825 { 0826 d->mouseButtonPressed = true; 0827 d->initialPressPosition = event->pos(); 0828 d->initialPressPosition.setY(d->initialPressPosition.y() + verticalOffset()); 0829 d->initialPressPosition.setX(d->initialPressPosition.x() + horizontalOffset()); 0830 } 0831 else if (event->button() == Qt::RightButton) 0832 { 0833 d->rightMouseButtonPressed = true; 0834 } 0835 0836 if (selectionModel()) 0837 { 0838 d->lastSelection = selectionModel()->selection(); 0839 } 0840 0841 viewport()->update(d->categoryVisualRect(d->hoveredCategory)); 0842 } 0843 0844 void DCategorizedView::mouseReleaseEvent(QMouseEvent* event) 0845 { 0846 d->mouseButtonPressed = false; 0847 d->rightMouseButtonPressed = false; 0848 0849 QListView::mouseReleaseEvent(event); 0850 0851 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0852 { 0853 return; 0854 } 0855 0856 QPoint initPressPos = viewport()->mapFromGlobal(QCursor::pos()); 0857 initPressPos.setY(initPressPos.y() + verticalOffset()); 0858 initPressPos.setX(initPressPos.x() + horizontalOffset()); 0859 0860 if ((selectionMode() != SingleSelection) && 0861 (selectionMode() != NoSelection) && 0862 (initPressPos == d->initialPressPosition)) 0863 { 0864 Q_FOREACH (const QString& category, d->categories) 0865 { 0866 if (d->categoryVisualRect(category).contains(event->pos()) && 0867 selectionModel()) 0868 { 0869 QItemSelection selection = selectionModel()->selection(); 0870 const QVector<int>& indexList = d->categoriesIndexes[category]; 0871 0872 Q_FOREACH (int row, indexList) 0873 { 0874 QModelIndex selectIndex = d->proxyModel->index(row, 0); 0875 0876 selection << QItemSelectionRange(selectIndex); 0877 } 0878 0879 selectionModel()->select(selection, QItemSelectionModel::SelectCurrent); 0880 0881 break; 0882 } 0883 } 0884 } 0885 0886 QRect rect; 0887 0888 if (state() != DraggingState) 0889 { 0890 QPoint start, end, newInitPressPos; 0891 0892 newInitPressPos = d->initialPressPosition; 0893 0894 newInitPressPos.setY(newInitPressPos.y() - verticalOffset()); 0895 newInitPressPos.setX(newInitPressPos.x() - horizontalOffset()); 0896 0897 if ((d->initialPressPosition.x() > d->mousePosition.x()) || 0898 (d->initialPressPosition.y() > d->mousePosition.y())) 0899 { 0900 start = d->mousePosition; 0901 end = newInitPressPos; 0902 } 0903 else 0904 { 0905 start = newInitPressPos; 0906 end = d->mousePosition; 0907 } 0908 0909 rect = QRect(start, end).adjusted(-16, -16, 16, 16); 0910 rect = rect.united(QRect(start, end).adjusted(16, 16, -16, -16)).intersected(viewport()->rect()); 0911 0912 viewport()->update(rect); 0913 } 0914 0915 if (d->hovered.isValid()) 0916 { 0917 viewport()->update(visualRect(d->hovered)); 0918 } 0919 else if (!d->hoveredCategory.isEmpty()) 0920 { 0921 viewport()->update(d->categoryVisualRect(d->hoveredCategory)); 0922 } 0923 } 0924 0925 void DCategorizedView::leaveEvent(QEvent* event) 0926 { 0927 d->hovered = QModelIndex(); 0928 d->hoveredCategory.clear(); 0929 d->forcedSelectionPosition = 0; 0930 d->mouseButtonPressed = false; 0931 0932 if (state() == DraggingState) 0933 { 0934 setState(NoState); 0935 } 0936 0937 QListView::leaveEvent(event); 0938 } 0939 0940 void DCategorizedView::startDrag(Qt::DropActions supportedActions) 0941 { 0942 // FIXME: QAbstractItemView does far better here since it sets the 0943 // pixmap of selected icons to the dragging cursor, but it sets a non 0944 // ARGB window so it is no transparent. Use QAbstractItemView when 0945 // this is fixed on Qt. 0946 // QAbstractItemView::startDrag(supportedActions); 0947 0948 #if defined(DOLPHIN_DRAGANDDROP) 0949 0950 Q_UNUSED(supportedActions); 0951 0952 #else 0953 0954 QListView::startDrag(supportedActions); 0955 0956 #endif 0957 0958 } 0959 0960 void DCategorizedView::dragMoveEvent(QDragMoveEvent* event) 0961 { 0962 0963 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0964 0965 d->mousePosition = event->position().toPoint(); 0966 0967 #else 0968 0969 d->mousePosition = event->pos(); 0970 0971 #endif 0972 0973 d->dragLeftViewport = false; 0974 0975 #if defined(DOLPHIN_DRAGANDDROP) 0976 0977 QAbstractItemView::dragMoveEvent(event); 0978 0979 #else 0980 0981 QListView::dragMoveEvent(event); 0982 0983 #endif 0984 0985 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 0986 { 0987 return; 0988 } 0989 0990 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0991 0992 d->hovered = indexAt(event->position().toPoint()); 0993 0994 #else 0995 0996 d->hovered = indexAt(event->pos()); 0997 0998 #endif 0999 1000 #if !defined(DOLPHIN_DRAGANDDROP) 1001 1002 d->drawDraggedItems(); 1003 1004 #endif 1005 1006 } 1007 1008 void DCategorizedView::dragLeaveEvent(QDragLeaveEvent* event) 1009 { 1010 d->dragLeftViewport = true; 1011 1012 #if defined(DOLPHIN_DRAGANDDROP) 1013 1014 QAbstractItemView::dragLeaveEvent(event); 1015 1016 #else 1017 1018 QListView::dragLeaveEvent(event); 1019 1020 #endif 1021 1022 } 1023 1024 void DCategorizedView::dropEvent(QDropEvent* event) 1025 { 1026 1027 #if defined(DOLPHIN_DRAGANDDROP) 1028 1029 QAbstractItemView::dropEvent(event); 1030 1031 #else 1032 1033 QListView::dropEvent(event); 1034 1035 #endif 1036 1037 } 1038 1039 QModelIndex DCategorizedView::moveCursor(CursorAction cursorAction, 1040 Qt::KeyboardModifiers modifiers) 1041 { 1042 if ((viewMode() != DCategorizedView::IconMode) || 1043 !d->proxyModel || 1044 !d->categoryDrawer || 1045 d->categories.isEmpty() || 1046 !d->proxyModel->isCategorizedModel()) 1047 { 1048 return QListView::moveCursor(cursorAction, modifiers); 1049 } 1050 1051 int viewportWidth = viewport()->width() - spacing(); 1052 int itemWidth; 1053 1054 if (gridSize().isEmpty()) 1055 { 1056 itemWidth = d->biggestItemSize.width(); 1057 } 1058 else 1059 { 1060 itemWidth = gridSize().width(); 1061 } 1062 1063 int itemWidthPlusSeparation = spacing() + itemWidth; 1064 1065 if (!itemWidthPlusSeparation) 1066 { 1067 ++itemWidthPlusSeparation; 1068 } 1069 1070 int elementsPerRow = viewportWidth / itemWidthPlusSeparation; 1071 1072 if (!elementsPerRow) 1073 { 1074 ++elementsPerRow; 1075 } 1076 1077 QModelIndex current = selectionModel() ? selectionModel()->currentIndex() 1078 : QModelIndex(); 1079 1080 if (!current.isValid()) 1081 { 1082 if (cursorAction == MoveEnd) 1083 { 1084 current = model()->index(model()->rowCount() - 1, 0, QModelIndex()); 1085 /* 1086 d->forcedSelectionPosition = d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow; 1087 */ 1088 } 1089 else 1090 { 1091 current = model()->index(0, 0, QModelIndex()); 1092 d->forcedSelectionPosition = 0; 1093 } 1094 1095 return current; 1096 } 1097 1098 QString lastCategory = d->categories.first(); 1099 QString theCategory = d->categories.first(); 1100 QString afterCategory = d->categories.first(); 1101 bool hasToBreak = false; 1102 1103 Q_FOREACH (const QString& category, d->categories) 1104 { 1105 // cppcheck-suppress knownConditionTrueFalse 1106 if (hasToBreak) 1107 { 1108 afterCategory = category; 1109 1110 break; 1111 } 1112 1113 if (category == d->elementsInfo[current.row()].category) 1114 { 1115 theCategory = category; 1116 hasToBreak = true; 1117 } 1118 1119 if (!hasToBreak) 1120 { 1121 lastCategory = category; 1122 } 1123 } 1124 1125 switch (cursorAction) 1126 { 1127 case QAbstractItemView::MovePageUp: 1128 { 1129 // We need to reimplement PageUp/Down as well because 1130 // default QListView implementation will not work properly with our custom layout 1131 1132 QModelIndexList visibleIndexes = d->intersectionSet(viewport()->rect()); 1133 1134 if (!visibleIndexes.isEmpty()) 1135 { 1136 int indexToMove = qMax(current.row() - visibleIndexes.size(), 0); 1137 1138 return d->proxyModel->index(indexToMove, 0); 1139 } 1140 1141 break; 1142 } 1143 1144 // fall through 1145 1146 case QAbstractItemView::MoveUp: 1147 { 1148 if (d->elementsInfo[current.row()].relativeOffsetToCategory >= elementsPerRow) 1149 { 1150 int indexToMove = current.row(); 1151 indexToMove -= qMin(((d->elementsInfo[current.row()].relativeOffsetToCategory) + 1152 d->forcedSelectionPosition), elementsPerRow - d->forcedSelectionPosition + 1153 (d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow)); 1154 1155 return (d->proxyModel->index(indexToMove, 0)); 1156 } 1157 else 1158 { 1159 int lastCategoryLastRow = (d->categoriesIndexes[lastCategory].count() - 1) % elementsPerRow; 1160 int indexToMove = current.row() - d->elementsInfo[current.row()].relativeOffsetToCategory; 1161 1162 if (d->forcedSelectionPosition >= lastCategoryLastRow) 1163 { 1164 indexToMove -= 1; 1165 } 1166 else 1167 { 1168 indexToMove -= qMin((lastCategoryLastRow - d->forcedSelectionPosition + 1), 1169 d->forcedSelectionPosition + elementsPerRow + 1); 1170 } 1171 1172 return d->proxyModel->index(indexToMove, 0); 1173 } 1174 } 1175 1176 case QAbstractItemView::MovePageDown: 1177 { 1178 QModelIndexList visibleIndexes = d->intersectionSet(viewport()->rect()); 1179 1180 if (!visibleIndexes.isEmpty()) 1181 { 1182 int indexToMove = qMin(current.row() + visibleIndexes.size(), d->elementsInfo.size() - 1); 1183 1184 return d->proxyModel->index(indexToMove, 0); 1185 } 1186 } 1187 1188 // fall through 1189 1190 case QAbstractItemView::MoveDown: 1191 { 1192 if (d->elementsInfo[current.row()].relativeOffsetToCategory < (d->categoriesIndexes[theCategory].count() - 1 - ((d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow))) 1193 { 1194 int indexToMove = current.row(); 1195 indexToMove += qMin(elementsPerRow, d->categoriesIndexes[theCategory].count() - 1 - 1196 d->elementsInfo[current.row()].relativeOffsetToCategory); 1197 1198 return d->proxyModel->index(indexToMove, 0); 1199 } 1200 else 1201 { 1202 int afterCategoryLastRow = qMin(elementsPerRow, d->categoriesIndexes[afterCategory].count()); 1203 int indexToMove = current.row() + (d->categoriesIndexes[theCategory].count() - 1204 d->elementsInfo[current.row()].relativeOffsetToCategory); 1205 1206 if (d->forcedSelectionPosition >= afterCategoryLastRow) 1207 { 1208 indexToMove += afterCategoryLastRow - 1; 1209 } 1210 else 1211 { 1212 indexToMove += qMin(d->forcedSelectionPosition, elementsPerRow); 1213 } 1214 1215 return d->proxyModel->index(indexToMove, 0); 1216 } 1217 } 1218 1219 case QAbstractItemView::MoveLeft: 1220 1221 if (layoutDirection() == Qt::RightToLeft) 1222 { 1223 if (((current.row() + 1) == d->elementsInfo.size()) || 1224 !(d->elementsInfo[current.row() + 1].relativeOffsetToCategory % elementsPerRow)) 1225 { 1226 return current; 1227 } 1228 1229 return d->proxyModel->index(current.row() + 1, 0); 1230 } 1231 1232 if ((current.row() == 0) || 1233 !(d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow)) 1234 { 1235 return current; 1236 } 1237 1238 return d->proxyModel->index(current.row() - 1, 0); 1239 1240 case QAbstractItemView::MoveRight: 1241 1242 if (layoutDirection() == Qt::RightToLeft) 1243 { 1244 if ((current.row() == 0) || 1245 !(d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow)) 1246 { 1247 return current; 1248 } 1249 1250 return d->proxyModel->index(current.row() - 1, 0); 1251 } 1252 1253 if (((current.row() + 1) == d->elementsInfo.size()) || 1254 !(d->elementsInfo[current.row() + 1].relativeOffsetToCategory % elementsPerRow)) 1255 { 1256 return current; 1257 } 1258 1259 return d->proxyModel->index(current.row() + 1, 0); 1260 1261 default: 1262 break; 1263 } 1264 1265 return QListView::moveCursor(cursorAction, modifiers); 1266 } 1267 1268 void DCategorizedView::rowsInserted(const QModelIndex& parent, int start, int end) 1269 { 1270 QListView::rowsInserted(parent, start, end); 1271 1272 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 1273 { 1274 d->forcedSelectionPosition = 0; 1275 d->hovered = QModelIndex(); 1276 d->biggestItemSize = QSize(0, 0); 1277 d->mouseButtonPressed = false; 1278 d->rightMouseButtonPressed = false; 1279 d->elementsInfo.clear(); 1280 d->elementsPosition.clear(); 1281 d->categoriesIndexes.clear(); 1282 d->categoriesPosition.clear(); 1283 d->categories.clear(); 1284 d->intersectedIndexes.clear(); 1285 1286 return; 1287 } 1288 1289 rowsInsertedArtifficial(parent, start, end); 1290 } 1291 1292 int DCategorizedView::Private::categoryUpperBound(SparseModelIndexVector& modelIndexList, int begin, int averageSize) 1293 { 1294 int end = modelIndexList.size(); 1295 QString category = proxyModel->data(modelIndexList[begin], 1296 DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 1297 1298 // First case: Small category with <10 entries 1299 1300 const int smallEnd = qMin(end, begin + 10); 1301 1302 for (int k = begin ; k < smallEnd ; ++k) 1303 { 1304 if (category != proxyModel->data(modelIndexList[k], 1305 DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString()) 1306 { 1307 return k; 1308 } 1309 } 1310 1311 begin += 10; 1312 1313 // Second case: only one category, test last value 1314 1315 QString value = proxyModel->data(modelIndexList[end - 1], 1316 DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 1317 1318 if (value == category) 1319 { 1320 return end; 1321 } 1322 1323 // Third case: use average of last category sizes 1324 1325 if (averageSize && ((begin + averageSize) < end)) 1326 { 1327 if (category != proxyModel->data(modelIndexList[begin + averageSize], 1328 DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString()) 1329 { 1330 end = begin + averageSize; 1331 } 1332 else if (begin + 2*averageSize < end) 1333 { 1334 if (category != proxyModel->data(modelIndexList[begin + 2*averageSize], 1335 DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString()) 1336 { 1337 end = begin + 2 * averageSize; 1338 } 1339 } 1340 } 1341 1342 // now apply a binary search - the model is sorted by category 1343 // from qUpperBound, Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 1344 1345 int middle; 1346 int n = end - begin; 1347 int half; 1348 1349 while (n > 0) 1350 { 1351 half = n >> 1; 1352 middle = begin + half; 1353 1354 if (category != proxyModel->data(modelIndexList[middle], 1355 DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString()) 1356 { 1357 n = half; 1358 } 1359 else 1360 { 1361 begin = middle + 1; 1362 n -= half + 1; 1363 } 1364 } 1365 1366 return begin; 1367 } 1368 1369 void DCategorizedView::rowsInsertedArtifficial(const QModelIndex& parent, int start, int end) 1370 { 1371 Q_UNUSED(parent); 1372 1373 d->forcedSelectionPosition = 0; 1374 d->hovered = QModelIndex(); 1375 d->biggestItemSize = QSize(0, 0); 1376 d->mouseButtonPressed = false; 1377 d->rightMouseButtonPressed = false; 1378 d->elementsInfo.clear(); 1379 d->elementsPosition.clear(); 1380 d->categoriesIndexes.clear(); 1381 d->categoriesPosition.clear(); 1382 d->categories.clear(); 1383 d->intersectedIndexes.clear(); 1384 1385 if ((start > end) || (end < 0) || (start < 0) || !d->proxyModel->rowCount()) 1386 { 1387 return; 1388 } 1389 1390 // Add all elements mapped to the source model and explore categories 1391 1392 const int rowCount = d->proxyModel->rowCount(); 1393 const int sortColumn = d->proxyModel->sortColumn(); 1394 QString lastCategory = d->proxyModel->data(d->proxyModel->index(0, sortColumn), 1395 DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 1396 int offset = -1; 1397 1398 SparseModelIndexVector modelIndexList(rowCount, d->proxyModel, sortColumn); 1399 1400 d->elementsInfo = QVector<Private::ElementInfo>(rowCount); 1401 int categorySizes = 0; 1402 int categoryCounts = 0; 1403 1404 if (uniformItemSizes()) 1405 { 1406 // use last index as sample for size hint 1407 1408 QModelIndex sample = d->proxyModel->index(rowCount - 1, modelColumn(), rootIndex()); 1409 d->biggestItemSize = sizeHintForIndex(sample); 1410 } 1411 else 1412 { 1413 QStyleOptionViewItem option; 1414 1415 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 1416 1417 initViewItemOption(&option); 1418 1419 #else 1420 1421 option = viewOptions(); 1422 1423 #endif 1424 1425 for (int k = 0 ; k < rowCount ; ++k) 1426 { 1427 QModelIndex indexSize = (sortColumn == 0) ? modelIndexList[k] : d->proxyModel->index(k, 0); 1428 1429 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 1430 1431 QSize hint = itemDelegateForIndex(indexSize)->sizeHint(option, indexSize); 1432 1433 #else 1434 1435 QSize hint = itemDelegate(indexSize)->sizeHint(option, indexSize); 1436 1437 #endif 1438 1439 d->biggestItemSize = QSize(qMax(hint.width(), d->biggestItemSize.width()), 1440 qMax(hint.height(), d->biggestItemSize.height())); 1441 } 1442 } 1443 1444 for (int k = 0 ; k < rowCount ; ) 1445 { 1446 lastCategory = d->proxyModel->data(modelIndexList[k], DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 1447 int upperBound = d->categoryUpperBound(modelIndexList, k, categorySizes / ++categoryCounts); 1448 categorySizes += upperBound - k; 1449 offset = 0; 1450 1451 QVector<int> rows(upperBound - k); 1452 1453 for (int i = k ; i < upperBound ; ++i, ++offset) 1454 { 1455 rows[offset] = i; 1456 Private::ElementInfo& elementInfo = d->elementsInfo[i]; 1457 elementInfo.category = lastCategory; 1458 elementInfo.relativeOffsetToCategory = offset; 1459 } 1460 1461 k = upperBound; 1462 1463 d->categoriesIndexes.insert(lastCategory, rows); 1464 d->categories << lastCategory; 1465 } 1466 1467 d->updateScrollbars(); 1468 1469 // FIXME: We need to safely save the last selection. This is on my TODO 1470 // list (ereslibre). 1471 // Note: QItemSelectionModel will save it selection in persistend indexes 1472 // on layoutChanged(). All works fine for me. 1473 /* 1474 selectionModel()->clear(); 1475 */ 1476 } 1477 1478 void DCategorizedView::rowsRemoved(const QModelIndex& parent, int start, int end) 1479 { 1480 Q_UNUSED(parent); 1481 Q_UNUSED(start); 1482 Q_UNUSED(end); 1483 1484 if (d->proxyModel && d->categoryDrawer && d->proxyModel->isCategorizedModel()) 1485 { 1486 // Force the view to update all elements 1487 1488 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1); 1489 } 1490 } 1491 1492 void DCategorizedView::updateGeometries() 1493 { 1494 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 1495 { 1496 QListView::updateGeometries(); 1497 return; 1498 } 1499 1500 // Avoid QListView::updateGeometries(), since it will try to set another 1501 // range to our scroll bars, what we don't want 1502 1503 QAbstractItemView::updateGeometries(); 1504 } 1505 1506 void DCategorizedView::slotLayoutChanged() 1507 { 1508 if (d->proxyModel && d->categoryDrawer && d->proxyModel->isCategorizedModel()) 1509 { 1510 // all cached values are invalidated, recompute immediately 1511 1512 rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1); 1513 } 1514 } 1515 1516 void DCategorizedView::currentChanged(const QModelIndex& current, const QModelIndex& previous) 1517 { 1518 if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel()) 1519 { 1520 QListView::currentChanged(current, previous); 1521 return; 1522 } 1523 1524 // We need to update the forcedSelectionPosition property in order to correctly 1525 // navigate after with keyboard using up & down keys 1526 1527 int viewportWidth = viewport()->width() - spacing(); 1528 1529 // int itemHeight; 1530 int itemWidth; 1531 1532 if (gridSize().isEmpty()) 1533 { 1534 // itemHeight = d->biggestItemSize.height(); 1535 itemWidth = d->biggestItemSize.width(); 1536 } 1537 else 1538 { 1539 // itemHeight = gridSize().height(); 1540 itemWidth = gridSize().width(); 1541 } 1542 1543 int itemWidthPlusSeparation = spacing() + itemWidth; 1544 1545 if (!itemWidthPlusSeparation) 1546 { 1547 ++itemWidthPlusSeparation; 1548 } 1549 1550 int elementsPerRow = viewportWidth / itemWidthPlusSeparation; 1551 1552 if (!elementsPerRow) 1553 { 1554 ++elementsPerRow; 1555 } 1556 1557 if (current.isValid()) 1558 { 1559 d->forcedSelectionPosition = d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow; 1560 } 1561 1562 QListView::currentChanged(current, previous); 1563 } 1564 1565 } // namespace Digikam 1566 1567 #include "moc_dcategorizedview.cpp"