Subversion Repositories Games.Chess Giants

Rev

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

  1. // window_games.cpp
  2.  
  3. #include "../common.h"
  4.  
  5.  
  6. // window parameters
  7. #define WINDOW_CLASSNAME PROGRAM_NAME L" Games WndClass"
  8. #define WINDOW_DEFAULT_WIDTH 800
  9. #define WINDOW_DEFAULT_HEIGHT 600
  10. #define WINDOW_MIN_WIDTH 480
  11. #define WINDOW_MIN_HEIGHT 240
  12.  
  13.  
  14. // local definitions
  15. #define WINDOW_TEXT_SELECTGAME 1
  16. #define WINDOW_LIST_GAMES 2
  17. #define WINDOW_BUTTON_OK 3
  18. #define WINDOW_BUTTON_CANCEL 4
  19. #define WINDOW_TEXT_STATUSBAR 5
  20.  
  21.  
  22. // list view column definition
  23. typedef struct listviewcolumn_s
  24. {
  25.    int width;
  26.    int alignment;
  27.    bool sort_descending;
  28.    wchar_t *text;
  29.    HWND hToolTipWnd;
  30. } listviewcolumn_t;
  31.  
  32.  
  33. // global variables used in this module only
  34. static bool is_classregistered = false;
  35. static listviewcolumn_t listviewcolumns[] =
  36. {
  37.    { 130, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"Games_Event")*/, NULL }, // text address needs to be set at runtime, because it's mallocated
  38.    { 127, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"Games_Site")*/, NULL },
  39.    { 70, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"Games_Date")*/, NULL },
  40.    { 150, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"Games_White")*/, NULL },
  41.    { 150, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"Games_Black")*/, NULL },
  42.    { 60, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"Games_Result")*/, NULL },
  43.    { 40, LVCFMT_LEFT, false, NULL /*LOCALIZE (L"Games_ECO")*/, NULL },
  44. };
  45. static pgngame_t *chosen_game = NULL;
  46. //static HWND hThisWnd = NULL;
  47. #define hThisWnd hGamesWnd // shared variable
  48.  
  49.  
  50. // prototypes of local functions
  51. static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam);
  52. static int CALLBACK CompareProc_ListGames (LPARAM lParam1, LPARAM lParam2, LPARAM column);
  53. static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam);
  54.  
  55.  
  56. void Window_Games (void)
  57. {
  58.    // helper function to fire up the child window
  59.  
  60.    WNDCLASSEX wc;
  61.  
  62.    // is the window we want to display already displayed ?
  63.    if (IsWindow (hThisWnd))
  64.       SetForegroundWindow (hThisWnd); // if so, just bring it to front
  65.  
  66.    // else the way is clear
  67.    else
  68.    {
  69.       chosen_game = NULL; // no game is chosen until told otherwise
  70.  
  71.       // is the window class NOT registered yet ?
  72.       if (!is_classregistered)
  73.       {
  74.          // if so, register the window class once and for all
  75.          memset (&wc, 0, sizeof (wc));
  76.          wc.cbSize = sizeof (wc);
  77.          wc.style = CS_HREDRAW | CS_VREDRAW;
  78.          wc.lpfnWndProc = WindowProc_ThisWindow;
  79.          wc.hInstance = hAppInstance;
  80.          wc.hIcon = LoadIcon (hAppInstance, (wchar_t *) ICON_MAIN);
  81.          wc.hCursor = LoadCursor (NULL, IDC_ARROW);
  82.          wc.hbrBackground = GetSysColorBrush (COLOR_3DFACE);
  83.          wc.lpszClassName = WINDOW_CLASSNAME;
  84.          RegisterClassEx (&wc);
  85.  
  86.          is_classregistered = true; // remember this window class is registered
  87.       }
  88.  
  89.       // create the child window
  90.       hThisWnd = CreateWindowEx (WS_EX_CLIENTEDGE, WINDOW_CLASSNAME, LOCALIZE (L"Games_Title"), WS_OVERLAPPEDWINDOW,
  91.                                  CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT,
  92.                                  hMainWnd, NULL, hAppInstance, NULL);
  93.    }
  94.  
  95.    return; // return as soon as the thread is fired up
  96. }
  97.  
  98.  
  99. void Window_Games_Validated (void)
  100. {
  101.    // callback function called by the main game thread when the window is validated
  102.  
  103.    // remember this callback is no longer to be called
  104.    is_window_games_validated = false;
  105.  
  106.    // load the requested PGN game
  107.    if (PGNFile_LoadGame (&the_board, load_pathname, chosen_game))
  108.    {
  109.       animation_endtime = current_time + ANIMATION_DURATION; // start with an animation
  110.       sound_playtime = current_time + ANIMATION_DURATION - 0.1f; // play sound near the end of animation
  111.       the_board.reevaluate = true; // evaluate the new board
  112.       the_scene.update = true; // update scene
  113.    }
  114.    else
  115.    {
  116.       messagebox.hWndParent = hMainWnd;
  117.       wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
  118.       wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_LoadFailed"));
  119.       messagebox.flags = MB_ICONWARNING | MB_OK;
  120.       DialogBox_Message (&messagebox); // on error, display a modeless error message box
  121.    }
  122.  
  123.    return; // finished, PGN game is loaded
  124. }
  125.  
  126.  
  127. static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
  128. {
  129.    // message handler for the child window
  130.  
  131.    unsigned short wParam_hiword;
  132.    unsigned short wParam_loword;
  133.    pgngame_t *game;
  134.    HWND hListWnd;
  135.    LVCOLUMN lvc;
  136.    LVITEM lvi;
  137.    HDITEM hdi;
  138.    NMLISTVIEW *lv;
  139.    MINMAXINFO *minmax;
  140.    RECT client_rect;
  141.    int column_index;
  142.    int insert_index;
  143.    int game_index;
  144.    int item_index;
  145.  
  146.    // filter out the commonly used message values
  147.    wParam_hiword = HIWORD (wParam);
  148.    wParam_loword = LOWORD (wParam);
  149.  
  150.    // have we just fired up this window ?
  151.    if (message == WM_CREATE)
  152.    {
  153.       // center the window
  154.       CenterWindow (hWnd, hMainWnd);
  155.  
  156.       // populate the window
  157.       CreateWindowEx (0, L"static", L"",
  158.                       WS_CHILD | WS_VISIBLE,
  159.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_SELECTGAME, hAppInstance, NULL);
  160.       CreateWindowEx (WS_EX_STATICEDGE, L"syslistview32", L"",
  161.                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT,
  162.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_LIST_GAMES, hAppInstance, NULL);
  163.       CreateWindowEx (0, L"button", L"",
  164.                       WS_CHILD | WS_VISIBLE,
  165.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_BUTTON_OK, hAppInstance, NULL);
  166.       CreateWindowEx (0, L"button", L"",
  167.                       WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
  168.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_BUTTON_CANCEL, hAppInstance, NULL);
  169.       CreateWindowEx (WS_EX_RIGHT, L"static", L"",
  170.                       WS_CHILD | WS_VISIBLE,
  171.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_STATUSBAR, hAppInstance, NULL);
  172.  
  173.       // associate the default GUI font with the leading caption and set its text
  174.       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_SELECTGAME), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  175.       Static_SetText (GetDlgItem (hWnd, WINDOW_TEXT_SELECTGAME), LOCALIZE (L"Games_SelectGame"));
  176.  
  177.       // associate the default GUI font with the OK button and set its text
  178.       SendMessage (GetDlgItem (hWnd, WINDOW_BUTTON_OK), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  179.       SetWindowText (GetDlgItem (hWnd, WINDOW_BUTTON_OK), LOCALIZE (L"Button_OK"));
  180.  
  181.       // associate the default GUI font with the cancel button and set its text
  182.       SendMessage (GetDlgItem (hWnd, WINDOW_BUTTON_CANCEL), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  183.       SetWindowText (GetDlgItem (hWnd, WINDOW_BUTTON_CANCEL), LOCALIZE (L"Button_Cancel"));
  184.  
  185.       // associate the default GUI font with the status bar and set its text
  186.       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  187.       Static_SetText (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), LOCALIZE (L"Games_StatusBar"));
  188.  
  189.       // get a quick access to the list control
  190.       hListWnd = GetDlgItem (hWnd, WINDOW_LIST_GAMES);
  191.  
  192.       // add full row select and header columns rearranging to it
  193.       ListView_SetExtendedListViewStyle (hListWnd, ListView_GetExtendedListViewStyle (hListWnd)
  194.                                                    | LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP);
  195.  
  196.       // subclass the list view procedure to handle header tooltips and save the old procedure as one of the window's property
  197.       SetProp (hListWnd, L"BaseWndProc", (HANDLE) SetWindowLongPtr (hListWnd, GWL_WNDPROC, (long) ListView_WndProc));
  198.  
  199.       // create the list control columns
  200.  
  201.       // tell Windows which members of the LVCOLUMN structure we're filling
  202.       memset (&lvc, 0, sizeof (lvc));
  203.       lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  204.       for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  205.       {
  206.          lvc.iSubItem = column_index;
  207.          if (column_index == 0) lvc.pszText = LOCALIZE (L"Games_Event");
  208.          else if (column_index == 1) lvc.pszText = LOCALIZE (L"Games_Site");
  209.          else if (column_index == 2) lvc.pszText = LOCALIZE (L"Games_Date");
  210.          else if (column_index == 3) lvc.pszText = LOCALIZE (L"Games_White");
  211.          else if (column_index == 4) lvc.pszText = LOCALIZE (L"Games_Black");
  212.          else if (column_index == 5) lvc.pszText = LOCALIZE (L"Games_Result");
  213.          else if (column_index == 6) lvc.pszText = LOCALIZE (L"Games_ECO");
  214.          lvc.cx = listviewcolumns[column_index].width;
  215.          lvc.fmt = listviewcolumns[column_index].alignment;
  216.          ListView_InsertColumn (hListWnd, column_index, &lvc); // add each column to list view
  217.       }
  218.  
  219.       // populate the list control
  220.       ListView_SetItemCount (hListWnd, game_count);
  221.  
  222.       // tell Windows which members of the LVCOLUMN structure we're filling
  223.       memset (&lvi, 0, sizeof (lvi));
  224.       lvi.mask = LVIF_PARAM; // we want to set the item's pointer
  225.       for (game_index = 0; game_index < game_count; game_index++)
  226.       {
  227.          game = &games[game_index]; // quick access to PGN game
  228.  
  229.          lvi.iItem = game_index;
  230.          lvi.lParam = (LPARAM) game;
  231.  
  232.          insert_index = ListView_InsertItem (hListWnd, &lvi); // add each item to list view
  233.  
  234.          // set item's substrings
  235.          ListView_SetItemText (hListWnd, insert_index, 0, game->event_str);
  236.          ListView_SetItemText (hListWnd, insert_index, 1, game->site_str);
  237.          ListView_SetItemText (hListWnd, insert_index, 2, game->date_str);
  238.          ListView_SetItemText (hListWnd, insert_index, 3, game->white_str);
  239.          ListView_SetItemText (hListWnd, insert_index, 4, game->black_str);
  240.          ListView_SetItemText (hListWnd, insert_index, 5, game->result_str);
  241.          ListView_SetItemText (hListWnd, insert_index, 6, game->eco_str);
  242.       }
  243.  
  244.       // convert the status bar message to a hyperlink
  245.       ConvertStaticToHyperlink (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR));
  246.  
  247.       // now show the window
  248.       ShowWindow (hWnd, SW_SHOW);
  249.    }
  250.  
  251.    // else did we click the close button on the title bar ?
  252.    else if (message == WM_CLOSE)
  253.    {
  254.       chosen_game = NULL; // we chose no game
  255.       DestroyWindow (hWnd); // close the window
  256.    }
  257.  
  258.    // else are we destroying this window ?
  259.    else if (message == WM_DESTROY)
  260.    {
  261.       hThisWnd = NULL; // window is closed
  262.       SetForegroundWindow (hMainWnd); // restore focus on the main window
  263.       is_window_games_validated = true; // return a "load PGN game" value (cancel value will be implied if chosen_game is NULL)
  264.    }
  265.  
  266.    // else are we resizing the window ?
  267.    else if (message == WM_SIZE)
  268.    {
  269.       // get the new window size
  270.       GetClientRect (hWnd, &client_rect);
  271.  
  272.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_SELECTGAME), NULL, 16, 16, client_rect.right - 32, 32, SWP_NOZORDER);
  273.       SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_GAMES), NULL, 16, 48, client_rect.right - 32, client_rect.bottom - 104, SWP_NOZORDER);
  274.       SetWindowPos (GetDlgItem (hWnd, WINDOW_BUTTON_OK), NULL, client_rect.right / 2 - 104, client_rect.bottom - 48, 96, 32, SWP_NOZORDER);
  275.       SetWindowPos (GetDlgItem (hWnd, WINDOW_BUTTON_CANCEL), NULL, client_rect.right / 2 + 8, client_rect.bottom - 48, 96, 32, SWP_NOZORDER);
  276.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), NULL, 0, client_rect.bottom - 16, client_rect.right, 16, SWP_NOZORDER);
  277.    }
  278.  
  279.    // else are we asking how big/small we can resize ?
  280.    else if (message == WM_GETMINMAXINFO)
  281.    {
  282.       minmax = (MINMAXINFO *) lParam; // get a pointer to the min/max info structure
  283.  
  284.       minmax->ptMinTrackSize.x = WINDOW_MIN_WIDTH;
  285.       minmax->ptMinTrackSize.y = WINDOW_MIN_HEIGHT;
  286.    }
  287.  
  288.    // is it a list view message ?
  289.    else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_GAMES))
  290.    {
  291.       lv = (NMLISTVIEW *) lParam; // quick access to list view
  292.  
  293.       // is it a click on one of the headers' columns ?
  294.       if (lv->hdr.code == LVN_COLUMNCLICK)
  295.       {
  296.          // cycle through all columns and reset their sort order
  297.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  298.          {
  299.             memset (&hdi, 0, sizeof (hdi));
  300.             hdi.mask = HDI_FORMAT;
  301.             Header_GetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // get the column's sort arrow state
  302.             hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
  303.             if (column_index == lv->iSubItem)
  304.             {
  305.                listviewcolumns[column_index].sort_descending ^= true; // revert the sort order for the clicked column
  306.                hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP);
  307.             }
  308.             else
  309.                listviewcolumns[column_index].sort_descending = false; // reset the sort order for all the other ones
  310.             Header_SetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // update the column's sort arrow state
  311.          }
  312.  
  313.          // now sort the list view according to the column we selected and its current sort order
  314.          ListView_SortItems (lv->hdr.hwndFrom, CompareProc_ListGames, lv->iSubItem);
  315.       }
  316.  
  317.       // else is it a single click on one of the elements ?
  318.       else if (lv->hdr.code == NM_CLICK)
  319.          EnableWindow (GetDlgItem (hWnd, WINDOW_BUTTON_OK), true); // enable the OK button back
  320.  
  321.       // else is it a double-click on one of the elements ?
  322.       else if (lv->hdr.code == NM_DBLCLK)
  323.          PostMessage (hWnd, WM_COMMAND, WINDOW_BUTTON_OK, NULL); // if so, act as if we clicked the OK button
  324.    }
  325.  
  326.    // else did we take action on one of the controls ?
  327.    else if (message == WM_COMMAND)
  328.    {
  329.       // was it the new online game button ?
  330.       if (wParam_loword == WINDOW_BUTTON_OK)
  331.       {
  332.          chosen_game = NULL; // we chose no game until told otherwise
  333.          hListWnd = GetDlgItem (hWnd, WINDOW_LIST_GAMES); // quick access to the list control
  334.          item_index = ListView_GetNextItem (hListWnd, -1, LVNI_FOCUSED | LVNI_SELECTED); // get the first selected item
  335.          if (item_index > -1)
  336.          {
  337.             memset (&lvi, 0, sizeof (lvi));
  338.             lvi.mask = LVIF_PARAM; // we want to set the item's pointer
  339.             lvi.iItem = item_index;
  340.             if (ListView_GetItem (hListWnd, &lvi))
  341.                chosen_game = (pgngame_t *) lvi.lParam; // retrieve a pointer to the PGN game from the item
  342.          }
  343.  
  344.          DestroyWindow (hWnd); // close the window
  345.       }
  346.  
  347.       // else was it the cancel button ?
  348.       else if (wParam_loword == WINDOW_BUTTON_CANCEL)
  349.       {
  350.          chosen_game = NULL; // we chose no game
  351.          DestroyWindow (hWnd); // close the window
  352.       }
  353.  
  354.       // else was it the status bar hyperlink ?
  355.       else if (wParam_loword == STATICTEXT_NEWGAME_STATUSBAR)
  356.          ShellExecute (NULL, L"open", PROGRAM_URL, NULL, NULL, SW_MAXIMIZE); // open the donation page in the default browser, maximized
  357.    }
  358.  
  359.    // call the default window message processing function to keep things going
  360.    return (DefWindowProc (hWnd, message, wParam, lParam));
  361. }
  362.  
  363.  
  364. static int CALLBACK CompareProc_ListGames (LPARAM lParam1, LPARAM lParam2, LPARAM column)
  365. {
  366.    // callback function that tells whether the lParam1 listview element comes before lParam2 in the
  367.    // sort order of the specified column
  368.  
  369.    wchar_t *string_to_compare1;
  370.    wchar_t *string_to_compare2;
  371.  
  372.    // retrieve strings to compare according to the column we want
  373.    if (column == 0)
  374.    {
  375.       string_to_compare1 = ((pgngame_t *) lParam1)->event_str; // compare events
  376.       string_to_compare2 = ((pgngame_t *) lParam2)->event_str;
  377.    }
  378.    else if (column == 1)
  379.    {
  380.       string_to_compare1 = ((pgngame_t *) lParam1)->site_str; // compare sites
  381.       string_to_compare2 = ((pgngame_t *) lParam2)->site_str;
  382.    }
  383.    else if (column == 2)
  384.    {
  385.       string_to_compare1 = ((pgngame_t *) lParam1)->date_str; // compare dates
  386.       string_to_compare2 = ((pgngame_t *) lParam2)->date_str;
  387.    }
  388.    else if (column == 3)
  389.    {
  390.       string_to_compare1 = ((pgngame_t *) lParam1)->white_str; // compare whites
  391.       string_to_compare2 = ((pgngame_t *) lParam2)->white_str;
  392.    }
  393.    else if (column == 4)
  394.    {
  395.       string_to_compare1 = ((pgngame_t *) lParam1)->black_str; // compare blacks
  396.       string_to_compare2 = ((pgngame_t *) lParam2)->black_str;
  397.    }
  398.    else if (column == 5)
  399.    {
  400.       string_to_compare1 = ((pgngame_t *) lParam1)->result_str; // compare results
  401.       string_to_compare2 = ((pgngame_t *) lParam2)->result_str;
  402.    }
  403.    else if (column == 6)
  404.    {
  405.       string_to_compare1 = ((pgngame_t *) lParam1)->eco_str; // compare ECO
  406.       string_to_compare2 = ((pgngame_t *) lParam2)->eco_str;
  407.    }
  408.  
  409.    // which order do we want this column to be sorted ?
  410.    if (listviewcolumns[column].sort_descending)
  411.       return (wcscmp (string_to_compare1, string_to_compare2)); // normal order
  412.    else
  413.       return (-wcscmp (string_to_compare1, string_to_compare2)); // reverse order
  414. }
  415.  
  416.  
  417. static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
  418. {
  419.    // callback that subclasses the original ListView window procedure, so that we can hook
  420.    // some of its messages
  421.  
  422.    static bool tooltips_initialized = false;
  423.    static bool update_tooltips = false;
  424.    WNDPROC BaseWndProc;
  425.    TOOLINFO toolinfo;
  426.    HWND hHeaderWnd;
  427.    int column_index;
  428.  
  429.    // get a pointer to the base window procedure (it was stored as a window property)
  430.    BaseWndProc = (WNDPROC) GetProp (hWnd, L"BaseWndProc");
  431.    if (BaseWndProc == NULL)
  432.       return (DefWindowProc (hWnd, message, wParam, lParam)); // consistency check
  433.  
  434.    // is the mouse moving around ?
  435.    if (message == WM_MOUSEMOVE)
  436.    {
  437.       // do the tooltips need to be created ?
  438.       if (!tooltips_initialized)
  439.       {
  440.          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
  441.  
  442.          // add a tooltip for each column
  443.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  444.          {
  445.             // create the tooltip and set its window topmost
  446.             listviewcolumns[column_index].hToolTipWnd = CreateWindowEx (WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
  447.                                                                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  448.                                                                         hHeaderWnd, NULL, hAppInstance, NULL);
  449.  
  450.             // associate the tooltip with the tool
  451.             memset (&toolinfo, 0, sizeof (toolinfo));
  452.             toolinfo.cbSize = sizeof (toolinfo);
  453.             toolinfo.uFlags = TTF_SUBCLASS;
  454.             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
  455.             toolinfo.uId = column_index; // tooltip ID will be column ID
  456.             toolinfo.hinst = hAppInstance;
  457.             toolinfo.lpszText = listviewcolumns[column_index].text; // tooltip text
  458.             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
  459.             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_ADDTOOL, 0, (LPARAM) &toolinfo);
  460.          }
  461.  
  462.          tooltips_initialized = true; // do this only once
  463.       }
  464.  
  465.       // else do the tooltips need to be updated ?
  466.       else if (update_tooltips)
  467.       {
  468.          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
  469.  
  470.          // cycle through all columns
  471.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  472.          {
  473.             // update the tooltip rectangle
  474.             memset (&toolinfo, 0, sizeof (toolinfo));
  475.             toolinfo.cbSize = sizeof (toolinfo);
  476.             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
  477.             toolinfo.uId = column_index; // tooltip ID is column ID
  478.             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
  479.             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_NEWTOOLRECT, 0, (LPARAM) &toolinfo);
  480.          }
  481.  
  482.          update_tooltips = false; // do this only once
  483.       }
  484.    }
  485.  
  486.    // else has the user finished dragging/resizing a column header ?
  487.    else if ((message == WM_NOTIFY) && ((((NMHDR *) lParam)->code == HDN_ENDTRACK) || (((NMHDR *) lParam)->code == HDN_ENDDRAG)))
  488.       update_tooltips = true; // if so, remember to update tooltips on the next mouse move
  489.  
  490.    // in any case, forward all messages to the original ListView hook procedure
  491.    return (CallWindowProc (BaseWndProc, hWnd, message, wParam, lParam));
  492. }
  493.