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// Copyright (c) 2002 Merkur ( devs@emule-project.net / http://www.emule-project.net )
6//
7// Any parts of this program derived from the xMule, lMule or eMule project,
8// or contributed by third-party developers are copyrighted by their
9// respective authors.
10//
11// This program is free software; you can redistribute it and/or modify
12// it under the terms of the GNU General Public License as published by
13// the Free Software Foundation; either version 2 of the License, or
14// (at your option) any later version.
15//
16// This program is distributed in the hope that it will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19// GNU General Public License for more details.
20//
21// You should have received a copy of the GNU General Public License
22// along with this program; if not, write to the Free Software
23// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
24//
25
26#include "DownloadListCtrl.h"	// Interface declarations
27
28#include <protocol/ed2k/ClientSoftware.h>
29#include <common/MenuIDs.h>
30
31#include <common/Format.h>	// Needed for CFormat
32#include "amule.h"		// Needed for theApp
33#include "amuleDlg.h"		// Needed for CamuleDlg
34#include "BarShader.h"		// Needed for CBarShader
35#include "CommentDialogLst.h"	// Needed for CCommentDialogLst
36#include "DataToText.h"		// Needed for PriorityToStr
37#include "DownloadQueue.h"
38#include "FileDetailDialog.h"	// Needed for CFileDetailDialog
39#include "GuiEvents.h"		// Needed for CoreNotify_*
40#include "Logger.h"
41#include "muuli_wdr.h"		// Needed for ID_DLOADLIST
42#include "PartFile.h"		// Needed for CPartFile
43#include "Preferences.h"
44#include "SharedFileList.h"	// Needed for CSharedFileList
45#include "TerminationProcess.h"	// Needed for CTerminationProcess
46#include "TransferWnd.h"
47#include "SourceListCtrl.h"
48
49class CPartFile;
50
51
52struct FileCtrlItem_Struct
53{
54	FileCtrlItem_Struct()
55		: dwUpdated(0),
56		  status(NULL),
57		  m_fileValue(NULL)
58	{ }
59
60	~FileCtrlItem_Struct() {
61		delete status;
62	}
63
64	CPartFile* GetFile() const {
65		return m_fileValue;
66	}
67
68	void SetContents(CPartFile* file) {
69		m_fileValue = file;
70	}
71
72	uint32		dwUpdated;
73	wxBitmap*	status;
74
75private:
76	CPartFile*			m_fileValue;
77};
78
79#define m_ImageList theApp->amuledlg->m_imagelist
80
81enum ColumnEnum {
82	ColumnPart = 0,
83	ColumnFileName,
84	ColumnSize,
85	ColumnTransferred,
86	ColumnCompleted,
87	ColumnSpeed,
88	ColumnProgress,
89	ColumnSources,
90	ColumnPriority,
91	ColumnStatus,
92	ColumnTimeRemaining,
93	ColumnLastSeenComplete,
94	ColumnLastReception,
95	ColumnNumberOfColumns
96};
97
98BEGIN_EVENT_TABLE(CDownloadListCtrl, CMuleListCtrl)
99	EVT_LIST_ITEM_ACTIVATED(ID_DLOADLIST,	CDownloadListCtrl::OnItemActivated)
100	EVT_LIST_ITEM_RIGHT_CLICK(ID_DLOADLIST, CDownloadListCtrl::OnMouseRightClick)
101	EVT_LIST_ITEM_MIDDLE_CLICK(ID_DLOADLIST, CDownloadListCtrl::OnMouseMiddleClick)
102	EVT_LIST_ITEM_SELECTED(ID_DLOADLIST, CDownloadListCtrl::OnItemSelectionChanged)
103	EVT_LIST_ITEM_DESELECTED(ID_DLOADLIST, CDownloadListCtrl::OnItemSelectionChanged)
104
105	EVT_CHAR( CDownloadListCtrl::OnKeyPressed )
106
107	EVT_MENU( MP_CANCEL, 			CDownloadListCtrl::OnCancelFile )
108
109	EVT_MENU( MP_PAUSE,			CDownloadListCtrl::OnSetStatus )
110	EVT_MENU( MP_STOP,			CDownloadListCtrl::OnSetStatus )
111	EVT_MENU( MP_RESUME,			CDownloadListCtrl::OnSetStatus )
112
113	EVT_MENU( MP_PRIOLOW,			CDownloadListCtrl::OnSetPriority )
114	EVT_MENU( MP_PRIONORMAL,		CDownloadListCtrl::OnSetPriority )
115	EVT_MENU( MP_PRIOHIGH,			CDownloadListCtrl::OnSetPriority )
116	EVT_MENU( MP_PRIOAUTO,			CDownloadListCtrl::OnSetPriority )
117
118	EVT_MENU( MP_SWAP_A4AF_TO_THIS,		CDownloadListCtrl::OnSwapSources )
119	EVT_MENU( MP_SWAP_A4AF_TO_THIS_AUTO,	CDownloadListCtrl::OnSwapSources )
120	EVT_MENU( MP_SWAP_A4AF_TO_ANY_OTHER,	CDownloadListCtrl::OnSwapSources )
121
122	EVT_MENU_RANGE( MP_ASSIGNCAT, MP_ASSIGNCAT + 99, CDownloadListCtrl::OnSetCategory )
123
124	EVT_MENU( MP_CLEARCOMPLETED,		CDownloadListCtrl::OnClearCompleted )
125
126	EVT_MENU( MP_GETMAGNETLINK,		CDownloadListCtrl::OnGetLink )
127	EVT_MENU( MP_GETED2KLINK,		CDownloadListCtrl::OnGetLink )
128
129	EVT_MENU( MP_METINFO,			CDownloadListCtrl::OnViewFileInfo )
130	EVT_MENU( MP_VIEW,			CDownloadListCtrl::OnPreviewFile )
131	EVT_MENU( MP_VIEWFILECOMMENTS,		CDownloadListCtrl::OnViewFileComments )
132
133	EVT_MENU( MP_WS,			CDownloadListCtrl::OnGetFeedback )
134
135END_EVENT_TABLE()
136
137//! This listtype is used when gathering the selected items.
138typedef std::list<FileCtrlItem_Struct*>	ItemList;
139
140CDownloadListCtrl::CDownloadListCtrl(
141	wxWindow *parent, wxWindowID winid, const wxPoint& pos, const wxSize& size,
142	long style, const wxValidator& validator, const wxString& name )
143:
144CMuleListCtrl( parent, winid, pos, size, style | wxLC_OWNERDRAW, validator, name )
145{
146	// Setting the sorter function.
147	SetSortFunc( SortProc );
148
149	// Set the table-name (for loading and saving preferences).
150	SetTableName( wxT("Download") );
151
152	m_menu = NULL;
153
154	m_hilightBrush  = CMuleColour(wxSYS_COLOUR_HIGHLIGHT).Blend(125).GetBrush();
155
156	m_hilightUnfocusBrush = CMuleColour(wxSYS_COLOUR_BTNSHADOW).Blend(125).GetBrush();
157
158	InsertColumn( ColumnPart,			_("Part"),					wxLIST_FORMAT_LEFT,  30, wxT("a") );
159	InsertColumn( ColumnFileName,		_("File Name"),				wxLIST_FORMAT_LEFT, 260, wxT("N") );
160	InsertColumn( ColumnSize,			_("Size"),					wxLIST_FORMAT_LEFT,  60, wxT("Z") );
161	InsertColumn( ColumnTransferred,	_("Transferred"),			wxLIST_FORMAT_LEFT,  65, wxT("T") );
162	InsertColumn( ColumnCompleted,		_("Completed"),				wxLIST_FORMAT_LEFT,  65, wxT("C") );
163	InsertColumn( ColumnSpeed,			_("Speed"),					wxLIST_FORMAT_LEFT,  65, wxT("S") );
164	InsertColumn( ColumnProgress,		_("Progress"),				wxLIST_FORMAT_LEFT, 170, wxT("P") );
165	InsertColumn( ColumnSources,		_("Sources"),				wxLIST_FORMAT_LEFT,  50, wxT("u") );
166	InsertColumn( ColumnPriority,		_("Priority"),				wxLIST_FORMAT_LEFT,  55, wxT("p") );
167	InsertColumn( ColumnStatus,			_("Status"),				wxLIST_FORMAT_LEFT,  70, wxT("s") );
168	InsertColumn( ColumnTimeRemaining,  _("Time Remaining"),		wxLIST_FORMAT_LEFT, 110, wxT("r") );
169	InsertColumn( ColumnLastSeenComplete, _("Last Seen Complete"),	wxLIST_FORMAT_LEFT, 220, wxT("c") );
170	InsertColumn( ColumnLastReception,	_("Last Reception"),		wxLIST_FORMAT_LEFT, 220, wxT("R") );
171
172	m_category = 0;
173	m_filecount = 0;
174	m_ItemSelectionChangePending = false;
175	LoadSettings();
176
177	//m_ready = true;
178}
179
180// This is the order the columns had before extendable list-control settings save/load code was introduced.
181// Don't touch when inserting new columns!
182wxString CDownloadListCtrl::GetOldColumnOrder() const
183{
184	return wxT("N,Z,T,C,S,P,u,p,s,r,c,R");
185}
186
187CDownloadListCtrl::~CDownloadListCtrl()
188{
189	while ( !m_ListItems.empty() ) {
190		delete m_ListItems.begin()->second;
191		m_ListItems.erase( m_ListItems.begin() );
192	}
193}
194
195void CDownloadListCtrl::AddFile( CPartFile* file )
196{
197	wxASSERT( file );
198
199	// Avoid duplicate entries of files
200	if ( m_ListItems.find( file ) == m_ListItems.end() ) {
201		FileCtrlItem_Struct* newitem = new FileCtrlItem_Struct;
202		newitem->SetContents(file);
203
204		m_ListItems.insert( ListItemsPair( file, newitem ) );
205
206		// Check if the new file is visible in the current category
207		if ( file->CheckShowItemInGivenCat( m_category ) ) {
208			ShowFile( file, true );
209			if (file->IsCompleted()) {
210				CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->Enable(true);
211			}
212			SortList();
213		}
214	}
215}
216
217void CDownloadListCtrl::RemoveFile( CPartFile* file )
218{
219	wxASSERT( file );
220
221	// Ensure that any list-entries are removed
222	ShowFile( file, false );
223
224	// Find the assosiated list-item
225	ListItems::iterator it = m_ListItems.find( file );
226
227	if ( it != m_ListItems.end() ) {
228		delete it->second;
229
230		m_ListItems.erase( it );
231	}
232}
233
234
235void CDownloadListCtrl::UpdateItem(const void* toupdate)
236{
237	// Retrieve all entries matching the source
238	ListIteratorPair rangeIt = m_ListItems.equal_range( toupdate );
239
240	// Visible lines, default to all because not all platforms
241	// support the GetVisibleLines function
242	long first = 0, last = GetItemCount();
243
244#ifndef __WXMSW__
245	// Get visible lines if we need them
246	if ( rangeIt.first != rangeIt.second ) {
247		GetVisibleLines( &first, &last );
248	}
249#endif
250
251	for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second; ++it ) {
252		FileCtrlItem_Struct* item = it->second;
253
254		long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
255
256		// Determine if the file should be shown in the current category
257
258		CPartFile* file = item->GetFile();
259
260		bool show = file->CheckShowItemInGivenCat( m_category );
261
262		if ( index > -1 ) {
263			if ( show ) {
264				item->dwUpdated = 0;
265
266				// Only update visible lines
267				if ( index >= first && index <= last) {
268					RefreshItem( index );
269				}
270			} else {
271				// Item should no longer be shown in
272				// the current category
273				ShowFile( file, false );
274			}
275		} else if ( show ) {
276			// Item has been hidden but new status means
277			// that it should it should be shown in the
278			// current category
279			ShowFile( file, true );
280		}
281
282		if (file->IsCompleted() && show) {
283			CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->Enable(true);
284		}
285	}
286}
287
288
289void CDownloadListCtrl::ShowFile( CPartFile* file, bool show )
290{
291	wxASSERT( file );
292
293	ListItems::iterator it = m_ListItems.find( file );
294
295	if ( it != m_ListItems.end() ) {
296		FileCtrlItem_Struct* item = it->second;
297
298		if ( show ) {
299			// Check if the file is already being displayed
300			long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
301			if ( index == -1 ) {
302				long newitem = InsertItem( GetItemCount(), wxEmptyString );
303
304				SetItemPtrData( newitem, reinterpret_cast<wxUIntPtr>(item) );
305
306				wxListItem myitem;
307				myitem.m_itemId = newitem;
308				myitem.SetBackgroundColour( GetBackgroundColour() );
309
310				SetItem(myitem);
311
312				RefreshItem( newitem );
313
314				ShowFilesCount( 1 );
315			}
316		} else {
317			// Try to find the file and remove it
318			long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
319			if ( index > -1 ) {
320				DeleteItem( index );
321				ShowFilesCount( -1 );
322			}
323		}
324	}
325}
326
327void CDownloadListCtrl::ChangeCategory( int newCategory )
328{
329	Freeze();
330
331	bool hasCompletedDownloads = false;
332
333	// remove all displayed files with a different cat and show the correct ones
334	for (ListItems::const_iterator it = m_ListItems.begin(); it != m_ListItems.end(); it++) {
335
336		CPartFile* file =  it->second->GetFile();
337
338		bool curVisibility = file->CheckShowItemInGivenCat( m_category );
339		bool newVisibility = file->CheckShowItemInGivenCat( newCategory );
340
341		if (newVisibility && file->IsCompleted()) {
342			hasCompletedDownloads = true;
343		}
344
345		// Check if the visibility of the file has changed. However, if the
346		// current category is the default (0) category, then we can't use
347		// curVisiblity to see if the visibility has changed but instead
348		// have to let ShowFile() check if the file is or isn't on the list.
349		if ( curVisibility != newVisibility || !newCategory ) {
350			ShowFile( file, newVisibility );
351		}
352	}
353
354	CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->Enable(hasCompletedDownloads);
355
356	Thaw();
357
358	m_category = newCategory;
359}
360
361
362uint8 CDownloadListCtrl::GetCategory() const
363{
364	return m_category;
365}
366
367
368/**
369 * Helper-function: This function is used to gather selected items.
370 *
371 * @param list A pointer to the list to gather items from.
372 * @return A list containing the selected items.
373 */
374ItemList GetSelectedItems( CDownloadListCtrl* list)
375{
376	ItemList results;
377
378	long index = list->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
379
380	while ( index > -1 ) {
381		FileCtrlItem_Struct* item = (FileCtrlItem_Struct*)list->GetItemData( index );
382		results.push_back( item );
383
384		index = list->GetNextItem( index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
385	}
386
387	return results;
388}
389
390
391void CDownloadListCtrl::OnCancelFile(wxCommandEvent& WXUNUSED(event))
392{
393	ItemList files = ::GetSelectedItems(this);
394	for (ItemList::iterator it = files.begin(); it != files.end(); ) {
395		ItemList::iterator it1 = it++;
396		CPartFile* file = (*it1)->GetFile();
397		if (file) {
398			switch (file->GetStatus()) {
399				case PS_WAITINGFORHASH:
400				case PS_HASHING:
401				case PS_COMPLETING:
402				case PS_COMPLETE:
403					files.erase(it1);
404					break;
405			}
406		}
407	}
408	if (files.size()) {
409		wxString question;
410		if (files.size() == 1) {
411			question = _("Are you sure that you wish to delete the selected file?");
412		} else {
413			question = _("Are you sure that you wish to delete the selected files?");
414		}
415		if (wxMessageBox( question, _("Cancel"), wxICON_QUESTION | wxYES_NO, this) == wxYES) {
416			for (ItemList::iterator it = files.begin(); it != files.end(); ++it) {
417				CPartFile* file = (*it)->GetFile();
418				if (file) {
419					CoreNotify_PartFile_Delete(file);
420				}
421			}
422		}
423	}
424}
425
426
427void CDownloadListCtrl::OnSetPriority( wxCommandEvent& event )
428{
429	int priority = 0;
430	switch ( event.GetId() ) {
431		case MP_PRIOLOW:	priority = PR_LOW;	break;
432		case MP_PRIONORMAL:	priority = PR_NORMAL;	break;
433		case MP_PRIOHIGH:	priority = PR_HIGH;	break;
434		case MP_PRIOAUTO:	priority = PR_AUTO;	break;
435		default:
436			wxFAIL;
437	}
438
439	ItemList files = ::GetSelectedItems( this );
440
441	for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
442		CPartFile* file = (*it)->GetFile();
443
444		if ( priority == PR_AUTO ) {
445			CoreNotify_PartFile_PrioAuto( file, true );
446		} else {
447			CoreNotify_PartFile_PrioAuto( file, false );
448
449			CoreNotify_PartFile_PrioSet( file, priority, true );
450		}
451	}
452}
453
454
455void CDownloadListCtrl::OnSwapSources( wxCommandEvent& event )
456{
457	ItemList files = ::GetSelectedItems( this );
458
459	for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
460		CPartFile* file = (*it)->GetFile();
461
462		switch ( event.GetId() ) {
463			case MP_SWAP_A4AF_TO_THIS:
464				CoreNotify_PartFile_Swap_A4AF( file );
465				break;
466
467			case MP_SWAP_A4AF_TO_THIS_AUTO:
468				CoreNotify_PartFile_Swap_A4AF_Auto( file );
469				break;
470
471			case MP_SWAP_A4AF_TO_ANY_OTHER:
472				CoreNotify_PartFile_Swap_A4AF_Others( file );
473				break;
474		}
475	}
476}
477
478
479void CDownloadListCtrl::OnSetCategory( wxCommandEvent& event )
480{
481	ItemList files = ::GetSelectedItems( this );
482
483	for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
484		CoreNotify_PartFile_SetCat( (*it)->GetFile(), event.GetId() - MP_ASSIGNCAT );
485		ShowFile((*it)->GetFile(), false);
486	}
487	wxListEvent ev;
488	OnItemSelectionChanged(ev);	// clear clients that may have been shown
489
490	ChangeCategory( m_category );	// This only updates the visibility of the clear completed button
491	theApp->amuledlg->m_transferwnd->UpdateCatTabTitles();
492}
493
494
495void CDownloadListCtrl::OnSetStatus( wxCommandEvent& event )
496{
497	ItemList files = ::GetSelectedItems( this );
498
499	for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
500		CPartFile* file = (*it)->GetFile();
501
502		switch ( event.GetId() ) {
503			case MP_PAUSE:
504				CoreNotify_PartFile_Pause( file );
505				break;
506
507			case MP_RESUME:
508				CoreNotify_PartFile_Resume( file );
509				break;
510
511			case MP_STOP:
512				CoreNotify_PartFile_Stop( file );
513				break;
514		}
515	}
516}
517
518
519void CDownloadListCtrl::OnClearCompleted( wxCommandEvent& WXUNUSED(event) )
520{
521	ClearCompleted();
522}
523
524
525void CDownloadListCtrl::OnGetLink(wxCommandEvent& event)
526{
527	ItemList files = ::GetSelectedItems( this );
528
529	wxString URIs;
530
531	for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
532		CPartFile* file = (*it)->GetFile();
533
534		if ( event.GetId() == MP_GETED2KLINK ) {
535			URIs += theApp->CreateED2kLink( file ) + wxT("\n");
536		} else {
537			URIs += theApp->CreateMagnetLink( file ) + wxT("\n");
538		}
539	}
540
541	if ( !URIs.IsEmpty() ) {
542		theApp->CopyTextToClipboard( URIs.BeforeLast(wxT('\n')) );
543	}
544}
545
546
547void CDownloadListCtrl::OnGetFeedback(wxCommandEvent& WXUNUSED(event))
548{
549	wxString feed;
550	ItemList files = ::GetSelectedItems( this );
551
552	for (ItemList::iterator it = files.begin(); it != files.end(); ++it) {
553		if (feed.IsEmpty()) {
554			feed = CFormat(_("Feedback from: %s (%s)\n\n")) % thePrefs::GetUserNick() % theApp->GetFullMuleVersion();
555		} else {
556			feed += wxT("\n");
557		}
558		feed += (*it)->GetFile()->GetFeedback();
559	}
560
561	if (!feed.IsEmpty()) {
562		theApp->CopyTextToClipboard(feed);
563	}
564}
565
566void CDownloadListCtrl::OnViewFileInfo( wxCommandEvent& WXUNUSED(event) )
567{
568	long index = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
569
570	if (index >= 0) {
571		ShowFileDetailDialog(index);
572	}
573}
574
575
576void CDownloadListCtrl::OnViewFileComments( wxCommandEvent& WXUNUSED(event) )
577{
578	ItemList files = ::GetSelectedItems( this );
579
580	if ( files.size() == 1 ) {
581		CCommentDialogLst dialog( this, files.front()->GetFile() );
582		dialog.ShowModal();
583	}
584}
585
586void CDownloadListCtrl::OnPreviewFile( wxCommandEvent& WXUNUSED(event) )
587{
588	ItemList files = ::GetSelectedItems( this );
589
590	if ( files.size() == 1 ) {
591		PreviewFile(files.front()->GetFile());
592	}
593}
594
595void CDownloadListCtrl::OnItemActivated( wxListEvent& evt )
596{
597	CPartFile* file = ((FileCtrlItem_Struct*)GetItemData( evt.GetIndex()))->GetFile();
598
599	if ((!file->IsPartFile() || file->IsCompleted()) && file->PreviewAvailable()) {
600		PreviewFile( file );
601	}
602}
603
604void CDownloadListCtrl::OnItemSelectionChanged( wxListEvent& )
605{
606	if (!m_ItemSelectionChangePending && !IsSorting()) {
607		m_ItemSelectionChangePending = true;
608		Notify_DownloadCtrlDoItemSelectionChanged();
609	}
610}
611
612void CDownloadListCtrl::DoItemSelectionChanged()
613{
614	m_ItemSelectionChangePending = false;
615	CKnownFileVector filesVector;
616	filesVector.reserve(GetSelectedItemCount());
617
618	long index = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
619
620	while ( index > -1 ) {
621		CPartFile* file = ((FileCtrlItem_Struct*)GetItemData( index ))->GetFile();
622		if (file->IsPartFile()) {
623			filesVector.push_back(file);
624		}
625		index = GetNextItem( index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
626	}
627
628	std::sort(filesVector.begin(), filesVector.end());
629	theApp->amuledlg->m_transferwnd->clientlistctrl->ShowSources(filesVector);
630}
631
632void CDownloadListCtrl::OnMouseRightClick(wxListEvent& evt)
633{
634	long index = CheckSelection(evt);
635	if (index < 0) {
636		return;
637	}
638
639	delete m_menu;
640	m_menu = NULL;
641
642	FileCtrlItem_Struct* item = (FileCtrlItem_Struct*)GetItemData( index );
643	m_menu = new wxMenu( _("Downloads") );
644
645	wxMenu* priomenu = new wxMenu();
646	priomenu->AppendCheckItem(MP_PRIOLOW, _("Low"));
647	priomenu->AppendCheckItem(MP_PRIONORMAL, _("Normal"));
648	priomenu->AppendCheckItem(MP_PRIOHIGH, _("High"));
649	priomenu->AppendCheckItem(MP_PRIOAUTO, _("Auto"));
650
651	m_menu->Append(MP_MENU_PRIO, _("Priority"), priomenu);
652	m_menu->Append(MP_CANCEL, _("Cancel"));
653	m_menu->Append(MP_STOP, _("&Stop"));
654	m_menu->Append(MP_PAUSE, _("&Pause"));
655	m_menu->Append(MP_RESUME, _("&Resume"));
656	m_menu->Append(MP_CLEARCOMPLETED, _("C&lear completed"));
657	//-----------------------------------------------------
658	m_menu->AppendSeparator();
659	//-----------------------------------------------------
660	wxMenu* extendedmenu = new wxMenu();
661	extendedmenu->Append(MP_SWAP_A4AF_TO_THIS,
662		_("Swap every A4AF to this file now"));
663	extendedmenu->AppendCheckItem(MP_SWAP_A4AF_TO_THIS_AUTO,
664		_("Swap every A4AF to this file (Auto)"));
665	//-----------------------------------------------------
666	extendedmenu->AppendSeparator();
667	//-----------------------------------------------------
668	extendedmenu->Append(MP_SWAP_A4AF_TO_ANY_OTHER,
669		_("Swap every A4AF to any other file now"));
670	//-----------------------------------------------------
671	m_menu->Append(MP_MENU_EXTD,
672		_("Extended Options"), extendedmenu);
673	//-----------------------------------------------------
674	m_menu->AppendSeparator();
675	//-----------------------------------------------------
676
677	m_menu->Append(MP_VIEW, _("Preview"));
678	m_menu->Append(MP_METINFO, _("Show file &details"));
679	m_menu->Append(MP_VIEWFILECOMMENTS, _("Show all comments"));
680	//-----------------------------------------------------
681	m_menu->AppendSeparator();
682	//-----------------------------------------------------
683	m_menu->Append(MP_GETMAGNETLINK,
684		_("Copy magnet URI to clipboard"));
685	m_menu->Append(MP_GETED2KLINK,
686		_("Copy eD2k &link to clipboard"));
687	m_menu->Append(MP_WS,
688		_("Copy feedback to clipboard"));
689	//-----------------------------------------------------
690	m_menu->AppendSeparator();
691	//-----------------------------------------------------
692	// Add dynamic entries
693	wxMenu *cats = new wxMenu(_("Category"));
694	if (theApp->glob_prefs->GetCatCount() > 1) {
695		for (uint32 i = 0; i < theApp->glob_prefs->GetCatCount(); i++) {
696			if ( i == 0 ) {
697				cats->Append( MP_ASSIGNCAT, _("unassign") );
698			} else {
699				cats->Append( MP_ASSIGNCAT + i,
700					theApp->glob_prefs->GetCategory(i)->title );
701			}
702		}
703	}
704	m_menu->Append(MP_MENU_CATS, _("Assign to category"), cats);
705	m_menu->Enable(MP_MENU_CATS, (theApp->glob_prefs->GetCatCount() > 1) );
706
707	CPartFile* file = item->GetFile();
708	// then set state
709	bool canStop;
710	bool canPause;
711	bool canCancel;
712	bool fileResumable;
713	if (file->GetStatus(true) != PS_ALLOCATING) {
714		const uint8_t fileStatus = file->GetStatus();
715		canStop =
716			(fileStatus != PS_ERROR) &&
717			(fileStatus != PS_COMPLETE) &&
718			(file->IsStopped() != true);
719		canPause = (file->GetStatus() != PS_PAUSED) && canStop;
720		fileResumable =
721			(fileStatus == PS_PAUSED) ||
722			(fileStatus == PS_ERROR) ||
723			(fileStatus == PS_INSUFFICIENT);
724		canCancel = fileStatus != PS_COMPLETE;
725	} else {
726		canStop = canPause = canCancel = fileResumable = false;
727	}
728
729	m_menu->Enable( MP_CANCEL,	canCancel );
730	m_menu->Enable( MP_PAUSE,	canPause );
731	m_menu->Enable( MP_STOP,	canStop );
732	m_menu->Enable( MP_RESUME, 	fileResumable );
733	m_menu->Enable( MP_CLEARCOMPLETED, CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->IsEnabled() );
734
735	wxString view;
736	if (file->IsPartFile() && !file->IsCompleted()) {
737		view = CFormat(wxT("%s [%s]")) % _("Preview")
738				% file->GetPartMetFileName().RemoveExt();
739	} else if ( file->IsCompleted() ) {
740		view = _("&Open the file");
741	}
742	m_menu->SetLabel(MP_VIEW, view);
743	m_menu->Enable(MP_VIEW, file->PreviewAvailable());
744
745	FileRatingList ratingList;
746	item->GetFile()->GetRatingAndComments(ratingList);
747	m_menu->Enable(MP_VIEWFILECOMMENTS, !ratingList.empty());
748
749	m_menu->Check(  MP_SWAP_A4AF_TO_THIS_AUTO, 	file->IsA4AFAuto() );
750
751	int priority = file->IsAutoDownPriority() ? PR_AUTO : file->GetDownPriority();
752
753	priomenu->Check( MP_PRIOHIGH,	priority == PR_HIGH );
754	priomenu->Check( MP_PRIONORMAL, priority == PR_NORMAL );
755	priomenu->Check( MP_PRIOLOW,	priority == PR_LOW );
756	priomenu->Check( MP_PRIOAUTO,	priority == PR_AUTO );
757
758	m_menu->Enable( MP_MENU_EXTD, canPause );
759
760	bool autosort = thePrefs::AutoSortDownload(false);
761	PopupMenu(m_menu, evt.GetPoint());
762	thePrefs::AutoSortDownload(autosort);
763
764	delete m_menu;
765	m_menu = NULL;
766}
767
768
769void CDownloadListCtrl::OnMouseMiddleClick(wxListEvent& evt)
770{
771	// Check if clicked item is selected. If not, unselect all and select it.
772	long index = CheckSelection(evt);
773	if (index >= 0) {
774		ShowFileDetailDialog(index);
775	}
776}
777
778
779void CDownloadListCtrl::ShowFileDetailDialog(long index)
780{
781	// Make list of part files in control
782	std::vector<CPartFile *> files;
783	int nrItems = GetItemCount();
784	files.reserve(nrItems);
785	for (int i = 0; i < nrItems; i++) {
786		files.push_back(((FileCtrlItem_Struct*)GetItemData(i))->GetFile());
787	}
788	bool autosort = thePrefs::AutoSortDownload(false);
789	CFileDetailDialog(this, files, index).ShowModal();
790	thePrefs::AutoSortDownload(autosort);
791}
792
793
794void CDownloadListCtrl::OnKeyPressed( wxKeyEvent& event )
795{
796	// Check if delete was pressed
797	switch (event.GetKeyCode()) {
798		case WXK_NUMPAD_DELETE:
799		case WXK_DELETE: {
800			wxCommandEvent evt;
801			OnCancelFile( evt );
802			break;
803		}
804		case WXK_F2: {
805			ItemList files = ::GetSelectedItems( this );
806			if (files.size() == 1) {
807				CPartFile* file = files.front()->GetFile();
808
809				// Currently renaming of completed files causes problem with kad
810				if (file->IsPartFile()) {
811					wxString strNewName = ::wxGetTextFromUser(
812						_("Enter new name for this file:"),
813						_("File rename"), file->GetFileName().GetPrintable());
814
815					CPath newName = CPath(strNewName);
816					if (newName.IsOk() && (newName != file->GetFileName())) {
817						theApp->sharedfiles->RenameFile(file, newName);
818					}
819				}
820			}
821			break;
822		}
823		default:
824			event.Skip();
825	}
826}
827
828
829void CDownloadListCtrl::OnDrawItem(
830	int item, wxDC* dc, const wxRect& rect, const wxRect& rectHL, bool highlighted)
831{
832	// Don't do any drawing if there's nobody to see it.
833	if ( !theApp->amuledlg->IsDialogVisible( CamuleDlg::DT_TRANSFER_WND ) ) {
834		return;
835	}
836
837	FileCtrlItem_Struct* content = (FileCtrlItem_Struct *)GetItemData(item);
838
839	// Define text-color and background
840	// and the border of the drawn area
841	if (highlighted) {
842		CMuleColour colour;
843		if (GetFocus()) {
844			dc->SetBackground(m_hilightBrush);
845			colour = m_hilightBrush.GetColour();
846		} else {
847			dc->SetBackground(m_hilightUnfocusBrush);
848			colour = m_hilightUnfocusBrush.GetColour();
849		}
850		dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
851		dc->SetPen( colour.Blend(65).GetPen() );
852	} else {
853		dc->SetBackground(*(wxTheBrushList->FindOrCreateBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX), wxSOLID)));
854		dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
855		dc->SetPen(*wxTRANSPARENT_PEN);
856	}
857	dc->SetBrush( dc->GetBackground() );
858
859	dc->DrawRectangle( rectHL.x, rectHL.y, rectHL.width, rectHL.height );
860
861	dc->SetPen(*wxTRANSPARENT_PEN);
862
863	if (!highlighted || !GetFocus() ) {
864		// If we have category, override textforeground with what category tells us.
865		CPartFile *file = content->GetFile();
866		if ( file->GetCategory() ) {
867			dc->SetTextForeground(CMuleColour(theApp->glob_prefs->GetCatColor(file->GetCategory())) );
868		}
869	}
870
871	// Various constant values we use
872	const int iTextOffset = ( rect.GetHeight() - dc->GetCharHeight() ) / 2;
873	const int iOffset = 4;
874
875	wxRect cur_rec( iOffset, rect.y, 0, rect.height );
876	for (int i = 0; i < GetColumnCount(); i++) {
877		wxListItem listitem;
878		GetColumn(i, listitem);
879
880		if (listitem.GetWidth() > 2*iOffset) {
881			cur_rec.width = listitem.GetWidth() - 2*iOffset;
882
883			// Make a copy of the current rectangle so we can apply specific tweaks
884			wxRect target_rec = cur_rec;
885			if ( i == ColumnProgress ) {
886				// Double the offset to make room for the cirle-marker
887				target_rec.x += iOffset;
888				target_rec.width -= iOffset;
889			} else {
890				// will ensure that text is about in the middle ;)
891				target_rec.y += iTextOffset;
892			}
893
894			// Draw the item
895			DrawFileItem(dc, i, target_rec, content);
896		}
897
898		// Increment to the next column
899		cur_rec.x += listitem.GetWidth();
900	}
901}
902
903
904void CDownloadListCtrl::DrawFileItem( wxDC* dc, int nColumn, const wxRect& rect, FileCtrlItem_Struct* item ) const
905{
906	wxDCClipper clipper( *dc, rect.GetX(), rect.GetY(), rect.GetWidth(), rect.GetHeight() );
907
908	const CPartFile* file = item->GetFile();
909
910	// Used to contain the contenst of cells that dont need any fancy drawing, just text.
911	wxString text;
912
913	switch (nColumn) {
914		// Part Number
915		case ColumnPart: {
916			if (file->IsPartFile() && !file->IsCompleted()) {
917			  text = CFormat(wxT("%03d")) % file->GetPartMetNumber();
918			}
919			break;
920		}
921		// Filename
922		case ColumnFileName: {
923			wxString filename = file->GetFileName().GetPrintable();
924
925			if (file->HasRating() || file->HasComment()) {
926				int image = Client_CommentOnly_Smiley;
927				if (file->HasRating()) {
928					image = Client_InvalidRating_Smiley + file->UserRating() - 1;
929				}
930
931				wxASSERT(image >= Client_InvalidRating_Smiley);
932				wxASSERT(image <= Client_CommentOnly_Smiley);
933
934				int imgWidth = 16;
935
936				// it's already centered by OnDrawItem() ...
937				m_ImageList.Draw(image, *dc, rect.GetX(), rect.GetY() - 1,
938					wxIMAGELIST_DRAW_TRANSPARENT);
939				dc->DrawText(filename, rect.GetX() + imgWidth + 4, rect.GetY());
940			} else {
941				dc->DrawText(filename, rect.GetX(), rect.GetY());
942			}
943			break;
944		}
945
946		// Filesize
947		case ColumnSize:
948			text = CastItoXBytes( file->GetFileSize() );
949			break;
950
951		// Transferred
952		case ColumnTransferred:
953			text = CastItoXBytes( file->GetTransferred() );
954			break;
955
956		// Completed
957		case ColumnCompleted:
958			text = CastItoXBytes( file->GetCompletedSize() );
959			break;
960
961		// Speed
962		case ColumnSpeed:
963			if ( file->GetTransferingSrcCount() ) {
964				text = CFormat(_("%.1f kB/s")) % file->GetKBpsDown();
965			}
966			break;
967
968		// Progress
969		case ColumnProgress:{
970			if (thePrefs::ShowProgBar()) {
971				int iWidth  = rect.GetWidth() - 2;
972				int iHeight = rect.GetHeight() - 2;
973
974				// DO NOT DRAW IT ALL THE TIME
975				uint32 dwTicks = GetTickCount();
976
977				wxMemoryDC cdcStatus;
978
979				if ( item->dwUpdated < dwTicks || !item->status || iWidth != item->status->GetWidth() ) {
980					if ( item->status == NULL) {
981						item->status = new wxBitmap(iWidth, iHeight);
982					} else if ( item->status->GetWidth() != iWidth ) {
983						// Only recreate if the size has changed
984						item->status->Create(iWidth, iHeight);
985					}
986
987					cdcStatus.SelectObject( *item->status );
988
989					if ( thePrefs::UseFlatBar() ) {
990						DrawFileStatusBar( file, &cdcStatus,
991							wxRect(0, 0, iWidth, iHeight), true);
992					} else {
993						DrawFileStatusBar( file, &cdcStatus,
994							wxRect(1, 1, iWidth - 2, iHeight - 2), false);
995
996						// Draw black border
997						cdcStatus.SetPen( *wxBLACK_PEN );
998						cdcStatus.SetBrush( *wxTRANSPARENT_BRUSH );
999						cdcStatus.DrawRectangle( 0, 0, iWidth, iHeight );
1000					}
1001
1002					item->dwUpdated = dwTicks + 5000; // Plus five seconds
1003				} else {
1004					cdcStatus.SelectObject( *item->status );
1005				}
1006
1007				dc->Blit( rect.GetX(), rect.GetY() + 1, iWidth, iHeight, &cdcStatus, 0, 0);
1008
1009				if (thePrefs::ShowPercent()) {
1010					// Percentage of completing
1011					// We strip anything below the first decimal point,
1012					// to avoid Format doing roundings
1013					float percent = floor( file->GetPercentCompleted() * 10.0f ) / 10.0f;
1014
1015					wxString buffer = CFormat(wxT("%.1f%%")) % percent;
1016					int middlex = (2*rect.GetX() + rect.GetWidth()) >> 1;
1017					int middley = (2*rect.GetY() + rect.GetHeight()) >> 1;
1018
1019					wxCoord textwidth, textheight;
1020
1021					dc->GetTextExtent(buffer, &textwidth, &textheight);
1022					wxColour AktColor = dc->GetTextForeground();
1023					if (thePrefs::ShowProgBar()) {
1024						dc->SetTextForeground(*wxWHITE);
1025					} else {
1026						dc->SetTextForeground(*wxBLACK);
1027					}
1028					dc->DrawText(buffer, middlex - (textwidth >> 1), middley - (textheight >> 1));
1029					dc->SetTextForeground(AktColor);
1030				}
1031			}
1032
1033			break;
1034		}
1035
1036		// Sources
1037		case ColumnSources:	{
1038			uint16 sc = file->GetSourceCount();
1039			uint16 ncsc = file->GetNotCurrentSourcesCount();
1040			if ( ncsc ) {
1041				text = CFormat(wxT("%i/%i")) % (sc - ncsc) % sc;
1042			} else {
1043				text = CFormat(wxT("%i")) % sc;
1044			}
1045
1046			if ( file->GetSrcA4AFCount() ) {
1047				text += CFormat(wxT("+%i")) % file->GetSrcA4AFCount();
1048			}
1049
1050			if ( file->GetTransferingSrcCount() ) {
1051				text += CFormat(wxT(" (%i)")) % file->GetTransferingSrcCount();
1052			}
1053
1054			break;
1055		}
1056
1057		// Priority
1058		case ColumnPriority:
1059			text = PriorityToStr( file->GetDownPriority(), file->IsAutoDownPriority() );
1060			break;
1061
1062		// File-status
1063		case ColumnStatus:
1064			text = file->getPartfileStatus();
1065			break;
1066
1067		// Remaining
1068		case ColumnTimeRemaining: {
1069			if ((file->GetStatus() != PS_COMPLETING) && file->IsPartFile()) {
1070				uint64 remainSize = file->GetFileSize() - file->GetCompletedSize();
1071				sint32 remainTime = file->getTimeRemaining();
1072
1073				if (remainTime >= 0) {
1074					text = CastSecondsToHM(remainTime);
1075				} else {
1076					text = _("Unknown");
1077				}
1078
1079				text += wxT(" (") + CastItoXBytes(remainSize) + wxT(")");
1080			}
1081			break;
1082		}
1083
1084		// Last seen completed
1085		case ColumnLastSeenComplete: {
1086			if ( file->lastseencomplete ) {
1087				text = wxDateTime( file->lastseencomplete ).Format( _("%y/%m/%d %H:%M:%S") );
1088			} else {
1089				text = _("Unknown");
1090			}
1091			break;
1092		}
1093
1094		// Last received
1095		case ColumnLastReception: {
1096			const time_t lastReceived = file->GetLastChangeDatetime();
1097			if (lastReceived) {
1098				text = wxDateTime(lastReceived).Format( _("%y/%m/%d %H:%M:%S") );
1099			} else {
1100				text = _("Unknown");
1101			}
1102		}
1103	}
1104
1105	if ( !text.IsEmpty() ) {
1106		dc->DrawText( text, rect.GetX(), rect.GetY() );
1107	}
1108}
1109
1110wxString CDownloadListCtrl::GetTTSText(unsigned item) const
1111{
1112	return ((FileCtrlItem_Struct*)GetItemData(item))->GetFile()->GetFileName().GetPrintable();
1113}
1114
1115
1116int CDownloadListCtrl::SortProc(wxUIntPtr param1, wxUIntPtr param2, long sortData)
1117{
1118	FileCtrlItem_Struct* item1 = (FileCtrlItem_Struct*)param1;
1119	FileCtrlItem_Struct* item2 = (FileCtrlItem_Struct*)param2;
1120
1121	int sortMod = (sortData & CMuleListCtrl::SORT_DES) ? -1 : 1;
1122	sortData &= CMuleListCtrl::COLUMN_MASK;
1123
1124	// We modify the result so that it matches with ascending or decending
1125	return sortMod * Compare( item1->GetFile(), item2->GetFile(), sortData);
1126}
1127
1128
1129int CDownloadListCtrl::Compare( const CPartFile* file1, const CPartFile* file2, long lParamSort)
1130{
1131	int result = 0;
1132
1133	switch (lParamSort) {
1134	// Sort by part number
1135	case ColumnPart:
1136		result = CmpAny(
1137			file1->GetPartMetNumber(),
1138			file2->GetPartMetNumber() );
1139		break;
1140
1141	// Sort by filename
1142	case ColumnFileName:
1143		result = CmpAny(
1144			file1->GetFileName(),
1145			file2->GetFileName() );
1146		break;
1147
1148	// Sort by size
1149	case ColumnSize:
1150		result = CmpAny(
1151			file1->GetFileSize(),
1152			file2->GetFileSize() );
1153		break;
1154
1155	// Sort by transferred
1156	case ColumnTransferred:
1157		result = CmpAny(
1158			file1->GetTransferred(),
1159			file2->GetTransferred() );
1160		break;
1161
1162	// Sort by completed
1163	case ColumnCompleted:
1164		result = CmpAny(
1165			file1->GetCompletedSize(),
1166			file2->GetCompletedSize() );
1167		break;
1168
1169	// Sort by speed
1170	case ColumnSpeed:
1171		result = CmpAny(
1172			file1->GetKBpsDown() * 1024,
1173			file2->GetKBpsDown() * 1024 );
1174		break;
1175
1176	// Sort by percentage completed
1177	case ColumnProgress:
1178		result = CmpAny(
1179			file1->GetPercentCompleted(),
1180			file2->GetPercentCompleted() );
1181		break;
1182
1183	// Sort by number of sources
1184	case ColumnSources:
1185		result = CmpAny(
1186			file1->GetSourceCount(),
1187			file2->GetSourceCount() );
1188		break;
1189
1190	// Sort by priority
1191	case ColumnPriority:
1192		result = CmpAny(
1193			file1->GetDownPriority(),
1194			file2->GetDownPriority() );
1195		break;
1196
1197	// Sort by status
1198	case ColumnStatus:
1199		result = CmpAny(
1200			file1->getPartfileStatusRang(),
1201			file2->getPartfileStatusRang() );
1202		break;
1203
1204	// Sort by remaining time
1205	case ColumnTimeRemaining:
1206		if (file1->getTimeRemaining() == -1) {
1207			if (file2->getTimeRemaining() == -1) {
1208				result = 0;
1209			} else {
1210				result = 1;
1211			}
1212		} else {
1213			if (file2->getTimeRemaining() == -1) {
1214				result = -1;
1215			} else {
1216				result = CmpAny(
1217					file1->getTimeRemaining(),
1218					file2->getTimeRemaining() );
1219			}
1220		}
1221		break;
1222
1223	// Sort by last seen complete
1224	case ColumnLastSeenComplete:
1225		result = CmpAny(
1226			file1->lastseencomplete,
1227			file2->lastseencomplete );
1228		break;
1229
1230	// Sort by last reception
1231	case ColumnLastReception:
1232		result = CmpAny(
1233			file1->GetLastChangeDatetime(),
1234			file2->GetLastChangeDatetime() );
1235		break;
1236	}
1237
1238	return result;
1239}
1240
1241void CDownloadListCtrl::ClearCompleted()
1242{
1243	CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->Enable(false);
1244
1245	// Search for completed files
1246	ListOfUInts32 toClear;
1247	for ( ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); ) {
1248		FileCtrlItem_Struct* item = it->second; ++it;
1249
1250		CPartFile* file = item->GetFile();
1251
1252		if (file->IsCompleted() && file->CheckShowItemInGivenCat(m_category)) {
1253			toClear.push_back(file->ECID());
1254		}
1255	}
1256	if (!toClear.empty()) {
1257		theApp->downloadqueue->ClearCompleted(toClear);
1258	}
1259}
1260
1261
1262void CDownloadListCtrl::ShowFilesCount( int diff )
1263{
1264	m_filecount += diff;
1265
1266	wxStaticText* label = CastByName( wxT("downloadsLabel"), GetParent(), wxStaticText );
1267
1268	label->SetLabel(CFormat(_("Downloads (%i)")) % m_filecount);
1269	label->GetParent()->Layout();
1270}
1271
1272
1273static const CMuleColour crHave(104, 104, 104);
1274static const CMuleColour crFlatHave(0, 0, 0);
1275
1276static const CMuleColour crPending(255, 208, 0);
1277static const CMuleColour crFlatPending(255, 255, 100);
1278
1279static const CMuleColour crProgress(0, 224, 0);
1280static const CMuleColour crFlatProgress(0, 150, 0);
1281
1282static const CMuleColour crMissing(255, 0, 0);
1283
1284void CDownloadListCtrl::DrawFileStatusBar(
1285	const CPartFile* file, wxDC* dc, const wxRect& rect, bool bFlat ) const
1286{
1287	static CBarShader s_ChunkBar(16);
1288
1289	s_ChunkBar.SetHeight(rect.height);
1290	s_ChunkBar.SetWidth(rect.width);
1291	s_ChunkBar.SetFileSize( file->GetFileSize() );
1292	s_ChunkBar.Set3dDepth( thePrefs::Get3DDepth() );
1293
1294	if ( file->IsCompleted() || file->GetStatus() == PS_COMPLETING ) {
1295		s_ChunkBar.Fill( bFlat ? crFlatProgress : crProgress );
1296		s_ChunkBar.Draw(dc, rect.x, rect.y, bFlat);
1297		return;
1298	}
1299
1300	// Part availability ( of missing parts )
1301	const CGapList& gaplist = file->GetGapList();
1302	CGapList::const_iterator it = gaplist.begin();
1303	uint64 lastGapEnd = 0;
1304	CMuleColour colour;
1305
1306	for (; it != gaplist.end(); ++it) {
1307
1308		// Start position
1309		uint32 start = ( it.start() / PARTSIZE );
1310		// fill the Have-Part (between this gap and the last)
1311		if (it.start()) {
1312		  s_ChunkBar.FillRange(lastGapEnd + 1, it.start() - 1,  bFlat ? crFlatHave : crHave);
1313		}
1314		lastGapEnd = it.end();
1315		// End position
1316		uint32 end   = ( it.end() / PARTSIZE ) + 1;
1317
1318		// Avoid going past the filesize. Dunno if this can happen, but the old code did check.
1319		if ( end > file->GetPartCount() ) {
1320			end = file->GetPartCount();
1321		}
1322
1323		// Place each gap, one PART at a time
1324		for ( uint64 i = start; i < end; ++i ) {
1325			if ( i < file->m_SrcpartFrequency.size() && file->m_SrcpartFrequency[i]) {
1326				int blue = 210 - ( 22 * ( file->m_SrcpartFrequency[i] - 1 ) );
1327				colour.Set(0, ( blue < 0 ? 0 : blue ), 255 );
1328			} else {
1329				colour = crMissing;
1330			}
1331
1332			if ( file->IsStopped() ) {
1333				colour.Blend(50);
1334			}
1335
1336			uint64 gap_begin = ( i == start   ? it.start() : PARTSIZE * i );
1337			uint64 gap_end   = ( i == end - 1 ? it.end()   : PARTSIZE * ( i + 1 ) - 1 );
1338
1339			s_ChunkBar.FillRange( gap_begin, gap_end,  colour);
1340		}
1341	}
1342
1343	// fill the last Have-Part (between this gap and the last)
1344	s_ChunkBar.FillRange(lastGapEnd + 1, file->GetFileSize() - 1,  bFlat ? crFlatHave : crHave);
1345
1346	// Pending parts
1347	const CPartFile::CReqBlockPtrList& requestedblocks_list = file->GetRequestedBlockList();
1348	CPartFile::CReqBlockPtrList::const_iterator it2 = requestedblocks_list.begin();
1349	// adjacing pending parts must be joined to avoid bright lines between them
1350	uint64 lastStartOffset = 0;
1351	uint64 lastEndOffset = 0;
1352
1353	colour = bFlat ? crFlatPending : crPending;
1354
1355	if ( file->IsStopped() ) {
1356		colour.Blend(50);
1357	}
1358
1359	for (; it2 != requestedblocks_list.end(); ++it2) {
1360
1361		if ((*it2)->StartOffset > lastEndOffset + 1) {
1362			// not adjacing, draw last block
1363			s_ChunkBar.FillRange(lastStartOffset, lastEndOffset, colour);
1364			lastStartOffset = (*it2)->StartOffset;
1365			lastEndOffset   = (*it2)->EndOffset;
1366		} else {
1367			// adjacing, grow block
1368			lastEndOffset   = (*it2)->EndOffset;
1369		}
1370	}
1371
1372	s_ChunkBar.FillRange(lastStartOffset, lastEndOffset, colour);
1373
1374
1375	// Draw the progress-bar
1376	s_ChunkBar.Draw( dc, rect.x, rect.y, bFlat );
1377
1378
1379	// Green progressbar width
1380	int width = (int)(( (float)rect.width / (float)file->GetFileSize() ) *
1381			file->GetCompletedSize() );
1382
1383	if ( bFlat ) {
1384		dc->SetBrush( crFlatProgress.GetBrush() );
1385
1386		dc->DrawRectangle( rect.x, rect.y, width, 3 );
1387	} else {
1388		// Draw the two black lines for 3d-effect
1389		dc->SetPen( *wxBLACK_PEN );
1390		dc->DrawLine( rect.x, rect.y + 0, rect.x + width, rect.y + 0 );
1391		dc->DrawLine( rect.x, rect.y + 2, rect.x + width, rect.y + 2 );
1392
1393		// Draw the green line
1394		dc->SetPen( *(wxThePenList->FindOrCreatePen( crProgress , 1, wxSOLID ) ));
1395		dc->DrawLine( rect.x, rect.y + 1, rect.x + width, rect.y + 1 );
1396	}
1397}
1398
1399#ifdef __WXMSW__
1400#	define QUOTE	wxT("\"")
1401#else
1402#	define QUOTE	wxT("\'")
1403#endif
1404
1405void CDownloadListCtrl::PreviewFile(CPartFile* file)
1406{
1407	wxString command;
1408	// If no player set in preferences, use mplayer.
1409	// And please, do a warning also :P
1410	if (thePrefs::GetVideoPlayer().IsEmpty()) {
1411		wxMessageBox(_(
1412			"To prevent this warning to show up in every preview,\nset your preferred video player in preferences (default is mplayer)."),
1413			_("File preview"), wxOK, this);
1414		// Since newer versions for some reason mplayer does not automatically
1415		// select video output device and needs a parameter, go figure...
1416		command = wxT("xterm -T \"aMule Preview\" -iconic -e mplayer ") QUOTE wxT("$file") QUOTE;
1417	} else {
1418		command = thePrefs::GetVideoPlayer();
1419	}
1420
1421	wxString partFile;	// File name with full path
1422	wxString partName;	// File name only, without path
1423
1424	// Check if we are (pre)viewing a completed file or not
1425	if (!file->IsCompleted()) {
1426		// Remove the .met and see if out video player specifiation uses the magic string
1427		partName = file->GetPartMetFileName().RemoveExt().GetRaw();
1428		partFile = thePrefs::GetTempDir().JoinPaths(file->GetPartMetFileName().RemoveExt()).GetRaw();
1429	} else {
1430		// This is a complete file
1431		// FIXME: This is probably not going to work if the filenames are mangled ...
1432		partName = file->GetFileName().GetRaw();
1433		partFile = file->GetFullName().GetRaw();
1434	}
1435
1436	// Compatibility with old behaviour
1437	if (!command.Replace(wxT("$file"), wxT("%PARTFILE"))) {
1438		if ((command.Find(wxT("%PARTFILE")) == wxNOT_FOUND) && (command.Find(wxT("%PARTNAME")) == wxNOT_FOUND)) {
1439			// No magic string, so we just append the filename to the player command
1440			// Need to use quotes in case filename contains spaces
1441			command << wxT(" ") << QUOTE << wxT("%PARTFILE") << QUOTE;
1442		}
1443	}
1444
1445#ifndef __WXMSW__
1446	// We have to escape quote characters in the file name, otherwise arbitrary
1447	// options could be passed to the player.
1448	partFile.Replace(QUOTE, wxT("\\") QUOTE);
1449	partName.Replace(QUOTE, wxT("\\") QUOTE);
1450#endif
1451
1452	command.Replace(wxT("%PARTFILE"), partFile);
1453	command.Replace(wxT("%PARTNAME"), partName);
1454
1455	// We can't use wxShell here, it blocks the app
1456	CTerminationProcess *p = new CTerminationProcess(command);
1457	int ret = wxExecute(command, wxEXEC_ASYNC, p);
1458	bool ok = ret > 0;
1459	if (!ok) {
1460		delete p;
1461		AddLogLineC(CFormat( _("ERROR: Failed to execute external media-player! Command: `%s'") ) %
1462			command );
1463	}
1464}
1465// File_checked_for_headers
1466