www.gusucode.com > eMule电驴下载VC++源代码-源码程序 > eMule电驴下载VC++源代码-源码程序\code\srchybrid\SearchListCtrl.cpp

    //Download by http://www.NewXing.com
//this file is part of eMule
//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net )
//
//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., 675 Mass Ave, Cambridge, MA 02139, USA.
#include "stdafx.h"
#include "emule.h"
#include "ResizableLib/ResizableSheet.h"
#include "SearchList.h"
#include "emuledlg.h"
#include "MetaDataDlg.h"
#include "SearchDlg.h"
#include "SearchListCtrl.h"
#include "SearchParams.h"
#include "ClosableTabCtrl.h"
#include "PreviewDlg.h"
#include "UpDownClient.h"
#include "ClientList.h"
#include "MemDC.h"
#include "SharedFileList.h"
#include "DownloadQueue.h"
#include "PartFile.h"
#include "KnownFileList.h"
#include "MenuCmds.h"
#include "OtherFunctions.h"
#include "Opcodes.h"
#include "Packets.h"
#include "WebServices.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif


//////////////////////////////////////////////////////////////////////////////
// CSearchResultFileDetailSheet

class CSearchResultFileDetailSheet : public CResizableSheet
{
	DECLARE_DYNAMIC(CSearchResultFileDetailSheet)

public:
	CSearchResultFileDetailSheet(const CSearchFile* file);
	virtual ~CSearchResultFileDetailSheet();

protected:
	const CSearchFile* m_file;
	CMetaDataDlg m_wndMetaData;

	static int sm_iLastActivePage;

	virtual BOOL OnInitDialog();

	DECLARE_MESSAGE_MAP()
	afx_msg void OnDestroy();
};

int CSearchResultFileDetailSheet::sm_iLastActivePage;

IMPLEMENT_DYNAMIC(CSearchResultFileDetailSheet, CResizableSheet)

BEGIN_MESSAGE_MAP(CSearchResultFileDetailSheet, CResizableSheet)
	ON_WM_DESTROY()
END_MESSAGE_MAP()

CSearchResultFileDetailSheet::CSearchResultFileDetailSheet(const CSearchFile* file)
{
	m_file = file;
	m_psh.dwFlags &= ~PSH_HASHELP;
	m_psh.dwFlags |= PSH_NOAPPLYNOW;
	
	m_wndMetaData.m_psp.dwFlags &= ~PSP_HASHELP;

	if (thePrefs.IsExtControlsEnabled())
		m_wndMetaData.SetFile(file);

	if (thePrefs.IsExtControlsEnabled())
		AddPage(&m_wndMetaData);
}

CSearchResultFileDetailSheet::~CSearchResultFileDetailSheet()
{
}

void CSearchResultFileDetailSheet::OnDestroy()
{
	sm_iLastActivePage = GetActiveIndex();
	CResizableSheet::OnDestroy();
}

BOOL CSearchResultFileDetailSheet::OnInitDialog()
{		
	EnableStackedTabs(FALSE);
	BOOL bResult = CResizableSheet::OnInitDialog();
	InitWindowStyles(this);
	EnableSaveRestore(_T("SearchResultFileDetailsSheet")); // call this after(!) OnInitDialog
	SetWindowText(GetResString(IDS_DETAILS) + _T(": ") + m_file->GetFileName());
	if (sm_iLastActivePage < GetPageCount())
		SetActivePage(sm_iLastActivePage);
	return bResult;
}


//////////////////////////////////////////////////////////////////////////////
// CSearchListCtrl
#define DLC_DT_TEXT (DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX|DT_END_ELLIPSIS)

IMPLEMENT_DYNAMIC(CSearchListCtrl, CMuleListCtrl)
CSearchListCtrl::CSearchListCtrl()
{
	searchlist = NULL;
	m_nResultsID = 0;
	SetGeneralPurposeFind(true);
}

void CSearchListCtrl::SetStyle()
{
	if (thePrefs.IsDoubleClickEnabled())
		SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
	else
		SetExtendedStyle(LVS_EX_ONECLICKACTIVATE | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);
}

void CSearchListCtrl::Init(CSearchList* in_searchlist)
{
	CImageList ilDummyImageList; //dummy list for getting the proper height of listview entries
	ilDummyImageList.Create(1, theApp.GetSmallSytemIconSize().cy,theApp.m_iDfltImageListColorFlags|ILC_MASK, 1, 1); 
	SetImageList(&ilDummyImageList, LVSIL_SMALL);
	ASSERT( (GetStyle() & LVS_SHAREIMAGELISTS) == 0 );
	ilDummyImageList.Detach();

	ModifyStyle(LVS_SINGLESEL,0);
	SetStyle();

	CToolTipCtrl* tooltip = GetToolTips();
	if (tooltip){
		tooltip->ModifyStyle(0, TTS_NOPREFIX);
		tooltip->SetDelayTime(TTDT_AUTOPOP, 20000);
		tooltip->SetDelayTime(TTDT_INITIAL, thePrefs.GetToolTipDelay()*1000);
	}
	searchlist = in_searchlist;

	InsertColumn(0,GetResString(IDS_DL_FILENAME),LVCFMT_LEFT,250);
	InsertColumn(1,GetResString(IDS_DL_SIZE),LVCFMT_LEFT,70);
	InsertColumn(2,GetResString(IDS_SEARCHAVAIL) + _T("/") + GetResString(IDS_COMPLETE) + _T(" (") + GetResString(IDS_DL_SOURCES) + _T(')'),LVCFMT_LEFT,50);
	InsertColumn(3,GetResString(IDS_TYPE),LVCFMT_LEFT,65);
	InsertColumn(4,GetResString(IDS_FILEID),LVCFMT_LEFT,220);
	InsertColumn(5,GetResString(IDS_ARTIST),LVCFMT_LEFT,100);
	InsertColumn(6,GetResString(IDS_ALBUM),LVCFMT_LEFT,100);
	InsertColumn(7,GetResString(IDS_TITLE),LVCFMT_LEFT,100);
	InsertColumn(8,GetResString(IDS_LENGTH),LVCFMT_LEFT,50);
	InsertColumn(9,GetResString(IDS_BITRATE),LVCFMT_LEFT,50);
	InsertColumn(10,GetResString(IDS_CODEC),LVCFMT_LEFT,50);
	InsertColumn(11,GetResString(IDS_FOLDER),LVCFMT_LEFT,220);
	InsertColumn(12,GetResString(IDS_KNOWN),LVCFMT_LEFT,50);

	CreateMenues();

	LoadSettings(CPreferences::tableSearch);

	// Barry - Use preferred sort order from preferences
	int sortItem = thePrefs.GetColumnSortItem(CPreferences::tableSearch);
	if (sortItem != -1){// don't force a sorting if '-1' is specified, so we can better see how the search results are arriving
		bool sortAscending = thePrefs.GetColumnSortAscending(CPreferences::tableSearch);
		SetSortArrow(sortItem, sortAscending);
		SortItems(SortProc, sortItem + (sortAscending ? 0:100));
	}
}

CSearchListCtrl::~CSearchListCtrl(){
}

void CSearchListCtrl::Localize()
{
	CHeaderCtrl* pHeaderCtrl = GetHeaderCtrl();
	HDITEM hdi;
	hdi.mask = HDI_TEXT;
	CString strRes;

	for (int icol=0;icol<pHeaderCtrl->GetItemCount();icol++) {
		switch (icol) {
			case 0: strRes = GetResString(IDS_DL_FILENAME); break;
			case 1: strRes = GetResString(IDS_DL_SIZE); break;
			case 2: strRes = GetResString(IDS_SEARCHAVAIL) + _T("/") + GetResString(IDS_COMPLETE) + _T(" (") + GetResString(IDS_DL_SOURCES) + _T(')'); break;
			case 3: strRes = GetResString(IDS_TYPE); break;
			case 4: strRes = GetResString(IDS_FILEID); break;
			case 5: strRes = GetResString(IDS_ARTIST); break;
			case 6: strRes = GetResString(IDS_ALBUM); break;
			case 7: strRes = GetResString(IDS_TITLE); break;
			case 8: strRes = GetResString(IDS_LENGTH); break;
			case 9: strRes = GetResString(IDS_BITRATE); break;
			case 10: strRes = GetResString(IDS_CODEC); break;
			case 11: strRes = GetResString(IDS_FOLDER); break;
			case 12: strRes = GetResString(IDS_KNOWN); break;
		}
	
		hdi.pszText = strRes.GetBuffer();
		pHeaderCtrl->SetItem(icol, &hdi);
		strRes.ReleaseBuffer();
	}

	CreateMenues();
}

BEGIN_MESSAGE_MAP(CSearchListCtrl, CMuleListCtrl)
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnClick)
	ON_WM_CONTEXTMENU()
	ON_NOTIFY_REFLECT(LVN_GETINFOTIP, OnLvnGetInfoTip)
	ON_NOTIFY_REFLECT(LVN_DELETEALLITEMS, OnLvnDeleteallitems)
	ON_NOTIFY_REFLECT(NM_CLICK, OnClick)
	ON_NOTIFY_REFLECT(NM_DBLCLK,OnDblClick)
	ON_WM_KEYDOWN()
END_MESSAGE_MAP()

// CSearchListCtrl message handlers

void CSearchListCtrl::AddResult(const CSearchFile* toshow)
{
	// update tab-counter for the given searchfile
	CClosableTabCtrl& searchselect = theApp.emuledlg->searchwnd->GetSearchSelector();
	int iTabItems = searchselect.GetItemCount();
	if (iTabItems > 0)
	{
		TCITEM tabitem;
		tabitem.mask = TCIF_PARAM;
		tabitem.lParam = -1;
		for (int tabCounter = 0; tabCounter < iTabItems; tabCounter++)
		{
			if (searchselect.GetItem(tabCounter, &tabitem) && tabitem.lParam != NULL)
				if (((const SSearchParams*)tabitem.lParam)->dwSearchID == toshow->GetSearchID())
					break;
		}

		if (tabitem.lParam != -1 && tabitem.lParam != NULL && ((const SSearchParams*)tabitem.lParam)->dwSearchID == toshow->GetSearchID())
		{
			TCHAR szText[510];
			tabitem.pszText = szText;
			tabitem.cchTextMax = ARRSIZE(szText);
			tabitem.mask = TCIF_TEXT;
			if (searchselect.GetItem(tabCounter, &tabitem))
			{
				LPTSTR psz = _tcsrchr(szText, _T('('));
				if (psz){
					if (psz > szText)
						psz--;
					*psz = _T('\0');
				}

				CString sourceStr;
				sourceStr.Format(_T("%s (%u)"), szText, searchlist->GetFoundFiles(toshow->GetSearchID()));

				tabitem.mask = TCIF_TEXT;
				tabitem.pszText = const_cast<LPTSTR>((LPCTSTR)sourceStr);
				searchselect.SetItem(tabCounter, &tabitem);
			}
		}
	}

	if (toshow->GetSearchID() != m_nResultsID)
		return;

	//TODO: Here we have a problem. Since this listview control is owner drawn the listview items may no longer
	//contain the actual information which is displayed. The usage of the 'Find' command may therefore deal with
	//wrong item contents (which are stored in the listview items right here in this function).

	int itemnr = InsertItem(LVIF_TEXT|LVIF_PARAM,GetItemCount(),toshow->GetFileName(),0,0,0,(LPARAM)toshow);
	TCHAR cbuffer[80];
	SetItemText(itemnr,1,CastItoXBytes(toshow->GetFileSize()));
	_itot(toshow->GetSourceCount(),cbuffer,10);
	uint32 uCompleteSources;
	if (toshow->GetIntTagValue(FT_COMPLETE_SOURCES, uCompleteSources))
		_sntprintf(cbuffer,ARRSIZE(cbuffer),_T("%s/%u"),cbuffer,uCompleteSources);
	int iClients = toshow->GetClientsCount();
	if (iClients)
		_sntprintf(cbuffer,ARRSIZE(cbuffer),_T("%s (%u)"),cbuffer,iClients);
	SetItemText(itemnr,2,cbuffer);
	SetItemText(itemnr,3,toshow->GetFileType());
	SetItemText(itemnr,4,md4str(toshow->GetFileHash()));
	SetItemText(itemnr,5,toshow->GetStrTagValue(FT_MEDIA_ARTIST));
	SetItemText(itemnr,6,toshow->GetStrTagValue(FT_MEDIA_ALBUM));
	SetItemText(itemnr,7,toshow->GetStrTagValue(FT_MEDIA_TITLE));
	uint32 nMediaLength = toshow->GetIntTagValue(FT_MEDIA_LENGTH);
	if (nMediaLength){
		CString strMediaLength;
		SecToTimeLength(nMediaLength, strMediaLength);
		_sntprintf(cbuffer,ARRSIZE(cbuffer),_T("%s"),strMediaLength);
	}
	else
		cbuffer[0] = _T('\0');
	SetItemText(itemnr,8,cbuffer);
	uint32 nBitrate = toshow->GetIntTagValue(FT_MEDIA_BITRATE);
	if (nBitrate)
		_sntprintf(cbuffer,ARRSIZE(cbuffer),_T("%u kBit/s"),nBitrate);
	else
		cbuffer[0] = _T('\0');
	SetItemText(itemnr,9,cbuffer);
	SetItemText(itemnr,10,toshow->GetStrTagValue(FT_MEDIA_CODEC));

	if (toshow->GetDirectory())
		SetItemText(itemnr,11,toshow->GetDirectory());

	if (toshow->m_eKnown == CSearchFile::Shared)
		SetItemText(itemnr,12,GetResString(IDS_SHARED));
	else if (toshow->m_eKnown == CSearchFile::Downloading)
		SetItemText(itemnr,12,GetResString(IDS_DOWNLOADING));
	else if (toshow->m_eKnown == CSearchFile::Downloaded)
		SetItemText(itemnr,12,GetResString(IDS_DOWNLOADED));
}

void CSearchListCtrl::UpdateSources(const CSearchFile* toupdate)
{
	LVFINDINFO find;
	find.flags = LVFI_PARAM;
	find.lParam = (LPARAM)toupdate;
	int index = FindItem(&find);
	if (index != (-1)){
		CString buffer;
		buffer.Format(_T("%u"), toupdate->GetSourceCount());
		uint32 uCompleteSources;
		if (toupdate->GetIntTagValue(FT_COMPLETE_SOURCES, uCompleteSources))
			buffer.AppendFormat(_T("/%u"), uCompleteSources);
		int iClientsCount = toupdate->GetClientsCount();
		if (iClientsCount)
			buffer.AppendFormat(_T(" (%u)"), iClientsCount);

		SetItemText(index,2,buffer);

		uint16 maxhitsname = (uint16)-1;
		bool change=false;
		CString strFileName = toupdate->GetFileName();
		// update expanded childs
		if (toupdate->GetListIsExpanded()) {
			for (POSITION pos = theApp.searchlist->list.GetHeadPosition(); pos != NULL; ){
				const CSearchFile* cur_file = theApp.searchlist->list.GetNext(pos);
				if (cur_file->GetListParent()==toupdate) {
					if (maxhitsname<cur_file->GetListChildCount()) {
						maxhitsname=cur_file->GetListChildCount();
						strFileName=cur_file->GetFileName();
						change=true;
					}
					// i am your child, darth! but do I exist? Do I?
					LVFINDINFO find;
					find.flags = LVFI_PARAM;
					find.lParam = (LPARAM)cur_file;
					int index2 = FindItem(&find);
					if (index2!=-1)
						// yes, I see you...
						Update(index2);
					else
						// no, but I create you now for display
						InsertItem(LVIF_PARAM|LVIF_TEXT,index+1,cur_file->GetFileName(),0,0,0,(LPARAM)cur_file);
				}
			}
		}

		// set name in list to the most used filename
		if (change)
			SetItemText(index,0,strFileName);
		Update(index);
	}
}

void CSearchListCtrl::RemoveResult(const CSearchFile* toremove)
{
	LVFINDINFO find;
	find.flags = LVFI_PARAM;
	find.lParam = (LPARAM)toremove;
	sint32 result = FindItem(&find);
	if(result != (-1) )
		DeleteItem(result);
}

void CSearchListCtrl::ShowResults(uint32 nResultsID)
{
	DeleteAllItems();
	m_nResultsID = nResultsID;
	searchlist->ShowResults(m_nResultsID);
}

void CSearchListCtrl::OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

	// Barry - Store sort order in preferences
	// Determine ascending based on whether already sorted on this column
	int sortItem = thePrefs.GetColumnSortItem(CPreferences::tableSearch);
	bool m_oldSortAscending = thePrefs.GetColumnSortAscending(CPreferences::tableSearch);
	bool sortAscending = (sortItem != pNMListView->iSubItem) ? true : !m_oldSortAscending;

	// Item is column clicked
	sortItem = pNMListView->iSubItem;

	// Save new preferences
	thePrefs.SetColumnSortItem(CPreferences::tableSearch, sortItem);
	thePrefs.SetColumnSortAscending(CPreferences::tableSearch, sortAscending);

	// Sort table
	SetSortArrow(sortItem, sortAscending);
	SortItems(SortProc, sortItem + (sortAscending ? 0:100));

	*pResult = 0;
}

int CSearchListCtrl::SortProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	const CSearchFile* item1 = (CSearchFile*)lParam1;
	const CSearchFile* item2 = (CSearchFile*)lParam2;

	int sortMod = 1;
	if(lParamSort >= 100) {
		sortMod = -1;
		lParamSort -= 100;
	}

	int comp;

	if (item1->GetListParent()==NULL && item2->GetListParent()!=NULL){
		if (item1 == item2->GetListParent())
			return -1;
		comp = Compare(item1, item2->m_list_parent, lParamSort) * sortMod;
	}
	else if (item2->GetListParent()==NULL && item1->GetListParent()!=NULL){
		if (item1->m_list_parent == item2)
			return 1;
		comp = Compare(item1->GetListParent(), item2, lParamSort) * sortMod;
	}
	else if (item1->GetListParent()==NULL){
		comp = Compare(item1, item2, lParamSort) * sortMod;
	}
	else{
		comp = Compare(item1->GetListParent(), item2->GetListParent(), lParamSort);
		if (comp != 0)
			return sortMod * comp;

		if ((item1->GetListParent()==NULL && item2->GetListParent()!=NULL) || (item2->GetListParent()==NULL && item1->GetListParent()!=NULL)){
			if (item1->GetListParent()==NULL)
				return -1;
			else
				return 1;
		}
		comp = CompareChild(item1, item2, lParamSort);
	}

	return comp;
}

int CSearchListCtrl::CompareChild(const CSearchFile* item1, const CSearchFile* item2, LPARAM lParamSort)
{
	switch(lParamSort){
		case 0: //filename asc
			return _tcsicmp(item1->GetFileName(),item2->GetFileName());
		case 100: //filename desc
			return _tcsicmp(item2->GetFileName(),item1->GetFileName());

		default:
			// always sort by descending availability
			return CompareUnsigned(item2->GetIntTagValue(FT_SOURCES), item1->GetIntTagValue(FT_SOURCES));
	}
}

int CSearchListCtrl::Compare(const CSearchFile* item1, const CSearchFile* item2, LPARAM lParamSort)
{
	switch(lParamSort){
		case 0: //filename asc
			return _tcsicmp(item1->GetFileName(),item2->GetFileName());
		case 100: //filename desc
			return _tcsicmp(item2->GetFileName(),item1->GetFileName());

		case 1: //size asc
			return CompareUnsigned(item1->GetFileSize(), item2->GetFileSize());
		case 101: //size desc
			return CompareUnsigned(item2->GetFileSize(), item1->GetFileSize());

		case 2: //sources asc
			return CompareUnsigned(item1->GetIntTagValue(FT_SOURCES), item2->GetIntTagValue(FT_SOURCES));
		case 102: //sources desc
			return CompareUnsigned(item2->GetIntTagValue(FT_SOURCES), item1->GetIntTagValue(FT_SOURCES));

		case 3: //type asc
			return item1->GetFileType().Compare(item2->GetFileType());
		case 103: //type  desc
			return item2->GetFileType().Compare(item1->GetFileType());

		case 4: //filehash asc
			return memcmp(item1->GetFileHash(),item2->GetFileHash(),16);
		case 104: //filehash desc
			return memcmp(item2->GetFileHash(),item1->GetFileHash(),16);

		case 5:
			return CompareOptStringNoCase(item1->GetStrTagValue(FT_MEDIA_ARTIST), item2->GetStrTagValue(FT_MEDIA_ARTIST));
		case 105:
			return -CompareOptStringNoCase(item1->GetStrTagValue(FT_MEDIA_ARTIST), item2->GetStrTagValue(FT_MEDIA_ARTIST));

		case 6:
			return CompareOptStringNoCase(item1->GetStrTagValue(FT_MEDIA_ALBUM), item2->GetStrTagValue(FT_MEDIA_ALBUM));
		case 106:
			return -CompareOptStringNoCase(item1->GetStrTagValue(FT_MEDIA_ALBUM), item2->GetStrTagValue(FT_MEDIA_ALBUM));

		case 7:
			return CompareOptStringNoCase(item1->GetStrTagValue(FT_MEDIA_TITLE), item2->GetStrTagValue(FT_MEDIA_TITLE));
		case 107:
			return -CompareOptStringNoCase(item1->GetStrTagValue(FT_MEDIA_TITLE), item2->GetStrTagValue(FT_MEDIA_TITLE));

		case 8:
			return CompareUnsigned(item1->GetIntTagValue(FT_MEDIA_LENGTH), item2->GetIntTagValue(FT_MEDIA_LENGTH));
		case 108:
			return -CompareUnsigned(item1->GetIntTagValue(FT_MEDIA_LENGTH), item2->GetIntTagValue(FT_MEDIA_LENGTH));

		case 9:
			return CompareUnsigned(item1->GetIntTagValue(FT_MEDIA_BITRATE), item2->GetIntTagValue(FT_MEDIA_BITRATE));
		case 109:
			return -CompareUnsigned(item1->GetIntTagValue(FT_MEDIA_BITRATE), item2->GetIntTagValue(FT_MEDIA_BITRATE));

		case 10:
			return CompareOptStringNoCase(item1->GetStrTagValue(FT_MEDIA_CODEC), item2->GetStrTagValue(FT_MEDIA_CODEC));
		case 110:
			return -CompareOptStringNoCase(item1->GetStrTagValue(FT_MEDIA_CODEC), item2->GetStrTagValue(FT_MEDIA_CODEC));

		case 11: //path asc
			return CompareOptStringNoCase(item1->GetDirectory(), item2->GetDirectory());
		case 111: //path desc
			return -CompareOptStringNoCase(item1->GetDirectory(), item2->GetDirectory());

		case 12:
			return item1->GetKnownType() - item2->GetKnownType();
		case 121:
			return -(item1->GetKnownType() - item2->GetKnownType());

		default:
			return 0;
	}
}

void CSearchListCtrl::OnContextMenu(CWnd* pWnd, CPoint point)
{
	int iSelected = 0;
	int iToDownload = 0;
	int iToPreview = 0;
	POSITION pos = GetFirstSelectedItemPosition();
	while (pos != NULL)
	{
		const CSearchFile* pFile = (CSearchFile*)GetItemData(GetNextSelectedItem(pos));
		if (pFile)
		{
			iSelected++;
			if (pFile->IsPreviewPossible())
				iToPreview++;
			if (!theApp.downloadqueue->IsFileExisting(pFile->GetFileHash(), false))
				iToDownload++;
		}
	}

	if (thePrefs.IsExtControlsEnabled())
		m_SearchFileMenu.EnableMenuItem(MP_RESUMEPAUSED, iToDownload > 0 ? MF_ENABLED : MF_GRAYED);
	m_SearchFileMenu.EnableMenuItem(MP_RESUME, iToDownload > 0 ? MF_ENABLED : MF_GRAYED);
	m_SearchFileMenu.EnableMenuItem(MP_DETAIL, iSelected == 1 ? MF_ENABLED : MF_GRAYED);
	m_SearchFileMenu.EnableMenuItem(MP_GETED2KLINK, iSelected > 0 ? MF_ENABLED : MF_GRAYED);
	m_SearchFileMenu.EnableMenuItem(MP_GETHTMLED2KLINK, iSelected > 0 ? MF_ENABLED : MF_GRAYED);
	m_SearchFileMenu.EnableMenuItem(MP_REMOVESELECTED, iSelected > 0 ? MF_ENABLED : MF_GRAYED);
	m_SearchFileMenu.EnableMenuItem(MP_PREVIEW, iToPreview == 1 ? MF_ENABLED : MF_GRAYED);
	m_SearchFileMenu.EnableMenuItem(MP_REMOVEALL, theApp.emuledlg->searchwnd->CanDeleteAllSearches() ? MF_ENABLED : MF_GRAYED);
	m_SearchFileMenu.EnableMenuItem(MP_REMOVE, theApp.emuledlg->searchwnd->CanDeleteSearch(m_nResultsID) ? MF_ENABLED : MF_GRAYED);
	m_SearchFileMenu.EnableMenuItem(MP_FIND, GetItemCount() > 0 ? MF_ENABLED : MF_GRAYED);

	CMenu WebMenu;
	WebMenu.CreateMenu();
	int iWebMenuEntries = theWebServices.GetFileMenuEntries(WebMenu);
	UINT flag2 = (iWebMenuEntries == 0 || iSelected != 1) ? MF_GRAYED : MF_STRING;
	m_SearchFileMenu.AppendMenu(MF_POPUP | flag2, (UINT_PTR)WebMenu.m_hMenu, GetResString(IDS_WEBSERVICES));
	
	if (iToDownload > 0)
		m_SearchFileMenu.SetDefaultItem( ( !thePrefs.AddNewFilesPaused() || !thePrefs.IsExtControlsEnabled() )?MP_RESUME:MP_RESUMEPAUSED);
	else
		m_SearchFileMenu.SetDefaultItem((UINT)-1);

	GetPopupMenuPos(*this, point);
	m_SearchFileMenu.TrackPopupMenu(TPM_LEFTALIGN |TPM_RIGHTBUTTON,point.x,point.y,this);
	m_SearchFileMenu.RemoveMenu(m_SearchFileMenu.GetMenuItemCount()-1,MF_BYPOSITION);
	VERIFY( WebMenu.DestroyMenu() );
}


BOOL CSearchListCtrl::OnCommand(WPARAM wParam, LPARAM lParam)
{
	if (wParam == MP_FIND)
	{
		OnFindStart();
		return TRUE;
	}

	int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED);
	if (iSel != -1)
	{
		const CSearchFile* file = (CSearchFile*)GetItemData(iSel);
		switch (wParam)
		{
			case MP_GETED2KLINK:{
				CWaitCursor curWait;
				CString clpbrd;
				POSITION pos = GetFirstSelectedItemPosition();
				while (pos!=NULL)
				{
					if (!clpbrd.IsEmpty())
						clpbrd += _T("\r\n");
					clpbrd += CreateED2kLink((CSearchFile*)GetItemData(GetNextSelectedItem(pos)));
				}
				theApp.CopyTextToClipboard(clpbrd);
				return TRUE;
			}
			case MP_GETHTMLED2KLINK:{
				CWaitCursor curWait;
				CString clpbrd;
				POSITION pos = GetFirstSelectedItemPosition();
				while (pos!=NULL)
				{
					if (!clpbrd.IsEmpty())
						clpbrd += _T("<br />\r\n");
					clpbrd += CreateHTMLED2kLink((CSearchFile*)GetItemData(GetNextSelectedItem(pos)));
				}
				theApp.CopyTextToClipboard(clpbrd);
				return TRUE;
			}
			case MP_RESUMEPAUSED:
			case MP_RESUME:
				theApp.emuledlg->searchwnd->DownloadSelected(wParam==MP_RESUMEPAUSED);
				return TRUE;
			case MP_REMOVESELECTED:{
				CWaitCursor curWait;
				SetRedraw(FALSE);
				while (GetFirstSelectedItemPosition()!=NULL) 
				{
					POSITION pos = GetFirstSelectedItemPosition();
					CSearchFile* pItem = (CSearchFile*)GetItemData(GetNextSelectedItem(pos));
					HideSources(pItem);
					theApp.searchlist->RemoveResult(pItem);
				}
				SetRedraw(TRUE);
				return TRUE;
			}
			case MPG_ALTENTER:
			case MP_DETAIL:
				if (file){
					CSearchResultFileDetailSheet sheet(file);
					sheet.DoModal();
				}
				return TRUE;
			case MP_PREVIEW:
				if (file){
					if (file->GetPreviews().GetSize() > 0){
						// already have previews
						(new PreviewDlg())->SetFile(file);
					}
					else{
						CUpDownClient* newclient = new CUpDownClient(NULL, file->GetClientPort(),file->GetClientID(),file->GetClientServerIP(),file->GetClientServerPort(), true);
						if (!theApp.clientlist->AttachToAlreadyKnown(&newclient,NULL)){
							theApp.clientlist->AddClient(newclient);
						}
						newclient->SendPreviewRequest(file);
						// add to res - later
						AddLogLine(true, "Preview Requested - Please wait");
					}
				}
				return TRUE;
			default:
				if (wParam>=MP_WEBURL && wParam<=MP_WEBURL+256){
					theWebServices.RunURL(file, wParam);
					return TRUE;
				}
				break;
		}
	}
	switch (wParam){
		case MP_REMOVEALL:{
			CWaitCursor curWait;
			theApp.emuledlg->searchwnd->DeleteAllSearchs();
			break;
		}
		case MP_REMOVE:{
			CWaitCursor curWait;
			theApp.emuledlg->searchwnd->DeleteSearch(m_nResultsID);
			break;
		}
	}

	return FALSE;
}

void CSearchListCtrl::OnLvnDeleteallitems(NMHDR *pNMHDR, LRESULT *pResult)
{
	// To suppress subsequent LVN_DELETEITEM notification messages, return TRUE.
	*pResult = TRUE;
}

void CSearchListCtrl::CreateMenues()
{
	if (m_SearchFileMenu)
		VERIFY( m_SearchFileMenu.DestroyMenu() );

	m_SearchFileMenu.CreatePopupMenu();
	m_SearchFileMenu.AddMenuTitle(GetResString(IDS_FILE));
	m_SearchFileMenu.AppendMenu(MF_STRING,MP_RESUME, GetResString(IDS_DOWNLOAD));

	if (thePrefs.IsExtControlsEnabled())
		m_SearchFileMenu.AppendMenu(MF_STRING, MP_RESUMEPAUSED, GetResString(IDS_DOWNLOAD) + _T(" (") + GetResString(IDS_PAUSED) + _T(")"));

	m_SearchFileMenu.AppendMenu(MF_STRING,MP_PREVIEW, GetResString(IDS_DL_PREVIEW));
	if (thePrefs.IsExtControlsEnabled())
		m_SearchFileMenu.AppendMenu(MF_STRING,MP_DETAIL, GetResString(IDS_SHOWDETAILS));
	m_SearchFileMenu.AppendMenu(MF_SEPARATOR);
	m_SearchFileMenu.AppendMenu(MF_STRING,MP_GETED2KLINK, GetResString(IDS_DL_LINK1));
	m_SearchFileMenu.AppendMenu(MF_STRING,MP_GETHTMLED2KLINK, GetResString(IDS_DL_LINK2));
	m_SearchFileMenu.AppendMenu(MF_SEPARATOR);
	m_SearchFileMenu.AppendMenu(MF_STRING,MP_REMOVESELECTED, GetResString(IDS_REMOVESELECTED));
	m_SearchFileMenu.AppendMenu(MF_STRING,MP_REMOVE, GetResString(IDS_REMOVESEARCHSTRING));
	m_SearchFileMenu.AppendMenu(MF_STRING,MP_REMOVEALL, GetResString(IDS_REMOVEALLSEARCH));

	m_SearchFileMenu.AppendMenu(MF_SEPARATOR);
	m_SearchFileMenu.AppendMenu(MF_STRING, MP_FIND, GetResString(IDS_FIND));
}

void CSearchListCtrl::OnLvnGetInfoTip(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMLVGETINFOTIP pGetInfoTip = reinterpret_cast<LPNMLVGETINFOTIP>(pNMHDR);
	if (pGetInfoTip->iSubItem == 0)
	{
		LVHITTESTINFO hti = {0};
		::GetCursorPos(&hti.pt);
		ScreenToClient(&hti.pt);
		bool bOverMainItem = (SubItemHitTest(&hti) != -1 && hti.iItem == pGetInfoTip->iItem && hti.iSubItem == 0);

		// those tooltips are very nice for debugging/testing but pretty annoying for general usage
		// enable tooltips only if Shift+Ctrl is currently pressed
		bool bShowInfoTip = ((GetKeyState(VK_SHIFT) & 0x8000) && (GetKeyState(VK_CONTROL) & 0x8000));

		if (!bShowInfoTip){
			if (!bOverMainItem){
				// don' show the default label tip for the main item, if the mouse is not over the main item
				if ((pGetInfoTip->dwFlags & LVGIT_UNFOLDED) == 0 && pGetInfoTip->cchTextMax > 0 && pGetInfoTip->pszText[0] != '\0')
					pGetInfoTip->pszText[0] = '\0';
			}
			return;
		}

		const CSearchFile* file = (CSearchFile*)GetItemData(pGetInfoTip->iItem);
		if (file && pGetInfoTip->pszText && pGetInfoTip->cchTextMax > 0){
			CString strInfo;
			const CArray<CTag*,CTag*>& tags = file->GetTags();
			for (int i = 0; i < tags.GetSize(); i++){
				const CTag* tag = tags[i];
				if (tag){
					CString strTag;
					switch (tag->tag.specialtag){
						case FT_FILENAME:
							strTag.Format(_T("%s: %s"), GetResString(IDS_SW_NAME), tag->tag.stringvalue);
							break;
						case FT_FILESIZE:
							strTag.Format(_T("%s: %s"), GetResString(IDS_DL_SIZE), CastItoXBytes(tag->tag.intvalue));
							break;
						case FT_FILETYPE:
							strTag.Format(_T("%s: %s"), GetResString(IDS_TYPE), tag->tag.stringvalue);
							break;
						case FT_FILEFORMAT:
							strTag.Format(_T("%s: %s"), GetResString(IDS_SEARCHEXTENTION), tag->tag.stringvalue);
							break;
						case FT_SOURCES:
							strTag.Format(_T("%s: %u"), GetResString(IDS_SEARCHAVAIL), tag->tag.intvalue);
							break;
						case 0x13: // remote client's upload file priority (tested with Hybrid 0.47)
							if (tag->tag.intvalue == 0)
								strTag = GetResString(IDS_PRIORITY) + _T(": ") + GetResString(IDS_PRIONORMAL);
							else if (tag->tag.intvalue == 2)
								strTag = GetResString(IDS_PRIORITY) + _T(": ") + GetResString(IDS_PRIOHIGH);
							else if (tag->tag.intvalue == -2)
								strTag = GetResString(IDS_PRIORITY) + _T(": ") + GetResString(IDS_PRIOLOW);
						#ifdef _DEBUG
							else
								strTag.Format(_T("%s: %d (***Unknown***)"), GetResString(IDS_PRIORITY), tag->tag.intvalue);
						#endif
							break;
						default:{
							bool bUnkTag = false;
							if (tag->tag.tagname){
								strTag.Format(_T("%s: "), tag->tag.tagname);
								strTag = strTag.Left(1).MakeUpper() + strTag.Mid(1);
							}
							else{
								extern CString GetName(const CTag* pTag);
								CString strTagName = GetName(tag);
								if (!strTagName.IsEmpty()){
									strTag.Format(_T("%s: "), strTagName);
								}
								else{
								#ifdef _DEBUG
									strTag.Format(_T("Unknown tag #%02X: "), tag->tag.specialtag);
								#else
									bUnkTag = true;
								#endif
								}
							}
							if (!bUnkTag){
								if (tag->tag.type == 2)
									strTag += tag->tag.stringvalue;
								else if (tag->tag.type == 3){
									if (tag->tag.specialtag == FT_MEDIA_LENGTH){
										CString strTemp;
										SecToTimeLength(tag->tag.intvalue, strTemp);
										strTag += strTemp;
									}
									else{
										TCHAR szBuff[16];
										_itot(tag->tag.intvalue, szBuff, 10);
										strTag += szBuff;
									}
								}
								else if (tag->tag.type == 4){
									TCHAR szBuff[32];
									_sntprintf(szBuff, ARRSIZE(szBuff), _T("%f"), tag->tag.floatvalue);
									strTag += szBuff;
								}
								else{
								#ifdef _DEBUG
									CString strBuff;
									strBuff.Format(_T("Unknown value type=#%02X"), tag->tag.type);
									strTag += strBuff;
								#else
									strTag.Empty();
								#endif
								}
							}
						}
					}
					if (!strTag.IsEmpty()){
						if (!strInfo.IsEmpty())
							strInfo += _T("\n");
						strInfo += strTag;
						if (strInfo.GetLength() >= pGetInfoTip->cchTextMax)
							break;
					}
				}
			}

#ifdef _DEBUG
			if (file->GetClientsCount()){
				CString strSource;
				if (file->GetClientID() && file->GetClientPort()){
					uint32 uClientIP = file->GetClientID();
					uint32 uServerIP = file->GetClientServerIP();
					strSource.Format(_T("Source: %u.%u.%u.%u:%u  Server: %u.%u.%u.%u:%u"), 
						(uint8)uClientIP,(uint8)(uClientIP>>8),(uint8)(uClientIP>>16),(uint8)(uClientIP>>24), file->GetClientPort(),
						(uint8)uServerIP,(uint8)(uServerIP>>8),(uint8)(uServerIP>>16),(uint8)(uServerIP>>24), file->GetClientServerPort());
					if (!strInfo.IsEmpty())
						strInfo += _T("\n");
					strInfo += strSource;
				}

				const CSimpleArray<CSearchFile::SClient>& aClients = file->GetClients();
				for (int i = 0; i < aClients.GetSize(); i++){
					uint32 uClientIP = aClients[i].m_nIP;
					uint32 uServerIP = aClients[i].m_nServerIP;
					strSource.Format(_T("Source: %u.%u.%u.%u:%u  Server: %u.%u.%u.%u:%u"), 
						(uint8)uClientIP,(uint8)(uClientIP>>8),(uint8)(uClientIP>>16),(uint8)(uClientIP>>24), aClients[i].m_nPort,
						(uint8)uServerIP,(uint8)(uServerIP>>8),(uint8)(uServerIP>>16),(uint8)(uServerIP>>24), aClients[i].m_nServerPort);
					if (!strInfo.IsEmpty())
						strInfo += _T("\n");
					strInfo += strSource;
					if (strInfo.GetLength() >= pGetInfoTip->cchTextMax)
						break;
				}
			}

			if (file->GetServers().GetSize()){
				CString strServer;
				const CSimpleArray<CSearchFile::SServer>& aServers = file->GetServers();
				for (int i = 0; i < aServers.GetSize(); i++){
					uint32 uServerIP = aServers[i].m_nIP;
					strServer.Format(_T("Server: %u.%u.%u.%u:%u  Avail: %u"), 
						(uint8)uServerIP,(uint8)(uServerIP>>8),(uint8)(uServerIP>>16),(uint8)(uServerIP>>24), aServers[i].m_nPort, aServers[i].m_uAvail);
					if (!strInfo.IsEmpty())
						strInfo += _T("\n");
					strInfo += strServer;
					if (strInfo.GetLength() >= pGetInfoTip->cchTextMax)
						break;
				}
			}
#endif
			_tcsncpy(pGetInfoTip->pszText, strInfo, pGetInfoTip->cchTextMax);
			pGetInfoTip->pszText[pGetInfoTip->cchTextMax-1] = _T('\0');
		}
	}
	
	*pResult = 0;
}

void CSearchListCtrl::ExpandCollapseItem(int item)
{
	if (item == -1)
		return;

	CSearchFile* searchfile = (CSearchFile*)GetItemData(item);
	if (searchfile->GetListParent() != NULL)
	{
		searchfile = searchfile->GetListParent();
 		LVFINDINFO find;
		find.flags = LVFI_PARAM;
		find.lParam = (LPARAM)searchfile;
		item = FindItem(&find);
		if (item == -1)
			return;
	}
	if (!searchfile)
		return;

	// Check if the source branch is disable
	if (!searchfile->GetListIsExpanded())
	{
		// only expand when more than one child (more than the original entry itself)
		if (searchfile->GetListChildCount() < 2)
			return;

		// Go through the whole list to find out the sources for this file
		SetRedraw(FALSE);
		for (POSITION pos = theApp.searchlist->list.GetHeadPosition(); pos != NULL; ){
			const CSearchFile* cur_file = theApp.searchlist->list.GetNext(pos);
			if(cur_file->GetListParent() == searchfile){
				searchfile->SetListIsExpanded(true);
				InsertItem(LVIF_PARAM|LVIF_TEXT,item+1,cur_file->GetFileName(),0,0,0,(LPARAM)cur_file);
			}
		}
		SetRedraw(TRUE);
	}
	else {
		HideSources(searchfile);
	}

	Update(item);
}

void CSearchListCtrl::HideSources(CSearchFile* toCollapse)
{
	SetRedraw(FALSE);
	int pre,post;
	pre = post = 0;
	for(int i = 0; i < GetItemCount(); i++) {
		const CSearchFile* item = (CSearchFile*)GetItemData(i);
		if(item->GetListParent() == toCollapse) {
			pre++;
			DeleteItem(i--);
			post++;
		}
	}
	if (pre - post == 0)
		toCollapse->SetListIsExpanded(false);
	SetRedraw(TRUE);
}

void CSearchListCtrl::OnClick(NMHDR *pNMHDR, LRESULT *pResult)
{
	POINT point;
	::GetCursorPos(&point);
	CPoint p = point; 
    ScreenToClient(&p);

	LPNMITEMACTIVATE pNMIA = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);

	if (p.x<10) 
		ExpandCollapseItem(pNMIA->iItem);
}

void CSearchListCtrl::OnDblClick(NMHDR *pNMHDR, LRESULT *pResult)
{
	POINT point;
	::GetCursorPos(&point);
	CPoint p = point; 
    ScreenToClient(&p);

	if (p.x > 10){
		if (GetKeyState(VK_MENU) & 0x8000){
			int iSel = GetNextItem(-1, LVIS_SELECTED | LVIS_FOCUSED);
			if (iSel != -1){
				const CSearchFile* file = (CSearchFile*)GetItemData(iSel);
				if (file){
					CSearchResultFileDetailSheet sheet(file);
					sheet.DoModal();
				}
			}
		}
		else
			theApp.emuledlg->searchwnd->DownloadSelected();
	}
}

void CSearchListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	if (!theApp.emuledlg->IsRunning())
		return;
	if (!lpDrawItemStruct->itemData)
		return;

	CDC* odc = CDC::FromHandle(lpDrawItemStruct->hDC);
	CSearchFile* content = (CSearchFile*)lpDrawItemStruct->itemData;
	BOOL bCtrlFocused = ((GetFocus() == this) || (GetStyle() & LVS_SHOWSELALWAYS));
	if (content->GetListParent()==NULL && (lpDrawItemStruct->itemAction | ODA_SELECT) && (lpDrawItemStruct->itemState & ODS_SELECTED))
	{
		if (bCtrlFocused)
			odc->SetBkColor(m_crHighlight);
		else
			odc->SetBkColor(m_crNoHighlight);
	}
	else
		odc->SetBkColor(GetBkColor());

	CMemDC dc(odc, &lpDrawItemStruct->rcItem);
	CFont *pOldFont = dc->SelectObject(GetFont());
	COLORREF crOldTextColor = dc->SetTextColor(m_crWindowText);

	int iOldBkMode;
	if (m_crWindowTextBk == CLR_NONE)
	{
		DefWindowProc(WM_ERASEBKGND, (WPARAM)(HDC)dc, 0);
		iOldBkMode = dc.SetBkMode(TRANSPARENT);
	}
	else
		iOldBkMode = OPAQUE;

	BOOL notLast = lpDrawItemStruct->itemID + 1 != GetItemCount();
	BOOL notFirst = lpDrawItemStruct->itemID != 0;
	int tree_start = 0;
	int tree_end = 0;

	CRect cur_rec(lpDrawItemStruct->rcItem);

	//offset was 4, now it's the standard 2 spaces
	int iOffset = dc->GetTextExtent(_T(" "), 1 ).cx*2;
	CHeaderCtrl *pHeaderCtrl = GetHeaderCtrl();
	int iCount = pHeaderCtrl->GetItemCount();
	cur_rec.right = cur_rec.left;
	cur_rec.right -= iOffset;
	cur_rec.left += iOffset;

	// icon
	int ofs;
	if (content->GetListParent()!=NULL)
		ofs = 14; // indent child items
	else
		ofs = 6;
	int iImage = theApp.GetFileTypeSystemImageIdx(content->GetFileName());
	ImageList_Draw(theApp.GetSystemImageList(), iImage, dc, cur_rec.left+ofs, cur_rec.top, ILD_NORMAL|ILD_TRANSPARENT);

	// Parent entries
	if (content->GetListParent() == NULL)
	{
		for (int iCurrent = 0; iCurrent < iCount; iCurrent++)
		{
			int iColumn = pHeaderCtrl->OrderToIndex(iCurrent);
			if (!IsColumnHidden(iColumn))
			{
				int cx = CListCtrl::GetColumnWidth(iColumn);
				if (iColumn == 0)
				{
					int iNextLeft = cur_rec.left + cx;
					
					//set up tree vars
					cur_rec.left = cur_rec.right + iOffset;
					cur_rec.right = cur_rec.left + min(8, cx);
					tree_start = cur_rec.left + 1;
					tree_end = cur_rec.right;
					
					//normal column stuff
					cur_rec.left = cur_rec.right + 1;
					cur_rec.right = tree_start + cx - iOffset;
					DrawSourceParent(dc, 0, &cur_rec, content);
					cur_rec.left = iNextLeft;
				}
				else
				{
					cur_rec.right += cx;
					DrawSourceParent(dc, iColumn, &cur_rec, content);
					cur_rec.left += cx;
				}
			}
		}
	}
	else
	{
		for (int iCurrent = 0; iCurrent < iCount; iCurrent++)
		{
			int iColumn = pHeaderCtrl->OrderToIndex(iCurrent);
			if (!IsColumnHidden(iColumn))
			{
				int cx = CListCtrl::GetColumnWidth(iColumn);
				if (iColumn == 0)
				{
					int iNextLeft = cur_rec.left + cx;
					
					//set up tree vars
					cur_rec.left = cur_rec.right + iOffset;
					cur_rec.right = cur_rec.left + min(8, cx);
					tree_start = cur_rec.left + 1;
					tree_end = cur_rec.right;
					
					//normal column stuff
					cur_rec.left = cur_rec.right + 1;
					cur_rec.right = tree_start + cx - iOffset;
					DrawSourceChild(dc, 0, &cur_rec, content);
					cur_rec.left = iNextLeft;
				}
				else
				{
					cur_rec.right += cx;
					DrawSourceChild(dc, iColumn, &cur_rec, content);
					cur_rec.left += cx;
				}
			}
		}
	}

	//draw rectangle around selected item(s)
	if (content->GetListParent()==NULL && (lpDrawItemStruct->itemAction | ODA_SELECT) && (lpDrawItemStruct->itemState & ODS_SELECTED))
	{
		RECT outline_rec = lpDrawItemStruct->rcItem;

		outline_rec.top--;
		outline_rec.bottom++;
		dc->FrameRect(&outline_rec, &CBrush(GetBkColor()));
		outline_rec.top++;
		outline_rec.bottom--;
		outline_rec.left++;
		outline_rec.right--;

		if (notFirst && GetItemState(lpDrawItemStruct->itemID - 1, LVIS_SELECTED))
		{
			const CSearchFile* prev = (CSearchFile*)GetItemData(lpDrawItemStruct->itemID - 1);
			if (prev->GetListParent()==NULL)
				outline_rec.top--;
		} 

		if (notLast && GetItemState(lpDrawItemStruct->itemID + 1, LVIS_SELECTED))
		{
			const CSearchFile* next = (CSearchFile*)GetItemData(lpDrawItemStruct->itemID + 1);
			if (next->GetListParent()==NULL)
				outline_rec.bottom++;
		} 

		if (bCtrlFocused)
			dc->FrameRect(&outline_rec, &CBrush(m_crFocusLine));
		else
			dc->FrameRect(&outline_rec, &CBrush(m_crNoFocusLine));
	}
	//draw focus rectangle around non-highlightable items when they have the focus
	else if (GetFocus() == this && (lpDrawItemStruct->itemState & ODS_FOCUS) == ODS_FOCUS)
	{
		RECT focus_rec;
		focus_rec.top    = lpDrawItemStruct->rcItem.top;
		focus_rec.bottom = lpDrawItemStruct->rcItem.bottom;
		focus_rec.left   = lpDrawItemStruct->rcItem.left + 1;
		focus_rec.right  = lpDrawItemStruct->rcItem.right - 1;
		dc->FrameRect(&focus_rec, &CBrush(m_crNoFocusLine));
	}

	//draw tree last so it draws over selected and focus (looks better)
	if(tree_start < tree_end)
	{
		//set new bounds
		RECT tree_rect;
		tree_rect.top    = lpDrawItemStruct->rcItem.top;
		tree_rect.bottom = lpDrawItemStruct->rcItem.bottom;
		tree_rect.left   = tree_start;
		tree_rect.right  = tree_end;
		dc->SetBoundsRect(&tree_rect, DCB_DISABLE);

		//gather some information
		BOOL hasNext = notLast && ((const CSearchFile*)GetItemData(lpDrawItemStruct->itemID + 1))->GetListParent()!=NULL;
		BOOL isOpenRoot = hasNext && content->GetListParent() == NULL;
		BOOL isChild = content->GetListParent()!= NULL;

		//might as well calculate these now
		int treeCenter = tree_start + 4;
		int middle = (cur_rec.top + cur_rec.bottom + 1) / 2;

		//set up a new pen for drawing the tree
		CPen pn, *oldpn;
		pn.CreatePen(PS_SOLID, 1, RGB(128,128,128)/*m_crWindowText*/);
		oldpn = dc->SelectObject(&pn);

		if(isChild)
		{
			//draw the line to the status bar
			dc->MoveTo(tree_end+10, middle);
			dc->LineTo(tree_start + 4, middle);

			//draw the line to the child node
			if(hasNext)
			{
				dc->MoveTo(treeCenter, middle);
				dc->LineTo(treeCenter, cur_rec.bottom + 1);
			}
		}
		else if (isOpenRoot || (content->GetListParent() == NULL && content->GetListChildCount() > 1))
		{
			//draw box
			RECT circle_rec;
			circle_rec.top    = middle - 5;
			circle_rec.bottom = middle + 4;
			circle_rec.left   = treeCenter - 4;
			circle_rec.right  = treeCenter + 5;
			dc->FrameRect(&circle_rec, &CBrush(RGB(128,128,128)/*m_crWindowText*/));
			CPen penBlack;
			penBlack.CreatePen(PS_SOLID, 1, m_crWindowText);
			CPen* pOldPen2;
			pOldPen2 = dc->SelectObject(&penBlack);
			dc->MoveTo(treeCenter-2,middle - 1);
			dc->LineTo(treeCenter+3,middle - 1);
			
			if (!content->GetListIsExpanded())
			{
				dc->MoveTo(treeCenter,middle-3);
				dc->LineTo(treeCenter,middle+2);
			}
			dc->SelectObject(pOldPen2);
			//draw the line to the child node
			if (hasNext)
			{
				dc->MoveTo(treeCenter, middle + 4);
				dc->LineTo(treeCenter, cur_rec.bottom + 1);
			}
		}

		//draw the line back up to parent node
		if (notFirst && isChild)
		{
			dc->MoveTo(treeCenter, middle);
			dc->LineTo(treeCenter, cur_rec.top - 1);
		}

		//put the old pen back
		dc->SelectObject(oldpn);
		pn.DeleteObject();
	}

	//put the original objects back
	if (m_crWindowTextBk == CLR_NONE)
		dc.SetBkMode(iOldBkMode);
	dc->SelectObject(pOldFont);
	dc->SetTextColor(crOldTextColor);
}

static COLORREF GetSearchItemColor(/*const*/ CSearchFile* src)
{
	// default to black color (may be "corrected" below)
	uint32 red = 0;
	uint32 green = 0;
	uint32 blue = 0;

	const CKnownFile* pFile = theApp.downloadqueue->GetFileByID(src->GetFileHash());
	if (pFile)
	{
		if (pFile->IsPartFile())
			src->SetKnownType(CSearchFile::Downloading);
		else
			src->SetKnownType(CSearchFile::Shared);
		red = 255;
	}
	else if (theApp.sharedfiles->GetFileByID(src->GetFileHash()))
	{
		src->SetKnownType(CSearchFile::Shared);
		red = 255;
	}
	else if (theApp.knownfiles->FindKnownFileByID(src->GetFileHash()))
	{
		src->SetKnownType(CSearchFile::Downloaded);
		green = 128;
	}
	else
	{
		// shade of blue = availability of the file
		blue = src->GetSourceCount() == 0 ? 0 : ((src->GetSourceCount() - 1) * 20);
		if (blue > 255)
			blue = 255;
		else if (blue == 0)
		{
			// avoid the worst case (drawing black text on black background) for users 
			// which are using extreme color schemes by eventually switching to default 
			// windows text color
			COLORREF crText = ::GetSysColor(COLOR_WINDOWTEXT);
			red = GetRValue(crText);
			green = GetGValue(crText);
			blue = GetBValue(crText);
		}
	}

	return RGB(red, green, blue);
}

void CSearchListCtrl::DrawSourceChild(CDC *dc, int nColumn, LPRECT lpRect, /*const*/ CSearchFile* src)
{
	if (!src)
		return;
	if (lpRect->left < lpRect->right)
	{
		CString buffer;
		COLORREF crOldTextColor = dc->SetTextColor(GetSearchItemColor(src));
		switch (nColumn)
		{
			case 0:			// file name
				lpRect->left += 30;
				dc->DrawText(src->GetFileName(), src->GetFileName().GetLength(), lpRect, DLC_DT_TEXT);
				lpRect->left -= 30;
				break;
			case 1:			// file size
				break;
			case 2:			// number of same filenames
				buffer.Format(_T("%u"), src->GetListChildCount());
				dc->DrawText(buffer,buffer.GetLength(), lpRect, DLC_DT_TEXT | DT_RIGHT);
				break;
			case 3:			// file type
				break;
			case 4:			// file hash
				break;
			case 5:
				buffer = src->GetStrTagValue(FT_MEDIA_ARTIST);
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
			case 6:
				buffer = src->GetStrTagValue(FT_MEDIA_ALBUM);
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
			case 7:
				buffer = src->GetStrTagValue(FT_MEDIA_TITLE);
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
			case 8:{
				uint32 nMediaLength = src->GetIntTagValue(FT_MEDIA_LENGTH);
				if (nMediaLength){
					SecToTimeLength(nMediaLength, buffer);
					dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT | DT_RIGHT);
				}
				break;
			}
			case 9:{
				uint32 nBitrate = src->GetIntTagValue(FT_MEDIA_BITRATE);
				if (nBitrate){
					buffer.Format(_T("%u kBit/s"), nBitrate);
					dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT | DT_RIGHT);
				}
				break;
			}
			case 10:
				buffer = src->GetStrTagValue(FT_MEDIA_CODEC);
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
			case 11:		// dir
				if (src->GetDirectory()){
					buffer = src->GetDirectory();
					dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				}
				break;
			case 12:
				if (src->m_eKnown == CSearchFile::Shared)
					buffer = GetResString(IDS_SHARED);
				else if (src->m_eKnown == CSearchFile::Downloading)
					buffer = GetResString(IDS_DOWNLOADING);
				else if (src->m_eKnown == CSearchFile::Downloaded)
					buffer = GetResString(IDS_DOWNLOADED);
				else
					buffer.Empty();
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
		}
		dc->SetTextColor(crOldTextColor);
	}
}

void CSearchListCtrl::DrawSourceParent(CDC *dc, int nColumn, LPRECT lpRect, /*const*/ CSearchFile* src)
{
	if (!src)
		return;

	if (lpRect->left < lpRect->right)
	{
		CString buffer;
		COLORREF crOldTextColor = dc->SetTextColor(GetSearchItemColor(src));
		switch (nColumn)
		{
			case 0:			// file name
				lpRect->left += 22;
				dc->DrawText(src->GetFileName(), src->GetFileName().GetLength(), lpRect, DLC_DT_TEXT);
				lpRect->left -= 22;
				break;
			case 1:			// file size
				buffer = CastItoXBytes(src->GetFileSize());
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT | DT_RIGHT);
				break;
			case 2:{		// avail
				buffer.Format(_T("%u"), src->GetIntTagValue(FT_SOURCES));
				uint32 uCompleteSources;
				if (src->GetIntTagValue(FT_COMPLETE_SOURCES, uCompleteSources))
					buffer.AppendFormat(_T("/%u"), uCompleteSources);
				int iClientsCount = src->GetClientsCount();
				if (iClientsCount)
					buffer.AppendFormat(_T(" (%u)"), iClientsCount);
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT | DT_RIGHT);
				break;
			}
			case 3:			// file type
				dc->DrawText(src->GetFileType(), src->GetFileType().GetLength(), lpRect, DLC_DT_TEXT);
				break;
			case 4:			// file hash
				buffer = md4str(src->GetFileHash());
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
			case 5:
				buffer = src->GetStrTagValue(FT_MEDIA_ARTIST);
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
			case 6:
				buffer = src->GetStrTagValue(FT_MEDIA_ALBUM);
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
			case 7:
				buffer = src->GetStrTagValue(FT_MEDIA_TITLE);
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
			case 8:{
				uint32 nMediaLength = src->GetIntTagValue(FT_MEDIA_LENGTH);
				if (nMediaLength){
					SecToTimeLength(nMediaLength, buffer);
					dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT | DT_RIGHT);
				}
				break;
			}
			case 9:{
				uint32 nBitrate = src->GetIntTagValue(FT_MEDIA_BITRATE);
				if (nBitrate){
					buffer.Format(_T("%u kBit/s"), nBitrate);
					dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT | DT_RIGHT);
				}
				break;
			}
			case 10:
				buffer = src->GetStrTagValue(FT_MEDIA_CODEC);
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
			case 11:		// dir
				if (src->GetDirectory()){
					buffer = src->GetDirectory();
					dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				}
				break;
			case 12:
				if (src->m_eKnown == CSearchFile::Shared)
					buffer = GetResString(IDS_SHARED);
				else if (src->m_eKnown == CSearchFile::Downloading)
					buffer = GetResString(IDS_DOWNLOADING);
				else if (src->m_eKnown == CSearchFile::Downloaded)
					buffer = GetResString(IDS_DOWNLOADED);
				else
					buffer.Empty();
				dc->DrawText(buffer, buffer.GetLength(), lpRect, DLC_DT_TEXT);
				break;
		}
		dc->SetTextColor(crOldTextColor);
	}
}

void CSearchListCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if (nChar == 'C' && (GetKeyState(VK_CONTROL) & 0x8000))
	{
		// Ctrl+C: Copy listview items to clipboard
		SendMessage(WM_COMMAND, MP_GETED2KLINK);
		return;
	}
	CMuleListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
}