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