File indexing completed on 2025-01-05 03:51:10

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-04-19
0007  * Description : Qt model-view for items - the delegate
0008  *
0009  * SPDX-FileCopyrightText: 2002-2005 by Renchi Raju <renchi dot raju at gmail dot com>
0010  * SPDX-FileCopyrightText: 2009-2011 by Andi Clemens <andi dot clemens at gmail dot com>
0011  * SPDX-FileCopyrightText: 2002-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  * SPDX-FileCopyrightText: 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "itemdelegate.h"
0019 #include "itemdelegate_p.h"
0020 
0021 // C++ includes
0022 
0023 #include <cmath>
0024 
0025 // Qt includes
0026 
0027 #include <QCache>
0028 #include <QPainter>
0029 #include <QIcon>
0030 #include <QApplication>
0031 
0032 // KDE includes
0033 
0034 #include <klocalizedstring.h>
0035 
0036 // Local includes
0037 
0038 #include "digikam_debug.h"
0039 #include "albummanager.h"
0040 #include "itemcategorydrawer.h"
0041 #include "itemcategorizedview.h"
0042 #include "itemdelegateoverlay.h"
0043 #include "itemmodel.h"
0044 #include "itemfiltermodel.h"
0045 #include "itemthumbnailmodel.h"
0046 #include "thumbnailloadthread.h"
0047 #include "applicationsettings.h"
0048 
0049 namespace Digikam
0050 {
0051 
0052 void ItemDelegate::ItemDelegatePrivate::clearRects()
0053 {
0054     ItemViewDelegatePrivate::clearRects();
0055     dateRect             = QRect(0, 0, 0, 0);
0056     modDateRect          = QRect(0, 0, 0, 0);
0057     pixmapRect           = QRect(0, 0, 0, 0);
0058     nameRect             = QRect(0, 0, 0, 0);
0059     titleRect            = QRect(0, 0, 0, 0);
0060     commentsRect         = QRect(0, 0, 0, 0);
0061     resolutionRect       = QRect(0, 0, 0, 0);
0062     coordinatesRect      = QRect(0, 0, 0, 0);
0063     arRect               = QRect(0, 0, 0, 0);
0064     sizeRect             = QRect(0, 0, 0, 0);
0065     tagRect              = QRect(0, 0, 0, 0);
0066     imageInformationRect = QRect(0, 0, 0, 0);
0067     pickLabelRect        = QRect(0, 0, 0, 0);
0068     groupRect            = QRect(0, 0, 0, 0);
0069 }
0070 
0071 ItemDelegate::ItemDelegate(QWidget* const parent)
0072     : ItemViewDelegate(*new ItemDelegatePrivate, parent)
0073 {
0074 }
0075 
0076 ItemDelegate::ItemDelegate(ItemDelegate::ItemDelegatePrivate& dd, QWidget* const parent)
0077     : ItemViewDelegate(dd, parent)
0078 {
0079 }
0080 
0081 ItemDelegate::~ItemDelegate()
0082 {
0083     Q_D(ItemDelegate);
0084     // crashes for a lot of people, see bug 230515. Cause unknown.
0085     //delete d->categoryDrawer;
0086     Q_UNUSED(d); // To please compiler about warnings.
0087 }
0088 
0089 void ItemDelegate::setView(ItemCategorizedView* view)
0090 {
0091     Q_D(ItemDelegate);
0092     setViewOnAllOverlays(view);
0093 
0094     if (d->currentView)
0095     {
0096         disconnect(d->currentView, SIGNAL(modelChanged()),
0097                    this, SLOT(modelChanged()));
0098     }
0099 
0100     d->currentView = view;
0101 
0102     setModel(view ? view->model() : nullptr);
0103 
0104     if (d->currentView)
0105     {
0106         connect(d->currentView, SIGNAL(modelChanged()),
0107                 this, SLOT(modelChanged()));
0108     }
0109 }
0110 
0111 void ItemDelegate::setModel(QAbstractItemModel* model)
0112 {
0113     Q_D(ItemDelegate);
0114 
0115     // 1) We only need the model to invalidate model-index based caches on change
0116     // 2) We do not need to care for overlays. The view calls setActive() on them on model change
0117 
0118     if (model == d->currentModel)
0119     {
0120         return;
0121     }
0122 
0123     if (d->currentModel)
0124     {
0125         disconnect(d->currentModel, nullptr, this, nullptr);
0126     }
0127 
0128     d->currentModel = model;
0129 
0130     if (d->currentModel)
0131     {
0132         connect(d->currentModel, SIGNAL(layoutAboutToBeChanged()),
0133                 this, SLOT(modelContentsChanged()));
0134 
0135         connect(d->currentModel, SIGNAL(modelAboutToBeReset()),
0136                 this, SLOT(modelContentsChanged()));
0137 
0138         connect(d->currentModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
0139                 this, SLOT(modelContentsChanged()));
0140 
0141         connect(d->currentModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
0142                 this, SLOT(modelContentsChanged()));
0143 
0144         connect(d->currentModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0145                 this, SLOT(modelContentsChanged()));
0146     }
0147 }
0148 
0149 void ItemDelegate::setSpacing(int spacing)
0150 {
0151     Q_D(ItemDelegate);
0152 
0153     if (d->categoryDrawer)
0154     {
0155         d->categoryDrawer->setLowerSpacing(spacing);
0156     }
0157 
0158     ItemViewDelegate::setSpacing(spacing);
0159 }
0160 
0161 ItemCategoryDrawer* ItemDelegate::categoryDrawer() const
0162 {
0163     Q_D(const ItemDelegate);
0164     return d->categoryDrawer;
0165 }
0166 
0167 QRect ItemDelegate::commentsRect() const
0168 {
0169     Q_D(const ItemDelegate);
0170     return d->commentsRect;
0171 }
0172 
0173 QRect ItemDelegate::tagsRect() const
0174 {
0175     Q_D(const ItemDelegate);
0176     return d->tagRect;
0177 }
0178 
0179 QRect ItemDelegate::pixmapRect() const
0180 {
0181     Q_D(const ItemDelegate);
0182     return d->pixmapRect;
0183 }
0184 
0185 QRect ItemDelegate::imageInformationRect() const
0186 {
0187     Q_D(const ItemDelegate);
0188     return d->imageInformationRect;
0189 }
0190 
0191 QRect ItemDelegate::groupIndicatorRect() const
0192 {
0193     Q_D(const ItemDelegate);
0194     return d->groupRect;
0195 }
0196 
0197 QRect ItemDelegate::coordinatesIndicatorRect() const
0198 {
0199     Q_D(const ItemDelegate);
0200     return d->coordinatesRect;
0201 }
0202 
0203 QPixmap ItemDelegate::retrieveThumbnailPixmap(const QModelIndex& index, int thumbnailSize)
0204 {
0205     // work around constness
0206     QAbstractItemModel* const model = const_cast<QAbstractItemModel*>(index.model());
0207     // set requested thumbnail size
0208     model->setData(index, thumbnailSize, ItemModel::ThumbnailRole);
0209     // get data from model
0210     QVariant thumbData              = index.data(ItemModel::ThumbnailRole);
0211     // reset to default thumbnail size
0212     model->setData(index, QVariant(), ItemModel::ThumbnailRole);
0213 
0214     return thumbData.value<QPixmap>();
0215 }
0216 
0217 QPixmap ItemDelegate::thumbnailPixmap(const QModelIndex& index) const
0218 {
0219     Q_D(const ItemDelegate);
0220     return retrieveThumbnailPixmap(index, d->thumbSize.size());
0221 }
0222 
0223 void ItemDelegate::paint(QPainter* p, const QStyleOptionViewItem& option, const QModelIndex& index) const
0224 {
0225     Q_D(const ItemDelegate);
0226     ItemInfo info = ItemModel::retrieveItemInfo(index);
0227 
0228     if (info.isNull())
0229     {
0230         return;
0231     }
0232 
0233     // state of painter must not be changed
0234     p->save();
0235     p->translate(option.rect.topLeft());
0236 
0237     bool isSelected = (option.state & QStyle::State_Selected);
0238 
0239     // Thumbnail
0240     QPixmap pix;
0241 
0242     if (isSelected)
0243     {
0244         pix = d->selPixmap;
0245     }
0246     else
0247     {
0248         pix = d->regPixmap;
0249     }
0250 
0251     bool groupedAndClosed  = (info.hasGroupedImages() &&
0252                               !index.data(ItemFilterModel::GroupIsOpenRole).toBool() &&
0253                               ApplicationSettings::instance()->getDrawFramesToGrouped());
0254 
0255     QRect actualPixmapRect = drawThumbnail(p, d->pixmapRect,
0256                                            pix, thumbnailPixmap(index),
0257                                            groupedAndClosed);
0258 
0259     if (!actualPixmapRect.isNull())
0260     {
0261         const_cast<ItemDelegate*>(this)->updateActualPixmapRect(index, actualPixmapRect);
0262     }
0263 
0264     if (!d->ratingRect.isNull())
0265     {
0266         drawRating(p, index, d->ratingRect, info.rating(), isSelected);
0267     }
0268 
0269     // Draw Color Label rectangle
0270     if (ApplicationSettings::instance()->getIconShowColorLabel())
0271     {
0272         drawColorLabelRect(p, option, isSelected, info.colorLabel());
0273     }
0274 
0275     p->setPen(isSelected ? qApp->palette().color(QPalette::HighlightedText)
0276                          : qApp->palette().color(QPalette::Text));
0277 
0278 /*
0279     // If there is ImageHistory present, paint a small icon over the thumbnail to indicate that this is derived image
0280     if (info.hasImageHistory())
0281     {
0282         p->drawPixmap(d->pixmapRect.right()-24, d->pixmapRect.bottom()-24, QIcon::fromTheme(QLatin1String("svn_switch")).pixmap(22, 22));
0283     }
0284 */
0285 
0286     if (!d->nameRect.isNull())
0287     {
0288         drawName(p, d->nameRect, info.name());
0289     }
0290 
0291     if (!d->titleRect.isNull())
0292     {
0293         drawTitle(p, d->titleRect, info.title());
0294     }
0295 
0296     if (!d->commentsRect.isNull())
0297     {
0298         drawComments(p, d->commentsRect, info.comment());
0299     }
0300 
0301     if (!d->dateRect.isNull() && info.dateTime().isValid())
0302     {
0303         drawCreationDate(p, d->dateRect, info.dateTime());
0304     }
0305 
0306     if (!d->modDateRect.isNull() && info.modDateTime().isValid())
0307     {
0308         drawModificationDate(p, d->modDateRect, info.modDateTime());
0309     }
0310 
0311     if (!d->resolutionRect.isNull())
0312     {
0313         drawImageSize(p, d->resolutionRect, info.dimensions());
0314     }
0315 
0316     if (!d->arRect.isNull())
0317     {
0318         drawAspectRatio(p, d->arRect, info.dimensions());
0319     }
0320 
0321     if (!d->sizeRect.isNull())
0322     {
0323         drawFileSize(p, d->sizeRect, info.fileSize());
0324     }
0325 
0326     if (!d->groupRect.isNull())
0327     {
0328         drawGroupIndicator(p, d->groupRect, info.numberOfGroupedImages(),
0329                            index.data(ItemFilterModel::GroupIsOpenRole).toBool());
0330     }
0331 
0332     if (!d->tagRect.isNull())
0333     {
0334         QStringList tagsList = AlbumManager::instance()->tagNames(info.tagIds());
0335         tagsList.removeDuplicates();
0336         tagsList.sort();
0337         QString tags         = tagsList.join(QLatin1String(", "));
0338         drawTags(p, d->tagRect, tags, isSelected);
0339     }
0340 
0341     if (!d->pickLabelRect.isNull())
0342     {
0343         drawPickLabelIcon(p, d->pickLabelRect, info.pickLabel());
0344     }
0345 
0346     bool left  = index.data(ItemModel::LTLeftPanelRole).toBool();
0347     bool right = index.data(ItemModel::LTRightPanelRole).toBool();
0348     drawPanelSideIcon(p, left, right);
0349 
0350     if (d->drawImageFormat)
0351     {
0352         QString frm  = info.format();
0353 
0354         if (frm.contains(QLatin1Char('-')))
0355             frm = frm.section(QLatin1Char('-'), -1);   // For RAW format annotated as "RAW-xxx" => "xxx"
0356 
0357         drawImageFormat(p, actualPixmapRect, frm, d->drawImageFormatTop);
0358     }
0359 
0360     if (info.id() == info.currentReferenceImage())
0361     {
0362         drawSpecialInfo(p, actualPixmapRect, i18n("Reference Image"));
0363     }
0364 
0365     if (d->drawCoordinates && info.hasCoordinates())
0366     {
0367         drawGeolocationIndicator(p, d->coordinatesRect);
0368     }
0369 
0370     if (d->drawFocusFrame)
0371     {
0372         drawFocusRect(p, option, isSelected);
0373     }
0374 
0375     if (d->drawMouseOverFrame)
0376     {
0377         drawMouseOverRect(p, option);
0378     }
0379 
0380     p->restore();
0381 
0382     drawOverlays(p, option, index);
0383 }
0384 
0385 QPixmap ItemDelegate::pixmapForDrag(const QStyleOptionViewItem& option, const QList<QModelIndex>& indexes) const
0386 {
0387     QPixmap icon;
0388 
0389     if (!indexes.isEmpty())
0390     {
0391         icon = thumbnailPixmap(indexes.first());
0392     }
0393 
0394     return makeDragPixmap(option, indexes, displayRatio(), icon);
0395 }
0396 
0397 bool ItemDelegate::acceptsToolTip(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
0398                                    QRect* toolTipRect) const
0399 {
0400     return onActualPixmapRect(pos, visualRect, index, toolTipRect);
0401 }
0402 
0403 bool ItemDelegate::acceptsActivation(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
0404                                       QRect* activationRect) const
0405 {
0406     return onActualPixmapRect(pos, visualRect, index, activationRect);
0407 }
0408 
0409 bool ItemDelegate::onActualPixmapRect(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
0410                                        QRect* returnRect) const
0411 {
0412     QRect actualRect = actualPixmapRect(index);
0413 
0414     if (actualRect.isNull())
0415     {
0416         return false;
0417     }
0418 
0419     actualRect.translate(visualRect.topLeft());
0420 
0421     if (returnRect)
0422     {
0423         *returnRect = actualRect;
0424     }
0425 
0426     return actualRect.contains(pos);
0427 }
0428 
0429 void ItemDelegate::setDefaultViewOptions(const QStyleOptionViewItem& option)
0430 {
0431     Q_D(ItemDelegate);
0432 
0433     if (d->categoryDrawer)
0434     {
0435         d->categoryDrawer->setDefaultViewOptions(option);
0436     }
0437 
0438     ItemViewDelegate::setDefaultViewOptions(option);
0439 }
0440 
0441 void ItemDelegate::invalidatePaintingCache()
0442 {
0443     Q_D(ItemDelegate);
0444 
0445     if (d->categoryDrawer)
0446     {
0447         d->categoryDrawer->invalidatePaintingCache();
0448     }
0449 
0450     ItemViewDelegate::invalidatePaintingCache();
0451 }
0452 
0453 void ItemDelegate::updateContentWidth()
0454 {
0455     Q_D(ItemDelegate);
0456     d->contentWidth = d->thumbSize.size() + 2*d->radius;
0457 }
0458 
0459 void ItemDelegate::updateSizeRectsAndPixmaps()
0460 {
0461     Q_D(ItemDelegate);
0462 
0463     // ---- Reset rects and prepare fonts ----
0464 
0465     d->clearRects();
0466     prepareFonts();
0467 
0468     // ---- Fixed sizes and metrics ----
0469 
0470     updateContentWidth();
0471     prepareMetrics(d->contentWidth);
0472 
0473     // ---- Calculate rects ----
0474 
0475     updateRects();
0476 
0477     // ---- Cached pixmaps ----
0478 
0479     prepareBackground();
0480 
0481     if (!d->ratingRect.isNull())
0482     {
0483         // Normally we prepare the pixmaps over the background of the rating rect.
0484         // If the rating is drawn over the thumbnail, we can only draw over a transparent pixmap.
0485         prepareRatingPixmaps(!d->ratingOverThumbnail);
0486     }
0487 
0488     // ---- Drawing related caches ----
0489 
0490     clearCaches();
0491 }
0492 
0493 void ItemDelegate::clearCaches()
0494 {
0495     Q_D(ItemDelegate);
0496     ItemViewDelegate::clearCaches();
0497     d->actualPixmapRectCache.clear();
0498 }
0499 
0500 void ItemDelegate::clearModelDataCaches()
0501 {
0502     Q_D(ItemDelegate);
0503     d->actualPixmapRectCache.clear();
0504 }
0505 
0506 void ItemDelegate::modelChanged()
0507 {
0508     Q_D(ItemDelegate);
0509     clearModelDataCaches();
0510     setModel(d->currentView ? d->currentView->model() : nullptr);
0511 }
0512 
0513 void ItemDelegate::modelContentsChanged()
0514 {
0515     clearModelDataCaches();
0516 }
0517 
0518 QRect ItemDelegate::actualPixmapRect(const QModelIndex& index) const
0519 {
0520     Q_D(const ItemDelegate);
0521     // We do not recompute if not found. Assumption is cache is always properly updated.
0522     QRect* rect = d->actualPixmapRectCache.object(index.row());
0523 
0524     if (rect)
0525     {
0526         return *rect;
0527     }
0528     else
0529     {
0530         return d->pixmapRect;
0531     }
0532 }
0533 
0534 void ItemDelegate::updateActualPixmapRect(const QModelIndex& index, const QRect& rect)
0535 {
0536     Q_D(ItemDelegate);
0537     QRect* const old = d->actualPixmapRectCache.object(index.row());
0538 
0539     if (!old || *old != rect)
0540     {
0541         d->actualPixmapRectCache.insert(index.row(), new QRect(rect));
0542     }
0543 }
0544 
0545 int ItemDelegate::calculatethumbSizeToFit(int ws)
0546 {
0547     Q_D(ItemDelegate);
0548 
0549     int ts     = thumbnailSize().size();
0550     int gs     = gridSize().width();
0551     int sp     = spacing();
0552     ws         = ws - 2*sp;
0553 
0554     // Thumbnails size loop to check (upper/lower)
0555     int ts1, ts2;
0556     // New grid size used in loop
0557     int ngs;
0558 
0559     double rs1 = fmod((double)ws, (double)gs);
0560 
0561     for (ts1 = ts ; ts1 < ThumbnailSize::maxThumbsSize() ; ++ts1)
0562     {
0563         ngs        = ts1 + 2*(d->margin + d->radius) + sp;
0564         double nrs = fmod((double)ws, (double)ngs);
0565 
0566         if (nrs <= rs1)
0567         {
0568             rs1 = nrs;
0569         }
0570         else
0571         {
0572             break;
0573         }
0574     }
0575 
0576     double rs2 = fmod((double)ws, (double)gs);
0577 
0578     for (ts2 = ts ; ts2 > ThumbnailSize::Small ; --ts2)
0579     {
0580         ngs        = ts2 + 2*(d->margin + d->radius) + sp;
0581         double nrs = fmod((double)ws, (double)ngs);
0582 
0583         if (nrs >= rs2)
0584         {
0585             rs2 = nrs;
0586         }
0587         else
0588         {
0589             rs2 = nrs;
0590             break;
0591         }
0592     }
0593 
0594     if (rs1 > rs2)
0595     {
0596         return (ts2);
0597     }
0598 
0599     return (ts1);
0600 }
0601 
0602 } // namespace Digikam
0603 
0604 #include "moc_itemdelegate.cpp"