File indexing completed on 2025-01-05 03:59:44

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2010-02-13
0007  * Description : a list of selectable options with preview
0008  *               effects as thumbnails.
0009  *
0010  * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "previewlist.h"
0017 
0018 // Qt includes
0019 
0020 #include <QTimer>
0021 #include <QPainter>
0022 #include <QMap>
0023 
0024 // KDE includes
0025 
0026 #include <klocalizedstring.h>
0027 
0028 // Local includes
0029 
0030 #include "dimg.h"
0031 #include "dimgthreadedfilter.h"
0032 #include "imageiface.h"
0033 #include "digikam_debug.h"
0034 #include "dlayoutbox.h"
0035 #include "dworkingpixmap.h"
0036 
0037 namespace Digikam
0038 {
0039 
0040 class Q_DECL_HIDDEN PreviewThreadWrapper::Private
0041 {
0042 
0043 public:
0044 
0045     explicit Private()
0046     {
0047     }
0048 
0049     QMap<int, DImgThreadedFilter*> map;
0050 };
0051 
0052 PreviewThreadWrapper::PreviewThreadWrapper(QObject* const parent)
0053     : QObject(parent),
0054       d      (new Private)
0055 {
0056 }
0057 
0058 PreviewThreadWrapper::~PreviewThreadWrapper()
0059 {
0060     qDeleteAll(d->map);
0061 
0062     delete d;
0063 }
0064 
0065 void PreviewThreadWrapper::registerFilter(int id, DImgThreadedFilter* const filter)
0066 {
0067     if (d->map.contains(id))
0068     {
0069         return;
0070     }
0071 
0072     filter->setParent(this);
0073     d->map.insert(id, filter);
0074 
0075     connect(filter, SIGNAL(started()),
0076             this, SLOT(slotFilterStarted()));
0077 
0078     connect(filter, SIGNAL(finished(bool)),
0079             this, SLOT(slotFilterFinished(bool)));
0080 
0081     connect(filter, SIGNAL(progress(int)),
0082             this, SLOT(slotFilterProgress(int)));
0083 }
0084 
0085 void PreviewThreadWrapper::slotFilterStarted()
0086 {
0087     DImgThreadedFilter* const filter = dynamic_cast<DImgThreadedFilter*>(sender());
0088 
0089     if (!filter)
0090     {
0091         return;
0092     }
0093 
0094     Q_EMIT signalFilterStarted(d->map.key(filter));
0095 }
0096 
0097 void PreviewThreadWrapper::slotFilterFinished(bool success)
0098 {
0099     DImgThreadedFilter* const filter = dynamic_cast<DImgThreadedFilter*>(sender());
0100 
0101     if (!filter)
0102     {
0103         return;
0104     }
0105 
0106     if (success)
0107     {
0108         int key     = d->map.key(filter);
0109         QPixmap pix = filter->getTargetImage().smoothScale(128, 128, Qt::KeepAspectRatio).convertToPixmap();
0110         Q_EMIT signalFilterFinished(key, pix);
0111     }
0112 }
0113 
0114 void PreviewThreadWrapper::slotFilterProgress(int /*progress*/)
0115 {
0116     DImgThreadedFilter* const filter = dynamic_cast<DImgThreadedFilter*>(sender());
0117 
0118     if (!filter)
0119     {
0120         return;
0121     }
0122 /*
0123     qCDebug(DIGIKAM_GENERAL_LOG) << filter->filterName() << " : " << progress << " %";
0124 */
0125 }
0126 
0127 void PreviewThreadWrapper::startFilters()
0128 {
0129     Q_FOREACH (DImgThreadedFilter* const filter, d->map)
0130     {
0131         filter->startFilter();
0132     }
0133 }
0134 
0135 void PreviewThreadWrapper::stopFilters()
0136 {
0137     Q_FOREACH (DImgThreadedFilter* const filter, d->map)
0138     {
0139         filter->cancelFilter();
0140         filter->deleteLater();
0141     }
0142 }
0143 
0144 // ---------------------------------------------------------------------
0145 
0146 class Q_DECL_HIDDEN PreviewListItem::Private
0147 {
0148 public:
0149 
0150     explicit Private()
0151       : busy(false),
0152         id  (0)
0153     {
0154     }
0155 
0156     bool busy;
0157     int  id;
0158 };
0159 
0160 PreviewListItem::PreviewListItem(QListWidget* const parent)
0161     : QListWidgetItem(parent),
0162       d              (new Private)
0163 {
0164 }
0165 
0166 PreviewListItem::~PreviewListItem()
0167 {
0168     delete d;
0169 }
0170 
0171 void PreviewListItem::setPixmap(const QPixmap& pix)
0172 {
0173     QIcon icon = QIcon(pix);
0174 
0175     //  We make sure the preview icon stays the same regardless of the role
0176 
0177     icon.addPixmap(pix, QIcon::Selected, QIcon::On);
0178     icon.addPixmap(pix, QIcon::Selected, QIcon::Off);
0179     icon.addPixmap(pix, QIcon::Active,   QIcon::On);
0180     icon.addPixmap(pix, QIcon::Active,   QIcon::Off);
0181     icon.addPixmap(pix, QIcon::Normal,   QIcon::On);
0182     icon.addPixmap(pix, QIcon::Normal,   QIcon::Off);
0183     setIcon(icon);
0184 }
0185 
0186 void PreviewListItem::setId(int id)
0187 {
0188     d->id = id;
0189 }
0190 
0191 int PreviewListItem::id() const
0192 {
0193     return d->id;
0194 }
0195 
0196 void PreviewListItem::setBusy(bool b)
0197 {
0198     d->busy = b;
0199 }
0200 
0201 bool PreviewListItem::isBusy() const
0202 {
0203     return d->busy;
0204 }
0205 
0206 // ---------------------------------------------------------------------
0207 
0208 class Q_DECL_HIDDEN PreviewList::Private
0209 {
0210 
0211 public:
0212 
0213     explicit Private()
0214       : progressCount(0),
0215         progressTimer(nullptr),
0216         progressPix  (nullptr),
0217         wrapper      (nullptr)
0218     {
0219     }
0220 
0221     int                   progressCount;
0222 
0223     QTimer*               progressTimer;
0224 
0225     DWorkingPixmap*       progressPix;
0226 
0227     PreviewThreadWrapper* wrapper;
0228 };
0229 
0230 PreviewList::PreviewList(QWidget* const parent)
0231     : QListWidget(parent),
0232       d          (new Private)
0233 {
0234     d->wrapper     = new PreviewThreadWrapper(this);
0235     d->progressPix = new DWorkingPixmap(this);
0236 
0237     setSelectionMode(QAbstractItemView::SingleSelection);
0238     setDropIndicatorShown(true);
0239     setSortingEnabled(false);
0240     setIconSize(QSize(96, 96));
0241     setViewMode(QListView::IconMode);
0242     setWrapping(true);
0243     setWordWrap(false);
0244     setMovement(QListView::Static);
0245     setSpacing(5);
0246     setGridSize(QSize(125, 100 + fontMetrics().height()));
0247     setResizeMode(QListView::Adjust);
0248     setTextElideMode(Qt::ElideRight);
0249     setCursor(Qt::PointingHandCursor);
0250     setStyleSheet(QLatin1String("QListWidget::item:selected:!active {show-decoration-selected: 0}"));
0251 
0252     d->progressTimer = new QTimer(this);
0253     d->progressTimer->setInterval(300);
0254 
0255     connect(d->progressTimer, SIGNAL(timeout()),
0256             this, SLOT(slotProgressTimerDone()));
0257 
0258     connect(d->wrapper, SIGNAL(signalFilterStarted(int)),
0259             this, SLOT(slotFilterStarted(int)));
0260 
0261     connect(d->wrapper, SIGNAL(signalFilterFinished(int,QPixmap)),
0262             this, SLOT(slotFilterFinished(int,QPixmap)));
0263 }
0264 
0265 PreviewList::~PreviewList()
0266 {
0267     stopFilters();
0268     delete d;
0269 }
0270 
0271 void PreviewList::startFilters()
0272 {
0273     d->progressTimer->start();
0274     d->wrapper->startFilters();
0275 }
0276 
0277 void PreviewList::stopFilters()
0278 {
0279     d->progressTimer->stop();
0280     d->wrapper->stopFilters();
0281 }
0282 
0283 PreviewListItem* PreviewList::addItem(DImgThreadedFilter* const filter, const QString& txt, int id)
0284 {
0285     if (!filter)
0286     {
0287         return nullptr;
0288     }
0289 
0290     d->wrapper->registerFilter(id, filter);
0291 
0292     PreviewListItem* const item = new PreviewListItem(this);
0293     item->setText(txt);
0294 
0295     // in case text is mangled by textelide, it is displayed by hovering.
0296 
0297     item->setToolTip(txt);
0298     item->setId(id);
0299 
0300     return item;
0301 }
0302 
0303 PreviewListItem* PreviewList::findItem(int id) const
0304 {
0305     int it = 0;
0306 
0307     while (it <= this->count())
0308     {
0309         PreviewListItem* const item = dynamic_cast<PreviewListItem*>(this->item(it));
0310 
0311         if (item && (item->id() == id))
0312         {
0313             return item;
0314         }
0315 
0316         ++it;
0317     }
0318 
0319     return nullptr;
0320 }
0321 
0322 void PreviewList::setCurrentId(int id)
0323 {
0324     int it = 0;
0325 
0326     while (it <= this->count())
0327     {
0328 
0329         PreviewListItem* const item = dynamic_cast<PreviewListItem*>(this->item(it));
0330 
0331         if (item && (item->id() == id))
0332         {
0333             setCurrentItem(item);
0334             item->setSelected(true);
0335 
0336             return;
0337         }
0338 
0339         ++it;
0340     }
0341 }
0342 
0343 int PreviewList::currentId() const
0344 {
0345     PreviewListItem* const item = dynamic_cast<PreviewListItem*>(currentItem());
0346 
0347     if (item)
0348     {
0349         return item->id();
0350     }
0351 
0352     return 0;
0353 }
0354 
0355 void PreviewList::slotProgressTimerDone()
0356 {
0357     QPixmap ppix(d->progressPix->frameAt(d->progressCount));
0358     QPixmap pixmap(128, 128);
0359     pixmap.fill(Qt::transparent);
0360     QPainter p(&pixmap);
0361     p.drawPixmap((pixmap.width() / 2) - (ppix.width() / 2), (pixmap.height() / 2) - (ppix.height() / 2), ppix);
0362 
0363     int busy                      = 0;
0364     int it                        = 0;
0365     PreviewListItem* selectedItem = nullptr;
0366 
0367     while (it <= this->count())
0368     {
0369         PreviewListItem* const item = dynamic_cast<PreviewListItem*>(this->item(it));
0370 
0371         if (item && item->isSelected())
0372         {
0373             selectedItem = item;
0374         }
0375 
0376         if (item && item->isBusy())
0377         {
0378             item->setPixmap(pixmap);
0379             ++busy;
0380         }
0381 
0382         ++it;
0383     }
0384 
0385     d->progressCount++;
0386 
0387     if (d->progressCount >= d->progressPix->frameCount())
0388     {
0389         d->progressCount = 0;
0390     }
0391 
0392     if (!busy)
0393     {
0394         d->progressTimer->stop();
0395 
0396         // Qt 4.5 doesn't display icons correctly centred over i18n(text),
0397         // Qt 4.6 doesn't even reset the previous selection correctly.
0398 
0399         this->reset();
0400 
0401         if (selectedItem)
0402         {
0403             setCurrentItem(selectedItem);
0404         }
0405     }
0406 }
0407 
0408 void PreviewList::slotFilterStarted(int id)
0409 {
0410     PreviewListItem* const item = findItem(id);
0411     item->setBusy(true);
0412 }
0413 
0414 void PreviewList::slotFilterFinished(int id, const QPixmap& pix)
0415 {
0416     PreviewListItem* const item = findItem(id);
0417 
0418     if (item)
0419     {
0420         item->setBusy(false);
0421         item->setPixmap(pix);
0422         update();
0423     }
0424 }
0425 
0426 } // namespace Digikam
0427 
0428 #include "moc_previewlist.cpp"