File indexing completed on 2025-01-19 03:50:38

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-04-20
0007  * Description : Qt model-view for items - category drawer
0008  *
0009  * SPDX-FileCopyrightText: 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2011      by Andi Clemens <andi dot clemens at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "itemcategorydrawer.h"
0017 
0018 // Qt includes
0019 
0020 #include <QPainter>
0021 #include <QApplication>
0022 #include <QLocale>
0023 
0024 // KDE includes
0025 
0026 #include <klocalizedstring.h>
0027 
0028 // Local includes
0029 
0030 #include "album.h"
0031 #include "albummanager.h"
0032 #include "itemalbummodel.h"
0033 #include "itemcategorizedview.h"
0034 #include "itemdelegate.h"
0035 #include "itemfiltermodel.h"
0036 #include "itemmodel.h"
0037 #include "itemscanner.h"
0038 #include "searchfolderview.h"
0039 #include "facetagsiface.h"
0040 
0041 namespace Digikam
0042 {
0043 
0044 class Q_DECL_HIDDEN ItemCategoryDrawer::Private
0045 {
0046 public:
0047 
0048     explicit Private()
0049       : lowerSpacing(0),
0050         view        (nullptr)
0051     {
0052     }
0053 
0054     QFont                font;
0055     QRect                rect;
0056     QPixmap              pixmap;
0057     int                  lowerSpacing;
0058     ItemCategorizedView* view;
0059 };
0060 
0061 ItemCategoryDrawer::ItemCategoryDrawer(ItemCategorizedView* const parent)
0062     : DCategoryDrawer(nullptr),
0063       d              (new Private)
0064 {
0065     d->view = parent;
0066 }
0067 
0068 ItemCategoryDrawer::~ItemCategoryDrawer()
0069 {
0070     delete d;
0071 }
0072 
0073 int ItemCategoryDrawer::categoryHeight(const QModelIndex& /*index*/, const QStyleOption& /*option*/) const
0074 {
0075     return (d->rect.height() + d->lowerSpacing);
0076 }
0077 
0078 int ItemCategoryDrawer::maximumHeight() const
0079 {
0080     return (d->rect.height() + d->lowerSpacing);
0081 }
0082 
0083 void ItemCategoryDrawer::setLowerSpacing(int spacing)
0084 {
0085     d->lowerSpacing = spacing;
0086 }
0087 
0088 void ItemCategoryDrawer::setDefaultViewOptions(const QStyleOptionViewItem& option)
0089 {
0090     d->font = option.font;
0091 
0092     if (option.rect.width() != d->rect.width())
0093     {
0094         updateRectsAndPixmaps(option.rect.width());
0095     }
0096 }
0097 
0098 void ItemCategoryDrawer::invalidatePaintingCache()
0099 {
0100     if (d->rect.isNull())
0101     {
0102         return;
0103     }
0104 
0105     updateRectsAndPixmaps(d->rect.width());
0106 }
0107 
0108 void ItemCategoryDrawer::drawCategory(const QModelIndex& index, int /*sortRole*/,
0109                                       const QStyleOption& option, QPainter* p) const
0110 {
0111     if (option.rect.width() != d->rect.width())
0112     {
0113         const_cast<ItemCategoryDrawer*>(this)->updateRectsAndPixmaps(option.rect.width());
0114     }
0115 
0116     p->save();
0117 
0118     p->translate(option.rect.topLeft());
0119 
0120     ItemSortSettings::CategorizationMode mode = (ItemSortSettings::CategorizationMode)index.data(ItemFilterModel::CategorizationModeRole).toInt();
0121 
0122     p->drawPixmap(0, 0, d->pixmap);
0123 
0124     QFont fontBold(d->font);
0125     QFont fontNormal(d->font);
0126     fontBold.setBold(true);
0127     int fnSize = fontBold.pointSize();
0128 
0129     if (fnSize > 0)
0130     {
0131         fontBold.setPointSize(fnSize+2);
0132     }
0133     else
0134     {
0135         fnSize = fontBold.pixelSize();
0136         fontBold.setPixelSize(fnSize+2);
0137     }
0138 
0139     QString header;
0140     QString subLine;
0141 
0142     switch (mode)
0143     {
0144         case ItemSortSettings::NoCategories:
0145             break;
0146 
0147         case ItemSortSettings::OneCategory:
0148             viewHeaderText(index, &header, &subLine);
0149             break;
0150 
0151         case ItemSortSettings::CategoryByAlbum:
0152             textForAlbum(index, &header, &subLine);
0153             break;
0154 
0155         case ItemSortSettings::CategoryByFormat:
0156             textForFormat(index, &header, &subLine);
0157             break;
0158 
0159         case ItemSortSettings::CategoryByMonth:
0160             textForMonth(index, &header, &subLine);
0161             break;
0162 
0163         case ItemSortSettings::CategoryByFaces:
0164             textForFace(index, &header, &subLine);
0165             break;
0166     }
0167 
0168     p->setPen(qApp->palette().color(QPalette::HighlightedText));
0169     p->setFont(fontBold);
0170 
0171     QRect tr;
0172     p->drawText(5, 5, d->rect.width(), d->rect.height(),
0173                 Qt::AlignLeft | Qt::AlignTop,
0174                 p->fontMetrics().elidedText(header, Qt::ElideRight, d->rect.width() - 10), &tr);
0175 
0176     int y = tr.height() + 2;
0177 
0178     p->setFont(fontNormal);
0179 
0180     p->drawText(5, y, d->rect.width(), d->rect.height() - y,
0181                 Qt::AlignLeft | Qt::AlignVCenter,
0182                 p->fontMetrics().elidedText(subLine, Qt::ElideRight, d->rect.width() - 10));
0183 
0184     p->restore();
0185 }
0186 
0187 void ItemCategoryDrawer::viewHeaderText(const QModelIndex& index, QString* header, QString* subLine) const
0188 {
0189     ItemModel* const sourceModel = index.data(ItemModel::ItemModelPointerRole).value<ItemModel*>();
0190 
0191     if (!sourceModel)
0192     {
0193         return;
0194     }
0195 
0196     int count = d->view->categoryRange(index).height();
0197 
0198     // Add here further model subclasses in use with ItemCategoryDrawer.
0199     // Note you need a Q_OBJECT in the class's header for this to work.
0200 
0201     ItemAlbumModel* const albumModel = qobject_cast<ItemAlbumModel*>(sourceModel);
0202 
0203     if (albumModel)
0204     {
0205         QList<Album*> albums = albumModel->currentAlbums();
0206         Album* album         = nullptr;
0207 
0208         if (albums.isEmpty())
0209         {
0210             return;
0211         }
0212 
0213         album = albums.first();
0214 
0215         if (!album)
0216         {
0217             return;
0218         }
0219 
0220         switch (album->type())
0221         {
0222             case Album::PHYSICAL:
0223                 textForPAlbum(static_cast<PAlbum*>(album), albumModel->isRecursingAlbums(), count, header, subLine);
0224                 break;
0225 
0226             case Album::TAG:
0227                 textForTAlbum(static_cast<TAlbum*>(album), albumModel->isRecursingTags(), count, header, subLine);
0228                 break;
0229 
0230             case Album::DATE:
0231                 textForDAlbum(static_cast<DAlbum*>(album), count, header, subLine);
0232                 break;
0233 
0234             case Album::SEARCH:
0235                 textForSAlbum(static_cast<SAlbum*>(album), count, header, subLine);
0236                 break;
0237 
0238             case Album::FACE:
0239             default:
0240                 break;
0241         }
0242     }
0243 }
0244 
0245 void ItemCategoryDrawer::textForAlbum(const QModelIndex& index, QString* header, QString* subLine) const
0246 {
0247     int albumId         = index.data(ItemFilterModel::CategoryAlbumIdRole).toInt();
0248     PAlbum* const album = AlbumManager::instance()->findPAlbum(albumId);
0249     int count           = d->view->categoryRange(index).height();
0250     textForPAlbum(album, false, count, header, subLine);
0251 }
0252 
0253 void ItemCategoryDrawer::textForFormat(const QModelIndex& index, QString* header, QString* subLine) const
0254 {
0255     QString format = index.data(ItemFilterModel::CategoryFormatRole).toString();
0256     format         = ItemScanner::formatToString(format);
0257     *header        = format;
0258     int count      = d->view->categoryRange(index).height();
0259     *subLine       = i18np("1 Item", "%1 Items", count);
0260 }
0261 
0262 void ItemCategoryDrawer::textForMonth(const QModelIndex& index, QString* header, QString* subLine) const
0263 {
0264     QDate date = index.data(ItemFilterModel::CategoryDateRole).toDate();
0265     *header    = date.toString(QLatin1String("MMM yyyy"));
0266     int count  = d->view->categoryRange(index).height();
0267     *subLine   = i18np("1 Item", "%1 Items", count);
0268 }
0269 
0270 void ItemCategoryDrawer::textForFace(const QModelIndex& index, QString* header, QString* subLine) const
0271 {
0272     *header    = index.data(ItemFilterModel::CategoryFaceRole).toString();
0273     int count  = d->view->categoryRange(index).height();
0274     *subLine   = i18np("1 Item", "%1 Items", count);
0275 }
0276 
0277 void ItemCategoryDrawer::textForPAlbum(PAlbum* album, bool recursive, int count, QString* header, QString* subLine) const
0278 {
0279     Q_UNUSED(recursive);
0280 
0281     if (!album)
0282     {
0283         return;
0284     }
0285 
0286     QDate date    = album->date();
0287 
0288     QLocale tmpLocale;
0289 
0290     // day of month with two digits
0291 
0292     QString day   = tmpLocale.toString(date, QLatin1String("dd"));
0293 
0294     // short form of the month
0295 
0296     QString month = tmpLocale.toString(date, QLatin1String("MMM"));
0297 
0298     // long form of the year
0299 
0300     QString year  = tmpLocale.toString(date, QLatin1String("yyyy"));
0301 
0302     *subLine      = i18ncp("%1: day of month with two digits, %2: short month name, %3: year",
0303                            "Album Date: %2 %3 %4 - 1 Item", "Album Date: %2 %3 %4 - %1 Items",
0304                            count, day, month, year);
0305 
0306     if (!album->caption().isEmpty())
0307     {
0308         QString caption = album->caption();
0309         *subLine       += QLatin1String(" - ") + caption.replace(QLatin1Char('\n'), QLatin1Char(' '));
0310     }
0311 
0312     *header = album->prettyUrl();
0313 }
0314 
0315 void ItemCategoryDrawer::textForTAlbum(TAlbum* talbum, bool recursive, int count, QString* header,
0316                                        QString* subLine) const
0317 {
0318     *header = talbum->title();
0319 
0320     if (recursive && talbum->firstChild())
0321     {
0322         int n=0;
0323 
0324         for (AlbumIterator it(talbum) ; it.current() ; ++it)
0325         {
0326             n++;
0327         }
0328 
0329         QString firstPart = i18ncp("%2: a tag title; %3: number of subtags",
0330                                    "%2 including 1 subtag", "%2 including %1 subtags",
0331                                    n, talbum->tagPath(false));
0332 
0333         *subLine = i18ncp("%2: the previous string (e.g. 'Foo including 7 subtags'); %1: number of items in tag",
0334                           "%2 - 1 Item", "%2 - %1 Items",
0335                           count, firstPart);
0336     }
0337     else
0338     {
0339         *subLine = i18np("%2 - 1 Item", "%2 - %1 Items", count, talbum->tagPath(false));
0340     }
0341 }
0342 
0343 void ItemCategoryDrawer::textForSAlbum(SAlbum* salbum, int count, QString* header, QString* subLine) const
0344 {
0345     QString title = salbum->displayTitle();
0346 
0347     *header = title;
0348 
0349     if      (salbum->isNormalSearch())
0350     {
0351         *subLine = i18np("Keyword Search - 1 Item", "Keyword Search - %1 Items", count);
0352     }
0353     else if (salbum->isAdvancedSearch())
0354     {
0355         *subLine = i18np("Advanced Search - 1 Item", "Advanced Search - %1 Items", count);
0356     }
0357     else
0358     {
0359         *subLine = i18np("1 Item", "%1 Items", count);
0360     }
0361 }
0362 
0363 void ItemCategoryDrawer::textForDAlbum(DAlbum* album, int count, QString* header, QString* subLine) const
0364 {
0365     QString year = QLocale().toString(album->date(), QLatin1String("yyyy"));
0366 
0367     if (album->range() == DAlbum::Month)
0368     {
0369         *header = i18nc("Month String - Year String", "%1 %2",
0370                         QLocale().standaloneMonthName(album->date().month(), QLocale::LongFormat), year);
0371     }
0372     else
0373     {
0374         *header = year;
0375     }
0376 
0377     *subLine = i18np("1 Item", "%1 Items", count);
0378 }
0379 
0380 void ItemCategoryDrawer::updateRectsAndPixmaps(int width)
0381 {
0382     d->rect = QRect(0, 0, 0, 0);
0383 
0384     // Title --------------------------------------------------------
0385 
0386     QFont fn(d->font);
0387     int fnSize = fn.pointSize();
0388     bool usePointSize;
0389 
0390     if (fnSize > 0)
0391     {
0392         fn.setPointSize(fnSize+2);
0393         usePointSize = true;
0394     }
0395     else
0396     {
0397         fnSize       = fn.pixelSize();
0398         fn.setPixelSize(fnSize+2);
0399         usePointSize = false;
0400     }
0401 
0402     fn.setBold(true);
0403     QFontMetrics fm(fn);
0404     QRect tr = fm.boundingRect(0, 0, width,
0405                                0xFFFFFFFF, Qt::AlignLeft | Qt::AlignVCenter,
0406                                QLatin1String("XXX"));
0407     d->rect.setHeight(tr.height());
0408 
0409     if (usePointSize)
0410     {
0411         fn.setPointSize(d->font.pointSize());
0412     }
0413     else
0414     {
0415         fn.setPixelSize(d->font.pixelSize());
0416     }
0417 
0418     fn.setBold(false);
0419     fm = QFontMetrics(fn);
0420     tr = fm.boundingRect(0, 0, width,
0421                          0xFFFFFFFF, Qt::AlignLeft | Qt::AlignVCenter,
0422                          QLatin1String("XXX"));
0423 
0424     d->rect.setHeight(d->rect.height() + tr.height() + 10);
0425     d->rect.setWidth(width);
0426 
0427     d->pixmap = QPixmap(d->rect.width(), d->rect.height());
0428     d->pixmap.fill(qApp->palette().color(QPalette::Highlight));
0429 }
0430 
0431 } // namespace Digikam
0432 
0433 #include "moc_itemcategorydrawer.cpp"