Working "Swap to this file" menu option for A4AF files in aMuleGUI
[amule.git] / src / GenericClientListCtrl.cpp
bloba56c886ca966098224d3aea3f0a7394e0ca7ed40
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 //
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
8 // respective authors.
9 //
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.
19 //
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 "ClientDetailDialog.h" // Needed for CClientDetailDialog
34 #include "ChatWnd.h" // Needed for CChatWnd
35 #include "CommentDialogLst.h" // Needed for CCommentDialogLst
36 #include "DataToText.h" // Needed for PriorityToStr
37 #include "FileDetailDialog.h" // Needed for CFileDetailDialog
38 #include "GuiEvents.h" // Needed for CoreNotify_*
39 #ifdef ENABLE_IP2COUNTRY
40 #include "IP2Country.h" // Needed for IP2Country
41 #endif
42 #include "Logger.h"
43 #include "muuli_wdr.h" // Needed for ID_DLOADLIST
44 #include "PartFile.h" // Needed for CPartFile
45 #include "Preferences.h"
46 #include "SharedFileList.h" // Needed for CSharedFileList
47 #include "TerminationProcess.h" // Needed for CTerminationProcess
48 #include "updownclient.h" // Needed for CUpDownClient
50 struct ClientCtrlItem_Struct
52 ClientCtrlItem_Struct()
53 : dwUpdated(0),
54 status(NULL),
55 m_owner(NULL),
56 m_sourceValue(NULL),
57 m_type(UNAVAILABLE_SOURCE)
58 { }
60 ~ClientCtrlItem_Struct() {
61 delete status;
64 SourceItemType GetType() const {
65 return m_type;
68 CKnownFile* GetOwner() const {
69 return m_owner;
73 CUpDownClient* GetSource() const {
74 return m_sourceValue;
77 void SetContents(CKnownFile* owner, CUpDownClient* source, SourceItemType type) {
78 m_owner = owner;
79 m_sourceValue = source;
80 m_type = type;
83 void SetType(SourceItemType type) { m_type = type; }
85 uint32 dwUpdated;
86 wxBitmap* status;
88 private:
89 CKnownFile* m_owner;
90 CUpDownClient* m_sourceValue;
91 SourceItemType m_type;
94 #define m_ImageList theApp->amuledlg->m_imagelist
96 BEGIN_EVENT_TABLE(CGenericClientListCtrl, CMuleListCtrl)
97 EVT_LIST_ITEM_ACTIVATED(wxID_ANY, CGenericClientListCtrl::OnItemActivated)
98 EVT_LIST_ITEM_RIGHT_CLICK(wxID_ANY, CGenericClientListCtrl::OnMouseRightClick)
99 EVT_LIST_ITEM_MIDDLE_CLICK(wxID_ANY, CGenericClientListCtrl::OnMouseMiddleClick)
101 EVT_CHAR( CGenericClientListCtrl::OnKeyPressed )
103 EVT_MENU( MP_CHANGE2FILE, CGenericClientListCtrl::OnSwapSource )
104 EVT_MENU( MP_SHOWLIST, CGenericClientListCtrl::OnViewFiles )
105 EVT_MENU( MP_ADDFRIEND, CGenericClientListCtrl::OnAddFriend )
106 EVT_MENU( MP_SENDMESSAGE, CGenericClientListCtrl::OnSendMessage )
107 EVT_MENU( MP_DETAIL, CGenericClientListCtrl::OnViewClientInfo )
108 END_EVENT_TABLE()
110 //! This listtype is used when gathering the selected items.
111 typedef std::list<ClientCtrlItem_Struct*> ItemList;
113 CGenericClientListCtrl::CGenericClientListCtrl(
114 const wxString& tablename,
115 wxWindow *parent, wxWindowID winid, const wxPoint& pos, const wxSize& size,
116 long style, const wxValidator& validator, const wxString& name )
118 CMuleListCtrl( parent, winid, pos, size, style | wxLC_OWNERDRAW | wxLC_VRULES | wxLC_HRULES, validator, name ),
119 m_columndata(0, NULL)
121 // Setting the sorter function must be done in the derived class, to use the translation function correctly.
122 // SetSortFunc( SortProc );
124 // Set the table-name (for loading and saving preferences).
125 SetTableName( tablename );
127 m_menu = NULL;
128 m_showing = false;
130 m_hilightBrush = CMuleColour(wxSYS_COLOUR_HIGHLIGHT).Blend(125).GetBrush();
132 m_hilightUnfocusBrush = CMuleColour(wxSYS_COLOUR_BTNSHADOW).Blend(125).GetBrush();
134 m_clientcount = 0;
137 wxString CGenericClientListCtrl::TranslateCIDToName(GenericColumnEnum cid)
139 wxString name = wxEmptyString;
141 switch (cid) {
142 case ColumnUserName:
143 name = wxT("N");
144 break;
145 case ColumnUserDownloaded:
146 name = wxT("D");
147 break;
148 case ColumnUserUploaded:
149 name = wxT("U");
150 break;
151 case ColumnUserSpeedDown:
152 name = wxT("S");
153 break;
154 case ColumnUserSpeedUp:
155 name = wxT("s");
156 break;
157 case ColumnUserProgress:
158 name = wxT("P");
159 break;
160 case ColumnUserAvailable:
161 name = wxT("A");
162 break;
163 case ColumnUserVersion:
164 name = wxT("V");
165 break;
166 case ColumnUserQueueRankLocal:
167 name = wxT("Q");
168 break;
169 case ColumnUserQueueRankRemote:
170 name = wxT("q");
171 break;
172 case ColumnUserOrigin:
173 name = wxT("O");
174 break;
175 case ColumnUserFileNameDownload:
176 name = wxT("F");
177 break;
178 case ColumnUserFileNameUpload:
179 name = wxT("f");
180 break;
181 case ColumnUserFileNameDownloadRemote:
182 name = wxT("R");
183 break;
184 case ColumnInvalid:
185 default:
186 wxASSERT(0);
187 break;
190 return name;
193 void CGenericClientListCtrl::InitColumnData()
195 if (!m_columndata.n_columns) {
196 throw wxString(wxT("CRITICAL: Initialization of the column data lacks subclass information"));
199 for (int i = 0; i < m_columndata.n_columns; ++i) {
200 InsertColumn( i, wxGetTranslation(m_columndata.columns[i].title), wxLIST_FORMAT_LEFT, m_columndata.columns[i].width, TranslateCIDToName(m_columndata.columns[i].cid));
203 LoadSettings();
206 CGenericClientListCtrl::~CGenericClientListCtrl()
208 while ( !m_ListItems.empty() ) {
209 delete m_ListItems.begin()->second;
210 m_ListItems.erase( m_ListItems.begin() );
213 void CGenericClientListCtrl::RawAddSource(CKnownFile* owner, CUpDownClient* source, SourceItemType type)
215 ClientCtrlItem_Struct* newitem = new ClientCtrlItem_Struct;
216 newitem->SetContents(owner, source, type);
218 m_ListItems.insert( ListItemsPair(source, newitem) );
220 long item = InsertItem( GetItemCount(), wxEmptyString );
221 SetItemPtrData( item, reinterpret_cast<wxUIntPtr>(newitem) );
222 SetItemBackgroundColour( item, GetBackgroundColour() );
225 void CGenericClientListCtrl::AddSource(CKnownFile* owner, CUpDownClient* source, SourceItemType type)
227 wxCHECK_RET(owner, wxT("NULL owner in CGenericClientListCtrl::AddSource"));
228 wxCHECK_RET(source, wxT("NULL source in CGenericClientListCtrl::AddSource"));
230 // Update the other instances of this source
231 bool bFound = false;
232 ListIteratorPair rangeIt = m_ListItems.equal_range(source);
233 for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second; ++it ) {
234 ClientCtrlItem_Struct* cur_item = it->second;
236 // Check if this source has been already added to this file => to be sure
237 if ( cur_item->GetOwner() == owner ) {
238 // Update this instance with its new setting
239 if ((type == A4AF_SOURCE) &&
240 cur_item->GetSource()->GetRequestFile()
241 && std::binary_search(m_knownfiles.begin(), m_knownfiles.end(), cur_item->GetSource()->GetRequestFile())) {
242 cur_item->SetContents(owner, source, AVAILABLE_SOURCE);
243 } else {
244 cur_item->SetContents(owner, source, type);
246 cur_item->dwUpdated = 0;
247 bFound = true;
248 } else if ( type == AVAILABLE_SOURCE ) {
249 // The state 'Available' is exclusive
250 cur_item->SetContents(cur_item->GetOwner(), source, A4AF_SOURCE);
251 cur_item->dwUpdated = 0;
255 if ( bFound ) {
256 return;
259 if ( std::binary_search(m_knownfiles.begin(), m_knownfiles.end(), owner) ) {
260 RawAddSource(owner, source, type);
262 ShowSourcesCount( 1 );
266 void CGenericClientListCtrl::RawRemoveSource( ListItems::iterator& it)
268 ClientCtrlItem_Struct* item = it->second;
270 long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
272 if ( index > -1 ) {
273 DeleteItem( index );
276 delete item;
278 // Remove it from the m_ListItems
279 m_ListItems.erase( it );
282 void CGenericClientListCtrl::RemoveSource( const CUpDownClient* source, const CKnownFile* owner )
284 // A NULL owner means remove it no matter what.
285 wxCHECK_RET(source, wxT("NULL source in CGenericClientListCtrl::RemoveSource"));
287 // Retrieve all entries matching the source
288 ListIteratorPair rangeIt = m_ListItems.equal_range(source);
290 int removedItems = 0;
292 for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second; /* no ++, it happens later */) {
293 ListItems::iterator tmp = it++;
295 if ( owner == NULL || owner == tmp->second->GetOwner() ) {
297 RawRemoveSource(tmp);
299 ++removedItems;
303 ShowSourcesCount((-1) * removedItems);
306 void CGenericClientListCtrl::UpdateItem(const void* toupdate, SourceItemType type)
308 // Retrieve all entries matching the source
309 ListIteratorPair rangeIt = m_ListItems.equal_range( toupdate );
311 if ( rangeIt.first != rangeIt.second ) {
312 // Visible lines, default to all because not all platforms
313 // support the GetVisibleLines function
314 long first = 0, last = GetItemCount();
316 #ifndef __WXMSW__
317 // Get visible lines if we need them
318 GetVisibleLines( &first, &last );
319 #endif
321 for ( ListItems::iterator it = rangeIt.first; it != rangeIt.second; ++it ) {
322 ClientCtrlItem_Struct* item = it->second;
324 long index = FindItem( -1, reinterpret_cast<wxUIntPtr>(item) );
326 if ((type == A4AF_SOURCE) &&
327 item->GetSource()->GetRequestFile()
328 && std::binary_search(m_knownfiles.begin(), m_knownfiles.end(), item->GetSource()->GetRequestFile())) {
330 item->SetType(AVAILABLE_SOURCE);
331 } else {
332 item->SetType(type);
335 item->dwUpdated = 0;
337 // Only update visible lines
338 if ( index >= first && index <= last) {
339 RefreshItem( index );
345 void CGenericClientListCtrl::ShowSources( const CKnownFileVector& files )
347 Freeze();
349 // The stored vector is sorted, as is the received one, so we can use binary_search
351 for (unsigned i = 0; i < m_knownfiles.size(); ++i) {
352 // Files that are not in the new list must have show set to false.
353 if (!std::binary_search(files.begin(), files.end(), m_knownfiles[i])) {
354 SetShowSources(m_knownfiles[i], false);
358 // This will call again SetShowSources in files that were in both vectors. Right now
359 // that function is just a inline setter, so any way to prevent it would be wasteful,
360 // but this must be reviewed if that fact changes.
362 for (unsigned i = 0; i < files.size(); ++i) {
363 SetShowSources(files[i], true);
366 // We must cleanup sources that are not in the received files.
368 int itemDiff = 0;
370 for (ListItems::iterator it = m_ListItems.begin(); it != m_ListItems.end(); /* no ++, it happens later */) {
371 ListItems::iterator tmp = it++;
372 ClientCtrlItem_Struct* item = tmp->second;
373 if (!std::binary_search(files.begin(), files.end(), item->GetOwner())) {
374 // Remove it from the m_ListItems
375 RawRemoveSource(tmp);
376 --itemDiff;
380 for (unsigned i = 0; i < files.size(); ++i) {
382 // Only those that weren't showing already
383 if (!std::binary_search(m_knownfiles.begin(), m_knownfiles.end(), files[i])) {
385 CKnownFile* file = files[i];
387 wxASSERT_MSG(file, wxT("NULL file in CGenericClientListCtrl::ShowSources"));
389 if (file) {
391 CKnownFile::SourceSet::const_iterator it;
393 if (IsShowingDownloadSources()) {
394 const CKnownFile::SourceSet& normSources = (dynamic_cast<CPartFile*>( file ))->GetSourceList();
395 const CKnownFile::SourceSet& a4afSources = (dynamic_cast<CPartFile*>( file ))->GetA4AFList();
397 // Adding normal sources
398 for ( it = normSources.begin(); it != normSources.end(); ++it ) {
399 switch ((*it)->GetDownloadState()) {
400 case DS_DOWNLOADING:
401 case DS_ONQUEUE:
402 RawAddSource( file, *it, AVAILABLE_SOURCE );
403 ++itemDiff;
404 break;
405 default:
406 // Any other state
407 RawAddSource( file, *it, UNAVAILABLE_SOURCE );
408 ++itemDiff;
412 // Adding A4AF sources
413 for ( it = a4afSources.begin(); it != a4afSources.end(); ++it ) {
414 // Only add if the A4AF file is not in the shown list.
415 if (!std::binary_search(files.begin(), files.end(), (*it)->GetRequestFile())) {
416 RawAddSource( file, *it, A4AF_SOURCE );
417 ++itemDiff;
420 } else {
421 // Known file
422 const CKnownFile::SourceSet& sources = file->m_ClientUploadList;
423 for ( it = sources.begin(); it != sources.end(); ++it ) {
424 switch ((*it)->GetUploadState()) {
425 case US_UPLOADING:
426 case US_ONUPLOADQUEUE:
427 RawAddSource( file, *it, AVAILABLE_SOURCE );
428 ++itemDiff;
429 break;
430 default:
431 // Any other state
432 RawAddSource( file, *it, UNAVAILABLE_SOURCE );
433 ++itemDiff;
441 m_knownfiles = files;
443 ShowSourcesCount( itemDiff );
445 SortList();
447 Thaw();
451 * Helper-function: This function is used to gather selected items.
453 * @param list A pointer to the list to gather items from.
454 * @return A list containing the selected items of the choosen types.
456 ItemList GetSelectedItems( CGenericClientListCtrl* list )
458 ItemList results;
460 long index = list->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
462 while ( index > -1 ) {
463 ClientCtrlItem_Struct* item = (ClientCtrlItem_Struct*)list->GetItemData( index );
465 results.push_back( item );
467 index = list->GetNextItem( index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
470 return results;
473 void CGenericClientListCtrl::OnSwapSource( wxCommandEvent& WXUNUSED(event) )
475 ItemList sources = ::GetSelectedItems( this );
477 for ( ItemList::iterator it = sources.begin(); it != sources.end(); ++it ) {
478 CKnownFile * kf = (*it)->GetOwner();
479 if (!kf->IsPartFile()) {
480 wxFAIL_MSG(wxT("File is not a partfile when swapping sources"));
481 continue;
483 (*it)->GetSource()->SwapToAnotherFile( true, false, false, dynamic_cast<CPartFile*>(kf));
488 void CGenericClientListCtrl::OnViewFiles( wxCommandEvent& WXUNUSED(event) )
490 ItemList sources = ::GetSelectedItems( this );
492 if ( sources.size() == 1 ) {
493 sources.front()->GetSource()->RequestSharedFileList();
498 void CGenericClientListCtrl::OnAddFriend( wxCommandEvent& WXUNUSED(event) )
500 ItemList sources = ::GetSelectedItems( this );
502 for ( ItemList::iterator it = sources.begin(); it != sources.end(); ++it ) {
503 CUpDownClient* client = (*it)->GetSource();
504 if (client->IsFriend()) {
505 theApp->amuledlg->m_chatwnd->RemoveFriend(client->GetUserHash(), client->GetIP(), client->GetUserPort());
506 } else {
507 theApp->amuledlg->m_chatwnd->AddFriend( client );
513 void CGenericClientListCtrl::OnSendMessage( wxCommandEvent& WXUNUSED(event) )
515 ItemList sources = ::GetSelectedItems( this );
517 if ( sources.size() == 1 ) {
518 CUpDownClient* source = (sources.front())->GetSource();
520 // These values are cached, since calling wxGetTextFromUser will
521 // start an event-loop, in which the client may be deleted.
522 wxString userName = source->GetUserName();
523 uint64 userID = GUI_ID(source->GetIP(), source->GetUserPort());
525 wxString message = ::wxGetTextFromUser(_("Send message to user"),
526 _("Message to send:"));
527 if ( !message.IsEmpty() ) {
528 theApp->amuledlg->m_chatwnd->SendMessage(message, userName, userID);
534 void CGenericClientListCtrl::OnViewClientInfo( wxCommandEvent& WXUNUSED(event) )
536 ItemList sources = ::GetSelectedItems( this );
538 if ( sources.size() == 1 ) {
539 CClientDetailDialog( this, sources.front()->GetSource() ).ShowModal();
544 void CGenericClientListCtrl::OnItemActivated( wxListEvent& evt )
546 CClientDetailDialog( this, ((ClientCtrlItem_Struct*)GetItemData( evt.GetIndex()))->GetSource()).ShowModal();
550 void CGenericClientListCtrl::OnMouseRightClick(wxListEvent& evt)
552 long index = CheckSelection(evt);
553 if (index < 0) {
554 return;
557 delete m_menu;
558 m_menu = NULL;
560 ClientCtrlItem_Struct* item = (ClientCtrlItem_Struct*)GetItemData( index );
561 CUpDownClient* client = item->GetSource();
563 m_menu = new wxMenu(wxT("Clients"));
564 m_menu->Append(MP_DETAIL, _("Show &Details"));
565 m_menu->Append(MP_ADDFRIEND, client->IsFriend() ? _("Remove from friends") : _("Add to Friends"));
566 m_menu->Append(MP_SHOWLIST, _("View Files"));
567 m_menu->Append(MP_SENDMESSAGE, _("Send message"));
569 m_menu->Append(MP_CHANGE2FILE, _("Swap to this file"));
571 // Only enable the Swap option for A4AF sources
572 m_menu->Enable(MP_CHANGE2FILE, (item->GetType() == A4AF_SOURCE));
573 // We need a valid IP if we are to message the client
574 m_menu->Enable(MP_SENDMESSAGE, (client->GetIP() != 0));
576 m_menu->Enable(MP_SHOWLIST, !client->HasDisabledSharedFiles());
578 PopupMenu(m_menu, evt.GetPoint());
580 delete m_menu;
581 m_menu = NULL;
586 void CGenericClientListCtrl::OnMouseMiddleClick(wxListEvent& evt)
588 // Check if clicked item is selected. If not, unselect all and select it.
589 long index = CheckSelection(evt);
590 if ( index < 0 ) {
591 return;
594 CClientDetailDialog(this, ((ClientCtrlItem_Struct*)GetItemData( index ))->GetSource()).ShowModal();
598 void CGenericClientListCtrl::OnKeyPressed( wxKeyEvent& event )
600 // No actions right now.
601 //switch (event.GetKeyCode()) {
602 // default:
603 event.Skip();
608 void CGenericClientListCtrl::OnDrawItem(
609 int item, wxDC* dc, const wxRect& rect, const wxRect& rectHL, bool highlighted)
611 // Don't do any drawing if there's nobody to see it.
612 if ( !theApp->amuledlg->IsDialogVisible( GetParentDialog() ) ) {
613 return;
616 ClientCtrlItem_Struct* content = (ClientCtrlItem_Struct *)GetItemData(item);
618 // Define text-color and background
619 // and the border of the drawn area
620 if (highlighted) {
621 CMuleColour colour;
622 if (GetFocus()) {
623 dc->SetBackground(m_hilightBrush);
624 colour = m_hilightBrush.GetColour();
625 } else {
626 dc->SetBackground(m_hilightUnfocusBrush);
627 colour = m_hilightUnfocusBrush.GetColour();
629 dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
630 dc->SetPen( colour.Blend(65).GetPen() );
631 } else {
632 dc->SetBackground(*(wxTheBrushList->FindOrCreateBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX), wxSOLID)));
633 dc->SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
634 dc->SetPen(*wxTRANSPARENT_PEN);
636 dc->SetBrush( dc->GetBackground() );
638 dc->DrawRectangle( rectHL.x, rectHL.y, rectHL.width, rectHL.height );
640 dc->SetPen(*wxTRANSPARENT_PEN);
642 // Various constant values we use
643 const int iTextOffset = (( rect.GetHeight() - dc->GetCharHeight() ) / 2) + 1 /* Fixes rounding in the centering math, much easier than floor() */;
644 const int iOffset = 2;
645 wxASSERT(m_ImageList.GetImageCount() > 0);
646 int imageListBitmapYOffset = 0;
647 int imageListBitmapXSize = 0;
648 if (m_ImageList.GetSize(0, imageListBitmapXSize, imageListBitmapYOffset)) {
649 imageListBitmapXSize += 2; // Padding.
650 imageListBitmapYOffset = ((rect.GetHeight() - imageListBitmapYOffset) / 2) + 1 /* Fixes rounding like above */;
651 } else {
652 wxASSERT(0);
655 wxRect cur_rec( iOffset, rect.y, 0, rect.height );
657 for (int i = 0; i < GetColumnCount(); ++i) {
659 int columnwidth = GetColumnWidth(i);
661 if (columnwidth > 2*iOffset) {
662 // Make a copy of the current rectangle so we can apply specific tweaks
663 wxRect target_rec = cur_rec;
664 target_rec.width = columnwidth - 2*iOffset;
666 GenericColumnEnum cid = m_columndata.columns[i].cid;
668 // Draw the item
669 DrawClientItem(dc, cid, target_rec, content, iTextOffset, imageListBitmapYOffset, imageListBitmapXSize);
672 // Increment to the next column
673 cur_rec.x += columnwidth;
677 void CGenericClientListCtrl::DrawClientItem(wxDC* dc, int nColumn, const wxRect& rect, ClientCtrlItem_Struct* item, int iTextOffset, int iBitmapOffset, int iBitmapXSize ) const
679 wxDCClipper clipper( *dc, rect.GetX(), rect.GetY(), rect.GetWidth(), rect.GetHeight() );
680 wxString buffer;
682 const CUpDownClient* client = item->GetSource();
684 switch (nColumn) {
685 // Client name + various icons
686 case ColumnUserName: {
687 // Point will get shifted per drawing.
689 wxPoint point( rect.GetX(), rect.GetY() );
691 uint8 image = Client_Grey_Smiley;
693 if (item->GetType() != A4AF_SOURCE) {
695 switch (client->GetDownloadState()) {
696 case DS_CONNECTING:
697 case DS_CONNECTED:
698 case DS_WAITCALLBACK:
699 case DS_TOOMANYCONNS:
700 image = Client_Red_Smiley;
701 break;
702 case DS_ONQUEUE:
703 if (client->IsRemoteQueueFull()) {
704 image = Client_Grey_Smiley;
705 } else {
706 image = Client_Yellow_Smiley;
708 break;
709 case DS_DOWNLOADING:
710 case DS_REQHASHSET:
711 image = Client_Green_Smiley;
712 break;
713 case DS_NONEEDEDPARTS:
714 case DS_LOWTOLOWIP:
715 image = Client_Grey_Smiley; // Redundant
716 break;
717 default: // DS_NONE i.e.
718 image = Client_White_Smiley;
721 } else {
722 // Default (Client_Grey_Smiley)
725 m_ImageList.Draw(image, *dc, point.x, point.y + iBitmapOffset, wxIMAGELIST_DRAW_TRANSPARENT);
727 // Next
729 point.x += iBitmapXSize;
731 uint8 clientImage = Client_Unknown;
733 if ( client->IsFriend() ) {
734 clientImage = Client_Friend_Smiley;
735 } else {
736 switch ( client->GetClientSoft() ) {
737 case SO_AMULE:
738 clientImage = Client_aMule_Smiley;
739 break;
740 case SO_MLDONKEY:
741 case SO_NEW_MLDONKEY:
742 case SO_NEW2_MLDONKEY:
743 clientImage = Client_mlDonkey_Smiley;
744 break;
745 case SO_EDONKEY:
746 case SO_EDONKEYHYBRID:
747 clientImage = Client_eDonkeyHybrid_Smiley;
748 break;
749 case SO_EMULE:
750 clientImage = Client_eMule_Smiley;
751 break;
752 case SO_LPHANT:
753 clientImage = Client_lphant_Smiley;
754 break;
755 case SO_SHAREAZA:
756 case SO_NEW_SHAREAZA:
757 case SO_NEW2_SHAREAZA:
758 clientImage = Client_Shareaza_Smiley;
759 break;
760 case SO_LXMULE:
761 clientImage = Client_xMule_Smiley;
762 break;
763 default:
764 // cDonkey, Compatible, Unknown
765 // No icon for those yet.
766 // Using the eMule one + '?'
767 // Which is a faillback to the default (Client_Unknown)
768 break;
772 int realY = point.y + iBitmapOffset;
773 m_ImageList.Draw(clientImage, *dc, point.x, realY, wxIMAGELIST_DRAW_TRANSPARENT);
775 if (client->GetScoreRatio() > 1) {
776 // Has credits, draw the gold star
777 m_ImageList.Draw(Client_CreditsYellow_Smiley, *dc, point.x, realY,
778 wxIMAGELIST_DRAW_TRANSPARENT );
779 } else if ( !client->ExtProtocolAvailable() ) {
780 // No Ext protocol -> Draw the '-'
781 m_ImageList.Draw(Client_ExtendedProtocol_Smiley, *dc, point.x, realY,
782 wxIMAGELIST_DRAW_TRANSPARENT);
785 if (client->IsIdentified()) {
786 // the 'v'
787 m_ImageList.Draw(Client_SecIdent_Smiley, *dc, point.x, realY,
788 wxIMAGELIST_DRAW_TRANSPARENT);
789 } else if (client->IsBadGuy()) {
790 // the 'X'
791 m_ImageList.Draw(Client_BadGuy_Smiley, *dc, point.x, realY,
792 wxIMAGELIST_DRAW_TRANSPARENT);
795 if (client->GetObfuscationStatus() == OBST_ENABLED) {
796 // the "¿" except it's a key
797 m_ImageList.Draw(Client_Encryption_Smiley, *dc, point.x, realY,
798 wxIMAGELIST_DRAW_TRANSPARENT);
801 // Next
803 point.x += iBitmapXSize;
805 wxString userName;
806 #ifdef ENABLE_IP2COUNTRY
807 if (theApp->amuledlg->m_IP2Country->IsEnabled() && thePrefs::IsGeoIPEnabled()) {
808 // Draw the flag. Size can't be precached.
809 const CountryData& countrydata = theApp->amuledlg->m_IP2Country->GetCountryData(client->GetFullIP());
811 realY = point.y + (rect.GetHeight() - countrydata.Flag.GetHeight())/2 + 1 /* floor() */;
813 dc->DrawBitmap(countrydata.Flag,
814 point.x, realY,
815 true);
817 userName << countrydata.Name;
819 userName << wxT(" - ");
821 point.x += countrydata.Flag.GetWidth() + 2 /*Padding*/;
823 #endif // ENABLE_IP2COUNTRY
824 if (client->GetUserName().IsEmpty()) {
825 userName << wxT("?");
826 } else {
827 userName << client->GetUserName();
830 dc->DrawText(userName, point.x, rect.GetY() + iTextOffset);
832 break;
834 case ColumnUserDownloaded:
835 if (item->GetType() != A4AF_SOURCE && client->GetTransferredDown()) {
836 buffer = CastItoXBytes(client->GetTransferredDown());
837 dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
839 break;
840 case ColumnUserUploaded:
841 if (item->GetType() != A4AF_SOURCE && client->GetTransferredUp()) {
842 buffer = CastItoXBytes(client->GetTransferredUp());
843 dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
845 break;
846 case ColumnUserSpeedDown:
847 if (item->GetType() != A4AF_SOURCE && client->GetKBpsDown() > 0.001) {
848 buffer = CFormat(_("%.1f kB/s")) % client->GetKBpsDown();
849 dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
851 break;
852 case ColumnUserSpeedUp:
853 // Datarate is in bytes.
854 if (item->GetType() != A4AF_SOURCE && client->GetUploadDatarate() > 1024) {
855 buffer = CFormat(_("%.1f kB/s")) % (client->GetUploadDatarate() / 1024.0);
856 dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
858 break;
859 case ColumnUserProgress:
860 if ( thePrefs::ShowProgBar() ) {
861 int iWidth = rect.GetWidth() - 2;
862 int iHeight = rect.GetHeight() - 2;
864 // don't draw Text beyond the bar
865 dc->SetClippingRegion(rect.GetX(), rect.GetY() + 1, iWidth, iHeight);
867 if ( item->GetType() != A4AF_SOURCE ) {
868 uint32 dwTicks = GetTickCount();
870 wxMemoryDC cdcStatus;
872 if ( item->dwUpdated < dwTicks || !item->status ||
873 iWidth != item->status->GetWidth() ) {
875 if (item->status == NULL) {
876 item->status = new wxBitmap(iWidth, iHeight);
877 } else if ( item->status->GetWidth() != iWidth ) {
878 // Only recreate if size has changed
879 item->status->Create(iWidth, iHeight);
882 cdcStatus.SelectObject(*(item->status));
884 if ( thePrefs::UseFlatBar() ) {
885 DrawSourceStatusBar( client, &cdcStatus,
886 wxRect(0, 0, iWidth, iHeight), true);
887 } else {
888 DrawSourceStatusBar( client, &cdcStatus,
889 wxRect(1, 1, iWidth - 2, iHeight - 2), false);
891 // Draw black border
892 cdcStatus.SetPen( *wxBLACK_PEN );
893 cdcStatus.SetBrush( *wxTRANSPARENT_BRUSH );
894 cdcStatus.DrawRectangle( 0, 0, iWidth, iHeight );
897 // Plus ten seconds
898 item->dwUpdated = dwTicks + 10000;
899 } else {
900 cdcStatus.SelectObject(*(item->status));
903 dc->Blit(rect.GetX(), rect.GetY() + 1, iWidth, iHeight, &cdcStatus, 0, 0);
904 } else {
905 wxString a4af;
906 CPartFile* p = client->GetRequestFile();
907 if (p) {
908 a4af = p->GetFileName().GetPrintable();
909 } else {
910 a4af = wxT("?");
912 buffer = CFormat(wxT("%s: %s")) % _("A4AF") % a4af;
914 int midx = (2*rect.GetX() + rect.GetWidth()) >> 1;
915 int midy = (2*rect.GetY() + rect.GetHeight()) >> 1;
917 wxCoord txtwidth, txtheight;
919 dc->GetTextExtent(buffer, &txtwidth, &txtheight);
921 dc->SetTextForeground(*wxBLACK);
922 dc->DrawText(buffer, wxMax(rect.GetX() + 2, midx - (txtwidth >> 1)), midy - (txtheight >> 1));
924 // Draw black border
925 dc->SetPen( *wxBLACK_PEN );
926 dc->SetBrush( *wxTRANSPARENT_BRUSH );
927 dc->DrawRectangle( rect.GetX(), rect.GetY() + 1, iWidth, iHeight );
930 break;
932 case ColumnUserAvailable: {
933 if ( client->GetUpPartCount() ) {
934 DrawStatusBar( client, dc, rect );
936 break;
939 case ColumnUserVersion: {
940 dc->DrawText(client->GetClientVerString(), rect.GetX(), rect.GetY() + iTextOffset);
941 break;
944 case ColumnUserQueueRankRemote: {
945 sint16 qrDiff = 0;
946 wxColour savedColour = dc->GetTextForeground();
947 // We only show the queue rank for sources actually queued for that file
948 if ( item->GetType() != A4AF_SOURCE && client->GetDownloadState() == DS_ONQUEUE ) {
949 if (client->IsRemoteQueueFull()) {
950 buffer = _("Queue Full");
951 } else {
952 if (client->GetRemoteQueueRank()) {
953 qrDiff = client->GetRemoteQueueRank() -
954 client->GetOldRemoteQueueRank();
955 if (qrDiff == client->GetRemoteQueueRank() ) {
956 qrDiff = 0;
958 if ( qrDiff < 0 ) {
959 dc->SetTextForeground(*wxBLUE);
961 if ( qrDiff > 0 ) {
962 dc->SetTextForeground(*wxRED);
964 buffer = CFormat(_("On Queue: %u (%i)")) % client->GetRemoteQueueRank() % qrDiff;
965 } else {
966 buffer = _("On Queue");
969 } else {
970 if (item->GetType() != A4AF_SOURCE) {
971 buffer = DownloadStateToStr( client->GetDownloadState(),
972 client->IsRemoteQueueFull() );
973 } else {
974 buffer = _("Asked for another file");
975 if ( client->GetRequestFile() &&
976 client->GetRequestFile()->GetFileName().IsOk()) {
977 buffer += CFormat(wxT(" (%s)"))
978 % client->GetRequestFile()->GetFileName();
982 dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
983 if (qrDiff) {
984 dc->SetTextForeground(savedColour);
986 break;
988 case ColumnUserQueueRankLocal:
989 if (item->GetType() != A4AF_SOURCE) {
990 if (client->GetUploadState() == US_ONUPLOADQUEUE ) {
991 uint16 nRank = client->GetUploadQueueWaitingPosition();
992 if (nRank == 0) {
993 buffer = _("Waiting for upload slot");
994 } else {
995 buffer = CFormat(_("On Queue: %u")) % nRank;
997 } else if (client->GetUploadState() == US_UPLOADING) {
998 buffer = _("Uploading");
999 } else {
1000 buffer = _("None");
1002 } else {
1003 buffer = _("Asked for another file");
1005 dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1006 break;
1007 // Source comes from?
1008 case ColumnUserOrigin: {
1009 buffer = wxGetTranslation(OriginToText(client->GetSourceFrom()));
1010 dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1011 break;
1013 // Local file name to identify on multi select
1014 case ColumnUserFileNameDownload: {
1015 const CPartFile * pf = client->GetRequestFile();
1016 if (pf) {
1017 buffer = pf->GetFileName().GetPrintable();
1018 } else {
1019 buffer = wxT("[Unknown]");
1021 dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1022 break;
1024 case ColumnUserFileNameUpload: {
1025 const CKnownFile * kf = client->GetUploadFile();
1026 if (kf) {
1027 buffer = kf->GetFileName().GetPrintable();
1028 } else {
1029 buffer = wxT("[Unknown]");
1031 dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1032 break;
1034 case ColumnUserFileNameDownloadRemote: {
1035 bool nameMissmatch = false;
1036 wxColour savedColour = dc->GetTextForeground();
1037 if (client->GetClientFilename().IsEmpty()) {
1038 buffer = wxT("[Unknown]");
1039 } else {
1040 buffer = client->GetClientFilename();
1041 const CPartFile * pf = client->GetRequestFile();
1042 if (pf && (pf->GetFileName().GetPrintable().CmpNoCase(buffer) != 0)) {
1043 nameMissmatch = true;
1044 dc->SetTextForeground(*wxRED);
1047 dc->DrawText(buffer, rect.GetX(), rect.GetY() + iTextOffset);
1048 if (nameMissmatch) {
1049 dc->SetTextForeground(savedColour);
1051 break;
1056 int CGenericClientListCtrl::SortProc(wxUIntPtr param1, wxUIntPtr param2, long sortData)
1058 ClientCtrlItem_Struct* item1 = (ClientCtrlItem_Struct*)param1;
1059 ClientCtrlItem_Struct* item2 = (ClientCtrlItem_Struct*)param2;
1061 int sortMod = (sortData & CMuleListCtrl::SORT_DES) ? -1 : 1;
1062 sortData &= CMuleListCtrl::COLUMN_MASK;
1063 int comp = 0;
1065 // Two sources, some different possibilites
1066 // Avilable sources first, if we have both an
1067 // available and an unavailable
1068 comp = ( item2->GetType() - item1->GetType() );
1070 if (comp) {
1071 // unavailable and available. The order is fixed regardless of sort-order.
1072 return comp;
1073 } else {
1074 comp = Compare(item1->GetSource(), item2->GetSource(), sortData);
1077 // We modify the result so that it matches with ascending or decending
1078 return sortMod * comp;
1081 int CGenericClientListCtrl::Compare(
1082 const CUpDownClient* client1, const CUpDownClient* client2, long lParamSort)
1084 switch (lParamSort) {
1085 // Sort by name
1086 case ColumnUserName:
1087 return CmpAny( client1->GetUserName(), client2->GetUserName() );
1089 // Sort by transferred in the following fields
1090 case ColumnUserDownloaded:
1091 return CmpAny( client1->GetTransferredDown(), client2->GetTransferredDown() );
1093 // Sort by transferred in the following fields
1094 case ColumnUserUploaded:
1095 return CmpAny( client1->GetTransferredUp(), client2->GetTransferredUp() );
1097 // Sort by speed
1098 case ColumnUserSpeedDown:
1099 return CmpAny( client1->GetKBpsDown(), client2->GetKBpsDown() );
1101 // Sort by speed
1102 case ColumnUserSpeedUp:
1103 return CmpAny( client1->GetUploadDatarate(), client2->GetUploadDatarate() );
1105 // Sort by parts offered
1106 case ColumnUserProgress:
1107 return CmpAny(
1108 client1->GetAvailablePartCount(),
1109 client2->GetAvailablePartCount() );
1111 // Sort by client version
1112 case ColumnUserVersion: {
1113 if (client1->GetClientSoft() != client2->GetClientSoft()) {
1114 return client1->GetSoftStr().Cmp(client2->GetSoftStr());
1117 if (client1->GetVersion() != client2->GetVersion()) {
1118 return CmpAny(client1->GetVersion(), client2->GetVersion());
1121 return client1->GetClientModString().Cmp(client2->GetClientModString());
1124 // Sort by Queue-Rank
1125 case ColumnUserQueueRankRemote: {
1126 // This will sort by download state: Downloading, OnQueue, Connecting ...
1127 // However, Asked For Another will always be placed last, due to
1128 // sorting in SortProc
1129 if ( client1->GetDownloadState() != client2->GetDownloadState() ) {
1130 return client1->GetDownloadState() - client2->GetDownloadState();
1133 // Placing items on queue before items on full queues
1134 if ( client1->IsRemoteQueueFull() ) {
1135 if ( client2->IsRemoteQueueFull() ) {
1136 return 0;
1137 } else {
1138 return 1;
1140 } else if ( client2->IsRemoteQueueFull() ) {
1141 return -1;
1142 } else {
1143 if ( client1->GetRemoteQueueRank() ) {
1144 if ( client2->GetRemoteQueueRank() ) {
1145 return CmpAny(
1146 client1->GetRemoteQueueRank(),
1147 client2->GetRemoteQueueRank() );
1148 } else {
1149 return -1;
1151 } else {
1152 if ( client2->GetRemoteQueueRank() ) {
1153 return 1;
1154 } else {
1155 return 0;
1161 // Sort by Queue-Rank
1162 case ColumnUserQueueRankLocal: {
1163 // This will sort by download state: Downloading, OnQueue, Connecting ...
1164 // However, Asked For Another will always be placed last, due to
1165 // sorting in SortProc
1166 if ( client1->GetUploadState() != client2->GetUploadState() ) {
1167 return client1->GetUploadState() - client2->GetUploadState();
1170 uint16 rank1 = client1->GetUploadQueueWaitingPosition();
1171 uint16 rank2 = client2->GetUploadQueueWaitingPosition();
1172 // Placing items on queue before items on full queues
1173 if ( !rank1 ) {
1174 if ( !rank2 ) {
1175 return 0;
1176 } else {
1177 return 1;
1179 } else if ( !rank2 ) {
1180 return -1;
1181 } else {
1182 if ( rank1 ) {
1183 if ( rank2 ) {
1184 return CmpAny(
1185 rank1,
1186 rank2 );
1187 } else {
1188 return -1;
1190 } else {
1191 if ( rank2 ) {
1192 return 1;
1193 } else {
1194 return 0;
1200 // Source of source ;)
1201 case ColumnUserOrigin:
1202 return CmpAny(client1->GetSourceFrom(), client2->GetSourceFrom());
1204 // Sort by local filename (download)
1205 case ColumnUserFileNameDownload: {
1206 wxString buffer1, buffer2;
1207 const CPartFile * pf1 = client1->GetRequestFile();
1208 if (pf1) {
1209 buffer1 = pf1->GetFileName().GetPrintable();
1211 const CPartFile * pf2 = client2->GetRequestFile();
1212 if (pf2) {
1213 buffer2 = pf2->GetFileName().GetPrintable();
1215 return CmpAny(buffer1, buffer2);
1218 // Sort by local filename (upload)
1219 case ColumnUserFileNameUpload: {
1220 wxString buffer1, buffer2;
1221 const CKnownFile * kf1 = client1->GetUploadFile();
1222 if (kf1) {
1223 buffer1 = kf1->GetFileName().GetPrintable();
1225 const CKnownFile * kf2 = client2->GetUploadFile();
1226 if (kf2) {
1227 buffer2 = kf2->GetFileName().GetPrintable();
1229 return CmpAny(buffer1, buffer2);
1232 case ColumnUserFileNameDownloadRemote: {
1233 return CmpAny(client1->GetClientFilename(), client2->GetClientFilename());
1236 default:
1237 return 0;
1242 void CGenericClientListCtrl::ShowSourcesCount( int diff )
1244 m_clientcount += diff;
1245 wxStaticText* label = CastByID( ID_CLIENTCOUNT, GetParent(), wxStaticText );
1247 if (label) {
1248 label->SetLabel(CFormat(wxT("%i")) % m_clientcount);
1249 label->GetParent()->Layout();
1253 static const CMuleColour crBoth(0, 192, 0);
1254 static const CMuleColour crFlatBoth(0, 150, 0);
1256 static const CMuleColour crNeither(240, 240, 240);
1257 static const CMuleColour crFlatNeither(224, 224, 224);
1259 static const CMuleColour crClientOnly(104, 104, 104);
1260 static const CMuleColour crFlatClientOnly(0, 0, 0);
1262 static const CMuleColour crPending(255, 208, 0);
1263 static const CMuleColour crNextPending(255, 255, 100);
1265 void CGenericClientListCtrl::DrawSourceStatusBar(
1266 const CUpDownClient* source, wxDC* dc, const wxRect& rect, bool bFlat) const
1268 static CBarShader s_StatusBar(16);
1270 CPartFile* reqfile = source->GetRequestFile();
1272 s_StatusBar.SetHeight(rect.height);
1273 s_StatusBar.SetWidth(rect.width);
1274 s_StatusBar.Set3dDepth( thePrefs::Get3DDepth() );
1275 const BitVector& partStatus = source->GetPartStatus();
1277 if (reqfile && reqfile->GetPartCount() == partStatus.size()) {
1278 s_StatusBar.SetFileSize(reqfile->GetFileSize());
1279 uint16 lastDownloadingPart = source->GetDownloadState() == DS_DOWNLOADING
1280 ? source->GetLastDownloadingPart() : 0xffff;
1281 uint16 nextRequestedPart = source->GetNextRequestedPart();
1283 for ( uint16 i = 0; i < partStatus.size(); i++ ) {
1284 uint64 uStart = PARTSIZE * i;
1285 uint64 uEnd = uStart + reqfile->GetPartSize(i) - 1;
1287 CMuleColour colour;
1288 if (!partStatus.get(i)) {
1289 // client does not have this part
1290 // light grey
1291 colour = bFlat ? crFlatNeither : crNeither;
1292 } else if ( reqfile->IsComplete(i)) {
1293 // completed part
1294 // green
1295 colour = bFlat ? crFlatBoth : crBoth;
1296 } else if (lastDownloadingPart == i) {
1297 // downloading part
1298 // yellow
1299 colour = crPending;
1300 } else if (nextRequestedPart == i) {
1301 // requested part
1302 // light yellow
1303 colour = crNextPending;
1304 } else {
1305 // client has this part, we need it
1306 // black
1307 colour = bFlat ? crFlatClientOnly : crClientOnly;
1310 if ( source->GetRequestFile()->IsStopped() ) {
1311 colour.Blend(50);
1314 s_StatusBar.FillRange(uStart, uEnd, colour);
1316 } else {
1317 s_StatusBar.SetFileSize(1);
1318 s_StatusBar.FillRange(0, 1, bFlat ? crFlatNeither : crNeither);
1321 s_StatusBar.Draw(dc, rect.x, rect.y, bFlat);
1324 static const CMuleColour crUnavailable(240, 240, 240);
1325 static const CMuleColour crFlatUnavailable(224, 224, 224);
1327 static const CMuleColour crAvailable(104, 104, 104);
1328 static const CMuleColour crFlatAvailable(0, 0, 0);
1330 void CGenericClientListCtrl::DrawStatusBar( const CUpDownClient* client, wxDC* dc, const wxRect& rect1 ) const
1332 wxRect rect = rect1;
1333 rect.y += 1;
1334 rect.height -= 2;
1336 wxPen old_pen = dc->GetPen();
1337 wxBrush old_brush = dc->GetBrush();
1338 bool bFlat = thePrefs::UseFlatBar();
1340 wxRect barRect = rect;
1341 if (!bFlat) { // round bar has a black border, the bar itself is 1 pixel less on each border
1342 barRect.x ++;
1343 barRect.y ++;
1344 barRect.height -= 2;
1345 barRect.width -= 2;
1347 static CBarShader s_StatusBar(16);
1349 uint32 partCount = client->GetUpPartCount();
1351 // Seems the partfile in the client object is not necessarily valid when bar is drawn for the first time.
1352 // Keep it simple and make all parts same size.
1353 s_StatusBar.SetFileSize(partCount * PARTSIZE);
1354 s_StatusBar.SetHeight(barRect.height);
1355 s_StatusBar.SetWidth(barRect.width);
1356 s_StatusBar.Set3dDepth( thePrefs::Get3DDepth() );
1358 uint64 uEnd = 0;
1359 for ( uint64 i = 0; i < partCount; i++ ) {
1360 uint64 uStart = PARTSIZE * i;
1361 uEnd = uStart + PARTSIZE - 1;
1363 s_StatusBar.FillRange(uStart, uEnd, client->IsUpPartAvailable(i) ? (bFlat ? crFlatAvailable : crAvailable) : (bFlat ? crFlatUnavailable : crUnavailable));
1365 // fill the rest (if partStatus is empty)
1366 s_StatusBar.FillRange(uEnd + 1, partCount * PARTSIZE - 1, bFlat ? crFlatUnavailable : crUnavailable);
1367 s_StatusBar.Draw(dc, barRect.x, barRect.y, bFlat);
1369 if (!bFlat) {
1370 // Draw black border
1371 dc->SetPen( *wxBLACK_PEN );
1372 dc->SetBrush( *wxTRANSPARENT_BRUSH );
1373 dc->DrawRectangle(rect);
1376 dc->SetPen( old_pen );
1377 dc->SetBrush( old_brush );
1380 // File_checked_for_headers