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