// vim: set tabstop=4 shiftwidth=4 noexpandtab:
/*	Gwenview - A simple image viewer for KDE
	Copyright 2000-2004 Aur�lien G�teau
	This class is based on the ImagePreviewJob class from Konqueror.
	Original copyright follows.
*/
/*	This file is part of the KDE project
	Copyright (C) 2000 David Faure <faure@kde.org>
				  2000 Carsten Pfeiffer <pfeiffer@kde.org>

	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 of the License, 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.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
#include "thumbnailloadjob.moc"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>

// TQt
#include <tqdir.h>
#include <tqfile.h>
#include <tqimage.h>
#include <tqpixmap.h>
#include <tqtimer.h>

// KDE 
#include <kapplication.h>
#include <kdebug.h>
#include <kfileitem.h>
#include <kiconloader.h>
#include <kio/previewjob.h>
#include <klargefile.h>
#include <kmdcodec.h>
#include <kstandarddirs.h>
#include <ktempfile.h>

// libjpeg 
#include <setjmp.h>
#define XMD_H
extern "C" {
#include <jpeglib.h>
}

// Local
#include "cache.h"
#include "mimetypeutils.h"
#include "miscconfig.h"
#include "imageutils/jpegcontent.h"
#include "imageutils/imageutils.h"
#include "thumbnailsize.h"
#include "fileviewconfig.h"
namespace Gwenview {

#undef ENABLE_LOG
#undef LOG
//#define ENABLE_LOG
#ifdef ENABLE_LOG
#define LOG(x) kdDebug() << k_funcinfo << x << endl
#else
#define LOG(x) ;
#endif


static TQString generateOriginalURI(KURL url) {
	// Don't include the password if any
	url.setPass(TQString());
	return url.url();
}


static TQString generateThumbnailPath(const TQString& uri, int size) {
	KMD5 md5( TQFile::encodeName(uri).data() );
	TQString baseDir=ThumbnailLoadJob::thumbnailBaseDir(size);
	return baseDir + TQString(TQFile::encodeName( md5.hexDigest())) + ".png";
}

//------------------------------------------------------------------------
//
// ThumbnailThread
//
//------------------------------------------------------------------------
void ThumbnailThread::load(
	const TQString& originalURI, time_t originalTime, int originalSize, const TQString& originalMimeType,
	const TQString& pixPath,
	const TQString& thumbnailPath,
	int size, bool storeThumbnail)
{
	TQMutexLocker lock( &mMutex );
	assert( mPixPath.isNull());

	mOriginalURI = TSDeepCopy(originalURI);
	mOriginalTime = originalTime;
	mOriginalSize = originalSize;
	mOriginalMimeType = TSDeepCopy(originalMimeType);
	mPixPath = TSDeepCopy(pixPath);
	mThumbnailPath = TSDeepCopy(thumbnailPath);
	mThumbnailSize = size;
	mStoreThumbnailsInCache = storeThumbnail;
	if(!running()) start();
	mCond.wakeOne();
}

void ThumbnailThread::run() {
	TQMutexLocker lock( &mMutex );
	while( !testCancel()) {
		// empty mPixPath means nothing to do
		while( mPixPath.isNull()) {
			mCond.cancellableWait( &mMutex );
			if( testCancel()) return;
		}
		loadThumbnail();
		mPixPath = TQString(); // done, ready for next
		TQSize size(mOriginalWidth, mOriginalHeight);
		emitCancellableSignal( this, TQT_SIGNAL( done( const TQImage&, const TQSize&)), mImage, size);
	}
}

void ThumbnailThread::loadThumbnail() {
	mImage = TQImage();
	bool loaded=false;
	bool needCaching=true;
	
	// If it's a JPEG, try to load a small image directly from the file
	if (isJPEG()) {
		ImageUtils::JPEGContent content;
		content.load(mPixPath);
		mOriginalWidth = content.size().width();
		mOriginalHeight = content.size().height();
		mImage = content.thumbnail();
	
		if( !mImage.isNull()
			&& ( mImage.width() >= mThumbnailSize // don't use small thumbnails
			|| mImage.height() >= mThumbnailSize )) {
			loaded = true;
			needCaching = false;
		}
		if(!loaded) {
			loaded=loadJPEG();
		}
		if (loaded && MiscConfig::autoRotateImages()) {
			// Rotate if necessary
			ImageUtils::Orientation orientation = content.orientation();
			mImage=ImageUtils::transform(mImage,orientation);
		}
	}
	// File is not a JPEG, or JPEG optimized load failed, load file using TQt
	if (!loaded) {
		TQImage originalImage;
		if (originalImage.load(mPixPath)) {
			mOriginalWidth=originalImage.width();
			mOriginalHeight=originalImage.height();
			int thumbSize=mThumbnailSize<=ThumbnailSize::NORMAL ? ThumbnailSize::NORMAL : ThumbnailSize::LARGE;

			if( testCancel()) return;

			if (TQMAX(mOriginalWidth, mOriginalHeight)<=thumbSize ) {
				mImage=originalImage;
				needCaching = false;
			} else {
				mImage=ImageUtils::scale(originalImage,thumbSize,thumbSize,ImageUtils::SMOOTH_FAST,TQ_ScaleMin);
			}
			loaded = true;
		}
	}

	if( testCancel()) return;

	if( mStoreThumbnailsInCache && needCaching ) {
		mImage.setText("Thumb::URI", 0, mOriginalURI);
		mImage.setText("Thumb::MTime", 0, TQString::number(mOriginalTime));
		mImage.setText("Thumb::Size", 0, TQString::number(mOriginalSize));
		mImage.setText("Thumb::Mimetype", 0, mOriginalMimeType);
		mImage.setText("Thumb::Image::Width", 0, TQString::number(mOriginalWidth));
		mImage.setText("Thumb::Image::Height", 0, TQString::number(mOriginalHeight));
		mImage.setText("Software", 0, "Gwenview");

		TQString thumbnailDir = ThumbnailLoadJob::thumbnailBaseDir(mThumbnailSize);
		KStandardDirs::makeDir(thumbnailDir, 0700);

		KTempFile tmp(thumbnailDir + "/gwenview", ".png");
		tmp.setAutoDelete(true);
		if (tmp.status()!=0) {
			TQString reason( strerror(tmp.status()) );
			kdWarning() << "Could not create a temporary file.\nReason: " << reason << endl;
			return;
		}

		if (!mImage.save(tmp.name(), "PNG")) {
			kdWarning() << "Could not save thumbnail for file " << mOriginalURI << endl;
			return;
		}
		
		rename(TQFile::encodeName(tmp.name()), TQFile::encodeName(mThumbnailPath));
	}
}


bool ThumbnailThread::isJPEG() {
	TQString format=TQImageIO::imageFormat(mPixPath);
	return format=="JPEG";
}



struct JPEGFatalError : public jpeg_error_mgr {
	jmp_buf mJmpBuffer;

	static void handler(j_common_ptr cinfo) {
		JPEGFatalError* error=static_cast<JPEGFatalError*>(cinfo->err);
		(error->output_message)(cinfo);
		longjmp(error->mJmpBuffer,1);
	}
};

bool ThumbnailThread::loadJPEG() {
	struct jpeg_decompress_struct cinfo;

	// Open file
	FILE* inputFile=fopen(TQFile::encodeName( mPixPath ).data(), "rb");
	if(!inputFile) return false;
	
	// Error handling
	struct JPEGFatalError jerr;
	cinfo.err = jpeg_std_error(&jerr);
	cinfo.err->error_exit = JPEGFatalError::handler;
	if (setjmp(jerr.mJmpBuffer)) {
		jpeg_destroy_decompress(&cinfo);
		fclose(inputFile);
		return false;
	}

	// Init decompression
	jpeg_create_decompress(&cinfo);
	jpeg_stdio_src(&cinfo, inputFile);
	jpeg_read_header(&cinfo, TRUE);

	// Get image size and check if we need a thumbnail
	int size= mThumbnailSize <= ThumbnailSize::NORMAL ? ThumbnailSize::NORMAL : ThumbnailSize::LARGE;
	int imgSize = TQMAX(cinfo.image_width, cinfo.image_height);

	if (imgSize<=size) {
		fclose(inputFile);
		return mImage.load(mPixPath);
	}	

	// Compute scale value
	int scale=1;
	while(size*scale*2<=imgSize) {
		scale*=2;
	}
	if(scale>8) scale=8;

	cinfo.scale_num=1;
	cinfo.scale_denom=scale;

	// Create TQImage
	jpeg_start_decompress(&cinfo);

	switch(cinfo.output_components) {
	case 3:
	case 4:
		mImage.create( cinfo.output_width, cinfo.output_height, 32 );
		break;
	case 1: // B&W image
		mImage.create( cinfo.output_width, cinfo.output_height, 8, 256 );
		for (int i=0; i<256; i++)
			mImage.setColor(i, tqRgb(i,i,i));
		break;
	default:
		jpeg_destroy_decompress(&cinfo);
		fclose(inputFile);
		return false;
	}

	uchar** lines = mImage.jumpTable();
	while (cinfo.output_scanline < cinfo.output_height) {
		jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline, cinfo.output_height);
	}
	jpeg_finish_decompress(&cinfo);

// Expand 24->32 bpp
	if ( cinfo.output_components == 3 ) {
		for (uint j=0; j<cinfo.output_height; j++) {
			uchar *in = mImage.scanLine(j) + cinfo.output_width*3;
			TQRgb *out = (TQRgb*)( mImage.scanLine(j) );

			for (uint i=cinfo.output_width; i--; ) {
				in-=3;
				out[i] = tqRgb(in[0], in[1], in[2]);
			}
		}
	}

	int newMax = TQMAX(cinfo.output_width, cinfo.output_height);
	int newx = size*cinfo.output_width / newMax;
	int newy = size*cinfo.output_height / newMax;

	mImage=ImageUtils::scale(mImage,newx, newy,ImageUtils::SMOOTH_FAST);

	jpeg_destroy_decompress(&cinfo);
	fclose(inputFile);

	return true;
}


//------------------------------------------------------------------------
//
// ThumbnailLoadJob static methods
//
//------------------------------------------------------------------------
TQString ThumbnailLoadJob::thumbnailBaseDir() {
	static TQString dir;
	if (!dir.isEmpty()) return dir;
	dir=TQDir::homeDirPath() + "/.thumbnails/";
	return dir;
}


TQString ThumbnailLoadJob::thumbnailBaseDir(int size) {
	TQString dir = thumbnailBaseDir();
	if (size<=ThumbnailSize::NORMAL) {
		dir+="normal/";
	} else {
		dir+="large/";
	}
	return dir;
}


void ThumbnailLoadJob::deleteImageThumbnail(const KURL& url) {
	TQString uri=generateOriginalURI(url);
	TQFile::remove(generateThumbnailPath(uri, ThumbnailSize::NORMAL));
	TQFile::remove(generateThumbnailPath(uri, ThumbnailSize::LARGE));
}


//------------------------------------------------------------------------
//
// ThumbnailLoadJob implementation
//
//------------------------------------------------------------------------


/*

 This class tries to first generate the most important thumbnails, i.e.
 first the currently selected one, then the ones that are visible, and then
 the rest, the closer the the currently selected one the sooner

 mAllItems contains all thumbnails
 mItems contains pending thumbnails, in the priority order
 mCurrentItem is currently processed thumbnail, already removed from mItems
 mProcessedState needs to match mAllItems, and contains information about every
   thumbnail whether it has been already processed

 thumbnailIndex() returns index of a thumbnail in mAllItems, or -1
 updateItemsOrder() builds mItems from mAllItems
*/

ThumbnailLoadJob::ThumbnailLoadJob(const TQValueVector<const KFileItem*>* items, int size)
: KIO::Job(false), mState( STATE_NEXTTHUMB ),
  mCurrentVisibleIndex( -1 ), mFirstVisibleIndex( -1 ), mLastVisibleIndex( -1 ),
  mThumbnailSize(size), mSuspended( false )
{
	LOG("");
	
	mBrokenPixmap=KGlobal::iconLoader()->loadIcon("file_broken", 
		KIcon::NoGroup, ThumbnailSize::MIN);

	// Look for images and store the items in our todo list
	Q_ASSERT(!items->empty());
	mAllItems=*items;
	mProcessedState.resize( mAllItems.count());
	tqFill( mProcessedState.begin(), mProcessedState.end(), false );
	mCurrentItem = NULL;

	connect(&mThumbnailThread, TQT_SIGNAL(done(const TQImage&, const TQSize&)),
		TQT_SLOT(thumbnailReady(const TQImage&, const TQSize&)) );
	Cache::instance()->updateAge(); // see addThumbnail in Cache
}


ThumbnailLoadJob::~ThumbnailLoadJob() {
	LOG("");
	mThumbnailThread.cancel();
	mThumbnailThread.wait();
}


void ThumbnailLoadJob::start() {
	// build mItems from mAllItems if not done yet
	if (mLastVisibleIndex == -1 ) {
		setPriorityItems( NULL, NULL, NULL );
	}
	if (mItems.isEmpty()) {
		LOG("Nothing to do");
		emit result(this);
		delete this;
		return;
	}

	determineNextIcon();
}

void ThumbnailLoadJob::suspend() {
	mSuspended = true;
}

void ThumbnailLoadJob::resume() {
	if( !mSuspended ) return;
	mSuspended = false;
	if( mState == STATE_NEXTTHUMB ) // don't load next while already loading
		determineNextIcon();
}

//-Internal--------------------------------------------------------------
void ThumbnailLoadJob::appendItem(const KFileItem* item) {
	int index = thumbnailIndex( item );
	if( index >= 0 ) {
		mProcessedState[ index ] = false;
		return;
	}
	mAllItems.append(item);
	mProcessedState.append( false );
	updateItemsOrder();
}


void ThumbnailLoadJob::itemRemoved(const KFileItem* item) {
	Q_ASSERT(item);

	// If we are removing the next item, update to be the item after or the
	// first if we removed the last item
	mItems.remove( item );
	int index = thumbnailIndex( item );
	if( index >= 0 ) {
		mAllItems.erase( mAllItems.begin() + index );
		mProcessedState.erase( mProcessedState.begin() + index );
	}

	if (item == mCurrentItem) {
		// Abort
		mCurrentItem = NULL;
		if (subjobs.first()) {
			subjobs.first()->kill();
			subjobs.removeFirst();
		}
		determineNextIcon();
	}
}


void ThumbnailLoadJob::setPriorityItems(const KFileItem* current, const KFileItem* first, const KFileItem* last) {
	if( mAllItems.isEmpty()) {
		mCurrentVisibleIndex = mFirstVisibleIndex = mLastVisibleIndex = 0;
		return;
	}
	mFirstVisibleIndex = -1;
	mLastVisibleIndex = - 1;
	mCurrentVisibleIndex = -1;
	if( first != NULL ) mFirstVisibleIndex = thumbnailIndex( first );
	if( last != NULL ) mLastVisibleIndex = thumbnailIndex( last );
	if( current != NULL ) mCurrentVisibleIndex = thumbnailIndex( current );
	if( mFirstVisibleIndex == -1 ) mFirstVisibleIndex = 0;
	if( mLastVisibleIndex == -1 ) mLastVisibleIndex = mAllItems.count() - 1;
	if( mCurrentVisibleIndex == -1 ) mCurrentVisibleIndex = mFirstVisibleIndex;
	updateItemsOrder();
}

void ThumbnailLoadJob::updateItemsOrder() {
	mItems.clear();
	int forward = mCurrentVisibleIndex + 1;
	int backward = mCurrentVisibleIndex;
	int first = mFirstVisibleIndex;
	int last = mLastVisibleIndex;
	updateItemsOrderHelper( forward, backward, first, last );
	if( first != 0 || last != int( mAllItems.count()) - 1 ) {
		// add non-visible items
		updateItemsOrderHelper( last + 1, first - 1, 0, mAllItems.count() - 1);
	}
}

void ThumbnailLoadJob::updateItemsOrderHelper( int forward, int backward, int first, int last ) {
	// start from the current item, add one following it, and one preceding it, for all visible items
	while( forward <= last || backward >= first ) {
		// start with backward - that's the curent item for the first time
		while( backward >= first ) {
			if( !mProcessedState[ backward ] ) {
				mItems.append( mAllItems[ backward ] );
				--backward;
				break;
			}
			--backward;
		}
		while( forward <= last ) {
			if( !mProcessedState[ forward ] ) {
				mItems.append( mAllItems[ forward ] );
				++forward;
				break;
			}
			++forward;
		}
	}
}

void ThumbnailLoadJob::determineNextIcon() {
	mState = STATE_NEXTTHUMB;
	if( mSuspended ) {
		return;
	}

	// No more items ?
	if (mItems.isEmpty()) {
		// Done
		LOG("emitting result");
		emit result(this);
		delete this;
		return;
	}

	mCurrentItem=mItems.first();
	mItems.pop_front();
	Q_ASSERT( !mProcessedState[ thumbnailIndex( mCurrentItem )] );
	mProcessedState[ thumbnailIndex( mCurrentItem )] = true;

	// First, stat the orig file
	mState = STATE_STATORIG;
	mOriginalTime = 0;
	mCurrentURL = mCurrentItem->url();
	mCurrentURL.cleanPath();

	// Do direct stat instead of using KIO if the file is local (faster)
	if( mCurrentURL.isLocalFile()
		&& !KIO::probably_slow_mounted( mCurrentURL.path())) {
		KDE_struct_stat buff;
		if ( KDE_stat( TQFile::encodeName(mCurrentURL.path()), &buff ) == 0 )  {
			mOriginalTime = buff.st_mtime;
			TQTimer::singleShot( 0, this, TQT_SLOT( checkThumbnail()));
		}
	}
	if( mOriginalTime == 0 ) { // KIO must be used
		KIO::Job* job = KIO::stat(mCurrentURL,false);
		job->setWindow(KApplication::kApplication()->mainWidget());
		LOG( "KIO::stat orig " << mCurrentURL.url() );
		addSubjob(job);
	}
}


void ThumbnailLoadJob::slotResult(KIO::Job * job) {
	LOG(mState);
	subjobs.remove(job);
	Q_ASSERT(subjobs.isEmpty());	// We should have only one job at a time ...

	switch (mState) {
	case STATE_NEXTTHUMB:
		Q_ASSERT(false);
		determineNextIcon();
		return;

	case STATE_STATORIG: {
		// Could not stat original, drop this one and move on to the next one
		if (job->error()) {
			emitThumbnailLoadingFailed();
			determineNextIcon();
			return;
		}

		// Get modification time of the original file
		KIO::UDSEntry entry = static_cast<KIO::StatJob*>(job)->statResult();
		KIO::UDSEntry::ConstIterator it= entry.begin();
		mOriginalTime = 0;
		for (; it!=entry.end(); ++it) {
			if ((*it).m_uds == KIO::UDS_MODIFICATION_TIME) {
				mOriginalTime = (time_t)((*it).m_long);
				break;
			}
		}
		checkThumbnail();
		return;
	}

	case STATE_DOWNLOADORIG: 
		if (job->error()) {
			emitThumbnailLoadingFailed();
			LOG("Delete temp file " << mTempPath);
			TQFile::remove(mTempPath);
			mTempPath = TQString();
			determineNextIcon();
		} else {
			startCreatingThumbnail(mTempPath);
		}
		return;
	
	case STATE_PREVIEWJOB:
		determineNextIcon();
		return;
	}
}


void ThumbnailLoadJob::thumbnailReady( const TQImage& im, const TQSize& _size) {
	TQImage img = TSDeepCopy( im );
	TQSize size = _size;
	if ( !img.isNull()) {
		emitThumbnailLoaded(img, size);
	} else {
		emitThumbnailLoadingFailed();
	}
	if( !mTempPath.isEmpty()) {
		LOG("Delete temp file " << mTempPath);
		TQFile::remove(mTempPath);
		mTempPath = TQString();
	}
	determineNextIcon();
}

void ThumbnailLoadJob::checkThumbnail() {
	// If we are in the thumbnail dir, just load the file
	if (mCurrentURL.isLocalFile()
		&& mCurrentURL.directory(false).startsWith(thumbnailBaseDir()) )
	{
		TQImage image(mCurrentURL.path());
		emitThumbnailLoaded(image, image.size());
		determineNextIcon();
		return;
	}
	TQSize imagesize;
	if( mOriginalTime == time_t( Cache::instance()->timestamp( mCurrentURL ).toTime_t())) {
		TQPixmap cached = Cache::instance()->thumbnail( mCurrentURL, imagesize );
		if( !cached.isNull()) {
			emit thumbnailLoaded(mCurrentItem, cached, imagesize);
			determineNextIcon();
			return;
		}
	}
	
	mOriginalURI=generateOriginalURI(mCurrentURL);
	mThumbnailPath=generateThumbnailPath(mOriginalURI, mThumbnailSize);

	LOG("Stat thumb " << mThumbnailPath);
	
	TQImage thumb;
	if ( thumb.load(mThumbnailPath) ) { 
		if (thumb.text("Thumb::URI", 0) == mOriginalURI &&
			thumb.text("Thumb::MTime", 0).toInt() == mOriginalTime )
		{
			int width=0, height=0;
			TQSize size;
			bool ok;

			width=thumb.text("Thumb::Image::Width", 0).toInt(&ok);
			if (ok) height=thumb.text("Thumb::Image::Height", 0).toInt(&ok);
			if (ok) {
				size=TQSize(width, height);
			} else {
				LOG("Thumbnail for " << mOriginalURI << " does not contain correct image size information");
				KFileMetaInfo fmi(mCurrentURL);
				if (fmi.isValid()) {
					KFileMetaInfoItem item=fmi.item("Dimensions");
					if (item.isValid()) {
						size=item.value().toSize();
					} else {
						LOG("KFileMetaInfoItem for " << mOriginalURI << " did not get image size information");
					}
				} else {
					LOG("Could not get a valid KFileMetaInfo instance for " << mOriginalURI);
				}
			}
			emitThumbnailLoaded(thumb, size);
			determineNextIcon();
			return;
		}
	}

	// Thumbnail not found or not valid
	if ( MimeTypeUtils::rasterImageMimeTypes().contains(mCurrentItem->mimetype()) ) {
		// This is a raster image
		if (mCurrentURL.isLocalFile()) {
			// Original is a local file, create the thumbnail
			startCreatingThumbnail(mCurrentURL.path());
		} else {
			// Original is remote, download it
			mState=STATE_DOWNLOADORIG;
			KTempFile tmpFile;
			mTempPath=tmpFile.name();
			KURL url;
			url.setPath(mTempPath);
			KIO::Job* job=KIO::file_copy(mCurrentURL, url,-1,true,false,false);
			job->setWindow(KApplication::kApplication()->mainWidget());
			LOG("Download remote file " << mCurrentURL.prettyURL());
			addSubjob(job);
		}
	} else {
		// Not a raster image, use a KPreviewJob
		mState=STATE_PREVIEWJOB;
		KFileItemList list;
		list.append(mCurrentItem);
		KIO::Job* job=KIO::filePreview(list, mThumbnailSize);
		job->setWindow(KApplication::kApplication()->mainWidget());
		connect(job, TQT_SIGNAL(gotPreview(const KFileItem*, const TQPixmap&)),
			this, TQT_SLOT(slotGotPreview(const KFileItem*, const TQPixmap&)) );
		connect(job, TQT_SIGNAL(failed(const KFileItem*)),
			this, TQT_SLOT(emitThumbnailLoadingFailed()) );
		addSubjob(job);
		return;
	}
}

void ThumbnailLoadJob::startCreatingThumbnail(const TQString& pixPath) {
	LOG("Creating thumbnail from " << pixPath);
	mThumbnailThread.load( mOriginalURI, mOriginalTime, mCurrentItem->size(),
		mCurrentItem->mimetype(), pixPath, mThumbnailPath, mThumbnailSize,
		FileViewConfig::storeThumbnailsInCache());
}


void ThumbnailLoadJob::slotGotPreview(const KFileItem* item, const TQPixmap& pixmap) {
	LOG("");
	TQSize size;
	emit thumbnailLoaded(item, pixmap, size);
}


void ThumbnailLoadJob::emitThumbnailLoaded(const TQImage& img, TQSize size) {
	int biggestDimension=TQMAX(img.width(), img.height());

	TQImage thumbImg;
	if (biggestDimension>mThumbnailSize) {
		// Scale down thumbnail if necessary
		thumbImg=ImageUtils::scale(img,mThumbnailSize, mThumbnailSize, ImageUtils::SMOOTH_FAST,TQ_ScaleMin);
	} else {
		thumbImg=img;
	}
	TQDateTime tm;
	tm.setTime_t( mOriginalTime );
	TQPixmap thumb( thumbImg ); // store as TQPixmap in cache (faster to retrieve, no conversion needed)
	Cache::instance()->addThumbnail( mCurrentURL, thumb, size, tm );
	emit thumbnailLoaded(mCurrentItem, thumb, size);
}


void ThumbnailLoadJob::emitThumbnailLoadingFailed() {
	TQSize size;
	emit thumbnailLoaded(mCurrentItem, mBrokenPixmap, size);
}


} // namespace
