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

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 <tqcheckbox.h>
#include <tqdatetimeedit.h>
#include <tqhbox.h>
#include <tqlayout.h>
#include <tqpopupmenu.h>
#include <tqpushbutton.h>
#include <tqtooltip.h>
#include <tqwidgetstack.h>

// KDE
#include <tdeaction.h>
#include <tdeapplication.h>
#include <kcombobox.h>
#include <kdebug.h>
#include <kdirlister.h>
#include <kicontheme.h>
#include <kiconeffect.h>
#include <kiconloader.h>
#include <kimageio.h>
#include <tdelistview.h>
#include <tdelocale.h>
#include <kpropertiesdialog.h>
#include <kprotocolinfo.h>
#include <kstdaction.h>
#include <tdetoolbar.h>
#include <tdetoolbarlabelaction.h>
#include <kurldrag.h>
#include <tdeio/job.h>
#include <tdeio/file.h>
#include <tdeio/metainfojob.h>

// Local
#include "archive.h"
#include "cache.h"
#include "clicklineedit.h"
#include "cursortracker.h"
#include "filedetailview.h"
#include "fileoperation.h"
#include "filethumbnailview.h"
#include "filterbar.h"
#include "imageloader.h"
#include "mimetypeutils.h"
#include "timeutils.h"
#include "thumbnailsize.h"
#include "fileviewconfig.h"
#include "miscconfig.h"

#include "fileviewcontroller.moc"
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 const int SLIDER_RESOLUTION=4;


//-----------------------------------------------------------------------
//
// internal class which allows dynamically turning off visual error reporting
// 
//-----------------------------------------------------------------------
class DirLister : public KDirLister {
public:
	DirLister()
	: KDirLister()
	, mError(false)
	, mCheck(false) {}

	virtual bool validURL(const KURL& url) const {
		if( !url.isValid()) mError = true;
		if( mCheck ) return KDirLister::validURL( url );
		return url.isValid();
	}

	virtual void handleError(TDEIO::Job* job) {
		mError = true;
		if(mCheck) KDirLister::handleError( job );
	};

	bool error() const {
		return mError;
	}

	void clearError() {
		mError = false;
	}

	void setCheck(bool c) {
		mCheck = c;
	}

	void setDateFilter(const TQDate& from, const TQDate& to) {
		mFromDate = from;
		mToDate =to;
	}

	virtual bool itemMatchFilters(const KFileItem* item) const {
		if (!matchesFilter(item)) return false;
		return matchesMimeFilter(item);
	}

public:
	virtual bool matchesMimeFilter(const KFileItem* item) const {
		// Do mime filtering ourself because we use startsWith instead of ==
		TQStringList lst = mimeFilters();
		TQStringList::Iterator it = lst.begin(), end = lst.end();
		bool result = false;
		TQString type = item->mimetype();
		for (; it!=end; ++it) {
			if (type.startsWith(*it)) {
				result = true;
				break;
			}
		}
		if (!result) return false;

		if (item->isDir() || Archive::fileItemIsArchive(item)) {
			// Do not filter out dirs or archives
			return true;
		}

		if (!mFromDate.isValid() && !mToDate.isValid()) return result;

		// Convert item time to a TQDate
		time_t time=TimeUtils::getTime(item);
		TQDateTime dateTime;
		dateTime.setTime_t(time);
		TQDate date=dateTime.date();

		if (mFromDate.isValid() && date < mFromDate) return false;
		if (mToDate.isValid() && date > mToDate) return false;
		return true;
	}

private:
	mutable bool mError;
	bool mCheck;
	TQDate mFromDate;
	TQDate mToDate;
};


//-----------------------------------------------------------------------
//
// FileViewController::Private
//
//-----------------------------------------------------------------------
struct FileViewController::Private {
	~Private() {
		delete mSliderTracker;
	}
	FileViewController* that;
	FilterBar* mFilterBar;
	TDEToolBar* mToolBar;
	TQWidgetStack* mStack;
	TDESelectAction* mSortAction;
	TDEToggleAction* mRevertSortAction;
	TipTracker* mSliderTracker;

	TQHBox* mFilterHBox;
	TQComboBox* mFilterComboBox;
	TQCheckBox* mShowFilterBarCheckBox;

	TDEIO::MetaInfoJob* mMetaInfoJob;
	KFileItemList mMetaInfoTodo;

	void initFilterBar() {
		mFilterBar=new FilterBar(that);
		mFilterBar->setSizePolicy(TQSizePolicy::Preferred, TQSizePolicy::Fixed);
		mFilterBar->hide();

		TQIconSet resetIS=BarIcon("locationbar_erase");
		mFilterBar->mResetNameCombo->setIconSet(resetIS);
		mFilterBar->mResetFrom->setIconSet(resetIS);
		mFilterBar->mResetTo->setIconSet(resetIS);

		TQObject::connect(
			mFilterBar->mResetNameCombo, TQ_SIGNAL(clicked()),
			that, TQ_SLOT(resetNameFilter()) );
		TQObject::connect(
			mFilterBar->mResetFrom, TQ_SIGNAL(clicked()),
			that, TQ_SLOT(resetFromFilter()) );
		TQObject::connect(
			mFilterBar->mResetTo, TQ_SIGNAL(clicked()),
			that, TQ_SLOT(resetToFilter()) );

		TQObject::connect(
			mFilterBar->mFilterButton, TQ_SIGNAL(clicked()),
			that, TQ_SLOT(applyFilter()) );
	}

	void initFilterCombo() {
		mFilterHBox=new TQHBox(mToolBar, "tde toolbar widget");
		mFilterHBox->setSpacing(KDialog::spacingHint());

		mFilterComboBox=new TQComboBox(mFilterHBox);
		mFilterComboBox->insertItem(i18n("All files"), ALL);
		mFilterComboBox->insertItem(i18n("Images only"), IMAGES_ONLY);
		mFilterComboBox->insertItem(i18n("Videos only"), VIDEOS_ONLY);

		TQObject::connect(
			mFilterComboBox, TQ_SIGNAL(activated(int)),
			that, TQ_SLOT(applyFilter()) );

		mShowFilterBarCheckBox = new TQCheckBox(i18n("More"), mFilterHBox);
		TQObject::connect(
			mShowFilterBarCheckBox, TQ_SIGNAL(toggled(bool)),
			mFilterBar, TQ_SLOT(setShown(bool)) );
		TQObject::connect(
			mShowFilterBarCheckBox, TQ_SIGNAL(toggled(bool)),
			that, TQ_SLOT(applyFilter()) );
	}


	void loadFilterSettings() {
		mFilterComboBox->setCurrentItem(FileViewConfig::filterMode());
		mShowFilterBarCheckBox->setChecked(FileViewConfig::showFilterBar());
		mFilterBar->mNameEdit->setText(FileViewConfig::nameFilter());
		mFilterBar->mFromDateEdit->setDate(FileViewConfig::fromDateFilter().date());
		mFilterBar->mToDateEdit->setDate(FileViewConfig::toDateFilter().date());
	}
};


//-----------------------------------------------------------------------
//
// FileViewController
//
//-----------------------------------------------------------------------
FileViewController::FileViewController(TQWidget* parent,TDEActionCollection* actionCollection)
: TQWidget(parent)
, mMode(FILE_LIST)
, mPrefetch( NULL )
, mChangeDirStatus(CHANGE_DIR_STATUS_NONE)
, mBrowsing(false)
, mSelecting(false)
{
	d=new Private;
	d->that=this;
	setMinimumWidth(1);
	d->mToolBar=new TDEToolBar(this, "", true);
	d->initFilterBar();
	d->initFilterCombo();
	d->mStack=new TQWidgetStack(this);
	d->mMetaInfoJob=NULL;
	
	TQVBoxLayout *layout=new TQVBoxLayout(this);
	layout->addWidget(d->mToolBar);
	layout->addWidget(d->mFilterBar);
	layout->addWidget(d->mStack);

	// Actions
	mSelectFirst=new TDEAction(i18n("&First"),
		TQApplication::reverseLayout() ? "2rightarrow":"2leftarrow", Key_Home,
		this,TQ_SLOT(slotSelectFirst()), actionCollection, "first");

	mSelectLast=new TDEAction(i18n("&Last"),
		TQApplication::reverseLayout() ? "2leftarrow":"2rightarrow", Key_End,
		this,TQ_SLOT(slotSelectLast()), actionCollection, "last");

	mSelectPrevious=new TDEAction(i18n("&Previous"),
		TQApplication::reverseLayout() ? "1rightarrow":"1leftarrow", Key_BackSpace,
		this,TQ_SLOT(slotSelectPrevious()), actionCollection, "previous");

	mSelectNext=new TDEAction(i18n("&Next"),
		TQApplication::reverseLayout() ? "1leftarrow":"1rightarrow", Key_Space,
		this,TQ_SLOT(slotSelectNext()), actionCollection, "next");

	mSelectPreviousDir=new TDEAction(i18n("&Previous Folder"),
		TQApplication::reverseLayout() ? "media-seek-forward":"media-seek-backward", ALT + Key_BackSpace,
		this,TQ_SLOT(slotSelectPreviousDir()), actionCollection, "previous_folder");

	mSelectNextDir=new TDEAction(i18n("&Next Folder"),
		TQApplication::reverseLayout() ? "media-seek-backward":"media-seek-forward", ALT + Key_Space,
		this,TQ_SLOT(slotSelectNextDir()), actionCollection, "next_folder");

	mSelectFirstSubDir=new TDEAction(i18n("&First Sub Folder"), "go-down", ALT + Key_Down,
		this,TQ_SLOT(slotSelectFirstSubDir()), actionCollection, "first_sub_folder");

	mListMode=new TDERadioAction(i18n("Details"),"view_detailed",0,this,TQ_SLOT(updateViewMode()),actionCollection,"list_mode");
	mListMode->setExclusiveGroup("thumbnails");
	mSideThumbnailMode=new TDERadioAction(i18n("Thumbnails with Info on Side"),"view_multicolumn",0,this,TQ_SLOT(updateViewMode()),actionCollection,"side_thumbnail_mode");
	mSideThumbnailMode->setExclusiveGroup("thumbnails");
	mBottomThumbnailMode=new TDERadioAction(i18n("Thumbnails with Info on Bottom"),"view_icon",0,this,TQ_SLOT(updateViewMode()),actionCollection,"bottom_thumbnail_mode");
	mBottomThumbnailMode->setExclusiveGroup("thumbnails");

	// Size slider
	mSizeSlider=new TQSlider(Horizontal, d->mToolBar);
	mSizeSlider->setFixedWidth(120);
	mSizeSlider->setRange(
		ThumbnailSize::MIN/SLIDER_RESOLUTION,
		ThumbnailSize::LARGE/SLIDER_RESOLUTION);
	mSizeSlider->setValue(FileViewConfig::thumbnailSize() / SLIDER_RESOLUTION);

	connect(mSizeSlider, TQ_SIGNAL(valueChanged(int)), this, TQ_SLOT(updateThumbnailSize(int)) );
	connect(mListMode, TQ_SIGNAL(toggled(bool)), mSizeSlider, TQ_SLOT(setDisabled(bool)) );
	TDEAction* sliderAction=new KWidgetAction(mSizeSlider, i18n("Thumbnail Size"), 0, 0, 0, actionCollection, "size_slider");
	d->mSliderTracker=new TipTracker("", mSizeSlider);
	// /Size slider

	mShowDotFiles=new TDEToggleAction(i18n("Show &Hidden Files"),CTRL + Key_H,this,TQ_SLOT(toggleShowDotFiles()),actionCollection,"show_dot_files");

	d->mSortAction=new TDESelectAction(i18n("Sort"), 0, this, TQ_SLOT(setSorting()), actionCollection, "view_sort");
	TQStringList sortItems;
	sortItems << i18n("By Name") << i18n("By Date") << i18n("By Size");
	d->mSortAction->setItems(sortItems);
	d->mSortAction->setCurrentItem(0);

	d->mRevertSortAction=new TDEToggleAction(i18n("Descending"),0, this, TQ_SLOT(setSorting()), actionCollection, "descending");
	TQPopupMenu* sortMenu=d->mSortAction->popupMenu();
	Q_ASSERT(sortMenu);
	sortMenu->insertSeparator();
	d->mRevertSortAction->plug(sortMenu);

	// Dir lister
	mDirLister=new DirLister;
	mDirLister->setMainWindow(topLevelWidget());
	connect(mDirLister,TQ_SIGNAL(clear()),
		this,TQ_SLOT(dirListerClear()) );

	connect(mDirLister,TQ_SIGNAL(newItems(const KFileItemList&)),
		this,TQ_SLOT(dirListerNewItems(const KFileItemList&)) );

	connect(mDirLister,TQ_SIGNAL(deleteItem(KFileItem*)),
		this,TQ_SLOT(dirListerDeleteItem(KFileItem*)) );

	connect(mDirLister,TQ_SIGNAL(refreshItems(const KFileItemList&)),
		this,TQ_SLOT(dirListerRefreshItems(const KFileItemList&)) );

	connect(mDirLister,TQ_SIGNAL(started(const KURL&)),
		this,TQ_SLOT(dirListerStarted()) );

	connect(mDirLister,TQ_SIGNAL(completed()),
		this,TQ_SLOT(dirListerCompleted()) );

	connect(mDirLister,TQ_SIGNAL(canceled()),
		this,TQ_SLOT(dirListerCanceled()) );

	// Propagate canceled signals
	connect(mDirLister,TQ_SIGNAL(canceled()),
		this,TQ_SIGNAL(canceled()) );

	// File detail widget
	mFileDetailView=new FileDetailView(d->mStack, "filedetailview");
	d->mStack->addWidget(mFileDetailView,0);
	mFileDetailView->viewport()->installEventFilter(this);

	connect(mFileDetailView,TQ_SIGNAL(executed(TQListViewItem*)),
		this,TQ_SLOT(slotViewExecuted()) );
	connect(mFileDetailView,TQ_SIGNAL(returnPressed(TQListViewItem*)),
		this,TQ_SLOT(slotViewExecuted()) );
	connect(mFileDetailView,TQ_SIGNAL(currentChanged(TQListViewItem*)),
		this,TQ_SLOT(slotViewClicked()) );
	connect(mFileDetailView,TQ_SIGNAL(selectionChanged()),
		this,TQ_SLOT(slotViewClicked()) );
	connect(mFileDetailView,TQ_SIGNAL(clicked(TQListViewItem*)),
		this,TQ_SLOT(slotViewClicked()) );
	connect(mFileDetailView,TQ_SIGNAL(contextMenu(TDEListView*, TQListViewItem*, const TQPoint&)),
		this,TQ_SLOT(openContextMenu(TDEListView*, TQListViewItem*, const TQPoint&)) );
	connect(mFileDetailView,TQ_SIGNAL(dropped(TQDropEvent*,KFileItem*)),
		this,TQ_SLOT(openDropURLMenu(TQDropEvent*, KFileItem*)) );
	connect(mFileDetailView, TQ_SIGNAL(sortingChanged(TQDir::SortSpec)),
		this, TQ_SLOT(updateSortMenu(TQDir::SortSpec)) );
	connect(mFileDetailView, TQ_SIGNAL(doubleClicked(TQListViewItem*)),
		this, TQ_SLOT(slotViewDoubleClicked()) );
	connect(mFileDetailView, TQ_SIGNAL(selectionChanged()),
		this, TQ_SIGNAL(selectionChanged()) );

	// Thumbnail widget
	mFileThumbnailView=new FileThumbnailView(d->mStack);
	d->mStack->addWidget(mFileThumbnailView,1);
	mFileThumbnailView->viewport()->installEventFilter(this);

	connect(mFileThumbnailView,TQ_SIGNAL(executed(TQIconViewItem*)),
		this,TQ_SLOT(slotViewExecuted()) );
	connect(mFileThumbnailView,TQ_SIGNAL(returnPressed(TQIconViewItem*)),
		this,TQ_SLOT(slotViewExecuted()) );
	connect(mFileThumbnailView,TQ_SIGNAL(currentChanged(TQIconViewItem*)),
		this,TQ_SLOT(slotViewClicked()) );
	connect(mFileThumbnailView,TQ_SIGNAL(selectionChanged()),
		this,TQ_SLOT(slotViewClicked()) );
	connect(mFileThumbnailView,TQ_SIGNAL(clicked(TQIconViewItem*)),
		this,TQ_SLOT(slotViewClicked()) );
	connect(mFileThumbnailView,TQ_SIGNAL(contextMenuRequested(TQIconViewItem*,const TQPoint&)),
		this,TQ_SLOT(openContextMenu(TQIconViewItem*,const TQPoint&)) );
	connect(mFileThumbnailView,TQ_SIGNAL(dropped(TQDropEvent*,KFileItem*)),
		this,TQ_SLOT(openDropURLMenu(TQDropEvent*, KFileItem*)) );
	connect(mFileThumbnailView, TQ_SIGNAL(doubleClicked(TQIconViewItem*)),
		this, TQ_SLOT(slotViewDoubleClicked()) );
	connect(mFileThumbnailView, TQ_SIGNAL(selectionChanged()),
		this, TQ_SIGNAL(selectionChanged()) );
	
	// Thumbnail details dialog
	TDEAction* thumbnailDetailsDialogAction=new TDEAction(i18n("Edit Thumbnail Details..."), "configure", 0, mFileThumbnailView, TQ_SLOT(showThumbnailDetailsDialog()), actionCollection, "thumbnail_details_dialog");
	connect(mBottomThumbnailMode, TQ_SIGNAL(toggled(bool)),
		thumbnailDetailsDialogAction, TQ_SLOT(setEnabled(bool)) );
	
	// Fill toolbar
	mListMode->plug(d->mToolBar);
	mSideThumbnailMode->plug(d->mToolBar);
	mBottomThumbnailMode->plug(d->mToolBar);
	d->mToolBar->insertSeparator();
	sliderAction->plug(d->mToolBar);
	d->mToolBar->insertSeparator();
	thumbnailDetailsDialogAction->plug(d->mToolBar);

	int id=d->mToolBar->insertWidget(-1, 0, d->mFilterHBox);
	d->mToolBar->alignItemRight(id, true);

	mShowDotFiles->setChecked(FileViewConfig::showDotFiles());

	bool startWithThumbnails=FileViewConfig::startWithThumbnails();
	setMode(startWithThumbnails?THUMBNAIL:FILE_LIST);
	mSizeSlider->setEnabled(startWithThumbnails);

	if (startWithThumbnails) {
		if (mFileThumbnailView->itemTextPos()==TQIconView::Right) {
			mSideThumbnailMode->setChecked(true);
		} else {
			mBottomThumbnailMode->setChecked(true);
		}
		// Make sure the thumbnail view and the slider tooltip are updated
		updateThumbnailSize(mSizeSlider->value());
		mFileThumbnailView->startThumbnailUpdate();
	} else {
		mListMode->setChecked(true);
	}
	thumbnailDetailsDialogAction->setEnabled(mBottomThumbnailMode->isChecked());

	if (MiscConfig::rememberFilter()) {
		d->loadFilterSettings();
	}
	updateFromSettings();
}

	
FileViewController::~FileViewController() {
	// Save various settings
	FileViewConfig::setStartWithThumbnails(mMode==THUMBNAIL);
	
	int filterMode = d->mFilterComboBox->currentItem();
	FileViewConfig::setFilterMode(filterMode);
	
	FileViewConfig::setShowFilterBar(d->mShowFilterBarCheckBox->isChecked());
	FileViewConfig::setNameFilter(d->mFilterBar->mNameEdit->text());
	FileViewConfig::setFromDateFilter(d->mFilterBar->mFromDateEdit->date());
	FileViewConfig::setToDateFilter(d->mFilterBar->mToDateEdit->date());
	
	FileViewConfig::writeConfig();
	stopMetaInfoUpdate();
	delete mDirLister;
	delete d;
}


void FileViewController::setFocus() {
	currentFileView()->widget()->setFocus();
}


/**
 * Do not let double click events propagate if Ctrl or Shift is down, to avoid
 * toggling fullscreen
 */
bool FileViewController::eventFilter(TQObject*, TQEvent* event) {
	if (event->type()!=TQEvent::MouseButtonDblClick) return false;

	TQMouseEvent* mouseEvent=static_cast<TQMouseEvent*>(event);
	if (mouseEvent->state() & TQt::ControlButton || mouseEvent->state() & TQt::ShiftButton) {
		return true;
	}
	return false;
}


bool FileViewController::lastURLError() const {
	return mDirLister->error();
}


//-----------------------------------------------------------------------
//
// Public slots
//
//-----------------------------------------------------------------------
void FileViewController::setDirURL(const KURL& url) {
	LOG(url.prettyURL());
	if ( mDirURL.equals(url,true) ) {
		LOG("Same URL");
		return;
	}
	prefetchDone();
	mDirURL=url;
	if (!KProtocolInfo::supportsListing(mDirURL)) {
		LOG("Protocol does not support listing");
		return;
	}

	mDirLister->clearError();
	currentFileView()->setShownFileItem(0L);
	mFileNameToSelect=TQString();
	mDirLister->openURL(mDirURL);
	emit urlChanged(mDirURL);
	emit directoryChanged(mDirURL);
	updateActions();
}


/**
 * Sets the file to select once the dir lister is done. If it's not running,
 * immediatly selects the file.
 */
void FileViewController::setFileNameToSelect(const TQString& fileName) {
	mFileNameToSelect=fileName;
	if (mDirLister->isFinished()) {
		browseToFileNameToSelect();
	}
}

void FileViewController::prefetch( KFileItem* item ) {
	prefetchDone();
	if( item == NULL ) return;
	mPrefetch = ImageLoader::loader( item->url(), this, BUSY_PRELOADING );
	connect( mPrefetch, TQ_SIGNAL( imageLoaded( bool )), TQ_SLOT( prefetchDone()));
}

void FileViewController::prefetchDone() {
	if( mPrefetch != NULL ) {
		mPrefetch->release( this );
		mPrefetch = NULL;
	}
}

void FileViewController::slotSelectFirst() {
	browseTo( findFirstImage());
	prefetch( findNextImage());
}

void FileViewController::slotSelectLast() {
	browseTo(findLastImage());
	prefetch( findPreviousImage());
}

void FileViewController::slotSelectPrevious() {
	browseTo(findPreviousImage());
	prefetch( findPreviousImage());
}

void FileViewController::slotSelectNext() {
	browseTo(findNextImage());
	prefetch( findNextImage());
}

void FileViewController::slotSelectPreviousDir() {
	mChangeDirStatus = CHANGE_DIR_STATUS_PREV;
	mDirLister->clearError();
	mDirLister->openURL(mDirURL.upURL());
}

void FileViewController::slotSelectNextDir() {
	mChangeDirStatus = CHANGE_DIR_STATUS_NEXT;
	mDirLister->clearError();
	mDirLister->openURL(mDirURL.upURL());
}

void FileViewController::slotSelectFirstSubDir() {
	KFileItem* item=currentFileView()->firstFileItem();
	while (item && !Archive::fileItemIsDirOrArchive(item)) {
		item=currentFileView()->nextItem(item);
	}
	if (!item) {
		LOG("No item found");
		return;
	}
	LOG("item->url(): " << item->url().prettyURL());
	KURL tmp=item->url();
	if (Archive::fileItemIsArchive(item)) {
		tmp.setProtocol(Archive::protocolForMimeType(item->mimetype()));
	}
	tmp.adjustPath(1);
	setDirURL(tmp);
}


void FileViewController::resetNameFilter() {
	d->mFilterBar->mNameEdit->clear();
}


void FileViewController::resetFromFilter() {
	d->mFilterBar->mFromDateEdit->setDate(TQDate());
}


void FileViewController::resetToFilter() {
	d->mFilterBar->mToDateEdit->setDate(TQDate());
}

	
void FileViewController::browseTo(KFileItem* item) {
	prefetchDone();
	if (mBrowsing) return;
	mBrowsing = true;
	if (item) {
		currentFileView()->setCurrentItem(item);
		currentFileView()->clearSelection();
		currentFileView()->setSelected(item,true);
		currentFileView()->ensureItemVisible(item);
		if (!item->isDir() && !Archive::fileItemIsArchive(item)) {
			emitURLChanged();
		}
	}
	updateActions();
	mBrowsing = false;
}


void FileViewController::browseToFileNameToSelect() {
	// There's something to select
	if (!mFileNameToSelect.isEmpty()) {
		browseTo(findItemByFileName(mFileNameToSelect));
		mFileNameToSelect=TQString();
		return;
	}

	// Nothing to select, but an item is already shown
	if (currentFileView()->shownFileItem()) return;

	// Now we have to make some default choice
	slotSelectFirst();

	// If no item is selected, make sure the first one is
	if (currentFileView()->selectedItems()->count()==0) {
		KFileItem* item=currentFileView()->firstFileItem();
		if (item) {
			currentFileView()->setCurrentItem(item);
			currentFileView()->setSelected(item, true);
			currentFileView()->ensureItemVisible(item);
		}
	}
}


void FileViewController::updateThumbnail(const KURL& url) {
	if (mMode==FILE_LIST) return;

	KFileItem* item=mDirLister->findByURL(url);
	if (!item) return;
	mFileThumbnailView->updateThumbnail(item);
}


//-----------------------------------------------------------------------
//
// Private slots
//
//-----------------------------------------------------------------------
void FileViewController::slotViewExecuted() {
	KFileItem* item=currentFileView()->currentFileItem();
	if (!item) return;

	bool isDir=item->isDir();
	bool isArchive=Archive::fileItemIsArchive(item);
	if (isDir || isArchive) {
		KURL tmp=url();

		if (isArchive) {
			tmp.setProtocol(Archive::protocolForMimeType(item->mimetype()));
		}
		tmp.adjustPath(1);
		setDirURL(tmp);
	} else {
		emitURLChanged();
	}
}


void FileViewController::slotViewClicked() {
	updateActions();
	KFileItem* item=currentFileView()->currentFileItem();
	if (!item || Archive::fileItemIsDirOrArchive(item)) return;

	mSelecting = true;
	emitURLChanged();
	mSelecting = false;
}


void FileViewController::slotViewDoubleClicked() {
	updateActions();
	KFileItem* item=currentFileView()->currentFileItem();
	if (item && !Archive::fileItemIsDirOrArchive(item)) emit imageDoubleClicked();
}


void FileViewController::updateViewMode() {
	KFileItem* currentItem=currentFileView()->currentFileItem();
	if (mListMode->isChecked()) {
		setMode(FILE_LIST);
		return;
	}
	if (mSideThumbnailMode->isChecked()) {
		mFileThumbnailView->setItemTextPos(TQIconView::Right);
	} else {
		mFileThumbnailView->setItemTextPos(TQIconView::Bottom);
	}

	// Only switch the view if we are going from no thumbs to either side or
	// bottom thumbs, not when switching between side and bottom thumbs
	if (mMode==FILE_LIST) {
		setMode(THUMBNAIL);
	} else {
		KFileItemList items=*mFileThumbnailView->items();
		KFileItem* shownFileItem=mFileThumbnailView->shownFileItem();

		mFileThumbnailView->FileViewBase::clear();
		mFileThumbnailView->addItemList(items);
		mFileThumbnailView->setShownFileItem(shownFileItem);
	}

	browseTo(currentItem);
	updateThumbnailSize(mSizeSlider->value());
	mFileThumbnailView->startThumbnailUpdate();
}


void FileViewController::updateThumbnailSize(int size) {
	size*=SLIDER_RESOLUTION;
	d->mSliderTracker->setText(i18n("Thumbnail size: %1x%2").arg(size).arg(size));
	FileViewConfig::setThumbnailSize(size);
	mFileThumbnailView->setThumbnailSize(size);
	Cache::instance()->checkThumbnailSize(size);
}


void FileViewController::toggleShowDotFiles() {
	mDirLister->setShowingDotFiles(mShowDotFiles->isChecked());
	mDirLister->openURL(mDirURL);
}


void FileViewController::updateSortMenu(TQDir::SortSpec _spec) {
	int	spec=_spec & (TQDir::Name | TQDir::Time | TQDir::Size);
	int item;
	switch (spec) {
	case TQDir::Name:
		item=0;
		break;
	case TQDir::Time:
		item=1;
		break;
	case TQDir::Size:
		item=2;
		break;
	default:
		item=-1;
		break;
	}
	d->mSortAction->setCurrentItem(item);
}


void FileViewController::setSorting() {
	TQDir::SortSpec spec;

	switch (d->mSortAction->currentItem()) {
	case 0: // Name
		spec=TQDir::Name;
		break;
	case 1: // Date
		spec=TQDir::Time;
		break;
	case 2: // Size
		spec=TQDir::Size;
		break;
	default:
		return;
	}
	if (d->mRevertSortAction->isChecked()) {
		spec=TQDir::SortSpec(spec | TQDir::Reversed);
	}
	currentFileView()->setSorting(TQDir::SortSpec(spec | TQDir::DirsFirst));
	emit sortingChanged();
}


//-----------------------------------------------------------------------
//
// Context menu
//
//-----------------------------------------------------------------------
void FileViewController::openContextMenu(TDEListView*,TQListViewItem* item,const TQPoint& pos) {
	emit requestContextMenu(pos, item!=0);
}


void FileViewController::openContextMenu(TQIconViewItem* item,const TQPoint& pos) {
	emit requestContextMenu(pos, item!=0);
}


//-----------------------------------------------------------------------
//
// Drop URL menu
//
//-----------------------------------------------------------------------
void FileViewController::openDropURLMenu(TQDropEvent* event, KFileItem* item) {
	KURL dest;

	if (item) {
		dest=item->url();
	} else {
		dest=mDirURL;
	}

	KURL::List urls;
	if (!KURLDrag::decode(event,urls)) return;

	FileOperation::openDropURLMenu(d->mStack, urls, dest);
}


//-----------------------------------------------------------------------
//
// File operations
//
//-----------------------------------------------------------------------
KURL::List FileViewController::selectedURLs() const {
	KURL::List list;

	KFileItemListIterator it( *currentFileView()->selectedItems() );
	for ( ; it.current(); ++it ) {
		list.append(it.current()->url());
	}
	if (list.isEmpty()) {
		const KFileItem* item=currentFileView()->shownFileItem();
		if (item) list.append(item->url());
	}
	return list;
}


KURL::List FileViewController::selectedImageURLs() const {
	KURL::List list;

	KFileItemListIterator it( *currentFileView()->selectedItems() );
	for ( ; it.current(); ++it ) {
		KFileItem* item=it.current();
		if (!Archive::fileItemIsDirOrArchive(item)) {
			list.append(item->url());
		}
	}
	if (list.isEmpty()) {
		const KFileItem* item=currentFileView()->shownFileItem();
		if (item && !Archive::fileItemIsDirOrArchive(item)) list.append(item->url());
	}
	return list;
}


//-----------------------------------------------------------------------
//
// Properties
//
//-----------------------------------------------------------------------
TQString FileViewController::fileName() const {
	KFileItem* item=currentFileView()->currentFileItem();
	if (!item) return "";
	return item->text();
}


FileViewBase* FileViewController::currentFileView() const {
	if (mMode==FILE_LIST) {
		return mFileDetailView;
	} else {
		return mFileThumbnailView;
	}
}


uint FileViewController::fileCount() const {
	uint count=currentFileView()->count();

	KFileItem* item=currentFileView()->firstFileItem();
	while (item && Archive::fileItemIsDirOrArchive(item)) {
		item=currentFileView()->nextItem(item);
		count--;
	}
	return count;
}


int FileViewController::shownFilePosition() const {
	KFileItem* shownItem=currentFileView()->shownFileItem();
	if (!shownItem) return -1;
	KFileItem* item=currentFileView()->firstFileItem();
	int position=0;
	for (;
		item && item!=shownItem;
		item=currentFileView()->nextItem(item) )
	{
		if (!Archive::fileItemIsDirOrArchive(item)) ++position;
	}
	return position;
}


KURL FileViewController::url() const {
	KFileItem* item=currentFileView()->currentFileItem();
	if (!item) return mDirURL;
	return item->url();
}

KURL FileViewController::dirURL() const {
	return mDirURL;
}


uint FileViewController::selectionSize() const {
	const KFileItemList* selectedItems=currentFileView()->selectedItems();
	return selectedItems->count();
}


void FileViewController::setMode(FileViewController::Mode mode) {
	const KFileItemList* items;
	FileViewBase* oldView;
	FileViewBase* newView;

	mMode=mode;

	if (mMode==FILE_LIST) {
		mFileThumbnailView->stopThumbnailUpdate();
		oldView=mFileThumbnailView;
		newView=mFileDetailView;
	} else {
		oldView=mFileDetailView;
		newView=mFileThumbnailView;
	}

	bool wasFocused=oldView->widget()->hasFocus();
	// Show the new active view
	d->mStack->raiseWidget(newView->widget());
	if (wasFocused) newView->widget()->setFocus();

	// Fill the new view
	newView->clear();
	newView->addItemList(*oldView->items());

	// Set the new view to the same state as the old
	items=oldView->selectedItems();
	for(KFileItemListIterator it(*items);it.current()!=0L;++it) {
		newView->setSelected(it.current(), true);
	}
	newView->setShownFileItem(oldView->shownFileItem());
	newView->setCurrentItem(oldView->currentFileItem());

	// Remove references to the old view from KFileItems
	items=oldView->items();
	for(KFileItemListIterator it(*items);it.current()!=0L;++it) {
		it.current()->removeExtraData(oldView);
	}

	// Update sorting
	newView->setSorting(oldView->sorting());

	// Clear the old view
	oldView->FileViewBase::clear();
}


void FileViewController::updateFromSettings() {
	applyFilter();
	mFileThumbnailView->setMarginSize(FileViewConfig::thumbnailMarginSize());
	mFileThumbnailView->setItemDetails(FileViewConfig::thumbnailDetails());
	currentFileView()->widget()->update();
}


void FileViewController::setSilentMode( bool silent ) {
	mDirLister->setCheck( !silent );
}


void FileViewController::retryURL() {
	mDirLister->clearError();
	mDirLister->openURL(url().directory());
}


//-----------------------------------------------------------------------
//
// Dir lister slots
//
//-----------------------------------------------------------------------
void FileViewController::dirListerDeleteItem(KFileItem* item) {
	KFileItem* newShownItem=0L;
	const KFileItem* shownItem=currentFileView()->shownFileItem();
	if (shownItem==item) {
		newShownItem=findNextImage();
		if (!newShownItem) newShownItem=findPreviousImage();
	}

	// Remove item from metadata todo list and job's pending update lists
	d->mMetaInfoTodo.remove(item);
	if (d->mMetaInfoJob) {
		d->mMetaInfoJob->removeItem(item);
	}

	currentFileView()->removeItem(item);

	if (shownItem==item) {
		currentFileView()->setCurrentItem(newShownItem);
		currentFileView()->setSelected(newShownItem, true);
		if (newShownItem) {
			emit urlChanged(newShownItem->url());
		} else {
			emit urlChanged(KURL());
		}
	}
}


void FileViewController::dirListerNewItems(const KFileItemList& items) {
	LOG("");
	mThumbnailsNeedUpdate=true;
	currentFileView()->addItemList(items);
	if (FileViewConfig::loadMetadata()) {
		loadMetaInfo(items);
	}
}


void FileViewController::dirListerRefreshItems(const KFileItemList& list) {
	LOG("");
	KFileItemListIterator it(list);
	for (; *it!=0L; ++it) {
		updateViewItem(*it);
	}

	if (FileViewConfig::loadMetadata()) {
		loadMetaInfo(list, true);
	}
}

void FileViewController::updateViewItem(const KFileItem *item, bool metaDataOnly) {
	currentFileView()->updateView(item, metaDataOnly);
	if (item==currentFileView()->shownFileItem()) {
		emit shownFileItemRefreshed(item);
	}
}

void FileViewController::refreshItems(const KURL::List& urls) {
	LOG("");
	KFileItemList list;
	for( KURL::List::ConstIterator it = urls.begin();
	     it != urls.end();
	     ++it ) {
		KURL dir = *it;
		dir.setFileName( TQString() );
		if( dir != mDirURL ) continue;
		// TODO this could be quite slow for many images?
		KFileItem* item = findItemByFileName( (*it).filename());
		if( item ) list.append( item );
	}
	dirListerRefreshItems( list );
}


void FileViewController::dirListerClear() {
	currentFileView()->clear();
}


void FileViewController::dirListerStarted() {
	LOG("");
	mThumbnailsNeedUpdate=false;
}


void FileViewController::dirListerCompleted() {
	LOG("");
	// Delay the code to be executed when the dir lister has completed its job
	// to avoid crash in KDirLister (see bug #57991)
	TQTimer::singleShot(0,this,TQ_SLOT(delayedDirListerCompleted()));
}


void FileViewController::delayedDirListerCompleted() {
	// The call to sort() is a work around to a bug which causes
	// FileThumbnailView::firstFileItem() to return a wrong item.  This work
	// around is not in firstFileItem() because it's const and sort() is a non
	// const method
	if (mMode!=FILE_LIST) {
		mFileThumbnailView->sortView();
	}

	if (mChangeDirStatus != CHANGE_DIR_STATUS_NONE) {
		KFileItem *item;
		TQString fileName = mDirURL.filename();
		for (item=currentFileView()->firstFileItem(); item; item=currentFileView()->nextItem(item) ) {
			if (item->name() == fileName) {
				if (mChangeDirStatus == CHANGE_DIR_STATUS_NEXT) {
					do {
						item=currentFileView()->nextItem(item);
					} while (item && !Archive::fileItemIsDirOrArchive(item));
				} else {
					do {
						item=currentFileView()->prevItem(item);
					} while (item && !Archive::fileItemIsDirOrArchive(item));
				}
				break;
			};
		}
		mChangeDirStatus = CHANGE_DIR_STATUS_NONE;
		if (!item) {
			mDirLister->openURL(mDirURL);
		} else {
			KURL tmp=item->url();
			LOG("item->url(): " << item->url().prettyURL());
			if (Archive::fileItemIsArchive(item)) {
				tmp.setProtocol(Archive::protocolForMimeType(item->mimetype()));
			}
			tmp.adjustPath(1);
			setDirURL(tmp);
		}
	} else {
		browseToFileNameToSelect();
		emit completed();

		if (mMode!=FILE_LIST && mThumbnailsNeedUpdate) {
			mFileThumbnailView->startThumbnailUpdate();
		}
	}
}


void FileViewController::dirListerCanceled() {
	if (mMode!=FILE_LIST) {
		mFileThumbnailView->stopThumbnailUpdate();
	}
	stopMetaInfoUpdate();

	browseToFileNameToSelect();
}


void FileViewController::setShowFilterBar(bool value) {
	d->mShowFilterBarCheckBox->setChecked(value);
}


void FileViewController::setFilterMode(int mode) {
	d->mFilterComboBox->setCurrentItem(mode);
}


void FileViewController::setFilterName(const TQString& name) {
	d->mFilterBar->mNameEdit->setText(name);
}


void FileViewController::setFilterFromDate(const TQDate& date) {
	d->mFilterBar->mFromDateEdit->setDate(date);
}


void FileViewController::setFilterToDate(const TQDate& date) {
	d->mFilterBar->mToDateEdit->setDate(date);
}


void FileViewController::applyFilter() {
	TQStringList mimeTypes;
	FilterMode filterMode = static_cast<FilterMode>( d->mFilterComboBox->currentItem() );
	
	if (FileViewConfig::showDirs()) {
		mimeTypes << "inode/directory";
		mimeTypes += Archive::mimeTypes();
	}

	if (filterMode != VIDEOS_ONLY) {
		mimeTypes += MimeTypeUtils::rasterImageMimeTypes();
		mimeTypes << "image/svg";
	}
	
	if (filterMode != IMAGES_ONLY) {
		mimeTypes << "video/";
	}

	if (d->mShowFilterBarCheckBox->isChecked()) {
		TQString txt=d->mFilterBar->mNameEdit->text();
		TQDate from=d->mFilterBar->mFromDateEdit->date();
		TQDate to=d->mFilterBar->mToDateEdit->date();

		mDirLister->setNameFilter(txt);
		mDirLister->setDateFilter(from, to);
	} else {
		mDirLister->setNameFilter(TQString());
		mDirLister->setDateFilter(TQDate(), TQDate());
	}

	mDirLister->setShowingDotFiles(mShowDotFiles->isChecked());
	mDirLister->setMimeFilter(mimeTypes);

	// Find next item matching the filter if any, so that we can keep it
	// current
	KFileItem* item=currentFileView()->currentFileItem();
	for (; item; item=currentFileView()->nextItem(item)) {
		if (mDirLister->itemMatchFilters(item)) {
			mFileNameToSelect=item->name();
			break;
		}
	}

	mDirLister->openURL(mDirURL);
}


void FileViewController::updateActions() {
	KFileItem* firstImage=findFirstImage();

	// There isn't any image, no need to continue
	if (!firstImage) {
		mSelectFirst->setEnabled(false);
		mSelectPrevious->setEnabled(false);
		mSelectNext->setEnabled(false);
		mSelectLast->setEnabled(false);
		return;
	}

	// We did not select any image, let's activate everything
	KFileItem* currentItem=currentFileView()->currentFileItem();
	if (!currentItem || Archive::fileItemIsDirOrArchive(currentItem)) {
		mSelectFirst->setEnabled(true);
		mSelectPrevious->setEnabled(true);
		mSelectNext->setEnabled(true);
		mSelectLast->setEnabled(true);
		return;
	}

	// There is at least one image, and an image is selected, let's be precise
	bool isFirst=currentItem==firstImage;
	bool isLast=currentItem==findLastImage();

	mSelectFirst->setEnabled(!isFirst);
	mSelectPrevious->setEnabled(!isFirst);
	mSelectNext->setEnabled(!isLast);
	mSelectLast->setEnabled(!isLast);
}


void FileViewController::emitURLChanged() {
	KFileItem* item=currentFileView()->currentFileItem();
	currentFileView()->setShownFileItem(item);

	// We use a tmp value because the signal parameter is a reference
	KURL tmp=url();
	LOG("urlChanged: " << tmp.prettyURL());
	emit urlChanged(tmp);
}

KFileItem* FileViewController::findFirstImage() const {
	KFileItem* item=currentFileView()->firstFileItem();
	while (item && Archive::fileItemIsDirOrArchive(item)) {
		item=currentFileView()->nextItem(item);
	}
	if (item) {
		LOG("item->url(): " << item->url().prettyURL());
	} else {
		LOG("No item found");
	}
	return item;
}

KFileItem* FileViewController::findLastImage() const {
	KFileItem* item=currentFileView()->items()->getLast();
	while (item && Archive::fileItemIsDirOrArchive(item)) {
		item=currentFileView()->prevItem(item);
	}
	return item;
}

KFileItem* FileViewController::findPreviousImage() const {
	KFileItem* item=currentFileView()->shownFileItem();
	if (!item) return 0L;
	do {
		item=currentFileView()->prevItem(item);
	} while (item && Archive::fileItemIsDirOrArchive(item));
	return item;
}

KFileItem* FileViewController::findNextImage() const {
	KFileItem* item=currentFileView()->shownFileItem();
	if (!item) return 0L;
	do {
		item=currentFileView()->nextItem(item);
	} while (item && Archive::fileItemIsDirOrArchive(item));
	return item;
}

KFileItem* FileViewController::findItemByFileName(const TQString& fileName) const {
	KFileItem *item;
	if (fileName.isEmpty()) return 0L;
	for (item=currentFileView()->firstFileItem();
		item;
		item=currentFileView()->nextItem(item) ) {
		if (item->name()==fileName) return item;
	}

	return 0L;
}

//-----------------------------------------------------------------------------
//
// MetaInfo loading
//
//-----------------------------------------------------------------------------

void FileViewController::loadMetaInfo(const KFileItemList& items, bool forceReload) {
	if (forceReload) {
		// Reset metadata of all items if we want MetaInfoJob to reload it
		for(const auto& item: items) {
			item->setMetaInfo(KFileMetaInfo());
		}
	}

	if(d->mMetaInfoTodo.isEmpty()) {
		d->mMetaInfoTodo = items;
	} else {
		for(const auto& item: items) {
			d->mMetaInfoTodo.append(item);
		}
	}

	if(!d->mMetaInfoJob) {
		nextMetaInfoJob();
	}
}

void FileViewController::nextMetaInfoJob() {
	if( !d->mMetaInfoTodo.isEmpty() ) {
		d->mMetaInfoJob = new TDEIO::MetaInfoJob(d->mMetaInfoTodo);
		connect( d->mMetaInfoJob, TQ_SIGNAL(result(TDEIO::Job *)),
				this, TQ_SLOT(nextMetaInfoJob()) );
		connect( d->mMetaInfoJob, TQ_SIGNAL( gotMetaInfo(const KFileItem *)),
				this, TQ_SLOT(updateItemMetaInfo(const KFileItem *)) );
		d->mMetaInfoTodo.clear();
	} else {
		d->mMetaInfoJob = NULL;

		// When we finished all metadata load we should update view sorting
		// We can't do it during the item update due to performance impact
		if (mMode!=FILE_LIST) {
			mFileThumbnailView->sortView();
		}
	}
}

void FileViewController::stopMetaInfoUpdate() {
	d->mMetaInfoTodo.clear();
	if(d->mMetaInfoJob) {
		d->mMetaInfoJob->kill();
		d->mMetaInfoJob = 0;
	}
}

void FileViewController::updateItemMetaInfo(const KFileItem *item) {
	updateViewItem(item, /* metaInfoOnly = */ true);
}

} // namespace
