Subversion Repositories Games.Chess Giants

Rev

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

  1. // window_sought.cpp
  2.  
  3. #include "../common.h"
  4.  
  5.  
  6. // window parameters
  7. #define WINDOW_CLASSNAME PROGRAM_NAME L" SoughtGames WndClass"
  8. #define WINDOW_DEFAULT_WIDTH 823
  9. #define WINDOW_DEFAULT_HEIGHT 600
  10. #define WINDOW_MIN_WIDTH 823
  11. #define WINDOW_MIN_HEIGHT 200
  12.  
  13.  
  14. // local definitions
  15. #define WINDOW_TEXT_DOUBLECLICKORNARROWDOWN 1
  16. #define WINDOW_TEXT_GAMEIS 2
  17. #define WINDOW_COMBOBOX_GAMEIS 3
  18. #define WINDOW_COMBOBOX_RATEDUNRATED 4
  19. #define WINDOW_TEXT_LEVELFROM 5
  20. #define WINDOW_EDITBOX_LEVELFROM 6
  21. #define WINDOW_TEXT_LEVELTO 7
  22. #define WINDOW_EDITBOX_LEVELTO 8
  23. #define WINDOW_TEXT_YOUCANALSOPOSTYOURSBYCLICKINGHERE 9
  24. #define WINDOW_LIST_SOUGHTGAMES 10
  25. #define WINDOW_TEXT_STATUSBAR 11
  26. #define WINDOW_TIMER_REFRESH 1
  27.  
  28.  
  29. // list view column definition
  30. typedef struct listviewcolumn_s
  31. {
  32.    int width;
  33.    int alignment;
  34.    bool sort_descending;
  35.    wchar_t *text;
  36.    HWND hToolTipWnd;
  37. } listviewcolumn_t;
  38.  
  39.  
  40. // game types definition
  41. typedef struct gametype_s
  42. {
  43.    wchar_t name[32]; // game type name
  44. } gametype_t;
  45.  
  46.  
  47. // global variables used in this module only
  48. static bool is_classregistered = false;
  49. static int listviewicons[sizeof (handlestatus) / sizeof (handlestatus_t)]; // as big as the handlestatus global array
  50. static listviewcolumn_t listviewcolumns[] =
  51. {
  52.    { 110, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"SoughtGames_ColumnGameType")*/, NULL }, // text address needs to be set at runtime, because it's mallocated
  53.    { 75, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"SoughtGames_ColumnRatedUnrated")*/, NULL },
  54.    { 75, LVCFMT_LEFT, true, NULL /*LOCALIZE (L"SoughtGames_ColumnInitialTime")*/, NULL },
  55.    { 75, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"SoughtGames_ColumnIncrement")*/, NULL },
  56.    { 130, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"SoughtGames_ColumnOpponent")*/, NULL },
  57.    { 50, LVCFMT_CENTER, false, NULL /*LOCALIZE (L"SoughtGames_ColumnRating")*/, NULL },
  58.    { 55, LVCFMT_CENTER, false, NULL /*LOCALIZE (L"SoughtGames_ColumnColor")*/, NULL },
  59.    { 50, LVCFMT_RIGHT, false, NULL /*LOCALIZE (L"SoughtGames_ColumnAcceptsFrom")*/, NULL },
  60.    { 50, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"SoughtGames_ColumnUpTo")*/, NULL },
  61.    { 40, LVCFMT_CENTER, false, NULL /*LOCALIZE (L"SoughtGames_ColumnFilter")*/, NULL },
  62.    { 40, LVCFMT_CENTER, false, NULL /*LOCALIZE (L"SoughtGames_ColumnAutomatic")*/, NULL },
  63. };
  64. static int current_sortcolumn = 2;
  65. static gametype_t *gametypes; // mallocated
  66. static int gametype_count;
  67. static wchar_t temp_string[256];
  68. static wchar_t gametype_name[32];
  69. //static HWND hThisWnd = NULL;
  70. #define hThisWnd hSoughtWnd // shared variable
  71.  
  72.  
  73. // prototypes of local functions
  74. static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam);
  75. static int CALLBACK CompareProc_ListSoughtGames (LPARAM lParam1, LPARAM lParam2, LPARAM column);
  76. static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam);
  77.  
  78.  
  79. void Window_Sought (void)
  80. {
  81.    // helper function to fire up the child window
  82.  
  83.    WNDCLASSEX wc;
  84.    player_t *network_player;
  85.  
  86.    // is the window we want to display already displayed ?
  87.    if (IsWindow (hThisWnd))
  88.       SetForegroundWindow (hThisWnd); // if so, just bring it to front
  89.  
  90.    // else the way is clear
  91.    else
  92.    {
  93.       // find the network player and make him ask an update from the server
  94.       if ((network_player = Player_FindByType (PLAYER_INTERNET)) != NULL)
  95.       {
  96.          SAFE_free ((void **) &soughtgames); // free the sought games list we know
  97.          soughtgame_count = -1; // and reset its count (-1 means "reply not arrived")
  98.          Player_SendBuffer_Add (network_player, 1000, L"sought all\n"); // send the sought games update request
  99.       }
  100.  
  101.       // is the window class NOT registered yet ?
  102.       if (!is_classregistered)
  103.       {
  104.          // if so, register the window class once and for all
  105.          memset (&wc, 0, sizeof (wc));
  106.          wc.cbSize = sizeof (wc);
  107.          wc.style = CS_HREDRAW | CS_VREDRAW;
  108.          wc.lpfnWndProc = WindowProc_ThisWindow;
  109.          wc.hInstance = hAppInstance;
  110.          wc.hIcon = LoadIcon (hAppInstance, (wchar_t *) ICON_MAIN);
  111.          wc.hCursor = LoadCursor (NULL, IDC_ARROW);
  112.          wc.hbrBackground = GetSysColorBrush (COLOR_3DFACE);
  113.          wc.lpszClassName = WINDOW_CLASSNAME;
  114.          RegisterClassEx (&wc);
  115.  
  116.          is_classregistered = true; // remember this window class is registered
  117.       }
  118.  
  119.       // create the child window
  120.       hThisWnd = CreateWindowEx (WS_EX_CLIENTEDGE, WINDOW_CLASSNAME, LOCALIZE (L"SoughtGames_TitleBuildingList"), WS_OVERLAPPEDWINDOW,
  121.                                  CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT,
  122.                                  hMainWnd, NULL, hAppInstance, NULL);
  123.    }
  124.  
  125.    return; // return as soon as the thread is fired up
  126. }
  127.  
  128.  
  129. void Window_Sought_Validated (void)
  130. {
  131.    // callback function called by the main game thread when the window is validated
  132.  
  133.    // remember this callback is no longer to be called
  134.    is_window_sought_validated = false;
  135.  
  136.    return; // finished, sought answer has been issued
  137. }
  138.  
  139.  
  140. static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
  141. {
  142.    // message handler for the child window
  143.  
  144.    unsigned short wParam_hiword;
  145.    unsigned short wParam_loword;
  146.    int soughtgame_gameis;
  147.    int soughtgame_ratedunrated;
  148.    int soughtgame_ratingfrom;
  149.    int soughtgame_ratingto;
  150.    int result;
  151.    HWND hComboBoxWnd;
  152.    HWND hListWnd;
  153.    LVCOLUMN lvc;
  154.    LVITEM lvi;
  155.    HDITEM hdi;
  156.    NMLISTVIEW *lv;
  157.    NMITEMACTIVATE *clickeditem;
  158.    HIMAGELIST imagelist;
  159.    MINMAXINFO *minmax;
  160.    RECT client_rect;
  161.    soughtgame_t *soughtgame;
  162.    player_t *network_player;
  163.    player_t *local_player;
  164.    int soughtgame_index;
  165.    int gametype_index;
  166.    int column_index;
  167.    int insert_index;
  168.  
  169.    // filter out the commonly used message values
  170.    wParam_hiword = HIWORD (wParam);
  171.    wParam_loword = LOWORD (wParam);
  172.  
  173.    // have we just fired up this window ?
  174.    if (message == WM_CREATE)
  175.    {
  176.       // center the window
  177.       CenterWindow (hWnd, hMainWnd);
  178.  
  179.       // populate the window
  180.       CreateWindowEx (0, L"static", L"",
  181.                       WS_CHILD | WS_VISIBLE,
  182.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_DOUBLECLICKORNARROWDOWN, hAppInstance, NULL);
  183.       CreateWindowEx (WS_EX_RIGHT, L"static", L"",
  184.                       WS_CHILD | WS_VISIBLE,
  185.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_GAMEIS, hAppInstance, NULL);
  186.       CreateWindowEx (WS_EX_CLIENTEDGE, L"combobox", L"",
  187.                       WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST,
  188.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_COMBOBOX_GAMEIS, hAppInstance, NULL);
  189.       CreateWindowEx (WS_EX_CLIENTEDGE, L"combobox", L"",
  190.                       WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST,
  191.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_COMBOBOX_RATEDUNRATED, hAppInstance, NULL);
  192.       CreateWindowEx (WS_EX_RIGHT, L"static", L"",
  193.                       WS_CHILD | WS_VISIBLE,
  194.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_LEVELFROM, hAppInstance, NULL);
  195.       CreateWindowEx (WS_EX_CLIENTEDGE, L"edit", L"",
  196.                       WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | SS_CENTERIMAGE,
  197.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_EDITBOX_LEVELFROM, hAppInstance, NULL);
  198.       CreateWindowEx (WS_EX_RIGHT, L"static", L"",
  199.                       WS_CHILD | WS_VISIBLE,
  200.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_LEVELTO, hAppInstance, NULL);
  201.       CreateWindowEx (WS_EX_CLIENTEDGE, L"edit", L"",
  202.                       WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | SS_CENTERIMAGE,
  203.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_EDITBOX_LEVELTO, hAppInstance, NULL);
  204.       CreateWindowEx (0, L"static", L"",
  205.                       WS_CHILD | WS_VISIBLE,
  206.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_YOUCANALSOPOSTYOURSBYCLICKINGHERE, hAppInstance, NULL);
  207.       CreateWindowEx (WS_EX_STATICEDGE, L"syslistview32", L"",
  208.                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT,
  209.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_LIST_SOUGHTGAMES, hAppInstance, NULL);
  210.       CreateWindowEx (WS_EX_RIGHT, L"static", L"",
  211.                       WS_CHILD | WS_VISIBLE,
  212.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_STATUSBAR, hAppInstance, NULL);
  213.  
  214.       // prepare the list view : do it before anything that could trigger a fill
  215.  
  216.       // get a quick access to the list control
  217.       hListWnd = GetDlgItem (hWnd, WINDOW_LIST_SOUGHTGAMES);
  218.  
  219.       // add full row select and header columns rearranging to it
  220.       ListView_SetExtendedListViewStyle (hListWnd, ListView_GetExtendedListViewStyle (hListWnd)
  221.                                                    | LVS_EX_GRIDLINES | LVS_EX_LABELTIP
  222.                                                    | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP);
  223.  
  224.       // subclass the list view procedure to handle header tooltips and save the old procedure as one of the window's property
  225.       SetProp (hListWnd, L"BaseWndProc", (HANDLE) SetWindowLongPtr (hListWnd, GWL_WNDPROC, (long) ListView_WndProc));
  226.  
  227.       // tell Windows which members of the LVCOLUMN structure we're filling
  228.       memset (&lvc, 0, sizeof (lvc));
  229.       lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  230.       for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  231.       {
  232.          lvc.iSubItem = column_index;
  233.          if (column_index == 0) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnGameType");
  234.          else if (column_index == 1) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnRatedUnrated");
  235.          else if (column_index == 2) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnInitialTime");
  236.          else if (column_index == 3) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnIncrement");
  237.          else if (column_index == 4) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnOpponent");
  238.          else if (column_index == 5) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnRating");
  239.          else if (column_index == 6) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnColor");
  240.          else if (column_index == 7) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnAcceptsFrom");
  241.          else if (column_index == 8) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnUpTo");
  242.          else if (column_index == 9) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnFilter");
  243.          else if (column_index == 10) lvc.pszText = LOCALIZE (L"SoughtGames_ColumnAutomatic");
  244.          lvc.cx = listviewcolumns[column_index].width;
  245.          lvc.fmt = listviewcolumns[column_index].alignment;
  246.          ListView_InsertColumn (hListWnd, column_index, &lvc); // add each column to list view
  247.       }
  248.  
  249.       // create the listview image list
  250.       imagelist = ImageList_Create (16, 16, ILC_COLOR32, sizeof (handlestatus) / sizeof (handlestatus_t), 1); // create an imagelist holding N 32-bit images
  251.       for (insert_index = 1; insert_index < sizeof (handlestatus) / sizeof (handlestatus_t); insert_index++)
  252.          listviewicons[insert_index] = ImageList_AddIcon (imagelist, handlestatus[insert_index].icon); // add our icons in the image list
  253.       ListView_SetImageList (hListWnd, imagelist, LVSIL_SMALL); // associate it with the listview
  254.  
  255.       // associate the default GUI font with the "double-click or narrow down" message and set its text
  256.       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKORNARROWDOWN), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  257.       SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKORNARROWDOWN), LOCALIZE (L"SoughtGames_DoubleClickOrNarrowDown"));
  258.  
  259.       // associate the default GUI font with the "status is" message and set its text
  260.       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_GAMEIS), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  261.       SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_GAMEIS), LOCALIZE (L"SoughtGames_LookingForGameOfType"));
  262.  
  263.       // associate the default GUI font with the "game is" combo box
  264.       SendMessage (GetDlgItem (hWnd, WINDOW_COMBOBOX_GAMEIS), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  265.  
  266.       // reset the game types array
  267.       gametypes = NULL;
  268.       gametype_count = 0;
  269.  
  270.       // associate the default GUI font with the "rated/unrated" combo box and populate it
  271.       SendMessage (GetDlgItem (hWnd, WINDOW_COMBOBOX_RATEDUNRATED), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  272.       ComboBox_ResetContent (GetDlgItem (hWnd, WINDOW_COMBOBOX_RATEDUNRATED));
  273.       ComboBox_AddString (GetDlgItem (hWnd, WINDOW_COMBOBOX_RATEDUNRATED), L"");
  274.       ComboBox_AddString (GetDlgItem (hWnd, WINDOW_COMBOBOX_RATEDUNRATED), LOCALIZE (L"Unrated"));
  275.       ComboBox_AddString (GetDlgItem (hWnd, WINDOW_COMBOBOX_RATEDUNRATED), LOCALIZE (L"Rated"));
  276. //      if (_wcsicmp (options.network.login, L"guest") == 0)
  277. //         ComboBox_SetCurSel (GetDlgItem (hWnd, WINDOW_COMBOBOX_RATEDUNRATED), 1); // if we log in as guest, select unrated games by default
  278.  
  279.       // associate the default GUI font with the "level from" message and set its text
  280.       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_LEVELFROM), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  281.       SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_LEVELFROM), LOCALIZE (L"SoughtGames_LevelFrom"));
  282.  
  283.       // associate the default GUI font with the "level from" edit box and reset it
  284.       SendMessage (GetDlgItem (hWnd, WINDOW_EDITBOX_LEVELFROM), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  285.       SetDlgItemText (hWnd, WINDOW_EDITBOX_LEVELFROM, L"");
  286.  
  287.       // associate the default GUI font with the "level to" message and set its text
  288.       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_LEVELTO), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  289.       SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_LEVELTO), LOCALIZE (L"SoughtGames_LevelTo"));
  290.  
  291.       // associate the default GUI font with the "level to" edit box and reset it
  292.       SendMessage (GetDlgItem (hWnd, WINDOW_EDITBOX_LEVELTO), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  293.       SetDlgItemText (hWnd, WINDOW_EDITBOX_LEVELTO, L"");
  294.  
  295.       // associate the default GUI font with the "you can also post yours" message and set its text, then convert it to a hyperlink
  296.       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_YOUCANALSOPOSTYOURSBYCLICKINGHERE), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  297.       SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_YOUCANALSOPOSTYOURSBYCLICKINGHERE), LOCALIZE (L"SoughtGames_YouCanAlsoPostYoursByClickingHere"));
  298.       ConvertStaticToHyperlink (GetDlgItem (hWnd, WINDOW_TEXT_YOUCANALSOPOSTYOURSBYCLICKINGHERE));
  299.  
  300.       // associate the default GUI font with the status bar and set its text
  301.       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  302.       SetWindowText (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), LOCALIZE (L"SoughtGames_StatusBar"));
  303.  
  304.       // refresh the window every second
  305.       SetTimer (hWnd, WINDOW_TIMER_REFRESH, 1000, NULL);
  306.  
  307.       // convert the status bar message to a hyperlink
  308.       ConvertStaticToHyperlink (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR));
  309.  
  310.       // now show the window
  311.       ShowWindow (hWnd, SW_SHOW);
  312.    }
  313.  
  314.    // else did we click the close button on the title bar ?
  315.    else if (message == WM_CLOSE)
  316.       DestroyWindow (hWnd); // close the window
  317.  
  318.    // else are we destroying the window ?
  319.    else if (message == WM_DESTROY)
  320.    {
  321.       KillTimer (hWnd, WINDOW_TIMER_REFRESH); // destroy the timer we used to refresh the window
  322.       hThisWnd = NULL; // window is closed
  323.  
  324.       // now that we're sure the window is closed...
  325.       SAFE_free ((void **) &soughtgames); // free the sought games list we know
  326.       soughtgame_count = 0; // and reset its count
  327.  
  328.       SAFE_free ((void **) &gametypes); // free the game types array
  329.       gametype_count = 0; // and reset its count
  330.  
  331.       is_window_sought_validated = true; // remember we closed this window
  332.    }
  333.  
  334.    // else are we resizing the window ?
  335.    else if (message == WM_SIZE)
  336.    {
  337.       // get the new window size
  338.       GetClientRect (hWnd, &client_rect);
  339.  
  340.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKORNARROWDOWN), NULL, 16, 16, client_rect.right - 32, 16, SWP_NOZORDER);
  341.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_GAMEIS),           NULL,   8, 40, 208, 16, SWP_NOZORDER);
  342.       SetWindowPos (GetDlgItem (hWnd, WINDOW_COMBOBOX_GAMEIS),       NULL, 224, 38, 128, 20, SWP_NOZORDER);
  343.       SetWindowPos (GetDlgItem (hWnd, WINDOW_COMBOBOX_RATEDUNRATED), NULL, 384, 38,  96, 20, SWP_NOZORDER);
  344.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_LEVELFROM),        NULL, 512, 40, 120, 16, SWP_NOZORDER);
  345.       SetWindowPos (GetDlgItem (hWnd, WINDOW_EDITBOX_LEVELFROM),     NULL, 640, 38,  48, 20, SWP_NOZORDER);
  346.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_LEVELTO),          NULL, 688, 40,  40, 16, SWP_NOZORDER);
  347.       SetWindowPos (GetDlgItem (hWnd, WINDOW_EDITBOX_LEVELTO),       NULL, 736, 38,  48, 20, SWP_NOZORDER);
  348.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_YOUCANALSOPOSTYOURSBYCLICKINGHERE), NULL, 16, 64, client_rect.right - 32, 16, SWP_NOZORDER);
  349.       SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_SOUGHTGAMES), NULL, 16, 88, client_rect.right - 32, client_rect.bottom - 104, SWP_NOZORDER);
  350.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), NULL, 0, client_rect.bottom - 16, client_rect.right, 16, SWP_NOZORDER);
  351.    }
  352.  
  353.    // else are we asking how big/small we can resize ?
  354.    else if (message == WM_GETMINMAXINFO)
  355.    {
  356.       minmax = (MINMAXINFO *) lParam; // get a pointer to the min/max info structure
  357.  
  358.       minmax->ptMinTrackSize.x = WINDOW_MIN_WIDTH;
  359.       minmax->ptMinTrackSize.y = WINDOW_MIN_HEIGHT;
  360.    }
  361.  
  362.    // else did we take action on one of the controls ?
  363.    else if (message == WM_COMMAND)
  364.    {
  365.       // was the filter combo box changed ?
  366.       if ((wParam_loword == WINDOW_COMBOBOX_GAMEIS) && (wParam_hiword == CBN_SELCHANGE))
  367.       {
  368.          soughtgames_updated = true; // remember sought games display is to be updated
  369.          SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
  370.       }
  371.  
  372.       // else was the rated/unrated combo box changed ?
  373.       if ((wParam_loword == WINDOW_COMBOBOX_RATEDUNRATED) && (wParam_hiword == CBN_SELCHANGE))
  374.       {
  375.          soughtgames_updated = true; // remember sought games display is to be updated
  376.          SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
  377.       }
  378.  
  379.       // else was either of the rating edit box changed ?
  380.       else if ((wParam_loword == WINDOW_EDITBOX_LEVELFROM) && (wParam_hiword == EN_CHANGE))
  381.       {
  382.          soughtgames_updated = true; // remember sought games display is to be updated
  383.          SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
  384.       }
  385.       else if ((wParam_loword == WINDOW_EDITBOX_LEVELTO) && (wParam_hiword == EN_CHANGE))
  386.       {
  387.          soughtgames_updated = true; // remember sought games display is to be updated
  388.          SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
  389.       }
  390.  
  391.       // else was it the "seek" hyperlink ?
  392.       else if (wParam_loword == WINDOW_TEXT_YOUCANALSOPOSTYOURSBYCLICKINGHERE)
  393.          DialogBox_SendSeek (); // if so, open the seek dialog box
  394.  
  395.       // else was it the status bar hyperlink ?
  396.       else if (wParam_loword == WINDOW_TEXT_STATUSBAR)
  397.          ShellExecute (NULL, L"open", PROGRAM_URL, NULL, NULL, SW_MAXIMIZE); // open the donation page in the default browser, maximized
  398.    }
  399.  
  400.    // else is it a timer event AND is it our refresh timer AND do we need to update the sought games list ?
  401.    else if ((message == WM_TIMER) && (wParam == WINDOW_TIMER_REFRESH) && (soughtgames_updated))
  402.    {
  403.       // get a quick access to the list and the combo box controls
  404.       hListWnd = GetDlgItem (hWnd, WINDOW_LIST_SOUGHTGAMES);
  405.       hComboBoxWnd = GetDlgItem (hWnd, WINDOW_COMBOBOX_GAMEIS);
  406.  
  407.       // grab the different filters
  408.       soughtgame_gameis = ComboBox_GetCurSel (hComboBoxWnd);
  409.       if (soughtgame_gameis > gametype_count)
  410.          soughtgame_gameis = 0; // consistency check
  411.       if (soughtgame_gameis > 0)
  412.          wcscpy_s (gametype_name, WCHAR_SIZEOF (gametype_name), gametypes[soughtgame_gameis - 1].name); // save game type name
  413.       else
  414.          gametype_name[0] = 0;
  415.       soughtgame_ratedunrated = ComboBox_GetCurSel (GetDlgItem (hWnd, WINDOW_COMBOBOX_RATEDUNRATED));
  416.       soughtgame_ratingfrom = GetDlgItemInt (hWnd, WINDOW_EDITBOX_LEVELFROM, &result, false);
  417.       soughtgame_ratingto = GetDlgItemInt (hWnd, WINDOW_EDITBOX_LEVELTO, &result, false);
  418.       if (result == 0)
  419.          soughtgame_ratingto = 9999; // if we couldn't read a number, set the default value
  420.  
  421.       // first, build the game types drop box
  422.  
  423.       SAFE_free ((void **) &gametypes); // free the game types array
  424.       gametype_count = 0; // and reset its count
  425.  
  426.       // cycle through all sought games...
  427.       for (soughtgame_index = 0; soughtgame_index < soughtgame_count; soughtgame_index++)
  428.       {
  429.          // cycle through all game types we know and see if we find it
  430.          for (gametype_index = 0; gametype_index < gametype_count; gametype_index++)
  431.             if (wcscmp (gametypes[gametype_index].name, soughtgames[soughtgame_index].game_type) == 0)
  432.                break; // break as soon as we find it
  433.  
  434.          // have we NOT found it ?
  435.          if (gametype_index == gametype_count)
  436.          {
  437.             gametypes = (gametype_t *) SAFE_realloc (gametypes, gametype_count, gametype_count + 1, sizeof (gametype_t), false);
  438.             wcscpy_s (gametypes[gametype_count].name, WCHAR_SIZEOF (gametypes[gametype_count].name), soughtgames[soughtgame_index].game_type);
  439.             gametype_count++; // if so, reallocate game types array and remember one game type more
  440.          }
  441.       }
  442.  
  443.       // now populate the game types combo box
  444.       ComboBox_ResetContent (hComboBoxWnd);
  445.       ComboBox_AddString (hComboBoxWnd, L"");
  446.       for (gametype_index = 0; gametype_index < gametype_count; gametype_index++)
  447.          ComboBox_AddString (hComboBoxWnd, gametypes[gametype_index].name);
  448.  
  449.       // cycle through all the updated game types now...
  450.       for (gametype_index = 0; gametype_index < gametype_count; gametype_index++)
  451.          if (wcscmp (gametypes[gametype_index].name, gametype_name) == 0)
  452.          {
  453.             ComboBox_SetCurSel (hComboBoxWnd, gametype_index + 1); // put the selection back
  454.             break; // and stop searching as soon as we've found it again
  455.          }
  456.  
  457.       // populate the list control
  458.       ListView_DeleteAllItems (hListWnd); // start by emptying it first
  459.  
  460.       // tell Windows which members of the LVCOLUMN structure we're filling
  461.       memset (&lvi, 0, sizeof (lvi));
  462.       lvi.mask = LVIF_IMAGE | LVIF_PARAM; // we want to set the image and the item's pointer
  463.       for (soughtgame_index = 0; soughtgame_index < soughtgame_count; soughtgame_index++)
  464.       {
  465.          soughtgame = &soughtgames[soughtgame_index]; // quick access to sought game
  466.  
  467.          // should item be skipped because of its game type ?
  468.          if ((soughtgame_gameis > 0) && (_wcsicmp (soughtgame->game_type, gametype_name) != 0))
  469.             continue; // if so, skip it
  470.  
  471.          // should it be skipped because it's NOT rated and we want it to be, or the other way around ?
  472.          if (((soughtgame->rating_type != GAMERATINGTYPE_SUPPORTEDUNRATED) && (soughtgame_ratedunrated == 1))
  473.              || ((soughtgame->rating_type != GAMERATINGTYPE_SUPPORTEDRATED) && (soughtgame_ratedunrated == 2)))
  474.             continue; // if so, skip it
  475.  
  476.          // should it be skipped because of its rating ?
  477.          if (soughtgame->rating < soughtgame_ratingfrom)
  478.             continue; // if so, skip it
  479.          else if (soughtgame->rating > soughtgame_ratingto)
  480.             continue; // if so, skip it
  481.  
  482.          // first, attach the right structure to this item
  483.          lvi.lParam = (LPARAM) soughtgame;
  484.  
  485.          // set item's image and name
  486.          lvi.iItem = soughtgame_index;
  487.          if (soughtgame->rating_type != GAMERATINGTYPE_UNSUPPORTED)
  488.             lvi.iImage = listviewicons[HANDLESTATUS_INGAME]; // supported variant
  489.          else
  490.             lvi.iImage = listviewicons[HANDLESTATUS_NOTOPENFORAMATCH]; // unsupported variant
  491.  
  492.          insert_index = ListView_InsertItem (hListWnd, &lvi); // add each item to list view
  493.  
  494.          // set item's substrings
  495.  
  496.          // game type
  497.          ListView_SetItemText (hListWnd, insert_index, 0, soughtgame->game_type);
  498.  
  499.          // rated/unrated
  500.          if (soughtgame->rating_type == GAMERATINGTYPE_SUPPORTEDRATED)
  501.             ListView_SetItemText (hListWnd, insert_index, 1, LOCALIZE (L"Rated")) // rated game
  502.          else if (soughtgame->rating_type == GAMERATINGTYPE_SUPPORTEDUNRATED)
  503.             ListView_SetItemText (hListWnd, insert_index, 1, LOCALIZE (L"Unrated")) // unrated game
  504.  
  505.          // initial time
  506.          if (soughtgame->initial_time > 0.0f)
  507.          {
  508.             swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%.0f %s", soughtgame->initial_time, LOCALIZE (L"Minutes"));
  509.             ListView_SetItemText (hListWnd, insert_index, 2, temp_string);
  510.          }
  511.  
  512.          // Fischer increment
  513.          if (soughtgame->increment > 0.0f)
  514.          {
  515.             swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%.0f %s", soughtgame->increment, LOCALIZE (L"Seconds"));
  516.             ListView_SetItemText (hListWnd, insert_index, 3, temp_string);
  517.          }
  518.  
  519.          // opponent
  520.          ListView_SetItemText (hListWnd, insert_index, 4, soughtgame->nickname);
  521.  
  522.          // opponent's ELO
  523.          if (soughtgame->rating > 0)
  524.          {
  525.             swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%d", soughtgame->rating);
  526.             ListView_SetItemText (hListWnd, insert_index, 5, temp_string);
  527.          }
  528.  
  529.          // requested color
  530.          if (soughtgame->color != COLOR_UNSPECIFIED)
  531.          {
  532.             if (soughtgame->color == COLOR_BLACK)
  533.                swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), LOCALIZE (L"Games_Black"));
  534.             else
  535.                swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), LOCALIZE (L"Games_White"));
  536.             ListView_SetItemText (hListWnd, insert_index, 6, temp_string);
  537.          }
  538.  
  539.          // minimal accepted ELO
  540.          if (soughtgame->lowest_accepted > 0)
  541.          {
  542.             swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"> %d", soughtgame->lowest_accepted);
  543.             ListView_SetItemText (hListWnd, insert_index, 7, temp_string);
  544.          }
  545.  
  546.          // maximal accepted ELO
  547.          if ((soughtgame->highest_accepted > 0) && (soughtgame->highest_accepted < 9999))
  548.          {
  549.             swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"< %d", soughtgame->highest_accepted);
  550.             ListView_SetItemText (hListWnd, insert_index, 8, temp_string);
  551.          }
  552.  
  553.          // whether this sought game's replies will be filtered
  554.          if (soughtgame->formula_checked)
  555.             ListView_SetItemText (hListWnd, insert_index, 9, L"×");
  556.  
  557.          // whether this sought game will start automatically
  558.          if (!soughtgame->manual_start)
  559.             ListView_SetItemText (hListWnd, insert_index, 10, L"×");
  560.       }
  561.  
  562.       // now sort the list view according to the column we selected and its current sort order
  563.       ListView_SortItems (hListWnd, CompareProc_ListSoughtGames, current_sortcolumn);
  564.  
  565.       // cycle through all columns and reset their sort order
  566.       for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  567.       {
  568.          memset (&hdi, 0, sizeof (hdi));
  569.          hdi.mask = HDI_FORMAT;
  570.          Header_GetItem (ListView_GetHeader (hListWnd), column_index, &hdi); // get the column's sort arrow state
  571.          hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
  572.          if (column_index == current_sortcolumn)
  573.             hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP);
  574.          Header_SetItem (ListView_GetHeader (hListWnd), column_index, &hdi); // update the column's sort arrow state
  575.       }
  576.  
  577.       // now that the display is finished, IF the reply is arrived, set the totals in the window title
  578.       if (soughtgame_count >= 0)
  579.          SetWindowText (hWnd, LOCALIZE (L"SoughtGames_Title"));
  580.  
  581.       soughtgames_updated = false; // remember we updated the list (don't do it twice)
  582.    }
  583.  
  584.    // else is it a list view message AND is it for this list view ?
  585.    else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_SOUGHTGAMES))
  586.    {
  587.       lv = (NMLISTVIEW *) lParam; // quick access to list view
  588.  
  589.       // is it a click on one of the headers' columns ?
  590.       if (lv->hdr.code == LVN_COLUMNCLICK)
  591.       {
  592.          // cycle through all columns and reset their sort order
  593.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  594.          {
  595.             memset (&hdi, 0, sizeof (hdi));
  596.             hdi.mask = HDI_FORMAT;
  597.             Header_GetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // get the column's sort arrow state
  598.             hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
  599.             if (column_index == lv->iSubItem)
  600.             {
  601.                listviewcolumns[column_index].sort_descending ^= true; // revert the sort order for the clicked column
  602.                hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP);
  603.                current_sortcolumn = column_index; // save the current sort column
  604.             }
  605.             else
  606.                listviewcolumns[column_index].sort_descending = false; // reset the sort order for all the other ones
  607.             Header_SetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // update the column's sort arrow state
  608.          }
  609.  
  610.          // now sort the list view according to the column we selected and its current sort order
  611.          ListView_SortItems (lv->hdr.hwndFrom, CompareProc_ListSoughtGames, current_sortcolumn);
  612.       }
  613.  
  614.       // else is it a double-click on one of the elements ?
  615.       else if (lv->hdr.code == NM_DBLCLK)
  616.       {
  617.          clickeditem = (NMITEMACTIVATE *) lParam; // get the item it is
  618.  
  619.          // is it valid ?
  620.          if (clickeditem->iItem != -1)
  621.          {
  622.             // get which item it is in the listview data
  623.             memset (&lvi, 0, sizeof (lvi));
  624.             lvi.iItem = clickeditem->iItem;
  625.             lvi.mask = LVIF_IMAGE | LVIF_PARAM;
  626.             ListView_GetItem (lv->hdr.hwndFrom, &lvi);
  627.  
  628.             soughtgame = (soughtgame_t *) lvi.lParam; // get the sought game it is
  629.  
  630.             local_player = Player_FindByType (PLAYER_HUMAN); // find the local player
  631.  
  632.             // is it NOT ourselves ?
  633.             if ((local_player != NULL) && (wcscmp (soughtgame->nickname, local_player->name) != 0))
  634.             {
  635.                // is this an unsupported chess variant ?
  636.                if (lvi.iImage == listviewicons[HANDLESTATUS_NOTOPENFORAMATCH])
  637.                {
  638.                   messagebox.hWndParent = hWnd;
  639.                   wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), soughtgame->game_type);
  640.                   wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_UnsupportedChessVariant"));
  641.                   messagebox.flags = MB_ICONINFORMATION | MB_OK;
  642.                   DialogBox_Message (&messagebox); // display a modeless error message box
  643.                }
  644.                else
  645.                {
  646.                   network_player = Player_FindByType (PLAYER_INTERNET); // quick access to network player
  647.                   if (network_player == NULL)
  648.                      return (0); // consistency check
  649.  
  650.                   // send the play notification and wait for the reply
  651.                   Player_SendBuffer_Add (network_player, 1000, L"play %d\n", soughtgame->id);
  652.  
  653.                   // if this sought game is set to manual start, send a notification to this player's chat window
  654.                   // HACK: don't display if we're likely to be refused because of guest not allowed to play rated games
  655.                   if (soughtgame->manual_start && !((soughtgame->rating_type == GAMERATINGTYPE_SUPPORTEDRATED) && (wcscmp (options.network.login, L"guest") == 0)))
  656.                      Interlocutor_Notify (Interlocutor_FindOrCreate (soughtgame->nickname), LOCALIZE (L"Chat_InvitationSent"), soughtgame->nickname);
  657.                }
  658.             }
  659.             else
  660.                DialogBox_SendSeek (); // else it's our own seek, so open the seek dialog box to modify it
  661.          }
  662.       }
  663.    }
  664.  
  665.    // call the default window message processing function to keep things going
  666.    return (DefWindowProc (hWnd, message, wParam, lParam));
  667. }
  668.  
  669.  
  670. static int CALLBACK CompareProc_ListSoughtGames (LPARAM lParam1, LPARAM lParam2, LPARAM column)
  671. {
  672.    // callback function that tells whether the lParam1 listview element comes before lParam2 in the
  673.    // sort order of the specified column
  674.  
  675.    wchar_t *string_to_compare1;
  676.    wchar_t *string_to_compare2;
  677.  
  678.    // game type
  679.    if (column == 0)
  680.    {
  681.       string_to_compare1 = ((soughtgame_t *) lParam1)->game_type;
  682.       string_to_compare2 = ((soughtgame_t *) lParam2)->game_type;
  683.    }
  684.  
  685.    // rated/unrated
  686.    else if (column == 1)
  687.    {
  688.       if (((soughtgame_t *) lParam1)->rating_type == GAMERATINGTYPE_SUPPORTEDRATED)
  689.          string_to_compare1 = LOCALIZE (L"Rated");
  690.       else if (((soughtgame_t *) lParam1)->rating_type == GAMERATINGTYPE_SUPPORTEDUNRATED)
  691.          string_to_compare1 = LOCALIZE (L"Unrated");
  692.       else
  693.          string_to_compare1 = L"";
  694.  
  695.       if (((soughtgame_t *) lParam2)->rating_type == GAMERATINGTYPE_SUPPORTEDRATED)
  696.          string_to_compare2 = LOCALIZE (L"Rated");
  697.       else if (((soughtgame_t *) lParam2)->rating_type == GAMERATINGTYPE_SUPPORTEDUNRATED)
  698.          string_to_compare2 = LOCALIZE (L"Unrated");
  699.       else
  700.          string_to_compare2 = L"";
  701.    }
  702.  
  703.    // initial time
  704.    else if (column == 2)
  705.    {
  706.       if (listviewcolumns[column].sort_descending)
  707.          return (((soughtgame_t *) lParam1)->initial_time <= ((soughtgame_t *) lParam2)->initial_time);
  708.       else
  709.          return (((soughtgame_t *) lParam1)->initial_time >= ((soughtgame_t *) lParam2)->initial_time);
  710.    }
  711.  
  712.    // increment
  713.    else if (column == 3)
  714.    {
  715.       if (listviewcolumns[column].sort_descending)
  716.          return (((soughtgame_t *) lParam1)->increment <= ((soughtgame_t *) lParam2)->increment);
  717.       else
  718.          return (((soughtgame_t *) lParam1)->increment >= ((soughtgame_t *) lParam2)->increment);
  719.    }
  720.  
  721.    // opponent
  722.    else if (column == 4)
  723.    {
  724.       string_to_compare1 = ((soughtgame_t *) lParam1)->nickname;
  725.       string_to_compare2 = ((soughtgame_t *) lParam2)->nickname;
  726.    }
  727.  
  728.    // rating
  729.    else if (column == 5)
  730.    {
  731.       if (listviewcolumns[column].sort_descending)
  732.          return (((soughtgame_t *) lParam1)->rating <= ((soughtgame_t *) lParam2)->rating);
  733.       else
  734.          return (((soughtgame_t *) lParam1)->rating >= ((soughtgame_t *) lParam2)->rating);
  735.    }
  736.  
  737.    // color
  738.    else if (column == 6)
  739.    {
  740.       if (((soughtgame_t *) lParam1)->color == COLOR_BLACK)
  741.          string_to_compare1 = LOCALIZE (L"BlackMoves");
  742.       else if (((soughtgame_t *) lParam1)->color == COLOR_WHITE)
  743.          string_to_compare1 = LOCALIZE (L"WhiteMoves");
  744.       else
  745.          string_to_compare1 = L"";
  746.  
  747.       if (((soughtgame_t *) lParam2)->color == COLOR_BLACK)
  748.          string_to_compare2 = LOCALIZE (L"BlackMoves");
  749.       else if (((soughtgame_t *) lParam2)->color == COLOR_WHITE)
  750.          string_to_compare2 = LOCALIZE (L"WhiteMoves");
  751.       else
  752.          string_to_compare2 = L"";
  753.    }
  754.  
  755.    // accepts from
  756.    else if (column == 7)
  757.    {
  758.       if (listviewcolumns[column].sort_descending)
  759.          return (((soughtgame_t *) lParam1)->lowest_accepted <= ((soughtgame_t *) lParam2)->lowest_accepted);
  760.       else
  761.          return (((soughtgame_t *) lParam1)->lowest_accepted >= ((soughtgame_t *) lParam2)->lowest_accepted);
  762.    }
  763.  
  764.    // up to
  765.    else if (column == 8)
  766.    {
  767.       if (listviewcolumns[column].sort_descending)
  768.          return (((soughtgame_t *) lParam1)->highest_accepted <= ((soughtgame_t *) lParam2)->highest_accepted);
  769.       else
  770.          return (((soughtgame_t *) lParam1)->highest_accepted >= ((soughtgame_t *) lParam2)->highest_accepted);
  771.    }
  772.  
  773.    // filter
  774.    else if (column == 9)
  775.    {
  776.       string_to_compare1 = (((soughtgame_t *) lParam1)->formula_checked ? L"×" : L" ");
  777.       string_to_compare2 = (((soughtgame_t *) lParam2)->formula_checked ? L"×" : L" ");
  778.    }
  779.  
  780.    // automatic
  781.    else if (column == 10)
  782.    {
  783.       string_to_compare1 = (((soughtgame_t *) lParam1)->manual_start ? L" " : L"×");
  784.       string_to_compare2 = (((soughtgame_t *) lParam2)->manual_start ? L" " : L"×");
  785.    }
  786.  
  787.    // which order do we want this column to be sorted ?
  788.    if (listviewcolumns[column].sort_descending)
  789.       return (_wcsicmp (string_to_compare1, string_to_compare2)); // normal order
  790.    else
  791.       return (-_wcsicmp (string_to_compare1, string_to_compare2)); // reverse order
  792. }
  793.  
  794.  
  795. static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
  796. {
  797.    // callback that subclasses the original ListView window procedure, so that we can hook
  798.    // some of its messages
  799.  
  800.    static bool tooltips_initialized = false;
  801.    static bool update_tooltips = false;
  802.    WNDPROC BaseWndProc;
  803.    TOOLINFO toolinfo;
  804.    HWND hHeaderWnd;
  805.    int column_index;
  806.  
  807.    // get a pointer to the base window procedure (it was stored as a window property)
  808.    BaseWndProc = (WNDPROC) GetProp (hWnd, L"BaseWndProc");
  809.    if (BaseWndProc == NULL)
  810.       return (DefWindowProc (hWnd, message, wParam, lParam)); // consistency check
  811.  
  812.    // is the mouse moving around ?
  813.    if (message == WM_MOUSEMOVE)
  814.    {
  815.       // do the tooltips need to be created ?
  816.       if (!tooltips_initialized)
  817.       {
  818.          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
  819.  
  820.          // add a tooltip for each column
  821.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  822.          {
  823.             // create the tooltip and set its window topmost
  824.             listviewcolumns[column_index].hToolTipWnd = CreateWindowEx (WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
  825.                                                                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  826.                                                                         hHeaderWnd, NULL, hAppInstance, NULL);
  827.  
  828.             // associate the tooltip with the tool
  829.             memset (&toolinfo, 0, sizeof (toolinfo));
  830.             toolinfo.cbSize = sizeof (toolinfo);
  831.             toolinfo.uFlags = TTF_SUBCLASS;
  832.             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
  833.             toolinfo.uId = column_index; // tooltip ID will be column ID
  834.             toolinfo.hinst = hAppInstance;
  835.             toolinfo.lpszText = listviewcolumns[column_index].text; // tooltip text
  836.             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
  837.             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_ADDTOOL, 0, (LPARAM) &toolinfo);
  838.          }
  839.  
  840.          tooltips_initialized = true; // do this only once
  841.       }
  842.  
  843.       // else do the tooltips need to be updated ?
  844.       else if (update_tooltips)
  845.       {
  846.          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
  847.  
  848.          // cycle through all columns
  849.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  850.          {
  851.             // update the tooltip rectangle
  852.             memset (&toolinfo, 0, sizeof (toolinfo));
  853.             toolinfo.cbSize = sizeof (toolinfo);
  854.             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
  855.             toolinfo.uId = column_index; // tooltip ID is column ID
  856.             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
  857.             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_NEWTOOLRECT, 0, (LPARAM) &toolinfo);
  858.          }
  859.  
  860.          update_tooltips = false; // do this only once
  861.       }
  862.    }
  863.  
  864.    // else has the user finished dragging/resizing a column header ?
  865.    else if ((message == WM_NOTIFY) && ((((NMHDR *) lParam)->code == HDN_ENDTRACK) || (((NMHDR *) lParam)->code == HDN_ENDDRAG)))
  866.       update_tooltips = true; // if so, remember to update tooltips on the next mouse move
  867.  
  868.    // in any case, forward all messages to the original ListView hook procedure
  869.    return (CallWindowProc (BaseWndProc, hWnd, message, wParam, lParam));
  870. }
  871.