Subversion Repositories Games.Chess Giants

Rev

Rev 21 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
// window_chatterchannels.cpp
2
 
3
#include "../common.h"
4
 
5
 
6
// window parameters
7
#define WINDOW_CLASSNAME PROGRAM_NAME L" ChatterChannels WndClass"
8
#define WINDOW_DEFAULT_WIDTH 800
9
#define WINDOW_DEFAULT_HEIGHT 600
10
#define WINDOW_MIN_WIDTH 480
11
#define WINDOW_MIN_HEIGHT 240
12
 
13
 
14
// local definitions
15
#define WINDOW_TEXT_DOUBLECLICKTOTOGGLE 1
16
#define WINDOW_LIST_CHANNELS 2
17
#define WINDOW_LIST_CHANNELMEMBERS 3
18
#define WINDOW_TEXT_STATUSBAR 4
19
#define WINDOW_TIMER_REFRESH 1
20
 
21
 
22
// list view column definition
23
typedef struct listviewcolumn_s
24
{
25
   int width;
26
   int alignment;
27
   bool sort_descending;
28
   wchar_t *text;
29
   HWND hToolTipWnd;
30
} listviewcolumn_t;
31
 
32
 
33
// list view icon definition
34
typedef struct listviewicon_s
35
{
36
   HICON icon;
37
   int load_index;
38
} listviewicon_t;
39
 
40
 
41
// global variables used in this module only
42
static bool is_classregistered = false;
43
static int listviewicons[sizeof (handlestatus) / sizeof (handlestatus_t)]; // as big as the handlestatus global array
44
static listviewcolumn_t listviewcolumns[] =
45
{
46
   { 50, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"ChatterChannels_ColumnChannelNumber")*/, NULL }, // text address needs to be set at runtime, because it's mallocated
47
   { 170, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"ChatterChannels_ColumnChannelTheme")*/, NULL },
48
   { 55, LVCFMT_RIGHT, false, NULL /*LOCALIZE (L"ChatterChannels_ColumnChannelMembers")*/, NULL },
49
};
50
static wchar_t temp_string[256];
51
static bool update_selection = false;
52
static bool update_sortorder = false;
53
static int channellist_checksum = 0;
54
static listviewicon_t channelicons[2] = { { NULL, 0 }, { NULL, 0 } };
55
//static HWND hThisWnd = NULL;
56
#define hThisWnd hChatterChannelsWnd // shared variable
57
 
58
 
59
// prototypes of local functions
60
static int CALLBACK CompareProc_ListChannels (LPARAM lParam1, LPARAM lParam2, LPARAM column);
61
static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam);
62
static void ListView_ScrollTilItem (HWND hListViewWnd, void *item);
63
 
64
 
65
// prototypes of local functions
66
static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam);
67
 
68
 
69
void Window_ChatterChannels (void)
70
{
71
   // helper function to fire up the child window
72
 
73
   WNDCLASSEX wc;
74
   player_t *network_player;
75
 
76
   // is the window we want to display already displayed ?
77
   if (IsWindow (hThisWnd))
78
      SetForegroundWindow (hThisWnd); // if so, just bring it to front
79
 
80
   // else the way is clear
81
   else
82
   {
83
      // find the network player and make him ask an update from the server
84
      if ((network_player = Player_FindByType (PLAYER_INTERNET)) != NULL)
85
         Player_SendBuffer_Add (network_player, 1000, L"inchannel\n"); // send the channels update request
86
 
87
      // is the window class NOT registered yet ?
88
      if (!is_classregistered)
89
      {
90
         // if so, register the window class once and for all
91
         memset (&wc, 0, sizeof (wc));
92
         wc.cbSize = sizeof (wc);
93
         wc.style = CS_HREDRAW | CS_VREDRAW;
94
         wc.lpfnWndProc = WindowProc_ThisWindow;
95
         wc.hInstance = hAppInstance;
96
         wc.hIcon = LoadIcon (hAppInstance, (wchar_t *) ICON_MAIN);
97
         wc.hCursor = LoadCursor (NULL, IDC_ARROW);
98
         wc.hbrBackground = GetSysColorBrush (COLOR_3DFACE);
99
         wc.lpszClassName = WINDOW_CLASSNAME;
100
         RegisterClassEx (&wc);
101
 
102
         // also load the icons we need
103
         channelicons[0].icon = (HICON) LoadImage (NULL, L"data/icons/led-red.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
104
         channelicons[1].icon = (HICON) LoadImage (NULL, L"data/icons/led-green.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
105
 
106
         is_classregistered = true; // remember this window class is registered
107
      }
108
 
109
      // create the child window
110
      hThisWnd = CreateWindowEx (WS_EX_CLIENTEDGE, WINDOW_CLASSNAME, LOCALIZE (L"ChatterChannels_TitleBuildingList"), WS_OVERLAPPEDWINDOW,
111
                                 CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT,
112
                                 hMainWnd, NULL, hAppInstance, NULL);
113
   }
114
 
115
   return; // return as soon as the thread is fired up
116
}
117
 
118
 
119
void Window_ChatterChannels_Validated (void)
120
{
121
   // callback function called by the main game thread when the window is validated
122
 
123
   // remember this callback is no longer to be called
124
   is_window_chatterchannels_validated = false;
125
 
126
   return; // finished
127
}
128
 
129
 
130
static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
131
{
132
   // message handler for the child window
133
 
134
   unsigned short wParam_hiword;
135
   unsigned short wParam_loword;
136
   HWND hListChannelsWnd;
137
   HWND hListMembersWnd;
138
   LVCOLUMN lvc;
139
   LVITEM lvi;
140
   HDITEM hdi;
141
   NMLISTVIEW *lv;
142
   HIMAGELIST channels_imagelist;
143
   HIMAGELIST members_imagelist;
144
   MINMAXINFO *minmax;
145
   RECT client_rect;
146
   chatterchannel_t *chatterchannel;
147
   chatterchannelmember_t *chatterchannelmember;
148
   player_t *network_player;
149
   player_t *local_player;
150
   int column_index;
151
   int insert_index;
152
   int channel_index;
153
   int member_index;
154
   int new_checksum;
155
 
156
   // filter out the commonly used message values
157
   wParam_hiword = HIWORD (wParam);
158
   wParam_loword = LOWORD (wParam);
159
 
160
   // have we just fired up this window ?
161
   if (message == WM_CREATE)
162
   {
163
      // center the window
164
      CenterWindow (hWnd, hMainWnd);
165
 
166
      // populate the window
167
      CreateWindowEx (0, L"static", L"",
168
                      WS_CHILD | WS_VISIBLE,
169
                      0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_DOUBLECLICKTOTOGGLE, hAppInstance, NULL);
170
      CreateWindowEx (WS_EX_STATICEDGE, L"syslistview32", L"",
171
                      WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT,
172
                      0, 0, 0, 0, hWnd, (HMENU) WINDOW_LIST_CHANNELS, hAppInstance, NULL);
173
      CreateWindowEx (WS_EX_STATICEDGE, L"syslistview32", L"",
174
                      WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_LIST | LVS_SINGLESEL | LVS_ALIGNLEFT,
175
                      0, 0, 0, 0, hWnd, (HMENU) WINDOW_LIST_CHANNELMEMBERS, hAppInstance, NULL);
176
      CreateWindowEx (WS_EX_RIGHT, L"static", L"",
177
                      WS_CHILD | WS_VISIBLE,
178
                      0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_STATUSBAR, hAppInstance, NULL);
179
 
180
      // prepare the list view : do it before anything that could trigger a fill
181
 
182
      // get a quick access to the list controls
183
      hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
184
      hListMembersWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS);
185
 
186
      // add full row select and header columns rearranging
187
      ListView_SetExtendedListViewStyle (hListChannelsWnd, ListView_GetExtendedListViewStyle (hListChannelsWnd)
188
                                                           | LVS_EX_GRIDLINES | LVS_EX_LABELTIP
189
                                                           | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP);
190
      ListView_SetExtendedListViewStyle (hListMembersWnd, ListView_GetExtendedListViewStyle (hListMembersWnd) | LVS_EX_LABELTIP);
191
 
192
      // subclass the list view procedure to handle header tooltips and save the old procedure as one of the window's property
193
      SetProp (hListChannelsWnd, L"BaseWndProc", (HANDLE) SetWindowLongPtr (hListChannelsWnd, GWL_WNDPROC, (long) ListView_WndProc));
194
 
195
      // tell Windows which members of the LVCOLUMN structure we're filling
196
      memset (&lvc, 0, sizeof (lvc));
197
      lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
198
      for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
199
      {
200
         lvc.iSubItem = column_index;
201
         if (column_index == 0) lvc.pszText = LOCALIZE (L"ChatterChannels_ColumnChannelNumber");
202
         else if (column_index == 1) lvc.pszText = LOCALIZE (L"ChatterChannels_ColumnChannelTheme");
203
         else if (column_index == 2) lvc.pszText = LOCALIZE (L"ChatterChannels_ColumnChannelMembers");
204
         lvc.cx = listviewcolumns[column_index].width;
205
         lvc.fmt = listviewcolumns[column_index].alignment;
206
         ListView_InsertColumn (hListChannelsWnd, column_index, &lvc); // add each column to list view
207
      }
208
 
209
      // create the listviews image lists
210
      channels_imagelist = ImageList_Create (16, 16, ILC_COLOR32, 2, 1); // create an imagelist holding 2 32-bit images
211
      for (insert_index = 0; insert_index < 2; insert_index++)
212
         channelicons[insert_index].load_index = ImageList_AddIcon (channels_imagelist, channelicons[insert_index].icon); // add our icons in the image list
213
      ListView_SetImageList (hListChannelsWnd, channels_imagelist, LVSIL_SMALL); // associate it with the channels listview
214
 
215
      members_imagelist = ImageList_Create (16, 16, ILC_COLOR32, sizeof (handlestatus) / sizeof (handlestatus_t), 1); // create an imagelist holding N 32-bit images
216
      for (insert_index = 1; insert_index < sizeof (handlestatus) / sizeof (handlestatus_t); insert_index++)
217
         listviewicons[insert_index] = ImageList_AddIcon (members_imagelist, handlestatus[insert_index].icon); // add our icons in the image list
218
      ListView_SetImageList (hListMembersWnd, members_imagelist, LVSIL_SMALL); // associate it with the members listviews too
219
 
220
      // refresh the window every second
221
      SetTimer (hWnd, WINDOW_TIMER_REFRESH, 1000, NULL);
222
      update_selection = false; // we aren't aware yet of any selected channel
223
      update_sortorder = false; // we don't need to sort anything yet
224
 
225
      // associate the default GUI font with the help text and set its text
226
      SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKTOTOGGLE), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
227
      SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKTOTOGGLE), LOCALIZE (L"ChatterChannels_DoubleClickToToggle"));
228
 
229
      // associate the default GUI font with the status bar and set its text
230
      SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
231
      SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), LOCALIZE (L"ChatterChannels_StatusBar"));
232
 
233
      // convert the status bar message to a hyperlink
234
      ConvertStaticToHyperlink (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR));
235
 
236
      // now show the window
237
      ShowWindow (hWnd, SW_SHOW);
238
   }
239
 
240
   // else did we click the close button on the title bar ?
241
   else if (message == WM_CLOSE)
242
      DestroyWindow (hWnd); // destroy this window
243
 
244
   // else is the window being destroyed ?
245
   else if (message == WM_DESTROY)
246
   {
247
      KillTimer (hWnd, WINDOW_TIMER_REFRESH); // destroy the timer we used to refresh the window
248
      hThisWnd = NULL; // window is closed
249
      SetForegroundWindow (hMainWnd); // restore focus on the main window
250
      is_window_chatterchannels_validated = true; // remember we closed this window
251
   }
252
 
253
   // else are we resizing the window ?
254
   else if (message == WM_SIZE)
255
   {
256
      // get the new window size
257
      GetClientRect (hWnd, &client_rect);
258
 
259
      // position the window elements
260
      SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_CHANNELS), NULL, 16, 64, 296, client_rect.bottom - 80, SWP_NOZORDER);
261
      SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS), NULL, 316, 64, client_rect.right - 332, client_rect.bottom - 80, SWP_NOZORDER);
262
      SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKTOTOGGLE), NULL, 16, 16, client_rect.right - 16, 48, SWP_NOZORDER);
263
      SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), NULL, 0, client_rect.bottom - 16, client_rect.right, 16, SWP_NOZORDER);
264
   }
265
 
266
   // else are we asking how big/small we can resize ?
267
   else if (message == WM_GETMINMAXINFO)
268
   {
269
      minmax = (MINMAXINFO *) lParam; // get a pointer to the min/max info structure
270
 
271
      minmax->ptMinTrackSize.x = WINDOW_MIN_WIDTH;
272
      minmax->ptMinTrackSize.y = WINDOW_MIN_HEIGHT;
273
   }
274
 
275
   // else is it a timer event AND is it our refresh timer ?
276
   else if ((message == WM_TIMER) && (wParam == WINDOW_TIMER_REFRESH))
277
   {
278
      //////////////////////////////////////////////////
279
      // do we need to update the chatter channel list ?
280
      if (chatterchannels_updated)
281
      {
282
         // get a quick access to the list control
283
         hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
284
 
285
         // compute the new checksum and see if it differs from the one we know
286
         new_checksum = 0;
287
         for (channel_index = 0; channel_index < chatterchannel_count; channel_index++)
288
            new_checksum += chatterchannels[channel_index].is_open + wcslen (chatterchannels[channel_index].theme) + chatterchannels[channel_index].member_count;
289
 
290
         // does it differ ?
291
         if (new_checksum != channellist_checksum)
292
         {
293
            // populate the list control and compute its new checksum on the fly
294
            ListView_DeleteAllItems (hListChannelsWnd); // start by emptying it first
295
 
296
            // tell Windows which members of the LVCOLUMN structure we're filling
297
            memset (&lvi, 0, sizeof (lvi));
298
            lvi.mask = LVIF_IMAGE | LVIF_PARAM; // we want to set the image and the item's pointer
299
            for (channel_index = 0; channel_index < chatterchannel_count; channel_index++)
300
            {
301
               // first, attach the right structure to this item
302
               lvi.lParam = (LPARAM) &chatterchannels[channel_index];
303
 
304
               // set item's image and name
305
               lvi.iItem = channel_index;
306
               if (chatterchannels[channel_index].is_open)
307
                  lvi.iImage = channelicons[1].load_index; // this channel is open
308
               else
309
                  lvi.iImage = channelicons[0].load_index; // this channel is silenced
310
               insert_index = ListView_InsertItem (hListChannelsWnd, &lvi); // add each item to list view
311
 
312
               // set item's substrings
313
 
314
               // channel number, theme and member count
315
               swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%d", chatterchannels[channel_index].id);
316
               ListView_SetItemText (hListChannelsWnd, insert_index, 0, temp_string); // channel number
317
               ListView_SetItemText (hListChannelsWnd, insert_index, 1, chatterchannels[channel_index].theme); // theme
318
               swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%d", chatterchannels[channel_index].member_count);
319
               ListView_SetItemText (hListChannelsWnd, insert_index, 2, temp_string); // member count
320
 
321
               // is it the selected chatter channel ? if so, mark it as selected
322
               if (&chatterchannels[channel_index] == selected_chatterchannel)
323
                  ListView_SetItemState (hListChannelsWnd, channel_index, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
324
            }
325
 
326
            channellist_checksum = new_checksum; // remember the new checksum
327
         }
328
 
329
         // now that the display is finished, IF the reply is arrived, set the totals in the window title
330
         if (chatterchannel_count >= 0)
331
            SetWindowText (hWnd, LOCALIZE (L"ChatterChannels_Title"));
332
 
333
         update_selection = true; // update the selected chatter channel members display
334
         update_sortorder = true; // update the sort order
335
         chatterchannels_updated = false; // and remember we updated the list (don't do it twice)
336
      }
337
 
338
      /////////////////////////////////////////////////////////////
339
      // do we need to update the chatter channels member display ?
340
      if (update_selection)
341
      {
342
         // get a quick access to the list controls
343
         hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
344
         hListMembersWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS);
345
 
346
         // if so, populate the channel members list view
347
         ListView_DeleteAllItems (hListMembersWnd); // start by emptying it first
348
         if ((chatterchannel_count > 0) && (selected_chatterchannel != NULL))
349
         {
350
            // select the selected chatter channel in the channel list
351
            ListView_SetItemState (hListChannelsWnd, selected_chatterchannel, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
352
 
353
            memset (&lvi, 0, sizeof (lvi));
354
            lvi.mask = LVIF_IMAGE | LVIF_PARAM; // we want to set the image and the item's pointer
355
            for (member_index = 0; member_index < selected_chatterchannel->member_count; member_index++)
356
            {
357
               // first, attach the right structure to this item
358
               lvi.lParam = (LPARAM) &selected_chatterchannel->members[member_index];
359
 
360
               // set item's image and name
361
               lvi.iItem = member_index;
362
               if (selected_chatterchannel->members[member_index].is_silenced)
363
                  lvi.iImage = listviewicons[HANDLESTATUS_NOTOPENFORAMATCH]; // this player is silenced
364
               else
365
                  lvi.iImage = listviewicons[HANDLESTATUS_AVAILABLE]; // this player is available
366
 
367
               insert_index = ListView_InsertItem (hListMembersWnd, &lvi); // add each item to list view
368
               ListView_SetItemText (hListMembersWnd, insert_index, 0, selected_chatterchannel->members[member_index].nickname); // member name
369
            }
370
         }
371
 
372
         update_selection = false; // don't do it twice
373
      }
374
 
375
      //////////////////////////////////////////////////////
376
      // do we need to sort the chatter channels list view ?
377
      if (update_sortorder)
378
      {
379
         // get a quick access to the list control
380
         hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
381
 
382
         // cycle through all columns and see which one is the sort criteria
383
         memset (&hdi, 0, sizeof (hdi));
384
         hdi.mask = HDI_FORMAT;
385
         for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
386
         {
387
            Header_GetItem (ListView_GetHeader (hListChannelsWnd), column_index, &hdi); // get the column's sort arrow state
388
            if ((hdi.fmt & HDF_SORTDOWN) || (hdi.fmt & HDF_SORTUP))
389
               break; // break as soon as we find it
390
         }
391
 
392
         // have we NOT found it ?
393
         if (column_index == sizeof (listviewcolumns) / sizeof (listviewcolumn_t))
394
            column_index = 0; // if so, sort the list view according to the first column
395
 
396
         // now sort the list view
397
         ListView_SortItems (hListChannelsWnd, CompareProc_ListChannels, column_index);
398
 
399
         // is a channel selected ? if so, scroll to the selected item
400
         if ((chatterchannel_count > 0) && (selected_chatterchannel != NULL))
401
            ListView_ScrollTilItem (hListChannelsWnd, selected_chatterchannel);
402
 
403
         update_sortorder = false; // don't do this twice
404
      }
405
   }
406
 
407
   /////////////////////////////////////////////////////////////////
408
   // else is it a list view message from the chatter channel list ?
409
   else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_CHANNELS))
410
   {
411
      lv = (NMLISTVIEW *) lParam; // quick access to list view
412
 
413
      /////////////////////////////////////////////////
414
      // is it a click on one of the headers' columns ?
415
      if (lv->hdr.code == LVN_COLUMNCLICK)
416
      {
417
         // cycle through all columns and reset their sort order
418
         memset (&hdi, 0, sizeof (hdi));
419
         hdi.mask = HDI_FORMAT;
420
         for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
421
         {
422
            Header_GetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // get the column's sort arrow state
423
            hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
424
            if (column_index == lv->iSubItem)
425
            {
426
               listviewcolumns[column_index].sort_descending ^= true; // revert the sort order for the clicked column
427
               hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP);
428
            }
429
            else
430
               listviewcolumns[column_index].sort_descending = false; // reset the sort order for all the other ones
431
            Header_SetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // update the column's sort arrow state
432
         }
433
 
434
         update_sortorder = true; // update sort order
435
         SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // and refresh window now
436
      }
437
 
438
      /////////////////////////////////////////////////////////////
439
      // else is it a single click on one of the chatter channels ?
440
      else if ((lv->hdr.code == NM_CLICK) && (((NMITEMACTIVATE *) lParam)->iItem != -1))
441
      {
442
         // get which item it is in the listview data
443
         memset (&lvi, 0, sizeof (lvi));
444
         lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem;
445
         lvi.mask = LVIF_PARAM;
446
         ListView_GetItem (lv->hdr.hwndFrom, &lvi);
447
 
448
         chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is
449
 
450
         // is it valid ?
451
         if ((chatterchannel >= chatterchannels) && (chatterchannel <= &chatterchannels[chatterchannel_count - 1]))
452
         {
453
            selected_chatterchannel = chatterchannel; // save selected chatter channel
454
            update_selection = true; // update selection
455
            the_scene.update = true; // update scene (because if we were typing something, the chatter channel has changed)
456
            SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // and refresh window now
457
         }
458
      }
459
 
460
      /////////////////////////////////////////////////////////////
461
      // else is it a double-click on one of the chatter channels ?
462
      else if ((lv->hdr.code == NM_DBLCLK) && (((NMITEMACTIVATE *) lParam)->iItem != -1))
463
      {
464
         // get which item it is in the listview data
465
         memset (&lvi, 0, sizeof (lvi));
466
         lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem;
467
         lvi.mask = LVIF_PARAM;
468
         ListView_GetItem (lv->hdr.hwndFrom, &lvi);
469
 
470
         chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is
471
 
472
         // find the network player and make him ask to toggle channel on/off to the server
473
         if ((network_player = Player_FindByType (PLAYER_INTERNET)) != NULL)
474
         {
475
            Player_SendBuffer_Add (network_player, 1000, L"%schannel %d\n", (chatterchannel->is_open ? L"-" : L"+"), chatterchannel->id);
476
            Player_SendBuffer_Add (network_player, 1000, L"inchannel\n"); // send the channels update request
477
         }
478
 
479
         chatterchannels_updated = true; // and update display
480
         SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
481
      }
482
   }
483
 
484
   /////////////////////////////////////////////////////////////////////////
485
   // else is it a list view message from the chatter channel members list ?
486
   else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_CHANNELMEMBERS))
487
   {
488
      lv = (NMLISTVIEW *) lParam; // quick access to list view
489
 
490
      ///////////////////////////////////////////////////////
491
      // is it a double-click on one of the channel members ?
492
      if ((lv->hdr.code == NM_DBLCLK) && (((NMITEMACTIVATE *) lParam)->iItem != -1) && (chatterchannel_count > 0))
493
      {
494
         // get which item it is in the listview data
495
         memset (&lvi, 0, sizeof (lvi));
496
         lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem;
497
         lvi.mask = LVIF_PARAM;
498
         ListView_GetItem (lv->hdr.hwndFrom, &lvi);
499
 
500
         chatterchannelmember = (chatterchannelmember_t *) lvi.lParam; // get the chatter channel it is
501
 
502
         local_player = Player_FindByType (PLAYER_HUMAN); // find the local player
503
 
504
         // is it open AND is it NOT ourselves ? if so, find or create an interlocutor structure for this cc member
505
         if (!chatterchannelmember->is_silenced && (local_player != NULL) && (wcscmp (chatterchannelmember->nickname, local_player->name) != 0))
506
            Interlocutor_FindOrCreate (chatterchannelmember->nickname);
507
         else
508
            PlayerCard_FindOrCreate (chatterchannelmember->nickname); // else just display his player card
509
      }
510
   }
511
 
512
   // else did we take action on one of the controls ?
513
   else if (message == WM_COMMAND)
514
   {
515
      // was it the status bar hyperlink ?
516
      if (wParam_loword == WINDOW_TEXT_STATUSBAR)
517
         ShellExecute (NULL, L"open", PROGRAM_URL, NULL, NULL, SW_MAXIMIZE); // open the donation page in the default browser, maximized
518
   }
519
 
520
   // call the default window message processing function to keep things going
521
   return (DefWindowProc (hWnd, message, wParam, lParam));
522
}
523
 
524
 
525
static int CALLBACK CompareProc_ListChannels (LPARAM lParam1, LPARAM lParam2, LPARAM column)
526
{
527
   // callback function that tells whether the lParam1 listview element comes before lParam2 in the
528
   // sort order of the specified column
529
 
530
   // channel ID
531
   if (column == 0)
532
   {
533
      if (listviewcolumns[column].sort_descending)
534
         return (((chatterchannel_t *) lParam1)->id <= ((chatterchannel_t *) lParam2)->id);
535
      else
536
         return (((chatterchannel_t *) lParam1)->id >= ((chatterchannel_t *) lParam2)->id);
537
   }
538
 
539
   // channel topic
540
   else if (column == 1)
541
   {
542
      if (listviewcolumns[column].sort_descending)
543
         return (_wcsicmp (((chatterchannel_t *) lParam1)->theme, ((chatterchannel_t *) lParam2)->theme));
544
      else
545
         return (-_wcsicmp (((chatterchannel_t *) lParam1)->theme, ((chatterchannel_t *) lParam2)->theme));
546
   }
547
 
548
   // member count
549
   else if (column == 2)
550
   {
551
      if (listviewcolumns[column].sort_descending)
552
         return (((chatterchannel_t *) lParam1)->member_count <= ((chatterchannel_t *) lParam2)->member_count);
553
      else
554
         return (((chatterchannel_t *) lParam1)->member_count >= ((chatterchannel_t *) lParam2)->member_count);
555
   }
556
 
557
   return (0); // should neever reach here
558
}
559
 
560
 
561
static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
562
{
563
   // callback that subclasses the original ListView window procedure, so that we can hook
564
   // some of its messages
565
 
566
   static bool tooltips_initialized = false;
567
   static bool update_tooltips = false;
568
   WNDPROC BaseWndProc;
569
   TOOLINFO toolinfo;
570
   HWND hHeaderWnd;
571
   LVITEM lvi;
572
   chatterchannel_t *chatterchannel;
573
   int column_index;
574
 
575
   // get a pointer to the base window procedure (it was stored as a window property)
576
   BaseWndProc = (WNDPROC) GetProp (hWnd, L"BaseWndProc");
577
   if (BaseWndProc == NULL)
578
      return (DefWindowProc (hWnd, message, wParam, lParam)); // consistency check
579
 
580
   // is the mouse moving around ?
581
   if (message == WM_MOUSEMOVE)
582
   {
583
      // do the tooltips need to be created ?
584
      if (!tooltips_initialized)
585
      {
586
         hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
587
 
588
         // add a tooltip for each column
589
         for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
590
         {
591
            // create the tooltip and set its window topmost
592
            listviewcolumns[column_index].hToolTipWnd = CreateWindowEx (WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
593
                                                                        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
594
                                                                        hHeaderWnd, NULL, hAppInstance, NULL);
595
 
596
            // associate the tooltip with the tool
597
            memset (&toolinfo, 0, sizeof (toolinfo));
598
            toolinfo.cbSize = sizeof (toolinfo);
599
            toolinfo.uFlags = TTF_SUBCLASS;
600
            toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
601
            toolinfo.uId = column_index; // tooltip ID will be column ID
602
            toolinfo.hinst = hAppInstance;
603
            toolinfo.lpszText = listviewcolumns[column_index].text; // tooltip text
604
            Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
605
            SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_ADDTOOL, 0, (LPARAM) &toolinfo);
606
         }
607
 
608
         tooltips_initialized = true; // do this only once
609
      }
610
 
611
      // else do the tooltips need to be updated ?
612
      else if (update_tooltips)
613
      {
614
         hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
615
 
616
         // cycle through all columns
617
         for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
618
         {
619
            // update the tooltip rectangle
620
            memset (&toolinfo, 0, sizeof (toolinfo));
621
            toolinfo.cbSize = sizeof (toolinfo);
622
            toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
623
            toolinfo.uId = column_index; // tooltip ID is column ID
624
            Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
625
            SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_NEWTOOLRECT, 0, (LPARAM) &toolinfo);
626
         }
627
 
628
         update_tooltips = false; // do this only once
629
      }
630
   }
631
 
632
   // else is it a directional key release ?
633
   else if ((message == WM_KEYUP) && ((wParam == VK_UP) || (wParam == VK_DOWN)
634
                                      || (wParam == VK_PRIOR) || (wParam == VK_NEXT) || (wParam == VK_END) || (wParam == VK_HOME)))
635
   {
636
      // get which item is selected in the listview data
637
      memset (&lvi, 0, sizeof (lvi));
638
      lvi.iItem = ListView_GetNextItem (hWnd, -1, LVNI_SELECTED);
639
      lvi.mask = LVIF_PARAM;
640
      ListView_GetItem (hWnd, &lvi);
641
 
642
      chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is
643
 
644
      // is it valid ?
645
      if ((chatterchannel >= chatterchannels) && (chatterchannel <= &chatterchannels[chatterchannel_count - 1]))
646
      {
647
         selected_chatterchannel = chatterchannel; // save selected chatter channel
648
         update_selection = true; // update selection
649
         the_scene.update = true; // update scene (because if we were typing something, the chatter channel has changed)
650
         SendMessage (GetParent (hWnd), WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
651
      }
652
   }
653
 
654
   // else has the user finished dragging/resizing a column header ?
655
   else if ((message == WM_NOTIFY) && ((((NMHDR *) lParam)->code == HDN_ENDTRACK) || (((NMHDR *) lParam)->code == HDN_ENDDRAG)))
656
      update_tooltips = true; // if so, remember to update tooltips on the next mouse move
657
 
658
   // in any case, forward all messages to the original ListView hook procedure
659
   return (CallWindowProc (BaseWndProc, hWnd, message, wParam, lParam));
660
}
661
 
662
 
663
static void ListView_ScrollTilItem (HWND hListViewWnd, void *item)
664
{
665
   // helper function to scroll the given listview til the given item appears
666
 
667
   LVFINDINFO lvfi;
668
   POINT point;
669
   RECT rect;
670
   int item_index;
671
   int top_index;
672
   int count_per_page;
673
   int total_count;
674
   int scroll_pixels;
675
 
676
   // scroll to the selected chatter channel index
677
   memset (&lvfi, 0, sizeof (lvfi));
678
   lvfi.flags = LVFI_PARAM;
679
   lvfi.lParam = (LPARAM) item;
680
   item_index = ListView_FindItem (hListViewWnd, -1, &lvfi); // get the position of the selected chatter channel in the list
681
   if (item_index > 0)
682
   {
683
      // figure out how many items in the listview, how many fit in one page and which index is at the top of the page
684
      total_count = ListView_GetItemCount (hListViewWnd);
685
      count_per_page = ListView_GetCountPerPage (hListViewWnd);
686
      top_index = ListView_GetTopIndex (hListViewWnd);
687
 
688
      // is index already visible ?
689
      if ((item_index >= top_index) && (item_index <= top_index + count_per_page))
690
         return; // if so, no need to scroll
691
 
692
      ListView_GetItemRect (hListViewWnd, 0, &rect, LVIR_BOUNDS); // get the size of a standard item
693
      ListView_GetOrigin (hListViewWnd, &point); // get the list view origin
694
 
695
      scroll_pixels = (rect.bottom - rect.top) * item_index - point.y; // compute how many pixels we should scroll
696
 
697
      // do we need to scroll upwards or downwards ?
698
      if (scroll_pixels < 0)
699
         item_index -= 1 * count_per_page / 3; // scroll up one third of the window
700
      else
701
         item_index -= 2 * count_per_page / 3; // scroll up two thirds of the window
702
 
703
      if (item_index < 0)
704
         item_index = 0; // don't go beyond the beginning
705
      if (item_index > total_count - count_per_page)
706
         item_index = total_count - count_per_page; // don't go beyond the end
707
 
708
      scroll_pixels = (rect.bottom - rect.top) * item_index - point.y; // recompute how many pixels we should scroll
709
      ListView_Scroll (hListViewWnd, 0, scroll_pixels); // and scroll to the desired position
710
   }
711
 
712
   return; // finished
713
}