1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2009-04-20
7  * Description : Qt model-view for items - category drawer
8  *
9  * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10  * Copyright (C) 2011      by Andi Clemens <andi dot clemens 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 "itemcategorydrawer.h"
26 
27 // Qt includes
28 
29 #include <QPainter>
30 #include <QApplication>
31 #include <QLocale>
32 
33 // KDE includes
34 
35 #include <klocalizedstring.h>
36 
37 // Local includes
38 
39 #include "album.h"
40 #include "albummanager.h"
41 #include "itemalbummodel.h"
42 #include "itemcategorizedview.h"
43 #include "itemdelegate.h"
44 #include "itemfiltermodel.h"
45 #include "itemmodel.h"
46 #include "itemscanner.h"
47 #include "searchfolderview.h"
48 #include "facetagsiface.h"
49 
50 namespace Digikam
51 {
52 
53 class Q_DECL_HIDDEN ItemCategoryDrawer::Private
54 {
55 public:
56 
Private()57     explicit Private()
58       : lowerSpacing(0),
59         view        (nullptr)
60     {
61     }
62 
63     QFont                font;
64     QRect                rect;
65     QPixmap              pixmap;
66     int                  lowerSpacing;
67     ItemCategorizedView* view;
68 };
69 
ItemCategoryDrawer(ItemCategorizedView * const parent)70 ItemCategoryDrawer::ItemCategoryDrawer(ItemCategorizedView* const parent)
71     : DCategoryDrawer(nullptr),
72       d              (new Private)
73 {
74     d->view = parent;
75 }
76 
~ItemCategoryDrawer()77 ItemCategoryDrawer::~ItemCategoryDrawer()
78 {
79     delete d;
80 }
81 
categoryHeight(const QModelIndex &,const QStyleOption &) const82 int ItemCategoryDrawer::categoryHeight(const QModelIndex& /*index*/, const QStyleOption& /*option*/) const
83 {
84     return (d->rect.height() + d->lowerSpacing);
85 }
86 
maximumHeight() const87 int ItemCategoryDrawer::maximumHeight() const
88 {
89     return (d->rect.height() + d->lowerSpacing);
90 }
91 
setLowerSpacing(int spacing)92 void ItemCategoryDrawer::setLowerSpacing(int spacing)
93 {
94     d->lowerSpacing = spacing;
95 }
96 
setDefaultViewOptions(const QStyleOptionViewItem & option)97 void ItemCategoryDrawer::setDefaultViewOptions(const QStyleOptionViewItem& option)
98 {
99     d->font = option.font;
100 
101     if (option.rect.width() != d->rect.width())
102     {
103         updateRectsAndPixmaps(option.rect.width());
104     }
105 }
106 
invalidatePaintingCache()107 void ItemCategoryDrawer::invalidatePaintingCache()
108 {
109     if (d->rect.isNull())
110     {
111         return;
112     }
113 
114     updateRectsAndPixmaps(d->rect.width());
115 }
116 
drawCategory(const QModelIndex & index,int,const QStyleOption & option,QPainter * p) const117 void ItemCategoryDrawer::drawCategory(const QModelIndex& index, int /*sortRole*/,
118                                       const QStyleOption& option, QPainter* p) const
119 {
120     if (option.rect.width() != d->rect.width())
121     {
122         const_cast<ItemCategoryDrawer*>(this)->updateRectsAndPixmaps(option.rect.width());
123     }
124 
125     p->save();
126 
127     p->translate(option.rect.topLeft());
128 
129     ItemSortSettings::CategorizationMode mode = (ItemSortSettings::CategorizationMode)index.data(ItemFilterModel::CategorizationModeRole).toInt();
130 
131     p->drawPixmap(0, 0, d->pixmap);
132 
133     QFont fontBold(d->font);
134     QFont fontNormal(d->font);
135     fontBold.setBold(true);
136     int fnSize = fontBold.pointSize();
137 
138     if (fnSize > 0)
139     {
140         fontBold.setPointSize(fnSize+2);
141     }
142     else
143     {
144         fnSize = fontBold.pixelSize();
145         fontBold.setPixelSize(fnSize+2);
146     }
147 
148     QString header;
149     QString subLine;
150 
151     switch (mode)
152     {
153         case ItemSortSettings::NoCategories:
154             break;
155 
156         case ItemSortSettings::OneCategory:
157             viewHeaderText(index, &header, &subLine);
158             break;
159 
160         case ItemSortSettings::CategoryByAlbum:
161             textForAlbum(index, &header, &subLine);
162             break;
163 
164         case ItemSortSettings::CategoryByFormat:
165             textForFormat(index, &header, &subLine);
166             break;
167 
168         case ItemSortSettings::CategoryByMonth:
169             textForMonth(index, &header, &subLine);
170             break;
171 
172         case ItemSortSettings::CategoryByFaces:
173             textForFace(index, &header, &subLine);
174             break;
175     }
176 
177     p->setPen(qApp->palette().color(QPalette::HighlightedText));
178     p->setFont(fontBold);
179 
180     QRect tr;
181     p->drawText(5, 5, d->rect.width(), d->rect.height(),
182                 Qt::AlignLeft | Qt::AlignTop,
183                 p->fontMetrics().elidedText(header, Qt::ElideRight, d->rect.width() - 10), &tr);
184 
185     int y = tr.height() + 2;
186 
187     p->setFont(fontNormal);
188 
189     p->drawText(5, y, d->rect.width(), d->rect.height() - y,
190                 Qt::AlignLeft | Qt::AlignVCenter,
191                 p->fontMetrics().elidedText(subLine, Qt::ElideRight, d->rect.width() - 10));
192 
193     p->restore();
194 }
195 
viewHeaderText(const QModelIndex & index,QString * header,QString * subLine) const196 void ItemCategoryDrawer::viewHeaderText(const QModelIndex& index, QString* header, QString* subLine) const
197 {
198     ItemModel* const sourceModel = index.data(ItemModel::ItemModelPointerRole).value<ItemModel*>();
199 
200     if (!sourceModel)
201     {
202         return;
203     }
204 
205     int count = d->view->categoryRange(index).height();
206 
207     // Add here further model subclasses in use with ItemCategoryDrawer.
208     // Note you need a Q_OBJECT in the class's header for this to work.
209 
210     ItemAlbumModel* const albumModel = qobject_cast<ItemAlbumModel*>(sourceModel);
211 
212     if (albumModel)
213     {
214         QList<Album*> albums = albumModel->currentAlbums();
215         Album* album         = nullptr;
216 
217         if (albums.isEmpty())
218         {
219             return;
220         }
221 
222         album = albums.first();
223 
224         if (!album)
225         {
226             return;
227         }
228 
229         switch (album->type())
230         {
231             case Album::PHYSICAL:
232                 textForPAlbum(static_cast<PAlbum*>(album), albumModel->isRecursingAlbums(), count, header, subLine);
233                 break;
234 
235             case Album::TAG:
236                 textForTAlbum(static_cast<TAlbum*>(album), albumModel->isRecursingTags(), count, header, subLine);
237                 break;
238 
239             case Album::DATE:
240                 textForDAlbum(static_cast<DAlbum*>(album), count, header, subLine);
241                 break;
242 
243             case Album::SEARCH:
244                 textForSAlbum(static_cast<SAlbum*>(album), count, header, subLine);
245                 break;
246 
247             case Album::FACE:
248             default:
249                 break;
250         }
251     }
252 }
253 
textForAlbum(const QModelIndex & index,QString * header,QString * subLine) const254 void ItemCategoryDrawer::textForAlbum(const QModelIndex& index, QString* header, QString* subLine) const
255 {
256     int albumId         = index.data(ItemFilterModel::CategoryAlbumIdRole).toInt();
257     PAlbum* const album = AlbumManager::instance()->findPAlbum(albumId);
258     int count           = d->view->categoryRange(index).height();
259     textForPAlbum(album, false, count, header, subLine);
260 }
261 
textForFormat(const QModelIndex & index,QString * header,QString * subLine) const262 void ItemCategoryDrawer::textForFormat(const QModelIndex& index, QString* header, QString* subLine) const
263 {
264     QString format = index.data(ItemFilterModel::CategoryFormatRole).toString();
265     format         = ItemScanner::formatToString(format);
266     *header        = format;
267     int count      = d->view->categoryRange(index).height();
268     *subLine       = i18np("1 Item", "%1 Items", count);
269 }
270 
textForMonth(const QModelIndex & index,QString * header,QString * subLine) const271 void ItemCategoryDrawer::textForMonth(const QModelIndex& index, QString* header, QString* subLine) const
272 {
273     QDate date = index.data(ItemFilterModel::CategoryDateRole).toDate();
274     *header    = date.toString(QLatin1String("MMM yyyy"));
275     int count  = d->view->categoryRange(index).height();
276     *subLine   = i18np("1 Item", "%1 Items", count);
277 }
278 
textForFace(const QModelIndex & index,QString * header,QString * subLine) const279 void ItemCategoryDrawer::textForFace(const QModelIndex& index, QString* header, QString* subLine) const
280 {
281     *header    = index.data(ItemFilterModel::CategoryFaceRole).toString();
282     int count  = d->view->categoryRange(index).height();
283     *subLine   = i18np("1 Item", "%1 Items", count);
284 }
285 
textForPAlbum(PAlbum * album,bool recursive,int count,QString * header,QString * subLine) const286 void ItemCategoryDrawer::textForPAlbum(PAlbum* album, bool recursive, int count, QString* header, QString* subLine) const
287 {
288     Q_UNUSED(recursive);
289 
290     if (!album)
291     {
292         return;
293     }
294 
295     QDate date    = album->date();
296 
297     QLocale tmpLocale;
298 
299     // day of month with two digits
300 
301     QString day   = tmpLocale.toString(date, QLatin1String("dd"));
302 
303     // short form of the month
304 
305     QString month = tmpLocale.toString(date, QLatin1String("MMM"));
306 
307     // long form of the year
308 
309     QString year  = tmpLocale.toString(date, QLatin1String("yyyy"));
310 
311     *subLine      = i18ncp("%1: day of month with two digits, %2: short month name, %3: year",
312                            "Album Date: %2 %3 %4 - 1 Item", "Album Date: %2 %3 %4 - %1 Items",
313                            count, day, month, year);
314 
315     if (!album->caption().isEmpty())
316     {
317         QString caption = album->caption();
318         *subLine       += QLatin1String(" - ") + caption.replace(QLatin1Char('\n'), QLatin1Char(' '));
319     }
320 
321     *header = album->prettyUrl();
322 }
323 
textForTAlbum(TAlbum * talbum,bool recursive,int count,QString * header,QString * subLine) const324 void ItemCategoryDrawer::textForTAlbum(TAlbum* talbum, bool recursive, int count, QString* header,
325                                        QString* subLine) const
326 {
327     *header = talbum->title();
328 
329     if (recursive && talbum->firstChild())
330     {
331         int n=0;
332 
333         for (AlbumIterator it(talbum) ; it.current() ; ++it)
334         {
335             n++;
336         }
337 
338         QString firstPart = i18ncp("%2: a tag title; %3: number of subtags",
339                                    "%2 including 1 subtag", "%2 including %1 subtags",
340                                    n, talbum->tagPath(false));
341 
342         *subLine = i18ncp("%2: the previous string (e.g. 'Foo including 7 subtags'); %1: number of items in tag",
343                           "%2 - 1 Item", "%2 - %1 Items",
344                           count, firstPart);
345     }
346     else
347     {
348         *subLine = i18np("%2 - 1 Item", "%2 - %1 Items", count, talbum->tagPath(false));
349     }
350 }
351 
textForSAlbum(SAlbum * salbum,int count,QString * header,QString * subLine) const352 void ItemCategoryDrawer::textForSAlbum(SAlbum* salbum, int count, QString* header, QString* subLine) const
353 {
354     QString title = salbum->displayTitle();
355 
356     *header = title;
357 
358     if      (salbum->isNormalSearch())
359     {
360         *subLine = i18np("Keyword Search - 1 Item", "Keyword Search - %1 Items", count);
361     }
362     else if (salbum->isAdvancedSearch())
363     {
364         *subLine = i18np("Advanced Search - 1 Item", "Advanced Search - %1 Items", count);
365     }
366     else
367     {
368         *subLine = i18np("1 Item", "%1 Items", count);
369     }
370 }
371 
textForDAlbum(DAlbum * album,int count,QString * header,QString * subLine) const372 void ItemCategoryDrawer::textForDAlbum(DAlbum* album, int count, QString* header, QString* subLine) const
373 {
374     QString year = QLocale().toString(album->date(), QLatin1String("yyyy"));
375 
376     if (album->range() == DAlbum::Month)
377     {
378         *header = i18nc("Month String - Year String", "%1 %2",
379                         QLocale().standaloneMonthName(album->date().month(), QLocale::LongFormat), year);
380     }
381     else
382     {
383         *header = year;
384     }
385 
386     *subLine = i18np("1 Item", "%1 Items", count);
387 }
388 
updateRectsAndPixmaps(int width)389 void ItemCategoryDrawer::updateRectsAndPixmaps(int width)
390 {
391     d->rect = QRect(0, 0, 0, 0);
392 
393     // Title --------------------------------------------------------
394 
395     QFont fn(d->font);
396     int fnSize = fn.pointSize();
397     bool usePointSize;
398 
399     if (fnSize > 0)
400     {
401         fn.setPointSize(fnSize+2);
402         usePointSize = true;
403     }
404     else
405     {
406         fnSize       = fn.pixelSize();
407         fn.setPixelSize(fnSize+2);
408         usePointSize = false;
409     }
410 
411     fn.setBold(true);
412     QFontMetrics fm(fn);
413     QRect tr = fm.boundingRect(0, 0, width,
414                                0xFFFFFFFF, Qt::AlignLeft | Qt::AlignVCenter,
415                                QLatin1String("XXX"));
416     d->rect.setHeight(tr.height());
417 
418     if (usePointSize)
419     {
420         fn.setPointSize(d->font.pointSize());
421     }
422     else
423     {
424         fn.setPixelSize(d->font.pixelSize());
425     }
426 
427     fn.setBold(false);
428     fm = QFontMetrics(fn);
429     tr = fm.boundingRect(0, 0, width,
430                          0xFFFFFFFF, Qt::AlignLeft | Qt::AlignVCenter,
431                          QLatin1String("XXX"));
432 
433     d->rect.setHeight(d->rect.height() + tr.height() + 10);
434     d->rect.setWidth(width);
435 
436     d->pixmap = QPixmap(d->rect.width(), d->rect.height());
437     d->pixmap.fill(qApp->palette().color(QPalette::Highlight));
438 }
439 
440 } // namespace Digikam
441