2 // This file is part of the aMule Project.
4 // Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
6 // Any parts of this program derived from the xMule, lMule or eMule project,
7 // or contributed by third-party developers are copyrighted by their
10 // This program is free software; you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License as published by
12 // the Free Software Foundation; either version 2 of the License, or
13 // (at your option) any later version.
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 #include "GenericClientListCtrl.h"
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 "BitVector.h"
34 #include "ClientDetailDialog.h" // Needed for CClientDetailDialog
35 #include "ChatWnd.h" // Needed for CChatWnd
36 #include "CommentDialogLst.h" // Needed for CCommentDialogLst
37 #include "DataToText.h" // Needed for PriorityToStr
38 #include "FileDetailDialog.h" // Needed for CFileDetailDialog
39 #include "GetTickCount.h" // Needed for GetTickCount
40 #include "GuiEvents.h" // Needed for CoreNotify_*
41 #ifdef ENABLE_IP2COUNTRY
42 #include "IP2Country.h" // Needed for IP2Country
45 #include "muuli_wdr.h" // Needed for ID_DLOADLIST
46 #include "PartFile.h" // Needed for CPartFile
47 #include "Preferences.h"
48 #include "SharedFileList.h" // Needed for CSharedFileList
49 #include "TerminationProcess.h" // Needed for CTerminationProcess
50 #include "ClientRef.h" // Needed for CClientRef
51 #include "FriendList.h"
53 struct ClientCtrlItem_Struct
55 ClientCtrlItem_Struct()
59 m_type(UNAVAILABLE_SOURCE
)
62 ~ClientCtrlItem_Struct() {
66 SourceItemType
GetType() const {
70 CKnownFile
* GetOwner() const {
75 CClientRef
& GetSource() {
79 void SetContents(CKnownFile
* owner
, const CClientRef
& source
, SourceItemType type
) {
81 m_sourceValue
= source
;
85 void SetType(SourceItemType type
) { m_type
= type
; }
92 CClientRef m_sourceValue
;
93 SourceItemType m_type
;
96 #define m_ImageList theApp->amuledlg->m_imagelist
98 BEGIN_EVENT_TABLE(CGenericClientListCtrl
, CMuleListCtrl
)
99 EVT_LIST_ITEM_ACTIVATED(wxID_ANY
, CGenericClientListCtrl::OnItemActivated
)
100 EVT_LIST_ITEM_RIGHT_CLICK(wxID_ANY
, CGenericClientListCtrl::OnMouseRightClick
)
101 EVT_LIST_ITEM_MIDDLE_CLICK(wxID_ANY
, CGenericClientListCtrl::OnMouseMiddleClick
)
103 EVT_CHAR( CGenericClientListCtrl::OnKeyPressed
)
105 EVT_MENU( MP_CHANGE2FILE
, CGenericClientListCtrl::OnSwapSource
)
106 EVT_MENU( MP_SHOWLIST
, CGenericClientListCtrl::OnViewFiles
)
107 EVT_MENU( MP_ADDFRIEND
, CGenericClientListCtrl::OnAddFriend
)
108 EVT_MENU( MP_FRIENDSLOT
, CGenericClientListCtrl::OnSetFriendslot
)
109 EVT_MENU( MP_SENDMESSAGE
, CGenericClientListCtrl::OnSendMessage
)
110 EVT_MENU( MP_DETAIL
, CGenericClientListCtrl::OnViewClientInfo
)
113 //! This listtype is used when gathering the selected items.
114 typedef std::list
<ClientCtrlItem_Struct
*> ItemList
;
116 CGenericClientListCtrl::CGenericClientListCtrl(
117 const wxString
& tablename
,
118 wxWindow
*parent
, wxWindowID winid
, const wxPoint
& pos
, const wxSize
& size
,
119 long style
, const wxValidator
& validator
, const wxString
& name
)
121 CMuleListCtrl( parent
, winid
, pos
, size
, style
| wxLC_OWNERDRAW
| wxLC_VRULES
| wxLC_HRULES
, validator
, name
),
122 m_columndata(0, NULL
)
124 // Setting the sorter function must be done in the derived class, to use the translation function correctly.
125 // SetSortFunc( SortProc );
127 // Set the table-name (for loading and saving preferences).
128 SetTableName( tablename
);
133 m_hilightBrush
= CMuleColour(wxSYS_COLOUR_HIGHLIGHT
).Blend(125).GetBrush();
135 m_hilightUnfocusBrush
= CMuleColour(wxSYS_COLOUR_BTNSHADOW
).Blend(125).GetBrush();
140 wxString
CGenericClientListCtrl::TranslateCIDToName(GenericColumnEnum cid
)
148 case ColumnUserDownloaded
:
151 case ColumnUserUploaded
:
154 case ColumnUserSpeedDown
:
157 case ColumnUserSpeedUp
:
160 case ColumnUserProgress
:
163 case ColumnUserAvailable
:
166 case ColumnUserVersion
:
169 case ColumnUserQueueRankLocal
:
172 case ColumnUserQueueRankRemote
:
175 case ColumnUserOrigin
:
178 case ColumnUserFileNameDownload
:
181 case ColumnUserFileNameUpload
:
184 case ColumnUserFileNameDownloadRemote
:
187 case ColumnUserSharedFiles
:
199 void CGenericClientListCtrl::InitColumnData()
201 if (!m_columndata
.n_columns
) {
202 throw wxString(wxT("CRITICAL: Initialization of the column data lacks subclass information"));
205 for (int i
= 0; i
< m_columndata
.n_columns
; ++i
) {
206 InsertColumn( i
, wxGetTranslation(m_columndata
.columns
[i
].title
), wxLIST_FORMAT_LEFT
, m_columndata
.columns
[i
].width
, TranslateCIDToName(m_columndata
.columns
[i
].cid
));
212 CGenericClientListCtrl::~CGenericClientListCtrl()
214 while ( !m_ListItems
.empty() ) {
215 delete m_ListItems
.begin()->second
;
216 m_ListItems
.erase( m_ListItems
.begin() );
219 void CGenericClientListCtrl::RawAddSource(CKnownFile
* owner
, CClientRef source
, SourceItemType type
)
221 ClientCtrlItem_Struct
* newitem
= new ClientCtrlItem_Struct
;
222 newitem
->SetContents(owner
, source
, type
);
224 m_ListItems
.insert( ListItemsPair(source
.ECID(), newitem
) );
226 long item
= InsertItem( GetItemCount(), wxEmptyString
);
227 SetItemPtrData( item
, reinterpret_cast<wxUIntPtr
>(newitem
) );
228 SetItemBackgroundColour( item
, GetBackgroundColour() );
231 void CGenericClientListCtrl::AddSource(CKnownFile
* owner
, const CClientRef
& source
, SourceItemType type
)
233 wxCHECK_RET(owner
, wxT("NULL owner in CGenericClientListCtrl::AddSource"));
234 wxCHECK_RET(source
.IsLinked(), wxT("Unlinked source in CGenericClientListCtrl::AddSource"));
236 // Update the other instances of this source
238 ListIteratorPair rangeIt
= m_ListItems
.equal_range(source
.ECID());
239 for ( ListItems::iterator it
= rangeIt
.first
; it
!= rangeIt
.second
; ++it
) {
240 ClientCtrlItem_Struct
* cur_item
= it
->second
;
242 // Check if this source has been already added to this file => to be sure
243 if ( cur_item
->GetOwner() == owner
) {
244 // Update this instance with its new setting
245 if ((type
== A4AF_SOURCE
) &&
246 cur_item
->GetSource().GetRequestFile()
247 && std::binary_search(m_knownfiles
.begin(), m_knownfiles
.end(), cur_item
->GetSource().GetRequestFile())) {
248 cur_item
->SetContents(owner
, source
, AVAILABLE_SOURCE
);
250 cur_item
->SetContents(owner
, source
, type
);
252 cur_item
->dwUpdated
= 0;
254 } else if ( type
== AVAILABLE_SOURCE
) {
255 // The state 'Available' is exclusive
256 cur_item
->SetContents(cur_item
->GetOwner(), source
, A4AF_SOURCE
);
257 cur_item
->dwUpdated
= 0;
265 if ( std::binary_search(m_knownfiles
.begin(), m_knownfiles
.end(), owner
) ) {
266 RawAddSource(owner
, source
, type
);
268 ShowSourcesCount( 1 );
272 void CGenericClientListCtrl::RawRemoveSource( ListItems::iterator
& it
)
274 ClientCtrlItem_Struct
* item
= it
->second
;
276 long index
= FindItem( -1, reinterpret_cast<wxUIntPtr
>(item
) );
284 // Remove it from the m_ListItems
285 m_ListItems
.erase( it
);
288 void CGenericClientListCtrl::RemoveSource(uint32 source
, const CKnownFile
* owner
)
290 // A NULL owner means remove it no matter what.
291 wxCHECK_RET(source
, wxT("NULL source in CGenericClientListCtrl::RemoveSource"));
293 // Retrieve all entries matching the source
294 ListIteratorPair rangeIt
= m_ListItems
.equal_range(source
);
296 int removedItems
= 0;
298 for ( ListItems::iterator it
= rangeIt
.first
; it
!= rangeIt
.second
; /* no ++, it happens later */) {
299 ListItems::iterator tmp
= it
++;
301 if ( owner
== NULL
|| owner
== tmp
->second
->GetOwner() ) {
303 RawRemoveSource(tmp
);
309 ShowSourcesCount((-1) * removedItems
);
312 void CGenericClientListCtrl::UpdateItem(uint32 toupdate
, SourceItemType type
)
314 // Retrieve all entries matching the source
315 ListIteratorPair rangeIt
= m_ListItems
.equal_range( toupdate
);
317 if ( rangeIt
.first
!= rangeIt
.second
) {
318 // Visible lines, default to all because not all platforms
319 // support the GetVisibleLines function
320 long first
= 0, last
= GetItemCount();
323 // Get visible lines if we need them
324 GetVisibleLines( &first
, &last
);
327 for ( ListItems::iterator it
= rangeIt
.first
; it
!= rangeIt
.second
; ++it
) {
328 ClientCtrlItem_Struct
* item
= it
->second
;
330 long index
= FindItem( -1, reinterpret_cast<wxUIntPtr
>(item
) );
332 if ((type
== A4AF_SOURCE
) &&
333 item
->GetSource().GetRequestFile()
334 && std::binary_search(m_knownfiles
.begin(), m_knownfiles
.end(), item
->GetSource().GetRequestFile())) {
336 item
->SetType(AVAILABLE_SOURCE
);
343 // Only update visible lines
344 if ( index
>= first
&& index
<= last
) {
345 RefreshItem( index
);
351 void CGenericClientListCtrl::ShowSources( const CKnownFileVector
& files
)
355 // The stored vector is sorted, as is the received one, so we can use binary_search
357 for (unsigned i
= 0; i
< m_knownfiles
.size(); ++i
) {
358 // Files that are not in the new list must have show set to false.
359 if (!std::binary_search(files
.begin(), files
.end(), m_knownfiles
[i
])) {
360 SetShowSources(m_knownfiles
[i
], false);
364 // This will call again SetShowSources in files that were in both vectors. Right now
365 // that function is just a inline setter, so any way to prevent it would be wasteful,
366 // but this must be reviewed if that fact changes.
368 for (unsigned i
= 0; i
< files
.size(); ++i
) {
369 SetShowSources(files
[i
], true);
372 // We must cleanup sources that are not in the received files.
376 for (ListItems::iterator it
= m_ListItems
.begin(); it
!= m_ListItems
.end(); /* no ++, it happens later */) {
377 ListItems::iterator tmp
= it
++;
378 ClientCtrlItem_Struct
* item
= tmp
->second
;
379 if (!std::binary_search(files
.begin(), files
.end(), item
->GetOwner())) {
380 // Remove it from the m_ListItems
381 RawRemoveSource(tmp
);
386 for (unsigned i
= 0; i
< files
.size(); ++i
) {
388 // Only those that weren't showing already
389 if (!std::binary_search(m_knownfiles
.begin(), m_knownfiles
.end(), files
[i
])) {
391 CKnownFile
* file
= files
[i
];
393 wxASSERT_MSG(file
, wxT("NULL file in CGenericClientListCtrl::ShowSources"));
397 CKnownFile::SourceSet::const_iterator it
;
399 if (IsShowingDownloadSources()) {
400 const CKnownFile::SourceSet
& normSources
= (dynamic_cast<CPartFile
*>( file
))->GetSourceList();
401 const CKnownFile::SourceSet
& a4afSources
= (dynamic_cast<CPartFile
*>( file
))->GetA4AFList();
403 // Adding normal sources
404 for ( it
= normSources
.begin(); it
!= normSources
.end(); ++it
) {
405 switch (it
->GetDownloadState()) {
408 RawAddSource( file
, *it
, AVAILABLE_SOURCE
);
413 RawAddSource( file
, *it
, UNAVAILABLE_SOURCE
);
418 // Adding A4AF sources
419 for ( it
= a4afSources
.begin(); it
!= a4afSources
.end(); ++it
) {
420 // Only add if the A4AF file is not in the shown list.
421 if (!std::binary_search(files
.begin(), files
.end(), it
->GetRequestFile())) {
422 RawAddSource( file
, *it
, A4AF_SOURCE
);
428 const CKnownFile::SourceSet
& sources
= file
->m_ClientUploadList
;
429 for ( it
= sources
.begin(); it
!= sources
.end(); ++it
) {
430 switch (it
->GetUploadState()) {
432 case US_ONUPLOADQUEUE
:
433 RawAddSource( file
, *it
, AVAILABLE_SOURCE
);
438 RawAddSource( file
, *it
, UNAVAILABLE_SOURCE
);
447 m_knownfiles
= files
;
449 ShowSourcesCount( itemDiff
);
457 * Helper-function: This function is used to gather selected items.
459 * @param list A pointer to the list to gather items from.
460 * @return A list containing the selected items of the choosen types.
462 static ItemList
GetSelectedItems( CGenericClientListCtrl
* list
)
466 long index
= list
->GetNextItem( -1, wxLIST_NEXT_ALL
, wxLIST_STATE_SELECTED
);
468 while ( index
> -1 ) {
469 ClientCtrlItem_Struct
* item
= reinterpret_cast<ClientCtrlItem_Struct
*>(list
->GetItemData( index
));
471 results
.push_back( item
);
473 index
= list
->GetNextItem( index
, wxLIST_NEXT_ALL
, wxLIST_STATE_SELECTED
);
479 void CGenericClientListCtrl::OnSwapSource( wxCommandEvent
& WXUNUSED(event
) )
481 ItemList sources
= ::GetSelectedItems( this );
483 for ( ItemList::iterator it
= sources
.begin(); it
!= sources
.end(); ++it
) {
484 CKnownFile
* kf
= (*it
)->GetOwner();
485 if (!kf
->IsPartFile()) {
486 wxFAIL_MSG(wxT("File is not a partfile when swapping sources"));
489 (*it
)->GetSource().SwapToAnotherFile( true, false, false, dynamic_cast<CPartFile
*>(kf
));
494 void CGenericClientListCtrl::OnViewFiles( wxCommandEvent
& WXUNUSED(event
) )
496 ItemList sources
= ::GetSelectedItems( this );
498 if ( sources
.size() == 1 ) {
499 sources
.front()->GetSource().RequestSharedFileList();
504 void CGenericClientListCtrl::OnAddFriend( wxCommandEvent
& WXUNUSED(event
) )
506 ItemList sources
= ::GetSelectedItems( this );
508 for ( ItemList::iterator it
= sources
.begin(); it
!= sources
.end(); ++it
) {
509 CClientRef
&client
= (*it
)->GetSource();
510 if (client
.IsFriend()) {
511 theApp
->friendlist
->RemoveFriend(client
.GetFriend());
513 theApp
->friendlist
->AddFriend(client
);
519 void CGenericClientListCtrl::OnSetFriendslot(wxCommandEvent
& evt
)
521 ItemList sources
= ::GetSelectedItems( this );
523 ItemList::iterator it
= sources
.begin();
524 if (it
!= sources
.end()) {
525 CClientRef
&client
= (*it
)->GetSource();
526 theApp
->friendlist
->SetFriendSlot(client
.GetFriend(), evt
.IsChecked());
529 if (it
!= sources
.end()) {
530 wxMessageBox(_("You are not allowed to set more than one friendslot.\n Only one slot was assigned."), _("Multiple selection"), wxOK
| wxICON_ERROR
, this);
535 void CGenericClientListCtrl::OnSendMessage( wxCommandEvent
& WXUNUSED(event
) )
537 ItemList sources
= ::GetSelectedItems( this );
539 if ( sources
.size() == 1 ) {
540 CClientRef
& source
= (sources
.front())->GetSource();
542 // These values are cached, since calling wxGetTextFromUser will
543 // start an event-loop, in which the client may be deleted.
544 wxString userName
= source
.GetUserName();
545 uint64 userID
= GUI_ID(source
.GetIP(), source
.GetUserPort());
547 wxString message
= ::wxGetTextFromUser(_("Send message to user"),
548 _("Message to send:"));
549 if ( !message
.IsEmpty() ) {
550 theApp
->amuledlg
->m_chatwnd
->SendMessage(message
, userName
, userID
);
556 void CGenericClientListCtrl::OnViewClientInfo( wxCommandEvent
& WXUNUSED(event
) )
558 ItemList sources
= ::GetSelectedItems( this );
560 if ( sources
.size() == 1 ) {
561 CClientDetailDialog( this, sources
.front()->GetSource() ).ShowModal();
566 void CGenericClientListCtrl::OnItemActivated( wxListEvent
& evt
)
568 CClientDetailDialog( this, reinterpret_cast<ClientCtrlItem_Struct
*>(GetItemData( evt
.GetIndex()))->GetSource() ).ShowModal();
572 void CGenericClientListCtrl::OnMouseRightClick(wxListEvent
& evt
)
574 long index
= CheckSelection(evt
);
582 ClientCtrlItem_Struct
* item
= reinterpret_cast<ClientCtrlItem_Struct
*>(GetItemData( index
));
583 CClientRef
& client
= item
->GetSource();
585 m_menu
= new wxMenu(wxT("Clients"));
586 m_menu
->Append(MP_DETAIL
, _("Show &Details"));
587 m_menu
->Append(MP_ADDFRIEND
, client
.IsFriend() ? _("Remove from friends") : _("Add to Friends"));
589 m_menu
->AppendCheckItem(MP_FRIENDSLOT
, _("Establish Friend Slot"));
590 if (client
.IsFriend()) {
591 m_menu
->Enable(MP_FRIENDSLOT
, true);
592 m_menu
->Check(MP_FRIENDSLOT
, client
.GetFriendSlot());
594 m_menu
->Enable(MP_FRIENDSLOT
, false);
597 m_menu
->Append(MP_SHOWLIST
, _("View Files"));
598 m_menu
->Append(MP_SENDMESSAGE
, _("Send message"));
600 m_menu
->Append(MP_CHANGE2FILE
, _("Swap to this file"));
602 // Only enable the Swap option for A4AF sources
603 m_menu
->Enable(MP_CHANGE2FILE
, (item
->GetType() == A4AF_SOURCE
));
604 // We need a valid IP if we are to message the client
605 m_menu
->Enable(MP_SENDMESSAGE
, (client
.GetIP() != 0));
607 m_menu
->Enable(MP_SHOWLIST
, !client
.HasDisabledSharedFiles());
609 PopupMenu(m_menu
, evt
.GetPoint());
617 void CGenericClientListCtrl::OnMouseMiddleClick(wxListEvent
& evt
)
619 // Check if clicked item is selected. If not, unselect all and select it.
620 long index
= CheckSelection(evt
);
625 CClientDetailDialog(this, reinterpret_cast<ClientCtrlItem_Struct
*>(GetItemData( index
))->GetSource()).ShowModal();
629 void CGenericClientListCtrl::OnKeyPressed( wxKeyEvent
& event
)
631 // No actions right now.
632 //switch (event.GetKeyCode()) {
639 void CGenericClientListCtrl::OnDrawItem(
640 int item
, wxDC
* dc
, const wxRect
& rect
, const wxRect
& rectHL
, bool highlighted
)
642 // Don't do any drawing if there's nobody to see it.
643 if ( !theApp
->amuledlg
->IsDialogVisible( GetParentDialog() ) ) {
647 ClientCtrlItem_Struct
* content
= reinterpret_cast<ClientCtrlItem_Struct
*>(GetItemData(item
));
649 // Define text-color and background
650 // and the border of the drawn area
654 dc
->SetBackground(m_hilightBrush
);
655 colour
= m_hilightBrush
.GetColour();
657 dc
->SetBackground(m_hilightUnfocusBrush
);
658 colour
= m_hilightUnfocusBrush
.GetColour();
660 dc
->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT
));
661 dc
->SetPen( colour
.Blend(65).GetPen() );
663 dc
->SetBackground(*(wxTheBrushList
->FindOrCreateBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX
), wxSOLID
)));
664 dc
->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT
));
665 dc
->SetPen(*wxTRANSPARENT_PEN
);
667 dc
->SetBrush( dc
->GetBackground() );
669 dc
->DrawRectangle( rectHL
.x
, rectHL
.y
, rectHL
.width
, rectHL
.height
);
671 dc
->SetPen(*wxTRANSPARENT_PEN
);
673 // Various constant values we use
674 const int iTextOffset
= (( rect
.GetHeight() - dc
->GetCharHeight() ) / 2) + 1 /* Fixes rounding in the centering math, much easier than floor() */;
675 const int iOffset
= 2;
676 wxASSERT(m_ImageList
.GetImageCount() > 0);
677 int imageListBitmapYOffset
= 0;
678 int imageListBitmapXSize
= 0;
679 if (m_ImageList
.GetSize(0, imageListBitmapXSize
, imageListBitmapYOffset
)) {
680 imageListBitmapXSize
+= 2; // Padding.
681 imageListBitmapYOffset
= ((rect
.GetHeight() - imageListBitmapYOffset
) / 2) + 1 /* Fixes rounding like above */;
686 wxRect
cur_rec( iOffset
, rect
.y
, 0, rect
.height
);
688 for (int i
= 0; i
< GetColumnCount(); ++i
) {
690 int columnwidth
= GetColumnWidth(i
);
692 if (columnwidth
> 2*iOffset
) {
693 // Make a copy of the current rectangle so we can apply specific tweaks
694 wxRect target_rec
= cur_rec
;
695 target_rec
.width
= columnwidth
- 2*iOffset
;
697 GenericColumnEnum cid
= m_columndata
.columns
[i
].cid
;
700 DrawClientItem(dc
, cid
, target_rec
, content
, iTextOffset
, imageListBitmapYOffset
, imageListBitmapXSize
);
703 // Increment to the next column
704 cur_rec
.x
+= columnwidth
;
708 void CGenericClientListCtrl::DrawClientItem(wxDC
* dc
, int nColumn
, const wxRect
& rect
, ClientCtrlItem_Struct
* item
, int iTextOffset
, int iBitmapOffset
, int iBitmapXSize
) const
710 wxDCClipper
clipper( *dc
, rect
.GetX(), rect
.GetY(), rect
.GetWidth(), rect
.GetHeight() );
713 const CClientRef
& client
= item
->GetSource();
716 // Client name + various icons
717 case ColumnUserName
: {
718 // Point will get shifted per drawing.
720 wxPoint
point( rect
.GetX(), rect
.GetY() );
722 uint8 image
= Client_Grey_Smiley
;
724 if (item
->GetType() != A4AF_SOURCE
) {
726 switch (client
.GetDownloadState()) {
729 case DS_WAITCALLBACK
:
730 case DS_TOOMANYCONNS
:
731 image
= Client_Red_Smiley
;
734 if (client
.IsRemoteQueueFull()) {
735 image
= Client_Grey_Smiley
;
737 image
= Client_Yellow_Smiley
;
742 image
= Client_Green_Smiley
;
744 case DS_NONEEDEDPARTS
:
746 image
= Client_Grey_Smiley
; // Redundant
748 default: // DS_NONE i.e.
749 image
= Client_White_Smiley
;
753 // Default (Client_Grey_Smiley)
756 m_ImageList
.Draw(image
, *dc
, point
.x
, point
.y
+ iBitmapOffset
, wxIMAGELIST_DRAW_TRANSPARENT
);
760 point
.x
+= iBitmapXSize
;
762 uint8 clientImage
= Client_Unknown
;
764 if ( client
.IsFriend() ) {
765 clientImage
= Client_Friend_Smiley
;
767 switch ( client
.GetClientSoft() ) {
769 clientImage
= Client_aMule_Smiley
;
772 case SO_NEW_MLDONKEY
:
773 case SO_NEW2_MLDONKEY
:
774 clientImage
= Client_mlDonkey_Smiley
;
777 case SO_EDONKEYHYBRID
:
778 clientImage
= Client_eDonkeyHybrid_Smiley
;
781 clientImage
= Client_eMule_Smiley
;
784 clientImage
= Client_lphant_Smiley
;
787 case SO_NEW_SHAREAZA
:
788 case SO_NEW2_SHAREAZA
:
789 clientImage
= Client_Shareaza_Smiley
;
792 clientImage
= Client_xMule_Smiley
;
795 // cDonkey, Compatible, Unknown
796 // No icon for those yet.
797 // Using the eMule one + '?'
798 // Which is a faillback to the default (Client_Unknown)
803 int realY
= point
.y
+ iBitmapOffset
;
804 m_ImageList
.Draw(clientImage
, *dc
, point
.x
, realY
, wxIMAGELIST_DRAW_TRANSPARENT
);
806 if (client
.GetScoreRatio() > 1) {
807 // Has credits, draw the gold star
808 m_ImageList
.Draw(Client_CreditsYellow_Smiley
, *dc
, point
.x
, realY
,
809 wxIMAGELIST_DRAW_TRANSPARENT
);
810 } else if ( !client
.ExtProtocolAvailable() ) {
811 // No Ext protocol -> Draw the '-'
812 m_ImageList
.Draw(Client_ExtendedProtocol_Smiley
, *dc
, point
.x
, realY
,
813 wxIMAGELIST_DRAW_TRANSPARENT
);
816 if (client
.IsIdentified()) {
818 m_ImageList
.Draw(Client_SecIdent_Smiley
, *dc
, point
.x
, realY
,
819 wxIMAGELIST_DRAW_TRANSPARENT
);
820 } else if (client
.IsBadGuy()) {
822 m_ImageList
.Draw(Client_BadGuy_Smiley
, *dc
, point
.x
, realY
,
823 wxIMAGELIST_DRAW_TRANSPARENT
);
826 if (client
.GetObfuscationStatus() == OBST_ENABLED
) {
827 // the "¿" except it's a key
828 m_ImageList
.Draw(Client_Encryption_Smiley
, *dc
, point
.x
, realY
,
829 wxIMAGELIST_DRAW_TRANSPARENT
);
834 point
.x
+= iBitmapXSize
;
837 #ifdef ENABLE_IP2COUNTRY
838 if (theApp
->amuledlg
->m_IP2Country
->IsEnabled() && thePrefs::IsGeoIPEnabled()) {
839 // Draw the flag. Size can't be precached.
840 const CountryData
& countrydata
= theApp
->amuledlg
->m_IP2Country
->GetCountryData(client
.GetFullIP());
842 realY
= point
.y
+ (rect
.GetHeight() - countrydata
.Flag
.GetHeight())/2 + 1 /* floor() */;
844 dc
->DrawBitmap(countrydata
.Flag
,
848 userName
<< countrydata
.Name
;
850 userName
<< wxT(" - ");
852 point
.x
+= countrydata
.Flag
.GetWidth() + 2 /*Padding*/;
854 #endif // ENABLE_IP2COUNTRY
855 if (client
.GetUserName().IsEmpty()) {
856 userName
<< wxT("?");
858 userName
<< client
.GetUserName();
861 dc
->DrawText(userName
, point
.x
, rect
.GetY() + iTextOffset
);
865 case ColumnUserDownloaded
:
866 if (item
->GetType() != A4AF_SOURCE
&& client
.GetTransferredDown()) {
867 buffer
= CastItoXBytes(client
.GetTransferredDown());
868 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
871 case ColumnUserUploaded
:
872 if (item
->GetType() != A4AF_SOURCE
&& client
.GetTransferredUp()) {
873 buffer
= CastItoXBytes(client
.GetTransferredUp());
874 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
877 case ColumnUserSpeedDown
:
878 if (item
->GetType() != A4AF_SOURCE
&& client
.GetKBpsDown() > 0.001) {
879 if (client
.GetKBpsDown() >= 1024) {
880 buffer
= CFormat(_("%.1f MB/s")) % (client
.GetKBpsDown() / 1024.0);
882 buffer
= CFormat(_("%.1f kB/s")) % client
.GetKBpsDown();
884 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
887 case ColumnUserSpeedUp
:
888 // Datarate is in bytes.
889 if (item
->GetType() != A4AF_SOURCE
&& client
.GetUploadDatarate() >= 1024) {
890 if (client
.GetUploadDatarate() >= 1048576) {
891 buffer
= CFormat(_("%.1f MB/s")) % (client
.GetUploadDatarate() / 1048576.0);
893 buffer
= CFormat(_("%.1f kB/s")) % (client
.GetUploadDatarate() / 1024.0);
895 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
898 case ColumnUserProgress
:
899 if ( thePrefs::ShowProgBar() ) {
900 int iWidth
= rect
.GetWidth() - 2;
901 int iHeight
= rect
.GetHeight() - 2;
903 // don't draw Text beyond the bar
904 dc
->SetClippingRegion(rect
.GetX(), rect
.GetY() + 1, iWidth
, iHeight
);
906 if ( item
->GetType() != A4AF_SOURCE
) {
907 uint32 dwTicks
= GetTickCount();
909 wxMemoryDC cdcStatus
;
911 if ( item
->dwUpdated
< dwTicks
|| !item
->status
||
912 iWidth
!= item
->status
->GetWidth() ) {
914 if (item
->status
== NULL
) {
915 item
->status
= new wxBitmap(iWidth
, iHeight
);
916 } else if ( item
->status
->GetWidth() != iWidth
) {
917 // Only recreate if size has changed
918 item
->status
->Create(iWidth
, iHeight
);
921 cdcStatus
.SelectObject(*(item
->status
));
923 if ( thePrefs::UseFlatBar() ) {
924 DrawSourceStatusBar( client
, &cdcStatus
,
925 wxRect(0, 0, iWidth
, iHeight
), true);
927 DrawSourceStatusBar( client
, &cdcStatus
,
928 wxRect(1, 1, iWidth
- 2, iHeight
- 2), false);
931 cdcStatus
.SetPen( *wxBLACK_PEN
);
932 cdcStatus
.SetBrush( *wxTRANSPARENT_BRUSH
);
933 cdcStatus
.DrawRectangle( 0, 0, iWidth
, iHeight
);
937 item
->dwUpdated
= dwTicks
+ 10000;
939 cdcStatus
.SelectObject(*(item
->status
));
942 dc
->Blit(rect
.GetX(), rect
.GetY() + 1, iWidth
, iHeight
, &cdcStatus
, 0, 0);
945 CPartFile
* p
= client
.GetRequestFile();
947 a4af
= p
->GetFileName().GetPrintable();
951 buffer
= CFormat(wxT("%s: %s")) % _("A4AF") % a4af
;
953 int midx
= (2*rect
.GetX() + rect
.GetWidth()) >> 1;
954 int midy
= (2*rect
.GetY() + rect
.GetHeight()) >> 1;
956 wxCoord txtwidth
, txtheight
;
958 dc
->GetTextExtent(buffer
, &txtwidth
, &txtheight
);
960 dc
->SetTextForeground(*wxBLACK
);
961 dc
->DrawText(buffer
, wxMax(rect
.GetX() + 2, midx
- (txtwidth
>> 1)), midy
- (txtheight
>> 1));
964 dc
->SetPen( *wxBLACK_PEN
);
965 dc
->SetBrush( *wxTRANSPARENT_BRUSH
);
966 dc
->DrawRectangle( rect
.GetX(), rect
.GetY() + 1, iWidth
, iHeight
);
971 case ColumnUserAvailable
: {
972 if ( client
.GetUpPartCount() ) {
973 DrawStatusBar( client
, dc
, rect
);
978 case ColumnUserVersion
: {
979 dc
->DrawText(client
.GetClientVerString(), rect
.GetX(), rect
.GetY() + iTextOffset
);
983 case ColumnUserQueueRankRemote
: {
985 wxColour savedColour
= dc
->GetTextForeground();
986 // We only show the queue rank for sources actually queued for that file
987 if ( item
->GetType() != A4AF_SOURCE
&& client
.GetDownloadState() == DS_ONQUEUE
) {
988 if (client
.IsRemoteQueueFull()) {
989 buffer
= _("Queue Full");
991 uint16 rank
= client
.GetRemoteQueueRank();
993 qrDiff
= rank
- client
.GetOldRemoteQueueRank();
994 if (qrDiff
== rank
) {
998 dc
->SetTextForeground(*wxBLUE
);
1001 dc
->SetTextForeground(*wxRED
);
1003 buffer
= CFormat(_("On Queue: %u (%i)")) % rank
% qrDiff
;
1005 buffer
= _("On Queue");
1009 if (item
->GetType() != A4AF_SOURCE
) {
1010 buffer
= DownloadStateToStr( client
.GetDownloadState(),
1011 client
.IsRemoteQueueFull() );
1013 buffer
= _("Asked for another file");
1014 if ( client
.GetRequestFile() &&
1015 client
.GetRequestFile()->GetFileName().IsOk()) {
1016 buffer
+= CFormat(wxT(" (%s)"))
1017 % client
.GetRequestFile()->GetFileName();
1021 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
1023 dc
->SetTextForeground(savedColour
);
1027 case ColumnUserQueueRankLocal
:
1028 if (item
->GetType() != A4AF_SOURCE
) {
1029 if (client
.GetUploadState() == US_ONUPLOADQUEUE
) {
1030 uint16 nRank
= client
.GetUploadQueueWaitingPosition();
1032 buffer
= _("Waiting for upload slot");
1034 buffer
= CFormat(_("On Queue: %u")) % nRank
;
1036 } else if (client
.GetUploadState() == US_UPLOADING
) {
1037 buffer
= _("Uploading");
1042 buffer
= _("Asked for another file");
1044 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
1046 // Source comes from?
1047 case ColumnUserOrigin
: {
1048 buffer
= wxGetTranslation(OriginToText(client
.GetSourceFrom()));
1049 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
1052 // Local file name to identify on multi select
1053 case ColumnUserFileNameDownload
: {
1054 const CPartFile
* pf
= client
.GetRequestFile();
1056 buffer
= pf
->GetFileName().GetPrintable();
1058 buffer
= _("Unknown");
1059 buffer
= wxT("[") + buffer
+ wxT("]");
1061 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
1064 case ColumnUserFileNameUpload
: {
1065 const CKnownFile
* kf
= client
.GetUploadFile();
1067 buffer
= kf
->GetFileName().GetPrintable();
1069 buffer
= _("Unknown");
1070 buffer
= wxT("[") + buffer
+ wxT("]");
1072 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
1075 case ColumnUserFileNameDownloadRemote
: {
1076 bool nameMissmatch
= false;
1077 wxColour savedColour
= dc
->GetTextForeground();
1078 if (client
.GetClientFilename().IsEmpty() || item
->GetType() == A4AF_SOURCE
) {
1079 buffer
= _("Unknown");
1080 buffer
= wxT("[") + buffer
+ wxT("]");
1082 buffer
= client
.GetClientFilename();
1083 const CPartFile
* pf
= client
.GetRequestFile();
1084 if (pf
&& (pf
->GetFileName().GetPrintable().CmpNoCase(buffer
) != 0)) {
1085 nameMissmatch
= true;
1086 dc
->SetTextForeground(*wxRED
);
1089 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
1090 if (nameMissmatch
) {
1091 dc
->SetTextForeground(savedColour
);
1095 case ColumnUserSharedFiles
: {
1096 if(client
.HasDisabledSharedFiles()) {
1101 dc
->DrawText(buffer
, rect
.GetX(), rect
.GetY() + iTextOffset
);
1107 int CGenericClientListCtrl::SortProc(wxUIntPtr param1
, wxUIntPtr param2
, long sortData
)
1109 ClientCtrlItem_Struct
* item1
= reinterpret_cast<ClientCtrlItem_Struct
*>(param1
);
1110 ClientCtrlItem_Struct
* item2
= reinterpret_cast<ClientCtrlItem_Struct
*>(param2
);
1112 int sortMod
= (sortData
& CMuleListCtrl::SORT_DES
) ? -1 : 1;
1113 sortData
&= CMuleListCtrl::COLUMN_MASK
;
1116 // Two sources, some different possibilites
1117 // Avilable sources first, if we have both an
1118 // available and an unavailable
1119 comp
= ( item2
->GetType() - item1
->GetType() );
1122 // unavailable and available. The order is fixed regardless of sort-order.
1125 comp
= Compare(item1
->GetSource(), item2
->GetSource(), sortData
);
1128 // We modify the result so that it matches with ascending or decending
1129 return sortMod
* comp
;
1132 int CGenericClientListCtrl::Compare(
1133 const CClientRef
& client1
, const CClientRef
& client2
, long lParamSort
)
1135 switch (lParamSort
) {
1137 case ColumnUserName
:
1138 return CmpAny( client1
.GetUserName(), client2
.GetUserName() );
1140 // Sort by transferred in the following fields
1141 case ColumnUserDownloaded
:
1142 return CmpAny( client1
.GetTransferredDown(), client2
.GetTransferredDown() );
1144 // Sort by transferred in the following fields
1145 case ColumnUserUploaded
:
1146 return CmpAny( client1
.GetTransferredUp(), client2
.GetTransferredUp() );
1149 case ColumnUserSpeedDown
:
1150 return CmpAny( client1
.GetKBpsDown(), client2
.GetKBpsDown() );
1153 case ColumnUserSpeedUp
:
1154 return CmpAny( client1
.GetUploadDatarate(), client2
.GetUploadDatarate() );
1156 // Sort by parts offered
1157 case ColumnUserProgress
:
1159 client1
.GetAvailablePartCount(),
1160 client2
.GetAvailablePartCount() );
1162 // Sort by client version
1163 case ColumnUserVersion
: {
1164 int cmp
= client1
.GetSoftStr().Cmp(client2
.GetSoftStr());
1167 cmp
= CmpAny(client1
.GetVersion(), client2
.GetVersion());
1170 cmp
= client1
.GetClientModString().Cmp(client2
.GetClientModString());
1175 // Sort by Queue-Rank
1176 case ColumnUserQueueRankRemote
: {
1177 // This will sort by download state: Downloading, OnQueue, Connecting ...
1178 // However, Asked For Another will always be placed last, due to
1179 // sorting in SortProc
1180 if ( client1
.GetDownloadState() != client2
.GetDownloadState() ) {
1181 return client1
.GetDownloadState() - client2
.GetDownloadState();
1184 // Placing items on queue before items on full queues
1185 if ( client1
.IsRemoteQueueFull() ) {
1186 if ( client2
.IsRemoteQueueFull() ) {
1191 } else if ( client2
.IsRemoteQueueFull() ) {
1194 if ( client1
.GetRemoteQueueRank() ) {
1195 if ( client2
.GetRemoteQueueRank() ) {
1197 client1
.GetRemoteQueueRank(),
1198 client2
.GetRemoteQueueRank() );
1203 if ( client2
.GetRemoteQueueRank() ) {
1212 // Sort by Queue-Rank
1213 case ColumnUserQueueRankLocal
: {
1214 // This will sort by download state: Downloading, OnQueue, Connecting ...
1215 // However, Asked For Another will always be placed last, due to
1216 // sorting in SortProc
1217 if ( client1
.GetUploadState() != client2
.GetUploadState() ) {
1218 return client1
.GetUploadState() - client2
.GetUploadState();
1221 uint16 rank1
= client1
.GetUploadQueueWaitingPosition();
1222 uint16 rank2
= client2
.GetUploadQueueWaitingPosition();
1223 // Placing items on queue before items on full queues
1230 } else if ( !rank2
) {
1251 // Source of source ;)
1252 case ColumnUserOrigin
:
1253 return CmpAny(client1
.GetSourceFrom(), client2
.GetSourceFrom());
1255 // Sort by local filename (download)
1256 case ColumnUserFileNameDownload
: {
1257 wxString buffer1
, buffer2
;
1258 const CPartFile
* pf1
= client1
.GetRequestFile();
1260 buffer1
= pf1
->GetFileName().GetPrintable();
1262 const CPartFile
* pf2
= client2
.GetRequestFile();
1264 buffer2
= pf2
->GetFileName().GetPrintable();
1266 return CmpAny(buffer1
, buffer2
);
1269 // Sort by local filename (upload)
1270 case ColumnUserFileNameUpload
: {
1271 wxString buffer1
, buffer2
;
1272 const CKnownFile
* kf1
= client1
.GetUploadFile();
1274 buffer1
= kf1
->GetFileName().GetPrintable();
1276 const CKnownFile
* kf2
= client2
.GetUploadFile();
1278 buffer2
= kf2
->GetFileName().GetPrintable();
1280 return CmpAny(buffer1
, buffer2
);
1283 case ColumnUserFileNameDownloadRemote
: {
1284 return CmpAny(client1
.GetClientFilename(), client2
.GetClientFilename());
1287 case ColumnUserSharedFiles
: {
1288 return CmpAny(client1
.HasDisabledSharedFiles(), client2
.HasDisabledSharedFiles());
1297 void CGenericClientListCtrl::ShowSourcesCount( int diff
)
1299 m_clientcount
+= diff
;
1300 wxStaticText
* label
= CastByID( ID_CLIENTCOUNT
, GetParent(), wxStaticText
);
1303 label
->SetLabel(CFormat(wxT("%i")) % m_clientcount
);
1304 label
->GetParent()->Layout();
1308 static const CMuleColour
crBoth(0, 192, 0);
1309 static const CMuleColour
crFlatBoth(0, 150, 0);
1311 static const CMuleColour
crNeither(240, 240, 240);
1312 static const CMuleColour
crFlatNeither(224, 224, 224);
1314 static const CMuleColour
crClientOnly(104, 104, 104);
1315 static const CMuleColour
crFlatClientOnly(0, 0, 0);
1317 static const CMuleColour
crPending(255, 208, 0);
1318 static const CMuleColour
crNextPending(255, 255, 100);
1320 void CGenericClientListCtrl::DrawSourceStatusBar(
1321 const CClientRef
& source
, wxDC
* dc
, const wxRect
& rect
, bool bFlat
) const
1323 static CBarShader
s_StatusBar(16);
1325 CPartFile
* reqfile
= source
.GetRequestFile();
1327 s_StatusBar
.SetHeight(rect
.height
);
1328 s_StatusBar
.SetWidth(rect
.width
);
1329 s_StatusBar
.Set3dDepth( thePrefs::Get3DDepth() );
1330 const BitVector
& partStatus
= source
.GetPartStatus();
1332 if (reqfile
&& reqfile
->GetPartCount() == partStatus
.size()) {
1333 s_StatusBar
.SetFileSize(reqfile
->GetFileSize());
1334 uint16 lastDownloadingPart
= source
.GetDownloadState() == DS_DOWNLOADING
1335 ? source
.GetLastDownloadingPart() : 0xffff;
1336 uint16 nextRequestedPart
= source
.GetNextRequestedPart();
1338 for ( uint16 i
= 0; i
< partStatus
.size(); i
++ ) {
1339 uint64 uStart
= PARTSIZE
* i
;
1340 uint64 uEnd
= uStart
+ reqfile
->GetPartSize(i
) - 1;
1343 if (!partStatus
.get(i
)) {
1344 // client does not have this part
1346 colour
= bFlat
? crFlatNeither
: crNeither
;
1347 } else if ( reqfile
->IsComplete(i
)) {
1350 colour
= bFlat
? crFlatBoth
: crBoth
;
1351 } else if (lastDownloadingPart
== i
) {
1355 } else if (nextRequestedPart
== i
) {
1358 colour
= crNextPending
;
1360 // client has this part, we need it
1362 colour
= bFlat
? crFlatClientOnly
: crClientOnly
;
1365 if ( source
.GetRequestFile()->IsStopped() ) {
1369 s_StatusBar
.FillRange(uStart
, uEnd
, colour
);
1372 s_StatusBar
.SetFileSize(1);
1373 s_StatusBar
.FillRange(0, 1, bFlat
? crFlatNeither
: crNeither
);
1376 s_StatusBar
.Draw(dc
, rect
.x
, rect
.y
, bFlat
);
1379 static const CMuleColour
crUnavailable(240, 240, 240);
1380 static const CMuleColour
crFlatUnavailable(224, 224, 224);
1382 static const CMuleColour
crAvailable(104, 104, 104);
1383 static const CMuleColour
crFlatAvailable(0, 0, 0);
1385 void CGenericClientListCtrl::DrawStatusBar( const CClientRef
& client
, wxDC
* dc
, const wxRect
& rect1
) const
1387 wxRect rect
= rect1
;
1391 wxPen old_pen
= dc
->GetPen();
1392 wxBrush old_brush
= dc
->GetBrush();
1393 bool bFlat
= thePrefs::UseFlatBar();
1395 wxRect barRect
= rect
;
1396 if (!bFlat
) { // round bar has a black border, the bar itself is 1 pixel less on each border
1399 barRect
.height
-= 2;
1402 static CBarShader
s_StatusBar(16);
1404 uint32 partCount
= client
.GetUpPartCount();
1406 // Seems the partfile in the client object is not necessarily valid when bar is drawn for the first time.
1407 // Keep it simple and make all parts same size.
1408 s_StatusBar
.SetFileSize(partCount
* PARTSIZE
);
1409 s_StatusBar
.SetHeight(barRect
.height
);
1410 s_StatusBar
.SetWidth(barRect
.width
);
1411 s_StatusBar
.Set3dDepth( thePrefs::Get3DDepth() );
1414 for ( uint64 i
= 0; i
< partCount
; i
++ ) {
1415 uint64 uStart
= PARTSIZE
* i
;
1416 uEnd
= uStart
+ PARTSIZE
- 1;
1418 s_StatusBar
.FillRange(uStart
, uEnd
, client
.IsUpPartAvailable(i
) ? (bFlat
? crFlatAvailable
: crAvailable
) : (bFlat
? crFlatUnavailable
: crUnavailable
));
1420 // fill the rest (if partStatus is empty)
1421 s_StatusBar
.FillRange(uEnd
+ 1, partCount
* PARTSIZE
- 1, bFlat
? crFlatUnavailable
: crUnavailable
);
1422 s_StatusBar
.Draw(dc
, barRect
.x
, barRect
.y
, bFlat
);
1425 // Draw black border
1426 dc
->SetPen( *wxBLACK_PEN
);
1427 dc
->SetBrush( *wxTRANSPARENT_BRUSH
);
1428 dc
->DrawRectangle(rect
);
1431 dc
->SetPen( old_pen
);
1432 dc
->SetBrush( old_brush
);
1435 // File_checked_for_headers