Upstream tarball 9882
[amule.git] / src / DownloadListCtrl.cpp
blob8fc747c3e0a9bf23f2da032db2635e4d82275e59
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 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.
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.
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
26 #include <protocol/ed2k/ClientSoftware.h>
27 #include <common/MenuIDs.h>
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 "ClientDetailDialog.h" // Needed for CClientDetailDialog
34 #include "ChatWnd.h" // Needed for CChatWnd
35 #include "CommentDialogLst.h" // Needed for CCommentDialogLst
36 #include "DownloadListCtrl.h" // Interface declarations
37 #include "DataToText.h" // Needed for PriorityToStr
38 #include "FileDetailDialog.h" // Needed for CFileDetailDialog
39 #include "GuiEvents.h" // Needed for CoreNotify_*
40 #ifdef ENABLE_IP2COUNTRY
41 #include "IP2Country.h" // Needed for IP2Country
42 #endif
43 #include "Logger.h"
44 #include "muuli_wdr.h" // Needed for ID_DLOADLIST
45 #include "PartFile.h" // Needed for CPartFile
46 #include "Preferences.h"
47 #include "SharedFileList.h" // Needed for CSharedFileList
48 #include "TerminationProcess.h" // Needed for CTerminationProcess
49 #include "updownclient.h" // Needed for CUpDownClient
52 class CPartFile;
55 struct CtrlItem_Struct
57 CtrlItem_Struct()
58 : dwUpdated(0),
59 status(NULL),
60 m_owner(NULL),
61 m_fileValue(NULL),
62 m_sourceValue(NULL),
63 m_type(FILE_TYPE)
64 { }
66 ~CtrlItem_Struct() {
67 delete status;
70 DownloadItemType GetType() const {
71 return m_type;
74 CPartFile* GetOwner() const {
75 return m_owner;
78 CPartFile* GetFile() const {
79 return m_fileValue;
82 CUpDownClient* GetSource() const {
83 return m_sourceValue;
86 void SetContents(CPartFile* file) {
87 m_owner = NULL;
88 m_fileValue = file;
89 m_sourceValue = NULL;
90 m_owner = NULL;
91 m_type = FILE_TYPE;
94 void SetContents(CPartFile* owner, CUpDownClient* source, DownloadItemType type) {
95 wxCHECK_RET(type != FILE_TYPE, wxT("Invalid type, not a source"));
97 m_owner = owner;
98 m_fileValue = NULL;
99 m_sourceValue = source;
100 m_type = type;
104 uint32 dwUpdated;
105 wxBitmap* status;
107 private:
108 CPartFile* m_owner;
109 CPartFile* m_fileValue;
110 CUpDownClient* m_sourceValue;
111 DownloadItemType m_type;
116 #define m_ImageList theApp->amuledlg->m_imagelist
119 BEGIN_EVENT_TABLE(CDownloadListCtrl, CMuleListCtrl)
120 EVT_LIST_ITEM_ACTIVATED(ID_DLOADLIST, CDownloadListCtrl::OnItemActivated)
121 EVT_LIST_ITEM_RIGHT_CLICK(ID_DLOADLIST, CDownloadListCtrl::OnMouseRightClick)
122 EVT_LIST_ITEM_MIDDLE_CLICK(ID_DLOADLIST, CDownloadListCtrl::OnMouseMiddleClick)
124 EVT_CHAR( CDownloadListCtrl::OnKeyPressed )
126 EVT_MENU( MP_CANCEL, CDownloadListCtrl::OnCancelFile )
128 EVT_MENU( MP_PAUSE, CDownloadListCtrl::OnSetStatus )
129 EVT_MENU( MP_STOP, CDownloadListCtrl::OnSetStatus )
130 EVT_MENU( MP_RESUME, CDownloadListCtrl::OnSetStatus )
132 EVT_MENU( MP_PRIOLOW, CDownloadListCtrl::OnSetPriority )
133 EVT_MENU( MP_PRIONORMAL, CDownloadListCtrl::OnSetPriority )
134 EVT_MENU( MP_PRIOHIGH, CDownloadListCtrl::OnSetPriority )
135 EVT_MENU( MP_PRIOAUTO, CDownloadListCtrl::OnSetPriority )
137 EVT_MENU( MP_SWAP_A4AF_TO_THIS, CDownloadListCtrl::OnSwapSources )
138 EVT_MENU( MP_SWAP_A4AF_TO_THIS_AUTO, CDownloadListCtrl::OnSwapSources )
139 EVT_MENU( MP_SWAP_A4AF_TO_ANY_OTHER, CDownloadListCtrl::OnSwapSources )
141 EVT_MENU_RANGE( MP_ASSIGNCAT, MP_ASSIGNCAT + 99, CDownloadListCtrl::OnSetCategory )
143 EVT_MENU( MP_CLEARCOMPLETED, CDownloadListCtrl::OnClearCompleted )
145 EVT_MENU( MP_GETMAGNETLINK, CDownloadListCtrl::OnGetLink )
146 EVT_MENU( MP_GETED2KLINK, CDownloadListCtrl::OnGetLink )
148 EVT_MENU( MP_METINFO, CDownloadListCtrl::OnViewFileInfo )
149 EVT_MENU( MP_VIEW, CDownloadListCtrl::OnPreviewFile )
150 EVT_MENU( MP_VIEWFILECOMMENTS, CDownloadListCtrl::OnViewFileComments )
152 EVT_MENU( MP_WS, CDownloadListCtrl::OnGetFeedback )
153 EVT_MENU( MP_RAZORSTATS, CDownloadListCtrl::OnGetRazorStats )
155 EVT_MENU( MP_CHANGE2FILE, CDownloadListCtrl::OnSwapSource )
156 EVT_MENU( MP_SHOWLIST, CDownloadListCtrl::OnViewFiles )
157 EVT_MENU( MP_ADDFRIEND, CDownloadListCtrl::OnAddFriend )
158 EVT_MENU( MP_SENDMESSAGE, CDownloadListCtrl::OnSendMessage )
159 EVT_MENU( MP_DETAIL, CDownloadListCtrl::OnViewClientInfo )
160 END_EVENT_TABLE()
164 //! This listtype is used when gathering the selected items.
165 typedef std::list<CtrlItem_Struct*> ItemList;
169 CDownloadListCtrl::CDownloadListCtrl(
170 wxWindow *parent, wxWindowID winid, const wxPoint& pos, const wxSize& size,
171 long style, const wxValidator& validator, const wxString& name )
173 CMuleListCtrl( parent, winid, pos, size, style | wxLC_OWNERDRAW, validator, name )
175 // Setting the sorter function.
176 SetSortFunc( SortProc );
178 // Set the table-name (for loading and saving preferences).
179 SetTableName( wxT("Download") );
181 m_menu = NULL;
183 m_hilightBrush = CMuleColour(wxSYS_COLOUR_HIGHLIGHT).Blend(125).GetBrush();
185 m_hilightUnfocusBrush = CMuleColour(wxSYS_COLOUR_BTNSHADOW).Blend(125).GetBrush();
187 InsertColumn( ColumnPart, _("Part"), wxLIST_FORMAT_LEFT, 30, wxT("a") );
188 InsertColumn( ColumnFileName, _("File Name"), wxLIST_FORMAT_LEFT, 260, wxT("N") );
189 InsertColumn( ColumnSize, _("Size"), wxLIST_FORMAT_LEFT, 60, wxT("Z") );
190 InsertColumn( ColumnTransferred, _("Transferred"), wxLIST_FORMAT_LEFT, 65, wxT("T") );
191 InsertColumn( ColumnCompleted, _("Completed"), wxLIST_FORMAT_LEFT, 65, wxT("C") );
192 InsertColumn( ColumnSpeed, _("Speed"), wxLIST_FORMAT_LEFT, 65, wxT("S") );
193 InsertColumn( ColumnProgress, _("Progress"), wxLIST_FORMAT_LEFT, 170, wxT("P") );
194 InsertColumn( ColumnSources, _("Sources"), wxLIST_FORMAT_LEFT, 50, wxT("u") );
195 InsertColumn( ColumnPriority, _("Priority"), wxLIST_FORMAT_LEFT, 55, wxT("p") );
196 InsertColumn( ColumnStatus, _("Status"), wxLIST_FORMAT_LEFT, 70, wxT("s") );
197 InsertColumn( ColumnTimeRemaining, _("Time Remaining"), wxLIST_FORMAT_LEFT, 110, wxT("r") );
198 InsertColumn( ColumnLastSeenComplete, _("Last Seen Complete"), wxLIST_FORMAT_LEFT, 220, wxT("c") );
199 InsertColumn( ColumnLastReception, _("Last Reception"), wxLIST_FORMAT_LEFT, 220, wxT("R") );
201 m_category = 0;
202 m_completedFiles = 0;
203 m_filecount = 0;
204 LoadSettings();
207 // This is the order the columns had before extendable list-control settings save/load code was introduced.
208 // Don't touch when inserting new columns!
209 wxString CDownloadListCtrl::GetOldColumnOrder() const
211 return wxT("N,Z,T,C,S,P,u,p,s,r,c,R");
214 CDownloadListCtrl::~CDownloadListCtrl()
216 while ( !m_ListItems.empty() ) {
217 delete m_ListItems.begin()->second;
218 m_ListItems.erase( m_ListItems.begin() );
223 void CDownloadListCtrl::AddFile( CPartFile* file )
225 wxASSERT( file );
227 // Avoid duplicate entries of files
228 if ( m_ListItems.find( file ) == m_ListItems.end() ) {
229 CtrlItem_Struct* newitem = new CtrlItem_Struct;
230 newitem->SetContents(file);
232 m_ListItems.insert( ListItemsPair( file, newitem ) );
234 // Check if the new file is visible in the current category
235 if ( file->CheckShowItemInGivenCat( m_category ) ) {
236 ShowFile( file, true );
237 SortList();
243 void CDownloadListCtrl::AddSource(CPartFile* owner, CUpDownClient* source, DownloadItemType type)
245 wxCHECK_RET(owner, wxT("NULL owner in CDownloadListCtrl::AddSource"));
246 wxCHECK_RET(source, wxT("NULL source in CDownloadListCtrl::AddSource"));
247 wxCHECK_RET(type != FILE_TYPE, wxT("Invalid type, not a source"));
249 // Update the other instances of this source
250 bool bFound = false;
251 ListIteratorPair rangeIt = m_ListItems.equal_range(source);
252 for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second; ++it ) {
253 CtrlItem_Struct* cur_item = it->second;
255 // Check if this source has been already added to this file => to be sure
256 if ( cur_item->GetOwner() == owner ) {
257 // Update this instance with its new setting
258 cur_item->SetContents(owner, source, type);
259 cur_item->dwUpdated = 0;
260 bFound = true;
261 } else if ( type == AVAILABLE_SOURCE ) {
262 // The state 'Available' is exclusive
263 cur_item->SetContents(cur_item->GetOwner(), source, A4AF_SOURCE);
264 cur_item->dwUpdated = 0;
268 if ( bFound ) {
269 return;
272 if ( owner->ShowSources() ) {
273 CtrlItem_Struct* newitem = new CtrlItem_Struct;
274 newitem->SetContents(owner, source, type);
276 m_ListItems.insert( ListItemsPair(source, newitem) );
278 // Find the owner-object
279 ListItems::iterator it = m_ListItems.find( owner );
281 if ( it != m_ListItems.end() ) {
282 long item = FindItem( -1, reinterpret_cast<wxUIntPtr>(it->second) );
284 if ( item > -1 ) {
285 item = InsertItem( item + 1, wxEmptyString );
287 SetItemPtrData( item, reinterpret_cast<wxUIntPtr>(newitem) );
289 // background.. this should be in a function
290 wxListItem listitem;
291 listitem.m_itemId = item;
293 listitem.SetBackgroundColour( GetBackgroundColour() );
295 SetItem( listitem );
302 void CDownloadListCtrl::RemoveSource( const CUpDownClient* source, const CPartFile* owner )
304 wxASSERT( source );
306 // Retrieve all entries matching the source
307 ListIteratorPair rangeIt = m_ListItems.equal_range(source);
309 for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second; ) {
310 ListItems::iterator tmp = it++;
312 CtrlItem_Struct* item = tmp->second;
313 if ( owner == NULL || owner == item->GetOwner() ) {
314 // Remove it from the m_ListItems
315 m_ListItems.erase( tmp );
317 long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
319 if ( index > -1 ) {
320 DeleteItem( index );
323 delete item;
329 void CDownloadListCtrl::RemoveFile( CPartFile* file )
331 wxASSERT( file );
333 // Ensure that any assosiated sources and list-entries are removed
334 ShowFile( file, false );
336 // Find the assosiated list-item
337 ListItems::iterator it = m_ListItems.find( file );
339 if ( it != m_ListItems.end() ) {
340 delete it->second;
342 m_ListItems.erase( it );
347 void CDownloadListCtrl::UpdateItem(const void* toupdate)
349 // Retrieve all entries matching the source
350 ListIteratorPair rangeIt = m_ListItems.equal_range( toupdate );
352 // Visible lines, default to all because not all platforms
353 // support the GetVisibleLines function
354 long first = 0, last = GetItemCount();
356 #ifndef __WXMSW__
357 // Get visible lines if we need them
358 if ( rangeIt.first != rangeIt.second ) {
359 GetVisibleLines( &first, &last );
361 #endif
363 for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second; ++it ) {
364 CtrlItem_Struct* item = it->second;
366 long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
368 // Determine if the file should be shown in the current category
369 if ( item->GetType() == FILE_TYPE ) {
370 CPartFile* file = item->GetFile();
372 bool show = file->CheckShowItemInGivenCat( m_category );
374 if ( index > -1 ) {
375 if ( show ) {
376 item->dwUpdated = 0;
378 // Only update visible lines
379 if ( index >= first && index <= last) {
380 RefreshItem( index );
382 } else {
383 // Item should no longer be shown in
384 // the current category
385 ShowFile( file, false );
387 } else if ( show ) {
388 // Item has been hidden but new status means
389 // that it should it should be shown in the
390 // current category
391 ShowFile( file, true );
394 if (file->GetStatus() == PS_COMPLETE) {
395 m_completedFiles = true;
397 CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->Enable(true);
399 } else {
400 item->dwUpdated = 0;
402 // Only update visible lines
403 if ( index >= first && index <= last) {
404 RefreshItem( index );
411 void CDownloadListCtrl::ShowFile( CPartFile* file, bool show )
413 wxASSERT( file );
415 ListItems::iterator it = m_ListItems.find( file );
417 if ( it != m_ListItems.end() ) {
418 CtrlItem_Struct* item = it->second;
420 if ( show ) {
421 // Check if the file is already being displayed
422 long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
423 if ( index == -1 ) {
424 long newitem = InsertItem( GetItemCount(), wxEmptyString );
426 SetItemPtrData( newitem, reinterpret_cast<wxUIntPtr>(item) );
428 wxListItem myitem;
429 myitem.m_itemId = newitem;
430 myitem.SetBackgroundColour( GetBackgroundColour() );
432 SetItem(myitem);
434 RefreshItem( newitem );
436 ShowFilesCount( 1 );
438 } else {
439 // Ensure sources are hidden
440 ShowSources( file, false );
442 // Try to find the file and remove it
443 long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
444 if ( index > -1 ) {
445 DeleteItem( index );
446 ShowFilesCount( -1 );
453 void CDownloadListCtrl::ShowSources( CPartFile* file, bool show )
455 // Check if the current state is the same as the new state
456 if ( file->ShowSources() == show ) {
457 return;
460 Freeze();
462 file->SetShowSources( show );
464 if ( show ) {
465 const CPartFile::SourceSet& normSources = file->GetSourceList();
466 const CPartFile::SourceSet& a4afSources = file->GetA4AFList();
468 // Adding normal sources
469 CPartFile::SourceSet::const_iterator it;
470 for ( it = normSources.begin(); it != normSources.end(); ++it ) {
471 switch ((*it)->GetDownloadState()) {
472 case DS_DOWNLOADING:
473 case DS_ONQUEUE:
474 AddSource( file, *it, AVAILABLE_SOURCE );
475 default:
476 // Any other state
477 AddSource( file, *it, UNAVAILABLE_SOURCE );
482 // Adding A4AF sources
483 for ( it = a4afSources.begin(); it != a4afSources.end(); ++it ) {
484 AddSource( file, *it, A4AF_SOURCE );
487 SortList();
488 } else {
489 for ( int i = GetItemCount() - 1; i >= 0; --i ) {
490 CtrlItem_Struct* item = (CtrlItem_Struct*)GetItemData(i);
492 if ( item->GetType() != FILE_TYPE && item->GetOwner() == file ) {
493 // Remove from the grand list, this call doesn't remove the source
494 // from the listctrl, because ShowSources is now false. This also
495 // deletes the item.
496 RemoveSource(item->GetSource(), file);
501 Thaw();
505 void CDownloadListCtrl::ChangeCategory( int newCategory )
507 Freeze();
509 // remove all displayed files with a different cat and show the correct ones
510 for (ListItems::const_iterator it = m_ListItems.begin(); it != m_ListItems.end(); it++) {
511 const CtrlItem_Struct *cur_item = it->second;
513 if ( cur_item->GetType() == FILE_TYPE ) {
514 CPartFile* file = cur_item->GetFile();
516 bool curVisibility = file->CheckShowItemInGivenCat( m_category );
517 bool newVisibility = file->CheckShowItemInGivenCat( newCategory );
519 // Check if the visibility of the file has changed. However, if the
520 // current category is the default (0) category, then we can't use
521 // curVisiblity to see if the visibility has changed but instead
522 // have to let ShowFile() check if the file is or isn't on the list.
523 if ( curVisibility != newVisibility || !newCategory ) {
524 ShowFile( file, newVisibility );
529 Thaw();
531 m_category = newCategory;
535 uint8 CDownloadListCtrl::GetCategory() const
537 return m_category;
544 const int itFILES = 1;
545 const int itSOURCES = 2;
548 * Helper-function: This function is used to gather selected items.
550 * @param list A pointer to the list to gather items from.
551 * @param types The desired types OR'd together.
552 * @return A list containing the selected items of the choosen types.
554 ItemList GetSelectedItems( CDownloadListCtrl* list, int types )
556 ItemList results;
558 long index = list->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
560 while ( index > -1 ) {
561 CtrlItem_Struct* item = (CtrlItem_Struct*)list->GetItemData( index );
563 bool add = false;
564 add |= ( item->GetType() == FILE_TYPE ) && ( types & itFILES );
565 add |= ( item->GetType() != FILE_TYPE ) && ( types & itSOURCES );
567 if ( add ) {
568 results.push_back( item );
571 index = list->GetNextItem( index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
574 return results;
578 void CDownloadListCtrl::OnCancelFile(wxCommandEvent& WXUNUSED(event))
580 ItemList files = ::GetSelectedItems(this, itFILES);
581 if (files.size()) {
582 wxString question;
583 if (files.size() == 1) {
584 question = _("Are you sure that you wish to delete the selected file?");
585 } else {
586 question = _("Are you sure that you wish to delete the selected files?");
588 if (wxMessageBox( question, _("Cancel"), wxICON_QUESTION | wxYES_NO, this) == wxYES) {
589 for (ItemList::iterator it = files.begin(); it != files.end(); ++it) {
590 CPartFile* file = (*it)->GetFile();
591 if (file) {
592 switch (file->GetStatus()) {
593 case PS_WAITINGFORHASH:
594 case PS_HASHING:
595 case PS_COMPLETING:
596 case PS_COMPLETE:
597 break;
598 default:
599 CoreNotify_PartFile_Delete(file);
608 void CDownloadListCtrl::OnSetPriority( wxCommandEvent& event )
610 int priority = 0;
611 switch ( event.GetId() ) {
612 case MP_PRIOLOW: priority = PR_LOW; break;
613 case MP_PRIONORMAL: priority = PR_NORMAL; break;
614 case MP_PRIOHIGH: priority = PR_HIGH; break;
615 case MP_PRIOAUTO: priority = PR_AUTO; break;
616 default:
617 wxASSERT( false );
620 ItemList files = ::GetSelectedItems( this, itFILES );
622 for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
623 CPartFile* file = (*it)->GetFile();
625 if ( priority == PR_AUTO ) {
626 CoreNotify_PartFile_PrioAuto( file, true );
627 } else {
628 CoreNotify_PartFile_PrioAuto( file, false );
630 CoreNotify_PartFile_PrioSet( file, priority, true );
636 void CDownloadListCtrl::OnSwapSources( wxCommandEvent& event )
638 ItemList files = ::GetSelectedItems( this, itFILES );
640 for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
641 CPartFile* file = (*it)->GetFile();
643 switch ( event.GetId() ) {
644 case MP_SWAP_A4AF_TO_THIS:
645 CoreNotify_PartFile_Swap_A4AF( file );
646 break;
648 case MP_SWAP_A4AF_TO_THIS_AUTO:
649 CoreNotify_PartFile_Swap_A4AF_Auto( file );
650 break;
652 case MP_SWAP_A4AF_TO_ANY_OTHER:
653 CoreNotify_PartFile_Swap_A4AF_Others( file );
654 break;
660 void CDownloadListCtrl::OnSetCategory( wxCommandEvent& event )
662 ItemList files = ::GetSelectedItems( this, itFILES );
664 for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
665 CPartFile* file = (*it)->GetFile();
667 CoreNotify_PartFile_SetCat( file, event.GetId() - MP_ASSIGNCAT );
670 ChangeCategory( m_category );
674 void CDownloadListCtrl::OnSetStatus( wxCommandEvent& event )
676 ItemList files = ::GetSelectedItems( this, itFILES );
678 for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
679 CPartFile* file = (*it)->GetFile();
681 switch ( event.GetId() ) {
682 case MP_PAUSE:
683 CoreNotify_PartFile_Pause( file );
684 break;
686 case MP_RESUME:
687 CoreNotify_PartFile_Resume( file );
688 break;
690 case MP_STOP:
691 ShowSources(file, false);
692 CoreNotify_PartFile_Stop( file );
693 break;
699 void CDownloadListCtrl::OnClearCompleted( wxCommandEvent& WXUNUSED(event) )
701 ClearCompleted();
705 void CDownloadListCtrl::OnGetLink(wxCommandEvent& event)
707 ItemList files = ::GetSelectedItems( this, itFILES );
709 wxString URIs;
711 for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
712 CPartFile* file = (*it)->GetFile();
714 if ( event.GetId() == MP_GETED2KLINK ) {
715 URIs += theApp->CreateED2kLink( file ) + wxT("\n");
716 } else {
717 URIs += theApp->CreateMagnetLink( file ) + wxT("\n");
721 if ( !URIs.IsEmpty() ) {
722 theApp->CopyTextToClipboard( URIs.BeforeLast(wxT('\n')) );
727 void CDownloadListCtrl::OnGetFeedback(wxCommandEvent& WXUNUSED(event))
729 wxString feed;
730 ItemList files = ::GetSelectedItems(this, itFILES);
732 for (ItemList::iterator it = files.begin(); it != files.end(); ++it) {
733 if (feed.IsEmpty()) {
734 feed = CFormat(_("Feedback from: %s (%s)\n\n")) % thePrefs::GetUserNick() % GetFullMuleVersion();
735 } else {
736 feed += wxT("\n");
738 feed += (*it)->GetFile()->GetFeedback();
741 if (!feed.IsEmpty()) {
742 theApp->CopyTextToClipboard(feed);
747 void CDownloadListCtrl::OnGetRazorStats( wxCommandEvent& WXUNUSED(event) )
749 ItemList files = ::GetSelectedItems( this, itFILES );
751 if ( files.size() == 1 ) {
752 CPartFile* file = files.front()->GetFile();
754 theApp->amuledlg->LaunchUrl(
755 wxT("http://stats.razorback2.com/ed2khistory?ed2k=") +
756 file->GetFileHash().Encode());
761 void CDownloadListCtrl::OnViewFileInfo( wxCommandEvent& WXUNUSED(event) )
763 ItemList files = ::GetSelectedItems( this, itFILES );
765 if ( files.size() == 1 ) {
766 CPartFile* file = files.front()->GetFile();
768 CFileDetailDialog dialog( this, file );
769 dialog.ShowModal();
774 void CDownloadListCtrl::OnViewFileComments( wxCommandEvent& WXUNUSED(event) )
776 ItemList files = ::GetSelectedItems( this, itFILES );
778 if ( files.size() == 1 ) {
779 CPartFile* file = files.front()->GetFile();
781 CCommentDialogLst dialog( this, file );
782 dialog.ShowModal();
787 void CDownloadListCtrl::OnPreviewFile( wxCommandEvent& WXUNUSED(event) )
789 ItemList files = ::GetSelectedItems( this, itFILES );
791 if ( files.size() == 1 ) {
792 PreviewFile(files.front()->GetFile());
796 void CDownloadListCtrl::OnSwapSource( wxCommandEvent& WXUNUSED(event) )
798 ItemList sources = ::GetSelectedItems( this, itSOURCES );
800 for ( ItemList::iterator it = sources.begin(); it != sources.end(); ++it ) {
801 CPartFile* file = (*it)->GetOwner();
802 CUpDownClient* source = (*it)->GetSource();
804 source->SwapToAnotherFile( true, false, false, file );
809 void CDownloadListCtrl::OnViewFiles( wxCommandEvent& WXUNUSED(event) )
811 ItemList sources = ::GetSelectedItems( this, itSOURCES );
813 if ( sources.size() == 1 ) {
814 CUpDownClient* source = sources.front()->GetSource();
816 source->RequestSharedFileList();
821 void CDownloadListCtrl::OnAddFriend( wxCommandEvent& WXUNUSED(event) )
823 ItemList sources = ::GetSelectedItems( this, itSOURCES );
825 for ( ItemList::iterator it = sources.begin(); it != sources.end(); ++it ) {
826 CUpDownClient* client = (*it)->GetSource();
827 if (client->IsFriend()) {
828 theApp->amuledlg->m_chatwnd->RemoveFriend(client->GetUserHash(), client->GetIP(), client->GetUserPort());
829 } else {
830 theApp->amuledlg->m_chatwnd->AddFriend( client );
836 void CDownloadListCtrl::OnSendMessage( wxCommandEvent& WXUNUSED(event) )
838 ItemList sources = ::GetSelectedItems( this, itSOURCES );
840 if ( sources.size() == 1 ) {
841 CUpDownClient* source = (sources.front())->GetSource();
843 // These values are cached, since calling wxGetTextFromUser will
844 // start an event-loop, in which the client may be deleted.
845 wxString userName = source->GetUserName();
846 uint64 userID = GUI_ID(source->GetIP(), source->GetUserPort());
848 wxString message = ::wxGetTextFromUser(_("Send message to user"),
849 _("Message to send:"));
850 if ( !message.IsEmpty() ) {
851 theApp->amuledlg->m_chatwnd->SendMessage(message, userName, userID);
857 void CDownloadListCtrl::OnViewClientInfo( wxCommandEvent& WXUNUSED(event) )
859 ItemList sources = ::GetSelectedItems( this, itSOURCES );
861 if ( sources.size() == 1 ) {
862 CUpDownClient* source = (sources.front())->GetSource();
864 CClientDetailDialog dialog( this, source );
865 dialog.ShowModal();
870 void CDownloadListCtrl::OnItemActivated( wxListEvent& evt )
872 CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData( evt.GetIndex() );
874 if ( content->GetType() == FILE_TYPE ) {
875 CPartFile* file = content->GetFile();
877 if ((!file->IsPartFile() || file->GetStatus() == PS_COMPLETE) && file->PreviewAvailable()) {
878 PreviewFile( file );
879 } else {
880 ShowSources( file, !file->ShowSources() );
887 void CDownloadListCtrl::OnMouseRightClick(wxListEvent& evt)
889 long index = CheckSelection(evt);
890 if (index < 0) {
891 return;
894 delete m_menu;
895 m_menu = NULL;
897 CtrlItem_Struct* item = (CtrlItem_Struct*)GetItemData( index );
898 if (item->GetType() == FILE_TYPE) {
899 m_menu = new wxMenu( _("Downloads") );
901 wxMenu* priomenu = new wxMenu();
902 priomenu->AppendCheckItem(MP_PRIOLOW, _("Low"));
903 priomenu->AppendCheckItem(MP_PRIONORMAL, _("Normal"));
904 priomenu->AppendCheckItem(MP_PRIOHIGH, _("High"));
905 priomenu->AppendCheckItem(MP_PRIOAUTO, _("Auto"));
907 m_menu->Append(MP_MENU_PRIO, _("Priority"), priomenu);
908 m_menu->Append(MP_CANCEL, _("Cancel"));
909 m_menu->Append(MP_STOP, _("&Stop"));
910 m_menu->Append(MP_PAUSE, _("&Pause"));
911 m_menu->Append(MP_RESUME, _("&Resume"));
912 m_menu->Append(MP_CLEARCOMPLETED, _("C&lear completed"));
913 //-----------------------------------------------------
914 m_menu->AppendSeparator();
915 //-----------------------------------------------------
916 wxMenu* extendedmenu = new wxMenu();
917 extendedmenu->Append(MP_SWAP_A4AF_TO_THIS,
918 _("Swap every A4AF to this file now"));
919 extendedmenu->AppendCheckItem(MP_SWAP_A4AF_TO_THIS_AUTO,
920 _("Swap every A4AF to this file (Auto)"));
921 //-----------------------------------------------------
922 extendedmenu->AppendSeparator();
923 //-----------------------------------------------------
924 extendedmenu->Append(MP_SWAP_A4AF_TO_ANY_OTHER,
925 _("Swap every A4AF to any other file now"));
926 //-----------------------------------------------------
927 m_menu->Append(MP_MENU_EXTD,
928 _("Extended Options"), extendedmenu);
929 //-----------------------------------------------------
930 m_menu->AppendSeparator();
931 //-----------------------------------------------------
932 /* Commented out till RB2 is back
933 m_menu->Append( MP_RAZORSTATS,
934 _("Get Razorback 2's stats for this file"));
935 //-----------------------------------------------------
936 m_menu->AppendSeparator();
937 //-----------------------------------------------------
939 m_menu->Append(MP_VIEW, _("Preview"));
940 m_menu->Append(MP_METINFO, _("Show file &details"));
941 m_menu->Append(MP_VIEWFILECOMMENTS,
942 _("Show all comments"));
943 //-----------------------------------------------------
944 m_menu->AppendSeparator();
945 //-----------------------------------------------------
946 m_menu->Append(MP_GETMAGNETLINK,
947 _("Copy magnet URI to clipboard"));
948 m_menu->Append(MP_GETED2KLINK,
949 _("Copy eD2k &link to clipboard"));
950 m_menu->Append(MP_WS,
951 _("Copy feedback to clipboard"));
952 //-----------------------------------------------------
953 m_menu->AppendSeparator();
954 //-----------------------------------------------------
955 // Add dinamic entries
956 wxMenu *cats = new wxMenu(_("Category"));
957 if (theApp->glob_prefs->GetCatCount() > 1) {
958 for (uint32 i = 0; i < theApp->glob_prefs->GetCatCount(); i++) {
959 if ( i == 0 ) {
960 cats->Append( MP_ASSIGNCAT, _("unassign") );
961 } else {
962 cats->Append( MP_ASSIGNCAT + i,
963 theApp->glob_prefs->GetCategory(i)->title );
967 m_menu->Append(MP_MENU_CATS, _("Assign to category"), cats);
968 m_menu->Enable(MP_MENU_CATS, (theApp->glob_prefs->GetCatCount() > 1) );
970 CPartFile* file = item->GetFile();
971 // then set state
972 bool canStop;
973 bool canPause;
974 bool canCancel;
975 bool fileResumable;
976 if (file->GetStatus(true) != PS_ALLOCATING) {
977 const uint8_t fileStatus = file->GetStatus();
978 canStop =
979 (fileStatus != PS_ERROR) &&
980 (fileStatus != PS_COMPLETE) &&
981 (file->IsStopped() != true);
982 canPause = (file->GetStatus() != PS_PAUSED) && canStop;
983 fileResumable =
984 (fileStatus == PS_PAUSED) ||
985 (fileStatus == PS_ERROR) ||
986 (fileStatus == PS_INSUFFICIENT);
987 canCancel = fileStatus != PS_COMPLETE;
988 } else {
989 canStop = canPause = canCancel = fileResumable = false;
992 wxMenu* menu = m_menu;
993 menu->Enable( MP_CANCEL, canCancel );
994 menu->Enable( MP_PAUSE, canPause );
995 menu->Enable( MP_STOP, canStop );
996 menu->Enable( MP_RESUME, fileResumable );
997 menu->Enable( MP_CLEARCOMPLETED, m_completedFiles );
999 wxString view;
1000 if (file->IsPartFile() && (file->GetStatus() != PS_COMPLETE)) {
1001 view = CFormat(wxT("%s [%s]")) % _("Preview")
1002 % file->GetPartMetFileName().RemoveExt();
1003 } else if ( file->GetStatus() == PS_COMPLETE ) {
1004 view = _("&Open the file");
1006 menu->SetLabel(MP_VIEW, view);
1007 menu->Enable(MP_VIEW, file->PreviewAvailable() );
1009 menu->Check( MP_SWAP_A4AF_TO_THIS_AUTO, file->IsA4AFAuto() );
1011 int priority = file->IsAutoDownPriority() ?
1012 PR_AUTO : file->GetDownPriority();
1014 priomenu->Check( MP_PRIOHIGH, priority == PR_HIGH );
1015 priomenu->Check( MP_PRIONORMAL, priority == PR_NORMAL );
1016 priomenu->Check( MP_PRIOLOW, priority == PR_LOW );
1017 priomenu->Check( MP_PRIOAUTO, priority == PR_AUTO );
1019 menu->Enable( MP_MENU_EXTD, canPause );
1021 PopupMenu(m_menu, evt.GetPoint());
1023 } else {
1024 CUpDownClient* client = item->GetSource();
1026 m_menu = new wxMenu(wxT("Clients"));
1027 m_menu->Append(MP_DETAIL, _("Show &Details"));
1028 m_menu->Append(MP_ADDFRIEND, client->IsFriend() ? _("Remove from friends") : _("Add to Friends"));
1029 m_menu->Append(MP_SHOWLIST, _("View Files"));
1030 m_menu->Append(MP_SENDMESSAGE, _("Send message"));
1031 m_menu->Append(MP_CHANGE2FILE, _("Swap to this file"));
1033 // Only enable the Swap option for A4AF sources
1034 m_menu->Enable(MP_CHANGE2FILE, (item->GetType() == A4AF_SOURCE));
1035 // We need a valid IP if we are to message the client
1036 m_menu->Enable(MP_SENDMESSAGE, (client->GetIP() != 0));
1038 m_menu->Enable(MP_SHOWLIST, !client->HasDisabledSharedFiles());
1040 PopupMenu(m_menu, evt.GetPoint());
1044 delete m_menu;
1045 m_menu = NULL;
1050 void CDownloadListCtrl::OnMouseMiddleClick(wxListEvent& evt)
1052 // Check if clicked item is selected. If not, unselect all and select it.
1053 long index = CheckSelection(evt);
1054 if ( index < 0 ) {
1055 return;
1058 CtrlItem_Struct* item = (CtrlItem_Struct*)GetItemData( index );
1060 if ( item->GetType() == FILE_TYPE ) {
1061 CFileDetailDialog(this, item->GetFile()).ShowModal();
1062 } else {
1063 CClientDetailDialog(this, item->GetSource()).ShowModal();
1068 void CDownloadListCtrl::OnKeyPressed( wxKeyEvent& event )
1070 // Check if delete was pressed
1071 switch (event.GetKeyCode()) {
1072 case WXK_NUMPAD_DELETE:
1073 case WXK_DELETE: {
1074 wxCommandEvent evt;
1075 OnCancelFile( evt );
1076 break;
1078 case WXK_F2: {
1079 ItemList files = ::GetSelectedItems( this, itFILES );
1080 if (files.size() == 1) {
1081 CPartFile* file = files.front()->GetFile();
1083 // Currently renaming of completed files causes problem with kad
1084 if (file->IsPartFile()) {
1085 wxString strNewName = ::wxGetTextFromUser(
1086 _("Enter new name for this file:"),
1087 _("File rename"), file->GetFileName().GetPrintable());
1089 CPath newName = CPath(strNewName);
1090 if (newName.IsOk() && (newName != file->GetFileName())) {
1091 theApp->sharedfiles->RenameFile(file, newName);
1095 break;
1097 default:
1098 event.Skip();
1103 void CDownloadListCtrl::OnDrawItem(
1104 int item, wxDC* dc, const wxRect& rect, const wxRect& rectHL, bool highlighted)
1106 // Don't do any drawing if there's nobody to see it.
1107 if ( !theApp->amuledlg->IsDialogVisible( CamuleDlg::DT_TRANSFER_WND ) ) {
1108 return;
1111 CtrlItem_Struct* content = (CtrlItem_Struct *)GetItemData(item);
1113 // Define text-color and background
1114 if ((content->GetType() == FILE_TYPE) && (highlighted)) {
1115 if (GetFocus()) {
1116 dc->SetBackground(m_hilightBrush);
1117 dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
1118 } else {
1119 dc->SetBackground(m_hilightUnfocusBrush);
1120 dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
1122 } else {
1123 dc->SetBackground(*(wxTheBrushList->FindOrCreateBrush(
1124 wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX), wxSOLID)));
1125 dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
1129 // Define the border of the drawn area
1130 if ( highlighted ) {
1131 CMuleColour colour;
1132 if ( ( content->GetType() == FILE_TYPE ) && !GetFocus() ) {
1133 colour = m_hilightUnfocusBrush.GetColour();
1134 } else {
1135 colour = m_hilightBrush.GetColour();
1138 dc->SetPen( colour.Blend(65).GetPen() );
1139 } else {
1140 dc->SetPen(*wxTRANSPARENT_PEN);
1144 dc->SetBrush( dc->GetBackground() );
1145 dc->DrawRectangle( rectHL.x, rectHL.y, rectHL.width, rectHL.height );
1147 dc->SetPen(*wxTRANSPARENT_PEN);
1149 if ( content->GetType() == FILE_TYPE && ( !highlighted || !GetFocus() ) ) {
1150 // If we have category, override textforeground with what category tells us.
1151 CPartFile *file = content->GetFile();
1152 if ( file->GetCategory() ) {
1153 dc->SetTextForeground(
1154 CMuleColour(theApp->glob_prefs->GetCatColor(file->GetCategory())) );
1158 // Various constant values we use
1159 const int iTextOffset = ( rect.GetHeight() - dc->GetCharHeight() ) / 2;
1160 const int iOffset = 4;
1162 // The starting end ending position of the tree
1163 bool tree_show = false;
1164 int tree_start = 0;
1165 int tree_end = 0;
1167 wxRect cur_rec( iOffset, rect.y, 0, rect.height );
1168 for (int i = 0; i < GetColumnCount(); i++) {
1169 wxListItem listitem;
1170 GetColumn(i, listitem);
1172 if (listitem.GetWidth() > 2*iOffset) {
1173 cur_rec.width = listitem.GetWidth() - 2*iOffset;
1175 // Make a copy of the current rectangle so we can apply specific tweaks
1176 wxRect target_rec = cur_rec;
1177 if ( i == ColumnProgress ) {
1178 tree_show = ( listitem.GetWidth() > 0 );
1180 tree_start = cur_rec.x - iOffset;
1181 tree_end = cur_rec.x + iOffset;
1183 // Double the offset to make room for the cirle-marker
1184 target_rec.x += iOffset;
1185 target_rec.width -= iOffset;
1186 } else {
1187 // will ensure that text is about in the middle ;)
1188 target_rec.y += iTextOffset;
1191 // Draw the item
1192 if ( content->GetType() == FILE_TYPE ) {
1193 DrawFileItem(dc, i, target_rec, content);
1194 } else {
1195 DrawSourceItem(dc, i, target_rec, content);
1199 // Increment to the next column
1200 cur_rec.x += listitem.GetWidth();
1203 // Draw tree last so it draws over selected and focus (looks better)
1204 if ( tree_show ) {
1205 // Gather some information
1206 const bool notLast = item + 1 != GetItemCount();
1207 const bool notFirst = item != 0;
1208 const bool hasNext = notLast &&
1209 ((CtrlItem_Struct*)GetItemData(item + 1))->GetType() != FILE_TYPE;
1210 const bool isOpenRoot = content->GetType() == FILE_TYPE &&
1211 (content->GetFile())->ShowSources() &&
1212 ((content->GetFile())->GetStatus() != PS_COMPLETE);
1213 const bool isChild = content->GetType() != FILE_TYPE;
1215 // Might as well calculate these now
1216 const int treeCenter = tree_start + 3;
1217 const int middle = cur_rec.y + ( cur_rec.height + 1 ) / 2;
1219 // Set up a new pen for drawing the tree
1220 dc->SetPen( *(wxThePenList->FindOrCreatePen(dc->GetTextForeground(), 1, wxSOLID)) );
1222 if (isChild) {
1223 // Draw the line to the status bar
1224 dc->DrawLine(tree_end, middle, tree_start + 3, middle);
1226 // Draw the line to the child node
1227 if (hasNext) {
1228 dc->DrawLine(treeCenter, middle, treeCenter, cur_rec.y + cur_rec.height + 1);
1231 // Draw the line back up to parent node
1232 if (notFirst) {
1233 dc->DrawLine(treeCenter, middle, treeCenter, cur_rec.y - 1);
1235 } else if ( isOpenRoot ) {
1236 // Draw empty circle
1237 dc->SetBrush(*wxTRANSPARENT_BRUSH);
1239 dc->DrawCircle( treeCenter, middle, 3 );
1241 // Draw the line to the child node if there are any children
1242 if (hasNext) {
1243 dc->DrawLine(treeCenter, middle + 3, treeCenter, cur_rec.y + cur_rec.height + 1);
1251 void CDownloadListCtrl::DrawFileItem( wxDC* dc, int nColumn, const wxRect& rect, CtrlItem_Struct* item ) const
1253 wxDCClipper clipper( *dc, rect.GetX(), rect.GetY(), rect.GetWidth(), rect.GetHeight() );
1255 const CPartFile* file = item->GetFile();
1257 // Used to contain the contenst of cells that dont need any fancy drawing, just text.
1258 wxString text;
1260 switch (nColumn) {
1261 // Part Number
1262 case ColumnPart: {
1263 wxString partno;
1266 if (file->IsPartFile() && !(file->GetStatus() == PS_COMPLETE)) {
1267 partno = CFormat(wxT("%s")) % file->GetPartMetFileName().RemoveAllExt();
1269 dc->DrawText(partno, rect.GetX(), rect.GetY());
1271 break;
1272 // Filename
1273 case ColumnFileName: {
1274 wxString filename = file->GetFileName().GetPrintable();
1276 if (file->HasRating() || file->HasComment()) {
1277 int image = Client_CommentOnly_Smiley;
1278 if (file->HasRating()) {
1279 image = Client_InvalidRating_Smiley + file->UserRating() - 1;
1282 wxASSERT(image >= Client_InvalidRating_Smiley);
1283 wxASSERT(image <= Client_CommentOnly_Smiley);
1285 int imgWidth = 16;
1287 // it's already centered by OnDrawItem() ...
1288 m_ImageList.Draw(image, *dc, rect.GetX(), rect.GetY() - 1,
1289 wxIMAGELIST_DRAW_TRANSPARENT);
1290 dc->DrawText(filename, rect.GetX() + imgWidth + 4, rect.GetY());
1291 } else {
1292 dc->DrawText(filename, rect.GetX(), rect.GetY());
1295 break;
1297 // Filesize
1298 case ColumnSize:
1299 text = CastItoXBytes( file->GetFileSize() );
1300 break;
1302 // Transferred
1303 case ColumnTransferred:
1304 text = CastItoXBytes( file->GetTransferred() );
1305 break;
1307 // Completed
1308 case ColumnCompleted:
1309 text = CastItoXBytes( file->GetCompletedSize() );
1310 break;
1312 // Speed
1313 case ColumnSpeed:
1314 if ( file->GetTransferingSrcCount() ) {
1315 text = wxString::Format( wxT("%.1f "), file->GetKBpsDown() ) +
1316 _("kB/s");
1318 break;
1320 // Progress
1321 case ColumnProgress:
1323 if (thePrefs::ShowProgBar())
1325 int iWidth = rect.GetWidth() - 2;
1326 int iHeight = rect.GetHeight() - 2;
1328 // DO NOT DRAW IT ALL THE TIME
1329 uint32 dwTicks = GetTickCount();
1331 wxMemoryDC cdcStatus;
1333 if ( item->dwUpdated < dwTicks || !item->status || iWidth != item->status->GetWidth() ) {
1334 if ( item->status == NULL) {
1335 item->status = new wxBitmap(iWidth, iHeight);
1336 } else if ( item->status->GetWidth() != iWidth ) {
1337 // Only recreate if the size has changed
1338 item->status->Create(iWidth, iHeight);
1341 cdcStatus.SelectObject( *item->status );
1343 if ( thePrefs::UseFlatBar() ) {
1344 DrawFileStatusBar( file, &cdcStatus,
1345 wxRect(0, 0, iWidth, iHeight), true);
1346 } else {
1347 DrawFileStatusBar( file, &cdcStatus,
1348 wxRect(1, 1, iWidth - 2, iHeight - 2), false);
1350 // Draw black border
1351 cdcStatus.SetPen( *wxBLACK_PEN );
1352 cdcStatus.SetBrush( *wxTRANSPARENT_BRUSH );
1353 cdcStatus.DrawRectangle( 0, 0, iWidth, iHeight );
1356 item->dwUpdated = dwTicks + 5000; // Plus five seconds
1357 } else {
1358 cdcStatus.SelectObject( *item->status );
1361 dc->Blit( rect.GetX(), rect.GetY() + 1, iWidth, iHeight, &cdcStatus, 0, 0);
1364 if (thePrefs::ShowPercent()) {
1365 // Percentage of completing
1366 // We strip anything below the first decimal point,
1367 // to avoid Format doing roundings
1368 float percent = floor( file->GetPercentCompleted() * 10.0f ) / 10.0f;
1370 wxString buffer = wxString::Format( wxT("%.1f%%"), percent );
1371 int middlex = (2*rect.GetX() + rect.GetWidth()) >> 1;
1372 int middley = (2*rect.GetY() + rect.GetHeight()) >> 1;
1374 wxCoord textwidth, textheight;
1376 dc->GetTextExtent(buffer, &textwidth, &textheight);
1377 wxColour AktColor = dc->GetTextForeground();
1378 if (thePrefs::ShowProgBar()) {
1379 dc->SetTextForeground(*wxWHITE);
1380 } else {
1381 dc->SetTextForeground(*wxBLACK);
1383 dc->DrawText(buffer, middlex - (textwidth >> 1), middley - (textheight >> 1));
1384 dc->SetTextForeground(AktColor);
1387 break;
1389 // Sources
1390 case ColumnSources: {
1391 uint16 sc = file->GetSourceCount();
1392 uint16 ncsc = file->GetNotCurrentSourcesCount();
1393 if ( ncsc ) {
1394 text = wxString::Format( wxT("%i/%i" ), sc - ncsc, sc );
1395 } else {
1396 text = wxString::Format( wxT("%i"), sc );
1399 if ( file->GetSrcA4AFCount() ) {
1400 text += wxString::Format( wxT("+%i"), file->GetSrcA4AFCount() );
1403 if ( file->GetTransferingSrcCount() ) {
1404 text += wxString::Format( wxT(" (%i)"), file->GetTransferingSrcCount() );
1407 break;
1410 // Priority
1411 case ColumnPriority:
1412 text = PriorityToStr( file->GetDownPriority(), file->IsAutoDownPriority() );
1413 break;
1415 // File-status
1416 case ColumnStatus:
1417 text = file->getPartfileStatus();
1418 break;
1420 // Remaining
1421 case ColumnTimeRemaining: {
1422 if ((file->GetStatus() != PS_COMPLETING) && file->IsPartFile()) {
1423 uint64 remainSize = file->GetFileSize() - file->GetCompletedSize();
1424 sint32 remainTime = file->getTimeRemaining();
1426 if (remainTime >= 0) {
1427 text = CastSecondsToHM(remainTime);
1428 } else {
1429 text = _("Unknown");
1432 text += wxT(" (") + CastItoXBytes(remainSize) + wxT(")");
1434 break;
1437 // Last seen completed
1438 case ColumnLastSeenComplete: {
1439 if ( file->lastseencomplete ) {
1440 text = wxDateTime( file->lastseencomplete ).Format( _("%y/%m/%d %H:%M:%S") );
1441 } else {
1442 text = _("Unknown");
1444 break;
1447 // Last received
1448 case ColumnLastReception: {
1449 const time_t lastReceived = file->GetLastChangeDatetime();
1450 if (lastReceived) {
1451 text = wxDateTime(lastReceived).Format( _("%y/%m/%d %H:%M:%S") );
1452 } else {
1453 text = _("Unknown");
1456 } // switch
1458 if ( !text.IsEmpty() ) {
1459 dc->DrawText( text, rect.GetX(), rect.GetY() );
1464 void CDownloadListCtrl::DrawSourceItem(
1465 wxDC* dc, int nColumn, const wxRect& rect, CtrlItem_Struct* item ) const
1467 wxDCClipper clipper( *dc, rect.GetX(), rect.GetY(), rect.GetWidth(), rect.GetHeight() );
1468 wxString buffer;
1470 const CUpDownClient* client = item->GetSource();
1472 switch (nColumn) {
1473 // Client name + various icons
1474 case ColumnFileName: {
1475 wxRect cur_rec = rect;
1476 // +3 is added by OnDrawItem()... so take it off
1477 // Kry - eMule says +1, so I'm trusting it
1478 wxPoint point( cur_rec.GetX(), cur_rec.GetY()+1 );
1480 if (item->GetType() != A4AF_SOURCE) {
1481 uint8 image = 0;
1483 switch (client->GetDownloadState()) {
1484 case DS_CONNECTING:
1485 case DS_CONNECTED:
1486 case DS_WAITCALLBACK:
1487 case DS_TOOMANYCONNS:
1488 image = Client_Red_Smiley;
1489 break;
1490 case DS_ONQUEUE:
1491 if (client->IsRemoteQueueFull()) {
1492 image = Client_Grey_Smiley;
1493 } else {
1494 image = Client_Yellow_Smiley;
1496 break;
1497 case DS_DOWNLOADING:
1498 case DS_REQHASHSET:
1499 image = Client_Green_Smiley;
1500 break;
1501 case DS_NONEEDEDPARTS:
1502 case DS_LOWTOLOWIP:
1503 image = Client_Grey_Smiley;
1504 break;
1505 default: // DS_NONE i.e.
1506 image = Client_White_Smiley;
1509 m_ImageList.Draw(image, *dc, point.x, point.y,
1510 wxIMAGELIST_DRAW_TRANSPARENT);
1511 } else {
1512 m_ImageList.Draw(Client_Grey_Smiley, *dc, point.x, point.y,
1513 wxIMAGELIST_DRAW_TRANSPARENT);
1516 cur_rec.x += 20;
1517 wxPoint point2( cur_rec.GetX(), cur_rec.GetY() + 1 );
1519 uint8 clientImage;
1521 if ( client->IsFriend() ) {
1522 clientImage = Client_Friend_Smiley;
1523 } else {
1524 switch ( client->GetClientSoft() ) {
1525 case SO_AMULE:
1526 clientImage = Client_aMule_Smiley;
1527 break;
1528 case SO_MLDONKEY:
1529 case SO_NEW_MLDONKEY:
1530 case SO_NEW2_MLDONKEY:
1531 clientImage = Client_mlDonkey_Smiley;
1532 break;
1533 case SO_EDONKEY:
1534 case SO_EDONKEYHYBRID:
1535 clientImage = Client_eDonkeyHybrid_Smiley;
1536 break;
1537 case SO_EMULE:
1538 clientImage = Client_eMule_Smiley;
1539 break;
1540 case SO_LPHANT:
1541 clientImage = Client_lphant_Smiley;
1542 break;
1543 case SO_SHAREAZA:
1544 case SO_NEW_SHAREAZA:
1545 case SO_NEW2_SHAREAZA:
1546 clientImage = Client_Shareaza_Smiley;
1547 break;
1548 case SO_LXMULE:
1549 clientImage = Client_xMule_Smiley;
1550 break;
1551 default:
1552 // cDonkey, Compatible, Unknown
1553 // No icon for those yet.
1554 // Using the eMule one + '?'
1555 clientImage = Client_Unknown;
1556 break;
1560 m_ImageList.Draw(clientImage, *dc, point2.x, point.y,
1561 wxIMAGELIST_DRAW_TRANSPARENT);
1563 if (client->GetScoreRatio() > 1) {
1564 // Has credits, draw the gold star
1565 m_ImageList.Draw(Client_CreditsYellow_Smiley, *dc, point2.x, point.y,
1566 wxIMAGELIST_DRAW_TRANSPARENT );
1567 } else if ( client->ExtProtocolAvailable() ) {
1568 // Ext protocol -> Draw the '+'
1569 m_ImageList.Draw(Client_ExtendedProtocol_Smiley, *dc, point2.x, point.y,
1570 wxIMAGELIST_DRAW_TRANSPARENT);
1573 if (client->IsIdentified()) {
1574 // the 'v'
1575 m_ImageList.Draw(Client_SecIdent_Smiley, *dc, point2.x, point.y,
1576 wxIMAGELIST_DRAW_TRANSPARENT);
1577 } else if (client->IsBadGuy()) {
1578 // the 'X'
1579 m_ImageList.Draw(Client_BadGuy_Smiley, *dc, point2.x, point.y,
1580 wxIMAGELIST_DRAW_TRANSPARENT);
1583 if (client->HasObfuscatedConnectionBeenEstablished()) {
1584 // the "¿" except it's a key
1585 m_ImageList.Draw(Client_Encryption_Smiley, *dc, point2.x, point.y,
1586 wxIMAGELIST_DRAW_TRANSPARENT);
1589 wxString userName;
1590 #ifdef ENABLE_IP2COUNTRY
1591 // Draw the flag
1592 const CountryData& countrydata = theApp->amuledlg->m_IP2Country->GetCountryData(client->GetFullIP());
1593 dc->DrawBitmap(countrydata.Flag,
1594 rect.x + 40, rect.y + 5,
1595 wxIMAGELIST_DRAW_TRANSPARENT);
1597 userName << countrydata.Name;
1599 userName << wxT(" - ");
1600 #endif // ENABLE_IP2COUNTRY
1601 if (client->GetUserName().IsEmpty()) {
1602 userName << wxT("?");
1603 } else {
1604 userName << client->GetUserName();
1606 dc->DrawText(userName, rect.GetX() + 60, rect.GetY());
1608 break;
1610 case ColumnCompleted: // completed
1611 if (item->GetType() != A4AF_SOURCE && client->GetTransferredDown()) {
1612 buffer = CastItoXBytes(client->GetTransferredDown());
1613 dc->DrawText(buffer, rect.GetX(), rect.GetY());
1615 break;
1617 case ColumnSpeed: // speed
1618 if (item->GetType() != A4AF_SOURCE && client->GetKBpsDown() > 0.001) {
1619 buffer = wxString::Format(wxT("%.1f "),
1620 client->GetKBpsDown()) + _("kB/s");
1621 dc->DrawText(buffer, rect.GetX(), rect.GetY());
1623 break;
1625 case ColumnProgress: // file info
1626 if ( thePrefs::ShowProgBar() ) {
1627 int iWidth = rect.GetWidth() - 2;
1628 int iHeight = rect.GetHeight() - 2;
1630 // don't draw Text beyond the bar
1631 dc->SetClippingRegion(rect.GetX(), rect.GetY() + 1, iWidth, iHeight);
1633 if ( item->GetType() != A4AF_SOURCE ) {
1634 uint32 dwTicks = GetTickCount();
1636 wxMemoryDC cdcStatus;
1638 if ( item->dwUpdated < dwTicks || !item->status ||
1639 iWidth != item->status->GetWidth() ) {
1641 if (item->status == NULL) {
1642 item->status = new wxBitmap(iWidth, iHeight);
1643 } else if ( item->status->GetWidth() != iWidth ) {
1644 // Only recreate if size has changed
1645 item->status->Create(iWidth, iHeight);
1648 cdcStatus.SelectObject(*(item->status));
1650 if ( thePrefs::UseFlatBar() ) {
1651 DrawSourceStatusBar( client, &cdcStatus,
1652 wxRect(0, 0, iWidth, iHeight), true);
1653 } else {
1654 DrawSourceStatusBar( client, &cdcStatus,
1655 wxRect(1, 1, iWidth - 2, iHeight - 2), false);
1657 // Draw black border
1658 cdcStatus.SetPen( *wxBLACK_PEN );
1659 cdcStatus.SetBrush( *wxTRANSPARENT_BRUSH );
1660 cdcStatus.DrawRectangle( 0, 0, iWidth, iHeight );
1663 // Plus ten seconds
1664 item->dwUpdated = dwTicks + 10000;
1665 } else {
1666 cdcStatus.SelectObject(*(item->status));
1669 dc->Blit(rect.GetX(), rect.GetY() + 1, iWidth, iHeight, &cdcStatus, 0, 0);
1670 } else {
1671 wxString a4af;
1672 CPartFile* p = client->GetRequestFile();
1673 if (p) {
1674 a4af = p->GetFileName().GetPrintable();
1675 } else {
1676 a4af = wxT("?");
1678 buffer = CFormat(wxT("%s: %s")) % _("A4AF") % a4af;
1680 int midx = (2*rect.GetX() + rect.GetWidth()) >> 1;
1681 int midy = (2*rect.GetY() + rect.GetHeight()) >> 1;
1683 wxCoord txtwidth, txtheight;
1685 dc->GetTextExtent(buffer, &txtwidth, &txtheight);
1687 dc->SetTextForeground(*wxBLACK);
1688 dc->DrawText(buffer, wxMax(rect.GetX() + 2, midx - (txtwidth >> 1)), midy - (txtheight >> 1));
1690 // Draw black border
1691 dc->SetPen( *wxBLACK_PEN );
1692 dc->SetBrush( *wxTRANSPARENT_BRUSH );
1693 dc->DrawRectangle( rect.GetX(), rect.GetY() + 1, iWidth, iHeight );
1696 break;
1698 case ColumnSources: {
1699 // Version
1700 dc->DrawText(client->GetClientVerString(), rect.GetX(), rect.GetY());
1701 break;
1704 case ColumnPriority: // prio
1705 // We only show priority for sources actually queued for that file
1706 if ( item->GetType() != A4AF_SOURCE &&
1707 client->GetDownloadState() == DS_ONQUEUE ) {
1708 if (client->IsRemoteQueueFull()) {
1709 buffer = _("Queue Full");
1710 dc->DrawText(buffer, rect.GetX(), rect.GetY());
1711 } else {
1712 if (client->GetRemoteQueueRank()) {
1713 sint16 qrDiff = client->GetRemoteQueueRank() -
1714 client->GetOldRemoteQueueRank();
1715 if(qrDiff == client->GetRemoteQueueRank() ) {
1716 qrDiff = 0;
1718 wxColour savedColour = dc->GetTextForeground();
1719 if( qrDiff < 0 ) {
1720 dc->SetTextForeground(*wxBLUE);
1722 if( qrDiff > 0 ) {
1723 dc->SetTextForeground(*wxRED);
1725 //if( qrDiff == 0 ) {
1726 // dc->SetTextForeground(*wxLIGHT_GREY);
1728 buffer = wxString::Format(_("QR: %u (%i)"),
1729 client->GetRemoteQueueRank(), qrDiff);
1730 dc->DrawText(buffer, rect.GetX(), rect.GetY());
1731 dc->SetTextForeground(savedColour);
1735 break;
1737 case ColumnStatus: // status
1738 if (item->GetType() != A4AF_SOURCE) {
1739 buffer = DownloadStateToStr( client->GetDownloadState(),
1740 client->IsRemoteQueueFull() );
1741 } else {
1742 buffer = _("Asked for another file");
1743 if ( client->GetRequestFile() &&
1744 client->GetRequestFile()->GetFileName().IsOk()) {
1745 buffer += CFormat(wxT(" (%s)"))
1746 % client->GetRequestFile()->GetFileName();
1749 dc->DrawText(buffer, rect.GetX(), rect.GetY());
1750 break;
1751 // Source comes from?
1752 case ColumnTimeRemaining: {
1753 buffer = wxGetTranslation(OriginToText(client->GetSourceFrom()));
1754 dc->DrawText(buffer, rect.GetX(), rect.GetY());
1755 break;
1761 wxString CDownloadListCtrl::GetTTSText(unsigned item) const
1763 CtrlItem_Struct* content = (CtrlItem_Struct*)GetItemData(item);
1765 if (content->GetType() == FILE_TYPE) {
1766 CPartFile* file = content->GetFile();
1768 return file->GetFileName().GetPrintable();
1771 return wxEmptyString;
1775 int CDownloadListCtrl::SortProc(wxUIntPtr param1, wxUIntPtr param2, long sortData)
1777 CtrlItem_Struct* item1 = (CtrlItem_Struct*)param1;
1778 CtrlItem_Struct* item2 = (CtrlItem_Struct*)param2;
1780 int sortMod = (sortData & CMuleListCtrl::SORT_DES) ? -1 : 1;
1781 sortData &= CMuleListCtrl::COLUMN_MASK;
1782 int comp = 0;
1784 if ( item1->GetType() == FILE_TYPE ) {
1785 if ( item2->GetType() == FILE_TYPE ) {
1786 // Both are files, so we just compare them
1787 comp = Compare( item1->GetFile(), item2->GetFile(), sortData);
1788 } else {
1789 // A file and a source, checking if they belong to each other
1790 if ( item1->GetFile() == item2->GetOwner() ) {
1791 // A file should always be above its sources
1792 // Returning directly to avoid the modifier
1793 return -1;
1794 } else {
1795 // Source belongs to anther file, so we compare the files instead
1796 comp = Compare( item1->GetFile(), item2->GetOwner(), sortData);
1799 } else {
1800 if ( item2->GetType() == FILE_TYPE ) {
1801 // A source and a file, checking if they belong to each other
1802 if ( item1->GetOwner() == item2->GetFile() ) {
1803 // A source should always be below its file
1804 // Returning directly to avoid the modifier
1805 return 1;
1806 } else {
1807 // Source belongs to anther file, so we compare the files instead
1808 comp = Compare( item1->GetOwner(), item2->GetFile(), sortData);
1810 } else {
1811 // Two sources, some different possibilites
1812 if ( item1->GetOwner() == item2->GetOwner() ) {
1813 // Avilable sources first, if we have both an
1814 // available and an unavailable
1815 comp = ( item2->GetType() - item1->GetType() );
1817 if (comp) {
1818 // A4AF and non-A4AF. The order is fixed regardless of sort-order.
1819 return comp;
1820 } else {
1821 comp = Compare(item1->GetSource(), item2->GetSource(), sortData);
1823 } else {
1824 // Belongs to different files, so we compare the files
1825 comp = Compare( item1->GetOwner(), item2->GetOwner(), sortData);
1830 // We modify the result so that it matches with ascending or decending
1831 return sortMod * comp;
1835 int CDownloadListCtrl::Compare( const CPartFile* file1, const CPartFile* file2, long lParamSort)
1837 int result = 0;
1839 switch (lParamSort) {
1840 // Sort by part number
1841 case ColumnPart:
1842 result = CmpAny(
1843 file1->GetPartMetFileName().RemoveAllExt(),
1844 file2->GetPartMetFileName().RemoveAllExt() );
1845 break;
1847 // Sort by filename
1848 case ColumnFileName:
1849 result = CmpAny(
1850 file1->GetFileName(),
1851 file2->GetFileName() );
1852 break;
1854 // Sort by size
1855 case ColumnSize:
1856 result = CmpAny(
1857 file1->GetFileSize(),
1858 file2->GetFileSize() );
1859 break;
1861 // Sort by transferred
1862 case ColumnTransferred:
1863 result = CmpAny(
1864 file1->GetTransferred(),
1865 file2->GetTransferred() );
1866 break;
1868 // Sort by completed
1869 case ColumnCompleted:
1870 result = CmpAny(
1871 file1->GetCompletedSize(),
1872 file2->GetCompletedSize() );
1873 break;
1875 // Sort by speed
1876 case ColumnSpeed:
1877 result = CmpAny(
1878 file1->GetKBpsDown() * 1024,
1879 file2->GetKBpsDown() * 1024 );
1880 break;
1882 // Sort by percentage completed
1883 case ColumnProgress:
1884 result = CmpAny(
1885 file1->GetPercentCompleted(),
1886 file2->GetPercentCompleted() );
1887 break;
1889 // Sort by number of sources
1890 case ColumnSources:
1891 result = CmpAny(
1892 file1->GetSourceCount(),
1893 file2->GetSourceCount() );
1894 break;
1896 // Sort by priority
1897 case ColumnPriority:
1898 result = CmpAny(
1899 file1->GetDownPriority(),
1900 file2->GetDownPriority() );
1901 break;
1903 // Sort by status
1904 case ColumnStatus:
1905 result = CmpAny(
1906 file1->getPartfileStatusRang(),
1907 file2->getPartfileStatusRang() );
1908 break;
1910 // Sort by remaining time
1911 case ColumnTimeRemaining:
1912 if (file1->getTimeRemaining() == -1) {
1913 if (file2->getTimeRemaining() == -1) {
1914 result = 0;
1915 } else {
1916 result = -1;
1918 } else {
1919 if (file2->getTimeRemaining() == -1) {
1920 result = 1;
1921 } else {
1922 result = CmpAny(
1923 file1->getTimeRemaining(),
1924 file2->getTimeRemaining() );
1927 break;
1929 // Sort by last seen complete
1930 case ColumnLastSeenComplete:
1931 result = CmpAny(
1932 file1->lastseencomplete,
1933 file2->lastseencomplete );
1934 break;
1936 // Sort by last reception
1937 case ColumnLastReception:
1938 result = CmpAny(
1939 file1->GetLastChangeDatetime(),
1940 file2->GetLastChangeDatetime() );
1941 break;
1944 return result;
1948 int CDownloadListCtrl::Compare(
1949 const CUpDownClient* client1, const CUpDownClient* client2, long lParamSort)
1951 switch (lParamSort) {
1952 // Sort by name
1953 case ColumnPart:
1954 case ColumnFileName:
1955 return CmpAny( client1->GetUserName(), client2->GetUserName() );
1957 // Sort by status (size field)
1958 case ColumnSize:
1959 return CmpAny( client1->GetDownloadState(), client2->GetDownloadState() );
1961 // Sort by transferred in the following fields
1962 case ColumnTransferred:
1963 case ColumnCompleted:
1964 return CmpAny( client1->GetTransferredDown(), client2->GetTransferredDown() );
1966 // Sort by speed
1967 case ColumnSpeed:
1968 return CmpAny( client1->GetKBpsDown(), client2->GetKBpsDown() );
1970 // Sort by parts offered (Progress field)
1971 case ColumnProgress:
1972 return CmpAny(
1973 client1->GetAvailablePartCount(),
1974 client2->GetAvailablePartCount() );
1976 // Sort by client version
1977 case ColumnSources: {
1978 if (client1->GetClientSoft() != client2->GetClientSoft()) {
1979 return client1->GetSoftStr().Cmp(client2->GetSoftStr());
1982 if (client1->GetVersion() != client2->GetVersion()) {
1983 return CmpAny(client1->GetVersion(), client2->GetVersion());
1986 return client1->GetClientModString().Cmp(client2->GetClientModString());
1989 // Sort by Queue-Rank
1990 case ColumnPriority: {
1991 // This will sort by download state: Downloading, OnQueue, Connecting ...
1992 // However, Asked For Another will always be placed last, due to
1993 // sorting in SortProc
1994 if ( client1->GetDownloadState() != client2->GetDownloadState() ) {
1995 return client1->GetDownloadState() - client2->GetDownloadState();
1998 // Placing items on queue before items on full queues
1999 if ( client1->IsRemoteQueueFull() ) {
2000 if ( client2->IsRemoteQueueFull() ) {
2001 return 0;
2002 } else {
2003 return 1;
2005 } else if ( client2->IsRemoteQueueFull() ) {
2006 return -1;
2007 } else {
2008 if ( client1->GetRemoteQueueRank() ) {
2009 if ( client2->GetRemoteQueueRank() ) {
2010 return CmpAny(
2011 client1->GetRemoteQueueRank(),
2012 client2->GetRemoteQueueRank() );
2013 } else {
2014 return -1;
2016 } else {
2017 if ( client2->GetRemoteQueueRank() ) {
2018 return 1;
2019 } else {
2020 return 0;
2026 // Sort by state
2027 case ColumnStatus: {
2028 if (client1->GetDownloadState() == client2->GetDownloadState()) {
2029 return CmpAny(
2030 client1->IsRemoteQueueFull(),
2031 client2->IsRemoteQueueFull() );
2032 } else {
2033 return CmpAny(
2034 client1->GetDownloadState(),
2035 client2->GetDownloadState() );
2039 // Source of source ;)
2040 case ColumnTimeRemaining:
2041 return CmpAny(client1->GetSourceFrom(), client2->GetSourceFrom());
2043 default:
2044 return 0;
2049 void CDownloadListCtrl::ClearCompleted()
2051 m_completedFiles = false;
2052 CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->Enable(false);
2054 // Search for completed files
2055 for ( ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); ) {
2056 CtrlItem_Struct* item = it->second; ++it;
2058 if ( item->GetType() == FILE_TYPE ) {
2059 CPartFile* file = item->GetFile();
2061 if ( file->IsPartFile() == false ) {
2062 RemoveFile(file);
2069 void CDownloadListCtrl::ShowFilesCount( int diff )
2071 m_filecount += diff;
2073 wxString str = wxString::Format( _("Downloads (%i)"), m_filecount );
2074 wxStaticText* label = CastByName( wxT("downloadsLabel"), GetParent(), wxStaticText );
2076 label->SetLabel( str );
2077 label->GetParent()->Layout();
2083 bool CDownloadListCtrl::ShowItemInCurrentCat(
2084 const CPartFile* file, int newsel ) const
2086 return
2087 ((newsel == 0 && !thePrefs::ShowAllNotCats()) ||
2088 (newsel == 0 && thePrefs::ShowAllNotCats() && file->GetCategory() == 0)) ||
2089 (newsel > 0 && newsel == file->GetCategory());
2092 static const CMuleColour crHave(104, 104, 104);
2093 static const CMuleColour crFlatHave(0, 0, 0);
2095 static const CMuleColour crPending(255, 208, 0);
2096 static const CMuleColour crFlatPending(255, 255, 100);
2098 static const CMuleColour crProgress(0, 224, 0);
2099 static const CMuleColour crFlatProgress(0, 150, 0);
2101 static const CMuleColour crMissing(255, 0, 0);
2103 void CDownloadListCtrl::DrawFileStatusBar(
2104 const CPartFile* file, wxDC* dc, const wxRect& rect, bool bFlat ) const
2106 static CBarShader s_ChunkBar(16);
2108 s_ChunkBar.SetHeight(rect.height);
2109 s_ChunkBar.SetWidth(rect.width);
2110 s_ChunkBar.SetFileSize( file->GetFileSize() );
2111 s_ChunkBar.Set3dDepth( thePrefs::Get3DDepth() );
2113 if ( file->GetStatus() == PS_COMPLETE || file->GetStatus() == PS_COMPLETING ) {
2114 s_ChunkBar.Fill( bFlat ? crFlatProgress : crProgress );
2115 s_ChunkBar.Draw(dc, rect.x, rect.y, bFlat);
2116 return;
2119 // Part availability ( of missing parts )
2120 const CPartFile::CGapPtrList& gaplist = file->GetGapList();
2121 CPartFile::CGapPtrList::const_iterator it = gaplist.begin();
2122 uint64 lastGapEnd = 0;
2123 CMuleColour colour;
2125 for (; it != gaplist.end(); ++it) {
2126 Gap_Struct* gap = *it;
2128 // Start position
2129 uint32 start = ( gap->start / PARTSIZE );
2130 // fill the Have-Part (between this gap and the last)
2131 if (gap->start) {
2132 s_ChunkBar.FillRange(lastGapEnd + 1, gap->start - 1, bFlat ? crFlatHave : crHave);
2134 lastGapEnd = gap->end;
2135 // End position
2136 uint32 end = ( gap->end / PARTSIZE ) + 1;
2138 // Avoid going past the filesize. Dunno if this can happen, but the old code did check.
2139 if ( end > file->GetPartCount() ) {
2140 end = file->GetPartCount();
2143 // Place each gap, one PART at a time
2144 for ( uint64 i = start; i < end; ++i ) {
2145 if ( i < file->m_SrcpartFrequency.size() && file->m_SrcpartFrequency[i]) {
2146 int blue = 210 - ( 22 * ( file->m_SrcpartFrequency[i] - 1 ) );
2147 colour.Set(0, ( blue < 0 ? 0 : blue ), 255 );
2148 } else {
2149 colour = crMissing;
2152 if ( file->IsStopped() ) {
2153 colour.Blend(50);
2156 uint64 gap_begin = ( i == start ? gap->start : PARTSIZE * i );
2157 uint64 gap_end = ( i == end - 1 ? gap->end : PARTSIZE * ( i + 1 ) - 1 );
2159 s_ChunkBar.FillRange( gap_begin, gap_end, colour);
2163 // fill the last Have-Part (between this gap and the last)
2164 s_ChunkBar.FillRange(lastGapEnd + 1, file->GetFileSize() - 1, bFlat ? crFlatHave : crHave);
2166 // Pending parts
2167 const CPartFile::CReqBlockPtrList& requestedblocks_list = file->GetRequestedBlockList();
2168 CPartFile::CReqBlockPtrList::const_iterator it2 = requestedblocks_list.begin();
2169 // adjacing pending parts must be joined to avoid bright lines between them
2170 uint64 lastStartOffset = 0;
2171 uint64 lastEndOffset = 0;
2173 colour = bFlat ? crFlatPending : crPending;
2175 if ( file->IsStopped() ) {
2176 colour.Blend(50);
2179 for (; it2 != requestedblocks_list.end(); ++it2) {
2181 if ((*it2)->StartOffset > lastEndOffset + 1) {
2182 // not adjacing, draw last block
2183 s_ChunkBar.FillRange(lastStartOffset, lastEndOffset, colour);
2184 lastStartOffset = (*it2)->StartOffset;
2185 lastEndOffset = (*it2)->EndOffset;
2186 } else {
2187 // adjacing, grow block
2188 lastEndOffset = (*it2)->EndOffset;
2192 s_ChunkBar.FillRange(lastStartOffset, lastEndOffset, colour);
2195 // Draw the progress-bar
2196 s_ChunkBar.Draw( dc, rect.x, rect.y, bFlat );
2199 // Green progressbar width
2200 int width = (int)(( (float)rect.width / (float)file->GetFileSize() ) *
2201 file->GetCompletedSize() );
2203 if ( bFlat ) {
2204 dc->SetBrush( crFlatProgress.GetBrush() );
2206 dc->DrawRectangle( rect.x, rect.y, width, 3 );
2207 } else {
2208 // Draw the two black lines for 3d-effect
2209 dc->SetPen( *wxBLACK_PEN );
2210 dc->DrawLine( rect.x, rect.y + 0, rect.x + width, rect.y + 0 );
2211 dc->DrawLine( rect.x, rect.y + 2, rect.x + width, rect.y + 2 );
2213 // Draw the green line
2214 dc->SetPen( *(wxThePenList->FindOrCreatePen( crProgress , 1, wxSOLID ) ));
2215 dc->DrawLine( rect.x, rect.y + 1, rect.x + width, rect.y + 1 );
2219 static const CMuleColour crBoth(0, 192, 0);
2220 static const CMuleColour crFlatBoth(0, 150, 0);
2222 static const CMuleColour crNeither(240, 240, 240);
2223 static const CMuleColour crFlatNeither(224, 224, 224);
2225 #define crClientOnly crHave
2226 #define crFlatClientOnly crFlatHave
2227 #define crNextPending crFlatPending
2229 void CDownloadListCtrl::DrawSourceStatusBar(
2230 const CUpDownClient* source, wxDC* dc, const wxRect& rect, bool bFlat) const
2232 static CBarShader s_StatusBar(16);
2234 CPartFile* reqfile = source->GetRequestFile();
2236 s_StatusBar.SetFileSize( reqfile->GetFileSize() );
2237 s_StatusBar.SetHeight(rect.height);
2238 s_StatusBar.SetWidth(rect.width);
2239 s_StatusBar.Set3dDepth( thePrefs::Get3DDepth() );
2241 // Barry - was only showing one part from client, even when reserved bits from 2 parts
2242 wxString gettingParts = source->ShowDownloadingParts();
2244 const BitVector& partStatus = source->GetPartStatus();
2246 uint64 uEnd = 0;
2247 for ( uint64 i = 0; i < partStatus.size(); i++ ) {
2248 uint64 uStart = PARTSIZE * i;
2249 uEnd = wxMin(reqfile->GetFileSize(), uStart + PARTSIZE) - 1;
2251 CMuleColour colour;
2252 if (!partStatus[i]) {
2253 colour = bFlat ? crFlatNeither : crNeither;
2254 } else if ( reqfile->IsComplete(uStart, uEnd)) {
2255 colour = bFlat ? crFlatBoth : crBoth;
2256 } else if ( source->GetDownloadState() == DS_DOWNLOADING &&
2257 source->GetLastBlockOffset() <= uEnd &&
2258 source->GetLastBlockOffset() >= uStart) {
2259 colour = crPending;
2260 } else if (gettingParts.GetChar((uint16)i) == 'Y') {
2261 colour = crNextPending;
2262 } else {
2263 colour = bFlat ? crFlatClientOnly : crClientOnly;
2266 if ( source->GetRequestFile()->IsStopped() ) {
2267 colour.Blend(50);
2270 s_StatusBar.FillRange(uStart, uEnd, colour);
2272 // fill the rest (if partStatus is empty)
2273 s_StatusBar.FillRange(uEnd + 1, reqfile->GetFileSize() - 1, bFlat ? crFlatNeither : crNeither);
2275 s_StatusBar.Draw(dc, rect.x, rect.y, bFlat);
2278 #ifdef __WXMSW__
2279 # define QUOTE wxT("\"")
2280 #else
2281 # define QUOTE wxT("\'")
2282 #endif
2284 void CDownloadListCtrl::PreviewFile(CPartFile* file)
2286 wxString command;
2287 // If no player set in preferences, use mplayer.
2288 // And please, do a warning also :P
2289 if (thePrefs::GetVideoPlayer().IsEmpty()) {
2290 wxMessageBox(_(
2291 "To prevent this warning to show up in every preview,\nset your preferred video player in preferences (default is mplayer)."),
2292 _("File preview"), wxOK, this);
2293 // Since newer versions for some reason mplayer does not automatically
2294 // select video output device and needs a parameter, go figure...
2295 command = wxT("xterm -T \"aMule Preview\" -iconic -e mplayer ") QUOTE wxT("$file") QUOTE;
2296 } else {
2297 command = thePrefs::GetVideoPlayer();
2300 // Check if we are (pre)viewing a completed file or not
2301 if (file->GetStatus() != PS_COMPLETE) {
2302 // Remove the .met and see if out video player specifiation uses the magic string
2303 wxString fileWithoutMet = thePrefs::GetTempDir().JoinPaths(
2304 file->GetPartMetFileName().RemoveExt()).GetRaw();
2305 if (!command.Replace(wxT("$file"), fileWithoutMet)) {
2306 // No magic string, so we just append the filename to the player command
2307 // Need to use quotes in case filename contains spaces
2308 command << wxT(" ") << QUOTE << fileWithoutMet << QUOTE;
2310 } else {
2311 // This is a complete file
2312 // FIXME: This is probably not going to work if the filenames are mangled ...
2313 wxString rawFileName = file->GetFullName().GetRaw();
2314 if (!command.Replace(wxT("$file"), rawFileName)) {
2315 // No magic string, so we just append the filename to the player command
2316 // Need to use quotes in case filename contains spaces
2317 command << wxT(" ") << QUOTE << rawFileName << QUOTE;
2321 // We can't use wxShell here, it blocks the app
2322 CTerminationProcess *p = new CTerminationProcess(command);
2323 int ret = wxExecute(command, wxEXEC_ASYNC, p);
2324 bool ok = ret > 0;
2325 if (!ok) {
2326 delete p;
2327 AddLogLineM( true,
2328 CFormat( _("ERROR: Failed to execute external media-player! Command: `%s'") ) %
2329 command );
2332 // File_checked_for_headers