/*
Gwenview - A simple image viewer for TDE
Copyright 2000-2004 Aur�lien G�teau

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.

*/

// TQt
#include <tqframe.h>
#include <tqlayout.h>
#include <tqpainter.h>
#include <tqpen.h>
#include <tqpixmap.h>
#include <tqpushbutton.h>
#include <tqtimer.h>
#include <tqvaluevector.h>

// KDE
#include <tdeapplication.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <tdeglobalsettings.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <kprogress.h>
#include <kstandarddirs.h>
#include <kurldrag.h>
#include <kwordwrap.h>

// Local
#include "fileviewconfig.h"
#include "filethumbnailviewitem.h"
#include "archive.h"
#include "dragpixmapgenerator.h"
#include "thumbnailloadjob.h"
#include "busylevelmanager.h"
#include "imageloader.h"
#include "timeutils.h"
#include "thumbnailsize.h"
#include "thumbnaildetailsdialog.h"

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

#include "filethumbnailview.moc"
namespace Gwenview {

static const int THUMBNAIL_UPDATE_DELAY=500;
	
static const int RIGHT_TEXT_WIDTH=128;
static const int BOTTOM_MIN_TEXT_WIDTH=96;

class ProgressWidget : public TQFrame {
	KProgress* mProgressBar;
	TQPushButton* mStop;
public:
	ProgressWidget(FileThumbnailView* view, int count)
	: TQFrame(view)
	{
		TQHBoxLayout* layout=new TQHBoxLayout(this, 3, 3);
		layout->setAutoAdd(true);
		setFrameStyle( TQFrame::StyledPanel | TQFrame::Raised );
		
		mStop=new TQPushButton(this);
		mStop->setPixmap(SmallIcon("process-stop"));
		mStop->setFlat(true);

		mProgressBar=new KProgress(count, this);
		mProgressBar->setFormat("%v/%m");

		view->clipper()->installEventFilter(this);
	}
	
	void polish() {
		TQFrame::polish();
		setMinimumWidth(layout()->minimumSize().width());
		//setFixedHeight( mProgressBar->height() );
		setFixedHeight( mStop->height() );
	}

	void showEvent(TQShowEvent*) {
		updatePosition();
	}

	bool eventFilter(TQObject*, TQEvent* event) {
		if (event->type()==TQEvent::Resize) {
			updatePosition();
		}
		return false;
	}

	void updatePosition() {
		FileThumbnailView* view=static_cast<FileThumbnailView*>(parent());
		TQSize tmp=view->clipper()->size() - size();
		move(tmp.width() - 2, tmp.height() - 2);
	}

	KProgress* progressBar() const { return mProgressBar; }
	TQPushButton* stopButton() const { return mStop; }
};


struct FileThumbnailView::Private {
	int mThumbnailSize;
	int mMarginSize;
	bool mUpdateThumbnailsOnNextShow;
	TQPixmap mWaitPixmap; // The wait pixmap (32 x 32)
	TQPixmap mWaitThumbnail; // The wait thumbnail (mThumbnailSize x mThumbnailSize)
	ProgressWidget* mProgressWidget;

	TQGuardedPtr<ThumbnailLoadJob> mThumbnailLoadJob;
	
	TQTimer* mThumbnailUpdateTimer;

	int mItemDetails;

	ImageLoader* mPrefetch;
	ThumbnailDetailsDialog* mThumbnailsDetailDialog;

	void updateWaitThumbnail(const FileThumbnailView* view) {
		mWaitThumbnail=TQPixmap(mThumbnailSize, mThumbnailSize);
		mWaitThumbnail.fill(view->paletteBackgroundColor());
		TQPainter painter(&mWaitThumbnail);
		
		painter.setPen(view->colorGroup().button());
		painter.drawRect(0,0,mThumbnailSize,mThumbnailSize);
		painter.drawPixmap(
			(mThumbnailSize-mWaitPixmap.width())/2,
			(mThumbnailSize-mWaitPixmap.height())/2,
			mWaitPixmap);
		painter.end();
	}
};


static FileThumbnailViewItem* viewItem(const FileThumbnailView* view, const KFileItem* fileItem) {
	if (!fileItem) return 0L;
	return static_cast<FileThumbnailViewItem*>( const_cast<void*>(fileItem->extraData(view) ) );
}


FileThumbnailView::FileThumbnailView(TQWidget* parent)
: TDEIconView(parent), FileViewBase()
{
	d=new Private;
	d->mUpdateThumbnailsOnNextShow=false;
	d->mThumbnailLoadJob=0L;
	d->mWaitPixmap=TQPixmap(::locate("appdata", "thumbnail/wait.png"));
	d->mProgressWidget=0L;
	d->mThumbnailUpdateTimer=new TQTimer(this);
	d->mMarginSize=FileViewConfig::thumbnailMarginSize();
	d->mItemDetails=FileViewConfig::thumbnailDetails();
	d->mPrefetch = nullptr;
	d->mThumbnailSize = 0;
	d->mThumbnailsDetailDialog = 0;

	setItemTextPos( TQIconView::ItemTextPos(FileViewConfig::thumbnailTextPos()) );
	setAutoArrange(true);
	TQIconView::setSorting(true);
	setItemsMovable(false);
	setResizeMode(Adjust);
	setShowToolTips(true);
	setSpacing(0);
	setAcceptDrops(true);

	// We can't use TDEIconView::Execute mode because in this mode the current
	// item is unselected after being clicked, so we use TDEIconView::Select mode
	// and emit the execute() signal with slotClicked() ourself.
	setMode(TDEIconView::Select);
	connect(this, TQ_SIGNAL(clicked(TQIconViewItem*)),
		this, TQ_SLOT(slotClicked(TQIconViewItem*)) );
	connect(this, TQ_SIGNAL(doubleClicked(TQIconViewItem*)),
		this, TQ_SLOT(slotDoubleClicked(TQIconViewItem*)) );

	connect(this, TQ_SIGNAL(dropped(TQDropEvent*,const TQValueList<TQIconDragItem>&)),
		this, TQ_SLOT(slotDropped(TQDropEvent*)) );
	connect(this, TQ_SIGNAL( contentsMoving( int, int )),
		this, TQ_SLOT( slotContentsMoving( int, int )));
	connect(this, TQ_SIGNAL(currentChanged(TQIconViewItem*)),
		this, TQ_SLOT(slotCurrentChanged(TQIconViewItem*)) );

	TQIconView::setSelectionMode(Extended);

	connect(BusyLevelManager::instance(), TQ_SIGNAL(busyLevelChanged(BusyLevel)),
		this, TQ_SLOT( slotBusyLevelChanged(BusyLevel)));

	connect(d->mThumbnailUpdateTimer, TQ_SIGNAL(timeout()),
		this, TQ_SLOT( startThumbnailUpdate()) );
}


FileThumbnailView::~FileThumbnailView() {
	stopThumbnailUpdate();
	FileViewConfig::setThumbnailDetails(d->mItemDetails);
	FileViewConfig::setThumbnailTextPos( int(itemTextPos()) );
	FileViewConfig::writeConfig();
	delete d;
}


void FileThumbnailView::setThumbnailSize(int value) {
	if (value==d->mThumbnailSize) return;
	d->mThumbnailSize=value;
	updateGrid();
		
	KFileItemListIterator it( *items() );
	for ( ; it.current(); ++it ) {
		KFileItem *item=it.current();
		TQPixmap pixmap=createItemPixmap(item);
		TQIconViewItem* iconItem=viewItem(this, item);
		if (iconItem) iconItem->setPixmap(pixmap);
	}
	arrangeItemsInGrid();
	d->mThumbnailUpdateTimer->start(THUMBNAIL_UPDATE_DELAY, true);
}


int FileThumbnailView::thumbnailSize() const {
	return d->mThumbnailSize;
}


/**
 * Overriden to call updateGrid
 */
void FileThumbnailView::setItemTextPos(ItemTextPos pos) {
	TQIconView::setItemTextPos(pos);
	updateGrid();
}


void FileThumbnailView::setMarginSize(int value) {
	if (value==d->mMarginSize) return;
	d->mMarginSize=value;
	updateGrid();
}


int FileThumbnailView::marginSize() const {
	return d->mMarginSize;
}


void FileThumbnailView::setItemDetails(int details) {
	d->mItemDetails=details;
	for (TQIconViewItem* item=firstItem(); item; item=item->nextItem()) {
		static_cast<FileThumbnailViewItem*>(item)->updateLines();
	}
	arrangeItemsInGrid();
}


int FileThumbnailView::itemDetails() const {
	return d->mItemDetails;
}


void FileThumbnailView::setThumbnailPixmap(const KFileItem* fileItem, const TQPixmap& thumbnail, const TQSize& size) {
	FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
	if (!iconItem) return;

	iconItem->setPixmap(thumbnail);

	// Update item info
	if (size.isValid()) {
		iconItem->setImageSize(size);
	}
	iconItem->repaint();

	// Notify progress
	if (d->mProgressWidget) {
		// mProgressWidget might be null if we get called after the thumbnail
		// job finished. This can happen when the thumbnail job use KPreviewJob
		// to generate a thumbnail.
		d->mProgressWidget->progressBar()->advance(1);
	}
}




void FileThumbnailView::setShownFileItem(KFileItem* fileItem) {
	if( fileItem == mShownFileItem ) return;
	FileThumbnailViewItem* oldShownItem=viewItem(this, mShownFileItem);
	FileThumbnailViewItem* newShownItem=viewItem(this, fileItem);

	FileViewBase::setShownFileItem(fileItem);
	if (oldShownItem) repaintItem(oldShownItem);
	if (newShownItem) repaintItem(newShownItem);
}


//-----------------------------------------------------------------------------
//
// Thumbnail code
//
//-----------------------------------------------------------------------------
TQPixmap FileThumbnailView::createItemPixmap(const KFileItem* item) const {
	bool isDirOrArchive=item->isDir() || Archive::fileItemIsArchive(item);
	if (!isDirOrArchive) {
		if (d->mWaitThumbnail.width()!=d->mThumbnailSize) {
			d->updateWaitThumbnail(this);
		}
		return d->mWaitThumbnail;
	}

	TQPixmap thumbnail(d->mThumbnailSize, d->mThumbnailSize);
	thumbnail.fill(paletteBackgroundColor());
	TQPainter painter(&thumbnail);

	// Load the icon
	TQPixmap itemPix=item->pixmap(TQMIN(d->mThumbnailSize, ThumbnailSize::NORMAL));
	painter.drawPixmap(
		(d->mThumbnailSize-itemPix.width())/2,
		(d->mThumbnailSize-itemPix.height())/2,
		itemPix);

	return thumbnail;
}


void FileThumbnailView::startThumbnailUpdate() {
	// Delay thumbnail update if the widget is not visible
	if (!isVisible()) {
		d->mUpdateThumbnailsOnNextShow=true;
		return;
	}
	d->mUpdateThumbnailsOnNextShow=false;
	stopThumbnailUpdate(); // just in case
	doStartThumbnailUpdate(items());
}


void FileThumbnailView::doStartThumbnailUpdate(const KFileItemList* list) {
	TQValueVector<const KFileItem*> imageList;
	imageList.reserve( list->count());
	TQPtrListIterator<KFileItem> it(*list);
	for (;it.current(); ++it) {
		KFileItem* item=it.current();
		if (!item->isDir() && !Archive::fileItemIsArchive(item)) {
			imageList.append( item );
		}
	}
	if (imageList.empty()) return;

	BusyLevelManager::instance()->setBusyLevel( this, BUSY_THUMBNAILS );

	Q_ASSERT(!d->mProgressWidget);
	d->mProgressWidget=new ProgressWidget(this, imageList.count() );
	
	connect(d->mProgressWidget->stopButton(), TQ_SIGNAL(clicked()),
		this, TQ_SLOT(stopThumbnailUpdate()) );
	d->mProgressWidget->show();
	
	d->mThumbnailLoadJob = new ThumbnailLoadJob(&imageList, d->mThumbnailSize);

	connect(d->mThumbnailLoadJob, TQ_SIGNAL(thumbnailLoaded(const KFileItem*, const TQPixmap&, const TQSize&)),
		this, TQ_SLOT(setThumbnailPixmap(const KFileItem*,const TQPixmap&, const TQSize&)) );
	connect(d->mThumbnailLoadJob, TQ_SIGNAL(result(TDEIO::Job*)),
		this, TQ_SLOT(slotUpdateEnded()) );

	slotBusyLevelChanged( BusyLevelManager::instance()->busyLevel());
	// start updating at visible position
	slotContentsMoving( contentsX(), contentsY());
	d->mThumbnailLoadJob->start();
}


void FileThumbnailView::stopThumbnailUpdate() {
	if (!d->mThumbnailLoadJob.isNull()) {
		d->mThumbnailLoadJob->kill(false);
		d->mThumbnailLoadJob=nullptr;
		// The job loads image dimensions and this may add extra line to the descriptions, which
		// may mess up the grid, even if the job is canceled, some items may be already updated.
		arrangeItemsInGrid();
	}
}


void FileThumbnailView::slotUpdateEnded() {
	Q_ASSERT(d->mProgressWidget);
	delete d->mProgressWidget;
	d->mProgressWidget=0L;

	BusyLevelManager::instance()->setBusyLevel( this, BUSY_NONE );
	// Besides thumbnails the job loads image dimensions and this may mess up the grid
	arrangeItemsInGrid();
}


void FileThumbnailView::updateThumbnail(const KFileItem* fileItem) {
	if (fileItem->isDir() || Archive::fileItemIsArchive(fileItem)) {
		return;
	}

	ThumbnailLoadJob::deleteImageThumbnail(fileItem->url());
	if (d->mThumbnailLoadJob.isNull()) {
		KFileItemList list;
		list.append(fileItem);
		doStartThumbnailUpdate(&list);
	} else {
		d->mThumbnailLoadJob->appendItem(fileItem);
	}
}

// temporarily stop loading thumbnails when busy loading the selected image,
// otherwise thumbnail loading slows it down
void FileThumbnailView::slotBusyLevelChanged(BusyLevel level) {
	if( !d->mThumbnailLoadJob.isNull()) {
		if( level > BUSY_THUMBNAILS ) {
			d->mThumbnailLoadJob->suspend();
		} else {
			d->mThumbnailLoadJob->resume();
		}
	}
}

//-----------------------------------------------------------------------------
//
// KFileView methods
//
//-----------------------------------------------------------------------------
void FileThumbnailView::clearView() {
	stopThumbnailUpdate();
	mShownFileItem=0L;
	TQIconView::clear();
}


void FileThumbnailView::insertItem(KFileItem* item) {
	if (!item) return;
	bool isDirOrArchive=item->isDir() || Archive::fileItemIsArchive(item);

	TQPixmap thumbnail=createItemPixmap(item);
	FileThumbnailViewItem* iconItem=new FileThumbnailViewItem(this,item->text(),thumbnail,item);
	iconItem->setDropEnabled(isDirOrArchive);

	setSortingKey(iconItem, item);
	item->setExtraData(this,iconItem);
}


void FileThumbnailView::updateView(const KFileItem* fileItem, bool metaInfoOnly) {
	if (!fileItem) return;
	FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
	if (iconItem) {
		if (metaInfoOnly) {
			iconItem->updateLines();
			// Note: resort will be done when metadata is completely loaded
		} else {
			iconItem->setText(fileItem->text());
			updateThumbnail(fileItem);
			sortView();
		}
	}
}


void FileThumbnailView::ensureItemVisible(const KFileItem* fileItem) {
	if (!fileItem) return;

	FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
	if (iconItem) TQIconView::ensureItemVisible(iconItem);
}


void FileThumbnailView::setCurrentItem(const KFileItem* fileItem) {
	if (!fileItem) return;

	FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
	if (iconItem) TQIconView::setCurrentItem(iconItem);
}


void FileThumbnailView::setSelected(const KFileItem* fileItem,bool enable) {
	if (!fileItem) return;

	FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
	if (iconItem) TQIconView::setSelected(iconItem, enable, true /* do not unselect others */);
}


bool FileThumbnailView::isSelected(const KFileItem* fileItem) const {
	if (!fileItem) return false;

	FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
	if (!iconItem) return false;

	return iconItem->isSelected();
}


void FileThumbnailView::removeItem(const KFileItem* fileItem) {
	if (!fileItem) return;

	// Remove it from the image preview job
	if (!d->mThumbnailLoadJob.isNull())
		d->mThumbnailLoadJob->itemRemoved(fileItem);

	if (fileItem==mShownFileItem) mShownFileItem=0L;

	// Remove it from our view
	FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
	if (iconItem) delete iconItem;
	KFileView::removeItem(fileItem);
	arrangeItemsInGrid();
}


KFileItem* FileThumbnailView::firstFileItem() const {
	FileThumbnailViewItem* iconItem=static_cast<FileThumbnailViewItem*>(firstItem());
	if (!iconItem) return 0L;
	return iconItem->fileItem();
}


KFileItem* FileThumbnailView::prevItem(const KFileItem* fileItem) const {
	const FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
	if (!iconItem) return 0L;

	iconItem=static_cast<const FileThumbnailViewItem*>(iconItem->prevItem());
	if (!iconItem) return 0L;

	return iconItem->fileItem();
}


KFileItem* FileThumbnailView::currentFileItem() const {
	const TQIconViewItem* iconItem=currentItem();
	if (!iconItem) return 0L;

	return static_cast<const FileThumbnailViewItem*>(iconItem)->fileItem();
}


KFileItem* FileThumbnailView::nextItem(const KFileItem* fileItem) const {
	const FileThumbnailViewItem* iconItem=viewItem(this, fileItem);
	if (!iconItem) return 0L;

	iconItem=static_cast<const FileThumbnailViewItem*>(iconItem->nextItem());
	if (!iconItem) return 0L;

	return iconItem->fileItem();
}


void FileThumbnailView::setSorting(TQDir::SortSpec spec) {
	KFileView::setSorting(spec);

	KFileItem *item;
	KFileItemListIterator it( *items() );

	for ( ; (item = it.current() ); ++it ) {
		TQIconViewItem* iconItem=viewItem(this, item);
		if (iconItem) setSortingKey(iconItem, item);
	}

	sortView();
}

//--------------------------------------------------------------------------
//
// Drop support
//
//--------------------------------------------------------------------------
void FileThumbnailView::contentsDragEnterEvent(TQDragEnterEvent* event) {
	return event->accept( KURLDrag::canDecode(event) );
}


void FileThumbnailView::slotDropped(TQDropEvent* event) {
	emit dropped(event,0L);
}


void FileThumbnailView::showEvent(TQShowEvent* event) {
	TDEIconView::showEvent(event);
	if (!d->mUpdateThumbnailsOnNextShow) return;

	d->mUpdateThumbnailsOnNextShow=false;
	TQTimer::singleShot(0, this, TQ_SLOT(startThumbnailUpdate()));
}


//--------------------------------------------------------------------------
//
// Private
//
//--------------------------------------------------------------------------
void FileThumbnailView::updateGrid() {
	if (itemTextPos()==Right) {
		setGridX(
			d->mThumbnailSize
			+ FileThumbnailViewItem::PADDING*3
			+ RIGHT_TEXT_WIDTH);
	} else {
		setGridX(
			TQMAX(d->mThumbnailSize, BOTTOM_MIN_TEXT_WIDTH)
			+ FileThumbnailViewItem::PADDING*2);
	}
	setSpacing(d->mMarginSize);
}


void FileThumbnailView::setSortingKey(TQIconViewItem *iconItem, const KFileItem *item)
{
	// see also setSorting()
	TQDir::SortSpec spec = KFileView::sorting();
	bool isDirOrArchive=item->isDir() || Archive::fileItemIsArchive(item);

	TQString key;
	if ( spec & TQDir::Time ) {
		time_t time = TimeUtils::getTime(item);
		key=sortingKey(time, isDirOrArchive, spec);
		
	} else if ( spec & TQDir::Size ) {
		key=sortingKey( item->size(), isDirOrArchive, spec );
		
	} else {
		// Name or Unsorted
		key=sortingKey( item->text(), isDirOrArchive, spec );
	}

	iconItem->setKey(key);
}


//--------------------------------------------------------------------------
//
// Private slots
//
//--------------------------------------------------------------------------
void FileThumbnailView::slotDoubleClicked(TQIconViewItem* iconItem) {
	if (!iconItem) return;
	if (TDEGlobalSettings::singleClick()) return;
	FileThumbnailViewItem* thumbItem=static_cast<FileThumbnailViewItem*>(iconItem);

	KFileItem* fileItem=thumbItem->fileItem();

	if (fileItem->isDir() || Archive::fileItemIsArchive(fileItem)) {
		emit executed(iconItem);
	}
}


void FileThumbnailView::slotClicked(TQIconViewItem* iconItem) {
	if (!iconItem) return;
	if (!TDEGlobalSettings::singleClick()) return;
	FileThumbnailViewItem* thumbItem=static_cast<FileThumbnailViewItem*>(iconItem);

	KFileItem* fileItem=thumbItem->fileItem();

	if (fileItem->isDir() || Archive::fileItemIsArchive(fileItem)) {
		emit executed(iconItem);
	}
}

void FileThumbnailView::slotContentsMoving( int x, int y ) {
	updateVisibilityInfo( x, y ); // use x,y, the signal is emitted before moving
}

void FileThumbnailView::slotCurrentChanged(TQIconViewItem* item ) {
	// trigger generating thumbnails from the current one
	updateVisibilityInfo( contentsX(), contentsY());
	prefetchDone();
	// if the first image is selected, no matter how, preload the next one
	for( TQIconViewItem* pos = item;
	     pos != nullptr;
	     pos = pos->nextItem()) {
		FileThumbnailViewItem* cur = static_cast< FileThumbnailViewItem* >( pos );
		if( cur->fileItem()->isDir() || Archive::fileItemIsArchive(cur->fileItem())) continue;
		if( pos == item && pos->nextItem() != nullptr ) {
			d->mPrefetch = ImageLoader::loader(
				static_cast<const FileThumbnailViewItem*>( cur->nextItem() )->fileItem()->url(),
				this, BUSY_PRELOADING );
			connect( d->mPrefetch, TQ_SIGNAL( imageLoaded( bool )), TQ_SLOT( prefetchDone()));
		}
	}
}

/**
 * when generating thumbnails, make the current thumbnail
 * to be the next one processed by the thumbnail job, if visible,
 * otherwise use the first visible thumbnail
 */
void FileThumbnailView::updateVisibilityInfo( int x, int y ) {
	if (d->mThumbnailLoadJob.isNull()) return;
	
	TQRect rect( x, y, visibleWidth(), visibleHeight());
	FileThumbnailViewItem* first = static_cast< FileThumbnailViewItem* >( findFirstVisibleItem( rect ));
	if (!first) {
		d->mThumbnailLoadJob->setPriorityItems(nullptr,nullptr,nullptr);
		return;
	}
	
	FileThumbnailViewItem* last = static_cast< FileThumbnailViewItem* >( findLastVisibleItem( rect ));
	Q_ASSERT(last); // If we get a first item, then there must be a last
	
	if (currentItem() && currentItem()->intersects(rect)) {
		KFileItem* fileItem = currentFileItem();
		d->mThumbnailLoadJob->setPriorityItems(fileItem,
			first->fileItem(), last->fileItem());
		return;
	}
	
	d->mThumbnailLoadJob->setPriorityItems(
		first->fileItem(),
		first->fileItem(),
		last->fileItem());
}

void FileThumbnailView::keyPressEvent( TQKeyEvent* e ) {
// When the user presses e.g. the Down key, try to preload the next image in that direction.
	if( e->key() != Key_Left
		&& e->key() != Key_Right
		&& e->key() != Key_Up
		&& e->key() != Key_Down ) return TDEIconView::keyPressEvent( e );

	TQIconViewItem* current = currentItem();
	TDEIconView::keyPressEvent( e );
	TQIconViewItem* next = nullptr;
	if( current != currentItem() && currentItem() != nullptr ) { // it actually moved
		switch( e->key()) {
			case Key_Left:
				next = currentItem()->prevItem();
				break;
			case Key_Right:
				next = currentItem()->nextItem();
				break;
			case Key_Up:
			// This relies on the thumbnails being in a grid ( x() == x() )
				for( next = currentItem()->prevItem();
				     next != nullptr && next->x() != currentItem()->x();
				     next = next->prevItem())
					;
				break;
			case Key_Down:
				for( next = currentItem()->nextItem();
				     next != nullptr && next->x() != currentItem()->x();
				     next = next->nextItem())
					;
				break;
		}

	}
	prefetchDone();
	if( next != nullptr ) {
		d->mPrefetch = ImageLoader::loader(
			static_cast<const FileThumbnailViewItem*>( next )->fileItem()->url(),
			this, BUSY_PRELOADING );
		connect( d->mPrefetch, TQ_SIGNAL( imageLoaded( bool )), TQ_SLOT( prefetchDone()));
	}
}

void FileThumbnailView::prefetchDone() {
	if( d->mPrefetch != nullptr ) {
		d->mPrefetch->release( this );
		d->mPrefetch = nullptr;
	}
}

void FileThumbnailView::sortView() {
	TDEIconView::sort( !(KFileView::sorting() & TQDir::Reversed) );
}

//--------------------------------------------------------------------------
//
// Protected
//
//--------------------------------------------------------------------------
void FileThumbnailView::startDrag() {
	/**
	 * The item drawer for DragPixmapGenerator
	 */
	struct ItemDrawer : public DragPixmapItemDrawer<KFileItem*> {
		ItemDrawer(FileThumbnailView* view)
		: mView(view) {}

		TQSize itemSize(KFileItem* fileItem) {
			TQPixmap* pix = pixmapFromFileItem(fileItem);
			if (!pix) return TQSize();
					
			TQSize size = pix->size();
			int maxWidth = mGenerator->maxWidth();
			if (size.width() > maxWidth) {
				size.rheight() = size.height() * maxWidth / size.width();
				size.rwidth() = maxWidth;
			}
			return size;
		}

		int spacing() const {
			return 2;
		}
			
		void drawItem(TQPainter* painter, int left, int top, KFileItem* fileItem) {
			TQPixmap* pix = pixmapFromFileItem(fileItem);
			if (!pix) return;

			TQSize size = itemSize(fileItem);
			left += (mGenerator->pixmapWidth() - size.width()) / 2;
			if (size == pix->size()) {
				painter->drawPixmap(left, top, *pix);
				return;
			}
			
			TQImage img = pix->convertToImage();
			img = img.smoothScale(size, TQImage::ScaleMin);
			painter->drawImage(left, top, img);
		}

		TQPixmap* pixmapFromFileItem(KFileItem* fileItem) {
			FileThumbnailViewItem* iconItem = viewItem(mView, fileItem);
			Q_ASSERT(iconItem);
			if (!iconItem) return 0;
			
			TQPixmap* pix = iconItem->pixmap();
			Q_ASSERT(pix);
			if (!pix) return 0;
			return pix;
		}
		
		FileThumbnailView* mView;
	};
	ItemDrawer drawer(this);


	KURL::List urls;
	KFileItemListIterator it(*KFileView::selectedItems());

	DragPixmapGenerator<KFileItem*> generator;
	generator.setItemDrawer(&drawer);
	
	for ( ; it.current(); ++it ) {
		urls.append(it.current()->url());
		generator.addItem(it.current());
	}

	if (urls.isEmpty()) {
		kdWarning() << "No item to drag\n";
		return;
	}

	TQDragObject* drag=new KURLDrag(urls, this, 0);
	TQPixmap dragPixmap = generator.generate();

	drag->setPixmap( dragPixmap, TQPoint(generator.DRAG_OFFSET, -generator.DRAG_OFFSET));
	drag->dragCopy();
}


void FileThumbnailView::showThumbnailDetailsDialog() {
	if (!d->mThumbnailsDetailDialog) {
		d->mThumbnailsDetailDialog = new ThumbnailDetailsDialog(this);
	}
	d->mThumbnailsDetailDialog->show();
}


} // namespace
