File indexing completed on 2022-09-27 16:29:03

0001 /**
0002  * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût <slaout@linux62.org>
0003  * SPDX-License-Identifier: GPL-2.0-or-later
0004  */
0005 
0006 #include "backgroundmanager.h"
0007 
0008 #include <KConfig>
0009 #include <KConfigGroup>
0010 
0011 #include <QStandardPaths>
0012 #include <QUrl>
0013 #include <QtCore/QDir>
0014 #include <QtGui/QImage>
0015 #include <QtGui/QPainter>
0016 #include <QtGui/QPixmap>
0017 
0018 /** class BackgroundEntry: */
0019 
0020 BackgroundEntry::BackgroundEntry(const QString &location)
0021 {
0022     this->location = location;
0023     name = QUrl::fromLocalFile(location).fileName();
0024     tiled = false;
0025     pixmap = nullptr;
0026     preview = nullptr;
0027     customersCount = 0;
0028 }
0029 
0030 BackgroundEntry::~BackgroundEntry()
0031 {
0032     delete pixmap;
0033     delete preview;
0034 }
0035 
0036 /** class OpaqueBackgroundEntry: */
0037 
0038 OpaqueBackgroundEntry::OpaqueBackgroundEntry(const QString &name, const QColor &color)
0039 {
0040     this->name = name;
0041     this->color = color;
0042     pixmap = nullptr;
0043     customersCount = 0;
0044 }
0045 
0046 OpaqueBackgroundEntry::~OpaqueBackgroundEntry()
0047 {
0048     delete pixmap;
0049 }
0050 
0051 /** class BackgroundManager: */
0052 
0053 BackgroundManager::BackgroundManager()
0054 {
0055     /// qDebug() << "BackgroundManager: Found the following background images in  ";
0056     QStringList directories = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation) /* WARNING: no more trailing slashes */; // eg. { "/home/seb/.kde/share/apps/", "/usr/share/apps/" }
0057     // For each folder:
0058     for (QStringList::Iterator it = directories.begin(); it != directories.end(); ++it) {
0059         // For each file in those directories:
0060         QDir dir(*it + "basket/backgrounds/", /*nameFilder=*/"*.png", /*sortSpec=*/QDir::Name | QDir::IgnoreCase, /*filterSpec=*/QDir::Files | QDir::NoSymLinks);
0061         ///     qDebug() << *it + "basket/backgrounds/  ";
0062         QStringList files = dir.entryList();
0063         for (QStringList::Iterator it2 = files.begin(); it2 != files.end(); ++it2) // TODO: If an image name is present in two folders?
0064             addImage(*it + "basket/backgrounds/" + *it2);
0065     }
0066 
0067     /// qDebug() << ":";
0068     /// for (BackgroundsList::Iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it)
0069     ///     qDebug() << "* " << (*it)->location << "  [ref: " << (*it)->name << "]";
0070 
0071     connect(&m_garbageTimer, SIGNAL(timeout()), this, SLOT(doGarbage()));
0072 }
0073 
0074 BackgroundManager::~BackgroundManager()
0075 {
0076     qDeleteAll(m_backgroundsList);
0077     qDeleteAll(m_opaqueBackgroundsList);
0078 }
0079 
0080 void BackgroundManager::addImage(const QString &fullPath)
0081 {
0082     m_backgroundsList.append(new BackgroundEntry(fullPath));
0083 }
0084 
0085 BackgroundEntry *BackgroundManager::backgroundEntryFor(const QString &image)
0086 {
0087     for (BackgroundsList::Iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it)
0088         if ((*it)->name == image)
0089             return *it;
0090     return nullptr;
0091 }
0092 
0093 OpaqueBackgroundEntry *BackgroundManager::opaqueBackgroundEntryFor(const QString &image, const QColor &color)
0094 {
0095     for (OpaqueBackgroundsList::Iterator it = m_opaqueBackgroundsList.begin(); it != m_opaqueBackgroundsList.end(); ++it)
0096         if ((*it)->name == image && (*it)->color == color)
0097             return *it;
0098     return nullptr;
0099 }
0100 
0101 bool BackgroundManager::subscribe(const QString &image)
0102 {
0103     BackgroundEntry *entry = backgroundEntryFor(image);
0104     if (entry) {
0105         // If it's the first time something subscribe to this image:
0106         if (!entry->pixmap) {
0107             // Try to load the pixmap:
0108             entry->pixmap = new QPixmap(entry->location);
0109             // Try to figure out if it's a tiled background image or not (default to NO):
0110             KConfig config(entry->location + ".config", KConfig::SimpleConfig);
0111             KConfigGroup configGroup = config.group("BasKet Background Image Configuration");
0112             entry->tiled = configGroup.readEntry("tiled", false);
0113         }
0114         // Return if the image loading has failed:
0115         if (entry->pixmap->isNull()) {
0116             ///         qDebug() << "BackgroundManager: Failed to load " << entry->location;
0117             return false;
0118         }
0119         // Success: effectively subscribe:
0120         ++entry->customersCount;
0121         return true;
0122     } else {
0123         // Don't exist: subscription failed:
0124         ///     qDebug() << "BackgroundManager: Requested unexisting image: " << image;
0125         return false;
0126     }
0127 }
0128 
0129 bool BackgroundManager::subscribe(const QString &image, const QColor &color)
0130 {
0131     BackgroundEntry *backgroundEntry = backgroundEntryFor(image);
0132 
0133     // First, if the image doesn't exist, isn't subscribed, or failed to load then we don't go further:
0134     if (!backgroundEntry || !backgroundEntry->pixmap || backgroundEntry->pixmap->isNull()) {
0135         ///     qDebug() << "BackgroundManager: Requested an unexisting or unsubscribed image: (" << image << "," << color.name() << ")...";
0136         return false;
0137     }
0138 
0139     OpaqueBackgroundEntry *opaqueBackgroundEntry = opaqueBackgroundEntryFor(image, color);
0140 
0141     // If this couple is requested for the first time or it haven't been subscribed for a long time enough, create it:
0142     if (!opaqueBackgroundEntry) {
0143         ///     qDebug() << "BackgroundManager: Computing (" << image << "," << color.name() << ")...";
0144         opaqueBackgroundEntry = new OpaqueBackgroundEntry(image, color);
0145         opaqueBackgroundEntry->pixmap = new QPixmap(backgroundEntry->pixmap->size());
0146         opaqueBackgroundEntry->pixmap->fill(color);
0147         QPainter painter(opaqueBackgroundEntry->pixmap);
0148         painter.drawPixmap(0, 0, *(backgroundEntry->pixmap));
0149         painter.end();
0150         m_opaqueBackgroundsList.append(opaqueBackgroundEntry);
0151     }
0152 
0153     // We are now sure the entry exist, do the subscription:
0154     ++opaqueBackgroundEntry->customersCount;
0155     return true;
0156 }
0157 
0158 void BackgroundManager::unsubscribe(const QString &image)
0159 {
0160     BackgroundEntry *entry = backgroundEntryFor(image);
0161 
0162     if (!entry) {
0163         ///     qDebug() << "BackgroundManager: Wanted to unsubscribe a not subscribed image: " << image;
0164         return;
0165     }
0166 
0167     --entry->customersCount;
0168     if (entry->customersCount <= 0)
0169         requestDelayedGarbage();
0170 }
0171 
0172 void BackgroundManager::unsubscribe(const QString &image, const QColor &color)
0173 {
0174     OpaqueBackgroundEntry *entry = opaqueBackgroundEntryFor(image, color);
0175 
0176     if (!entry) {
0177         ///     qDebug() << "BackgroundManager: Wanted to unsubscribe a not subscribed colored image: (" << image << "," << color.name() << ")";
0178         return;
0179     }
0180 
0181     --entry->customersCount;
0182     if (entry->customersCount <= 0)
0183         requestDelayedGarbage();
0184 }
0185 
0186 QPixmap *BackgroundManager::pixmap(const QString &image)
0187 {
0188     BackgroundEntry *entry = backgroundEntryFor(image);
0189 
0190     if (!entry || !entry->pixmap || entry->pixmap->isNull()) {
0191         ///     qDebug() << "BackgroundManager: Requested an unexisting or unsubscribed image: " << image;
0192         return nullptr;
0193     }
0194 
0195     return entry->pixmap;
0196 }
0197 
0198 QPixmap *BackgroundManager::opaquePixmap(const QString &image, const QColor &color)
0199 {
0200     OpaqueBackgroundEntry *entry = opaqueBackgroundEntryFor(image, color);
0201 
0202     if (!entry || !entry->pixmap || entry->pixmap->isNull()) {
0203         ///     qDebug() << "BackgroundManager: Requested an unexisting or unsubscribed colored image: (" << image << "," << color.name() << ")";
0204         return nullptr;
0205     }
0206 
0207     return entry->pixmap;
0208 }
0209 
0210 bool BackgroundManager::tiled(const QString &image)
0211 {
0212     BackgroundEntry *entry = backgroundEntryFor(image);
0213 
0214     if (!entry || !entry->pixmap || entry->pixmap->isNull()) {
0215         ///     qDebug() << "BackgroundManager: Requested an unexisting or unsubscribed image: " << image;
0216         return false;
0217     }
0218 
0219     return entry->tiled;
0220 }
0221 
0222 bool BackgroundManager::exists(const QString &image)
0223 {
0224     for (BackgroundsList::iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it)
0225         if ((*it)->name == image)
0226             return true;
0227     return false;
0228 }
0229 
0230 QStringList BackgroundManager::imageNames()
0231 {
0232     QStringList list;
0233     for (BackgroundsList::iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it)
0234         list.append((*it)->name);
0235     return list;
0236 }
0237 
0238 QPixmap *BackgroundManager::preview(const QString &image)
0239 {
0240     static const int MAX_WIDTH = 100;
0241     static const int MAX_HEIGHT = 75;
0242     static const QColor PREVIEW_BG = Qt::white;
0243 
0244     BackgroundEntry *entry = backgroundEntryFor(image);
0245 
0246     if (!entry) {
0247         ///     qDebug() << "BackgroundManager: Requested the preview of an unexisting image: " << image;
0248         return nullptr;
0249     }
0250 
0251     // The easiest way: already computed:
0252     if (entry->preview)
0253         return entry->preview;
0254 
0255     // Then, try to load the preview from file:
0256     QString previewPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "basket/backgrounds/previews/" + entry->name);
0257     QPixmap *previewPixmap = new QPixmap(previewPath);
0258     // Success:
0259     if (!previewPixmap->isNull()) {
0260         ///     qDebug() << "BackgroundManager: Loaded image preview for " << entry->location << " from file " << previewPath;
0261         entry->preview = previewPixmap;
0262         return entry->preview;
0263     }
0264 
0265     // We failed? Then construct it:
0266     // Note: if a preview is requested, it's because the user is currently choosing an image.
0267     // Since we need that image to create the preview, we keep the image in memory.
0268     // Then, it will already be loaded when user press [OK] in the background image chooser.
0269     // BUT we also delay a garbage because we don't want EVERY images to be loaded if the user use only a few of them, of course:
0270 
0271     // Already used? Good: we don't have to load it...
0272     if (!entry->pixmap) {
0273         // Note: it's a code duplication from BackgroundManager::subscribe(const QString &image),
0274         // Because, as we are loading the pixmap we ALSO need to know if it's a tile or not, in case that image will soon be used (and not destroyed by the garbager):
0275         entry->pixmap = new QPixmap(entry->location);
0276         // Try to figure out if it's a tiled background image or not (default to NO):
0277         KConfig config(entry->location + ".config");
0278         KConfigGroup configGroup = config.group("BasKet Background Image Configuration");
0279         entry->tiled = configGroup.readEntry("tiled", false);
0280     }
0281 
0282     // The image cannot be loaded, we failed:
0283     if (entry->pixmap->isNull())
0284         return nullptr;
0285 
0286     // Good that we are still alive: entry->pixmap contains the pixmap to rescale down for the preview:
0287     // Compute new size:
0288     int width = entry->pixmap->width();
0289     int height = entry->pixmap->height();
0290     if (width > MAX_WIDTH) {
0291         height = height * MAX_WIDTH / width;
0292         width = MAX_WIDTH;
0293     }
0294     if (height > MAX_HEIGHT) {
0295         width = width * MAX_HEIGHT / height;
0296         height = MAX_HEIGHT;
0297     }
0298     // And create the resulting pixmap:
0299     QPixmap *result = new QPixmap(width, height);
0300     result->fill(PREVIEW_BG);
0301     QImage imageToScale = entry->pixmap->toImage();
0302     QPixmap pmScaled = QPixmap::fromImage(imageToScale.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
0303     QPainter painter(result);
0304     painter.drawPixmap(0, 0, pmScaled);
0305     painter.end();
0306 
0307     // Saving it to file for later:
0308     QString folder = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/basket/backgrounds/previews/";
0309     result->save(folder + entry->name, "PNG");
0310 
0311     // Ouf! That's done:
0312     entry->preview = result;
0313     requestDelayedGarbage();
0314     return entry->preview;
0315 }
0316 
0317 QString BackgroundManager::pathForImageName(const QString &image)
0318 {
0319     BackgroundEntry *entry = backgroundEntryFor(image);
0320     if (entry == nullptr) {
0321         return QString();
0322     } else
0323         return entry->location;
0324 }
0325 
0326 QString BackgroundManager::previewPathForImageName(const QString &image)
0327 {
0328     BackgroundEntry *entry = backgroundEntryFor(image);
0329     if (entry == nullptr) {
0330         return QString();
0331     } else {
0332         QString previewPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "basket/backgrounds/previews/" + entry->name);
0333         QDir dir;
0334         if (!dir.exists(previewPath))
0335             return QString();
0336         else
0337             return previewPath;
0338     }
0339 }
0340 
0341 void BackgroundManager::requestDelayedGarbage()
0342 {
0343     static const int DELAY = 60 /*seconds*/;
0344 
0345     if (!m_garbageTimer.isActive()) {
0346         m_garbageTimer.setSingleShot(true);
0347         m_garbageTimer.start(DELAY * 1000 /*ms*/);
0348     }
0349 }
0350 
0351 void BackgroundManager::doGarbage()
0352 {
0353     /// qDebug() << "BackgroundManager: Doing garbage...";
0354 
0355     /// qDebug() << "BackgroundManager: Images:";
0356     for (BackgroundsList::Iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it) {
0357         BackgroundEntry *entry = *it;
0358         ///     qDebug() << "* " << entry->name << ": used " << entry->customersCount << " times";
0359         if (entry->customersCount <= 0 && entry->pixmap) {
0360             ///         qDebug() << " [Deleted cached pixmap]";
0361             delete entry->pixmap;
0362             entry->pixmap = nullptr;
0363         }
0364         ///     qDebug();
0365     }
0366 
0367     /// qDebug() << "BackgroundManager: Opaque Cached Images:";
0368     for (OpaqueBackgroundsList::Iterator it = m_opaqueBackgroundsList.begin(); it != m_opaqueBackgroundsList.end();) {
0369         OpaqueBackgroundEntry *entry = *it;
0370         ///     qDebug() << "* " << entry->name << "," << entry->color.name() << ": used " << entry->customersCount << " times";
0371         if (entry->customersCount <= 0) {
0372             ///         qDebug() << " [Deleted entry]";
0373             delete entry->pixmap;
0374             entry->pixmap = nullptr;
0375             it = m_opaqueBackgroundsList.erase(it);
0376         } else
0377             ++it;
0378         ///     qDebug();
0379     }
0380 }