Subversion Repositories Games.Chess Giants

Rev

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