File indexing completed on 2024-05-05 17:55:39
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 } 0381 0382 #include "moc_backgroundmanager.cpp"