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"