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