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::Private::Private(DCategorizedView* const lv) 0023 : listView (lv), 0024 categoryDrawer (nullptr), 0025 biggestItemSize (QSize(0, 0)), 0026 mouseButtonPressed (false), 0027 rightMouseButtonPressed (false), 0028 dragLeftViewport (false), 0029 drawItemsWhileDragging (true), 0030 forcedSelectionPosition (0), 0031 proxyModel (nullptr) 0032 { 0033 } 0034 0035 DCategorizedView::Private::~Private() 0036 { 0037 } 0038 0039 const QModelIndexList& DCategorizedView::Private::intersectionSet(const QRect& rect) 0040 { 0041 QModelIndex index; 0042 QRect indexVisualRect; 0043 int itemHeight; 0044 0045 intersectedIndexes.clear(); 0046 0047 if (listView->gridSize().isEmpty()) 0048 { 0049 itemHeight = biggestItemSize.height(); 0050 } 0051 else 0052 { 0053 itemHeight = listView->gridSize().height(); 0054 } 0055 0056 // Lets find out where we should start 0057 0058 int top = proxyModel->rowCount() - 1; 0059 int bottom = 0; 0060 int middle = (top + bottom) / 2; 0061 0062 while (bottom <= top) 0063 { 0064 middle = (top + bottom) / 2; 0065 index = proxyModel->index(middle, 0); 0066 indexVisualRect = visualRect(index); 0067 0068 // We need the whole height (not only the visualRect). This will help us to update 0069 // all needed indexes correctly (ereslibre) 0070 0071 indexVisualRect.setHeight(indexVisualRect.height() + (itemHeight - indexVisualRect.height())); 0072 0073 if (qMax(indexVisualRect.topLeft().y(), indexVisualRect.bottomRight().y()) < 0074 qMin(rect.topLeft().y(), rect.bottomRight().y())) 0075 { 0076 bottom = middle + 1; 0077 } 0078 else 0079 { 0080 top = middle - 1; 0081 } 0082 } 0083 0084 for (int i = middle ; i < proxyModel->rowCount() ; ++i) 0085 { 0086 index = proxyModel->index(i, 0); 0087 indexVisualRect = visualRect(index); 0088 0089 if (rect.intersects(indexVisualRect)) 0090 { 0091 intersectedIndexes.append(index); 0092 } 0093 0094 // If we passed next item, stop searching for hits 0095 0096 if (qMax(rect.bottomRight().y(), 0097 rect.topLeft().y()) < qMin(indexVisualRect.topLeft().y(), indexVisualRect.bottomRight().y())) 0098 { 0099 break; 0100 } 0101 } 0102 0103 return intersectedIndexes; 0104 } 0105 0106 QRect DCategorizedView::Private::visualRectInViewport(const QModelIndex& index) const 0107 { 0108 if (!index.isValid() || (index.row() >= elementsInfo.size())) 0109 { 0110 return QRect(); 0111 } 0112 0113 QRect retRect; 0114 QString curCategory = elementsInfo[index.row()].category; 0115 const bool leftToRightFlow = (listView->flow() == QListView::LeftToRight); 0116 QStyleOptionViewItem option; 0117 0118 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0119 0120 listView->initViewItemOption(&option); 0121 0122 #else 0123 0124 option = listView->viewOptions(); 0125 0126 #endif 0127 0128 if (leftToRightFlow) 0129 { 0130 if (listView->layoutDirection() == Qt::LeftToRight) 0131 { 0132 retRect = QRect(listView->spacing(), listView->spacing() * 2 + 0133 categoryDrawer->categoryHeight(index, option), 0, 0); 0134 } 0135 else 0136 { 0137 retRect = QRect(listView->viewport()->width() - listView->spacing(), listView->spacing() * 2 + 0138 categoryDrawer->categoryHeight(index, option), 0, 0); 0139 } 0140 } 0141 else 0142 { 0143 retRect = QRect(listView->spacing(), listView->spacing() * 2 + 0144 categoryDrawer->categoryHeight(index, option), 0, 0); 0145 } 0146 0147 int viewportWidth = listView->viewport()->width() - listView->spacing(); 0148 int itemHeight; 0149 int itemWidth; 0150 0151 if (listView->gridSize().isEmpty() && leftToRightFlow) 0152 { 0153 itemHeight = biggestItemSize.height(); 0154 itemWidth = biggestItemSize.width(); 0155 } 0156 else if (leftToRightFlow) 0157 { 0158 itemHeight = listView->gridSize().height(); 0159 itemWidth = listView->gridSize().width(); 0160 } 0161 else if (listView->gridSize().isEmpty() && !leftToRightFlow) 0162 { 0163 itemHeight = biggestItemSize.height(); 0164 itemWidth = listView->viewport()->width() - listView->spacing() * 2; 0165 } 0166 else 0167 { 0168 itemHeight = listView->gridSize().height(); 0169 itemWidth = listView->gridSize().width() - listView->spacing() * 2; 0170 } 0171 0172 int itemWidthPlusSeparation = listView->spacing() + itemWidth; 0173 0174 if (!itemWidthPlusSeparation) 0175 { 0176 ++itemWidthPlusSeparation; 0177 } 0178 0179 int elementsPerRow = viewportWidth / itemWidthPlusSeparation; 0180 0181 if (!elementsPerRow) 0182 { 0183 ++elementsPerRow; 0184 } 0185 0186 int column; 0187 int row; 0188 0189 if (leftToRightFlow) 0190 { 0191 column = elementsInfo[index.row()].relativeOffsetToCategory % elementsPerRow; 0192 row = elementsInfo[index.row()].relativeOffsetToCategory / elementsPerRow; 0193 0194 if (listView->layoutDirection() == Qt::LeftToRight) 0195 { 0196 retRect.setLeft(retRect.left() + column * listView->spacing() + 0197 column * itemWidth); 0198 } 0199 else 0200 { 0201 retRect.setLeft(retRect.right() - column * listView->spacing() - 0202 column * itemWidth - itemWidth); 0203 0204 retRect.setRight(retRect.right() - column * listView->spacing() - 0205 column * itemWidth); 0206 } 0207 } 0208 else 0209 { 0210 elementsPerRow = 1; 0211 column = elementsInfo[index.row()].relativeOffsetToCategory % elementsPerRow; 0212 row = elementsInfo[index.row()].relativeOffsetToCategory / elementsPerRow; 0213 (void)column; // Remove clang warnings. 0214 } 0215 0216 Q_FOREACH (const QString& category, categories) 0217 { 0218 if (category == curCategory) 0219 { 0220 break; 0221 } 0222 0223 float rows = (float) ((float) categoriesIndexes[category].count() / 0224 (float) elementsPerRow); 0225 0226 int rowsInt = categoriesIndexes[category].count() / elementsPerRow; 0227 0228 if (rows - trunc(rows)) 0229 { 0230 ++rowsInt; 0231 } 0232 0233 retRect.setTop(retRect.top() + 0234 (rowsInt * itemHeight) + 0235 categoryDrawer->categoryHeight(index, option) + 0236 listView->spacing() * 2); 0237 0238 if (listView->gridSize().isEmpty()) 0239 { 0240 retRect.setTop(retRect.top() + 0241 (rowsInt * listView->spacing())); 0242 } 0243 } 0244 0245 if (listView->gridSize().isEmpty()) 0246 { 0247 retRect.setTop(retRect.top() + row * listView->spacing() + 0248 (row * itemHeight)); 0249 } 0250 else 0251 { 0252 retRect.setTop(retRect.top() + (row * itemHeight)); 0253 } 0254 0255 retRect.setWidth(itemWidth); 0256 0257 QModelIndex heightIndex = proxyModel->index(index.row(), 0); 0258 0259 if (listView->gridSize().isEmpty()) 0260 { 0261 retRect.setHeight(listView->sizeHintForIndex(heightIndex).height()); 0262 } 0263 else 0264 { 0265 const QSize sizeHint = listView->sizeHintForIndex(heightIndex); 0266 0267 if (sizeHint.width() < itemWidth && leftToRightFlow) 0268 { 0269 retRect.setWidth(sizeHint.width()); 0270 retRect.moveLeft(retRect.left() + (itemWidth - sizeHint.width()) / 2); 0271 } 0272 0273 retRect.setHeight(qMin(sizeHint.height(), listView->gridSize().height())); 0274 } 0275 0276 return retRect; 0277 } 0278 0279 QRect DCategorizedView::Private::visualCategoryRectInViewport(const QString& category) const 0280 { 0281 QRect retRect(listView->spacing(), 0282 listView->spacing(), 0283 listView->viewport()->width() - listView->spacing() * 2, 0284 0); 0285 0286 if (!proxyModel || 0287 !categoryDrawer || 0288 !proxyModel->isCategorizedModel() || 0289 !proxyModel->rowCount() || 0290 !categories.contains(category)) 0291 { 0292 return QRect(); 0293 } 0294 0295 QModelIndex index = proxyModel->index(0, 0, QModelIndex()); 0296 int viewportWidth = listView->viewport()->width() - listView->spacing(); 0297 int itemHeight; 0298 int itemWidth; 0299 0300 if (listView->gridSize().isEmpty()) 0301 { 0302 itemHeight = biggestItemSize.height(); 0303 itemWidth = biggestItemSize.width(); 0304 } 0305 else 0306 { 0307 itemHeight = listView->gridSize().height(); 0308 itemWidth = listView->gridSize().width(); 0309 } 0310 0311 int itemWidthPlusSeparation = listView->spacing() + itemWidth; 0312 int elementsPerRow = viewportWidth / itemWidthPlusSeparation; 0313 0314 if (!elementsPerRow) 0315 { 0316 ++elementsPerRow; 0317 } 0318 0319 if (listView->flow() == QListView::TopToBottom) 0320 { 0321 elementsPerRow = 1; 0322 } 0323 0324 QStyleOptionViewItem option; 0325 0326 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0327 0328 listView->initViewItemOption(&option); 0329 0330 #else 0331 0332 option = listView->viewOptions(); 0333 0334 #endif 0335 0336 Q_FOREACH (const QString& itCategory, categories) 0337 { 0338 if (itCategory == category) 0339 { 0340 break; 0341 } 0342 0343 float rows = (float) ((float) categoriesIndexes[itCategory].count() / 0344 (float) elementsPerRow); 0345 int rowsInt = categoriesIndexes[itCategory].count() / elementsPerRow; 0346 0347 if (rows - trunc(rows)) 0348 { 0349 ++rowsInt; 0350 } 0351 0352 retRect.setTop(retRect.top() + 0353 (rowsInt * itemHeight) + 0354 categoryDrawer->categoryHeight(index, option) + 0355 listView->spacing() * 2); 0356 0357 if (listView->gridSize().isEmpty()) 0358 { 0359 retRect.setTop(retRect.top() + 0360 (rowsInt * listView->spacing())); 0361 } 0362 } 0363 0364 retRect.setHeight(categoryDrawer->categoryHeight(index, option)); 0365 0366 return retRect; 0367 } 0368 0369 /** 0370 * We're sure elementsPosition doesn't contain index 0371 */ 0372 const QRect& DCategorizedView::Private::cacheIndex(const QModelIndex& index) 0373 { 0374 QRect rect = visualRectInViewport(index); 0375 QHash<int, QRect>::iterator it = elementsPosition.insert(index.row(), rect); 0376 0377 return *it; 0378 } 0379 0380 /** 0381 * We're sure categoriesPosition doesn't contain category 0382 */ 0383 const QRect& DCategorizedView::Private::cacheCategory(const QString& category) 0384 { 0385 QRect rect = visualCategoryRectInViewport(category); 0386 QHash<QString, QRect>::iterator it = categoriesPosition.insert(category, rect); 0387 0388 return *it; 0389 } 0390 0391 const QRect& DCategorizedView::Private::cachedRectIndex(const QModelIndex& index) 0392 { 0393 QHash<int, QRect>::const_iterator it = elementsPosition.constFind(index.row()); 0394 0395 if (it != elementsPosition.constEnd()) // If we have it cached 0396 { 0397 // return it 0398 0399 return *it; 0400 } 0401 else // Otherwise, cache it 0402 { 0403 // and return it 0404 0405 return cacheIndex(index); 0406 } 0407 } 0408 0409 const QRect& DCategorizedView::Private::cachedRectCategory(const QString& category) 0410 { 0411 QHash<QString, QRect>::const_iterator it = categoriesPosition.constFind(category); 0412 0413 if (it != categoriesPosition.constEnd()) // If we have it cached 0414 { 0415 // return it 0416 0417 return *it; 0418 } 0419 else // Otherwise, cache it and 0420 { 0421 // return it 0422 0423 return cacheCategory(category); 0424 } 0425 } 0426 0427 QRect DCategorizedView::Private::visualRect(const QModelIndex& index) 0428 { 0429 QRect retRect = cachedRectIndex(index); 0430 int dx = -listView->horizontalOffset(); 0431 int dy = -listView->verticalOffset(); 0432 retRect.adjust(dx, dy, dx, dy); 0433 0434 return retRect; 0435 } 0436 0437 QRect DCategorizedView::Private::categoryVisualRect(const QString& category) 0438 { 0439 QRect retRect = cachedRectCategory(category); 0440 int dx = -listView->horizontalOffset(); 0441 int dy = -listView->verticalOffset(); 0442 retRect.adjust(dx, dy, dx, dy); 0443 0444 return retRect; 0445 } 0446 0447 QSize DCategorizedView::Private::contentsSize() 0448 { 0449 // find the last index in the last category 0450 0451 QModelIndex lastIndex = categoriesIndexes.isEmpty() ? QModelIndex() 0452 : proxyModel->index(categoriesIndexes[categories.last()].last(), 0); 0453 0454 int lastItemBottom = cachedRectIndex(lastIndex).top() + 0455 listView->spacing() + 0456 (listView->gridSize().isEmpty() ? biggestItemSize.height() 0457 : listView->gridSize().height()) - listView->viewport()->height(); 0458 0459 return QSize(listView->viewport()->width(), lastItemBottom); 0460 } 0461 0462 void DCategorizedView::Private::drawNewCategory(const QModelIndex& index, int sortRole, const QStyleOption& option, QPainter* painter) 0463 { 0464 if (!index.isValid()) 0465 { 0466 return; 0467 } 0468 0469 QStyleOption optionCopy = option; 0470 const QString category = proxyModel->data(index, DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 0471 optionCopy.state &= ~QStyle::State_Selected; 0472 0473 if ((listView->selectionMode() != SingleSelection) && (listView->selectionMode() != NoSelection)) 0474 { 0475 if ((category == hoveredCategory) && !mouseButtonPressed) 0476 { 0477 optionCopy.state |= QStyle::State_MouseOver; 0478 } 0479 else if ((category == hoveredCategory) && mouseButtonPressed) 0480 { 0481 QPoint initialPressPosition = listView->viewport()->mapFromGlobal(QCursor::pos()); 0482 initialPressPosition.setY(initialPressPosition.y() + listView->verticalOffset()); 0483 initialPressPosition.setX(initialPressPosition.x() + listView->horizontalOffset()); 0484 0485 if (initialPressPosition == this->initialPressPosition) 0486 { 0487 optionCopy.state |= QStyle::State_Selected; 0488 } 0489 } 0490 } 0491 0492 categoryDrawer->drawCategory(index, sortRole, optionCopy, painter); 0493 } 0494 0495 void DCategorizedView::Private::updateScrollbars() 0496 { 0497 listView->horizontalScrollBar()->setRange(0, 0); 0498 0499 if (listView->verticalScrollMode() == QAbstractItemView::ScrollPerItem) 0500 { 0501 listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); 0502 } 0503 0504 if (listView->horizontalScrollMode() == QAbstractItemView::ScrollPerItem) 0505 { 0506 listView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); 0507 } 0508 0509 listView->verticalScrollBar()->setSingleStep(listView->viewport()->height() / 10); 0510 listView->verticalScrollBar()->setPageStep(listView->viewport()->height()); 0511 listView->verticalScrollBar()->setRange(0, contentsSize().height()); 0512 } 0513 0514 void DCategorizedView::Private::drawDraggedItems(QPainter* painter) 0515 { 0516 QStyleOptionViewItem option; 0517 0518 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0519 0520 listView->initViewItemOption(&option); 0521 0522 #else 0523 0524 option = listView->viewOptions(); 0525 0526 #endif 0527 0528 option.state &= ~QStyle::State_MouseOver; 0529 0530 Q_FOREACH (const QModelIndex& index, listView->selectionModel()->selectedIndexes()) 0531 { 0532 const int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset(); 0533 const int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset(); 0534 option.rect = visualRect(index); 0535 option.rect.adjust(dx, dy, dx, dy); 0536 0537 if (option.rect.intersects(listView->viewport()->rect())) 0538 { 0539 0540 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0541 0542 listView->itemDelegateForIndex(index)->paint(painter, option, index); 0543 0544 #else 0545 0546 listView->itemDelegate(index)->paint(painter, option, index); 0547 0548 #endif 0549 0550 } 0551 } 0552 } 0553 0554 void DCategorizedView::Private::drawDraggedItems() 0555 { 0556 QRect rectToUpdate; 0557 QRect currentRect; 0558 0559 Q_FOREACH (const QModelIndex& index, listView->selectionModel()->selectedIndexes()) 0560 { 0561 int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset(); 0562 int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset(); 0563 currentRect = visualRect(index); 0564 currentRect.adjust(dx, dy, dx, dy); 0565 0566 if (currentRect.intersects(listView->viewport()->rect())) 0567 { 0568 rectToUpdate = rectToUpdate.united(currentRect); 0569 } 0570 } 0571 0572 listView->viewport()->update(lastDraggedItemsRect.united(rectToUpdate)); 0573 0574 lastDraggedItemsRect = rectToUpdate; 0575 } 0576 0577 } // namespace Digikam