|
|
|
/* ============================================================
|
|
|
|
*
|
|
|
|
* This file is a part of digiKam project
|
|
|
|
* http://www.digikam.org
|
|
|
|
*
|
|
|
|
* Date : 2006-04-14
|
|
|
|
* Description : Load and cache tag thumbnails
|
|
|
|
*
|
|
|
|
* Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it
|
|
|
|
* and/or modify it under the terms of the GNU General
|
|
|
|
* Public License as published by the Free Software Foundation;
|
|
|
|
* either version 2, or (at your option)
|
|
|
|
* any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
// C includes.
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
// TQt includes.
|
|
|
|
|
|
|
|
#include <tqmap.h>
|
|
|
|
#include <tqpainter.h>
|
|
|
|
#include <tqvaluelist.h>
|
|
|
|
|
|
|
|
// KDE includes.
|
|
|
|
|
|
|
|
#include <tdeapplication.h>
|
|
|
|
#include <kiconloader.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
|
|
// Local includes.
|
|
|
|
|
|
|
|
#include "ddebug.h"
|
|
|
|
#include "thumbnailjob.h"
|
|
|
|
#include "thumbnailsize.h"
|
|
|
|
#include "album.h"
|
|
|
|
#include "albummanager.h"
|
|
|
|
#include "albumsettings.h"
|
|
|
|
#include "albumthumbnailloader.h"
|
|
|
|
#include "albumthumbnailloader.moc"
|
|
|
|
|
|
|
|
namespace Digikam
|
|
|
|
{
|
|
|
|
|
|
|
|
typedef TQMap<KURL, TQValueList<int> > UrlAlbumMap;
|
|
|
|
typedef TQMap<int, TQPixmap> TagThumbnailMap;
|
|
|
|
|
|
|
|
class AlbumThumbnailLoaderPrivate
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
AlbumThumbnailLoaderPrivate()
|
|
|
|
{
|
|
|
|
iconSize = AlbumSettings::instance()->getDefaultTreeIconSize();
|
|
|
|
minBlendSize = 20;
|
|
|
|
iconAlbumThumbJob = 0;
|
|
|
|
iconTagThumbJob = 0;
|
|
|
|
//cache = new TQCache<TQPixmap>(101, 211);
|
|
|
|
}
|
|
|
|
|
|
|
|
int iconSize;
|
|
|
|
int minBlendSize;
|
|
|
|
|
|
|
|
ThumbnailJob *iconTagThumbJob;
|
|
|
|
|
|
|
|
ThumbnailJob *iconAlbumThumbJob;
|
|
|
|
|
|
|
|
UrlAlbumMap urlAlbumMap;
|
|
|
|
|
|
|
|
TagThumbnailMap tagThumbnailMap;
|
|
|
|
|
|
|
|
//TQCache<TQPixmap> *cache;
|
|
|
|
};
|
|
|
|
|
|
|
|
class AlbumThumbnailLoaderEvent : public TQCustomEvent
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
AlbumThumbnailLoaderEvent(int albumID, const TQPixmap &thumbnail)
|
|
|
|
: TQCustomEvent(TQEvent::User),
|
|
|
|
albumID(albumID), thumbnail(thumbnail)
|
|
|
|
{};
|
|
|
|
|
|
|
|
int albumID;
|
|
|
|
TQPixmap thumbnail;
|
|
|
|
};
|
|
|
|
|
|
|
|
AlbumThumbnailLoader *AlbumThumbnailLoader::m_instance = 0;
|
|
|
|
|
|
|
|
AlbumThumbnailLoader *AlbumThumbnailLoader::instance()
|
|
|
|
{
|
|
|
|
if (!m_instance)
|
|
|
|
m_instance = new AlbumThumbnailLoader;
|
|
|
|
return m_instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AlbumThumbnailLoader::cleanUp()
|
|
|
|
{
|
|
|
|
delete m_instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
AlbumThumbnailLoader::AlbumThumbnailLoader()
|
|
|
|
{
|
|
|
|
d = new AlbumThumbnailLoaderPrivate;
|
|
|
|
|
|
|
|
connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumIconChanged(Album*)),
|
|
|
|
this, TQ_SLOT(slotIconChanged(Album*)));
|
|
|
|
|
|
|
|
connect(AlbumManager::instance(), TQ_SIGNAL(signalAlbumDeleted(Album*)),
|
|
|
|
this, TQ_SLOT(slotIconChanged(Album*)));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
AlbumThumbnailLoader::~AlbumThumbnailLoader()
|
|
|
|
{
|
|
|
|
if (d->iconTagThumbJob)
|
|
|
|
d->iconTagThumbJob->kill();
|
|
|
|
|
|
|
|
if (d->iconAlbumThumbJob)
|
|
|
|
d->iconAlbumThumbJob->kill();
|
|
|
|
//delete d->cache;
|
|
|
|
|
|
|
|
delete d;
|
|
|
|
|
|
|
|
m_instance = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPixmap AlbumThumbnailLoader::getStandardTagIcon(RelativeSize relativeSize)
|
|
|
|
{
|
|
|
|
return loadIcon("tag", computeIconSize(relativeSize));
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPixmap AlbumThumbnailLoader::getStandardTagRootIcon(RelativeSize relativeSize)
|
|
|
|
{
|
|
|
|
return loadIcon("tag-folder", computeIconSize(relativeSize));
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPixmap AlbumThumbnailLoader::getStandardTagIcon(TAlbum *album, RelativeSize relativeSize)
|
|
|
|
{
|
|
|
|
if (album->isRoot())
|
|
|
|
return getStandardTagRootIcon(relativeSize);
|
|
|
|
else
|
|
|
|
return getStandardTagIcon(relativeSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPixmap AlbumThumbnailLoader::getStandardAlbumIcon(RelativeSize relativeSize)
|
|
|
|
{
|
|
|
|
return loadIcon("folder", computeIconSize(relativeSize));
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPixmap AlbumThumbnailLoader::getStandardAlbumRootIcon(RelativeSize relativeSize)
|
|
|
|
{
|
|
|
|
return loadIcon("folder_image", computeIconSize(relativeSize));
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPixmap AlbumThumbnailLoader::getStandardAlbumIcon(PAlbum *album, RelativeSize relativeSize)
|
|
|
|
{
|
|
|
|
if (album->isRoot())
|
|
|
|
return getStandardAlbumRootIcon(relativeSize);
|
|
|
|
else
|
|
|
|
return getStandardAlbumIcon(relativeSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
int AlbumThumbnailLoader::computeIconSize(RelativeSize relativeSize)
|
|
|
|
{
|
|
|
|
if (relativeSize == SmallerSize)
|
|
|
|
{
|
|
|
|
// when size was 32 smaller was 20. Scale.
|
|
|
|
return lround(20.0 / 32.0 * (double)d->iconSize);
|
|
|
|
}
|
|
|
|
return d->iconSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQRect AlbumThumbnailLoader::computeBlendRect(int iconSize)
|
|
|
|
{
|
|
|
|
// when drawing a 20x20 thumbnail in a 32x32 icon, starting point was (6,9). Scale.
|
|
|
|
double largerSize = iconSize;
|
|
|
|
double x = 6.0 / 32.0 * largerSize;
|
|
|
|
double y = 9.0 / 32.0 * largerSize;
|
|
|
|
double size = 20.0 / 32.0 * largerSize;
|
|
|
|
return TQRect(lround(x), lround(y), lround(size), lround(size));
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPixmap AlbumThumbnailLoader::loadIcon(const TQString &name, int size)
|
|
|
|
{
|
|
|
|
TDEIconLoader *iconLoader = TDEApplication::kApplication()->iconLoader();
|
|
|
|
return iconLoader->loadIcon(name, TDEIcon::NoGroup,
|
|
|
|
size, TDEIcon::DefaultState,
|
|
|
|
0, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AlbumThumbnailLoader::getTagThumbnail(TAlbum *album, TQPixmap &icon)
|
|
|
|
{
|
|
|
|
int size = computeIconSize(SmallerSize);
|
|
|
|
/*
|
|
|
|
if (size >= d->minBlendSize)
|
|
|
|
{
|
|
|
|
TQRect rect = computeBlendRect(size);
|
|
|
|
size = rect.width();
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
if(!album->icon().isEmpty())
|
|
|
|
{
|
|
|
|
if(album->icon().startsWith("/"))
|
|
|
|
{
|
|
|
|
KURL iconKURL;
|
|
|
|
iconKURL.setPath(album->icon());
|
|
|
|
addURL(album, iconKURL);
|
|
|
|
icon = TQPixmap();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
icon = loadIcon(album->icon(), size);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
icon = TQPixmap();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AlbumThumbnailLoader::getAlbumThumbnail(PAlbum *album)
|
|
|
|
{
|
|
|
|
if(!album->icon().isEmpty() && d->iconSize > d->minBlendSize)
|
|
|
|
{
|
|
|
|
addURL(album, album->iconKURL());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AlbumThumbnailLoader::addURL(Album *album, const KURL &url)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
TQPixmap* pix = d->cache->find(album->iconKURL().path());
|
|
|
|
if (pix)
|
|
|
|
return pix;
|
|
|
|
*/
|
|
|
|
|
|
|
|
// First check cached thumbnails.
|
|
|
|
// At startup, this is not relevant, as the views will add their requests in a row.
|
|
|
|
// This is to speed up context menu and IE imagedescedit
|
|
|
|
TagThumbnailMap::iterator ttit = d->tagThumbnailMap.find(album->globalID());
|
|
|
|
if (ttit != d->tagThumbnailMap.end())
|
|
|
|
{
|
|
|
|
// It is not necessary to return cached icon asynchronously - they could be
|
|
|
|
// returned by getTagThumbnail already - but this would make the API
|
|
|
|
// less elegant, it feels much better this way.
|
|
|
|
TQApplication::postEvent(this, new AlbumThumbnailLoaderEvent(album->globalID(), *ttit));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the URL has already been added (ThumbnailJob will _not_ check this)
|
|
|
|
UrlAlbumMap::iterator it = d->urlAlbumMap.find(url);
|
|
|
|
|
|
|
|
if (it == d->urlAlbumMap.end())
|
|
|
|
{
|
|
|
|
// use two IOslaves so that tag and album thumbnails are loaded
|
|
|
|
// in parallel and not first album, then tag thumbnails
|
|
|
|
if (album->type() == Album::TAG)
|
|
|
|
{
|
|
|
|
if(!d->iconTagThumbJob)
|
|
|
|
{
|
|
|
|
d->iconTagThumbJob = new ThumbnailJob(url,
|
|
|
|
d->iconSize,
|
|
|
|
true,
|
|
|
|
AlbumSettings::instance()->getExifRotate());
|
|
|
|
connect(d->iconTagThumbJob,
|
|
|
|
TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)),
|
|
|
|
TQ_SLOT(slotGotThumbnailFromIcon(const KURL&, const TQPixmap&)));
|
|
|
|
connect(d->iconTagThumbJob,
|
|
|
|
TQ_SIGNAL(signalFailed(const KURL&)),
|
|
|
|
TQ_SLOT(slotThumbnailLost(const KURL&)));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
d->iconTagThumbJob->addItem(url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(!d->iconAlbumThumbJob)
|
|
|
|
{
|
|
|
|
d->iconAlbumThumbJob = new ThumbnailJob(url,
|
|
|
|
d->iconSize,
|
|
|
|
true,
|
|
|
|
AlbumSettings::instance()->getExifRotate());
|
|
|
|
connect(d->iconAlbumThumbJob,
|
|
|
|
TQ_SIGNAL(signalThumbnail(const KURL&, const TQPixmap&)),
|
|
|
|
TQ_SLOT(slotGotThumbnailFromIcon(const KURL&, const TQPixmap&)));
|
|
|
|
connect(d->iconAlbumThumbJob,
|
|
|
|
TQ_SIGNAL(signalFailed(const KURL&)),
|
|
|
|
TQ_SLOT(slotThumbnailLost(const KURL&)));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
d->iconAlbumThumbJob->addItem(url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// insert new entry to map, add album globalID
|
|
|
|
TQValueList<int> &list = d->urlAlbumMap[url];
|
|
|
|
list.remove(album->globalID());
|
|
|
|
list.push_back(album->globalID());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// only add album global ID to list which is already inserted in map
|
|
|
|
(*it).remove(album->globalID());
|
|
|
|
(*it).push_back(album->globalID());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AlbumThumbnailLoader::setThumbnailSize(int size)
|
|
|
|
{
|
|
|
|
if (d->iconSize == size)
|
|
|
|
return;
|
|
|
|
|
|
|
|
d->iconSize = size;
|
|
|
|
|
|
|
|
// clear task list
|
|
|
|
d->urlAlbumMap.clear();
|
|
|
|
// clear cached thumbnails
|
|
|
|
d->tagThumbnailMap.clear();
|
|
|
|
|
|
|
|
if (d->iconAlbumThumbJob)
|
|
|
|
{
|
|
|
|
d->iconAlbumThumbJob->kill();
|
|
|
|
d->iconAlbumThumbJob= 0;
|
|
|
|
}
|
|
|
|
if (d->iconTagThumbJob)
|
|
|
|
{
|
|
|
|
d->iconTagThumbJob->kill();
|
|
|
|
d->iconTagThumbJob= 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
emit signalReloadThumbnails();
|
|
|
|
}
|
|
|
|
|
|
|
|
int AlbumThumbnailLoader::thumbnailSize() const
|
|
|
|
{
|
|
|
|
return d->iconSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AlbumThumbnailLoader::slotGotThumbnailFromIcon(const KURL &url, const TQPixmap &thumbnail)
|
|
|
|
{
|
|
|
|
// We need to find all albums for which the given url has been requested,
|
|
|
|
// and emit a signal for each album.
|
|
|
|
|
|
|
|
UrlAlbumMap::iterator it = d->urlAlbumMap.find(url);
|
|
|
|
|
|
|
|
if (it != d->urlAlbumMap.end())
|
|
|
|
{
|
|
|
|
TQPixmap tagThumbnail;
|
|
|
|
|
|
|
|
AlbumManager *manager = AlbumManager::instance();
|
|
|
|
for (TQValueList<int>::iterator vit = (*it).begin(); vit != (*it).end(); ++vit)
|
|
|
|
{
|
|
|
|
// look up with global id
|
|
|
|
Album *album = manager->findAlbum(*vit);
|
|
|
|
if (album)
|
|
|
|
{
|
|
|
|
if (album->type() == Album::TAG)
|
|
|
|
{
|
|
|
|
// create tag thumbnail if needed
|
|
|
|
if (tagThumbnail.isNull())
|
|
|
|
{
|
|
|
|
tagThumbnail = createTagThumbnail(thumbnail);
|
|
|
|
d->tagThumbnailMap.insert(album->globalID(), tagThumbnail);
|
|
|
|
}
|
|
|
|
|
|
|
|
emit signalThumbnail(album, tagThumbnail);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
emit signalThumbnail(album, thumbnail);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
d->urlAlbumMap.remove(it);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void AlbumThumbnailLoader::customEvent(TQCustomEvent *e)
|
|
|
|
{
|
|
|
|
// for cached thumbnails
|
|
|
|
|
|
|
|
AlbumThumbnailLoaderEvent *atle = (AlbumThumbnailLoaderEvent *)e;
|
|
|
|
AlbumManager *manager = AlbumManager::instance();
|
|
|
|
Album *album = manager->findAlbum(atle->albumID);
|
|
|
|
if (album)
|
|
|
|
{
|
|
|
|
if (atle->thumbnail.isNull())
|
|
|
|
emit signalFailed(album);
|
|
|
|
else
|
|
|
|
emit signalThumbnail(album, atle->thumbnail);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AlbumThumbnailLoader::slotIconChanged(Album* album)
|
|
|
|
{
|
|
|
|
if(!album || album->type() != Album::TAG)
|
|
|
|
return;
|
|
|
|
|
|
|
|
d->tagThumbnailMap.remove(album->globalID());
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPixmap AlbumThumbnailLoader::createTagThumbnail(const TQPixmap &albumThumbnail)
|
|
|
|
{
|
|
|
|
// tag thumbnails are cropped
|
|
|
|
|
|
|
|
TQPixmap tagThumbnail;
|
|
|
|
int thumbSize = TQMAX(albumThumbnail.width(), albumThumbnail.height());
|
|
|
|
|
|
|
|
if(!albumThumbnail.isNull() && thumbSize >= d->minBlendSize)
|
|
|
|
{
|
|
|
|
TQRect rect = computeBlendRect(thumbSize);
|
|
|
|
int w1 = albumThumbnail.width();
|
|
|
|
int w2 = rect.width();
|
|
|
|
int h1 = albumThumbnail.height();
|
|
|
|
int h2 = rect.height();
|
|
|
|
tagThumbnail.resize(w2,h2);
|
|
|
|
bitBlt(&tagThumbnail, 0, 0, &albumThumbnail, (w1-w2)/2, (h1-h2)/2, w2, h2);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tagThumbnail = albumThumbnail;
|
|
|
|
}
|
|
|
|
|
|
|
|
return tagThumbnail;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AlbumThumbnailLoader::slotThumbnailLost(const KURL &url)
|
|
|
|
{
|
|
|
|
// Same code as above, only different signal
|
|
|
|
|
|
|
|
UrlAlbumMap::iterator it = d->urlAlbumMap.find(url);
|
|
|
|
|
|
|
|
if (it != d->urlAlbumMap.end())
|
|
|
|
{
|
|
|
|
AlbumManager *manager = AlbumManager::instance();
|
|
|
|
for (TQValueList<int>::iterator vit = (*it).begin(); vit != (*it).end(); ++vit)
|
|
|
|
{
|
|
|
|
Album *album = manager->findAlbum(*vit);
|
|
|
|
if (album)
|
|
|
|
emit signalFailed(album);
|
|
|
|
}
|
|
|
|
|
|
|
|
d->urlAlbumMap.remove(it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPixmap AlbumThumbnailLoader::blendIcons(TQPixmap dstIcon, const TQPixmap &tagIcon)
|
|
|
|
{
|
|
|
|
int dstIconSize = TQMAX(dstIcon.width(), dstIcon.height());
|
|
|
|
|
|
|
|
if (dstIconSize >= d->minBlendSize)
|
|
|
|
{
|
|
|
|
if(!tagIcon.isNull())
|
|
|
|
{
|
|
|
|
TQRect rect = computeBlendRect(dstIconSize);
|
|
|
|
TQPainter p(&dstIcon);
|
|
|
|
p.drawPixmap(rect.x(), rect.y(), tagIcon, 0, 0, rect.width(), rect.height());
|
|
|
|
p.end();
|
|
|
|
}
|
|
|
|
return dstIcon;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return tagIcon;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Digikam
|