]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/menu/nexuiz/serverlist.c
First working version of an editable Connect/Add-to-favorites feature
[divverent/nexuiz.git] / data / qcsrc / menu / nexuiz / serverlist.c
1 #ifdef INTERFACE
2 CLASS(NexuizServerList) EXTENDS(NexuizListBox)
3         METHOD(NexuizServerList, configureNexuizServerList, void(entity))
4         ATTRIB(NexuizServerList, rowsPerItem, float, 1)
5         METHOD(NexuizServerList, draw, void(entity))
6         METHOD(NexuizServerList, drawListBoxItem, void(entity, float, vector, float))
7         METHOD(NexuizServerList, clickListBoxItem, void(entity, float, vector))
8         METHOD(NexuizServerList, resizeNotify, void(entity, vector, vector, vector, vector))
9         METHOD(NexuizServerList, keyDown, float(entity, float, float, float))
10
11         ATTRIB(NexuizServerList, realFontSize, vector, '0 0 0')
12         ATTRIB(NexuizServerList, realUpperMargin, float, 0)
13         ATTRIB(NexuizServerList, columnPingOrigin, float, 0)
14         ATTRIB(NexuizServerList, columnPingSize, float, 0)
15         ATTRIB(NexuizServerList, columnNameOrigin, float, 0)
16         ATTRIB(NexuizServerList, columnNameSize, float, 0)
17         ATTRIB(NexuizServerList, columnMapOrigin, float, 0)
18         ATTRIB(NexuizServerList, columnMapSize, float, 0)
19         ATTRIB(NexuizServerList, columnTypeOrigin, float, 0)
20         ATTRIB(NexuizServerList, columnTypeSize, float, 0)
21         ATTRIB(NexuizServerList, columnPlayersOrigin, float, 0)
22         ATTRIB(NexuizServerList, columnPlayersSize, float, 0)
23
24         ATTRIB(NexuizServerList, selectedServer, string, string_null) // to restore selected server when needed
25         METHOD(NexuizServerList, setSelected, void(entity, float))
26         METHOD(NexuizServerList, setSortOrder, void(entity, float, float))
27         ATTRIB(NexuizServerList, filterShowEmpty, float, 1)
28         ATTRIB(NexuizServerList, filterShowFull, float, 1)
29         ATTRIB(NexuizServerList, filterString, string, string_null)
30         ATTRIB(NexuizServerList, controlledTextbox, entity, NULL)
31         ATTRIB(NexuizServerList, nextRefreshTime, float, 0)
32         METHOD(NexuizServerList, refreshServerList, void(entity, float)) // refresh mode: 0 = just reparametrize, 1 = send new requests, 2 = clear
33         ATTRIB(NexuizServerList, needsRefresh, float, 1)
34         METHOD(NexuizServerList, focusEnter, void(entity))
35         METHOD(NexuizServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
36         ATTRIB(NexuizServerList, sortButton1, entity, NULL)
37         ATTRIB(NexuizServerList, sortButton2, entity, NULL)
38         ATTRIB(NexuizServerList, sortButton3, entity, NULL)
39         ATTRIB(NexuizServerList, sortButton4, entity, NULL)
40         ATTRIB(NexuizServerList, sortButton5, entity, NULL)
41         ATTRIB(NexuizServerList, connectButton, entity, NULL)
42         ATTRIB(NexuizServerList, currentSortOrder, float, 0)
43         ATTRIB(NexuizServerList, currentSortField, float, -1)
44         ATTRIB(NexuizServerList, lastClickedServer, float, -1)
45         ATTRIB(NexuizServerList, lastClickedTime, float, 0)
46 ENDCLASS(NexuizServerList)
47 entity makeNexuizServerList();
48 void ServerList_Connect_Click(entity btn, entity me);
49 void ServerList_ShowEmpty_Click(entity box, entity me);
50 void ServerList_ShowFull_Click(entity box, entity me);
51 void ServerList_Filter_Change(entity box, entity me);
52 void ServerList_AddRemoveFavorites(string fav, float resolv, float addonly);
53 #endif
54
55 #ifdef IMPLEMENTATION
56 float SLIST_FIELD_CNAME;
57 float SLIST_FIELD_PING;
58 float SLIST_FIELD_GAME;
59 float SLIST_FIELD_MOD;
60 float SLIST_FIELD_MAP;
61 float SLIST_FIELD_NAME;
62 float SLIST_FIELD_MAXPLAYERS;
63 float SLIST_FIELD_NUMPLAYERS;
64 float SLIST_FIELD_NUMHUMANS;
65 float SLIST_FIELD_NUMBOTS;
66 float SLIST_FIELD_PROTOCOL;
67 float SLIST_FIELD_FREESLOTS;
68 float SLIST_FIELD_PLAYERS;
69 float SLIST_FIELD_QCSTATUS;
70 float SLIST_FIELD_ISFAVORITE;
71 void ServerList_UpdateFieldIDs()
72 {
73         SLIST_FIELD_CNAME = gethostcacheindexforkey( "cname" );
74         SLIST_FIELD_PING = gethostcacheindexforkey( "ping" );
75         SLIST_FIELD_GAME = gethostcacheindexforkey( "game" );
76         SLIST_FIELD_MOD = gethostcacheindexforkey( "mod" );
77         SLIST_FIELD_MAP = gethostcacheindexforkey( "map" );
78         SLIST_FIELD_NAME = gethostcacheindexforkey( "name" );
79         SLIST_FIELD_MAXPLAYERS = gethostcacheindexforkey( "maxplayers" );
80         SLIST_FIELD_NUMPLAYERS = gethostcacheindexforkey( "numplayers" );
81         SLIST_FIELD_NUMHUMANS = gethostcacheindexforkey( "numhumans" );
82         SLIST_FIELD_NUMBOTS = gethostcacheindexforkey( "numbots" );
83         SLIST_FIELD_PROTOCOL = gethostcacheindexforkey( "protocol" );
84         SLIST_FIELD_FREESLOTS = gethostcacheindexforkey( "freeslots" );
85         SLIST_FIELD_PLAYERS = gethostcacheindexforkey( "players" );
86         SLIST_FIELD_QCSTATUS = gethostcacheindexforkey( "qcstatus" );
87         SLIST_FIELD_ISFAVORITE = gethostcacheindexforkey( "isfavorite" );
88 }
89
90 entity makeNexuizServerList()
91 {
92         entity me;
93         me = spawnNexuizServerList();
94         me.configureNexuizServerList(me);
95         return me;
96 }
97 void configureNexuizServerListNexuizServerList(entity me)
98 {
99         me.configureNexuizListBox(me);
100
101         ServerList_UpdateFieldIDs();
102
103         me.nItems = 0;
104 }
105 void setSelectedNexuizServerList(entity me, float i)
106 {
107         float save;
108         save = me.selectedItem;
109         setSelectedListBox(me, i);
110         /*
111         if(me.selectedItem == save)
112                 return;
113         */
114         if(me.nItems == 0)
115                 return;
116         if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != me.nItems)
117                 return; // sorry, it would be wrong
118         if(me.selectedServer)
119                 strunzone(me.selectedServer);
120         me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
121 }
122 void refreshServerListNexuizServerList(entity me, float mode)
123 {
124         // 0: just reparametrize
125         // 1: also ask for new servers
126         // 2: clear
127         //print("refresh of type ", ftos(mode), "\n");
128         /* if(mode == 2) // borken
129         {
130                 // clear list
131                 localcmd("net_slist\n");
132                 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
133         }
134         else */
135         {
136                 float m, o;
137                 string s, typestr;
138                 s = me.filterString;
139
140                 m = strstrofs(s, ":", 0);
141                 if(m >= 0)
142                 {
143                         typestr = substring(s, 0, m);
144                         s = substring(s, m + 1, strlen(s) - m - 1);
145                         while(substring(s, 0, 1) == " ")
146                                 s = substring(s, 1, strlen(s) - 1);
147                 }
148                 else
149                         typestr = "";
150
151                 m = SLIST_MASK_AND - 1;
152                 resethostcachemasks();
153                 if(!me.filterShowFull)
154                         sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL);
155                 if(!me.filterShowEmpty)
156                         sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
157                 if(typestr != "")
158                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
159                 m = SLIST_MASK_OR - 1;
160                 if(s != "")
161                 {
162                         sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
163                         sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
164                         sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
165                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
166                 }
167                 o = 2; // favorites first
168                 if(me.currentSortOrder < 0)
169                         o |= 1; // descending
170                 sethostcachesort(me.currentSortField, o);
171                 resorthostcache();
172                 if(mode >= 1)
173                         refreshhostcache();
174         }
175 }
176 void focusEnterNexuizServerList(entity me)
177 {
178         if(time < me.nextRefreshTime)
179         {
180                 //print("sorry, no refresh yet\n");
181                 return;
182         }
183         me.nextRefreshTime = time + 10;
184         me.refreshServerList(me, 1);
185 }
186 void drawNexuizServerList(entity me)
187 {
188         float i, found;
189
190         if(me.currentSortField == -1)
191         {
192                 me.setSortOrder(me, SLIST_FIELD_PING, +1);
193                 me.refreshServerList(me, 2);
194         }
195         else if(me.needsRefresh == 1)
196         {
197                 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
198         }
199         else if(me.needsRefresh == 2)
200         {
201                 me.needsRefresh = 0;
202                 me.refreshServerList(me, 0);
203         }
204
205         me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
206         me.connectButton.disabled = (me.nItems == 0);
207
208         found = 0;
209         if(me.selectedServer)
210         {
211                 for(i = 0; i < me.nItems; ++i)
212                         if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
213                         {
214                                 if(i != me.selectedItem)
215                                 {
216                                         me.lastClickedServer = -1;
217                                         me.selectedItem = i;
218                                 }
219                                 found = 1;
220                                 break;
221                         }
222         }
223         if(!found)
224                 if(me.nItems > 0)
225                 {
226                         if(me.selectedItem >= me.nItems)
227                                 me.selectedItem = me.nItems - 1;
228                         if(me.selectedServer)
229                                 strunzone(me.selectedServer);
230                         me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
231                 }
232
233         drawListBox(me);
234 }
235 void ServerList_PingSort_Click(entity btn, entity me)
236 {
237         me.setSortOrder(me, SLIST_FIELD_PING, +1);
238 }
239 void ServerList_NameSort_Click(entity btn, entity me)
240 {
241         me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
242 }
243 void ServerList_MapSort_Click(entity btn, entity me)
244 {
245         me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
246 }
247 void ServerList_PlayerSort_Click(entity btn, entity me)
248 {
249         me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
250 }
251 void ServerList_TypeSort_Click(entity btn, entity me)
252 {
253         string s, t;
254         float i, m;
255         s = me.filterString;
256         m = strstrofs(s, ":", 0);
257         if(m >= 0)
258         {
259                 s = substring(s, 0, m);
260                 while(substring(s, m+1, 1) == " ") // skip spaces
261                         ++m;
262         }
263         else
264                 s = "";
265
266         for(i = 1; ; ++i) // 20 modes ought to be enough for anyone
267         {
268                 t = GametypeNameFromType(i);
269                 if(i > 1)
270                         if(t == GametypeNameFromType(0)) // it repeats (default case)
271                         {
272                                 // no type was found
273                                 // choose the first one
274                                 s = t;
275                                 break;
276                         }
277                 if(s == GametypeNameFromType(i))
278                 {
279                         // the type was found
280                         // choose the next one
281                         s = GametypeNameFromType(i + 1);
282                         if(s == GametypeNameFromType(0))
283                                 s = "";
284                         break;
285                 }
286         }
287
288         if(s != "")
289                 s = strcat(s, ":");
290         s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
291
292         me.controlledTextbox.setText(me.controlledTextbox, s);
293         me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
294         me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
295         //ServerList_Filter_Change(me.controlledTextbox, me);
296 }
297 void ServerList_Filter_Change(entity box, entity me)
298 {
299         if(me.filterString)
300                 strunzone(me.filterString);
301         if(box.text != "")
302                 me.filterString = strzone(box.text);
303         else
304                 me.filterString = string_null;
305         me.refreshServerList(me, 0);
306 }
307 void ServerList_ShowEmpty_Click(entity box, entity me)
308 {
309         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
310         me.refreshServerList(me, 0);
311 }
312 void ServerList_ShowFull_Click(entity box, entity me)
313 {
314         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
315         me.refreshServerList(me, 0);
316 }
317 void setSortOrderNexuizServerList(entity me, float field, float direction)
318 {
319         if(me.currentSortField == field)
320                 direction = -me.currentSortOrder;
321         me.currentSortOrder = direction;
322         me.currentSortField = field;
323         me.sortButton1.forcePressed = (field == SLIST_FIELD_PING);
324         me.sortButton2.forcePressed = (field == SLIST_FIELD_NAME);
325         me.sortButton3.forcePressed = (field == SLIST_FIELD_MAP);
326         me.sortButton4.forcePressed = 0;
327         me.sortButton5.forcePressed = (field == SLIST_FIELD_NUMHUMANS);
328         me.selectedItem = 0;
329         if(me.selectedServer)
330                 strunzone(me.selectedServer);
331         me.selectedServer = string_null;
332         me.refreshServerList(me, 0);
333 }
334 void positionSortButtonNexuizServerList(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
335 {
336         vector originInLBSpace, sizeInLBSpace;
337         originInLBSpace = eY * (-me.itemHeight);
338         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
339
340         vector originInDialogSpace, sizeInDialogSpace;
341         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
342         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
343
344         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
345         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
346         btn.setText(btn, theTitle);
347         btn.onClick = theFunc;
348         btn.onClickEntity = me;
349         btn.resized = 1;
350 }
351 void resizeNotifyNexuizServerList(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
352 {
353         resizeNotifyNexuizListBox(me, relOrigin, relSize, absOrigin, absSize);
354
355         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
356         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
357         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
358
359         me.columnPingOrigin = 0;
360         me.columnPingSize = me.realFontSize_x * 4;
361         me.columnMapSize = me.realFontSize_x * 12;
362         me.columnTypeSize = me.realFontSize_x * 4;
363         me.columnPlayersSize = me.realFontSize_x * 6;
364         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnTypeSize - 4 * me.realFontSize_x;
365         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
366         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
367         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
368         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
369
370         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, "Ping", ServerList_PingSort_Click);
371         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, "Host name", ServerList_NameSort_Click);
372         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, "Map", ServerList_MapSort_Click);
373         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, "Type", ServerList_TypeSort_Click);
374         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, "Players", ServerList_PlayerSort_Click);
375
376         float f;
377         f = me.currentSortField;
378         if(f >= 0)
379         {
380                 me.currentSortField = -1;
381                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
382         }
383 }
384 void ServerList_Connect_Click(entity btn, entity me)
385 {
386         if(me.nItems > 0)
387                 localcmd("connect ", me.selectedServer, "\n");
388 }
389 void ServerList_AddRemoveFavorites(string fav, float resolv, float addonly)
390 {
391         string s;
392         float i, o;
393
394         if (resolv)
395         {
396                 o = strstrofs(fav, ":", 0);
397                 if (o != -1)
398                 {
399                         i = stof(substring(fav, o + 1, strlen(fav) - o - 1));
400                         fav = substring(fav, 0, o);
401                 }
402                 s = netaddress_resolve(fav, i);
403                 if(s!="") fav = s;
404         }
405         
406         s = cvar_string("net_slist_favorites");
407         o = strstrofs(strcat(" ", s, " "), strcat(" ", fav, " "), 0);
408         if(o == -1)
409                 cvar_set("net_slist_favorites", strcat(s, " ", fav));
410         else if(!addonly)
411                         cvar_set("net_slist_favorites", strcat(
412                                          substring(s, 0, o - 1), substring(s, o + strlen(fav), strlen(s) - o - strlen(fav))));
413         resorthostcache();
414 }
415 void clickListBoxItemNexuizServerList(entity me, float i, vector where)
416 {
417         if(i == me.lastClickedServer)
418                 if(time < me.lastClickedTime + 0.3)
419                 {
420                         // DOUBLE CLICK!
421                         ServerList_Connect_Click(NULL, me);
422                 }
423         me.lastClickedServer = i;
424         me.lastClickedTime = time;
425 }
426 void drawListBoxItemNexuizServerList(entity me, float i, vector absSize, float isSelected)
427 {
428         // layout: Ping, Server name, Map name, NP, TP, MP
429         string s;
430         float p;
431         vector theColor;
432         float theAlpha;
433
434         if(isSelected)
435                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
436
437         if(gethostcachenumber(SLIST_FIELD_NUMPLAYERS, i) >= gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i))
438                 theAlpha = SKINALPHA_SERVERLIST_FULL;
439         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
440                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
441         else
442                 theAlpha = 1;
443         
444         p = gethostcachenumber(SLIST_FIELD_PING, i);
445 #define PING_LOW 75
446 #define PING_MED 200
447 #define PING_HIGH 500
448         if(p < PING_LOW)
449                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
450         else if(p < PING_MED)
451                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
452         else if(p < PING_HIGH)
453         {
454                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
455                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
456         }
457         else
458         {
459                 theColor = eX;
460                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
461         }
462
463         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, i))
464         {
465                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
466                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
467         }
468
469         s = ftos(p);
470         draw_Text(me.realUpperMargin * eY + (me.columnPingSize - draw_TextWidth(s, 0) * me.realFontSize_x) * eX, s, me.realFontSize, theColor, theAlpha, 0);
471         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize / me.realFontSize_x, 0);
472         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
473         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), me.columnMapSize / me.realFontSize_x, 0);
474         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0) * me.realFontSize_x) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
475         s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
476         p = strstrofs(s, ":", 0);
477         if(p >= 0)
478                 s = substring(s, 0, p);
479         else
480                 s = "";
481         s = draw_TextShortenToWidth(s, me.columnMapSize / me.realFontSize_x, 0);
482         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0) * me.realFontSize_x) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
483         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
484         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0) * me.realFontSize_x) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
485 }
486
487 float keyDownNexuizServerList(entity me, float scan, float ascii, float shift)
488 {
489         float i, o;
490         string s;
491
492         if(scan == K_ENTER)
493         {
494                 ServerList_Connect_Click(NULL, me);
495                 return 1;
496         }
497         else if(scan == K_INS || scan == K_MOUSE2 || scan == K_MOUSE3)
498         {
499                 i = me.selectedItem;
500                 if(i < me.nItems)
501                 {
502                         ServerList_AddRemoveFavorites(me.selectedServer, false, false);
503                 }
504                 me.lastClickedServer = -1; // inhibit double clicks using these buttons
505                 if(scan != K_INS)
506                         me.setSelected(me, me.selectedItem);
507         }
508         else if(keyDownListBox(me, scan, ascii, shift))
509                 return 1;
510         else if(!me.controlledTextbox)
511                 return 0;
512         else
513                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
514 }
515 #endif