Subversion Repositories Games.Chess Giants

Rev

Rev 21 | Details | Compare with Previous | 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)
21 pmbaty 242
      DestroyWindow (hWnd); // close the window
1 pmbaty 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
124 pmbaty 251
      the_board.reevaluate = true; // refresh the GUI buttons if needed
1 pmbaty 252
   }
253
 
254
   // else are we resizing the window ?
255
   else if (message == WM_SIZE)
256
   {
257
      // get the new window size
258
      GetClientRect (hWnd, &client_rect);
259
 
260
      // position the window elements
261
      SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_CHANNELS), NULL, 16, 64, 296, client_rect.bottom - 80, SWP_NOZORDER);
262
      SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS), NULL, 316, 64, client_rect.right - 332, client_rect.bottom - 80, SWP_NOZORDER);
263
      SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKTOTOGGLE), NULL, 16, 16, client_rect.right - 16, 48, SWP_NOZORDER);
264
      SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), NULL, 0, client_rect.bottom - 16, client_rect.right, 16, SWP_NOZORDER);
265
   }
266
 
267
   // else are we asking how big/small we can resize ?
268
   else if (message == WM_GETMINMAXINFO)
269
   {
270
      minmax = (MINMAXINFO *) lParam; // get a pointer to the min/max info structure
271
 
272
      minmax->ptMinTrackSize.x = WINDOW_MIN_WIDTH;
273
      minmax->ptMinTrackSize.y = WINDOW_MIN_HEIGHT;
274
   }
275
 
276
   // else is it a timer event AND is it our refresh timer ?
277
   else if ((message == WM_TIMER) && (wParam == WINDOW_TIMER_REFRESH))
278
   {
279
      //////////////////////////////////////////////////
280
      // do we need to update the chatter channel list ?
281
      if (chatterchannels_updated)
282
      {
283
         // get a quick access to the list control
284
         hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
285
 
286
         // compute the new checksum and see if it differs from the one we know
287
         new_checksum = 0;
288
         for (channel_index = 0; channel_index < chatterchannel_count; channel_index++)
289
            new_checksum += chatterchannels[channel_index].is_open + wcslen (chatterchannels[channel_index].theme) + chatterchannels[channel_index].member_count;
290
 
291
         // does it differ ?
292
         if (new_checksum != channellist_checksum)
293
         {
294
            // populate the list control and compute its new checksum on the fly
295
            ListView_DeleteAllItems (hListChannelsWnd); // start by emptying it first
296
 
297
            // tell Windows which members of the LVCOLUMN structure we're filling
298
            memset (&lvi, 0, sizeof (lvi));
299
            lvi.mask = LVIF_IMAGE | LVIF_PARAM; // we want to set the image and the item's pointer
300
            for (channel_index = 0; channel_index < chatterchannel_count; channel_index++)
301
            {
302
               // first, attach the right structure to this item
303
               lvi.lParam = (LPARAM) &chatterchannels[channel_index];
304
 
305
               // set item's image and name
306
               lvi.iItem = channel_index;
307
               if (chatterchannels[channel_index].is_open)
308
                  lvi.iImage = channelicons[1].load_index; // this channel is open
309
               else
310
                  lvi.iImage = channelicons[0].load_index; // this channel is silenced
311
               insert_index = ListView_InsertItem (hListChannelsWnd, &lvi); // add each item to list view
312
 
313
               // set item's substrings
314
 
315
               // channel number, theme and member count
316
               swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%d", chatterchannels[channel_index].id);
317
               ListView_SetItemText (hListChannelsWnd, insert_index, 0, temp_string); // channel number
318
               ListView_SetItemText (hListChannelsWnd, insert_index, 1, chatterchannels[channel_index].theme); // theme
319
               swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%d", chatterchannels[channel_index].member_count);
320
               ListView_SetItemText (hListChannelsWnd, insert_index, 2, temp_string); // member count
321
 
322
               // is it the selected chatter channel ? if so, mark it as selected
323
               if (&chatterchannels[channel_index] == selected_chatterchannel)
324
                  ListView_SetItemState (hListChannelsWnd, channel_index, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
325
            }
326
 
327
            channellist_checksum = new_checksum; // remember the new checksum
328
         }
329
 
330
         // now that the display is finished, IF the reply is arrived, set the totals in the window title
331
         if (chatterchannel_count >= 0)
332
            SetWindowText (hWnd, LOCALIZE (L"ChatterChannels_Title"));
333
 
334
         update_selection = true; // update the selected chatter channel members display
335
         update_sortorder = true; // update the sort order
336
         chatterchannels_updated = false; // and remember we updated the list (don't do it twice)
337
      }
338
 
339
      /////////////////////////////////////////////////////////////
340
      // do we need to update the chatter channels member display ?
341
      if (update_selection)
342
      {
343
         // get a quick access to the list controls
344
         hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
345
         hListMembersWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS);
346
 
347
         // if so, populate the channel members list view
348
         ListView_DeleteAllItems (hListMembersWnd); // start by emptying it first
349
         if ((chatterchannel_count > 0) && (selected_chatterchannel != NULL))
350
         {
351
            // select the selected chatter channel in the channel list
352
            ListView_SetItemState (hListChannelsWnd, selected_chatterchannel, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
353
 
354
            memset (&lvi, 0, sizeof (lvi));
355
            lvi.mask = LVIF_IMAGE | LVIF_PARAM; // we want to set the image and the item's pointer
356
            for (member_index = 0; member_index < selected_chatterchannel->member_count; member_index++)
357
            {
358
               // first, attach the right structure to this item
359
               lvi.lParam = (LPARAM) &selected_chatterchannel->members[member_index];
360
 
361
               // set item's image and name
362
               lvi.iItem = member_index;
363
               if (selected_chatterchannel->members[member_index].is_silenced)
364
                  lvi.iImage = listviewicons[HANDLESTATUS_NOTOPENFORAMATCH]; // this player is silenced
365
               else
366
                  lvi.iImage = listviewicons[HANDLESTATUS_AVAILABLE]; // this player is available
367
 
368
               insert_index = ListView_InsertItem (hListMembersWnd, &lvi); // add each item to list view
369
               ListView_SetItemText (hListMembersWnd, insert_index, 0, selected_chatterchannel->members[member_index].nickname); // member name
370
            }
371
         }
372
 
373
         update_selection = false; // don't do it twice
374
      }
375
 
376
      //////////////////////////////////////////////////////
377
      // do we need to sort the chatter channels list view ?
378
      if (update_sortorder)
379
      {
380
         // get a quick access to the list control
381
         hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS);
382
 
383
         // cycle through all columns and see which one is the sort criteria
384
         memset (&hdi, 0, sizeof (hdi));
385
         hdi.mask = HDI_FORMAT;
386
         for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
387
         {
388
            Header_GetItem (ListView_GetHeader (hListChannelsWnd), column_index, &hdi); // get the column's sort arrow state
389
            if ((hdi.fmt & HDF_SORTDOWN) || (hdi.fmt & HDF_SORTUP))
390
               break; // break as soon as we find it
391
         }
392
 
393
         // have we NOT found it ?
394
         if (column_index == sizeof (listviewcolumns) / sizeof (listviewcolumn_t))
395
            column_index = 0; // if so, sort the list view according to the first column
396
 
397
         // now sort the list view
398
         ListView_SortItems (hListChannelsWnd, CompareProc_ListChannels, column_index);
399
 
400
         // is a channel selected ? if so, scroll to the selected item
401
         if ((chatterchannel_count > 0) && (selected_chatterchannel != NULL))
402
            ListView_ScrollTilItem (hListChannelsWnd, selected_chatterchannel);
403
 
404
         update_sortorder = false; // don't do this twice
405
      }
406
   }
407
 
408
   /////////////////////////////////////////////////////////////////
409
   // else is it a list view message from the chatter channel list ?
410
   else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_CHANNELS))
411
   {
412
      lv = (NMLISTVIEW *) lParam; // quick access to list view
413
 
414
      /////////////////////////////////////////////////
415
      // is it a click on one of the headers' columns ?
416
      if (lv->hdr.code == LVN_COLUMNCLICK)
417
      {
418
         // cycle through all columns and reset their sort order
419
         memset (&hdi, 0, sizeof (hdi));
420
         hdi.mask = HDI_FORMAT;
421
         for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
422
         {
423
            Header_GetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // get the column's sort arrow state
424
            hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
425
            if (column_index == lv->iSubItem)
426
            {
427
               listviewcolumns[column_index].sort_descending ^= true; // revert the sort order for the clicked column
428
               hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP);
429
            }
430
            else
431
               listviewcolumns[column_index].sort_descending = false; // reset the sort order for all the other ones
432
            Header_SetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // update the column's sort arrow state
433
         }
434
 
435
         update_sortorder = true; // update sort order
436
         SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // and refresh window now
437
      }
438
 
439
      /////////////////////////////////////////////////////////////
440
      // else is it a single click on one of the chatter channels ?
441
      else if ((lv->hdr.code == NM_CLICK) && (((NMITEMACTIVATE *) lParam)->iItem != -1))
442
      {
443
         // get which item it is in the listview data
444
         memset (&lvi, 0, sizeof (lvi));
445
         lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem;
446
         lvi.mask = LVIF_PARAM;
447
         ListView_GetItem (lv->hdr.hwndFrom, &lvi);
448
 
449
         chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is
450
 
451
         // is it valid ?
452
         if ((chatterchannel >= chatterchannels) && (chatterchannel <= &chatterchannels[chatterchannel_count - 1]))
453
         {
454
            selected_chatterchannel = chatterchannel; // save selected chatter channel
455
            update_selection = true; // update selection
456
            the_scene.update = true; // update scene (because if we were typing something, the chatter channel has changed)
457
            SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // and refresh window now
458
         }
459
      }
460
 
461
      /////////////////////////////////////////////////////////////
462
      // else is it a double-click on one of the chatter channels ?
463
      else if ((lv->hdr.code == NM_DBLCLK) && (((NMITEMACTIVATE *) lParam)->iItem != -1))
464
      {
465
         // get which item it is in the listview data
466
         memset (&lvi, 0, sizeof (lvi));
467
         lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem;
468
         lvi.mask = LVIF_PARAM;
469
         ListView_GetItem (lv->hdr.hwndFrom, &lvi);
470
 
471
         chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is
472
 
473
         // find the network player and make him ask to toggle channel on/off to the server
474
         if ((network_player = Player_FindByType (PLAYER_INTERNET)) != NULL)
475
         {
476
            Player_SendBuffer_Add (network_player, 1000, L"%schannel %d\n", (chatterchannel->is_open ? L"-" : L"+"), chatterchannel->id);
477
            Player_SendBuffer_Add (network_player, 1000, L"inchannel\n"); // send the channels update request
478
         }
479
 
480
         chatterchannels_updated = true; // and update display
481
         SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
482
      }
483
   }
484
 
485
   /////////////////////////////////////////////////////////////////////////
486
   // else is it a list view message from the chatter channel members list ?
487
   else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_CHANNELMEMBERS))
488
   {
489
      lv = (NMLISTVIEW *) lParam; // quick access to list view
490
 
491
      ///////////////////////////////////////////////////////
492
      // is it a double-click on one of the channel members ?
493
      if ((lv->hdr.code == NM_DBLCLK) && (((NMITEMACTIVATE *) lParam)->iItem != -1) && (chatterchannel_count > 0))
494
      {
495
         // get which item it is in the listview data
496
         memset (&lvi, 0, sizeof (lvi));
497
         lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem;
498
         lvi.mask = LVIF_PARAM;
499
         ListView_GetItem (lv->hdr.hwndFrom, &lvi);
500
 
501
         chatterchannelmember = (chatterchannelmember_t *) lvi.lParam; // get the chatter channel it is
502
 
503
         local_player = Player_FindByType (PLAYER_HUMAN); // find the local player
504
 
505
         // is it open AND is it NOT ourselves ? if so, find or create an interlocutor structure for this cc member
506
         if (!chatterchannelmember->is_silenced && (local_player != NULL) && (wcscmp (chatterchannelmember->nickname, local_player->name) != 0))
507
            Interlocutor_FindOrCreate (chatterchannelmember->nickname);
508
         else
509
            PlayerCard_FindOrCreate (chatterchannelmember->nickname); // else just display his player card
510
      }
511
   }
512
 
513
   // else did we take action on one of the controls ?
514
   else if (message == WM_COMMAND)
515
   {
516
      // was it the status bar hyperlink ?
517
      if (wParam_loword == WINDOW_TEXT_STATUSBAR)
518
         ShellExecute (NULL, L"open", PROGRAM_URL, NULL, NULL, SW_MAXIMIZE); // open the donation page in the default browser, maximized
519
   }
520
 
521
   // call the default window message processing function to keep things going
522
   return (DefWindowProc (hWnd, message, wParam, lParam));
523
}
524
 
525
 
526
static int CALLBACK CompareProc_ListChannels (LPARAM lParam1, LPARAM lParam2, LPARAM column)
527
{
528
   // callback function that tells whether the lParam1 listview element comes before lParam2 in the
529
   // sort order of the specified column
530
 
531
   // channel ID
532
   if (column == 0)
533
   {
534
      if (listviewcolumns[column].sort_descending)
535
         return (((chatterchannel_t *) lParam1)->id <= ((chatterchannel_t *) lParam2)->id);
536
      else
537
         return (((chatterchannel_t *) lParam1)->id >= ((chatterchannel_t *) lParam2)->id);
538
   }
539
 
540
   // channel topic
541
   else if (column == 1)
542
   {
543
      if (listviewcolumns[column].sort_descending)
544
         return (_wcsicmp (((chatterchannel_t *) lParam1)->theme, ((chatterchannel_t *) lParam2)->theme));
545
      else
546
         return (-_wcsicmp (((chatterchannel_t *) lParam1)->theme, ((chatterchannel_t *) lParam2)->theme));
547
   }
548
 
549
   // member count
550
   else if (column == 2)
551
   {
552
      if (listviewcolumns[column].sort_descending)
553
         return (((chatterchannel_t *) lParam1)->member_count <= ((chatterchannel_t *) lParam2)->member_count);
554
      else
555
         return (((chatterchannel_t *) lParam1)->member_count >= ((chatterchannel_t *) lParam2)->member_count);
556
   }
557
 
558
   return (0); // should neever reach here
559
}
560
 
561
 
562
static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
563
{
564
   // callback that subclasses the original ListView window procedure, so that we can hook
565
   // some of its messages
566
 
567
   static bool tooltips_initialized = false;
568
   static bool update_tooltips = false;
569
   WNDPROC BaseWndProc;
570
   TOOLINFO toolinfo;
571
   HWND hHeaderWnd;
572
   LVITEM lvi;
573
   chatterchannel_t *chatterchannel;
574
   int column_index;
575
 
576
   // get a pointer to the base window procedure (it was stored as a window property)
577
   BaseWndProc = (WNDPROC) GetProp (hWnd, L"BaseWndProc");
578
   if (BaseWndProc == NULL)
579
      return (DefWindowProc (hWnd, message, wParam, lParam)); // consistency check
580
 
581
   // is the mouse moving around ?
582
   if (message == WM_MOUSEMOVE)
583
   {
584
      // do the tooltips need to be created ?
585
      if (!tooltips_initialized)
586
      {
587
         hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
588
 
589
         // add a tooltip for each column
590
         for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
591
         {
592
            // create the tooltip and set its window topmost
593
            listviewcolumns[column_index].hToolTipWnd = CreateWindowEx (WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
594
                                                                        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
595
                                                                        hHeaderWnd, NULL, hAppInstance, NULL);
596
 
597
            // associate the tooltip with the tool
598
            memset (&toolinfo, 0, sizeof (toolinfo));
599
            toolinfo.cbSize = sizeof (toolinfo);
600
            toolinfo.uFlags = TTF_SUBCLASS;
601
            toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
602
            toolinfo.uId = column_index; // tooltip ID will be column ID
603
            toolinfo.hinst = hAppInstance;
604
            toolinfo.lpszText = listviewcolumns[column_index].text; // tooltip text
605
            Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
606
            SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_ADDTOOL, 0, (LPARAM) &toolinfo);
607
         }
608
 
609
         tooltips_initialized = true; // do this only once
610
      }
611
 
612
      // else do the tooltips need to be updated ?
613
      else if (update_tooltips)
614
      {
615
         hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
616
 
617
         // cycle through all columns
618
         for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
619
         {
620
            // update the tooltip rectangle
621
            memset (&toolinfo, 0, sizeof (toolinfo));
622
            toolinfo.cbSize = sizeof (toolinfo);
623
            toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
624
            toolinfo.uId = column_index; // tooltip ID is column ID
625
            Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
626
            SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_NEWTOOLRECT, 0, (LPARAM) &toolinfo);
627
         }
628
 
629
         update_tooltips = false; // do this only once
630
      }
631
   }
632
 
633
   // else is it a directional key release ?
634
   else if ((message == WM_KEYUP) && ((wParam == VK_UP) || (wParam == VK_DOWN)
635
                                      || (wParam == VK_PRIOR) || (wParam == VK_NEXT) || (wParam == VK_END) || (wParam == VK_HOME)))
636
   {
637
      // get which item is selected in the listview data
638
      memset (&lvi, 0, sizeof (lvi));
639
      lvi.iItem = ListView_GetNextItem (hWnd, -1, LVNI_SELECTED);
640
      lvi.mask = LVIF_PARAM;
641
      ListView_GetItem (hWnd, &lvi);
642
 
643
      chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is
644
 
645
      // is it valid ?
646
      if ((chatterchannel >= chatterchannels) && (chatterchannel <= &chatterchannels[chatterchannel_count - 1]))
647
      {
648
         selected_chatterchannel = chatterchannel; // save selected chatter channel
649
         update_selection = true; // update selection
650
         the_scene.update = true; // update scene (because if we were typing something, the chatter channel has changed)
651
         SendMessage (GetParent (hWnd), WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
652
      }
653
   }
654
 
655
   // else has the user finished dragging/resizing a column header ?
656
   else if ((message == WM_NOTIFY) && ((((NMHDR *) lParam)->code == HDN_ENDTRACK) || (((NMHDR *) lParam)->code == HDN_ENDDRAG)))
657
      update_tooltips = true; // if so, remember to update tooltips on the next mouse move
658
 
659
   // in any case, forward all messages to the original ListView hook procedure
660
   return (CallWindowProc (BaseWndProc, hWnd, message, wParam, lParam));
661
}
662
 
663
 
664
static void ListView_ScrollTilItem (HWND hListViewWnd, void *item)
665
{
666
   // helper function to scroll the given listview til the given item appears
667
 
668
   LVFINDINFO lvfi;
669
   POINT point;
670
   RECT rect;
671
   int item_index;
672
   int top_index;
673
   int count_per_page;
674
   int total_count;
675
   int scroll_pixels;
676
 
677
   // scroll to the selected chatter channel index
678
   memset (&lvfi, 0, sizeof (lvfi));
679
   lvfi.flags = LVFI_PARAM;
680
   lvfi.lParam = (LPARAM) item;
681
   item_index = ListView_FindItem (hListViewWnd, -1, &lvfi); // get the position of the selected chatter channel in the list
682
   if (item_index > 0)
683
   {
684
      // figure out how many items in the listview, how many fit in one page and which index is at the top of the page
685
      total_count = ListView_GetItemCount (hListViewWnd);
686
      count_per_page = ListView_GetCountPerPage (hListViewWnd);
687
      top_index = ListView_GetTopIndex (hListViewWnd);
688
 
689
      // is index already visible ?
690
      if ((item_index >= top_index) && (item_index <= top_index + count_per_page))
691
         return; // if so, no need to scroll
692
 
693
      ListView_GetItemRect (hListViewWnd, 0, &rect, LVIR_BOUNDS); // get the size of a standard item
694
      ListView_GetOrigin (hListViewWnd, &point); // get the list view origin
695
 
696
      scroll_pixels = (rect.bottom - rect.top) * item_index - point.y; // compute how many pixels we should scroll
697
 
698
      // do we need to scroll upwards or downwards ?
699
      if (scroll_pixels < 0)
700
         item_index -= 1 * count_per_page / 3; // scroll up one third of the window
701
      else
702
         item_index -= 2 * count_per_page / 3; // scroll up two thirds of the window
703
 
704
      if (item_index < 0)
705
         item_index = 0; // don't go beyond the beginning
706
      if (item_index > total_count - count_per_page)
707
         item_index = total_count - count_per_page; // don't go beyond the end
708
 
709
      scroll_pixels = (rect.bottom - rect.top) * item_index - point.y; // recompute how many pixels we should scroll
710
      ListView_Scroll (hListViewWnd, 0, scroll_pixels); // and scroll to the desired position
711
   }
712
 
713
   return; // finished
714
}