/***************************************************************************
                          dcfilebrowser.cpp  -  description
                             -------------------
    begin                : Fre Nov 29 2002
    copyright            : (C) 2002 by Mathias Küster
    email                : mathen@users.berlios.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 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "dcfilebrowser.h"

#include <qapplication.h>
#include <q3header.h>
#include <q3listview.h>
#include <qlabel.h>
#include <qcursor.h>
#include <QFileDialog>
#include <QUrl>
#include <qtoolbutton.h>
#include <qcombobox.h>
#include <qclipboard.h>
#include <qmessagebox.h>
#include <qstyle.h>
#include <qmdiarea.h>
#include <qlist.h>
#include <QInputDialog>
#include <QtAlgorithms>

#include "dcmenuhandler.h"
#include "dcconfig.h"
#include "dciconloader.h"
#include "dcfilebrowseritems.h"
#include "dcfilelistdecompressor.h"
#include "dcevent.h"

// for user menu commands
#include "dcclient.h"
#include "dcconnectionmanager.h"

// for searching by TTH
#include "dchubsearch.h"

#include "dcfiletool.h"

#include <dclib/dcos.h>
#include <dclib/core/cstring.h>
#include <dclib/core/cdir.h>
#include <dclib/core/cxml.h>

// for file type icons
#include <dclib/cfilemanager.h>

#include "dcguiutils.h"

/** */
DCFileBrowser::DCFileBrowser( QWidget * parent, bool allowopen )
	: QWidget( parent )
{
	setupUi(this);
	
	m_bAllowOpen = allowopen;
	m_bAllowDownload = true;
	
	// set caption
	setWindowTitle(tr("Filebrowser"));
	setWindowIcon( g_pIconLoader->GetPixmap(eiVIEW_SIDETREE) );
	
	// set width mode
	ListView_DIRECTORY->setColumnWidthMode( 0, Q3ListView::Maximum );
	ListView_FILES->setColumnWidthMode( 0, Q3ListView::Maximum );
	ListView_FILES->setColumnWidthMode( 1, Q3ListView::Maximum );
	ListView_FILES->setColumnWidthMode( 2, Q3ListView::Maximum );
	ListView_FILES->setColumnWidthMode( 3, Q3ListView::Maximum );
	ListView_FILES->setColumnWidthMode( 4, Q3ListView::Maximum );

	// set right alignment on size columns
	ListView_DIRECTORY->setColumnAlignment( 1, Qt::AlignRight );
	ListView_FILES->setColumnAlignment( 1, Qt::AlignRight );
	ListView_FILES->setColumnAlignment( 2, Qt::AlignRight );
	
	// hide the column header - only if not using size column
	if ( g_pConfig->GetFolderSizesInLeftPane() == false )
	{
		ListView_DIRECTORY->header()->hide();
	}

	m_nShareSize = 0;
	m_nFileCount = 0;

	m_pDecomp = 0;
	m_pFoundDir = 0;
	m_nFoundFile = 0;

	ToolButton_OPEN->setIcon( style()->standardIcon(QStyle::SP_DialogOpenButton,0,ToolButton_OPEN) );
	ToolButton_SAVE->setIcon( style()->standardIcon(QStyle::SP_DialogSaveButton,0,ToolButton_SAVE) );
	ToolButton_GOTO_USER->setIcon( g_pIconLoader->GetPixmap(eiUSERS) );
	ToolButton_FIND->setIcon( g_pIconLoader->GetPixmap(eiFILEFIND) );
	ToolButton_NEXT->setIcon( g_pIconLoader->GetPixmap(eiNEXT) );
	
	connect( ListView_DIRECTORY, SIGNAL(selectionChanged()), this, SLOT(slotCurrentChangedDirectory()) );
	connect( ListView_DIRECTORY, SIGNAL(expanded(Q3ListViewItem*)), this, SLOT(slotExpandedDirectory(Q3ListViewItem*)) );
	connect( ListView_DIRECTORY, SIGNAL(collapsed(Q3ListViewItem*)), this, SLOT(slotCollapsedDirectory(Q3ListViewItem*)) );
	connect( ListView_DIRECTORY, SIGNAL(contextMenuRequested( Q3ListViewItem *, const QPoint &, int )), this, SLOT(slotRightButtonClickedDirectory(Q3ListViewItem*, const QPoint &, int )) );
	connect( ListView_FILES, SIGNAL(contextMenuRequested( Q3ListViewItem *, const QPoint &, int )), this, SLOT(slotRightButtonClickedFiles(Q3ListViewItem*, const QPoint &, int )) );
	connect( ListView_FILES, SIGNAL(doubleClicked( Q3ListViewItem *, const QPoint &, int)), this, SLOT(slotItemDoubleClicked( Q3ListViewItem *, const QPoint &, int )) );

 	connect( ToolButton_OPEN, SIGNAL(clicked()), this, SLOT(slotFileOpen()) );
 	connect( ToolButton_SAVE, SIGNAL(clicked()), this, SLOT(slotFileSave()) );
	connect( ToolButton_GOTO_USER, SIGNAL(clicked()), this, SLOT(slotGotoUser()) );
	connect( ToolButton_FIND, SIGNAL(clicked()), this, SLOT(slotFind()) );
	connect( ToolButton_NEXT, SIGNAL(clicked()), this, SLOT(slotFindNext()) );
	
	ToolButton_OPEN->setEnabled(m_bAllowOpen);
	ToolButton_GOTO_USER->setEnabled(false);
}

/** */
DCFileBrowser::~DCFileBrowser()
{
	ClearView();
	
	if ( m_pDecomp != 0 )
	{
		/* there is no way to stop CBZ::Decompress so we are a bit stuck */
		printf("~DCFileBrowser: waiting for thread to finish\n");
		m_pDecomp->wait();
		printf("~DCFileBrowser: thread finished, cleaning up\n");
		if ( m_pDecomp->getData() != 0 )
		{
			delete m_pDecomp->getData();
		}
		if ( m_pDecomp->recompressed != 0 )
		{
			delete m_pDecomp->recompressed;
		}
		delete m_pDecomp;
	}
}

/** */
void DCFileBrowser::customEvent( QEvent * event )
{
	if ( event->type() == EVENT_OPEN_FILELIST_FOLDER )
	{
		DCFileBrowserListItem * item = ((DC_OpenFilelistFolderEvent*)event)->m_pItem;
		
		ListView_DIRECTORY->clearSelection();
		ListView_DIRECTORY->ensureItemVisible( item );
		ListView_DIRECTORY->setSelected( item, true );
		ListView_DIRECTORY->setCurrentItem( item );
		
		event->accept();
	}
	else
	{
		event->ignore();
	}
}

/** */
void DCFileBrowser::slotThreadFinishedLoading( bool ok )
{
	if ( ok )
	{
		if ( m_pDecomp->recompressed != 0 )
		{
			m_ListData.SetSize(0);
			m_ListData.Append( m_pDecomp->recompressed->Data(), m_pDecomp->recompressed->Size() );
			delete m_pDecomp->recompressed;
		}
		ContinueInitTree( m_pDecomp->getData() ); // which will delete the data when it finishes
	}
	else
	{
		QMessageBox::critical(
			this,
			tr("Failed to load filelist"),
			tr("Unable to load") + " " + m_sFileName
		);
		setWindowTitle(tr("Filebrowser"));
		EnableGuiElements();
	}
	
	disconnect( m_pDecomp, SIGNAL(finished(bool)), this, SLOT(slotThreadFinishedLoading(bool)) );
	delete m_pDecomp;
	m_pDecomp = 0;
}

/** */
void DCFileBrowser::ClearView()
{
	m_pFoundDir = 0;
	m_nFoundFile = 0;
	m_FoundFiles.clear();
	
	ListView_FILES->clear();

	Q3ListViewItemIterator it( ListView_DIRECTORY );

	for ( ; it.current(); it++ )
	{
		// no longer have auto-delete with QT4 QList
		for ( int i = 0; i < ((DCFileBrowserListItem*)it.current())->m_pFileList.size(); i++ )
		{
			delete ((DCFileBrowserListItem*)it.current())->m_pFileList.at(i);
		}
		
		((DCFileBrowserListItem*)it.current())->m_pFileList.clear();
	}

	ListView_DIRECTORY->clear();
}

/** */
void DCFileBrowser::slotFileOpen()
{
	QString file;
	if ( m_sFileName.isEmpty() )
	{
		file = QString::fromAscii(g_pConfig->GetFileListPath().Data());
	}
	else
	{
		file = m_sFileName;
	}
	
	QString s = QFileDialog::getOpenFileName(
		this,
		tr("Choose a file"),
		file,
		tr("Modern XML Filelists") + " (*.xml.bz2);;" +
		tr("Modern XML Filelists (uncompressed)") + " (*.xml);;" +
		tr("Old BZ2 Text Filelists") + " (*.bz2);;" +
		tr("Very old HE3 Text Filelists") + " (*.DcLst);;" +
		tr("Old Valknut Filelists") + " (*.filelist);;" +
		tr("All files") + " (*)"
	);

	if ( !s.isEmpty() )
	{
		InitTree(QString::null,QString::null,QString::null,s);
	}
}

/** */
void DCFileBrowser::slotFileSave()
{
	if ( m_ListData.Size() == 0 )
	{
		QMessageBox::critical(
			this,
			tr("Cannot save file"),
			tr("No data available to save.")
		);
		return;
	}
	
	QString s = QFileDialog::getSaveFileName(
		this,
		tr("Choose a filename to save under"),
		m_sFileName
	);

	if ( s.isEmpty() )
	{
		return;
	}
	
	if ( m_ListData.SaveToFile(s.toAscii().constData()) == false )
	{
		QMessageBox::critical(
			this,
			tr("Error saving file"),
			tr("Failed to save") + " " + s
		);
	}
}

/** */
void DCFileBrowser::slotCurrentChangedDirectory()
{
	DCFileItem * FileItem;
	QString s;
	QList<Q3ListViewItem*> selitems;
	ulonglong size=0;
	int items = 0;
	int citems = 0;
	Q3ListViewItem * item = 0;
	Q3ListViewItem * select = 0;

	// clear filelist
	ListView_FILES->clear();

	// check if only one dir selected
	if ( DCGuiUtils::SelectedItems( ListView_DIRECTORY, selitems ) == 1 )
	{
		item = selitems.first();

		for ( int i = 0; i < ((DCFileBrowserListItem*)item)->m_pFileList.size(); i++ )
		{
			FileItem = ((DCFileBrowserListItem*)item)->m_pFileList.at(i);
			
			if ( (g_pConfig->GetFoldersInRightPane() == false) && FileItem->m_bIsDir )
			{
				// do not show it
				continue;
			}
			
			size += FileItem->m_nSize;

			DC_FBListViewItem *item1 = new DC_FBListViewItem(ListView_FILES);
			item1->myvalue = FileItem->m_nSize;
			item1->mycol   = 1;
			item1->m_bSortTop = false;
			item1->pDirItem = ((DCFileBrowserListItem*)item);

			if ( FileItem->m_bIsDir )
			{
				s = tr("Folder");
				item1->setPixmap(0,m_FolderPixmap);
				
				if ( g_pConfig->GetFoldersInRightPaneOnTop() )
				{
					item1->m_bSortTop = true;
				}
			}
			else
			{
				s = CDir::Extension(FileItem->m_sName.toAscii().constData()).Data();

				if ( !s.isEmpty() )
				{
					s += " ";
					s += tr("file");
				}
				
				eFileTypes type = CFileManager::Instance()->GetFileType(FileItem->m_sName.toAscii().constData());
				QPixmap icon;
				
				switch (type)
				{
					case eftMP3:
						icon = g_pIconLoader->GetPixmap(eiFILETYPE_MP3);
						break;
					case eftARCHIVE:
						icon = g_pIconLoader->GetPixmap(eiFILETYPE_ARCHIVE);
						break;
					case eftDOCUMENT:
						icon = g_pIconLoader->GetPixmap(eiFILETYPE_DOCUMENT);
						break;
					case eftAPPLICATION:
						icon = g_pIconLoader->GetPixmap(eiFILETYPE_APPLICATION);
						break;
					case eftPICTURE:
						icon = g_pIconLoader->GetPixmap(eiFILETYPE_PICTURE);
						break;
					case eftVIDEO:
						icon = g_pIconLoader->GetPixmap(eiFILETYPE_VIDEO);
						break;
					default:
						icon = g_pIconLoader->GetPixmap(eiFILETYPE_UNKNOWN);
						break;
				}
				
				item1->setPixmap( 0, icon );

				items++;
				
				if ( !m_sJumpTo.isEmpty() && (FileItem->m_sName == m_sJumpTo) )
				{
					select = item1;
					m_sJumpTo = "";
				}
			}

			item1->setText(0,FileItem->m_sName);
			item1->setText(1,DCGuiUtils::GetSizeString(FileItem->m_nSize));
			item1->setText(2,QString::number(FileItem->m_nSize));
			item1->setText(3,s);
			item1->setText(4,FileItem->m_sHash);
		}
		
		if ( select )
		{
			ListView_FILES->clearSelection();
			ListView_FILES->ensureItemVisible( select );
			ListView_FILES->setSelected( select, true );
			ListView_FILES->setCurrentItem( select );
		}
		
		/* do not use it again */
		m_sJumpTo = "";
	}

	if ( item )
	{
		citems = item->childCount();
	}

	TextLabel_STATUS->setText( QString().setNum(items+citems) + " " + tr("Items") + " - " + 
					QString().setNum(items) + " " + tr("Files") +
					" (" + DCGuiUtils::GetSizeString(size) + " " + tr("Total") + ") - " +
					QString().setNum(citems) + " " + tr("Directories") );
	TextLabel_STATUS->setToolTip( QString::number(size) + " B" );
}

/** */
void DCFileBrowser::slotExpandedDirectory( Q3ListViewItem * item )
{
	if ( item->parent() != NULL )
		item->setPixmap(0,g_pIconLoader->GetPixmap(eiFOLDER_BLUE_OPEN));
}

/** */
void DCFileBrowser::slotCollapsedDirectory( Q3ListViewItem * item )
{
	if ( item->parent() != NULL )
		item->setPixmap(0,g_pIconLoader->GetPixmap(eiFOLDER_BLUE));
}

/** */
void DCFileBrowser::slotRightButtonClickedDirectory( Q3ListViewItem * item, const QPoint &, int column )
{
	CreateMenu(item,true,column);
}

/** */
void DCFileBrowser::slotRightButtonClickedFiles( Q3ListViewItem * item, const QPoint &, int column )
{
	CreateMenu(item,false,column);
}

/** */
void DCFileBrowser::AddDirectory( Q3ListViewItem * item, QString name )
{
	((DCFileBrowserListItem*)item)->m_sName = name;

	item->setText(0,name);
	item->setPixmap(0,m_FolderPixmap);
	
	((DCFileBrowserListItem*)item)->m_nBytes = 0;
}

/** */
void DCFileBrowser::AddFile( Q3ListViewItem * item, DCFileItem * fileitem )
{
	// increment filecounter
	if ( fileitem->m_bIsDir == false )
	{
		m_nFileCount++;
		m_nShareSize += fileitem->m_nSize;
	}

	// add fileitem to filelist
	((DCFileBrowserListItem*)item)->m_pFileList.append(fileitem);
	
	// add to folder contents size
	((DCFileBrowserListItem*)item)->m_nBytes += fileitem->m_nSize;
	
	DCFileItem * pFileItem = ((DCFileBrowserListItem*)item)->m_pFileItem;
	
	if ( pFileItem != 0 )
	{
		pFileItem->m_nSize = ((DCFileBrowserListItem*)item)->m_nBytes;
	}
}

/** */
void DCFileBrowser::InitTree( QString nick, QString hubname, QString hubhost, QString filename, QString jumpto, QStringList dirs )
{
	if ( filename.isEmpty() )
	{
		return;
	}
	
	if ( m_pDecomp != 0 )
	{
		QMessageBox::critical(
			this,
			tr("Cannot open list now"),
			tr("Filelist browser is busy")
		);
		return;
	}
	
	// store values
	m_sNick     = nick;
	m_sHubName  = hubname;
	m_sHubHost  = hubhost;
	m_sFileName = filename;
	m_sJumpTo   = jumpto;
	m_lDownloadDirs = dirs;
	
	// first check if filename is our own list
	if ( filename == "/dev/null/ownfilelist" )
	{
		m_bAllowDownload = false;
		GetOwnList();
		
		//printf("Size of own list = %lu\n", m_ListData.Size());
		
		if ( (m_sFileName == "myfiles.xml.bz2") || (m_sFileName == "myfiles.txt.bz2") || (m_sFileName == "myfiles.DcLst") )
		{
			// nothing, it all got moved into the thread
		}
		else
		{
			QMessageBox::critical(
				this,
				tr("Error opening own filelist"),
				tr("Failed to open your own filelist")
			);
			
			return;
		}
		
		DisableGuiElements();
		m_pDecomp = new DCFilelistDecompressor( this, &m_ListData );
		connect( m_pDecomp, SIGNAL(finished(bool)), this, SLOT(slotThreadFinishedLoading(bool)) );
		m_pDecomp->start();
		return;
	}
	else
	{
		m_bAllowDownload = true;
		if ( !QFile::exists(filename) )
		{
			QMessageBox::critical(
				this,
				tr("Error opening filelist"),
				tr("File") + " " + filename + " " + tr("does not exist.")
			);
			
			return;
		}
		
		DisableGuiElements();
		m_pDecomp = new DCFilelistDecompressor( this, filename.toAscii().constData() );
		connect( m_pDecomp, SIGNAL(finished(bool)), this, SLOT(slotThreadFinishedLoading(bool)) );
		m_pDecomp->start();
		return;
	}
}

/** */
void DCFileBrowser::ContinueInitTree( CString * data )
{
	/* A bz2 file that decompressed to zero bytes probably... */
	if ( (data == 0) || (data->Length() == 0) )
	{
		QMessageBox::critical(
			this,
			tr("Error opening filelist"),
			m_sFileName + " " + tr("is likely not a valid filelist")
		);
		setWindowTitle(tr("Filebrowser"));
		EnableGuiElements();
		return;
	}
	
	Q3ListViewItem * item;
	
	// reset count values
	m_nShareSize = 0;
	m_nFileCount = 0;

	ClearView();

	// get folder pixmap
	m_FolderPixmap = g_pIconLoader->GetPixmap(eiFOLDER_BLUE);

	// disable updates
	ListView_DIRECTORY->setUpdatesEnabled(false);
	
	// parse filename
	
	QFileInfo fi(m_sFileName);
	QString fname = fi.fileName();
	
	if ( (m_sNick.isEmpty()) || (m_sHubName.isEmpty()) || (m_sHubHost.isEmpty()) )
	{
		int i = fname.indexOf('@');
		if ( i != -1 )
		{
			m_sNick = fname.left(i);
			m_sHubHost = fname.mid(i+1);
			m_sHubHost.remove(".xml.bz2");
			m_sHubHost.remove(".bz2");
			m_sHubHost.remove(".xml");
			m_sHubHost.remove(".DcLst");
			m_sHubHost.remove(".filelist");
			
			// replace _ with : before port number
			int i2 = m_sHubHost.lastIndexOf('_');
			if ( i2 != -1 )
			{
				bool ok;
				m_sHubHost.mid(i2+1).toInt(&ok);
				if ( ok )
				{
					m_sHubHost = m_sHubHost.left(i2) + ":" + m_sHubHost.mid(i2+1);
				}
			}
			
			if ( m_sHubName.isEmpty() )
			{
				m_sHubName = m_sHubHost;
			}
		}
	}
	
	// set dialog caption
	
	if ( m_sNick.isEmpty() )
	{
		fname.remove(".xml.bz2");
		fname.remove(".bz2");
		fname.remove(".xml");
		fname.remove(".DcLst");
		fname.remove(".filelist");
		m_sNick = fname;
	}
	
	if ( m_sHubName.isEmpty() )
	{
		setWindowTitle( m_sNick + " - " + tr("Filebrowser") );
	}
	else
	{
		setWindowTitle( m_sNick + " - " + tr("Filebrowser") + " [" + m_sHubName + "]" );
	}
	
	// create root item
	item = new DCFileBrowserListItem( ListView_DIRECTORY );
	item->setText(0,tr("Root Directory"));
	item->setPixmap(0,g_pIconLoader->GetPixmap(eiFOLDER_RED));
	((DCFileBrowserListItem*)item)->m_pFileItem = 0;

	if ( data->Left(9).Find("<?xml") != -1 )
		InitXMLTree(item,data);			
	else
		InitTXTTree(item,data);
	
	delete data;

	// update summary
	TextLabel_SUMMARY->setText( QString().setNum(m_nFileCount) + " " + tr("Files") + " (" + 
					DCGuiUtils::GetSizeString(m_nShareSize) + " " + tr("Total") + ")" );

	// update tooltip
	TextLabel_SUMMARY->setToolTip( QString().setNum(m_nShareSize) + " B" );

	// update view
	ListView_DIRECTORY->setUpdatesEnabled(true);
	ListView_DIRECTORY->triggerUpdate();

	// open first tree
	ListView_DIRECTORY->setOpen(ListView_DIRECTORY->firstChild(),true);
	
	EnableGuiElements();
	
	/* download any directories */
	for ( QStringList::const_iterator it = m_lDownloadDirs.constBegin(); it != m_lDownloadDirs.constEnd(); ++it )
	{
		DownloadDirectory( *it );
	}
	m_lDownloadDirs.clear();
	
	/* jump to target file or folder */
	if ( !m_sJumpTo.isEmpty() )
	{
		JumpTo( m_sJumpTo );
	}
	
}

/** */
void DCFileBrowser::DisableGuiElements()
{
	setWindowTitle(tr("Processing filelist..."));
	ToolButton_FIND->setEnabled(false);
	ToolButton_NEXT->setEnabled(false);
	ToolButton_OPEN->setEnabled(false);
	ToolButton_SAVE->setEnabled(false);
	ToolButton_GOTO_USER->setEnabled(false);
	ListView_DIRECTORY->setEnabled(false);
	ListView_FILES->setEnabled(false);
}

void DCFileBrowser::EnableGuiElements()
{
	ToolButton_FIND->setEnabled(true);
	ToolButton_NEXT->setEnabled(true);
	ToolButton_OPEN->setEnabled(m_bAllowOpen);
	ToolButton_SAVE->setEnabled(true);
	ListView_DIRECTORY->setEnabled(true);
	ListView_FILES->setEnabled(true);
	
	if ( !m_bAllowOpen || m_sNick.isEmpty() || m_sHubHost.isEmpty() )
	{
		ToolButton_GOTO_USER->setEnabled(false);
	}
	else
	{
		ToolButton_GOTO_USER->setEnabled(true);
	}
}

/** */
void DCFileBrowser::InitTXTTree( Q3ListViewItem * item, CString * data )
{
	QString s1,sname,slength;
	long i,j,d;
	int depth;

	i = j = 0;

	while((i=data->Find("\xD\xA",j))>=0)
	{
		s1 = data->Mid(j,i-j).Data();

		depth=0;
		while( (s1.indexOf("\x9",depth)) != -1 )
			depth++;
		s1.remove( "\x9" );

		if (item)
			while ( depth < item->depth() )
				if ( (item=item->parent()) == 0 )
					break;

		if ( s1.isEmpty() )
			s1 = "\\";

		if ( !s1.isEmpty() )
		{
			d = s1.lastIndexOf("|");

			if ( d != -1 ) // parse file entry
			{
				DCFileItem * FileItem = new DCFileItem();

				FileItem->m_sName = s1.mid(0,d);
				FileItem->m_nSize = s1.right(s1.length()-d-1).toULongLong();
				FileItem->m_bIsDir = false;
				
				AddFile( item, FileItem );
			}
			else // parse dir entry
			{
				DCFileItem * FileItem = new DCFileItem();
				
				FileItem->m_sName = s1;
				FileItem->m_nSize = 0;
				FileItem->m_bIsDir = true;
				
				AddFile( item, FileItem );
				
				if ( item == 0 )
					item = new DCFileBrowserListItem( ListView_DIRECTORY );
				else
					item = new DCFileBrowserListItem( item );
				
				((DCFileBrowserListItem*)item)->m_pFileItem = FileItem;

				AddDirectory( item, s1 );
			}
		}

		j = i+2;
	}
	
	// fixup the folder sizes
	Q3ListViewItem * cur = ListView_DIRECTORY->firstChild();
	while ( cur )
	{
		CalcDirSize( cur );
		
		cur = cur->nextSibling();
	}
}

/** */
void DCFileBrowser::InitXMLTree( Q3ListViewItem * item, CString * data )
{
	CXml * xml = new CXml();
	
	// QString::length() is not the length of the UTF-8 data
	// but CString::Length() is
	if ( xml->ParseMemory(data->Data(),data->Length()) && xml->DocFirstChild() )
	{
		// maybe we should just parse the first FileListing?
		do
		{
			if ( (xml->Name() == "FileListing") && xml->FirstChild() )
			{
				ParseXMLTree(xml,item);
				xml->Parent();
			}
		}
		while ( xml->NextNode() );
	}

	delete xml;
}

/** */
void DCFileBrowser::ParseXMLTree( CXml * xml, Q3ListViewItem * item )
{
	CString s;

	do
	{
		if ( xml->Name() == "File" )
		{
			DCFileItem * FileItem = new DCFileItem();
			s = xml->Prop("Name");
			FileItem->m_sName = QString::fromUtf8(s.Data());
			FileItem->m_nSize = xml->Prop("Size").asULL();
			FileItem->m_sHash = xml->Prop("TTH").Data();
			FileItem->m_bIsDir = false;
			
			AddFile(item,FileItem);
		}
		else if ( xml->Name() == "Directory" )
		{
			Q3ListViewItem * item1;
			
			item1 = new DCFileBrowserListItem( item );
			((DCFileBrowserListItem*)item1)->m_pFileItem = 0;
			
			s = xml->Prop("Name");
			AddDirectory(item1,QString::fromUtf8(s.Data()));
			
			if ( xml->FirstChild() )
			{
				ParseXMLTree(xml,item1);
				xml->Parent();
			}
			
			DCFileItem * FileItem = new DCFileItem();
			FileItem->m_sName = QString::fromUtf8(s.Data());
			FileItem->m_nSize = ((DCFileBrowserListItem*)item1)->m_nBytes;
			FileItem->m_bIsDir = true;
			AddFile(item,FileItem);
			((DCFileBrowserListItem*)item1)->m_pFileItem = FileItem;
			
			if ( g_pConfig->GetFolderSizesInLeftPane() )
			{
				item1->setText( 1, DCGuiUtils::GetSizeString( ((DCFileBrowserListItem*)item1)->m_nBytes ) );
			}
		}
	}
	while ( xml->NextNode() );
}

/** */
QString DCFileBrowser::CreateRemotePath( Q3ListViewItem * item )
{
	QString s;
	Q3ListViewItem * pitem;
	
	if ( !item )
	{
		return s;
	}

	pitem = item;
	s = ((DCFileBrowserListItem*)pitem)->m_sName;
	
	while ( (pitem=pitem->parent()) != 0 )
	{
		// check for root entry
		if ( pitem->parent() != 0 )
		{
			s = ((DCFileBrowserListItem*)pitem)->m_sName + "\\" + s;
		}
	}

	s.replace( "\\\\\\\\", "\\" );

	return s;
}

/** */
void DCFileBrowser::CreateMenu( Q3ListViewItem *, bool direntry, int column )
{
	int i;
	QString s;
	ulonglong size;
	QString rootPath  = QString::null;
	QString localName = QString::null;
	QList<Q3ListViewItem*> selitems;
	Q3ListViewItem * curitem;
	bool hasFolderItems = false;
	DCClient * client = 0;
	QMap<QAction*, DC_UserMenuCommand*> addedcommands;
	
	if ( direntry )
	{
		i = DCGuiUtils::SelectedItems( ListView_DIRECTORY, selitems );
	}
	else
	{
		i = DCGuiUtils::SelectedItems( ListView_FILES, selitems );
		
		QListIterator<Q3ListViewItem*> it ( selitems );
		
		Q3ListViewItem * cur = 0;
		
		while ( it.hasNext() )
		{
			cur = it.next();
			
			if ( cur->text(3) == tr("Folder") )
			{
				hasFolderItems = true;
				break;
			} 
		}
	}

	// nothing on no selection
	if ( i == 0 )
	{
		return;
	}
	else
	{
		curitem = selitems.first();
	}

/* TODO readadd test
	if ( direntry && (item->parent() == 0) )
	{
		return;
	}
*/
	QMenu * m = new QMenu(this);

	QAction * dl = DCMenuHandler::addAction(m, emiDOWNLOAD, m_bAllowDownload);
	QAction * dl_to = DCMenuHandler::addAction(m, emiDOWNLOAD_TO, m_bAllowDownload);
	// disable 'download as' on directories
	QAction * dl_as = DCMenuHandler::addAction(m, emiDOWNLOAD_AS, (m_bAllowDownload && !direntry && !hasFolderItems && (i == 1) ) );
	QAction * dl_in = DCMenuHandler::addAction(m, emiDOWNLOAD_IN, (m_bAllowDownload && !direntry && !hasFolderItems) );
	DCMenuHandler::addAction(m, emiSEPARATOR);
	QAction * search_tth = m->addAction( QIcon(g_pIconLoader->GetPixmap(eiFILEFIND)), tr("Search by TTH") );
	search_tth->setEnabled( m_bAllowOpen && !direntry && curitem && !(curitem->text(4).isEmpty()) );
	DCMenuHandler::addAction(m, emiSEPARATOR);
	QAction * copycol = DCMenuHandler::addAction(m, emiCOPY_COLUMN_TO_CLIPBOARD, !direntry );
	QAction * copyrow = DCMenuHandler::addAction(m, emiCOPY_ROW_TO_CLIPBOARD, !direntry );
	QAction * copymagnet = DCMenuHandler::addAction(m, emiCOPYMAGNETLINK, (!direntry && !hasFolderItems ));
	
	client = g_pConnectionManager->GetClientForHub( m_sHubName.toAscii().constData(), m_sHubHost.toAscii().constData() );
	
	if ( client != 0 )
	{
		addedcommands = client->AddMenuCommands( m, euccFilelist );
	}
	
	QAction * chosen = m->exec(QCursor::pos());

	delete m;
	
	if ( chosen == copycol )
	{
		QString s;

                for ( int c = 0; c < selitems.size(); c++ )
                {
			curitem = selitems.at(c);
			s += curitem->text(column);
			s += "\n";
		}

		s = s.trimmed();
		QApplication::clipboard()->setText(s);
	}
	else if ( chosen == copyrow )
	{
		int idx;
		QString s;

                for ( int c = 0; c < selitems.size(); c++ )
                {
			curitem = selitems.at(c);
			
			for( idx = 0; idx < ListView_FILES->columns(); idx++ )
			{
				s += curitem->text(idx);
				s += " ";
			}

			s += "\n";
		}

		s = s.trimmed();
		QApplication::clipboard()->setText(s);
	}
	else if ( chosen == copymagnet )
	{
		//magnet:?xt=urn:tree:tiger:EOSA5AGTL5SD3VWCF3R2OH2WMGXV3S3R7SYN4YA&xl=708780032&dn=FC-6-i386-disc1.iso
		QString all;
		
		for ( int c = 0; c < selitems.size(); c++ )
		{
			curitem = selitems.at(c);
			
			// add tth
			all += "magnet:?xt=urn:tree:tiger:";
			all += curitem->text(4);
			
			// add filesize
			all += "&xl=";
			all += curitem->text(2);
			
			// add filename
			all += "&dn=";
			
			QString filename = QString(QUrl::toPercentEncoding(curitem->text(0)));
			
			//fixme DC++ replaces spaces with + character
			//filename = filename.replace(' ','+');
			
			filename.replace("%20","+");
			all += filename;
			
			all += "\n";
		}
		
		// remove trailing "\n"
		all = all.trimmed();
		
		QApplication::clipboard()->setText(all);
	}
	// Check if the user wants a non-default destination
	else if ( chosen == dl_to )
	{
		rootPath = QFileDialog::getExistingDirectory( this, tr("Select destination") );
		if ( rootPath.isEmpty() )
		{
			// If the user cancel, then we don't download
			return;
		}
	}
	// Should the file be renamed
	else if ( chosen == dl_as )
	{
		rootPath = QFileDialog::getSaveFileName( this, tr("Select a filename"), selitems.first()->text(0) );
		if ( rootPath.isEmpty() )
		{
			return;
		}

		QFileInfo fi(rootPath);
		rootPath = fi.path();
		localName = fi.fileName();

		if ( rootPath.isEmpty() || localName.isEmpty() )
		{
			return;
		}
	}
	else if ( chosen == dl_in )
	{
		QString localrootpath;
		QString localname;
		QString localpath;
		QString remotePath;

		if ( !(curitem = selitems.first()) )
			return;

		size = ((DC_QListViewItem *)curitem)->myvalue;
		QString tth = curitem->text(4);

		// all files need equal size and TTH
                for ( int c = 0; c < selitems.size(); c++ )
                {
			curitem = selitems.at(c);
			
			if ( size != ((DC_QListViewItem *)curitem)->myvalue )
				return;
			if ( tth != curitem->text(4) )
				return;
		}

		if ( DCFileTool::SelectFileSource( this, size, tth, localname, localrootpath, localpath ) == false )
		{
			return;
		}

		QString remotePathAndFile;
                for ( int c = 0; c < selitems.size(); c++ )
                {
			curitem = selitems.at(c);
			
			s = curitem->text(0);
			
			// get the full remote path
			remotePath = CreateRemotePath(GetDirItem(curitem));
			
			remotePathAndFile  = remotePath;
			remotePathAndFile += "\\";
			remotePathAndFile += s;
					    
			// add transfer to the waitlist
			DCFileTool::CheckFile( this, m_sNick.toAscii().constData(), m_sHubName.toAscii().constData(), m_sHubHost.toAscii().constData(),
				  remotePathAndFile.toAscii().constData(), localname.toAscii().constData(), localpath.toAscii().constData(), localrootpath.toAscii().constData(), eltFILE,
		  		  size, curitem->text(4).toAscii().constData(), true);
		}
	}
	else if ( chosen == search_tth )
	{
		DCHubSearch * hubsearch = new DCHubSearch( g_pConnectionManager->GetMdiArea() );
		hubsearch->SetSearchForFile( curitem->text(4), eftHASH );
		hubsearch->show();
		hubsearch->StartSearchWithPrompt();
	}
	else if ( addedcommands.contains( chosen ) )
	{
		DC_UserMenuCommand * umc = addedcommands[ chosen ];
		
		QString usercommand = umc->m_sCommand;
		QString origUserCommand = usercommand;
		QString fullfilename, filesize, filesizeshort, filetth, filetype;
		
		for ( int c = 0; c < selitems.size(); c++ )
		{
			// filelist browsers are only for the one nick
			if ( (umc->m_nType == euctRawOnce) && (c > 0) )
			{
				break;
			}
			
			curitem = selitems.at(c);
			
			usercommand = client->replaceCommandTags( origUserCommand, m_sNick );
			
			if ( usercommand.isEmpty() )
			{
				// had a %[line:reason] but dialog was cancelled
				continue;
			}
			
			if ( direntry )
			{
				fullfilename = CreateRemotePath(curitem);
				filesize.setNum(((DCFileBrowserListItem*)curitem)->m_nBytes);
				filesizeshort = DCGuiUtils::GetSizeString( ((DCFileBrowserListItem*)curitem)->m_nBytes );
				filetth = tr("None");
				filetype = tr("Directory");
			}
			else
			{
				fullfilename = CreateRemotePath(GetDirItem(curitem));
				filesize = curitem->text(2);
				filesizeshort = curitem->text(1);
				filetth = curitem->text(4);
				if ( filetth.isEmpty() )
				{
					filetth = tr("None");
				}
				
				if ( curitem->text(3) == tr("Folder") )
				{
					filetype = tr("Directory");
				}
				else
				{
					filetype = tr("File");
					fullfilename += "\\";
					fullfilename += curitem->text(0);
				}
			}
			
			usercommand.replace( "%[file]", fullfilename );
			usercommand.replace( "%[fileFN]", fullfilename );
			
			usercommand.replace( "%[filesize]", filesize );
			usercommand.replace( "%[fileSI]", filesize );
			
			usercommand.replace( "%[filesizeshort]", filesizeshort );
			usercommand.replace( "%[fileSIshort]", filesizeshort );
			
			usercommand.replace( "%[tth]", filetth );
			usercommand.replace( "%[fileTR]", filetth );
			
			usercommand.replace( "%[type]", filetype );
			
			//client->m_pHubChat->AddStatus( usercommand.toAscii().constData() );
			client->SendString( usercommand.toAscii().constData() );
		}
	}

	// Is this a download?
	if ( (chosen == dl) || (chosen == dl_to) || (chosen == dl_as) )
	{
                for ( int c = 0; c < selitems.size(); c++ )
                {
			curitem = selitems.at(c);
			
			// directory download from left pane
			if ( direntry )
			{
				s = curitem->text(0);
					
				DownloadPath( rootPath, s, QString::null, curitem );
			}
			else if ( curitem->text(3) == tr("Folder") ) // directory download from right pane
			{
				Q3ListViewItem * diritem = GetDirItem( curitem );
				
				if ( diritem == 0 )
				{
					QMessageBox::critical( this, tr("Cannot download"), tr("Could not find the path for ") + curitem->text(0) );
				}
				else
				{
					DownloadPath( rootPath, diritem->text(0), QString::null, diritem );
				}
 			}
			// Single file download
			else
			{
				QString remotePath;
				QString remoteName;
				
				// set localname
				if ( chosen != dl_as )
				{
					localName = curitem->text(0);
				}

				// get the full remote path
				remotePath = CreateRemotePath(GetDirItem(curitem));
				
				remoteName = curitem->text(0);
				
				DownloadFile( rootPath, QString(), localName, remotePath, remoteName, ((DC_QListViewItem *)curitem)->myvalue, curitem->text(4) );
			}
		}
	}
}

/** */
void DCFileBrowser::DownloadPath( QString rootPath, QString localPath, QString localName, Q3ListViewItem * item )
{
	QString remotePath;
	DCFileItem * FileItem = 0;
	QString s;
	QString remoteFile;

	if ( !item )
	{
		return;
	}
	
	// get the full remote path
	remotePath = CreateRemotePath(item);
	
	for ( int i = 0; i < ((DCFileBrowserListItem*)item)->m_pFileList.size(); i++ )
	{
		FileItem = ((DCFileBrowserListItem*)item)->m_pFileList.at(i);
		
		if ( FileItem->m_bIsDir == false )
		{
			remoteFile = FileItem->m_sName;
			DownloadFile( rootPath, localPath, localName, remotePath, 
				remoteFile, FileItem->m_nSize, FileItem->m_sHash );
		}
	}

	item = item->firstChild();

	while( item )
	{
		s = item->text(0);
			
		DownloadPath(rootPath,localPath + "\\" + s,localName,item);
		item = item->nextSibling();
	}
}

/** */
void DCFileBrowser::DownloadFile( QString rootPath, QString localPath, QString localName, 
				  QString remotePath, QString remoteName, ulonglong size,
				  QString hash )
{
	// append filename to the remote path
	remotePath += "\\";
	remotePath += remoteName;
		    
	if ( localName == QString::null )
	{
		localName = remoteName;
	}

	
	DCFileTool::CheckFile(this, m_sNick.toAscii().constData(),
		m_sHubName.toAscii().constData(),
		m_sHubHost.toAscii().constData(),
		remotePath.toAscii().constData(),
		localName.toAscii().constData(),
		localPath.toAscii().constData(),
		rootPath.toAscii().constData(), eltFILE,
		size, hash.toAscii().constData() );
}

/** Double click to download the item, copied from DCFileBrowser::CreateMenu */
void DCFileBrowser::slotItemDoubleClicked( Q3ListViewItem * item, const QPoint &, int )
{
	QString rootPath = QString::null;
	QString localName = QString::null;
	
	if ( item == 0 ) // null pointer check, just in case
	{
		return;
	}
	
	Q3ListViewItem * diritem = GetDirItem( item );
	
	if ( diritem == 0 )
	{
		QMessageBox::critical( this, tr("Cannot download"), tr("Could not find the path for ") + item->text(0) );
		return;
	}
	
	if ( item->text(3) == tr("Folder") )
	{
		// we cannot open the folder since that would delete the existing
		// Q3ListViewItems, which is not allowed in this slot and causes
		// random crashes
		
		/* Q3ListViewItem * diritem = ListView_DIRECTORY->currentItem();
		
		if ( diritem == 0 )
		{
			printf("Double click a folder needs a ListView_DIRECTORY->currentItem()\n");
			return;
		}
		
		for ( diritem = diritem->firstChild(); diritem != 0; diritem = diritem->nextSibling() )
		{
			if ( diritem->text(0) == item->text(0) )
			{
				ListView_DIRECTORY->clearSelection();
				ListView_DIRECTORY->ensureItemVisible( diritem );
				ListView_DIRECTORY->setSelected( diritem, true );
				ListView_DIRECTORY->setCurrentItem( diritem );
				break;
			}
		} */
		
		if ( g_pConfig->GetOpenFoldersInRightPane() )
		{
			QApplication::postEvent( this, new DC_OpenFilelistFolderEvent( (DCFileBrowserListItem*)diritem ) );
		}
		else if ( m_bAllowDownload )
		{
			// ask for confirmation since this is unusual behaviour
			int confirm = QMessageBox::question(
				this,
				tr("Download contents?"),
				tr("Download the contents of \"") + diritem->text(0) + "\" ?",
				QMessageBox::Yes | QMessageBox::No,
				QMessageBox::No
			);
			
			if ( confirm == QMessageBox::Yes )
			{
				DownloadPath( rootPath, diritem->text(0), localName, diritem );
			}
		}
	}
	else if ( m_bAllowDownload )
	{
		QString remotePath;
		QString remoteName;
		
		// get the full remote path
		remotePath = CreateRemotePath(diritem);
		
		remoteName = item->text(0);
		
		DownloadFile( rootPath, QString(), localName, remotePath, remoteName, ((DC_QListViewItem *)item)->myvalue, item->text(4) );
	}
}

/** Only needed for non-xml filelists */
ulonglong DCFileBrowser::CalcDirSize( Q3ListViewItem * item )
{
	ulonglong total = 0;

	DCFileBrowserListItem * cur = (DCFileBrowserListItem*) item;
	
	QListIterator<DCFileItem*> it( cur->m_pFileList );
	
	DCFileItem * current = 0;
	
	while ( it.hasNext())
	{
		current = it.next();
		
		if ( current->m_bIsDir )
		{
			Q3ListViewItem * child = item->firstChild();
			
			while ( child )
			{
				if ( child->text(0) == current->m_sName )
				{
					total += CalcDirSize( child );
					break;
				}
				
				child = child->nextSibling();
			}
		}
		else
		{
			total += current->m_nSize;
		}
	}
	
	DCFileItem * pFileItem = cur->m_pFileItem;
	if ( pFileItem != 0 )
	{
		pFileItem->m_nSize = total;
	}
	
	cur->m_nBytes = total;
	
	if ( g_pConfig->GetFolderSizesInLeftPane() )
	{
		cur->setText( 1, DCGuiUtils::GetSizeString( total ) );
	}
	
	//printf( "CalcDirSize for %s = %lld\n", item->text(0).toAscii().constData(), total );
	
	return total;
}

/** */
Q3ListViewItem * DCFileBrowser::GetDirItem( Q3ListViewItem * item )
{
	if ( item == 0 )
	{
		return 0;
	}

	Q3ListViewItem * diritem = ((DC_FBListViewItem*)item)->pDirItem;
	
	if ( item->text(3) != tr("Folder") )
	{
		return diritem;
	}
	
	if ( diritem == 0 )
	{
		return 0;
	}
	
	for ( diritem = diritem->firstChild(); diritem != 0; diritem = diritem->nextSibling() )
	{
		if ( diritem->text(0) == item->text(0) )
		{
			return diritem;
		}
	}
	
	return 0;
}

/** */
void DCFileBrowser::GetOwnList()
{
	m_ListData.SetSize(0);
	
	if ( CFileManager::Instance()->GetShareBuffer( esbtXMLBZ, &m_ListData, false ) == 0 )
	{
		m_sFileName = "myfiles.xml.bz2";
	}
	else if ( CFileManager::Instance()->GetShareBuffer( esbtBZ, &m_ListData, false ) == 0 )
	{
		m_sFileName = "myfiles.txt.bz2";
	}
	else if ( CFileManager::Instance()->GetShareBuffer( esbtHE3, &m_ListData, false ) == 0 )
	{
		m_sFileName = "myfiles.DcLst";
	}
	else
	{
		m_sFileName = "";
		m_ListData.SetSize(0);
	}
}

/** */
bool DCFileBrowser::DownloadDirectory( QString dir )
{
	bool res = false;
	bool found = false;
	QStringList dirs = dir.split( "\\", QString::SkipEmptyParts );
	
	qApp->processEvents();
	
	Q3ListViewItem * curitem = ListView_DIRECTORY->firstChild();
	
	if ( !curitem )
	{
		return res;
	}
	
	for ( QStringList::const_iterator it = dirs.constBegin(); it != dirs.constEnd(); ++it )
	{
		curitem = curitem->firstChild();
		
		while ( curitem )
		{
			if ( curitem->text(0) == *it )
			{
				found = true;
				break;
			}
			
			curitem = curitem->nextSibling();
		}
		
		if ( !found )
		{
			return res;
		}
		else
		{
			found = false;
		}
	}
	
	if ( curitem )
	{
		DownloadPath( QString::null, curitem->text(0), QString::null, curitem );
		res = true;
	}
	
	return res;
}

/** */
void DCFileBrowser::JumpTo( QString target )
{
	QStringList parts = target.split( "\\", QString::SkipEmptyParts );
	bool found = false;
	
	Q3ListViewItem * curitem = ListView_DIRECTORY->firstChild();
	Q3ListViewItem * parent;
	
	QStringList::const_iterator it = parts.constBegin();
	for ( ; it != parts.constEnd(); ++it )
	{
		parent = curitem;
		curitem = curitem->firstChild();
		
		while ( curitem )
		{
			if ( curitem->text(0) == *it )
			{
				found = true;
				break;
			}
			
			curitem = curitem->nextSibling();
		}
		
		if ( !found )
		{
			curitem = parent;
			break;
		}
		else
		{
			found = false;
		}
	}
	
	if ( curitem )
	{
		if ( it != parts.constEnd() )
		{
			m_sJumpTo = *it;
		}
		else
		{
			m_sJumpTo = "";
		}
		
		ListView_DIRECTORY->clearSelection();
		ListView_DIRECTORY->ensureItemVisible( curitem );
		ListView_DIRECTORY->setSelected( curitem, true );
		ListView_DIRECTORY->setCurrentItem( curitem );
	}
	else
	{
		m_sJumpTo = "";
	}
}

/** */
void DCFileBrowser::slotGotoUser()
{
	DCClient * client = 0;
	if ( m_sNick.isEmpty() || m_sHubHost.isEmpty() )
	{
		client = 0;
	}
	else
	{
		client = g_pConnectionManager->GetClientForHub( m_sHubName.toAscii().constData(), m_sHubHost.toAscii().constData() );
	}
	
	if ( client )
	{
		if ( client->jumpToNick( m_sNick ) )
		{
			client->raise();
			client->setFocus();
		}
		else
		{
			QMessageBox::information(
				this,
				tr("Go to user"),
				tr("Cannot find user")
			);
		}
	}
	else
	{
		QMessageBox::information(
			this,
			tr("Go to user"),
			tr("Cannot find hub")
		);
	}
}

/** */
void DCFileBrowser::slotFind()
{
	bool ok = false;
	QString text = QInputDialog::getText(
		this,
		tr("Find"),
		tr("Search string"),
		QLineEdit::Normal,
		m_sFindString,
		&ok
	);
	
	if ( !ok || text.isEmpty() )
	{
		return;
	}
	else
	{
		m_sFindString = text;
		m_pFoundDir = 0;
		m_nFoundFile = 0;
		m_FoundFiles.clear();
		slotFindNext();
	}
}

bool NameLessThan( const DCFileItem * left, const DCFileItem * right )
{
	return QString::localeAwareCompare( left->m_sName, right->m_sName ) < 0;
}

bool NameGreaterThan( const DCFileItem * left, const DCFileItem * right )
{
	return QString::localeAwareCompare( left->m_sName, right->m_sName ) > 0;
}

bool SizeLessThan( const DCFileItem * left, const DCFileItem * right )
{
	return left->m_nSize < right->m_nSize;
}

bool SizeGreaterThan( const DCFileItem * left, const DCFileItem * right )
{
	return left->m_nSize > right->m_nSize;
}

bool TTHLessThan( const DCFileItem * left, const DCFileItem * right )
{
	return QString::localeAwareCompare( left->m_sHash, right->m_sHash ) < 0;
}

bool TTHGreaterThan( const DCFileItem * left, const DCFileItem * right )
{
	return QString::localeAwareCompare( left->m_sHash, right->m_sHash ) > 0;
}

bool ExtLessThan( const DCFileItem * left, const DCFileItem * right )
{
	/* directory names searched separately so no need to add " file" */
	QString left_ext, right_ext;
	
	int pos = left->m_sName.lastIndexOf('.');
	if ( pos != -1 )
	{
		left_ext = left->m_sName.mid(pos+1);
	}
	
	pos = right->m_sName.lastIndexOf('.');
	if ( pos != -1 )
	{
		right_ext = right->m_sName.mid(pos+1);
	}
	
	return QString::localeAwareCompare( left_ext, right_ext ) < 0;
}

bool ExtGreaterThan( const DCFileItem * left, const DCFileItem * right )
{
	/* directory names searched separately so no need to add " file" */
	QString left_ext, right_ext;
	
	int pos = left->m_sName.lastIndexOf('.');
	if ( pos != -1 )
	{
		left_ext = left->m_sName.mid(pos+1);
	}
	
	pos = right->m_sName.lastIndexOf('.');
	if ( pos != -1 )
	{
		right_ext = right->m_sName.mid(pos+1);
	}
	
	return QString::localeAwareCompare( left_ext, right_ext ) > 0;
}

/** */
void DCFileBrowser::slotFindNext()
{
	/*
	 * Cannot use QListView::findItem() because we need to
	 * search in file names.
	 * 
	 * The search starts from the first directory and
	 * continues from the last found directory and file.
	 */
	
	if ( m_sFindString.isEmpty() )
	{
		/*
		 * copied and pasted to avoid
		 * ending up in a loop of
		 * slotFind() -> slotFindNext() -> slotFind() ...
		 */
		bool ok = false;
		QString text = QInputDialog::getText(
			this,
			tr("Find"),
			tr("Search string"),
			QLineEdit::Normal,
			m_sFindString,
			&ok
		);
		
		if ( !ok || text.isEmpty() )
		{
			return;
		}
		else
		{
			m_sFindString = text;
			m_pFoundDir = 0;
			m_nFoundFile = 0;
			m_FoundFiles.clear();
		}
	}
	
	if ( !m_pFoundDir )
	{
		m_pFoundDir = (DCFileBrowserListItem*) ListView_DIRECTORY->firstChild();
		
		if ( !m_pFoundDir )
		{
			QMessageBox::information(
				this,
				tr("Find"),
				tr("Empty list")
			);
			return;
		}
	}
	
	/*
	 * current directory - already searched,
	 * select next item if there is one
	 */
	if ( m_pFoundDir == ListView_DIRECTORY->currentItem() )
	{
		Q3ListViewItemIterator selcount( ListView_FILES, Q3ListViewItemIterator::Selected );
		if ( selcount.current() )
		{
			++m_nFoundFile;
		}
		
		if ( m_nFoundFile < m_FoundFiles.size() )
		{
			Q3ListViewItem * vi = ListView_FILES->findItem( m_FoundFiles.at(m_nFoundFile), 0 );
			if ( vi )
			{
				ListView_FILES->clearSelection();
				ListView_FILES->ensureItemVisible( vi );
				ListView_FILES->setSelected( vi, true );
				ListView_FILES->setCurrentItem( vi );
				
				return;
			}
		}
	}
	
	/*
	 * No more matches in the current directory.
	 * Start/resume walking the tree.
	 */
	
	Q3ListViewItemIterator dir_it( m_pFoundDir );
	++dir_it;
	
	bool dirmatches = false;
	m_nFoundFile = 0;
	m_FoundFiles.clear();
	
	while ( dir_it.current() )
	{
		DCFileBrowserListItem * fbitem = (DCFileBrowserListItem*) dir_it.current();
		
		/* check if directory name matches */
		if ( fbitem->text(0).contains( m_sFindString, Qt::CaseInsensitive ) )
		{
			m_pFoundDir = fbitem;
			dirmatches = true;
		}
		
		/* search all file names */
		QList<DCFileItem*> founditems;
		for ( QList<DCFileItem*>::const_iterator fnit = fbitem->m_pFileList.constBegin(); fnit != fbitem->m_pFileList.constEnd(); ++fnit )
		{
			/* directory names already searched */
			if ( ((*fnit)->m_bIsDir == false) && ((*fnit)->m_sName.contains( m_sFindString, Qt::CaseInsensitive )) )
			{
				m_pFoundDir = fbitem;
				founditems.append( *fnit );
			}
		}
		
		/* sort the found items according to the view */
		if ( ListView_FILES->sortOrder() == Qt::AscendingOrder )
		{
			switch ( ListView_FILES->sortColumn() )
			{
				case 0:
					qStableSort( founditems.begin(), founditems.end(), NameLessThan );
					break;
				case 1:
				case 2:
					qStableSort( founditems.begin(), founditems.end(), SizeLessThan );
					break;
				case 3:
					qStableSort( founditems.begin(), founditems.end(), ExtLessThan );
					break;
				case 4:
					qStableSort( founditems.begin(), founditems.end(), TTHLessThan );
					break;
				default:
					break;
			};
		}
		else
		{
			switch ( ListView_FILES->sortColumn() )
			{
				case 0:
					qStableSort( founditems.begin(), founditems.end(), NameGreaterThan );
					break;
				case 1:
				case 2:
					qStableSort( founditems.begin(), founditems.end(), SizeGreaterThan );
					break;
				case 3:
					qStableSort( founditems.begin(), founditems.end(), ExtGreaterThan );
					break;
				case 4:
					qStableSort( founditems.begin(), founditems.end(), TTHGreaterThan );
					break;
				default:
					break;
			};
		}
		
		for ( QList<DCFileItem*>::const_iterator found_iter = founditems.constBegin(); found_iter != founditems.constEnd(); ++found_iter )
		{
			m_FoundFiles.append( (*found_iter)->m_sName );
		}
		
		if ( !dirmatches && !m_FoundFiles.isEmpty() )
		{
			m_sJumpTo = m_FoundFiles.first();
		}
		
		if ( dirmatches || !m_FoundFiles.isEmpty() )
		{
			ListView_DIRECTORY->clearSelection();
			ListView_DIRECTORY->ensureItemVisible( fbitem );
			ListView_DIRECTORY->setSelected( fbitem, true );
			ListView_DIRECTORY->setCurrentItem( fbitem );
			
			return;
		}
		
		++dir_it;
	}
	
	QMessageBox::information(
		this,
		tr("Find"),
		tr("No more matches.")
	);
	m_pFoundDir = 0;
	m_nFoundFile = 0;
	m_FoundFiles.clear();
}
