Subversion Repositories Games.Chess Giants

Rev

Rev 21 | 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.       return (0); // as MSDN says
  314.    }
  315.  
  316.    // else did we click the close button on the title bar ?
  317.    else if (message == WM_CLOSE)
  318.    {
  319.       DestroyWindow (hWnd); // close the window
  320.       return (0); // as MSDN says
  321.    }
  322.  
  323.    // else are we destroying the window ?
  324.    else if (message == WM_DESTROY)
  325.    {
  326.       KillTimer (hWnd, WINDOW_TIMER_REFRESH); // destroy the timer we used to refresh the window
  327.       hThisWnd = NULL; // window is closed
  328.  
  329.       // now that we're sure the window is closed...
  330.       SAFE_free ((void **) &soughtgames); // free the sought games list we know
  331.       soughtgame_count = 0; // and reset its count
  332.  
  333.       SAFE_free ((void **) &gametypes); // free the game types array
  334.       gametype_count = 0; // and reset its count
  335.  
  336.       is_window_sought_validated = true; // remember we closed this window
  337.       the_board.reevaluate = true; // refresh the GUI buttons if needed
  338.       return (0); // as MSDN says
  339.    }
  340.  
  341.    // else are we resizing the window ?
  342.    else if (message == WM_SIZE)
  343.    {
  344.       // get the new window size
  345.       GetClientRect (hWnd, &client_rect);
  346.  
  347.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_DOUBLECLICKORNARROWDOWN), NULL, 16, 16, client_rect.right - 32, 16, SWP_NOZORDER);
  348.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_GAMEIS),           NULL,   8, 40, 208, 16, SWP_NOZORDER);
  349.       SetWindowPos (GetDlgItem (hWnd, WINDOW_COMBOBOX_GAMEIS),       NULL, 224, 38, 128, 20, SWP_NOZORDER);
  350.       SetWindowPos (GetDlgItem (hWnd, WINDOW_COMBOBOX_RATEDUNRATED), NULL, 384, 38,  96, 20, SWP_NOZORDER);
  351.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_LEVELFROM),        NULL, 512, 40, 120, 16, SWP_NOZORDER);
  352.       SetWindowPos (GetDlgItem (hWnd, WINDOW_EDITBOX_LEVELFROM),     NULL, 640, 38,  48, 20, SWP_NOZORDER);
  353.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_LEVELTO),          NULL, 688, 40,  40, 16, SWP_NOZORDER);
  354.       SetWindowPos (GetDlgItem (hWnd, WINDOW_EDITBOX_LEVELTO),       NULL, 736, 38,  48, 20, SWP_NOZORDER);
  355.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_YOUCANALSOPOSTYOURSBYCLICKINGHERE), NULL, 16, 64, client_rect.right - 32, 16, SWP_NOZORDER);
  356.       SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_SOUGHTGAMES), NULL, 16, 88, client_rect.right - 32, client_rect.bottom - 104, SWP_NOZORDER);
  357.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), NULL, 0, client_rect.bottom - 16, client_rect.right, 16, SWP_NOZORDER);
  358.  
  359.       return (0); // as MSDN says
  360.    }
  361.  
  362.    // else are we asking how big/small we can resize ?
  363.    else if (message == WM_GETMINMAXINFO)
  364.    {
  365.       minmax = (MINMAXINFO *) lParam; // get a pointer to the min/max info structure
  366.  
  367.       minmax->ptMinTrackSize.x = WINDOW_MIN_WIDTH;
  368.       minmax->ptMinTrackSize.y = WINDOW_MIN_HEIGHT;
  369.  
  370.       return (0); // as MSDN says
  371.    }
  372.  
  373.    // else did we take action on one of the controls ?
  374.    else if (message == WM_COMMAND)
  375.    {
  376.       // was the filter combo box changed ?
  377.       if ((wParam_loword == WINDOW_COMBOBOX_GAMEIS) && (wParam_hiword == CBN_SELCHANGE))
  378.       {
  379.          soughtgames_updated = true; // remember sought games display is to be updated
  380.          SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
  381.       }
  382.  
  383.       // else was the rated/unrated combo box changed ?
  384.       if ((wParam_loword == WINDOW_COMBOBOX_RATEDUNRATED) && (wParam_hiword == CBN_SELCHANGE))
  385.       {
  386.          soughtgames_updated = true; // remember sought games display is to be updated
  387.          SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
  388.       }
  389.  
  390.       // else was either of the rating edit box changed ?
  391.       else if ((wParam_loword == WINDOW_EDITBOX_LEVELFROM) && (wParam_hiword == EN_CHANGE))
  392.       {
  393.          soughtgames_updated = true; // remember sought games display is to be updated
  394.          SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
  395.       }
  396.       else if ((wParam_loword == WINDOW_EDITBOX_LEVELTO) && (wParam_hiword == EN_CHANGE))
  397.       {
  398.          soughtgames_updated = true; // remember sought games display is to be updated
  399.          SendMessage (hWnd, WM_TIMER, WINDOW_TIMER_REFRESH, NULL); // refresh window now
  400.       }
  401.  
  402.       // else was it the "seek" hyperlink ?
  403.       else if (wParam_loword == WINDOW_TEXT_YOUCANALSOPOSTYOURSBYCLICKINGHERE)
  404.          DialogBox_SendSeek (); // if so, open the seek dialog box
  405.  
  406.       // else was it the status bar hyperlink ?
  407.       else if (wParam_loword == WINDOW_TEXT_STATUSBAR)
  408.          ShellExecute (NULL, L"open", PROGRAM_URL, NULL, NULL, SW_MAXIMIZE); // open the donation page in the default browser, maximized
  409.  
  410.       return (0); // as MSDN says
  411.    }
  412.  
  413.    // else is it a timer event AND is it our refresh timer AND do we need to update the sought games list ?
  414.    else if ((message == WM_TIMER) && (wParam == WINDOW_TIMER_REFRESH) && (soughtgames_updated))
  415.    {
  416.       // get a quick access to the list and the combo box controls
  417.       hListWnd = GetDlgItem (hWnd, WINDOW_LIST_SOUGHTGAMES);
  418.       hComboBoxWnd = GetDlgItem (hWnd, WINDOW_COMBOBOX_GAMEIS);
  419.  
  420.       // grab the different filters
  421.       soughtgame_gameis = ComboBox_GetCurSel (hComboBoxWnd);
  422.       if (soughtgame_gameis > gametype_count)
  423.          soughtgame_gameis = 0; // consistency check
  424.       if (soughtgame_gameis > 0)
  425.          wcscpy_s (gametype_name, WCHAR_SIZEOF (gametype_name), gametypes[soughtgame_gameis - 1].name); // save game type name
  426.       else
  427.          gametype_name[0] = 0;
  428.       soughtgame_ratedunrated = ComboBox_GetCurSel (GetDlgItem (hWnd, WINDOW_COMBOBOX_RATEDUNRATED));
  429.       soughtgame_ratingfrom = GetDlgItemInt (hWnd, WINDOW_EDITBOX_LEVELFROM, &result, false);
  430.       soughtgame_ratingto = GetDlgItemInt (hWnd, WINDOW_EDITBOX_LEVELTO, &result, false);
  431.       if (result == 0)
  432.          soughtgame_ratingto = 9999; // if we couldn't read a number, set the default value
  433.  
  434.       // first, build the game types drop box
  435.  
  436.       SAFE_free ((void **) &gametypes); // free the game types array
  437.       gametype_count = 0; // and reset its count
  438.  
  439.       // cycle through all sought games...
  440.       for (soughtgame_index = 0; soughtgame_index < soughtgame_count; soughtgame_index++)
  441.       {
  442.          // cycle through all game types we know and see if we find it
  443.          for (gametype_index = 0; gametype_index < gametype_count; gametype_index++)
  444.             if (wcscmp (gametypes[gametype_index].name, soughtgames[soughtgame_index].game_type) == 0)
  445.                break; // break as soon as we find it
  446.  
  447.          // have we NOT found it ?
  448.          if (gametype_index == gametype_count)
  449.          {
  450.             gametypes = (gametype_t *) SAFE_realloc (gametypes, gametype_count, gametype_count + 1, sizeof (gametype_t), false);
  451.             wcscpy_s (gametypes[gametype_count].name, WCHAR_SIZEOF (gametypes[gametype_count].name), soughtgames[soughtgame_index].game_type);
  452.             gametype_count++; // if so, reallocate game types array and remember one game type more
  453.          }
  454.       }
  455.  
  456.       // now populate the game types combo box
  457.       ComboBox_ResetContent (hComboBoxWnd);
  458.       ComboBox_AddString (hComboBoxWnd, L"");
  459.       for (gametype_index = 0; gametype_index < gametype_count; gametype_index++)
  460.          ComboBox_AddString (hComboBoxWnd, gametypes[gametype_index].name);
  461.  
  462.       // cycle through all the updated game types now...
  463.       for (gametype_index = 0; gametype_index < gametype_count; gametype_index++)
  464.          if (wcscmp (gametypes[gametype_index].name, gametype_name) == 0)
  465.          {
  466.             ComboBox_SetCurSel (hComboBoxWnd, gametype_index + 1); // put the selection back
  467.             break; // and stop searching as soon as we've found it again
  468.          }
  469.  
  470.       // populate the list control
  471.       ListView_DeleteAllItems (hListWnd); // start by emptying it first
  472.  
  473.       // tell Windows which members of the LVCOLUMN structure we're filling
  474.       memset (&lvi, 0, sizeof (lvi));
  475.       lvi.mask = LVIF_IMAGE | LVIF_PARAM; // we want to set the image and the item's pointer
  476.       for (soughtgame_index = 0; soughtgame_index < soughtgame_count; soughtgame_index++)
  477.       {
  478.          soughtgame = &soughtgames[soughtgame_index]; // quick access to sought game
  479.  
  480.          // should item be skipped because of its game type ?
  481.          if ((soughtgame_gameis > 0) && (_wcsicmp (soughtgame->game_type, gametype_name) != 0))
  482.             continue; // if so, skip it
  483.  
  484.          // should it be skipped because it's NOT rated and we want it to be, or the other way around ?
  485.          if (((soughtgame->rating_type != GAMERATINGTYPE_SUPPORTEDUNRATED) && (soughtgame_ratedunrated == 1))
  486.              || ((soughtgame->rating_type != GAMERATINGTYPE_SUPPORTEDRATED) && (soughtgame_ratedunrated == 2)))
  487.             continue; // if so, skip it
  488.  
  489.          // should it be skipped because of its rating ?
  490.          if (soughtgame->rating < soughtgame_ratingfrom)
  491.             continue; // if so, skip it
  492.          else if (soughtgame->rating > soughtgame_ratingto)
  493.             continue; // if so, skip it
  494.  
  495.          // first, attach the right structure to this item
  496.          lvi.lParam = (LPARAM) soughtgame;
  497.  
  498.          // set item's image and name
  499.          lvi.iItem = soughtgame_index;
  500.          if (soughtgame->rating_type != GAMERATINGTYPE_UNSUPPORTED)
  501.             lvi.iImage = listviewicons[HANDLESTATUS_INGAME]; // supported variant
  502.          else
  503.             lvi.iImage = listviewicons[HANDLESTATUS_NOTOPENFORAMATCH]; // unsupported variant
  504.  
  505.          insert_index = ListView_InsertItem (hListWnd, &lvi); // add each item to list view
  506.  
  507.          // set item's substrings
  508.  
  509.          // game type
  510.          ListView_SetItemText (hListWnd, insert_index, 0, soughtgame->game_type);
  511.  
  512.          // rated/unrated
  513.          if (soughtgame->rating_type == GAMERATINGTYPE_SUPPORTEDRATED)
  514.             ListView_SetItemText (hListWnd, insert_index, 1, LOCALIZE (L"Rated")) // rated game
  515.          else if (soughtgame->rating_type == GAMERATINGTYPE_SUPPORTEDUNRATED)
  516.             ListView_SetItemText (hListWnd, insert_index, 1, LOCALIZE (L"Unrated")) // unrated game
  517.  
  518.          // initial time
  519.          if (soughtgame->initial_time > 0.0f)
  520.          {
  521.             swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%.0f %s", soughtgame->initial_time, LOCALIZE (L"Minutes"));
  522.             ListView_SetItemText (hListWnd, insert_index, 2, temp_string);
  523.          }
  524.  
  525.          // Fischer increment
  526.          if (soughtgame->increment > 0.0f)
  527.          {
  528.             swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%.0f %s", soughtgame->increment, LOCALIZE (L"Seconds"));
  529.             ListView_SetItemText (hListWnd, insert_index, 3, temp_string);
  530.          }
  531.  
  532.          // opponent
  533.          ListView_SetItemText (hListWnd, insert_index, 4, soughtgame->nickname);
  534.  
  535.          // opponent's ELO
  536.          if (soughtgame->rating > 0)
  537.          {
  538.             swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"%d", soughtgame->rating);
  539.             ListView_SetItemText (hListWnd, insert_index, 5, temp_string);
  540.          }
  541.  
  542.          // requested color
  543.          if (soughtgame->color != COLOR_UNSPECIFIED)
  544.          {
  545.             if (soughtgame->color == COLOR_BLACK)
  546.                swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), LOCALIZE (L"Games_Black"));
  547.             else
  548.                swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), LOCALIZE (L"Games_White"));
  549.             ListView_SetItemText (hListWnd, insert_index, 6, temp_string);
  550.          }
  551.  
  552.          // minimal accepted ELO
  553.          if (soughtgame->lowest_accepted > 0)
  554.          {
  555.             swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"> %d", soughtgame->lowest_accepted);
  556.             ListView_SetItemText (hListWnd, insert_index, 7, temp_string);
  557.          }
  558.  
  559.          // maximal accepted ELO
  560.          if ((soughtgame->highest_accepted > 0) && (soughtgame->highest_accepted < 9999))
  561.          {
  562.             swprintf_s (temp_string, WCHAR_SIZEOF (temp_string), L"< %d", soughtgame->highest_accepted);
  563.             ListView_SetItemText (hListWnd, insert_index, 8, temp_string);
  564.          }
  565.  
  566.          // whether this sought game's replies will be filtered
  567.          if (soughtgame->formula_checked)
  568.             ListView_SetItemText (hListWnd, insert_index, 9, L"×");
  569.  
  570.          // whether this sought game will start automatically
  571.          if (!soughtgame->manual_start)
  572.             ListView_SetItemText (hListWnd, insert_index, 10, L"×");
  573.       }
  574.  
  575.       // now sort the list view according to the column we selected and its current sort order
  576.       ListView_SortItems (hListWnd, CompareProc_ListSoughtGames, current_sortcolumn);
  577.  
  578.       // cycle through all columns and reset their sort order
  579.       for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  580.       {
  581.          memset (&hdi, 0, sizeof (hdi));
  582.          hdi.mask = HDI_FORMAT;
  583.          Header_GetItem (ListView_GetHeader (hListWnd), column_index, &hdi); // get the column's sort arrow state
  584.          hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
  585.          if (column_index == current_sortcolumn)
  586.             hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP);
  587.          Header_SetItem (ListView_GetHeader (hListWnd), column_index, &hdi); // update the column's sort arrow state
  588.       }
  589.  
  590.       // now that the display is finished, IF the reply is arrived, set the totals in the window title
  591.       if (soughtgame_count >= 0)
  592.          SetWindowText (hWnd, LOCALIZE (L"SoughtGames_Title"));
  593.  
  594.       soughtgames_updated = false; // remember we updated the list (don't do it twice)
  595.  
  596.       return (0); // as MSDN says
  597.    }
  598.  
  599.    // else is it a list view message AND is it for this list view ?
  600.    else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_SOUGHTGAMES))
  601.    {
  602.       lv = (NMLISTVIEW *) lParam; // quick access to list view
  603.  
  604.       // is it a click on one of the headers' columns ?
  605.       if (lv->hdr.code == LVN_COLUMNCLICK)
  606.       {
  607.          // cycle through all columns and reset their sort order
  608.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  609.          {
  610.             memset (&hdi, 0, sizeof (hdi));
  611.             hdi.mask = HDI_FORMAT;
  612.             Header_GetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // get the column's sort arrow state
  613.             hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
  614.             if (column_index == lv->iSubItem)
  615.             {
  616.                listviewcolumns[column_index].sort_descending ^= true; // revert the sort order for the clicked column
  617.                hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP);
  618.                current_sortcolumn = column_index; // save the current sort column
  619.             }
  620.             else
  621.                listviewcolumns[column_index].sort_descending = false; // reset the sort order for all the other ones
  622.             Header_SetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // update the column's sort arrow state
  623.          }
  624.  
  625.          // now sort the list view according to the column we selected and its current sort order
  626.          ListView_SortItems (lv->hdr.hwndFrom, CompareProc_ListSoughtGames, current_sortcolumn);
  627.       }
  628.  
  629.       // else is it a double-click on one of the elements ?
  630.       else if (lv->hdr.code == NM_DBLCLK)
  631.       {
  632.          clickeditem = (NMITEMACTIVATE *) lParam; // get the item it is
  633.  
  634.          // is it valid ?
  635.          if (clickeditem->iItem != -1)
  636.          {
  637.             // get which item it is in the listview data
  638.             memset (&lvi, 0, sizeof (lvi));
  639.             lvi.iItem = clickeditem->iItem;
  640.             lvi.mask = LVIF_IMAGE | LVIF_PARAM;
  641.             ListView_GetItem (lv->hdr.hwndFrom, &lvi);
  642.  
  643.             soughtgame = (soughtgame_t *) lvi.lParam; // get the sought game it is
  644.  
  645.             local_player = Player_FindByType (PLAYER_HUMAN); // find the local player
  646.  
  647.             // is it NOT ourselves ?
  648.             if ((local_player != NULL) && (wcscmp (soughtgame->nickname, local_player->name) != 0))
  649.             {
  650.                // is this an unsupported chess variant ?
  651.                if (lvi.iImage == listviewicons[HANDLESTATUS_NOTOPENFORAMATCH])
  652.                {
  653.                   messagebox.hWndParent = hWnd;
  654.                   wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), soughtgame->game_type);
  655.                   wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_UnsupportedChessVariant"));
  656.                   messagebox.flags = MB_ICONINFORMATION | MB_OK;
  657.                   DialogBox_Message (&messagebox); // display a modeless error message box
  658.                }
  659.                else
  660.                {
  661.                   network_player = Player_FindByType (PLAYER_INTERNET); // quick access to network player
  662.                   if (network_player == NULL)
  663.                      return (0); // consistency check
  664.  
  665.                   // send the play notification and wait for the reply
  666.                   Player_SendBuffer_Add (network_player, 1000, L"play %d\n", soughtgame->id);
  667.  
  668.                   // if this sought game is set to manual start, send a notification to this player's chat window
  669.                   // HACK: don't display if we're likely to be refused because of guest not allowed to play rated games
  670.                   if (soughtgame->manual_start && !((soughtgame->rating_type == GAMERATINGTYPE_SUPPORTEDRATED) && (wcscmp (options.network.login, L"guest") == 0)))
  671.                      Interlocutor_Notify (Interlocutor_FindOrCreate (soughtgame->nickname), LOCALIZE (L"Chat_InvitationSent"), soughtgame->nickname);
  672.                }
  673.             }
  674.             else
  675.                DialogBox_SendSeek (); // else it's our own seek, so open the seek dialog box to modify it
  676.          }
  677.       }
  678.  
  679.       return (0); // as MSDN says
  680.    }
  681.  
  682.    // call the default window message processing function to keep things going
  683.    return (DefWindowProc (hWnd, message, wParam, lParam));
  684. }
  685.  
  686.  
  687. static int CALLBACK CompareProc_ListSoughtGames (LPARAM lParam1, LPARAM lParam2, LPARAM column)
  688. {
  689.    // callback function that tells whether the lParam1 listview element comes before lParam2 in the
  690.    // sort order of the specified column
  691.  
  692.    wchar_t *string_to_compare1;
  693.    wchar_t *string_to_compare2;
  694.  
  695.    // game type
  696.    if (column == 0)
  697.    {
  698.       string_to_compare1 = ((soughtgame_t *) lParam1)->game_type;
  699.       string_to_compare2 = ((soughtgame_t *) lParam2)->game_type;
  700.    }
  701.  
  702.    // rated/unrated
  703.    else if (column == 1)
  704.    {
  705.       if (((soughtgame_t *) lParam1)->rating_type == GAMERATINGTYPE_SUPPORTEDRATED)
  706.          string_to_compare1 = LOCALIZE (L"Rated");
  707.       else if (((soughtgame_t *) lParam1)->rating_type == GAMERATINGTYPE_SUPPORTEDUNRATED)
  708.          string_to_compare1 = LOCALIZE (L"Unrated");
  709.       else
  710.          string_to_compare1 = L"";
  711.  
  712.       if (((soughtgame_t *) lParam2)->rating_type == GAMERATINGTYPE_SUPPORTEDRATED)
  713.          string_to_compare2 = LOCALIZE (L"Rated");
  714.       else if (((soughtgame_t *) lParam2)->rating_type == GAMERATINGTYPE_SUPPORTEDUNRATED)
  715.          string_to_compare2 = LOCALIZE (L"Unrated");
  716.       else
  717.          string_to_compare2 = L"";
  718.    }
  719.  
  720.    // initial time
  721.    else if (column == 2)
  722.    {
  723.       if (listviewcolumns[column].sort_descending)
  724.          return (((soughtgame_t *) lParam1)->initial_time <= ((soughtgame_t *) lParam2)->initial_time);
  725.       else
  726.          return (((soughtgame_t *) lParam1)->initial_time >= ((soughtgame_t *) lParam2)->initial_time);
  727.    }
  728.  
  729.    // increment
  730.    else if (column == 3)
  731.    {
  732.       if (listviewcolumns[column].sort_descending)
  733.          return (((soughtgame_t *) lParam1)->increment <= ((soughtgame_t *) lParam2)->increment);
  734.       else
  735.          return (((soughtgame_t *) lParam1)->increment >= ((soughtgame_t *) lParam2)->increment);
  736.    }
  737.  
  738.    // opponent
  739.    else if (column == 4)
  740.    {
  741.       string_to_compare1 = ((soughtgame_t *) lParam1)->nickname;
  742.       string_to_compare2 = ((soughtgame_t *) lParam2)->nickname;
  743.    }
  744.  
  745.    // rating
  746.    else if (column == 5)
  747.    {
  748.       if (listviewcolumns[column].sort_descending)
  749.          return (((soughtgame_t *) lParam1)->rating <= ((soughtgame_t *) lParam2)->rating);
  750.       else
  751.          return (((soughtgame_t *) lParam1)->rating >= ((soughtgame_t *) lParam2)->rating);
  752.    }
  753.  
  754.    // color
  755.    else if (column == 6)
  756.    {
  757.       if (((soughtgame_t *) lParam1)->color == COLOR_BLACK)
  758.          string_to_compare1 = LOCALIZE (L"BlackMoves");
  759.       else if (((soughtgame_t *) lParam1)->color == COLOR_WHITE)
  760.          string_to_compare1 = LOCALIZE (L"WhiteMoves");
  761.       else
  762.          string_to_compare1 = L"";
  763.  
  764.       if (((soughtgame_t *) lParam2)->color == COLOR_BLACK)
  765.          string_to_compare2 = LOCALIZE (L"BlackMoves");
  766.       else if (((soughtgame_t *) lParam2)->color == COLOR_WHITE)
  767.          string_to_compare2 = LOCALIZE (L"WhiteMoves");
  768.       else
  769.          string_to_compare2 = L"";
  770.    }
  771.  
  772.    // accepts from
  773.    else if (column == 7)
  774.    {
  775.       if (listviewcolumns[column].sort_descending)
  776.          return (((soughtgame_t *) lParam1)->lowest_accepted <= ((soughtgame_t *) lParam2)->lowest_accepted);
  777.       else
  778.          return (((soughtgame_t *) lParam1)->lowest_accepted >= ((soughtgame_t *) lParam2)->lowest_accepted);
  779.    }
  780.  
  781.    // up to
  782.    else if (column == 8)
  783.    {
  784.       if (listviewcolumns[column].sort_descending)
  785.          return (((soughtgame_t *) lParam1)->highest_accepted <= ((soughtgame_t *) lParam2)->highest_accepted);
  786.       else
  787.          return (((soughtgame_t *) lParam1)->highest_accepted >= ((soughtgame_t *) lParam2)->highest_accepted);
  788.    }
  789.  
  790.    // filter
  791.    else if (column == 9)
  792.    {
  793.       string_to_compare1 = (((soughtgame_t *) lParam1)->formula_checked ? L"×" : L" ");
  794.       string_to_compare2 = (((soughtgame_t *) lParam2)->formula_checked ? L"×" : L" ");
  795.    }
  796.  
  797.    // automatic
  798.    else if (column == 10)
  799.    {
  800.       string_to_compare1 = (((soughtgame_t *) lParam1)->manual_start ? L" " : L"×");
  801.       string_to_compare2 = (((soughtgame_t *) lParam2)->manual_start ? L" " : L"×");
  802.    }
  803.  
  804.    // which order do we want this column to be sorted ?
  805.    if (listviewcolumns[column].sort_descending)
  806.       return (_wcsicmp (string_to_compare1, string_to_compare2)); // normal order
  807.    else
  808.       return (-_wcsicmp (string_to_compare1, string_to_compare2)); // reverse order
  809. }
  810.  
  811.  
  812. static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
  813. {
  814.    // callback that subclasses the original ListView window procedure, so that we can hook
  815.    // some of its messages
  816.  
  817.    static bool tooltips_initialized = false;
  818.    static bool update_tooltips = false;
  819.    WNDPROC BaseWndProc;
  820.    TOOLINFO toolinfo;
  821.    HWND hHeaderWnd;
  822.    int column_index;
  823.  
  824.    // get a pointer to the base window procedure (it was stored as a window property)
  825.    BaseWndProc = (WNDPROC) GetProp (hWnd, L"BaseWndProc");
  826.    if (BaseWndProc == NULL)
  827.       return (DefWindowProc (hWnd, message, wParam, lParam)); // consistency check
  828.  
  829.    // is the mouse moving around ?
  830.    if (message == WM_MOUSEMOVE)
  831.    {
  832.       // do the tooltips need to be created ?
  833.       if (!tooltips_initialized)
  834.       {
  835.          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
  836.  
  837.          // add a tooltip for each column
  838.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  839.          {
  840.             // create the tooltip and set its window topmost
  841.             listviewcolumns[column_index].hToolTipWnd = CreateWindowEx (WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
  842.                                                                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  843.                                                                         hHeaderWnd, NULL, hAppInstance, NULL);
  844.  
  845.             // associate the tooltip with the tool
  846.             memset (&toolinfo, 0, sizeof (toolinfo));
  847.             toolinfo.cbSize = sizeof (toolinfo);
  848.             toolinfo.uFlags = TTF_SUBCLASS;
  849.             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
  850.             toolinfo.uId = column_index; // tooltip ID will be column ID
  851.             toolinfo.hinst = hAppInstance;
  852.             toolinfo.lpszText = listviewcolumns[column_index].text; // tooltip text
  853.             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
  854.             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_ADDTOOL, 0, (LPARAM) &toolinfo);
  855.          }
  856.  
  857.          tooltips_initialized = true; // do this only once
  858.       }
  859.  
  860.       // else do the tooltips need to be updated ?
  861.       else if (update_tooltips)
  862.       {
  863.          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
  864.  
  865.          // cycle through all columns
  866.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  867.          {
  868.             // update the tooltip rectangle
  869.             memset (&toolinfo, 0, sizeof (toolinfo));
  870.             toolinfo.cbSize = sizeof (toolinfo);
  871.             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
  872.             toolinfo.uId = column_index; // tooltip ID is column ID
  873.             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
  874.             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_NEWTOOLRECT, 0, (LPARAM) &toolinfo);
  875.          }
  876.  
  877.          update_tooltips = false; // do this only once
  878.       }
  879.    }
  880.  
  881.    // else has the user finished dragging/resizing a column header ?
  882.    else if ((message == WM_NOTIFY) && ((((NMHDR *) lParam)->code == HDN_ENDTRACK) || (((NMHDR *) lParam)->code == HDN_ENDDRAG)))
  883.       update_tooltips = true; // if so, remember to update tooltips on the next mouse move
  884.  
  885.    // in any case, forward all messages to the original ListView hook procedure
  886.    return (CallWindowProc (BaseWndProc, hWnd, message, wParam, lParam));
  887. }
  888.