// 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); // close the 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
 
}