1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2013-08-01
7  * Description : Qt model view for Showfoto item - the delegate
8  *
9  * Copyright (C) 2013      by Mohamed_Anwer <m_dot_anwer at gmx dot com>
10  * Copyright (C) 2013-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  *
12  * This program is free software; you can redistribute it
13  * and/or modify it under the terms of the GNU General
14  * Public License as published by the Free Software Foundation;
15  * either version 2, or (at your option)
16  * any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * ============================================================ */
24 
25 #include "showfotodelegate_p.h"
26 
27 namespace ShowFoto
28 {
29 
clearRects()30 void ShowfotoDelegate::ShowfotoDelegatePrivate::clearRects()
31 {
32     ShowfotoItemViewDelegatePrivate::clearRects();
33     dateRect             = QRect(0, 0, 0, 0);
34     pixmapRect           = QRect(0, 0, 0, 0);
35     nameRect             = QRect(0, 0, 0, 0);
36     resolutionRect       = QRect(0, 0, 0, 0);
37     sizeRect             = QRect(0, 0, 0, 0);
38     imageInformationRect = QRect(0, 0, 0, 0);
39     coordinatesRect      = QRect(0, 0, 0, 0);
40 }
41 
ShowfotoDelegate(QObject * const parent)42 ShowfotoDelegate::ShowfotoDelegate(QObject* const parent)
43     : ShowfotoItemViewDelegate(*new ShowfotoDelegatePrivate, parent)
44 {
45 }
46 
ShowfotoDelegate(ShowfotoDelegate::ShowfotoDelegatePrivate & dd,QObject * const parent)47 ShowfotoDelegate::ShowfotoDelegate(ShowfotoDelegate::ShowfotoDelegatePrivate& dd, QObject* const parent)
48     : ShowfotoItemViewDelegate(dd, parent)
49 {
50 }
51 
~ShowfotoDelegate()52 ShowfotoDelegate::~ShowfotoDelegate()
53 {
54     Q_D(ShowfotoDelegate);
55     Q_UNUSED(d); // To please compiler about warnings.
56 }
57 
setView(ShowfotoThumbnailBar * view)58 void ShowfotoDelegate::setView(ShowfotoThumbnailBar* view)
59 {
60     Q_D(ShowfotoDelegate);
61     setViewOnAllOverlays(view);
62 
63     if (d->currentView)
64     {
65         disconnect(d->currentView, SIGNAL(modelChanged()),
66                    this, SLOT(modelChanged()));
67     }
68 
69     d->currentView = view;
70 
71     setModel(view ? view->model() : nullptr);
72 
73     if (d->currentView)
74     {
75         connect(d->currentView, SIGNAL(modelChanged()),
76                 this, SLOT(modelChanged()));
77     }
78 }
79 
setModel(QAbstractItemModel * model)80 void ShowfotoDelegate::setModel(QAbstractItemModel* model)
81 {
82     Q_D(ShowfotoDelegate);
83 
84     // 1) We only need the model to invalidate model-index based caches on change
85     // 2) We do not need to care for overlays. The view calls setActive() on them on model change
86 
87     if (model == d->currentModel)
88     {
89         return;
90     }
91 
92     if (d->currentModel)
93     {
94         disconnect(d->currentModel, nullptr, this, nullptr);
95     }
96 
97     d->currentModel = model;
98 
99     if (d->currentModel)
100     {
101         connect(d->currentModel, SIGNAL(layoutAboutToBeChanged()),
102                 this, SLOT(modelContentsChanged()));
103 
104         connect(d->currentModel, SIGNAL(modelAboutToBeReset()),
105                 this, SLOT(modelContentsChanged()));
106 
107         connect(d->currentModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
108                 this, SLOT(modelContentsChanged()));
109 
110         connect(d->currentModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
111                 this, SLOT(modelContentsChanged()));
112 
113         connect(d->currentModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
114                 this, SLOT(modelContentsChanged()));
115     }
116 }
117 
setSpacing(int spacing)118 void ShowfotoDelegate::setSpacing(int spacing)
119 {
120     ShowfotoItemViewDelegate::setSpacing(spacing);
121 }
122 
pixmapRect() const123 QRect ShowfotoDelegate::pixmapRect() const
124 {
125     Q_D(const ShowfotoDelegate);
126 
127     return d->pixmapRect;
128 }
129 
imageInformationRect() const130 QRect ShowfotoDelegate::imageInformationRect() const
131 {
132     Q_D(const ShowfotoDelegate);
133 
134     return d->imageInformationRect;
135 }
136 
groupIndicatorRect() const137 QRect ShowfotoDelegate::groupIndicatorRect() const
138 {
139     Q_D(const ShowfotoDelegate);
140 
141     return d->groupRect;
142 }
143 
coordinatesIndicatorRect() const144 QRect ShowfotoDelegate::coordinatesIndicatorRect() const
145 {
146     Q_D(const ShowfotoDelegate);
147 
148     return d->coordinatesRect;
149 }
150 
retrieveThumbnailPixmap(const QModelIndex & index,int thumbnailSize)151 QPixmap ShowfotoDelegate::retrieveThumbnailPixmap(const QModelIndex& index, int thumbnailSize)
152 {
153     // work around constness
154 
155     QAbstractItemModel* const model = const_cast<QAbstractItemModel*>(index.model());
156 
157     // set requested thumbnail size
158 
159     model->setData(index, thumbnailSize, ShowfotoItemModel::ThumbnailRole);
160 
161     // get data from model
162 
163     QVariant thumbData              = index.data(ShowfotoItemModel::ThumbnailRole);
164 
165     return thumbData.value<QPixmap>();
166 }
167 
thumbnailPixmap(const QModelIndex & index) const168 QPixmap ShowfotoDelegate::thumbnailPixmap(const QModelIndex& index) const
169 {
170     Q_D(const ShowfotoDelegate);
171 
172     return retrieveThumbnailPixmap(index, d->thumbSize.size());
173 }
174 
paint(QPainter * p,const QStyleOptionViewItem & option,const QModelIndex & index) const175 void ShowfotoDelegate::paint(QPainter* p, const QStyleOptionViewItem& option, const QModelIndex& index) const
176 {
177     Q_D(const ShowfotoDelegate);
178     ShowfotoItemInfo info     = ShowfotoItemModel::retrieveShowfotoItemInfo(index);
179     KSharedConfig::Ptr config = KSharedConfig::openConfig();
180     KConfigGroup group        = config->group("ImageViewer Settings");
181 
182     if (info.isNull())
183     {
184         return;
185     }
186 
187     // state of painter must not be changed
188 
189     p->save();
190     p->translate(option.rect.topLeft());
191 
192     bool isSelected = (option.state & QStyle::State_Selected);
193 
194     // Thumbnail
195 
196     QPixmap pix;
197 
198     if (isSelected)
199     {
200         pix = d->selPixmap;
201     }
202     else
203     {
204         pix = d->regPixmap;
205     }
206 
207     QRect actualPixmapRect = drawThumbnail(p, d->pixmapRect, pix, thumbnailPixmap(index));
208 
209     if (!actualPixmapRect.isNull())
210     {
211         const_cast<ShowfotoDelegate*>(this)->updateActualPixmapRect(index, actualPixmapRect);
212     }
213 
214     p->setPen(isSelected ? qApp->palette().color(QPalette::HighlightedText)
215                          : qApp->palette().color(QPalette::Text));
216 
217     if (!d->nameRect.isNull())
218     {
219         drawName(p, d->nameRect, info.name);
220     }
221 
222     if (!d->dateRect.isNull())
223     {
224         drawCreationDate(p, d->dateRect, (info.ctime.isValid() ? info.ctime : info.dtime));
225     }
226 
227     if (!d->sizeRect.isNull())
228     {
229         drawFileSize(p, d->sizeRect, info.size);
230     }
231 
232     if (ShowfotoSettings::instance()->getShowFormatOverThumbnail())
233     {
234         QString frm = info.mime;
235         drawImageFormat(p, actualPixmapRect, frm);
236     }
237 
238     if (ShowfotoSettings::instance()->getShowCoordinates() && info.photoInfo.hasCoordinates)
239     {
240         drawGeolocationIndicator(p, d->coordinatesRect);
241     }
242 
243     if (d->drawFocusFrame)
244     {
245         drawFocusRect(p, option, isSelected);
246     }
247 
248     if (d->drawMouseOverFrame)
249     {
250         drawMouseOverRect(p, option);
251     }
252 
253     p->restore();
254 
255     drawOverlays(p, option, index);
256 }
257 
pixmapForDrag(const QStyleOptionViewItem & option,const QList<QModelIndex> & indexes) const258 QPixmap ShowfotoDelegate::pixmapForDrag(const QStyleOptionViewItem& option,
259                                         const QList<QModelIndex>& indexes) const
260 {
261     QPixmap icon;
262 
263     if (!indexes.isEmpty())
264     {
265         icon = thumbnailPixmap(indexes.first());
266     }
267 
268     return makeDragPixmap(option, indexes, icon);
269 }
270 
acceptsToolTip(const QPoint & pos,const QRect & visualRect,const QModelIndex & index,QRect * toolTipRect) const271 bool ShowfotoDelegate::acceptsToolTip(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
272                                       QRect* toolTipRect) const
273 {
274     return onActualPixmapRect(pos, visualRect, index, toolTipRect);
275 }
276 
acceptsActivation(const QPoint & pos,const QRect & visualRect,const QModelIndex & index,QRect * activationRect) const277 bool ShowfotoDelegate::acceptsActivation(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
278                                          QRect* activationRect) const
279 {
280     return onActualPixmapRect(pos, visualRect, index, activationRect);
281 }
282 
onActualPixmapRect(const QPoint & pos,const QRect & visualRect,const QModelIndex & index,QRect * returnRect) const283 bool ShowfotoDelegate::onActualPixmapRect(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
284                                           QRect* returnRect) const
285 {
286     QRect actualRect = actualPixmapRect(index);
287 
288     if (actualRect.isNull())
289     {
290         return false;
291     }
292 
293     actualRect.translate(visualRect.topLeft());
294 
295     if (returnRect)
296     {
297         *returnRect = actualRect;
298     }
299 
300     return actualRect.contains(pos);
301 }
302 
setDefaultViewOptions(const QStyleOptionViewItem & option)303 void ShowfotoDelegate::setDefaultViewOptions(const QStyleOptionViewItem& option)
304 {
305 
306     ShowfotoItemViewDelegate::setDefaultViewOptions(option);
307 }
308 
invalidatePaintingCache()309 void ShowfotoDelegate::invalidatePaintingCache()
310 {
311     ShowfotoItemViewDelegate::invalidatePaintingCache();
312 }
313 
updateContentWidth()314 void ShowfotoDelegate::updateContentWidth()
315 {
316     Q_D(ShowfotoDelegate);
317     d->contentWidth = d->thumbSize.size() + 2*d->radius;
318 }
319 
updateSizeRectsAndPixmaps()320 void ShowfotoDelegate::updateSizeRectsAndPixmaps()
321 {
322     Q_D(ShowfotoDelegate);
323 
324     // ---- Reset rects and prepare fonts ----
325 
326     d->clearRects();
327     prepareFonts();
328 
329     // ---- Fixed sizes and metrics ----
330 
331     updateContentWidth();
332     prepareMetrics(d->contentWidth);
333 
334     // ---- Calculate rects ----
335 
336     updateRects();
337 
338     // ---- Cached pixmaps ----
339 
340     prepareBackground();
341 
342     // ---- Drawing related caches ----
343 
344     clearCaches();
345 }
346 
clearCaches()347 void ShowfotoDelegate::clearCaches()
348 {
349     Q_D(ShowfotoDelegate);
350     ShowfotoItemViewDelegate::clearCaches();
351     d->actualPixmapRectCache.clear();
352 }
353 
clearModelDataCaches()354 void ShowfotoDelegate::clearModelDataCaches()
355 {
356     Q_D(ShowfotoDelegate);
357     d->actualPixmapRectCache.clear();
358 }
359 
modelChanged()360 void ShowfotoDelegate::modelChanged()
361 {
362     Q_D(ShowfotoDelegate);
363     clearModelDataCaches();
364     setModel(d->currentView ? d->currentView->model() : nullptr);
365 }
366 
modelContentsChanged()367 void ShowfotoDelegate::modelContentsChanged()
368 {
369     clearModelDataCaches();
370 }
371 
actualPixmapRect(const QModelIndex & index) const372 QRect ShowfotoDelegate::actualPixmapRect(const QModelIndex& index) const
373 {
374     Q_D(const ShowfotoDelegate);
375 
376     // We do not recompute if not found. Assumption is cache is always properly updated.
377 
378     QRect* const rect = d->actualPixmapRectCache.object(index.row());
379 
380     if (rect)
381     {
382         return *rect;
383     }
384     else
385     {
386         return d->pixmapRect;
387     }
388 }
389 
updateActualPixmapRect(const QModelIndex & index,const QRect & rect)390 void ShowfotoDelegate::updateActualPixmapRect(const QModelIndex& index, const QRect& rect)
391 {
392     Q_D(ShowfotoDelegate);
393     QRect* const old = d->actualPixmapRectCache.object(index.row());
394 
395     if (!old || (*old != rect))
396     {
397         d->actualPixmapRectCache.insert(index.row(), new QRect(rect));
398     }
399 }
400 
calculatethumbSizeToFit(int ws)401 int ShowfotoDelegate::calculatethumbSizeToFit(int ws)
402 {
403     Q_D(ShowfotoDelegate);
404 
405     int ts     = thumbnailSize().size();
406     int gs     = gridSize().width();
407     int sp     = spacing();
408     ws         = ws - 2*sp;
409 
410     // Thumbnails size loop to check (upper/lower)
411 
412     int ts1, ts2;
413 
414     // New grid size used in loop
415 
416     int ngs;
417 
418     double rs1 = fmod((double)ws, (double)gs);
419 
420     for (ts1 = ts ; ts1 < ThumbnailSize::Huge ; ++ts1)
421     {
422         ngs        = ts1 + 2*(d->margin + d->radius) + sp;
423         double nrs = fmod((double)ws, (double)ngs);
424 
425         if (nrs <= rs1)
426         {
427             rs1 = nrs;
428         }
429         else
430         {
431             break;
432         }
433     }
434 
435     double rs2 = fmod((double)ws, (double)gs);
436 
437     for (ts2 = ts ; ts2 > ThumbnailSize::Small ; --ts2)
438     {
439         ngs        = ts2 + 2*(d->margin + d->radius) + sp;
440         double nrs = fmod((double)ws, (double)ngs);
441 
442         if (nrs >= rs2)
443         {
444             rs2 = nrs;
445         }
446         else
447         {
448             rs2 = nrs;
449             break;
450         }
451     }
452 
453     if (rs1 > rs2)
454     {
455         return (ts2);
456     }
457 
458     return (ts1);
459 }
460 
461 // --- ShowfotoThumbnailDelegate ---------------------------------------
462 
init(ShowfotoThumbnailDelegate * const q)463 void ShowfotoThumbnailDelegatePrivate::init(ShowfotoThumbnailDelegate* const q)
464 {
465     Q_UNUSED(q);
466 }
467 
468 // ------------------------------------------------------------------------------------------------
469 
ShowfotoThumbnailDelegate(ShowfotoThumbnailBar * const parent)470 ShowfotoThumbnailDelegate::ShowfotoThumbnailDelegate(ShowfotoThumbnailBar* const parent)
471     : ShowfotoDelegate(*new ShowfotoThumbnailDelegatePrivate, parent)
472 {
473     Q_D(ShowfotoThumbnailDelegate);
474     d->init(this);
475 }
476 
~ShowfotoThumbnailDelegate()477 ShowfotoThumbnailDelegate::~ShowfotoThumbnailDelegate()
478 {
479 }
480 
setFlow(QListView::Flow flow)481 void ShowfotoThumbnailDelegate::setFlow(QListView::Flow flow)
482 {
483     Q_D(ShowfotoThumbnailDelegate);
484     d->flow = flow;
485 }
486 
setDefaultViewOptions(const QStyleOptionViewItem & option)487 void ShowfotoThumbnailDelegate::setDefaultViewOptions(const QStyleOptionViewItem& option)
488 {
489     Q_D(ShowfotoThumbnailDelegate);
490 
491     // store before calling parent class
492 
493     d->viewSize = option.rect;
494     ShowfotoDelegate::setDefaultViewOptions(option);
495 }
496 
maximumSize() const497 int ShowfotoThumbnailDelegate::maximumSize() const
498 {
499     Q_D(const ShowfotoThumbnailDelegate);
500 
501     return (ThumbnailSize::Huge + (2*d->radius + 2*d->margin));
502 }
503 
minimumSize() const504 int ShowfotoThumbnailDelegate::minimumSize() const
505 {
506     Q_D(const ShowfotoThumbnailDelegate);
507 
508     return (ThumbnailSize::Small + 2*d->radius + 2*d->margin);
509 }
510 
acceptsActivation(const QPoint & pos,const QRect & visualRect,const QModelIndex & index,QRect * activationRect) const511 bool ShowfotoThumbnailDelegate::acceptsActivation(const QPoint& pos, const QRect& visualRect,
512                                                   const QModelIndex& index, QRect* activationRect) const
513 {
514     // reuse implementation from grandparent
515 
516     return ShowfotoItemViewDelegate::acceptsActivation(pos, visualRect, index, activationRect); // clazy:exclude=skipped-base-method
517 }
518 
updateContentWidth()519 void ShowfotoThumbnailDelegate::updateContentWidth()
520 {
521     Q_D(ShowfotoThumbnailDelegate);
522 
523     int maxSize;
524 
525     if (d->flow == QListView::LeftToRight)
526     {
527         maxSize = d->viewSize.height();
528     }
529     else
530     {
531         maxSize = d->viewSize.width();
532     }
533 
534     d->thumbSize = ThumbnailSize(thumbnailPixmapSize(true, maxSize - 2*d->radius - 2*d->margin));
535 
536     ShowfotoDelegate::updateContentWidth();
537 }
538 
thumbnailPixmapSize(bool withHighlight,int size)539 int ShowfotoThumbnailDelegate::thumbnailPixmapSize(bool withHighlight, int size)
540 {
541     if (withHighlight && (size >= 10))
542     {
543         return (size + 2);
544     }
545 
546     return size;
547 }
548 
updateRects()549 void ShowfotoThumbnailDelegate::updateRects()
550 {
551     Q_D(ShowfotoThumbnailDelegate);
552 
553     d->pixmapRect      = QRect(d->margin, d->margin, d->contentWidth, d->contentWidth);
554     d->rect            = QRect(0, 0, d->contentWidth + 2*d->margin, d->contentWidth + 2*d->margin);
555     d->coordinatesRect = QRect(d->contentWidth - 16+2, d->pixmapRect.top(), 16, 16);
556 
557     if (d->flow == QListView::LeftToRight)
558     {
559         d->gridSize = QSize(d->rect.width() + d->spacing, d->rect.height());
560     }
561     else
562     {
563         d->gridSize = QSize(d->rect.width(), d->rect.height() + d->spacing);
564     }
565 }
566 
567 // --- ShowfotoNormalDelegate -----------------------------------------------------------------------
568 
init(ShowfotoNormalDelegate * const q,ShowfotoThumbnailBar * const parent)569 void ShowfotoNormalDelegatePrivate::init(ShowfotoNormalDelegate* const q, ShowfotoThumbnailBar* const parent)
570 {
571 /*
572     categoryDrawer = new ShowfotoCategoryDrawer(parent);
573 */
574     Q_UNUSED(parent);
575     Q_UNUSED(q);
576 }
577 
578 // ------------------------------------------------------------------------------------------------
579 
ShowfotoNormalDelegate(ShowfotoThumbnailBar * const bar,QObject * const)580 ShowfotoNormalDelegate::ShowfotoNormalDelegate(ShowfotoThumbnailBar* const bar,
581                                                QObject* const)
582     : ShowfotoDelegate(*new ShowfotoNormalDelegatePrivate, bar)
583 {
584     Q_D(ShowfotoNormalDelegate);
585 
586     d->init(this, bar);
587 }
588 
ShowfotoNormalDelegate(ShowfotoNormalDelegatePrivate & dd,ShowfotoThumbnailBar * const bar,QObject * const)589 ShowfotoNormalDelegate::ShowfotoNormalDelegate(ShowfotoNormalDelegatePrivate& dd,
590                                                ShowfotoThumbnailBar* const bar,
591                                                QObject* const)
592     : ShowfotoDelegate(dd, bar)
593 {
594     Q_D(ShowfotoNormalDelegate);
595 
596     d->init(this, bar);
597 }
598 
~ShowfotoNormalDelegate()599 ShowfotoNormalDelegate::~ShowfotoNormalDelegate()
600 {
601 }
602 
updateRects()603 void ShowfotoNormalDelegate::updateRects()
604 {
605     Q_D(ShowfotoNormalDelegate);
606 
607     int y                   = d->margin;
608     d->pixmapRect           = QRect(d->margin, y, d->contentWidth, d->contentWidth);
609     y                       = d->pixmapRect.bottom();
610     d->imageInformationRect = QRect(d->margin, y, d->contentWidth, 0);
611 
612     d->imageInformationRect.setBottom(y);
613 
614     d->rect                 = QRect(0, 0, d->contentWidth + 2*d->margin, y+d->margin+d->radius);
615     d->gridSize             = QSize(d->rect.width() + d->spacing, d->rect.height() + d->spacing);
616 }
617 
618 } // namespace ShowFoto
619