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.       return (0); // as MSDN says
  251.    }
  252.  
  253.    // else did we click the close button on the title bar ?
  254.    else if (message == WM_CLOSE)
  255.    {
  256.       chosen_game = NULL; // we chose no game
  257.       DestroyWindow (hWnd); // close the window
  258.  
  259.       return (0); // as MSDN says
  260.    }
  261.  
  262.    // else are we destroying this window ?
  263.    else if (message == WM_DESTROY)
  264.    {
  265.       hThisWnd = NULL; // window is closed
  266.       SetForegroundWindow (hMainWnd); // restore focus on the main window
  267.       is_window_games_validated = true; // return a "load PGN game" value (cancel value will be implied if chosen_game is NULL)
  268.       the_board.reevaluate = true; // refresh the GUI buttons if needed
  269.       return (0); // as MSDN says
  270.    }
  271.  
  272.    // else are we resizing the window ?
  273.    else if (message == WM_SIZE)
  274.    {
  275.       // get the new window size
  276.       GetClientRect (hWnd, &client_rect);
  277.  
  278.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_SELECTGAME), NULL, 16, 16, client_rect.right - 32, 32, SWP_NOZORDER);
  279.       SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_GAMES), NULL, 16, 48, client_rect.right - 32, client_rect.bottom - 104, SWP_NOZORDER);
  280.       SetWindowPos (GetDlgItem (hWnd, WINDOW_BUTTON_OK), NULL, client_rect.right / 2 - 104, client_rect.bottom - 48, 96, 32, SWP_NOZORDER);
  281.       SetWindowPos (GetDlgItem (hWnd, WINDOW_BUTTON_CANCEL), NULL, client_rect.right / 2 + 8, client_rect.bottom - 48, 96, 32, SWP_NOZORDER);
  282.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), NULL, 0, client_rect.bottom - 16, client_rect.right, 16, SWP_NOZORDER);
  283.  
  284.       return (0); // as MSDN says
  285.    }
  286.  
  287.    // else are we asking how big/small we can resize ?
  288.    else if (message == WM_GETMINMAXINFO)
  289.    {
  290.       minmax = (MINMAXINFO *) lParam; // get a pointer to the min/max info structure
  291.  
  292.       minmax->ptMinTrackSize.x = WINDOW_MIN_WIDTH;
  293.       minmax->ptMinTrackSize.y = WINDOW_MIN_HEIGHT;
  294.  
  295.       return (0); // as MSDN says
  296.    }
  297.  
  298.    // is it a list view message ?
  299.    else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_GAMES))
  300.    {
  301.       lv = (NMLISTVIEW *) lParam; // quick access to list view
  302.  
  303.       // is it a click on one of the headers' columns ?
  304.       if (lv->hdr.code == LVN_COLUMNCLICK)
  305.       {
  306.          // cycle through all columns and reset their sort order
  307.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  308.          {
  309.             memset (&hdi, 0, sizeof (hdi));
  310.             hdi.mask = HDI_FORMAT;
  311.             Header_GetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // get the column's sort arrow state
  312.             hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
  313.             if (column_index == lv->iSubItem)
  314.             {
  315.                listviewcolumns[column_index].sort_descending ^= true; // revert the sort order for the clicked column
  316.                hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP);
  317.             }
  318.             else
  319.                listviewcolumns[column_index].sort_descending = false; // reset the sort order for all the other ones
  320.             Header_SetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // update the column's sort arrow state
  321.          }
  322.  
  323.          // now sort the list view according to the column we selected and its current sort order
  324.          ListView_SortItems (lv->hdr.hwndFrom, CompareProc_ListGames, lv->iSubItem);
  325.       }
  326.  
  327.       // else is it a single click on one of the elements ?
  328.       else if (lv->hdr.code == NM_CLICK)
  329.          EnableWindow (GetDlgItem (hWnd, WINDOW_BUTTON_OK), true); // enable the OK button back
  330.  
  331.       // else is it a double-click on one of the elements ?
  332.       else if (lv->hdr.code == NM_DBLCLK)
  333.          PostMessage (hWnd, WM_COMMAND, WINDOW_BUTTON_OK, NULL); // if so, act as if we clicked the OK button
  334.  
  335.       // no return value, says MSDN
  336.    }
  337.  
  338.    // else did we take action on one of the controls ?
  339.    else if (message == WM_COMMAND)
  340.    {
  341.       // was it the new online game button ?
  342.       if (wParam_loword == WINDOW_BUTTON_OK)
  343.       {
  344.          chosen_game = NULL; // we chose no game until told otherwise
  345.          hListWnd = GetDlgItem (hWnd, WINDOW_LIST_GAMES); // quick access to the list control
  346.          item_index = ListView_GetNextItem (hListWnd, -1, LVNI_FOCUSED | LVNI_SELECTED); // get the first selected item
  347.          if (item_index > -1)
  348.          {
  349.             memset (&lvi, 0, sizeof (lvi));
  350.             lvi.mask = LVIF_PARAM; // we want to set the item's pointer
  351.             lvi.iItem = item_index;
  352.             if (ListView_GetItem (hListWnd, &lvi))
  353.                chosen_game = (pgngame_t *) lvi.lParam; // retrieve a pointer to the PGN game from the item
  354.          }
  355.  
  356.          DestroyWindow (hWnd); // close the window
  357.       }
  358.  
  359.       // else was it the cancel button ?
  360.       else if (wParam_loword == WINDOW_BUTTON_CANCEL)
  361.       {
  362.          chosen_game = NULL; // we chose no game
  363.          DestroyWindow (hWnd); // close the window
  364.       }
  365.  
  366.       // else was it the status bar hyperlink ?
  367.       else if (wParam_loword == STATICTEXT_NEWGAME_STATUSBAR)
  368.          ShellExecute (NULL, L"open", PROGRAM_URL, NULL, NULL, SW_MAXIMIZE); // open the donation page in the default browser, maximized
  369.  
  370.       return (0); // as MSDN says
  371.    }
  372.  
  373.    // call the default window message processing function to keep things going
  374.    return (DefWindowProc (hWnd, message, wParam, lParam));
  375. }
  376.  
  377.  
  378. static int CALLBACK CompareProc_ListGames (LPARAM lParam1, LPARAM lParam2, LPARAM column)
  379. {
  380.    // callback function that tells whether the lParam1 listview element comes before lParam2 in the
  381.    // sort order of the specified column
  382.  
  383.    wchar_t *string_to_compare1;
  384.    wchar_t *string_to_compare2;
  385.  
  386.    // retrieve strings to compare according to the column we want
  387.    if (column == 0)
  388.    {
  389.       string_to_compare1 = ((pgngame_t *) lParam1)->event_str; // compare events
  390.       string_to_compare2 = ((pgngame_t *) lParam2)->event_str;
  391.    }
  392.    else if (column == 1)
  393.    {
  394.       string_to_compare1 = ((pgngame_t *) lParam1)->site_str; // compare sites
  395.       string_to_compare2 = ((pgngame_t *) lParam2)->site_str;
  396.    }
  397.    else if (column == 2)
  398.    {
  399.       string_to_compare1 = ((pgngame_t *) lParam1)->date_str; // compare dates
  400.       string_to_compare2 = ((pgngame_t *) lParam2)->date_str;
  401.    }
  402.    else if (column == 3)
  403.    {
  404.       string_to_compare1 = ((pgngame_t *) lParam1)->white_str; // compare whites
  405.       string_to_compare2 = ((pgngame_t *) lParam2)->white_str;
  406.    }
  407.    else if (column == 4)
  408.    {
  409.       string_to_compare1 = ((pgngame_t *) lParam1)->black_str; // compare blacks
  410.       string_to_compare2 = ((pgngame_t *) lParam2)->black_str;
  411.    }
  412.    else if (column == 5)
  413.    {
  414.       string_to_compare1 = ((pgngame_t *) lParam1)->result_str; // compare results
  415.       string_to_compare2 = ((pgngame_t *) lParam2)->result_str;
  416.    }
  417.    else if (column == 6)
  418.    {
  419.       string_to_compare1 = ((pgngame_t *) lParam1)->eco_str; // compare ECO
  420.       string_to_compare2 = ((pgngame_t *) lParam2)->eco_str;
  421.    }
  422.  
  423.    // which order do we want this column to be sorted ?
  424.    if (listviewcolumns[column].sort_descending)
  425.       return (wcscmp (string_to_compare1, string_to_compare2)); // normal order
  426.    else
  427.       return (-wcscmp (string_to_compare1, string_to_compare2)); // reverse order
  428. }
  429.  
  430.  
  431. static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
  432. {
  433.    // callback that subclasses the original ListView window procedure, so that we can hook
  434.    // some of its messages
  435.  
  436.    static bool tooltips_initialized = false;
  437.    static bool update_tooltips = false;
  438.    WNDPROC BaseWndProc;
  439.    TOOLINFO toolinfo;
  440.    HWND hHeaderWnd;
  441.    int column_index;
  442.  
  443.    // get a pointer to the base window procedure (it was stored as a window property)
  444.    BaseWndProc = (WNDPROC) GetProp (hWnd, L"BaseWndProc");
  445.    if (BaseWndProc == NULL)
  446.       return (DefWindowProc (hWnd, message, wParam, lParam)); // consistency check
  447.  
  448.    // is the mouse moving around ?
  449.    if (message == WM_MOUSEMOVE)
  450.    {
  451.       // do the tooltips need to be created ?
  452.       if (!tooltips_initialized)
  453.       {
  454.          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
  455.  
  456.          // add a tooltip for each column
  457.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  458.          {
  459.             // create the tooltip and set its window topmost
  460.             listviewcolumns[column_index].hToolTipWnd = CreateWindowEx (WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
  461.                                                                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  462.                                                                         hHeaderWnd, NULL, hAppInstance, NULL);
  463.  
  464.             // associate the tooltip with the tool
  465.             memset (&toolinfo, 0, sizeof (toolinfo));
  466.             toolinfo.cbSize = sizeof (toolinfo);
  467.             toolinfo.uFlags = TTF_SUBCLASS;
  468.             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
  469.             toolinfo.uId = column_index; // tooltip ID will be column ID
  470.             toolinfo.hinst = hAppInstance;
  471.             toolinfo.lpszText = listviewcolumns[column_index].text; // tooltip text
  472.             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
  473.             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_ADDTOOL, 0, (LPARAM) &toolinfo);
  474.          }
  475.  
  476.          tooltips_initialized = true; // do this only once
  477.       }
  478.  
  479.       // else do the tooltips need to be updated ?
  480.       else if (update_tooltips)
  481.       {
  482.          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
  483.  
  484.          // cycle through all columns
  485.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  486.          {
  487.             // update the tooltip rectangle
  488.             memset (&toolinfo, 0, sizeof (toolinfo));
  489.             toolinfo.cbSize = sizeof (toolinfo);
  490.             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
  491.             toolinfo.uId = column_index; // tooltip ID is column ID
  492.             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
  493.             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_NEWTOOLRECT, 0, (LPARAM) &toolinfo);
  494.          }
  495.  
  496.          update_tooltips = false; // do this only once
  497.       }
  498.    }
  499.  
  500.    // else has the user finished dragging/resizing a column header ?
  501.    else if ((message == WM_NOTIFY) && ((((NMHDR *) lParam)->code == HDN_ENDTRACK) || (((NMHDR *) lParam)->code == HDN_ENDDRAG)))
  502.       update_tooltips = true; // if so, remember to update tooltips on the next mouse move
  503.  
  504.    // in any case, forward all messages to the original ListView hook procedure
  505.    return (CallWindowProc (BaseWndProc, hWnd, message, wParam, lParam));
  506. }
  507.