Subversion Repositories Games.Chess Giants

Rev

Rev 124 | 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.       the_board.reevaluate = true; // evaluate the new board
  111.       the_scene.update = true; // update scene
  112.    }
  113.    else
  114.    {
  115.       messagebox.hWndParent = hMainWnd;
  116.       wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
  117.       wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_LoadFailed"));
  118.       messagebox.flags = MB_ICONWARNING | MB_OK;
  119.       DialogBox_Message (&messagebox); // on error, display a modeless error message box
  120.    }
  121.  
  122.    return; // finished, PGN game is loaded
  123. }
  124.  
  125.  
  126. static LRESULT CALLBACK WindowProc_ThisWindow (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
  127. {
  128.    // message handler for the child window
  129.  
  130.    unsigned short wParam_hiword;
  131.    unsigned short wParam_loword;
  132.    pgngame_t *game;
  133.    HWND hListWnd;
  134.    LVCOLUMN lvc;
  135.    LVITEM lvi;
  136.    HDITEM hdi;
  137.    NMLISTVIEW *lv;
  138.    MINMAXINFO *minmax;
  139.    RECT client_rect;
  140.    int column_index;
  141.    int insert_index;
  142.    int game_index;
  143.    int item_index;
  144.  
  145.    // filter out the commonly used message values
  146.    wParam_hiword = HIWORD (wParam);
  147.    wParam_loword = LOWORD (wParam);
  148.  
  149.    // have we just fired up this window ?
  150.    if (message == WM_CREATE)
  151.    {
  152.       // center the window
  153.       CenterWindow (hWnd, hMainWnd);
  154.  
  155.       // populate the window
  156.       CreateWindowEx (0, L"static", L"",
  157.                       WS_CHILD | WS_VISIBLE,
  158.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_SELECTGAME, hAppInstance, NULL);
  159.       CreateWindowEx (WS_EX_STATICEDGE, L"syslistview32", L"",
  160.                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT,
  161.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_LIST_GAMES, hAppInstance, NULL);
  162.       CreateWindowEx (0, L"button", L"",
  163.                       WS_CHILD | WS_VISIBLE,
  164.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_BUTTON_OK, hAppInstance, NULL);
  165.       CreateWindowEx (0, L"button", L"",
  166.                       WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
  167.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_BUTTON_CANCEL, hAppInstance, NULL);
  168.       CreateWindowEx (WS_EX_RIGHT, L"static", L"",
  169.                       WS_CHILD | WS_VISIBLE,
  170.                       0, 0, 0, 0, hWnd, (HMENU) WINDOW_TEXT_STATUSBAR, hAppInstance, NULL);
  171.  
  172.       // associate the default GUI font with the leading caption and set its text
  173.       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_SELECTGAME), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  174.       Static_SetText (GetDlgItem (hWnd, WINDOW_TEXT_SELECTGAME), LOCALIZE (L"Games_SelectGame"));
  175.  
  176.       // associate the default GUI font with the OK button and set its text
  177.       SendMessage (GetDlgItem (hWnd, WINDOW_BUTTON_OK), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  178.       SetWindowText (GetDlgItem (hWnd, WINDOW_BUTTON_OK), LOCALIZE (L"Button_OK"));
  179.  
  180.       // associate the default GUI font with the cancel button and set its text
  181.       SendMessage (GetDlgItem (hWnd, WINDOW_BUTTON_CANCEL), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  182.       SetWindowText (GetDlgItem (hWnd, WINDOW_BUTTON_CANCEL), LOCALIZE (L"Button_Cancel"));
  183.  
  184.       // associate the default GUI font with the status bar and set its text
  185.       SendMessage (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), WM_SETFONT, (WPARAM) GetStockObject (DEFAULT_GUI_FONT), false);
  186.       Static_SetText (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), LOCALIZE (L"Games_StatusBar"));
  187.  
  188.       // get a quick access to the list control
  189.       hListWnd = GetDlgItem (hWnd, WINDOW_LIST_GAMES);
  190.  
  191.       // add full row select and header columns rearranging to it
  192.       ListView_SetExtendedListViewStyle (hListWnd, ListView_GetExtendedListViewStyle (hListWnd)
  193.                                                    | LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP);
  194.  
  195.       // subclass the list view procedure to handle header tooltips and save the old procedure as one of the window's property
  196.       SetProp (hListWnd, L"BaseWndProc", (HANDLE) SetWindowLongPtr (hListWnd, GWL_WNDPROC, (long) ListView_WndProc));
  197.  
  198.       // create the list control columns
  199.  
  200.       // tell Windows which members of the LVCOLUMN structure we're filling
  201.       memset (&lvc, 0, sizeof (lvc));
  202.       lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  203.       for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  204.       {
  205.          lvc.iSubItem = column_index;
  206.          if (column_index == 0) lvc.pszText = LOCALIZE (L"Games_Event");
  207.          else if (column_index == 1) lvc.pszText = LOCALIZE (L"Games_Site");
  208.          else if (column_index == 2) lvc.pszText = LOCALIZE (L"Games_Date");
  209.          else if (column_index == 3) lvc.pszText = LOCALIZE (L"Games_White");
  210.          else if (column_index == 4) lvc.pszText = LOCALIZE (L"Games_Black");
  211.          else if (column_index == 5) lvc.pszText = LOCALIZE (L"Games_Result");
  212.          else if (column_index == 6) lvc.pszText = LOCALIZE (L"Games_ECO");
  213.          lvc.cx = listviewcolumns[column_index].width;
  214.          lvc.fmt = listviewcolumns[column_index].alignment;
  215.          ListView_InsertColumn (hListWnd, column_index, &lvc); // add each column to list view
  216.       }
  217.  
  218.       // populate the list control
  219.       ListView_SetItemCount (hListWnd, game_count);
  220.  
  221.       // tell Windows which members of the LVCOLUMN structure we're filling
  222.       memset (&lvi, 0, sizeof (lvi));
  223.       lvi.mask = LVIF_PARAM; // we want to set the item's pointer
  224.       for (game_index = 0; game_index < game_count; game_index++)
  225.       {
  226.          game = &games[game_index]; // quick access to PGN game
  227.  
  228.          lvi.iItem = game_index;
  229.          lvi.lParam = (LPARAM) game;
  230.  
  231.          insert_index = ListView_InsertItem (hListWnd, &lvi); // add each item to list view
  232.  
  233.          // set item's substrings
  234.          ListView_SetItemText (hListWnd, insert_index, 0, game->event_str);
  235.          ListView_SetItemText (hListWnd, insert_index, 1, game->site_str);
  236.          ListView_SetItemText (hListWnd, insert_index, 2, game->date_str);
  237.          ListView_SetItemText (hListWnd, insert_index, 3, game->white_str);
  238.          ListView_SetItemText (hListWnd, insert_index, 4, game->black_str);
  239.          ListView_SetItemText (hListWnd, insert_index, 5, game->result_str);
  240.          ListView_SetItemText (hListWnd, insert_index, 6, game->eco_str);
  241.       }
  242.  
  243.       // convert the status bar message to a hyperlink
  244.       ConvertStaticToHyperlink (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR));
  245.  
  246.       // now show the window
  247.       ShowWindow (hWnd, SW_SHOW);
  248.  
  249.       return (0); // as MSDN says
  250.    }
  251.  
  252.    // else did we click the close button on the title bar ?
  253.    else if (message == WM_CLOSE)
  254.    {
  255.       chosen_game = NULL; // we chose no game
  256.       DestroyWindow (hWnd); // close the window
  257.  
  258.       return (0); // as MSDN says
  259.    }
  260.  
  261.    // else are we destroying this window ?
  262.    else if (message == WM_DESTROY)
  263.    {
  264.       hThisWnd = NULL; // window is closed
  265.       SetForegroundWindow (hMainWnd); // restore focus on the main window
  266.       is_window_games_validated = true; // return a "load PGN game" value (cancel value will be implied if chosen_game is NULL)
  267.       the_board.reevaluate = true; // refresh the GUI buttons if needed
  268.       return (0); // as MSDN says
  269.    }
  270.  
  271.    // else are we resizing the window ?
  272.    else if (message == WM_SIZE)
  273.    {
  274.       // get the new window size
  275.       GetClientRect (hWnd, &client_rect);
  276.  
  277.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_SELECTGAME), NULL, 16, 16, client_rect.right - 32, 32, SWP_NOZORDER);
  278.       SetWindowPos (GetDlgItem (hWnd, WINDOW_LIST_GAMES), NULL, 16, 48, client_rect.right - 32, client_rect.bottom - 104, SWP_NOZORDER);
  279.       SetWindowPos (GetDlgItem (hWnd, WINDOW_BUTTON_OK), NULL, client_rect.right / 2 - 104, client_rect.bottom - 48, 96, 32, SWP_NOZORDER);
  280.       SetWindowPos (GetDlgItem (hWnd, WINDOW_BUTTON_CANCEL), NULL, client_rect.right / 2 + 8, client_rect.bottom - 48, 96, 32, SWP_NOZORDER);
  281.       SetWindowPos (GetDlgItem (hWnd, WINDOW_TEXT_STATUSBAR), NULL, 0, client_rect.bottom - 16, client_rect.right, 16, SWP_NOZORDER);
  282.  
  283.       return (0); // as MSDN says
  284.    }
  285.  
  286.    // else are we asking how big/small we can resize ?
  287.    else if (message == WM_GETMINMAXINFO)
  288.    {
  289.       minmax = (MINMAXINFO *) lParam; // get a pointer to the min/max info structure
  290.  
  291.       minmax->ptMinTrackSize.x = WINDOW_MIN_WIDTH;
  292.       minmax->ptMinTrackSize.y = WINDOW_MIN_HEIGHT;
  293.  
  294.       return (0); // as MSDN says
  295.    }
  296.  
  297.    // is it a list view message ?
  298.    else if ((message == WM_NOTIFY) && (wParam == WINDOW_LIST_GAMES))
  299.    {
  300.       lv = (NMLISTVIEW *) lParam; // quick access to list view
  301.  
  302.       // is it a click on one of the headers' columns ?
  303.       if (lv->hdr.code == LVN_COLUMNCLICK)
  304.       {
  305.          // cycle through all columns and reset their sort order
  306.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  307.          {
  308.             memset (&hdi, 0, sizeof (hdi));
  309.             hdi.mask = HDI_FORMAT;
  310.             Header_GetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // get the column's sort arrow state
  311.             hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
  312.             if (column_index == lv->iSubItem)
  313.             {
  314.                listviewcolumns[column_index].sort_descending ^= true; // revert the sort order for the clicked column
  315.                hdi.fmt |= (listviewcolumns[column_index].sort_descending ? HDF_SORTDOWN : HDF_SORTUP);
  316.             }
  317.             else
  318.                listviewcolumns[column_index].sort_descending = false; // reset the sort order for all the other ones
  319.             Header_SetItem (ListView_GetHeader (lv->hdr.hwndFrom), column_index, &hdi); // update the column's sort arrow state
  320.          }
  321.  
  322.          // now sort the list view according to the column we selected and its current sort order
  323.          ListView_SortItems (lv->hdr.hwndFrom, CompareProc_ListGames, lv->iSubItem);
  324.       }
  325.  
  326.       // else is it a single click on one of the elements ?
  327.       else if (lv->hdr.code == NM_CLICK)
  328.          EnableWindow (GetDlgItem (hWnd, WINDOW_BUTTON_OK), true); // enable the OK button back
  329.  
  330.       // else is it a double-click on one of the elements ?
  331.       else if (lv->hdr.code == NM_DBLCLK)
  332.          PostMessage (hWnd, WM_COMMAND, WINDOW_BUTTON_OK, NULL); // if so, act as if we clicked the OK button
  333.  
  334.       // no return value, says MSDN
  335.    }
  336.  
  337.    // else did we take action on one of the controls ?
  338.    else if (message == WM_COMMAND)
  339.    {
  340.       // was it the new online game button ?
  341.       if (wParam_loword == WINDOW_BUTTON_OK)
  342.       {
  343.          chosen_game = NULL; // we chose no game until told otherwise
  344.          hListWnd = GetDlgItem (hWnd, WINDOW_LIST_GAMES); // quick access to the list control
  345.          item_index = ListView_GetNextItem (hListWnd, -1, LVNI_FOCUSED | LVNI_SELECTED); // get the first selected item
  346.          if (item_index > -1)
  347.          {
  348.             memset (&lvi, 0, sizeof (lvi));
  349.             lvi.mask = LVIF_PARAM; // we want to set the item's pointer
  350.             lvi.iItem = item_index;
  351.             if (ListView_GetItem (hListWnd, &lvi))
  352.                chosen_game = (pgngame_t *) lvi.lParam; // retrieve a pointer to the PGN game from the item
  353.          }
  354.  
  355.          DestroyWindow (hWnd); // close the window
  356.       }
  357.  
  358.       // else was it the cancel button ?
  359.       else if (wParam_loword == WINDOW_BUTTON_CANCEL)
  360.       {
  361.          chosen_game = NULL; // we chose no game
  362.          DestroyWindow (hWnd); // close the window
  363.       }
  364.  
  365.       // else was it the status bar hyperlink ?
  366.       else if (wParam_loword == STATICTEXT_NEWGAME_STATUSBAR)
  367.          ShellExecute (NULL, L"open", PROGRAM_URL, NULL, NULL, SW_MAXIMIZE); // open the donation page in the default browser, maximized
  368.  
  369.       return (0); // as MSDN says
  370.    }
  371.  
  372.    // call the default window message processing function to keep things going
  373.    return (DefWindowProc (hWnd, message, wParam, lParam));
  374. }
  375.  
  376.  
  377. static int CALLBACK CompareProc_ListGames (LPARAM lParam1, LPARAM lParam2, LPARAM column)
  378. {
  379.    // callback function that tells whether the lParam1 listview element comes before lParam2 in the
  380.    // sort order of the specified column
  381.  
  382.    wchar_t *string_to_compare1;
  383.    wchar_t *string_to_compare2;
  384.  
  385.    // retrieve strings to compare according to the column we want
  386.    if (column == 0)
  387.    {
  388.       string_to_compare1 = ((pgngame_t *) lParam1)->event_str; // compare events
  389.       string_to_compare2 = ((pgngame_t *) lParam2)->event_str;
  390.    }
  391.    else if (column == 1)
  392.    {
  393.       string_to_compare1 = ((pgngame_t *) lParam1)->site_str; // compare sites
  394.       string_to_compare2 = ((pgngame_t *) lParam2)->site_str;
  395.    }
  396.    else if (column == 2)
  397.    {
  398.       string_to_compare1 = ((pgngame_t *) lParam1)->date_str; // compare dates
  399.       string_to_compare2 = ((pgngame_t *) lParam2)->date_str;
  400.    }
  401.    else if (column == 3)
  402.    {
  403.       string_to_compare1 = ((pgngame_t *) lParam1)->white_str; // compare whites
  404.       string_to_compare2 = ((pgngame_t *) lParam2)->white_str;
  405.    }
  406.    else if (column == 4)
  407.    {
  408.       string_to_compare1 = ((pgngame_t *) lParam1)->black_str; // compare blacks
  409.       string_to_compare2 = ((pgngame_t *) lParam2)->black_str;
  410.    }
  411.    else if (column == 5)
  412.    {
  413.       string_to_compare1 = ((pgngame_t *) lParam1)->result_str; // compare results
  414.       string_to_compare2 = ((pgngame_t *) lParam2)->result_str;
  415.    }
  416.    else if (column == 6)
  417.    {
  418.       string_to_compare1 = ((pgngame_t *) lParam1)->eco_str; // compare ECO
  419.       string_to_compare2 = ((pgngame_t *) lParam2)->eco_str;
  420.    }
  421.  
  422.    // which order do we want this column to be sorted ?
  423.    if (listviewcolumns[column].sort_descending)
  424.       return (wcscmp (string_to_compare1, string_to_compare2)); // normal order
  425.    else
  426.       return (-wcscmp (string_to_compare1, string_to_compare2)); // reverse order
  427. }
  428.  
  429.  
  430. static int WINAPI ListView_WndProc (HWND hWnd, unsigned int message, WPARAM wParam, LPARAM lParam)
  431. {
  432.    // callback that subclasses the original ListView window procedure, so that we can hook
  433.    // some of its messages
  434.  
  435.    static bool tooltips_initialized = false;
  436.    static bool update_tooltips = false;
  437.    WNDPROC BaseWndProc;
  438.    TOOLINFO toolinfo;
  439.    HWND hHeaderWnd;
  440.    int column_index;
  441.  
  442.    // get a pointer to the base window procedure (it was stored as a window property)
  443.    BaseWndProc = (WNDPROC) GetProp (hWnd, L"BaseWndProc");
  444.    if (BaseWndProc == NULL)
  445.       return (DefWindowProc (hWnd, message, wParam, lParam)); // consistency check
  446.  
  447.    // is the mouse moving around ?
  448.    if (message == WM_MOUSEMOVE)
  449.    {
  450.       // do the tooltips need to be created ?
  451.       if (!tooltips_initialized)
  452.       {
  453.          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
  454.  
  455.          // add a tooltip for each column
  456.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  457.          {
  458.             // create the tooltip and set its window topmost
  459.             listviewcolumns[column_index].hToolTipWnd = CreateWindowEx (WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
  460.                                                                         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  461.                                                                         hHeaderWnd, NULL, hAppInstance, NULL);
  462.  
  463.             // associate the tooltip with the tool
  464.             memset (&toolinfo, 0, sizeof (toolinfo));
  465.             toolinfo.cbSize = sizeof (toolinfo);
  466.             toolinfo.uFlags = TTF_SUBCLASS;
  467.             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
  468.             toolinfo.uId = column_index; // tooltip ID will be column ID
  469.             toolinfo.hinst = hAppInstance;
  470.             toolinfo.lpszText = listviewcolumns[column_index].text; // tooltip text
  471.             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
  472.             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_ADDTOOL, 0, (LPARAM) &toolinfo);
  473.          }
  474.  
  475.          tooltips_initialized = true; // do this only once
  476.       }
  477.  
  478.       // else do the tooltips need to be updated ?
  479.       else if (update_tooltips)
  480.       {
  481.          hHeaderWnd = (HWND) SendMessage (hWnd, LVM_GETHEADER, 0, 0); // get listview header
  482.  
  483.          // cycle through all columns
  484.          for (column_index = 0; column_index < sizeof (listviewcolumns) / sizeof (listviewcolumn_t); column_index++)
  485.          {
  486.             // update the tooltip rectangle
  487.             memset (&toolinfo, 0, sizeof (toolinfo));
  488.             toolinfo.cbSize = sizeof (toolinfo);
  489.             toolinfo.hwnd = hHeaderWnd; // this tooltip works on the list header window handle
  490.             toolinfo.uId = column_index; // tooltip ID is column ID
  491.             Header_GetItemRect (hHeaderWnd, column_index, &toolinfo.rect); // get header's item rectangle
  492.             SendMessage (listviewcolumns[column_index].hToolTipWnd, TTM_NEWTOOLRECT, 0, (LPARAM) &toolinfo);
  493.          }
  494.  
  495.          update_tooltips = false; // do this only once
  496.       }
  497.    }
  498.  
  499.    // else has the user finished dragging/resizing a column header ?
  500.    else if ((message == WM_NOTIFY) && ((((NMHDR *) lParam)->code == HDN_ENDTRACK) || (((NMHDR *) lParam)->code == HDN_ENDDRAG)))
  501.       update_tooltips = true; // if so, remember to update tooltips on the next mouse move
  502.  
  503.    // in any case, forward all messages to the original ListView hook procedure
  504.    return (CallWindowProc (BaseWndProc, hWnd, message, wParam, lParam));
  505. }
  506.