Subversion Repositories Games.Chess Giants

Rev

Rev 21 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

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