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"