Fix language getting reset to system default on saving preferences
[amule.git] / src / DownloadListCtrl.cpp
blob4f41a2e12e60304999c0a64654d9da220e47e85d
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 "DownloadListCtrl.h" // Interface declarations
28 #include <protocol/ed2k/ClientSoftware.h>
29 #include <common/MenuIDs.h>
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 "updownclient.h" // Needed for CUpDownClient
47 #include "TransferWnd.h"
48 #include "SourceListCtrl.h"
50 class CPartFile;
53 struct FileCtrlItem_Struct
55 FileCtrlItem_Struct()
56 : dwUpdated(0),
57 status(NULL),
58 m_fileValue(NULL)
59 { }
61 ~FileCtrlItem_Struct() {
62 delete status;
65 CPartFile* GetFile() const {
66 return m_fileValue;
69 void SetContents(CPartFile* file) {
70 m_fileValue = file;
73 uint32 dwUpdated;
74 wxBitmap* status;
76 private:
77 CPartFile* m_fileValue;
80 #define m_ImageList theApp->amuledlg->m_imagelist
82 enum ColumnEnum {
83 ColumnPart = 0,
84 ColumnFileName,
85 ColumnSize,
86 ColumnTransferred,
87 ColumnCompleted,
88 ColumnSpeed,
89 ColumnProgress,
90 ColumnSources,
91 ColumnPriority,
92 ColumnStatus,
93 ColumnTimeRemaining,
94 ColumnLastSeenComplete,
95 ColumnLastReception,
96 ColumnNumberOfColumns
99 BEGIN_EVENT_TABLE(CDownloadListCtrl, CMuleListCtrl)
100 EVT_LIST_ITEM_ACTIVATED(ID_DLOADLIST, CDownloadListCtrl::OnItemActivated)
101 EVT_LIST_ITEM_RIGHT_CLICK(ID_DLOADLIST, CDownloadListCtrl::OnMouseRightClick)
102 EVT_LIST_ITEM_MIDDLE_CLICK(ID_DLOADLIST, CDownloadListCtrl::OnMouseMiddleClick)
103 EVT_LIST_ITEM_SELECTED(ID_DLOADLIST, CDownloadListCtrl::OnItemSelectionChanged)
104 EVT_LIST_ITEM_DESELECTED(ID_DLOADLIST, CDownloadListCtrl::OnItemSelectionChanged)
106 EVT_CHAR( CDownloadListCtrl::OnKeyPressed )
108 EVT_MENU( MP_CANCEL, CDownloadListCtrl::OnCancelFile )
110 EVT_MENU( MP_PAUSE, CDownloadListCtrl::OnSetStatus )
111 EVT_MENU( MP_STOP, CDownloadListCtrl::OnSetStatus )
112 EVT_MENU( MP_RESUME, CDownloadListCtrl::OnSetStatus )
114 EVT_MENU( MP_PRIOLOW, CDownloadListCtrl::OnSetPriority )
115 EVT_MENU( MP_PRIONORMAL, CDownloadListCtrl::OnSetPriority )
116 EVT_MENU( MP_PRIOHIGH, CDownloadListCtrl::OnSetPriority )
117 EVT_MENU( MP_PRIOAUTO, CDownloadListCtrl::OnSetPriority )
119 EVT_MENU( MP_SWAP_A4AF_TO_THIS, CDownloadListCtrl::OnSwapSources )
120 EVT_MENU( MP_SWAP_A4AF_TO_THIS_AUTO, CDownloadListCtrl::OnSwapSources )
121 EVT_MENU( MP_SWAP_A4AF_TO_ANY_OTHER, CDownloadListCtrl::OnSwapSources )
123 EVT_MENU_RANGE( MP_ASSIGNCAT, MP_ASSIGNCAT + 99, CDownloadListCtrl::OnSetCategory )
125 EVT_MENU( MP_CLEARCOMPLETED, CDownloadListCtrl::OnClearCompleted )
127 EVT_MENU( MP_GETMAGNETLINK, CDownloadListCtrl::OnGetLink )
128 EVT_MENU( MP_GETED2KLINK, CDownloadListCtrl::OnGetLink )
130 EVT_MENU( MP_METINFO, CDownloadListCtrl::OnViewFileInfo )
131 EVT_MENU( MP_VIEW, CDownloadListCtrl::OnPreviewFile )
132 EVT_MENU( MP_VIEWFILECOMMENTS, CDownloadListCtrl::OnViewFileComments )
134 EVT_MENU( MP_WS, CDownloadListCtrl::OnGetFeedback )
136 END_EVENT_TABLE()
138 //! This listtype is used when gathering the selected items.
139 typedef std::list<FileCtrlItem_Struct*> ItemList;
141 CDownloadListCtrl::CDownloadListCtrl(
142 wxWindow *parent, wxWindowID winid, const wxPoint& pos, const wxSize& size,
143 long style, const wxValidator& validator, const wxString& name )
145 CMuleListCtrl( parent, winid, pos, size, style | wxLC_OWNERDRAW, validator, name )
147 // Setting the sorter function.
148 SetSortFunc( SortProc );
150 // Set the table-name (for loading and saving preferences).
151 SetTableName( wxT("Download") );
153 m_menu = NULL;
155 m_hilightBrush = CMuleColour(wxSYS_COLOUR_HIGHLIGHT).Blend(125).GetBrush();
157 m_hilightUnfocusBrush = CMuleColour(wxSYS_COLOUR_BTNSHADOW).Blend(125).GetBrush();
159 InsertColumn( ColumnPart, _("Part"), wxLIST_FORMAT_LEFT, 30, wxT("a") );
160 InsertColumn( ColumnFileName, _("File Name"), wxLIST_FORMAT_LEFT, 260, wxT("N") );
161 InsertColumn( ColumnSize, _("Size"), wxLIST_FORMAT_LEFT, 60, wxT("Z") );
162 InsertColumn( ColumnTransferred, _("Transferred"), wxLIST_FORMAT_LEFT, 65, wxT("T") );
163 InsertColumn( ColumnCompleted, _("Completed"), wxLIST_FORMAT_LEFT, 65, wxT("C") );
164 InsertColumn( ColumnSpeed, _("Speed"), wxLIST_FORMAT_LEFT, 65, wxT("S") );
165 InsertColumn( ColumnProgress, _("Progress"), wxLIST_FORMAT_LEFT, 170, wxT("P") );
166 InsertColumn( ColumnSources, _("Sources"), wxLIST_FORMAT_LEFT, 50, wxT("u") );
167 InsertColumn( ColumnPriority, _("Priority"), wxLIST_FORMAT_LEFT, 55, wxT("p") );
168 InsertColumn( ColumnStatus, _("Status"), wxLIST_FORMAT_LEFT, 70, wxT("s") );
169 InsertColumn( ColumnTimeRemaining, _("Time Remaining"), wxLIST_FORMAT_LEFT, 110, wxT("r") );
170 InsertColumn( ColumnLastSeenComplete, _("Last Seen Complete"), wxLIST_FORMAT_LEFT, 220, wxT("c") );
171 InsertColumn( ColumnLastReception, _("Last Reception"), wxLIST_FORMAT_LEFT, 220, wxT("R") );
173 m_category = 0;
174 m_filecount = 0;
175 LoadSettings();
177 //m_ready = true;
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!
182 wxString CDownloadListCtrl::GetOldColumnOrder() const
184 return wxT("N,Z,T,C,S,P,u,p,s,r,c,R");
187 CDownloadListCtrl::~CDownloadListCtrl()
189 while ( !m_ListItems.empty() ) {
190 delete m_ListItems.begin()->second;
191 m_ListItems.erase( m_ListItems.begin() );
195 void CDownloadListCtrl::AddFile( CPartFile* file )
197 wxASSERT( file );
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);
204 m_ListItems.insert( ListItemsPair( file, newitem ) );
206 // Check if the new file is visible in the current category
207 if ( file->CheckShowItemInGivenCat( m_category ) ) {
208 ShowFile( file, true );
209 SortList();
214 void CDownloadListCtrl::RemoveFile( CPartFile* file )
216 wxASSERT( file );
218 // Ensure that any list-entries are removed
219 ShowFile( file, false );
221 // Find the assosiated list-item
222 ListItems::iterator it = m_ListItems.find( file );
224 if ( it != m_ListItems.end() ) {
225 delete it->second;
227 m_ListItems.erase( it );
232 void CDownloadListCtrl::UpdateItem(const void* toupdate)
234 // Retrieve all entries matching the source
235 ListIteratorPair rangeIt = m_ListItems.equal_range( toupdate );
237 // Visible lines, default to all because not all platforms
238 // support the GetVisibleLines function
239 long first = 0, last = GetItemCount();
241 #ifndef __WXMSW__
242 // Get visible lines if we need them
243 if ( rangeIt.first != rangeIt.second ) {
244 GetVisibleLines( &first, &last );
246 #endif
248 for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second; ++it ) {
249 FileCtrlItem_Struct* item = it->second;
251 long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
253 // Determine if the file should be shown in the current category
255 CPartFile* file = item->GetFile();
257 bool show = file->CheckShowItemInGivenCat( m_category );
259 if ( index > -1 ) {
260 if ( show ) {
261 item->dwUpdated = 0;
263 // Only update visible lines
264 if ( index >= first && index <= last) {
265 RefreshItem( index );
267 } else {
268 // Item should no longer be shown in
269 // the current category
270 ShowFile( file, false );
272 } else if ( show ) {
273 // Item has been hidden but new status means
274 // that it should it should be shown in the
275 // current category
276 ShowFile( file, true );
279 if (file->IsCompleted() && show) {
280 CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->Enable(true);
286 void CDownloadListCtrl::ShowFile( CPartFile* file, bool show )
288 wxASSERT( file );
290 ListItems::iterator it = m_ListItems.find( file );
292 if ( it != m_ListItems.end() ) {
293 FileCtrlItem_Struct* item = it->second;
295 if ( show ) {
296 // Check if the file is already being displayed
297 long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
298 if ( index == -1 ) {
299 long newitem = InsertItem( GetItemCount(), wxEmptyString );
301 SetItemPtrData( newitem, reinterpret_cast<wxUIntPtr>(item) );
303 wxListItem myitem;
304 myitem.m_itemId = newitem;
305 myitem.SetBackgroundColour( GetBackgroundColour() );
307 SetItem(myitem);
309 RefreshItem( newitem );
311 ShowFilesCount( 1 );
313 } else {
314 // Try to find the file and remove it
315 long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
316 if ( index > -1 ) {
317 DeleteItem( index );
318 ShowFilesCount( -1 );
324 void CDownloadListCtrl::ChangeCategory( int newCategory )
326 Freeze();
328 bool hasCompletedDownloads = false;
330 // remove all displayed files with a different cat and show the correct ones
331 for (ListItems::const_iterator it = m_ListItems.begin(); it != m_ListItems.end(); it++) {
333 CPartFile* file = it->second->GetFile();
335 bool curVisibility = file->CheckShowItemInGivenCat( m_category );
336 bool newVisibility = file->CheckShowItemInGivenCat( newCategory );
338 if (newVisibility && file->IsCompleted()) {
339 hasCompletedDownloads = true;
342 // Check if the visibility of the file has changed. However, if the
343 // current category is the default (0) category, then we can't use
344 // curVisiblity to see if the visibility has changed but instead
345 // have to let ShowFile() check if the file is or isn't on the list.
346 if ( curVisibility != newVisibility || !newCategory ) {
347 ShowFile( file, newVisibility );
351 CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->Enable(hasCompletedDownloads);
353 Thaw();
355 m_category = newCategory;
359 uint8 CDownloadListCtrl::GetCategory() const
361 return m_category;
366 * Helper-function: This function is used to gather selected items.
368 * @param list A pointer to the list to gather items from.
369 * @return A list containing the selected items.
371 ItemList GetSelectedItems( CDownloadListCtrl* list)
373 ItemList results;
375 long index = list->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
377 while ( index > -1 ) {
378 FileCtrlItem_Struct* item = (FileCtrlItem_Struct*)list->GetItemData( index );
379 results.push_back( item );
381 index = list->GetNextItem( index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
384 return results;
388 void CDownloadListCtrl::OnCancelFile(wxCommandEvent& WXUNUSED(event))
390 ItemList files = ::GetSelectedItems(this);
391 if (files.size()) {
392 wxString question;
393 if (files.size() == 1) {
394 question = _("Are you sure that you wish to delete the selected file?");
395 } else {
396 question = _("Are you sure that you wish to delete the selected files?");
398 if (wxMessageBox( question, _("Cancel"), wxICON_QUESTION | wxYES_NO, this) == wxYES) {
399 for (ItemList::iterator it = files.begin(); it != files.end(); ++it) {
400 CPartFile* file = (*it)->GetFile();
401 if (file) {
402 switch (file->GetStatus()) {
403 case PS_WAITINGFORHASH:
404 case PS_HASHING:
405 case PS_COMPLETING:
406 case PS_COMPLETE:
407 break;
408 default:
409 CoreNotify_PartFile_Delete(file);
418 void CDownloadListCtrl::OnSetPriority( wxCommandEvent& event )
420 int priority = 0;
421 switch ( event.GetId() ) {
422 case MP_PRIOLOW: priority = PR_LOW; break;
423 case MP_PRIONORMAL: priority = PR_NORMAL; break;
424 case MP_PRIOHIGH: priority = PR_HIGH; break;
425 case MP_PRIOAUTO: priority = PR_AUTO; break;
426 default:
427 wxASSERT( false );
430 ItemList files = ::GetSelectedItems( this );
432 for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
433 CPartFile* file = (*it)->GetFile();
435 if ( priority == PR_AUTO ) {
436 CoreNotify_PartFile_PrioAuto( file, true );
437 } else {
438 CoreNotify_PartFile_PrioAuto( file, false );
440 CoreNotify_PartFile_PrioSet( file, priority, true );
446 void CDownloadListCtrl::OnSwapSources( wxCommandEvent& event )
448 ItemList files = ::GetSelectedItems( this );
450 for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
451 CPartFile* file = (*it)->GetFile();
453 switch ( event.GetId() ) {
454 case MP_SWAP_A4AF_TO_THIS:
455 CoreNotify_PartFile_Swap_A4AF( file );
456 break;
458 case MP_SWAP_A4AF_TO_THIS_AUTO:
459 CoreNotify_PartFile_Swap_A4AF_Auto( file );
460 break;
462 case MP_SWAP_A4AF_TO_ANY_OTHER:
463 CoreNotify_PartFile_Swap_A4AF_Others( file );
464 break;
470 void CDownloadListCtrl::OnSetCategory( wxCommandEvent& event )
472 ItemList files = ::GetSelectedItems( this );
474 for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
475 CoreNotify_PartFile_SetCat( (*it)->GetFile(), event.GetId() - MP_ASSIGNCAT );
476 ShowFile((*it)->GetFile(), false);
478 wxListEvent ev;
479 OnItemSelectionChanged(ev); // clear clients that may have been shown
481 ChangeCategory( m_category ); // This only updates the visibility of the clear completed button
485 void CDownloadListCtrl::OnSetStatus( wxCommandEvent& event )
487 ItemList files = ::GetSelectedItems( this );
489 for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
490 CPartFile* file = (*it)->GetFile();
492 switch ( event.GetId() ) {
493 case MP_PAUSE:
494 CoreNotify_PartFile_Pause( file );
495 break;
497 case MP_RESUME:
498 CoreNotify_PartFile_Resume( file );
499 break;
501 case MP_STOP:
502 CoreNotify_PartFile_Stop( file );
503 break;
509 void CDownloadListCtrl::OnClearCompleted( wxCommandEvent& WXUNUSED(event) )
511 ClearCompleted();
515 void CDownloadListCtrl::OnGetLink(wxCommandEvent& event)
517 ItemList files = ::GetSelectedItems( this );
519 wxString URIs;
521 for ( ItemList::iterator it = files.begin(); it != files.end(); ++it ) {
522 CPartFile* file = (*it)->GetFile();
524 if ( event.GetId() == MP_GETED2KLINK ) {
525 URIs += theApp->CreateED2kLink( file ) + wxT("\n");
526 } else {
527 URIs += theApp->CreateMagnetLink( file ) + wxT("\n");
531 if ( !URIs.IsEmpty() ) {
532 theApp->CopyTextToClipboard( URIs.BeforeLast(wxT('\n')) );
537 void CDownloadListCtrl::OnGetFeedback(wxCommandEvent& WXUNUSED(event))
539 wxString feed;
540 ItemList files = ::GetSelectedItems( this );
542 for (ItemList::iterator it = files.begin(); it != files.end(); ++it) {
543 if (feed.IsEmpty()) {
544 feed = CFormat(_("Feedback from: %s (%s)\n\n")) % thePrefs::GetUserNick() % theApp->GetFullMuleVersion();
545 } else {
546 feed += wxT("\n");
548 feed += (*it)->GetFile()->GetFeedback();
551 if (!feed.IsEmpty()) {
552 theApp->CopyTextToClipboard(feed);
556 void CDownloadListCtrl::OnViewFileInfo( wxCommandEvent& WXUNUSED(event) )
558 ItemList files = ::GetSelectedItems( this );
560 if ( files.size() == 1 ) {
561 CFileDetailDialog dialog( this, files.front()->GetFile() );
562 dialog.ShowModal();
567 void CDownloadListCtrl::OnViewFileComments( wxCommandEvent& WXUNUSED(event) )
569 ItemList files = ::GetSelectedItems( this );
571 if ( files.size() == 1 ) {
572 CCommentDialogLst dialog( this, files.front()->GetFile() );
573 dialog.ShowModal();
577 void CDownloadListCtrl::OnPreviewFile( wxCommandEvent& WXUNUSED(event) )
579 ItemList files = ::GetSelectedItems( this );
581 if ( files.size() == 1 ) {
582 PreviewFile(files.front()->GetFile());
586 void CDownloadListCtrl::OnItemActivated( wxListEvent& evt )
588 CPartFile* file = ((FileCtrlItem_Struct*)GetItemData( evt.GetIndex()))->GetFile();
590 if ((!file->IsPartFile() || file->IsCompleted()) && file->PreviewAvailable()) {
591 PreviewFile( file );
595 void CDownloadListCtrl::OnItemSelectionChanged( wxListEvent& )
597 if (!IsSorting()) {
598 CKnownFileVector filesVector;
599 filesVector.reserve(GetSelectedItemCount());
601 long index = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
603 while ( index > -1 ) {
604 CPartFile* file = ((FileCtrlItem_Struct*)GetItemData( index ))->GetFile();
605 if (file->IsPartFile()) {
606 filesVector.push_back(file);
608 index = GetNextItem( index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
611 std::sort(filesVector.begin(), filesVector.end());
612 theApp->amuledlg->m_transferwnd->clientlistctrl->ShowSources(filesVector);
616 void CDownloadListCtrl::OnMouseRightClick(wxListEvent& evt)
618 long index = CheckSelection(evt);
619 if (index < 0) {
620 return;
623 delete m_menu;
624 m_menu = NULL;
626 FileCtrlItem_Struct* item = (FileCtrlItem_Struct*)GetItemData( index );
627 m_menu = new wxMenu( _("Downloads") );
629 wxMenu* priomenu = new wxMenu();
630 priomenu->AppendCheckItem(MP_PRIOLOW, _("Low"));
631 priomenu->AppendCheckItem(MP_PRIONORMAL, _("Normal"));
632 priomenu->AppendCheckItem(MP_PRIOHIGH, _("High"));
633 priomenu->AppendCheckItem(MP_PRIOAUTO, _("Auto"));
635 m_menu->Append(MP_MENU_PRIO, _("Priority"), priomenu);
636 m_menu->Append(MP_CANCEL, _("Cancel"));
637 m_menu->Append(MP_STOP, _("&Stop"));
638 m_menu->Append(MP_PAUSE, _("&Pause"));
639 m_menu->Append(MP_RESUME, _("&Resume"));
640 m_menu->Append(MP_CLEARCOMPLETED, _("C&lear completed"));
641 //-----------------------------------------------------
642 m_menu->AppendSeparator();
643 //-----------------------------------------------------
644 wxMenu* extendedmenu = new wxMenu();
645 extendedmenu->Append(MP_SWAP_A4AF_TO_THIS,
646 _("Swap every A4AF to this file now"));
647 extendedmenu->AppendCheckItem(MP_SWAP_A4AF_TO_THIS_AUTO,
648 _("Swap every A4AF to this file (Auto)"));
649 //-----------------------------------------------------
650 extendedmenu->AppendSeparator();
651 //-----------------------------------------------------
652 extendedmenu->Append(MP_SWAP_A4AF_TO_ANY_OTHER,
653 _("Swap every A4AF to any other file now"));
654 //-----------------------------------------------------
655 m_menu->Append(MP_MENU_EXTD,
656 _("Extended Options"), extendedmenu);
657 //-----------------------------------------------------
658 m_menu->AppendSeparator();
659 //-----------------------------------------------------
661 m_menu->Append(MP_VIEW, _("Preview"));
662 m_menu->Append(MP_METINFO, _("Show file &details"));
663 m_menu->Append(MP_VIEWFILECOMMENTS, _("Show all comments"));
664 //-----------------------------------------------------
665 m_menu->AppendSeparator();
666 //-----------------------------------------------------
667 m_menu->Append(MP_GETMAGNETLINK,
668 _("Copy magnet URI to clipboard"));
669 m_menu->Append(MP_GETED2KLINK,
670 _("Copy eD2k &link to clipboard"));
671 m_menu->Append(MP_WS,
672 _("Copy feedback to clipboard"));
673 //-----------------------------------------------------
674 m_menu->AppendSeparator();
675 //-----------------------------------------------------
676 // Add dynamic entries
677 wxMenu *cats = new wxMenu(_("Category"));
678 if (theApp->glob_prefs->GetCatCount() > 1) {
679 for (uint32 i = 0; i < theApp->glob_prefs->GetCatCount(); i++) {
680 if ( i == 0 ) {
681 cats->Append( MP_ASSIGNCAT, _("unassign") );
682 } else {
683 cats->Append( MP_ASSIGNCAT + i,
684 theApp->glob_prefs->GetCategory(i)->title );
688 m_menu->Append(MP_MENU_CATS, _("Assign to category"), cats);
689 m_menu->Enable(MP_MENU_CATS, (theApp->glob_prefs->GetCatCount() > 1) );
691 CPartFile* file = item->GetFile();
692 // then set state
693 bool canStop;
694 bool canPause;
695 bool canCancel;
696 bool fileResumable;
697 if (file->GetStatus(true) != PS_ALLOCATING) {
698 const uint8_t fileStatus = file->GetStatus();
699 canStop =
700 (fileStatus != PS_ERROR) &&
701 (fileStatus != PS_COMPLETE) &&
702 (file->IsStopped() != true);
703 canPause = (file->GetStatus() != PS_PAUSED) && canStop;
704 fileResumable =
705 (fileStatus == PS_PAUSED) ||
706 (fileStatus == PS_ERROR) ||
707 (fileStatus == PS_INSUFFICIENT);
708 canCancel = fileStatus != PS_COMPLETE;
709 } else {
710 canStop = canPause = canCancel = fileResumable = false;
713 m_menu->Enable( MP_CANCEL, canCancel );
714 m_menu->Enable( MP_PAUSE, canPause );
715 m_menu->Enable( MP_STOP, canStop );
716 m_menu->Enable( MP_RESUME, fileResumable );
717 m_menu->Enable( MP_CLEARCOMPLETED, CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->IsEnabled() );
719 wxString view;
720 if (file->IsPartFile() && !file->IsCompleted()) {
721 view = CFormat(wxT("%s [%s]")) % _("Preview")
722 % file->GetPartMetFileName().RemoveExt();
723 } else if ( file->IsCompleted() ) {
724 view = _("&Open the file");
726 m_menu->SetLabel(MP_VIEW, view);
727 m_menu->Enable(MP_VIEW, file->PreviewAvailable());
729 FileRatingList ratingList;
730 item->GetFile()->GetRatingAndComments(ratingList);
731 m_menu->Enable(MP_VIEWFILECOMMENTS, !ratingList.empty());
733 m_menu->Check( MP_SWAP_A4AF_TO_THIS_AUTO, file->IsA4AFAuto() );
735 int priority = file->IsAutoDownPriority() ? PR_AUTO : file->GetDownPriority();
737 priomenu->Check( MP_PRIOHIGH, priority == PR_HIGH );
738 priomenu->Check( MP_PRIONORMAL, priority == PR_NORMAL );
739 priomenu->Check( MP_PRIOLOW, priority == PR_LOW );
740 priomenu->Check( MP_PRIOAUTO, priority == PR_AUTO );
742 m_menu->Enable( MP_MENU_EXTD, canPause );
744 bool autosort = thePrefs::AutoSortDownload(false);
745 PopupMenu(m_menu, evt.GetPoint());
746 thePrefs::AutoSortDownload(autosort);
748 delete m_menu;
749 m_menu = NULL;
753 void CDownloadListCtrl::OnMouseMiddleClick(wxListEvent& evt)
755 // Check if clicked item is selected. If not, unselect all and select it.
756 long index = CheckSelection(evt);
757 if ( index < 0 ) {
758 return;
761 bool autosort = thePrefs::AutoSortDownload(false);
762 CFileDetailDialog(this, ((FileCtrlItem_Struct*)GetItemData( index ))->GetFile()).ShowModal();
763 thePrefs::AutoSortDownload(autosort);
767 void CDownloadListCtrl::OnKeyPressed( wxKeyEvent& event )
769 // Check if delete was pressed
770 switch (event.GetKeyCode()) {
771 case WXK_NUMPAD_DELETE:
772 case WXK_DELETE: {
773 wxCommandEvent evt;
774 OnCancelFile( evt );
775 break;
777 case WXK_F2: {
778 ItemList files = ::GetSelectedItems( this );
779 if (files.size() == 1) {
780 CPartFile* file = files.front()->GetFile();
782 // Currently renaming of completed files causes problem with kad
783 if (file->IsPartFile()) {
784 wxString strNewName = ::wxGetTextFromUser(
785 _("Enter new name for this file:"),
786 _("File rename"), file->GetFileName().GetPrintable());
788 CPath newName = CPath(strNewName);
789 if (newName.IsOk() && (newName != file->GetFileName())) {
790 theApp->sharedfiles->RenameFile(file, newName);
794 break;
796 default:
797 event.Skip();
802 void CDownloadListCtrl::OnDrawItem(
803 int item, wxDC* dc, const wxRect& rect, const wxRect& rectHL, bool highlighted)
805 // Don't do any drawing if there's nobody to see it.
806 if ( !theApp->amuledlg->IsDialogVisible( CamuleDlg::DT_TRANSFER_WND ) ) {
807 return;
810 FileCtrlItem_Struct* content = (FileCtrlItem_Struct *)GetItemData(item);
812 // Define text-color and background
813 // and the border of the drawn area
814 if (highlighted) {
815 CMuleColour colour;
816 if (GetFocus()) {
817 dc->SetBackground(m_hilightBrush);
818 colour = m_hilightBrush.GetColour();
819 } else {
820 dc->SetBackground(m_hilightUnfocusBrush);
821 colour = m_hilightUnfocusBrush.GetColour();
823 dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
824 dc->SetPen( colour.Blend(65).GetPen() );
825 } else {
826 dc->SetBackground(*(wxTheBrushList->FindOrCreateBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX), wxSOLID)));
827 dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
828 dc->SetPen(*wxTRANSPARENT_PEN);
830 dc->SetBrush( dc->GetBackground() );
832 dc->DrawRectangle( rectHL.x, rectHL.y, rectHL.width, rectHL.height );
834 dc->SetPen(*wxTRANSPARENT_PEN);
836 if (!highlighted || !GetFocus() ) {
837 // If we have category, override textforeground with what category tells us.
838 CPartFile *file = content->GetFile();
839 if ( file->GetCategory() ) {
840 dc->SetTextForeground(CMuleColour(theApp->glob_prefs->GetCatColor(file->GetCategory())) );
844 // Various constant values we use
845 const int iTextOffset = ( rect.GetHeight() - dc->GetCharHeight() ) / 2;
846 const int iOffset = 4;
848 wxRect cur_rec( iOffset, rect.y, 0, rect.height );
849 for (int i = 0; i < GetColumnCount(); i++) {
850 wxListItem listitem;
851 GetColumn(i, listitem);
853 if (listitem.GetWidth() > 2*iOffset) {
854 cur_rec.width = listitem.GetWidth() - 2*iOffset;
856 // Make a copy of the current rectangle so we can apply specific tweaks
857 wxRect target_rec = cur_rec;
858 if ( i == ColumnProgress ) {
859 // Double the offset to make room for the cirle-marker
860 target_rec.x += iOffset;
861 target_rec.width -= iOffset;
862 } else {
863 // will ensure that text is about in the middle ;)
864 target_rec.y += iTextOffset;
867 // Draw the item
868 DrawFileItem(dc, i, target_rec, content);
871 // Increment to the next column
872 cur_rec.x += listitem.GetWidth();
877 void CDownloadListCtrl::DrawFileItem( wxDC* dc, int nColumn, const wxRect& rect, FileCtrlItem_Struct* item ) const
879 wxDCClipper clipper( *dc, rect.GetX(), rect.GetY(), rect.GetWidth(), rect.GetHeight() );
881 const CPartFile* file = item->GetFile();
883 // Used to contain the contenst of cells that dont need any fancy drawing, just text.
884 wxString text;
886 switch (nColumn) {
887 // Part Number
888 case ColumnPart: {
889 if (file->IsPartFile() && !file->IsCompleted()) {
890 text = file->GetPartMetFileName().RemoveAllExt().GetPrintable();
892 break;
894 // Filename
895 case ColumnFileName: {
896 wxString filename = file->GetFileName().GetPrintable();
898 if (file->HasRating() || file->HasComment()) {
899 int image = Client_CommentOnly_Smiley;
900 if (file->HasRating()) {
901 image = Client_InvalidRating_Smiley + file->UserRating() - 1;
904 wxASSERT(image >= Client_InvalidRating_Smiley);
905 wxASSERT(image <= Client_CommentOnly_Smiley);
907 int imgWidth = 16;
909 // it's already centered by OnDrawItem() ...
910 m_ImageList.Draw(image, *dc, rect.GetX(), rect.GetY() - 1,
911 wxIMAGELIST_DRAW_TRANSPARENT);
912 dc->DrawText(filename, rect.GetX() + imgWidth + 4, rect.GetY());
913 } else {
914 dc->DrawText(filename, rect.GetX(), rect.GetY());
916 break;
919 // Filesize
920 case ColumnSize:
921 text = CastItoXBytes( file->GetFileSize() );
922 break;
924 // Transferred
925 case ColumnTransferred:
926 text = CastItoXBytes( file->GetTransferred() );
927 break;
929 // Completed
930 case ColumnCompleted:
931 text = CastItoXBytes( file->GetCompletedSize() );
932 break;
934 // Speed
935 case ColumnSpeed:
936 if ( file->GetTransferingSrcCount() ) {
937 text = CFormat(_("%.1f kB/s")) % file->GetKBpsDown();
939 break;
941 // Progress
942 case ColumnProgress:{
943 if (thePrefs::ShowProgBar()) {
944 int iWidth = rect.GetWidth() - 2;
945 int iHeight = rect.GetHeight() - 2;
947 // DO NOT DRAW IT ALL THE TIME
948 uint32 dwTicks = GetTickCount();
950 wxMemoryDC cdcStatus;
952 if ( item->dwUpdated < dwTicks || !item->status || iWidth != item->status->GetWidth() ) {
953 if ( item->status == NULL) {
954 item->status = new wxBitmap(iWidth, iHeight);
955 } else if ( item->status->GetWidth() != iWidth ) {
956 // Only recreate if the size has changed
957 item->status->Create(iWidth, iHeight);
960 cdcStatus.SelectObject( *item->status );
962 if ( thePrefs::UseFlatBar() ) {
963 DrawFileStatusBar( file, &cdcStatus,
964 wxRect(0, 0, iWidth, iHeight), true);
965 } else {
966 DrawFileStatusBar( file, &cdcStatus,
967 wxRect(1, 1, iWidth - 2, iHeight - 2), false);
969 // Draw black border
970 cdcStatus.SetPen( *wxBLACK_PEN );
971 cdcStatus.SetBrush( *wxTRANSPARENT_BRUSH );
972 cdcStatus.DrawRectangle( 0, 0, iWidth, iHeight );
975 item->dwUpdated = dwTicks + 5000; // Plus five seconds
976 } else {
977 cdcStatus.SelectObject( *item->status );
980 dc->Blit( rect.GetX(), rect.GetY() + 1, iWidth, iHeight, &cdcStatus, 0, 0);
982 if (thePrefs::ShowPercent()) {
983 // Percentage of completing
984 // We strip anything below the first decimal point,
985 // to avoid Format doing roundings
986 float percent = floor( file->GetPercentCompleted() * 10.0f ) / 10.0f;
988 wxString buffer = CFormat(wxT("%.1f%%")) % percent;
989 int middlex = (2*rect.GetX() + rect.GetWidth()) >> 1;
990 int middley = (2*rect.GetY() + rect.GetHeight()) >> 1;
992 wxCoord textwidth, textheight;
994 dc->GetTextExtent(buffer, &textwidth, &textheight);
995 wxColour AktColor = dc->GetTextForeground();
996 if (thePrefs::ShowProgBar()) {
997 dc->SetTextForeground(*wxWHITE);
998 } else {
999 dc->SetTextForeground(*wxBLACK);
1001 dc->DrawText(buffer, middlex - (textwidth >> 1), middley - (textheight >> 1));
1002 dc->SetTextForeground(AktColor);
1006 break;
1009 // Sources
1010 case ColumnSources: {
1011 uint16 sc = file->GetSourceCount();
1012 uint16 ncsc = file->GetNotCurrentSourcesCount();
1013 if ( ncsc ) {
1014 text = CFormat(wxT("%i/%i")) % (sc - ncsc) % sc;
1015 } else {
1016 text = CFormat(wxT("%i")) % sc;
1019 if ( file->GetSrcA4AFCount() ) {
1020 text += CFormat(wxT("+%i")) % file->GetSrcA4AFCount();
1023 if ( file->GetTransferingSrcCount() ) {
1024 text += CFormat(wxT(" (%i)")) % file->GetTransferingSrcCount();
1027 break;
1030 // Priority
1031 case ColumnPriority:
1032 text = PriorityToStr( file->GetDownPriority(), file->IsAutoDownPriority() );
1033 break;
1035 // File-status
1036 case ColumnStatus:
1037 text = file->getPartfileStatus();
1038 break;
1040 // Remaining
1041 case ColumnTimeRemaining: {
1042 if ((file->GetStatus() != PS_COMPLETING) && file->IsPartFile()) {
1043 uint64 remainSize = file->GetFileSize() - file->GetCompletedSize();
1044 sint32 remainTime = file->getTimeRemaining();
1046 if (remainTime >= 0) {
1047 text = CastSecondsToHM(remainTime);
1048 } else {
1049 text = _("Unknown");
1052 text += wxT(" (") + CastItoXBytes(remainSize) + wxT(")");
1054 break;
1057 // Last seen completed
1058 case ColumnLastSeenComplete: {
1059 if ( file->lastseencomplete ) {
1060 text = wxDateTime( file->lastseencomplete ).Format( _("%y/%m/%d %H:%M:%S") );
1061 } else {
1062 text = _("Unknown");
1064 break;
1067 // Last received
1068 case ColumnLastReception: {
1069 const time_t lastReceived = file->GetLastChangeDatetime();
1070 if (lastReceived) {
1071 text = wxDateTime(lastReceived).Format( _("%y/%m/%d %H:%M:%S") );
1072 } else {
1073 text = _("Unknown");
1078 if ( !text.IsEmpty() ) {
1079 dc->DrawText( text, rect.GetX(), rect.GetY() );
1083 wxString CDownloadListCtrl::GetTTSText(unsigned item) const
1085 return ((FileCtrlItem_Struct*)GetItemData(item))->GetFile()->GetFileName().GetPrintable();
1089 int CDownloadListCtrl::SortProc(wxUIntPtr param1, wxUIntPtr param2, long sortData)
1091 FileCtrlItem_Struct* item1 = (FileCtrlItem_Struct*)param1;
1092 FileCtrlItem_Struct* item2 = (FileCtrlItem_Struct*)param2;
1094 int sortMod = (sortData & CMuleListCtrl::SORT_DES) ? -1 : 1;
1095 sortData &= CMuleListCtrl::COLUMN_MASK;
1097 // We modify the result so that it matches with ascending or decending
1098 return sortMod * Compare( item1->GetFile(), item2->GetFile(), sortData);
1102 int CDownloadListCtrl::Compare( const CPartFile* file1, const CPartFile* file2, long lParamSort)
1104 int result = 0;
1106 switch (lParamSort) {
1107 // Sort by part number
1108 case ColumnPart:
1109 result = CmpAny(
1110 file1->GetPartMetFileName().RemoveAllExt(),
1111 file2->GetPartMetFileName().RemoveAllExt() );
1112 break;
1114 // Sort by filename
1115 case ColumnFileName:
1116 result = CmpAny(
1117 file1->GetFileName(),
1118 file2->GetFileName() );
1119 break;
1121 // Sort by size
1122 case ColumnSize:
1123 result = CmpAny(
1124 file1->GetFileSize(),
1125 file2->GetFileSize() );
1126 break;
1128 // Sort by transferred
1129 case ColumnTransferred:
1130 result = CmpAny(
1131 file1->GetTransferred(),
1132 file2->GetTransferred() );
1133 break;
1135 // Sort by completed
1136 case ColumnCompleted:
1137 result = CmpAny(
1138 file1->GetCompletedSize(),
1139 file2->GetCompletedSize() );
1140 break;
1142 // Sort by speed
1143 case ColumnSpeed:
1144 result = CmpAny(
1145 file1->GetKBpsDown() * 1024,
1146 file2->GetKBpsDown() * 1024 );
1147 break;
1149 // Sort by percentage completed
1150 case ColumnProgress:
1151 result = CmpAny(
1152 file1->GetPercentCompleted(),
1153 file2->GetPercentCompleted() );
1154 break;
1156 // Sort by number of sources
1157 case ColumnSources:
1158 result = CmpAny(
1159 file1->GetSourceCount(),
1160 file2->GetSourceCount() );
1161 break;
1163 // Sort by priority
1164 case ColumnPriority:
1165 result = CmpAny(
1166 file1->GetDownPriority(),
1167 file2->GetDownPriority() );
1168 break;
1170 // Sort by status
1171 case ColumnStatus:
1172 result = CmpAny(
1173 file1->getPartfileStatusRang(),
1174 file2->getPartfileStatusRang() );
1175 break;
1177 // Sort by remaining time
1178 case ColumnTimeRemaining:
1179 if (file1->getTimeRemaining() == -1) {
1180 if (file2->getTimeRemaining() == -1) {
1181 result = 0;
1182 } else {
1183 result = -1;
1185 } else {
1186 if (file2->getTimeRemaining() == -1) {
1187 result = 1;
1188 } else {
1189 result = CmpAny(
1190 file1->getTimeRemaining(),
1191 file2->getTimeRemaining() );
1194 break;
1196 // Sort by last seen complete
1197 case ColumnLastSeenComplete:
1198 result = CmpAny(
1199 file1->lastseencomplete,
1200 file2->lastseencomplete );
1201 break;
1203 // Sort by last reception
1204 case ColumnLastReception:
1205 result = CmpAny(
1206 file1->GetLastChangeDatetime(),
1207 file2->GetLastChangeDatetime() );
1208 break;
1211 return result;
1214 void CDownloadListCtrl::ClearCompleted()
1216 CastByID(ID_BTNCLRCOMPL, GetParent(), wxButton)->Enable(false);
1218 // Search for completed files
1219 ListOfUInts32 toClear;
1220 for ( ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); ) {
1221 FileCtrlItem_Struct* item = it->second; ++it;
1223 CPartFile* file = item->GetFile();
1225 if (file->IsCompleted() && file->CheckShowItemInGivenCat(m_category)) {
1226 toClear.push_back(file->ECID());
1229 if (!toClear.empty()) {
1230 theApp->downloadqueue->ClearCompleted(toClear);
1235 void CDownloadListCtrl::ShowFilesCount( int diff )
1237 m_filecount += diff;
1239 wxStaticText* label = CastByName( wxT("downloadsLabel"), GetParent(), wxStaticText );
1241 label->SetLabel(CFormat(_("Downloads (%i)")) % m_filecount);
1242 label->GetParent()->Layout();
1246 static const CMuleColour crHave(104, 104, 104);
1247 static const CMuleColour crFlatHave(0, 0, 0);
1249 static const CMuleColour crPending(255, 208, 0);
1250 static const CMuleColour crFlatPending(255, 255, 100);
1252 static const CMuleColour crProgress(0, 224, 0);
1253 static const CMuleColour crFlatProgress(0, 150, 0);
1255 static const CMuleColour crMissing(255, 0, 0);
1257 void CDownloadListCtrl::DrawFileStatusBar(
1258 const CPartFile* file, wxDC* dc, const wxRect& rect, bool bFlat ) const
1260 static CBarShader s_ChunkBar(16);
1262 s_ChunkBar.SetHeight(rect.height);
1263 s_ChunkBar.SetWidth(rect.width);
1264 s_ChunkBar.SetFileSize( file->GetFileSize() );
1265 s_ChunkBar.Set3dDepth( thePrefs::Get3DDepth() );
1267 if ( file->IsCompleted() || file->GetStatus() == PS_COMPLETING ) {
1268 s_ChunkBar.Fill( bFlat ? crFlatProgress : crProgress );
1269 s_ChunkBar.Draw(dc, rect.x, rect.y, bFlat);
1270 return;
1273 // Part availability ( of missing parts )
1274 const CGapList& gaplist = file->GetGapList();
1275 CGapList::const_iterator it = gaplist.begin();
1276 uint64 lastGapEnd = 0;
1277 CMuleColour colour;
1279 for (; it != gaplist.end(); ++it) {
1281 // Start position
1282 uint32 start = ( it.start() / PARTSIZE );
1283 // fill the Have-Part (between this gap and the last)
1284 if (it.start()) {
1285 s_ChunkBar.FillRange(lastGapEnd + 1, it.start() - 1, bFlat ? crFlatHave : crHave);
1287 lastGapEnd = it.end();
1288 // End position
1289 uint32 end = ( it.end() / PARTSIZE ) + 1;
1291 // Avoid going past the filesize. Dunno if this can happen, but the old code did check.
1292 if ( end > file->GetPartCount() ) {
1293 end = file->GetPartCount();
1296 // Place each gap, one PART at a time
1297 for ( uint64 i = start; i < end; ++i ) {
1298 if ( i < file->m_SrcpartFrequency.size() && file->m_SrcpartFrequency[i]) {
1299 int blue = 210 - ( 22 * ( file->m_SrcpartFrequency[i] - 1 ) );
1300 colour.Set(0, ( blue < 0 ? 0 : blue ), 255 );
1301 } else {
1302 colour = crMissing;
1305 if ( file->IsStopped() ) {
1306 colour.Blend(50);
1309 uint64 gap_begin = ( i == start ? it.start() : PARTSIZE * i );
1310 uint64 gap_end = ( i == end - 1 ? it.end() : PARTSIZE * ( i + 1 ) - 1 );
1312 s_ChunkBar.FillRange( gap_begin, gap_end, colour);
1316 // fill the last Have-Part (between this gap and the last)
1317 s_ChunkBar.FillRange(lastGapEnd + 1, file->GetFileSize() - 1, bFlat ? crFlatHave : crHave);
1319 // Pending parts
1320 const CPartFile::CReqBlockPtrList& requestedblocks_list = file->GetRequestedBlockList();
1321 CPartFile::CReqBlockPtrList::const_iterator it2 = requestedblocks_list.begin();
1322 // adjacing pending parts must be joined to avoid bright lines between them
1323 uint64 lastStartOffset = 0;
1324 uint64 lastEndOffset = 0;
1326 colour = bFlat ? crFlatPending : crPending;
1328 if ( file->IsStopped() ) {
1329 colour.Blend(50);
1332 for (; it2 != requestedblocks_list.end(); ++it2) {
1334 if ((*it2)->StartOffset > lastEndOffset + 1) {
1335 // not adjacing, draw last block
1336 s_ChunkBar.FillRange(lastStartOffset, lastEndOffset, colour);
1337 lastStartOffset = (*it2)->StartOffset;
1338 lastEndOffset = (*it2)->EndOffset;
1339 } else {
1340 // adjacing, grow block
1341 lastEndOffset = (*it2)->EndOffset;
1345 s_ChunkBar.FillRange(lastStartOffset, lastEndOffset, colour);
1348 // Draw the progress-bar
1349 s_ChunkBar.Draw( dc, rect.x, rect.y, bFlat );
1352 // Green progressbar width
1353 int width = (int)(( (float)rect.width / (float)file->GetFileSize() ) *
1354 file->GetCompletedSize() );
1356 if ( bFlat ) {
1357 dc->SetBrush( crFlatProgress.GetBrush() );
1359 dc->DrawRectangle( rect.x, rect.y, width, 3 );
1360 } else {
1361 // Draw the two black lines for 3d-effect
1362 dc->SetPen( *wxBLACK_PEN );
1363 dc->DrawLine( rect.x, rect.y + 0, rect.x + width, rect.y + 0 );
1364 dc->DrawLine( rect.x, rect.y + 2, rect.x + width, rect.y + 2 );
1366 // Draw the green line
1367 dc->SetPen( *(wxThePenList->FindOrCreatePen( crProgress , 1, wxSOLID ) ));
1368 dc->DrawLine( rect.x, rect.y + 1, rect.x + width, rect.y + 1 );
1372 #ifdef __WXMSW__
1373 # define QUOTE wxT("\"")
1374 #else
1375 # define QUOTE wxT("\'")
1376 #endif
1378 void CDownloadListCtrl::PreviewFile(CPartFile* file)
1380 wxString command;
1381 // If no player set in preferences, use mplayer.
1382 // And please, do a warning also :P
1383 if (thePrefs::GetVideoPlayer().IsEmpty()) {
1384 wxMessageBox(_(
1385 "To prevent this warning to show up in every preview,\nset your preferred video player in preferences (default is mplayer)."),
1386 _("File preview"), wxOK, this);
1387 // Since newer versions for some reason mplayer does not automatically
1388 // select video output device and needs a parameter, go figure...
1389 command = wxT("xterm -T \"aMule Preview\" -iconic -e mplayer ") QUOTE wxT("$file") QUOTE;
1390 } else {
1391 command = thePrefs::GetVideoPlayer();
1394 wxString partFile; // File name with full path
1395 wxString partName; // File name only, without path
1397 // Check if we are (pre)viewing a completed file or not
1398 if (!file->IsCompleted()) {
1399 // Remove the .met and see if out video player specifiation uses the magic string
1400 partName = file->GetPartMetFileName().RemoveExt().GetRaw();
1401 partFile = thePrefs::GetTempDir().JoinPaths(file->GetPartMetFileName().RemoveExt()).GetRaw();
1402 } else {
1403 // This is a complete file
1404 // FIXME: This is probably not going to work if the filenames are mangled ...
1405 partName = file->GetFileName().GetRaw();
1406 partFile = file->GetFullName().GetRaw();
1409 // Compatibility with old behaviour
1410 if (!command.Replace(wxT("$file"), wxT("%PARTFILE"))) {
1411 if ((command.Find(wxT("%PARTFILE")) == wxNOT_FOUND) && (command.Find(wxT("%PARTNAME")) == wxNOT_FOUND)) {
1412 // No magic string, so we just append the filename to the player command
1413 // Need to use quotes in case filename contains spaces
1414 command << wxT(" ") << QUOTE << wxT("%PARTFILE") << QUOTE;
1418 #ifndef __WXMSW__
1419 // We have to escape quote characters in the file name, otherwise arbitrary
1420 // options could be passed to the player.
1421 partFile.Replace(QUOTE, wxT("\\") QUOTE);
1422 partName.Replace(QUOTE, wxT("\\") QUOTE);
1423 #endif
1425 command.Replace(wxT("%PARTFILE"), partFile);
1426 command.Replace(wxT("%PARTNAME"), partName);
1428 // We can't use wxShell here, it blocks the app
1429 CTerminationProcess *p = new CTerminationProcess(command);
1430 int ret = wxExecute(command, wxEXEC_ASYNC, p);
1431 bool ok = ret > 0;
1432 if (!ok) {
1433 delete p;
1434 AddLogLineC(CFormat( _("ERROR: Failed to execute external media-player! Command: `%s'") ) %
1435 command );
1438 // File_checked_for_headers