1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2009-04-19
7 * Description : Qt model-view for items - the delegate
8 *
9 * Copyright (C) 2002-2005 by Renchi Raju <renchi dot raju at gmail dot com>
10 * Copyright (C) 2009-2011 by Andi Clemens <andi dot clemens at gmail dot com>
11 * Copyright (C) 2002-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
12 * Copyright (C) 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
13 *
14 * This program is free software; you can redistribute it
15 * and/or modify it under the terms of the GNU General
16 * Public License as published by the Free Software Foundation;
17 * either version 2, or (at your option)
18 * any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * ============================================================ */
26
27 #include "itemdelegate.h"
28 #include "itemdelegate_p.h"
29
30 // C++ includes
31
32 #include <cmath>
33
34 // Qt includes
35
36 #include <QCache>
37 #include <QPainter>
38 #include <QIcon>
39 #include <QApplication>
40
41 // KDE includes
42
43 #include <klocalizedstring.h>
44
45 // Local includes
46
47 #include "digikam_debug.h"
48 #include "albummanager.h"
49 #include "itemcategorydrawer.h"
50 #include "itemcategorizedview.h"
51 #include "itemdelegateoverlay.h"
52 #include "itemmodel.h"
53 #include "itemfiltermodel.h"
54 #include "itemthumbnailmodel.h"
55 #include "thumbnailloadthread.h"
56 #include "applicationsettings.h"
57
58 namespace Digikam
59 {
60
clearRects()61 void ItemDelegate::ItemDelegatePrivate::clearRects()
62 {
63 ItemViewDelegatePrivate::clearRects();
64 dateRect = QRect(0, 0, 0, 0);
65 modDateRect = QRect(0, 0, 0, 0);
66 pixmapRect = QRect(0, 0, 0, 0);
67 nameRect = QRect(0, 0, 0, 0);
68 titleRect = QRect(0, 0, 0, 0);
69 commentsRect = QRect(0, 0, 0, 0);
70 resolutionRect = QRect(0, 0, 0, 0);
71 coordinatesRect = QRect(0, 0, 0, 0);
72 arRect = QRect(0, 0, 0, 0);
73 sizeRect = QRect(0, 0, 0, 0);
74 tagRect = QRect(0, 0, 0, 0);
75 imageInformationRect = QRect(0, 0, 0, 0);
76 pickLabelRect = QRect(0, 0, 0, 0);
77 groupRect = QRect(0, 0, 0, 0);
78 }
79
ItemDelegate(QObject * const parent)80 ItemDelegate::ItemDelegate(QObject* const parent)
81 : ItemViewDelegate(*new ItemDelegatePrivate, parent)
82 {
83 }
84
ItemDelegate(ItemDelegate::ItemDelegatePrivate & dd,QObject * const parent)85 ItemDelegate::ItemDelegate(ItemDelegate::ItemDelegatePrivate& dd, QObject* const parent)
86 : ItemViewDelegate(dd, parent)
87 {
88 }
89
~ItemDelegate()90 ItemDelegate::~ItemDelegate()
91 {
92 Q_D(ItemDelegate);
93 // crashes for a lot of people, see bug 230515. Cause unknown.
94 //delete d->categoryDrawer;
95 Q_UNUSED(d); // To please compiler about warnings.
96 }
97
setView(ItemCategorizedView * view)98 void ItemDelegate::setView(ItemCategorizedView* view)
99 {
100 Q_D(ItemDelegate);
101 setViewOnAllOverlays(view);
102
103 if (d->currentView)
104 {
105 disconnect(d->currentView, SIGNAL(modelChanged()),
106 this, SLOT(modelChanged()));
107 }
108
109 d->currentView = view;
110
111 setModel(view ? view->model() : nullptr);
112
113 if (d->currentView)
114 {
115 connect(d->currentView, SIGNAL(modelChanged()),
116 this, SLOT(modelChanged()));
117 }
118 }
119
setModel(QAbstractItemModel * model)120 void ItemDelegate::setModel(QAbstractItemModel* model)
121 {
122 Q_D(ItemDelegate);
123
124 // 1) We only need the model to invalidate model-index based caches on change
125 // 2) We do not need to care for overlays. The view calls setActive() on them on model change
126
127 if (model == d->currentModel)
128 {
129 return;
130 }
131
132 if (d->currentModel)
133 {
134 disconnect(d->currentModel, nullptr, this, nullptr);
135 }
136
137 d->currentModel = model;
138
139 if (d->currentModel)
140 {
141 connect(d->currentModel, SIGNAL(layoutAboutToBeChanged()),
142 this, SLOT(modelContentsChanged()));
143
144 connect(d->currentModel, SIGNAL(modelAboutToBeReset()),
145 this, SLOT(modelContentsChanged()));
146
147 connect(d->currentModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
148 this, SLOT(modelContentsChanged()));
149
150 connect(d->currentModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
151 this, SLOT(modelContentsChanged()));
152
153 connect(d->currentModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
154 this, SLOT(modelContentsChanged()));
155 }
156 }
157
setSpacing(int spacing)158 void ItemDelegate::setSpacing(int spacing)
159 {
160 Q_D(ItemDelegate);
161
162 if (d->categoryDrawer)
163 {
164 d->categoryDrawer->setLowerSpacing(spacing);
165 }
166
167 ItemViewDelegate::setSpacing(spacing);
168 }
169
categoryDrawer() const170 ItemCategoryDrawer* ItemDelegate::categoryDrawer() const
171 {
172 Q_D(const ItemDelegate);
173 return d->categoryDrawer;
174 }
175
commentsRect() const176 QRect ItemDelegate::commentsRect() const
177 {
178 Q_D(const ItemDelegate);
179 return d->commentsRect;
180 }
181
tagsRect() const182 QRect ItemDelegate::tagsRect() const
183 {
184 Q_D(const ItemDelegate);
185 return d->tagRect;
186 }
187
pixmapRect() const188 QRect ItemDelegate::pixmapRect() const
189 {
190 Q_D(const ItemDelegate);
191 return d->pixmapRect;
192 }
193
imageInformationRect() const194 QRect ItemDelegate::imageInformationRect() const
195 {
196 Q_D(const ItemDelegate);
197 return d->imageInformationRect;
198 }
199
groupIndicatorRect() const200 QRect ItemDelegate::groupIndicatorRect() const
201 {
202 Q_D(const ItemDelegate);
203 return d->groupRect;
204 }
205
coordinatesIndicatorRect() const206 QRect ItemDelegate::coordinatesIndicatorRect() const
207 {
208 Q_D(const ItemDelegate);
209 return d->coordinatesRect;
210 }
211
retrieveThumbnailPixmap(const QModelIndex & index,int thumbnailSize)212 QPixmap ItemDelegate::retrieveThumbnailPixmap(const QModelIndex& index, int thumbnailSize)
213 {
214 // work around constness
215 QAbstractItemModel* const model = const_cast<QAbstractItemModel*>(index.model());
216 // set requested thumbnail size
217 model->setData(index, thumbnailSize, ItemModel::ThumbnailRole);
218 // get data from model
219 QVariant thumbData = index.data(ItemModel::ThumbnailRole);
220 // reset to default thumbnail size
221 model->setData(index, QVariant(), ItemModel::ThumbnailRole);
222
223 return thumbData.value<QPixmap>();
224 }
225
thumbnailPixmap(const QModelIndex & index) const226 QPixmap ItemDelegate::thumbnailPixmap(const QModelIndex& index) const
227 {
228 Q_D(const ItemDelegate);
229 return retrieveThumbnailPixmap(index, d->thumbSize.size());
230 }
231
paint(QPainter * p,const QStyleOptionViewItem & option,const QModelIndex & index) const232 void ItemDelegate::paint(QPainter* p, const QStyleOptionViewItem& option, const QModelIndex& index) const
233 {
234 Q_D(const ItemDelegate);
235 ItemInfo info = ItemModel::retrieveItemInfo(index);
236
237 if (info.isNull())
238 {
239 return;
240 }
241
242 // state of painter must not be changed
243 p->save();
244 p->translate(option.rect.topLeft());
245
246 bool isSelected = (option.state & QStyle::State_Selected);
247
248 // Thumbnail
249 QPixmap pix;
250
251 if (isSelected)
252 {
253 pix = d->selPixmap;
254 }
255 else
256 {
257 pix = d->regPixmap;
258 }
259
260 bool groupedAndClosed = (info.hasGroupedImages() &&
261 !index.data(ItemFilterModel::GroupIsOpenRole).toBool() &&
262 ApplicationSettings::instance()->getDrawFramesToGrouped());
263
264 QRect actualPixmapRect = drawThumbnail(p, d->pixmapRect,
265 pix, thumbnailPixmap(index),
266 groupedAndClosed);
267
268 if (!actualPixmapRect.isNull())
269 {
270 const_cast<ItemDelegate*>(this)->updateActualPixmapRect(index, actualPixmapRect);
271 }
272
273 if (!d->ratingRect.isNull())
274 {
275 drawRating(p, index, d->ratingRect, info.rating(), isSelected);
276 }
277
278 // Draw Color Label rectangle
279 drawColorLabelRect(p, option, isSelected, info.colorLabel());
280
281 p->setPen(isSelected ? qApp->palette().color(QPalette::HighlightedText)
282 : qApp->palette().color(QPalette::Text));
283
284 /*
285 // If there is ImageHistory present, paint a small icon over the thumbnail to indicate that this is derived image
286 if (info.hasImageHistory())
287 {
288 p->drawPixmap(d->pixmapRect.right()-24, d->pixmapRect.bottom()-24, QIcon::fromTheme(QLatin1String("svn_switch")).pixmap(22, 22));
289 }
290 */
291
292 if (!d->nameRect.isNull())
293 {
294 drawName(p, d->nameRect, info.name());
295 }
296
297 if (!d->titleRect.isNull())
298 {
299 drawTitle(p, d->titleRect, info.title());
300 }
301
302 if (!d->commentsRect.isNull())
303 {
304 drawComments(p, d->commentsRect, info.comment());
305 }
306
307 if (!d->dateRect.isNull() && info.dateTime().isValid())
308 {
309 drawCreationDate(p, d->dateRect, info.dateTime());
310 }
311
312 if (!d->modDateRect.isNull() && info.modDateTime().isValid())
313 {
314 drawModificationDate(p, d->modDateRect, info.modDateTime());
315 }
316
317 if (!d->resolutionRect.isNull())
318 {
319 drawImageSize(p, d->resolutionRect, info.dimensions());
320 }
321
322 if (!d->arRect.isNull())
323 {
324 drawAspectRatio(p, d->arRect, info.dimensions());
325 }
326
327 if (!d->sizeRect.isNull())
328 {
329 drawFileSize(p, d->sizeRect, info.fileSize());
330 }
331
332 if (!d->groupRect.isNull())
333 {
334 drawGroupIndicator(p, d->groupRect, info.numberOfGroupedImages(),
335 index.data(ItemFilterModel::GroupIsOpenRole).toBool());
336 }
337
338 if (!d->tagRect.isNull())
339 {
340 QStringList tagsList = AlbumManager::instance()->tagNames(info.tagIds());
341 tagsList.sort();
342 QString tags = tagsList.join(QLatin1String(", "));
343 drawTags(p, d->tagRect, tags, isSelected);
344 }
345
346 if (!d->pickLabelRect.isNull())
347 {
348 drawPickLabelIcon(p, d->pickLabelRect, info.pickLabel());
349 }
350
351 bool left = index.data(ItemModel::LTLeftPanelRole).toBool();
352 bool right = index.data(ItemModel::LTRightPanelRole).toBool();
353 drawPanelSideIcon(p, left, right);
354
355 if (d->drawImageFormat)
356 {
357 QString frm = info.format();
358
359 if (frm.contains(QLatin1Char('-')))
360 frm = frm.section(QLatin1Char('-'), -1); // For RAW format annotated as "RAW-xxx" => "xxx"
361
362 drawImageFormat(p, actualPixmapRect, frm, d->drawImageFormatTop);
363 }
364
365 if (info.id() == info.currentReferenceImage())
366 {
367 drawSpecialInfo(p, actualPixmapRect, i18n("Reference Image"));
368 }
369
370 if (d->drawCoordinates && info.hasCoordinates())
371 {
372 drawGeolocationIndicator(p, d->coordinatesRect);
373 }
374
375 if (d->drawFocusFrame)
376 {
377 drawFocusRect(p, option, isSelected);
378 }
379
380 if (d->drawMouseOverFrame)
381 {
382 drawMouseOverRect(p, option);
383 }
384
385 p->restore();
386
387 drawOverlays(p, option, index);
388 }
389
pixmapForDrag(const QStyleOptionViewItem & option,const QList<QModelIndex> & indexes) const390 QPixmap ItemDelegate::pixmapForDrag(const QStyleOptionViewItem& option, const QList<QModelIndex>& indexes) const
391 {
392 QPixmap icon;
393
394 if (!indexes.isEmpty())
395 {
396 icon = thumbnailPixmap(indexes.first());
397 }
398
399 return makeDragPixmap(option, indexes, icon);
400 }
401
acceptsToolTip(const QPoint & pos,const QRect & visualRect,const QModelIndex & index,QRect * toolTipRect) const402 bool ItemDelegate::acceptsToolTip(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
403 QRect* toolTipRect) const
404 {
405 return onActualPixmapRect(pos, visualRect, index, toolTipRect);
406 }
407
acceptsActivation(const QPoint & pos,const QRect & visualRect,const QModelIndex & index,QRect * activationRect) const408 bool ItemDelegate::acceptsActivation(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
409 QRect* activationRect) const
410 {
411 return onActualPixmapRect(pos, visualRect, index, activationRect);
412 }
413
onActualPixmapRect(const QPoint & pos,const QRect & visualRect,const QModelIndex & index,QRect * returnRect) const414 bool ItemDelegate::onActualPixmapRect(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
415 QRect* returnRect) const
416 {
417 QRect actualRect = actualPixmapRect(index);
418
419 if (actualRect.isNull())
420 {
421 return false;
422 }
423
424 actualRect.translate(visualRect.topLeft());
425
426 if (returnRect)
427 {
428 *returnRect = actualRect;
429 }
430
431 return actualRect.contains(pos);
432 }
433
setDefaultViewOptions(const QStyleOptionViewItem & option)434 void ItemDelegate::setDefaultViewOptions(const QStyleOptionViewItem& option)
435 {
436 Q_D(ItemDelegate);
437
438 if (d->categoryDrawer)
439 {
440 d->categoryDrawer->setDefaultViewOptions(option);
441 }
442
443 ItemViewDelegate::setDefaultViewOptions(option);
444 }
445
invalidatePaintingCache()446 void ItemDelegate::invalidatePaintingCache()
447 {
448 Q_D(ItemDelegate);
449
450 if (d->categoryDrawer)
451 {
452 d->categoryDrawer->invalidatePaintingCache();
453 }
454
455 ItemViewDelegate::invalidatePaintingCache();
456 }
457
updateContentWidth()458 void ItemDelegate::updateContentWidth()
459 {
460 Q_D(ItemDelegate);
461 d->contentWidth = d->thumbSize.size() + 2*d->radius;
462 }
463
updateSizeRectsAndPixmaps()464 void ItemDelegate::updateSizeRectsAndPixmaps()
465 {
466 Q_D(ItemDelegate);
467
468 // ---- Reset rects and prepare fonts ----
469
470 d->clearRects();
471 prepareFonts();
472
473 // ---- Fixed sizes and metrics ----
474
475 updateContentWidth();
476 prepareMetrics(d->contentWidth);
477
478 // ---- Calculate rects ----
479
480 updateRects();
481
482 // ---- Cached pixmaps ----
483
484 prepareBackground();
485
486 if (!d->ratingRect.isNull())
487 {
488 // Normally we prepare the pixmaps over the background of the rating rect.
489 // If the rating is drawn over the thumbnail, we can only draw over a transparent pixmap.
490 prepareRatingPixmaps(!d->ratingOverThumbnail);
491 }
492
493 // ---- Drawing related caches ----
494
495 clearCaches();
496 }
497
clearCaches()498 void ItemDelegate::clearCaches()
499 {
500 Q_D(ItemDelegate);
501 ItemViewDelegate::clearCaches();
502 d->actualPixmapRectCache.clear();
503 }
504
clearModelDataCaches()505 void ItemDelegate::clearModelDataCaches()
506 {
507 Q_D(ItemDelegate);
508 d->actualPixmapRectCache.clear();
509 }
510
modelChanged()511 void ItemDelegate::modelChanged()
512 {
513 Q_D(ItemDelegate);
514 clearModelDataCaches();
515 setModel(d->currentView ? d->currentView->model() : nullptr);
516 }
517
modelContentsChanged()518 void ItemDelegate::modelContentsChanged()
519 {
520 clearModelDataCaches();
521 }
522
actualPixmapRect(const QModelIndex & index) const523 QRect ItemDelegate::actualPixmapRect(const QModelIndex& index) const
524 {
525 Q_D(const ItemDelegate);
526 // We do not recompute if not found. Assumption is cache is always properly updated.
527 QRect* rect = d->actualPixmapRectCache.object(index.row());
528
529 if (rect)
530 {
531 return *rect;
532 }
533 else
534 {
535 return d->pixmapRect;
536 }
537 }
538
updateActualPixmapRect(const QModelIndex & index,const QRect & rect)539 void ItemDelegate::updateActualPixmapRect(const QModelIndex& index, const QRect& rect)
540 {
541 Q_D(ItemDelegate);
542 QRect* const old = d->actualPixmapRectCache.object(index.row());
543
544 if (!old || *old != rect)
545 {
546 d->actualPixmapRectCache.insert(index.row(), new QRect(rect));
547 }
548 }
549
calculatethumbSizeToFit(int ws)550 int ItemDelegate::calculatethumbSizeToFit(int ws)
551 {
552 Q_D(ItemDelegate);
553
554 int ts = thumbnailSize().size();
555 int gs = gridSize().width();
556 int sp = spacing();
557 ws = ws - 2*sp;
558
559 // Thumbnails size loop to check (upper/lower)
560 int ts1, ts2;
561 // New grid size used in loop
562 int ngs;
563
564 double rs1 = fmod((double)ws, (double)gs);
565
566 for (ts1 = ts ; ts1 < ThumbnailSize::maxThumbsSize() ; ++ts1)
567 {
568 ngs = ts1 + 2*(d->margin + d->radius) + sp;
569 double nrs = fmod((double)ws, (double)ngs);
570
571 if (nrs <= rs1)
572 {
573 rs1 = nrs;
574 }
575 else
576 {
577 break;
578 }
579 }
580
581 double rs2 = fmod((double)ws, (double)gs);
582
583 for (ts2 = ts ; ts2 > ThumbnailSize::Small ; --ts2)
584 {
585 ngs = ts2 + 2*(d->margin + d->radius) + sp;
586 double nrs = fmod((double)ws, (double)ngs);
587
588 if (nrs >= rs2)
589 {
590 rs2 = nrs;
591 }
592 else
593 {
594 rs2 = nrs;
595 break;
596 }
597 }
598
599 if (rs1 > rs2)
600 {
601 return (ts2);
602 }
603
604 return (ts1);
605 }
606
607 } // namespace Digikam
608