1 /* Copyright (C) 2007 The SpringLobby Team. All rights reserved. */
2 
3 #include "battlelistctrl.h"
4 
5 #include <wx/intl.h>
6 #include <wx/menu.h>
7 
8 #include "user.h"
9 #include "iconimagelist.h"
10 #include "uiutils.h"
11 #include "ui.h"
12 #include "server.h"
13 #include "countrycodes.h"
14 #include "settings.h"
15 #include "utils/customdialogs.h"
16 #include "useractions.h"
17 #include "helper/sortutil.h"
18 #include "aui/auimanager.h"
19 
20 template<> SortOrder CustomVirtListCtrl<IBattle*,BattleListCtrl>::m_sortorder = SortOrder();
21 
BEGIN_EVENT_TABLE(BattleListCtrl,BattleListCtrl::BaseType)22 BEGIN_EVENT_TABLE(BattleListCtrl, BattleListCtrl::BaseType )
23 
24   EVT_LIST_ITEM_RIGHT_CLICK( BLIST_LIST, BattleListCtrl::OnListRightClick )
25   EVT_MENU                 ( BLIST_DLMAP, BattleListCtrl::OnDLMap )
26   EVT_MENU                 ( BLIST_DLMOD, BattleListCtrl::OnDLMod )
27 #if wxUSE_TIPWINDOW
28 #if !defined(__WXMSW__) /* && !defined(__WXMAC__) */ //disables tooltips on msw /* and mac */
29   EVT_MOTION(BattleListCtrl::OnMouseMotion)
30 #endif
31 #endif
32 END_EVENT_TABLE()
33 
34 BattleListCtrl::BattleListCtrl( wxWindow* parent )
35 	: CustomVirtListCtrl< IBattle *,BattleListCtrl>(parent, BLIST_LIST, wxDefaultPosition, wxDefaultSize,
36 													wxSUNKEN_BORDER | wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_ALIGN_LEFT,
37 													_T("BattleListCtrl"), 4, &BattleListCtrl::CompareOneCrit,
38 													true /*highlight*/, UserActions::ActHighlight, true /*periodic sort*/ ),
39     m_popup( 0 )
40 {
41     GetAui().manager->AddPane( this, wxLEFT, _T("battlelistctrl") );
42 
43 	AddColumn( 0, 26, wxEmptyString, _("Status") );
44 	AddColumn( 1, 26, wxEmptyString, _("Country") );
45 	AddColumn( 2, 26, wxEmptyString, _("Minimum rank to join") );
46 	AddColumn( 3, 335, _("Description"), _("Battle description") );
47 	AddColumn( 4, 170, _("Map"), _("Mapname") );
48 	AddColumn( 5, 238, _("Game"), _("Gamename") );
49 	AddColumn( 6, 123, _("Host"), _("Name of the Host") );
50 	AddColumn( 7, wxLIST_AUTOSIZE_USEHEADER, _("Spectators"), _("Number of Spectators") );
51 	AddColumn( 8, wxLIST_AUTOSIZE_USEHEADER, _("Players"), _("Number of Players joined") );
52 	AddColumn( 9, wxLIST_AUTOSIZE_USEHEADER, _("Max"), _("Maximum number of Players that can join") );
53 	AddColumn( 10, wxLIST_AUTOSIZE_USEHEADER, _("Running"), _("How long the game has been running for") );
54 	AddColumn( 11, wxLIST_AUTOSIZE_USEHEADER, _("Version"), _("Version of the engine") );
55 
56     if ( m_sortorder.size() == 0 ) {
57         m_sortorder[0].col = 0;
58         m_sortorder[0].direction = true;
59         m_sortorder[1].col = 5;
60         m_sortorder[1].direction = true;
61         m_sortorder[2].col = 9;
62         m_sortorder[2].direction = true;
63         m_sortorder[3].col = 4;
64         m_sortorder[3].direction = true;
65     }
66 
67 }
68 
69 
~BattleListCtrl()70 BattleListCtrl::~BattleListCtrl()
71 {
72     delete m_popup;
73 }
74 
GetItemText(long item,long column) const75 wxString BattleListCtrl::GetItemText(long item, long column) const
76 {
77     if ( m_data[item] == NULL )
78         return wxEmptyString;
79 
80     const IBattle& battle= *m_data[item];
81     const BattleOptions& opts = battle.GetBattleOptions();
82 
83     switch ( column ) {
84         case 0:
85         case 1:
86         case 2:
87         default: return wxEmptyString;
88 
89         case 3: return ( opts.description );
90 		case 4: return ( battle.GetHostMapName() );
91 		case 5: return ( battle.GetHostModName() );
92         case 6: return ( opts.founder );
93 		case 7: return wxFormat(_T("%d") ) % int(battle.GetSpectators());
94 		case 8: return wxFormat(_T("%d") ) % (int(battle.GetNumUsers()) - int(battle.GetSpectators()));
95 		case 9: return wxFormat(_T("%d") ) % int(battle.GetMaxPlayers());
96         case 10: return ( wxTimeSpan(0/*h*/,0/*m*/,
97                                      battle.GetBattleRunningTime()).Format(_T("%H:%M")) );
98 		case 11: return battle.GetEngineVersion();
99     }
100 }
101 
GetItemImage(long item) const102 int BattleListCtrl::GetItemImage(long item) const
103 {
104     if ( m_data[item] == NULL )
105         return -1;
106 
107     return icons().GetBattleStatusIcon( *m_data[item] );
108 }
109 
GetItemColumnImage(long item,long column) const110 int BattleListCtrl::GetItemColumnImage(long item, long column) const
111 {
112     if ( m_data[item] == NULL )
113         return -1;
114 
115     const IBattle& battle= *m_data[item];
116 
117     switch ( column ) {
118         default: return -1;
119 
120         case 0: return icons().GetBattleStatusIcon( battle );
121         case 1:
122         {
123         	try
124         	{
125         	 return icons().GetFlagIcon( battle.GetFounder().GetCountry() );
126         	}catch(...){}
127 					break;
128         }
129 		case 2: return icons().GetRankLimitIcon( battle.GetRankNeeded(), false );
130         case 4: return battle.MapExists() ? icons().ICON_EXISTS : icons().ICON_NEXISTS;
131         case 5: return battle.ModExists() ? icons().ICON_EXISTS : icons().ICON_NEXISTS;
132     }
133     return -1; // simply to avoid compiler warning
134 }
135 
GetItemAttr(long item) const136 wxListItemAttr* BattleListCtrl::GetItemAttr(long item) const
137 {
138     if ( item < (long)m_data.size() && item > -1 ) {
139         const IBattle& b = *m_data[item];
140         try
141         {
142 					wxString host = b.GetFounder().GetNick();
143 					wxListItemAttr* attr = HighlightItemUser( host );
144 					if ( attr != NULL )
145 							return attr;
146 
147 					//to avoid color flicker check first if highlighting should be done
148 					//and return if it should
149 					for ( unsigned int i = 0; i < b.GetNumUsers(); ++i){
150 							wxString name = b.GetUser(i).GetNick();
151 							attr = HighlightItemUser( name );
152 							if ( attr != NULL )
153 									return attr;
154 
155 					}
156 				}catch(...){}
157     }
158     return NULL;
159 }
160 
161 
AddBattle(IBattle & battle)162 void BattleListCtrl::AddBattle( IBattle& battle )
163 {
164     if ( AddItem( &battle ) )
165         return;
166 
167     wxLogWarning( _T("Battle already in list.") );
168 }
169 
RemoveBattle(IBattle & battle)170 void BattleListCtrl::RemoveBattle( IBattle& battle )
171 {
172     if ( RemoveItem( &battle ) )
173         return;
174 
175     wxLogError( _T("Didn't find the battle to remove.") );
176 }
177 
UpdateBattle(IBattle & battle)178 void BattleListCtrl::UpdateBattle( IBattle& battle )
179 {
180     int index = GetIndexFromData( &battle );
181 
182     RefreshItem( index );
183     MarkDirtySort();
184 }
185 
OnListRightClick(wxListEvent & event)186 void BattleListCtrl::OnListRightClick( wxListEvent& event )
187 {
188     int idx = event.GetIndex();
189     if ( idx < (long)m_data.size() && idx > -1 ) {
190 
191         DataType dt = m_data[idx];
192         bool mod_missing = !dt->ModExists();
193         bool map_missing = !dt->MapExists();
194         m_popup = new wxMenu( wxEmptyString );
195         // &m enables shortcout "alt + m" and underlines m
196         if ( map_missing )
197             m_popup->Append( BLIST_DLMAP, _("Download &map") );
198 
199         if ( mod_missing )
200 			m_popup->Append( BLIST_DLMOD, _("Download &game") );
201 
202         if ( map_missing || mod_missing )
203             PopupMenu( m_popup );
204     }
205 }
206 
OnDLMap(wxCommandEvent &)207 void BattleListCtrl::OnDLMap( wxCommandEvent& /*unused*/  )
208 {
209     if ( m_selected_index > 0 &&  (long)m_data.size() > m_selected_index ) {
210         DataType dt = m_data[m_selected_index];
211         ui().Download( _T("map"), dt->GetHostMapName(), dt->GetHostMapHash() );
212     }
213 }
214 
OnDLMod(wxCommandEvent &)215 void BattleListCtrl::OnDLMod( wxCommandEvent& /*unused*/  )
216 {
217     if ( m_selected_index > 0 &&  (long)m_data.size() > m_selected_index ) {
218         DataType dt = m_data[m_selected_index];
219         ui().Download(_T("game"), dt->GetHostModName(), dt->GetHostModHash() );
220     }
221 }
222 
Sort()223 void BattleListCtrl::Sort()
224 {
225     if ( m_data.size() > 0 )
226     {
227         SaveSelection();
228         SLInsertionSort( m_data, m_comparator );
229         RestoreSelection();
230     }
231 }
232 
CompareOneCrit(DataType u1,DataType u2,int col,int dir) const233 int BattleListCtrl::CompareOneCrit( DataType u1, DataType u2, int col, int dir ) const
234 {
235     switch ( col ) {
236         case 0: return dir * CompareStatus( u1, u2 );
237         case 1:
238         {
239 					try
240 					{
241         	 return dir * u1->GetFounder().GetCountry().CmpNoCase( u2->GetFounder().GetCountry() );
242         	 }catch(...){}
243 					break;
244         }
245         case 2: return dir * compareSimple( u1->GetRankNeeded(), u2->GetRankNeeded() );
246         case 3: return dir * u1->GetDescription().CmpNoCase( u2->GetDescription() );
247 		case 4: return dir * u1->GetHostMapName().CmpNoCase( u2->GetHostMapName() );
248 		case 5: return dir * u1->GetHostModName().CmpNoCase( u2->GetHostModName() );
249         case 6:
250         {
251         	try
252 					{
253         	 return dir * u1->GetFounder().GetNick().CmpNoCase( u2->GetFounder().GetNick() );
254         	 }catch(...){}
255         	 break;
256         }
257         case 7: return dir * compareSimple( u1->GetSpectators(), u2->GetSpectators() );
258         case 8: return dir * ComparePlayer( u1, u2 );
259         case 9: return dir * compareSimple( u1->GetMaxPlayers(), u2->GetMaxPlayers() );
260         case 10: return dir * compareSimple( u1->GetBattleRunningTime(), u2->GetBattleRunningTime());
261 		case 11: return dir * compareSimple( u1->GetEngineVersion(), u2->GetEngineVersion() );
262         default: return 0;
263     }
264     return 0; // simply to avoid compiler warning
265 }
266 
CompareStatus(DataType u1,DataType u2)267 int BattleListCtrl::CompareStatus( DataType u1, DataType u2 )
268 {
269   const IBattle& battle1 = *u1;
270   const IBattle& battle2 = *u2;
271 
272   int b1 = 0, b2 = 0;
273 
274   if ( battle1.GetNumActivePlayers() == 0 )
275   	b1 += 2000;
276   if ( battle2.GetNumActivePlayers() == 0 )
277   	b2 += 2000;
278   if ( battle1.GetInGame() )
279     b1 += 1000;
280   if ( battle2.GetInGame() )
281     b2 += 1000;
282   if ( battle1.IsLocked() )
283     b1 += 100;
284   if ( battle2.IsLocked() )
285     b2 += 100;
286   if ( battle1.IsPassworded() )
287     b1 += 50;
288   if ( battle2.IsPassworded() )
289     b2 += 50;
290   if ( battle1.IsFull() )
291     b1 += 25;
292   if ( battle2.IsFull() )
293     b2 += 25;
294 
295   // inverse the order
296   if ( b1 < b2 )
297       return -1;
298   if ( b1 > b2 )
299       return 1;
300 
301   return 0;
302 }
303 
304 
ComparePlayer(DataType u1,DataType u2)305 int BattleListCtrl::ComparePlayer( DataType u1, DataType u2 )
306 {
307     const IBattle& battle1 = *u1;
308     const IBattle& battle2 = *u2;
309 
310     int n1 = battle1.GetNumUsers() - battle1.GetSpectators();
311     int n2 = battle2.GetNumUsers() - battle2.GetSpectators();
312     return compareSimple( n1, n2 );
313 }
314 
SetTipWindowText(const long item_hit,const wxPoint & position)315 void BattleListCtrl::SetTipWindowText( const long item_hit, const wxPoint& position)
316 {
317     if ( (long)m_data.size() < item_hit ) {
318         m_tiptext = wxEmptyString;
319         return;
320     }
321 
322     const IBattle& battle= *m_data[item_hit];
323 
324     int column = getColumnFromPosition(position);
325     switch (column)
326     {
327         case 0: // status
328         m_tiptext = icons().GetBattleStatus(battle);
329             break;
330         case 1: // country
331 						try
332 						{
333 							m_tiptext = GetFlagNameFromCountryCode(battle.GetFounder().GetCountry());
334             }catch(...){}
335             break;
336         case 2: // rank_min
337             m_tiptext = m_colinfovec[column].tip;
338             break;
339         case 3: // descrp
340             m_tiptext = battle.GetDescription();
341             break;
342         case 4: //map
343 			m_tiptext = battle.GetHostMapName();
344             break;
345         case 5: //mod
346 			m_tiptext = battle.GetHostModName();
347             break;
348         case 6: // host
349 					try
350 					{
351             m_tiptext = battle.GetFounder().GetNick();
352 					}catch(...){}
353             break;
354         case 7: // specs
355             m_tiptext = _("Spectators:");
356             for (unsigned int i = 0; i < battle.GetNumUsers(); ++i )
357             {
358                 if ( battle.GetUser(i).BattleStatus().spectator ) m_tiptext << _T("\n") << battle.GetUser(i).GetNick();
359             }
360             break;
361         case 8: // player
362             m_tiptext = _("Active Players:");
363             for ( unsigned int i = 0; i < battle.GetNumUsers(); ++i )
364             {
365                 if ( !battle.GetUser(i).BattleStatus().spectator ) m_tiptext << _T("\n") << battle.GetUser(i).GetNick();
366             }
367             break;
368         case 9: //max player
369             m_tiptext = (m_colinfovec[column].tip);
370             break;
371         case 10: //running time
372             m_tiptext = (m_colinfovec[column].tip);
373             break;
374         default: m_tiptext = wxEmptyString;
375             break;
376     }
377 }
378 
379 
GetIndexFromData(const DataType & data) const380 int BattleListCtrl::GetIndexFromData( const DataType& data ) const
381 {
382     static long seekpos;
383     seekpos = LSL::Util::Clamp( seekpos, 0l , (long)m_data.size() );
384     int index = seekpos;
385 
386     for ( DataCIter f_idx = m_data.begin() + seekpos; f_idx != m_data.end() ; ++f_idx )
387     {
388         if ( *f_idx != 0 && data->Equals( *(*f_idx) ) )
389         {
390             seekpos = index;
391             return seekpos;
392         }
393         index++;
394     }
395     //it's ok to init with seekpos, if it had changed this would not be reached
396     int r_index = seekpos - 1;
397     for ( DataRevCIter r_idx = m_data.rbegin() + ( m_data.size() - seekpos ); r_idx != m_data.rend() ; ++r_idx )
398     {
399         if ( *r_idx != 0 && data->Equals( *(*r_idx) ) )
400         {
401             seekpos = r_index;
402             return seekpos;
403         }
404         r_index--;
405     }
406 
407     return -1;
408 }
409 
410