- // window_chatterchannels.cpp 
-   
- #include "../common.h" 
-   
-   
- // window parameters 
- #define WINDOW_CLASSNAME PROGRAM_NAME L" ChatterChannels WndClass" 
- #define WINDOW_DEFAULT_WIDTH 800 
- #define WINDOW_DEFAULT_HEIGHT 600 
- #define WINDOW_MIN_WIDTH 480 
- #define WINDOW_MIN_HEIGHT 240 
-   
-   
- // local definitions 
- #define WINDOW_TEXT_DOUBLECLICKTOTOGGLE 1 
- #define WINDOW_LIST_CHANNELS 2 
- #define WINDOW_LIST_CHANNELMEMBERS 3 
- #define WINDOW_TEXT_STATUSBAR 4 
- #define WINDOW_TIMER_REFRESH 1 
-   
-   
- // list view column definition 
- typedef struct listviewcolumn_s 
- { 
-    int width; 
-    int alignment; 
-    bool sort_descending; 
-    wchar_t *text; 
-    HWND hToolTipWnd; 
- } listviewcolumn_t; 
-   
-   
- // list view icon definition 
- typedef struct listviewicon_s 
- { 
-    HICON icon; 
-    int load_index; 
- } listviewicon_t; 
-   
-   
- // global variables used in this module only 
- static bool is_classregistered = false; 
- static int listviewicons[sizeof (handlestatus) / sizeof (handlestatus_t)]; // as big as the handlestatus global array 
- static listviewcolumn_t listviewcolumns[] = 
- { 
-    { 50, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"ChatterChannels_ColumnChannelNumber")*/, NULL }, // text address needs to be set at runtime, because it's mallocated 
-    { 170, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"ChatterChannels_ColumnChannelTheme")*/, NULL }, 
-    { 55, LVCFMT_RIGHT, false, NULL /*LOCALIZE (L"ChatterChannels_ColumnChannelMembers")*/, NULL }, 
- }; 
- static wchar_t temp_string[256]; 
- static bool update_selection = false; 
- static bool update_sortorder = false; 
- static int channellist_checksum = 0; 
- static listviewicon_t channelicons[2] = { { NULL, 0 }, { NULL, 0 } }; 
- //static HWND hThisWnd = NULL; 
- #define hThisWnd hChatterChannelsWnd // shared variable 
-   
-   
- // prototypes of local functions 
- static int CALLBACK CompareProc_ListChannels (LPARAM lParam1, LPARAM lParam2, LPARAM column); 
- static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam); 
- static void ListView_ScrollTilItem (HWND hListViewWnd, void *item); 
-   
-   
- // prototypes of local functions 
- static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam); 
-   
-   
- void Window_ChatterChannels (void) 
- { 
-    // helper function to fire up the child window 
-   
-    WNDCLASSEX wc; 
-    player_t *network_player; 
-   
-    // is the window we want to display already displayed ? 
-    if (IsWindow (hThisWnd)) 
-       SetForegroundWindow (hThisWnd); // if so, just bring it to front 
-   
-    // else the way is clear 
-    else 
-    { 
-       // find the network player and make him ask an update from the server 
-       if ((network_player = Player_FindByType (PLAYER_INTERNET)) != NULL) 
-          Player_SendBuffer_Add (network_player, 1000, L"inchannel\n"); // send the channels update request 
-   
-       // is the window class NOT registered yet ? 
-       if (!is_classregistered) 
-       { 
-          // if so, register the window class once and for all 
-          memset (&wc, 0, sizeof (wc)); 
-          wc.cbSize = sizeof (wc); 
-          wc.style = CS_HREDRAW | CS_VREDRAW; 
-          wc.lpfnWndProc = WindowProc_ThisWindow; 
-          wc.hInstance = hAppInstance; 
-          wc.hIcon = LoadIcon (hAppInstance, (wchar_t *) ICON_MAIN); 
-          wc.hCursor = LoadCursor (NULL, IDC_ARROW); 
-          wc.hbrBackground = GetSysColorBrush (COLOR_3DFACE); 
-          wc.lpszClassName = WINDOW_CLASSNAME; 
-          RegisterClassEx (&wc); 
-   
-          // also load the icons we need 
-          channelicons[0].icon = (HICON) LoadImage (NULL, L"data/icons/led-red.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE); 
-          channelicons[1].icon = (HICON) LoadImage (NULL, L"data/icons/led-green.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE); 
-   
-          is_classregistered = true; // remember this window class is registered 
-       } 
-   
-       // create the child window 
-       hThisWnd = CreateWindowEx (WS_EX_CLIENTEDGE, WINDOW_CLASSNAME, LOCALIZE (L"ChatterChannels_TitleBuildingList"), WS_OVERLAPPEDWINDOW, 
-                                  CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT, 
-                                  hMainWnd, NULL, hAppInstance, NULL); 
-    } 
-   
-    return; // return as soon as the thread is fired up 
- } 
-   
-   
- void Window_ChatterChannels_Validated (void) 
- { 
-    // callback function called by the main game thread when the window is validated 
-   
-    // remember this callback is no longer to be called 
-    is_window_chatterchannels_validated = false; 
-   
-    return; // finished 
- } 
-   
-   
- static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam) 
- { 
-    // message handler for the child window 
-   
-    unsigned short wParam_hiword; 
-    unsigned short wParam_loword; 
-    HWND hListChannelsWnd; 
-    HWND hListMembersWnd; 
-    LVCOLUMN lvc; 
-    LVITEM lvi; 
-    HDITEM hdi; 
-    NMLISTVIEW *lv; 
-    HIMAGELIST channels_imagelist; 
-    HIMAGELIST members_imagelist; 
-    MINMAXINFO *minmax; 
-    RECT client_rect; 
-    chatterchannel_t *chatterchannel; 
-    chatterchannelmember_t *chatterchannelmember; 
-    player_t *network_player; 
-    player_t *local_player; 
-    int column_index; 
-    int insert_index; 
-    int channel_index; 
-    int member_index; 
-    int new_checksum; 
-   
-    // filter out the commonly used message values 
-    wParam_hiword = HIWORD (wParam); 
-    wParam_loword = LOWORD (wParam); 
-   
-    // have we just fired up this window ? 
-    if (message == WM_CREATE) 
-    { 
-       // center the window 
-       CenterWindow (hWnd, hMainWnd); 
-   
-       // populate the window 
-       CreateWindowEx (0, L"static", L"", 
-                       WS_CHILD | WS_VISIBLE, 
-                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_DOUBLECLICKTOTOGGLE, hAppInstance, NULL); 
-       CreateWindowEx (WS_EX_STATICEDGE, L"syslistview32", L"",  
-                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT,  
-                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_LIST_CHANNELS, hAppInstance, NULL); 
-       CreateWindowEx (WS_EX_STATICEDGE, L"syslistview32", L"",  
-                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_LIST | LVS_SINGLESEL | LVS_ALIGNLEFT,  
-                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_LIST_CHANNELMEMBERS, hAppInstance, NULL); 
-       CreateWindowEx (WS_EX_RIGHT, L"static", L"", 
-                       WS_CHILD | WS_VISIBLE, 
-                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_STATUSBAR, hAppInstance, NULL); 
-   
-       // prepare the list view : do it before anything that could trigger a fill 
-   
-       // get a quick access to the list controls 
-       hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS); 
-       hListMembersWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS); 
-   
-       // add full row select and header columns rearranging 
-       ListView_SetExtendedListViewStyle (hListChannelsWnd, ListView_GetExtendedListViewStyle (hListChannelsWnd) 
-                                                            | LVS_EX_GRIDLINES | LVS_EX_LABELTIP 
-                                                            | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP); 
-       ListView_SetExtendedListViewStyle (hListMembersWnd, ListView_GetExtendedListViewStyle (hListMembersWnd) | LVS_EX_LABELTIP); 
-   
-       // subclass the list view procedure to handle header tooltips and save the old procedure as one of the window's property 
-       SetProp (hListChannelsWnd, L"BaseWndProc", (HANDLE) SetWindowLongPtr (hListChannelsWnd, GWL_WNDPROC, (long) ListView_WndProc)); 
-   
-       // tell Windows which members of the LVCOLUMN structure we're filling 
-       memset (&lvc, 0, sizeof (lvc)); 
-       lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;  
-       for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++) 
-       { 
-          lvc.iSubItem = column_index; 
-          if (column_index == 0) lvc.pszText = LOCALIZE (L"ChatterChannels_ColumnChannelNumber"); 
-          else if (column_index == 1) lvc.pszText = LOCALIZE (L"ChatterChannels_ColumnChannelTheme"); 
-          else if (column_index == 2) lvc.pszText = LOCALIZE (L"ChatterChannels_ColumnChannelMembers"); 
-          lvc.cx = listviewcolumns[column_index].width; 
-          lvc.fmt = listviewcolumns[column_index].alignment; 
-          ListView_InsertColumn (hListChannelsWnd, column_index, &lvc); // add each column to list view 
-       } 
-   
-       // create the listviews image lists 
-       channels_imagelist = ImageList_Create (16, 16, ILC_COLOR32, 2, 1); // create an imagelist holding 2 32-bit images 
-       for (insert_index = 0; insert_index < 2; insert_index++) 
-          channelicons[insert_index].load_index = ImageList_AddIcon (channels_imagelist, channelicons[insert_index].icon); // add our icons in the image list 
-       ListView_SetImageList (hListChannelsWnd, channels_imagelist, LVSIL_SMALL); // associate it with the channels listview 
-   
-       members_imagelist = ImageList_Create (16, 16, ILC_COLOR32, sizeof (handlestatus) / sizeof (handlestatus_t), 1); // create an imagelist holding N 32-bit images 
-       for (insert_index = 1; insert_index < sizeof (handlestatus) / sizeof (handlestatus_t); insert_index++) 
-          listviewicons[insert_index] = ImageList_AddIcon (members_imagelist, handlestatus[insert_index].icon); // add our icons in the image list 
-       ListView_SetImageList (hListMembersWnd, members_imagelist, LVSIL_SMALL); // associate it with the members listviews too 
-   
-       // refresh the window every second 
-       SetTimer (hWnd, WINDOW_TIMER_REFRESH, 1000, NULL); 
-       update_selection = false; // we aren't aware yet of any selected channel 
-       update_sortorder = false; // we don't need to sort anything yet 
-   
-       // associate the default GUI font with the help text and set its text 
-       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKTOTOGGLE), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false); 
-       SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKTOTOGGLE), LOCALIZE (L"ChatterChannels_DoubleClickToToggle")); 
-   
-       // associate the default GUI font with the status bar and set its text 
-       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false); 
-       SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), LOCALIZE (L"ChatterChannels_StatusBar")); 
-   
-       // convert the status bar message to a hyperlink 
-       ConvertStaticToHyperlink (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR)); 
-   
-       // now show the window 
-       ShowWindow (hWnd, SW_SHOW); 
-    } 
-   
-    // else did we click the close button on the title bar ? 
-    else if (message == WM_CLOSE) 
-       DestroyWindow (hWnd); // destroy this window 
-   
-    // else is the window being destroyed ? 
-    else if (message == WM_DESTROY) 
-    { 
-       KillTimer (hWnd, WINDOW_TIMER_REFRESH); // destroy the timer we used to refresh the window 
-       hThisWnd = NULL; // window is closed 
-       SetForegroundWindow (hMainWnd); // restore focus on the main window 
-       is_window_chatterchannels_validated = true; // remember we closed this window 
-    } 
-   
-    // else are we resizing the window ? 
-    else if (message == WM_SIZE) 
-    { 
-       // get the new window size 
-       GetClientRect (hWnd, &client_rect); 
-   
-       // position the window elements 
-       SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_CHANNELS), NULL, 16, 64, 296, client_rect.bottom - 80, SWP_NOZORDER); 
-       SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS), NULL, 316, 64, client_rect.right - 332, client_rect.bottom - 80, SWP_NOZORDER); 
-       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKTOTOGGLE), NULL, 16, 16, client_rect.right - 16, 48, SWP_NOZORDER); 
-       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), NULL, 0, client_rect.bottom - 16, client_rect.right, 16, SWP_NOZORDER); 
-    } 
-   
-    // else are we asking how big/small we can resize ? 
-    else if (message == WM_GETMINMAXINFO) 
-    { 
-       minmax = (MINMAXINFO *) lParam; // get a pointer to the min/max info structure 
-   
-       minmax->ptMinTrackSize.x = WINDOW_MIN_WIDTH; 
-       minmax->ptMinTrackSize.y = WINDOW_MIN_HEIGHT; 
-    } 
-   
-    // else is it a timer event AND is it our refresh timer ? 
-    else if ((message == WM_TIMER) && (wParam == WINDOW_TIMER_REFRESH)) 
-    { 
-       ////////////////////////////////////////////////// 
-       // do we need to update the chatter channel list ? 
-       if (chatterchannels_updated) 
-       { 
-          // get a quick access to the list control 
-          hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS); 
-   
-          // compute the new checksum and see if it differs from the one we know 
-          new_checksum = 0; 
-          for (channel_index = 0; channel_index < chatterchannel_count; channel_index++) 
-             new_checksum += chatterchannels[channel_index].is_open + wcslen (chatterchannels[channel_index].theme) + chatterchannels[channel_index].member_count; 
-   
-          // does it differ ? 
-          if (new_checksum != channellist_checksum) 
-          { 
-             // populate the list control and compute its new checksum on the fly 
-             ListView_DeleteAllItems (hListChannelsWnd); // start by emptying it first 
-   
-             // tell Windows which members of the LVCOLUMN structure we're filling 
-             memset (&lvi, 0, sizeof (lvi)); 
-             lvi.mask = LVIF_IMAGE | LVIF_PARAM; // we want to set the image and the item's pointer 
-             for (channel_index = 0; channel_index < chatterchannel_count; channel_index++) 
-             { 
-                // first, attach the right structure to this item 
-                lvi.lParam = (LPARAM) &chatterchannels[channel_index]; 
-   
-                // set item's image and name 
-                lvi.iItem = channel_index; 
-                if (chatterchannels[channel_index].is_open) 
-                   lvi.iImage = channelicons[1].load_index; // this channel is open 
-                else 
-                   lvi.iImage = channelicons[0].load_index; // this channel is silenced 
-                insert_index = ListView_InsertItem (hListChannelsWnd, &lvi); // add each item to list view 
-   
-                // set item's substrings 
-   
-                // channel number, theme and member count 
-                swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%d", chatterchannels[channel_index].id); 
-                ListView_SetItemText (hListChannelsWnd, insert_index, 0, temp_string); // channel number 
-                ListView_SetItemText (hListChannelsWnd, insert_index, 1, chatterchannels[channel_index].theme); // theme 
-                swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%d", chatterchannels[channel_index].member_count); 
-                ListView_SetItemText (hListChannelsWnd, insert_index, 2, temp_string); // member count 
-   
-                // is it the selected chatter channel ? if so, mark it as selected 
-                if (&chatterchannels[channel_index] == selected_chatterchannel) 
-                   ListView_SetItemState (hListChannelsWnd, channel_index, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); 
-             } 
-   
-             channellist_checksum = new_checksum; // remember the new checksum 
-          } 
-   
-          // now that the display is finished, IF the reply is arrived, set the totals in the window title 
-          if (chatterchannel_count >= 0) 
-             SetWindowText (hWnd, LOCALIZE (L"ChatterChannels_Title")); 
-   
-          update_selection = true; // update the selected chatter channel members display 
-          update_sortorder = true; // update the sort order 
-          chatterchannels_updated = false; // and remember we updated the list (don't do it twice) 
-       } 
-   
-       ///////////////////////////////////////////////////////////// 
-       // do we need to update the chatter channels member display ? 
-       if (update_selection) 
-       { 
-          // get a quick access to the list controls 
-          hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS); 
-          hListMembersWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELMEMBERS); 
-   
-          // if so, populate the channel members list view 
-          ListView_DeleteAllItems (hListMembersWnd); // start by emptying it first 
-          if ((chatterchannel_count > 0) && (selected_chatterchannel != NULL)) 
-          { 
-             // select the selected chatter channel in the channel list 
-             ListView_SetItemState (hListChannelsWnd, selected_chatterchannel, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); 
-   
-             memset (&lvi, 0, sizeof (lvi)); 
-             lvi.mask = LVIF_IMAGE | LVIF_PARAM; // we want to set the image and the item's pointer 
-             for (member_index = 0; member_index < selected_chatterchannel->member_count; member_index++) 
-             { 
-                // first, attach the right structure to this item 
-                lvi.lParam = (LPARAM) &selected_chatterchannel->members[member_index]; 
-   
-                // set item's image and name 
-                lvi.iItem = member_index; 
-                if (selected_chatterchannel->members[member_index].is_silenced) 
-                   lvi.iImage = listviewicons[HANDLESTATUS_NOTOPENFORAMATCH]; // this player is silenced 
-                else 
-                   lvi.iImage = listviewicons[HANDLESTATUS_AVAILABLE]; // this player is available 
-   
-                insert_index = ListView_InsertItem (hListMembersWnd, &lvi); // add each item to list view 
-                ListView_SetItemText (hListMembersWnd, insert_index, 0, selected_chatterchannel->members[member_index].nickname); // member name 
-             } 
-          } 
-   
-          update_selection = false; // don't do it twice 
-       } 
-   
-       ////////////////////////////////////////////////////// 
-       // do we need to sort the chatter channels list view ? 
-       if (update_sortorder) 
-       { 
-          // get a quick access to the list control 
-          hListChannelsWnd = GetDlgItem (hWnd, WINDOW_LIST_CHANNELS); 
-   
-          // cycle through all columns and see which one is the sort criteria 
-          memset (&hdi, 0, sizeof (hdi)); 
-          hdi.mask = HDI_FORMAT; 
-          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++) 
-          { 
-             Header_GetItem (ListView_GetHeader (hListChannelsWnd), column_index, &hdi); // get the column's sort arrow state 
-             if ((hdi.fmt & HDF_SORTDOWN) || (hdi.fmt & HDF_SORTUP)) 
-                break; // break as soon as we find it 
-          } 
-   
-          // have we NOT found it ? 
-          if (column_index == sizeof (listviewcolumns) / sizeof (listviewcolumn_t)) 
-             column_index = 0; // if so, sort the list view according to the first column 
-   
-          // now sort the list view 
-          ListView_SortItems (hListChannelsWnd, CompareProc_ListChannels, column_index); 
-   
-          // is a channel selected ? if so, scroll to the selected item 
-          if ((chatterchannel_count > 0) && (selected_chatterchannel != NULL)) 
-             ListView_ScrollTilItem (hListChannelsWnd, selected_chatterchannel); 
-   
-          update_sortorder = false; // don't do this twice 
-       } 
-    } 
-   
-    ///////////////////////////////////////////////////////////////// 
-    // else is it a list view message from the chatter channel list ? 
-    else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_CHANNELS)) 
-    { 
-       lv = (NMLISTVIEW *) lParam; // quick access to list view 
-   
-       ///////////////////////////////////////////////// 
-       // is it a click on one of the headers' columns ? 
-       if (lv->hdr.code == LVN_COLUMNCLICK) 
-       { 
-          // cycle through all columns and reset their sort order 
-          memset (&hdi, 0, sizeof (hdi)); 
-          hdi.mask = HDI_FORMAT; 
-          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++) 
-          { 
-             Header_GetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // get the column's sort arrow state 
-             hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP); 
-             if (column_index == lv->iSubItem) 
-             { 
-                listviewcolumns[column_index].sort_descending ^= true; // revert the sort order for the clicked column 
-                hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP); 
-             } 
-             else 
-                listviewcolumns[column_index].sort_descending = false; // reset the sort order for all the other ones 
-             Header_SetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // update the column's sort arrow state 
-          } 
-   
-          update_sortorder = true; // update sort order 
-          SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // and refresh window now 
-       } 
-   
-       ///////////////////////////////////////////////////////////// 
-       // else is it a single click on one of the chatter channels ? 
-       else if ((lv->hdr.code == NM_CLICK) && (((NMITEMACTIVATE *) lParam)->iItem != -1)) 
-       { 
-          // get which item it is in the listview data 
-          memset (&lvi, 0, sizeof (lvi)); 
-          lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem; 
-          lvi.mask = LVIF_PARAM; 
-          ListView_GetItem (lv->hdr.hwndFrom, &lvi); 
-   
-          chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is 
-   
-          // is it valid ? 
-          if ((chatterchannel >= chatterchannels) && (chatterchannel <= &chatterchannels[chatterchannel_count - 1])) 
-          { 
-             selected_chatterchannel = chatterchannel; // save selected chatter channel 
-             update_selection = true; // update selection 
-             the_scene.update = true; // update scene (because if we were typing something, the chatter channel has changed) 
-             SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // and refresh window now 
-          } 
-       } 
-   
-       ///////////////////////////////////////////////////////////// 
-       // else is it a double-click on one of the chatter channels ? 
-       else if ((lv->hdr.code == NM_DBLCLK) && (((NMITEMACTIVATE *) lParam)->iItem != -1)) 
-       { 
-          // get which item it is in the listview data 
-          memset (&lvi, 0, sizeof (lvi)); 
-          lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem; 
-          lvi.mask = LVIF_PARAM; 
-          ListView_GetItem (lv->hdr.hwndFrom, &lvi); 
-   
-          chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is 
-   
-          // find the network player and make him ask to toggle channel on/off to the server 
-          if ((network_player = Player_FindByType (PLAYER_INTERNET)) != NULL) 
-          { 
-             Player_SendBuffer_Add (network_player, 1000, L"%schannel %d\n", (chatterchannel->is_open ? L"-" : L"+"), chatterchannel->id); 
-             Player_SendBuffer_Add (network_player, 1000, L"inchannel\n"); // send the channels update request 
-          } 
-   
-          chatterchannels_updated = true; // and update display 
-          SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now 
-       } 
-    } 
-   
-    ///////////////////////////////////////////////////////////////////////// 
-    // else is it a list view message from the chatter channel members list ? 
-    else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_CHANNELMEMBERS)) 
-    { 
-       lv = (NMLISTVIEW *) lParam; // quick access to list view 
-   
-       /////////////////////////////////////////////////////// 
-       // is it a double-click on one of the channel members ? 
-       if ((lv->hdr.code == NM_DBLCLK) && (((NMITEMACTIVATE *) lParam)->iItem != -1) && (chatterchannel_count > 0)) 
-       { 
-          // get which item it is in the listview data 
-          memset (&lvi, 0, sizeof (lvi)); 
-          lvi.iItem = ((NMITEMACTIVATE *) lParam)->iItem; 
-          lvi.mask = LVIF_PARAM; 
-          ListView_GetItem (lv->hdr.hwndFrom, &lvi); 
-   
-          chatterchannelmember = (chatterchannelmember_t *) lvi.lParam; // get the chatter channel it is 
-   
-          local_player = Player_FindByType (PLAYER_HUMAN); // find the local player 
-   
-          // is it open AND is it NOT ourselves ? if so, find or create an interlocutor structure for this cc member 
-          if (!chatterchannelmember->is_silenced && (local_player != NULL) && (wcscmp (chatterchannelmember->nickname, local_player->name) != 0)) 
-             Interlocutor_FindOrCreate (chatterchannelmember->nickname); 
-          else 
-             PlayerCard_FindOrCreate (chatterchannelmember->nickname); // else just display his player card 
-       } 
-    } 
-   
-    // else did we take action on one of the controls ? 
-    else if (message == WM_COMMAND) 
-    { 
-       // was it the status bar hyperlink ? 
-       if (wParam_loword == WINDOW_TEXT_STATUSBAR) 
-          ShellExecute (NULL, L"open", PROGRAM_URL, NULL, NULL, SW_MAXIMIZE); // open the donation page in the default browser, maximized 
-    } 
-   
-    // call the default window message processing function to keep things going 
-    return (DefWindowProc (hWnd, message, wParam, lParam)); 
- } 
-   
-   
- static int CALLBACK CompareProc_ListChannels (LPARAM lParam1, LPARAM lParam2, LPARAM column) 
- { 
-    // callback function that tells whether the lParam1 listview element comes before lParam2 in the 
-    // sort order of the specified column 
-   
-    // channel ID 
-    if (column == 0) 
-    { 
-       if (listviewcolumns[column].sort_descending) 
-          return (((chatterchannel_t *) lParam1)->id <= ((chatterchannel_t *) lParam2)->id); 
-       else 
-          return (((chatterchannel_t *) lParam1)->id >= ((chatterchannel_t *) lParam2)->id); 
-    } 
-   
-    // channel topic 
-    else if (column == 1) 
-    { 
-       if (listviewcolumns[column].sort_descending) 
-          return (_wcsicmp (((chatterchannel_t *) lParam1)->theme, ((chatterchannel_t *) lParam2)->theme)); 
-       else 
-          return (-_wcsicmp (((chatterchannel_t *) lParam1)->theme, ((chatterchannel_t *) lParam2)->theme)); 
-    } 
-   
-    // member count 
-    else if (column == 2) 
-    { 
-       if (listviewcolumns[column].sort_descending) 
-          return (((chatterchannel_t *) lParam1)->member_count <= ((chatterchannel_t *) lParam2)->member_count); 
-       else 
-          return (((chatterchannel_t *) lParam1)->member_count >= ((chatterchannel_t *) lParam2)->member_count); 
-    } 
-   
-    return (0); // should neever reach here 
- } 
-   
-   
- static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam) 
- { 
-    // callback that subclasses the original ListView window procedure, so that we can hook 
-    // some of its messages 
-   
-    static bool tooltips_initialized = false; 
-    static bool update_tooltips = false; 
-    WNDPROC BaseWndProc; 
-    TOOLINFO toolinfo; 
-    HWND hHeaderWnd; 
-    LVITEM lvi; 
-    chatterchannel_t *chatterchannel; 
-    int column_index; 
-   
-    // get a pointer to the base window procedure (it was stored as a window property) 
-    BaseWndProc = (WNDPROC) GetProp (hWnd, L"BaseWndProc"); 
-    if (BaseWndProc == NULL) 
-       return (DefWindowProc (hWnd, message, wParam, lParam)); // consistency check 
-   
-    // is the mouse moving around ? 
-    if (message == WM_MOUSEMOVE) 
-    { 
-       // do the tooltips need to be created ? 
-       if (!tooltips_initialized) 
-       { 
-          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header 
-   
-          // add a tooltip for each column 
-          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++) 
-          { 
-             // create the tooltip and set its window topmost 
-             listviewcolumns[column_index].hToolTipWnd = CreateWindowEx (WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, 
-                                                                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
-                                                                         hHeaderWnd, NULL, hAppInstance, NULL); 
-   
-             // associate the tooltip with the tool 
-             memset (&toolinfo, 0, sizeof (toolinfo)); 
-             toolinfo.cbSize = sizeof (toolinfo); 
-             toolinfo.uFlags = TTF_SUBCLASS; 
-             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle 
-             toolinfo.uId = column_index; // tooltip ID will be column ID 
-             toolinfo.hinst = hAppInstance; 
-             toolinfo.lpszText = listviewcolumns[column_index].text; // tooltip text 
-             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle 
-             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_ADDTOOL, 0, (LPARAM) &toolinfo); 
-          } 
-   
-          tooltips_initialized = true; // do this only once 
-       } 
-   
-       // else do the tooltips need to be updated ? 
-       else if (update_tooltips) 
-       { 
-          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header 
-   
-          // cycle through all columns 
-          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++) 
-          { 
-             // update the tooltip rectangle 
-             memset (&toolinfo, 0, sizeof (toolinfo)); 
-             toolinfo.cbSize = sizeof (toolinfo); 
-             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle 
-             toolinfo.uId = column_index; // tooltip ID is column ID 
-             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle 
-             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_NEWTOOLRECT, 0, (LPARAM) &toolinfo); 
-          } 
-   
-          update_tooltips = false; // do this only once 
-       } 
-    } 
-   
-    // else is it a directional key release ? 
-    else if ((message == WM_KEYUP) && ((wParam == VK_UP) || (wParam == VK_DOWN) 
-                                       || (wParam == VK_PRIOR) || (wParam == VK_NEXT) || (wParam == VK_END) || (wParam == VK_HOME))) 
-    { 
-       // get which item is selected in the listview data 
-       memset (&lvi, 0, sizeof (lvi)); 
-       lvi.iItem = ListView_GetNextItem (hWnd, -1, LVNI_SELECTED); 
-       lvi.mask = LVIF_PARAM; 
-       ListView_GetItem (hWnd, &lvi); 
-   
-       chatterchannel = (chatterchannel_t *) lvi.lParam; // get the chatter channel it is 
-   
-       // is it valid ? 
-       if ((chatterchannel >= chatterchannels) && (chatterchannel <= &chatterchannels[chatterchannel_count - 1])) 
-       { 
-          selected_chatterchannel = chatterchannel; // save selected chatter channel 
-          update_selection = true; // update selection 
-          the_scene.update = true; // update scene (because if we were typing something, the chatter channel has changed) 
-          SendMessage (GetParent (hWnd), WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now 
-       } 
-    } 
-   
-    // else has the user finished dragging/resizing a column header ? 
-    else if ((message == WM_NOTIFY) && ((((NMHDR *) lParam)->code == HDN_ENDTRACK) || (((NMHDR *) lParam)->code == HDN_ENDDRAG))) 
-       update_tooltips = true; // if so, remember to update tooltips on the next mouse move 
-   
-    // in any case, forward all messages to the original ListView hook procedure 
-    return (CallWindowProc (BaseWndProc, hWnd, message, wParam, lParam)); 
- } 
-   
-   
- static void ListView_ScrollTilItem (HWND hListViewWnd, void *item) 
- { 
-    // helper function to scroll the given listview til the given item appears 
-   
-    LVFINDINFO lvfi; 
-    POINT point; 
-    RECT rect; 
-    int item_index; 
-    int top_index; 
-    int count_per_page; 
-    int total_count; 
-    int scroll_pixels; 
-   
-    // scroll to the selected chatter channel index 
-    memset (&lvfi, 0, sizeof (lvfi)); 
-    lvfi.flags = LVFI_PARAM; 
-    lvfi.lParam = (LPARAM) item; 
-    item_index = ListView_FindItem (hListViewWnd, -1, &lvfi); // get the position of the selected chatter channel in the list 
-    if (item_index > 0) 
-    { 
-       // figure out how many items in the listview, how many fit in one page and which index is at the top of the page 
-       total_count = ListView_GetItemCount (hListViewWnd); 
-       count_per_page = ListView_GetCountPerPage (hListViewWnd); 
-       top_index = ListView_GetTopIndex (hListViewWnd); 
-   
-       // is index already visible ? 
-       if ((item_index >= top_index) && (item_index <= top_index + count_per_page)) 
-          return; // if so, no need to scroll 
-   
-       ListView_GetItemRect (hListViewWnd, 0, &rect, LVIR_BOUNDS); // get the size of a standard item 
-       ListView_GetOrigin (hListViewWnd, &point); // get the list view origin 
-   
-       scroll_pixels = (rect.bottom - rect.top) * item_index - point.y; // compute how many pixels we should scroll 
-   
-       // do we need to scroll upwards or downwards ? 
-       if (scroll_pixels < 0) 
-          item_index -= 1 * count_per_page / 3; // scroll up one third of the window 
-       else 
-          item_index -= 2 * count_per_page / 3; // scroll up two thirds of the window 
-   
-       if (item_index < 0) 
-          item_index = 0; // don't go beyond the beginning 
-       if (item_index > total_count - count_per_page) 
-          item_index = total_count - count_per_page; // don't go beyond the end 
-   
-       scroll_pixels = (rect.bottom - rect.top) * item_index - point.y; // recompute how many pixels we should scroll 
-       ListView_Scroll (hListViewWnd, 0, scroll_pixels); // and scroll to the desired position 
-    } 
-   
-    return; // finished 
- } 
-