1//
2// This file is part of the aMule Project.
3//
4// Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5//
6// Any parts of this program derived from the xMule, lMule or eMule project,
7// or contributed by third-party developers are copyrighted by their
8// respective authors.
9//
10// This program is free software; you can redistribute it and/or modify
11// it under the terms of the GNU General Public License as published by
12// the Free Software Foundation; either version 2 of the License, or
13// (at your option) any later version.
14//
15// This program is distributed in the hope that it will be useful,
16// but WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with this program; if not, write to the Free Software
22// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
23//
24#include "GenericClientListCtrl.h"
25
26#include <protocol/ed2k/ClientSoftware.h>
27#include <common/MenuIDs.h>
28
29#include <common/Format.h>	// Needed for CFormat
30#include "amule.h"		// Needed for theApp
31#include "amuleDlg.h"		// Needed for CamuleDlg
32#include "BarShader.h"		// Needed for CBarShader
33#include "BitVector.h"
34#include "ClientDetailDialog.h"	// Needed for CClientDetailDialog
35#include "ChatWnd.h"		// Needed for CChatWnd
36#include "CommentDialogLst.h"	// Needed for CCommentDialogLst
37#include "DataToText.h"		// Needed for PriorityToStr
38#include "FileDetailDialog.h"	// Needed for CFileDetailDialog
39#include "GetTickCount.h"	// Needed for GetTickCount
40#include "GuiEvents.h"		// Needed for CoreNotify_*
41#ifdef ENABLE_IP2COUNTRY
42	#include "IP2Country.h"	// Needed for IP2Country
43#endif
44#include "Logger.h"
45#include "muuli_wdr.h"		// Needed for ID_DLOADLIST
46#include "PartFile.h"		// Needed for CPartFile
47#include "Preferences.h"
48#include "SharedFileList.h"	// Needed for CSharedFileList
49#include "TerminationProcess.h"	// Needed for CTerminationProcess
50#include "ClientRef.h"		// Needed for CClientRef
51#include "FriendList.h"
52
53struct ClientCtrlItem_Struct
54{
55	ClientCtrlItem_Struct()
56		: dwUpdated(0),
57		  status(NULL),
58		  m_owner(NULL),
59		  m_type(UNAVAILABLE_SOURCE)
60	{ }
61
62	~ClientCtrlItem_Struct() {
63		delete status;
64	}
65
66	SourceItemType GetType() const {
67		return m_type;
68	}
69
70	CKnownFile* GetOwner() const {
71		return m_owner;
72	}
73
74
75	CClientRef& GetSource() {
76		return m_sourceValue;
77	}
78
79	void SetContents(CKnownFile* owner, const CClientRef& source, SourceItemType type) {
80		m_owner = owner;
81		m_sourceValue = source;
82		m_type = type;
83	}
84
85	void SetType(SourceItemType type) { m_type = type; }
86
87	uint32		dwUpdated;
88	wxBitmap*	status;
89
90private:
91	CKnownFile*		m_owner;
92	CClientRef		m_sourceValue;
93	SourceItemType	m_type;
94};
95
96#define m_ImageList theApp->amuledlg->m_imagelist
97
98BEGIN_EVENT_TABLE(CGenericClientListCtrl, CMuleListCtrl)
99	EVT_LIST_ITEM_ACTIVATED(wxID_ANY,	CGenericClientListCtrl::OnItemActivated)
100	EVT_LIST_ITEM_RIGHT_CLICK(wxID_ANY, CGenericClientListCtrl::OnMouseRightClick)
101	EVT_LIST_ITEM_MIDDLE_CLICK(wxID_ANY, CGenericClientListCtrl::OnMouseMiddleClick)
102
103	EVT_CHAR( CGenericClientListCtrl::OnKeyPressed )
104
105	EVT_MENU( MP_CHANGE2FILE,		CGenericClientListCtrl::OnSwapSource )
106	EVT_MENU( MP_SHOWLIST,			CGenericClientListCtrl::OnViewFiles )
107	EVT_MENU( MP_ADDFRIEND,			CGenericClientListCtrl::OnAddFriend )
108	EVT_MENU( MP_FRIENDSLOT,		CGenericClientListCtrl::OnSetFriendslot )
109	EVT_MENU( MP_SENDMESSAGE,		CGenericClientListCtrl::OnSendMessage )
110	EVT_MENU( MP_DETAIL,			CGenericClientListCtrl::OnViewClientInfo )
111END_EVENT_TABLE()
112
113//! This listtype is used when gathering the selected items.
114typedef std::list<ClientCtrlItem_Struct*>	ItemList;
115
116CGenericClientListCtrl::CGenericClientListCtrl(
117    const wxString& tablename,
118	wxWindow *parent, wxWindowID winid, const wxPoint& pos, const wxSize& size,
119	long style, const wxValidator& validator, const wxString& name )
120:
121CMuleListCtrl( parent, winid, pos, size, style | wxLC_OWNERDRAW | wxLC_VRULES | wxLC_HRULES, validator, name ),
122m_columndata(0, NULL)
123{
124	// Setting the sorter function must be done in the derived class, to use the translation function correctly.
125	// SetSortFunc( SortProc );
126
127	// Set the table-name (for loading and saving preferences).
128	SetTableName( tablename );
129
130	m_menu = NULL;
131	m_showing = false;
132
133	m_hilightBrush  = CMuleColour(wxSYS_COLOUR_HIGHLIGHT).Blend(125).GetBrush();
134
135	m_hilightUnfocusBrush = CMuleColour(wxSYS_COLOUR_BTNSHADOW).Blend(125).GetBrush();
136
137	m_clientcount = 0;
138}
139
140wxString CGenericClientListCtrl::TranslateCIDToName(GenericColumnEnum cid)
141{
142	wxString name;
143
144	switch (cid) {
145		case ColumnUserName:
146			name = wxT("N");
147			break;
148		case ColumnUserDownloaded:
149			name = wxT("D");
150			break;
151		case ColumnUserUploaded:
152			name = wxT("U");
153			break;
154		case ColumnUserSpeedDown:
155			name = wxT("S");
156			break;
157		case ColumnUserSpeedUp:
158			name = wxT("s");
159			break;
160		case ColumnUserProgress:
161			name = wxT("P");
162			break;
163		case ColumnUserAvailable:
164			name = wxT("A");
165			break;
166		case ColumnUserVersion:
167			name = wxT("V");
168			break;
169		case ColumnUserQueueRankLocal:
170			name = wxT("Q");
171			break;
172		case ColumnUserQueueRankRemote:
173			name = wxT("q");
174			break;
175		case ColumnUserOrigin:
176			name = wxT("O");
177			break;
178		case ColumnUserFileNameDownload:
179			name = wxT("F");
180			break;
181		case ColumnUserFileNameUpload:
182			name = wxT("f");
183			break;
184		case ColumnUserFileNameDownloadRemote:
185			name = wxT("R");
186			break;
187		case ColumnUserSharedFiles:
188			name = wxT("m");
189			break;
190		case ColumnInvalid:
191		default:
192			wxFAIL;
193			break;
194	}
195
196	return name;
197}
198
199void CGenericClientListCtrl::InitColumnData()
200{
201	if (!m_columndata.n_columns) {
202		throw wxString(wxT("CRITICAL: Initialization of the column data lacks subclass information"));
203	}
204
205	for (int i = 0; i < m_columndata.n_columns; ++i) {
206		InsertColumn( i, wxGetTranslation(m_columndata.columns[i].title), wxLIST_FORMAT_LEFT, m_columndata.columns[i].width, TranslateCIDToName(m_columndata.columns[i].cid));
207	}
208
209	LoadSettings();
210}
211
212CGenericClientListCtrl::~CGenericClientListCtrl()
213{
214	while ( !m_ListItems.empty() ) {
215		delete m_ListItems.begin()->second;
216		m_ListItems.erase( m_ListItems.begin() );
217	}
218}
219void CGenericClientListCtrl::RawAddSource(CKnownFile* owner, CClientRef source, SourceItemType type)
220{
221	ClientCtrlItem_Struct* newitem = new ClientCtrlItem_Struct;
222	newitem->SetContents(owner, source, type);
223
224	m_ListItems.insert( ListItemsPair(source.ECID(), newitem) );
225
226	long item = InsertItem( GetItemCount(), wxEmptyString );
227	SetItemPtrData( item, reinterpret_cast<wxUIntPtr>(newitem) );
228	SetItemBackgroundColour( item, GetBackgroundColour() );
229}
230
231void CGenericClientListCtrl::AddSource(CKnownFile* owner, const CClientRef& source, SourceItemType type)
232{
233	wxCHECK_RET(owner, wxT("NULL owner in CGenericClientListCtrl::AddSource"));
234	wxCHECK_RET(source.IsLinked(), wxT("Unlinked source in CGenericClientListCtrl::AddSource"));
235
236	// Update the other instances of this source
237	bool bFound = false;
238	ListIteratorPair rangeIt = m_ListItems.equal_range(source.ECID());
239	for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second; ++it ) {
240		ClientCtrlItem_Struct* cur_item = it->second;
241
242		// Check if this source has been already added to this file => to be sure
243		if ( cur_item->GetOwner() == owner ) {
244			// Update this instance with its new setting
245			if ((type == A4AF_SOURCE) &&
246				cur_item->GetSource().GetRequestFile()
247				&& std::binary_search(m_knownfiles.begin(), m_knownfiles.end(), cur_item->GetSource().GetRequestFile())) {
248				cur_item->SetContents(owner, source, AVAILABLE_SOURCE);
249			} else {
250				cur_item->SetContents(owner, source, type);
251			}
252			cur_item->dwUpdated = 0;
253			bFound = true;
254		} else if ( type == AVAILABLE_SOURCE ) {
255			// The state 'Available' is exclusive
256			cur_item->SetContents(cur_item->GetOwner(), source, A4AF_SOURCE);
257			cur_item->dwUpdated = 0;
258		}
259	}
260
261	if ( bFound ) {
262		return;
263	}
264
265	if ( std::binary_search(m_knownfiles.begin(), m_knownfiles.end(), owner) ) {
266		RawAddSource(owner, source, type);
267
268		ShowSourcesCount( 1 );
269	}
270}
271
272void CGenericClientListCtrl::RawRemoveSource( ListItems::iterator& it)
273{
274	ClientCtrlItem_Struct* item = it->second;
275
276	long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
277
278	if ( index > -1 ) {
279		DeleteItem( index );
280	}
281
282	delete item;
283
284	// Remove it from the m_ListItems
285	m_ListItems.erase( it );
286}
287
288void CGenericClientListCtrl::RemoveSource(uint32 source, const CKnownFile* owner)
289{
290	// A NULL owner means remove it no matter what.
291	wxCHECK_RET(source, wxT("NULL source in CGenericClientListCtrl::RemoveSource"));
292
293	// Retrieve all entries matching the source
294	ListIteratorPair rangeIt = m_ListItems.equal_range(source);
295
296	int removedItems = 0;
297
298	for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second;  /* no ++, it happens later */) {
299		ListItems::iterator tmp = it++;
300
301		if ( owner == NULL || owner == tmp->second->GetOwner() ) {
302
303			RawRemoveSource(tmp);
304
305			++removedItems;
306		}
307	}
308
309	ShowSourcesCount((-1) * removedItems);
310}
311
312void CGenericClientListCtrl::UpdateItem(uint32 toupdate, SourceItemType type)
313{
314	// Retrieve all entries matching the source
315	ListIteratorPair rangeIt = m_ListItems.equal_range( toupdate );
316
317	if ( rangeIt.first != rangeIt.second ) {
318		// Visible lines, default to all because not all platforms
319		// support the GetVisibleLines function
320		long first = 0, last = GetItemCount();
321
322	#ifndef __WXMSW__
323		// Get visible lines if we need them
324		GetVisibleLines( &first, &last );
325	#endif
326
327		for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second; ++it ) {
328			ClientCtrlItem_Struct* item = it->second;
329
330			long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
331
332			if ((type == A4AF_SOURCE) &&
333					item->GetSource().GetRequestFile()
334					&& std::binary_search(m_knownfiles.begin(), m_knownfiles.end(), item->GetSource().GetRequestFile())) {
335
336				item->SetType(AVAILABLE_SOURCE);
337			} else {
338				item->SetType(type);
339			}
340
341			item->dwUpdated = 0;
342
343			// Only update visible lines
344			if ( index >= first && index <= last) {
345				RefreshItem( index );
346			}
347		}
348	}
349}
350
351void CGenericClientListCtrl::ShowSources( const CKnownFileVector& files )
352{
353	Freeze();
354
355	// The stored vector is sorted, as is the received one, so we can use binary_search
356
357	for (unsigned i = 0; i < m_knownfiles.size(); ++i) {
358		// Files that are not in the new list must have show set to false.
359		if (!std::binary_search(files.begin(), files.end(), m_knownfiles[i])) {
360			SetShowSources(m_knownfiles[i], false);
361		}
362	}
363
364	// This will call again SetShowSources in files that were in both vectors. Right now
365	// that function is just a inline setter, so any way to prevent it would be wasteful,
366	// but this must be reviewed if that fact changes.
367
368	for (unsigned i = 0; i < files.size(); ++i) {
369		SetShowSources(files[i], true);
370	}
371
372	// We must cleanup sources that are not in the received files.
373
374	int itemDiff = 0;
375
376	for (ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); /* no ++, it happens later */) {
377		ListItems::iterator tmp = it++;
378		ClientCtrlItem_Struct* item = tmp->second;
379		if (!std::binary_search(files.begin(), files.end(), item->GetOwner())) {
380			// Remove it from the m_ListItems
381			RawRemoveSource(tmp);
382			--itemDiff;
383		}
384	}
385
386	for (unsigned i = 0; i < files.size(); ++i) {
387
388		// Only those that weren't showing already
389		if (!std::binary_search(m_knownfiles.begin(), m_knownfiles.end(), files[i])) {
390
391			CKnownFile* file = files[i];
392
393			wxASSERT_MSG(file, wxT("NULL file in CGenericClientListCtrl::ShowSources"));
394
395			if (file) {
396
397				CKnownFile::SourceSet::const_iterator it;
398
399				if (IsShowingDownloadSources()) {
400					const CKnownFile::SourceSet& normSources = (dynamic_cast<CPartFile*>( file ))->GetSourceList();
401					const CKnownFile::SourceSet& a4afSources = (dynamic_cast<CPartFile*>( file ))->GetA4AFList();
402
403					// Adding normal sources
404					for ( it = normSources.begin(); it != normSources.end(); ++it ) {
405						switch (it->GetDownloadState()) {
406							case DS_DOWNLOADING:
407							case DS_ONQUEUE:
408								RawAddSource( file, *it, AVAILABLE_SOURCE );
409								++itemDiff;
410								break;
411							default:
412								// Any other state
413								RawAddSource( file, *it, UNAVAILABLE_SOURCE );
414								++itemDiff;
415						}
416					}
417
418					// Adding A4AF sources
419					for ( it = a4afSources.begin(); it != a4afSources.end(); ++it ) {
420						// Only add if the A4AF file is not in the shown list.
421						if (!std::binary_search(files.begin(), files.end(), it->GetRequestFile())) {
422							RawAddSource( file, *it, A4AF_SOURCE );
423							++itemDiff;
424						}
425					}
426				} else {
427					// Known file
428					const CKnownFile::SourceSet& sources = file->m_ClientUploadList;
429					for ( it = sources.begin(); it != sources.end(); ++it ) {
430						switch (it->GetUploadState()) {
431							case US_UPLOADING:
432							case US_ONUPLOADQUEUE:
433								RawAddSource( file, *it, AVAILABLE_SOURCE );
434								++itemDiff;
435								break;
436							default:
437								// Any other state
438								RawAddSource( file, *it, UNAVAILABLE_SOURCE );
439								++itemDiff;
440						}
441					}
442				}
443			}
444		}
445	}
446
447	m_knownfiles = files;
448
449	ShowSourcesCount( itemDiff );
450
451	SortList();
452
453	Thaw();
454}
455
456/**
457 * Helper-function: This function is used to gather selected items.
458 *
459 * @param list A pointer to the list to gather items from.
460 * @return A list containing the selected items of the choosen types.
461 */
462ItemList GetSelectedItems( CGenericClientListCtrl* list )
463{
464	ItemList results;
465
466	long index = list->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
467
468	while ( index > -1 ) {
469		ClientCtrlItem_Struct* item = (ClientCtrlItem_Struct*)list->GetItemData( index );
470
471		results.push_back( item );
472
473		index = list->GetNextItem( index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
474	}
475
476	return results;
477}
478
479void CGenericClientListCtrl::OnSwapSource( wxCommandEvent& WXUNUSED(event) )
480{
481	ItemList sources = ::GetSelectedItems( this );
482
483	for ( ItemList::iterator it = sources.begin(); it != sources.end(); ++it ) {
484		CKnownFile * kf = (*it)->GetOwner();
485		if (!kf->IsPartFile()) {
486			wxFAIL_MSG(wxT("File is not a partfile when swapping sources"));
487			continue;
488		}
489		(*it)->GetSource().SwapToAnotherFile( true, false, false,  dynamic_cast<CPartFile*>(kf));
490	}
491}
492
493
494void CGenericClientListCtrl::OnViewFiles( wxCommandEvent& WXUNUSED(event) )
495{
496	ItemList sources = ::GetSelectedItems( this );
497
498	if ( sources.size() == 1 ) {
499		sources.front()->GetSource().RequestSharedFileList();
500	}
501}
502
503
504void CGenericClientListCtrl::OnAddFriend( wxCommandEvent& WXUNUSED(event) )
505{
506	ItemList sources = ::GetSelectedItems( this );
507
508	for ( ItemList::iterator it = sources.begin(); it != sources.end(); ++it ) {
509		CClientRef &client = (*it)->GetSource();
510		if (client.IsFriend()) {
511			theApp->friendlist->RemoveFriend(client.GetFriend());
512		} else {
513			theApp->friendlist->AddFriend(client);
514		}
515	}
516}
517
518
519void CGenericClientListCtrl::OnSetFriendslot(wxCommandEvent& evt)
520{
521	ItemList sources = ::GetSelectedItems( this );
522
523	ItemList::iterator it = sources.begin();
524	if (it != sources.end()) {
525		CClientRef &client = (*it)->GetSource();
526		theApp->friendlist->SetFriendSlot(client.GetFriend(), evt.IsChecked());
527		it++;
528	}
529	if (it != sources.end()) {
530		wxMessageBox(_("You are not allowed to set more than one friendslot.\n Only one slot was assigned."), _("Multiple selection"), wxOK | wxICON_ERROR, this);
531	}
532}
533
534
535void CGenericClientListCtrl::OnSendMessage( wxCommandEvent& WXUNUSED(event) )
536{
537	ItemList sources = ::GetSelectedItems( this );
538
539	if ( sources.size() == 1 ) {
540		CClientRef & source = (sources.front())->GetSource();
541
542		// These values are cached, since calling wxGetTextFromUser will
543		// start an event-loop, in which the client may be deleted.
544		wxString userName = source.GetUserName();
545		uint64 userID = GUI_ID(source.GetIP(), source.GetUserPort());
546
547		wxString message = ::wxGetTextFromUser(_("Send message to user"),
548			_("Message to send:"));
549		if ( !message.IsEmpty() ) {
550			theApp->amuledlg->m_chatwnd->SendMessage(message, userName, userID);
551		}
552	}
553}
554
555
556void CGenericClientListCtrl::OnViewClientInfo( wxCommandEvent& WXUNUSED(event) )
557{
558	ItemList sources = ::GetSelectedItems( this );
559
560	if ( sources.size() == 1 ) {
561		CClientDetailDialog( this, sources.front()->GetSource() ).ShowModal();
562	}
563}
564
565
566void CGenericClientListCtrl::OnItemActivated( wxListEvent& evt )
567{
568	CClientDetailDialog( this, ((ClientCtrlItem_Struct*)GetItemData( evt.GetIndex()))->GetSource() ).ShowModal();
569}
570
571
572void CGenericClientListCtrl::OnMouseRightClick(wxListEvent& evt)
573{
574	long index = CheckSelection(evt);
575	if (index < 0) {
576		return;
577	}
578
579	delete m_menu;
580	m_menu = NULL;
581
582	ClientCtrlItem_Struct* item = (ClientCtrlItem_Struct*)GetItemData( index );
583	CClientRef& client = item->GetSource();
584
585	m_menu = new wxMenu(wxT("Clients"));
586	m_menu->Append(MP_DETAIL, _("Show &Details"));
587	m_menu->Append(MP_ADDFRIEND, client.IsFriend() ? _("Remove from friends") : _("Add to Friends"));
588
589	m_menu->AppendCheckItem(MP_FRIENDSLOT, _("Establish Friend Slot"));
590	if (client.IsFriend()) {
591		m_menu->Enable(MP_FRIENDSLOT, true);
592		m_menu->Check(MP_FRIENDSLOT, client.GetFriendSlot());
593	} else {
594		m_menu->Enable(MP_FRIENDSLOT, false);
595	}
596
597	m_menu->Append(MP_SHOWLIST, _("View Files"));
598	m_menu->Append(MP_SENDMESSAGE, _("Send message"));
599
600	m_menu->Append(MP_CHANGE2FILE, _("Swap to this file"));
601
602	// Only enable the Swap option for A4AF sources
603	m_menu->Enable(MP_CHANGE2FILE, (item->GetType() == A4AF_SOURCE));
604	// We need a valid IP if we are to message the client
605	m_menu->Enable(MP_SENDMESSAGE, (client.GetIP() != 0));
606
607	m_menu->Enable(MP_SHOWLIST, !client.HasDisabledSharedFiles());
608
609	PopupMenu(m_menu, evt.GetPoint());
610
611	delete m_menu;
612	m_menu = NULL;
613
614}
615
616
617void CGenericClientListCtrl::OnMouseMiddleClick(wxListEvent& evt)
618{
619	// Check if clicked item is selected. If not, unselect all and select it.
620	long index = CheckSelection(evt);
621	if ( index < 0 ) {
622		return;
623	}
624
625	CClientDetailDialog(this, ((ClientCtrlItem_Struct*)GetItemData( index ))->GetSource()).ShowModal();
626}
627
628
629void CGenericClientListCtrl::OnKeyPressed( wxKeyEvent& event )
630{
631	// No actions right now.
632	//switch (event.GetKeyCode()) {
633	//	default:
634			event.Skip();
635	//}
636}
637
638
639void CGenericClientListCtrl::OnDrawItem(
640	int item, wxDC* dc, const wxRect& rect, const wxRect& rectHL, bool highlighted)
641{
642	// Don't do any drawing if there's nobody to see it.
643	if ( !theApp->amuledlg->IsDialogVisible( GetParentDialog() ) ) {
644		return;
645	}
646
647	ClientCtrlItem_Struct* content = (ClientCtrlItem_Struct *)GetItemData(item);
648
649	// Define text-color and background
650	// and the border of the drawn area
651	if (highlighted) {
652		CMuleColour colour;
653		if (GetFocus()) {
654			dc->SetBackground(m_hilightBrush);
655			colour = m_hilightBrush.GetColour();
656		} else {
657			dc->SetBackground(m_hilightUnfocusBrush);
658			colour = m_hilightUnfocusBrush.GetColour();
659		}
660		dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
661		dc->SetPen( colour.Blend(65).GetPen() );
662	} else {
663		dc->SetBackground(*(wxTheBrushList->FindOrCreateBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX), wxSOLID)));
664		dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
665		dc->SetPen(*wxTRANSPARENT_PEN);
666	}
667	dc->SetBrush( dc->GetBackground() );
668
669	dc->DrawRectangle( rectHL.x, rectHL.y, rectHL.width, rectHL.height );
670
671	dc->SetPen(*wxTRANSPARENT_PEN);
672
673	// Various constant values we use
674	const int iTextOffset = (( rect.GetHeight() - dc->GetCharHeight() ) / 2) + 1 /* Fixes rounding in the centering math, much easier than floor() */;
675	const int iOffset = 2;
676	wxASSERT(m_ImageList.GetImageCount() > 0);
677	int imageListBitmapYOffset = 0;
678	int imageListBitmapXSize = 0;
679	if (m_ImageList.GetSize(0, imageListBitmapXSize, imageListBitmapYOffset)) {
680		imageListBitmapXSize += 2; // Padding.
681		imageListBitmapYOffset = ((rect.GetHeight() - imageListBitmapYOffset) / 2) + 1 /* Fixes rounding like above */;
682	} else {
683		wxFAIL;
684	}
685
686	wxRect cur_rec( iOffset, rect.y, 0, rect.height );
687
688	for (int i = 0; i < GetColumnCount(); ++i) {
689
690		int columnwidth = GetColumnWidth(i);
691
692		if (columnwidth > 2*iOffset) {
693			// Make a copy of the current rectangle so we can apply specific tweaks
694			wxRect target_rec = cur_rec;
695			target_rec.width = columnwidth - 2*iOffset;
696
697			GenericColumnEnum cid = m_columndata.columns[i].cid;
698
699			// Draw the item
700			DrawClientItem(dc, cid, target_rec, content, iTextOffset, imageListBitmapYOffset, imageListBitmapXSize);
701		}
702
703		// Increment to the next column
704		cur_rec.x += columnwidth;
705	}
706}
707
708void CGenericClientListCtrl::DrawClientItem(wxDC* dc, int nColumn, const wxRect& rect, ClientCtrlItem_Struct* item, int iTextOffset, int iBitmapOffset, int iBitmapXSize ) const
709{
710	wxDCClipper clipper( *dc, rect.GetX(), rect.GetY(), rect.GetWidth(), rect.GetHeight() );
711	wxString buffer;
712
713	const CClientRef& client = item->GetSource();
714
715	switch (nColumn) {
716		// Client name + various icons
717		case ColumnUserName: {
718			// Point will get shifted per drawing.
719
720			wxPoint point( rect.GetX(), rect.GetY() );
721
722			uint8 image = Client_Grey_Smiley;
723
724			if (item->GetType() != A4AF_SOURCE) {
725
726				switch (client.GetDownloadState()) {
727					case DS_CONNECTING:
728					case DS_CONNECTED:
729					case DS_WAITCALLBACK:
730					case DS_TOOMANYCONNS:
731						image = Client_Red_Smiley;
732						break;
733					case DS_ONQUEUE:
734						if (client.IsRemoteQueueFull()) {
735							image = Client_Grey_Smiley;
736						} else {
737							image = Client_Yellow_Smiley;
738						}
739						break;
740					case DS_DOWNLOADING:
741					case DS_REQHASHSET:
742						image = Client_Green_Smiley;
743						break;
744					case DS_NONEEDEDPARTS:
745					case DS_LOWTOLOWIP:
746						image = Client_Grey_Smiley; // Redundant
747						break;
748					default: // DS_NONE i.e.
749						image = Client_White_Smiley;
750					}
751
752				} else {
753					// Default (Client_Grey_Smiley)
754				}
755
756				m_ImageList.Draw(image, *dc, point.x, point.y + iBitmapOffset, wxIMAGELIST_DRAW_TRANSPARENT);
757
758				// Next
759
760				point.x += iBitmapXSize;
761
762				uint8 clientImage = Client_Unknown;
763
764				if ( client.IsFriend() ) {
765					clientImage = Client_Friend_Smiley;
766				} else {
767					switch ( client.GetClientSoft() ) {
768						case SO_AMULE:
769							clientImage = Client_aMule_Smiley;
770							break;
771						case SO_MLDONKEY:
772						case SO_NEW_MLDONKEY:
773						case SO_NEW2_MLDONKEY:
774							clientImage = Client_mlDonkey_Smiley;
775							break;
776						case SO_EDONKEY:
777						case SO_EDONKEYHYBRID:
778							clientImage = Client_eDonkeyHybrid_Smiley;
779							break;
780						case SO_EMULE:
781							clientImage = Client_eMule_Smiley;
782							break;
783						case SO_LPHANT:
784							clientImage = Client_lphant_Smiley;
785							break;
786						case SO_SHAREAZA:
787						case SO_NEW_SHAREAZA:
788						case SO_NEW2_SHAREAZA:
789							clientImage = Client_Shareaza_Smiley;
790							break;
791						case SO_LXMULE:
792							clientImage = Client_xMule_Smiley;
793							break;
794						default:
795							// cDonkey, Compatible, Unknown
796							// No icon for those yet.
797							// Using the eMule one + '?'
798							// Which is a faillback to the default (Client_Unknown)
799							break;
800					}
801				}
802
803				int realY = point.y + iBitmapOffset;
804				m_ImageList.Draw(clientImage, *dc, point.x, realY, wxIMAGELIST_DRAW_TRANSPARENT);
805
806				if (client.GetScoreRatio() > 1) {
807					// Has credits, draw the gold star
808					m_ImageList.Draw(Client_CreditsYellow_Smiley, *dc, point.x, realY,
809						wxIMAGELIST_DRAW_TRANSPARENT );
810				}	else if ( !client.ExtProtocolAvailable() ) {
811					// No Ext protocol -> Draw the '-'
812					m_ImageList.Draw(Client_ExtendedProtocol_Smiley, *dc, point.x, realY,
813						wxIMAGELIST_DRAW_TRANSPARENT);
814				}
815
816				if (client.IsIdentified()) {
817					// the 'v'
818					m_ImageList.Draw(Client_SecIdent_Smiley, *dc, point.x, realY,
819						wxIMAGELIST_DRAW_TRANSPARENT);
820				} else if (client.IsBadGuy()) {
821					// the 'X'
822					m_ImageList.Draw(Client_BadGuy_Smiley, *dc, point.x, realY,
823						wxIMAGELIST_DRAW_TRANSPARENT);
824				}
825
826				if (client.GetObfuscationStatus() == OBST_ENABLED) {
827					// the "��" except it's a key
828					m_ImageList.Draw(Client_Encryption_Smiley, *dc, point.x, realY,
829						wxIMAGELIST_DRAW_TRANSPARENT);
830				}
831
832				// Next
833
834				point.x += iBitmapXSize;
835
836				wxString userName;
837#ifdef ENABLE_IP2COUNTRY
838				if (theApp->amuledlg->m_IP2Country->IsEnabled() && thePrefs::IsGeoIPEnabled()) {
839					// Draw the flag. Size can't be precached.
840					const CountryData& countrydata = theApp->amuledlg->m_IP2Country->GetCountryData(client.GetFullIP());
841
842					realY = point.y + (rect.GetHeight() - countrydata.Flag.GetHeight())/2 + 1 /* floor() */;
843
844					dc->DrawBitmap(countrydata.Flag,
845						point.x, realY,
846						true);
847
848					userName << countrydata.Name;
849
850					userName << wxT(" - ");
851
852					point.x += countrydata.Flag.GetWidth() + 2 /*Padding*/;
853				}
854#endif // ENABLE_IP2COUNTRY
855				if (client.GetUserName().IsEmpty()) {
856					userName << wxT("?");
857				} else {
858					userName << client.GetUserName();
859				}
860
861				dc->DrawText(userName, point.x, rect.GetY() + iTextOffset);
862			}
863			break;
864
865		case ColumnUserDownloaded:
866			if (item->GetType() != A4AF_SOURCE && client.GetTransferredDown()) {
867				buffer = CastItoXBytes(client.GetTransferredDown());
868				dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
869			}
870			break;
871		case ColumnUserUploaded:
872			if (item->GetType() != A4AF_SOURCE && client.GetTransferredUp()) {
873				buffer = CastItoXBytes(client.GetTransferredUp());
874				dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
875			}
876			break;
877		case ColumnUserSpeedDown:
878			if (item->GetType() != A4AF_SOURCE && client.GetKBpsDown() > 0.001) {
879				buffer = CFormat(_("%.1f kB/s")) % client.GetKBpsDown();
880				dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
881			}
882			break;
883		case ColumnUserSpeedUp:
884			// Datarate is in bytes.
885			if (item->GetType() != A4AF_SOURCE && client.GetUploadDatarate() > 1024) {
886				buffer = CFormat(_("%.1f kB/s")) % (client.GetUploadDatarate() / 1024.0);
887				dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
888			}
889			break;
890		case ColumnUserProgress:
891			if ( thePrefs::ShowProgBar() ) {
892				int iWidth = rect.GetWidth() - 2;
893				int iHeight = rect.GetHeight() - 2;
894
895				// don't draw Text beyond the bar
896				dc->SetClippingRegion(rect.GetX(), rect.GetY() + 1, iWidth, iHeight);
897
898				if ( item->GetType() != A4AF_SOURCE ) {
899					uint32 dwTicks = GetTickCount();
900
901					wxMemoryDC cdcStatus;
902
903					if ( item->dwUpdated < dwTicks || !item->status ||
904							iWidth != item->status->GetWidth() ) {
905
906						if (item->status == NULL) {
907							item->status = new wxBitmap(iWidth, iHeight);
908						} else if ( item->status->GetWidth() != iWidth ) {
909							// Only recreate if size has changed
910							item->status->Create(iWidth, iHeight);
911						}
912
913						cdcStatus.SelectObject(*(item->status));
914
915						if ( thePrefs::UseFlatBar() ) {
916							DrawSourceStatusBar( client, &cdcStatus,
917								wxRect(0, 0, iWidth, iHeight), true);
918						} else {
919							DrawSourceStatusBar( client, &cdcStatus,
920								wxRect(1, 1, iWidth - 2, iHeight - 2), false);
921
922							// Draw black border
923							cdcStatus.SetPen( *wxBLACK_PEN );
924							cdcStatus.SetBrush( *wxTRANSPARENT_BRUSH );
925							cdcStatus.DrawRectangle( 0, 0, iWidth, iHeight );
926						}
927
928						// Plus ten seconds
929						item->dwUpdated = dwTicks + 10000;
930					} else {
931						cdcStatus.SelectObject(*(item->status));
932					}
933
934					dc->Blit(rect.GetX(), rect.GetY() + 1, iWidth, iHeight, &cdcStatus, 0, 0);
935				} else {
936					wxString a4af;
937					CPartFile* p = client.GetRequestFile();
938					if (p) {
939						a4af = p->GetFileName().GetPrintable();
940					} else {
941						a4af = wxT("?");
942					}
943					buffer = CFormat(wxT("%s: %s")) % _("A4AF") % a4af;
944
945					int midx = (2*rect.GetX() + rect.GetWidth()) >> 1;
946					int midy = (2*rect.GetY() + rect.GetHeight()) >> 1;
947
948					wxCoord txtwidth, txtheight;
949
950					dc->GetTextExtent(buffer, &txtwidth, &txtheight);
951
952					dc->SetTextForeground(*wxBLACK);
953					dc->DrawText(buffer, wxMax(rect.GetX() + 2, midx - (txtwidth >> 1)), midy - (txtheight >> 1));
954
955					// Draw black border
956					dc->SetPen( *wxBLACK_PEN );
957					dc->SetBrush( *wxTRANSPARENT_BRUSH );
958					dc->DrawRectangle( rect.GetX(), rect.GetY() + 1, iWidth, iHeight );
959				}
960			}
961			break;
962
963		case ColumnUserAvailable: {
964				if ( client.GetUpPartCount() ) {
965					DrawStatusBar( client, dc, rect );
966				}
967				break;
968			}
969
970		case ColumnUserVersion: {
971				dc->DrawText(client.GetClientVerString(), rect.GetX(), rect.GetY() + iTextOffset);
972				break;
973			}
974
975		case ColumnUserQueueRankRemote: {
976			sint16 qrDiff = 0;
977			wxColour savedColour = dc->GetTextForeground();
978			// We only show the queue rank for sources actually queued for that file
979			if (	item->GetType() != A4AF_SOURCE && client.GetDownloadState() == DS_ONQUEUE ) {
980				if (client.IsRemoteQueueFull()) {
981					buffer = _("Queue Full");
982				} else {
983					uint16 rank = client.GetRemoteQueueRank();
984					if (rank) {
985						qrDiff = rank - client.GetOldRemoteQueueRank();
986						if (qrDiff == rank) {
987							qrDiff = 0;
988						}
989						if ( qrDiff < 0 ) {
990							dc->SetTextForeground(*wxBLUE);
991						}
992						if ( qrDiff > 0 ) {
993							dc->SetTextForeground(*wxRED);
994						}
995						buffer = CFormat(_("On Queue: %u (%i)")) % rank % qrDiff;
996					} else {
997						buffer = _("On Queue");
998					}
999				}
1000			} else {
1001				if (item->GetType() != A4AF_SOURCE) {
1002					buffer = DownloadStateToStr( client.GetDownloadState(),
1003						client.IsRemoteQueueFull() );
1004				} else {
1005					buffer = _("Asked for another file");
1006					if (	client.GetRequestFile() &&
1007						client.GetRequestFile()->GetFileName().IsOk()) {
1008						buffer += CFormat(wxT(" (%s)"))
1009							% client.GetRequestFile()->GetFileName();
1010					}
1011				}
1012			}
1013			dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1014			if (qrDiff) {
1015				dc->SetTextForeground(savedColour);
1016			}
1017			break;
1018		}
1019		case ColumnUserQueueRankLocal:
1020			if (item->GetType() != A4AF_SOURCE) {
1021				if (client.GetUploadState() == US_ONUPLOADQUEUE ) {
1022					uint16 nRank = client.GetUploadQueueWaitingPosition();
1023					if (nRank == 0) {
1024						buffer = _("Waiting for upload slot");
1025					} else {
1026						buffer = CFormat(_("On Queue: %u")) % nRank;
1027					}
1028				} else if (client.GetUploadState() == US_UPLOADING) {
1029					buffer = _("Uploading");
1030				} else {
1031					buffer = _("None");
1032				}
1033			} else {
1034				buffer = _("Asked for another file");
1035			}
1036			dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1037			break;
1038		// Source comes from?
1039		case ColumnUserOrigin: {
1040			buffer = wxGetTranslation(OriginToText(client.GetSourceFrom()));
1041			dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1042			break;
1043		}
1044		// Local file name to identify on multi select
1045		case ColumnUserFileNameDownload: {
1046			const CPartFile * pf = client.GetRequestFile();
1047			if (pf) {
1048				buffer = pf->GetFileName().GetPrintable();
1049			} else {
1050				buffer = _("Unknown");
1051				buffer = wxT("[") + buffer + wxT("]");
1052			}
1053			dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1054			break;
1055		}
1056		case ColumnUserFileNameUpload: {
1057			const CKnownFile * kf = client.GetUploadFile();
1058			if (kf) {
1059				buffer = kf->GetFileName().GetPrintable();
1060			} else {
1061				buffer = _("Unknown");
1062				buffer = wxT("[") + buffer + wxT("]");
1063			}
1064			dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1065			break;
1066		}
1067		case ColumnUserFileNameDownloadRemote: {
1068			bool nameMissmatch = false;
1069			wxColour savedColour = dc->GetTextForeground();
1070			if (client.GetClientFilename().IsEmpty() || item->GetType() == A4AF_SOURCE) {
1071				buffer = _("Unknown");
1072				buffer = wxT("[") + buffer + wxT("]");
1073			} else {
1074				buffer = client.GetClientFilename();
1075				const CPartFile * pf = client.GetRequestFile();
1076				if (pf && (pf->GetFileName().GetPrintable().CmpNoCase(buffer) != 0)) {
1077					nameMissmatch = true;
1078					dc->SetTextForeground(*wxRED);
1079				}
1080			}
1081			dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1082			if (nameMissmatch) {
1083				dc->SetTextForeground(savedColour);
1084			}
1085			break;
1086		}
1087		case ColumnUserSharedFiles: {
1088			if(client.HasDisabledSharedFiles()) {
1089				buffer = _("No");
1090			} else {
1091				buffer = _("Yes");
1092			}
1093			dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1094			break;
1095		}
1096	}
1097}
1098
1099int CGenericClientListCtrl::SortProc(wxUIntPtr param1, wxUIntPtr param2, long sortData)
1100{
1101	ClientCtrlItem_Struct* item1 = (ClientCtrlItem_Struct*)param1;
1102	ClientCtrlItem_Struct* item2 = (ClientCtrlItem_Struct*)param2;
1103
1104	int sortMod = (sortData & CMuleListCtrl::SORT_DES) ? -1 : 1;
1105	sortData &= CMuleListCtrl::COLUMN_MASK;
1106	int comp = 0;
1107
1108	// Two sources, some different possibilites
1109	// Avilable sources first, if we have both an
1110	// available and an unavailable
1111	comp = ( item2->GetType() - item1->GetType() );
1112
1113	if (comp) {
1114			// unavailable and available. The order is fixed regardless of sort-order.
1115		return comp;
1116	} else {
1117		comp = Compare(item1->GetSource(), item2->GetSource(), sortData);
1118	}
1119
1120	// We modify the result so that it matches with ascending or decending
1121	return sortMod * comp;
1122}
1123
1124int CGenericClientListCtrl::Compare(
1125	const CClientRef& client1, const CClientRef& client2, long lParamSort)
1126{
1127	switch (lParamSort) {
1128		// Sort by name
1129		case ColumnUserName:
1130			return CmpAny( client1.GetUserName(), client2.GetUserName() );
1131
1132		// Sort by transferred in the following fields
1133		case ColumnUserDownloaded:
1134			return CmpAny( client1.GetTransferredDown(), client2.GetTransferredDown() );
1135
1136		// Sort by transferred in the following fields
1137		case ColumnUserUploaded:
1138			return CmpAny( client1.GetTransferredUp(), client2.GetTransferredUp() );
1139
1140		// Sort by speed
1141		case ColumnUserSpeedDown:
1142			return CmpAny( client1.GetKBpsDown(), client2.GetKBpsDown() );
1143
1144		// Sort by speed
1145		case ColumnUserSpeedUp:
1146			return CmpAny( client1.GetUploadDatarate(), client2.GetUploadDatarate() );
1147
1148		// Sort by parts offered
1149		case ColumnUserProgress:
1150			return CmpAny(
1151				client1.GetAvailablePartCount(),
1152				client2.GetAvailablePartCount() );
1153
1154		// Sort by client version
1155		case ColumnUserVersion: {
1156			int cmp = client1.GetSoftStr().Cmp(client2.GetSoftStr());
1157
1158			if (cmp == 0) {
1159				cmp = CmpAny(client1.GetVersion(), client2.GetVersion());
1160			}
1161			if (cmp == 0) {
1162				cmp = client1.GetClientModString().Cmp(client2.GetClientModString());
1163			}
1164			return cmp;
1165		}
1166
1167		// Sort by Queue-Rank
1168		case ColumnUserQueueRankRemote: {
1169			// This will sort by download state: Downloading, OnQueue, Connecting ...
1170			// However, Asked For Another will always be placed last, due to
1171			// sorting in SortProc
1172			if ( client1.GetDownloadState() != client2.GetDownloadState() ) {
1173				return client1.GetDownloadState() - client2.GetDownloadState();
1174			}
1175
1176			// Placing items on queue before items on full queues
1177			if ( client1.IsRemoteQueueFull() ) {
1178				if ( client2.IsRemoteQueueFull() ) {
1179					return 0;
1180				} else {
1181					return  1;
1182				}
1183			} else if ( client2.IsRemoteQueueFull() ) {
1184				return -1;
1185			} else {
1186				if ( client1.GetRemoteQueueRank() ) {
1187					if ( client2.GetRemoteQueueRank() ) {
1188						return CmpAny(
1189							client1.GetRemoteQueueRank(),
1190							client2.GetRemoteQueueRank() );
1191					} else {
1192						return -1;
1193					}
1194				} else {
1195					if ( client2.GetRemoteQueueRank() ) {
1196						return  1;
1197					} else {
1198						return  0;
1199					}
1200				}
1201			}
1202		}
1203
1204		// Sort by Queue-Rank
1205		case ColumnUserQueueRankLocal: {
1206			// This will sort by download state: Downloading, OnQueue, Connecting ...
1207			// However, Asked For Another will always be placed last, due to
1208			// sorting in SortProc
1209			if ( client1.GetUploadState() != client2.GetUploadState() ) {
1210				return client1.GetUploadState() - client2.GetUploadState();
1211			}
1212
1213			uint16 rank1 = client1.GetUploadQueueWaitingPosition();
1214			uint16 rank2 = client2.GetUploadQueueWaitingPosition();
1215			// Placing items on queue before items on full queues
1216			if ( !rank1 ) {
1217				if ( !rank2 ) {
1218					return 0;
1219				} else {
1220					return 1;
1221				}
1222			} else if ( !rank2 ) {
1223				return -1;
1224			} else {
1225				if ( rank1 ) {
1226					if ( rank2 ) {
1227						return CmpAny(
1228							rank1,
1229							rank2 );
1230					} else {
1231						return -1;
1232					}
1233				} else {
1234					if ( rank2 ) {
1235						return  1;
1236					} else {
1237						return  0;
1238					}
1239				}
1240			}
1241		}
1242
1243		// Source of source ;)
1244		case ColumnUserOrigin:
1245			return CmpAny(client1.GetSourceFrom(), client2.GetSourceFrom());
1246
1247		// Sort by local filename (download)
1248		case ColumnUserFileNameDownload: {
1249			wxString buffer1, buffer2;
1250			const CPartFile * pf1 = client1.GetRequestFile();
1251			if (pf1) {
1252				buffer1 = pf1->GetFileName().GetPrintable();
1253			}
1254			const CPartFile * pf2 = client2.GetRequestFile();
1255			if (pf2) {
1256				buffer2 = pf2->GetFileName().GetPrintable();
1257			}
1258			return CmpAny(buffer1, buffer2);
1259		}
1260
1261		// Sort by local filename (upload)
1262		case ColumnUserFileNameUpload: {
1263			wxString buffer1, buffer2;
1264			const CKnownFile * kf1 = client1.GetUploadFile();
1265			if (kf1) {
1266				buffer1 = kf1->GetFileName().GetPrintable();
1267			}
1268			const CKnownFile * kf2 = client2.GetUploadFile();
1269			if (kf2) {
1270				buffer2 = kf2->GetFileName().GetPrintable();
1271			}
1272			return CmpAny(buffer1, buffer2);
1273		}
1274
1275		case ColumnUserFileNameDownloadRemote: {
1276			return CmpAny(client1.GetClientFilename(), client2.GetClientFilename());
1277		}
1278
1279		case ColumnUserSharedFiles: {
1280			return CmpAny(client1.HasDisabledSharedFiles(), client2.HasDisabledSharedFiles());
1281		}
1282
1283		default:
1284			return 0;
1285	}
1286}
1287
1288
1289void CGenericClientListCtrl::ShowSourcesCount( int diff )
1290{
1291	m_clientcount += diff;
1292	wxStaticText* label = CastByID( ID_CLIENTCOUNT, GetParent(), wxStaticText );
1293
1294	if (label) {
1295		label->SetLabel(CFormat(wxT("%i")) % m_clientcount);
1296		label->GetParent()->Layout();
1297	}
1298}
1299
1300static const CMuleColour crBoth(0, 192, 0);
1301static const CMuleColour crFlatBoth(0, 150, 0);
1302
1303static const CMuleColour crNeither(240, 240, 240);
1304static const CMuleColour crFlatNeither(224, 224, 224);
1305
1306static const CMuleColour crClientOnly(104, 104, 104);
1307static const CMuleColour crFlatClientOnly(0, 0, 0);
1308
1309static const CMuleColour crPending(255, 208, 0);
1310static const CMuleColour crNextPending(255, 255, 100);
1311
1312void CGenericClientListCtrl::DrawSourceStatusBar(
1313	const CClientRef& source, wxDC* dc, const wxRect& rect, bool bFlat) const
1314{
1315	static CBarShader s_StatusBar(16);
1316
1317	CPartFile* reqfile = source.GetRequestFile();
1318
1319	s_StatusBar.SetHeight(rect.height);
1320	s_StatusBar.SetWidth(rect.width);
1321	s_StatusBar.Set3dDepth( thePrefs::Get3DDepth() );
1322	const BitVector& partStatus = source.GetPartStatus();
1323
1324	if (reqfile && reqfile->GetPartCount() == partStatus.size()) {
1325		s_StatusBar.SetFileSize(reqfile->GetFileSize());
1326		uint16 lastDownloadingPart = source.GetDownloadState() == DS_DOWNLOADING
1327									? source.GetLastDownloadingPart() : 0xffff;
1328		uint16 nextRequestedPart = source.GetNextRequestedPart();
1329
1330		for ( uint16 i = 0; i < partStatus.size(); i++ ) {
1331			uint64 uStart = PARTSIZE * i;
1332			uint64 uEnd = uStart + reqfile->GetPartSize(i) - 1;
1333
1334			CMuleColour colour;
1335			if (!partStatus.get(i)) {
1336				// client does not have this part
1337				// light grey
1338				colour = bFlat ? crFlatNeither : crNeither;
1339			} else if ( reqfile->IsComplete(i)) {
1340				// completed part
1341				// green
1342				colour = bFlat ? crFlatBoth : crBoth;
1343			} else if (lastDownloadingPart == i) {
1344				// downloading part
1345				// yellow
1346				colour = crPending;
1347			} else if (nextRequestedPart == i) {
1348				// requested part
1349				// light yellow
1350				colour = crNextPending;
1351			} else {
1352				// client has this part, we need it
1353				// black
1354				colour = bFlat ? crFlatClientOnly : crClientOnly;
1355			}
1356
1357			if ( source.GetRequestFile()->IsStopped() ) {
1358				colour.Blend(50);
1359			}
1360
1361			s_StatusBar.FillRange(uStart, uEnd, colour);
1362		}
1363	} else {
1364		s_StatusBar.SetFileSize(1);
1365		s_StatusBar.FillRange(0, 1, bFlat ? crFlatNeither : crNeither);
1366	}
1367
1368	s_StatusBar.Draw(dc, rect.x, rect.y, bFlat);
1369}
1370
1371static const CMuleColour crUnavailable(240, 240, 240);
1372static const CMuleColour crFlatUnavailable(224, 224, 224);
1373
1374static const CMuleColour crAvailable(104, 104, 104);
1375static const CMuleColour crFlatAvailable(0, 0, 0);
1376
1377void CGenericClientListCtrl::DrawStatusBar( const CClientRef& client, wxDC* dc, const wxRect& rect1 ) const
1378{
1379	wxRect rect = rect1;
1380	rect.y		+= 1;
1381	rect.height	-= 2;
1382
1383	wxPen   old_pen   = dc->GetPen();
1384	wxBrush old_brush = dc->GetBrush();
1385	bool bFlat = thePrefs::UseFlatBar();
1386
1387	wxRect barRect = rect;
1388	if (!bFlat) { // round bar has a black border, the bar itself is 1 pixel less on each border
1389		barRect.x ++;
1390		barRect.y ++;
1391		barRect.height -= 2;
1392		barRect.width -= 2;
1393	}
1394	static CBarShader s_StatusBar(16);
1395
1396	uint32 partCount = client.GetUpPartCount();
1397
1398	// Seems the partfile in the client object is not necessarily valid when bar is drawn for the first time.
1399	// Keep it simple and make all parts same size.
1400	s_StatusBar.SetFileSize(partCount * PARTSIZE);
1401	s_StatusBar.SetHeight(barRect.height);
1402	s_StatusBar.SetWidth(barRect.width);
1403	s_StatusBar.Set3dDepth( thePrefs::Get3DDepth() );
1404
1405	uint64 uEnd = 0;
1406	for ( uint64 i = 0; i < partCount; i++ ) {
1407		uint64 uStart = PARTSIZE * i;
1408		uEnd = uStart + PARTSIZE - 1;
1409
1410		s_StatusBar.FillRange(uStart, uEnd, client.IsUpPartAvailable(i) ? (bFlat ? crFlatAvailable : crAvailable) : (bFlat ? crFlatUnavailable : crUnavailable));
1411	}
1412	// fill the rest (if partStatus is empty)
1413	s_StatusBar.FillRange(uEnd + 1, partCount * PARTSIZE - 1, bFlat ? crFlatUnavailable : crUnavailable);
1414	s_StatusBar.Draw(dc, barRect.x, barRect.y, bFlat);
1415
1416	if (!bFlat) {
1417		// Draw black border
1418		dc->SetPen( *wxBLACK_PEN );
1419		dc->SetBrush( *wxTRANSPARENT_BRUSH );
1420		dc->DrawRectangle(rect);
1421	}
1422
1423	dc->SetPen( old_pen );
1424	dc->SetBrush( old_brush );
1425}
1426
1427// File_checked_for_headers
1428