home *** CD-ROM | disk | FTP | other *** search
/ Computer Shopper 242 / Issue 242 - April 2008 - DPCS0408DVD.ISO / Open Source / AutoHotKey / Source / AutoHotkey104705_source.exe / source / script_gui.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2007-11-20  |  494.5 KB  |  9,314 lines

  1. /*
  2. AutoHotkey
  3.  
  4. Copyright 2003-2007 Chris Mallett (support@autohotkey.com)
  5.  
  6. This program is free software; you can redistribute it and/or
  7. modify it under the terms of the GNU General Public License
  8. as published by the Free Software Foundation; either version 2
  9. of the License, or (at your option) any later version.
  10.  
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15. */
  16.  
  17. #include "stdafx.h" // pre-compiled headers
  18. #include "script.h"
  19. #include "globaldata.h" // for a lot of things
  20. #include "application.h" // for MsgSleep()
  21. #include "window.h" // for SetForegroundWindowEx()
  22. #include "qmath.h" // for qmathLog()
  23.  
  24.  
  25. ResultType Script::PerformGui(char *aCommand, char *aParam2, char *aParam3, char *aParam4)
  26. {
  27.     int window_index = g.GuiDefaultWindowIndex; // Which window to operate upon.  Initialized to thread's default.
  28.     char *options; // This will contain something that is meaningful only when gui_command == GUI_CMD_OPTIONS.
  29.     GuiCommands gui_command = Line::ConvertGuiCommand(aCommand, &window_index, &options);
  30.     if (gui_command == GUI_CMD_INVALID)
  31.         return ScriptError(ERR_PARAM1_INVALID ERR_ABORT, aCommand);
  32.     if (window_index < 0 || window_index >= MAX_GUI_WINDOWS)
  33.         return ScriptError("Max window number is " MAX_GUI_WINDOWS_STR "." ERR_ABORT, aCommand);
  34.  
  35.     // First completely handle any sub-command that doesn't require the window to exist.
  36.     // In other words, don't auto-create the window before doing this command like we do
  37.     // for the others:
  38.     switch(gui_command)
  39.     {
  40.     case GUI_CMD_DESTROY:
  41.         return GuiType::Destroy(window_index);
  42.  
  43.     case GUI_CMD_DEFAULT:
  44.         // Change the "default" member, not g.GuiWindowIndex because that contains the original
  45.         // window number reponsible for launching this thread, which should not be changed because it is
  46.         // used to produce the contents of A_Gui.  Also, it's okay if the specify window index doesn't
  47.         // currently exist.
  48.         g.GuiDefaultWindowIndex = window_index;
  49.         return OK;
  50.     }
  51.  
  52.  
  53.     // If the window doesn't currently exist, don't auto-create it for those commands for
  54.     // which it wouldn't make sense. Note that things like FONT and COLOR are allowed to
  55.     // auto-create the window, since those commands can be legitimately used prior to the
  56.     // first "Gui Add" command.  Also, it seems best to allow SHOW even though all it will
  57.     // do is create and display an empty window.
  58.     if (!g_gui[window_index])
  59.     {
  60.         switch(gui_command)
  61.         {
  62.         case GUI_CMD_SUBMIT:
  63.         case GUI_CMD_CANCEL:
  64.         case GUI_CMD_FLASH:
  65.         case GUI_CMD_MINIMIZE:
  66.         case GUI_CMD_MAXIMIZE:
  67.         case GUI_CMD_RESTORE:
  68.             return OK; // Nothing needs to be done since the window object doesn't exist.
  69.  
  70.         // v1.0.43.09:
  71.         // Don't overload "+LastFound" because it would break existing scripts that rely on the window
  72.         // being created by +LastFound.
  73.         case GUI_CMD_OPTIONS:
  74.             if (!stricmp(options, "+LastFoundExist"))
  75.             {
  76.                 g.hWndLastUsed = NULL;
  77.                 return OK;
  78.             }
  79.             break;
  80.         }
  81.  
  82.         // Otherwise: Create the object and (later) its window, since all the other sub-commands below need it:
  83.         if (   !(g_gui[window_index] = new GuiType(window_index))   )
  84.             return FAIL; // No error displayed since extremely rare.
  85.         if (   !(g_gui[window_index]->mControl = (GuiControlType *)malloc(GUI_CONTROL_BLOCK_SIZE * sizeof(GuiControlType)))   )
  86.         {
  87.             delete g_gui[window_index];
  88.             g_gui[window_index] = NULL;
  89.             return FAIL; // No error displayed since extremely rare.
  90.         }
  91.         g_gui[window_index]->mControlCapacity = GUI_CONTROL_BLOCK_SIZE;
  92.         // Probably better to increment here rather than in constructor in case GuiType objects
  93.         // are ever created outside of the g_gui array (such as for temp local variables):
  94.         ++GuiType::sGuiCount; // This count is maintained to help performance in the main event loop and other places.
  95.     }
  96.  
  97.     GuiType &gui = *g_gui[window_index];  // For performance and convenience.
  98.  
  99.     // Now handle any commands that should be handled prior to creation of the window in the case
  100.     // where the window doesn't already exist:
  101.     bool set_last_found_window = false;
  102.     ToggleValueType own_dialogs = TOGGLE_INVALID;
  103.     if (gui_command == GUI_CMD_OPTIONS)
  104.         if (!gui.ParseOptions(options, set_last_found_window, own_dialogs))
  105.             return FAIL;  // It already displayed the error.
  106.  
  107.     // Create the window if needed.  Since it should not be possible for our window to get destroyed
  108.     // without our knowning about it (via the explicit handling in its window proc), it shouldn't
  109.     // be necessary to check the result of IsWindow(gui.mHwnd):
  110.     if (!gui.mHwnd && !gui.Create())
  111.     {
  112.         GuiType::Destroy(window_index); // Get rid of the object so that it stays in sync with the window's existence.
  113.         return ScriptError("Could not create window." ERR_ABORT);
  114.     }
  115.  
  116.     // After creating the window, return from any commands that were fully handled above:
  117.     if (gui_command == GUI_CMD_OPTIONS)
  118.     {
  119.         if (set_last_found_window)
  120.             g.hWndLastUsed = gui.mHwnd;
  121.         // Fix for v1.0.35.05: Must do the following only if gui_command==GUI_CMD_OPTIONS, otherwise
  122.         // the own_dialogs setting will get reset during other commands such as "Gui Show", "Gui Add"
  123.         if (own_dialogs != TOGGLE_INVALID) // v1.0.35.06: Plus or minus "OwnDialogs" was present rather than being entirely absent.
  124.             g.DialogOwnerIndex = (own_dialogs == TOGGLED_ON) ? window_index : MAX_GUI_WINDOWS; // Reset to out-of-bounds when "-OwnDialogs" is present.
  125.         return OK;
  126.     }
  127.  
  128.     GuiControls gui_control_type = GUI_CONTROL_INVALID;
  129.     int index;
  130.  
  131.     switch (gui_command)
  132.     {
  133.     case GUI_CMD_ADD:
  134.         if (   !(gui_control_type = Line::ConvertGuiControl(aParam2))   )
  135.             return ScriptError(ERR_PARAM2_INVALID ERR_ABORT, aParam2);
  136.         return gui.AddControl(gui_control_type, aParam3, aParam4); // It already displayed any error.
  137.  
  138.     case GUI_CMD_MARGIN:
  139.         if (*aParam2)
  140.             gui.mMarginX = ATOI(aParam2); // Seems okay to allow negative margins.
  141.         if (*aParam3)
  142.             gui.mMarginY = ATOI(aParam3); // Seems okay to allow negative margins.
  143.         return OK;
  144.         
  145.     case GUI_CMD_MENU:
  146.         UserMenu *menu;
  147.         if (*aParam2)
  148.         {
  149.             // By design, the below will give a slightly misleading error if the specified menu is the
  150.             // TRAY menu, since it should be obvious that it cannot be used as a menu bar (since it
  151.             // must always be of the popup type):
  152.             if (   !(menu = FindMenu(aParam2)) || menu == g_script.mTrayMenu   ) // Relies on short-circuit boolean.
  153.                 return ScriptError(ERR_MENU ERR_ABORT, aParam2);
  154.             menu->Create(MENU_TYPE_BAR);  // Ensure the menu physically exists and is the "non-popup" type (for a menu bar).
  155.         }
  156.         else
  157.             menu = NULL;
  158.         SetMenu(gui.mHwnd, menu ? menu->mMenu : NULL);  // Add or remove the menu.
  159.         return OK;
  160.  
  161.     case GUI_CMD_SHOW:
  162.         return gui.Show(aParam2, aParam3);
  163.  
  164.     case GUI_CMD_SUBMIT:
  165.         return gui.Submit(stricmp(aParam2, "NoHide"));
  166.  
  167.     case GUI_CMD_CANCEL:
  168.         return gui.Cancel();
  169.  
  170.     case GUI_CMD_MINIMIZE:
  171.         // If the window is hidden, it is unhidden as a side-effect (this happens even for SW_SHOWMINNOACTIVE).
  172.         ShowWindow(gui.mHwnd, SW_MINIMIZE);
  173.         return OK;
  174.  
  175.     case GUI_CMD_MAXIMIZE:
  176.         ShowWindow(gui.mHwnd, SW_MAXIMIZE); // If the window is hidden, it is unhidden as a side-effect.
  177.         return OK;
  178.  
  179.     case GUI_CMD_RESTORE:
  180.         ShowWindow(gui.mHwnd, SW_RESTORE); // If the window is hidden, it is unhidden as a side-effect.
  181.         return OK;
  182.  
  183.     case GUI_CMD_FONT:
  184.         return gui.SetCurrentFont(aParam2, aParam3);
  185.  
  186.     case GUI_CMD_LISTVIEW:
  187.     case GUI_CMD_TREEVIEW:
  188.         if (*aParam2)
  189.         {
  190.             GuiIndexType control_index = gui.FindControl(aParam2); // Search on either the control's variable name or its ClassNN.
  191.             if (control_index != -1) // Must compare directly to -1 due to unsigned.
  192.             {
  193.                 GuiControlType &control = gui.mControl[control_index]; // For maintainability, and might slightly reduce code size.
  194.                 if (gui_command == GUI_CMD_LISTVIEW)
  195.                 {
  196.                     if (control.type == GUI_CONTROL_LISTVIEW) // v1.0.46.09: Must validate that it's the right type of control; otherwise some LV_* functions can crash due to the control not having malloc'd the special ListView struct that tracks column attributes.
  197.                         gui.mCurrentListView = &control;
  198.                     //else mismatched control type, so just leave it unchanged.
  199.                 }
  200.                 else // GUI_CMD_TREEVIEW
  201.                 {
  202.                     if (control.type == GUI_CONTROL_TREEVIEW)
  203.                         gui.mCurrentTreeView = &control;
  204.                     //else mismatched control type, so just leave it unchanged.
  205.                 }
  206.             }
  207.             //else it seems best never to change ite to be "no control" since it doesn't seem to have much use.
  208.         }
  209.         return OK;
  210.  
  211.     case GUI_CMD_TAB:
  212.     {
  213.         TabIndexType prev_tab_index = gui.mCurrentTabIndex;
  214.         TabControlIndexType prev_tab_control_index = gui.mCurrentTabControlIndex;
  215.         if (!*aParam2 && !*aParam3) // Both the tab control number and the tab number were omitted.
  216.             gui.mCurrentTabControlIndex = MAX_TAB_CONTROLS; // i.e. "no tab"
  217.         else
  218.         {
  219.             if (*aParam3) // Which tab control. Must be processed prior to Param2 since it might change mCurrentTabControlIndex.
  220.             {
  221.                 index = ATOI(aParam3) - 1;
  222.                 if (index < 0 || index > MAX_TAB_CONTROLS - 1)
  223.                     return ScriptError(ERR_PARAM3_INVALID ERR_ABORT, aParam3);
  224.                 if (index != gui.mCurrentTabControlIndex) // This is checked early in case of early return in the next section due to error.
  225.                 {
  226.                     gui.mCurrentTabControlIndex = index;
  227.                     // Fix for v1.0.38.02: Changing to a different tab control (or none at all when there
  228.                     // was one before, or vice versa) should start a new radio group:
  229.                     gui.mInRadioGroup = false;
  230.                 }
  231.             }
  232.             if (*aParam2) // Index or name of a particular tab inside a control.
  233.             {
  234.                 if (!*aParam3 && gui.mCurrentTabControlIndex == MAX_TAB_CONTROLS)
  235.                     // Provide a default: the most recently added tab control.  If there are no
  236.                     // tab controls, assume the index is the first tab control (i.e. a tab control
  237.                     // to be created in the future).  Fix for v1.0.46.16: This section must be done
  238.                     // prior to gui.FindTabControl() below because otherwise, a script that does
  239.                     // "Gui Tab" will find that a later use of "Gui Tab, TabName" won't work unless
  240.                     // the third parameter (which tab control) is explicitly specified.
  241.                     gui.mCurrentTabControlIndex = gui.mTabControlCount ? gui.mTabControlCount - 1 : 0;
  242.                 bool exact_match = !stricmp(aParam4, "Exact"); // v1.0.37.03.
  243.                 // Unlike "GuiControl, Choose", in this case, don't allow negatives since that would just
  244.                 // generate an error msg further below:
  245.                 if (!exact_match && IsPureNumeric(aParam2, false, false))
  246.                 {
  247.                     index = ATOI(aParam2) - 1;
  248.                     if (index < 0 || index > MAX_TABS_PER_CONTROL - 1)
  249.                         return ScriptError(ERR_PARAM2_INVALID ERR_ABORT, aParam2);
  250.                 }
  251.                 else
  252.                 {
  253.                     index = -1;  // Set default to be "failure".
  254.                     GuiControlType *tab_control = gui.FindTabControl(gui.mCurrentTabControlIndex);
  255.                     if (tab_control)
  256.                         index = gui.FindTabIndexByName(*tab_control, aParam2, exact_match); // Returns -1 on failure.
  257.                     if (index == -1)
  258.                         return ScriptError("Tab name doesn't exist yet." ERR_ABORT, aParam2);
  259.                 }
  260.                 gui.mCurrentTabIndex = index;
  261.             }
  262.             if (gui.mCurrentTabIndex != prev_tab_index || gui.mCurrentTabControlIndex != prev_tab_control_index)
  263.                 gui.mInRadioGroup = false; // A fix for v1.0.38.02, see comments at similar line above.
  264.         }
  265.         return OK;
  266.     }
  267.         
  268.     case GUI_CMD_COLOR:
  269.         // AssignColor() takes care of deleting old brush, etc.
  270.         // In this case, a blank for either param means "leaving existing color alone", in which
  271.         // case AssignColor() is not called since it would assume CLR_NONE then.
  272.         if (*aParam2)
  273.             AssignColor(aParam2, gui.mBackgroundColorWin, gui.mBackgroundBrushWin);
  274.         if (*aParam3)
  275.         {
  276.             AssignColor(aParam3, gui.mBackgroundColorCtl, gui.mBackgroundBrushCtl);
  277.             // As documented, the following is not done.  Primary reasons:
  278.             // 1) Allows any custom color that was explicitly specified via "Gui, Add, ListView, BackgroundGreen"
  279.             //    to stay in effect rather than being overridden by this change.  You could argue that this
  280.             //    could be detected by asking the control its background color and if it matches the previous
  281.             //    mBackgroundColorCtl (which might be CLR_DEFAULT?), it's 99% likely it was not an
  282.             //    individual/explicit custom color and thus should be changed here.  But that would be even
  283.             //    more complexity so it seems better to keep it simple.
  284.             // 2) Reduce code size.
  285.             //for (GuiIndexType u = 0; u < gui.mControlCount; ++u)
  286.             //    if (gui.mControl[u].type == GUI_CONTROL_LISTVIEW && ListView_GetTextBkColor(..) != prev_bk_color_ctl)
  287.             //    {
  288.             //        ListView_SetTextBkColor(gui.mControl[u].hwnd, gui.mBackgroundColorCtl);
  289.             //        ListView_SetBkColor(gui.mControl[u].hwnd, gui.mBackgroundColorCtl);
  290.             //    }
  291.             //  ... and probably similar for TREEVIEW.
  292.         }
  293.         if (IsWindowVisible(gui.mHwnd))
  294.             // Force the window to repaint so that colors take effect immediately.
  295.             // UpdateWindow() isn't enough sometimes/always, so do something more aggressive:
  296.             InvalidateRect(gui.mHwnd, NULL, TRUE);
  297.         return OK;
  298.  
  299.     case GUI_CMD_FLASH:
  300.         // Note that FlashWindowEx() would have to be loaded dynamically since it is not available
  301.         // on Win9x/NT.  But for now, just this simple method is provided.  In the future, more
  302.         // sophisticated parameters can be made available to flash the window a given number of times
  303.         // and at a certain frequency, and other options such as only-taskbar-button or only-caption.
  304.         // Set FlashWindowEx() for more ideas:
  305.         FlashWindow(gui.mHwnd, stricmp(aParam2, "Off") ? TRUE : FALSE);
  306.         return OK;
  307.  
  308.     } // switch()
  309.  
  310.     return FAIL;  // Should never be reached, but avoids compiler warning and improves bug detection.
  311. }
  312.  
  313.  
  314.  
  315. ResultType Line::GuiControl(char *aCommand, char *aControlID, char *aParam3)
  316. {
  317.     char *options; // This will contain something that is meaningful only when gui_command == GUICONTROL_CMD_OPTIONS.
  318.     int window_index = g.GuiDefaultWindowIndex; // Which window to operate upon.  Initialized to thread's default.
  319.     GuiControlCmds guicontrol_cmd = Line::ConvertGuiControlCmd(aCommand, &window_index, &options);
  320.     if (guicontrol_cmd == GUICONTROL_CMD_INVALID)
  321.         // This is caught at load-time 99% of the time and can only occur here if the sub-command name
  322.         // is contained in a variable reference.  Since it's so rare, the handling of it is debatable,
  323.         // but to keep it simple just set ErrorLevel:
  324.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  325.     if (window_index < 0 || window_index >= MAX_GUI_WINDOWS || !g_gui[window_index]) // Relies on short-circuit boolean order.
  326.         // This departs from the tradition used by PerformGui() but since this type of error is rare,
  327.         // and since use ErrorLevel adds a little bit of flexibility (since the script's curretn thread
  328.         // is not unconditionally aborted), this seems best:
  329.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  330.  
  331.     GuiType &gui = *g_gui[window_index];  // For performance and convenience.
  332.     GuiIndexType control_index = gui.FindControl(aControlID);
  333.     if (control_index >= gui.mControlCount) // Not found.
  334.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  335.     GuiControlType &control = gui.mControl[control_index];   // For performance and convenience.
  336.  
  337.     // Beyond this point, errors are rare so set the default to "no error":
  338.     g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  339.  
  340.     char *malloc_buf;
  341.     RECT rect;
  342.     WPARAM checked;
  343.     GuiControlType *tab_control;
  344.     int new_pos;
  345.     SYSTEMTIME st[2];
  346.     bool do_redraw_if_in_tab = false;
  347.     bool do_redraw_unconditionally = false;
  348.  
  349.     switch (guicontrol_cmd)
  350.     {
  351.  
  352.     case GUICONTROL_CMD_OPTIONS:
  353.     {
  354.         GuiControlOptionsType go; // Its contents not currently used here, but it might be in the future.
  355.         gui.ControlInitOptions(go, control);
  356.         return gui.ControlParseOptions(options, go, control, control_index);
  357.     }
  358.  
  359.     case GUICONTROL_CMD_CONTENTS:
  360.     case GUICONTROL_CMD_TEXT:
  361.         switch (control.type)
  362.         {
  363.         case GUI_CONTROL_TEXT:
  364.         case GUI_CONTROL_GROUPBOX:
  365.             do_redraw_unconditionally = (control.attrib & GUI_CONTROL_ATTRIB_BACKGROUND_TRANS); // v1.0.40.01.
  366.             // Note that it isn't sufficient in this case to do InvalidateRect(control.hwnd, ...).
  367.             break;
  368.  
  369.         case GUI_CONTROL_PIC:
  370.         {
  371.             // Update: The below doesn't work, so it will be documented that a picture control
  372.             // should be always be referred to by its original filename even if the picture changes.
  373.             // Set the text unconditionally even if the picture can't be loaded.  This text must
  374.             // be set to allow GuiControl(Get) to be able to operate upon the picture without
  375.             // needing to indentify it via something like "Static14".
  376.             //SetWindowText(control.hwnd, aParam3);
  377.             //SendMessage(control.hwnd, WM_SETTEXT, 0, (LPARAM)aParam3);
  378.  
  379.             // Set default options, to be possibly overridden by any options actually present:
  380.             // Fixed for v1.0.23: Below should use GetClientRect() vs. GetWindowRect(), otherwise
  381.             // a size too large will be returned if the control has a border:
  382.             GetClientRect(control.hwnd, &rect);
  383.             int width = rect.right - rect.left;
  384.             int height = rect.bottom - rect.top;
  385.             int icon_number = 0; // Zero means "load icon or bitmap (doesn't matter)".
  386.  
  387.             // The below must be done only after the above, because setting the control's picture handle
  388.             // to NULL sometimes or always shrinks the control down to zero dimensions:
  389.             // Although all HBITMAPs are freed upon program termination, if the program changes
  390.             // the picture frequently, memory/resources would continue to rise in the meantime
  391.             // unless this is done.
  392.             // 1.0.40.12: For maintainability, destroy the handle returned by STM_SETIMAGE, even though it
  393.             // should be identical to control.union_hbitmap (due to a call to STM_GETIMAGE in another section).
  394.             if (control.union_hbitmap)
  395.                 if (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) // union_hbitmap is an icon or cursor.
  396.                     // The control's image is set to NULL for the following reasons:
  397.                     // 1) It turns off the control's animation timer in case the new image is not animated.
  398.                     // 2) It feels a little bit safter to destroy the image only after it has been removed
  399.                     //    from the control.
  400.                     // NOTE: IMAGE_ICON or IMAGE_CURSOR must be passed, not IMAGE_BITMAP.  Otherwise the
  401.                     // animated property of the control (via a timer that the control created) will remain
  402.                     // in effect for the next image, even if it isn't animated, which results in a
  403.                     // flashing/redrawing effect:
  404.                     DestroyIcon((HICON)SendMessage(control.hwnd, STM_SETIMAGE, IMAGE_CURSOR, NULL));
  405.                     // DestroyIcon() works on cursors too.  See notes in LoadPicture().
  406.                 else // union_hbitmap is a bitmap
  407.                     DeleteObject((HGDIOBJ)SendMessage(control.hwnd, STM_SETIMAGE, IMAGE_BITMAP, NULL));
  408.  
  409.             // Parse any options that are present in front of the filename:
  410.             char *next_option = omit_leading_whitespace(aParam3);
  411.             if (*next_option == '*') // Options are present.  Must check this here and in the for-loop to avoid omitting legitimate whitespace in a filename that starts with spaces.
  412.             {
  413.                 char *option_end, orig_char;
  414.                 for (; *next_option == '*'; next_option = omit_leading_whitespace(option_end))
  415.                 {
  416.                     // Find the end of this option item:
  417.                     if (   !(option_end = StrChrAny(next_option, " \t"))   )  // Space or tab.
  418.                         option_end = next_option + strlen(next_option); // Set to position of zero terminator instead.
  419.                     // Permanently terminate in between options to help eliminate ambiguity for words contained
  420.                     // inside other words, and increase confidence in decimal and hexadecimal conversion.
  421.                     orig_char = *option_end;
  422.                     *option_end = '\0';
  423.                     ++next_option; // Skip over the asterisk.  It might point to a zero terminator now.
  424.                     if (!strnicmp(next_option, "Icon", 4))
  425.                         icon_number = ATOI(next_option + 4); // LoadPicture() correctly handles any negative value.
  426.                     else
  427.                     {
  428.                         switch (toupper(*next_option))
  429.                         {
  430.                         case 'W':
  431.                             width = ATOI(next_option + 1);
  432.                             break;
  433.                         case 'H':
  434.                             height = ATOI(next_option + 1);
  435.                             break;
  436.                         // If not one of the above, such as zero terminator or a number, just ignore it.
  437.                         }
  438.                     }
  439.  
  440.                     *option_end = orig_char; // Undo the temporary termination so that loop's omit_leading() will work.
  441.                 } // for() each item in option list
  442.  
  443.                 // The below assigns option_end + 1 vs. next_option in case the filename is contained in a
  444.                 // variable ref and/ that filename contains leading spaces.  Example:
  445.                 // GuiControl,, MyPic, *w100 *h-1 %FilenameWithLeadingSpaces%
  446.                 // Update: Windows XP and perhaps other OSes will load filenames-containing-leading-spaces
  447.                 // even if those spaces are omitted.  However, I'm not sure whether all API calls that
  448.                 // use filenames do this, so it seems best to include those spaces wheneve possible.
  449.                 aParam3 = *option_end ? option_end + 1 : option_end; // Set aParam3 to the start of the image's filespec.
  450.             } 
  451.             //else options are not present, so do not set aParam3 to be next_option because that would
  452.             // omit legitimate spaces and tabs that might exist at the beginning of a real filename (file
  453.             // names can start with spaces).
  454.  
  455.             // See comments in AddControl():
  456.             int image_type;
  457.             if (   !(control.union_hbitmap = LoadPicture(aParam3, width, height, image_type, icon_number
  458.                 , control.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT))   )
  459.                 return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  460.             DWORD style = GetWindowLong(control.hwnd, GWL_STYLE);
  461.             DWORD style_image_type = style & 0x0F;
  462.             style &= ~0x0F;  // Purge the low-order four bits in case style-image-type needs to be altered below.
  463.             if (image_type == IMAGE_BITMAP)
  464.             {
  465.                 if (style_image_type != SS_BITMAP)
  466.                     SetWindowLong(control.hwnd, GWL_STYLE, style | SS_BITMAP);
  467.             }
  468.             else // Icon or Cursor.
  469.                 if (style_image_type != SS_ICON) // Must apply SS_ICON or such handles cannot be displayed.
  470.                     SetWindowLong(control.hwnd, GWL_STYLE, style | SS_ICON);
  471.             // LoadPicture() uses CopyImage() to scale the image, which seems to provide better scaling
  472.             // quality than using MoveWindow() (followed by redrawing the parent window) on the static
  473.             // control that contains the image.
  474.             SendMessage(control.hwnd, STM_SETIMAGE, image_type, (LPARAM)control.union_hbitmap); // Always returns NULL due to previous call to STM_SETIMAGE above.
  475.             // Fix for 1.0.40.12: The below was added because STM_SETIMAGE above may have caused the control to
  476.             // create a new hbitmap (possibly only for alpha channel bitmaps on XP, but might also apply to icons),
  477.             // in which case we now have two handles: the one inside the control and the one from which
  478.             // it was copied.  Task Manager confirms that the control does not delete the original
  479.             // handle when it creates a new handle.  Rather than waiting until later to delete the handle,
  480.             // it seems best to do it here so that:
  481.             // 1) The script uses less memory during the time that the picture control exists.
  482.             // 2) Don't have to delete two handles (control.union_hbitmap and the one returned by STM_SETIMAGE)
  483.             //    when the time comes to change the image inside the control.
  484.             //
  485.             // MSDN: "With Microsoft Windows XP, if the bitmap passed in the STM_SETIMAGE message contains pixels
  486.             // with non-zero alpha, the static control takes a copy of the bitmap. This copied bitmap is returned
  487.             // by the next STM_SETIMAGE message... if it does not check and release the bitmaps returned from
  488.             // STM_SETIMAGE messages, the bitmaps are leaked."
  489.             HBITMAP hbitmap_actual;
  490.             if (   (hbitmap_actual = (HBITMAP)SendMessage(control.hwnd, STM_GETIMAGE, image_type, 0)) // Assign
  491.                 && hbitmap_actual != control.union_hbitmap   )  // The control decided to make a new handle.
  492.             {
  493.                 if (image_type == IMAGE_BITMAP)
  494.                     DeleteObject(control.union_hbitmap);
  495.                 else // Icon or cursor.
  496.                     DestroyIcon((HICON)control.union_hbitmap); // Works on cursors too.
  497.                 // In additional to improving maintainability, the following might also be necessary to allow
  498.                 // Gui::Destroy() to avoid  a memory leak when the picture control is destroyed as a result
  499.                 // of its parent being destroyed (though I've read that the control is supposed to destroy its
  500.                 // hbitmap when it was directly responsible for creating it originally [but not otherwise]):
  501.                 control.union_hbitmap = hbitmap_actual;
  502.             }
  503.             if (image_type == IMAGE_BITMAP)
  504.                 control.attrib &= ~GUI_CONTROL_ATTRIB_ALTBEHAVIOR;  // Flag it as a bitmap so that DeleteObject vs. DestroyIcon will be called for it.
  505.             else // Cursor or Icon, which are functionally identical.
  506.                 control.attrib |= GUI_CONTROL_ATTRIB_ALTBEHAVIOR;
  507.             // Fix for v1.0.33.02: If this control belongs to a tab control and is visible (i.e. its page
  508.             // in the tab control is the current page), must redraw the tab control to get the picture/icon
  509.             // to update correctly.  v1.0.40.01: Pictures such as .Gif sometimes disappear (even if they're
  510.             // not in a tab control):
  511.             //do_redraw_if_in_tab = true;
  512.             do_redraw_unconditionally = true;
  513.             break; // Rather than return, continue on to do the redraw.
  514.         }
  515.  
  516.         case GUI_CONTROL_BUTTON:
  517.             break;
  518.  
  519.         case GUI_CONTROL_CHECKBOX:
  520.         case GUI_CONTROL_RADIO:
  521.             if (guicontrol_cmd == GUICONTROL_CMD_CONTENTS && IsPureNumeric(aParam3, true, false))
  522.             {
  523.                 checked = ATOI(aParam3);
  524.                 if (!checked || checked == 1 || (control.type == GUI_CONTROL_CHECKBOX && checked == -1))
  525.                 {
  526.                     if (checked == -1)
  527.                         checked = BST_INDETERMINATE;
  528.                     //else the "checked" var is already set correctly.
  529.                     if (control.type == GUI_CONTROL_RADIO)
  530.                     {
  531.                         gui.ControlCheckRadioButton(control, control_index, checked);
  532.                         return OK;
  533.                     }
  534.                     // Otherwise, we're operating upon a checkbox.
  535.                     SendMessage(control.hwnd, BM_SETCHECK, checked, 0);
  536.                     return OK;
  537.                 }
  538.                 //else the default SetWindowText() action will be taken below.
  539.             }
  540.             // else assume it's the text/caption for the item, so the default SetWindowText() action will be taken below.
  541.             break; // Fix for v1.0.35.01: Don't return, continue onward.
  542.  
  543.         case GUI_CONTROL_LISTVIEW:
  544.         case GUI_CONTROL_TREEVIEW:
  545.             // Due to the fact that an LV's first col. can't be directly deleted and other complexities,
  546.             // this is not currently supported (also helps reduce code size).  The built-in function
  547.             // for modifying columns should be used instead.  Similar for TreeView.
  548.             return OK;
  549.  
  550.         case GUI_CONTROL_EDIT:
  551.             // Note that TranslateLFtoCRLF() will return the original buffer we gave it if no translation
  552.             // is needed.  Otherwise, it will return a new buffer which we are responsible for freeing
  553.             // when done (or NULL if it failed to allocate the memory).
  554.             malloc_buf = (*aParam3 && (GetWindowLong(control.hwnd, GWL_STYLE) & ES_MULTILINE))
  555.                 ? TranslateLFtoCRLF(aParam3) : aParam3; // Automatic translation, as documented.
  556.             SetWindowText(control.hwnd,  malloc_buf ? malloc_buf : aParam3); // malloc_buf is checked again in case the mem alloc failed.
  557.             if (malloc_buf && malloc_buf != aParam3)
  558.                 free(malloc_buf);
  559.             return OK;
  560.  
  561.         case GUI_CONTROL_DATETIME:
  562.             if (guicontrol_cmd == GUICONTROL_CMD_CONTENTS)
  563.             {
  564.                 if (*aParam3)
  565.                 {
  566.                     if (YYYYMMDDToSystemTime(aParam3, st[0], true))
  567.                         DateTime_SetSystemtime(control.hwnd, GDT_VALID, st);
  568.                     //else invalid, so leave current sel. unchanged.
  569.                 }
  570.                 else // User wants there to be no date selection.
  571.                 {
  572.                     // Ensure the DTS_SHOWNONE style is present, otherwise it won't work.  However,
  573.                     // it appears that this style cannot be applied after the control is created, so
  574.                     // this line is commented out:
  575.                     //SetWindowLong(control.hwnd, GWL_STYLE, GetWindowLong(control.hwnd, GWL_STYLE) | DTS_SHOWNONE);
  576.                     DateTime_SetSystemtime(control.hwnd, GDT_NONE, st);  // Contents of st are ignored in this mode.
  577.                 }
  578.             }
  579.             else // GUICONTROL_CMD_TEXT
  580.             {
  581.                 bool use_custom_format = false; // Set default.
  582.                 // Reset style to "pure" so that new style (or custom format) can take effect.
  583.                 DWORD style = GetWindowLong(control.hwnd, GWL_STYLE) // DTS_SHORTDATEFORMAT==0 so can be omitted below.
  584.                     & ~(DTS_LONGDATEFORMAT | DTS_SHORTDATECENTURYFORMAT | DTS_TIMEFORMAT);
  585.                 if (*aParam3)
  586.                 {
  587.                     // DTS_SHORTDATEFORMAT and DTS_SHORTDATECENTURYFORMAT
  588.                     // seem to produce identical results (both display 4-digit year), at least on XP.  Perhaps
  589.                     // DTS_SHORTDATECENTURYFORMAT is obsolete.  In any case, it's uncommon so for simplicity, is
  590.                     // not a named style.  It can always be applied numerically if desired.  Update:
  591.                     // DTS_SHORTDATECENTURYFORMAT is now applied by default upon creation, which can be overridden
  592.                     // explicitly via -0x0C in the control's options.
  593.                     if (!stricmp(aParam3, "LongDate")) // LongDate seems more readable than "Long".  It also matches the keyword used by FormatTime.
  594.                         style |= DTS_LONGDATEFORMAT; // Competing styles were already purged above.
  595.                     else if (!stricmp(aParam3, "Time"))
  596.                         style |= DTS_TIMEFORMAT; // Competing styles were already purged above.
  597.                     else // Custom format.
  598.                         use_custom_format = true;
  599.                 }
  600.                 //else aText is blank and use_custom_format==false, which will put DTS_SHORTDATEFORMAT into effect.
  601.                 if (!use_custom_format)
  602.                     SetWindowLong(control.hwnd, GWL_STYLE, style);
  603.                 //else leave style unchanged so that if format is later removed, the underlying named style will
  604.                 // not have been altered.
  605.                 // This both adds and removes the custom format depending on aParma3:
  606.                 DateTime_SetFormat(control.hwnd, use_custom_format ? aParam3 : NULL); // NULL removes any custom format so that the underlying style format is revealed.
  607.             }
  608.             return OK;
  609.  
  610.         case GUI_CONTROL_MONTHCAL:
  611.             if (*aParam3)
  612.             {
  613.                 DWORD gdtr = YYYYMMDDToSystemTime2(aParam3, st);
  614.                 if (!gdtr) // Neither min nor max is present (or both are invalid).
  615.                     break; // Leave current sel. unchanged.
  616.                 if (GetWindowLong(control.hwnd, GWL_STYLE) & MCS_MULTISELECT) // Must use range-selection even if selection is only one date.
  617.                 {
  618.                     if (gdtr == GDTR_MIN) // No maximum is present, so set maximum to minimum.
  619.                         st[1] = st[0];
  620.                     //else just max, or both are present.  Assume both for code simplicity.
  621.                     MonthCal_SetSelRange(control.hwnd, st);
  622.                 }
  623.                 else
  624.                     MonthCal_SetCurSel(control.hwnd, st);
  625.                 //else invalid, so leave current sel. unchanged.
  626.                 do_redraw_if_in_tab = true; // Confirmed necessary.
  627.                 break;
  628.             }
  629.             //else blank, so do nothing (control does not support having "no selection").
  630.             return OK; // Don't break since don't the other actions below to be taken.
  631.  
  632.         case GUI_CONTROL_HOTKEY:
  633.             SendMessage(control.hwnd, HKM_SETHOTKEY, gui.TextToHotkey(aParam3), 0); // This will set it to "None" if aParam3 is blank.
  634.             return OK; // Don't break since don't the other actions below to be taken.
  635.         
  636.         case GUI_CONTROL_UPDOWN:
  637.             if (*aParam3 == '+') // Apply as delta from its current position.
  638.             {
  639.                 new_pos = ATOI(aParam3 + 1);
  640.                 // Any out of range or non-numeric value in the buddy is ignored since error reporting is
  641.                 // left up to the script, which can compare contents of buddy to those of UpDown to check
  642.                 // validity if it wants.
  643.                 if (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) // It has a 32-bit vs. 16-bit range.
  644.                     new_pos += (int)SendMessage(control.hwnd, UDM_GETPOS32, 0, 0);
  645.                 else // 16-bit.  Must cast to short to omit the error portion (see comment above).
  646.                     new_pos += (short)SendMessage(control.hwnd, UDM_GETPOS, 0, 0);
  647.                 // Above uses +1 to omit the plus sign, which allows a negative delta via +-5.
  648.                 // -5 is not treated as a delta because that would be ambiguous with an absolute position.
  649.                 // In any case, it seems like too much code to be justified.
  650.             }
  651.             else
  652.                 new_pos = ATOI(aParam3);
  653.             // MSDN: "If the parameter is outside the control's specified range, nPos will be set to the nearest
  654.             // valid value."
  655.             SendMessage(control.hwnd, (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) ? UDM_SETPOS32 : UDM_SETPOS
  656.                 , 0, new_pos); // Unnecessary to cast to short in the case of UDM_SETPOS, since it ignores the high-order word.
  657.             return OK; // Don't break since don't the other actions below to be taken.
  658.  
  659.         case GUI_CONTROL_SLIDER:
  660.             // Confirmed this fact from MSDN: That the control automatically deals with out-of-range values
  661.             // by setting slider to min or max:
  662.             if (*aParam3 == '+') // Apply as delta from its current position.
  663.             {
  664.                 new_pos = ATOI(aParam3 + 1);
  665.                 if (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR)
  666.                     new_pos = -new_pos;  // Delta moves to opposite direction if control is inverted.
  667.                 SendMessage(control.hwnd, TBM_SETPOS, TRUE
  668.                     , SendMessage(control.hwnd, TBM_GETPOS, 0, 0) + new_pos);
  669.                 // Above uses +1 to omit the plus sign, which allows a negative delta via +-5.
  670.                 // -5 is not treated as a delta because that would be ambiguous with an absolute position.
  671.                 // In any case, it seems like too much code to be justified.
  672.             }
  673.             else
  674.                 SendMessage(control.hwnd, TBM_SETPOS, TRUE, gui.ControlInvertSliderIfNeeded(control, ATOI(aParam3)));
  675.                 // Above msg has no return value.
  676.             return OK; // Don't break since don't the other actions below to be taken.
  677.  
  678.         case GUI_CONTROL_PROGRESS:
  679.             // Confirmed through testing (PBM_DELTAPOS was also tested): The control automatically deals
  680.             // with out-of-range values by setting bar to min or max.  
  681.             if (*aParam3 == '+')
  682.                 // This allows a negative delta, e.g. via +-5.  Nothing fancier is done since the need
  683.                 // to go backwards in a progress bar is rare.
  684.                 SendMessage(control.hwnd, PBM_DELTAPOS, ATOI(aParam3 + 1), 0);
  685.             else
  686.                 SendMessage(control.hwnd, PBM_SETPOS, ATOI(aParam3), 0);
  687.             return OK; // Don't break since don't the other actions below to be taken.
  688.  
  689.         case GUI_CONTROL_STATUSBAR:
  690.             SetWindowText(control.hwnd, aParam3);
  691.             return OK;
  692.  
  693.         default: // Namely the following:
  694.         //case GUI_CONTROL_DROPDOWNLIST:
  695.         //case GUI_CONTROL_COMBOBOX:
  696.         //case GUI_CONTROL_LISTBOX:
  697.         //case GUI_CONTROL_TAB:
  698.             if (control.type == GUI_CONTROL_COMBOBOX && guicontrol_cmd == GUICONTROL_CMD_TEXT)
  699.             {
  700.                 // Fix for v1.0.40.08: Must clear the current selection to avoid Submit/GuiControlGet
  701.                 // retrieving it instead of the text that's about to be put into the Edit field.  Note that
  702.                 // whatever changes are done here should tested to work with ComboBox's AltSubmit option also.
  703.                 // After the next text is added to the Edit field, upon GuiControlGet or "Gui Submit", that
  704.                 // text will be checked against the drop-list to see if it matches any of the selections
  705.                 // It's done at that stage rather than here because doing it there also solves the issue
  706.                 // of the user manually entering a selection into the Edit field and then failing to get
  707.                 // the position of the matching item when the ComboBox is set to AltSubmit mode.
  708.                 SendMessage(control.hwnd, CB_SETCURSEL, -1, 0);
  709.                 break; // v1.0.38: Fall through to the SetWindowText() method, which works to set combo's edit field.
  710.             }
  711.             // Seems best not to do the below due to the extreme rarity of anyone wanting to change a
  712.             // ListBox or ComboBox's hidden caption.  That can be done via ControlSetText if it is
  713.             // ever needed.  The advantage of not doing this is that the "TEXT" command can be used
  714.             // as a gentle, slight-variant of GUICONTROL_CMDCONTENTS, i.e. without needing to worry
  715.             // what the target control's type is:
  716.             //if (guicontrol_cmd == GUICONTROL_CMD_TEXT)
  717.             //    break;
  718.             bool list_replaced;
  719.             if (*aParam3 == gui.mDelimiter) // The signal to overwrite rather than append to the list.
  720.             {
  721.                 list_replaced = true;
  722.                 ++aParam3;  // Exclude the initial pipe from further consideration.
  723.                 int msg;
  724.                 switch (control.type)
  725.                 {
  726.                 case GUI_CONTROL_TAB: msg = TCM_DELETEALLITEMS; break; // Same as TabCtrl_DeleteAllItems().
  727.                 case GUI_CONTROL_LISTBOX: msg = LB_RESETCONTENT; break;
  728.                 default: // DropDownList or ComboBox
  729.                     msg = CB_RESETCONTENT;
  730.                 }
  731.                 SendMessage(control.hwnd, msg, 0, 0);  // Delete all items currently in the list.
  732.             }
  733.             else
  734.                 list_replaced = false;
  735.             gui.ControlAddContents(control, aParam3, 0);
  736.             if (control.type == GUI_CONTROL_TAB && list_replaced)
  737.             {
  738.  
  739.                 // In case replacement tabs deleted the currently active tab, update the tab.
  740.                 // The "false" param will cause focus to jump to first item in z-order if
  741.                 // a the control that previously had focus was inside a tab that was just
  742.                 // deleted (seems okay since this kind of operation is fairly rare):
  743.                 gui.ControlUpdateCurrentTab(control, false);
  744.                 // Must invalidate part of parent window to get controls to redraw correctly, at least
  745.                 // in the following case: Tab that is currently active still exists and is still active
  746.                 // after the tab-rebuild done above.
  747.                 // For simplicitly, invalidate the whole thing since changing the quantity/names of tabs
  748.                 // while the window is visible is rare.  NOTE: It might be necessary to invalidate
  749.                 // the entire window *anyway* in case some of this tab's controls exist outside its
  750.                 // boundaries (e.g. TCS_BUTTONS).  Another reason is the fact that there have been
  751.                 // problems retrieving an accurate client area for tab controls when they have certain
  752.                 // styles such as TCS_VERTICAL:
  753.                 InvalidateRect(gui.mHwnd, NULL, TRUE); // TRUE = Seems safer to erase, not knowing all possible overlaps.
  754.             }
  755.             return OK; // Don't break since don't the other actions below to be taken.
  756.         } // inner switch() for control's type for contents/txt sub-commands.
  757.  
  758.         if (do_redraw_if_in_tab) // Excludes the SetWindowText() below, but might need changing for future control types.
  759.             break;
  760.         // Otherwise:
  761.         // The only other reason it wouldn't have already returned is to fall back to SetWindowText() here.
  762.         // Since above didn't return or break, it's either:
  763.         // 1) A control that uses the standard SetWindowText() method such as GUI_CONTROL_TEXT,
  764.         //    GUI_CONTROL_GROUPBOX, or GUI_CONTROL_BUTTON.
  765.         // 2) A radio or checkbox whose caption is being changed instead of its checked state.
  766.         SetWindowText(control.hwnd, aParam3); // Seems more reliable to set text before doing the redraw, plus it saves code size.
  767.         if (do_redraw_unconditionally)
  768.             break;
  769.         return OK;
  770.  
  771.     case GUICONTROL_CMD_MOVE:
  772.     case GUICONTROL_CMD_MOVEDRAW:
  773.     {
  774.         int xpos = COORD_UNSPECIFIED;
  775.         int ypos = COORD_UNSPECIFIED;
  776.         int width = COORD_UNSPECIFIED;
  777.         int height = COORD_UNSPECIFIED;
  778.  
  779.         for (char *cp = aParam3; *cp; ++cp)
  780.         {
  781.             switch(toupper(*cp))
  782.             {
  783.             // For options such as W, H, X and Y:
  784.             // Use atoi() vs. ATOI() to avoid interpreting something like 0x01B as hex when in fact
  785.             // the B was meant to be an option letter (though in this case, none of the hex digits are
  786.             // currently used as option letters):
  787.             case 'W':
  788.                 width = atoi(cp + 1);
  789.                 break;
  790.             case 'H':
  791.                 height = atoi(cp + 1);
  792.                 break;
  793.             case 'X':
  794.                 xpos = atoi(cp + 1);
  795.                 break;
  796.             case 'Y':
  797.                 ypos = atoi(cp + 1);
  798.                 break;
  799.             }
  800.         }
  801.  
  802.         GetWindowRect(control.hwnd, &rect); // Failure seems too rare to check for.
  803.         POINT dest_pt = {rect.left, rect.top};
  804.         ScreenToClient(gui.mHwnd, &dest_pt); // Set default x/y target position, to be possibly overridden below.
  805.         if (xpos != COORD_UNSPECIFIED)
  806.             dest_pt.x = xpos;
  807.         if (ypos != COORD_UNSPECIFIED)
  808.             dest_pt.y = ypos;
  809.  
  810.         if (!MoveWindow(control.hwnd, dest_pt.x, dest_pt.y
  811.             , width == COORD_UNSPECIFIED ? rect.right - rect.left : width
  812.             , height == COORD_UNSPECIFIED ? rect.bottom - rect.top : height
  813.             , TRUE))  // Do repaint.
  814.             return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  815.  
  816.         // Note that GUI_CONTROL_UPDOWN has no special handling here.  This is because unlike slider buddies,
  817.         // whose only purpose is to label the control, an up-down's is also content-linked to it, so the
  818.         // inability to move the up-down to separate it from its buddy would be a loss of flexibility.  For
  819.         // this reason and also to reduce code size, the control is not re-buddied to snap them together.
  820.         if (control.type == GUI_CONTROL_SLIDER) // It seems buddies don't move automatically, so trigger the move.
  821.         {
  822.             HWND buddy1 = (HWND)SendMessage(control.hwnd, TBM_GETBUDDY, TRUE, 0);
  823.             HWND buddy2 = (HWND)SendMessage(control.hwnd, TBM_GETBUDDY, FALSE, 0);
  824.             if (buddy1)
  825.             {
  826.                 SendMessage(control.hwnd, TBM_SETBUDDY, TRUE, (LPARAM)buddy1);
  827.                 // It doesn't always redraw the buddies correctly, at least on XP, so do it manually:
  828.                 InvalidateRect(buddy1, NULL, TRUE);
  829.             }
  830.             if (buddy2)
  831.             {
  832.                 SendMessage(control.hwnd, TBM_SETBUDDY, FALSE, (LPARAM)buddy2);
  833.                 InvalidateRect(buddy2, NULL, TRUE);
  834.             }
  835.         }
  836.  
  837.         // v1.0.41.02: To prevent severe flickering when resizing ListViews and other controls,
  838.         // the MOVE mode now avoids doing the invalidate-rect, but the MOVEDRAW mode does do it.
  839.         if (guicontrol_cmd == GUICONTROL_CMD_MOVEDRAW)
  840.         {
  841.             // This must be done, at least in cases such as GroupBox under certain themes/conditions.
  842.             // More than just control.hwnd must be invalided, otherwise the interior of the GroupBox retains
  843.             // a ghost image of whatever was in it before the move:
  844.             GetWindowRect(control.hwnd, &rect); // Limit it to only that part of the client area that is receiving the rect.
  845.             MapWindowPoints(NULL, gui.mHwnd, (LPPOINT)&rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
  846.             InvalidateRect(gui.mHwnd, &rect, TRUE); // Seems safer to use TRUE, not knowing all possible overlaps, etc.
  847.         }
  848.         return OK;
  849.     }
  850.  
  851.     case GUICONTROL_CMD_FOCUS:
  852.         return SetFocus(control.hwnd) ? OK : g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  853.  
  854.     case GUICONTROL_CMD_ENABLE:
  855.     case GUICONTROL_CMD_DISABLE:
  856.         // GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED is maintained for use with tab controls.  It allows controls
  857.         // on inactive tabs to be marked for later enabling.  It also allows explicitly disabled controls to
  858.         // stay disabled even when their tab/page becomes active. It is updated unconditionally for simplicity
  859.         // and maintainability.  
  860.         if (guicontrol_cmd == GUICONTROL_CMD_ENABLE)
  861.             control.attrib &= ~GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED;
  862.         else
  863.             control.attrib |= GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED;
  864.         if ((tab_control = gui.FindTabControl(control.tab_control_index)) // It belongs to a tab control that already exists.
  865.             && ((GetWindowLong(tab_control->hwnd, GWL_STYLE) & WS_DISABLED) // But its tab control is disabled...
  866.                 || TabCtrl_GetCurSel(tab_control->hwnd) != control.tab_index))
  867.                 // ... or either there is no current tab/page (or there are no tabs at all) or the one selected
  868.                 // is not this control's: Do not disable or re-enable the control in this case.
  869.             return OK;
  870.         // Since above didn't return, act upon the enabled/disable:
  871.         EnableWindow(control.hwnd, guicontrol_cmd == GUICONTROL_CMD_ENABLE ? TRUE : FALSE);
  872.         if (control.type == GUI_CONTROL_TAB) // This control is a tab control.
  873.             // Update the control so that its current tab's controls will all be enabled or disabled (now
  874.             // that the tab control itself has just been enabled or disabled):
  875.             gui.ControlUpdateCurrentTab(control, false);
  876.         return OK;
  877.  
  878.     case GUICONTROL_CMD_SHOW:
  879.     case GUICONTROL_CMD_HIDE:
  880.         // GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN is maintained for use with tab controls.  It allows controls
  881.         // on inactive tabs to be marked for later showing.  It also allows explicitly hidden controls to
  882.         // stay hidden even when their tab/page becomes active. It is updated unconditionally for simplicity
  883.         // and maintainability.  
  884.         if (guicontrol_cmd == GUICONTROL_CMD_SHOW)
  885.             control.attrib &= ~GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN;
  886.         else
  887.             control.attrib |= GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN;
  888.         if ((tab_control = gui.FindTabControl(control.tab_control_index)) // It belongs to a tab control that already exists.
  889.             && (!(GetWindowLong(tab_control->hwnd, GWL_STYLE) & WS_VISIBLE) // But its tab control is hidden...
  890.                 || TabCtrl_GetCurSel(tab_control->hwnd) != control.tab_index))
  891.                 // ... or either there is no current tab/page (or there are no tabs at all) or the one selected
  892.                 // is not this control's: Do not show or hide the control in this case.
  893.             return OK;
  894.         // Since above didn't return, act upon the show/hide:
  895.         ShowWindow(control.hwnd, guicontrol_cmd == GUICONTROL_CMD_SHOW ? SW_SHOWNOACTIVATE : SW_HIDE);
  896.         if (control.type == GUI_CONTROL_TAB) // This control is a tab control.
  897.             // Update the control so that its current tab's controls will all be shown or hidden (now
  898.             // that the tab control itself has just been shown or hidden):
  899.             gui.ControlUpdateCurrentTab(control, false);
  900.         return OK;
  901.  
  902.     case GUICONTROL_CMD_CHOOSE:
  903.     case GUICONTROL_CMD_CHOOSESTRING:
  904.     {
  905.         int selection_index;
  906.         int extra_actions = 0; // Set default.
  907.         if (*aParam3 == gui.mDelimiter) // First extra action.
  908.         {
  909.             ++aParam3; // Omit this pipe char from further consideration below.
  910.             ++extra_actions;
  911.         }
  912.         if (control.type == GUI_CONTROL_TAB)
  913.         {
  914.             // Generating the TCN_SELCHANGING and TCN_SELCHANGE messages manually is fairly complex since they
  915.             // need a struct and who knows whether it's even valid for sources other than the tab controls
  916.             // themselves to generate them.  I would use TabCtrl_SetCurFocus(), but that is shot down by
  917.             // the fact that it only generates TCN_SELCHANGING and TCN_SELCHANGE if the tab control lacks
  918.             // the TCS_BUTTONS style, which would make it an incomplete/inconsistent solution.  But I guess
  919.             // it's better than nothing as long as it's documented.
  920.             // MSDN: "If the tab control does not have the TCS_BUTTONS style, changing the focus also changes
  921.             // selected tab. In this case, the tab control sends the TCN_SELCHANGING and TCN_SELCHANGE
  922.             // notification messages to its parent window. 
  923.             // Automatically switch to CHOOSESTRING if parameter isn't numeric:
  924.             if (guicontrol_cmd == GUICONTROL_CMD_CHOOSE && !IsPureNumeric(aParam3, true, false))
  925.                 guicontrol_cmd = GUICONTROL_CMD_CHOOSESTRING;
  926.             if (guicontrol_cmd == GUICONTROL_CMD_CHOOSESTRING)
  927.                 selection_index = gui.FindTabIndexByName(control, aParam3); // Returns -1 on failure.
  928.             else
  929.                 selection_index = ATOI(aParam3) - 1;
  930.             if (selection_index < 0 || selection_index > MAX_TABS_PER_CONTROL - 1)
  931.                 return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  932.             int previous_selection_index = TabCtrl_GetCurSel(control.hwnd);
  933.             if (!extra_actions || (GetWindowLong(control.hwnd, GWL_STYLE) & TCS_BUTTONS))
  934.             {
  935.                 if (TabCtrl_SetCurSel(control.hwnd, selection_index) == -1)
  936.                     return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  937.                 // In this case but not the "else" below, must update the tab to show the proper controls:
  938.                 if (previous_selection_index != selection_index)
  939.                     gui.ControlUpdateCurrentTab(control, extra_actions > 0); // And set focus if the more forceful extra_actions was done.
  940.             }
  941.             else // There is an extra_action and it's not TCS_BUTTONS, so extra_action is possible via TabCtrl_SetCurFocus.
  942.             {
  943.                 TabCtrl_SetCurFocus(control.hwnd, selection_index); // No return value, so check for success below.
  944.                 if (TabCtrl_GetCurSel(control.hwnd) != selection_index)
  945.                     return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  946.             }
  947.             return OK;
  948.         }
  949.         // Otherwise, it's not a tab control, but a ListBox/DropDownList/Combo or other control:
  950.         if (*aParam3 == gui.mDelimiter && control.type != GUI_CONTROL_TAB) // Second extra action.
  951.         {
  952.             ++aParam3; // Omit this pipe char from further consideration below.
  953.             ++extra_actions;
  954.         }
  955.         if (guicontrol_cmd == GUICONTROL_CMD_CHOOSE && !IsPureNumeric(aParam3, true, false)) // Must be done only after the above.
  956.             guicontrol_cmd = GUICONTROL_CMD_CHOOSESTRING;
  957.         UINT msg, x_msg, y_msg;
  958.         switch(control.type)
  959.         {
  960.         case GUI_CONTROL_DROPDOWNLIST:
  961.         case GUI_CONTROL_COMBOBOX:
  962.             msg = (guicontrol_cmd == GUICONTROL_CMD_CHOOSE) ? CB_SETCURSEL : CB_SELECTSTRING;
  963.             x_msg = CBN_SELCHANGE;
  964.             y_msg = CBN_SELENDOK;
  965.             break;
  966.         case GUI_CONTROL_LISTBOX:
  967.             if (GetWindowLong(control.hwnd, GWL_STYLE) & (LBS_EXTENDEDSEL|LBS_MULTIPLESEL))
  968.             {
  969.                 if (guicontrol_cmd == GUICONTROL_CMD_CHOOSE)
  970.                     msg = LB_SETSEL;
  971.                 else
  972.                     // MSDN: Do not use [LB_SELECTSTRING] with a list box that has the LBS_MULTIPLESEL or the
  973.                     // LBS_EXTENDEDSEL styles:
  974.                     msg = LB_FINDSTRING;
  975.             }
  976.             else // single-select listbox
  977.                 if (guicontrol_cmd == GUICONTROL_CMD_CHOOSE)
  978.                     msg = LB_SETCURSEL;
  979.                 else
  980.                     msg = LB_SELECTSTRING;
  981.             x_msg = LBN_SELCHANGE;
  982.             y_msg = LBN_DBLCLK;
  983.             break;
  984.         default:  // Not a supported control type.
  985.             return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  986.         } // switch(control.type)
  987.  
  988.         if (guicontrol_cmd == GUICONTROL_CMD_CHOOSESTRING)
  989.         {
  990.             if (msg == LB_FINDSTRING)
  991.             {
  992.                 // This msg is needed for multi-select listbox because LB_SELECTSTRING is not supported
  993.                 // in this case.
  994.                 LRESULT found_item = SendMessage(control.hwnd, msg, -1, (LPARAM)aParam3);
  995.                 if (found_item == CB_ERR) // CB_ERR == LB_ERR
  996.                     return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  997.                 if (SendMessage(control.hwnd, LB_SETSEL, TRUE, found_item) == CB_ERR) // CB_ERR == LB_ERR
  998.                     return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  999.             }
  1000.             else // Fixed 1 to be -1 in v1.0.35.05:
  1001.                 if (SendMessage(control.hwnd, msg, -1, (LPARAM)aParam3) == CB_ERR) // CB_ERR == LB_ERR
  1002.                     return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1003.         }
  1004.         else // Choose by position vs. string.
  1005.         {
  1006.             selection_index = ATOI(aParam3) - 1;
  1007.             if (selection_index < 0)
  1008.                 return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1009.             if (msg == LB_SETSEL) // Multi-select, so use the cumulative method.
  1010.             {
  1011.                 if (SendMessage(control.hwnd, msg, TRUE, selection_index) == CB_ERR) // CB_ERR == LB_ERR
  1012.                     return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1013.             }
  1014.             else
  1015.                 if (SendMessage(control.hwnd, msg, selection_index, 0) == CB_ERR) // CB_ERR == LB_ERR
  1016.                     return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1017.         }
  1018.         int control_id = GUI_INDEX_TO_ID(control_index);
  1019.         if (extra_actions > 0)
  1020.             SendMessage(gui.mHwnd, WM_COMMAND, (WPARAM)MAKELONG(control_id, x_msg), (LPARAM)control.hwnd);
  1021.         if (extra_actions > 1)
  1022.             SendMessage(gui.mHwnd, WM_COMMAND, (WPARAM)MAKELONG(control_id, y_msg), (LPARAM)control.hwnd);
  1023.         return OK;
  1024.     } // case
  1025.  
  1026.     case GUICONTROL_CMD_FONT:
  1027.         // Done in regardless of USES_FONT_AND_TEXT_COLOR to allow future OSes or common control updates
  1028.         // to be given an explicit font, even though it would have no effect currently:
  1029.         SendMessage(control.hwnd, WM_SETFONT, (WPARAM)gui.sFont[gui.mCurrentFontIndex].hfont, 0);
  1030.         if (USES_FONT_AND_TEXT_COLOR(control.type)) // Must check this to avoid corrupting union_hbitmap.
  1031.         {
  1032.             if (control.type != GUI_CONTROL_LISTVIEW) // Must check this to avoid corrupting union col attribs.
  1033.                 control.union_color = gui.mCurrentColor; // Used by WM_CTLCOLORSTATIC et. al. for some types of controls.
  1034.             switch (control.type)
  1035.             {
  1036.             case GUI_CONTROL_LISTVIEW:
  1037.                 ListView_SetTextColor(control.hwnd, gui.mCurrentColor); // Must use gui.mCurrentColor not control.union_color, see above.
  1038.                 break;
  1039.             case GUI_CONTROL_TREEVIEW:
  1040.                 TreeView_SetTextColor(control.hwnd, gui.mCurrentColor);
  1041.                 break;
  1042.             case GUI_CONTROL_DATETIME:
  1043.                 // Since message MCM_SETCOLOR != DTM_SETMCCOLOR, can't combine the two types:
  1044.                 DateTime_SetMonthCalColor(control.hwnd, MCSC_TEXT, gui.mCurrentColor); // Hopefully below will revert to default if color is CLR_DEFAULT.
  1045.                 break;
  1046.             case GUI_CONTROL_MONTHCAL:
  1047.                 MonthCal_SetColor(control.hwnd, MCSC_TEXT, gui.mCurrentColor); // Hopefully below will revert to default if color is CLR_DEFAULT.
  1048.                 break;
  1049.             }
  1050.         }
  1051.         InvalidateRect(control.hwnd, NULL, TRUE); // Required for refresh, at least for edit controls, probably some others.
  1052.         // Note: The DateTime_SetMonthCalFont() macro is not used for GUI_CONTROL_DATETIME because
  1053.         // WM_SETFONT+InvalidateRect() above appear to be sufficient for it too.
  1054.         return OK;
  1055.     } // switch()
  1056.  
  1057.     // If the above didn't return, it wants this check:
  1058.     if (   do_redraw_unconditionally
  1059.         || (tab_control = gui.FindTabControl(control.tab_control_index)) && IsWindowVisible(control.hwnd)   )
  1060.     {
  1061.         GetWindowRect(control.hwnd, &rect); // Limit it to only that part of the client area that is receiving the rect.
  1062.         MapWindowPoints(NULL, gui.mHwnd, (LPPOINT)&rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
  1063.         InvalidateRect(gui.mHwnd, &rect, TRUE); // Seems safer to use TRUE, not knowing all possible overlaps, etc.
  1064.         //Overkill: InvalidateRect(gui.mHwnd, NULL, FALSE); // Erase doesn't seem to be necessary.
  1065.         // None of the following is enough:
  1066.         //Changes focused control, so no good: gui.ControlUpdateCurrentTab(*tab_control, false);
  1067.         //RedrawWindow(tab_control->hwnd, NULL, NULL, 0 ..or.. RDW_INVALIDATE);
  1068.         //InvalidateRect(control.hwnd, NULL, TRUE);
  1069.         //InvalidateRect(tab_control->hwnd, NULL, TRUE);
  1070.     }
  1071.     return OK;
  1072. }
  1073.  
  1074.  
  1075.  
  1076. ResultType Line::GuiControlGet(char *aCommand, char *aControlID, char *aParam3)
  1077. {
  1078.     Var &output_var = *OUTPUT_VAR;
  1079.     int window_index = g.GuiDefaultWindowIndex; // Which window to operate upon.  Initialized to thread's default.
  1080.     GuiControlGetCmds guicontrolget_cmd = Line::ConvertGuiControlGetCmd(aCommand, &window_index);
  1081.     if (guicontrolget_cmd == GUICONTROLGET_CMD_INVALID)
  1082.     {
  1083.         // This is caught at load-time 99% of the time and can only occur here if the sub-command name
  1084.         // is contained in a variable reference.  Since it's so rare, the handling of it is debatable,
  1085.         // but to keep it simple just set ErrorLevel:
  1086.         output_var.Assign(); // For backward-compatibility and also serves as an additional indicator of failure.
  1087.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1088.     }
  1089.     else if (guicontrolget_cmd != GUICONTROLGET_CMD_POS) // v1.0.46.09: Avoid resetting the variable for the POS mode, since it uses and array and the user might want the existing contents of the GUI variable retained.
  1090.         output_var.Assign(); // Set default to be blank for all commands except POS, for consistency.
  1091.     if (window_index < 0 || window_index >= MAX_GUI_WINDOWS || !g_gui[window_index]) // Relies on short-circuit boolean order.
  1092.         // This departs from the tradition used by PerformGui() but since this type of error is rare,
  1093.         // and since use ErrorLevel adds a little bit of flexibility (since the script's curretn thread
  1094.         // is not unconditionally aborted), this seems best:
  1095.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1096.  
  1097.     GuiType &gui = *g_gui[window_index];  // For performance and convenience.
  1098.     if (!*aControlID) // In this case, default to the name of the output variable, as documented.
  1099.         aControlID = output_var.mName;
  1100.  
  1101.     // Beyond this point, errors are rare so set the default to "no error":
  1102.     g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  1103.  
  1104.     // Handle GUICONTROLGET_CMD_FOCUS(V) early since it doesn't need a specified ControlID:
  1105.     if (guicontrolget_cmd == GUICONTROLGET_CMD_FOCUS || guicontrolget_cmd == GUICONTROLGET_CMD_FOCUSV)
  1106.     {
  1107.         class_and_hwnd_type cah;
  1108.         cah.hwnd = GetFocus();
  1109.         GuiControlType *pcontrol;
  1110.         if (!cah.hwnd || !(pcontrol = gui.FindControl(cah.hwnd))) // Relies on short-circuit boolean order.
  1111.             return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1112.         char focused_control[WINDOW_CLASS_SIZE];
  1113.         if (guicontrolget_cmd == GUICONTROLGET_CMD_FOCUSV) // v1.0.43.06.
  1114.             // GUI_HWND_TO_INDEX vs FindControl() is enough because FindControl() was alraedy called above:
  1115.             GuiType::ControlGetName(window_index, GUI_HWND_TO_INDEX(pcontrol->hwnd), focused_control);
  1116.         else // GUICONTROLGET_CMD_FOCUS (ClassNN mode)
  1117.         {
  1118.             // This section is the same as that in ControlGetFocus():
  1119.             cah.class_name = focused_control;
  1120.             if (!GetClassName(cah.hwnd, focused_control, sizeof(focused_control) - 5)) // -5 to allow room for sequence number.
  1121.                 return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1122.             cah.class_count = 0;  // Init for the below.
  1123.             cah.is_found = false; // Same.
  1124.             EnumChildWindows(gui.mHwnd, EnumChildFindSeqNum, (LPARAM)&cah);
  1125.             if (!cah.is_found) // Should be impossible due to FindControl() having already found it above.
  1126.                 return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1127.             // Append the class sequence number onto the class name set the output param to be that value:
  1128.             snprintfcat(focused_control, sizeof(focused_control), "%d", cah.class_count);
  1129.         }
  1130.         return output_var.Assign(focused_control); // And leave ErrorLevel set to NONE.
  1131.     }
  1132.  
  1133.     GuiIndexType control_index = gui.FindControl(aControlID);
  1134.     if (control_index >= gui.mControlCount) // Not found.
  1135.         return g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  1136.     GuiControlType &control = gui.mControl[control_index];   // For performance and convenience.
  1137.  
  1138.     switch(guicontrolget_cmd)
  1139.     {
  1140.     case GUICONTROLGET_CMD_CONTENTS:
  1141.         // Because the below returns FAIL only if a critical error occurred, g_ErrorLevel is
  1142.         // left at NONE as set above for all cases.
  1143.         return gui.ControlGetContents(output_var, control, aParam3);
  1144.  
  1145.     case GUICONTROLGET_CMD_POS:
  1146.     {
  1147.         // In this case, output_var itself is not used directly, but is instead used to:
  1148.         // 1) Help performance by giving us the location in the linked list of variables of
  1149.         //    where to find the X/Y/W/H "array elements".
  1150.         // 2) Simplify the code by avoiding the need to classify GuiControlGet's param #1
  1151.         //    as something that is only sometimes a variable.
  1152.         RECT rect;
  1153.         GetWindowRect(control.hwnd, &rect);
  1154.         POINT pt = {rect.left, rect.top};
  1155.         ScreenToClient(gui.mHwnd, &pt);  // Failure seems too rare to check for.
  1156.         // Make it longer than Max var name so that FindOrAddVar() will be able to spot and report
  1157.         // var names that are too long:
  1158.         char var_name[MAX_VAR_NAME_LENGTH + 20];
  1159.         Var *var;
  1160.         int always_use = output_var.IsLocal() ? ALWAYS_USE_LOCAL : ALWAYS_USE_GLOBAL;
  1161.         if (   !(var = g_script.FindOrAddVar(var_name
  1162.             , snprintf(var_name, sizeof(var_name), "%sX", output_var.mName)
  1163.             , always_use))   ) // Called with output_var to enhance performance.
  1164.             return FAIL;  // It will have already displayed the error.
  1165.         var->Assign(pt.x);
  1166.         if (   !(var = g_script.FindOrAddVar(var_name
  1167.             , snprintf(var_name, sizeof(var_name), "%sY", output_var.mName)
  1168.             , always_use))   ) // Called with output_var to enhance performance.
  1169.             return FAIL;  // It will have already displayed the error.
  1170.         var->Assign(pt.y);
  1171.         if (   !(var = g_script.FindOrAddVar(var_name
  1172.             , snprintf(var_name, sizeof(var_name), "%sW", output_var.mName)
  1173.             , always_use))   ) // Called with output_var to enhance performance.
  1174.             return FAIL;  // It will have already displayed the error.
  1175.         var->Assign(rect.right - rect.left);
  1176.         if (   !(var = g_script.FindOrAddVar(var_name
  1177.             , snprintf(var_name, sizeof(var_name), "%sH", output_var.mName)
  1178.             , always_use))   ) // Called with output_var to enhance performance.
  1179.             return FAIL;  // It will have already displayed the error.
  1180.         return var->Assign(rect.bottom - rect.top);
  1181.     }
  1182.  
  1183.     case GUICONTROLGET_CMD_ENABLED:
  1184.         // See commment below.
  1185.         return output_var.Assign(IsWindowEnabled(control.hwnd) ? "1" : "0");
  1186.  
  1187.     case GUICONTROLGET_CMD_VISIBLE:
  1188.         // From working on Window Spy, I seem to remember that IsWindowVisible() uses different standards
  1189.         // for determining visibility than simply checking for WS_VISIBLE is the control and its parent
  1190.         // window.  If so, it might be undocumented in MSDN.  It is mentioned here to explain why
  1191.         // this "visible" sub-cmd is kept separate from some figure command such as "GuiControlGet, Out, Style":
  1192.         // 1) The style method is cumbersome to script with since it requires bitwise operates afterward.
  1193.         // 2) IsVisible() uses a different standard of detection than simply checking WS_VISIBLE.
  1194.         return output_var.Assign(IsWindowVisible(control.hwnd) ? "1" : "0");
  1195.  
  1196.     case GUICONTROLGET_CMD_HWND: // v1.0.46.16: Although it overlaps with HwndOutputVar, Majkinetor wanted this to help with encapsulation/modularization.
  1197.         return output_var.AssignHWND(control.hwnd); // See also: CONTROLGET_CMD_HWND
  1198.     } // switch()
  1199.  
  1200.     return FAIL;  // Should never be reached, but avoids compiler warning and improves bug detection.
  1201. }
  1202.  
  1203.  
  1204.  
  1205. /////////////////
  1206. // Static members
  1207. /////////////////
  1208. FontType *GuiType::sFont = NULL; // An array of structs, allocated upon first use.
  1209. int GuiType::sFontCount = 0;
  1210. int GuiType::sGuiCount = 0;
  1211. HWND GuiType::sTreeWithEditInProgress = NULL;
  1212.  
  1213.  
  1214.  
  1215. ResultType GuiType::Destroy(GuiIndexType aWindowIndex)
  1216. // Rather than deal with the confusion of an object destroying itself, this method is static
  1217. // and designed to deal with one particular window index in the g_gui array.
  1218. {
  1219.     if (aWindowIndex >= MAX_GUI_WINDOWS)
  1220.         return FAIL;
  1221.     if (!g_gui[aWindowIndex]) // It's already in the right state.
  1222.         return OK;
  1223.     GuiType &gui = *g_gui[aWindowIndex];  // For performance and convenience.
  1224.     GuiIndexType u, gui_count;
  1225.  
  1226.     if (gui.mHwnd)
  1227.     {
  1228.         // First destroy any windows owned by this window, since they will be auto-destroyed
  1229.         // anyway due to their being owned.  By destroying them explicitly, the Destroy()
  1230.         // function is called recursively which keeps everything relatively neat.
  1231.         for (u = 0, gui_count = 0; u < MAX_GUI_WINDOWS; ++u)
  1232.         {
  1233.             if (g_gui[u])
  1234.             {
  1235.                 if (g_gui[u]->mOwner == gui.mHwnd)
  1236.                     GuiType::Destroy(u);
  1237.                 if (sGuiCount == ++gui_count) // No need to keep searching.
  1238.                     break;
  1239.             }
  1240.         }
  1241.         // Testing shows that this must be done prior to calling DestroyWindow() later below, presumably
  1242.         // because the destruction immediately destroys the status bar, or prevents it from answering messages.
  1243.         // This seems at odds with MSDN's comment: "During the processing of [WM_DESTROY], it can be assumed
  1244.         // that all child windows still exist".
  1245.         if (gui.mStatusBarHwnd) // IsWindow(gui.mStatusBarHwnd) isn't called because even if possible for it to have been destroyed, SendMessage below should return 0.
  1246.         {
  1247.             // This is done because the vast majority of people wouldn't want to have to worry about it.
  1248.             // They can always use DllCall() if they want to share the same HICON among multiple parts of
  1249.             // the same bar, or among different windows (fairly rare).
  1250.             HICON hicon;
  1251.             LRESULT part_count = SendMessage(gui.mStatusBarHwnd, SB_GETPARTS, 0, NULL); // MSDN: "This message always returns the number of parts in the status bar [regardless of how it is called]".
  1252.             for (LRESULT i = 0; i < part_count; ++i)
  1253.                 if (hicon = (HICON)SendMessage(gui.mStatusBarHwnd, SB_GETICON, i, 0))
  1254.                     DestroyIcon(hicon);
  1255.         }
  1256.         if (IsWindow(gui.mHwnd)) // If WM_DESTROY called us, the window might already be partially destroyed.
  1257.         {
  1258.             // If this window is using a menu bar but that menu is also used by some other window, first
  1259.             // detatch the menu so that it doesn't get auto-destroyed with the window.  This is done
  1260.             // unconditionally since such a menu will be automatically destroyed when the script exits
  1261.             // or when the menu is destroyed explicitly via the Menu command.  It also prevents any
  1262.             // submenus attached to the menu bar from being destroyed, since those submenus might be
  1263.             // also used by other menus (however, this is not really an issue since menus destroyed
  1264.             // would be automatically re-created upon next use).  But in the case of a window that
  1265.             // is currently using a menu bar, destroying that bar in conjunction with the destruction
  1266.             // of some other window might cause bad side effects on some/all OSes.
  1267.             ShowWindow(gui.mHwnd, SW_HIDE);  // Hide it to prevent re-drawing due to menu removal.
  1268.             SetMenu(gui.mHwnd, NULL);
  1269.             if (!gui.mDestroyWindowHasBeenCalled)
  1270.             {
  1271.                 gui.mDestroyWindowHasBeenCalled = true;  // Signal the WM_DESTROY routine not to call us.
  1272.                 DestroyWindow(gui.mHwnd);  // The WindowProc is immediately called and it now destroys the window.
  1273.             }
  1274.             // else WM_DESTROY was called by a function other than this one (possibly auto-destruct due to
  1275.             // being owned by script's main window), so it would be bad to call DestroyWindow() again since
  1276.             // it's already in progress.
  1277.         }
  1278.     } // if (gui.mHwnd)
  1279.  
  1280.     if (gui.mBackgroundBrushWin)
  1281.         DeleteObject(gui.mBackgroundBrushWin);
  1282.     if (gui.mBackgroundBrushCtl)
  1283.         DeleteObject(gui.mBackgroundBrushCtl);
  1284.     if (gui.mHdrop)
  1285.         DragFinish(gui.mHdrop);
  1286.  
  1287.     // It seems best to delete the bitmaps whenever the control changes to a new image or
  1288.     // whenever the control is destroyed.  Otherwise, if a control or its parent window is
  1289.     // destroyed and recreated many times, memory allocation would continue to grow from
  1290.     // all the abandoned pointers:
  1291.     for (u = 0; u < gui.mControlCount; ++u)
  1292.     {
  1293.         GuiControlType &control = gui.mControl[u];
  1294.         if (control.type == GUI_CONTROL_PIC && control.union_hbitmap)
  1295.         {
  1296.             if (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR)
  1297.                 DestroyIcon((HICON)control.union_hbitmap); // Works on cursors too.  See notes in LoadPicture().
  1298.             else // union_hbitmap is a bitmap rather than an icon or cursor.
  1299.                 DeleteObject(control.union_hbitmap);
  1300.             //else do nothing, since it isn't the right type to have a valid union_hbitmap member.
  1301.         }
  1302.         else if (control.type == GUI_CONTROL_LISTVIEW) // It was ensured at an earlier stage that union_lv_attrib != NULL.
  1303.             free(control.union_lv_attrib);
  1304.     }
  1305.     // Not necessary since the object itself is about to be destroyed:
  1306.     //gui.mHwnd = NULL;
  1307.     //gui.mControlCount = 0; // All child windows (controls) are automatically destroyed with parent.
  1308.     HICON icon_eligible_for_destruction = gui.mIconEligibleForDestruction;
  1309.     free(gui.mControl); // Free the control array, which was previously malloc'd.
  1310.     delete g_gui[aWindowIndex]; // After this, the var "gui" is invalid so should not be referenced, i.e. the next line.
  1311.     g_gui[aWindowIndex] = NULL;
  1312.     --sGuiCount; // This count is maintained to help performance in the main event loop and other places.
  1313.     if (icon_eligible_for_destruction && icon_eligible_for_destruction != g_script.mCustomIcon) // v1.0.37.07.
  1314.         DestroyIconIfUnused(icon_eligible_for_destruction); // Must be done only after "g_gui[aWindowIndex] = NULL".
  1315.     // For simplicity and performance, any fonts used *solely* by a destroyed window are destroyed
  1316.     // only when the program terminates.  Another reason for this is that sometimes a destroyed window
  1317.     // is soon recreated to use the same fonts it did before.
  1318.     return OK;
  1319. }
  1320.  
  1321.  
  1322.  
  1323. void GuiType::DestroyIconIfUnused(HICON ahIcon)
  1324. // Caller has ensured that the GUI window previously using ahIcon has been destroyed prior to calling
  1325. // this function.
  1326. {
  1327.     if (!ahIcon) // Caller relies on this check.
  1328.         return;
  1329.     int i, gui_count;
  1330.     for (i = 0, gui_count = 0; i < MAX_GUI_WINDOWS && gui_count < sGuiCount; ++i)
  1331.         if (g_gui[i]) // This GUI window exists as an object.
  1332.         {
  1333.             // If another window is using this icon, don't destroy the because that has been reported to disrupt
  1334.             // the window's display of the icon in some cases (apparently WM_SETICON doesn't make a copy of the
  1335.             // icon).  The windows still using the icon will be responsible for destroying it later.
  1336.             if (g_gui[i]->mIconEligibleForDestruction == ahIcon)
  1337.                 return;
  1338.             ++gui_count;
  1339.         }
  1340.     // Since above didn't return, this icon is not currently in use by a GUI window.  The caller has
  1341.     // authorized us to destroy it.
  1342.     DestroyIcon(ahIcon);
  1343. }
  1344.  
  1345.  
  1346.  
  1347. ResultType GuiType::Create()
  1348. {
  1349.     if (mHwnd) // It already exists
  1350.         return FAIL;  // Seems best for now, since it shouldn't really be called this way.
  1351.  
  1352.     // Use a separate class for GUI, which gives it a separate WindowProc and allows it to be more
  1353.     // distinct when used with the ahk_class method of addressing windows.
  1354.     static bool sGuiInitialized = false;
  1355.     if (!sGuiInitialized)
  1356.     {
  1357.         WNDCLASSEX wc = {0};
  1358.         wc.cbSize = sizeof(wc);
  1359.         wc.lpszClassName = WINDOW_CLASS_GUI;
  1360.         wc.hInstance = g_hInstance;
  1361.         wc.lpfnWndProc = GuiWindowProc;
  1362.         wc.hIcon = wc.hIconSm = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_MAIN), IMAGE_ICON, 0, 0, LR_SHARED); // Use LR_SHARED to conserve memory (since the main icon is loaded for so many purposes).
  1363.         wc.style = CS_DBLCLKS; // v1.0.44.12: CS_DBLCLKS is accepted as a good default by nearly everyone.  It causes the window to receive WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, and WM_MBUTTONDBLCLK (even without this, all windows receive WM_NCLBUTTONDBLCLK, WM_NCMBUTTONDBLCLK, and WM_NCRBUTTONDBLCLK).
  1364.             // CS_HREDRAW and CS_VREDRAW are not included above because they cause extra flickering.  It's generally better for a window to manage its own redrawing when it's resized.
  1365.         wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
  1366.         wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
  1367.         wc.cbWndExtra = DLGWINDOWEXTRA;  // So that it will be the type that uses DefDlgProc() vs. DefWindowProc().
  1368.         if (!RegisterClassEx(&wc))
  1369.         {
  1370.             MsgBox("RegClass"); // Short/generic msg since so rare.
  1371.             return FAIL;
  1372.         }
  1373.         sGuiInitialized = true;
  1374.     }
  1375.  
  1376.     if (!mLabelsHaveBeenSet) // i.e. don't set the defaults if the labels were set prior to the creation of the window.
  1377.         SetLabels(NULL);
  1378.     // The above is done prior to creating the window so that mLabelForDropFiles can determine
  1379.     // whether to add the WS_EX_ACCEPTFILES style.
  1380.  
  1381.     // WS_EX_APPWINDOW: "Forces a top-level window onto the taskbar when the window is minimized."
  1382.     // But it doesn't since the window is currently always unowned, there is not yet any need to use it.
  1383.     if (   !(mHwnd = CreateWindowEx(mExStyle, WINDOW_CLASS_GUI, g_script.mFileName, mStyle, 0, 0, 0, 0
  1384.         , mOwner, NULL, g_hInstance, NULL))   )
  1385.         return FAIL;
  1386.  
  1387.     HICON main_icon;
  1388.     if (g_script.mCustomIcon)
  1389.     {
  1390.         main_icon = g_script.mCustomIcon;
  1391.         mIconEligibleForDestruction = main_icon;
  1392.     }
  1393.     else
  1394.         main_icon = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_MAIN), IMAGE_ICON, 0, 0, LR_SHARED); // Use LR_SHARED to conserve memory (since the main icon is loaded for so many purposes).
  1395.         // Unlike mCustomIcon, leave mIconEligibleForDestruction NULL because a shared HICON such as one
  1396.         // loaded via LR_SHARED should never be destroyed.
  1397.     // Setting the small icon puts it in the upper left corner of the dialog window.
  1398.     // Setting the big icon makes the dialog show up correctly in the Alt-Tab menu (but big seems to
  1399.     // have no effect unless the window is unowned, i.e. it has a button on the task bar).
  1400.     // Change for v1.0.37.07: Set both icons unconditionally for code simplicity, and also in case
  1401.     // it's possible for the window to change after creation in a way that would make a custom icon
  1402.     // become relevant.  Set the big icon even if it's owned because there might be ways
  1403.     // an owned window can have an entry in the alt-tab menu.  The following ways come close
  1404.     // but don't actually succeed:
  1405.     // 1) It's owned by the main window but the main window isn't visible: It acquires the main window's icon
  1406.     //    in the alt-tab menu regardless of whether it was given a big icon of its own.
  1407.     // 2) It's owned by another GUI window but it has the WS_EX_APPWINDOW style (might force a taskbar button):
  1408.     //    Same effect as in #1.
  1409.     // 3) Possibly other ways.
  1410.     SendMessage(mHwnd, WM_SETICON, ICON_SMALL, (LPARAM)main_icon); // Testing shows that a zero is returned for both;
  1411.     SendMessage(mHwnd, WM_SETICON, ICON_BIG, (LPARAM)main_icon);   // i.e. there is no previous icon to destroy in this case.
  1412.  
  1413.     return OK;
  1414. }
  1415.  
  1416.  
  1417.  
  1418. void GuiType::SetLabels(char *aLabelPrefix)
  1419. // v1.0.44.09: Allow custom label prefix to be set; e.g. MyGUI vs. "5Gui" or "2Gui".  This increases flexibility
  1420. // for scripts that dynamically create a varying number of windows, and also allows multiple windows to call the
  1421. // same set of subroutines.
  1422. // This function mustn't assume that mHwnd is a valid window because it might not have been created yet.
  1423. // Caller passes NULL to indicate "use default label prefix" (i.e. the WindowNumber followed by the string "Gui").
  1424. // Caller is reponsible for checking mLabelsHaveBeenSet as a pre-condition to calling us, if desired.
  1425. // Caller must ensure that mExStyle is up-to-date if mHwnd is an existing window.  In addition, caller must
  1426. // apply any changes to mExStyle that we make here.
  1427. {
  1428.     mLabelsHaveBeenSet = true; // Although it's value only matters in some contexts, it's set unconditionally for simplicity.
  1429.  
  1430.     #define MAX_GUI_PREFIX_LENGTH 255
  1431.     char *label_suffix, label_name[MAX_GUI_PREFIX_LENGTH+64]; // Labels are unlimited in length, but keep prefix+suffix relatively short so that it stays reasonable (to make it easier to limit it in the future should that ever be desirable).
  1432.     if (aLabelPrefix)
  1433.         strlcpy(label_name, aLabelPrefix, MAX_GUI_PREFIX_LENGTH+1); // Reserve the rest of label_name's size for the suffix below to ensure no chance of overflow.
  1434.     else // Caller is indicating that the defaults should be used.
  1435.     {
  1436.         if (mWindowIndex > 0) // Prepend the window number for windows other than the first.
  1437.             sprintf(label_name, "%dGui", mWindowIndex + 1);
  1438.         else
  1439.             strcpy(label_name, "Gui");
  1440.     }
  1441.     label_suffix = label_name + strlen(label_name); // This is the position at which the rest of the label name will be copied.
  1442.  
  1443.     // Find the label to run automatically when the form closes (if any):
  1444.     strcpy(label_suffix, "Close");
  1445.     mLabelForClose = g_script.FindLabel(label_name);  // OK if NULL (closing the window is the same as "gui, cancel").
  1446.  
  1447.     // Find the label to run automatically when the user presses Escape (if any):
  1448.     strcpy(label_suffix, "Escape");
  1449.     mLabelForEscape = g_script.FindLabel(label_name);  // OK if NULL (pressing ESCAPE does nothing).
  1450.  
  1451.     // Find the label to run automatically when the user resizes the window (if any):
  1452.     strcpy(label_suffix, "Size");
  1453.     mLabelForSize = g_script.FindLabel(label_name);  // OK if NULL.
  1454.  
  1455.     // Find the label to run automatically when the user invokes context menu via AppsKey, Rightclick, or Shift-F10:
  1456.     strcpy(label_suffix, "ContextMenu");
  1457.     mLabelForContextMenu = g_script.FindLabel(label_name);  // OK if NULL (leaves context menu unhandled).
  1458.  
  1459.     // Find the label to run automatically when files are dropped onto the window:
  1460.     strcpy(label_suffix, "DropFiles");
  1461.     if ((mLabelForDropFiles = g_script.FindLabel(label_name))  // OK if NULL (dropping files is disallowed).
  1462.         && !mHdrop) // i.e. don't allow user to visibly drop files onto window if a drop is already queued or running.
  1463.         mExStyle |= WS_EX_ACCEPTFILES; // Makes the window accept drops. Otherwise, the WM_DROPFILES msg is not received.
  1464.     else
  1465.         mExStyle &= ~WS_EX_ACCEPTFILES;
  1466.     // It is not necessary to apply any style change made above because the caller detects changes and applies them.
  1467. }
  1468.  
  1469.  
  1470.  
  1471. void GuiType::UpdateMenuBars(HMENU aMenu)
  1472. // Caller has changed aMenu and wants the change visibly reflected in any windows that that
  1473. // use aMenu as a menu bar.  For example, if a menu item has been disabled, the grey-color
  1474. // won't show up immediately unless the window is refreshed.
  1475. {
  1476.     int i, gui_count;
  1477.     for (i = 0, gui_count = 0; i < MAX_GUI_WINDOWS; ++i)
  1478.     {
  1479.         if (g_gui[i])
  1480.         {
  1481.             if (g_gui[i]->mHwnd && GetMenu(g_gui[i]->mHwnd) == aMenu && IsWindowVisible(g_gui[i]->mHwnd))
  1482.             {
  1483.                 // Neither of the below two calls by itself is enough for all types of changes.
  1484.                 // Thought it's possible that every type of change only needs one or the other, both
  1485.                 // are done for simplicity:
  1486.                 // This first line is necessary at least for cases where the height of the menu bar
  1487.                 // (the number of rows needed to display all its items) has changed as a result
  1488.                 // of the caller's change.  In addition, I believe SetWindowPos() must be called
  1489.                 // before RedrawWindow() to prevent artifacts in some cases:
  1490.                 SetWindowPos(g_gui[i]->mHwnd, NULL, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
  1491.                 // This line is necessary at least when a single new menu item has been added:
  1492.                 RedrawWindow(g_gui[i]->mHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW);
  1493.                 // RDW_UPDATENOW: Seems best so that the window is in an visible updated state when function
  1494.                 // returns.  This is because if the menu bar happens to be two rows or its size is changed
  1495.                 // in any other way, the window dimensions themselves might change, and the caller might
  1496.                 // rely on such a change being visibly finished for PixelGetColor, etc.
  1497.                 //Not enough: UpdateWindow(g_gui[i]->mHwnd);
  1498.             }
  1499.             if (sGuiCount == ++gui_count) // No need to keep searching.
  1500.                 break;
  1501.         }
  1502.     }
  1503. }
  1504.  
  1505.  
  1506.  
  1507. ResultType GuiType::AddControl(GuiControls aControlType, char *aOptions, char *aText)
  1508. // Caller must have ensured that mHwnd is non-NULL (i.e. that the parent window already exists).
  1509. {
  1510.     #define TOO_MANY_CONTROLS "Too many controls." ERR_ABORT // Short msg since so rare.
  1511.     if (mControlCount >= MAX_CONTROLS_PER_GUI)
  1512.         return g_script.ScriptError(TOO_MANY_CONTROLS);
  1513.     if (mControlCount >= mControlCapacity) // The section below on the above check already having been done.
  1514.     {
  1515.         // realloc() to keep the array contiguous, which allows better-performing methods to be
  1516.         // used to access the list of controls in various places.
  1517.         // Expand the array by one block:
  1518.         GuiControlType *realloc_temp;  // Needed since realloc returns NULL on failure but leaves original block allocated.
  1519.         if (   !(realloc_temp = (GuiControlType *)realloc(mControl  // If passed NULL, realloc() will do a malloc().
  1520.             , (mControlCapacity + GUI_CONTROL_BLOCK_SIZE) * sizeof(GuiControlType)))   )
  1521.             return g_script.ScriptError(TOO_MANY_CONTROLS); // A non-specific msg since this error is so rare.
  1522.         mControl = realloc_temp;
  1523.         mControlCapacity += GUI_CONTROL_BLOCK_SIZE;
  1524.     }
  1525.  
  1526.     ////////////////////////////////////////////////////////////////////////////////////////
  1527.     // Set defaults for the various options, to be overridden individually by any specified.
  1528.     ////////////////////////////////////////////////////////////////////////////////////////
  1529.     GuiControlType &control = mControl[mControlCount];
  1530.     ZeroMemory(&control, sizeof(GuiControlType));
  1531.  
  1532.     if (aControlType == GUI_CONTROL_TAB2) // v1.0.47.05: Replace TAB2 with TAB at an early stage to simplify the code.  The only purpose of TAB2 is to flag this as the new type of tab that avoids redrawing issues but has a new z-order that would break some existing scripts.
  1533.     {
  1534.         aControlType = GUI_CONTROL_TAB;
  1535.         control.attrib |= GUI_CONTROL_ATTRIB_ALTBEHAVIOR; // v1.0.47.05: A means for new scripts to solve redrawing problems in tab controls at the cost of putting the tab control after its controls in the z-order.
  1536.     }
  1537.     if (aControlType == GUI_CONTROL_TAB)
  1538.     {
  1539.         if (mTabControlCount == MAX_TAB_CONTROLS)
  1540.             return g_script.ScriptError("Too many tab controls." ERR_ABORT); // Short msg since so rare.
  1541.         // For now, don't allow a tab control to be create inside another tab control because it raises
  1542.         // doubt and probably would create complications.  If it ever is allowed, note that
  1543.         // control.tab_index stores this tab control's index (0 for the first tab control, 1 for the
  1544.         // second, etc.) -- this is done for performance reasons.
  1545.         control.tab_control_index = MAX_TAB_CONTROLS;
  1546.         control.tab_index = mTabControlCount; // Store its control-index to help look-up performance in other sections.
  1547.     }
  1548.     else if (aControlType == GUI_CONTROL_STATUSBAR)
  1549.     {
  1550.         if (mStatusBarHwnd)
  1551.             return g_script.ScriptError("Too many status bars." ERR_ABORT); // Short msg since so rare.
  1552.         control.tab_control_index = MAX_TAB_CONTROLS; // Indicate that bar isn't owned by any tab control.
  1553.         // No need to do the following because ZeroMem did it:
  1554.         //control.tab_index = 0; // Ignored but set for maintainability/consistency.
  1555.     }
  1556.     else
  1557.     {
  1558.         control.tab_control_index = mCurrentTabControlIndex;
  1559.         control.tab_index = mCurrentTabIndex;
  1560.     }
  1561.  
  1562.     // If this is the first control, set the default margin for the window based on the size
  1563.     // of the current font, but only if the margins haven't already been set:
  1564.     if (!mControlCount)
  1565.     {
  1566.         if (mMarginX == COORD_UNSPECIFIED)
  1567.             mMarginX = (int)(1.25 * sFont[mCurrentFontIndex].point_size);  // Seems to be a good rule of thumb.
  1568.         if (mMarginY == COORD_UNSPECIFIED)
  1569.             mMarginY = (int)(0.75 * sFont[mCurrentFontIndex].point_size);  // Also seems good.
  1570.         mPrevX = mMarginX;  // This makes first control be positioned correctly if it lacks both X & Y coords.
  1571.     }
  1572.  
  1573.     control.type = aControlType; // Improves maintainability to do this early, but must be done after TAB2 vs. TAB is resolved higher above.
  1574.     GuiControlOptionsType opt;
  1575.     ControlInitOptions(opt, control);
  1576.     // aOpt.checked is already okay since BST_UNCHECKED == 0
  1577.     // Similarly, the zero-init of "control" higher above set the right values for password_char, new_section, etc.
  1578.  
  1579.     /////////////////////////////////////////////////
  1580.     // Set control-specific defaults for any options.
  1581.     /////////////////////////////////////////////////
  1582.     opt.style_add |= WS_VISIBLE;  // Starting default for all control types.
  1583.     opt.use_theme = mUseTheme; // Set default.
  1584.  
  1585.     // Radio buttons are handled separately here, outside the switch() further below:
  1586.     if (aControlType == GUI_CONTROL_RADIO)
  1587.     {
  1588.         // The BS_NOTIFY style is probably better not applied by default to radios because although it
  1589.         // causes the control to send BN_DBLCLK messages, each double-click by the user is seen only
  1590.         // as one click for the purpose of cosmetically making the button appear like it is being
  1591.         // clicked rapidly.  Update: the usefulness of double-clicking a radio button seems to
  1592.         // outweigh the rare cosmetic deficiency of rapidly clicking a radio button, so it seems
  1593.         // better to provide it as a default that can be overridden via explicit option.
  1594.         // v1.0.47.04: Removed BS_MULTILINE from default because it is conditionally applied later below.
  1595.         opt.style_add |= BS_NOTIFY;  // No WS_TABSTOP here since that is applied elsewhere depending on radio group nature.
  1596.         if (!mInRadioGroup)
  1597.             opt.style_add |= WS_GROUP; // Tabstop must be handled later below.
  1598.             // The mInRadioGroup flag will be changed accordingly after the control is successfully created.
  1599.         //else by default, no WS_TABSTOP or WS_GROUP.  However, WS_GROUP can be applied manually via the
  1600.         // options list to split this radio group off from one immediately prior to it.
  1601.     }
  1602.     else // Not a radio.
  1603.         if (mInRadioGroup) // Close out the prior radio group by giving this control the WS_GROUP style.
  1604.             opt.style_add |= WS_GROUP; // This might not be necessary on all OSes, but it seems traditional / best-practice.
  1605.  
  1606.     // Set control's default text color:
  1607.     bool uses_font_and_text_color = USES_FONT_AND_TEXT_COLOR(aControlType); // Resolve macro only once.
  1608.     if (uses_font_and_text_color) // Must check this to avoid corrupting union_hbitmap for PIC controls.
  1609.     {
  1610.         if (control.type != GUI_CONTROL_LISTVIEW) // Must check this to avoid corrupting union_lv_attrib.
  1611.             control.union_color = mCurrentColor; // Default to the most recently set color.
  1612.         opt.color_listview = mCurrentColor;  // v1.0.44: Added so that ListViews start off with current font color unless overridden in their options.
  1613.     }
  1614.     else if (aControlType == GUI_CONTROL_PROGRESS) // This must be done to detect custom Progress color.
  1615.         control.union_color = CLR_DEFAULT; // Set progress to default color avoids unnecessary stripping of theme.
  1616.     //else don't change union_color since it shares the same address as union_hbitmap & union_col.
  1617.  
  1618.     switch (aControlType) // Set starting defaults based on control type (the above also does some of that).
  1619.     {
  1620.     // Some controls also have the WS_EX_CLIENTEDGE exstyle by default because they look pretty strange
  1621.     // without them.  This seems to be the standard default used by most applications.
  1622.     // Note: It seems that WS_BORDER is hardly ever used in practice with controls, just parent windows.
  1623.     case GUI_CONTROL_DROPDOWNLIST:
  1624.         opt.style_add |= WS_TABSTOP|WS_VSCROLL;  // CBS_DROPDOWNLIST is forcibly applied later. WS_VSCROLL is necessary.
  1625.         break;
  1626.     case GUI_CONTROL_COMBOBOX:
  1627.         // CBS_DROPDOWN is set as the default here to allow the flexibilty for it to be changed to
  1628.         // CBS_SIMPLE.  CBS_SIMPLE is allowed for ComboBox but not DropDownList because CBS_SIMPLE
  1629.         // has an edit control just like a combo, which DropDownList isn't equipped to handle via Submit().
  1630.         // Also, if CBS_AUTOHSCROLL is omitted, typed text cannot go beyond the visible width of the
  1631.         // edit control, so it seems best to havethat as a default also:
  1632.         opt.style_add |= WS_TABSTOP|WS_VSCROLL|CBS_AUTOHSCROLL|CBS_DROPDOWN;  // WS_VSCROLL is necessary.
  1633.         break;
  1634.     case GUI_CONTROL_LISTBOX:
  1635.         // Omit LBS_STANDARD because it includes LBS_SORT, which we don't want as a default style.
  1636.         // However, as of v1.0.30.03, LBS_USETABSTOPS is included by default because:
  1637.         // 1) Not doing so seems to make it impossible to apply tab stops after the control has been created.
  1638.         // 2) Without this style, tabs appears as empty squares in the text, which seems undesirable for
  1639.         //    99.9% of applications.
  1640.         // 3) LBS_USETABSTOPS can be explicitly removed by specifying -0x80 in the options of "Gui Add".
  1641.         opt.style_add |= WS_TABSTOP|WS_VSCROLL|LBS_USETABSTOPS;  // WS_VSCROLL seems the most desirable default.
  1642.         opt.exstyle_add |= WS_EX_CLIENTEDGE;
  1643.         break;
  1644.     case GUI_CONTROL_LISTVIEW:
  1645.         // The ListView extended styles are actually an entirely separate class of styles that exist
  1646.         // separately from ExStyles.  This explains why Get/SetWindowLong doesn't work on them.
  1647.         // But keep in mind that some of the normal/classic extended styles can still be applied
  1648.         // to a ListView via Get/SetWindowLong.
  1649.         // The listview extended styles all require at least ComCtl32.dll 4.70 (some might require more)
  1650.         // and thus will have no effect in Win 95/NT unless they have MSIE 3.x or similar patch installed.
  1651.         // Thus, things like LVS_EX_FULLROWSELECT and LVS_EX_HEADERDRAGDROP will have no effect on those systems.
  1652.         opt.listview_style |= LVS_EX_FULLROWSELECT|LVS_EX_HEADERDRAGDROP; // LVS_AUTOARRANGE seems to disrupt the display of the column separators and have other weird effects in Report view.
  1653.         opt.style_add |= WS_TABSTOP|LVS_SHOWSELALWAYS; // LVS_REPORT is omitted to help catch bugs involving opt.listview_view.  WS_THICKFRAME allows the control itself to be drag-resized.
  1654.         opt.exstyle_add |= WS_EX_CLIENTEDGE; // WS_EX_STATICEDGE/WS_EX_WINDOWEDGE/WS_BORDER(non-ex) don't look as nice. WS_EX_DLGMODALFRAME is a weird but interesting effect.
  1655.         opt.listview_view = LVS_REPORT; // Improves maintainability by avoiding the need to check if it's -1 in other places.
  1656.         break;
  1657.     case GUI_CONTROL_TREEVIEW:
  1658.         // Default style is somewhat debatable, but the familiarity of Explorer's own defaults seems best.
  1659.         // TVS_SHOWSELALWAYS seems preferable by most people, and is also consistent to what is used for ListView.
  1660.         // Lines and buttons also seem preferable because the main feature of a tree is its hierarchical nature,
  1661.         // and that nature isn't well revealed without buttons, and buttons can't be shown at the root level
  1662.         // without TVS_LINESATROOT, which in turn can't be active without TVS_HASLINES.
  1663.         opt.style_add |= WS_TABSTOP|TVS_SHOWSELALWAYS|TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS; // TVS_LINESATROOT is necessary to get plus/minus buttons on root-level items.
  1664.         opt.exstyle_add |= WS_EX_CLIENTEDGE; // Debatable, but seems best for consistency with ListView.
  1665.         break;
  1666.     case GUI_CONTROL_EDIT:
  1667.         opt.style_add |= WS_TABSTOP;
  1668.         opt.exstyle_add |= WS_EX_CLIENTEDGE;
  1669.         break;
  1670.     case GUI_CONTROL_UPDOWN:
  1671.         // UDS_NOTHOUSANDS is debatable:
  1672.         // 1) The primary means by which a script validates whether the buddy contains an invalid
  1673.         //    or out-of-range value for its UpDown is to compare the contents of the two.  If one
  1674.         //    has commas and the other doesn't, the commas must first be removed before comparing.
  1675.         // 2) Presence of commas in numeric data is going to be a source of script bugs for those
  1676.         //    who take the buddy's contents rather than the UpDown's contents as the user input.
  1677.         //    However, you could argue that script is not proper if it does this blindly because
  1678.         //    the buddy could contain an out-of-range or non-numeric value.
  1679.         // 3) Display is more ergonomic if it has commas in it.
  1680.         // The above make it pretty hard to decide, so sticking with the default of have commas
  1681.         // seems ok.  Also, UDS_ALIGNRIGHT must be present by default because otherwise buddying
  1682.         // will not take effect correctly.
  1683.         opt.style_add |= UDS_SETBUDDYINT|UDS_ALIGNRIGHT|UDS_AUTOBUDDY|UDS_ARROWKEYS;
  1684.         break;
  1685.     case GUI_CONTROL_DATETIME: // Gets a tabstop even when it contains an empty checkbox indicating "no date".
  1686.         // DTS_SHORTDATECENTURYFORMAT is applied by default because it should make results more consistent
  1687.         // across old and new systems.  This is because new systems display a 4-digit year even without
  1688.         // this style, but older ones might display a two digit year.  This should make any system capable
  1689.         // of displaying a 4-digit year display it in the locale's customary format.  On systems that don't
  1690.         // support DTS_SHORTDATECENTURYFORMAT, it should be ignored, resulting in DTS_SHORTDATEFORMAT taking
  1691.         // effect automatically (untested).
  1692.         opt.style_add |= WS_TABSTOP|DTS_SHORTDATECENTURYFORMAT;
  1693.         break;
  1694.     case GUI_CONTROL_BUTTON: // v1.0.45: Removed BS_MULTILINE from default because it is conditionally applied later below.
  1695.     case GUI_CONTROL_CHECKBOX: // v1.0.47.04: Removed BS_MULTILINE from default because it is conditionally applied later below.
  1696.     case GUI_CONTROL_HOTKEY:
  1697.     case GUI_CONTROL_SLIDER:
  1698.         opt.style_add |= WS_TABSTOP;
  1699.         break;
  1700.     case GUI_CONTROL_PROGRESS:
  1701.         opt.style_add |= PBS_SMOOTH; // The smooth ones seem preferable as a default.  Theme is removed later below.
  1702.         break;
  1703.     case GUI_CONTROL_TAB:
  1704.         // Override the normal default, requiring a manual +Theme in the control's options.  This is done
  1705.         // because themed tabs have a gradient background that is currently not well supported by the method
  1706.         // used here (controls' backgrounds do not match the gradient):
  1707.         opt.use_theme = false;
  1708.         opt.style_add |= WS_TABSTOP|TCS_MULTILINE;
  1709.         break;
  1710.     case GUI_CONTROL_STATUSBAR:
  1711.         // Although the following appears unncessary, at least on XP, there's a good chance it's required
  1712.         // on older OSes such as Win 95/NT.  On newer OSes, apparantly the control shows a grip on
  1713.         // its own even though it doesn't even give itself the SBARS_SIZEGRIP style.
  1714.         if (mStyle & WS_SIZEBOX) // Parent window is resizable.
  1715.             opt.style_add |= SBARS_SIZEGRIP; // Provide a grip by default.
  1716.         // Below: Seems best to provide SBARS_TOOLTIPS by default, since we're not bound by backward compatbility
  1717.         // like the OS is.  In tneory, tips should be displayed only when the script has actually set some tip text
  1718.         // (e.g. via SendMessage).  In practice, tips are never displayed, even under the precise conditions
  1719.         // described at MSDN's SB_SETTIPTEXT, perhaps under certain OS versions and themes.  See bottom of
  1720.         // BIF_StatusBar() for more comments.
  1721.         opt.style_add |= SBARS_TOOLTIPS;
  1722.         break;
  1723.     // Nothing extra for these currently:
  1724.     //case GUI_CONTROL_RADIO: This one is handled separately above the switch().
  1725.     //case GUI_CONTROL_TEXT:
  1726.     //case GUI_CONTROL_MONTHCAL: Can't be focused, so no tabstop.
  1727.     //case GUI_CONTROL_PIC:
  1728.     //case GUI_CONTROL_GROUPBOX:
  1729.         // v1.0.44.11: The following was commented out for GROUPBOX to avoid unwanted wrapping of last letter when
  1730.         // the font is bold on XP Classic theme (other font styles and desktop themes may also be cause this).
  1731.         // Avoiding this problem seems to outweigh the breaking of old scripts that use GroupBoxes with more than
  1732.         // one line of text (which are likely to be very rare).
  1733.         //opt.style_add |= BS_MULTILINE;
  1734.         break;
  1735.     }
  1736.  
  1737.     /////////////////////////////
  1738.     // Parse the list of options.
  1739.     /////////////////////////////
  1740.     if (!ControlParseOptions(aOptions, opt, control))
  1741.         return FAIL;  // It already displayed the error.
  1742.  
  1743.     // The following is needed by ControlSetListViewOptions/ControlSetTreeViewOptions, and possibly others
  1744.     // in the future. It must be done only after ControlParseOptions() so that cases where mCurrentColor
  1745.     // is not CLR_DEFAULT but the options contained cDefault are handled properly.
  1746.     // The following will set opt.color_changed to an invalid value for GUI_CONTROL_PIC (which stores something
  1747.     // else in the union_color's union) and other types that don't even use union_color for anything yet.  But
  1748.     // that should be okay because those types should never consult opt.color_changed.
  1749.     opt.color_changed = CLR_DEFAULT != (aControlType == GUI_CONTROL_LISTVIEW ? opt.color_listview : control.union_color);
  1750.     if (opt.color_bk == CLR_DEFAULT) // i.e. the options list must have explicitly specified BackgroundDefault.
  1751.         opt.color_bk = CLR_INVALID; // Tell things like ControlSetListViewOptions "no color change needed".
  1752.     else if (opt.color_bk == CLR_INVALID && mBackgroundColorCtl != CLR_DEFAULT // No bk color was specified in options param.
  1753.         && aControlType != GUI_CONTROL_PROGRESS && aControlType != GUI_CONTROL_STATUSBAR) // And the control obeys the current "Gui, Color,, CtlBkColor".  Status bars don't obey it because it seems slightly less desirable for most people, and also because system default bar color might be diff. than system default win color on some themes.
  1754.         // Since bkgnd color was not explicitly specified in options, use the current background color (except progress bars, which do their own thing).
  1755.         opt.color_bk = mBackgroundColorCtl; // Use window's global custom, control background.
  1756.     //else leave it as invalid so that ControlSetListView/TreeView/ProgressOptions() etc. won't bother changing it.
  1757.  
  1758.     // Change for v1.0.45 (buttons) and v1.0.47.04 (checkboxes and radios): Under some desktop themes and
  1759.     // unusual DPI settings, it has been reported that the last letter of the control's text gets truncated
  1760.     // and/or causes an unwanted wrap that prevents proper display of the text.  To solve this, default to
  1761.     // "wrapping enabled" only when necessary.  One case it's usually necessary is when there's an explicit
  1762.     // width present because then the text can automatically word-wrap to the next line if it contains any
  1763.     // spaces/tabs/dashes (this also improves backward compatibility).
  1764.     DWORD contains_bs_multiline_if_applicable =
  1765.         (opt.width != COORD_UNSPECIFIED || opt.height != COORD_UNSPECIFIED
  1766.             || opt.row_count > 1.5 || StrChrAny(aText, "\n\r")) // Both LF and CR can start new lines.
  1767.         ? (BS_MULTILINE & ~opt.style_remove) // Add BS_MULTILINE unless it was explicitly removed.
  1768.         : 0; // Otherwise: Omit BS_MULTILINE (unless it was explicitly added [the "0" is verified correct]) because on some unsuual DPI settings (i.e. DPIs other than 96 or 120), DrawText() sometimes yields a width that is slightly too narrow, which causes unwanted wrapping in single-line checkboxes/radios/buttons.
  1769.  
  1770.     DWORD style = opt.style_add & ~opt.style_remove;
  1771.     DWORD exstyle = opt.exstyle_add & ~opt.exstyle_remove;
  1772.  
  1773.     //////////////////////////////////////////
  1774.     // Force any mandatory styles into effect.
  1775.     //////////////////////////////////////////
  1776.     style |= WS_CHILD;  // All control types must have this, even if script attempted to remove it explicitly.
  1777.     switch (aControlType)
  1778.     {
  1779.     case GUI_CONTROL_GROUPBOX:
  1780.         // There doesn't seem to be any flexibility lost by forcing the buttons to be the right type,
  1781.         // and doing so improves maintainability and peace-of-mind:
  1782.         style = (style & ~BS_TYPEMASK) | BS_GROUPBOX;  // Force it to be the right type of button.
  1783.         break;
  1784.     case GUI_CONTROL_BUTTON:
  1785.         if (style & BS_DEFPUSHBUTTON) // i.e. its single bit is present. BS_TYPEMASK is not involved in this line because it's a purity check.
  1786.             style = (style & ~BS_TYPEMASK) | BS_DEFPUSHBUTTON; // Done to ensure the lowest four bits are pure.
  1787.         else
  1788.             style &= ~BS_TYPEMASK;  // Force it to be the right type of button --> BS_PUSHBUTTON == 0
  1789.         style |= contains_bs_multiline_if_applicable;
  1790.         break;
  1791.     case GUI_CONTROL_CHECKBOX:
  1792.         // Note: BS_AUTO3STATE and BS_AUTOCHECKBOX are mutually exclusive due to their overlap within
  1793.         // the bit field:
  1794.         if ((style & BS_AUTO3STATE) == BS_AUTO3STATE) // Fixed for v1.0.45.03 to check if all the BS_AUTO3STATE bits are present, not just "any" of them. BS_TYPEMASK is not involved here because this is a purity check, and TYPEMASK would defeat the whole purpose.
  1795.             style = (style & ~BS_TYPEMASK) | BS_AUTO3STATE; // Done to ensure the lowest four bits are pure.
  1796.         else
  1797.             style = (style & ~BS_TYPEMASK) | BS_AUTOCHECKBOX;  // Force it to be the right type of button.
  1798.         style |= contains_bs_multiline_if_applicable; // v1.0.47.04: Added to avoid unwanted wrapping on systems with unusual DPI settings (DPIs other than 96 and 120 sometimes seem to cause a roundoff problem with DrawText()).
  1799.         break;
  1800.     case GUI_CONTROL_RADIO:
  1801.         style = (style & ~BS_TYPEMASK) | BS_AUTORADIOBUTTON;  // Force it to be the right type of button.
  1802.         // This below must be handled here rather than in the set-defaults section because this
  1803.         // radio might be the first of its group due to the script having explicitly specified the word
  1804.         // Group in options (useful to make two adjacent radio groups).
  1805.         if (style & WS_GROUP && !(opt.style_remove & WS_TABSTOP))
  1806.             style |= WS_TABSTOP;
  1807.         // Otherwise it lacks a tabstop by default.
  1808.         style |= contains_bs_multiline_if_applicable; // v1.0.47.04: Added to avoid unwanted wrapping on systems with unusual DPI settings (DPIs other than 96 and 120 sometimes seem to cause a roundoff problem with DrawText()).
  1809.         break;
  1810.     case GUI_CONTROL_DROPDOWNLIST:
  1811.         style |= CBS_DROPDOWNLIST;  // This works because CBS_DROPDOWNLIST == CBS_SIMPLE|CBS_DROPDOWN
  1812.         break;
  1813.     case GUI_CONTROL_COMBOBOX:
  1814.         if (style & CBS_SIMPLE) // i.e. CBS_SIMPLE has been added to the original default, so assume it is SIMPLE.
  1815.             style = (style & ~0x0F) | CBS_SIMPLE; // Done to ensure the lowest four bits are pure.
  1816.         else
  1817.             style = (style & ~0x0F) | CBS_DROPDOWN; // Done to ensure the lowest four bits are pure.
  1818.         break;
  1819.     case GUI_CONTROL_LISTBOX:
  1820.         style |= LBS_NOTIFY;  // There doesn't seem to be any flexibility lost by forcing this style.
  1821.         break;
  1822.     case GUI_CONTROL_EDIT:
  1823.         // This is done for maintainability and peace-of-mind, though it might not strictly be required
  1824.         // to be done at this stage:
  1825.         if (opt.row_count > 1.5 || strchr(aText, '\n')) // Multiple rows or contents contain newline.
  1826.             style |= (ES_MULTILINE & ~opt.style_remove); // Add multiline unless it was explicitly removed.
  1827.         // This next check is relied upon by other things.  If this edit has the multiline style either
  1828.         // due to the above check or any other reason, provide other default styles if those styles
  1829.         // weren't explicitly removed in the options list:
  1830.         if (style & ES_MULTILINE) // If allowed, enable vertical scrollbar and capturing of ENTER keystrokes.
  1831.             // Safest to include ES_AUTOVSCROLL, though it appears to have no effect on XP.  See also notes below:
  1832.             #define EDIT_MULTILINE_DEFAULT (WS_VSCROLL|ES_WANTRETURN|ES_AUTOVSCROLL)
  1833.             style |= EDIT_MULTILINE_DEFAULT & ~opt.style_remove;
  1834.             // In addition, word-wrapping is implied unless explicitly disabled via -wrap in options.
  1835.             // This is because -wrap adds the ES_AUTOHSCROLL style.
  1836.         // else: Single-line edit.  ES_AUTOHSCROLL will be applied later below if all the other checks
  1837.         // fail to auto-detect this edit as a multi-line edit.
  1838.         // Notes: ES_MULTILINE is required for any CRLFs in the default value to display correctly.
  1839.         // If ES_MULTILINE is in effect: "If you do not specify ES_AUTOHSCROLL, the control automatically
  1840.         // wraps words to the beginning of the next line when necessary."
  1841.         // Also, ES_AUTOVSCROLL seems to have no additional effect, perhaps because this window type
  1842.         // is considered to be a dialog. MSDN: "When the multiline edit control is not in a dialog box
  1843.         // and the ES_AUTOVSCROLL style is specified, the edit control shows as many lines as possible
  1844.         // and scrolls vertically when the user presses the ENTER key. If you do not specify ES_AUTOVSCROLL,
  1845.         // the edit control shows as many lines as possible and beeps if the user presses the ENTER key when
  1846.         // no more lines can be displayed."
  1847.         break;
  1848.     case GUI_CONTROL_TAB:
  1849.         style |= WS_CLIPSIBLINGS; // MSDN: Both the parent window and the tab control must have the WS_CLIPSIBLINGS window style.
  1850.         // TCS_OWNERDRAWFIXED is required to implement custom Text color in the tabs.
  1851.         // For some reason, it's also required for TabWindowProc's WM_ERASEBKGND to be able to
  1852.         // override the background color of the control's interior, at least when an XP theme is in effect.
  1853.         // (which is currently impossible since theme is always removed from a tab).
  1854.         // Even if that weren't the case, would still want owner-draw because otherwise the background
  1855.         // color of the tab-text would be different than the tab's interior, which testing shows looks
  1856.         // pretty strange.
  1857.         if (mBackgroundBrushWin && !(control.attrib & GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT)
  1858.             || control.union_color != CLR_DEFAULT)
  1859.             style |= TCS_OWNERDRAWFIXED;
  1860.         else
  1861.             style &= ~TCS_OWNERDRAWFIXED;
  1862.         break;
  1863.  
  1864.     // Nothing extra for these currently:
  1865.     //case GUI_CONTROL_TEXT:  Ensuring SS_BITMAP and such are absent seems too over-protective.
  1866.     //case GUI_CONTROL_PIC:   SS_BITMAP/SS_ICON are applied after the control isn't created so that it doesn't try to auto-load a resource.
  1867.     //case GUI_CONTROL_LISTVIEW:
  1868.     //case GUI_CONTROL_TREEVIEW:
  1869.     //case GUI_CONTROL_DATETIME:
  1870.     //case GUI_CONTROL_MONTHCAL:
  1871.     //case GUI_CONTROL_HOTKEY:
  1872.     //case GUI_CONTROL_UPDOWN:
  1873.     //case GUI_CONTROL_SLIDER:
  1874.     //case GUI_CONTROL_PROGRESS:
  1875.     //case GUI_CONTROL_STATUSBAR:
  1876.     }
  1877.  
  1878.     ////////////////////////////////////////////////////////////////////////////////////////////
  1879.     // If the above didn't already set a label for this control and this control type qualifies,
  1880.     // check if an automatic/implicit label exists for it in the script.
  1881.     ////////////////////////////////////////////////////////////////////////////////////////////
  1882.     if (aControlType == GUI_CONTROL_BUTTON
  1883.         && !control.jump_to_label && !(control.attrib & GUI_CONTROL_ATTRIB_IMPLICIT_CANCEL))
  1884.     {
  1885.         char label_name[1024]; // Subroutine labels are nearly unlimited in length, so use a size to cover anything realistic.
  1886.         if (mWindowIndex > 0) // Prepend the window number for windows other than the first.
  1887.             _itoa(mWindowIndex + 1, label_name, 10);
  1888.         else
  1889.             *label_name = '\0';
  1890.         snprintfcat(label_name, sizeof(label_name), "Button%s", aText);
  1891.         // Remove spaces and ampersands.  Although ampersands are legal in labels, it seems
  1892.         // more friendly to omit them in the automatic-label label name.  Note that a button
  1893.         // or menu item can contain a literal ampersand by using two ampersands, such as
  1894.         // "Save && Exit" (in this example, the auto-label would be named "ButtonSaveExit").
  1895.         // v1.0.46.01: tabs and accents are also removed since labels can't contain them.
  1896.         // However, colons are NOT removed because labels CAN contain them (except at the very end;
  1897.         // but due to rarity and backward compatibility, it doesn't seem worth adding code size for that).
  1898.         StrReplace(label_name, "\r", "", SCS_SENSITIVE);
  1899.         StrReplace(label_name, "\n", "", SCS_SENSITIVE);
  1900.         StrReplace(label_name, "\t", "", SCS_SENSITIVE);
  1901.         StrReplace(label_name, " ", "", SCS_SENSITIVE);
  1902.         StrReplace(label_name, "&", "", SCS_SENSITIVE);
  1903.         StrReplace(label_name, "`", "", SCS_SENSITIVE);
  1904.         // Alternate method, but seems considerably larger in code size based on OBJ size:
  1905.         //char *string_list[] = {"\r", "\n", " ", "\t", "&", "`", NULL}; // \r is separate from \n in case they're ever unpaired. Last char must be NULL to terminate the list.
  1906.         //for (char **cp = string_list; *cp; ++cp)
  1907.         //    StrReplace(label_name, *cp, "", SCS_SENSITIVE);
  1908.         control.jump_to_label = g_script.FindLabel(label_name);  // OK if NULL (the button will do nothing).
  1909.     }
  1910.  
  1911.     // The below will yield NULL for GUI_CONTROL_STATUSBAR because control.tab_control_index==OutOfBounds for it.
  1912.     GuiControlType *owning_tab_control = FindTabControl(control.tab_control_index); // For use in various places.
  1913.     GuiControlType control_temp; // Contents are unused (left uninitialized for performance and to help catch bugs).
  1914.     GuiControlType &prev_control = mControlCount ? mControl[mControlCount - 1] : control_temp; // For code size reduction, performance, and maintainability.
  1915.  
  1916.     ////////////////////////////////////////////////////////////////////////////////////////////
  1917.     // Automatically set the control's position in the client area if no position was specified.
  1918.     ////////////////////////////////////////////////////////////////////////////////////////////
  1919.     if (opt.x == COORD_UNSPECIFIED && opt.y == COORD_UNSPECIFIED)
  1920.     {
  1921.         if (owning_tab_control && !GetControlCountOnTabPage(control.tab_control_index, control.tab_index)) // Relies on short-circuit boolean.
  1922.         {
  1923.             // Since this control belongs to a tab control and that tab control already exists,
  1924.             // Position it relative to the tab control's client area upper-left corner if this
  1925.             // is the first control on this particular tab/page:
  1926.             POINT pt = GetPositionOfTabClientArea(*owning_tab_control);
  1927.             // Since both coords were unspecified, position this control at the upper left corner of its page.
  1928.             opt.x = pt.x + mMarginX;
  1929.             opt.y = pt.y + mMarginY;
  1930.         }
  1931.         else
  1932.         {
  1933.             // GUI_CONTROL_STATUSBAR ignores these:
  1934.             // Since both coords were unspecified, proceed downward from the previous control, using a default margin.
  1935.             opt.x = mPrevX;
  1936.             opt.y = mPrevY + mPrevHeight + mMarginY;  // Don't use mMaxExtentDown in this is a new column.
  1937.         }
  1938.         if (aControlType == GUI_CONTROL_TEXT && mControlCount // This is a text control and there is a previous control before it.
  1939.             && prev_control.type == GUI_CONTROL_TEXT
  1940.             && prev_control.tab_control_index == control.tab_control_index  // v1.0.44.03: Don't do the adjustment if
  1941.             && prev_control.tab_index == control.tab_index)                 // it's on another page or in another tab control.
  1942.             // Since this text control is being auto-positioned immediately below another, provide extra
  1943.             // margin space so that any edit control, dropdownlist, or other "tall input" control later added
  1944.             // to its right in "vertical progression" mode will line up with it.
  1945.             // v1.0.44: For code simplicity, this doesn't handle any status bar that might be present in between,
  1946.             // since that seems too rare and the consequences too mild.
  1947.             opt.y += GUI_CTL_VERTICAL_DEADSPACE;
  1948.     }
  1949.     // Can't happen due to the logic in the options-parsing section:
  1950.     //else if (opt.x == COORD_UNSPECIFIED)
  1951.     //    opt.x = mPrevX;
  1952.     //else if (y == COORD_UNSPECIFIED)
  1953.     //    opt.y = mPrevY;
  1954.  
  1955.  
  1956.     /////////////////////////////////////////////////////////////////////////////////////
  1957.     // For certain types of controls, provide a standard row_count if none was specified.
  1958.     /////////////////////////////////////////////////////////////////////////////////////
  1959.  
  1960.     // Set default for all control types.  GUI_CONTROL_MONTHCAL must be set up here because
  1961.     // an explictly specified row-count must also avoid calculating height from row count
  1962.     // in the standard way.
  1963.     bool calc_height_later = aControlType == GUI_CONTROL_MONTHCAL || aControlType == GUI_CONTROL_LISTVIEW
  1964.          || aControlType == GUI_CONTROL_TREEVIEW;
  1965.     bool calc_control_height_from_row_count = !calc_height_later; // Set default.
  1966.  
  1967.     if (opt.height == COORD_UNSPECIFIED && opt.row_count < 1)
  1968.     {
  1969.         switch(aControlType)
  1970.         {
  1971.         case GUI_CONTROL_DROPDOWNLIST:
  1972.         case GUI_CONTROL_COMBOBOX: // For these 2, row-count is defined as the number of items to display in the list.
  1973.             // Update: Unfortunately, heights taller than the desktop do not work: pre-v6 common controls
  1974.             // misbehave when the height is too tall to fit on the screen.  So the below comment is
  1975.             // obsolete and kept only for reference:
  1976.             // Since no height or row-count was given, make the control very tall so that OSes older than
  1977.             // XP will behavior similar to XP: they will let the desktop height determine how tall the
  1978.             // control can be. One exception to this is a true CBS_SIMPLE combo, which has appearance
  1979.             // and functionality similar to a ListBox.  In that case, a default row-count is provided
  1980.             // since that is more appropriate than having a really tall box hogging the window.
  1981.             // Because CBS_DROPDOWNLIST includes both CBS_SIMPLE and CBS_DROPDOWN, a true "simple"
  1982.             // (which is similar to a listbox) must omit CBS_DROPDOWN:
  1983.             opt.row_count = 3;  // Actual height will be calculated below using this.
  1984.             // Avoid doing various calculations later below if the XP+ will ignore the height anyway.
  1985.             // CBS_NOINTEGRALHEIGHT is checked in case that style was explicitly applied to the control
  1986.             // by the script. This is because on XP+ it will cause the combo/DropDownList to obey the
  1987.             // specified default height set above.  Also, for a pure CBS_SIMPLE combo, the OS always
  1988.             // obeys height:
  1989.             if ((!(style & CBS_SIMPLE) || (style & CBS_DROPDOWN)) // Not a pure CBS_SIMPLE.
  1990.                 && g_os.IsWinXPorLater() // ... and the OS is XP+.
  1991.                 && !(style & CBS_NOINTEGRALHEIGHT)) // ... and XP won't obey the height.
  1992.                 calc_control_height_from_row_count = false; // Don't bother calculating the height (i.e. override the default).
  1993.             break;
  1994.         case GUI_CONTROL_LISTBOX:
  1995.             opt.row_count = 3;  // Actual height will be calculated below using this.
  1996.             break;
  1997.         case GUI_CONTROL_LISTVIEW:
  1998.         case GUI_CONTROL_TREEVIEW:
  1999.             opt.row_count = 5;  // Actual height will be calculated below using this.
  2000.             break;
  2001.         case GUI_CONTROL_GROUPBOX:
  2002.             // Seems more appropriate to give GUI_CONTROL_GROUPBOX exactly two rows: the first for the
  2003.             // title of the group-box and the second for its content (since it might contain controls
  2004.             // placed horizontally end-to-end, and thus only need one row).
  2005.             opt.row_count = 2;
  2006.             break;
  2007.         case GUI_CONTROL_EDIT:
  2008.             // If there's no default text in the control from which to later calc the height, use a basic default.
  2009.             if (!*aText)
  2010.                 opt.row_count = (style & ES_MULTILINE) ? 3.0F : 1.0F;
  2011.             break;
  2012.         case GUI_CONTROL_DATETIME:
  2013.         case GUI_CONTROL_HOTKEY:
  2014.             opt.row_count = 1;
  2015.             break;
  2016.         case GUI_CONTROL_UPDOWN: // A somewhat arbitrary default in case it will lack a buddy to "snap to".
  2017.             // Make vertical up-downs tall by default.  If their width has also been omitted, they
  2018.             // will be made narrow by default in a later section.
  2019.             if (style & UDS_HORZ)
  2020.                 // Height vs. row_count is specified to ensure the same thickness for both vertical
  2021.                 // and horizontal up-downs:
  2022.                 opt.height = PROGRESS_DEFAULT_THICKNESS; // Seems okay for up-down to use Progress's thickness.
  2023.             else
  2024.                 opt.row_count = 5.0F;
  2025.             break;
  2026.         case GUI_CONTROL_SLIDER:
  2027.             // Make vertical trackbars tall by default.  If their width has also been omitted, they
  2028.             // will be made narrow by default in a later section.
  2029.             if (style & TBS_VERT)
  2030.                 opt.row_count = 5.0F;
  2031.             else
  2032.                 opt.height = ControlGetDefaultSliderThickness(style, opt.thickness);
  2033.             break;
  2034.         case GUI_CONTROL_PROGRESS:
  2035.             // Make vertical progress bars tall by default.  If their width has also been omitted, they
  2036.             // will be made narrow by default in a later section.
  2037.             if (style & PBS_VERTICAL)
  2038.                 opt.row_count = 5.0F;
  2039.             else
  2040.                 // Height vs. row_count is specified to ensure the same thickness for both vertical
  2041.                 // and horizontal progress bars:
  2042.                 opt.height = PROGRESS_DEFAULT_THICKNESS;
  2043.             break;
  2044.         case GUI_CONTROL_TAB:
  2045.             opt.row_count = 10;
  2046.             break;
  2047.         // Types not included
  2048.         // ------------------
  2049.         //case GUI_CONTROL_TEXT:      Rows are based on control's contents.
  2050.         //case GUI_CONTROL_PIC:       N/A
  2051.         //case GUI_CONTROL_BUTTON:    Rows are based on control's contents.
  2052.         //case GUI_CONTROL_CHECKBOX:  Same
  2053.         //case GUI_CONTROL_RADIO:     Same
  2054.         //case GUI_CONTROL_MONTHCAL:  Leave row-count unspecified so that an explicit r1 can be distinguished from "unspecified".
  2055.         //case GUI_CONTROL_STATUSBAR: For now, row-count is ignored/unused.
  2056.         }
  2057.     }
  2058.     else // Either a row_count or a height was explicitly specified.
  2059.         // If OS is XP+, must apply the CBS_NOINTEGRALHEIGHT style for these reasons:
  2060.         // 1) The app now has a manifest, which tells OS to use common controls v6.
  2061.         // 2) Common controls v6 will not obey the the user's specified height for the control's
  2062.         //    list portion unless the CBS_NOINTEGRALHEIGHT style is present.
  2063.         if ((aControlType == GUI_CONTROL_DROPDOWNLIST || aControlType == GUI_CONTROL_COMBOBOX) && g_os.IsWinXPorLater())
  2064.             style |= CBS_NOINTEGRALHEIGHT; // Forcibly applied, even if removed in options.
  2065.  
  2066.     ////////////////////////////////////////////////////////////////////////////////////////////
  2067.     // In case the control being added requires an HDC to calculate its size, provide the means.
  2068.     ////////////////////////////////////////////////////////////////////////////////////////////
  2069.     HDC hdc = NULL;
  2070.     HFONT hfont_old = NULL;
  2071.     TEXTMETRIC tm; // Used in more than one place.
  2072.     // To improve maintainability, always use this macro to deal with the above.
  2073.     // HDC will be released much further below when it is no longer needed.
  2074.     // Remember to release DC if ever need to return FAIL in the middle of auto-sizing/positioning.
  2075.     #define GUI_SET_HDC \
  2076.         if (!hdc)\
  2077.         {\
  2078.             hdc = GetDC(mHwnd);\
  2079.             hfont_old = (HFONT)SelectObject(hdc, sFont[mCurrentFontIndex].hfont);\
  2080.         }
  2081.  
  2082.     //////////////////////////////////////////////////////////////////////////////////////
  2083.     // If a row-count was specified or made available by the above defaults, calculate the
  2084.     // control's actual height (to be used when creating the window).  Note: If both
  2085.     // row_count and height were explicitly specified, row_count takes precedence.
  2086.     //////////////////////////////////////////////////////////////////////////////////////
  2087.     if (opt.row_count > 0)
  2088.     {
  2089.         // For GroupBoxes, add 1 to any row_count greater than 1 so that the title itself is
  2090.         // already included automatically.  In other words, the R-value specified by the user
  2091.         // should be the number of rows available INSIDE the box.
  2092.         // For DropDownLists and ComboBoxes, 1 is added because row_count is defined as the
  2093.         // number of rows shown in the drop-down portion of the control, so we need one extra
  2094.         // (used in later calculations) for the always visible portion of the control.
  2095.         switch (aControlType)
  2096.         {
  2097.         case GUI_CONTROL_GROUPBOX:
  2098.         case GUI_CONTROL_DROPDOWNLIST:
  2099.         case GUI_CONTROL_COMBOBOX: // LISTVIEW has custom handling later on, so isn't listed here.
  2100.             ++opt.row_count;
  2101.             break;
  2102.         }
  2103.         if (calc_control_height_from_row_count)
  2104.         {
  2105.             GUI_SET_HDC
  2106.             GetTextMetrics(hdc, &tm);
  2107.             // Calc the height by adding up the font height for each row, and including the space between lines
  2108.             // (tmExternalLeading) if there is more than one line.  0.5 is used in two places to prevent
  2109.             // negatives in one, and round the overall result in the other.
  2110.             opt.height = (int)((tm.tmHeight * opt.row_count) + (tm.tmExternalLeading * ((int)(opt.row_count + 0.5) - 1)) + 0.5);
  2111.             switch (aControlType)
  2112.             {
  2113.             case GUI_CONTROL_DROPDOWNLIST:
  2114.             case GUI_CONTROL_COMBOBOX:
  2115.             case GUI_CONTROL_LISTBOX:
  2116.             case GUI_CONTROL_EDIT:
  2117.             case GUI_CONTROL_DATETIME:
  2118.             case GUI_CONTROL_HOTKEY:
  2119.                 opt.height += GUI_CTL_VERTICAL_DEADSPACE;
  2120.                 if (style & WS_HSCROLL)
  2121.                     opt.height += GetSystemMetrics(SM_CYHSCROLL);
  2122.                 break;
  2123.             case GUI_CONTROL_BUTTON:
  2124.                 // Provide a extra space for top/bottom margin together, proportional to the current font
  2125.                 // size so that it looks better with very large or small fonts.  The +2 seems to make
  2126.                 // it look just right on all font sizes, especially the default GUI size of 8 where the
  2127.                 // height should be about 23 to be standard(?)
  2128.                 opt.height += sFont[mCurrentFontIndex].point_size + 2;
  2129.                 break;
  2130.             case GUI_CONTROL_GROUPBOX: // Since groups usually contain other controls, the below sizing seems best.
  2131.                 // Use row_count-2 because of the +1 added above for GUI_CONTROL_GROUPBOX.
  2132.                 // The current font's height is added in to provide an upper/lower margin in the box
  2133.                 // proportional to the current font size, which makes it look better in most cases:
  2134.                 opt.height += mMarginY * (2 + ((int)(opt.row_count + 0.5) - 2));
  2135.                 break;
  2136.             case GUI_CONTROL_TAB:
  2137.                 opt.height += mMarginY * (2 + ((int)(opt.row_count + 0.5) - 1)); // -1 vs. the -2 used above.
  2138.                 break;
  2139.             // Types not included
  2140.             // ------------------
  2141.             //case GUI_CONTROL_TEXT:     Uses basic height calculated above the switch().
  2142.             //case GUI_CONTROL_PIC:      Uses basic height calculated above the switch() (seems OK even for pic).
  2143.             //case GUI_CONTROL_CHECKBOX: Uses basic height calculated above the switch().
  2144.             //case GUI_CONTROL_RADIO:    Same.
  2145.             //case GUI_CONTROL_UPDOWN:   Same.
  2146.             //case GUI_CONTROL_SLIDER:   Same.
  2147.             //case GUI_CONTROL_PROGRESS: Same.
  2148.             //case GUI_CONTROL_MONTHCAL: Not included at all in this section because it treats "rows" differently.
  2149.             //case GUI_CONTROL_STATUSBAR: N/A
  2150.             } // switch
  2151.         }
  2152.         else // calc_control_height_from_row_count == false
  2153.             // Assign a default just to allow the control to be created successfully. 13 is the default
  2154.             // height of a text/radio control for the typical 8 point font size, but the exact value
  2155.             // shouldn't matter (within reason) since calc_control_height_from_row_count is telling us this type of
  2156.             // control will not obey the height anyway.  Update: It seems better to use a small constant
  2157.             // value to help catch bugs while still allowing the control to be created:
  2158.             if (!calc_height_later)
  2159.                 opt.height = 30;
  2160.             //else MONTHCAL and others must keep their "unspecified height" value for later detection.
  2161.     }
  2162.  
  2163.     bool control_width_was_set_by_contents = false;
  2164.  
  2165.     if (opt.height == COORD_UNSPECIFIED || opt.width == COORD_UNSPECIFIED)
  2166.     {
  2167.         // Set defaults:
  2168.         int extra_width = 0;
  2169.         UINT draw_format = DT_CALCRECT;
  2170.  
  2171.         switch (aControlType)
  2172.         {
  2173.         case GUI_CONTROL_EDIT:
  2174.             if (!*aText) // Only auto-calculate edit's dimensions if there is text to do it with.
  2175.                 break;
  2176.             // Since edit controls leave approximate 1 avg-char-width margin on the right side,
  2177.             // and probably exactly 4 pixels on the left counting its border and the internal
  2178.             // margin), adjust accordingly so that DrawText() will calculate the correct
  2179.             // control height based on word-wrapping.  Note: Can't use EM_GETRECT because
  2180.             // control doesn't exist yet (though that might be an alternative approach for
  2181.             // the future):
  2182.             GUI_SET_HDC
  2183.             GetTextMetrics(hdc, &tm);
  2184.             extra_width += 4 + tm.tmAveCharWidth;
  2185.             // Determine whether there will be a vertical scrollbar present.  If ES_MULTILINE hasn't
  2186.             // already been applied or auto-detected above, it's possible that a scrollbar will be
  2187.             // added later due to the text auto-wrapping.  In that case, the calculated height may
  2188.             // be incorrect due to the additional wrapping caused by the width taken up by the
  2189.             // scrollbar.  Since this combination of circumstances is rare, and since there are easy
  2190.             // workarounds, it's just documented here as a limitation:
  2191.             if (style & WS_VSCROLL)
  2192.                 extra_width += GetSystemMetrics(SM_CXVSCROLL);
  2193.             // DT_EDITCONTROL: "the average character width is calculated in the same manner as for an edit control"
  2194.             // It might help some aspects of the estimate conducted below.
  2195.             // Also include DT_EXPANDTABS under the assumption that if there are tabs present, the user
  2196.             // intended for them to be there because a multiline edit would expand them (rather than trying
  2197.             // to worry about whether this control *might* become auto-multiline after this point.
  2198.             draw_format |= DT_EXPANDTABS|DT_EDITCONTROL|DT_NOPREFIX; // v1.0.44.10: Added DT_NOPREFIX because otherwise, if the text contains & or &&, the control won't be sized properly.
  2199.             // and now fall through and have the dimensions calculated based on what's in the control.
  2200.             // ABOVE FALLS THROUGH TO BELOW
  2201.         case GUI_CONTROL_TEXT:
  2202.         case GUI_CONTROL_BUTTON:
  2203.         case GUI_CONTROL_CHECKBOX:
  2204.         case GUI_CONTROL_RADIO:
  2205.         {
  2206.             GUI_SET_HDC
  2207.             if (aControlType == GUI_CONTROL_TEXT)
  2208.             {
  2209.                 draw_format |= DT_EXPANDTABS; // Buttons can't expand tabs, so don't add this for them.
  2210.                 if (style & SS_NOPREFIX) // v1.0.44.10: This is necessary to auto-width the control properly if its contents include any ampersands.
  2211.                     draw_format |= DT_NOPREFIX;
  2212.             }
  2213.             else if (aControlType == GUI_CONTROL_CHECKBOX || aControlType == GUI_CONTROL_RADIO)
  2214.             {
  2215.                 // Both Checkbox and Radio seem to have the same spacing characteristics:
  2216.                 // Expand to allow room for button itself, its border, and the space between
  2217.                 // the button and the first character of its label (this space seems to
  2218.                 // be the same as tmAveCharWidth).  +2 seems to be needed to make it work
  2219.                 // for the various sizes of Courier New vs. Verdana that I tested.  The
  2220.                 // alternative, (2 * GetSystemMetrics(SM_CXEDGE)), seems to add a little
  2221.                 // too much width (namely 4 vs. 2).
  2222.                 GetTextMetrics(hdc, &tm);
  2223.                 extra_width += GetSystemMetrics(SM_CXMENUCHECK) + tm.tmAveCharWidth + 2; // v1.0.40.03: Reverted to +2 vs. +3 (it had been changed to +3 in v1.0.40.01).
  2224.             }
  2225.             if (opt.width != COORD_UNSPECIFIED) // Since a width was given, auto-expand the height via word-wrapping.
  2226.                 draw_format |= DT_WORDBREAK;
  2227.             RECT draw_rect;
  2228.             draw_rect.left = 0;
  2229.             draw_rect.top = 0;
  2230.             draw_rect.right = (opt.width == COORD_UNSPECIFIED) ? 0 : opt.width - extra_width; // extra_width
  2231.             draw_rect.bottom = (opt.height == COORD_UNSPECIFIED) ? 0 : opt.height;
  2232.             // If no text, "H" is used in case the function requires a non-empty string to give consistent results:
  2233.             int draw_height = DrawText(hdc, *aText ? aText : "H", -1, &draw_rect, draw_format);
  2234.             int draw_width = draw_rect.right - draw_rect.left;
  2235.             // Even if either height or width was already explicitly specified above, it seems best to
  2236.             // override it if DrawText() says it's not big enough.  REASONING: It seems too rare that
  2237.             // someone would want to use an explicit height/width to selectively hide part of a control's
  2238.             // contents, presumably for revelation later.  If that is truly desired, ControlMove or
  2239.             // similar can be used to resize the control afterward.  In addition, by specifying BOTH
  2240.             // width and height/rows, none of these calculations happens anyway, so that's another way
  2241.             // this override can be overridden.  UPDATE for v1.0.44.10: The override is now not done for Edit
  2242.             // controls because unlike the other control types enumerated above, it is much more common to
  2243.             // have an Edit not be tall enough to show all of it's initial text.  This fixes the following:
  2244.             //   Gui, Add, Edit, r2 ,Line1`nLine2`nLine3`nLine4
  2245.             // Since there's no explicit width above, the r2 option (or even an H option) would otherwise
  2246.             // be overridden in favor of making the edit tall enough to hold all 4 lines.
  2247.             // Another reason for not changing the other control types to be like Edit is that backward
  2248.             // compatibility probably outweighs any value added by changing them (and the added value is dubious
  2249.             // when the comments above are carefully considered).
  2250.             if (opt.height == COORD_UNSPECIFIED || (draw_height > opt.height && aControlType != GUI_CONTROL_EDIT))
  2251.             {
  2252.                 opt.height = draw_height;
  2253.                 if (aControlType == GUI_CONTROL_EDIT)
  2254.                 {
  2255.                     opt.height += GUI_CTL_VERTICAL_DEADSPACE;
  2256.                     if (style & WS_HSCROLL)
  2257.                         opt.height += GetSystemMetrics(SM_CYHSCROLL);
  2258.                 }
  2259.                 else if (aControlType == GUI_CONTROL_BUTTON)
  2260.                     opt.height += sFont[mCurrentFontIndex].point_size + 2;  // +2 makes it standard height.
  2261.             }
  2262.             if (opt.width == COORD_UNSPECIFIED || draw_width > opt.width)
  2263.             {
  2264.                 // v1.0.44.08: Fixed the following line by moving it into this IF block.  This prevents
  2265.                 // an up-down from widening its edit control when that edit control had an explicit width.
  2266.                 control_width_was_set_by_contents = true; // Indicate that this control was auto-width'd.
  2267.                 opt.width = draw_width + extra_width;  // See comments above for why specified width is overridden.
  2268.                 if (aControlType == GUI_CONTROL_BUTTON)
  2269.                     // Allow room for border and an internal margin proportional to the font height.
  2270.                     // Button's border is 3D by default, so SM_CXEDGE vs. SM_CXBORDER is used?
  2271.                     opt.width += 2 * GetSystemMetrics(SM_CXEDGE) + sFont[mCurrentFontIndex].point_size;
  2272.             }
  2273.             break;
  2274.         } // case for text/button/checkbox/radio
  2275.  
  2276.         // Types not included
  2277.         // ------------------
  2278.         //case GUI_CONTROL_PIC:           If needed, it is given some default dimensions at the time of creation.
  2279.         //case GUI_CONTROL_GROUPBOX:      Seems too rare than anyone would want its width determined by its text.
  2280.         //case GUI_CONTROL_EDIT:          It is included, but only if it has default text inside it.
  2281.         //case GUI_CONTROL_TAB:           Seems too rare than anyone would want its width determined by tab-count.
  2282.  
  2283.         //case GUI_CONTROL_LISTVIEW:      Has custom handling later below.
  2284.         //case GUI_CONTROL_TREEVIEW:      Same.
  2285.         //case GUI_CONTROL_MONTHCAL:      Same.
  2286.         //case GUI_CONTROL_STATUSBAR:     Ignores width/height, so no need to handle here.
  2287.  
  2288.         //case GUI_CONTROL_DROPDOWNLIST:  These last ones are given (later below) a standard width based on font size.
  2289.         //case GUI_CONTROL_COMBOBOX:      In addition, their height has already been determined further above.
  2290.         //case GUI_CONTROL_LISTBOX:
  2291.         //case GUI_CONTROL_DATETIME:
  2292.         //case GUI_CONTROL_HOTKEY:
  2293.         //case GUI_CONTROL_UPDOWN:
  2294.         //case GUI_CONTROL_SLIDER:
  2295.         //case GUI_CONTROL_PROGRESS:
  2296.         } // switch()
  2297.     }
  2298.  
  2299.     //////////////////////////////////////////////////////////////////////////////////////////
  2300.     // If the width was not specified and the above did not already determine it (which should
  2301.     // only be possible for the cases contained in the switch-stmt below), provide a default.
  2302.     //////////////////////////////////////////////////////////////////////////////////////////
  2303.     if (opt.width == COORD_UNSPECIFIED)
  2304.     {
  2305.         int gui_standard_width = GUI_STANDARD_WIDTH; // Resolve macro only once for performance/code size.
  2306.         switch(aControlType)
  2307.         {
  2308.         case GUI_CONTROL_DROPDOWNLIST:
  2309.         case GUI_CONTROL_COMBOBOX:
  2310.         case GUI_CONTROL_LISTBOX:
  2311.         case GUI_CONTROL_HOTKEY:
  2312.         case GUI_CONTROL_EDIT:
  2313.             opt.width = gui_standard_width;
  2314.             break;
  2315.         case GUI_CONTROL_LISTVIEW:
  2316.         case GUI_CONTROL_TREEVIEW:
  2317.         case GUI_CONTROL_DATETIME: // Seems better to have wider default to fit LongDate and because drop-down calendar is fairly wide (though the latter is a weak reason).
  2318.             opt.width = gui_standard_width * 2;
  2319.             break;
  2320.         case GUI_CONTROL_UPDOWN: // Iffy, but needs some kind of default?
  2321.             opt.width = (style & UDS_HORZ) ? gui_standard_width : PROGRESS_DEFAULT_THICKNESS; // Progress's default seems ok for up-down too.
  2322.             break;
  2323.         case GUI_CONTROL_SLIDER:
  2324.             // Make vertical trackbars narrow by default.  For vertical trackbars: there doesn't seem
  2325.             // to be much point in defaulting the width to something proportional to font size because
  2326.             // the thumb only seems to have two sizes and doesn't auto-grow any larger than that.
  2327.             opt.width = (style & TBS_VERT) ? ControlGetDefaultSliderThickness(style, opt.thickness) : gui_standard_width;
  2328.             break;
  2329.         case GUI_CONTROL_PROGRESS:
  2330.             opt.width = (style & PBS_VERTICAL) ? PROGRESS_DEFAULT_THICKNESS : gui_standard_width;
  2331.             break;
  2332.         case GUI_CONTROL_GROUPBOX:
  2333.             // Since groups and tabs contain other controls, allow room inside them for a margin based
  2334.             // on current font size.
  2335.             opt.width = gui_standard_width + (2 * mMarginX);
  2336.             break;
  2337.         case GUI_CONTROL_TAB:
  2338.             // Tabs tend to be wide so that that tabs can all fit on the top row, and because they
  2339.             // are usually used to fill up the entire window.  Therefore, default them to the ability
  2340.             // to hold two columns of standard-width controls:
  2341.             opt.width = (2 * gui_standard_width) + (3 * mMarginX);  // 3 vs. 2 to allow space in between columns.
  2342.             break;
  2343.         // Types not included
  2344.         // ------------------
  2345.         //case GUI_CONTROL_TEXT:      Exact width should already have been calculated based on contents.
  2346.         //case GUI_CONTROL_PIC:       Calculated based on actual pic size if no explicit width was given.
  2347.         //case GUI_CONTROL_BUTTON:    Exact width should already have been calculated based on contents.
  2348.         //case GUI_CONTROL_CHECKBOX:  Same.
  2349.         //case GUI_CONTROL_RADIO:     Same.
  2350.         //case GUI_CONTROL_MONTHCAL:  Exact width will be calculated after the control is created (size to fit month).
  2351.         //case GUI_CONTROL_STATUSBAR: Ignores width, so no need to handle here.
  2352.         }
  2353.     }
  2354.  
  2355.     /////////////////////////////////////////////////////////////////////////////////////////
  2356.     // For edit controls: If the above didn't already determine how many rows it should have,
  2357.     // auto-detect that by comparing the current font size with the specified height. At this
  2358.     // stage, the above has already ensured that an Edit has at least a height or a row_count.
  2359.     /////////////////////////////////////////////////////////////////////////////////////////
  2360.     if (aControlType == GUI_CONTROL_EDIT && !(style & ES_MULTILINE))
  2361.     {
  2362.         if (opt.row_count < 1) // Determine the row-count to auto-detect multi-line vs. single-line.
  2363.         {
  2364.             GUI_SET_HDC
  2365.             GetTextMetrics(hdc, &tm);
  2366.             int height_beyond_first_row = opt.height - GUI_CTL_VERTICAL_DEADSPACE - tm.tmHeight;
  2367.             if (style & WS_HSCROLL)
  2368.                 height_beyond_first_row -= GetSystemMetrics(SM_CYHSCROLL);
  2369.             if (height_beyond_first_row > 0)
  2370.             {
  2371.                 opt.row_count = 1 + ((float)height_beyond_first_row / (tm.tmHeight + tm.tmExternalLeading));
  2372.                 // This section is a near exact match for one higher above.  Search for comment
  2373.                 // "Add multiline unless it was explicitly removed" for a full explanation and keep
  2374.                 // the below in sync with that section above:
  2375.                 if (opt.row_count > 1.5)
  2376.                 {
  2377.                     style |= (ES_MULTILINE & ~opt.style_remove); // Add multiline unless it was explicitly removed.
  2378.                     // Do the below only if the above actually added multiline:
  2379.                     if (style & ES_MULTILINE) // If allowed, enable vertical scrollbar and capturing of ENTER keystrokes.
  2380.                         style |= EDIT_MULTILINE_DEFAULT & ~opt.style_remove;
  2381.                     // else: Single-line edit.  ES_AUTOHSCROLL will be applied later below if all the other checks
  2382.                     // fail to auto-detect this edit as a multi-line edit.
  2383.                 }
  2384.             }
  2385.             else // there appears to be only one row.
  2386.                 opt.row_count = 1;
  2387.                 // And ES_AUTOHSCROLL will be applied later below if all the other checks
  2388.                 // fail to auto-detect this edit as a multi-line edit.
  2389.         }
  2390.     }
  2391.  
  2392.     // If either height or width is still undetermined, leave it set to COORD_UNSPECIFIED since that
  2393.     // is a large negative number and should thus help catch bugs.  In other words, the above
  2394.     // hueristics should be designed to handle all cases and always resolve height/width to something,
  2395.     // with the possible exception of things that auto-size based on external content such as
  2396.     // GUI_CONTROL_PIC.
  2397.  
  2398.     //////////////////////
  2399.     //
  2400.     // CREATE THE CONTROL.
  2401.     //
  2402.     //////////////////////
  2403.     bool do_strip_theme = !opt.use_theme;   // Set defaults.
  2404.     bool retrieve_dimensions = false;       //
  2405.     int item_height, min_list_height;
  2406.     RECT rect;
  2407.     char *malloc_buf;
  2408.     HMENU control_id = (HMENU)(size_t)GUI_INDEX_TO_ID(mControlCount); // Cast to size_t avoids compiler warning.
  2409.  
  2410.     bool font_was_set = false;          // "
  2411.     bool is_parent_visible = IsWindowVisible(mHwnd) && !IsIconic(mHwnd);
  2412.     #define GUI_SETFONT \
  2413.     {\
  2414.         SendMessage(control.hwnd, WM_SETFONT, (WPARAM)sFont[mCurrentFontIndex].hfont, is_parent_visible);\
  2415.         font_was_set = true;\
  2416.     }
  2417.  
  2418.     // If a control is being added to a tab, even if the parent window is hidden (since it might
  2419.     // have been hidden by Gui, Cancel), make sure the control isn't visible unless it's on a
  2420.     // visible tab.
  2421.     // The below alters style vs. style_remove, since later below style_remove is checked to
  2422.     // find out if the control was explicitly hidden vs. hidden by the automatic action here:
  2423.     bool on_visible_page_of_tab_control = false;
  2424.     if (control.tab_control_index < MAX_TAB_CONTROLS) // This control belongs to a tab control (must check this even though FindTabControl() does too).
  2425.     {
  2426.         if (owning_tab_control) // Its tab control exists...
  2427.         {
  2428.             if (!(GetWindowLong(owning_tab_control->hwnd, GWL_STYLE) & WS_VISIBLE) // Don't use IsWindowVisible().
  2429.                 || TabCtrl_GetCurSel(owning_tab_control->hwnd) != control.tab_index)
  2430.                 // ... but it's not set to the page/tab that contains this control, or the entire tab control is hidden.
  2431.                 style &= ~WS_VISIBLE;
  2432.             else // Make the following true as long as the parent is also visible.
  2433.                 on_visible_page_of_tab_control = is_parent_visible;  // For use later below.
  2434.         }
  2435.         else // Its tab control does not exist, so this control is kept hidden until such time that it does.
  2436.             style &= ~WS_VISIBLE;
  2437.     }
  2438.     // else do nothing.
  2439.  
  2440.     switch(aControlType)
  2441.     {
  2442.     case GUI_CONTROL_TEXT:
  2443.         // Seems best to omit SS_NOPREFIX by default so that ampersand can be used to create shortcut keys.
  2444.         control.hwnd = CreateWindowEx(exstyle, "static", aText, style
  2445.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL);
  2446.         break;
  2447.  
  2448.     case GUI_CONTROL_PIC:
  2449.         if (opt.width == COORD_UNSPECIFIED)
  2450.             opt.width = 0;  // Use zero to tell LoadPicture() to keep original width.
  2451.         if (opt.height == COORD_UNSPECIFIED)
  2452.             opt.height = 0;  // Use zero to tell LoadPicture() to keep original height.
  2453.         // Must set its caption to aText so that documented ability to refer to a picture by its original
  2454.         // filename is possible:
  2455.         if (control.hwnd = CreateWindowEx(exstyle, "static", aText, style
  2456.             , opt.x, opt.y, opt.width, opt.height  // OK if zero, control creation should still succeed.
  2457.             , mHwnd, control_id, g_hInstance, NULL))
  2458.         {
  2459.             // In light of the below, it seems best to delete the bitmaps whenever the control changes
  2460.             // to a new image or whenever the control is destroyed.  Otherwise, if a control or its
  2461.             // parent window is destroyed and recreated many times, memory allocation would continue
  2462.             // to grow from all the abandoned pointers.
  2463.             // MSDN: "When you are finished using a bitmap...loaded without specifying the LR_SHARED flag,
  2464.             // you can release its associated memory by calling...DeleteObject."
  2465.             // MSDN: "The system automatically deletes these resources when the process that created them
  2466.             // terminates, however, calling the appropriate function saves memory and decreases the size
  2467.             // of the process's working set."
  2468.             // LoadPicture() uses CopyImage() to scale the image, which seems to provide better scaling
  2469.             // quality than using MoveWindow() (followed by redrawing the parent window) on the static
  2470.             // control that contains the image.
  2471.             int image_type;
  2472.             if (   !(control.union_hbitmap = LoadPicture(aText, opt.width, opt.height, image_type, opt.icon_number
  2473.                 , control.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT))   )
  2474.                 break;  // By design, no error is reported.  The picture is simply not displayed, nor is its
  2475.                         // style set to SS_BITMAP/SS_ICON, which allows the control to have the specified size
  2476.                         // yet lack an image (SS_BITMAP/SS_ICON tend to cause the control to auto-size to
  2477.                         // zero dimensions).
  2478.             // For image to display correctly, must apply SS_ICON for cursors/icons and SS_BITMAP for bitmaps.
  2479.             // This style change is made *after* the control is created so that the act of creation doesn't
  2480.             // attempt to load the image from a resource (which as documented by SS_ICON/SS_BITMAP, would happen
  2481.             // since text is interpreted as the name of a bitmap in the resource file).
  2482.             SetWindowLong(control.hwnd, GWL_STYLE, style | (image_type == IMAGE_BITMAP ? SS_BITMAP : SS_ICON));
  2483.             // Above uses ~0x0F to ensure the lowest four/five bits are pure.
  2484.             // Also note that it does not seem correct to use SS_TYPEMASK if bitmaps/icons can also have
  2485.             // any of the following styles.  MSDN confirms(?) this by saying that SS_TYPEMASK is out of date
  2486.             // and should not be used:
  2487.             //#define SS_ETCHEDHORZ       0x00000010L
  2488.             //#define SS_ETCHEDVERT       0x00000011L
  2489.             //#define SS_ETCHEDFRAME      0x00000012L
  2490.             SendMessage(control.hwnd, STM_SETIMAGE, (WPARAM)image_type, (LPARAM)control.union_hbitmap);
  2491.             if (image_type == IMAGE_BITMAP)
  2492.                 control.attrib &= ~GUI_CONTROL_ATTRIB_ALTBEHAVIOR;  // Flag it as a bitmap so that DeleteObject vs. DestroyIcon will be called for it.
  2493.             else // Cursor or Icon, which are functionally identical for our purposes.
  2494.                 control.attrib |= GUI_CONTROL_ATTRIB_ALTBEHAVIOR;
  2495.             // UPDATE ABOUT THE BELOW: Rajat says he can't get the Smart GUI working without
  2496.             // the controls retaining their original numbering/z-order.  This has to do with the fact
  2497.             // that TEXT controls and PIC controls are both static.  If only PIC controls were reordered,
  2498.             // that's not enought to make things work for him.  If only TEXT controls were ALSO reordered
  2499.             // to be at the top of the list (so that all statics retain their original ordering with
  2500.             // respect to each other) I have a 90% expectation (based on testing) that prefix/shortcut
  2501.             // keys inside static text controls would jump to the wrong control because their associated
  2502.             // control seems to be based solely on z-order.  ANOTHER REASON NOT to ever change the z-order
  2503.             // of controls automatically: Every time a picture is added, it would increment the z-order
  2504.             // number of all other control types by 1.  This is bad especially for text controls because
  2505.             // they are static just like pictures, and thus their Class+NN "unique ID" would change (as
  2506.             // seen by the ControlXXX commands) if a picture were ever added after a window was shown.
  2507.             // Older note: calling SetWindowPos() with HWND_TOPMOST doesn't seem to provide any useful
  2508.             // effect that I could discern (by contrast, HWND_TOP does actually move a control to the
  2509.             // top of the z-order).
  2510.             // The below is OBSOLETE and its code further below is commented out:
  2511.             // Facts about how overlapping controls are drawn vs. which one receives mouse clicks:
  2512.             // 1) The first control created is at the top of the Z-order (i.e. the lowest z-order number
  2513.             //    and the first in tab navigation), the second is next, and so on.
  2514.             // 2) Controls get drawn in ascending Z-order (i.e. the first control is drawn first
  2515.             //    and any later controls that overlap are drawn on top of it, except for controls that
  2516.             //    have WS_CLIPSIBLINGS).
  2517.             // 3) When a user clicks a point that contains two overlapping controls and each control is
  2518.             //    capable of capturing clicks, the one closer to the top captures the click even though it
  2519.             //    was drawn beneath (overlapped by) the other control.
  2520.             // Because of this behavior, the following policy seems best:
  2521.             // 1) Move all static images to the top of the Z-order so that other controls are always
  2522.             //    drawn on top of them.  This is done because it seems to be the behavior that would
  2523.             //    be desired at least 90% of the time.
  2524.             // 2) Do not do the same for static text and GroupBoxes because it seems too rare that
  2525.             //    overlapping would be done in such cases, and even if it is done it seems more
  2526.             //    flexible to allow the order in which the controls were created to determine how they
  2527.             //    overlap and which one get the clicks.
  2528.             //
  2529.             // Rather than push static pictures to the top in the reverse order they were created -- 
  2530.             // which might be a little more intuitive since the ones created first would then always
  2531.             // be "behind" ones created later -- for simplicity, we do it here at the time the control
  2532.             // is created.  This avoids complications such as a picture being added after the
  2533.             // window is shown for the first time and then not getting sent to the top where it
  2534.             // should be.  Update: The control is now kept in its original order for two reasons:
  2535.             // 1) The reason mentioned above (that later images should overlap earlier images).
  2536.             // 2) Allows Rajat's SmartGUI Creator to support picture controls (due to its grid background pic).
  2537.             // First find the last picture control in the array.  The last one should already be beneath
  2538.             // all the others in the z-order due to having been originally added using the below method.
  2539.             // Might eventually need to switch to EnumWindows() if it's possible for Z-order to be
  2540.             // altered, or controls to be insertted between others, by any commands in the future.
  2541.             //GuiIndexType index_of_last_picture = UINT_MAX;
  2542.             //for (u = 0; u < mControlCount; ++u)
  2543.             //{
  2544.             //    if (mControl[u].type == GUI_CONTROL_PIC)
  2545.             //        index_of_last_picture = u;
  2546.             //}
  2547.             //if (index_of_last_picture == UINT_MAX) // There are no other pictures, so put this one on top.
  2548.             //    SetWindowPos(control.hwnd, HWND_TOP, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_NOMOVE|SWP_NOSIZE);
  2549.             //else // Put this picture after the last picture in the z-order.
  2550.             //    SetWindowPos(control.hwnd, mControl[index_of_last_picture].hwnd, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_NOMOVE|SWP_NOSIZE);
  2551.             //// Adjust to control's actual size in case it changed for any reason (failure to load picture, etc.)
  2552.             retrieve_dimensions = true;
  2553.         }
  2554.         break;
  2555.  
  2556.     case GUI_CONTROL_GROUPBOX:
  2557.         // In this case, BS_MULTILINE will obey literal newlines in the text, but it does not automatically
  2558.         // wrap the text, at least on XP.  Since it's strange-looking to have multiple lines, newlines
  2559.         // should be rarely present anyway.  Also, BS_NOTIFY seems to have no effect on GroupBoxes (it
  2560.         // never sends any BN_CLICKED/BN_DBLCLK messages).  This has been verified twice.
  2561.         control.hwnd = CreateWindowEx(exstyle, "button", aText, style
  2562.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL);
  2563.         break;
  2564.  
  2565.     case GUI_CONTROL_BUTTON:
  2566.         // For all "button" type controls, BS_MULTILINE is included by default so that any literal
  2567.         // newlines in the button's name will start a new line of text as the user intended.
  2568.         // In addition, this causes automatic wrapping to occur if the user specified a width
  2569.         // too small to fit the entire line.
  2570.         if (control.hwnd = CreateWindowEx(exstyle, "button", aText, style
  2571.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  2572.         {
  2573.             if (style & BS_DEFPUSHBUTTON)
  2574.             {
  2575.                 // First remove the style from the old default button, if there is one:
  2576.                 if (mDefaultButtonIndex < mControlCount)
  2577.                 {
  2578.                     // MSDN says this is necessary in some cases:
  2579.                     // Since the window might be visbible at this point, send BM_SETSTYLE rather than
  2580.                     // SetWindowLong() so that the button will get redrawn.  Update: The redraw doesn't
  2581.                     // actually seem to happen (both the old and the new buttons retain the default-button
  2582.                     // appearance until the window gets entirely redraw such as via alt-tab).  This is fairly
  2583.                     // inexplicable since the exact same technique works with "GuiControl +Default".  In any
  2584.                     // case, this is kept because it also serves to change the default button appearance later,
  2585.                     // which is received in the WindowProc via WM_COMMAND:
  2586.                     SendMessage(mControl[mDefaultButtonIndex].hwnd, BM_SETSTYLE
  2587.                         , (WPARAM)LOWORD((GetWindowLong(mControl[mDefaultButtonIndex].hwnd, GWL_STYLE) & ~BS_DEFPUSHBUTTON))
  2588.                         , MAKELPARAM(TRUE, 0)); // Redraw = yes. It's probably smart enough not to do it if the window is hidden.
  2589.                     // The below attempts to get the old button to lose its default-border failed.  This might
  2590.                     // be due to the fact that if the window hasn't yet been shown for the first time, its
  2591.                     // client area isn't yet the right size, so the OS decides that no update is needed since
  2592.                     // the control is probably outside the boundaries of the window:
  2593.                     //InvalidateRect(mHwnd, NULL, TRUE);
  2594.                     //GetClientRect(mControl[mDefaultButtonIndex].hwnd, &client_rect);
  2595.                     //InvalidateRect(mControl[mDefaultButtonIndex].hwnd, &client_rect, TRUE);
  2596.                     //ShowWindow(mHwnd, SW_SHOWNOACTIVATE); // i.e. don't activate it if it wasn't before.
  2597.                     //ShowWindow(mHwnd, SW_HIDE);
  2598.                     //UpdateWindow(mHwnd);
  2599.                     //SendMessage(mHwnd, WM_NCPAINT, 1, 0);
  2600.                     //RedrawWindow(mHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW);
  2601.                     //SetWindowPos(mHwnd, NULL, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
  2602.                 }
  2603.                 mDefaultButtonIndex = mControlCount;
  2604.                 SendMessage(mHwnd, DM_SETDEFID, (WPARAM)GUI_INDEX_TO_ID(mDefaultButtonIndex), 0);
  2605.                 // Strangely, in spite of the control having been created with the BS_DEFPUSHBUTTON style,
  2606.                 // need to send BM_SETSTYLE or else the default button will lack its visual style when the
  2607.                 // dialog is first shown.  Also strange is that the following must be done *after*
  2608.                 // removing the visual/default style from the old default button and/or after doing
  2609.                 // DM_SETDEFID above.
  2610.                 SendMessage(control.hwnd, BM_SETSTYLE, (WPARAM)LOWORD(style), MAKELPARAM(TRUE, 0)); // Redraw = yes. It's probably smart enough not to do it if the window is hidden.
  2611.             }
  2612.         }
  2613.         break;
  2614.  
  2615.     case GUI_CONTROL_CHECKBOX:
  2616.         // The BS_NOTIFY style is not a good idea for checkboxes because although it causes the control
  2617.         // to send BN_DBLCLK messages, any rapid clicks by the user on (for example) a tri-state checkbox
  2618.         // are seen only as one click for the purpose of changing the box's state.
  2619.         if (control.hwnd = CreateWindowEx(exstyle, "button", aText, style
  2620.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  2621.         {
  2622.             if (opt.checked != BST_UNCHECKED) // Set the specified state.
  2623.                 SendMessage(control.hwnd, BM_SETCHECK, opt.checked, 0);
  2624.         }
  2625.         break;
  2626.  
  2627.     case GUI_CONTROL_RADIO:
  2628.         control.hwnd = CreateWindowEx(exstyle, "button", aText, style
  2629.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL);
  2630.         // opt.checked is handled later below.
  2631.         break;
  2632.  
  2633.     case GUI_CONTROL_DROPDOWNLIST:
  2634.     case GUI_CONTROL_COMBOBOX:
  2635.         // It has been verified that that EM_LIMITTEXT has no effect when sent directly
  2636.         // to a ComboBox hwnd; however, it might work if sent to its edit-child. But for now,
  2637.         // a Combobox can only be limited to its visible width.  Later, there might
  2638.         // be a way to send a message to its child control to limit its width directly.
  2639.         if (opt.limit && control.type == GUI_CONTROL_COMBOBOX)
  2640.             style &= ~CBS_AUTOHSCROLL;
  2641.         // Since the control's variable can change, it seems best to pass in the empty string
  2642.         // as the control's caption, rather than the name of the variable.  The name of the variable
  2643.         // isn't that useful anymore anyway since GuiControl(Get) can access controls directly by
  2644.         // their current output-var names:
  2645.         if (control.hwnd = CreateWindowEx(exstyle, "Combobox", "", style
  2646.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  2647.         {
  2648.             // Set font unconditionally to simplify calculations, which help ensure that at least one item
  2649.             // in the DropDownList/Combo is visible when the list drops down:
  2650.             GUI_SETFONT // Set font in preparation for asking it how tall each item is.
  2651.             if (calc_control_height_from_row_count)
  2652.             {
  2653.                 item_height = (int)SendMessage(control.hwnd, CB_GETITEMHEIGHT, 0, 0);
  2654.                 // Note that at this stage, height should contain a explicitly-specified height or height
  2655.                 // estimate from the above, even if row_count is greater than 0.
  2656.                 // The below calculation may need some fine tuning:
  2657.                 int cbs_extra_height = ((style & CBS_SIMPLE) && !(style & CBS_DROPDOWN)) ? 4 : 2;
  2658.                 min_list_height = (2 * item_height) + GUI_CTL_VERTICAL_DEADSPACE + cbs_extra_height;
  2659.                 if (opt.height < min_list_height) // Adjust so that at least 1 item can be shown.
  2660.                     opt.height = min_list_height;
  2661.                 else if (opt.row_count > 0)
  2662.                     // Now that we know the true item height (since the control has been created and we asked
  2663.                     // it), resize the control to try to get it to the match the specified number of rows.
  2664.                     // +2 seems to be the exact amount needed to prevent partial rows from showing
  2665.                     // on all font sizes and themes when NOINTEGRALHEIGHT is in effect:
  2666.                     opt.height = (int)(opt.row_count * item_height) + GUI_CTL_VERTICAL_DEADSPACE + cbs_extra_height;
  2667.             }
  2668.             MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint, since it might be visible.
  2669.             // Since combo's size is created to accomodate its drop-down height, adjust our true height
  2670.             // to its actual collapsed size.  This true height is used for auto-positioning the next
  2671.             // control, if it uses auto-positioning.  It might be possible for it's width to be different
  2672.             // also, such as if it snaps to a certain minimize width if one too small was specified,
  2673.             // so that is recalculated too:
  2674.             retrieve_dimensions = true;
  2675.         }
  2676.         break;
  2677.  
  2678.     case GUI_CONTROL_LISTBOX:
  2679.         // See GUI_CONTROL_COMBOBOX above for why empty string is passed in as the caption:
  2680.         if (control.hwnd = CreateWindowEx(exstyle, "Listbox", "", style
  2681.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  2682.         {
  2683.             if (opt.tabstop_count)
  2684.                 SendMessage(control.hwnd, LB_SETTABSTOPS, opt.tabstop_count, (LPARAM)opt.tabstop);
  2685.             // For now, it seems best to always override a height that would cause zero items to be
  2686.             // displayed.  This is because there is a very thin control visible even if the height
  2687.             // is explicitly set to zero, which seems pointless (there are other ways to draw thin
  2688.             // looking objects for unusual purposes anyway).
  2689.             // Set font unconditionally to simplify calculations, which help ensure that at least one item
  2690.             // in the DropDownList/Combo is visible when the list drops down:
  2691.             GUI_SETFONT // Set font in preparation for asking it how tall each item is.
  2692.             item_height = (int)SendMessage(control.hwnd, LB_GETITEMHEIGHT, 0, 0);
  2693.             // Note that at this stage, height should contain a explicitly-specified height or height
  2694.             // estimate from the above, even if opt.row_count is greater than 0.
  2695.             min_list_height = item_height + GUI_CTL_VERTICAL_DEADSPACE;
  2696.             if (style & WS_HSCROLL)
  2697.                 // Assume bar will be actually appear even though it won't in the rare case where
  2698.                 // its specified pixel-width is smaller than the width of the window:
  2699.                 min_list_height += GetSystemMetrics(SM_CYHSCROLL);
  2700.             if (opt.height < min_list_height) // Adjust so that at least 1 item can be shown.
  2701.                 opt.height = min_list_height;
  2702.             else if (opt.row_count > 0)
  2703.             {
  2704.                 // Now that we know the true item height (since the control has been created and we asked
  2705.                 // it), resize the control to try to get it to the match the specified number of rows.
  2706.                 opt.height = (int)(opt.row_count * item_height) + GUI_CTL_VERTICAL_DEADSPACE;
  2707.                 if (style & WS_HSCROLL)
  2708.                     // Assume bar will be actually appear even though it won't in the rare case where
  2709.                     // its specified pixel-width is smaller than the width of the window:
  2710.                 opt.height += GetSystemMetrics(SM_CYHSCROLL);
  2711.             }
  2712.             MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint, since it might be visible.
  2713.             // Since by default, the OS adjusts list's height to prevent a partial item from showing
  2714.             // (LBS_NOINTEGRALHEIGHT), fetch the actual height for possible use in positioning the
  2715.             // next control:
  2716.             retrieve_dimensions = true;
  2717.         }
  2718.         break;
  2719.  
  2720.     case GUI_CONTROL_LISTVIEW:
  2721.         if (opt.listview_view != LV_VIEW_TILE) // It was ensured earlier that listview_view can be set to LV_VIEW_TILE only for XP or later.
  2722.             style = (style & ~LVS_TYPEMASK) | opt.listview_view; // Create control in the correct view mode whenever possible (TILE is the exception because it can't be expressed via style).
  2723.         if (control.hwnd = CreateWindowEx(exstyle, WC_LISTVIEW, "", style, opt.x, opt.y // exstyle does apply to ListViews.
  2724.             , opt.width, opt.height == COORD_UNSPECIFIED ? 200 : opt.height, mHwnd, control_id, g_hInstance, NULL))
  2725.         {
  2726.             if (   !(control.union_lv_attrib = (lv_attrib_type *)malloc(sizeof(lv_attrib_type)))   )
  2727.             {
  2728.                 // Since mem alloc problem is so rare just get rid of the control and flag it to be reported
  2729.                 // later below as "cannot create control".  Doing this avoids the need to every worry whether
  2730.                 // control.union_lv_attrib is NULL in other places.
  2731.                 DestroyWindow(control.hwnd);
  2732.                 control.hwnd = NULL;
  2733.                 break;
  2734.             }
  2735.             // Otherwise:
  2736.             mCurrentListView = &control;
  2737.             ZeroMemory(control.union_lv_attrib, sizeof(lv_attrib_type));
  2738.             control.union_lv_attrib->sorted_by_col = -1; // Indicate that there is currently no sort order.
  2739.             control.union_lv_attrib->no_auto_sort = opt.listview_no_auto_sort;
  2740.  
  2741.             // v1.0.36.06: If this ListView is owned by a tab control, flag that tab control as needing
  2742.             // to stay after all of its controls in the z-order.  This solves ListView-inside-Tab redrawing
  2743.             // problems, namely the disappearance of the ListView or an incomplete drawing of it.
  2744.             if (owning_tab_control)
  2745.                 owning_tab_control->attrib |= GUI_CONTROL_ATTRIB_ALTBEHAVIOR;
  2746.  
  2747.             // Seems best to put tile view into effect before applying any styles that might be dependent upon it:
  2748.             if (opt.listview_view == LV_VIEW_TILE) // An earlier stage has verified that this is true only if OS is XP or later.
  2749.                 SendMessage(control.hwnd, LVM_SETVIEW, LV_VIEW_TILE, 0);
  2750.             if (opt.listview_style) // This is a third set of styles that exist in addition to normal & extended.
  2751.                 ListView_SetExtendedListViewStyle(control.hwnd, opt.listview_style); // No return value. Will have no effect on Win95/NT that lack comctl32.dll 4.70+ distributed with MSIE 3.x.
  2752.             ControlSetListViewOptions(control, opt); // Relies on adjustments to opt.color_changed and color_bk done higher above.
  2753.  
  2754.             if (opt.height == COORD_UNSPECIFIED) // Adjust the control's size to fit opt.row_count rows.
  2755.             {
  2756.                 // Known limitation: This will be inaccurate if an ImageList is later assigned to the
  2757.                 // ListView because that increases the height of each row slightly (or a lot if
  2758.                 // a large-icon list is forced into Details/Report view and other views that are
  2759.                 // traditionally small-icon).  The code size and complexity of trying to compensate
  2760.                 // for this doesn't seem likely to be worth it.
  2761.                 GUI_SETFONT  // Required before asking it for a height estimate.
  2762.                 switch (opt.listview_view)
  2763.                 {
  2764.                 case LVS_REPORT:
  2765.                     // The following formula has been tested on XP with the point sizes 8, 9, 10, 12, 14, and 18 for:
  2766.                     // Verdana
  2767.                     // Courier New
  2768.                     // Gui Default Font
  2769.                     // Times New Roman
  2770.                     opt.height = 4 + HIWORD(ListView_ApproximateViewRect(control.hwnd, -1, -1
  2771.                         , (WPARAM)opt.row_count - 1)); // -1 seems to be needed to make it calculate right for LVS_REPORT.
  2772.                     // Above: It seems best to exclude any horiz. scroll bar from consideration, even though it will
  2773.                     // block the last row if bar is present.  The bar can be dismissed by manually dragging the
  2774.                     // column dividers or using the GuiControl auto-size methods.
  2775.                     // Note that ListView_ApproximateViewRect() is not available on 95/NT4 that lack
  2776.                     // comctl32.dll 4.70+ distributed with MSIE 3.x  Therefore, rather than having a possibly-
  2777.                     // complicated work around in the code to detect DLL version, it will be documented in
  2778.                     // the help file that the "rows" method will produce an incorrect height on those platforms.
  2779.                     break;
  2780.  
  2781.                 case LV_VIEW_TILE: // This one can be safely integrated with the LVS ones because it doesn't overlap with them.
  2782.                     // The following approach doesn't seem to give back useful info about the total height of a
  2783.                     // tile and the border beneath it, so it isn't used:
  2784.                     //LVTILEVIEWINFO tvi;
  2785.                     //tvi.cbSize = sizeof(LVTILEVIEWINFO);
  2786.                     //tvi.dwMask = LVTVIM_TILESIZE | LVTVIM_LABELMARGIN;
  2787.                     //ListView_GetTileViewInfo(control.hwnd, &tvi);
  2788.                     // The following might not be perfect for integral scrolling purposes, but it does seem
  2789.                     // correct in terms of allowing exactly the right number of rows to be visible when the
  2790.                     // control is scrolled all the way to the top. It's also correct for eliminating a
  2791.                     // vertical scroll bar if the icons all fit into the specified number of rows. Tested on
  2792.                     // XP Theme and Classic theme.
  2793.                     opt.height = 7 + (int)((HIWORD(ListView_GetItemSpacing(control.hwnd, FALSE)) - 3) * opt.row_count);
  2794.                     break;
  2795.  
  2796.                 default: // Namely the following:
  2797.                 //case LVS_ICON:
  2798.                 //case LVS_SMALLICON:
  2799.                 //case LVS_LIST:
  2800.                     // For these non-report views, it seems far better to define row_count as the number of
  2801.                     // icons that can fit vertically rather than as the total number of icons, because the
  2802.                     // latter can result in heights that vary based on too many factors, resulting in too
  2803.                     // much inconsistency.
  2804.                     GUI_SET_HDC
  2805.                     GetTextMetrics(hdc, &tm);
  2806.                     if (opt.listview_view == LVS_ICON)
  2807.                     {
  2808.                         // The vertical space between icons is not dependent upon font size.  In other words,
  2809.                         // the control's total height to fit exactly N rows would be icon_height*N plus
  2810.                         // icon_spacing*(N-1).  However, the font height is added so that the last row has
  2811.                         // enough extra room to display one line of text beneath the icon.  The first/constant
  2812.                         // number below is a combination of two components: 1) The control's internal margin
  2813.                         // that it maintains to decide when to display scroll bars (perhaps 3 above and 3 below).
  2814.                         // The space between the icon and its first line of text (which seems constant, perhaps 10).
  2815.                         opt.height = 16 + tm.tmHeight + (int)(GetSystemMetrics(SM_CYICON) * opt.row_count
  2816.                             + HIWORD(ListView_GetItemSpacing(control.hwnd, FALSE) * (opt.row_count - 1)));
  2817.                         // More complex and doesn't seem as accurate:
  2818.                         //float half_icon_spacing = 0.5F * HIWORD(ListView_GetItemSpacing(control.hwnd, FALSE));
  2819.                         //opt.height = (int)(((HIWORD(ListView_ApproximateViewRect(control.hwnd, 5, 5, 1)) 
  2820.                         //    + half_icon_spacing + 4) * opt.row_count) + ((half_icon_spacing - 17) * (opt.row_count - 1)));
  2821.                     }
  2822.                     else // SMALLICON or LIST. For simplicity, it's done the same way for both, though it doesn't work as well for LIST.
  2823.                     {
  2824.                         // Seems way too high even with "TRUE": HIWORD(ListView_GetItemSpacing(control.hwnd, TRUE)
  2825.                         int cy_smicon = GetSystemMetrics(SM_CYSMICON);
  2826.                         // 11 seems to be the right value to prevent unwanted vertical scroll bar in SMALLICON view:
  2827.                         opt.height = 11 + (int)((cy_smicon > tm.tmHeight ? cy_smicon : tm.tmHeight) * opt.row_count
  2828.                             + 1 * (opt.row_count - 1));
  2829.                         break;
  2830.                     }
  2831.                     break;
  2832.                 } // switch()
  2833.                 MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint should be smart enough not to do it if window is hidden.
  2834.             } // if (opt.height == COORD_UNSPECIFIED)
  2835.         } // CreateWindowEx() succeeded.
  2836.         break;
  2837.  
  2838.     case GUI_CONTROL_TREEVIEW:
  2839.         if (control.hwnd = CreateWindowEx(exstyle, WC_TREEVIEW, "", style, opt.x, opt.y
  2840.             , opt.width, opt.height == COORD_UNSPECIFIED ? 200 : opt.height, mHwnd, control_id, g_hInstance, NULL))
  2841.         {
  2842.             mCurrentTreeView = &control;
  2843.             if (opt.checked)
  2844.                 // Testing confirms that unless the following advice is applied, an item's checkbox cannot
  2845.                 // be checked immediately after the item is created:
  2846.                 // MSDN: If you want to use the checkbox style, you must set the TVS_CHECKBOXES style (with
  2847.                 // SetWindowLong) after you create the tree-view control and before you populate the tree.
  2848.                 // Otherwise, the checkboxes might appear unchecked, depending on timing issues.
  2849.                 SetWindowLong(control.hwnd, GWL_STYLE, style | TVS_CHECKBOXES);
  2850.             ControlSetTreeViewOptions(control, opt); // Relies on adjustments to opt.color_changed and color_bk done higher above.
  2851.             if (opt.himagelist) // Currently only supported upon creation, not via GuiControl, since in that case the decision of whether to destroy the old imagelist would be uncertain.
  2852.                 TreeView_SetImageList(control.hwnd, opt.himagelist, TVSIL_NORMAL); // Currently no error reporting.
  2853.  
  2854.             if (opt.height == COORD_UNSPECIFIED) // Adjust the control's size to fit opt.row_count rows.
  2855.             {
  2856.                 // Known limitation (may exist for TreeViews the same as it does for ListViews):
  2857.                 // The follow might be inaccurate if an ImageList is later assigned to the TreeView because
  2858.                 // that may increase the height of each row. The code size and complexity of trying to
  2859.                 // compensate for this doesn't seem likely to be worth it.
  2860.                 GUI_SETFONT  // Required before asking it for a height estimate.
  2861.                 opt.height = TreeView_GetItemHeight(control.hwnd);
  2862.                 if (opt.height < 2) // Win95/NT without MSIE 4.0+ DLLs will probably yield 0 since this will send a message the control doesn't recognize.
  2863.                     opt.height = 2 * sFont[mCurrentFontIndex].point_size; // Crude estimate seems justified given rarity of lacking updated DLLs on 95/NT. Actuals for Verdana/DefaultGuiFont: 8 -> 16/16; 10 -> 18/18; 12 -> 20/22
  2864.                 // The following formula has been tested on XP fonts DefaultGUI, Verdana, Courier (for a few
  2865.                 // point sizes).
  2866.                 opt.height = 4 + (int)(opt.row_count * opt.height);
  2867.                 // Above: It seems best to exclude any horiz. scroll bar from consideration, even though it will
  2868.                 // block the last row if bar is present.  The bar can be dismissed by manually dragging the
  2869.                 // column dividers or using the GuiControl auto-size methods.
  2870.                 // Note that ListView_ApproximateViewRect() is not available on 95/NT4 that lack
  2871.                 // comctl32.dll 4.70+ distributed with MSIE 3.x  Therefore, rather than having a possibly-
  2872.                 // complicated work around in the code to detect DLL version, it will be documented in
  2873.                 // the help file that the "rows" method will produce an incorrect height on those platforms.
  2874.                 MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint should be smart enough not to do it if window is hidden.
  2875.             } // if (opt.height == COORD_UNSPECIFIED)
  2876.         } // CreateWindowEx() succeeded.
  2877.     break;
  2878.  
  2879.     case GUI_CONTROL_EDIT:
  2880.         if (!(style & ES_MULTILINE)) // ES_MULTILINE was not explicitly or automatically specified.
  2881.         {
  2882.             if (opt.limit < 0) // This is the signal to limit input length to visible width of field.
  2883.                 // But it can only work if the Edit isn't a multiline.
  2884.                 style &= ~(WS_HSCROLL|ES_AUTOHSCROLL); // Enable the limiting style.
  2885.             else // Since this is a single-line edit, add AutoHScroll if it wasn't explicitly removed.
  2886.                 style |= ES_AUTOHSCROLL & ~opt.style_remove;
  2887.                 // But no attempt is made to turn off WS_VSCROLL or ES_WANTRETURN since those might have some
  2888.                 // usefulness even in a single-line edit?  In any case, it seems too overprotective to do so.
  2889.         }
  2890.         // malloc() is done because edit controls in NT/2k/XP support more than 64K.
  2891.         // Mem alloc errors are so rare (since the text is usually less than 32K/64K) that no error is displayed.
  2892.         // Instead, the un-translated text is put in directly.  Also, translation is not done for
  2893.         // single-line edits since they can't display linebreaks correctly anyway.
  2894.         // Note that TranslateLFtoCRLF() will return the original buffer we gave it if no translation
  2895.         // is needed.  Otherwise, it will return a new buffer which we are responsible for freeing
  2896.         // when done (or NULL if it failed to allocate the memory).
  2897.         malloc_buf = (*aText && (style & ES_MULTILINE)) ? TranslateLFtoCRLF(aText) : aText;
  2898.         if (control.hwnd = CreateWindowEx(exstyle, "edit", malloc_buf ? malloc_buf : aText, style  // malloc_buf is checked again in case mem alloc failed.
  2899.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  2900.         {
  2901.             // As documented in MSDN, setting a password char will have no effect for multi-line edits
  2902.             // since they do not support password/mask char.
  2903.             // It seems best to allow password_char to be a literal asterisk so that there's a way to
  2904.             // have asterisk vs. bullet/closed-circle on OSes that default to bullet.
  2905.             if ((style & ES_PASSWORD) && opt.password_char) // Override default.
  2906.                 SendMessage(control.hwnd, EM_SETPASSWORDCHAR, (WPARAM)opt.password_char, 0);
  2907.             if (opt.limit < 0)
  2908.                 opt.limit = 0;
  2909.             //else leave it as the zero (unlimited) or positive (restricted) limit already set.
  2910.             // Now set the limit. Specifying a limit of zero opens the control to its maximum text capacity,
  2911.             // which removes the 32K size restriction.  Testing shows that this does not increase the actual
  2912.             // amount of memory used for controls containing small amounts of text.  All it does is allow
  2913.             // the control to allocate more memory as the user enters text.  By specifying zero, a max
  2914.             // of 64K becomes available on Windows 9x, and perhaps as much as 4 GB on NT/2k/XP.
  2915.             SendMessage(control.hwnd, EM_LIMITTEXT, (WPARAM)opt.limit, 0); // EM_LIMITTEXT == EM_SETLIMITTEXT
  2916.             if (opt.tabstop_count)
  2917.                 SendMessage(control.hwnd, EM_SETTABSTOPS, opt.tabstop_count, (LPARAM)opt.tabstop);
  2918.         }
  2919.         if (malloc_buf && malloc_buf != aText)
  2920.             free(malloc_buf);
  2921.         break;
  2922.  
  2923.     case GUI_CONTROL_DATETIME:
  2924.     {
  2925.         bool use_custom_format = false;
  2926.         if (*aText)
  2927.         {
  2928.             // DTS_SHORTDATEFORMAT and DTS_SHORTDATECENTURYFORMAT seem to produce identical results
  2929.             // (both display 4-digit year), at least on XP.  Perhaps DTS_SHORTDATECENTURYFORMAT is
  2930.             // obsolete.  In any case, it's uncommon so for simplicity, is not a named style.  It
  2931.             // can always be applied numerically if desired. Update: DTS_SHORTDATECENTURYFORMAT is
  2932.             // now applied by default higher above, which can be overridden explicitly via -0x0C
  2933.             // in the control's options.
  2934.             if (!stricmp(aText, "LongDate")) // LongDate seems more readable than "Long".  It also matches the keyword used by FormatTime.
  2935.                 style = (style & ~(DTS_SHORTDATECENTURYFORMAT | DTS_TIMEFORMAT)) | DTS_LONGDATEFORMAT; // Purify.
  2936.             else if (!stricmp(aText, "Time"))
  2937.                 style = (style & ~(DTS_SHORTDATECENTURYFORMAT | DTS_LONGDATEFORMAT)) | DTS_TIMEFORMAT;  // Purify.
  2938.             else // Custom format. Don't purify (to retain the underlying default in case custom format is ever removed).
  2939.                 use_custom_format = true;
  2940.         }
  2941.         if (opt.choice == 2) // "ChooseNone" was present, so ensure DTS_SHOWNONE is present to allow it.
  2942.             style |= DTS_SHOWNONE;
  2943.         //else it's blank, so retain the default DTS_SHORTDATEFORMAT (0x0000).
  2944.         if (control.hwnd = CreateWindowEx(exstyle, DATETIMEPICK_CLASS, "", style
  2945.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  2946.         {
  2947.             if (use_custom_format)
  2948.                 DateTime_SetFormat(control.hwnd, aText);
  2949.             //else keep the default or the format set via style higher above.
  2950.             // Feels safter to do range prior to selection even though unlike GUI_CONTROL_MONTHCAL,
  2951.             // GUI_CONTROL_DATETIME tolerates them in the reverse order when one doesn't fit the other.
  2952.             if (opt.gdtr_range) // If the date/time set above is invalid in light of the following new range, the date will be automatically to the closest valid date.
  2953.                 DateTime_SetRange(control.hwnd, opt.gdtr_range, opt.sys_time_range);
  2954.             //else keep default range, which is "unrestricted range".
  2955.             if (opt.choice) // The option "ChooseYYYYMMDD" was present and valid (or ChooseNone was present, choice==2)
  2956.                 DateTime_SetSystemtime(control.hwnd, opt.choice == 1 ? GDT_VALID : GDT_NONE, opt.sys_time);
  2957.             //else keep default, which is although undocumented appears to be today's date+time, which certainly is the expected default.
  2958.             if (control.union_color != CLR_DEFAULT)
  2959.                 DateTime_SetMonthCalColor(control.hwnd, MCSC_TEXT, control.union_color);
  2960.             // Note: The DateTime_SetMonthCalFont() macro is never used because apparently it's not required
  2961.             // to set the font, or even to repaint.
  2962.         }
  2963.         break;
  2964.     }
  2965.  
  2966.     case GUI_CONTROL_MONTHCAL:
  2967.         if (!opt.gdtr && *aText) // The option "ChooseYYYYMMDD" was not present, so fall back to Text (allow Text to be ignored in case it's incorrectly a date-time format, etc.)
  2968.         {
  2969.             opt.gdtr = YYYYMMDDToSystemTime2(aText, opt.sys_time);
  2970.             if (opt.gdtr == (GDTR_MIN | GDTR_MAX)) // When range is present, multi-select is automatically put into effect.
  2971.                 style |= MCS_MULTISELECT;  // Must be applied during control creation since it can't be changed afterward.
  2972.         }
  2973.         // Create the control with arbitrary width/height if no width/height were explicitly specified.
  2974.         // It will be resized after creation by querying the control:
  2975.         if (control.hwnd = CreateWindowEx(exstyle, MONTHCAL_CLASS, "", style, opt.x, opt.y
  2976.             , opt.width < 0 ? 100 : opt.width  // Negative width has special meaning upon creation (see below).
  2977.             , opt.height == COORD_UNSPECIFIED ? 100 : opt.height, mHwnd, control_id, g_hInstance, NULL))
  2978.         {
  2979.             if (style & MCS_MULTISELECT) // Must do this prior to setting initial contents in case contents is a range greater than 7 days.
  2980.                 MonthCal_SetMaxSelCount(control.hwnd, 366); // 7 days seems too restrictive a default, so expand.
  2981.             if (opt.gdtr_range) // If the date/time set above is invalid in light of the following new range, the date will be automatically to the closest valid date.
  2982.                 MonthCal_SetRange(control.hwnd, opt.gdtr_range, opt.sys_time_range);
  2983.             //else keep default range, which is "unrestricted range".
  2984.             if (opt.gdtr) // An explicit selection, either a range or single date, is present.
  2985.             {
  2986.                 if (style & MCS_MULTISELECT) // Must use range-selection even if selection is only one date.
  2987.                 {
  2988.                     if (opt.gdtr == GDTR_MIN) // No maximum is present, so set maximum to minimum.
  2989.                         opt.sys_time[1] = opt.sys_time[0];
  2990.                     //else just max, or both are present.  Assume both for code simplicity.
  2991.                     MonthCal_SetSelRange(control.hwnd, opt.sys_time);
  2992.                 }
  2993.                 else
  2994.                     MonthCal_SetCurSel(control.hwnd, opt.sys_time);
  2995.             }
  2996.             //else keep default, which is although undocumented appears to be today's date+time, which certainly is the expected default.
  2997.             if (control.union_color != CLR_DEFAULT)
  2998.                 MonthCal_SetColor(control.hwnd, MCSC_TEXT, control.union_color);
  2999.             GUI_SETFONT  // Required before asking it about its month size.
  3000.             if ((opt.width == COORD_UNSPECIFIED || opt.height == COORD_UNSPECIFIED)
  3001.                 && MonthCal_GetMinReqRect(control.hwnd, &rect))
  3002.             {
  3003.                 // Autosize width and/or height by asking the control how big each month is.
  3004.                 // MSDN: "The top and left members of lpRectInfo will always be zero. The right and bottom
  3005.                 // members represent the minimum cx and cy required for the control."
  3006.                 if (opt.width < 0) // Negative width vs. COORD_UNSPECIFIED are different in this case.
  3007.                 {
  3008.                     // MSDN: "The rectangle returned by MonthCal_GetMinReqRect does not include the width
  3009.                     // of the "Today" string, if it is present. If the MCS_NOTODAY style is not set,
  3010.                     // retrieve the rectangle that defines the "Today" string width by calling the
  3011.                     // MonthCal_GetMaxTodayWidth macro. Use the larger of the two rectangles to ensure
  3012.                     // that the "Today" string is not clipped.
  3013.                     int month_width;
  3014.                     if (style & MCS_NOTODAY) // No today-string, so width is always that from GetMinReqRect.
  3015.                         month_width = rect.right;
  3016.                     else // There will be a today-string present, so use the greater of the two widths.
  3017.                     {
  3018.                         month_width = MonthCal_GetMaxTodayWidth(control.hwnd);
  3019.                         if (month_width < rect.right)
  3020.                             month_width = rect.right;
  3021.                     }
  3022.                     if (opt.width == COORD_UNSPECIFIED) // Use default, which is to provide room for a single month.
  3023.                         opt.width = month_width;
  3024.                     else // It's some explicit negative number.  Use it as a multiplier to provide multiple months.
  3025.                     {
  3026.                         // Multiple months must need a little extra room for border between: 0.02 but 0.03 is okay.
  3027.                         // For safety, a larger value is used.
  3028.                         opt.width = -opt.width;
  3029.                         // Provide room for each separator.  There's one separator for each month after the
  3030.                         // first, and the separator always seems to be exactly 6 regardless of font face/size.
  3031.                         // This has been tested on both Classic and XP themes.
  3032.                         opt.width = opt.width*month_width + (opt.width - 1)*6;
  3033.                     }
  3034.                 }
  3035.                 if (opt.height == COORD_UNSPECIFIED)
  3036.                 {
  3037.                     opt.height = rect.bottom; // Init for default and for use below (room for only a single month's height).
  3038.                     if (opt.row_count > 0 && opt.row_count != 1.0) // row_count was explicitly specified by the script, so use its exact value, even if it isn't a whole number (for flexibility).
  3039.                     {
  3040.                         // Unlike horizontally stacked calendars, vertically stacking them produces no separator
  3041.                         // between them.
  3042.                         GUI_SET_HDC
  3043.                         GetTextMetrics(hdc, &tm);
  3044.                         // If there will be no today string, the height reported by MonthCal_GetMinReqRect
  3045.                         // is not correct for use in calculating the height of more than one month stacked
  3046.                         // vertically.  Must adjust it to make it display properly.
  3047.                         if (style & MCS_NOTODAY) // No today string, but space is still reserved for it, so must compensate for that.
  3048.                             opt.height += tm.tmHeight + 4; // Formula tested with Courier New and Verdana 8/10/12 with row counts between 1 and 5.
  3049.                         opt.height = (int)(opt.height * opt.row_count); // Calculate height of all months.
  3050.                         // Regardless of whether MCS_NOTODAY is present, the below is still the right formula.
  3051.                         // Room for the today-string is reserved only once at the bottom (even without MCS_NOTODAY),
  3052.                         // so need to subtract that (also note that some months have 6 rows and others only 5,
  3053.                         // but there is whitespace padding in the case of 5 to make all months the same height).
  3054.                         opt.height = (int)(opt.height - ((opt.row_count - 1) * (tm.tmHeight - 2)));
  3055.                         // Above: -2 seems to work for Verdana and Courier 8/10/12/14/18.
  3056.                     }
  3057.                     //else opt.row_count was unspecified, so stay with the default set above of exactly
  3058.                     // one month tall.
  3059.                 }
  3060.                 MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint should be smart enough not to do it if window is hidden.
  3061.             } // Width or height was unspecified.
  3062.         } // Control created OK.
  3063.         break;
  3064.  
  3065.     case GUI_CONTROL_HOTKEY:
  3066.         // In this case, not only doesn't the caption appear anywhere, it's not set either (or at least
  3067.         // not retrievable via GetWindowText()):
  3068.         if (control.hwnd = CreateWindowEx(exstyle, HOTKEY_CLASS, "", style
  3069.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  3070.         {
  3071.             if (*aText)
  3072.                 SendMessage(control.hwnd, HKM_SETHOTKEY, TextToHotkey(aText), 0);
  3073.             if (opt.limit > 0)
  3074.                 SendMessage(control.hwnd, HKM_SETRULES, opt.limit, MAKELPARAM(HOTKEYF_CONTROL|HOTKEYF_ALT, 0));
  3075.                 // Above must also specify Ctrl+Alt or some other default, otherwise the restriction will have
  3076.                 // no effect.
  3077.         }
  3078.         break;
  3079.  
  3080.     case GUI_CONTROL_UPDOWN:
  3081.         // The buddy of an up-down can meaningfully be one of the following:
  3082.         //case GUI_CONTROL_EDIT:
  3083.         //case GUI_CONTROL_TEXT:
  3084.         //case GUI_CONTROL_GROUPBOX:
  3085.         //case GUI_CONTROL_BUTTON:
  3086.         //case GUI_CONTROL_CHECKBOX:
  3087.         //case GUI_CONTROL_RADIO:
  3088.         //case GUI_CONTROL_LISTBOX:
  3089.         // (But testing shows, not these):
  3090.         //case GUI_CONTROL_UPDOWN: An up-down will snap onto another up-down, but not have any meaningful effect.
  3091.         //case GUI_CONTROL_DROPDOWNLIST:
  3092.         //case GUI_CONTROL_COMBOBOX:
  3093.         //case GUI_CONTROL_LISTVIEW:
  3094.         //case GUI_CONTROL_TREEVIEW:
  3095.         //case GUI_CONTROL_HOTKEY:
  3096.         //case GUI_CONTROL_UPDOWN:
  3097.         //case GUI_CONTROL_SLIDER:
  3098.         //case GUI_CONTROL_PROGRESS:
  3099.         //case GUI_CONTROL_TAB:
  3100.         //case GUI_CONTROL_STATUSBAR: As expected, it doesn't work properly.
  3101.  
  3102.         // v1.0.44: Don't allow buddying of UpDown to StatusBar (this must be done prior to the next section).
  3103.         // UPDATE: Due to rarity and user-should-know-better, this is not checked for (to reduce code size):
  3104.         //if (mControlCount && prev_control.type == GUI_CONTROL_STATUSBAR)
  3105.         //    style &= ~UDS_AUTOBUDDY;
  3106.         // v1.0.42.02: The below is a fix for tab controls that contain a ListView so that up-downs in the
  3107.         // tab control don't snap onto the tab control (due to the z-order change done by the ListView creation
  3108.         // section whenever a ListView exists inside a tab control).
  3109.         bool provide_buddy_manually;
  3110.         if (   provide_buddy_manually = (style & UDS_AUTOBUDDY)
  3111.             && (mStatusBarHwnd // Added for v1.0.44.01 (fixed in v1.0.44.04): Since the status bar is pushed to the bottom of the z-order after adding each other control, must do manual buddying whenever an UpDown is added after the status bar (to prevent it from attaching to the status bar).
  3112.             || (owning_tab_control // mControlCount is greater than zero whenever owning_tab_control!=NULL
  3113.                 && (owning_tab_control->attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR)))   )
  3114.             style &= ~UDS_AUTOBUDDY; // Remove it during control creation to avoid up-down snapping onto tab control.
  3115.  
  3116.         // The control is created unconditionally because if UDS_AUTOBUDDY is in effect, need to create the
  3117.         // control to find out its position and size (since it snaps to its buddy).  That size can then be
  3118.         // retrieved and used to figure out how to resize the buddy in cases where its width-set-automatically
  3119.         // -based-on-contents should not be squished as a result of buddying.
  3120.         // should not be squi
  3121.         if (control.hwnd = CreateWindowEx(exstyle, UPDOWN_CLASS, "", style
  3122.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  3123.         {
  3124.             if (provide_buddy_manually) // v1.0.42.02 (see comment where provide_buddy_manually is initialized).
  3125.                 SendMessage(control.hwnd, UDM_SETBUDDY, (WPARAM)prev_control.hwnd, 0); // See StatusBar notes above.  Also, mControlCount>0 whenever provide_buddy_manually==true.
  3126.             if (   mControlCount // Ensure there is a previous control to snap onto (below relies on this check).
  3127.                 && ((style & UDS_AUTOBUDDY) || provide_buddy_manually)   )
  3128.             {
  3129.                 // Since creation of a buddied up-down ignored the specified x/y and width/height, update them
  3130.                 // for use here and also later below for updating mMaxExtentRight, etc.
  3131.                 GetWindowRect(control.hwnd, &rect);
  3132.                 MapWindowPoints(NULL, mHwnd, (LPPOINT)&rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
  3133.                 opt.x = rect.left;
  3134.                 opt.y = rect.top;
  3135.                 opt.width = rect.right - rect.left;
  3136.                 opt.height = rect.bottom - rect.top;
  3137.                 // Get its buddy's rectangle for use in two places:
  3138.                 RECT buddy_rect;
  3139.                 GetWindowRect(prev_control.hwnd, &buddy_rect);
  3140.                 MapWindowPoints(NULL, mHwnd, (LPPOINT)&buddy_rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
  3141.                 // Note: It does not matter if UDS_HORZ is in effect because strangely, the up-down still
  3142.                 // winds up on the left or right side of the buddy, not the top/bottom.
  3143.                 if (mControlWidthWasSetByContents)
  3144.                 {
  3145.                     // Since the previous control's width was determined solely by the size of its contents,
  3146.                     // enlarge the control to undo the narrowing just done by the buddying process.
  3147.                     // This relies on the fact that during buddying, the UpDown was auto-sized and positioned
  3148.                     // to fit its buddy.
  3149.                     if (style & UDS_ALIGNRIGHT)
  3150.                     {
  3151.                         // Since moving an up-down's buddy is not enough to move the up-down,
  3152.                         // so that must be shifted too:
  3153.                         opt.x += opt.width;
  3154.                         rect.right += opt.width; // Updated for use further below.
  3155.                         MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint this control separately from its buddy below.
  3156.                     }
  3157.                     // Enlarge the buddy control to restore it to the size it had prior to being reduced by the
  3158.                     // buddying process:
  3159.                     buddy_rect.right += opt.width; // Must be updated for use in two places.
  3160.                     MoveWindow(prev_control.hwnd, buddy_rect.left, buddy_rect.top
  3161.                         , buddy_rect.right - buddy_rect.left, buddy_rect.bottom - buddy_rect.top, TRUE);
  3162.                 }
  3163.                 // Set x/y and width/height to be that of combined/buddied control so that auto-positioning
  3164.                 // of future controls will see it as a single control:
  3165.                 if (style & UDS_ALIGNRIGHT)
  3166.                 {
  3167.                     opt.x = buddy_rect.left;
  3168.                     // Must calculate the total width of the combined control not as a sum of their two widths,
  3169.                     // but as the different between right and left.  Otherwise, the width will be off by either
  3170.                     // 2 or 3 because of the slight overlap between the two controls.
  3171.                     opt.width = rect.right - buddy_rect.left;
  3172.                 }
  3173.                 else
  3174.                     opt.width = buddy_rect.right - rect.left;
  3175.                     //and opt.x set to the x position of the up-down, since it's on the leftmost side.
  3176.                 // Leave opt.y and opt.height as-is.
  3177.                 if (!opt.range_changed && prev_control.type == GUI_CONTROL_LISTBOX)
  3178.                 {
  3179.                     // ListBox buddy needs an inverted UpDown (if the UpDown is vertical) to work the way
  3180.                     // you'd expect.
  3181.                     opt.range_changed = true;
  3182.                     if (style & UDS_HORZ) // Use MAXVAL because otherwise ListBox select will be restricted to the first 100 entries.
  3183.                         opt.range_max = UD_MAXVAL;
  3184.                     else
  3185.                         opt.range_min = UD_MAXVAL;  // ListBox needs an inverted UpDown to work the way you'd expect.
  3186.                 }
  3187.             } // The up-down snapped onto a buddy control.
  3188.             if (!opt.range_changed) // Control's default is wacky inverted negative, so provide 0-100 as a better/traditional default.
  3189.             {
  3190.                 opt.range_changed = true;
  3191.                 opt.range_max = 100;
  3192.             }
  3193.             ControlSetUpDownOptions(control, opt); // This must be done prior to the below.
  3194.             // Set the position unconditionally, even if aText is blank.  This causes a blank aText to be
  3195.             // see as zero, which ensures the buddy has a legal starting value (which won't happen if the
  3196.             // range does not include zero, since it would default to zero then).
  3197.             // MSDN: "If the parameter is outside the control's specified range, nPos will be set to the nearest
  3198.             // valid value."
  3199.             SendMessage(control.hwnd, (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) ? UDM_SETPOS32 : UDM_SETPOS
  3200.                 , 0, ATOI(aText)); // Unnecessary to cast to short in the case of UDM_SETPOS, since it ignores the high-order word.
  3201.         } // Control was successfully created.
  3202.         break;
  3203.  
  3204.     case GUI_CONTROL_SLIDER:
  3205.         if (control.hwnd = CreateWindowEx(exstyle, TRACKBAR_CLASS, "", style
  3206.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  3207.         {
  3208.             ControlSetSliderOptions(control, opt); // Fix for v1.0.25.08: This must be done prior to the below.
  3209.             // The control automatically deals with out-of-range values by setting slider to min or max.
  3210.             // MSDN: "If this value is outside the control's maximum and minimum range, the position
  3211.             // is set to the maximum or minimum value."
  3212.             if (*aText)
  3213.                 SendMessage(control.hwnd, TBM_SETPOS, TRUE, ControlInvertSliderIfNeeded(control, ATOI(aText)));
  3214.                 // Above msg has no return value.
  3215.             //else leave it at the OS's default starting position (probably always the far left or top of the range).
  3216.         }
  3217.         break;
  3218.  
  3219.     case GUI_CONTROL_PROGRESS:
  3220.         if (control.hwnd = CreateWindowEx(exstyle, PROGRESS_CLASS, "", style
  3221.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  3222.         {
  3223.             // Progress bars don't default to mBackgroundColorCtl for their background color because it
  3224.             // would be undesired by the user 99% of the time (it usually would look bad since the bar's
  3225.             // bk-color almost always matches that of its parent window).
  3226.             ControlSetProgressOptions(control, opt, style); // Fix for v1.0.27.01: This must be done prior to the below.
  3227.             // This has been confirmed though testing, even when the range is dynamically changed
  3228.             // after the control is created to something that no longer includes the bar's current
  3229.             // position: The control automatically deals with out-of-range values by setting bar to
  3230.             // min or max.
  3231.             if (*aText)
  3232.                 SendMessage(control.hwnd, PBM_SETPOS, ATOI(aText), 0);
  3233.             //else leave it at the OS's default starting position (probably always the far left or top of the range).
  3234.             do_strip_theme = false;  // The above would have already stripped it if needed, so don't do it again.
  3235.         }
  3236.         break;
  3237.  
  3238.     case GUI_CONTROL_TAB:
  3239.         if (control.hwnd = CreateWindowEx(exstyle, WC_TABCONTROL, "", style
  3240.             , opt.x, opt.y, opt.width, opt.height, mHwnd, control_id, g_hInstance, NULL))
  3241.         {
  3242.             // For v1.0.23, theme is removed unconditionally for Tab controls because if an XP theme is
  3243.             // in effect, causing a non-solid background (such as an off-white gradient/fade), there are
  3244.             // many complications to getting the sub-controls' background to match the gradient.
  3245.             // The small advantages (styled tab appearance, yellow-bar hot-tracking, and the dubious
  3246.             // cosmetic appeal of the gradient itself) do not seem to outweigh the added complications.
  3247.             // The main approaches to supporting a themed tab control in the future are:
  3248.             // 1) Making a brush from a bitmap/snapshot of the background and applying that to Radios,
  3249.             //    Checkboxes, and GroupBoxes (and possibly other future control types).
  3250.             // 2) Using CreateDialog() or such to make a dialog window (child of main window, not
  3251.             //    child of the tab control).  The tab's controls are then made children of this dialog
  3252.             //    and automatically get the right background appearance by virtue of a call to
  3253.             //    EnableThemeDialogTexture().  It seems this call only works on true dialogs and their
  3254.             //    children.
  3255.             // See this and especially its reponses: http://www.codeproject.com/wtl/ThemedDialog.asp#xx727162xx
  3256.             // The following is no longer done because it was handled above via opt.use_theme:
  3257.             //do_strip_theme = true;
  3258.             // After a new tab control is created, default all subsequently created controls to belonging
  3259.             // to the first tab of this tab control: 
  3260.             mCurrentTabControlIndex = mTabControlCount;
  3261.             mCurrentTabIndex = 0;
  3262.             ++mTabControlCount;
  3263.             // Override the tab's window-proc so that custom background color becomes possible:
  3264.             g_TabClassProc = (WNDPROC)(size_t)SetWindowLong(control.hwnd, GWL_WNDPROC, (LONG)(size_t)TabWindowProc);
  3265.             // Doesn't work to remove theme background from tab:
  3266.             //MyEnableThemeDialogTexture(control.hwnd, ETDT_DISABLE);
  3267.             // This attempt to apply theme to the entire dialog window also has no effect, probably
  3268.             // because ETDT_ENABLETAB only works with true dialog windows (e.g. CreateDialog()):
  3269.             //MyEnableThemeDialogTexture(mHwnd, ETDT_ENABLETAB);
  3270.             // The above require the following line:
  3271.             //#include <uxtheme.h> // For EnableThemeDialogTexture()'s constants.
  3272.         }
  3273.         break;
  3274.  
  3275.     case GUI_CONTROL_STATUSBAR:
  3276.         if (control.hwnd = CreateStatusWindow(style, aText, mHwnd, (UINT)(size_t)control_id))
  3277.         {
  3278.             mStatusBarHwnd = control.hwnd;
  3279.             if (opt.color_bk != CLR_INVALID) // Explicit color change was requested.
  3280.                 SendMessage(mStatusBarHwnd, SB_SETBKCOLOR, 0, opt.color_bk);
  3281.         }
  3282.         break;
  3283.     } // switch() for control creation.
  3284.  
  3285.     ////////////////////////////////
  3286.     // Release the HDC if necessary.
  3287.     ////////////////////////////////
  3288.     if (hdc)
  3289.     {
  3290.         if (hfont_old)
  3291.         {
  3292.             SelectObject(hdc, hfont_old); // Necessary to avoid memory leak.
  3293.             hfont_old = NULL;
  3294.         }
  3295.         ReleaseDC(mHwnd, hdc);
  3296.         hdc = NULL;
  3297.     }
  3298.  
  3299.     // Below also serves as a bug check, i.e. GUI_CONTROL_INVALID or some unknown type.
  3300.     if (!control.hwnd)
  3301.         return g_script.ScriptError("Can't create control." ERR_ABORT);
  3302.     // Otherwise the above control creation succeeded.
  3303.     ++mControlCount;
  3304.     mControlWidthWasSetByContents = control_width_was_set_by_contents; // Set for use by next control, if any.
  3305.     if (opt.hwnd_output_var) // v1.0.46.01.
  3306.         opt.hwnd_output_var->AssignHWND(control.hwnd);
  3307.  
  3308.     if (control.type == GUI_CONTROL_RADIO)
  3309.     {
  3310.         if (opt.checked != BST_UNCHECKED)
  3311.             ControlCheckRadioButton(control, mControlCount - 1, opt.checked); // Also handles alteration of the group's tabstop, etc.
  3312.         //else since the control has just been created, there's no need to uncheck it or do any actions
  3313.         // related to unchecking, such as tabstop adjustment.
  3314.         mInRadioGroup = true; // Set here, only after creation was successful.
  3315.     }
  3316.     else // For code simplicity and due to rarity, even GUI_CONTROL_STATUSBAR starts a new radio group.
  3317.         mInRadioGroup = false;
  3318.  
  3319.     // Check style_remove vs. style because this control might be hidden just because it was added
  3320.     // to a tab that isn't active:
  3321.     if (opt.style_remove & WS_VISIBLE)
  3322.         control.attrib |= GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN;  // For use with tab controls.
  3323.     if (opt.style_add & WS_DISABLED)
  3324.         control.attrib |= GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED;
  3325.  
  3326.     // Strip theme from the control if called for:
  3327.     // It is stripped for radios, checkboxes, and groupboxes if they have a custom text color.
  3328.     // Otherwise the transparency and/or custom text color will not be obeyed on XP, at least when
  3329.     // a non-Classic theme is active.  For GroupBoxes, when a theme is active, it will obey 
  3330.     // custom background color but not a custom text color.  The same is true for radios and checkboxes.
  3331.     if (do_strip_theme || (control.union_color != CLR_DEFAULT && (control.type == GUI_CONTROL_CHECKBOX
  3332.         || control.type == GUI_CONTROL_RADIO || control.type == GUI_CONTROL_GROUPBOX)) // GroupBox needs it too.
  3333.         || (control.type == GUI_CONTROL_GROUPBOX && (control.attrib & GUI_CONTROL_ATTRIB_BACKGROUND_TRANS))   ) // Tested and found to be necessary.)
  3334.         MySetWindowTheme(control.hwnd, L"", L"");
  3335.  
  3336.     // Must set the font even if mCurrentFontIndex > 0, otherwise the bold SYSTEM_FONT will be used.
  3337.     // Note: Neither the slider's buddies nor itself are affected by the font setting, so it's not applied.
  3338.     // However, the buddies are affected at the time they are created if they are a type that uses a font.
  3339.     if (!font_was_set && uses_font_and_text_color)
  3340.         GUI_SETFONT
  3341.  
  3342.     if (opt.redraw == CONDITION_FALSE)
  3343.         SendMessage(control.hwnd, WM_SETREDRAW, FALSE, 0); // Disable redrawing for this control to allow contents to be added to it more quickly.
  3344.         // It's not necessary to do the following because by definition the control has just been created
  3345.         // and thus redraw can't have been off for it previously:
  3346.         //if (opt.redraw == CONDITION_TRUE) // Since redrawing is being turned back on, invalidate the control so that it updates itself.
  3347.         //    InvalidateRect(control.hwnd, NULL, TRUE);
  3348.  
  3349.     ///////////////////////////////////////////////////
  3350.     // Add any content to the control and set its font.
  3351.     ///////////////////////////////////////////////////
  3352.     ControlAddContents(control, aText, opt.choice); // Must be done after font-set above so that ListView columns can be auto-sized to fit their text.
  3353.  
  3354.     if (control.type == GUI_CONTROL_TAB && opt.row_count > 0)
  3355.     {
  3356.         // Now that the tabs have been added (possibly creating more than one row of tabs), resize so that
  3357.         // the interior of the control has the actual number of rows specified.
  3358.         GetClientRect(control.hwnd, &rect); // MSDN: "the coordinates of the upper-left corner are (0,0)"
  3359.         // This is a workaround for the fact that TabCtrl_AdjustRect() seems to give an invalid
  3360.         // height when the tabs are at the bottom, at least on XP.  Unfortunately, this workaround
  3361.         // does not work when the tabs or on the left or right side, so don't even bother with that
  3362.         // adjustment (it's very rare that a tab control would have an unspecified width anyway).
  3363.         bool bottom_is_in_effect = (style & TCS_BOTTOM) && !(style & TCS_VERTICAL);
  3364.         if (bottom_is_in_effect)
  3365.             SetWindowLong(control.hwnd, GWL_STYLE, style & ~TCS_BOTTOM);
  3366.         // Insist on a taller client area (or same height in the case of TCS_VERTICAL):
  3367.         TabCtrl_AdjustRect(control.hwnd, TRUE, &rect); // Calculate new window height.
  3368.         if (bottom_is_in_effect)
  3369.             SetWindowLong(control.hwnd, GWL_STYLE, style);
  3370.         opt.height = rect.bottom - rect.top;  // Update opt.height for here and for later use below.
  3371.         // The below is commented out because TabCtrl_AdjustRect() is unable to cope with tabs on
  3372.         // the left or right sides.  It would be rarely used anyway.
  3373.         //if (style & TCS_VERTICAL && width_was_originally_unspecified)
  3374.         //    // Also make the interior wider in this case, to make the interior as large as intended.
  3375.         //    // It is a known limitation that this adjustment does not occur when the script did not
  3376.         //    // specify a row_count or omitted height and row_count.
  3377.         //    opt.width = rect.right - rect.left;
  3378.         MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint, since parent might be visible.
  3379.     }
  3380.  
  3381.     if (retrieve_dimensions) // Update to actual size for use later below.
  3382.     {
  3383.         GetWindowRect(control.hwnd, &rect);
  3384.         opt.height = rect.bottom - rect.top;
  3385.         opt.width = rect.right - rect.left;
  3386.  
  3387.         if (aControlType == GUI_CONTROL_LISTBOX && (style & WS_HSCROLL))
  3388.         {
  3389.             if (opt.hscroll_pixels < 0) // Calculate a default based on control's width.
  3390.                 // Since horizontal scrollbar is relatively rarely used, no fancy method
  3391.                 // such as calculating scrolling-width via LB_GETTEXTLEN & current font's
  3392.                 // average width is used.
  3393.                 opt.hscroll_pixels = 3 * opt.width;
  3394.             // If hscroll_pixels is now zero or smaller than the width of the control, the
  3395.             // scrollbar will not be shown.  But the message is still sent unconditionally
  3396.             // in case it has some desirable side-effects:
  3397.             SendMessage(control.hwnd, LB_SETHORIZONTALEXTENT, (WPARAM)opt.hscroll_pixels, 0);
  3398.         }
  3399.     }
  3400.  
  3401.     // v1.0.36.06: If this tab control contains a ListView, keep the tab control after all of its controls
  3402.     // in the z-order.  This solves ListView-inside-Tab redrawing problems, namely the disappearance of
  3403.     // the ListView or an incomplete drawing of it.  Doing it this way preserves the tab-navigation
  3404.     // order of controls inside the tab control, both those above and those beneath the ListView.
  3405.     // The only thing it alters is the tab navigation to the tab control itself, which will now occur
  3406.     // after rather than before all the controls inside it. For most uses, this a very minor difference,
  3407.     // especially given the rarity of having ListViews inside tab controls.  If this solution ever
  3408.     // proves undesirable, one alternative is to somehow force the ListView to properly redraw whenever
  3409.     // its inside a tab control.  Perhaps this could be done by subclassing the ListView or Tab control
  3410.     // and having it do something different or additional in response to WM_ERASEBKGND.  It might
  3411.     // also be done in the parent window's proc in response to WM_ERASEBKGND.
  3412.     if (owning_tab_control)
  3413.     {
  3414.         // Fix for v1.0.35: Probably due to clip-siblings, adding a control within the area of a tab control
  3415.         // does not properly draw the control.  This seems to apply to most/all control types.
  3416.         if (on_visible_page_of_tab_control)
  3417.         {
  3418.             // Not enough for GUI_CONTROL_DATETIME (it's border is not drawn):
  3419.             //InvalidateRect(control.hwnd, NULL, TRUE);  // TRUE is required, at least for GUI_CONTROL_DATETIME.
  3420.             GetWindowRect(control.hwnd, &rect);
  3421.             MapWindowPoints(NULL, mHwnd, (LPPOINT)&rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
  3422.             InvalidateRect(mHwnd, &rect, FALSE);
  3423.         }
  3424.         if (owning_tab_control->attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) // Put the tab control after the newly added control. See comment higher above.
  3425.             SetWindowPos(owning_tab_control->hwnd, control.hwnd, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
  3426.     }
  3427.  
  3428.     // v1.0.44: Must keep status bar at the bottom of the z-order so that it gets drawn last.  This alleviates
  3429.     // (but does not completely prevent) other controls from overlapping it and getting drawn on top. This is
  3430.     // done each time a control is added -- rather than at some single time such as when the parent window is
  3431.     // first shown -- in case the script adds more controls later.
  3432.     if (mStatusBarHwnd) // Seems harmless to do it even if the just-added control IS the status bar. Also relies on the fact that that only one status bar is allowed.
  3433.         SetWindowPos(mStatusBarHwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
  3434.  
  3435.     if (aControlType != GUI_CONTROL_STATUSBAR) // i.e. don't let status bar affect positioning of controls relative to each other.
  3436.     {
  3437.         ////////////////////////////////////////////////////////////////////////////////////////////////////
  3438.         // Save the details of this control's position for posible use in auto-positioning the next control.
  3439.         ////////////////////////////////////////////////////////////////////////////////////////////////////
  3440.         mPrevX = opt.x;
  3441.         mPrevY = opt.y;
  3442.         mPrevWidth = opt.width;
  3443.         mPrevHeight = opt.height;
  3444.         int right = opt.x + opt.width;
  3445.         int bottom = opt.y + opt.height;
  3446.         if (right > mMaxExtentRight)
  3447.             mMaxExtentRight = right;
  3448.         if (bottom > mMaxExtentDown)
  3449.             mMaxExtentDown = bottom;
  3450.  
  3451.         // As documented, always start new section for very first control, but never if this control is GUI_CONTROL_STATUSBAR.
  3452.         if (opt.start_new_section || mControlCount == 1 // aControlType!=GUI_CONTROL_STATUSBAR due to check higher above.
  3453.             || (mControlCount == 2 && mControl[0].type == GUI_CONTROL_STATUSBAR)) // This is the first non-statusbar control.
  3454.         {
  3455.             mSectionX = opt.x;
  3456.             mSectionY = opt.y;
  3457.             mMaxExtentRightSection = right;
  3458.             mMaxExtentDownSection = bottom;
  3459.         }
  3460.         else
  3461.         {
  3462.             if (right > mMaxExtentRightSection)
  3463.                 mMaxExtentRightSection = right;
  3464.             if (bottom > mMaxExtentDownSection)
  3465.                 mMaxExtentDownSection = bottom;
  3466.         }
  3467.     }
  3468.  
  3469.     return OK;
  3470. }
  3471.  
  3472.  
  3473.  
  3474. ResultType GuiType::ParseOptions(char *aOptions, bool &aSetLastFoundWindow, ToggleValueType &aOwnDialogs)
  3475. // This function is similar to ControlParseOptions() further below, so should be maintained alongside it.
  3476. // Caller must have already initialized aSetLastFoundWindow/, bool &aOwnDialogs with desired starting values.
  3477. // Caller must ensure that aOptions is a modifiable string, since this method temporarily alters it.
  3478. {
  3479.     int owner_window_index;
  3480.     LONG nc_width, nc_height;
  3481.  
  3482.     if (mHwnd)
  3483.     {
  3484.         // Since window already exists, its mStyle and mExStyle members might be out-of-date due to
  3485.         // "WinSet Transparent", etc.  So update them:
  3486.         mStyle = GetWindowLong(mHwnd, GWL_STYLE);
  3487.         mExStyle = GetWindowLong(mHwnd, GWL_EXSTYLE);
  3488.     }
  3489.     DWORD style_orig = mStyle;
  3490.     DWORD exstyle_orig = mExStyle;
  3491.  
  3492.     char *pos_of_the_x, *next_option, *option_end, orig_char;
  3493.     bool adding; // Whether this option is beeing added (+) or removed (-).
  3494.  
  3495.     for (next_option = aOptions; *next_option; next_option = omit_leading_whitespace(option_end))
  3496.     {
  3497.         if (*next_option == '-')
  3498.         {
  3499.             adding = false;
  3500.             // omit_leading_whitespace() is not called, which enforces the fact that the option word must
  3501.             // immediately follow the +/- sign.  This is done to allow the flexibility to have options
  3502.             // omit the plus/minus sign, and also to reserve more flexibility for future option formats.
  3503.             ++next_option;  // Point it to the option word itself.
  3504.         }
  3505.         else
  3506.         {
  3507.             // Assume option is being added in the absence of either sign.  However, when we were
  3508.             // called by GuiControl(), the first option in the list must begin with +/- otherwise the cmd
  3509.             // would never have been properly detected as GUICONTROL_CMD_OPTIONS in the first place.
  3510.             adding = true;
  3511.             if (*next_option == '+')
  3512.                 ++next_option;  // Point it to the option word itself.
  3513.             //else do not increment, under the assumption that the plus has been omitted from a valid
  3514.             // option word and is thus an implicit plus.
  3515.         }
  3516.  
  3517.         if (!*next_option) // In case the entire option string ends in a naked + or -.
  3518.             break;
  3519.         // Find the end of this option item:
  3520.         if (   !(option_end = StrChrAny(next_option, " \t"))   )  // Space or tab.
  3521.             option_end = next_option + strlen(next_option); // Set to position of zero terminator instead.
  3522.         if (option_end == next_option)
  3523.             continue; // i.e. the string contains a + or - with a space or tab after it, which is intentionally ignored.
  3524.  
  3525.         // Temporarily terminate to help eliminate ambiguity for words contained inside other words,
  3526.         // such as "Checked" inside of "CheckedGray":
  3527.         orig_char = *option_end;
  3528.         *option_end = '\0';
  3529.  
  3530.         // Attributes and option words:
  3531.         if (!strnicmp(next_option, "Owner", 5))
  3532.         {
  3533.             if (!mHwnd)
  3534.             {
  3535.                 if (!adding)
  3536.                     mOwner = NULL;
  3537.                 else
  3538.                 {
  3539.                     if (option_end - next_option > 5) // Length is greater than 5, so it has a number (e.g. Owned1).
  3540.                     {
  3541.                         // Using ATOI() vs. atoi() seems okay in these cases since spaces are required
  3542.                         // between options:
  3543.                         owner_window_index = ATOI(next_option + 5) - 1;
  3544.                         if (owner_window_index > -1 && owner_window_index < MAX_GUI_WINDOWS
  3545.                             && owner_window_index != mWindowIndex  // Window can't own itself!
  3546.                             && g_gui[owner_window_index] && g_gui[owner_window_index]->mHwnd) // Relies on short-circuit boolean order.
  3547.                             mOwner = g_gui[owner_window_index]->mHwnd;
  3548.                         else
  3549.                             return g_script.ScriptError("Invalid or nonexistent owner window." ERR_ABORT, next_option);
  3550.                     }
  3551.                     else
  3552.                         mOwner = g_hWnd; // Make a window owned (by script's main window) omits its taskbar button.
  3553.                 }
  3554.             }
  3555.             //else mHwnd!=NULL. Since OS provides no way to change an existing window's owner, do nothing as documented.
  3556.         }
  3557.  
  3558.         else if (!stricmp(next_option, "AlwaysOnTop"))
  3559.         {
  3560.             // If the window already exists, SetWindowLong() isn't enough.  Must use SetWindowPos()
  3561.             // to make it take effect.
  3562.             if (mHwnd)
  3563.             {
  3564.                 SetWindowPos(mHwnd, adding ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0
  3565.                     , SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE); // SWP_NOACTIVATE prevents the side-effect of activating the window, which is undesirable if only its style is changing.
  3566.                 // Fix for v1.0.41.01: Update the original style too, so that the call to SetWindowLong() later below
  3567.                 // is made only if multiple styles are being changed on the same line, e.g. Gui +Disabled -SysMenu
  3568.                 if (adding) exstyle_orig |= WS_EX_TOPMOST; else exstyle_orig &= ~WS_EX_TOPMOST;
  3569.             }
  3570.             // Fix for v1.0.41.01: The following line is now done unconditionally.  Previously, it wasn't
  3571.             // done if the window already existed, which caused an example such as the following to first
  3572.             // set the window always on top and then immediately afterward try to unset it via SetWindowLong
  3573.             // (because mExStyle hadn't been updated to reflect the change made by SetWindowPos):
  3574.             // Gui, +AlwaysOnTop +Disabled -SysMenu
  3575.             if (adding) mExStyle |= WS_EX_TOPMOST; else mExStyle &= ~WS_EX_TOPMOST;
  3576.         }
  3577.  
  3578.         else if (!stricmp(next_option, "Border"))
  3579.             if (adding) mStyle |= WS_BORDER; else mStyle &= ~WS_BORDER;
  3580.  
  3581.         else if (!stricmp(next_option, "Caption"))
  3582.             // To remove title bar successfully, the WS_POPUP style must also be applied:
  3583.             if (adding) mStyle |= WS_CAPTION; else mStyle = mStyle & ~WS_CAPTION | WS_POPUP;
  3584.  
  3585.         else if (!strnicmp(next_option, "Delimiter", 9))
  3586.         {
  3587.             next_option += 9;
  3588.             // For simplicity, the value of "adding" is ignored since no use is forseeable for "-Delimiter".
  3589.             if (!stricmp(next_option, "Tab"))
  3590.                 mDelimiter = '\t';
  3591.             else if (!stricmp(next_option, "Space"))
  3592.                 mDelimiter = ' ';
  3593.             else
  3594.                 mDelimiter = *next_option ? *next_option : '|';
  3595.         }
  3596.  
  3597.         else if (!stricmp(next_option, "Disabled"))
  3598.         {
  3599.             if (mHwnd)
  3600.             {
  3601.                 EnableWindow(mHwnd, adding ? FALSE : TRUE);  // Must not not apply WS_DISABLED directly because that breaks the window.
  3602.                 // Fix for v1.0.41.01: Update the original style too, so that the call to SetWindowLong() later below
  3603.                 // is made only if multiple styles are being changed on the same line, e.g. Gui +Disabled -SysMenu
  3604.                 if (adding) style_orig |= WS_DISABLED; else style_orig &= ~WS_DISABLED;
  3605.             }
  3606.             // Fix for v1.0.41.01: The following line is now done unconditionally.  Previously, it wasn't
  3607.             // done if the window already existed, which caused an example such as the following to first
  3608.             // disable the window and then immediately afterward try to enable it via SetWindowLong
  3609.             // (because mStyle hadn't been updated to reflect the change made by SetWindowPos):
  3610.             // Gui, +AlwaysOnTop +Disabled -SysMenu
  3611.             if (adding) mStyle |= WS_DISABLED; else mStyle &= ~WS_DISABLED;
  3612.         }
  3613.  
  3614.         else if (!strnicmp(next_option, "Label", 5)) // v1.0.44.09: Allow custom label prefix for the reasons described in SetLabels().
  3615.         {
  3616.             if (adding)
  3617.                 SetLabels(next_option + 5);
  3618.             //else !adding (-Label), which currently does nothing.  Potential future uses include:
  3619.             // Disable all labels (seems too rare to be useful).
  3620.             // Revert to defaults (e.g. 2GuiSize): Doesn't seem to be of much value because the caller will likely
  3621.             // always know the number of the window in question (if nothing else, than via A_Gui) and can thus revert
  3622.             // to defaults via something like +Label%A_Gui%Gui.
  3623.             // Alternative: Could also use some char that's illegal in labels to indicate one or more of the above.
  3624.         }
  3625.  
  3626.         else if (!strnicmp(next_option, "LastFound", 9)) // strnicmp so that "LastFoundExist" is also recognized.
  3627.             aSetLastFoundWindow = true; // Regardless of whether "adding" is true or false.
  3628.  
  3629.         else if (!stricmp(next_option, "MaximizeBox")) // See above comment.
  3630.             if (adding) mStyle |= WS_MAXIMIZEBOX|WS_SYSMENU; else mStyle &= ~WS_MAXIMIZEBOX;
  3631.  
  3632.         else if (!stricmp(next_option, "MinimizeBox"))
  3633.             // WS_MINIMIZEBOX requires WS_SYSMENU to take effect.  It can be explicitly omitted
  3634.             // via "+MinimizeBox -SysMenu" if that functionality is ever needed.
  3635.             if (adding) mStyle |= WS_MINIMIZEBOX|WS_SYSMENU; else mStyle &= ~WS_MINIMIZEBOX;
  3636.  
  3637.         else if (!strnicmp(next_option, "MinSize", 7)) // v1.0.44.13: Added for use with WM_GETMINMAXINFO.
  3638.         {
  3639.             next_option += 7;
  3640.             if (adding)
  3641.             {
  3642.                 if (*next_option)
  3643.                 {
  3644.                     // The following will retrieve zeros if window hasn't yet been shown for the first time,
  3645.                     // in which case the first showing will do the NC adjustment for us.  The overall approach
  3646.                     // used here was chose to avoid any chance for Min/MaxSize to be adjusted more than once
  3647.                     // to convert client size to entire-size, which would be wrong since the adjustment must be
  3648.                     // applied only once.  Examples of such situations are when one of the coordinates is omitted,
  3649.                     // or when +MinSize is specified prior to the first "Gui Show" but +MaxSize is specified after.
  3650.                     GetNonClientArea(nc_width, nc_height);
  3651.                     // atoi() vs. ATOI() is used below to avoid ambiguity of "x" being hex 0x vs. a delimiter.
  3652.                     if ((pos_of_the_x = StrChrAny(next_option, "Xx")) && pos_of_the_x[1]) // Kept simple due to rarity of transgressions and their being inconsequential.
  3653.                         mMinHeight = atoi(pos_of_the_x + 1) + nc_height;
  3654.                     //else it's "MinSize333" or "MinSize333x", so leave height unchanged as documented.
  3655.                     if (pos_of_the_x != next_option) // There's no 'x' or it lies to the right of next_option.
  3656.                         mMinWidth = atoi(next_option) + nc_width; // atoi() automatically stops converting when it reaches non-numeric character.
  3657.                     //else it's "MinSizeX333", so leave width unchanged as documented.
  3658.                 }
  3659.                 else // Since no width or height was specified:
  3660.                     // Use the window's current size. But if window hasn't yet been shown for the
  3661.                     // first time, this will set the values to COORD_CENTERED, which tells the
  3662.                     // first-show routine to get the total width/height upon first showing (since
  3663.                     // that's where the window's initial size is determined).
  3664.                     GetTotalWidthAndHeight(mMinWidth, mMinHeight);
  3665.             }
  3666.             else // "-MinSize", so tell the WM_GETMINMAXINFO handler to use system defaults.
  3667.             {
  3668.                 mMinWidth = COORD_UNSPECIFIED;
  3669.                 mMinHeight = COORD_UNSPECIFIED;
  3670.             }
  3671.         }
  3672.  
  3673.         else if (!strnicmp(next_option, "MaxSize", 7)) // v1.0.44.13: Added for use with WM_GETMINMAXINFO.
  3674.         {
  3675.             // SEE "MinSize" section above for more comments because the section below is nearly identical to it.
  3676.             next_option += 7;
  3677.             if (adding)
  3678.             {
  3679.                 if (*next_option)
  3680.                 {
  3681.                     GetNonClientArea(nc_width, nc_height);
  3682.                     if ((pos_of_the_x = StrChrAny(next_option, "Xx")) && pos_of_the_x[1]) // Kept simple due to rarity of transgressions and their being inconsequential.
  3683.                         mMaxHeight = atoi(pos_of_the_x + 1) + nc_height;
  3684.                     if (pos_of_the_x != next_option) // There's no 'x' or it lies to the right of next_option.
  3685.                         mMaxWidth = atoi(next_option) + nc_width; // atoi() automatically stops converting when it reaches non-numeric character.
  3686.                 }
  3687.                 else // No width or height was specified. See comment in "MinSize" for details about this.
  3688.                     GetTotalWidthAndHeight(mMaxWidth, mMaxHeight); // If window hasn't yet been shown for the first time, this will set them to COORD_CENTERED, which tells the first-show routine to get the total width/height.
  3689.             }
  3690.             else // "-MaxSize", so tell the WM_GETMINMAXINFO handler to use system defaults.
  3691.             {
  3692.                 mMaxWidth = COORD_UNSPECIFIED;
  3693.                 mMaxHeight = COORD_UNSPECIFIED;
  3694.             }
  3695.         }
  3696.  
  3697.         else if (!stricmp(next_option, "OwnDialogs"))
  3698.             aOwnDialogs = (adding ? TOGGLED_ON : TOGGLED_OFF);
  3699.  
  3700.         else if (!stricmp(next_option, "Resize")) // Minus removes either or both.
  3701.             if (adding) mStyle |= WS_SIZEBOX|WS_MAXIMIZEBOX; else mStyle &= ~(WS_SIZEBOX|WS_MAXIMIZEBOX);
  3702.  
  3703.         else if (!stricmp(next_option, "SysMenu"))
  3704.             if (adding) mStyle |= WS_SYSMENU; else mStyle &= ~WS_SYSMENU;
  3705.  
  3706.         else if (!stricmp(next_option, "Theme"))
  3707.             mUseTheme = adding;
  3708.             // But don't apply/remove theme from parent window because that is usually undesirable.
  3709.             // This is because even old apps running on XP still have the new parent window theme,
  3710.             // at least for their title bar and title bar buttons (except console apps, maybe).
  3711.  
  3712.         else if (!stricmp(next_option, "ToolWindow"))
  3713.             // WS_EX_TOOLWINDOW provides narrower title bar, omits task bar button, and omits
  3714.             // entry in the alt-tab menu.
  3715.             if (adding) mExStyle |= WS_EX_TOOLWINDOW; else mExStyle &= ~WS_EX_TOOLWINDOW;
  3716.  
  3717.         // This one should be near the bottom since "E" is fairly vague and might be contained at the start
  3718.         // of future option words such as Edge, Exit, etc.
  3719.         else if (toupper(*next_option) == 'E') // Extended style
  3720.         {
  3721.             ++next_option; // Skip over the E itself.
  3722.             if (IsPureNumeric(next_option, false, false)) // Disallow whitespace in case option string ends in naked "E".
  3723.             {
  3724.                 // Pure numbers are assumed to be style additions or removals:
  3725.                 DWORD given_exstyle = ATOU(next_option); // ATOU() for unsigned.
  3726.                 if (adding)
  3727.                     mExStyle |= given_exstyle;
  3728.                 else
  3729.                     mExStyle &= ~given_exstyle;
  3730.             }
  3731.         }
  3732.  
  3733.         else // Handle things that are more general than the above, such as single letter options and pure numbers:
  3734.         {
  3735.             if (IsPureNumeric(next_option)) // Above has already verified that *next_option can't be whitespace.
  3736.             {
  3737.                 // Pure numbers are assumed to be style additions or removals:
  3738.                 DWORD given_style = ATOU(next_option); // ATOU() for unsigned.
  3739.                 if (adding)
  3740.                     mStyle |= given_style;
  3741.                 else
  3742.                     mStyle &= ~given_style;
  3743.             }
  3744.         }
  3745.  
  3746.         // If the item was not handled by the above, ignore it because it is unknown.
  3747.  
  3748.         *option_end = orig_char; // Undo the temporary termination because the caller needs aOptions to be unaltered.
  3749.  
  3750.     } // for() each item in option list
  3751.  
  3752.     // Besides reducing the code size and complexity, another reason all changes to style are made
  3753.     // here rather than above is that multiple changes might have been made above to the style,
  3754.     // and there's no point in redrawing/updating the window for each one:
  3755.     if (mHwnd && (mStyle != style_orig || mExStyle != exstyle_orig))
  3756.     {
  3757.         // v1.0.27.01: Must do this prior to SetWindowLong() because sometimes SetWindowLong()
  3758.         // traumatizes the window (such as "Gui -Caption"), making it effectively invisible
  3759.         // even though its non-functional remnant is still on the screen:
  3760.         bool is_visible = IsWindowVisible(mHwnd) && !IsIconic(mHwnd);
  3761.  
  3762.         // Since window already exists but its style has changed, attempt to update it dynamically.
  3763.         if (mStyle != style_orig)
  3764.             SetWindowLong(mHwnd, GWL_STYLE, mStyle);
  3765.         if (mExStyle != exstyle_orig)
  3766.             SetWindowLong(mHwnd, GWL_EXSTYLE, mExStyle);
  3767.  
  3768.         if (is_visible)
  3769.         {
  3770.             // Hiding then showing is the only way I've discovered to make it update.  If the window
  3771.             // is not updated, a strange effect occurs where the window is still visible but can no
  3772.             // longer be used at all (clicks pass right through it).  This show/hide method is less
  3773.             // desirable due to possible side effects caused to any script that happens to be watching
  3774.             // for its existence/non-existence, so it would be nice if some better way can be discovered
  3775.             // to do this.
  3776.             // SetWindowPos is also necessary, otherwise the frame thickness entirely around the window
  3777.             // does not get updated (just parts of it):
  3778.             SetWindowPos(mHwnd, NULL, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
  3779.             ShowWindow(mHwnd, SW_HIDE);
  3780.             ShowWindow(mHwnd, SW_SHOWNA); // i.e. don't activate it if it wasn't before. Note that SW_SHOWNA avoids restoring the window if it is currently minimized or maximized (unlike SW_SHOWNOACTIVATE).
  3781.             // None of the following methods alone is enough, at least not when the window is currently active:
  3782.             // 1) InvalidateRect(mHwnd, NULL, TRUE);
  3783.             // 2) SendMessage(mHwnd, WM_NCPAINT, 1, 0);  // 1 = Repaint entire frame.
  3784.             // 3) RedrawWindow(mHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW);
  3785.             // 4) SetWindowPos(mHwnd, NULL, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
  3786.         }
  3787.         // Continue on to create the window so that code is simplified in other places by
  3788.         // using the assumption that "if gui[i] object exists, so does its window".
  3789.         // Another important reason this is done is that if an owner window were to be destroyed
  3790.         // before the window it owns is actually created, the WM_DESTROY logic would have to check
  3791.         // for any windows owned by the window being destroyed and update them.
  3792.     }
  3793.  
  3794.     return OK;
  3795. }
  3796.  
  3797.  
  3798.  
  3799. void GuiType::GetNonClientArea(LONG &aWidth, LONG &aHeight)
  3800. // Added for v1.0.44.13.
  3801. // Yields only the *extra* width/height added by the windows non-client area.
  3802. // If the window hasn't been shown for the first time, the caller wants zeros.
  3803. // The reason for making the script specify size of client area rather than entire window is that it
  3804. // seems far more useful.  For example, a script might know exactly how much minimum height its
  3805. // controls require in the client area, but would find it inconvenient to have to take into account
  3806. // the height of the title bar and menu bar (which vary depending on theme and other settings).
  3807. {
  3808.     if (mGuiShowHasNeverBeenDone) // In this case, the script might not yet have added the menu bar and other styles that affect the size of the non-client area.  So caller wants to do these calculations later.
  3809.     {
  3810.         aWidth = 0;
  3811.         aHeight = 0;
  3812.         return;
  3813.     }
  3814.     // Otherwise, mGuiShowHasNeverBeenDone==false, which should mean that mHwnd!=NULL.
  3815.     RECT rect, client_rect;
  3816.     GetWindowRect(mHwnd, &rect);
  3817.     GetClientRect(mHwnd, &client_rect); // Client rect's left & top are always zero.
  3818.     aWidth = (rect.right - rect.left) - client_rect.right;
  3819.     aHeight = (rect.bottom - rect.top) - client_rect.bottom;
  3820. }
  3821.  
  3822.  
  3823.  
  3824. void GuiType::GetTotalWidthAndHeight(LONG &aWidth, LONG &aHeight)
  3825. // Added for v1.0.44.13.
  3826. // Yields total width and height of entire window.
  3827. // If the window hasn't been shown for the first time, the caller wants COORD_CENTERED.
  3828. {
  3829.     if (mGuiShowHasNeverBeenDone)
  3830.     {
  3831.         aWidth = COORD_CENTERED;
  3832.         aHeight = COORD_CENTERED;
  3833.         return;
  3834.     }
  3835.     // Otherwise, mGuiShowHasNeverBeenDone==false, which should mean that mHwnd!=NULL.
  3836.     RECT rect;
  3837.     GetWindowRect(mHwnd, &rect);
  3838.     aWidth = rect.right - rect.left;
  3839.     aHeight = rect.bottom - rect.top;
  3840. }
  3841.  
  3842.  
  3843.  
  3844. ResultType GuiType::ControlParseOptions(char *aOptions, GuiControlOptionsType &aOpt, GuiControlType &aControl
  3845.     , GuiIndexType aControlIndex)
  3846. // Caller must have already initialized aOpt with zeroes or any other desired starting values.
  3847. // Caller must ensure that aOptions is a modifiable string, since this method temporarily alters it.
  3848. {
  3849.     // If control type uses aControl's union for something other than color, communicate the chosen color
  3850.     // back through a means that doesn't corrupt the union:
  3851.     COLORREF &color_main = (aControl.type == GUI_CONTROL_LISTVIEW || aControl.type == GUI_CONTROL_PIC)
  3852.         ? aOpt.color_listview : aControl.union_color;
  3853.     char *next_option, *option_end, orig_char;
  3854.     bool adding; // Whether this option is beeing added (+) or removed (-).
  3855.     GuiControlType *tab_control;
  3856.     RECT rect;
  3857.     POINT pt;
  3858.  
  3859.     for (next_option = aOptions; *next_option; next_option = omit_leading_whitespace(option_end))
  3860.     {
  3861.         if (*next_option == '-')
  3862.         {
  3863.             adding = false;
  3864.             // omit_leading_whitespace() is not called, which enforces the fact that the option word must
  3865.             // immediately follow the +/- sign.  This is done to allow the flexibility to have options
  3866.             // omit the plus/minus sign, and also to reserve more flexibility for future option formats.
  3867.             ++next_option;  // Point it to the option word itself.
  3868.         }
  3869.         else
  3870.         {
  3871.             // Assume option is being added in the absence of either sign.  However, when we were
  3872.             // called by GuiControl(), the first option in the list must begin with +/- otherwise the cmd
  3873.             // would never have been properly detected as GUICONTROL_CMD_OPTIONS in the first place.
  3874.             adding = true;
  3875.             if (*next_option == '+')
  3876.                 ++next_option;  // Point it to the option word itself.
  3877.             //else do not increment, under the assumption that the plus has been omitted from a valid
  3878.             // option word and is thus an implicit plus.
  3879.         }
  3880.  
  3881.         if (!*next_option) // In case the entire option string ends in a naked + or -.
  3882.             break;
  3883.         // Find the end of this option item:
  3884.         if (   !(option_end = StrChrAny(next_option, " \t"))   )  // Space or tab.
  3885.             option_end = next_option + strlen(next_option); // Set to position of zero terminator instead.
  3886.         if (option_end == next_option)
  3887.             continue; // i.e. the string contains a + or - with a space or tab after it, which is intentionally ignored.
  3888.  
  3889.         // Temporarily terminate to help eliminate ambiguity for words contained inside other words,
  3890.         // such as "Checked" inside of "CheckedGray":
  3891.         orig_char = *option_end;
  3892.         *option_end = '\0';
  3893.  
  3894.         // Attributes:
  3895.         if (!stricmp(next_option, "Section")) // Adding and removing are treated the same in this case.
  3896.             aOpt.start_new_section = true;    // Ignored by caller when control already exists.
  3897.         else if (!stricmp(next_option, "AltSubmit") && aControl.type != GUI_CONTROL_EDIT)
  3898.         {
  3899.             // v1.0.44: Don't allow control's AltSubmit bit to be set unless it's valid option for
  3900.             // that type.  This protects the GUI_CONTROL_ATTRIB_ALTSUBMIT bit from being corrupted
  3901.             // in control types that use it for other/internal purposes.  Update: For code size reduction
  3902.             // and performance, only exclude control types that use the ALTSUBMIT bit for an internal
  3903.             // purpose vs. allowing the script to set it via "AltSubmit".
  3904.             if (adding) aControl.attrib |= GUI_CONTROL_ATTRIB_ALTSUBMIT; else aControl.attrib &= ~GUI_CONTROL_ATTRIB_ALTSUBMIT;
  3905.             //switch(aControl.type)
  3906.             //{
  3907.             //case GUI_CONTROL_TAB:
  3908.             //case GUI_CONTROL_PIC:
  3909.             //case GUI_CONTROL_DROPDOWNLIST:
  3910.             //case GUI_CONTROL_COMBOBOX:
  3911.             //case GUI_CONTROL_LISTBOX:
  3912.             //case GUI_CONTROL_LISTVIEW:
  3913.             //case GUI_CONTROL_TREEVIEW:
  3914.             //case GUI_CONTROL_MONTHCAL:
  3915.             //case GUI_CONTROL_SLIDER:
  3916.             //    if (adding) aControl.attrib |= GUI_CONTROL_ATTRIB_ALTSUBMIT; else aControl.attrib &= ~GUI_CONTROL_ATTRIB_ALTSUBMIT;
  3917.             //    break;
  3918.             //// All other types either use the bit for some internal purose or want it reserved for possible
  3919.             //// future use.  So don't allow the presence of "AltSubmit" to change the bit.
  3920.             //}
  3921.         }
  3922.  
  3923.         // Content of control (these are currently only effective if the control is being newly created):
  3924.         else if (!strnicmp(next_option, "Checked", 7)) // Caller knows to ignore if inapplicable. Applicable for ListView too.
  3925.         {
  3926.             next_option += 7;
  3927.             if (!stricmp(next_option, "Gray")) // Radios can't have the 3rd/gray state, but for simplicity it's permitted.
  3928.                 if (adding) aOpt.checked = BST_INDETERMINATE; else aOpt.checked = BST_UNCHECKED;
  3929.             else
  3930.             {
  3931.                 if (aControl.type == GUI_CONTROL_LISTVIEW)
  3932.                     if (adding) aOpt.listview_style |= LVS_EX_CHECKBOXES; else aOpt.listview_style &= ~LVS_EX_CHECKBOXES;
  3933.                 else
  3934.                 {
  3935.                     // As of v1.0.26, Checked/Hidden/Disabled can be followed by an optional 1/0/-1 so that
  3936.                     // there is a way for a script to set the starting state by reading from an INI or registry
  3937.                     // entry that contains 1 or 0 instead of needing the literal word "checked" stored in there.
  3938.                     // Otherwise, a script would have to do something like the following before every "Gui Add":
  3939.                     // if Box1Enabled
  3940.                     //    Enable = Enabled
  3941.                     // else
  3942.                     //    Enable =
  3943.                     // Gui Add, checkbox, %Enable%, My checkbox.
  3944.                     if (*next_option) // There's more after the word, namely a 1, 0, or -1.
  3945.                     {
  3946.                         aOpt.checked = ATOI(next_option);
  3947.                         if (aOpt.checked == -1)
  3948.                             aOpt.checked = BST_INDETERMINATE;
  3949.                     }
  3950.                     else // Below is also used for GUI_CONTROL_TREEVIEW creation because its checkboxes must be added AFTER the control is created.
  3951.                         aOpt.checked = adding; // BST_CHECKED == 1, BST_UNCHECKED == 0
  3952.                 }
  3953.             } // Non-checkedGRAY
  3954.         } // Checked.
  3955.         else if (!strnicmp(next_option, "Choose", 6))
  3956.         {
  3957.             // "CHOOSE" provides an easier way to conditionally select a different item at the time
  3958.             // the control is added.  Example: gui, add, ListBox, vMyList Choose%choice%, %MyItemList%
  3959.             // Caller should ignore aOpt.choice if it isn't applicable for this control type.
  3960.             if (adding)
  3961.             {
  3962.                 next_option += 6;
  3963.                 switch (aControl.type)
  3964.                 {
  3965.                 case GUI_CONTROL_DATETIME:
  3966.                     if (!stricmp(next_option, "None"))
  3967.                         aOpt.choice = 2; // Special flag value to indicate "none".
  3968.                     else // See if it's a valid date-time.
  3969.                         if (YYYYMMDDToSystemTime(next_option, aOpt.sys_time[0], true)) // Date string is valid.
  3970.                             aOpt.choice = 1; // Overwrite 0 to flag sys_time as both present and valid.
  3971.                         //else leave choice at its 0 default to indicate no valid Choose option was present.
  3972.                     break;
  3973.                 case GUI_CONTROL_MONTHCAL:
  3974.                     aOpt.gdtr = YYYYMMDDToSystemTime2(next_option, aOpt.sys_time);
  3975.                     // For code simplicity, both min and max must be present to enable a selected-range.
  3976.                     if (aOpt.gdtr == (GDTR_MIN | GDTR_MAX))
  3977.                         aOpt.style_add |= MCS_MULTISELECT;
  3978.                     //else never remove the style since it's valid to create a range-capable control via
  3979.                     // "Multi" that has only a single date selected (or none).  Also, if the control already
  3980.                     // exists, MSDN says that MCS_MULTISELECT cannot be added or removed.
  3981.                     break;
  3982.                 default:
  3983.                     aOpt.choice = ATOI(next_option);
  3984.                     if (aOpt.choice < 1) // Invalid: number should be 1 or greater.
  3985.                         aOpt.choice = 0; // Flag it as invalid.
  3986.                 }
  3987.             }
  3988.             //else do nothing (not currently implemented)
  3989.         }
  3990.  
  3991.         // Styles (general):
  3992.         else if (!stricmp(next_option, "Border"))
  3993.             if (adding) aOpt.style_add |= WS_BORDER; else aOpt.style_remove |= WS_BORDER;
  3994.         else if (!stricmp(next_option, "VScroll")) // Seems harmless in this case not to check aControl.type to ensure it's an input-capable control.
  3995.             if (adding) aOpt.style_add |= WS_VSCROLL; else aOpt.style_remove |= WS_VSCROLL;
  3996.         else if (!strnicmp(next_option, "HScroll", 7)) // Seems harmless in this case not to check aControl.type to ensure it's an input-capable control.
  3997.         {
  3998.             if (aControl.type == GUI_CONTROL_TREEVIEW)
  3999.                 // Testing shows that Tree doesn't seem to fully support removal of hscroll bar after creation.
  4000.                 if (adding) aOpt.style_remove |= TVS_NOHSCROLL; else aOpt.style_add |= TVS_NOHSCROLL;
  4001.             else
  4002.                 if (adding)
  4003.                 {
  4004.                     // MSDN: "To respond to the LB_SETHORIZONTALEXTENT message, the list box must have
  4005.                     // been defined with the WS_HSCROLL style."
  4006.                     aOpt.style_add |= WS_HSCROLL;
  4007.                     next_option += 7;
  4008.                     aOpt.hscroll_pixels = *next_option ? ATOI(next_option) : -1;  // -1 signals it to use a default based on control's width.
  4009.                 }
  4010.                 else
  4011.                     aOpt.style_remove |= WS_HSCROLL;
  4012.         }
  4013.         else if (!stricmp(next_option, "Tabstop")) // Seems harmless in this case not to check aControl.type to ensure it's an input-capable control.
  4014.             if (adding) aOpt.style_add |= WS_TABSTOP; else aOpt.style_remove |= WS_TABSTOP;
  4015.         else if (!stricmp(next_option, "NoTab")) // Supported for backward compatibility and it might be more ergonomic for "Gui Add".
  4016.             if (adding) aOpt.style_remove |= WS_TABSTOP; else aOpt.style_add |= WS_TABSTOP;
  4017.         else if (!stricmp(next_option, "Group")) // Because it starts with 'G', this overlaps with g-label, but seems well worth it in this case.
  4018.             if (adding) aOpt.style_add |= WS_GROUP; else aOpt.style_remove |= WS_GROUP;
  4019.         else if (!stricmp(next_option, "Redraw"))  // Seems a little more intuitive/memorable than "Draw".
  4020.             aOpt.redraw = adding ? CONDITION_TRUE : CONDITION_FALSE; // Otherwise leave it at its default of 0.
  4021.         else if (!strnicmp(next_option, "Disabled", 8))
  4022.         {
  4023.             // As of v1.0.26, Checked/Hidden/Disabled can be followed by an optional 1/0/-1 so that
  4024.             // there is a way for a script to set the starting state by reading from an INI or registry
  4025.             // entry that contains 1 or 0 instead of needing the literal word "checked" stored in there.
  4026.             // Otherwise, a script would have to do something like the following before every "Gui Add":
  4027.             // if Box1Enabled
  4028.             //    Enable = Enabled
  4029.             // else
  4030.             //    Enable =
  4031.             // Gui Add, checkbox, %Enable%, My checkbox.
  4032.             if (next_option[8] && !ATOI(next_option + 8)) // If it's Disabled0, invert the mode to become "enabled".
  4033.                 adding = !adding;
  4034.             if (aControl.hwnd) // More correct to call EnableWindow and let it set the style.  Do not set the style explicitly in this case since that might break it.
  4035.                 EnableWindow(aControl.hwnd, adding ? FALSE : TRUE);
  4036.             else
  4037.                 if (adding) aOpt.style_add |= WS_DISABLED; else aOpt.style_remove |= WS_DISABLED;
  4038.         }
  4039.         else if (!strnicmp(next_option, "Hidden", 6))
  4040.         {
  4041.             // As of v1.0.26, Checked/Hidden/Disabled can be followed by an optional 1/0/-1 so that
  4042.             // there is a way for a script to set the starting state by reading from an INI or registry
  4043.             // entry that contains 1 or 0 instead of needing the literal word "checked" stored in there.
  4044.             // Otherwise, a script would have to do something like the following before every "Gui Add":
  4045.             // if Box1Enabled
  4046.             //    Enable = Enabled
  4047.             // else
  4048.             //    Enable =
  4049.             // Gui Add, checkbox, %Enable%, My checkbox.
  4050.             if (next_option[6] && !ATOI(next_option + 6)) // If it's Hidden0, invert the mode to become "show".
  4051.                 adding = !adding;
  4052.             if (aControl.hwnd) // More correct to call ShowWindow() and let it set the style.  Do not set the style explicitly in this case since that might break it.
  4053.                 ShowWindow(aControl.hwnd, adding ? SW_HIDE : SW_SHOWNOACTIVATE);
  4054.             else
  4055.                 if (adding) aOpt.style_remove |= WS_VISIBLE; else aOpt.style_add |= WS_VISIBLE;
  4056.         }
  4057.         else if (!stricmp(next_option, "Wrap"))
  4058.         {
  4059.             switch(aControl.type)
  4060.             {
  4061.             case GUI_CONTROL_TEXT: // This one is a little tricky but the below should be appropriate in most cases:
  4062.                 if (adding) aOpt.style_remove |= SS_TYPEMASK; else aOpt.style_add = (aOpt.style_add & ~SS_TYPEMASK) | SS_LEFTNOWORDWRAP; // v1.0.44.10: Added SS_TYPEMASK to "else" section to provide more graceful handling for cases like "-Wrap +Center", which would otherwise put an unexpected style like SS_OWNERDRAW into effect.
  4063.                 break;
  4064.             case GUI_CONTROL_GROUPBOX:
  4065.             case GUI_CONTROL_BUTTON:
  4066.             case GUI_CONTROL_CHECKBOX:
  4067.             case GUI_CONTROL_RADIO:
  4068.                 if (adding) aOpt.style_add |= BS_MULTILINE; else aOpt.style_remove |= BS_MULTILINE;
  4069.                 break;
  4070.             case GUI_CONTROL_UPDOWN:
  4071.                 if (adding) aOpt.style_add |= UDS_WRAP; else aOpt.style_remove |= UDS_WRAP;
  4072.                 break;
  4073.             case GUI_CONTROL_EDIT: // Must be a multi-line now or shortly in the future or these will have no effect.
  4074.                 if (adding) aOpt.style_remove |= WS_HSCROLL|ES_AUTOHSCROLL; else aOpt.style_add |= ES_AUTOHSCROLL;
  4075.                 // WS_HSCROLL is removed because with it, wrapping is automatically off.
  4076.                 break;
  4077.             case GUI_CONTROL_TAB:
  4078.                 if (adding) aOpt.style_add |= TCS_MULTILINE; else aOpt.style_remove |= TCS_MULTILINE;
  4079.                 // WS_HSCROLL is removed because with it, wrapping is automatically off.
  4080.                 break;
  4081.             // N/A for these:
  4082.             //case GUI_CONTROL_PIC:
  4083.             //case GUI_CONTROL_DROPDOWNLIST:
  4084.             //case GUI_CONTROL_COMBOBOX:
  4085.             //case GUI_CONTROL_LISTBOX:
  4086.             //case GUI_CONTROL_LISTVIEW:
  4087.             //case GUI_CONTROL_TREEVIEW:
  4088.             //case GUI_CONTROL_DATETIME:
  4089.             //case GUI_CONTROL_MONTHCAL:
  4090.             //case GUI_CONTROL_HOTKEY:
  4091.             //case GUI_CONTROL_SLIDER:
  4092.             //case GUI_CONTROL_PROGRESS:
  4093.             }
  4094.         }
  4095.         else if (!strnicmp(next_option, "Background", 10))
  4096.         {
  4097.             next_option += 10;  // To help maintainability, point it to the optional suffix here.
  4098.             switch(aControl.type)
  4099.             {
  4100.             case GUI_CONTROL_PROGRESS:
  4101.             case GUI_CONTROL_LISTVIEW:
  4102.             case GUI_CONTROL_TREEVIEW:
  4103.             case GUI_CONTROL_STATUSBAR:
  4104.                 // Note that GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT and GUI_CONTROL_ATTRIB_BACKGROUND_TRANS
  4105.                 // don't apply to Progress or ListView controls because the window proc never receives
  4106.                 // CTLCOLOR messages for them.
  4107.                 if (adding)
  4108.                 {
  4109.                     aOpt.color_bk = ColorNameToBGR(next_option);
  4110.                     if (aOpt.color_bk == CLR_NONE) // A matching color name was not found, so assume it's in hex format.
  4111.                         // It seems strtol() automatically handles the optional leading "0x" if present:
  4112.                         aOpt.color_bk = rgb_to_bgr(strtol(next_option, NULL, 16));
  4113.                         // if next_option did not contain something hex-numeric, black (0x00) will be assumed,
  4114.                         // which seems okay given how rare such a problem would be.
  4115.                 }
  4116.                 else // Removing
  4117.                     aOpt.color_bk = CLR_DEFAULT;
  4118.                 break;
  4119.             default: // Other control types don't yet support custom colors other than TRANS.
  4120.                 if (adding)
  4121.                 {
  4122.                     aControl.attrib &= ~GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT;
  4123.                     if (!stricmp(next_option, "Trans"))
  4124.                         aControl.attrib |= GUI_CONTROL_ATTRIB_BACKGROUND_TRANS; // This is mutually exclusive of the above anyway.
  4125.                     else
  4126.                         aControl.attrib &= ~GUI_CONTROL_ATTRIB_BACKGROUND_TRANS;
  4127.                     // In the future, something like the below can help support background colors for individual controls.
  4128.                     //COLORREF background_color = ColorNameToBGR(next_option + 10);
  4129.                     //if (background_color == CLR_NONE) // A matching color name was not found, so assume it's in hex format.
  4130.                     //    // It seems strtol() automatically handles the optional leading "0x" if present:
  4131.                     //    background_color = rgb_to_bgr(strtol(next_option, NULL, 16));
  4132.                     //    // if next_option did not contain something hex-numeric, black (0x00) will be assumed,
  4133.                     //    // which seems okay given how rare such a problem would be.
  4134.                 }
  4135.                 else
  4136.                 {
  4137.                     // Note that "-BackgroundTrans" is not supported, since Trans is considered to be
  4138.                     // a color value for the purpose of expanding this feature in the future to support
  4139.                     // custom background colors on a per-control basis.  In other words, the trans factor
  4140.                     // can be turned off by using "-Background" or "+BackgroundBlue", etc.
  4141.                     aControl.attrib &= ~GUI_CONTROL_ATTRIB_BACKGROUND_TRANS;
  4142.                     aControl.attrib |= GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT;
  4143.                 }
  4144.             } // switch(aControl.type)
  4145.         } // Option "Background".
  4146.         else if (!stricmp(next_option, "Group")) // This overlaps with g-label, but seems well worth it in this case.
  4147.             if (adding) aOpt.style_add |= WS_GROUP; else aOpt.style_remove |= WS_GROUP;
  4148.         else if (!stricmp(next_option, "Theme"))
  4149.             aOpt.use_theme = adding;
  4150.         else if (!strnicmp(next_option, "Hwnd", 4))
  4151.             aOpt.hwnd_output_var = g_script.FindOrAddVar(next_option + 4, 0, ALWAYS_PREFER_LOCAL); // ALWAYS_PREFER_LOCAL is debatable, but for simplicity it seems best since it causes HwndOutputVar to behave the same as the vVar option.
  4152.  
  4153.         // Picture / ListView
  4154.         else if (!strnicmp(next_option, "Icon", 4)) // Caller should ignore aOpt.icon_number if it isn't applicable for this control type.
  4155.         {
  4156.             next_option += 4;
  4157.             if (aControl.type == GUI_CONTROL_LISTVIEW) // Unconditional regardless of the value of "adding".
  4158.                 aOpt.listview_view = stricmp(next_option, "Small") ? LVS_ICON : LVS_SMALLICON;
  4159.             else
  4160.                 if (adding)
  4161.                     aOpt.icon_number = ATOI(next_option);
  4162.                 //else do nothing (not currently implemented)
  4163.         }
  4164.         else if (!stricmp(next_option, "Report"))
  4165.             aOpt.listview_view = LVS_REPORT; // Unconditional regardless of the value of "adding".
  4166.         else if (!stricmp(next_option, "List"))
  4167.             aOpt.listview_view = LVS_LIST; // Unconditional regardless of the value of "adding".
  4168.         else if (!stricmp(next_option, "Tile")) // Fortunately, subsequent changes to the control's style do not pop it out of Tile mode. It's apparently smart enough to do that only when the LVS_TYPEMASK bits change.
  4169.         {
  4170.             if (g_os.IsWinXPorLater()) // Checking OS version here simplifies code in other places.
  4171.                 aOpt.listview_view = LV_VIEW_TILE; // LV_VIEW_TILE is compatible with LVS values such as LVS_REPORT because it doesn't overlap/conflict with them.
  4172.         }
  4173.         else if (aControl.type == GUI_CONTROL_LISTVIEW && !stricmp(next_option, "Hdr"))
  4174.             if (adding) aOpt.style_remove |= LVS_NOCOLUMNHEADER; else aOpt.style_add |= LVS_NOCOLUMNHEADER;
  4175.         else if (aControl.type == GUI_CONTROL_LISTVIEW && !strnicmp(next_option, "NoSort", 6))
  4176.         {
  4177.             if (!stricmp(next_option + 6, "Hdr")) // Prevents the header from being clickable like a set of buttons.
  4178.                 if (adding) aOpt.style_add |= LVS_NOSORTHEADER; else aOpt.style_remove |= LVS_NOSORTHEADER; // Testing shows it can't be changed after the control is created.
  4179.             else // Header is still clickable (unless above is *also* specified), but has no automatic sorting.
  4180.                 aOpt.listview_no_auto_sort = adding;
  4181.         }
  4182.         else if (aControl.type == GUI_CONTROL_LISTVIEW && !stricmp(next_option, "Grid"))
  4183.             if (adding) aOpt.listview_style |= LVS_EX_GRIDLINES; else aOpt.listview_style &= ~LVS_EX_GRIDLINES;
  4184.         else if (!strnicmp(next_option, "Count", 5)) // Script should only provide the option for ListViews.
  4185.             aOpt.limit = ATOI(next_option + 5); // For simplicity, the value of "adding" is ignored.
  4186.         else if (!strnicmp(next_option, "LV", 2))
  4187.         {
  4188.             next_option += 2;
  4189.             if (IsPureNumeric(next_option, false, false)) // Disallow whitespace in case option string ends in naked "LV".
  4190.             {
  4191.                 DWORD given_lvstyle = ATOU(next_option); // ATOU() for unsigned.
  4192.                 if (adding) aOpt.listview_style |= given_lvstyle; else aOpt.listview_style &= ~given_lvstyle;
  4193.             }
  4194.         }
  4195.         else if (!strnicmp(next_option, "ImageList", 9))
  4196.         {
  4197.             if (adding)
  4198.                 aOpt.himagelist = (HIMAGELIST)(size_t)ATOU(next_option + 9);
  4199.             //else removal not currently supported, since that would require detection of whether
  4200.             // to destroy the old imagelist, which is difficult to know because it might be in use
  4201.             // by other types of controls?
  4202.         }
  4203.  
  4204.         // Button
  4205.         else if (aControl.type == GUI_CONTROL_BUTTON && !stricmp(next_option, "Default"))
  4206.             if (adding) aOpt.style_add |= BS_DEFPUSHBUTTON; else aOpt.style_remove |= BS_DEFPUSHBUTTON;
  4207.         else if (aControl.type == GUI_CONTROL_CHECKBOX && !stricmp(next_option, "Check3")) // Radios can't have the 3rd/gray state.
  4208.             if (adding) aOpt.style_add |= BS_AUTO3STATE; else aOpt.style_remove |= BS_AUTO3STATE;
  4209.  
  4210.         // Edit (and upper/lowercase for combobox/ddl, and others)
  4211.         else if (!stricmp(next_option, "ReadOnly"))
  4212.         {
  4213.             switch (aControl.type)
  4214.             {
  4215.             case GUI_CONTROL_EDIT:
  4216.                 if (aControl.hwnd) // Update the existing edit.  Must use SendMessage() vs. changing the style.
  4217.                     SendMessage(aControl.hwnd, EM_SETREADONLY, adding, 0);
  4218.                 else
  4219.                     if (adding) aOpt.style_add |= ES_READONLY; else aOpt.style_remove |= ES_READONLY;
  4220.                 break;
  4221.             case GUI_CONTROL_LISTBOX:
  4222.                 if (adding) aOpt.style_add |= LBS_NOSEL; else aOpt.style_remove |= LBS_NOSEL;
  4223.                 break;
  4224.             case GUI_CONTROL_LISTVIEW:
  4225.                 if (adding) aOpt.style_remove |= LVS_EDITLABELS; else aOpt.style_add |= LVS_EDITLABELS;
  4226.                 break;
  4227.             case GUI_CONTROL_TREEVIEW:
  4228.                 if (adding) aOpt.style_remove |= TVS_EDITLABELS; else aOpt.style_add |= TVS_EDITLABELS;
  4229.                 break;
  4230.             }
  4231.         }
  4232.         else if (!stricmp(next_option, "Multi"))
  4233.         {
  4234.             // It was named "multi" vs. multiline and/or "MultiSel" because it seems easier to
  4235.             // remember in these cases.  In fact, any time two styles can be combined into one
  4236.             // name whose actual function depends on the control type, it seems likely to make
  4237.             // things easier to remember.
  4238.             switch(aControl.type)
  4239.             {
  4240.             case GUI_CONTROL_EDIT:
  4241.                 if (adding) aOpt.style_add |= ES_MULTILINE; else aOpt.style_remove |= ES_MULTILINE;
  4242.                 break;
  4243.             case GUI_CONTROL_LISTBOX:
  4244.                 if (adding) aOpt.style_add |= LBS_EXTENDEDSEL; else aOpt.style_remove |= LBS_EXTENDEDSEL;
  4245.                 break;
  4246.             case GUI_CONTROL_LISTVIEW:
  4247.                 if (adding) aOpt.style_remove |= LVS_SINGLESEL; else aOpt.style_add |= LVS_SINGLESEL;
  4248.                 break;
  4249.             case GUI_CONTROL_MONTHCAL:
  4250.                 if (adding) aOpt.style_add |= MCS_MULTISELECT; else aOpt.style_remove |= MCS_MULTISELECT;
  4251.                 break;
  4252.             }
  4253.         }
  4254.         else if (aControl.type == GUI_CONTROL_EDIT && !stricmp(next_option, "WantReturn"))
  4255.             if (adding) aOpt.style_add |= ES_WANTRETURN; else aOpt.style_remove |= ES_WANTRETURN;
  4256.         else if (aControl.type == GUI_CONTROL_EDIT && !stricmp(next_option, "WantTab"))
  4257.             if (adding) aControl.attrib |= GUI_CONTROL_ATTRIB_ALTBEHAVIOR; else aControl.attrib &= ~GUI_CONTROL_ATTRIB_ALTBEHAVIOR;
  4258.         else if (aControl.type == GUI_CONTROL_EDIT && !stricmp(next_option, "WantCtrlA")) // v1.0.44: Presence of AltSubmit bit means DON'T want Ctrl-A.
  4259.             if (adding) aControl.attrib &= ~GUI_CONTROL_ATTRIB_ALTSUBMIT; else aControl.attrib |= GUI_CONTROL_ATTRIB_ALTSUBMIT;
  4260.         else if ((aControl.type == GUI_CONTROL_LISTVIEW || aControl.type == GUI_CONTROL_TREEVIEW)
  4261.             && !stricmp(next_option, "WantF2")) // v1.0.44: All an F2 keystroke to edit the focused item.
  4262.             // Since WantF2 is the initial default, a script will almost never specify WantF2.  Therefore, it's
  4263.             // probably not worth the code size to put -ReadOnly into effect automatically for +WantF2.
  4264.             if (adding) aControl.attrib &= ~GUI_CONTROL_ATTRIB_ALTBEHAVIOR; else aControl.attrib |= GUI_CONTROL_ATTRIB_ALTBEHAVIOR;
  4265.         else if (aControl.type == GUI_CONTROL_EDIT && !stricmp(next_option, "Number"))
  4266.             if (adding) aOpt.style_add |= ES_NUMBER; else aOpt.style_remove |= ES_NUMBER;
  4267.         else if (!stricmp(next_option, "Lowercase"))
  4268.         {
  4269.             if (aControl.type == GUI_CONTROL_EDIT)
  4270.                 if (adding) aOpt.style_add |= ES_LOWERCASE; else aOpt.style_remove |= ES_LOWERCASE;
  4271.             else if (aControl.type == GUI_CONTROL_COMBOBOX || aControl.type == GUI_CONTROL_DROPDOWNLIST)
  4272.                 if (adding) aOpt.style_add |= CBS_LOWERCASE; else aOpt.style_remove |= CBS_LOWERCASE;
  4273.         }
  4274.         else if (!stricmp(next_option, "Uppercase"))
  4275.         {
  4276.             if (aControl.type == GUI_CONTROL_EDIT)
  4277.                 if (adding) aOpt.style_add |= ES_UPPERCASE; else aOpt.style_remove |= ES_UPPERCASE;
  4278.             else if (aControl.type == GUI_CONTROL_COMBOBOX || aControl.type == GUI_CONTROL_DROPDOWNLIST)
  4279.                 if (adding) aOpt.style_add |= CBS_UPPERCASE; else aOpt.style_remove |= CBS_UPPERCASE;
  4280.         }
  4281.         else if (aControl.type == GUI_CONTROL_EDIT && !strnicmp(next_option, "Password", 8))
  4282.         {
  4283.             // Allow a space to be the masking character, since it's conceivable that might
  4284.             // be wanted in cases where someone doesn't wany anyone to know they're typing a password.
  4285.             // Simplest to assign unconditionally, regardless of whether adding or removing:
  4286.             aOpt.password_char = next_option[8];  // Can be '\0', which indicates "use OS default".
  4287.             if (adding)
  4288.             {
  4289.                 aOpt.style_add |= ES_PASSWORD;
  4290.                 if (aControl.hwnd) // Update the existing edit.
  4291.                 {
  4292.                     // Don't know how to achieve the black circle on XP *after* the control has
  4293.                     // been created.  Maybe it's impossible.  Thus, provide default since otherwise
  4294.                     // pass-char will be removed vs. added:
  4295.                     if (!aOpt.password_char)
  4296.                         aOpt.password_char = '*';
  4297.                     SendMessage(aControl.hwnd, EM_SETPASSWORDCHAR, (WPARAM)aOpt.password_char, 0);
  4298.                 }
  4299.             }
  4300.             else
  4301.             {
  4302.                 aOpt.style_remove |= ES_PASSWORD;
  4303.                 if (aControl.hwnd) // Update the existing edit.
  4304.                     SendMessage(aControl.hwnd, EM_SETPASSWORDCHAR, 0, 0);
  4305.             }
  4306.         }
  4307.         else if (!strnicmp(next_option, "Limit", 5)) // This is used for Hotkey controls also.
  4308.         {
  4309.             if (adding)
  4310.             {
  4311.                 next_option += 5;
  4312.                 aOpt.limit = *next_option ? ATOI(next_option) : -1;  // -1 signals it to limit input to visible width of field.
  4313.                 // aOpt.limit will later be ignored for some control types.
  4314.             }
  4315.             else
  4316.                 aOpt.limit = INT_MIN; // Signal it to remove the limit.
  4317.         }
  4318.  
  4319.         // Combo/DropDownList/ListBox/ListView
  4320.         else if (aControl.type == GUI_CONTROL_COMBOBOX && !stricmp(next_option, "Simple")) // DDL is not equipped to handle this style.
  4321.             if (adding) aOpt.style_add |= CBS_SIMPLE; else aOpt.style_remove |= CBS_SIMPLE;
  4322.         else if (!strnicmp(next_option, "Sort", 4))
  4323.         {
  4324.             switch(aControl.type)
  4325.             {
  4326.             case GUI_CONTROL_LISTBOX:
  4327.                 if (adding) aOpt.style_add |= LBS_SORT; else aOpt.style_remove |= LBS_SORT;
  4328.                 break;
  4329.             case GUI_CONTROL_LISTVIEW: // LVS_SORTDESCENDING is not a named style due to rarity of use.
  4330.                 if (adding)
  4331.                     aOpt.style_add |= stricmp(next_option + 4, "Desc") ? LVS_SORTASCENDING : LVS_SORTDESCENDING;
  4332.                 else
  4333.                     aOpt.style_remove |= LVS_SORTASCENDING|LVS_SORTDESCENDING;
  4334.                 break;
  4335.             case GUI_CONTROL_DROPDOWNLIST:
  4336.             case GUI_CONTROL_COMBOBOX:
  4337.                 if (adding) aOpt.style_add |= CBS_SORT; else aOpt.style_remove |= CBS_SORT;
  4338.                 break;
  4339.             }
  4340.         }
  4341.  
  4342.         // UpDown
  4343.         else if (aControl.type == GUI_CONTROL_UPDOWN && !stricmp(next_option, "Horz"))
  4344.             if (adding)
  4345.             {
  4346.                 aOpt.style_add |= UDS_HORZ;
  4347.                 aOpt.style_add &= ~UDS_AUTOBUDDY; // Doing it this way allows "Horz +0x10" to override Horz's lack of buddy.
  4348.             }
  4349.             else
  4350.                 aOpt.style_remove |= UDS_HORZ; // But don't add UDS_AUTOBUDDY since it seems undesirable most of the time.
  4351.  
  4352.         // Slider
  4353.         else if (aControl.type == GUI_CONTROL_SLIDER && !stricmp(next_option, "Invert")) // Not called "Reverse" to avoid confusion with the non-functional style of that name.
  4354.             if (adding) aControl.attrib |= GUI_CONTROL_ATTRIB_ALTBEHAVIOR; else aControl.attrib &= ~GUI_CONTROL_ATTRIB_ALTBEHAVIOR;
  4355.         else if (aControl.type == GUI_CONTROL_SLIDER && !stricmp(next_option, "NoTicks"))
  4356.             if (adding) aOpt.style_add |= TBS_NOTICKS; else aOpt.style_remove |= TBS_NOTICKS;
  4357.         else if (aControl.type == GUI_CONTROL_SLIDER && !strnicmp(next_option, "TickInterval", 12))
  4358.         {
  4359.             if (adding)
  4360.             {
  4361.                 aOpt.style_add |= TBS_AUTOTICKS;
  4362.                 aOpt.tick_interval = ATOI(next_option + 12);
  4363.             }
  4364.             else
  4365.             {
  4366.                 aOpt.style_remove |= TBS_AUTOTICKS;
  4367.                 aOpt.tick_interval = -1;  // Signal it to remove the ticks later below (if the window exists).
  4368.             }
  4369.         }
  4370.         else if (!strnicmp(next_option, "Line", 4))
  4371.         {
  4372.             next_option += 4;
  4373.             if (aControl.type == GUI_CONTROL_SLIDER)
  4374.             {
  4375.                 if (adding)
  4376.                     aOpt.line_size = ATOI(next_option);
  4377.                 //else removal not supported.
  4378.             }
  4379.             else if (aControl.type == GUI_CONTROL_TREEVIEW && toupper(*next_option) == 'S')
  4380.                 // Seems best to consider TVS_HASLINES|TVS_LINESATROOT to be an inseparable group since
  4381.                 // one without the other is rare (script can always be overridden by specifying numeric styles):
  4382.                 if (adding) aOpt.style_add |= TVS_HASLINES|TVS_LINESATROOT; else aOpt.style_remove |= TVS_HASLINES|TVS_LINESATROOT;
  4383.         }
  4384.         else if (aControl.type == GUI_CONTROL_SLIDER && !strnicmp(next_option, "Page", 4))
  4385.         {
  4386.             if (adding)
  4387.                 aOpt.page_size = ATOI(next_option + 4);
  4388.             //else removal not supported.
  4389.         }
  4390.         else if (aControl.type == GUI_CONTROL_SLIDER && !strnicmp(next_option, "Thick", 5))
  4391.         {
  4392.             if (adding)
  4393.             {
  4394.                 aOpt.style_add |= TBS_FIXEDLENGTH;
  4395.                 aOpt.thickness = ATOI(next_option + 5);
  4396.             }
  4397.             else // Removing the style is enough to reset its appearance on both XP Theme and Classic Theme.
  4398.                 aOpt.style_remove |= TBS_FIXEDLENGTH;
  4399.         }
  4400.         else if (!strnicmp(next_option, "ToolTip", 7))
  4401.         {
  4402.             next_option += 7;
  4403.             // Below was commented out because the SBARS_TOOLTIPS doesn't seem to do much, if anything.
  4404.             // See bottom of BIF_StatusBar() for more comments.
  4405.             //if (aControl.type == GUI_CONTROL_STATUSBAR)
  4406.             //{
  4407.             //    if (!*next_option)
  4408.             //        if (adding) aOpt.style_add |= SBARS_TOOLTIPS; else aOpt.style_remove |= SBARS_TOOLTIPS;
  4409.             //}
  4410.             //else
  4411.             if (aControl.type == GUI_CONTROL_SLIDER)
  4412.             {
  4413.                 if (adding)
  4414.                 {
  4415.                     aOpt.tip_side = -1;  // Set default.
  4416.                     switch(toupper(*next_option))
  4417.                     {
  4418.                     case 'T': aOpt.tip_side = TBTS_TOP; break;
  4419.                     case 'L': aOpt.tip_side = TBTS_LEFT; break;
  4420.                     case 'B': aOpt.tip_side = TBTS_BOTTOM; break;
  4421.                     case 'R': aOpt.tip_side = TBTS_RIGHT; break;
  4422.                     }
  4423.                     if (aOpt.tip_side < 0)
  4424.                         aOpt.tip_side = 0; // Restore to the value that means "use default side".
  4425.                     else
  4426.                         ++aOpt.tip_side; // Offset by 1, since zero is reserved as "use default side".
  4427.                     aOpt.style_add |= TBS_TOOLTIPS;
  4428.                 }
  4429.                 else
  4430.                     aOpt.style_remove |= TBS_TOOLTIPS;
  4431.             }
  4432.         }
  4433.         else if (aControl.type == GUI_CONTROL_SLIDER && !strnicmp(next_option, "Buddy", 5))
  4434.         {
  4435.             if (adding)
  4436.             {
  4437.                 next_option += 5;
  4438.                 char which_buddy = *next_option;
  4439.                 if (which_buddy) // i.e. it's not the zero terminator
  4440.                 {
  4441.                     ++next_option; // Now it should point to the variable name of the buddy control.
  4442.                     // Check if there's an existing *global* variable of this name.  It must be global
  4443.                     // because the variable of a control can never be a local variable:
  4444.                     Var *var = g_script.FindVar(next_option, 0, NULL, ALWAYS_USE_GLOBAL); // Search globals only.
  4445.                     if (var)
  4446.                     {
  4447.                         var = var->ResolveAlias(); // Update it to its target if it's an alias.
  4448.                         if (!var->IsLocal()) // Must be global.  Note that an alias can point to a local vs. global var.
  4449.                             // Below relies on GuiIndexType underflow:
  4450.                             for (GuiIndexType u = mControlCount - 1; u < mControlCount; --u) // Search in reverse for better avg-case performance.
  4451.                                 if (mControl[u].output_var == var)
  4452.                                     if (which_buddy == '1')
  4453.                                         aOpt.buddy1 = &mControl[u];
  4454.                                     else // assume '2'
  4455.                                         aOpt.buddy2 = &mControl[u];
  4456.                     }
  4457.                 }
  4458.             }
  4459.             //else removal not supported.
  4460.         }
  4461.  
  4462.         // Progress and Slider
  4463.         else if (!stricmp(next_option, "Vertical"))
  4464.         {
  4465.             // Seems best not to recognize Vertical for Tab controls since Left and Right
  4466.             // already cover it very well.
  4467.             if (aControl.type == GUI_CONTROL_SLIDER)
  4468.                 if (adding) aOpt.style_add |= TBS_VERT; else aOpt.style_remove |= TBS_VERT;
  4469.             else if (aControl.type == GUI_CONTROL_PROGRESS)
  4470.                 if (adding) aOpt.style_add |= PBS_VERTICAL; else aOpt.style_remove |= PBS_VERTICAL;
  4471.             //else do nothing, not a supported type
  4472.         }
  4473.         else if (!strnicmp(next_option, "Range", 5)) // Caller should ignore aOpt.range_min/max if it isn't applicable for this control type.
  4474.         {
  4475.             if (adding)
  4476.             {
  4477.                 next_option += 5; // Helps with omitting the first minus sign, if any, below.
  4478.                 if (*next_option) // Prevent reading beyond the zero terminator due to next_option+1 in some places below.
  4479.                 {
  4480.                     char *cp;
  4481.                     if (aControl.type == GUI_CONTROL_DATETIME || aControl.type == GUI_CONTROL_MONTHCAL)
  4482.                     {
  4483.                         // Note: aOpt.range_changed is not set for these control types. aOpt.gdtr_range is used instead.
  4484.                         aOpt.gdtr_range = YYYYMMDDToSystemTime2(next_option, aOpt.sys_time_range);
  4485.                         if (aOpt.gdtr_range && aControl.hwnd) // Caller relies on us doing it now.
  4486.                         {
  4487.                             SendMessage(aControl.hwnd // MCM_SETRANGE != DTM_SETRANGE
  4488.                                 , aControl.type == GUI_CONTROL_DATETIME ? DTM_SETRANGE : MCM_SETRANGE
  4489.                                 , aOpt.gdtr_range, (LPARAM)aOpt.sys_time_range);
  4490.                             // Unlike GUI_CONTROL_DATETIME, GUI_CONTROL_MONTHCAL doesn't visibly update selection
  4491.                             // when new range is applied, not even with InvalidateRect().  In fact, it doesn't
  4492.                             // internally update either.  This might be worked around by getting the selected
  4493.                             // date (or range of dates) and reapplying them, but the need is rare enough that
  4494.                             // code size reduction seems more important.
  4495.                         }
  4496.                     }
  4497.                     else // Control types other than datetime/monthcal.
  4498.                     {
  4499.                         aOpt.range_changed = true;
  4500.                         aOpt.range_min = ATOI(next_option);
  4501.                         if (cp = strchr(next_option + 1, '-')) // +1 to omit the min's minus sign, if it has one.
  4502.                             aOpt.range_max = ATOI(cp + 1);
  4503.                     }
  4504.                 }
  4505.                 //else the Range word is present but has nothing after it.  Ignore it.
  4506.             }
  4507.             //else removing.  Do nothing (not currently implemented).
  4508.         }
  4509.  
  4510.         // Progress
  4511.         else if (aControl.type == GUI_CONTROL_PROGRESS && !stricmp(next_option, "Smooth"))
  4512.             if (adding) aOpt.style_add |= PBS_SMOOTH; else aOpt.style_remove |= PBS_SMOOTH;
  4513.  
  4514.         // Tab control
  4515.         else if (!stricmp(next_option, "Buttons"))
  4516.         {
  4517.             if (aControl.type == GUI_CONTROL_TAB)
  4518.                 if (adding) aOpt.style_add |= TCS_BUTTONS; else aOpt.style_remove |= TCS_BUTTONS;
  4519.             else if (aControl.type == GUI_CONTROL_TREEVIEW)
  4520.                 if (adding) aOpt.style_add |= TVS_HASBUTTONS; else aOpt.style_remove |= TVS_HASBUTTONS;
  4521.         }
  4522.         else if (aControl.type == GUI_CONTROL_TAB && !stricmp(next_option, "Bottom"))
  4523.             if (adding)
  4524.             {
  4525.                 aOpt.style_add |= TCS_BOTTOM;
  4526.                 aOpt.style_remove |= TCS_VERTICAL;
  4527.             }
  4528.             else
  4529.                 aOpt.style_remove |= TCS_BOTTOM;
  4530.  
  4531.         // Styles (alignment/justification):
  4532.         else if (!stricmp(next_option, "Center"))
  4533.             if (adding)
  4534.             {
  4535.                 switch (aControl.type)
  4536.                 {
  4537.                 case GUI_CONTROL_SLIDER:
  4538.                     if (adding) aOpt.style_add |= TBS_BOTH;
  4539.                     aOpt.style_remove |= TBS_LEFT;
  4540.                     break;
  4541.                 case GUI_CONTROL_TEXT:
  4542.                     aOpt.style_add |= SS_CENTER;
  4543.                     aOpt.style_remove |= (SS_TYPEMASK & ~SS_CENTER); // i.e. Zero out all type-bits except SS_CENTER's bit.
  4544.                     break;
  4545.                 case GUI_CONTROL_GROUPBOX: // Changes alignment of its label.
  4546.                 case GUI_CONTROL_BUTTON:   // Probably has no effect in this case, since it's centered by default?
  4547.                 case GUI_CONTROL_CHECKBOX: // Puts gap between box and label.
  4548.                 case GUI_CONTROL_RADIO:
  4549.                     aOpt.style_add |= BS_CENTER;
  4550.                     // But don't remove BS_LEFT or BS_RIGHT since BS_CENTER is defined as a combination of them.
  4551.                     break;
  4552.                 case GUI_CONTROL_EDIT:
  4553.                     aOpt.style_add |= ES_CENTER;
  4554.                     aOpt.style_remove |= ES_RIGHT; // Mutually exclusive since together they are (probably) invalid.
  4555.                     break;
  4556.                 // Not applicable for:
  4557.                 //case GUI_CONTROL_PIC: SS_CENTERIMAGE is currently not used due to auto-pic-scaling/fitting.
  4558.                 //case GUI_CONTROL_DROPDOWNLIST:
  4559.                 //case GUI_CONTROL_COMBOBOX:
  4560.                 //case GUI_CONTROL_LISTBOX:
  4561.                 //case GUI_CONTROL_LISTVIEW:
  4562.                 //case GUI_CONTROL_TREEVIEW:
  4563.                 //case GUI_CONTROL_UPDOWN:
  4564.                 //case GUI_CONTROL_DATETIME:
  4565.                 //case GUI_CONTROL_MONTHCAL:
  4566.                 }
  4567.             }
  4568.             else // Removing.
  4569.             {
  4570.                 switch (aControl.type)
  4571.                 {
  4572.                 case GUI_CONTROL_SLIDER:
  4573.                     aOpt.style_remove |= TBS_BOTH;
  4574.                     break;
  4575.                 case GUI_CONTROL_TEXT:
  4576.                     aOpt.style_remove |= SS_TYPEMASK; // Revert to SS_LEFT because there's no way of knowing what the intended or previous value was.
  4577.                     break;
  4578.                 case GUI_CONTROL_GROUPBOX:
  4579.                 case GUI_CONTROL_BUTTON:
  4580.                 case GUI_CONTROL_CHECKBOX:
  4581.                 case GUI_CONTROL_RADIO:
  4582.                     // BS_CENTER is a tricky one since it is a combination of BS_LEFT and BS_RIGHT.
  4583.                     // If the control exists and has either BS_LEFT or BS_RIGHT (but not both), do
  4584.                     // nothing:
  4585.                     if (aControl.hwnd)
  4586.                     {
  4587.                         // v1.0.44.08: Fixed the following by adding "== BS_CENTER":
  4588.                         if ((GetWindowLong(aControl.hwnd, GWL_STYLE) & BS_CENTER) == BS_CENTER) // i.e. it has both BS_LEFT and BS_RIGHT
  4589.                             aOpt.style_remove |= BS_CENTER;
  4590.                         //else nothing needs to be done.
  4591.                     }
  4592.                     else // v1.0.44.08: Fixed the following by adding "== BS_CENTER":
  4593.                         if ((aOpt.style_add & BS_CENTER) == BS_CENTER) // i.e. Both BS_LEFT and BS_RIGHT are set to be added.
  4594.                             aOpt.style_add &= ~BS_CENTER; // Undo it, which later helps avoid the need to apply style_add prior to style_remove.
  4595.                         //else nothing needs to be done.
  4596.                     break;
  4597.                 case GUI_CONTROL_EDIT:
  4598.                     aOpt.style_remove |= ES_CENTER;
  4599.                     break;
  4600.                 // Not applicable for:
  4601.                 //case GUI_CONTROL_PIC: SS_CENTERIMAGE is currently not used due to auto-pic-scaling/fitting.
  4602.                 //case GUI_CONTROL_DROPDOWNLIST:
  4603.                 //case GUI_CONTROL_COMBOBOX:
  4604.                 //case GUI_CONTROL_LISTBOX:
  4605.                 //case GUI_CONTROL_LISTVIEW:
  4606.                 //case GUI_CONTROL_TREEVIEW:
  4607.                 //case GUI_CONTROL_UPDOWN:
  4608.                 //case GUI_CONTROL_DATETIME:
  4609.                 //case GUI_CONTROL_MONTHCAL:
  4610.                 }
  4611.             }
  4612.  
  4613.         else if (!stricmp(next_option, "Right"))
  4614.             if (adding)
  4615.             {
  4616.                 switch (aControl.type)
  4617.                 {
  4618.                 case GUI_CONTROL_UPDOWN:
  4619.                     aOpt.style_remove |= UDS_ALIGNLEFT;
  4620.                     aOpt.style_add |= UDS_ALIGNRIGHT;
  4621.                     break;
  4622.                 case GUI_CONTROL_DATETIME:
  4623.                     aOpt.style_add |= DTS_RIGHTALIGN;
  4624.                     break;
  4625.                 case GUI_CONTROL_SLIDER:
  4626.                     aOpt.style_remove |= TBS_LEFT|TBS_BOTH;
  4627.                     break;
  4628.                 case GUI_CONTROL_TEXT:
  4629.                     aOpt.style_add |= SS_RIGHT;
  4630.                     aOpt.style_remove |= (SS_TYPEMASK & ~SS_RIGHT); // i.e. Zero out all type-bits except SS_RIGHT's bit.
  4631.                     break;
  4632.                 case GUI_CONTROL_GROUPBOX:
  4633.                 case GUI_CONTROL_BUTTON:
  4634.                 case GUI_CONTROL_CHECKBOX:
  4635.                 case GUI_CONTROL_RADIO:
  4636.                     aOpt.style_add |= BS_RIGHT;
  4637.                     // Doing this indirectly removes BS_CENTER, and does it in a way that makes unimportant
  4638.                     // the order in which style_add and style_remove are applied later:
  4639.                     aOpt.style_remove |= BS_LEFT;
  4640.                     // And by default, put button itself to the right of its label since that seems
  4641.                     // likely to be far more common/desirable (there can be a more obscure option
  4642.                     // later to change this default):
  4643.                     if (aControl.type == GUI_CONTROL_CHECKBOX || aControl.type == GUI_CONTROL_RADIO)
  4644.                         aOpt.style_add |= BS_RIGHTBUTTON;
  4645.                     break;
  4646.                 case GUI_CONTROL_EDIT:
  4647.                     aOpt.style_add |= ES_RIGHT;
  4648.                     aOpt.style_remove |= ES_CENTER; // Mutually exclusive since together they are (probably) invalid.
  4649.                     break;
  4650.                 case GUI_CONTROL_TAB:
  4651.                     aOpt.style_add |= TCS_VERTICAL|TCS_MULTILINE|TCS_RIGHT;
  4652.                     break;
  4653.                 // Not applicable for:
  4654.                 //case GUI_CONTROL_MONTHCAL:
  4655.                 //case GUI_CONTROL_PIC: SS_RIGHTJUST is currently not used due to auto-pic-scaling/fitting.
  4656.                 //case GUI_CONTROL_DROPDOWNLIST:
  4657.                 //case GUI_CONTROL_COMBOBOX:
  4658.                 //case GUI_CONTROL_LISTBOX:
  4659.                 //case GUI_CONTROL_LISTVIEW:
  4660.                 //case GUI_CONTROL_TREEVIEW:
  4661.                 }
  4662.             }
  4663.             else // Removing.
  4664.             {
  4665.                 switch (aControl.type)
  4666.                 {
  4667.                 case GUI_CONTROL_DATETIME:
  4668.                     aOpt.style_remove |= DTS_RIGHTALIGN;
  4669.                     break;
  4670.                 case GUI_CONTROL_SLIDER:
  4671.                     aOpt.style_add |= TBS_LEFT;
  4672.                     aOpt.style_remove |= TBS_BOTH; // Debatable.
  4673.                     break;
  4674.                 case GUI_CONTROL_TEXT:
  4675.                     aOpt.style_remove |= SS_TYPEMASK; // Revert to SS_LEFT because there's no way of knowing what the intended or previous value was.
  4676.                     break;
  4677.                 case GUI_CONTROL_GROUPBOX:
  4678.                 case GUI_CONTROL_BUTTON:
  4679.                 case GUI_CONTROL_CHECKBOX:
  4680.                 case GUI_CONTROL_RADIO:
  4681.                     // BS_RIGHT is a tricky one since it is included inside BS_CENTER.
  4682.                     // Thus, if the control exists and has BS_CENTER, do nothing since
  4683.                     // BS_RIGHT can't be in effect if BS_CENTER already is:
  4684.                     if (aControl.hwnd)
  4685.                     {
  4686.                         if ((GetWindowLong(aControl.hwnd, GWL_STYLE) & BS_CENTER) != BS_CENTER) // v1.0.44.08: Fixed by adding "!= BS_CENTER".
  4687.                             aOpt.style_remove |= BS_RIGHT;
  4688.                     }
  4689.                     else
  4690.                         if ((aOpt.style_add & BS_CENTER) != BS_CENTER) // v1.0.44.08: Fixed by adding "!= BS_CENTER".
  4691.                             aOpt.style_add &= ~BS_RIGHT;  // A little strange, but seems correct since control hasn't even been created yet.
  4692.                         //else nothing needs to be done because BS_RIGHT is already in effect removed since
  4693.                         //BS_CENTER makes BS_RIGHT impossible to manifest.
  4694.                     break;
  4695.                 case GUI_CONTROL_EDIT:
  4696.                     aOpt.style_remove |= ES_RIGHT;
  4697.                     break;
  4698.                 case GUI_CONTROL_TAB:
  4699.                     aOpt.style_remove |= TCS_VERTICAL|TCS_RIGHT;
  4700.                     break;
  4701.                 // Not applicable for:
  4702.                 //case GUI_CONTROL_UPDOWN: Removing "right" doesn't make much sense, so only adding "left" is supported.
  4703.                 //case GUI_CONTROL_MONTHCAL:
  4704.                 //case GUI_CONTROL_PIC: SS_RIGHTJUST is currently not used due to auto-pic-scaling/fitting.
  4705.                 //case GUI_CONTROL_DROPDOWNLIST:
  4706.                 //case GUI_CONTROL_COMBOBOX:
  4707.                 //case GUI_CONTROL_LISTBOX:
  4708.                 //case GUI_CONTROL_LISTVIEW:
  4709.                 //case GUI_CONTROL_TREEVIEW:
  4710.                 }
  4711.             }
  4712.  
  4713.         else if (!stricmp(next_option, "Left"))
  4714.         {
  4715.             if (adding)
  4716.             {
  4717.                 switch (aControl.type)
  4718.                 {
  4719.                 case GUI_CONTROL_UPDOWN:
  4720.                     aOpt.style_remove |= UDS_ALIGNRIGHT;
  4721.                     aOpt.style_add |= UDS_ALIGNLEFT;
  4722.                     break;
  4723.                 case GUI_CONTROL_DATETIME:
  4724.                     aOpt.style_remove |= DTS_RIGHTALIGN;
  4725.                     break;
  4726.                 case GUI_CONTROL_SLIDER:
  4727.                     aOpt.style_add |= TBS_LEFT;
  4728.                     aOpt.style_remove |= TBS_BOTH;
  4729.                     break;
  4730.                 case GUI_CONTROL_TEXT:
  4731.                     aOpt.style_remove |= SS_TYPEMASK; // i.e. Zero out all type-bits to expose the default of 0, which is SS_LEFT.
  4732.                     break;
  4733.                 case GUI_CONTROL_CHECKBOX:
  4734.                 case GUI_CONTROL_GROUPBOX:
  4735.                 case GUI_CONTROL_BUTTON:
  4736.                 case GUI_CONTROL_RADIO:
  4737.                     aOpt.style_add |= BS_LEFT;
  4738.                     // Doing this indirectly removes BS_CENTER, and does it in a way that makes unimportant
  4739.                     // the order in which style_add and style_remove are applied later:
  4740.                     aOpt.style_remove |= BS_RIGHT;
  4741.                     // And by default, put button itself to the left of its label since that seems
  4742.                     // likely to be far more common/desirable (there can be a more obscure option
  4743.                     // later to change this default):
  4744.                     if (aControl.type == GUI_CONTROL_CHECKBOX || aControl.type == GUI_CONTROL_RADIO)
  4745.                         aOpt.style_remove |= BS_RIGHTBUTTON;
  4746.                     break;
  4747.                 case GUI_CONTROL_EDIT:
  4748.                     aOpt.style_remove |= ES_RIGHT|ES_CENTER;  // Removing these exposes the default of 0, which is LEFT.
  4749.                     break;
  4750.                 case GUI_CONTROL_TAB:
  4751.                     aOpt.style_add |= TCS_VERTICAL|TCS_MULTILINE;
  4752.                     aOpt.style_remove |= TCS_RIGHT;
  4753.                     break;
  4754.                 // Not applicable for:
  4755.                 //case GUI_CONTROL_MONTHCAL:
  4756.                 //case GUI_CONTROL_PIC: SS_CENTERIMAGE is currently not used due to auto-pic-scaling/fitting.
  4757.                 //case GUI_CONTROL_DROPDOWNLIST:
  4758.                 //case GUI_CONTROL_COMBOBOX:
  4759.                 //case GUI_CONTROL_LISTBOX:
  4760.                 //case GUI_CONTROL_LISTVIEW:
  4761.                 //case GUI_CONTROL_TREEVIEW:
  4762.                 }
  4763.             }
  4764.             else // Removing.
  4765.             {
  4766.                 switch (aControl.type)
  4767.                 {
  4768.                 case GUI_CONTROL_SLIDER:
  4769.                     aOpt.style_remove |= TBS_LEFT|TBS_BOTH; // Removing TBS_BOTH is debatable, but "-left" is pretty rare/obscure anyway.
  4770.                     break;
  4771.                 case GUI_CONTROL_GROUPBOX:
  4772.                 case GUI_CONTROL_BUTTON:
  4773.                 case GUI_CONTROL_CHECKBOX:
  4774.                 case GUI_CONTROL_RADIO:
  4775.                     // BS_LEFT is a tricky one since it is included inside BS_CENTER.
  4776.                     // Thus, if the control exists and has BS_CENTER, do nothing since
  4777.                     // BS_LEFT can't be in effect if BS_CENTER already is:
  4778.                     if (aControl.hwnd)
  4779.                     {
  4780.                         if ((GetWindowLong(aControl.hwnd, GWL_STYLE) & BS_CENTER) != BS_CENTER) // v1.0.44.08: Fixed by adding "!= BS_CENTER".
  4781.                             aOpt.style_remove |= BS_LEFT;
  4782.                     }
  4783.                     else
  4784.                         if ((aOpt.style_add & BS_CENTER) != BS_CENTER) // v1.0.44.08: Fixed by adding "!= BS_CENTER".
  4785.                             aOpt.style_add &= ~BS_LEFT;  // A little strange, but seems correct since control hasn't even been created yet.
  4786.                         //else nothing needs to be done because BS_LEFT is already in effect removed since
  4787.                         //BS_CENTER makes BS_LEFT impossible to manifest.
  4788.                     break;
  4789.                 case GUI_CONTROL_TAB:
  4790.                     aOpt.style_remove |= TCS_VERTICAL;
  4791.                     break;
  4792.                 // Not applicable for these since their LEFT attributes are zero and thus cannot be removed:
  4793.                 //case GUI_CONTROL_UPDOWN: Removing "left" doesn't make much sense, so only adding "right" is supported.
  4794.                 //case GUI_CONTROL_DATETIME: Removing "left" is not supported since it seems counterintuitive and too rarely needed.
  4795.                 //case GUI_CONTROL_MONTHCAL:
  4796.                 //case GUI_CONTROL_TEXT:
  4797.                 //case GUI_CONTROL_PIC:
  4798.                 //case GUI_CONTROL_DROPDOWNLIST:
  4799.                 //case GUI_CONTROL_COMBOBOX:
  4800.                 //case GUI_CONTROL_LISTBOX:
  4801.                 //case GUI_CONTROL_LISTVIEW:
  4802.                 //case GUI_CONTROL_TREEVIEW:
  4803.                 //case GUI_CONTROL_EDIT:
  4804.                 }
  4805.             }
  4806.         } // else if
  4807.  
  4808.         else
  4809.         {
  4810.             // THE BELOW SHOULD BE DONE LAST so that they don't steal phrases/words that should be detected
  4811.             // as option words above.  An existing example is H for Hidden (above) or Height (below).
  4812.             // Additional examples:
  4813.             // if "visible" and "resize" ever become valid option words, the below would otherwise wrongly
  4814.             // detect them as variable=isible and row_count=esize, respectively.
  4815.  
  4816.             if (IsPureNumeric(next_option)) // Above has already verified that *next_option can't be whitespace.
  4817.             {
  4818.                 // Pure numbers are assumed to be style additions or removals:
  4819.                 DWORD given_style = ATOU(next_option); // ATOU() for unsigned.
  4820.                 if (adding) aOpt.style_add |= given_style; else aOpt.style_remove |= given_style;
  4821.                 *option_end = orig_char; // Undo the temporary termination because the caller needs aOptions to be unaltered.
  4822.                 continue;
  4823.             }
  4824.  
  4825.             ++next_option;  // Above has already verified that next_option isn't the empty string.
  4826.             if (!*next_option)
  4827.             {
  4828.                 // The option word consists of only one character, so ignore allow except the below
  4829.                 // since mandatory arg should immediately follow it.  Example: An isolated letter H
  4830.                 // should do nothing rather than cause the height to be set to zero.
  4831.                 switch (toupper(next_option[-1]))
  4832.                 {
  4833.                 case 'C':
  4834.                     if (!adding && aControl.type != GUI_CONTROL_PIC && color_main != CLR_DEFAULT)
  4835.                     {
  4836.                         color_main = CLR_DEFAULT; // i.e. treat "-C" as return to the default color. color_main is a reference to the right struct member.
  4837.                         aOpt.color_changed = true;
  4838.                     }
  4839.                     break;
  4840.                 case 'G':
  4841.                     aControl.jump_to_label = NULL;
  4842.                     break;
  4843.                 case 'V':
  4844.                     aControl.output_var = NULL;
  4845.                     break;
  4846.                 }
  4847.                 *option_end = orig_char; // Undo the temporary termination because the caller needs aOptions to be unaltered.
  4848.                 continue;
  4849.             }
  4850.  
  4851.             // Since above didn't "continue", there is text after the option letter, so take action accordingly.
  4852.             switch (toupper(next_option[-1]))
  4853.             {
  4854.             case 'G': // "Gosub" a label when this control is clicked or changed.
  4855.                 // For reasons of potential future use and compatibility, don't allow subroutines to be
  4856.                 // assigned to control types that have no present use for them.  Note: GroupBoxes do
  4857.                 // no support click-detection anyway, even if the BS_NOTIFY style is given to them
  4858.                 // (this has been verified twice):
  4859.                 if (aControl.type == GUI_CONTROL_GROUPBOX || aControl.type == GUI_CONTROL_PROGRESS)
  4860.                     // If control's hwnd exists, we were called from a caller who wants ErrorLevel set
  4861.                     // instead of a message displayed:
  4862.                     return aControl.hwnd ? g_ErrorLevel->Assign(ERRORLEVEL_ERROR)
  4863.                         : g_script.ScriptError("This control type should not have an associated subroutine."
  4864.                             ERR_ABORT, next_option - 1);
  4865.                 Label *candidate_label;
  4866.                 if (   !(candidate_label = g_script.FindLabel(next_option))   )
  4867.                 {
  4868.                     // If there is no explicit label, fall back to a special action if one is available
  4869.                     // for this keyword:
  4870.                     if (!stricmp(next_option, "Cancel"))
  4871.                         aControl.attrib |= GUI_CONTROL_ATTRIB_IMPLICIT_CANCEL;
  4872.                     // When the below is added, it should probably be made mutually exclusive of the above, probably
  4873.                     // by devoting two bits in the field for a total of three possible implicit actions (since the
  4874.                     // fourth is reserved as 00 = no action):
  4875.                     //else if (!stricmp(label_name, "Clear")) -->
  4876.                     //    control.options |= GUI_CONTROL_ATTRIB_IMPLICIT_CLEAR;
  4877.                     else // Since a non-special label was explicitly specified, it's an error that it can't be found.
  4878.                         return aControl.hwnd ? g_ErrorLevel->Assign(ERRORLEVEL_ERROR)
  4879.                             : g_script.ScriptError(ERR_NO_LABEL ERR_ABORT, next_option - 1);
  4880.                 }
  4881.                 if (aControl.type == GUI_CONTROL_TEXT || aControl.type == GUI_CONTROL_PIC)
  4882.                     // Apply the SS_NOTIFY style *only* if the control actually has an associated action.
  4883.                     // This is because otherwise the control would steal all clicks for any other controls
  4884.                     // drawn on top of it (e.g. a picture control with some edit fields drawn on top of it).
  4885.                     // See comments in the creation of GUI_CONTROL_PIC for more info:
  4886.                     aOpt.style_add |= SS_NOTIFY;
  4887.                 aControl.jump_to_label = candidate_label; // Will be NULL if something like gCancel (implicit was used).
  4888.                 break;
  4889.  
  4890.             case 'T': // Tabstop (the kind that exists inside a multi-line edit control or ListBox).
  4891.                 if (aOpt.tabstop_count < GUI_MAX_TABSTOPS)
  4892.                     aOpt.tabstop[aOpt.tabstop_count++] = ATOU(next_option);
  4893.                 //else ignore ones beyond the maximum.
  4894.                 break;
  4895.  
  4896.             case 'V': // Variable
  4897.                 // It seems best to allow an input-control to lack a variable, in which case its contents will be
  4898.                 // lost when the form is closed (unless fetched beforehand with something like ControlGetText).
  4899.                 // This is because it allows layout editors and other script generators to omit the variable
  4900.                 // and yet still be able to generate a runnable script.
  4901.                 Var *candidate_var;
  4902.                 // ALWAYS_PREFER_LOCAL is used below so that any existing local variable (e.g. a ByRef alias or
  4903.                 // static) will take precedence over a global of the same name when assume-global is in effect.
  4904.                 // If neither type of variable exists, a global will be created if assume-global is in effect.
  4905.                 if (   !(candidate_var = g_script.FindOrAddVar(next_option, 0, ALWAYS_PREFER_LOCAL))   ) // Find local or global, see below.
  4906.                     // For now, this is always a critical error that stops the current quasi-thread rather
  4907.                     // than setting ErrorLevel (if ErrorLevel is called for).  This is because adding a
  4908.                     // variable can cause one of any number of different errors to be displayed, and changing
  4909.                     // all those functions to have a silent mode doesn't seem worth the trouble given how
  4910.                     // rarely 1) a control needs to get a new variable; 2) that variable name is too long
  4911.                     // or not valid.
  4912.                     return FAIL;  // It already displayed the error (e.g. name too long). Existing var (if any) is retained.
  4913.                 // Below: Must be a global variable since otherwise, "Gui Submit" would store its results
  4914.                 // in the local variables of some function that isn't even currently running.  Reporting
  4915.                 // a runtime error seems the best way to solve this overall issue since the other
  4916.                 // alternatives seem overly complicated or have worse drawbacks.  One alternative would
  4917.                 // be to do load-time resolution of vVar and store the result in the lines mAttribute.
  4918.                 // But in addition to the problems of parsing vVar out of the list at loadtime, something
  4919.                 // like % "v" VarContainingVar (i.e. an expression) and other things seems would introduce
  4920.                 // an amount of complexity at loadtime that doesn't seem worth it.  Another possibility is
  4921.                 // to review a function's lines the first time its first "Gui Add" is encountered at runtime.
  4922.                 // Any local variable that match the name of the vVar global could be made into aliases so
  4923.                 // that they point to the global instead.  But that is pretty ugly and doesn't seem worth it.
  4924.                 candidate_var = candidate_var->ResolveAlias(); // Update it to its target if it's an alias.  This might be relied upon by Gui::FindControl() and other things, and also the section below.
  4925.                 if (candidate_var->IsNonStaticLocal()) // Note that an alias can point to a local vs. global var.
  4926.                     return g_script.ScriptError("A control's variable must be global or static." ERR_ABORT, next_option - 1);
  4927.                 // Another reason that the above always resolves aliases is because it allows the next
  4928.                 // check below to find true duplicates, even if different aliases are used to create the
  4929.                 // controls (i.e. if two alias both point to the same global).
  4930.                 // Check if any other control (visible or not, to avoid the complexity of a hidden control
  4931.                 // needing to be dupe-checked every time it becomes visible) on THIS gui window has the
  4932.                 // same variable.  That's an error because not only doesn't it make sense to do that,
  4933.                 // but it might be useful to uniquely identify a control by its variable name (when making
  4934.                 // changes to it, etc.)  Note that if this is the first control being added, mControlCount
  4935.                 // is now zero because this control has not yet actually been added.  That is why
  4936.                 // "u < mControlCount" is used:
  4937.                 GuiIndexType u;
  4938.                 for (u = 0; u < mControlCount; ++u)
  4939.                     if (mControl[u].output_var == candidate_var)
  4940.                         return aControl.hwnd ? g_ErrorLevel->Assign(ERRORLEVEL_ERROR)
  4941.                             : g_script.ScriptError("The same variable cannot be used for more than one control." // It used to say "one control per window" but that seems more confusing than it's worth.
  4942.                                 ERR_ABORT, next_option - 1);
  4943.                 aControl.output_var = candidate_var;
  4944.                 break;
  4945.  
  4946.             case 'E':  // Extended style
  4947.                 if (IsPureNumeric(next_option, false, false)) // Disallow whitespace in case option string ends in naked "E".
  4948.                 {
  4949.                     // Pure numbers are assumed to be style additions or removals:
  4950.                     DWORD given_exstyle = ATOU(next_option); // ATOU() for unsigned.
  4951.                     if (adding) aOpt.exstyle_add |= given_exstyle; else aOpt.exstyle_remove |= given_exstyle;
  4952.                 }
  4953.                 break;
  4954.  
  4955.             case 'C':  // Color
  4956.                 if (aControl.type == GUI_CONTROL_PIC) // Don't corrupt the union's hbitmap member.
  4957.                     break;
  4958.                 COLORREF new_color;
  4959.                 new_color = ColorNameToBGR(next_option);
  4960.                 if (new_color == CLR_NONE) // A matching color name was not found, so assume it's in hex format.
  4961.                     // It seems strtol() automatically handles the optional leading "0x" if present:
  4962.                     new_color = rgb_to_bgr(strtol(next_option, NULL, 16));
  4963.                     // if next_option did not contain something hex-numeric, black (0x00) will be assumed,
  4964.                     // which seems okay given how rare such a problem would be.
  4965.                 if (color_main != new_color || &color_main == &aOpt.color_listview) // Always indicate that it changed if it's not a stored attribute of the control (so that cDefault can be detected).
  4966.                 {
  4967.                     color_main = new_color; // color_main is a reference to the right struct member.
  4968.                     aOpt.color_changed = true;
  4969.                 }
  4970.                 break;
  4971.  
  4972.             case 'W':
  4973.                 if (toupper(*next_option) == 'P') // Use the previous control's value.
  4974.                     aOpt.width = mPrevWidth + ATOI(next_option + 1);
  4975.                 else
  4976.                     aOpt.width = ATOI(next_option);
  4977.                 break;
  4978.  
  4979.             case 'H':
  4980.                 if (toupper(*next_option) == 'P') // Use the previous control's value.
  4981.                     aOpt.height = mPrevHeight + ATOI(next_option + 1);
  4982.                 else
  4983.                     aOpt.height = ATOI(next_option);
  4984.                 break;
  4985.  
  4986.             case 'X':
  4987.                 if (*next_option == '+')
  4988.                 {
  4989.                     if (tab_control = FindTabControl(aControl.tab_control_index)) // Assign.
  4990.                     {
  4991.                         // Since this control belongs to a tab control and that tab control already exists,
  4992.                         // Position it relative to the tab control's client area upper-left corner if this
  4993.                         // is the first control on this particular tab/page:
  4994.                         if (!GetControlCountOnTabPage(aControl.tab_control_index, aControl.tab_index))
  4995.                         {
  4996.                             pt = GetPositionOfTabClientArea(*tab_control);
  4997.                             aOpt.x = pt.x + ATOI(next_option + 1);
  4998.                             if (aOpt.y == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  4999.                                 aOpt.y = pt.y + mMarginY;
  5000.                             break;
  5001.                         }
  5002.                         // else fall through and do it the standard way.
  5003.                     }
  5004.                     // Since above didn't break, do it the standard way.
  5005.                     aOpt.x = mPrevX + mPrevWidth + ATOI(next_option + 1);
  5006.                     if (aOpt.y == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5007.                         aOpt.y = mPrevY;  // Since moving in the X direction, retain the same Y as previous control.
  5008.                 }
  5009.                 // For the M and P sub-options, not that the +/- prefix is optional.  The number is simply
  5010.                 // read in as-is (though the use of + is more self-documenting in this case than omitting
  5011.                 // the sign entirely).
  5012.                 else if (toupper(*next_option) == 'M') // Use the X margin
  5013.                 {
  5014.                     aOpt.x = mMarginX + ATOI(next_option + 1);
  5015.                     if (aOpt.y == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5016.                         aOpt.y = mMaxExtentDown + mMarginY;
  5017.                 }
  5018.                 else if (toupper(*next_option) == 'P') // Use the previous control's X position.
  5019.                 {
  5020.                     aOpt.x = mPrevX + ATOI(next_option + 1);
  5021.                     if (aOpt.y == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5022.                         aOpt.y = mPrevY;  // Since moving in the X direction, retain the same Y as previous control.
  5023.                 }
  5024.                 else if (toupper(*next_option) == 'S') // Use the saved X position
  5025.                 {
  5026.                     aOpt.x = mSectionX + ATOI(next_option + 1);
  5027.                     if (aOpt.y == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5028.                         aOpt.y = mMaxExtentDownSection + mMarginY;  // In this case, mMarginY is the padding between controls.
  5029.                 }
  5030.                 else
  5031.                 {
  5032.                     aOpt.x = ATOI(next_option);
  5033.                     if (aOpt.y == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5034.                         aOpt.y = mMaxExtentDown + mMarginY;
  5035.                 }
  5036.                 break;
  5037.  
  5038.             case 'Y':
  5039.                 if (*next_option == '+')
  5040.                 {
  5041.                     if (tab_control = FindTabControl(aControl.tab_control_index)) // Assign.
  5042.                     {
  5043.                         // Since this control belongs to a tab control and that tab control already exists,
  5044.                         // Position it relative to the tab control's client area upper-left corner if this
  5045.                         // is the first control on this particular tab/page:
  5046.                         if (!GetControlCountOnTabPage(aControl.tab_control_index, aControl.tab_index))
  5047.                         {
  5048.                             pt = GetPositionOfTabClientArea(*tab_control);
  5049.                             aOpt.y = pt.y + ATOI(next_option + 1);
  5050.                             if (aOpt.x == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5051.                                 aOpt.x = pt.x + mMarginX;
  5052.                             break;
  5053.                         }
  5054.                         // else fall through and do it the standard way.
  5055.                     }
  5056.                     // Since above didn't break, do it the standard way.
  5057.                     aOpt.y = mPrevY + mPrevHeight + ATOI(next_option + 1);
  5058.                     if (aOpt.x == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5059.                         aOpt.x = mPrevX;  // Since moving in the Y direction, retain the same X as previous control.
  5060.                 }
  5061.                 // For the M and P sub-options, not that the +/- prefix is optional.  The number is simply
  5062.                 // read in as-is (though the use of + is more self-documenting in this case than omitting
  5063.                 // the sign entirely).
  5064.                 else if (toupper(*next_option) == 'M') // Use the Y margin
  5065.                 {
  5066.                     aOpt.y = mMarginY + ATOI(next_option + 1);
  5067.                     if (aOpt.x == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5068.                         aOpt.x = mMaxExtentRight + mMarginX;
  5069.                 }
  5070.                 else if (toupper(*next_option) == 'P') // Use the previous control's Y position.
  5071.                 {
  5072.                     aOpt.y = mPrevY + ATOI(next_option + 1);
  5073.                     if (aOpt.x == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5074.                         aOpt.x = mPrevX;  // Since moving in the Y direction, retain the same X as previous control.
  5075.                 }
  5076.                 else if (toupper(*next_option) == 'S') // Use the saved Y position
  5077.                 {
  5078.                     aOpt.y = mSectionY + ATOI(next_option + 1);
  5079.                     if (aOpt.x == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5080.                         aOpt.x = mMaxExtentRightSection + mMarginX; // In this case, mMarginX is the padding between controls.
  5081.                 }
  5082.                 else
  5083.                 {
  5084.                     aOpt.y = ATOI(next_option);
  5085.                     if (aOpt.x == COORD_UNSPECIFIED) // Not yet explicitly set, so use default.
  5086.                         aOpt.x = mMaxExtentRight + mMarginX;
  5087.                 }
  5088.                 break;
  5089.  
  5090.             case 'R': // The number of rows desired in the control.  Use ATOF() so that fractional rows are allowed.
  5091.                 aOpt.row_count = (float)ATOF(next_option); // Don't need double precision.
  5092.                 break;
  5093.             } // switch()
  5094.         } // Final "else" in the "else if" ladder.
  5095.  
  5096.         // If the item was not handled by the above, ignore it because it is unknown.
  5097.  
  5098.         *option_end = orig_char; // Undo the temporary termination because the caller needs aOptions to be unaltered.
  5099.  
  5100.     } // for() each item in option list
  5101.  
  5102.     // If the control has already been created, apply the new style and exstyle here, if any:
  5103.     if (aControl.hwnd)
  5104.     {
  5105.         DWORD current_style = GetWindowLong(aControl.hwnd, GWL_STYLE);
  5106.         DWORD new_style = (current_style | aOpt.style_add) & ~aOpt.style_remove; // Some things such as GUI_CONTROL_TEXT+SS_TYPEMASK might rely on style_remove being applied *after* style_add.
  5107.  
  5108.         // Fix for v1.0.24:
  5109.         // Certain styles can't be applied with a simple bit-or.  The below section is a subset of
  5110.         // a similar section in AddControl() to make sure that these styles are propertly handled:
  5111.         switch (aControl.type)
  5112.         {
  5113.         case GUI_CONTROL_PIC:
  5114.             // Fixed for v1.0.25.11 to prevent SS_ICON from getting changed to SS_BITMAP:
  5115.             new_style = (new_style & ~0x0F) | (current_style & 0x0F); // Done to ensure the lowest four bits are pure.
  5116.             break;
  5117.         case GUI_CONTROL_GROUPBOX:
  5118.             // There doesn't seem to be any flexibility lost by forcing the buttons to be the right type,
  5119.             // and doing so improves maintainability and peace-of-mind:
  5120.             new_style = (new_style & ~BS_TYPEMASK) | BS_GROUPBOX;  // Force it to be the right type of button.
  5121.             break;
  5122.         case GUI_CONTROL_BUTTON:
  5123.             if (new_style & BS_DEFPUSHBUTTON)
  5124.                 new_style = (new_style & ~BS_TYPEMASK) | BS_DEFPUSHBUTTON; // Done to ensure the lowest four bits are pure.
  5125.             else
  5126.                 new_style &= ~BS_TYPEMASK;  // Force it to be the right type of button --> BS_PUSHBUTTON == 0
  5127.             // Fixed for v1.0.33.01:
  5128.             // The following must be done here rather than later because it's possible for the
  5129.             // button to lack the BS_DEFPUSHBUTTON style even when it is the default button (such as when
  5130.             // keyboard focus is on some other button).  Consequently, no difference in style might be
  5131.             // detected further below, which is why it's done here:
  5132.             if (aControlIndex == mDefaultButtonIndex)
  5133.             {
  5134.                 if (aOpt.style_remove & BS_DEFPUSHBUTTON)
  5135.                 {
  5136.                     // Remove the default button (rarely needed so that's why there is current no
  5137.                     // "Gui, NoDefaultButton" command:
  5138.                     mDefaultButtonIndex = -1;
  5139.                     // This will alter the control id received via WM_COMMAND when the user presses ENTER:
  5140.                     SendMessage(mHwnd, DM_SETDEFID, (WPARAM)IDOK, 0); // restore to default
  5141.                     // Sometimes button visually has the default style even when GetWindowLong() says
  5142.                     // it doesn't.  Given how rare it is to not have a default button at all after having
  5143.                     // one, rather than try to analyze exactly what circumstances this happens in,
  5144.                     // unconditionally reset the style and redraw by indicating that current style is
  5145.                     // different from the new style:
  5146.                     current_style |= BS_DEFPUSHBUTTON;
  5147.                 }
  5148.             }
  5149.             else // This button isn't the default button yet, but is it becoming it?
  5150.             {
  5151.                 // Remember that the default button doesn't always have the BS_DEFPUSHBUTTON, namely at
  5152.                 // times when it shouldn't visually appear to be the default, such as when keyboard focus
  5153.                 // is on some other button.  Therefore, don't rely on new_style or current_style's
  5154.                 // having or not having BS_DEFPUSHBUTTON as being a correct indicator.
  5155.                 if (aOpt.style_add & BS_DEFPUSHBUTTON)
  5156.                 {
  5157.                     // First remove the style from the old default button, if there is one:
  5158.                     if (mDefaultButtonIndex < mControlCount)
  5159.                         SendMessage(mControl[mDefaultButtonIndex].hwnd, BM_SETSTYLE
  5160.                             , (WPARAM)LOWORD((GetWindowLong(mControl[mDefaultButtonIndex].hwnd, GWL_STYLE) & ~BS_DEFPUSHBUTTON))
  5161.                             , MAKELPARAM(TRUE, 0)); // Redraw = yes. It's probably smart enough not to do it if the window is hidden.
  5162.                     mDefaultButtonIndex = aControlIndex;
  5163.                     // This will alter the control id received via WM_COMMAND when the user presses ENTER:
  5164.                     SendMessage(mHwnd, DM_SETDEFID, (WPARAM)GUI_INDEX_TO_ID(mDefaultButtonIndex), 0);
  5165.                     // This button's visual/default appearance will be updated further below, if warranted.
  5166.                 }
  5167.             }
  5168.             break;
  5169.         case GUI_CONTROL_CHECKBOX:
  5170.             // Note: BS_AUTO3STATE and BS_AUTOCHECKBOX are mutually exclusive due to their overlap within
  5171.             // the bit field:
  5172.             if ((new_style & BS_AUTO3STATE) == BS_AUTO3STATE) // Fixed for v1.0.45.03 to check if all the BS_AUTO3STATE bits are present, not just "any" of them. BS_TYPEMASK is not involved here because this is a purity check, and TYPEMASK would defeat the whole purpose.
  5173.                 new_style = (new_style & ~BS_TYPEMASK) | BS_AUTO3STATE; // Done to ensure the lowest four bits are pure.
  5174.             else
  5175.                 new_style = (new_style & ~BS_TYPEMASK) | BS_AUTOCHECKBOX;  // Force it to be the right type of button.
  5176.             break;
  5177.         case GUI_CONTROL_RADIO:
  5178.             new_style = (new_style & ~BS_TYPEMASK) | BS_AUTORADIOBUTTON;  // Force it to be the right type of button.
  5179.             break;
  5180.         case GUI_CONTROL_DROPDOWNLIST:
  5181.             new_style |= CBS_DROPDOWNLIST;  // This works because CBS_DROPDOWNLIST == CBS_SIMPLE|CBS_DROPDOWN
  5182.             break;
  5183.         case GUI_CONTROL_COMBOBOX:
  5184.             if (new_style & CBS_SIMPLE) // i.e. CBS_SIMPLE has been added to the original default, so assume it is SIMPLE.
  5185.                 new_style = (new_style & ~0x0F) | CBS_SIMPLE; // Done to ensure the lowest four bits are pure.
  5186.             else
  5187.                 new_style = (new_style & ~0x0F) | CBS_DROPDOWN; // Done to ensure the lowest four bits are pure.
  5188.             break;
  5189.         case GUI_CONTROL_LISTVIEW: // Being in the switch serves to verify control's type because it isn't verified in places where listview_view is set.
  5190.             if (aOpt.listview_view != -1) // A new view was explicitly specified.
  5191.             {
  5192.                 // Fix for v1.0.36.04:
  5193.                 // For XP, must always use ListView_SetView() because otherwise switching from Tile view back to
  5194.                 // the *same* view that was in effect prior to tile view wouldn't work (since the the control's
  5195.                 // style LVS_TYPEMASK bits would not have changed).  This is because LV_VIEW_TILE is a special
  5196.                 // view that cannot be set via style change.
  5197.                 if (g_os.IsWinXPorLater())
  5198.                     ListView_SetView(aControl.hwnd, aOpt.listview_view);
  5199.                 // Regardless of whether SetView was called above, adjust the style too so that the upcoming
  5200.                 // style change won't undo what was just done above:
  5201.                 if (aOpt.listview_view != LV_VIEW_TILE) // It was ensured earlier that listview_view can be set to LV_VIEW_TILE only for XP or later.
  5202.                     new_style = (new_style & ~LVS_TYPEMASK) | aOpt.listview_view;
  5203.             }
  5204.             break;
  5205.         // Nothing extra for these currently:
  5206.         //case GUI_CONTROL_LISTBOX: i.e. allow LBS_NOTIFY to be removed in case anyone really wants to do that.
  5207.         //case GUI_CONTROL_TREEVIEW:
  5208.         //case GUI_CONTROL_EDIT:
  5209.         //case GUI_CONTROL_TEXT:  Ensuring SS_BITMAP and such are absent seems too over-protective.
  5210.         //case GUI_CONTROL_DATETIME:
  5211.         //case GUI_CONTROL_MONTHCAL:
  5212.         //case GUI_CONTROL_HOTKEY:
  5213.         //case GUI_CONTROL_UPDOWN:
  5214.         //case GUI_CONTROL_SLIDER:
  5215.         //case GUI_CONTROL_PROGRESS:
  5216.         //case GUI_CONTROL_TAB: i.e. allow WS_CLIPSIBLINGS to be removed (Rajat needs this) and also TCS_OWNERDRAWFIXED in case anyone really wants to.
  5217.         }
  5218.  
  5219.         // This needs to be done prior to applying the updated style since it sometimes adds
  5220.         // more style attributes:
  5221.         if (aOpt.limit) // A char length-limit was specified or de-specified for an edit/combo field.
  5222.         {
  5223.             // These styles are applied last so that multiline vs. singleline will already be resolved
  5224.             // and known, since all options have now been processed.
  5225.             if (aControl.type == GUI_CONTROL_EDIT)
  5226.             {
  5227.                 // For the below, note that EM_LIMITTEXT == EM_SETLIMITTEXT.
  5228.                 if (aOpt.limit < 0)
  5229.                 {
  5230.                     // Either limit to visible width of field, or remove existing limit.
  5231.                     // In both cases, first remove the control's internal limit in case it
  5232.                     // was something really small before:
  5233.                     SendMessage(aControl.hwnd, EM_LIMITTEXT, 0, 0);
  5234.                     // Limit > INT_MIN but less than zero is the signal to limit input length to visible
  5235.                     // width of field. But it can only work if the edit isn't a multiline.
  5236.                     if (aOpt.limit != INT_MIN && !(new_style & ES_MULTILINE))
  5237.                         new_style &= ~(WS_HSCROLL|ES_AUTOHSCROLL); // Enable the limit-to-visible-width style.
  5238.                 }
  5239.                 else // greater than zero, since zero itself it checked in one of the enclosing IFs above.
  5240.                     SendMessage(aControl.hwnd, EM_LIMITTEXT, aOpt.limit, 0); // Set a hard limit.
  5241.             }
  5242.             else if (aControl.type == GUI_CONTROL_HOTKEY)
  5243.             {
  5244.                 if (aOpt.limit < 0) // This is the signal to remove any existing limit.
  5245.                     SendMessage(aControl.hwnd, HKM_SETRULES, 0, 0);
  5246.                 else // greater than zero, since zero itself it checked in one of the enclosing IFs above.
  5247.                     SendMessage(aControl.hwnd, HKM_SETRULES, aOpt.limit, MAKELPARAM(HOTKEYF_CONTROL|HOTKEYF_ALT, 0));
  5248.                     // Above must also specify Ctrl+Alt or some other default, otherwise the restriction will have
  5249.                     // no effect.
  5250.             }
  5251.             // Altering the limit after the control exists appears to be ineffective, so this is commented out:
  5252.             //else if (aControl.type == GUI_CONTROL_COMBOBOX)
  5253.             //    // It has been verified that that EM_LIMITTEXT has no effect when sent directly
  5254.             //    // to a ComboBox hwnd; however, it might work if sent to its edit-child.
  5255.             //    // For now, a Combobox can only be limited to its visible width.  Later, there might
  5256.             //    // be a way to send a message to its child control to limit its width directly.
  5257.             //    if (aOpt.limit == INT_MIN) // remove existing limit
  5258.             //        new_style |= CBS_AUTOHSCROLL;
  5259.             //    else
  5260.             //        new_style &= ~CBS_AUTOHSCROLL;
  5261.             //    // i.e. SetWindowLong() cannot manifest the above style after the window exists.
  5262.         }
  5263.  
  5264.         bool style_change_ok;
  5265.         bool style_needed_changing = false; // Set default.
  5266.  
  5267.         if (current_style != new_style)
  5268.         {
  5269.             style_needed_changing = true; // Either style or exstyle is changing.
  5270.             style_change_ok = false; // Starting assumption.
  5271.             switch (aControl.type)
  5272.             {
  5273.             case GUI_CONTROL_BUTTON:
  5274.                 // For some reason, the following must be done *after* removing the visual/default style from
  5275.                 // the old default button and/or after doing DM_SETDEFID above.
  5276.                 // BM_SETSTYLE is much more likely to have an effect for buttons than SetWindowLong().
  5277.                 // Redraw = yes, though it seems to be ineffective sometimes. It's probably smart enough not to do
  5278.                 // it if the window is hidden.
  5279.                 // Fixed for v1.0.33.01: The HWND should be aControl.hwnd not mControl[mDefaultButtonIndex].hwnd
  5280.                 SendMessage(aControl.hwnd, BM_SETSTYLE, (WPARAM)LOWORD(new_style), MAKELPARAM(TRUE, 0));
  5281.                 break;
  5282.             case GUI_CONTROL_LISTBOX:
  5283.                 if ((new_style & WS_HSCROLL) && !(current_style & WS_HSCROLL)) // Scroll bar being added.
  5284.                 {
  5285.                     if (aOpt.hscroll_pixels < 0) // Calculate a default based on control's width.
  5286.                     {
  5287.                         // Since horizontal scrollbar is relatively rarely used, no fancy method
  5288.                         // such as calculating scrolling-width via LB_GETTEXTLEN and current font's
  5289.                         // average width is used.
  5290.                         GetWindowRect(aControl.hwnd, &rect);
  5291.                         aOpt.hscroll_pixels = 3 * (rect.right - rect.left);
  5292.                     }
  5293.                     // If hscroll_pixels is now zero or smaller than the width of the control,
  5294.                     // the scrollbar will not be shown:
  5295.                     SendMessage(aControl.hwnd, LB_SETHORIZONTALEXTENT, (WPARAM)aOpt.hscroll_pixels, 0);
  5296.                 }
  5297.                 else if (!(new_style & WS_HSCROLL) && (current_style & WS_HSCROLL)) // Scroll bar being removed.
  5298.                     SendMessage(aControl.hwnd, LB_SETHORIZONTALEXTENT, 0, 0);
  5299.                 break;
  5300.             } // switch()
  5301.             SetLastError(0); // Prior to SetWindowLong(), as recommended by MSDN.
  5302.             // Call this even for buttons because BM_SETSTYLE only handles the LOWORD part of the style:
  5303.             if (SetWindowLong(aControl.hwnd, GWL_STYLE, new_style) || !GetLastError()) // This is the precise way to detect success according to MSDN.
  5304.             {
  5305.                 // Even if it indicated success, sometimes it failed anyway.  Find out for sure:
  5306.                 if (GetWindowLong(aControl.hwnd, GWL_STYLE) != current_style)
  5307.                     style_change_ok = true; // Even a partial change counts as a success.
  5308.             }
  5309.         }
  5310.  
  5311.         DWORD current_exstyle = GetWindowLong(aControl.hwnd, GWL_EXSTYLE);
  5312.         DWORD new_exstyle = (current_exstyle | aOpt.exstyle_add) & ~aOpt.exstyle_remove;
  5313.         if (current_exstyle != new_exstyle)
  5314.         {
  5315.             if (!style_needed_changing)
  5316.             {
  5317.                 style_needed_changing = true; // Either style or exstyle is changing.
  5318.                 style_change_ok = false; // Starting assumption.
  5319.             }
  5320.             //ELSE don't change the value of style_change_ok because we want it to retain the value set by
  5321.             // the GWL_STYLE change above; i.e. a partial success on either style or exstyle counts as a full
  5322.             // success.
  5323.             SetLastError(0); // Prior to SetWindowLong(), as recommended by MSDN.
  5324.             if (SetWindowLong(aControl.hwnd, GWL_EXSTYLE, new_exstyle) || !GetLastError()) // This is the precise way to detect success according to MSDN.
  5325.             {
  5326.                 // Even if it indicated success, sometimes it failed anyway.  Find out for sure:
  5327.                 if (GetWindowLong(aControl.hwnd, GWL_EXSTYLE) != current_exstyle)
  5328.                     style_change_ok = true; // Even a partial change counts as a success.
  5329.             }
  5330.         }
  5331.  
  5332.         if (aControl.type == GUI_CONTROL_LISTVIEW)
  5333.         {
  5334.             // These ListView "extended styles" exist entirely separate from normal extended styles.
  5335.             // In other words, a ListView may have three types of styles: Normal, Extended, and its
  5336.             // own set of LV Extended styles.
  5337.             // Since LV extended styles are not supported on Win95/NT that lack comctl32.dll 4.70+ distributed
  5338.             // with MSIE 3.x, the following should already serve to indicate that via ErrorLevel since
  5339.             // the replies to the macros/messages will probably be zero:
  5340.             DWORD current_lv_style = ListView_GetExtendedListViewStyle(aControl.hwnd);
  5341.             if (current_lv_style != aOpt.listview_style)
  5342.             {
  5343.                 if (!style_needed_changing)
  5344.                 {
  5345.                     style_needed_changing = true; // Either style, exstyle, or lv_exstyle is changing.
  5346.                     style_change_ok = false; // Starting assumption.
  5347.                 }
  5348.                 //ELSE don't change the value of style_change_ok because we want it to retain the value set by
  5349.                 // the GWL_STYLE or EXSTYLE change above; i.e. a partial success on either style or exstyle
  5350.                 // counts as a full success.
  5351.                 ListView_SetExtendedListViewStyle(aControl.hwnd, aOpt.listview_style); // Has no return value.
  5352.                 if (ListView_GetExtendedListViewStyle(aControl.hwnd) != current_lv_style)
  5353.                     style_change_ok = true; // Even a partial change counts as a success.
  5354.             }
  5355.         }
  5356.  
  5357.           // Redrawing the controls is required in some cases, such as a checkbox losing its 3-state
  5358.         // style while it has a gray checkmark in it (which incidentally in this case only changes
  5359.         // the appearance of the control, not the internal stored value in this case).
  5360.         bool do_invalidate_rect = style_needed_changing && style_change_ok; // Set default.
  5361.  
  5362.         // Do the below only after applying the styles above since part of it requires that the style be
  5363.         // updated and applied above.
  5364.         switch (aControl.type)
  5365.         {
  5366.         case GUI_CONTROL_UPDOWN:
  5367.             ControlSetUpDownOptions(aControl, aOpt);
  5368.             break;
  5369.         case GUI_CONTROL_SLIDER:
  5370.             ControlSetSliderOptions(aControl, aOpt);
  5371.             if (aOpt.style_remove & TBS_TOOLTIPS)
  5372.                 SendMessage(aControl.hwnd, TBM_SETTOOLTIPS, NULL, 0); // i.e. removing the TBS_TOOLTIPS style is not enough.
  5373.             break;
  5374.         case GUI_CONTROL_LISTVIEW:
  5375.             ControlSetListViewOptions(aControl, aOpt);
  5376.             break;
  5377.         case GUI_CONTROL_TREEVIEW:
  5378.             ControlSetTreeViewOptions(aControl, aOpt);
  5379.             break;
  5380.         case GUI_CONTROL_PROGRESS:
  5381.             ControlSetProgressOptions(aControl, aOpt, new_style);
  5382.             // Above strips theme if required by new options.  It also applies new colors.
  5383.             break;
  5384.         case GUI_CONTROL_EDIT:
  5385.             if (aOpt.tabstop_count)
  5386.             {
  5387.                 SendMessage(aControl.hwnd, EM_SETTABSTOPS, aOpt.tabstop_count, (LPARAM)aOpt.tabstop);
  5388.                 // MSDN: "If the application is changing the tab stops for text already in the edit control,
  5389.                 // it should call the InvalidateRect function to redraw the edit control window."
  5390.                 do_invalidate_rect = true; // Override the default.
  5391.             }
  5392.             break;
  5393.         case GUI_CONTROL_LISTBOX:
  5394.             if (aOpt.tabstop_count)
  5395.             {
  5396.                 SendMessage(aControl.hwnd, LB_SETTABSTOPS, aOpt.tabstop_count, (LPARAM)aOpt.tabstop);
  5397.                 do_invalidate_rect = true; // This is done for the same reason that EDIT (above) does it.
  5398.             }
  5399.             break;
  5400.         case GUI_CONTROL_STATUSBAR:
  5401.             if (aOpt.color_bk != CLR_INVALID) // Explicit color change was requested.
  5402.                 SendMessage(aControl.hwnd, SB_SETBKCOLOR, 0, aOpt.color_bk);
  5403.             break;
  5404.         }
  5405.  
  5406.         if (aOpt.redraw)
  5407.         {
  5408.             SendMessage(aControl.hwnd, WM_SETREDRAW, aOpt.redraw == CONDITION_TRUE, 0);
  5409.             if (aOpt.redraw == CONDITION_TRUE // Since redrawing is being turned back on, invalidate the control so that it updates itself.
  5410.                 && aControl.type != GUI_CONTROL_TREEVIEW) // This type is documented not to need it; others like ListView are not, so might need it on some OSes or under some conditions.
  5411.                 do_invalidate_rect = true;
  5412.         }
  5413.  
  5414.         if (do_invalidate_rect)
  5415.             InvalidateRect(aControl.hwnd, NULL, TRUE); // Assume there's text in the control.
  5416.  
  5417.         if (style_needed_changing && !style_change_ok) // Override the default errorlevel set by our caller, GuiControl().
  5418.             g_ErrorLevel->Assign(ERRORLEVEL_ERROR);
  5419.     } // aControl.hwnd is not NULL
  5420.  
  5421.     return OK;
  5422. }
  5423.  
  5424.  
  5425.  
  5426. void GuiType::ControlInitOptions(GuiControlOptionsType &aOpt, GuiControlType &aControl)
  5427. // Not done as class to avoid code-size overhead of initializer list, etc.
  5428. {
  5429.     ZeroMemory(&aOpt, sizeof(GuiControlOptionsType));
  5430.     if (aControl.type == GUI_CONTROL_LISTVIEW) // Since this doesn't have the _add and _remove components, must initialize.
  5431.     {
  5432.         if (aControl.hwnd)
  5433.             aOpt.listview_style = ListView_GetExtendedListViewStyle(aControl.hwnd); // Will have no effect on 95/NT4 that lack comctl32.dll 4.70+ distributed with MSIE 3.x
  5434.         aOpt.listview_view = -1;  // Indicate "unspecified" so that changes can be detected.
  5435.     }
  5436.     aOpt.x = aOpt.y = aOpt.width = aOpt.height = COORD_UNSPECIFIED;
  5437.     aOpt.color_bk = CLR_INVALID;
  5438.     // Above: If it stays unaltered, CLR_INVALID means "leave color as it is".  This is for
  5439.     // use with "GuiControl, +option" so that ControlSetProgressOptions() and others know that
  5440.     // the "+/-Background" item was not among the options in the list.  The reason this is needed
  5441.     // for background color but not bar color is that bar_color is stored as a control attribute,
  5442.     // but to save memory, background color is not.  In addition, there is no good way to ask a
  5443.     // progress control what its background color currently is.
  5444.     aOpt.color_listview = CLR_DEFAULT; // But this one uses DEFAULT vs. INVALID because it has simpler logic.
  5445. }
  5446.  
  5447.  
  5448.  
  5449. void GuiType::ControlAddContents(GuiControlType &aControl, char *aContent, int aChoice, GuiControlOptionsType *aOpt)
  5450. // If INT_MIN is specified for aChoice, aControl should be the ListView to which a new row is being added.
  5451. // In that case, aOpt should be non-NULL.
  5452. // Caller must ensure that aContent is a writable memory area, since this function temporarily
  5453. // alters the string.
  5454. {
  5455.     if (!*aContent)
  5456.         return;
  5457.  
  5458.     UINT msg_add, msg_select;
  5459.  
  5460.     switch (aControl.type)
  5461.     {
  5462.     case GUI_CONTROL_LISTVIEW:
  5463.     case GUI_CONTROL_TAB: // These cases must be listed anyway to do a break vs. return, so might as well init conditionally rather than unconditionally.
  5464.         msg_add = 0;
  5465.         msg_select = 0;
  5466.         break;
  5467.     case GUI_CONTROL_DROPDOWNLIST:
  5468.     case GUI_CONTROL_COMBOBOX:
  5469.         msg_add = CB_ADDSTRING;
  5470.         msg_select = CB_SETCURSEL;
  5471.         break;
  5472.     case GUI_CONTROL_LISTBOX:
  5473.         msg_add = LB_ADDSTRING;
  5474.         msg_select = (GetWindowLong(aControl.hwnd, GWL_STYLE) & (LBS_EXTENDEDSEL|LBS_MULTIPLESEL))
  5475.             ? LB_SETSEL : LB_SETCURSEL;
  5476.         break;
  5477.     default:    // Do nothing for any other control type that doesn't require content to be added this way.
  5478.         return; // e.g. GUI_CONTROL_SLIDER, which the caller should handle.
  5479.     }
  5480.  
  5481.     bool temporarily_terminated;
  5482.     char *this_field, *next_field;
  5483.     LRESULT item_index;
  5484.     int requested_index = 0;
  5485.  
  5486.     // For tab controls:
  5487.     TCITEM tci;
  5488.     tci.mask = TCIF_TEXT | TCIF_IMAGE; // Simpler just to init unconditionally rather than checking control type.
  5489.     tci.iImage = -1;
  5490.  
  5491.     // For ListView:
  5492.     LVCOLUMN lvc;
  5493.     lvc.mask = LVCF_TEXT; // Simpler just to init unconditionally rather than checking control type.
  5494.  
  5495.     // Check *this_field at the top too, in case list ends in delimiter.
  5496.     for (this_field = aContent; *this_field;)
  5497.     {
  5498.         // Decided to use pipe as delimiter, rather than comma, because it makes the script more readable.
  5499.         // For example, it's easier to pick out the list of choices at a glance rather than having to
  5500.         // figure out where the commas delimit the beginning and end of "real" parameters vs. those params
  5501.         // that are a self-contained CSV list.  Of course, the pipe character itself is "sacrificed" and
  5502.         // cannot be used literally due to this method.  That limitation can now be avoided by specifying
  5503.         // a custom delimiter.
  5504.         if (next_field = strchr(this_field, mDelimiter)) // Assign
  5505.         {
  5506.             *next_field = '\0';  // Temporarily terminate (caller has ensured this is safe).
  5507.             temporarily_terminated = true;
  5508.         }
  5509.         else
  5510.         {
  5511.             next_field = this_field + strlen(this_field);  // Point it to the end of the string.
  5512.             temporarily_terminated = false;
  5513.         }
  5514.  
  5515.         // Add the item:
  5516.         switch (aControl.type)
  5517.         {
  5518.         case GUI_CONTROL_TAB:
  5519.             if (requested_index > MAX_TABS_PER_CONTROL - 1) // Unlikely, but indicate failure if so.
  5520.                 item_index = -1;
  5521.             else
  5522.             {
  5523.                 tci.pszText = this_field;
  5524.                 item_index = TabCtrl_InsertItem(aControl.hwnd, requested_index, &tci);
  5525.                 if (item_index != -1) // item_index is used later below as an indicator of success.
  5526.                     ++requested_index;
  5527.             }
  5528.             break;
  5529.         case GUI_CONTROL_LISTVIEW:
  5530.             lvc.pszText = this_field;
  5531.             item_index = ListView_InsertColumn(aControl.hwnd, requested_index, &lvc);
  5532.             if (item_index != -1) // item_index is used later below as an indicator of success.
  5533.                 ++requested_index;
  5534.             break;
  5535.         default:
  5536.             item_index = SendMessage(aControl.hwnd, msg_add, 0, (LPARAM)this_field); // In this case, ignore any errors, namely CB_ERR/LB_ERR and CB_ERRSPACE).
  5537.             // For the above, item_index must be retrieved and used as the item's index because it might
  5538.             // be different than expected if the control's SORT style is in effect.
  5539.         }
  5540.  
  5541.         if (temporarily_terminated)
  5542.         {
  5543.             *next_field = mDelimiter;  // Restore the original char.
  5544.             ++next_field;
  5545.             if (*next_field == mDelimiter)  // An item ending in two delimiters is a default (pre-selected) item.
  5546.             {
  5547.                 if (item_index > -1) // The item was successfully added.
  5548.                 {
  5549.                     if (aControl.type == GUI_CONTROL_TAB)
  5550.                         // MSDN: "A tab control does not send a TCN_SELCHANGING or TCN_SELCHANGE notification message
  5551.                         // when a tab is selected using the TCM_SETCURSEL message."
  5552.                         TabCtrl_SetCurSel(aControl.hwnd, item_index);
  5553.                     else if (msg_select == LB_SETSEL) // Multi-select box requires diff msg to have a cumulative effect.
  5554.                         SendMessage(aControl.hwnd, msg_select, (WPARAM)TRUE, (LPARAM)item_index);
  5555.                     else if (msg_select) // Ensure 
  5556.                         SendMessage(aControl.hwnd, msg_select, (WPARAM)item_index, 0);  // Select this item.
  5557.                 }
  5558.                 ++next_field;  // Now this could be a third mDelimiter, which would in effect be an empty item.
  5559.                 // It can also be the zero terminator if the list ends in a delimiter, e.g. item1|item2||
  5560.             }
  5561.         }
  5562.         this_field = next_field;
  5563.     } // for()
  5564.  
  5565.     if (aControl.type == GUI_CONTROL_LISTVIEW)
  5566.     {
  5567.         // Fix for v1.0.36.03: requested_index is already one beyond the number of columns that were added
  5568.         // because it's always set up for the next column that would be added if there were any.
  5569.         // Therefore, there is no need to add one to it to get the column count.
  5570.         aControl.union_lv_attrib->col_count = requested_index; // Keep track of column count, mostly so that LV_ModifyCol and such can properly maintain the array of columns).
  5571.         // It seems a useful default to do a basic auto-size upon creation, even though it won't take into
  5572.         // account contents of the rows or the later presence of a vertical scroll bar. The last column
  5573.         // will be overlapped when/if a v-scrollbar appears, which would produce an h-scrollbar too.
  5574.         // It seems best to retain this behavior rather than trying to shrink the last column to allow
  5575.         // room for a scroll bar because: 1) Having it use all available width is desirable at least
  5576.         // some of the time (such as times when there will be only a few rows; 2) It simplifies the code.
  5577.         // This method of auto-sizing each column to fit its text works much better than setting
  5578.         // lvc.cx to ListView_GetStringWidth upon creation of the column.
  5579.         if (ControlGetListViewMode(aControl.hwnd) == LVS_REPORT)
  5580.             for (int i = 0; i < requested_index; ++i) // Auto-size each column.
  5581.                 ListView_SetColumnWidth(aControl.hwnd, i, LVSCW_AUTOSIZE_USEHEADER);
  5582.     }
  5583.  
  5584.     // Have aChoice take precedence over any double-piped item(s) that appeared in the list:
  5585.     if (aChoice < 1)
  5586.         return;
  5587.     --aChoice;
  5588.  
  5589.     if (aControl.type == GUI_CONTROL_TAB)
  5590.         // MSDN: "A tab control does not send a TCN_SELCHANGING or TCN_SELCHANGE notification message
  5591.         // when a tab is selected using the TCM_SETCURSEL message."
  5592.         TabCtrl_SetCurSel(aControl.hwnd, aChoice);
  5593.     else if (msg_select == LB_SETSEL) // Multi-select box requires diff msg to have a cumulative effect.
  5594.         SendMessage(aControl.hwnd, msg_select, (WPARAM)TRUE, (LPARAM)aChoice);
  5595.     else
  5596.         SendMessage(aControl.hwnd, msg_select, (WPARAM)aChoice, 0);  // Select this item.
  5597. }
  5598.  
  5599.  
  5600.  
  5601. ResultType GuiType::Show(char *aOptions, char *aText)
  5602. {
  5603.     if (!mHwnd)
  5604.         return OK;  // Make this a harmless attempt.
  5605.  
  5606.     // In the future, it seems best to rely on mShowIsInProgress to prevent the Window Proc from ever
  5607.     // doing a MsgSleep() to launch a script subroutine.  This is because if anything we do in this
  5608.     // function results in a launch of the Window Proc (such as MoveWindow and ShowWindow), our
  5609.     // activity here might be interrupted in a destructive way.  For example, if a script subroutine
  5610.     // is launched while we're in the middle of something here, our activity is suspended until
  5611.     // the subroutine completes and the call stack collapses back to here.  But if that subroutine
  5612.     // recursively calls us while the prior call is still in progress, the mShowIsInProgress would
  5613.     // be set to false when that layer completes, leaving it false when it really should be true
  5614.     // because our layer isn't done yet.
  5615.     mShowIsInProgress = true; // Signal WM_SIZE to queue the GuiSize launch.  We'll unqueue via MsgSleep() when we're done.
  5616.  
  5617.     // Change the title to get that out of the way.  But in any case, the title must be changed before the
  5618.     // following:
  5619.     // 1) Before the window is shown (to make transition a little nicer).
  5620.     // 2) v1.0.25: Before MoveWindow(), because otherwise the GuiSize label (if any) will be launched
  5621.     //    while the the window still has its old title (or no title, if this is the first showing), which
  5622.     //    would not be desirable 99% of the time.
  5623.     if (*aText)
  5624.         SetWindowText(mHwnd, aText);
  5625.  
  5626.     // Set defaults, to be overridden by the presence of zero or more options:
  5627.     int x = COORD_UNSPECIFIED;
  5628.     int y = COORD_UNSPECIFIED;
  5629.     int width = COORD_UNSPECIFIED;
  5630.     int height = COORD_UNSPECIFIED;
  5631.     bool auto_size = false;
  5632.  
  5633.     BOOL is_maximized = IsZoomed(mHwnd); // Safer to assume it's possible for both to be true simultaneously in
  5634.     BOOL is_minimized = IsIconic(mHwnd); // future/past OSes.
  5635.     int show_mode;
  5636.     // There is evidence that SW_SHOWNORMAL might be better than SW_SHOW for the first showing because
  5637.     // someone reported that a window appears centered on the screen for its first showing even if some
  5638.     // other position was specified.  In addition, MSDN says (without explanation): "An application should
  5639.     // specify [SW_SHOWNORMAL] when displaying the window for the first time."  However, SW_SHOWNORMAL is
  5640.     // avoided after the first showing of the window because that would probably also do a "restore" on the
  5641.     // window if it was maximized previously.  Note that the description of SW_SHOWNORMAL is virtually the
  5642.     // same as that of SW_RESTORE in MSDN.  UPDATE: mGuiShowHasNeverBeenDone is used here instead of mFirstActivation
  5643.     // because it seems more flexible to have "Gui Show" behave consistently (SW_SHOW) every time after
  5644.     // the first use of "Gui Show".  UPDATE: Since SW_SHOW seems to have no effect on minimized windows,
  5645.     // at least on XP, and since such a minimized window will be restored by action of SetForegroundWindowEx(),
  5646.     // it seems best to unconditionally use SW_SHOWNORMAL, rather than "mGuiShowHasNeverBeenDone ? SW_SHOWNORMAL : SW_SHOW".
  5647.     // This is done so that the window will be restored and thus have a better chance of being successfully
  5648.     // activated (and thus not requiring the call to SetForegroundWindowEx()).
  5649.     // v1.0.44.08: Fixed to default to SW_SHOW for currently-maximized windows so that they don't get unmaximized
  5650.     // by "Gui Show" (unless other options require it).  Also, it's been observed that SW_SHOWNORMAL differs from
  5651.     // SW_RESTORE in at least one way: When the target is a minimized window, SW_SHOWNORMAL will both restore it
  5652.     // and unmaximize it.  But SW_RESTORE unminimizes the window in a way that retains its maximized state (if
  5653.     // it was previously maximized).  Therefore, SW_RESTORE is now the default if the window is currently minimized.
  5654.     if (is_minimized)
  5655.         show_mode = SW_RESTORE; // See above comments. For backward compatibility, window is unminimized even if it was previously hidden (rather than simply showing its taskbar button and keeping it minimized).
  5656.     else if (is_maximized)
  5657.         show_mode = SW_SHOW; // See above.
  5658.     else
  5659.         show_mode = SW_SHOWNORMAL;
  5660.  
  5661.     for (char *cp = aOptions; *cp; ++cp)
  5662.     {
  5663.         switch(toupper(*cp))
  5664.         {
  5665.         // For options such as W, H, X and Y: Use atoi() vs. ATOI() to avoid interpreting something like 0x01B
  5666.         // as hex when in fact the B was meant to be an option letter.
  5667.         case 'A':
  5668.             if (!strnicmp(cp, "AutoSize", 8))
  5669.             {
  5670.                 // Skip over the text of the name so that it isn't interpreted as option letters.
  5671.                 // 7 vs. 8 to avoid the loop's addition ++cp from reading beyond the length of the string:
  5672.                 cp += 7;
  5673.                 auto_size = true;
  5674.             }
  5675.             break;
  5676.         case 'C':
  5677.             if (!strnicmp(cp, "Center", 6))
  5678.             {
  5679.                 // Skip over the text of the name so that it isn't interpreted as option letters.
  5680.                 // 5 vs. 6 to avoid the loop's addition ++cp from reading beyond the length of the string:
  5681.                 cp += 5;
  5682.                 x = COORD_CENTERED;
  5683.                 y = COORD_CENTERED;
  5684.                 // If the window is currently maximized, show_mode isn't set to SW_RESTORE unconditionally here
  5685.                 // due to obscurity and because it might reduce flexibility.  If the window is currently minimized,
  5686.                 // above has already set the default show_mode to SW_RESTORE to ensure correct operation of
  5687.                 // something like "Gui, Show, Center".
  5688.             }
  5689.             break;
  5690.         case 'M':
  5691.             if (!strnicmp(cp, "Minimize", 8)) // Seems best to reserve "Min" for other things, such as Min W/H. "Minimize" is also more self-documenting.
  5692.             {
  5693.                 // Skip over the text of the name so that it isn't interpreted as option letters.
  5694.                 // 7 vs. 8 to avoid the loop's addition ++cp from reading beyond the length of the string:
  5695.                 cp += 7;
  5696.                 show_mode = SW_MINIMIZE;  // Seems more typically useful/desirable than SW_SHOWMINIMIZED.
  5697.             }
  5698.             else if (!strnicmp(cp, "Maximize", 8))
  5699.             {
  5700.                 // Skip over the text of the name so that it isn't interpreted as option letters.
  5701.                 // 7 vs. 8 to avoid the loop's addition ++cp from reading beyond the length of the string:
  5702.                 cp += 7;
  5703.                 show_mode = SW_MAXIMIZE;  // SW_MAXIMIZE == SW_SHOWMAXIMIZED
  5704.             }
  5705.             break;
  5706.         case 'N':
  5707.             if (!strnicmp(cp, "NA", 2))
  5708.             {
  5709.                 // Skip over the text of the name so that it isn't interpreted as option letters.
  5710.                 // 1 vs. 2 to avoid the loop's addition ++cp from reading beyond the length of the string:
  5711.                 cp += 1;
  5712.                 show_mode = SW_SHOWNA;
  5713.             }
  5714.             else if (!strnicmp(cp, "NoActivate", 10))
  5715.             {
  5716.                 // Skip over the text of the name so that it isn't interpreted as option letters.
  5717.                 // 9 vs. 10 to avoid the loop's addition ++cp from reading beyond the length of the string:
  5718.                 cp += 9;
  5719.                 show_mode = SW_SHOWNOACTIVATE;
  5720.             }
  5721.             break;
  5722.         case 'R':
  5723.             if (!strnicmp(cp, "Restore", 7))
  5724.             {
  5725.                 // Skip over the text of the name so that it isn't interpreted as option letters.
  5726.                 // 6 vs. 7 to avoid the loop's addition ++cp from reading beyond the length of the string:
  5727.                 cp += 6;
  5728.                 show_mode = SW_RESTORE;
  5729.             }
  5730.             break;
  5731.         case 'W':
  5732.             width = atoi(cp + 1);
  5733.             break;
  5734.         case 'H':
  5735.             if (!strnicmp(cp, "Hide", 4))
  5736.             {
  5737.                 // Skip over the text of the name so that it isn't interpreted as option letters.
  5738.                 // 3 vs. 4 to avoid the loop's addition ++cp from reading beyond the length of the string:
  5739.                 cp += 3;
  5740.                 show_mode = SW_HIDE;
  5741.             }
  5742.             else
  5743.                 // Allow any width/height to be specified so that the window can be "rolled up" to its title bar:
  5744.                 height = atoi(cp + 1);
  5745.             break;
  5746.         case 'X':
  5747.             if (!strnicmp(cp + 1, "Center", 6))
  5748.             {
  5749.                 cp += 6; // 6 in this case since we're working with cp + 1
  5750.                 x = COORD_CENTERED;
  5751.             }
  5752.             else
  5753.                 x = atoi(cp + 1);
  5754.             break;
  5755.         case 'Y':
  5756.             if (!strnicmp(cp + 1, "Center", 6))
  5757.             {
  5758.                 cp += 6; // 6 in this case since we're working with cp + 1
  5759.                 y = COORD_CENTERED;
  5760.             }
  5761.             else
  5762.                 y = atoi(cp + 1);
  5763.             break;
  5764.         // Otherwise: Ignore other characters, such as the digits that occur after the P/X/Y option letters.
  5765.         } // switch()
  5766.     } // for()
  5767.  
  5768.     int width_orig = width;
  5769.     int height_orig = height;
  5770.  
  5771.     // The following section must be done prior to any calls to GetWindow/ClientRect(mHwnd) because
  5772.     // neither of them can retrieve the correct diminsnions of a minmized window.  Similarly, if
  5773.     // the window is maximized but is about to be restored, do that prior to getting any of mHwnd's
  5774.     // rectangles because we want to use the restored size as the basis for centering, resizing, etc.
  5775.     // If show_mode is "hide", move the window only after hiding it (to reduce screen flicker).
  5776.     // If the window is being restored from a minimized or maximized state, move the window only
  5777.     // after restoring it; otherwise, any resize to be done by the MoveWindow() might not take effect.
  5778.     // Note that SW_SHOWNOACTIVATE is very similar to SW_RESTORE in its effects.
  5779.     bool show_was_done = false;
  5780.     if (show_mode == SW_HIDE // Hiding a window or restoring a window known to be minimized/maximized.
  5781.         || (show_mode == SW_RESTORE || show_mode == SW_SHOWNOACTIVATE) && (is_maximized || is_minimized)) // v1.0.44.08: Fixed missing "show_mode ==".
  5782.     {
  5783.         ShowWindow(mHwnd, show_mode);
  5784.         show_was_done = true;
  5785.     }
  5786.     // Note that SW_RESTORE and SW_SHOWNOACTIVATE will show a window if it's hidden.  Therefore, just
  5787.     // because the window is not in need of restoring doesn't mean the ShowWindow() call is skipped.
  5788.     // That is why show_was_done is left false in such cases.
  5789.  
  5790.     // Due to the checking above, if the window is minimized/maximized now, that means it will still be
  5791.     // minimized/maximized when this function is done.  As a result, it's not really valid to call
  5792.     // MoveWindow() for any purpose (auto-centering, auto-sizing, new position, new size, etc.).
  5793.     // The below is especially necessary for minimized windows because it avoid calculating window
  5794.     // dimensions, auto-centering, etc. based on incorrect values returned by GetWindow/ClientRect(mHwnd).
  5795.     // Update: For flexibililty, it seems best to allow a maximized window to be moved, which might be
  5796.     // valid on a multi-monitor system.  This maintains flexibility and doesn't appear to give up
  5797.     // anything because the script can do an explicit "Gui, Show, Restore" prior to a
  5798.     // "Gui, Show, x33 y44 w400" to be sure the window is restored before the operation (or combine
  5799.     // both of those commands into one).
  5800.     bool allow_move_window;
  5801.     RECT rect;
  5802.  
  5803.     if (allow_move_window = !IsIconic(mHwnd)) // Call IsIconic() again in case above changed the window's state.
  5804.     {
  5805.         if (auto_size) // Check this one first so that it takes precedence over mGuiShowHasNeverBeenDone below.
  5806.         {
  5807.             // Find out a different set of max extents rather than using mMaxExtentRight/Down, which should
  5808.             // not be altered because they are used to position any subsequently added controls.
  5809.             width = 0;
  5810.             height = 0;
  5811.             for (GuiIndexType u = 0; u < mControlCount; ++u)
  5812.             {
  5813.                 GuiControlType &control = mControl[u];
  5814.                 if (control.type != GUI_CONTROL_STATUSBAR // Status bar is compensated for in a diff. way.
  5815.                     && GetWindowLong(control.hwnd, GWL_STYLE) & WS_VISIBLE) // Don't use IsWindowVisible() in case parent window is hidden.
  5816.                 {
  5817.                     GetWindowRect(control.hwnd, &rect);
  5818.                     MapWindowPoints(NULL, mHwnd, (LPPOINT)&rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
  5819.                     if (rect.right > width)
  5820.                         width = rect.right;
  5821.                     if (rect.bottom > height)
  5822.                         height = rect.bottom;
  5823.                 }
  5824.             }
  5825.             if (width > 0)
  5826.                 width += mMarginX;
  5827.             if (height > 0)
  5828.                 height += mMarginY;
  5829.             // Don't use IsWindowVisible() because that would say that bar is hidden just because the parent
  5830.             // window is hidden.  We want to know if the bar is truly hidden, separately from the window:
  5831.             if (mStatusBarHwnd && GetWindowLong(mStatusBarHwnd, GWL_STYLE) & WS_VISIBLE)
  5832.             {
  5833.                 GetWindowRect(mStatusBarHwnd, &rect); // GetWindowRect vs. GetClientRect to include any borders it might have.
  5834.                 height += rect.bottom - rect.top;
  5835.             }
  5836.         }
  5837.         else if (width == COORD_UNSPECIFIED || height == COORD_UNSPECIFIED)
  5838.         {
  5839.             if (mGuiShowHasNeverBeenDone) // By default, center the window if this is the first use of "Gui Show" (even "Gui Show, Hide").
  5840.             {
  5841.                 if (width == COORD_UNSPECIFIED)
  5842.                     width = mMaxExtentRight + mMarginX;
  5843.                 if (height == COORD_UNSPECIFIED)
  5844.                 {
  5845.                     height = mMaxExtentDown + mMarginY;
  5846.                     if (mStatusBarHwnd && GetWindowLong(mStatusBarHwnd, GWL_STYLE) & WS_VISIBLE) // See comments in similar section above.
  5847.                     {
  5848.                         GetWindowRect(mStatusBarHwnd, &rect); // GetWindowRect vs. GetClientRect to include any borders it might have.
  5849.                         height += rect.bottom - rect.top;
  5850.                     }
  5851.                 }
  5852.             }
  5853.             else
  5854.             {
  5855.                 GetClientRect(mHwnd, &rect);
  5856.                 if (width == COORD_UNSPECIFIED) // Keep the current client width, as documented.
  5857.                     width = rect.right - rect.left;
  5858.                 if (height == COORD_UNSPECIFIED) // Keep the current client height, as documented.
  5859.                     height = rect.bottom - rect.top;
  5860.             }
  5861.         }
  5862.  
  5863.         // v1.0.44.13: For code size reasons and due to rarity-of-need, the following isn't done (also, it
  5864.         // can't catch all such situations; e.g. when "Gui +MinSize" can be used after "Gui Show".
  5865.         // Plus it might add a bit of flexibility to allow "Gui Show" to override min/max:
  5866.         // Older: The following prevents situations in which the window starts off at a size that's
  5867.         // too big or too small, which in turn causes it to snap to the min/max size the moment
  5868.         // the user tries to drag-move or drag-resize it:
  5869.         //if (mMinWidth >= 0 && width < mMinWidth) // mMinWidth >= 0 covers both COORD_UNSPECIFIED and COORD_CENTERED.
  5870.         //    width = mMinWidth;
  5871.         //else if (mMaxWidth >= 0 && width > mMaxWidth)
  5872.         //    width = mMaxWidth;
  5873.         //if (mMinHeight >= 0 && height < mMinHeight)
  5874.         //    height = mMinHeight;
  5875.         //else if (mMaxHeight >= 0 && height > mMaxHeight)
  5876.         //    height = mMaxHeight;
  5877.  
  5878.     } // if (allow_move_window)
  5879.  
  5880.     if (mGuiShowHasNeverBeenDone)
  5881.     {
  5882.         // Update any tab controls to show only their correct pane.  This should only be necessary
  5883.         // upon the first "Gui Show" (even "Gui, Show, Hide") of the window because subsequent switches
  5884.         // of the control's tab should result in a TCN_SELCHANGE notification.
  5885.         if (mTabControlCount)
  5886.             for (GuiIndexType u = 0; u < mControlCount; ++u)
  5887.                 if (mControl[u].type == GUI_CONTROL_TAB)
  5888.                     ControlUpdateCurrentTab(mControl[u], false); // Pass false so that default/z-order focus is used across entire window.
  5889.         // By default, center the window if this is the first time it's being shown:
  5890.         if (x == COORD_UNSPECIFIED)
  5891.             x = COORD_CENTERED;
  5892.         if (y == COORD_UNSPECIFIED)
  5893.             y = COORD_CENTERED;
  5894.     }
  5895.  
  5896.     BOOL is_visible = IsWindowVisible(mHwnd);
  5897.  
  5898.     if (allow_move_window)
  5899.     {
  5900.         // The above has determined the height/width of the client area.  From that area, determine
  5901.         // the window's new screen rect, including title bar, borders, etc.
  5902.         // If the window has a border or caption this also changes top & left *slightly* from zero.
  5903.         RECT rect = {0, 0, width, height}; // left,top,right,bottom
  5904.         AdjustWindowRectEx(&rect, GetWindowLong(mHwnd, GWL_STYLE), GetMenu(mHwnd) ? TRUE : FALSE
  5905.             , GetWindowLong(mHwnd, GWL_EXSTYLE));
  5906.         width = rect.right - rect.left;  // rect.left might be slightly less than zero.
  5907.         height = rect.bottom - rect.top; // rect.top might be slightly less than zero. A status bar is properly handled since it's inside the window's client area.
  5908.  
  5909.         RECT work_rect;
  5910.         SystemParametersInfo(SPI_GETWORKAREA, 0, &work_rect, 0);  // Get desktop rect excluding task bar.
  5911.         int work_width = work_rect.right - work_rect.left;  // Note that "left" won't be zero if task bar is on left!
  5912.         int work_height = work_rect.bottom - work_rect.top; // Note that "top" won't be zero if task bar is on top!
  5913.  
  5914.         // Seems best to restrict window size to the size of the desktop whenever explicit sizes
  5915.         // weren't given, since most users would probably want that.  But only on first use of
  5916.         // "Gui Show" (even "Gui, Show, Hide"):
  5917.         if (mGuiShowHasNeverBeenDone)
  5918.         {
  5919.             if (width_orig == COORD_UNSPECIFIED && width > work_width)
  5920.                 width = work_width;
  5921.             if (height_orig == COORD_UNSPECIFIED && height > work_height)
  5922.                 height = work_height;
  5923.         }
  5924.  
  5925.         if (x == COORD_CENTERED || y == COORD_CENTERED) // Center it, based on its dimensions determined above.
  5926.         {
  5927.             // This does not currently handle multi-monitor systems explicitly, since those calculations
  5928.             // require API functions that don't exist in Win95/NT (and thus would have to be loaded
  5929.             // dynamically to allow the program to launch).  Therefore, windows will likely wind up
  5930.             // being centered across the total dimensions of all monitors, which usually results in
  5931.             // half being on one monitor and half in the other.  This doesn't seem too terrible and
  5932.             // might even be what the user wants in some cases (i.e. for really big windows).
  5933.             if (x == COORD_CENTERED)
  5934.                 x = work_rect.left + ((work_width - width) / 2);
  5935.             if (y == COORD_CENTERED)
  5936.                 y = work_rect.top + ((work_height - height) / 2);
  5937.         }
  5938.  
  5939.         RECT old_rect;
  5940.         GetWindowRect(mHwnd, &old_rect);
  5941.         int old_width = old_rect.right - old_rect.left;
  5942.         int old_height = old_rect.bottom - old_rect.top;
  5943.  
  5944.         // Avoid calling MoveWindow() if nothing changed because it might repaint/redraw even if window size/pos
  5945.         // didn't change:
  5946.         if (width != old_width || height != old_height || (x != COORD_UNSPECIFIED && x != old_rect.left)
  5947.             || (y != COORD_UNSPECIFIED && y != old_rect.top)) // v1.0.45: Fixed to be old_rect.top not old_rect.bottom.
  5948.         {
  5949.             // v1.0.44.08: Window state gets messed up if it's resized without first unmaximizing it (for example,
  5950.             // it can't be resized by dragging its lower-right corner).  So it seems best to unmaximize, perhaps
  5951.             // even when merely the position is changing rather than the size (even on a multimonitor system,
  5952.             // it might not be valid to reposition a maximized window without unmaximizing it?)
  5953.             if (IsZoomed(mHwnd)) // Call IsZoomed() again in case above changed the state. No need to check IsIconic() because above already set default show-mode to SW_RESTORE for such windows.
  5954.                 ShowWindow(mHwnd, SW_RESTORE); // But restore isn't done for something like "Gui, Show, Center" because it's too obscure and might reduce flexibility (debatable).
  5955.             MoveWindow(mHwnd, x == COORD_UNSPECIFIED ? old_rect.left : x, y == COORD_UNSPECIFIED ? old_rect.top : y
  5956.                 , width, height, is_visible);  // Do repaint if window is visible.
  5957.         }
  5958.  
  5959.         // Added for v1.0.44.13:
  5960.         // Below is done inside this block (allow_move_window) because it that way, it should always
  5961.         // execute whenever mGuiShowHasNeverBeenDone (since the window shouldn't be iconic prior to
  5962.         // its first showing).  In additon, below must be down prior to any ShowWindow() that does
  5963.         // a minimize or maximize because that would prevent GetWindowRect/GetClientRect calculations
  5964.         // below from working properly.
  5965.         if (mGuiShowHasNeverBeenDone) // This is the first showing of this window.
  5966.         {
  5967.             // Now that the window's style, edge type, title bar, menu bar, and other non-client attributes have
  5968.             // likely (but not certainly) been determined, adjust MinMaxSize values from client size to
  5969.             // entire-window size for use with WM_GETMINMAXINFO.
  5970.             // To help reduce code size, the following isn't done (the calls later below are probably very fast):
  5971.             //if (   mMinWidth != COORD_UNSPECIFIED || mMinHeight != COORD_UNSPECIFIED
  5972.             //    || mMaxWidth != COORD_UNSPECIFIED || mMaxHeight != COORD_UNSPECIFIED   )
  5973.             //{
  5974.             // ...
  5975.             RECT rect, client_rect;
  5976.             GetWindowRect(mHwnd, &rect);        // Get both rects again in case MoveWindow wasn't
  5977.             GetClientRect(mHwnd, &client_rect); // above to grant the requested size.
  5978.             int total_width = rect.right - rect.left;
  5979.             int total_height = rect.bottom - rect.top;
  5980.             int extra_width = total_width - client_rect.right;
  5981.             int extra_height = total_height - client_rect.bottom;
  5982.  
  5983.             if (mMinWidth == COORD_CENTERED) // COORD_CENTERED is the flag that means, "use window's current, total width."
  5984.                 mMinWidth = total_width;
  5985.             else if (mMinWidth != COORD_UNSPECIFIED)
  5986.                 mMinWidth += extra_width;
  5987.  
  5988.             if (mMinHeight == COORD_CENTERED)
  5989.                 mMinHeight = total_height;
  5990.             else if (mMinHeight != COORD_UNSPECIFIED)
  5991.                 mMinHeight += extra_height;
  5992.  
  5993.             if (mMaxWidth == COORD_CENTERED)
  5994.                 mMaxWidth = total_width;
  5995.             else if (mMaxWidth != COORD_UNSPECIFIED)
  5996.                 mMaxWidth += extra_width;
  5997.  
  5998.             if (mMaxHeight == COORD_CENTERED)
  5999.                 mMaxHeight = total_height;
  6000.             else if (mMaxHeight != COORD_UNSPECIFIED)
  6001.                 mMaxHeight += extra_height;
  6002.         } // if (mGuiShowHasNeverBeenDone)
  6003.     } // if (allow_move_window)
  6004.  
  6005.     // Note that for SW_MINIMIZE and SW_MAXIMZE, the MoveWindow() above should be done prior to ShowWindow()
  6006.     // so that the window will "remember" its new size upon being restored later.
  6007.     if (!show_was_done)
  6008.         ShowWindow(mHwnd, show_mode);
  6009.  
  6010.     bool we_did_the_first_activation = false; // Set default.
  6011.  
  6012.     switch(show_mode)
  6013.     {
  6014.     case SW_SHOW:
  6015.     case SW_SHOWNORMAL:
  6016.     case SW_MAXIMIZE:
  6017.     case SW_RESTORE:
  6018.         if (mHwnd != GetForegroundWindow())
  6019.             SetForegroundWindowEx(mHwnd);   // In the above modes, try to force it to the foreground as documented.
  6020.         if (mFirstActivation)
  6021.         {
  6022.             // Since the window has never before been active, any of the above qualify as "first activation".
  6023.             // Thus, we are no longer at the first activation:
  6024.             mFirstActivation = false;
  6025.             we_did_the_first_activation = true; // And we're the ones who did the first activation.
  6026.         }
  6027.         break;
  6028.     // No action for these:
  6029.     //case SW_MINIMIZE:
  6030.     //case SW_SHOWNA:
  6031.     //case SW_SHOWNOACTIVATE:
  6032.     //case SW_HIDE:
  6033.     }
  6034.  
  6035.     // No attempt is made to handle the fact that Gui windows can be shown or activated via WinShow and
  6036.     // WinActivate.  In such cases, if the tab control itself is focused, mFirstActivation will stil focus
  6037.     // a control inside the tab rather than leaving the tab control focused.  Similarly, if the window
  6038.     // was shown with NA or NOACTIVATE or MINIMIZE, when the first use of an activation mode of "Gui Show"
  6039.     // is done, even if it's far into the future, long after the user has activated and navigated in the
  6040.     // window, the same "first activation" behavior will be done anyway.  This is documented here as a
  6041.     // known limitation, since fixing it would probably add an unreasonable amount of complexity.
  6042.     if (we_did_the_first_activation)
  6043.     {
  6044.         HWND focused_hwnd = GetFocus(); // Window probably must be visible and active for GetFocus() to work.
  6045.         if (focused_hwnd)
  6046.         {
  6047.             if (mTabControlCount)
  6048.             {
  6049.                 // Since this is the first activation, if the focus wound up on a tab control itself as a result
  6050.                 // of the above, focus the first control of that tab since that is traditional.  HOWEVER, do not
  6051.                 // instead default tab controls to lacking WS_TABSTOP since it is traditional for them to have
  6052.                 // that property, probably to aid accessibility.
  6053.                 GuiControlType *focused_control = FindControl(focused_hwnd);
  6054.                 if (focused_control && focused_control->type == GUI_CONTROL_TAB)
  6055.                 {
  6056.                     // v1.0.27: The following must be done, at least in some cases, because otherwise
  6057.                     // controls outside of the tab control will not get drawn correctly.  I suspect this
  6058.                     // is because at the exact moment execution reaches the line below, the window is in
  6059.                     // a transitional state, with some WM_PAINT and other messages waiting in the queue
  6060.                     // for it.  If those messages are not processed prior to ControlUpdateCurrentTab()'s
  6061.                     // use of WM_SETREDRAW, they might get dropped out of the queue and lost.
  6062.                     UpdateWindow(mHwnd);
  6063.                     ControlUpdateCurrentTab(*focused_control, true);
  6064.                 }
  6065.             }
  6066.             //else no tab controls, but focus has already been set.  Nothing needs to be done.
  6067.         }
  6068.         else // No window/control has keyboard focus (see comment below).
  6069.             SetFocus(mHwnd);
  6070.             // The above was added in v1.0.46.05 to fix the fact that a GUI window could be both active and
  6071.             // foreground yet not have keyboard focus.  This occurs under the following circumstances (and
  6072.             // possibly others):
  6073.             // 1) A script with a menu item that shows a GUI window is reloaded via its tray menu item "Reload".
  6074.             // 2) The GUI window is shown via its custom tray menu item.
  6075.             // 3) The window becomes active and foreground, but doesn't have keyboard focus (not even its
  6076.             //    GuiEscape label will work until you switch away from that window then back to it).
  6077.             // Note: SetFocus() apparently works even on parent windows, which is good because otherwise,
  6078.             // might have to do a loop to find the first input-capable control that's enabled+visible.
  6079.     }
  6080.  
  6081.     mGuiShowHasNeverBeenDone = false;
  6082.     // It seems best to reset this prior to SLEEP below, but after the above line (for code clarity) since
  6083.     // otherwise it might get stuck in a true state if the SLEEP results in the launch of a script
  6084.     // subroutine that takes a long time to complete:
  6085.     mShowIsInProgress = false;
  6086.  
  6087.     // Update for v1.0.25: The below is now done last to prevent the GuiSize label (if any) from launching
  6088.     // while this function is still incomplete; in other words, don't allow the GuiSize label to launch
  6089.     // until after all of the above members and actions have been completed.
  6090.     // This is done for the same reason it's done for ACT_SPLASHTEXTON.  If it weren't done, whenever
  6091.     // a command that blocks (fully uses) the main thread such as "Drive Eject" immediately follows
  6092.     // "Gui Show", the GUI window might not appear until afterward because our thread never had a
  6093.     // chance to call its WindowProc with all the messages needed to actually show the window:
  6094.     SLEEP_WITHOUT_INTERRUPTION(-1)
  6095.     // UpdateWindow() would probably achieve the same effect as the above, but it feels safer to do
  6096.     // the above because it ensures that our message queue is empty prior to returning to our caller.
  6097.  
  6098.     return OK;
  6099. }
  6100.  
  6101.  
  6102.  
  6103. ResultType GuiType::Clear() // Not implemented yet.
  6104. {
  6105.     //if (!mHwnd) // Operating on a non-existent GUI has no effect.
  6106.     //    return OK;
  6107.     return OK;
  6108. }
  6109.  
  6110.  
  6111.  
  6112. ResultType GuiType::Cancel()
  6113. {
  6114.     if (mHwnd)
  6115.         ShowWindow(mHwnd, SW_HIDE);
  6116.     return OK;
  6117. }
  6118.  
  6119.  
  6120.  
  6121. ResultType GuiType::Close()
  6122. // If there is a GuiClose label defined in for this event, launch it as a new thread.
  6123. // In this case, don't close or hide the window.  It's up to the subroutine to do that
  6124. // if it wants to.
  6125. // If there is no label, treat it the same as Cancel().
  6126. {
  6127.     if (!mLabelForClose)
  6128.         return Cancel();
  6129.     POST_AHK_GUI_ACTION(mHwnd, NO_CONTROL_INDEX, GUI_EVENT_CLOSE, NO_EVENT_INFO);
  6130.     // MsgSleep() is not done because "case AHK_GUI_ACTION" in GuiWindowProc() takes care of it.
  6131.     // See its comments for why.
  6132.     return OK;
  6133. }
  6134.  
  6135.  
  6136.  
  6137. ResultType GuiType::Escape() // Similar to close, except typically called when the user presses ESCAPE.
  6138. // If there is a GuiEscape label defined in for this event, launch it as a new thread.
  6139. // In this case, don't close or hide the window.  It's up to the subroutine to do that
  6140. // if it wants to.
  6141. // If there is no label, treat it the same as Cancel().
  6142. {
  6143.     if (!mLabelForEscape) // The user preference (via votes on forum poll) is to do nothing by default.
  6144.         return OK;
  6145.     // See lengthy comments in Event() about this section:
  6146.     POST_AHK_GUI_ACTION(mHwnd, NO_CONTROL_INDEX, GUI_EVENT_ESCAPE, NO_EVENT_INFO);
  6147.     // MsgSleep() is not done because "case AHK_GUI_ACTION" in GuiWindowProc() takes care of it.
  6148.     // See its comments for why.
  6149.     return OK;
  6150. }
  6151.  
  6152.  
  6153.  
  6154. ResultType GuiType::Submit(bool aHideIt)
  6155. // Caller has ensured that all controls have valid, non-NULL hwnds:
  6156. {
  6157.     if (!mHwnd) // Operating on a non-existent GUI has no effect.
  6158.         return OK;
  6159.  
  6160.     // Handle all non-radio controls:
  6161.     GuiIndexType u;
  6162.     for (u = 0; u < mControlCount; ++u)
  6163.         if (mControl[u].output_var && mControl[u].type != GUI_CONTROL_RADIO)
  6164.             ControlGetContents(*mControl[u].output_var, mControl[u], "Submit");
  6165.  
  6166.     // Handle GUI_CONTROL_RADIO separately so that any radio group that has a single variable
  6167.     // to share among all its members can be given special treatment:
  6168.     int group_radios = 0;          // The number of radio buttons in the current group.
  6169.     int group_radios_with_var = 0; // The number of the above that have an output var.
  6170.     Var *group_var = NULL;         // The last-found output variable of the current group.
  6171.     int selection_number = 0;      // Which radio in the current group is selected (0 if none).
  6172.     Var *output_var;
  6173.     char temp[32];
  6174.     
  6175.     // The below uses <= so that it goes one beyond the limit.  This allows the final radio group
  6176.     // (if any) to be noticed in the case where the very last control in the window is a radio button.
  6177.     // This is because in such a case, there is no "terminating control" having the WS_GROUP style:
  6178.     for (u = 0; u <= mControlCount; ++u)
  6179.     {
  6180.         // WS_GROUP is used to determine where one group ends and the next begins -- rather than using
  6181.         // seeing if the control's type is radio -- because in the future it may be possible for a radio
  6182.         // group to have other controls interspersed within it and yet still be a group for the purpose
  6183.         // of "auto radio button (only one selected at a time)" behavior:
  6184.         if (u == mControlCount || GetWindowLong(mControl[u].hwnd, GWL_STYLE) & WS_GROUP) // New group. Relies on short-circuit boolean order.
  6185.         {
  6186.             // If the prior group had exactly one output var but more than one radio in it, that
  6187.             // var is shared among all radios (as of v1.0.20).  Otherwise:
  6188.             // 1) If it has zero radios and/or zero variables: already fully handled by other logic.
  6189.             // 2) It has multiple variables: the default values assigned in the loop are simply retained.
  6190.             // 3) It has exactly one radio in it and that radio has an output var: same as above.
  6191.             if (group_radios_with_var == 1 && group_radios > 1)
  6192.             {
  6193.                 // Multiple buttons selected.  Since this is so rare, don't give it a distinct value.
  6194.                 // Instead, treat this the same as "none selected".  Update for v1.0.24: It is no longer
  6195.                 // directly possible to have multiple radios selected by having the word "checked" in
  6196.                 // more than one of their "Gui Add" commands.  However, there are probably other ways
  6197.                 // to get multiple buttons checked (perhaps the Control command), so this handling
  6198.                 // for multiple selections is left intact.
  6199.                 if (selection_number == -1)
  6200.                     selection_number = 0;
  6201.                 // Convert explicitly to decimal so that g.FormatIntAsHex is not obeyed.
  6202.                 // This is so that this result matches the decimal format tradition set by
  6203.                 // the "1" and "0" strings normally used for radios and checkboxes:
  6204.                 _itoa(selection_number, temp, 10); // selection_number can be legitimately zero.
  6205.                 group_var->Assign(temp); // group_var should not be NULL since group_radios_with_var == 1
  6206.             }
  6207.             if (u == mControlCount) // The last control in the window is a radio and its group was just processed.
  6208.                 break;
  6209.             group_radios = group_radios_with_var = selection_number = 0;
  6210.         }
  6211.         if (mControl[u].type == GUI_CONTROL_RADIO)
  6212.         {
  6213.             ++group_radios;
  6214.             if (output_var = mControl[u].output_var) // Assign.
  6215.             {
  6216.                 ++group_radios_with_var;
  6217.                 group_var = output_var; // If this group winds up having only one var, this will be it.
  6218.             }
  6219.             // Assign default value for now.  It will be overridden if this turns out to be the
  6220.             // only variable in this group:
  6221.             if (SendMessage(mControl[u].hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED)
  6222.             {
  6223.                 if (selection_number) // Multiple buttons selected, so flag this as an indeterminate state.
  6224.                     selection_number = -1;
  6225.                 else
  6226.                     selection_number = group_radios;
  6227.                 if (output_var)
  6228.                     output_var->Assign("1");
  6229.             }
  6230.             else
  6231.                 if (output_var)
  6232.                     output_var->Assign("0");
  6233.         }
  6234.     } // for()
  6235.  
  6236.     if (aHideIt)
  6237.         ShowWindow(mHwnd, SW_HIDE);
  6238.     return OK;
  6239. }
  6240.  
  6241.  
  6242.  
  6243. VarSizeType GuiType::ControlGetName(GuiIndexType aGuiWindowIndex, GuiIndexType aControlIndex, char *aBuf)
  6244. // Caller has ensured that aGuiWindowIndex is less than MAX_GUI_WINDOWS.
  6245. // We're returning the length of the var's contents, not the size.
  6246. {
  6247.     GuiType *pgui;
  6248.     // Relies on short-circuit boolean order:
  6249.     if (aControlIndex >= MAX_CONTROLS_PER_GUI // Must check this first due to short-circuit boolean.  A non-GUI thread or one triggered by GuiClose/Escape or Gui menu bar.
  6250.         || !(pgui = g_gui[aGuiWindowIndex]) // Gui Window no longer exists.
  6251.         || aControlIndex >= pgui->mControlCount) // Gui control no longer exists, perhaps because window was destroyed and recreated with fewer controls.
  6252.     {
  6253.         if (aBuf)
  6254.             *aBuf = '\0';
  6255.         return 0;
  6256.     }
  6257.     GuiControlType &control = pgui->mControl[aControlIndex]; // For performance and convenience.
  6258.     if (aBuf)
  6259.     {
  6260.         // Caller has already ensured aBuf is large enough.
  6261.         if (control.output_var)
  6262.             return (VarSizeType)strlen(strcpy(aBuf, control.output_var->mName));
  6263.         else // Fall back to getting the leading characters of its caption (most often used for buttons).
  6264.             #define A_GUICONTROL_TEXT_LENGTH (MAX_ALLOC_SIMPLE - 1)
  6265.             return GetWindowText(control.hwnd, aBuf, A_GUICONTROL_TEXT_LENGTH + 1); // +1 is verified correct.
  6266.             // Above: some callers don't call for a length estimate first, so they might rely on size never getting
  6267.             // larger than the above.
  6268.     }
  6269.     // Otherwise, just return the length:
  6270.     if (control.output_var)
  6271.         return (VarSizeType)strlen(control.output_var->mName);
  6272.     // Otherwise: Fall back to getting the leading characters of its caption (most often used for buttons)
  6273.     VarSizeType length = GetWindowTextLength(control.hwnd);
  6274.     return (length > A_GUICONTROL_TEXT_LENGTH) ? A_GUICONTROL_TEXT_LENGTH : length;
  6275. }
  6276.  
  6277.  
  6278.  
  6279. ResultType GuiType::ControlGetContents(Var &aOutputVar, GuiControlType &aControl, char *aMode)
  6280. {
  6281.     char *cp, buf[1024]; // For various uses.
  6282.     bool submit_mode = !stricmp(aMode, "Submit");
  6283.     int pos;
  6284.     LRESULT sel_count, i;  // LRESULT is a signed type (same as int/long).
  6285.     SYSTEMTIME st[2];
  6286.  
  6287.     // First handle any control types that behave the same regardless of aMode:
  6288.     switch (aControl.type)
  6289.     {
  6290.     case GUI_CONTROL_UPDOWN: // Doesn't seem useful to ever retrieve the control's actual caption, which is invisible.
  6291.         // Any out of range or non-numeric value in the buddy is ignored since error reporting is
  6292.         // left up to the script, which can compare contents of buddy to those of UpDown to check
  6293.         // validity if it wants.
  6294.         if (aControl.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) // It has a 32-bit vs. 16-bit range.
  6295.             pos = (int)SendMessage(aControl.hwnd, UDM_GETPOS32, 0, 0);
  6296.         else // 16-bit.  Must cast to short to omit the error portion (see comment above).
  6297.             pos = (short)SendMessage(aControl.hwnd, UDM_GETPOS, 0, 0);
  6298.         return aOutputVar.Assign(pos);
  6299.  
  6300.     case GUI_CONTROL_SLIDER: // Doesn't seem useful to ever retrieve the control's actual caption, which is invisible.
  6301.         return aOutputVar.Assign(ControlInvertSliderIfNeeded(aControl, (int)SendMessage(aControl.hwnd, TBM_GETPOS, 0, 0)));
  6302.         // Above assigns it as a signed value because testing shows a slider can have part or all of its
  6303.         // available range consist of negative values.  32-bit values are supported if the range is set
  6304.         // with the right messages.
  6305.  
  6306.     case GUI_CONTROL_PROGRESS:
  6307.         return submit_mode ? OK : aOutputVar.Assign((int)SendMessage(aControl.hwnd, PBM_GETPOS, 0, 0));
  6308.         // Above does not save to control during submit mode, since progress bars do not receive
  6309.         // user input so it seems wasteful 99% of the time.  "GuiControlGet, MyProgress" can be used instead.
  6310.  
  6311.     case GUI_CONTROL_DATETIME:
  6312.         return aOutputVar.Assign(DateTime_GetSystemtime(aControl.hwnd, st) == GDT_VALID
  6313.             ? SystemTimeToYYYYMMDD(buf, st[0]) : ""); // Blank string whenever GDT_NONE/GDT_ERROR.
  6314.  
  6315.     case GUI_CONTROL_MONTHCAL:
  6316.         if (GetWindowLong(aControl.hwnd, GWL_STYLE) & MCS_MULTISELECT)
  6317.         {
  6318.             // For code simplicity and due to the expected rarity of using the MonthCal control, much less
  6319.             // in its range-select mode, the range is returned with a dash between the min and max rather
  6320.             // than as an array or anything fancier.
  6321.             MonthCal_GetSelRange(aControl.hwnd, st);
  6322.             // Seems easier for script (due to consistency) to always return it in range format, even if
  6323.             // only one day is selected.
  6324.             SystemTimeToYYYYMMDD(buf, st[0]);
  6325.             buf[8] = '-'; // Retain only the first 8 chars to omit the time portion, which is unreliable (not relevant anyway).
  6326.             SystemTimeToYYYYMMDD(buf + 9, st[1]);
  6327.             return aOutputVar.Assign(buf, 17); // Limit to 17 chars to omit the time portion of the second timestamp.
  6328.         }
  6329.         else
  6330.         {
  6331.             MonthCal_GetCurSel(aControl.hwnd, st);
  6332.             return aOutputVar.Assign(SystemTimeToYYYYMMDD(buf, st[0]), 8); // Limit to 8 chars to omit the time portion, which is unreliable (not relevant anyway).
  6333.         }
  6334.  
  6335.     case GUI_CONTROL_HOTKEY:
  6336.         // Testing shows that neither GetWindowText() nor WM_GETTEXT can pull anything out of a hotkey
  6337.         // control, so the only type of retrieval that can be offered is the HKM_GETHOTKEY method:
  6338.         HotkeyToText((WORD)SendMessage(aControl.hwnd, HKM_GETHOTKEY, 0, 0), buf);
  6339.         return aOutputVar.Assign(buf);
  6340.     } // switch (aControl.type)
  6341.  
  6342.     if (stricmp(aMode, "Text")) // Non-text, i.e. don't unconditionally use the simple GetWindowText() method.
  6343.     {
  6344.         // The caller wants the contents of the control, which is often different from its
  6345.         // caption/text.  Any control types not mentioned in the switch() below will fall through
  6346.         // into the section at the bottom that applies the standard GetWindowText() method.
  6347.  
  6348.         LRESULT index, length, item_length;
  6349.  
  6350.         switch (aControl.type)
  6351.         {
  6352.         case GUI_CONTROL_CHECKBOX:
  6353.         case GUI_CONTROL_RADIO:
  6354.             // Submit() handles GUI_CONTROL_RADIO on its own, but other callers might need us to handle it.
  6355.             // In addition, rather than handling multi-radio groups that share a single output variable
  6356.             // in a special way, it's kept simple here because:
  6357.             // 1) It's more flexible (there might be cases when the user wants to get the value of
  6358.             //    a single radio in the group, not the group's currently-checked button).
  6359.             // 2) The multi-radio handling seems too complex to be justified given how rarely users would
  6360.             //    want such behavior (since "Submit, NoHide" can be used as a substitute).
  6361.             switch (SendMessage(aControl.hwnd, BM_GETCHECK, 0, 0))
  6362.             {
  6363.             case BST_CHECKED:
  6364.                 return aOutputVar.Assign("1");
  6365.             case BST_UNCHECKED:
  6366.                 return aOutputVar.Assign("0");
  6367.             case BST_INDETERMINATE:
  6368.                 // Seems better to use a value other than blank because blank might sometimes represent the
  6369.                 // state of an unintialized or unfetched control.  In other words, a blank variable often
  6370.                 // has an external meaning that transcends the more specific meaning often desirable when
  6371.                 // retrieving the state of the control:
  6372.                 return aOutputVar.Assign("-1");
  6373.             }
  6374.             return FAIL; // Shouldn't be reached since ZERO(BST_UNCHECKED) is returned on failure.
  6375.  
  6376.         case GUI_CONTROL_DROPDOWNLIST:
  6377.             if (aControl.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT) // Caller wanted the position, not the text retrieved.
  6378.             {
  6379.                 index = SendMessage(aControl.hwnd, CB_GETCURSEL, 0, 0); // Get index of currently selected item.
  6380.                 if (index == CB_ERR) // Maybe happens only if DROPDOWNLIST has no items at all, so ErrorLevel is not changed.
  6381.                     return aOutputVar.Assign();
  6382.                 return aOutputVar.Assign((int)index + 1);
  6383.             }
  6384.             break; // Fall through to the normal GetWindowText() method, which works for DDLs but not ComboBoxes.
  6385.  
  6386.         case GUI_CONTROL_COMBOBOX:
  6387.             index = SendMessage(aControl.hwnd, CB_GETCURSEL, 0, 0); // Get index of currently selected item.
  6388.             if (index == CB_ERR) // There is no selection (or very rarely, some other type of problem).
  6389.             {
  6390.                 // Fix for v1.0.40.08: It seems that any text put into a ComboBox's edit field via GuiControl or
  6391.                 // even the user typing/pasting it does not cause the box to update its current selection/position.
  6392.                 // Since this can be the reason for the CB_ERR retrieved above, check if the Edit field
  6393.                 // contains text that exactly matches one of the items in the drop-list.  If it does, that
  6394.                 // item's position should be retrieved in AltSubmit mode (even for non-AltSubmit mode, this
  6395.                 // should be done because the case of the item in the drop-list is usually preferable to any
  6396.                 // varying case the user may have manually typed).
  6397.                 if (GetWindowText(aControl.hwnd, buf, sizeof(buf))) // Buf size should be enough for anything realistic.
  6398.                 {
  6399.                     index = SendMessage(aControl.hwnd, CB_FINDSTRINGEXACT, -1, (LPARAM)&buf); // It's not case sensitive.
  6400.                     if (index == CB_ERR)
  6401.                         break;  // Break out of the switch rather than returning so that the GetWindowText() method can be applied.
  6402.                 }
  6403.                 else // Failure of GetWindowText() in this case might be nearly impossible, so just fall through to default handling.
  6404.                     break; // Same comment as above.
  6405.             }
  6406.             if (aControl.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT) // Caller wanted the position, not the text retrieved.
  6407.                 return aOutputVar.Assign((int)index + 1);
  6408.             length = SendMessage(aControl.hwnd, CB_GETLBTEXTLEN, (WPARAM)index, 0);
  6409.             if (length == CB_ERR) // Given the way it was called, this should be impossible based on MSDN docs.
  6410.                 return aOutputVar.Assign();
  6411.             // In unusual cases, MSDN says the indicated length might be longer than it actually winds up
  6412.             // being when the item's text is retrieved.  This should be harmless, since there are many
  6413.             // other precedents where a variable is sized to something larger than it winds up carrying.
  6414.             // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  6415.             // this call will set up the clipboard for writing:
  6416.             if (aOutputVar.Assign(NULL, (VarSizeType)length) != OK)
  6417.                 return FAIL;  // It already displayed the error.
  6418.             length = SendMessage(aControl.hwnd, CB_GETLBTEXT, (WPARAM)index, (LPARAM)aOutputVar.Contents());
  6419.             if (length == CB_ERR) // Given the way it was called, this should be impossible based on MSDN docs.
  6420.             {
  6421.                 aOutputVar.Close(); // In case it's the clipboard.
  6422.                 return aOutputVar.Assign();
  6423.             }
  6424.             aOutputVar.Length() = (VarSizeType)length;  // Update it to the actual length, which can vary from the estimate.
  6425.             return aOutputVar.Close(); // In case it's the clipboard.
  6426.  
  6427.         case GUI_CONTROL_LISTBOX:
  6428.             if (GetWindowLong(aControl.hwnd, GWL_STYLE) & (LBS_EXTENDEDSEL|LBS_MULTIPLESEL))
  6429.             {
  6430.                 sel_count = SendMessage(aControl.hwnd, LB_GETSELCOUNT, 0, 0);
  6431.                 if (sel_count < 1)  // <=0 to check for LB_ERR too (but it should be impossible in this case).
  6432.                     return aOutputVar.Assign();
  6433.                 int *item = (int *)malloc(sel_count * sizeof(int)); // dynamic since there can be a very large number of items.
  6434.                 if (!item)
  6435.                     return aOutputVar.Assign();
  6436.                 sel_count = SendMessage(aControl.hwnd, LB_GETSELITEMS, (WPARAM)sel_count, (LPARAM)item);
  6437.                 if (sel_count < 1)  // 0 or LB_ERR, but both these conditions should be impossible in this case.
  6438.                 {
  6439.                     free(item);
  6440.                     return aOutputVar.Assign();
  6441.                 }
  6442.                 if (aControl.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT) // Caller wanted the positions, not the text retrieved.
  6443.                 {
  6444.                     // Accumulate the length of delimited list of positions.
  6445.                     // length is initialized to sel_count - 1 to account for all the delimiter
  6446.                     // characters in the list, one delim after each item except the last:
  6447.                     for (length = sel_count - 1, i = 0; i < sel_count; ++i)
  6448.                     {
  6449.                         _itoa(item[i] + 1, buf, 10);  // +1 to convert from zero-based to 1-based.
  6450.                         length += strlen(buf);
  6451.                     }
  6452.                 }
  6453.                 else
  6454.                 {
  6455.                     // Accumulate the length of delimited list of selected items (not positions in this case).
  6456.                     // See above loop for more comments.
  6457.                     for (length = sel_count - 1, i = 0; i < sel_count; ++i)
  6458.                     {
  6459.                         item_length = SendMessage(aControl.hwnd, LB_GETTEXTLEN, (WPARAM)item[i], 0);
  6460.                         if (item_length == LB_ERR) // Realistically impossible based on MSDN.
  6461.                         {
  6462.                             free(item);
  6463.                             return aOutputVar.Assign();
  6464.                         }
  6465.                         length += item_length;
  6466.                     }
  6467.                 }
  6468.                 // In unusual cases, MSDN says the indicated length might be longer than it actually winds up
  6469.                 // being when the item's text is retrieved.  This should be harmless, since there are many
  6470.                 // other precedents where a variable is sized to something larger than it winds up carrying.
  6471.                 // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  6472.                 // this call will set up the clipboard for writing:
  6473.                 if (aOutputVar.Assign(NULL, (VarSizeType)length) != OK)
  6474.                     return FAIL;  // It already displayed the error.
  6475.                 cp = aOutputVar.Contents(); // Init for both of the loops below.
  6476.                 if (aControl.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT) // Caller wanted the positions, not the text retrieved.
  6477.                 {
  6478.                     // In this case, the original length estimate should be the same as the actual, so
  6479.                     // it is not re-accumulated.
  6480.                     // See above loop for more comments.
  6481.                     for (i = 0; i < sel_count; ++i)
  6482.                     {
  6483.                         if (i) // Serves to add delimiter after each item except the last (helps parsing loop).
  6484.                             *cp++ = mDelimiter;
  6485.                         _itoa(item[i] + 1, cp, 10);  // +1 to convert from zero-based to 1-based.
  6486.                         cp += strlen(cp);  // Point it to the terminator in preparation for the next write.
  6487.                     }
  6488.                 }
  6489.                 else // Store item text vs. position.
  6490.                 {
  6491.                     // See above loop for more comments.
  6492.                     for (length = sel_count - 1, i = 0; i < sel_count; ++i)
  6493.                     {
  6494.                         if (i) // Serves to add delimiter after each item except the last (helps parsing loop).
  6495.                             *cp++ = mDelimiter;
  6496.                         // Above:
  6497.                         // A hard-coded pipe delimiter is used for now because it seems fairly easy to
  6498.                         // add an option later for a custom delimtier (such as '\n') via an Param4 of
  6499.                         // GuiControlGetText and/or an option-word in "Gui Add".  The reason pipe is
  6500.                         // used as a delimiter is that it allows the selection to be easily inserted
  6501.                         // into another ListBox because it's already in the right format with the
  6502.                         // right delimiter.  In addition, literal pipes should be rare since that is
  6503.                         // the delimiter used when insertting and appending entries into a ListBox.
  6504.                         item_length = SendMessage(aControl.hwnd, LB_GETTEXT, (WPARAM)item[i], (LPARAM)cp);
  6505.                         if (item_length > 0) // Given the way it was called, LB_ERR (-1) should be impossible based on MSDN docs.  But if it happens, just skip that field.
  6506.                         {
  6507.                             length += item_length; // Accumulate actual vs. estimated length.
  6508.                             cp += item_length;  // Point it to the terminator in preparation for the next write.
  6509.                         }
  6510.                     }
  6511.                 }
  6512.                 free(item);
  6513.             }
  6514.             else // Single-select ListBox style.
  6515.             {
  6516.                 index = SendMessage(aControl.hwnd, LB_GETCURSEL, 0, 0); // Get index of currently selected item.
  6517.                 if (index == LB_ERR) // There is no selection (or very rarely, some other type of problem).
  6518.                     return aOutputVar.Assign();
  6519.                 if (aControl.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT) // Caller wanted the position, not the text retrieved.
  6520.                     return aOutputVar.Assign((int)index + 1);
  6521.                 length = SendMessage(aControl.hwnd, LB_GETTEXTLEN, (WPARAM)index, 0);
  6522.                 if (length == LB_ERR) // Given the way it was called, this should be impossible based on MSDN docs.
  6523.                     return aOutputVar.Assign();
  6524.                 // In unusual cases, MSDN says the indicated length might be longer than it actually winds up
  6525.                 // being when the item's text is retrieved.  This should be harmless, since there are many
  6526.                 // other precedents where a variable is sized to something larger than it winds up carrying.
  6527.                 // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  6528.                 // this call will set up the clipboard for writing:
  6529.                 if (aOutputVar.Assign(NULL, (VarSizeType)length) != OK)
  6530.                     return FAIL;  // It already displayed the error.
  6531.                 length = SendMessage(aControl.hwnd, LB_GETTEXT, (WPARAM)index, (LPARAM)aOutputVar.Contents());
  6532.                 if (length == LB_ERR) // Given the way it was called, this should be impossible based on MSDN docs.
  6533.                 {
  6534.                     aOutputVar.Close(); // In case it's the clipboard.
  6535.                     return aOutputVar.Assign();
  6536.                 }
  6537.             }
  6538.             aOutputVar.Length() = (VarSizeType)length;  // Update it to the actual length, which can vary from the estimate.
  6539.             return aOutputVar.Close(); // In case it's the clipboard.
  6540.  
  6541.         case GUI_CONTROL_TAB:
  6542.             index = TabCtrl_GetCurSel(aControl.hwnd); // Get index of currently selected item.
  6543.             if (index == -1) // There is no selection (maybe happens only if it has no tabs at all), so ErrorLevel is not changed.
  6544.                 return aOutputVar.Assign();
  6545.             if (aControl.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT) // Caller wanted the index, not the text retrieved.
  6546.                 return aOutputVar.Assign((int)index + 1);
  6547.             // Otherwise: Get the stored name/caption of this tab:
  6548.             TCITEM tci;
  6549.             tci.mask = TCIF_TEXT;
  6550.             tci.pszText = buf;
  6551.             tci.cchTextMax = sizeof(buf) - 1; // MSDN example uses -1.
  6552.             if (TabCtrl_GetItem(aControl.hwnd, index, &tci))
  6553.                 return aOutputVar.Assign(tci.pszText);
  6554.             return aOutputVar.Assign();
  6555.  
  6556.         case GUI_CONTROL_TEXT:
  6557.         case GUI_CONTROL_PIC:
  6558.         case GUI_CONTROL_GROUPBOX:
  6559.         case GUI_CONTROL_BUTTON:
  6560.         case GUI_CONTROL_PROGRESS:
  6561.         case GUI_CONTROL_LISTVIEW: // LV and TV do not obey Submit. Instead, more flexible methods are available to the script.
  6562.         case GUI_CONTROL_TREEVIEW: //
  6563.             if (submit_mode) // In submit mode, do not waste memory & cpu time to save the above.
  6564.                 // There doesn't seem to be a strong/net advantage to setting the vars to be blank
  6565.                 // because even if that were done, it seems it would not do much to reserve flexibility
  6566.                 // for future features in which these associated variables are used for a purpose other
  6567.                 // than uniquely identifying the control with GuiControl & GuiControlGet.
  6568.                 return OK;
  6569.             //else an explicit Get was called on the control, so it seems best to try to get it's text (if any).
  6570.             break;
  6571.         // Types specifically not handled here.  They will be handled by the section below this switch():
  6572.         //case GUI_CONTROL_EDIT:
  6573.         } // switch()
  6574.     } // if (!aGetText)
  6575.  
  6576.     // Since the above didn't return, at lest one of the following is true:
  6577.     // 1) aGetText is true (the caller wanted the simple GetWindowText() method applied unconditionally).
  6578.     // 2) This control's type is not mentioned in the switch because it does not require special handling.
  6579.     //   e.g.  GUI_CONTROL_EDIT, GUI_CONTROL_DROPDOWNLIST, and others that use a simple GetWindowText().
  6580.     // 3) This control is a ComboBox, but it lacks a selected item, so any text entered by the user
  6581.     //    into the control's edit field is fetched instead.
  6582.  
  6583.     // Set up the var, enlarging it if necessary.  If the output_var is of type VAR_CLIPBOARD,
  6584.     // this call will set up the clipboard for writing:
  6585.     int length = GetWindowTextLength(aControl.hwnd); // Might be zero, which is properly handled below.
  6586.     if (aOutputVar.Assign(NULL, (VarSizeType)length) != OK)
  6587.         return FAIL;  // It already displayed the error.
  6588.     // Update length using the actual length, rather than the estimate provided by GetWindowTextLength():
  6589.     if (   !(aOutputVar.Length() = (VarSizeType)GetWindowText(aControl.hwnd, aOutputVar.Contents(), (int)(length + 1)))   )
  6590.         // There was no text to get.  Set to blank explicitly just to be sure.
  6591.         *aOutputVar.Contents() = '\0';  // Safe because Assign() gave us a non-constant memory area.
  6592.     else if (aControl.type == GUI_CONTROL_EDIT) // Auto-translate CRLF to LF for better compatibility with other script commands.
  6593.     {
  6594.         // Since edit controls tend to have many hard returns in them, use "true" for the last param to
  6595.         // enhance performance.  This performance gain is extreme when the control contains thousands
  6596.         // of CRLFs:
  6597.         StrReplace(aOutputVar.Contents(), "\r\n", "\n", SCS_SENSITIVE);
  6598.         aOutputVar.Length() = (VarSizeType)strlen(aOutputVar.Contents());
  6599.     }
  6600.     return aOutputVar.Close();  // In case it's the clipboard.
  6601. }
  6602.  
  6603.  
  6604.  
  6605. GuiIndexType GuiType::FindControl(char *aControlID)
  6606. // Find the index of the control that matches the string, which can be either:
  6607. // 1) The name of a control's associated output variable.
  6608. // 2) Class+NN
  6609. // 3) Control's title/caption.
  6610. // Returns -1 if not found.
  6611. {
  6612.     // v1.0.44.08: Added the following check.  Without it, ControlExist() (further below) would retrieve the
  6613.     // topmost child, which isn't very useful or intuitive.  This currently affects only the following commands:
  6614.     // 1) GuiControl (but not GuiControlGet because it has special handling for a blank ControlID).
  6615.     // 2) Gui, ListView|TreeView, MyTree|MyList
  6616.     if (!*aControlID)
  6617.         return -1;
  6618.     GuiIndexType u;
  6619.     // To keep things simple, the first search method is always conducted: It looks for a
  6620.     // matching variable name, but only among the variables used by this particular window's
  6621.     // controls (i.e. avoid ambiguity by NOT having earlier matched up aControlID against
  6622.     // all variable names in the entire script, perhaps in PreparseBlocks() or something).
  6623.     // UPDATE: For v1.0.31, the performance is improved by resolving the variable to its
  6624.     // pointer first, rather than comparing the variable names for a match.  It's further
  6625.     // improved by skipping the first loop entirely when aControlID doesn't exist as a global
  6626.     // variable (GUI controls always have global variables, not locals).
  6627.     Var *var;
  6628.     if (var = g_script.FindVar(aControlID, 0, NULL, ALWAYS_USE_GLOBAL)) // First search globals only because for backward compatibility, a GUI control whose Var* is identical to that of a global should be given precedence over a static that matches some other control.  Furthermore, since most GUI variables are global, doing this check before the static check improves avg-case performance.
  6629.     {
  6630.         // No need to do "var = var->ResolveAlias()" because the line above never finds locals, only globals.
  6631.         // Similarly, there's no need to do confirm that var->IsLocal()==false.
  6632.         for (u = 0; u < mControlCount; ++u)
  6633.             if (mControl[u].output_var == var)
  6634.                 return u;  // Match found.
  6635.     }
  6636.     if (g.CurrentFunc // v1.0.46.15: Since above failed to match: if we're in a function (which is checked for performance reasons), search for a static or ByRef-that-points-to-a-global-or-static because both should be supported.
  6637.         && (var = g_script.FindVar(aControlID, 0, NULL, ALWAYS_USE_LOCAL)))
  6638.     {
  6639.         // No need to do "var = var->ResolveAlias()" because the line above never finds locals, only globals.
  6640.         // Similarly, there's no need to do confirm that var->IsLocal()==false.
  6641.         var = var->ResolveAlias(); // Update it to its target if it's an alias because that's how control-var's are stored (i.e. pre-resolved, never aliases).
  6642.         if (!var->IsNonStaticLocal()) // To be a valid control-var, it must be global, static, or a ByRef that points to a global or static.
  6643.             for (u = 0; u < mControlCount; ++u)
  6644.                 if (mControl[u].output_var == var)
  6645.                     return u;  // Match found.
  6646.     }
  6647.     // Otherwise: No match found, so fall back to standard control class and/or text finding method.
  6648.     HWND control_hwnd = ControlExist(mHwnd, aControlID);
  6649.     if (!control_hwnd)
  6650.         return -1; // No match found.
  6651.     for (u = 0; u < mControlCount; ++u)
  6652.         if (mControl[u].hwnd == control_hwnd)
  6653.             return u;  // Match found.
  6654.     // Otherwise: No match found.  At this stage, should be impossible if design is correct.
  6655.     return -1;
  6656. }
  6657.  
  6658.  
  6659.  
  6660. int GuiType::FindGroup(GuiIndexType aControlIndex, GuiIndexType &aGroupStart, GuiIndexType &aGroupEnd)
  6661. // Caller must provide a valid aControlIndex for an existing control.
  6662. // Returns the number of radio buttons inside the group. In addition, it provides start and end
  6663. // values to the caller via aGroupStart/End, where Start is the index of the first control in
  6664. // the group and End is the index of the control *after* the last control in the group (if none,
  6665. // aGroupEnd is set to mControlCount).
  6666. // NOTE: This returns the range covering the entire group, and it is possible for the group
  6667. // to contain non-radio type controls.  Thus, the caller should check each control inside the
  6668. // returned range to make sure it's a radio before operating upon it.
  6669. {
  6670.     // Work backwards in the control array until the first member of the group is found or the
  6671.     // first array index, whichever comes first (the first array index is the top control in the
  6672.     // Z-Order and thus treated by the OS as an implicit start of a new group):
  6673.     int group_radios = 0; // This and next are both init'd to 0 not 1 because the first loop checks aControlIndex itself.
  6674.     for (aGroupStart = aControlIndex;; --aGroupStart)
  6675.     {
  6676.         if (mControl[aGroupStart].type == GUI_CONTROL_RADIO)
  6677.             ++group_radios;
  6678.         if (!aGroupStart || GetWindowLong(mControl[aGroupStart].hwnd, GWL_STYLE) & WS_GROUP)
  6679.             break;
  6680.     }
  6681.     // Now find the control after the last control (or mControlCount if none).  Must start at +1
  6682.     // because if aControlIndex's control has the WS_GROUP style, we don't want to count that
  6683.     // as the end of the group (because in fact that would be the beginning of the group).
  6684.     for (aGroupEnd = aControlIndex + 1; aGroupEnd < mControlCount; ++aGroupEnd)
  6685.     {
  6686.         // Unlike the previous loop, this one must do this check prior to the next one:
  6687.         if (GetWindowLong(mControl[aGroupEnd].hwnd, GWL_STYLE) & WS_GROUP)
  6688.             break;
  6689.         if (mControl[aGroupEnd].type == GUI_CONTROL_RADIO)
  6690.             ++group_radios;
  6691.     }
  6692.     return group_radios;
  6693. }
  6694.  
  6695.  
  6696.  
  6697. ResultType GuiType::SetCurrentFont(char *aOptions, char *aFontName)
  6698. {
  6699.     COLORREF color;
  6700.     int font_index = FindOrCreateFont(aOptions, aFontName, &sFont[mCurrentFontIndex], &color);
  6701.     if (color != CLR_NONE) // Even if the above call failed, it returns a color if one was specified.
  6702.         mCurrentColor = color;
  6703.     if (font_index > -1) // Success.
  6704.     {
  6705.         mCurrentFontIndex = font_index;
  6706.         return OK;
  6707.     }
  6708.     // Failure of the above is rare because it falls back to default typeface if the one specified
  6709.     // isn't found.  It will have already displayed the error:
  6710.     return FAIL;
  6711. }
  6712.  
  6713.  
  6714.  
  6715. int GuiType::FindOrCreateFont(char *aOptions, char *aFontName, FontType *aFoundationFont, COLORREF *aColor)
  6716. // Returns the index of existing or new font within the sFont array (an index is returned so that
  6717. // caller can see that this is the default-gui-font whenever index 0 is returned).  Returns -1
  6718. // on error, but still sets *aColor to be the color name, if any was specified in aOptions.
  6719. // To prevent a large number of font handles from being created (such as one for each control
  6720. // that uses something other than GUI_DEFAULT_FONT), it seems best to conserve system resources
  6721. // by creating new fonts only when called for.  Therefore, this function will first check if
  6722. // the specified font already exists within the array of fonts.  If not found, a new font will
  6723. // be added to the array.
  6724. {
  6725.     // Set default output parameter in case of early return:
  6726.     if (aColor) // Caller wanted color returned in an output parameter.
  6727.         *aColor = CLR_NONE; // Because we want CLR_DEFAULT to indicate a real color.
  6728.  
  6729.     HDC hdc;
  6730.  
  6731.     if (!*aOptions && !*aFontName)
  6732.     {
  6733.         // Relies on the fact that first item in the font array is always the default font.
  6734.         // If there are fonts, the default font should be the first one (index 0).
  6735.         // If not, we create it here:
  6736.         if (!sFontCount)
  6737.         {
  6738.             // For simplifying other code sections, create an entry in the array for the default font
  6739.             // (GUI constructor relies on at least one font existing in the array).
  6740.             if (!sFont) // v1.0.44.14: Created upon first use to conserve ~14 KB memory in non-GUI scripts.
  6741.                 if (   !(sFont = (FontType *)malloc(sizeof(FontType) * MAX_GUI_FONTS))   )
  6742.                     g_script.ExitApp(EXIT_CRITICAL, ERR_OUTOFMEM); // Since this condition is so rare, just abort to avoid the need to add extra logic in several places to detect a failed/NULL array.
  6743.             // Doesn't seem likely that DEFAULT_GUI_FONT face/size will change while a script is running,
  6744.             // or even while the system is running for that matter.  I think it's always an 8 or 9 point
  6745.             // font regardless of desktop's appearance/theme settings.
  6746.             ZeroMemory(&sFont[sFontCount], sizeof(FontType));
  6747.             // SYSTEM_FONT seems to be the bold one that is used in a dialog window by default.
  6748.             // MSDN: "It is not necessary (but it is not harmful) to delete stock objects by calling DeleteObject."
  6749.             sFont[sFontCount].hfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
  6750.             // Get attributes of DEFAULT_GUI_FONT (name, size, etc.)
  6751.             hdc = GetDC(HWND_DESKTOP);
  6752.             HFONT hfont_old = (HFONT)SelectObject(hdc, sFont[sFontCount].hfont);
  6753.             GetTextFace(hdc, MAX_FONT_NAME_LENGTH, sFont[sFontCount].name);
  6754.             TEXTMETRIC tm;
  6755.             GetTextMetrics(hdc, &tm);
  6756.             // Convert height to points.  Use MulDiv's build-in rounding to get a more accurate result.
  6757.             // This is confirmed to be the correct formula to convert tm's height to font point size,
  6758.             // and it does yield 8 for DEFAULT_GUI_FONT as it should:
  6759.             sFont[sFontCount].point_size = MulDiv(tm.tmHeight - tm.tmInternalLeading, 72, GetDeviceCaps(hdc, LOGPIXELSY));
  6760.             sFont[sFontCount].weight = tm.tmWeight;
  6761.             // Probably unnecessary for default font, but just to be consistent:
  6762.             sFont[sFontCount].italic = (bool)tm.tmItalic;
  6763.             sFont[sFontCount].underline = (bool)tm.tmUnderlined;
  6764.             sFont[sFontCount].strikeout = (bool)tm.tmStruckOut;
  6765.             SelectObject(hdc, hfont_old); // Necessary to avoid memory leak.
  6766.             ReleaseDC(HWND_DESKTOP, hdc);
  6767.             ++sFontCount;
  6768.         }
  6769.         // Tell caller to return to default color, since this is documented behavior when
  6770.         // returning to default font:
  6771.         if (aColor) // Caller wanted color returned in an output parameter.
  6772.             *aColor = CLR_DEFAULT;
  6773.         return 0;  // Always returns 0 since that is always the index of the default font.
  6774.     }
  6775.  
  6776.     // Otherwise, a non-default name/size, etc. is being requested.  Find or create a font to match it.
  6777.     if (!aFoundationFont) // Caller didn't specify a font whose attributes should be used as default.
  6778.     {
  6779.         if (sFontCount > 0)
  6780.             aFoundationFont = &sFont[0]; // Use default if it exists.
  6781.         else
  6782.             return -1; // No error displayed since shouldn't happen if things are designed right.
  6783.     }
  6784.  
  6785.     // Copy the current default font's attributes into our local font structure.
  6786.     // The caller must ensure that mCurrentFontIndex array element exists:
  6787.     FontType font = *aFoundationFont;
  6788.     if (*aFontName)
  6789.         strlcpy(font.name, aFontName, MAX_FONT_NAME_LENGTH+1);
  6790.     COLORREF color = CLR_NONE; // Because we want to treat CLR_DEFAULT as a real color.
  6791.  
  6792.     // Temp vars:
  6793.     char color_str[32], *space_pos;
  6794.  
  6795.     for (char *cp = aOptions; *cp; ++cp)
  6796.     {
  6797.         switch(toupper(*cp))
  6798.         {
  6799.         case 'B':
  6800.             if (!strnicmp(cp, "bold", 4))
  6801.             {
  6802.                 font.weight = FW_BOLD;
  6803.                 cp += 3;  // Skip over the word itself to prevent next interation from seeing it as option letters.
  6804.             }
  6805.             break;
  6806.  
  6807.         case 'I':
  6808.             if (!strnicmp(cp, "italic", 6))
  6809.             {
  6810.                 font.italic = true;
  6811.                 cp += 5;  // Skip over the word itself to prevent next interation from seeing it as option letters.
  6812.             }
  6813.             break;
  6814.  
  6815.         case 'N':
  6816.             if (!strnicmp(cp, "norm", 4))
  6817.             {
  6818.                 font.italic = false;
  6819.                 font.underline = false;
  6820.                 font.strikeout = false;
  6821.                 font.weight = FW_NORMAL;
  6822.                 cp += 3;  // Skip over the word itself to prevent next interation from seeing it as option letters.
  6823.             }
  6824.             break;
  6825.  
  6826.         case 'U':
  6827.             if (!strnicmp(cp, "underline", 9))
  6828.             {
  6829.                 font.underline = true;
  6830.                 cp += 8;  // Skip over the word itself to prevent next interation from seeing it as option letters.
  6831.             }
  6832.             break;
  6833.  
  6834.         case 'C': // Color
  6835.             strlcpy(color_str, cp + 1, sizeof(color_str));
  6836.             if (space_pos = StrChrAny(color_str, " \t"))  // space or tab
  6837.                 *space_pos = '\0';
  6838.             //else a color name can still be present if it's at the end of the string.
  6839.             color = ColorNameToBGR(color_str);
  6840.             if (color == CLR_NONE) // A matching color name was not found, so assume it's in hex format.
  6841.             {
  6842.                 // For v1.0.22, this is no longer done because want to support an optional leading 0x
  6843.                 // if it is present, e.g. 0xFFAABB.  It seems strtol() automatically handles the
  6844.                 // optional leading "0x" if present:
  6845.                 //if (strlen(color_str) > 6)
  6846.                 //    color_str[6] = '\0';  // Shorten to exactly 6 chars, which happens if no space/tab delimiter is present.
  6847.                 color = rgb_to_bgr(strtol(color_str, NULL, 16));
  6848.                 // if color_str does not contain something hex-numeric, black (0x00) will be assumed,
  6849.                 // which seems okay given how rare such a problem would be.
  6850.             }
  6851.             // Skip over the color string to avoid interpreting hex digits or color names as option letters:
  6852.             cp += strlen(color_str);
  6853.             break;
  6854.  
  6855.         // For options such as S and W:
  6856.         // Use atoi()/atof() vs. ATOI()/ATOF() to avoid interpreting something like 0x01B as hex when in fact
  6857.         // the B was meant to be an option letter:
  6858.         case 'S':
  6859.             // Seems best to allow fractional point sizes via atof, though it might usually get rounded
  6860.             // by the OS anyway (at the time font is created):
  6861.             if (!strnicmp(cp, "strike", 6))
  6862.             {
  6863.                 font.strikeout = true;
  6864.                 cp += 5;  // Skip over the word itself to prevent next interation from seeing it as option letters.
  6865.             }
  6866.             else
  6867.                 font.point_size = (int)(atof(cp + 1) + 0.5);  // Round to nearest int.
  6868.             break;
  6869.  
  6870.         case 'W':
  6871.             font.weight = atoi(cp + 1);
  6872.             break;
  6873.  
  6874.         // Otherwise: Ignore other characters, such as the digits that occur after the P/X/Y option letters.
  6875.         } // switch()
  6876.     } // for()
  6877.  
  6878.     if (aColor) // Caller wanted color returned in an output parameter.
  6879.         *aColor = color;
  6880.  
  6881.     hdc = GetDC(HWND_DESKTOP);
  6882.     // Fetch the value every time in case it can change while the system is running (e.g. due to changing
  6883.     // display to TV-Out, etc).  In addition, this HDC is needed by 
  6884.     int pixels_per_point_y = GetDeviceCaps(hdc, LOGPIXELSY);
  6885.  
  6886.     // The reason it's done this way is that CreateFont() does not always (ever?) fail if given a
  6887.     // non-existent typeface:
  6888.     if (!FontExist(hdc, font.name)) // Fall back to foundation font's type face, as documented.
  6889.         strcpy(font.name, aFoundationFont ? aFoundationFont->name : sFont[0].name);
  6890.  
  6891.     ReleaseDC(HWND_DESKTOP, hdc);
  6892.     hdc = NULL;
  6893.  
  6894.     // Now that the attributes of the requested font are known, see if such a font already
  6895.     // exists in the array:
  6896.     int font_index = FindFont(font);
  6897.     if (font_index != -1) // Match found.
  6898.         return font_index;
  6899.  
  6900.     // Since above didn't return, create the font if there's room.
  6901.     if (sFontCount >= MAX_GUI_FONTS)
  6902.     {
  6903.         g_script.ScriptError("Too many fonts." ERR_ABORT);  // Short msg since so rare.
  6904.         return -1;
  6905.     }
  6906.  
  6907.     // MulDiv() is usually better because it has automatic rounding, getting the target font
  6908.     // closer to the size specified:
  6909.     if (   !(font.hfont = CreateFont(-MulDiv(font.point_size, pixels_per_point_y, 72), 0, 0, 0
  6910.         , font.weight, font.italic, font.underline, font.strikeout
  6911.         , DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_DONTCARE, font.name))   )
  6912.         // OUT_DEFAULT_PRECIS/OUT_TT_PRECIS ... DEFAULT_QUALITY/PROOF_QUALITY
  6913.     {
  6914.         g_script.ScriptError("Can't create font." ERR_ABORT);  // Short msg since so rare.
  6915.         return -1;
  6916.     }
  6917.  
  6918.     sFont[sFontCount++] = font; // Copy the newly created font's attributes into the next array element.
  6919.     return sFontCount - 1; // The index of the newly created font.
  6920. }
  6921.  
  6922.  
  6923.  
  6924. int GuiType::FindFont(FontType &aFont)
  6925. {
  6926.     for (int i = 0; i < sFontCount; ++i)
  6927.         if (!stricmp(sFont[i].name, aFont.name) // lstrcmpi() is not used: 1) avoids breaking exisitng scripts; 2) provides consistent behavior across multiple locales.
  6928.             && sFont[i].point_size == aFont.point_size
  6929.             && sFont[i].weight == aFont.weight
  6930.             && sFont[i].italic == aFont.italic
  6931.             && sFont[i].underline == aFont.underline
  6932.             && sFont[i].strikeout == aFont.strikeout) // Match found.
  6933.             return i;
  6934.     return -1;  // Indicate failure.
  6935. }
  6936.  
  6937.  
  6938.  
  6939. LRESULT CALLBACK GuiWindowProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
  6940. {
  6941.     // If a message pump other than our own is running -- such as that of a dialog like MsgBox -- it will
  6942.     // dispatch messages directly here.  This is detected by means of g.CalledByIsDialogMessageOrDispatch==false.
  6943.     // Such messages need to be checked here because MsgSleep hasn't seen the message and thus hasn't
  6944.     // done the check. The g.CalledByIsDialogMessageOrDispatch method relies on the fact that we never call
  6945.     // MsgSleep here for the types of messages dispatched from MsgSleep, which seems true.  Also, if
  6946.     // we do lauch a monitor thread here via MsgMonitor, that means g.CalledByIsDialogMessageOrDispatch==false.
  6947.     // Therefore, any calls to MsgSleep made by the new thread can't corrupt our caller's settings of
  6948.     // g.CalledByIsDialogMessageOrDispatch because in that case, our caller isn't MsgSleep's IsDialog/Dispatch.
  6949.     // As an added precaution against the complexity of these message issues (only one of several such scenarios
  6950.     // is described above), CalledByIsDialogMessageOrDispatch is put into the g-struct rather than being
  6951.     // a normal global.  That way, a thread's calls to MsgSleep can't interfere with the value of
  6952.     // CalledByIsDialogMessageOrDispatch for any threads beneath it.  Although this may technically be
  6953.     // unnecessary, it adds maintainability.
  6954.     LRESULT msg_reply;
  6955.     if (g_MsgMonitorCount // Count is checked here to avoid function-call overhead.
  6956.         && (!g.CalledByIsDialogMessageOrDispatch || g.CalledByIsDialogMessageOrDispatchMsg != iMsg) // v1.0.44.11: If called by IsDialog or Dispatch but they changed the message number, check if the script is monitoring that new number.
  6957.         && MsgMonitor(hWnd, iMsg, wParam, lParam, NULL, msg_reply))
  6958.         return msg_reply; // MsgMonitor has returned "true", indicating that this message should be omitted from further processing.
  6959.     g.CalledByIsDialogMessageOrDispatch = false;
  6960.     // Fixed for v1.0.40.01: The above line was added to resolve a case where our caller did make the value
  6961.     // true but the message it sent us results in a recursive call to us (such as when the user resizes a
  6962.     // window by dragging its borders: that apparently starts a loop in DefDlgProc that calls this
  6963.     // function recursively).  This fixes OnMessage(0x24, "WM_GETMINMAXINFO") and probably others.
  6964.     // Known limitation: If the above launched a thread but the thread didn't cause it turn return,
  6965.     // and iMsg is something like AHK_GUI_ACTION that will be reposted via PostMessage(), the monitor
  6966.     // will be launched again when MsgSleep is called in conjunction with the repost. Given the rarity
  6967.     // and the minimal consequences of this, no extra code (such as passing a new parameter to MsgSleep)
  6968.     // is added to handle this.
  6969.  
  6970.     GuiType *pgui;
  6971.     GuiControlType *pcontrol;
  6972.     GuiIndexType control_index;
  6973.     RECT rect;
  6974.     bool text_color_was_changed;
  6975.     char buf[1024];
  6976.  
  6977.     switch (iMsg)
  6978.     {
  6979.     // case WM_CREATE: --> Do nothing extra becuase DefDlgProc() appears to be sufficient.
  6980.  
  6981.     case WM_SIZE: // Listed first for performance.
  6982.         if (   !(pgui = GuiType::FindGui(hWnd))   )
  6983.             break; // Let default proc handle it.
  6984.         if (pgui->mStatusBarHwnd)
  6985.             // Send the msg even if the bar is hidden because the OS typically knows not to do extra drawing work for
  6986.             // hidden controls.  In addition, when the bar is shown again, it might be the wrong size if this isn't done.
  6987.             // Known/documented limitation: In spite of being in the right z-order position, any control that
  6988.             // overlaps the status bar might sometimes get drawn on top of it.
  6989.             SendMessage(pgui->mStatusBarHwnd, WM_SIZE, wParam, lParam); // It apparently ignores wParam and lParam, but just in case send it the actuals.
  6990.         // Note that SIZE_MAXSHOW/SIZE_MAXHIDE don't seem to ever be received under the conditions
  6991.         // described at MSDN, even if the window has WS_POPUP style.  Therefore, A_EventInfo will
  6992.         // probably never contain those values, and as a result they are not documented in the help file.
  6993.         if (pgui->mLabelForSize) // There is an event handler in the script.
  6994.             POST_AHK_GUI_ACTION(hWnd, LOWORD(wParam), GUI_EVENT_RESIZE, lParam); // LOWORD(wParam) just to be sure it fits in 16-bit, but SIZE_MAXIMIZED and the others all do.
  6995.             // MsgSleep() is not done because "case AHK_GUI_ACTION" in GuiWindowProc() takes care of it.
  6996.             // See its comments for why.
  6997.         return 0; // "If an application processes this message, it should return zero."
  6998.         // Testing shows that the window still resizes correctly (controls are revealed as the window
  6999.         // is expanded) even if the event isn't passed on to the default proc.
  7000.  
  7001.     case WM_GETMINMAXINFO: // Added for v1.0.44.13.
  7002.     {
  7003.         if (   !(pgui = GuiType::FindGui(hWnd))   )
  7004.             break; // Let default proc handle it.
  7005.         MINMAXINFO &mmi = *(LPMINMAXINFO)lParam;
  7006.         if (pgui->mMinWidth >= 0) // This check covers both COORD_UNSPECIFIED and COORD_CENTERED.
  7007.             mmi.ptMinTrackSize.x = pgui->mMinWidth;
  7008.         if (pgui->mMinHeight >= 0)
  7009.             mmi.ptMinTrackSize.y = pgui->mMinHeight;
  7010.         if (pgui->mMaxWidth >= 0)   // mmi.ptMaxSize.x/y aren't changed because it seems the OS
  7011.             mmi.ptMaxTrackSize.x = pgui->mMaxWidth; // automatically uses ptMaxTrackSize for them, at least when
  7012.         if (pgui->mMaxHeight >= 0)   // ptMaxTrackSize is smaller than the system's default for
  7013.             mmi.ptMaxTrackSize.y = pgui->mMaxHeight; // mmi.ptMaxSize.
  7014.         return 0; // "If an application processes this message, it should return zero."
  7015.     }
  7016.  
  7017.     case WM_COMMAND:
  7018.     {
  7019.         // First find which of the GUI windows is receiving this event:
  7020.         if (   !(pgui = GuiType::FindGui(hWnd))   )
  7021.             break; // No window (might be impossible since this function is for GUI windows, but seems best to let DefDlgProc handle it).
  7022.         int id = LOWORD(wParam);
  7023.         // For maintainability, this is checked first because "code" (the HIWORD) is sometimes or always 0,
  7024.         // which falsely indicates that the message is from a menu:
  7025.         if (id == IDCANCEL) // IDCANCEL is a special Control ID.  The user pressed esc.
  7026.         {
  7027.             // Known limitation:
  7028.             // Example:
  7029.             //Gui, Add, Text,, Gui1
  7030.             //Gui, Add, Text,, Gui2
  7031.             //Gui, Show, w333
  7032.             //GuiControl, Disable, Gui1
  7033.             //return
  7034.             //
  7035.             //GuiEscape:
  7036.             //MsgBox GuiEscape
  7037.             //return
  7038.             // It appears that in cases like the above, the OS doesn't send the WM_COMMAND+IDCANCEL message
  7039.             // to the program when you press Escape. Although it could be fixed by having the escape keystroke
  7040.             // unconditionally call the GuiEscape label, that might break existing features and scripts that
  7041.             // rely on escape's ability to perform other functions in a window. 
  7042.             // I'm not sure whether such functions exist and how many of them there are. Examples might include
  7043.             // using escape to close a menu, drop-list, or other pop-up attribute of a control inside the window.
  7044.             // Escape also cancels a user's drag-move of the window (restoring the window to its pre-drag location).
  7045.             // If pressing escape were to unconditionally call the GuiEscape label, features like these might be
  7046.             // broken.  So currently this behavior is documented in the help file as a known limitation.
  7047.             pgui->Escape();
  7048.             return 0; // Might be necessary to prevent auto-window-close.
  7049.             // Note: It is not necessary to check for IDOK because:
  7050.             // 1) If there is no default button, the IDOK message is ignored.
  7051.             // 2) If there is a default button, we should never receive IDOK because BM_SETSTYLE (sent earlier)
  7052.             // will have altered the message we receive to be the ID of the actual default button.
  7053.         }
  7054.         // Since above didn't return:
  7055.         if (id >= ID_USER_FIRST)
  7056.         {
  7057.             // Since all control id's are less than ID_USER_FIRST, this message is either
  7058.             // a user defined menu item ID or a bogus message due to it corresponding to
  7059.             // a non-existent menu item or a main/tray menu item (which should never be
  7060.             // received or processed here).
  7061.             HandleMenuItem(hWnd, id, pgui->mWindowIndex);
  7062.             return 0; // Indicate fully handled.
  7063.         }
  7064.         // Otherwise id should contain the ID of an actual control.  Validate that in case of bogus msg.
  7065.         // Perhaps because this is a DialogProc rather than a WindowProc, the following does not appear
  7066.         // to be true: MSDN: "The high-order word [of wParam] specifies the notification code if the message
  7067.         // is from a control. If the message is from an accelerator, [high order word] is 1. If the message
  7068.         // is from a menu, [high order word] is zero."
  7069.         GuiIndexType control_index = GUI_ID_TO_INDEX(id); // Convert from ID to array index. Relies on unsigned to flag as out-of-bounds.
  7070.         if (control_index < pgui->mControlCount // Relies on short-circuit boolean order.
  7071.             && pgui->mControl[control_index].hwnd == (HWND)lParam) // Handles match (this filters out bogus msgs).
  7072.             pgui->Event(control_index, HIWORD(wParam));
  7073.             // v1.0.35: And now pass it on to DefDlgProc() in case it needs to see certain types of messages.
  7074.         break;
  7075.     }
  7076.  
  7077.     case WM_SYSCOMMAND:
  7078.         if (wParam == SC_CLOSE)
  7079.         {
  7080.             if (   !(pgui = GuiType::FindGui(hWnd))   )
  7081.                 break; // Let DefDlgProc() handle it.
  7082.             pgui->Close();
  7083.             return 0;
  7084.         }
  7085.         break;
  7086.  
  7087.     case WM_NOTIFY:
  7088.     {
  7089.         if (   !(pgui = GuiType::FindGui(hWnd))   )
  7090.             break; // Let DefDlgProc() handle it.
  7091.  
  7092.         NMHDR &nmhdr = *(LPNMHDR)lParam;
  7093.         control_index = (GuiIndexType)GUI_ID_TO_INDEX(nmhdr.idFrom); // Convert from ID to array index.  Relies on unsigned to flag as out-of-bounds.
  7094.         if (control_index >= pgui->mControlCount)
  7095.             break;  // Invalid to us, but perhaps meaningful DefDlgProc(), so let it handle it.
  7096.         GuiControlType &control = pgui->mControl[control_index]; // For performance and convenience.
  7097.         if (control.hwnd != nmhdr.hwndFrom) // Handles match (this filters out bogus msgs).
  7098.             break;
  7099.  
  7100.         UINT event_info = NO_EVENT_INFO; // Set default, to be possibly overridden below.
  7101.         USHORT gui_event = '*'; // Something other than GUI_EVENT_NONE to flag events that don't get classified below. The special character helps debugging.
  7102.         bool ignore_unless_alt_submit = true; // Set default, which is set to "false" only for the most important and/or rarely occuring notifications (for script performance).
  7103.  
  7104.         switch (control.type)
  7105.         {
  7106.         /////////////////////
  7107.         // LISTVIEW WM_NOTIFY
  7108.         /////////////////////
  7109.         case GUI_CONTROL_LISTVIEW:
  7110.             bool is_actionable;
  7111.             is_actionable = true; // Set default.
  7112.  
  7113.             switch (nmhdr.code)
  7114.             {
  7115.             // MSDN: LVN_HOTTRACK: "Return zero to allow the list view to perform its normal track select processing."
  7116.             // Also, LVN_HOTTRACK is listed first for performance since it arrives far more often than any other notification.
  7117.             case LVN_HOTTRACK:  // v1.0.36.04: No longer an event because it occurs so often: Due to single-thread limit, it was decreasing the reliability of AltSubmit ListViews' receipt of other events such as "I", such as Toralf's Icon Viewer.
  7118.             case NM_CUSTOMDRAW: // Return CDRF_DODEFAULT (0).  Occurs for every redraw, such as mouse cursor sliding over control or window activation.
  7119.             case LVN_ITEMCHANGING: // Not yet supported (seems rarely needed), so always allow the change by returning 0 (FALSE).
  7120.             case LVN_INSERTITEM: // Any ways other than ListView_InsertItem() to insert items?
  7121.             case LVN_DELETEITEM: // Might be received for each individual (non-DeleteAll) deletion).
  7122.             case LVN_GETINFOTIPW: // v1.0.44: Received even without LVS_EX_INFOTIP?. In any case, there's currently no point
  7123.             case LVN_GETINFOTIPA: // in notifying the script because it would have no means of changing the tip (by altering the struct), except perhaps OnMessage.
  7124.                 return 0; // Return immediately to avoid calling Event() and DefDlgProc(). A return value of 0 is suitable for all of the above.
  7125.  
  7126.             case 0xFFFFFF4F: // Couldn't find these in commctrl.h anywhere. They seem to occur when control is first created and once for each row in the first set of added rows.
  7127.             case 0xFFFFFF5F:
  7128.             case 0xFFFFFF5D: // Probably something to do with incremental search since it seems to happen only when items are present and the user types a visible-character key.
  7129.                 is_actionable = false;
  7130.                 break; // Let default proc handle them since they might mean something to it.
  7131.  
  7132.             case LVN_ITEMCHANGED:
  7133.                 // This is received for selection/deselection, which means clicking a new item generates
  7134.                 // at least two of them (in practice, it generates between 1 and 3 but not sure why).
  7135.                 // It's also received for checking/unchecking an item.  Extending a selection via Shift-ArrowKey
  7136.                 // generates between 1 and 3 of them, perhaps at random?  Maybe all we can count on is that you
  7137.                 // get at least one when the selection has changed or a box is (un)checked.
  7138.                 if (control.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT) // Script asked for item-change notifications.
  7139.                 {
  7140.                     gui_event = 'I'; // Set default to be a plain I.
  7141.                     NMLISTVIEW &lv = *(LPNMLISTVIEW)lParam;
  7142.                     event_info = 1 + lv.iItem; // MSDN: If iItem is -1, the change has been applied to all items in the list view.
  7143.  
  7144.                     // Although the OS currently generates focus+select together, it sends de-focus and de-select
  7145.                     // separately.  However, since this behavior might vary in past/future OSes, it seems best to
  7146.                     // use a method that will work regardless of what combinations are possible.
  7147.                     UINT newly_changed =  lv.uNewState ^ lv.uOldState; // uChanged doesn't seem accurate: it's always 8?  So derive the "correct" value of which flags have actually changed.
  7148.                     UINT newly_on = newly_changed & lv.uNewState;
  7149.                     UINT newly_off = newly_changed & lv.uOldState;
  7150.                     if (newly_on & LVIS_FOCUSED)
  7151.                         gui_event |= AHK_LV_FOCUS;
  7152.                     else if (newly_off & LVIS_FOCUSED)
  7153.                         gui_event |= AHK_LV_DEFOCUS;
  7154.                     if (newly_on & LVIS_SELECTED)
  7155.                         gui_event |= AHK_LV_SELECT;
  7156.                     else if (newly_off & LVIS_SELECTED)
  7157.                         gui_event |= AHK_LV_DESELECT;
  7158.                     // The following are commented out for possible future use because currently, I think they
  7159.                     // don't happen at all (not for dropping of files anyway).  If dragging & dropping within
  7160.                     // a ListView or between two different ListViews ever becomes a built-in feature, this
  7161.                     // section (and its counterpart in the main event loop) can be re-enabled.
  7162.                     // In those very rare cases when a script needs LVIS_DROPHILITED, it can use OnMessage().
  7163.                     //if (newly_on & LVIS_DROPHILITED) // MSDN: LVIS_DROPHILITED means "the item is highlighted as a drag-and-drop target."
  7164.                     //    gui_event |= AHK_LV_DROPHILITE;
  7165.                     //else if (newly_off & LVIS_DROPHILITED)
  7166.                     //    gui_event |= AHK_LV_UNDROPHILITE;
  7167.  
  7168.                     // Below must occur only after all of the checks above:
  7169.                     if (newly_changed & LVIS_STATEIMAGEMASK) // State image changed.
  7170.                     {
  7171.                         if (lv.uOldState & LVIS_STATEIMAGEMASK) // Image is changing from a non-blank image to a different non-blank image.
  7172.                             // For simplicity, assume checkboxes are present rather than custom images.
  7173.                             // User can use OnMessage() to do custom handling in the rare event of having
  7174.                             // images other than checkboxes.
  7175.                             gui_event |= ((lv.uNewState & LVIS_STATEIMAGEMASK) == 0x1000) ? AHK_LV_UNCHECK : AHK_LV_CHECK; // The #1 image is "unchecked" and the #2 (or anything higher) is considered "checked".
  7176.                         else // State image changed from blank/none to some new image.  v1.0.46.10: Omit this event because it seems to do more harm than good in 99% of cases (especially since it typically only occurs when the script calls LV_Add/Insert).
  7177.                             if (gui_event == 'I') // But only omit the even if there are no other changes/reasons for it.
  7178.                                 is_actionable = false;
  7179.                     }
  7180.                 }
  7181.                 //else script isn't being notifid of item-changes, so leave everything uninitialized or at their
  7182.                 // defaults (it won't matter because further below, no event will be sent to the script).
  7183.                 break;
  7184.  
  7185.             case LVN_BEGINSCROLL: gui_event = 'S'; break;
  7186.             case LVN_ENDSCROLL: gui_event = 's'; break; // Lowercase to distinguish it.
  7187.             case LVN_MARQUEEBEGIN: gui_event = 'M'; break;
  7188.             case NM_RELEASEDCAPTURE: gui_event = 'C'; break;
  7189.             case NM_SETFOCUS: gui_event = 'F'; break;
  7190.             case NM_KILLFOCUS: gui_event = 'f'; break;  // Lowercase to distinguish it.
  7191.             //case NM_HOVER: gui_event = 'V'; break; // Spy++ indicates that NM_HOVER is never received.  Maybe a style has to be set to get it. Note: 'V' is used for Hover because 'H' is used for LVN_HOTTRACK.
  7192.             //case NM_RETURN (user has pressed the ENTER key): Apparently never received, probably because the parent window uses DefDlgProc() vs. DefWindowProc().
  7193.  
  7194.             case LVN_KEYDOWN:
  7195.                 // For simplicity and flexibility, it seems best to store the VK itself since it
  7196.                 // might not correspond to a visible character (such as a function key or modifier).
  7197.                 // This also helps to reduce code size since scripts will only rarely want to have
  7198.                 // key-down info.
  7199.                 gui_event = 'K';
  7200.                 event_info = ((LPNMLVKEYDOWN)lParam)->wVKey; // The one-based column number that was clicked.
  7201.                 if (event_info == VK_F2 && !(control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR)) // WantF2 is in effect.
  7202.                 {
  7203.                     int focused_index = ListView_GetNextItem(control.hwnd, -1, LVNI_FOCUSED);
  7204.                     if (focused_index != -1)
  7205.                         SendMessage(control.hwnd, LVM_EDITLABEL, focused_index, 0);  // Has no effect if the control is read-only.
  7206.                     // For flexibility, it seems to still notify the script of the F2 keystroke in case
  7207.                     // it wants to do extra things.  Testing shows that even if the script sends its own
  7208.                     // TVM_EDITLABEL message (such as pre-1.0.44 scripts that weren't updated to take into
  7209.                     // account WantF2), the label still goes into edit mode properly (though it does go out
  7210.                     // of edit mode then back in quickly due to the duplicate message).
  7211.                 }
  7212.                 break;
  7213.  
  7214.             // When alt-submit mode isn't in effect, it seems best to ignore all clicks except double-clicks, since
  7215.             // right-click should normally be handled via GuiContenxtMenu instead (to allow AppsKey to work, etc.);
  7216.             // and since left-clicks can be used to extend a selection (ctrl-click or shift-click), so are pretty
  7217.             // vague events that most scripts probably wouldn't have explicit handling for.  A script that needs
  7218.             // to know when the selection changes can turn on AltSubmit to catch a wide variety of ways the
  7219.             // selection can change, the most all-encompassing of which is probably LVN_ITEMCHANGED.
  7220.             case NM_CLICK:
  7221.                 // v1.0.36.03: For NM_CLICK/NM_RCLICK, it's somewhat debatable to set event_info when the
  7222.                 // ListView isn't single-select, but the usefulness seems to outweigh any confusion it might cause.
  7223.                 gui_event = GUI_EVENT_NORMAL;
  7224.                 event_info = 1 + ListView_GetNextItem(control.hwnd, -1, LVNI_FOCUSED); // Fetch manually for compatibility with Win95/NT lacking MSIE 3.0+.
  7225.                 break;
  7226.             case NM_RCLICK:
  7227.                 gui_event = GUI_EVENT_RCLK;
  7228.                 event_info = 1 + ListView_GetNextItem(control.hwnd, -1, LVNI_FOCUSED); // Fetch manually for compatibility with Win95/NT lacking MSIE 3.0+.
  7229.                 break;
  7230.             case NM_DBLCLK:
  7231.                 gui_event = GUI_EVENT_DBLCLK;
  7232.                 event_info = 1 + ListView_GetNextItem(control.hwnd, -1, LVNI_FOCUSED); // Fetch manually for compatibility with Win95/NT lacking MSIE 3.0+.
  7233.                 ignore_unless_alt_submit = false;
  7234.                 break;
  7235.             case NM_RDBLCLK:
  7236.                 gui_event = 'R'; // Rare, so just a simple mnemonic is stored (seems better than a digit).
  7237.                 event_info = 1 + ListView_GetNextItem(control.hwnd, -1, LVNI_FOCUSED); // Fetch manually for compatibility with Win95/NT lacking MSIE 3.0+.
  7238.                 ignore_unless_alt_submit = false;
  7239.                 break;
  7240.             case LVN_ITEMACTIVATE: // By default, this notification arrives when an item is double-clicked (depends on style).
  7241.                 gui_event = 'A';
  7242.                 event_info = 1 + ListView_GetNextItem(control.hwnd, -1, LVNI_FOCUSED); // Fetch manually for compatibility with Win95/NT lacking MSIE 3.0+.
  7243.                 break;
  7244.  
  7245.             case LVN_COLUMNCLICK:
  7246.             {
  7247.                 gui_event = GUI_EVENT_COLCLK;
  7248.                 NMLISTVIEW &lv = *(LPNMLISTVIEW)lParam;
  7249.                 event_info = 1 + lv.iSubItem; // The one-based column number that was clicked.
  7250.                 // The following must be done here rather than in Event() in case the control has no g-label:
  7251.                 if (!(control.union_lv_attrib->no_auto_sort)) // Automatic sorting is in effect.
  7252.                     GuiType::LV_Sort(control, lv.iSubItem, true); // -1 to convert column index back to zero-based.
  7253.                 ignore_unless_alt_submit = false;
  7254.                 break;
  7255.             }
  7256.  
  7257.             case LVN_BEGINLABELEDITW: // Received even for non-Unicode apps, at least on XP.  Even so, the text contained it the struct is apparently always ANSI vs. Unicode.
  7258.             case LVN_BEGINLABELEDITA: // Never received, at least not on XP?
  7259.                 gui_event = 'E';
  7260.                 event_info = 1 + ((NMLVDISPINFO *)lParam)->item.iItem;
  7261.                 // It seems best NOT to notify the script of this one except in AltSubmit mode because:
  7262.                 // 1) Script rarely cares about begin-edit, only end-edit.
  7263.                 // 2) Script would have to do case-insensitive comparison to distinguish between 'E' and 'e'.
  7264.                 break;
  7265.             case LVN_ENDLABELEDITW: // See comment above.
  7266.             case LVN_ENDLABELEDITA:
  7267.                 gui_event = 'e'; // Lowercase to distinguish it.
  7268.                 event_info = 1 + ((NMLVDISPINFO *)lParam)->item.iItem;
  7269.                 ignore_unless_alt_submit = false; // Seems best to default to notifying only after data may have been changed; plus it avoids the need for script to distinguish case of 'e' vs. 'E'.
  7270.                 break;
  7271.  
  7272.             // v1.0.44: Changed drag notifications to occur in non-AltSubmit mode due to how rare drags are.
  7273.             // This avoids the need for the script to turn on AltSubmit just for them.
  7274.             case LVN_BEGINDRAG: // Left-drag.
  7275.                 gui_event = 'D';
  7276.                 // v1.0.44: Testing shows that the following retrieves the row upon which the use clicked, which
  7277.                 // in a multi-select ListView isn't necessarily the same as the focused row (which was retrieved in
  7278.                 // previous versions).  However, due to obscurity and rarity, this is very unlikely to break any
  7279.                 // existing scripts and thus won't be documented as a change.
  7280.                 event_info = 1 + ((LPNMLISTVIEW)lParam)->iItem;
  7281.                 ignore_unless_alt_submit = false;
  7282.                 break;
  7283.             case LVN_BEGINRDRAG: // Right-drag.
  7284.                 gui_event = 'd'; // Lowercase to distinguish it.
  7285.                 event_info = 1 + ((LPNMLISTVIEW)lParam)->iItem; // See comment in previous "case".
  7286.                 ignore_unless_alt_submit = false;
  7287.                 break;
  7288.  
  7289.             case LVN_DELETEALLITEMS:
  7290.                 return TRUE; // For performance, tell it not to notify us as each individual item is deleted.
  7291.             } // switch(nmhdr.code).
  7292.  
  7293.             // Since above didn't return, make it an event.
  7294.             if (   is_actionable
  7295.                 && (!ignore_unless_alt_submit || (control.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT))   )
  7296.                 pgui->Event(control_index, nmhdr.code, gui_event, event_info);
  7297.  
  7298.             // After the event, explicitly return a special value for any notifications that absolutely
  7299.             // require it, and let default proc handle all the others.
  7300.             switch (nmhdr.code)
  7301.             {
  7302.             case LVN_ENDLABELEDITW: // Received even for non-Unicode apps, at least on XP.  Even so, the text contained it the struct is apparently always ANSI vs. Unicode.
  7303.             case LVN_ENDLABELEDITA: // Never received, at least not on XP?
  7304.                 // MSDN: "If the pszText member of the LVITEM structure is NULL, the return value is ignored."
  7305.                 // Therefore, returning TRUE to allow the edit should be the correct value in every case, at
  7306.                 // least until such time as the ability for a script to override individual edits is provided.
  7307.                 return TRUE; // Must return TRUE explicitly because apparently DefDlgProc() would return FALSE.
  7308.             }
  7309.             break; // Let default proc handle them all in case it does any extra processing.
  7310.  
  7311.         /////////////////////
  7312.         // TREEVIEW WM_NOTIFY
  7313.         /////////////////////
  7314.         case GUI_CONTROL_TREEVIEW:
  7315.             switch (nmhdr.code)
  7316.             {
  7317.             case NM_SETCURSOR:  // Received very often, every time the mouse moves while over the control.
  7318.             case NM_CUSTOMDRAW: // Return CDRF_DODEFAULT (0). Occurs for every redraw, such as mouse cursor sliding over control or window activation.
  7319.             case TVN_DELETEITEMW:
  7320.             case TVN_DELETEITEMA:
  7321.             // TVN_SELCHANGING, TVN_ITEMEXPANDING, and TVN_SINGLEEXPAND are not reported to the script as events
  7322.             // because there is currently no support for vetoing the selection-change or expansion; plus these
  7323.             // notifications each have an "-ED" counterpart notification that is reported to the script (even
  7324.             // TVN_SINGLEEXPAND is followed by a TVN_ITEMEXPANDED notification).
  7325.             case TVN_SELCHANGINGW:   // Received even for non-Unicode apps, at least on XP.
  7326.             case TVN_SELCHANGINGA:
  7327.             case TVN_ITEMEXPANDINGW: // Received even for non-Unicode apps, at least on XP.
  7328.             case TVN_ITEMEXPANDINGA:
  7329.             case TVN_SINGLEEXPAND: // Note that TVNRET_DEFAULT==0. This is received only when style contains TVS_SINGLEEXPAND.
  7330.             case TVN_GETINFOTIPA: // Received when TVS_INFOTIP is present. However, there's currently no point
  7331.             case TVN_GETINFOTIPW: // in notifying the script because it would have no means of changing the tip (by altering the struct), except perhaps OnMessage.
  7332.                 return 0; // Return immediately to avoid calling Event() and DefDlgProc(). A return value of 0 is suitable for all of the above.
  7333.  
  7334.             case TVN_SELCHANGEDW:
  7335.             case TVN_SELCHANGEDA:
  7336.                 // 'S' was chosen vs. 's' or 'C' because it seems easier to remember.  Known drawbacks:
  7337.                 // - Would have to use lowercase 's' for "TVN_SELCHANGING" in case it's ever wanted (though adding
  7338.                 // it directly would break existing scripts that rely on case insensitivity, so it would probably be
  7339.                 // better to choose an entirely different letter).
  7340.                 // - 'S' cannot be used for scrolling notifications in case TreeView ever adds them like ListViews.
  7341.                 gui_event = 'S';
  7342.                 // Having more than one item selected in a TreeView is fairly rare due to not being meaningful or
  7343.                 // supported by the control.  Therefore, performing a select-all on a TreeView by a script is
  7344.                 // likely to be uncommon, and thus the performance concern mentioned for expand-all above isn't
  7345.                 // as applicable.  For this reason and also because selecting an item TreeView is typically of
  7346.                 // high interest (since eacy item may often be a folder, in which case the script changes the
  7347.                 // contents in a corresponding ListView), it seems best to report these in non-alt-submit mode.
  7348.                 // On the other hand, if a script ever does some kind of automated traversal of the Tree, selecting
  7349.                 // each item one at a time (probably rare), this policy would reduce performance.
  7350.                 ignore_unless_alt_submit = false;
  7351.                 event_info = (UINT)(size_t)((LPNMTREEVIEW)lParam)->itemNew.hItem;
  7352.                 break;
  7353.  
  7354.             case TVN_ITEMEXPANDEDW: // Received even for non-Unicode apps, at least on XP.
  7355.             case TVN_ITEMEXPANDEDA:
  7356.                 // The "action" flag is a bitwise value that should always contain either TVE_COLLAPSE or
  7357.                 // TVE_EXPAND (testing shows that TVE_TOGGLE never occurs, as expected).
  7358.                 gui_event = (((LPNMTREEVIEW)lParam)->action & TVE_COLLAPSE) ? '-' : '+';
  7359.                 // It is especially important to store the HTREEITEM of this event for the TVS_SINGLEEXPAND style
  7360.                 // because an item that wasn't even clicked on is collapsed to allow the new one to expand.
  7361.                 // There might be no way to find out which item collapsed other than taking note of it here.
  7362.                 event_info = (UINT)(size_t)((LPNMTREEVIEW)lParam)->itemNew.hItem;
  7363.                 break;
  7364.  
  7365.             case TVN_BEGINLABELEDITW: // Received even for non-Unicode apps, at least on XP.  Even so, the text contained it the struct is apparently always ANSI vs. Unicode.
  7366.             case TVN_BEGINLABELEDITA: // Never received, at least not on XP?
  7367.                 gui_event = 'E';
  7368.                 event_info = (UINT)(size_t)((LPNMTVDISPINFO)lParam)->item.hItem;
  7369.                 GuiType::sTreeWithEditInProgress = control.hwnd;
  7370.                 // It seems best NOT to notify the script of this one except in AltSubmit mode because:
  7371.                 // 1) Script rarely cares about begin-edit, only end-edit.
  7372.                 // 2) Script would have to do case-insensitive comparison to distinguish between 'E' and 'e'.
  7373.                 break;
  7374.             case TVN_ENDLABELEDITW: // See comment above.
  7375.             case TVN_ENDLABELEDITA:
  7376.                 gui_event = 'e'; // Lowercase to distinguish it.
  7377.                 event_info = (UINT)(size_t)((LPNMTVDISPINFO)lParam)->item.hItem;
  7378.                 ignore_unless_alt_submit = false; // Seems best to default to notifying only after data may have been changed; plus it avoids the need for script to distinguish case of 'e' vs. 'E'.
  7379.                 GuiType::sTreeWithEditInProgress = NULL;
  7380.                 break;
  7381.  
  7382.             case TVN_BEGINDRAGW: // Received even for non-Unicode apps, at least on XP.  Even so, the text contained it the struct is apparently always ANSI vs. Unicode.
  7383.             case TVN_BEGINDRAGA: // Never received, at least not on XP?
  7384.                 gui_event = 'D';  // Left-drag.
  7385.                 event_info = (UINT)(size_t)((LPNMTREEVIEW)lParam)->itemNew.hItem;
  7386.                 ignore_unless_alt_submit = false; // Due to how rare drags are, it seems best to report them so that AltSubmit mode doesn't have to be turned on just for them.
  7387.                 break;
  7388.             case TVN_BEGINRDRAGW: // Same comments left-drag above.
  7389.             case TVN_BEGINRDRAGA: //
  7390.                 gui_event = 'd';  // Right-drag. Lowercase to distinguish it.
  7391.                 event_info = (UINT)(size_t)((LPNMTREEVIEW)lParam)->itemNew.hItem;
  7392.                 ignore_unless_alt_submit = false; // Same comment as left-drag above.
  7393.                 break;
  7394.  
  7395.             // Since a left-click is just one method of changing selection (keyboard navigation is another),
  7396.             // it seems desirable for performance not to report such clicks except in alt-submit mode.
  7397.             // Similarly, right-clicks are reported only in alt-submit mode because GuiContextMenu should be used
  7398.             // to catch right-clicks (due to its additional handling for the AppsKey).
  7399.             case NM_CLICK:
  7400.             case NM_RCLICK:
  7401.             case NM_DBLCLK:
  7402.             case NM_RDBLCLK:
  7403.                 switch(nmhdr.code)
  7404.                 {
  7405.                 case NM_CLICK: gui_event = GUI_EVENT_NORMAL; break;
  7406.                 case NM_RCLICK: gui_event = GUI_EVENT_RCLK; break;
  7407.                 case NM_DBLCLK: gui_event = GUI_EVENT_DBLCLK; ignore_unless_alt_submit = false; break;
  7408.                 case NM_RDBLCLK: gui_event = 'R'; ignore_unless_alt_submit = false; break; // Rare, so just a simple mnemonic is stored (seems better than a digit).
  7409.                 // Above: It's a known bug in Windows that NM_RDBLCLK is never actually generated by a TreeView
  7410.                 // (though it is for other controls such as ListView). But in case that bug is fixed in future
  7411.                 // patches or OSes, it seems best to handle the event (though it's currently undocumented for simplicity).
  7412.                 }
  7413.                 // Since testing shows that none of the NMHDR members contains the HTREEITEM, must use
  7414.                 // another method to discover it for the various mouse-click events.
  7415.                 TVHITTESTINFO ht;
  7416.                 // GetMessagePos() is used because it should be more accurate than GetCursorPos() in case a
  7417.                 // the message was in the queue a long time.  There is some concern due to GetMessagePos() being
  7418.                 // documented to be valid only for GetMessage(): there's no certainty that all message pumps
  7419.                 // (such as that of MsgBox) use GetMessage vs. PeekMessage, but its hard to imagine that
  7420.                 // GetMessagePos() dosen't work for PeekMessage().  In any case, all message pumps by built-in
  7421.                 // OS dialogs like MsgBox probably use GetMessage().  There's another concern: that this WM_NOTIFY
  7422.                 // msg was sent (vs. posted) from our own thread somehow, in which case it never got queued so
  7423.                 // GetMessagePos() might yield an inaccurate value.  But until that is proven to be an actual
  7424.                 // problem, it seems best to do it the "correct" way.
  7425.                 DWORD pos;
  7426.                 pos = GetMessagePos();
  7427.                 ht.pt.x = LOWORD(pos);
  7428.                 ht.pt.y = HIWORD(pos);
  7429.                 ScreenToClient(control.hwnd, &ht.pt);
  7430.                 event_info = (DWORD)(size_t)TreeView_HitTest(control.hwnd, &ht);
  7431.                 break;
  7432.  
  7433.             case NM_SETFOCUS: gui_event = 'F'; break;
  7434.             case NM_KILLFOCUS: gui_event = 'f'; break; // Lowercase to distinguish it.
  7435.             //case NM_RETURN (user has pressed the ENTER key): Apparently never received, probably because the parent window uses DefDlgProc() vs. DefWindowProc().
  7436.  
  7437.             case TVN_KEYDOWN:
  7438.                 // For simplicity and flexibility, it seems best to store the VK itself since it
  7439.                 // might not correspond to a visible character (such as a function key or modifier).
  7440.                 // This also helps to reduce code size since scripts will only rarely want to have
  7441.                 // key-down info.
  7442.                 gui_event = 'K';
  7443.                 event_info = ((LPNMTVKEYDOWN)lParam)->wVKey; // The one-based column number that was clicked.
  7444.                 if (event_info == VK_F2 && !(control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR)) // WantF2 is in effect.
  7445.                 {
  7446.                     HTREEITEM hitem;
  7447.                     if (hitem = TreeView_GetSelection(control.hwnd))
  7448.                         SendMessage(control.hwnd, TVM_EDITLABEL, 0, (LPARAM)hitem); // Has no effect if the control is read-only.
  7449.                     // For flexibility and consistency with ListView behavior, it seems to still notify the
  7450.                     // script of the F2 keystroke in case it wants to do extra things.
  7451.                 }
  7452.                 break;
  7453.             } // switch(nmhdr.code).
  7454.  
  7455.             // Since above didn't return, make it an event.
  7456.             if (!ignore_unless_alt_submit || (control.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT))
  7457.                 pgui->Event(control_index, nmhdr.code, gui_event, event_info);
  7458.  
  7459.             // After the event, explicitly return a special value for any notifications that absolutely
  7460.             // require it, and let default proc handle all the others.
  7461.             switch (nmhdr.code)
  7462.             {
  7463.             case TVN_ENDLABELEDITW: // Received even for non-Unicode apps, at least on XP.  Even so, the text contained it the struct is apparently always ANSI vs. Unicode.
  7464.             case TVN_ENDLABELEDITA: // Never received, at least not on XP?
  7465.                 // MSDN: "If the pszText member is NULL, the return value is ignored."
  7466.                 // Therefore, returning TRUE to allow the edit should be the correct value in every case, at
  7467.                 // least until such time as the ability for a script to override individual edits is provided.
  7468.                 return TRUE; // Must return TRUE explicitly because apparently DefDlgProc() would return FALSE.
  7469.             }
  7470.             break; // Let default proc handle them all in case it does any extra processing.
  7471.  
  7472.         //////////////////////
  7473.         // OTHER CONTROL TYPES
  7474.         //////////////////////
  7475.         case GUI_CONTROL_DATETIME: // NMDATETIMECHANGE struct contains an NMHDR as it's first member.
  7476.             if (nmhdr.code == DTN_DATETIMECHANGE)
  7477.             {
  7478.                 // Although the DTN_DATETIMECHANGE notification struct contains the control's current date/time,
  7479.                 // it simplifies the code to fetch it again (performance is probably good since the control
  7480.                 // almost certainly just passes back a pointer to its self-maintained struct).
  7481.                 if (control.output_var) // Above already confirmed it has a jump_to_label (or at least an implicit cancel).
  7482.                     pgui->ControlGetContents(*control.output_var, control);
  7483.                 // Both MonthCal's year spinner (when year is clicked on) and DateTime's drop-down calendar
  7484.                 // seem to start a new message pump.  This is one of the reason things were redesigned to
  7485.                 // avoid doing a MsgSleep(-1) after posting AHK_GUI_ACTION at the bottom of Event().
  7486.                 // See its comments for details.
  7487.                 pgui->Event(control_index, nmhdr.code, GUI_EVENT_NORMAL);
  7488.             }
  7489.             //else ignore all others here, for performance.
  7490.             return 0; // 0 is appropriate for all DATETIME notifications.
  7491.  
  7492.         case GUI_CONTROL_MONTHCAL:
  7493.             // Although the NMSELCHANGE notification struct contains the control's current date/time,
  7494.             // it simplifies the code to fetch it again (performance is probably good since the control
  7495.             // almost certainly just passes back a pointer to its self-maintained structs).
  7496.             // v1.0.35.09 adds more useful g-label in AltSubmit mode by passing all events and indicating
  7497.             // which ones they are.  This was done because the old way of launching the g-label only for
  7498.             // MCN_SELECT wasn't very useful because the label was not launched when the user scrolled
  7499.             // to a new month via the calendar's arrow buttons, even though doing so sets a new date inside
  7500.             // the control.  The label was also not launched when a new year or month was chosen by clicking
  7501.             // directly on the month or year.
  7502.             switch (nmhdr.code)
  7503.             {
  7504.             case MCN_SELCHANGE:
  7505.                 gui_event = GUI_EVENT_NORMAL;
  7506.                 break;
  7507.             case MCN_SELECT:
  7508.             case NM_RELEASEDCAPTURE:
  7509.                 if (!(control.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT))
  7510.                     return 0; // 0 is appropriate for all MONTHCAL notifications.
  7511.                 // Signal it to store the digit '1' or '2'.  Unlike slider -- which uses 0 so that the numbers
  7512.                 // match those defined in the API -- avoiding 0 seems best for this one since zero is equivalent
  7513.                 // and no conformance with API is desired.
  7514.                 gui_event = 49 + (nmhdr.code == NM_RELEASEDCAPTURE);
  7515.                 break;
  7516.             default: // MCN_GETDAYSTATE or any others that are specifically undesired.
  7517.                 return 0; // 0 is appropriate for all MONTHCAL notifications.
  7518.             }
  7519.             // Since the above did a "break" vs. "return", the label will be launched.
  7520.             // Update output-var if that is called for:
  7521.             if (control.output_var) // Above already confirmed it has a jump_to_label (or at least an implicit cancel).
  7522.                 pgui->ControlGetContents(*control.output_var, control);
  7523.             pgui->Event(control_index, nmhdr.code, gui_event);
  7524.             return 0; // 0 is appropriate for all MONTHCAL notifications.
  7525.  
  7526.         case GUI_CONTROL_UPDOWN:
  7527.             // Now it just returns 0 for simplicity, but the following are kept for reference.
  7528.             //if (nmhdr.code == UDN_DELTAPOS) // No script control/intervention over this currently.
  7529.             //    return 0; // MSDN: "Return nonzero to prevent the change in the control's position, or zero to allow the change."
  7530.             // Strangely, NM_RELEASEDCAPTURE never seems to be received.  In fact, nothing other than
  7531.             // UDN_DELTAPOS is ever received.  Therefore, WM_VSCROLL/WM_HSCROLL are relied upon instead.
  7532.             return 0;  // 0 is appropriate for all notifications in this case (no need to let DefDlgProc handle it).
  7533.  
  7534.         case GUI_CONTROL_TAB:
  7535.             if (nmhdr.code == TCN_SELCHANGE)
  7536.             {
  7537.                 // For code reduction and simplicity (and due to rarity of script needing it), A_EventInfo
  7538.                 // is not set to the newly selected tab name (or number in the case of AltSubmit).
  7539.                 pgui->ControlUpdateCurrentTab(control, true);
  7540.                 pgui->Event(control_index, nmhdr.code, GUI_EVENT_NORMAL);
  7541.             }
  7542.             else if (nmhdr.code == TCN_SELCHANGING)
  7543.                 if (control.output_var && control.jump_to_label) // Set the variable's contents, for use when the corresponding TCN_SELCHANGE comes in to launch the label after this.
  7544.                     pgui->ControlGetContents(*control.output_var, control);
  7545.             return 0; // 0 is appropriate for all TAB notifications.
  7546.  
  7547.         case GUI_CONTROL_STATUSBAR:
  7548.             if (!(control.jump_to_label || (control.attrib & GUI_CONTROL_ATTRIB_IMPLICIT_CANCEL)))// These is checked to avoid returning TRUE below, and also for performance.
  7549.                 break; // Let default proc handle it.
  7550.             switch(nmhdr.code)
  7551.             {
  7552.             case NM_CLICK:
  7553.             case NM_RCLICK:
  7554.             case NM_DBLCLK:
  7555.             case NM_RDBLCLK:
  7556.                 switch(nmhdr.code)
  7557.                 {
  7558.                 case NM_CLICK:  gui_event = GUI_EVENT_NORMAL; break;
  7559.                 case NM_RCLICK: gui_event = GUI_EVENT_RCLK;   break;
  7560.                 case NM_DBLCLK: gui_event = GUI_EVENT_DBLCLK; break;
  7561.                 case NM_RDBLCLK: gui_event = 'R';             break; // Rare, so just a simple mnemonic is stored (seems better than a digit).
  7562.                 }
  7563.                 // Pass the one-based part number that was clicked.  If the user clicked near the size grip,
  7564.                 // apparently a large number is returned (at least on some OSes).
  7565.                 pgui->Event(control_index, nmhdr.code, gui_event, (UINT)((LPNMMOUSE)lParam)->dwItemSpec + 1);
  7566.                 // It seems traditional by most apps not to display a context menu when the status bar
  7567.                 // is right-clicked (or a different-than-normal context menu).  In addition, AppsKey never
  7568.                 // applies to the status bar since it can't have focus.  For these reasons, it seems best
  7569.                 // to return TRUE below, the only known effect of which is to prevent generation of the
  7570.                 // WM_CONTEXTMENU notification.  This avoids calling both GuiContextMenu and the g-label when
  7571.                 // the bar has its own g-label (for performance and because most script's would probably want
  7572.                 // the simplification of not having to check in GuiContextMenu whether A_GuiControl==TheBar.
  7573.                 return TRUE; // See above.
  7574.             //default: Let default proc handle other notifications.
  7575.             } // switch(nmhdr.code)
  7576.         } // switch(control.type) within case WM_NOTIFY.
  7577.  
  7578.         break; // outermost switch()
  7579.     }
  7580.  
  7581.     case WM_VSCROLL: // These two should only be received for sliders and up-downs.
  7582.     case WM_HSCROLL:
  7583.         if (   !(pgui = GuiType::FindGui(hWnd))   )
  7584.             break; // Let default proc handle it.
  7585.         pgui->Event(GUI_HWND_TO_INDEX((HWND)lParam), LOWORD(wParam));
  7586.         return 0; // "If an application processes this message, it should return zero."
  7587.     
  7588.     case WM_ERASEBKGND:
  7589.         if (   !(pgui = GuiType::FindGui(hWnd))   )
  7590.             break; // Let DefDlgProc() handle it.
  7591.         if (!pgui->mBackgroundBrushWin) // Let default proc handle it.
  7592.             break;
  7593.         // Can't use SetBkColor(), need an real brush to fill it.
  7594.         GetClipBox((HDC)wParam, &rect);
  7595.         FillRect((HDC)wParam, &rect, pgui->mBackgroundBrushWin);
  7596.         return 1; // "An application should return nonzero if it erases the background."
  7597.  
  7598.     // The below seems to be the equivalent of the above (but MSDN indicates it will only work
  7599.     // if there is no WM_ERASEBKGND handler).  Although it might perform a little better,
  7600.     // the above is kept in effect to avoid introducing problems without a good reason:
  7601.     //case WM_CTLCOLORDLG:
  7602.     //    if (   !(pgui = GuiType::FindGui(hWnd))   )
  7603.     //        break; // Let DefDlgProc() handle it.
  7604.     //    if (!pgui->mBackgroundBrushWin) // Let default proc handle it.
  7605.     //        break;
  7606.     //    SetBkColor((HDC)wParam, pgui->mBackgroundColorWin);
  7607.     //    return (LRESULT)pgui->mBackgroundBrushWin;
  7608.  
  7609.     // It seems that scrollbars belong to controls (such as Edit and ListBox) do not send us
  7610.     // WM_CTLCOLORSCROLLBAR (unlike the static messages we receive for radio and checkbox).
  7611.     // Therefore, this section is commented out since it has no effect (it might be useful
  7612.     // if a control's class window-proc is ever overridden with a new proc):
  7613.     //case WM_CTLCOLORSCROLLBAR:
  7614.     //    if (   !(pgui = GuiType::FindGui(hWnd))   )
  7615.     //        break;
  7616.     //    if (pgui->mBackgroundBrushWin)
  7617.     //    {
  7618.     //        // Since we're processing this msg rather than passing it on to the default proc, must set
  7619.     //        // background color unconditionally, otherwise plain white will likely be used:
  7620.     //        SetTextColor((HDC)wParam, pgui->mBackgroundColorWin);
  7621.     //        SetBkColor((HDC)wParam, pgui->mBackgroundColorWin);
  7622.     //        // Always return a real HBRUSH so that Windows knows we altered the HDC for it to use:
  7623.     //        return (LRESULT)pgui->mBackgroundBrushWin;
  7624.     //    }
  7625.     //    break;
  7626.  
  7627.     case WM_CTLCOLORSTATIC:
  7628.     case WM_CTLCOLORLISTBOX:
  7629.     case WM_CTLCOLOREDIT:
  7630.         // MSDN: Buttons with the BS_PUSHBUTTON, BS_DEFPUSHBUTTON, or BS_PUSHLIKE styles do not use the
  7631.         // returned brush. Buttons with these styles are always drawn with the default system colors.
  7632.         // This is because "drawing push buttons requires several different brushes-face, highlight and
  7633.         // shadow". In short, to provide a custom appearance for push buttons, use an owner-drawn button.
  7634.         // Thus, WM_CTLCOLORBTN not handled here because it doesn't seem to have any effect on the
  7635.         // types of buttons used so far.  This has been confirmed: Even when a theme is in effect,
  7636.         // checkboxes, radios, and groupboxes do not receive WM_CTLCOLORBTN, but they do receive
  7637.         // WM_CTLCOLORSTATIC.
  7638.         if (   !(pgui = GuiType::FindGui(hWnd))   )
  7639.             break;
  7640.         if (   !(pcontrol = pgui->FindControl((HWND)lParam))   )
  7641.             break;
  7642.         if (pcontrol->type == GUI_CONTROL_COMBOBOX) // But GUI_CONTROL_DROPDOWNLIST partially works.
  7643.             // Setting the colors of combo boxes won't work without overriding the ComboBox window proc,
  7644.             // which introduces complexities because there is no knowing exactly what the default
  7645.             // window proc of a ComboBox really does in all OSes and under all visual themes.
  7646.             // Overriding it is likely to cause problems, or at the very least require testing across
  7647.             // various OSes and themes (XP vs. classic).
  7648.             break;
  7649.         if (text_color_was_changed = (pcontrol->type != GUI_CONTROL_PIC && pcontrol->union_color != CLR_DEFAULT))
  7650.             SetTextColor((HDC)wParam, pcontrol->union_color);
  7651.  
  7652.         if (pcontrol->attrib & GUI_CONTROL_ATTRIB_BACKGROUND_TRANS)
  7653.         {
  7654.             switch (pcontrol->type)
  7655.             {
  7656.             case GUI_CONTROL_CHECKBOX: // Checkbox and radios with trans background have problems with
  7657.             case GUI_CONTROL_RADIO:    // their focus rects being drawn incorrectly.
  7658.             case GUI_CONTROL_LISTBOX:  // ListBox and Edit are also a problem, at least under some theme settings.
  7659.             case GUI_CONTROL_EDIT:
  7660.             case GUI_CONTROL_SLIDER:   // Slider is a problem under both classic and XP themes.
  7661.                 break;  // Ignore the TRANS setting for the above control types.
  7662.             // Types not included above because they support transparent background or because the attempt
  7663.             // to make the background transparent has no effect:
  7664.             //case GUI_CONTROL_TEXT:         Supported via WM_CTLCOLORSTATIC
  7665.             //case GUI_CONTROL_PIC:          Supported via WM_CTLCOLORSTATIC
  7666.             //case GUI_CONTROL_GROUPBOX:     Supported via WM_CTLCOLORSTATIC
  7667.             //case GUI_CONTROL_BUTTON:       Can't reach this point because WM_CTLCOLORBTN is not handled above.
  7668.             //case GUI_CONTROL_DROPDOWNLIST: Can't reach this point because WM_CTLCOLORxxx is never received for it.
  7669.             //case GUI_CONTROL_COMBOBOX:     I believe WM_CTLCOLOREDIT is not received for it.
  7670.             //case GUI_CONTROL_LISTVIEW:     Can't reach this point because WM_CTLCOLORxxx is never received for it.
  7671.             //case GUI_CONTROL_TREEVIEW:     Same (verified).
  7672.             //case GUI_CONTROL_PROGRESS:     Same (verified).
  7673.             //case GUI_CONTROL_UPDOWN:       Same (verified).
  7674.             //case GUI_CONTROL_DATETIME:     Same (verified).
  7675.             //case GUI_CONTROL_MONTHCAL:     Same (verified).
  7676.             //case GUI_CONTROL_HOTKEY:       Same (verified).
  7677.             //case GUI_CONTROL_TAB:          Same.
  7678.             //case GUI_CONTROL_STATUSBAR:    Its text fields (parts) are its children, not ours, so its window proc probably receives WM_CTLCOLORSTATIC, not ours.
  7679.             default:
  7680.                 SetBkMode((HDC)wParam, TRANSPARENT);
  7681.                 return (LRESULT)GetStockObject(NULL_BRUSH);
  7682.             }
  7683.             //else ignore the TRANS setting, since it causes the ListBox (at least in Classic theme)
  7684.             // to appear to be multi-select even though it isn't.  And it causes Edit to have a
  7685.             // black background.
  7686.         }
  7687.         if (pcontrol->attrib & GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT) // i.e. TRANS (above) takes precedence over this.
  7688.         {
  7689.             if (!text_color_was_changed) // No need to return a brush since no changes are needed.  Let def. proc. handle it.
  7690.                 break;
  7691.             if (iMsg == WM_CTLCOLORSTATIC)
  7692.             {
  7693.                 SetBkColor((HDC)wParam, GetSysColor(COLOR_BTNFACE)); // Use default window color for static controls.
  7694.                 return (LRESULT)GetSysColorBrush(COLOR_BTNFACE);
  7695.             }
  7696.             else
  7697.             {
  7698.                 SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW)); // Use default control-bkgnd color for others.
  7699.                 return (LRESULT)GetSysColorBrush(COLOR_WINDOW);
  7700.             }
  7701.         }
  7702.  
  7703.         if (iMsg == WM_CTLCOLORSTATIC)
  7704.         {
  7705.             // If this static control both belongs to a tab control and is within its physical boundaries,
  7706.             // match its background to the tab control's.  This is only necessary if the tab control has
  7707.             // the GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT property, since otherwise its background would
  7708.             // be the same as the window's:
  7709.             bool override_to_default_color = pgui->ControlOverrideBkColor(*pcontrol);
  7710.             if (pgui->mBackgroundBrushWin && !override_to_default_color)
  7711.             {
  7712.                 // Since we're processing this msg rather than passing it on to the default proc, must set
  7713.                 // background color unconditionally, otherwise plain white will likely be used:
  7714.                 SetBkColor((HDC)wParam, pgui->mBackgroundColorWin);
  7715.                 // Always return a real HBRUSH so that Windows knows we altered the HDC for it to use:
  7716.                 return (LRESULT)pgui->mBackgroundBrushWin;
  7717.             }
  7718.             // else continue on through so that brush can be returned if text_color_was_changed == true.
  7719.         }
  7720.         else // WM_CTLCOLORLISTBOX or WM_CTLCOLOREDIT: The interior of a non-static control.  Use the control background color (if there is one).
  7721.         {
  7722.             if (pgui->mBackgroundBrushCtl)
  7723.             {
  7724.                 SetBkColor((HDC)wParam, pgui->mBackgroundColorCtl);
  7725.                 // Always return a real HBRUSH so that Windows knows we altered the HDC for it to use:
  7726.                 return (LRESULT)pgui->mBackgroundBrushCtl;
  7727.             }
  7728.         }
  7729.         // Since above didn't return a custom HBRUSH, we must return one here -- rather than letting the
  7730.         // default proc handle this message -- if the color of the text itself was changed.  This is so
  7731.         // that the OS will know that the DC has been altered:
  7732.         if (text_color_was_changed)
  7733.         {
  7734.             // Whenever the default proc won't be handling this message, the background color must be set
  7735.             // explicitly if something other than plain white is needed.  This must be done even for
  7736.             // non-static controls because otherwise the area doesn't get filled correctly:
  7737.             if (iMsg == WM_CTLCOLORSTATIC)
  7738.             {
  7739.                 // COLOR_BTNFACE is hard-coded because here because it is also the hard-coded background
  7740.                 // color of the GUI window class:
  7741.                 SetBkColor((HDC)wParam, GetSysColor(COLOR_BTNFACE));
  7742.                 return (LRESULT)GetSysColorBrush(COLOR_BTNFACE);
  7743.             }
  7744.             else
  7745.             {
  7746.                 // I'm pretty sure that COLOR_WINDOW is the color used by default for the background of
  7747.                 // all standard controls, such as ListBox, ComboBox, Edit, etc.  Although it's usually
  7748.                 // white, it can be different depending on theme/appearance settings:
  7749.                 SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW));
  7750.                 return (LRESULT)GetSysColorBrush(COLOR_WINDOW);
  7751.             }
  7752.         }
  7753.         //else since no colors were changed, let default proc handle it.
  7754.         break;
  7755.  
  7756.     case WM_DRAWITEM:
  7757.     {
  7758.         // WM_DRAWITEM msg is never received if there are no GUI windows containing a tab
  7759.         // control with custom tab colors.  The TCS_OWNERDRAWFIXED style is what causes
  7760.         // this message to be received.
  7761.         if (   !(pgui = GuiType::FindGui(hWnd))   )
  7762.             break;
  7763.         LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
  7764.         control_index = (GuiIndexType)GUI_ID_TO_INDEX(lpdis->CtlID); // Convert from ID to array index. Relies on unsigned to flag as out-of-bounds.
  7765.         if (control_index >= pgui->mControlCount // Relies on short-circuit eval order.
  7766.             || pgui->mControl[control_index].hwnd != lpdis->hwndItem  // Handles do not match (this filters out bogus msgs).
  7767.             || pgui->mControl[control_index].type != GUI_CONTROL_TAB) // In case this msg can be received for other types.
  7768.             break;
  7769.         GuiControlType &control = pgui->mControl[control_index]; // For performance & convenience.
  7770.         if (pgui->mBackgroundBrushWin && !(control.attrib & GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT))
  7771.         {
  7772.             FillRect(lpdis->hDC, &lpdis->rcItem, pgui->mBackgroundBrushWin); // Fill the tab itself.
  7773.             SetBkColor(lpdis->hDC, pgui->mBackgroundColorWin); // Set the text's background color.
  7774.         }
  7775.         else // Must do this anyway, otherwise there is an unwanted thin white line and possibly other problems.
  7776.             FillRect(lpdis->hDC, &lpdis->rcItem, (HBRUSH)(size_t)GetClassLong(control.hwnd, GCL_HBRBACKGROUND));
  7777.         // else leave background colors to default, in the case where only the text itself has a custom color.
  7778.         // Get the stored name/caption of this tab:
  7779.         TCITEM tci;
  7780.         tci.mask = TCIF_TEXT;
  7781.         tci.pszText = buf;
  7782.         tci.cchTextMax = sizeof(buf) - 1; // MSDN example uses -1.
  7783.         // Set text color if needed:
  7784.         COLORREF prev_color = CLR_INVALID;
  7785.         if (control.union_color != CLR_DEFAULT)
  7786.             prev_color = SetTextColor(lpdis->hDC, control.union_color);
  7787.         // Draw the text.  Note that rcItem contains the dimensions of a tab that has already been sized
  7788.         // to handle the amount of text in the tab at the specified WM_SETFONT font size, which makes
  7789.         // this much easier.
  7790.         if (TabCtrl_GetItem(lpdis->hwndItem, lpdis->itemID, &tci))
  7791.         {
  7792.             // The text is centered horizontally and vertically because that seems to be how the
  7793.             // control acts without the TCS_OWNERDRAWFIXED style.  DT_NOPREFIX is not specified
  7794.             // because that is not how the control acts without the TCS_OWNERDRAWFIXED style
  7795.             // (ampersands do cause underlined letters, even though they currently have no effect).
  7796.             if (TabCtrl_GetCurSel(control.hwnd) != lpdis->itemID)
  7797.                 lpdis->rcItem.top += 3; // For some reason, the non-current tabs' rects are a little off.
  7798.             DrawText(lpdis->hDC, tci.pszText, (int)strlen(tci.pszText), &lpdis->rcItem
  7799.                 , DT_CENTER|DT_VCENTER|DT_SINGLELINE); // DT_VCENTER requires DT_SINGLELINE.
  7800.             // Cruder method, probably not always accurate depending on theme/display-settings/etc.:
  7801.             //TextOut(lpdis->hDC, lpdis->rcItem.left + 5, lpdis->rcItem.top + 3, tci.pszText, (int)strlen(tci.pszText));
  7802.         }
  7803.         if (prev_color != CLR_INVALID) // Put the previous color back into effect for this DC.
  7804.             SetTextColor(lpdis->hDC, prev_color);
  7805.         break;
  7806.     }
  7807.  
  7808.     case WM_CONTEXTMENU:
  7809.         if ((pgui = GuiType::FindGui(hWnd)) && pgui->mLabelForContextMenu)
  7810.         {
  7811.             HWND clicked_hwnd = (HWND)wParam;
  7812.             bool from_keyboard; // Whether Context Menu was generated from keyboard (AppsKey or Shift-F10).
  7813.             if (   !(from_keyboard = (lParam == 0xFFFFFFFF))   ) // Mouse click vs. keyboard event.
  7814.             {
  7815.                 // If the click occurred above the client area, assume it was in title/menu bar or border.
  7816.                 // Let default proc handle it.
  7817.                 point_and_hwnd_type pah = {0};
  7818.                 pah.pt.x = LOWORD(lParam);
  7819.                 pah.pt.y = HIWORD(lParam);
  7820.                 POINT client_pt = pah.pt;
  7821.                 if (!ScreenToClient(hWnd, &client_pt) || client_pt.y < 0)
  7822.                     break; // Allows default proc to display standard system context menu for title bar.
  7823.                 // v1.0.38.01: Recognize clicks on pictures and text controls as occuring in that control
  7824.                 // (via A_GuiControl) rather than generically in the window:
  7825.                 if (clicked_hwnd == pgui->mHwnd)
  7826.                 {
  7827.                     // v1.0.40.01: Rather than doing "ChildWindowFromPoint(clicked_hwnd, client_pt)" -- which fails to
  7828.                     // detect text and picture controls (and perhaps others) when they're inside GroupBoxes and
  7829.                     // Tab controls -- use the MouseGetPos() method, which seems much more accurate.
  7830.                     EnumChildWindows(clicked_hwnd, EnumChildFindPoint, (LPARAM)&pah); // Find topmost control containing point.
  7831.                     clicked_hwnd = pah.hwnd_found; // Okay if NULL; the next stage will handle it.
  7832.                 }
  7833.             }
  7834.             // Finding control_index requires only GUI_HWND_TO_INDEX (not FindControl) since context menu message
  7835.             // never arrives for a ComboBox's Edit control (since that control has its own context menu).
  7836.             control_index = GUI_HWND_TO_INDEX(clicked_hwnd); // Yields a small negative value on failure, which due to unsigned is seen as a large positive number.
  7837.             if (control_index >= pgui->mControlCount) // The user probably clicked the parent window rather than inside one of its controls.
  7838.                 control_index = NO_CONTROL_INDEX;
  7839.                 // Above flags it as a non-control event. Must use NO_CONTROL_INDEX rather than something
  7840.                 // like 0xFFFFFFFF so that high-order bit is preserved for use below.
  7841.             POST_AHK_GUI_ACTION(hWnd, control_index, GUI_EVENT_CONTEXTMENU, from_keyboard);
  7842.             return 0; // Return value doesn't matter.
  7843.         }
  7844.         //else it's for some non-GUI window (probably impossible).  Let DefDlgProc() handle it.
  7845.         break;
  7846.  
  7847.     case WM_DROPFILES:
  7848.     {
  7849.         if (   !(pgui = GuiType::FindGui(hWnd))   )
  7850.             break; // Let DefDlgProc() handle it.
  7851.         HDROP hdrop = (HDROP)wParam;
  7852.         if (!pgui->mLabelForDropFiles || pgui->mHdrop)
  7853.         {
  7854.             // There is no event handler in the script, or this window is still processing a prior drop.
  7855.             // Ignore this drop and free its memory.
  7856.             DragFinish(hdrop);
  7857.             return 0; // "An application should return zero if it processes this message."
  7858.         }
  7859.         // Otherwise: Indicate that this window is now processing the drop.  DragFinish() will be called later.
  7860.         pgui->mHdrop = hdrop;
  7861.         point_and_hwnd_type pah = {0};
  7862.         // DragQueryPoint()'s return value is non-zero if the drop occurred in the client area.
  7863.         // However, that info seems too rarely needed to justify storing it anywhere:
  7864.         DragQueryPoint(hdrop, &pah.pt);
  7865.         ClientToScreen(hWnd, &pah.pt); // EnumChildFindPoint() requires screen coords.
  7866.         EnumChildWindows(hWnd, EnumChildFindPoint, (LPARAM)&pah); // Find topmost control containing point.
  7867.         // Look up the control in case the drop occurred in a child of a child, such as the edit portion
  7868.         // of a ComboBox (FindControl will take that into account):
  7869.         pcontrol = pah.hwnd_found ? pgui->FindControl(pah.hwnd_found) : NULL;
  7870.         // Finding control_index requires only GUI_HWND_TO_INDEX (not FindControl) since EnumChildFindPoint (above)
  7871.         // already properly resolves the Edit control of a ComboBox to be the ComboBox itself.
  7872.         control_index = pcontrol ? GUI_HWND_TO_INDEX(pcontrol->hwnd) : NO_CONTROL_INDEX;
  7873.         // Above: NO_CONTROL_INDEX indicates to GetGuiControl() that there is no control in this case.
  7874.         POST_AHK_GUI_ACTION(hWnd, control_index, GUI_EVENT_DROPFILES, NO_EVENT_INFO); // The HDROP is not passed via message so that it can be released (via the destructor) if the program closes during the drop operation.
  7875.         // MsgSleep() is not done because "case AHK_GUI_ACTION" in GuiWindowProc() takes care of it.
  7876.         // See its comments for why.
  7877.         return 0; // "An application should return zero if it processes this message."
  7878.     }
  7879.  
  7880.     case AHK_GUI_ACTION:
  7881.     case AHK_USER_MENU:
  7882.         // v1.0.36.03: The g_MenuIsVisible check was added as a means to discard the message. Otherwise
  7883.         // MSG_FILTER_MAX would result in a bouncing effect or something else that disrupts a popup menu,
  7884.         // namely a context menu shown by an AltSubmit ListView (regardless of whether it's shown by
  7885.         // GuiContextMenu or in response to a RightClick event).  This is because a ListView apparently
  7886.         // generates the following notifications while the context menu is displayed:
  7887.         // C: release mouse capture
  7888.         // H: hottrack
  7889.         // f: lost focus
  7890.         // I think the issue here is that there are times when messages should be reposted and
  7891.         // other times when they should not be.  The MonthCal and DateTime cases mentioned below are
  7892.         // times when they should, because the MSG_FILTER_MAX filter is not in effect then.  But when a
  7893.         // script's own popup menu is displayed, any message subject to filtering (which includes
  7894.         // AHK_GUI_ACTION and AHK_USER_MENU) should probably never be reposted because that disrupts
  7895.         // the ability to select an item in the menu or dismiss it (possibly due to the theoretical
  7896.         // bouncing-around effect described below).
  7897.         // OLDER: I don't think the below is a complete explanation since it doesn't take into account
  7898.         // the fact that the message filter might be in effect due to a menu being visible, which if
  7899.         // true would prevent MsgSleep from processing the message.
  7900.         // OLDEST: MsgSleep() is the critical step.  It forces our thread msg pump to handle the message now
  7901.         // because otherwise it would probably become a CPU-maxing loop wherein the dialog or MonthCal
  7902.         // msg pump that called us dispatches the above message right back to us, causing it to
  7903.         // bounce around thousands of times until that other msg pump finally finishes.
  7904.         if (!g_MenuIsVisible)
  7905.         {
  7906.             // Handling these messages here by reposting them to our thread relieves the one who posted them
  7907.             // from ever having to do a MsgSleep(-1), which in turn allows it or its caller to acknowledge
  7908.             // its message in a timely fashion, which in turn prevents undesirable side-effects when a
  7909.             // g-labeled DateTime's drop-down is navigated via its arrow buttons (jumps ahead two months
  7910.             // instead of one, infinite loop with mouse button stuck down on some systems, etc.). Another
  7911.             // side-effect is the failure of a g-labeled MonthCal to be able to notify of date change when
  7912.             // the user clicks the year and uses the spinner to select a new year.  This solves both of
  7913.             // those issues and almost certainly others:
  7914.             PostMessage(hWnd, iMsg, wParam, lParam);
  7915.             MsgSleep(-1, RETURN_AFTER_MESSAGES_SPECIAL_FILTER);
  7916.         }
  7917.         return 0;
  7918.  
  7919.     case WM_CLOSE: // For now, take the same action as SC_CLOSE.
  7920.         if (   !(pgui = GuiType::FindGui(hWnd))   )
  7921.             break; // Let DefDlgProc() handle it.
  7922.         pgui->Close();
  7923.         return 0;
  7924.  
  7925.     case WM_DESTROY:
  7926.         // Update to below: If a GUI window is owned by the script's main window (via "gui +owner"),
  7927.         // it can be destroyed automatically.  Because of this and the fact that it's difficult to
  7928.         // keep track of all the ways a window can be destroyed, it seems best for peace-of-mind to
  7929.         // have this WM_DESTROY handler 
  7930.         // Older Note: Let default-proc handle WM_DESTROY because with the current design, it should
  7931.         // be impossible for a window to be destroyed without the object "knowing about it" and
  7932.         // updating itself (then destroying itself) accordingly.  The object methods always
  7933.         // destroy (recursively) any windows it owns, so once again it "knows about it".
  7934.         if (pgui = GuiType::FindGui(hWnd)) // Assign.
  7935.             if (!pgui->mDestroyWindowHasBeenCalled)
  7936.             {
  7937.                 pgui->mDestroyWindowHasBeenCalled = true; // Tell it not to call DestroyWindow(), just clean up everything else.
  7938.                 GuiType::Destroy(pgui->mWindowIndex);
  7939.             }
  7940.         // Above: if mDestroyWindowHasBeenCalled==true, we were called by Destroy(), so don't call Destroy() again recursively.
  7941.         // And in any case, pass it on to DefDlgProc() in case it does any extra cleanup:
  7942.         break;
  7943.  
  7944.     // Cases for WM_ENTERMENULOOP and WM_EXITMENULOOP:
  7945.     HANDLE_MENU_LOOP
  7946.  
  7947.     } // switch()
  7948.  
  7949.     // This will handle anything not already fully handled and returned from above:
  7950.     return DefDlgProc(hWnd, iMsg, wParam, lParam);
  7951. }
  7952.  
  7953.  
  7954.  
  7955. LRESULT CALLBACK TabWindowProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
  7956. {
  7957.     // Variables are kept separate up here for future expansion of this function (to handle
  7958.     // more iMsgs/cases, etc.):
  7959.     GuiType *pgui;
  7960.     GuiControlType *pcontrol;
  7961.     HWND parent_window;
  7962.  
  7963.     if (iMsg == WM_ERASEBKGND)
  7964.     {
  7965.         parent_window = GetParent(hWnd);
  7966.         // Relies on short-circuit boolean order:
  7967.         if (   (pgui = GuiType::FindGui(parent_window)) && (pcontrol = pgui->FindControl(hWnd))
  7968.             && pgui->mBackgroundBrushWin && !(pcontrol->attrib & GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT)   )
  7969.         {
  7970.             // Can't use SetBkColor(), need an real brush to fill it.
  7971.             RECT clipbox;
  7972.             GetClipBox((HDC)wParam, &clipbox);
  7973.             FillRect((HDC)wParam, &clipbox, pgui->mBackgroundBrushWin);
  7974.             return 1; // "An application should return nonzero if it erases the background."
  7975.         }
  7976.         //else let default proc handle it.
  7977.     }
  7978.  
  7979.     // This will handle anything not already fully handled and returned from above:
  7980.     return CallWindowProc(g_TabClassProc, hWnd, iMsg, wParam, lParam);
  7981. }
  7982.  
  7983.  
  7984.  
  7985. void GuiType::Event(GuiIndexType aControlIndex, UINT aNotifyCode, USHORT aGuiEvent, UINT aEventInfo)
  7986. // Caller should pass GUI_EVENT_NONE (zero) for aGuiEvent if it wants us to determine aGuiEvent based on the
  7987. // type of control and the incoming aNotifyCode.
  7988. // This function handles events within a GUI window that caused one of its controls to change in a meaningful
  7989. // way, or that is an event that could trigger an external action, such as clicking a button or icon.
  7990. {
  7991.     if (aControlIndex >= mControlCount) // Caller probably already checked, but just to be safe.
  7992.         return;
  7993.     GuiControlType &control = mControl[aControlIndex];
  7994.     if (!(control.jump_to_label || (control.attrib & GUI_CONTROL_ATTRIB_IMPLICIT_CANCEL)))
  7995.         return; // No label or implicit-cancel associated with this control, so no action.
  7996.     //else continue on even if it's just GUI_CONTROL_ATTRIB_IMPLICIT_CANCEL so that the
  7997.     // event will get posted.  The control's output_var might also get updated, but for
  7998.     // simplicity that is done even when there is no jump_to_label.
  7999.  
  8000.     // Update: The below is now checked by MsgSleep() at the time the launch actually would occur because
  8001.     // g_nThreads will be more accurate/timely then:
  8002.     // If this control already has a thread running in its label, don't create a new thread to avoid
  8003.     // problems of buried threads, or a stack of suspended threads that might be resumed later
  8004.     // at an unexpected time. Users of timer subs that take a long time to run should be aware, as
  8005.     // documented in the help file, that long interruptions are then possible.
  8006.     //if (g_nThreads >= g_MaxThreadsTotal || (aControl->attrib & GUI_CONTROL_ATTRIB_LABEL_IS_RUNNING))
  8007.     //    continue
  8008.  
  8009.     if (aGuiEvent == GUI_EVENT_NONE) // Caller wants us to determine aGuiEvent based on control type and aNotifyCode.
  8010.     {
  8011.         aGuiEvent = GUI_EVENT_NORMAL; // Set default, to be possibly overridden below.
  8012.         switch(control.type)
  8013.         {
  8014.         case GUI_CONTROL_BUTTON:
  8015.         case GUI_CONTROL_CHECKBOX:
  8016.         case GUI_CONTROL_RADIO:
  8017.             // Must include BN_DBLCLK or these control types won't be responsive to rapid consecutive clicks.
  8018.             // Update: The above is true only if the button has the BS_NOTIFY option, and now it doesn't so
  8019.             // checking for BN_DBLCLK is no longer necessary.  Update: Double-clicks are now detected in
  8020.             // case that style every winds up on any of the above control types (currently it's the default
  8021.             // on GUI_CONTROL_RADIO anyway):
  8022.             switch (aNotifyCode)
  8023.             {
  8024.             case BN_CLICKED: // Must explicitly list this case since the default label below does a return.
  8025.                 // Fix for v1.0.24: The below excludes from consideration messages from radios that are
  8026.                 // being unchecked.  This prevents a radio group's g-label from being fired twice when the
  8027.                 // user navigates to a new radio via the arrow keys.  It also filters out the BN_CLICKED that
  8028.                 // occurs when the user tabs over to a radio group that lacks a selected button.  This new
  8029.                 // behavior seems like it would be desirable most of the time.
  8030.                 if (control.type == GUI_CONTROL_RADIO && SendMessage(control.hwnd, BM_GETCHECK, 0, 0) == BST_UNCHECKED)
  8031.                     return;
  8032.                 break;
  8033.             case BN_DBLCLK:
  8034.                 aGuiEvent = GUI_EVENT_DBLCLK;
  8035.                 break;
  8036.             default:
  8037.                 return;
  8038.             }
  8039.             break;
  8040.  
  8041.         case GUI_CONTROL_DROPDOWNLIST:
  8042.         case GUI_CONTROL_COMBOBOX:
  8043.             switch (aNotifyCode)
  8044.             {
  8045.             case CBN_SELCHANGE:  // Must explicitly list this case since the default label does a return.
  8046.             case CBN_EDITCHANGE: // Added for v1.0.24 to support detection of changes in a ComboBox's edit portion.
  8047.                 break;
  8048.             case CBN_DBLCLK: // Used by CBS_SIMPLE (i.e. list always visible).
  8049.                 aGuiEvent = GUI_EVENT_DBLCLK; // But due to rarity of use, the focused row number is not stored in aEventInfo.
  8050.                 break;
  8051.             default:
  8052.                 return;
  8053.             }
  8054.             break;
  8055.  
  8056.         case GUI_CONTROL_LISTBOX:
  8057.             switch (aNotifyCode)
  8058.             {
  8059.             case LBN_SELCHANGE: // Must explicitly list this case since the default label does a return.
  8060.                 break;
  8061.             case LBN_DBLCLK:
  8062.                 aGuiEvent = GUI_EVENT_DBLCLK;
  8063.                 aEventInfo = 1 + (UINT)SendMessage(control.hwnd, LB_GETCARETINDEX, 0, 0); // +1 to convert to one-based index.
  8064.                 break;
  8065.             default:
  8066.                 return;
  8067.             }
  8068.             break;
  8069.  
  8070.         case GUI_CONTROL_EDIT:
  8071.             // Seems more appropriate to check EN_CHANGE vs. EN_UPDATE since EN_CHANGE occurs only after
  8072.             // any redrawing of the control.
  8073.             if (aNotifyCode == EN_CHANGE)
  8074.                 break;
  8075.             return; // No action for other notifications.
  8076.  
  8077.         case GUI_CONTROL_HOTKEY: // The only notification sent by the hotkey control is EN_CHANGE.
  8078.             if (control.output_var) // Above already confirmed it has a jump_to_label (or at least an implicit cancel).
  8079.                 ControlGetContents(*control.output_var, control);
  8080.             break;
  8081.  
  8082.         case GUI_CONTROL_TEXT:
  8083.         case GUI_CONTROL_PIC:
  8084.             // Update: Unlike buttons, it's all-or-none for static controls.  Testing shows that if
  8085.             // STN_DBLCLK is not checked for and the user clicks rapidly, half the clicks will be
  8086.             // ignored:
  8087.             // Based on experience with BN_DBLCLK, it's likely that STN_DBLCLK must be included or else
  8088.             // these control types won't be responsive to rapid consecutive clicks:
  8089.             switch (aNotifyCode)
  8090.             {
  8091.             case STN_CLICKED: // Must explicitly list this case since the default label does a return.
  8092.                 break;
  8093.             case STN_DBLCLK:
  8094.                 aGuiEvent = GUI_EVENT_DBLCLK;
  8095.                 break;
  8096.             default:
  8097.                 return;
  8098.             }
  8099.             break;
  8100.  
  8101.         case GUI_CONTROL_UPDOWN:
  8102.             // Due to the difficulty in distinguishing between clicking an arrow button and pressing an
  8103.             // arrow key on the keyboard, there is currently no GUI_CONTROL_ATTRIB_ALTSUBMIT mode for
  8104.             // up-downs.  That mode could be reserved to allow the script to override the user's position
  8105.             // change of the up-down by means of the script returning 1 or 0 in response to UDN_DELTAPOS.
  8106.             if (aNotifyCode == SB_THUMBPOSITION)
  8107.             {
  8108.                 // User has pressed arrow keys or clicked down on the mouse on one of the arrows.
  8109.                 if (control.output_var) // Above already confirmed it has a jump_to_label (or at least an implicit cancel).
  8110.                     ControlGetContents(*control.output_var, control);
  8111.                 break;
  8112.             }
  8113.             // Otherwise, ignore all others.  SB_ENDSCROLL is received when user has released mouse after
  8114.             // scrolling one of the arrows (never arrives for arrow keys, even when holding them down).
  8115.             // That event (and any others, but especially that one) is ignored because it would launch
  8116.             // the g-label twice: once for lbutton-down and once for up.
  8117.             return;
  8118.  
  8119.         case GUI_CONTROL_SLIDER:
  8120.             switch (aNotifyCode)
  8121.             {
  8122.             case TB_ENDTRACK: // WM_KEYUP (the user released a key that sent a relevant virtual key code)
  8123.                 // Unfortunately, the control does not generate a TB_ENDTRACK notification when the slider
  8124.                 // was moved via the mouse wheel.  This is documented here as a known limitation.  The
  8125.                 // workaround is to use AltSubmit.
  8126.                 break;
  8127.             default:
  8128.                 // Namely the following:
  8129.                 //case TB_THUMBPOSITION: // Mouse wheel or WM_LBUTTONUP following a TB_THUMBTRACK notification message
  8130.                 //case TB_THUMBTRACK:    // Slider movement (the user dragged the slider)
  8131.                 //case TB_LINEUP:        // VK_LEFT or VK_UP
  8132.                 //case TB_LINEDOWN:      // VK_RIGHT or VK_DOWN
  8133.                 //case TB_PAGEUP:        // VK_PRIOR (the user clicked the channel above or to the left of the slider)
  8134.                 //case TB_PAGEDOWN:      // VK_NEXT (the user clicked the channel below or to the right of the slider)
  8135.                 //case TB_TOP:           // VK_HOME
  8136.                 //case TB_BOTTOM:        // VK_END
  8137.                 if (!(control.attrib & GUI_CONTROL_ATTRIB_ALTSUBMIT)) // Ignore this event.
  8138.                     return;
  8139.                 // Otherwise:
  8140.                 aGuiEvent = aNotifyCode + 48; // Signal it to store an ASCII character (digit) in A_GuiControlEvent.
  8141.             }
  8142.             if (control.output_var) // Above already confirmed it has a jump_to_label (or at least an implicit cancel).
  8143.                 ControlGetContents(*control.output_var, control);
  8144.             break;
  8145.  
  8146.         // The following need no extra handling because their info is already ready to be posted as an event below:
  8147.         //case GUI_CONTROL_TREEVIEW:
  8148.         //case GUI_CONTROL_LISTVIEW:
  8149.         //case GUI_CONTROL_TAB: // aNotifyCode == TCN_SELCHANGE should be the only possibility.
  8150.         //case GUI_CONTROL_DATETIME:
  8151.         //case GUI_CONTROL_MONTHCAL:
  8152.         //
  8153.         // The following are not needed because execution never reaches this point.  This is because these types
  8154.         // are forbidden from having a gLabel. Search on "case 'G'" for details.
  8155.         //case GUI_CONTROL_GROUPBOX:
  8156.         //case GUI_CONTROL_PROGRESS:
  8157.  
  8158.         } // switch(control.type)
  8159.     } // if (aGuiEvent == GUI_EVENT_NONE)
  8160.  
  8161.     POST_AHK_GUI_ACTION(mHwnd, aControlIndex, aGuiEvent, aEventInfo);
  8162.     // MsgSleep(-1, RETURN_AFTER_MESSAGES_SPECIAL_FILTER) is not done because "case AHK_GUI_ACTION" in GuiWindowProc()
  8163.     // takes care of it.  See its comments for why.
  8164.  
  8165.     // BACKGROUND ABOUT THE ABOVE:
  8166.     // Rather than launching the thread directly from here, it seems best to always post it to our
  8167.     // thread to be handled there.  Here are the reasons:
  8168.     // 1) We don't want to be in the situation where a thread launched here would return first
  8169.     //    to a dialog's message pump rather than MsgSleep's pump.  That's because our thread
  8170.     //    might have queued messages that would then be misrouted or lost because the dialog's
  8171.     //    dispatched them incorrectly, or didn't know what to do with them because they had a
  8172.     //    NULL hwnd.
  8173.     // 2) If the script happens to be uninterrutible, we would want to "re-queue" the messages
  8174.     //    in this fashion anyway (doing so avoids possible conflict if the current quasi-thread
  8175.     //    is in the middle of a critical operation, such as trying to open the clipboard for 
  8176.     //    a command it is right in the middle of executing).
  8177.     // 3) "Re-queuing" this event *only* for case #2 might cause problems with losing the original
  8178.     //     sequence of events that occurred in the GUI.  For example, if some events were re-queued
  8179.     //     due to uninterruptibility, but other more recent ones were not (because the thread became
  8180.     //     interruptible again at a critical moment), the more recent events would take effect before
  8181.     //     the older ones.  Requeuing all events ensures that when they do take effect, they do so
  8182.     //     in their original order.
  8183.     //
  8184.     // More explanation about Case #1 above.  Consider this order of events: 1) Current thread is
  8185.     // waiting for dialog, thus that dialog's msg pump is running. 2) User presses a button on GUI
  8186.     // window, and then another while the prev. button's thread is still uninterruptible (perhaps
  8187.     // this happened due to automating the form with the Send command).  3) Due to uninterruptibility,
  8188.     // this event would be re-queued to the thread's msg queue.  4) If the first thread ends before any
  8189.     // call to MsgSleep(), we'll be back at the dialog's msg pump again, and thus the requeued message
  8190.     // would be misrouted or discarded due to automatic dispatching.
  8191.     //
  8192.     // Info about why events are buffered when script is uninterruptible:
  8193.      // It seems best to buffer GUI events that would trigger a new thread, rather than ignoring
  8194.     // them or allowing them to launch unconditionally.  Ignoring them is bad because lost events
  8195.     // might cause a GUI to get out of sync with how its controls are designed to update and
  8196.     // interact with each other.  Allowing them to launch anyway is bad in the case where
  8197.     // a critical operation for another thread is underway (such as attempting to open the
  8198.     // clipboard).  Thus, post this event back to our thread so that even if a msg pump
  8199.     // other than our own is running (such as MsgBox or another dialog), the msg will stay
  8200.     // buffered (the msg might bounce around fiercely if we kept posting it to our WindowProc,
  8201.     // since any other msg pump would not have the buffering filter in place that ours has).
  8202.     //
  8203.     // UPDATE: I think some of the above might be obsolete now.  Consider this order of events:
  8204.     // 1) Current thread is waiting for dialog, thus that dialog's msg pump is running. 2) User presses
  8205.     // a button on GUI window. 3) We receive that message here and post it below. 4) When we exit,
  8206.     // the dialog's msg pump receives the message and dispatches it to GuiWindowProc. 5) GuiWindowProc
  8207.     // posts the message and immediately does a MsgSleep to force our msg pump to handle the message.
  8208.     // 6) Everything is fine unless our msg pump leaves the message queued due to uninterruptibility,
  8209.     // in which case the msg will bounce around, possibly maxing the CPU, until either the dialog's msg
  8210.     // pump ends or the other thread becomes interruptible (which usually doesn't take more than 20 ms).
  8211.     // If the script is uninterruptible due to some long operation such as sending keystrokes or trying
  8212.     // to open the clipboard when it is locked, any dialog message pump underneath on the call stack
  8213.     // shouldn't be an issue because the long-operation (clipboard/Send) does not return to that
  8214.     // msg pump until the operation is complete; in other words, the message to stay queued rather than
  8215.     // bouncing around.
  8216.     //
  8217.     // Concerning the practice of saving to a control's output variable prior to posting this message:
  8218.     // It's true that this would cause any other queued/unprocessed messages of the same event/control
  8219.     // to share the same value, which is undesirable but rare in practice (and usually inconsequential,
  8220.     // since only the most recent value tends to matter, not those that happened very quickly in between).
  8221.     // The code-simplicity of this approach seems worthwhile for now.
  8222.     // More info: In many cases (e.g. Slider), the control's output var is set to the value before posting
  8223.     // the message.  Therefore, if there are any of the same messages still in the queue when a new one
  8224.     // is posted, they will all have the same output-var value.  If you consider that output-var update is
  8225.     // just a convenience, this isn't much of an issue because if the script were to do GuiControlGet on it,
  8226.     // the same effect would occur.  But still, it should be fixed for those situations where it's important
  8227.     // (there don't appear to be any control types where this is important, but here's the list of those
  8228.     // whose g-labels change the value of the output-var):
  8229.     //GUI_CONTROL_LISTVIEW?
  8230.     //GUI_CONTROL_HOTKEY
  8231.     //GUI_CONTROL_DATETIME
  8232.     //GUI_CONTROL_MONTHCAL
  8233.     //GUI_CONTROL_UPDOWN
  8234.     //GUI_CONTROL_SLIDER
  8235.     //GUI_CONTROL_TAB (in GuiWindowProc)
  8236.  
  8237.     // Although an additional WORD of info could be squeezed into the message by passing the control's HWND
  8238.     // instead of the parent window's (and the msg pump could then look up the parent window via
  8239.     // GetNonChildParent), it's probably not feasible because if some other message pump is running, it would
  8240.     // route AHK_GUI_ACTION messages to the window proc. of the control rather than the parent window, which
  8241.     // would prevent them from being re-posted back to the queue (see "case AHK_GUI_ACTION" in GuiWindowProc()).
  8242. }
  8243.  
  8244.  
  8245.  
  8246. WORD GuiType::TextToHotkey(char *aText)
  8247. // Returns a WORD (not a DWORD -- MSDN is wrong about that) compatible with the HKM_SETHOTKEY message:
  8248. // LOBYTE is the virtual key.
  8249. // HIBYTE is a set of modifiers:
  8250. // HOTKEYF_ALT ALT key
  8251. // HOTKEYF_CONTROL CONTROL key
  8252. // HOTKEYF_SHIFT SHIFT key
  8253. // HOTKEYF_EXT Extended key
  8254. {
  8255.     BYTE modifiers = 0; // Set default.
  8256.     for (bool done = false; *aText; ++aText)
  8257.     {
  8258.         switch (*aText)
  8259.         {
  8260.         case '!': modifiers |= HOTKEYF_ALT; break;
  8261.         case '^': modifiers |= HOTKEYF_CONTROL; break;
  8262.         case '+': modifiers |= HOTKEYF_SHIFT; break;
  8263.         default: done = true;  // Some other character type, so it marks the end of the modifiers.
  8264.         }
  8265.         if (done) // This must be checked prior here otherwise the loop's ++aText will increment one too many.
  8266.             break;
  8267.     }
  8268.  
  8269.     // For translating the virtual key below, the following notes apply:
  8270.     // The following extended keys are unlikely, and in any case don't have a non-extended counterpart,
  8271.     // so no special handling:
  8272.     // VK_CANCEL (Ctrl-break)
  8273.     // VK_SNAPSHOT (PrintScreen).
  8274.     //
  8275.     // These do not have a non-extended counterpart, i.e. their VK has only one possible scan code:
  8276.     // VK_DIVIDE (NumpadDivide/slash)
  8277.     // VK_NUMLOCK
  8278.     //
  8279.     // All of the following are handled properly via the scan code logic below:
  8280.     // VK_INSERT
  8281.     // VK_PRIOR
  8282.     // VK_NEXT
  8283.     // VK_HOME
  8284.     // VK_END
  8285.     // VK_UP
  8286.     // VK_DOWN
  8287.     // VK_LEFT
  8288.     // VK_RIGHT
  8289.     //
  8290.     // Same note as above but these cannot be typed by the user, only programmatically inserted via
  8291.     // initial value of "Gui Add" or via "GuiControl,, MyHotkey, ^Delete":
  8292.     // VK_DELETE
  8293.     // VK_RETURN
  8294.     // Note: NumpadEnter (not Enter) is extended, unlike Home/End/Pgup/PgDn/Arrows, which are
  8295.     // NON-extended on the keypad.
  8296.  
  8297.     BYTE vk = TextToVK(aText);
  8298.     if (!vk)
  8299.         return 0;  // Indicate total failure because a hotkey control can't contain just modifiers without a VK.
  8300.     // Find out if the HOTKEYF_EXT flag should be set.
  8301.     sc_type sc = TextToSC(aText); // Better than vk_to_sc() since that has both an primary and secondary scan codes to choose from.
  8302.     if (!sc) // Since not found above, default to the primary scan code.
  8303.         sc = vk_to_sc(vk);
  8304.     if (sc & 0x100) // The scan code derived above is extended.
  8305.         modifiers |= HOTKEYF_EXT;
  8306.     return MAKEWORD(vk, modifiers);
  8307. }
  8308.  
  8309.  
  8310.  
  8311. char *GuiType::HotkeyToText(WORD aHotkey, char *aBuf)
  8312. // Caller has ensured aBuf is large enough to hold any hotkey name.
  8313. // Returns aBuf.
  8314. {
  8315.     BYTE modifiers = HIBYTE(aHotkey); // In this case, both the VK and the modifiers are bytes, not words.
  8316.     char *cp = aBuf;
  8317.     if (modifiers & HOTKEYF_SHIFT)
  8318.         *cp++ = '+';
  8319.     if (modifiers & HOTKEYF_CONTROL)
  8320.         *cp++ = '^';
  8321.     if (modifiers & HOTKEYF_ALT)
  8322.         *cp++ = '!';
  8323.     BYTE vk = LOBYTE(aHotkey);
  8324.  
  8325.     if (modifiers & HOTKEYF_EXT) // Try to find the extended version of this VK if it has two versions.
  8326.     {
  8327.         // Fix for v1.0.37.03: A virtual key that has only one scan code should be resolved by VK
  8328.         // rather than SC.  Otherwise, Numlock will wind up being SC145 and NumpadDiv something similar.
  8329.         // If a hotkey control could capture AppsKey, PrintScreen, Ctrl-Break (VK_CANCEL), which it can't, this
  8330.         // would also apply to them.
  8331.         sc_type sc1 = vk_to_sc(vk); // Primary scan code for this virtual key.
  8332.         sc_type sc2 = vk_to_sc(vk, true); // Secondary scan code (will be the same as above if the VK has only one SC).
  8333.         sc_type sc = (sc2 & 0x100) ? sc2 : sc1;
  8334.         if ((sc & 0x100) && sc1 != sc2) // "sc" is both non-zero and extended, and this isn't a single-scan-code VK.
  8335.         {
  8336.             SCtoKeyName(sc, cp, 100);
  8337.             return aBuf;
  8338.         }
  8339.     }
  8340.     // Since above didn't return, use a simple lookup on VK, since it gives preference to non-extended keys.
  8341.     // KNOWN ISSUE: Someone pointed out that the following will typically produce ^A instead of ^a, which will
  8342.     // produce an unwanted shift keystroke if for some reason the script uses the Send command to send the hotkey.
  8343.     // However, for the following reasons, it seems best not to try to "fix" it:
  8344.     // 1) It's not easy to fix it since VKtoKeyName indirectly calls GetKeyNameText() to get the key's name,
  8345.     //    and there's no telling what names (single-character or otherwise) various keyboard layouts/languages
  8346.     //    might produce.
  8347.     // 2) ^A seems more readable than ^a (which is probably the exact reason the OS's hotkey control displays it
  8348.     //     in uppercase).  Of course, this has merit only when the script actually displays the hotkey somewhere.
  8349.     // 3) There's a slight possibility that changing it would break existing scripts that rely on uppercase.
  8350.     // 4) Using the Send command to send the hotkey seems very rare; the script would normally Gosub the hotkey's
  8351.     //    subroutine instead.
  8352.     VKtoKeyName(vk, 0, cp, 100);
  8353.     // The above call might be produce an unknown key-name via GetKeyName().  Since it seems so rare and
  8354.     // the exact string to be returned (e.g. SC vs. VK) is uncertain/debatable: For now, it seems best to
  8355.     // leave it as its native-language name rather than attempting to convert it to an SC or VK that
  8356.     // can be compatible with GetKeyState or the Hotkey command:
  8357.     //if (!TextToVK(cp))
  8358.     //    sprintf(cp, "vk%02X", vk);
  8359.     return aBuf;
  8360. }
  8361.  
  8362.  
  8363.  
  8364. void GuiType::ControlCheckRadioButton(GuiControlType &aControl, GuiIndexType aControlIndex, WPARAM aCheckType)
  8365. {
  8366.     GuiIndexType radio_start, radio_end;
  8367.     FindGroup(aControlIndex, radio_start, radio_end); // Even if the return value is 1, do the below because it ensures things like tabstop are in the right state.
  8368.     if (aCheckType == BST_CHECKED)
  8369.         // This will check the specified button and uncheck all the others in the group.
  8370.         // There is at least one other reason to call CheckRadioButton() rather than doing something
  8371.         // manually: It prevents an unwanted firing of the radio's g-label upon WM_ACTIVATE,
  8372.         // at least when a radio group is first in the window's z-order and the radio group has
  8373.         // an initially selected button:
  8374.         CheckRadioButton(mHwnd, GUI_INDEX_TO_ID(radio_start), GUI_INDEX_TO_ID(radio_end - 1), GUI_INDEX_TO_ID(aControlIndex));
  8375.     else // Uncheck it.
  8376.     {
  8377.         // If the group was originally created with the tabstop style, unchecking the button that currently
  8378.         // has that style would also remove the tabstop style because apparently that's how radio buttons
  8379.         // respond to being unchecked.  Compensate for this by giving the first radio in the group the
  8380.         // tabstop style. Update: The below no longer checks to see if the radio group has the tabstop style
  8381.         // because it's fairly pointless.  This is because when the user checks/clicks a radio button,
  8382.         // the control automatically acquires the tabstop style.  In other words, even though -Tabstop is
  8383.         // allowed in a radio's options, it will be overridden the first time a user selects a radio button.
  8384.         HWND first_radio_in_group = NULL;
  8385.         // Find the first radio in this control group:
  8386.         for (GuiIndexType u = radio_start; u < radio_end; ++u)
  8387.             if (mControl[u].type == GUI_CONTROL_RADIO) // Since a group can have non-radio controls in it.
  8388.             {
  8389.                 first_radio_in_group = mControl[u].hwnd;
  8390.                 break;
  8391.             }
  8392.         // The below can't be done until after the above because it would remove the tabstop style
  8393.         // if the specified radio happens to be the one that has the tabstop style.  This is usually
  8394.         // the case since the button is usually on (since the script is now turning it off here),
  8395.         // and other logic has ensured that the on-button is the one with the tabstop style:
  8396.         SendMessage(aControl.hwnd, BM_SETCHECK, BST_UNCHECKED, 0);
  8397.         if (first_radio_in_group) // Apply the tabstop style to it.
  8398.             SetWindowLong(first_radio_in_group, GWL_STYLE, WS_TABSTOP | GetWindowLong(first_radio_in_group, GWL_STYLE));
  8399.     }
  8400. }
  8401.  
  8402.  
  8403.  
  8404. void GuiType::ControlSetUpDownOptions(GuiControlType &aControl, GuiControlOptionsType &aOpt)
  8405. // Caller has ensured that aControl.type is an UpDown.
  8406. {
  8407.     if (aOpt.range_changed)
  8408.     {
  8409.         // MSDN implies that UDM_SETPOS should not be used on a control with a 32-bit range.
  8410.         // Although testing shows that it works okay, the 16-bit compatibility mode is used
  8411.         // whenever possible by flagging here whether the control needs a 32-bit range.
  8412.         // This flag is checked in several other places to determine whether to use the 16 or
  8413.         // 32-bit method.  This way is easier than the alternative, which is to query the
  8414.         // control's current range each time to find out whether compatibility mode
  8415.         // should be used.  The other alternative is to use DllGetVersion() to see if this
  8416.         // version of ComCtl32 supports 32-bit mode, but that would add complexities of its own.
  8417.         if (aOpt.range_max > UD_MAXVAL || aOpt.range_min < UD_MINVAL)
  8418.         {
  8419.             aControl.attrib |= GUI_CONTROL_ATTRIB_ALTBEHAVIOR; // Flag it as 32-bit.
  8420.             // When range exceeds 16-bit boundaries, use the 32-bit method even though it doesn't work
  8421.             // on 95/NT if they lack MSIE 5.x.  This has been documented.
  8422.             SendMessage(aControl.hwnd, UDM_SETRANGE32, aOpt.range_min, aOpt.range_max);
  8423.         }
  8424.         else // Use 16-bit mode whenever possible to maximize compatibility.
  8425.         {
  8426.             aControl.attrib &= ~GUI_CONTROL_ATTRIB_ALTBEHAVIOR; // Flag it as 16-bit.
  8427.             SendMessage(aControl.hwnd, UDM_SETRANGE, 0, (LPARAM)MAKELONG((short)aOpt.range_max, (short)aOpt.range_min));
  8428.         }
  8429.     }
  8430. }
  8431.  
  8432.  
  8433.  
  8434. int GuiType::ControlGetDefaultSliderThickness(DWORD aStyle, int aThumbThickness)
  8435. {
  8436.     if (aThumbThickness < 1)
  8437.         aThumbThickness = 20;  // Set default.
  8438.     // Provide a small margin on both sides, otherwise the bar is sometimes truncated.
  8439.     aThumbThickness += 5; // 5 looks better than 4 in most styles/themes.
  8440.     if (aStyle & TBS_NOTICKS) // This takes precedence over TBS_BOTH (which if present will still make the thumb flat vs. pointed).
  8441.         return aThumbThickness;
  8442.     if (aStyle & TBS_BOTH)
  8443.         return aThumbThickness + 16;
  8444.     return aThumbThickness + 8;
  8445. }
  8446.  
  8447.  
  8448.  
  8449. int GuiType::ControlInvertSliderIfNeeded(GuiControlType &aControl, int aPosition)
  8450. // Caller has ensured that aControl.type is slider.
  8451. {
  8452.     return (aControl.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR)
  8453.         ? ((int)SendMessage(aControl.hwnd, TBM_GETRANGEMAX, 0, 0) - aPosition) + (int)SendMessage(aControl.hwnd, TBM_GETRANGEMIN, 0, 0)
  8454.         : aPosition;  // No inversion necessary.
  8455. }
  8456.  
  8457.  
  8458.  
  8459. void GuiType::ControlSetSliderOptions(GuiControlType &aControl, GuiControlOptionsType &aOpt)
  8460. // Caller has ensured that aControl.type is slider.
  8461. {
  8462.     if (aOpt.range_changed)
  8463.     {
  8464.         // Don't use TBM_SETRANGE because then only 16-bit values are supported:
  8465.         SendMessage(aControl.hwnd, TBM_SETRANGEMIN, FALSE, aOpt.range_min); // No redraw
  8466.         SendMessage(aControl.hwnd, TBM_SETRANGEMAX, TRUE, aOpt.range_max); // Redraw.
  8467.     }
  8468.     if (aOpt.tick_interval)
  8469.     {
  8470.         if (aOpt.tick_interval < 0) // This is the signal to remove the existing tickmarks.
  8471.             SendMessage(aControl.hwnd, TBM_CLEARTICS, TRUE, 0);
  8472.         else // greater than zero, since zero itself it checked in one of the enclose IFs above.
  8473.             SendMessage(aControl.hwnd, TBM_SETTICFREQ, aOpt.tick_interval, 0);
  8474.     }
  8475.     if (aOpt.line_size > 0) // Removal is not supported, so only positive values are considered.
  8476.         SendMessage(aControl.hwnd, TBM_SETLINESIZE, 0, aOpt.line_size);
  8477.     if (aOpt.page_size > 0) // Removal is not supported, so only positive values are considered.
  8478.         SendMessage(aControl.hwnd, TBM_SETPAGESIZE, 0, aOpt.page_size);
  8479.     if (aOpt.thickness > 0)
  8480.         SendMessage(aControl.hwnd, TBM_SETTHUMBLENGTH, aOpt.thickness, 0);
  8481.     if (aOpt.tip_side)
  8482.         SendMessage(aControl.hwnd, TBM_SETTIPSIDE, aOpt.tip_side - 1, 0); // -1 to convert back to zero base.
  8483.  
  8484.     // Buddy positioning is left primitive and automatic even when auto-position of this slider
  8485.     // or the controls that come after it is in effect.  This is because buddy controls seem too
  8486.     // rarely used (due to their lack of positioning options), which is the reason why extra code
  8487.     // isn't added here and in "GuiControl Move" to treat the buddies as part of the control rect
  8488.     // (i.e. as an entire unit), nor any code for auto-positioning the entire unit when the control
  8489.     // is created.  If such code were added, it would require even more code if the slider itself
  8490.     // has an automatic position, because the positions of its buddies would have to be recorded,
  8491.     // then after they are set as buddies (which moves them) the slider is moved up or left to the
  8492.     // position where its buddies used to be.  Otherwise, there would be a gap left during
  8493.     // auto-layout.
  8494.     // For these, removal is not supported, only changing, since removal seems too rarely needed:
  8495.     if (aOpt.buddy1)
  8496.         SendMessage(aControl.hwnd, TBM_SETBUDDY, TRUE, (LPARAM)aOpt.buddy1->hwnd);  // Left/top
  8497.     if (aOpt.buddy2)
  8498.         SendMessage(aControl.hwnd, TBM_SETBUDDY, FALSE, (LPARAM)aOpt.buddy2->hwnd); // Right/bottom
  8499. }
  8500.  
  8501.  
  8502.  
  8503. void GuiType::ControlSetListViewOptions(GuiControlType &aControl, GuiControlOptionsType &aOpt)
  8504. // Caller has ensured that aControl.type is ListView.
  8505. // Caller has ensured that aOpt.color_bk is CLR_INVALID if no change should be made to the
  8506. // current background color.
  8507. {
  8508.     if (aOpt.limit)
  8509.     {
  8510.         if (ListView_GetItemCount(aControl.hwnd) > 0)
  8511.             SendMessage(aControl.hwnd, LVM_SETITEMCOUNT, aOpt.limit, 0); // Last parameter should be 0 for LVS_OWNERDATA (verified if you look at the definition of ListView_SetItemCount macro).
  8512.         else
  8513.             // When the control has no rows, work around the fact that LVM_SETITEMCOUNT delivers less than 20%
  8514.             // of its full benefit unless done after the first row is added (at least on XP SP1).  The message
  8515.             // is deferred until later by setting this flag:
  8516.             aControl.union_lv_attrib->row_count_hint = aOpt.limit;
  8517.     }
  8518.     if (aOpt.color_changed || aOpt.color_bk != CLR_INVALID)
  8519.     {
  8520.         if (aOpt.color_changed)
  8521.             ListView_SetTextColor(aControl.hwnd, aOpt.color_listview);
  8522.         if (aOpt.color_bk != CLR_INVALID) // Explicit color change was requested.
  8523.         {
  8524.             // Making both the same seems the best default because BkColor only applies to the portion
  8525.             // of the control that doesn't have text in it, which is typically very little.
  8526.             // Unlike ListView_SetTextBkColor, ListView_SetBkColor() treats CLR_DEFAULT as black.
  8527.             // therefore, make them both GetSysColor(COLOR_WINDOW) for consistency.  This color is
  8528.             // probably the default anyway:
  8529.             COLORREF color = (aOpt.color_bk == CLR_DEFAULT) ? GetSysColor(COLOR_WINDOW) : aOpt.color_bk;
  8530.             ListView_SetTextBkColor(aControl.hwnd, color);
  8531.             ListView_SetBkColor(aControl.hwnd, color);
  8532.         }
  8533.         // It used to work without this; I don't know what conditions changed, but apparently it's needed
  8534.         // at least sometimes.  The last param must be TRUE otherwise an space not filled by rows or columns
  8535.         // doesn't get updated:
  8536.         InvalidateRect(aControl.hwnd, NULL, TRUE);
  8537.     }
  8538. }
  8539.  
  8540.  
  8541.  
  8542. void GuiType::ControlSetTreeViewOptions(GuiControlType &aControl, GuiControlOptionsType &aOpt)
  8543. // Caller has ensured that aControl.type is TreeView.
  8544. // Caller has ensured that aOpt.color_bk is CLR_INVALID if no change should be made to the
  8545. // current background color.
  8546. {
  8547.     if (aOpt.color_changed)
  8548.         TreeView_SetTextColor(aControl.hwnd, aControl.union_color);
  8549.     if (aOpt.color_bk != CLR_INVALID) // Explicit color change was requested.
  8550.         // TreeView_SetBkColor() treats CLR_DEFAULT as black.  Therefore, use GetSysColor(COLOR_WINDOW),
  8551.         // which is probably the system's default TreeView background.
  8552.         TreeView_SetBkColor(aControl.hwnd, (aOpt.color_bk == CLR_DEFAULT) ? GetSysColor(COLOR_WINDOW) : aOpt.color_bk);
  8553.     // Disabled because it apparently is not supported on XP:
  8554.     //if (aOpt.tabstop_count)
  8555.     //    // Although TreeView_GetIndent() confirms that the following takes effect, it doesn't seem to 
  8556.     //    // cause any visible change, at least under XP SP2 (tried various style changes as well as Classic vs.
  8557.     //    // XP theme, as well as the "-Theme" option.
  8558.     //    TreeView_SetIndent(aControl.hwnd, aOpt.tabstop[0]);
  8559.  
  8560.     // Unlike ListView, seems not to be needed:
  8561.     //InvalidateRect(aControl.hwnd, NULL, TRUE);
  8562. }
  8563.  
  8564.  
  8565.  
  8566. void GuiType::ControlSetProgressOptions(GuiControlType &aControl, GuiControlOptionsType &aOpt, DWORD aStyle)
  8567. // Caller has ensured that aControl.type is Progress.
  8568. // Caller has ensured that aOpt.color_bk is CLR_INVALID if no change should be made to the
  8569. // bar's current background color.
  8570. {
  8571.     // If any options are present that cannot be manifest while a visual theme is in effect, ensure any
  8572.     // such theme is removed from the control (currently, once removed it is never put back on):
  8573.     // Override the default so that colors/smooth can be manifest even when non-classic theme is in effect.
  8574.     if (aControl.union_color != CLR_DEFAULT
  8575.         || !(aOpt.color_bk == CLR_DEFAULT || aOpt.color_bk == CLR_INVALID)
  8576.         || (aStyle & PBS_SMOOTH))
  8577.         MySetWindowTheme(aControl.hwnd, L"", L""); // Remove theme if options call for something theme can't show.
  8578.  
  8579.     if (aOpt.range_min || aOpt.range_max) // Must check like this because although it valid for one to be zero, both should not be.
  8580.     {
  8581.         if (aOpt.range_min > -1 && aOpt.range_min < 0x10000 && aOpt.range_max > -1 && aOpt.range_max < 0x10000)
  8582.             // Since the values fall within the bounds for Win95/NT to support, use the old method
  8583.             // in case Win95/NT lacks MSIE 3.0:
  8584.             SendMessage(aControl.hwnd, PBM_SETRANGE, 0, MAKELPARAM(aOpt.range_min, aOpt.range_max));
  8585.         else
  8586.             SendMessage(aControl.hwnd, PBM_SETRANGE32, aOpt.range_min, aOpt.range_max);
  8587.     }
  8588.  
  8589.     if (aOpt.color_changed)
  8590.         SendMessage(aControl.hwnd, PBM_SETBARCOLOR, 0, aControl.union_color);
  8591.  
  8592.     switch (aOpt.color_bk)
  8593.     {
  8594.     case CLR_DEFAULT:
  8595.         // If background color is default, mBackgroundColorWin won't take effect if there is a visual theme
  8596.         // in effect for this control.  But do the below anyway because we don't want to strip the theme off
  8597.         // the control just to make the bar's background match the window or tab control.  But we do want
  8598.         // it to match if the theme happens to be absent (due to OS not supporting it, classic theme being
  8599.         // in effect, or -theme being in effect):
  8600.         SendMessage(aControl.hwnd, PBM_SETBKCOLOR, 0, ControlOverrideBkColor(aControl) ? GetSysColor(COLOR_BTNFACE)
  8601.             : mBackgroundColorWin);
  8602.         break;
  8603.     case CLR_INVALID: // Do nothing in this case because caller didn't want existing bkgnd color changed.
  8604.         break;
  8605.     default: // Custom background color.  In this case, theme would already have been stripped above.
  8606.         SendMessage(aControl.hwnd, PBM_SETBKCOLOR, 0, aOpt.color_bk);
  8607.     }
  8608. }
  8609.  
  8610.  
  8611.  
  8612. bool GuiType::ControlOverrideBkColor(GuiControlType &aControl)
  8613. // Caller has ensured that aControl.type is something for which the window's or tab control's background
  8614. // should apply (e.g. Progress or Text).
  8615. {
  8616.     GuiControlType *ptab_control;
  8617.     if (!mTabControlCount || !(ptab_control = FindTabControl(aControl.tab_control_index))
  8618.         || !(ptab_control->attrib & GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT)) // Relies on short-circuit boolean order.
  8619.         return false;  // Override not needed because control isn't on a tab, or it's tab has same color as window.
  8620.     // Does this control lie mostly inside the tab?  Note that controls can belong to a tab page even though
  8621.     // they aren't physically located inside the page.
  8622.     RECT overlap_rect, tab_rect, control_rect;
  8623.     GetWindowRect(ptab_control->hwnd, &tab_rect);
  8624.     GetWindowRect(aControl.hwnd, &control_rect);
  8625.     IntersectRect(&overlap_rect, &tab_rect, &control_rect);
  8626.     // Returns true if more than 50% of control's area is inside the tab:
  8627.     return (overlap_rect.right - overlap_rect.left) * (overlap_rect.bottom - overlap_rect.top)
  8628.         > 0.5 * (control_rect.right - control_rect.left) * (control_rect.bottom - control_rect.top);
  8629. }
  8630.  
  8631.  
  8632.  
  8633. void GuiType::ControlUpdateCurrentTab(GuiControlType &aTabControl, bool aFocusFirstControl)
  8634. // Handles the selection of a new tab in a tab control.
  8635. {
  8636.     int curr_tab_index = TabCtrl_GetCurSel(aTabControl.hwnd);
  8637.     if (curr_tab_index == -1) // No tab is selected.  Maybe only happens if the tab control has no tabs at all.
  8638.         return;
  8639.  
  8640.     // Fix for v1.0.23:
  8641.     // If the tab control lacks the visible property, hide all its controls on all its tabs.
  8642.     // Don't use IsWindowVisible() because that would say that tab is hidden just because the parent
  8643.     // window is hidden, which is not desirable because then when the parent is shown, the shower
  8644.     // would always have to remember to call us.  This "hide all" behavior is done here rather than
  8645.     // attempting to "rely on everyone else to do their jobs of keeping the controls in the right state"
  8646.     // because it improves maintainability:
  8647.     DWORD tab_style = GetWindowLong(aTabControl.hwnd, GWL_STYLE);
  8648.     bool hide_all = !(tab_style & WS_VISIBLE); // Regardless of whether mHwnd is visible or not.
  8649.     bool disable_all = (tab_style & WS_DISABLED); // Don't use IsWindowEnabled() because it might return false if parent is disabled?
  8650.     // Say that the focus was already set correctly if the entire tab control is hidden or caller said
  8651.     // not to focus it:
  8652.     bool focus_was_set;
  8653.     bool parent_is_visible = IsWindowVisible(mHwnd);
  8654.     bool parent_is_visible_and_not_minimized = parent_is_visible && !IsIconic(mHwnd);
  8655.     if (hide_all || disable_all)
  8656.         focus_was_set = true;  // Tell the below not to set focus, since all tab controls are hidden or disabled.
  8657.     else if (aFocusFirstControl)  // Note that SetFocus() has an effect even if the parent window is hidden. i.e. next time the window is shown, the control will be focused.
  8658.         focus_was_set = false; // Tell it to focus the first control on the new page.
  8659.     else
  8660.     {
  8661.         HWND focused_hwnd;
  8662.         GuiControlType *focused_control;
  8663.         // If the currently focused control is somewhere in this tab control (but not the tab control
  8664.         // itself, because arrow-key navigation relies on tabs stay focused while the user is pressing
  8665.         // left and right-arrow), override the fact that aFocusFirstControl is false so that when the
  8666.         // page changes, its first control will be focused:
  8667.         focus_was_set = !(   parent_is_visible && (focused_hwnd = GetFocus())
  8668.             && (focused_control = FindControl(focused_hwnd))
  8669.             && focused_control->tab_control_index == aTabControl.tab_index   );
  8670.     }
  8671.  
  8672.     bool will_be_visible, will_be_enabled, has_visible_style, has_enabled_style, member_of_current_tab, control_state_altered;
  8673.     DWORD style;
  8674.     RECT rect, tab_rect;
  8675.     POINT *rect_pt = (POINT *)▭ // i.e. have rect_pt be an array of the two points already within rect.
  8676.  
  8677.     GetWindowRect(aTabControl.hwnd, &tab_rect);
  8678.  
  8679.     // Update: Don't do the below because it causes a tab to look focused even when it isn't in cases
  8680.     // where a control was focused while drawing was suspended.  This is because the below omits the
  8681.     // tab rows themselves from the InvalidateRect() further below:
  8682.     // Tabs on left (TCS_BUTTONS only) require workaround, at least on XP.  Otherwise tab_rect.left will be
  8683.     // much too large.  Because of this, include entire tab rect if it can't be "deflated" reliably:
  8684.     //if (!(tab_style & TCS_VERTICAL) || (tab_style & TCS_RIGHT) || !(tab_style & TCS_BUTTONS))
  8685.     //    TabCtrl_AdjustRect(aTabControl.hwnd, FALSE, &tab_rect); // Reduce it to just the area without the tabs, since the tabs have already been redrawn.
  8686.  
  8687.     // For a likely cleaner transition between tabs, disable redrawing until the switch is complete.
  8688.     // Doing it this way also serves to refresh a tab whose controls have just been disabled via
  8689.     // something like "GuiControl, Disable, MyTab", which would otherwise not happen because unlike
  8690.     // ShowWindow(), EnableWindow() apparently does not cause a repaint to occur.
  8691.     // Fix for v1.0.25.14: Don't send the message below (and its counterpart later on) because that
  8692.     // sometimes or always, as a side-effect, shows the window if it's hidden:
  8693.     if (parent_is_visible_and_not_minimized)
  8694.         SendMessage(mHwnd, WM_SETREDRAW, FALSE, 0);
  8695.     bool invalidate_entire_parent = false; // Set default.
  8696.  
  8697.     // Even if mHwnd is hidden, set styles to Show/Hide and Enable/Disable any controls that need it.
  8698.     for (GuiIndexType u = 0; u < mControlCount; ++u)
  8699.     {
  8700.         // Note aTabControl.tab_index stores aTabControl's tab_control_index (true only for type GUI_CONTROL_TAB).
  8701.         if (mControl[u].tab_control_index != aTabControl.tab_index) // This control is not in this tab control.
  8702.             continue;
  8703.         GuiControlType &control = mControl[u]; // Probably helps performance; certainly improves conciseness.
  8704.         member_of_current_tab = (control.tab_index == curr_tab_index);
  8705.         will_be_visible = !hide_all && member_of_current_tab && !(control.attrib & GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN);
  8706.         will_be_enabled = !disable_all && member_of_current_tab && !(control.attrib & GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED);
  8707.         // Don't use IsWindowVisible() because if the parent window is hidden, I think that will
  8708.         // always say that the controls are hidden too.  In any case, IsWindowVisible() does not
  8709.         // work correctly for this when the window is first shown:
  8710.         style = GetWindowLong(control.hwnd, GWL_STYLE);
  8711.         has_visible_style =  style & WS_VISIBLE;
  8712.         has_enabled_style = !(style & WS_DISABLED);
  8713.         // Showing/hiding/enabling/disabling only when necessary might cut down on redrawing:
  8714.         control_state_altered = false;  // Set default.
  8715.         if (will_be_visible)
  8716.         {
  8717.             if (!has_visible_style)
  8718.             {
  8719.                 ShowWindow(control.hwnd, SW_SHOWNOACTIVATE);
  8720.                 control_state_altered = true;
  8721.             }
  8722.         }
  8723.         else
  8724.             if (has_visible_style)
  8725.             {
  8726.                 ShowWindow(control.hwnd, SW_HIDE);
  8727.                 control_state_altered = true;
  8728.             }
  8729.         if (will_be_enabled)
  8730.         {
  8731.             if (!has_enabled_style)
  8732.             {
  8733.                 EnableWindow(control.hwnd, TRUE);
  8734.                 control_state_altered = true;
  8735.             }
  8736.         }
  8737.         else
  8738.             if (has_enabled_style)
  8739.             {
  8740.                 // Note that it seems to make sense to disable even text/pic/groupbox controls because
  8741.                 // they can receive clicks and double clicks (except GroupBox).
  8742.                 EnableWindow(control.hwnd, FALSE);
  8743.                 control_state_altered = true;
  8744.             }
  8745.  
  8746.         if (control_state_altered)
  8747.         {
  8748.             // If this altered control lies at least partially outside the tab's interior,
  8749.             // set it up to do the full repaint of the parent window:
  8750.             GetWindowRect(control.hwnd, &rect);
  8751.             if (!(PtInRect(&tab_rect, rect_pt[0]) && PtInRect(&tab_rect, rect_pt[1])))
  8752.                 invalidate_entire_parent = true;
  8753.         }
  8754.         // The above's use of show/hide across a wide range of controls may be necessary to support things
  8755.         // such as the dynamic removal of tabs via "GuiControl,, MyTab, |NewTabSet1|NewTabSet2", i.e. if the
  8756.         // newly added removed tab was active, it's controls should now be hidden.
  8757.         // The below sets focus to the first input-capable control, which seems standard for the tab-control
  8758.         // dialogs I've seen.
  8759.         if (!focus_was_set && member_of_current_tab && will_be_visible && will_be_enabled)
  8760.         {
  8761.             switch(control.type)
  8762.             {
  8763.             case GUI_CONTROL_TEXT:
  8764.             case GUI_CONTROL_PIC:
  8765.             case GUI_CONTROL_GROUPBOX:
  8766.             case GUI_CONTROL_PROGRESS:
  8767.             case GUI_CONTROL_MONTHCAL:
  8768.             case GUI_CONTROL_UPDOWN: // It appears that not even non-buddied up-downs can be focused.
  8769.                 break; // Do nothing for the above types because they cannot be focused.
  8770.             default:
  8771.             //case GUI_CONTROL_STATUSBAR: Nothing needs to be done because other logic has ensured it can't be a member of any tab.
  8772.             //case GUI_CONTROL_BUTTON:
  8773.             //case GUI_CONTROL_CHECKBOX:
  8774.             //case GUI_CONTROL_RADIO:
  8775.             //case GUI_CONTROL_DROPDOWNLIST:
  8776.             //case GUI_CONTROL_COMBOBOX:
  8777.             //case GUI_CONTROL_LISTBOX:
  8778.             //case GUI_CONTROL_LISTVIEW:
  8779.             //case GUI_CONTROL_TREEVIEW:
  8780.             //case GUI_CONTROL_EDIT:
  8781.             //case GUI_CONTROL_DATETIME:
  8782.             //case GUI_CONTROL_HOTKEY:
  8783.             //case GUI_CONTROL_SLIDER:
  8784.             //case GUI_CONTROL_TAB:
  8785.                 // Fix for v1.0.24: Don't check the return value of SetFocus() because sometimes it returns
  8786.                 // NULL even when the call will wind up succeeding.  For example, if the user clicks on
  8787.                 // the second tab in a tab control, SetFocus() will probably return NULL because there
  8788.                 // is not previously focused control at the instant the call is made.  This is because
  8789.                 // the control that had focus has likely already been hidden and thus lost focus before
  8790.                 // we arrived at this stage:
  8791.                 SetFocus(control.hwnd); // Note that this has an effect even if the parent window is hidden. i.e. next time the parent is shown, this control will be focused.
  8792.                 focus_was_set = true; // i.e. SetFocus() only for the FIRST control that meets the above criteria.
  8793.             }
  8794.         }
  8795.     }
  8796.  
  8797.     if (parent_is_visible_and_not_minimized) // Fix for v1.0.25.14.  See further above for details.
  8798.         SendMessage(mHwnd, WM_SETREDRAW, TRUE, 0); // Re-enable drawing before below so that tab can be focused below.
  8799.  
  8800.     // In case tab is empty or there is no control capable of receiving focus, focus the tab itself
  8801.     // instead.  This allows the Ctrl-Pgdn/Pgup keyboard shortcuts to continue to navigate within
  8802.     // this tab control rather than having the focus get kicked backed outside the tab control
  8803.     // -- which I think happens when the tab contains no controls or only text controls (pic controls
  8804.     // seem okay for some reason), i.e. if the control with focus is hidden, the dialog falls back to
  8805.     // giving the focus to the the first focus-capable control in the z-order.
  8806.     if (!focus_was_set)
  8807.         SetFocus(aTabControl.hwnd); // Note that this has an effect even if the parent window is hidden. i.e. next time the parent is shown, this control will be focused.
  8808.  
  8809.     // UPDATE: Below is now only done when necessary to cut down on flicker:
  8810.     // Seems best to invalidate the entire client area because otherwise, if any of the tab's controls lie
  8811.     // outside of its interior (this is common for TCS_BUTTONS style), they would not get repainted properly.
  8812.     // In addition, tab controls tend to occupy the majority of their parent's client area anyway:
  8813.     if (parent_is_visible_and_not_minimized)
  8814.     {
  8815.         if (invalidate_entire_parent)
  8816.             InvalidateRect(mHwnd, NULL, TRUE); // TRUE seems safer.
  8817.         else
  8818.         {
  8819.             MapWindowPoints(NULL, mHwnd, (LPPOINT)&tab_rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
  8820.             InvalidateRect(mHwnd, &tab_rect, TRUE); // Seems safer to use TRUE, not knowing all possible overlaps, etc.
  8821.         }
  8822.     }
  8823. }
  8824.  
  8825.  
  8826.  
  8827. GuiControlType *GuiType::FindTabControl(TabControlIndexType aTabControlIndex)
  8828. {
  8829.     if (aTabControlIndex == MAX_TAB_CONTROLS)
  8830.         // This indicates it's not a member of a tab control. Callers rely on this check.
  8831.         return NULL;
  8832.     TabControlIndexType tab_control_index = 0;
  8833.     for (GuiIndexType u = 0; u < mControlCount; ++u)
  8834.         if (mControl[u].type == GUI_CONTROL_TAB)
  8835.             if (tab_control_index == aTabControlIndex)
  8836.                 return &mControl[u];
  8837.             else
  8838.                 ++tab_control_index;
  8839.     return NULL; // Since above didn't return, indicate failure.
  8840. }
  8841.  
  8842.  
  8843.  
  8844. int GuiType::FindTabIndexByName(GuiControlType &aTabControl, char *aName, bool aExactMatch)
  8845. // Find the first tab in this tab control whose leading-part-of-name matches aName.
  8846. // Return int vs. TabIndexType so that failure can be indicated.
  8847. {
  8848.     int tab_count = TabCtrl_GetItemCount(aTabControl.hwnd);
  8849.     if (!tab_count)
  8850.         return -1; // No match.
  8851.     if (!*aName)
  8852.         return 0;  // First item (index 0) matches the empty string.
  8853.  
  8854.     TCITEM tci;
  8855.     tci.mask = TCIF_TEXT;
  8856.     char buf[1024];
  8857.     tci.pszText = buf;
  8858.     tci.cchTextMax = sizeof(buf) - 1; // MSDN example uses -1.
  8859.  
  8860.     size_t aName_length = strlen(aName);
  8861.     if (aName_length >= sizeof(buf)) // Checking this early avoids having to check it in the loop.
  8862.         return -1; // No match possible.
  8863.  
  8864.     for (int i = 0; i < tab_count; ++i)
  8865.     {
  8866.         if (TabCtrl_GetItem(aTabControl.hwnd, i, &tci))
  8867.         {
  8868.             if (aExactMatch)
  8869.             {
  8870.                 if (!strcmp(tci.pszText, aName))  // Match found.
  8871.                     return i;
  8872.             }
  8873.             else
  8874.             {
  8875.                 tci.pszText[aName_length] = '\0'; // Facilitates checking of only the leading part like strncmp(). Buffer overflow is impossible due to a check higher above.
  8876.                 if (!lstrcmpi(tci.pszText, aName)) // Match found.
  8877.                     return i;
  8878.             }
  8879.         }
  8880.     }
  8881.  
  8882.     // Since above didn't return, no match found.
  8883.     return -1;
  8884. }
  8885.  
  8886.  
  8887.  
  8888. int GuiType::GetControlCountOnTabPage(TabControlIndexType aTabControlIndex, TabIndexType aTabIndex)
  8889. {
  8890.     int count = 0;
  8891.     for (GuiIndexType u = 0; u < mControlCount; ++u)
  8892.         if (mControl[u].tab_index == aTabIndex && mControl[u].tab_control_index == aTabControlIndex) // This boolean order helps performance.
  8893.             ++count;
  8894.     return count;
  8895. }
  8896.  
  8897.  
  8898.  
  8899. POINT GuiType::GetPositionOfTabClientArea(GuiControlType &aTabControl)
  8900. // Gets position of tab control relative to parent window's client area.
  8901. {
  8902.     RECT rect, entire_rect;
  8903.     GetWindowRect(aTabControl.hwnd, &entire_rect);
  8904.     POINT pt = {entire_rect.left, entire_rect.top};
  8905.     ScreenToClient(mHwnd, &pt);
  8906.     GetClientRect(aTabControl.hwnd, &rect); // Used because the coordinates of its upper-left corner are (0,0).
  8907.     DWORD style = GetWindowLong(aTabControl.hwnd, GWL_STYLE);
  8908.     // Tabs on left (TCS_BUTTONS only) require workaround, at least on XP.  Otherwise pt.x will be much too large.
  8909.     // This has been confirmed to be true even when theme has been stripped off the tab control.
  8910.     bool workaround = !(style & TCS_RIGHT) && (style & (TCS_VERTICAL | TCS_BUTTONS)) == (TCS_VERTICAL | TCS_BUTTONS);
  8911.     if (workaround)
  8912.         SetWindowLong(aTabControl.hwnd, GWL_STYLE, style & ~TCS_BUTTONS);
  8913.     TabCtrl_AdjustRect(aTabControl.hwnd, FALSE, &rect); // Retrieve the area beneath the tabs.
  8914.     if (workaround)
  8915.     {
  8916.         SetWindowLong(aTabControl.hwnd, GWL_STYLE, style);
  8917.         pt.x += 5 * TabCtrl_GetRowCount(aTabControl.hwnd); // Adjust for the fact that buttons are wider than tabs.
  8918.     }
  8919.     pt.x += rect.left - 2;  // -2 because testing shows that X (but not Y) is off by exactly 2.
  8920.     pt.y += rect.top;
  8921.     return pt;
  8922. }
  8923.  
  8924.  
  8925.  
  8926. ResultType GuiType::SelectAdjacentTab(GuiControlType &aTabControl, bool aMoveToRight, bool aFocusFirstControl
  8927.     , bool aWrapAround)
  8928. {
  8929.     int tab_count = TabCtrl_GetItemCount(aTabControl.hwnd);
  8930.     if (!tab_count)
  8931.         return FAIL;
  8932.  
  8933.     // Fix for v1.0.35: Keyboard navigation of a tab control should still launch the tab's g-label
  8934.     // if it has one.  The following sets the output-var to be the control's previous tab.
  8935.     // For simplicity, this is done unconditionally (i.e. even if the tab will not change because
  8936.     // it's at the min or max and aWrapAround==false):
  8937.     if (aTabControl.jump_to_label && aTabControl.output_var)
  8938.         ControlGetContents(*aTabControl.output_var, aTabControl);
  8939.  
  8940.     int selected_tab = TabCtrl_GetCurSel(aTabControl.hwnd);
  8941.     if (selected_tab == -1) // Not sure how this can happen in this case (since it has at least one tab).
  8942.         selected_tab = aMoveToRight ? 0 : tab_count - 1; // Select the first or last tab.
  8943.     else
  8944.     {
  8945.         if (aMoveToRight) // e.g. Ctrl-PgDn or Ctrl-Tab, right-arrow
  8946.         {
  8947.             ++selected_tab;
  8948.             if (selected_tab >= tab_count) // wrap around to the start
  8949.             {
  8950.                 if (!aWrapAround)
  8951.                     return FAIL; // Indicate that tab was not selected due to non-wrap.
  8952.                 selected_tab = 0;
  8953.             }
  8954.         }
  8955.         else // Ctrl-PgUp or Ctrl-Shift-Tab
  8956.         {
  8957.             --selected_tab;
  8958.             if (selected_tab < 0) // wrap around to the end
  8959.             {
  8960.                 if (!aWrapAround)
  8961.                     return FAIL; // Indicate that tab was not selected due to non-wrap.
  8962.                 selected_tab = tab_count - 1;
  8963.             }
  8964.         }
  8965.     }
  8966.     // MSDN: "A tab control does not send a TCN_SELCHANGING or TCN_SELCHANGE notification message
  8967.     // when a tab is selected using the TCM_SETCURSEL message."
  8968.     TabCtrl_SetCurSel(aTabControl.hwnd, selected_tab);
  8969.     ControlUpdateCurrentTab(aTabControl, aFocusFirstControl);
  8970.  
  8971.     // Fix for v1.0.35: Keyboard navigation of a tab control should still launch the tab's g-label
  8972.     // if it has one:
  8973.     if (aTabControl.jump_to_label) // Its output_var (if any) was already set higher above.
  8974.         Event(GUI_HWND_TO_INDEX(aTabControl.hwnd), TCN_SELCHANGE);
  8975.  
  8976.     return OK;
  8977. }
  8978.  
  8979.  
  8980.  
  8981. void GuiType::ControlGetPosOfFocusedItem(GuiControlType &aControl, POINT &aPoint)
  8982. // Caller has ensured that aControl is the focused control if the window has one.  If not,
  8983. // aControl can be any other control.
  8984. // Based on the control type, the position of the focused subitem within the control is
  8985. // returned (in screen coords) If the control has no focused item, the position of the
  8986. // control's caret (which seems to work okay on all control types, even pictures) is returned.
  8987. {
  8988.     LRESULT index;
  8989.     RECT rect;
  8990.     rect.left = COORD_UNSPECIFIED; // Init to detect whether rect has been set yet.
  8991.  
  8992.     switch (aControl.type)
  8993.     {
  8994.     case GUI_CONTROL_LISTBOX: // Testing shows that GetCaret() doesn't report focused row's position.
  8995.         index = SendMessage(aControl.hwnd, LB_GETCARETINDEX, 0, 0); // Testing shows that only one item at a time can have focus, even when mulitple items are selected.
  8996.         if (index != LB_ERR) //  LB_ERR == -1
  8997.             SendMessage(aControl.hwnd, LB_GETITEMRECT, index, (LPARAM)&rect);
  8998.         // If above didn't get the rect for either reason, a default method is used later below.
  8999.         break;
  9000.  
  9001.     case GUI_CONTROL_LISTVIEW: // Testing shows that GetCaret() doesn't report focused row's position.
  9002.         index = ListView_GetNextItem(aControl.hwnd, -1, LVNI_FOCUSED); // Testing shows that only one item at a time can have focus, even when mulitple items are selected.
  9003.         if (index != -1)
  9004.         {
  9005.             // If the focused item happens to be beneath the viewable area, the context menu gets
  9006.             // displayed beneath the ListView, but this behavior seems okay because of the rarity
  9007.             // and because Windows Explorer behaves the same way.
  9008.             // Don't use the ListView_GetItemRect macro in this case (to cut down on its code size).
  9009.             rect.left = LVIR_LABEL; // Seems better than LVIR_ICON in case icon is on right vs. left side of item.
  9010.             SendMessage(aControl.hwnd, LVM_GETITEMRECT, index, (LPARAM)&rect);
  9011.         }
  9012.         //else a default method is used later below, flagged by rect.left==COORD_UNSPECIFIED.
  9013.         break;
  9014.  
  9015.     case GUI_CONTROL_TREEVIEW: // Testing shows that GetCaret() doesn't report focused row's position.
  9016.         HTREEITEM hitem;
  9017.         if (hitem = TreeView_GetSelection(aControl.hwnd)) // Same as SendMessage(aControl.hwnd, TVM_GETNEXTITEM, TVGN_CARET, NULL).
  9018.             // If the focused item happens to be beneath the viewable area, the context menu gets
  9019.             // displayed beneath the ListView, but this behavior seems okay because of the rarity
  9020.             // and because Windows Explorer behaves the same way.
  9021.             // Don't use the ListView_GetItemRect macro in this case (to cut down on its code size).
  9022.             TreeView_GetItemRect(aControl.hwnd, hitem, &rect, TRUE); // Pass TRUE because caller typically wants to display a context menu, and this gives a more precise location for it.
  9023.         //else a default method is used later below, flagged by rect.left==COORD_UNSPECIFIED.
  9024.         break;
  9025.  
  9026.     case GUI_CONTROL_SLIDER: // GetCaretPos() doesn't retrieve thumb position, so it seems best to do so in case slider is very tall or long.
  9027.         SendMessage(aControl.hwnd, TBM_GETTHUMBRECT, 0, (WPARAM)&rect); // No return value.
  9028.         break;
  9029.     }
  9030.  
  9031.     // Notes about control types not handled above:
  9032.     //case GUI_CONTROL_STATUSBAR: For this and many others below, caller should never call it for this type.
  9033.     //case GUI_CONTROL_TEXT:
  9034.     //case GUI_CONTROL_PIC:
  9035.     //case GUI_CONTROL_GROUPBOX:
  9036.     //case GUI_CONTROL_BUTTON:
  9037.     //case GUI_CONTROL_CHECKBOX:
  9038.     //case GUI_CONTROL_RADIO:
  9039.     //case GUI_CONTROL_DROPDOWNLIST:
  9040.     //case GUI_CONTROL_COMBOBOX:
  9041.     //case GUI_CONTROL_EDIT:         Has it's own context menu.
  9042.     //case GUI_CONTROL_DATETIME:
  9043.     //case GUI_CONTROL_MONTHCAL:     Has it's own context menu. Can't be focused anyway.
  9044.     //case GUI_CONTROL_HOTKEY:
  9045.     //case GUI_CONTROL_UPDOWN:
  9046.     //case GUI_CONTROL_PROGRESS:
  9047.     //case GUI_CONTROL_TAB:  For simplicity, just do basic reporting rather than trying to find pos. of focused tab.
  9048.  
  9049.     if (rect.left == COORD_UNSPECIFIED) // Control's rect hasn't yet been fetched, so fall back to default method.
  9050.     {
  9051.         GetWindowRect(aControl.hwnd, &rect);
  9052.         // Decided againt this since it doesn't seem to matter for any current control types.  If a custom
  9053.         // context menu is ever supported for Edit controls, maybe use GetCaretPos() for them.
  9054.         //GetCaretPos(&aPoint); // For some control types, this might give a more precise/appropriate position than GetWindowRect().
  9055.         //ClientToScreen(aControl.hwnd, &aPoint);
  9056.         //// A little nicer for most control types (such as DateTime) to shift it down a little so that popup/context
  9057.         //// menu or tooltip doesn't fully obstruct the control's contents.
  9058.         //aPoint.y += 10;  // A constant 10 is used because varying it by font doesn't seem worthwhile given that menu/tooltip fonts are of fixed size.
  9059.     }
  9060.     else
  9061.         MapWindowPoints(aControl.hwnd, NULL, (LPPOINT)&rect, 2); // Convert rect from client coords to screen coords.
  9062.  
  9063.     aPoint.x = rect.left;
  9064.     aPoint.y = rect.top + 2 + (rect.bottom - rect.top)/2;  // +2 to shift it down a tad, revealing more of the selected item.
  9065.     // Above: Moving it down a little by default seems desirable 95% of the time to prevent it
  9066.     // from covering up the focused row, the slider's thumb, a datetime's single row, etc.
  9067. }
  9068.  
  9069.  
  9070.  
  9071. struct LV_SortType
  9072. {
  9073.     LVFINDINFO lvfi;
  9074.     LVITEM lvi;
  9075.     HWND hwnd;
  9076.     lv_col_type col;
  9077.     char buf1[LV_TEXT_BUF_SIZE];
  9078.     char buf2[LV_TEXT_BUF_SIZE];
  9079.     bool sort_ascending;
  9080.     bool incoming_is_index;
  9081. };
  9082.  
  9083.  
  9084.  
  9085. int CALLBACK LV_GeneralSort(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
  9086. // ListView sorting by field's text or something derived from the text for each call.
  9087. {
  9088.     LV_SortType &lvs = *(LV_SortType *)lParamSort;
  9089.  
  9090.     // v1.0.44.12: Testing shows that LVM_GETITEMW automatically converts the ANSI contents of our ListView
  9091.     // into Unicode, which is nice because it avoids the overhead and code size of having to call
  9092.     // ToWideChar(), along with the extra/temp buffers it requires to receive the wide version.
  9093.     UINT msg_lvm_getitem = (lvs.col.case_sensitive == SCS_INSENSITIVE_LOGICAL && lvs.col.type == LV_COL_TEXT)
  9094.         ? LVM_GETITEMW : LVM_GETITEM; // Both items above are checked so that SCS_INSENSITIVE_LOGICAL can be effect even for non-text columns because it allows a column to be later changed to TEXT and retain its "logical-sort" setting.
  9095.     // NOTE: It's safe to send a LVITEM struct rather than an LVITEMW with the LVM_GETITEMW message because
  9096.     // the only difference between them is the type "LPWSTR pszText", which is no problem as long as caller
  9097.     // has properly halved cchTextMax to reflect that wide-chars are twice as wide as 8-bit characters.
  9098.  
  9099.     // MSDN: "During the sorting process, the list-view contents are unstable. If the [ListView_SortItems]
  9100.     // callback function sends any messages to the list-view control, the results are unpredictable (aside
  9101.     // from LVM_GETITEM, which is allowed by ListView_SortItemsEx but not ListView_SortItems)."
  9102.     // Since SortItemsEx has become so much more common/available, the doubt about whether the non-Ex
  9103.     // ListView_SortItems actually allows LVM_GETITEM (which it probably does in spite of not being
  9104.     // documented) much less of a concern.
  9105.     // Older: It seems hard to believe that you shouldn't send ANY kind of message because how could you
  9106.     // ever use ListView_SortItems() without either having LVS_OWNERDATA or allocating temp memory for the
  9107.     // entire column (to whose rows lParam would point)?
  9108.     // UPDATE: The following seems to be one alternative:
  9109.     // Do a "virtual qsort" on this column's contents by having qsort() sort an array of row numbers according
  9110.     // to the contents of each particular row's field in that column (i.e. qsort's callback would call LV_GETITEM).
  9111.     // In other words, the array would start off in order (1,2,3) but afterward would contain the proper sort
  9112.     // (e.g. 3,1,2). Next, traverse the array and store the correct "order number" in the corresponding row's
  9113.     // special "lParam container" (for example, 3,1,2 would store 1 in row 3, 2 in row 1, and 3 in row 2, and 4 in...).
  9114.     // Then the ListView can be sorted via a method like the high performance LV_Int32Sort.
  9115.     // However, since the above would require TWO SORTS, it would probably be slower (though the second sort would
  9116.     // require only a tiny fraction of the time of the first).
  9117.     lvs.lvi.pszText = lvs.buf1; // lvi's other members were already set by the caller.
  9118.     if (lvs.incoming_is_index) // Serves to avoid the potentially high performance overhead of ListView_FindItem() where possible.
  9119.     {
  9120.         lvs.lvi.iItem = (int)lParam1;
  9121.         SendMessage(lvs.hwnd, msg_lvm_getitem, 0, (LPARAM)&lvs.lvi); // Use LVM_GETITEM vs. LVM_GETITEMTEXT because MSDN says that only LVM_GETITEM is safe during the sort.
  9122.     }
  9123.     else
  9124.     {
  9125.         // Unfortunately, lParam cannot be used as the index itself because apparently, the sorting
  9126.         // process puts the item indices into a state of flux.  In other words, the indices are
  9127.         // changing while the sort progresses, so it's not possible to use an item's original index
  9128.         // as a way to uniquely identify it.
  9129.         lvs.lvfi.lParam = lParam1;
  9130.         lvs.lvi.iItem = ListView_FindItem(lvs.hwnd, -1, &lvs.lvfi);
  9131.         if (lvs.lvi.iItem < 0) // Not found.  Impossible if caller set the LParam to a unique value.
  9132.             *lvs.buf1 = '\0';
  9133.         else
  9134.             SendMessage(lvs.hwnd, msg_lvm_getitem, 0, (LPARAM)&lvs.lvi);
  9135.     }
  9136.  
  9137.     // Must use lvi.pszText vs. buf because MSDN says (for LVM_GETITEM, but it might also apply to
  9138.     // LVM_GETITEMTEXT even though it isn't documented): "Applications should not assume that the text will
  9139.     // necessarily be placed in the specified buffer. The control may instead change the pszText member
  9140.     // of the structure to point to the new text rather than place it in the buffer."
  9141.     char *field1 = lvs.lvi.pszText; // Save value of pszText in case it no longer points to lvs.buf1.
  9142.  
  9143.     // Fetch Item #2 (see comments in #1 above):
  9144.     lvs.lvi.pszText = lvs.buf2; // lvi's other members were already set by the caller.
  9145.     if (lvs.incoming_is_index)
  9146.     {
  9147.         lvs.lvi.iItem = (int)lParam2;
  9148.         SendMessage(lvs.hwnd, msg_lvm_getitem, 0, (LPARAM)&lvs.lvi); // Use LVM_GETITEM vs. LVM_GETITEMTEXT because MSDN says that only LVM_GETITEM is safe during the sort.
  9149.     }
  9150.     else
  9151.     {
  9152.         // Set any lvfi members not already set by the caller.  Note that lvi.mask was set to LVIF_TEXT by the caller.
  9153.         lvs.lvfi.lParam = lParam2;
  9154.         lvs.lvi.iItem = ListView_FindItem(lvs.hwnd, -1, &lvs.lvfi);
  9155.         if (lvs.lvi.iItem < 0) // Not found.  Impossible if caller set the LParam to a unique value.
  9156.             *lvs.buf2 = '\0';
  9157.         else
  9158.             SendMessage(lvs.hwnd, msg_lvm_getitem, 0, (LPARAM)&lvs.lvi);
  9159.     }
  9160.  
  9161.     // MSDN: "return a negative value if the first item should precede the second"
  9162.     int result;
  9163.     if (lvs.col.type == LV_COL_TEXT)
  9164.     {
  9165.         if (lvs.col.case_sensitive == SCS_INSENSITIVE_LOGICAL) // v1.0.44.12: When this is true, caller has ensured that g_StrCmpLogicalW isn't NULL.
  9166.             result = g_StrCmpLogicalW((LPCWSTR)field1, (LPCWSTR)lvs.lvi.pszText);
  9167.         else
  9168.             result = strcmp2(field1, lvs.lvi.pszText, lvs.col.case_sensitive); // Must not refer to buf1/buf2 directly, see above.
  9169.     }
  9170.     else
  9171.     {
  9172.         // Unlike ACT_SORT, supporting hex for an explicit-floating point column seems far too rare to
  9173.         // justify, hence atof() is used vs. ATOF().  v1.0.46.03: Fixed to sort properly (formerly, it just case the difference to an int, which isn't right).
  9174.         double f1 = atof(field1), f2 = atof(lvs.lvi.pszText); // Must not refer to buf1/buf2 directly, see above.
  9175.         result = (f1 > f2) ? 1 : (f1 == f2 ? 0 : -1);
  9176.     }
  9177.     return lvs.sort_ascending ? result : -result;
  9178. }
  9179.  
  9180.  
  9181.  
  9182. int CALLBACK LV_Int32Sort(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
  9183. {
  9184.     // Caller-provided value of lParamSort is TRUE (non-zero) when ascending order is desired.
  9185.     // MSDN: "return a negative value if the first item should precede the second"
  9186.     return (int)(lParamSort ? (lParam1 - lParam2) : (lParam2 - lParam1));
  9187. }
  9188.  
  9189.  
  9190.  
  9191. void GuiType::LV_Sort(GuiControlType &aControl, int aColumnIndex, bool aSortOnlyIfEnabled, char aForceDirection)
  9192. // aForceDirection should be 'A' to force ascending, 'D' to force ascending, or '\0' to use the column's
  9193. // current default direction.
  9194. {
  9195.     if (aColumnIndex < 0 || aColumnIndex >= LV_MAX_COLUMNS) // Invalid (avoids array access violation).
  9196.         return;
  9197.     lv_attrib_type &lv_attrib = *aControl.union_lv_attrib;
  9198.     lv_col_type &col = lv_attrib.col[aColumnIndex];
  9199.  
  9200.     int item_count = ListView_GetItemCount(aControl.hwnd);
  9201.     if ((col.sort_disabled && aSortOnlyIfEnabled) || item_count < 2) // This column cannot be sorted or doesn't need to be.
  9202.         return; // Below relies on having returned here when control is empty or contains 1 item.
  9203.  
  9204.     // Init any lvs members that are needed by both LV_Int32Sort and the other sorting functions.
  9205.     // The new sort order is determined by the column's primary order unless the user clicked the current
  9206.     // sort-column, in which case the direction is reversed (unless the column is unidirectional):
  9207.     LV_SortType lvs;
  9208.     if (aForceDirection)
  9209.         lvs.sort_ascending = (aForceDirection == 'A');
  9210.     else
  9211.         lvs.sort_ascending = (aColumnIndex == lv_attrib.sorted_by_col && !col.unidirectional)
  9212.             ? !lv_attrib.is_now_sorted_ascending : !col.prefer_descending;
  9213.  
  9214.     // Init those members needed for LVM_GETITEM if it turns out to be needed.  This section
  9215.     // also serves to permanently init cchTextMax for use by the sorting functions too:
  9216.     lvs.lvi.pszText = lvs.buf1;
  9217.     lvs.lvi.cchTextMax = LV_TEXT_BUF_SIZE - 1; // Set default. Subtracts 1 because of that nagging doubt about size vs. length. Some MSDN examples subtract one, such as TabCtrl_GetItem()'s cchTextMax.
  9218.  
  9219.     if (col.type == LV_COL_INTEGER)
  9220.     {
  9221.         // Testing indicates that the following approach is 25 times faster than the general-sort method.
  9222.         // Assign the 32-bit integer as the items lParam at this early stage rather than getting the text
  9223.         // and converting it to an integer for every call of the sort proc.
  9224.         for (lvs.lvi.lParam = 0, lvs.lvi.iItem = 0; lvs.lvi.iItem < item_count; ++lvs.lvi.iItem)
  9225.         {
  9226.             lvs.lvi.mask = LVIF_TEXT;
  9227.             lvs.lvi.iSubItem = aColumnIndex; // Which field to fetch (must be reset each time since it's set to 0 below).
  9228.             lvs.lvi.lParam = SendMessage(aControl.hwnd, LVM_GETITEM, 0, (LPARAM)&lvs.lvi)
  9229.                 ? ATOI(lvs.lvi.pszText) : 0; // Must not refer to lvs.buf1 directly because MSDN says LVM_GETITEMTEXT might have changed pszText to point to some other string.
  9230.             lvs.lvi.mask = LVIF_PARAM;
  9231.             lvs.lvi.iSubItem = 0; // Indicate that an item vs. subitem is being operated on (subitems can't have an lParam).
  9232.             ListView_SetItem(aControl.hwnd, &lvs.lvi);
  9233.         }
  9234.         // Always use non-Ex() for this one because it's likely to perform best due to the lParam setup above.
  9235.         // The value of iSubItem is not reset to aColumnIndex because LV_Int32Sort() doesn't use it.
  9236.         SendMessage(aControl.hwnd, LVM_SORTITEMS, lvs.sort_ascending, (LPARAM)LV_Int32Sort); // Should always succeed since it uses non-Ex() version.
  9237.     }
  9238.     else // It's LV_COL_TEXT or LV_COL_FLOAT.
  9239.     {
  9240.         if (col.type == LV_COL_TEXT && col.case_sensitive == SCS_INSENSITIVE_LOGICAL) // SCS_INSENSITIVE_LOGICAL can be in effect even when type isn't LV_COL_TEXT because it allows a column to be later changed to TEXT and retain its "logical-sort" setting.
  9241.         {
  9242.             // v1.0.44.12: Support logical sorting, which treats numeric strings as true numbers like Windows XP
  9243.             // Explorer's sorting.  This is done here rather than in LV_ModifyCol() because it seems more
  9244.             // maintainable/robust (plus LV_GeneralSort() relies on us to do this check).
  9245.             if (!g_StrCmpLogicalW)
  9246.             {
  9247.                 HINSTANCE hinstLib;
  9248.                 if (hinstLib = LoadLibrary("shlwapi")) // For code simplicity and performance-upon-reuse, once loaded it is never freed.
  9249.                     g_StrCmpLogicalW = (StrCmpLogicalW_type)GetProcAddress(hinstLib, "StrCmpLogicalW");
  9250.             }
  9251.             if (g_StrCmpLogicalW) // Generally, this happens only if OS is older than XP. But OS version isn't checked in case it's possible for older OSes/emultators to ever have StrCmpLogicalW().
  9252.                 lvs.lvi.cchTextMax = lvs.lvi.cchTextMax/2 - 1; // Buffer can hold only half as many Unicode characters as non-Unicode (subtract 1 for the extra-wide NULL terminator).
  9253.             else
  9254.                 col.case_sensitive = SCS_INSENSITIVE_LOCALE; // LV_GeneralSort() relies on this fallback.  Also, it falls back to the LOCALE method because it is the closest match to LOGICAL (since testing shows that StrCmpLogicalW seems to use the user's locale).
  9255.         }
  9256.         // Since LVM_SORTITEMSEX requires comctl32.dll version 5.80+, the non-Ex version is used
  9257.         // whenever the EX version fails to work.  One reason to strongly prefer the Ex version
  9258.         // is that MSDN says the non-Ex version shouldn't query the control during the sort,
  9259.         // which although hard to believe, is a concern.  Therefore:
  9260.         // Try to use the SortEx() method first. If it doesn't work, fall back to the non-Ex method under
  9261.         // the assumption that the OS doesn't support the Ex() method.
  9262.         // Initialize struct members as much as possible so that the sort callback function doesn't have to do it
  9263.         // the many times it's called. Some of the others were already initialized higher above for internal use here.
  9264.         lvs.hwnd = aControl.hwnd;
  9265.         lvs.lvi.iSubItem = aColumnIndex; // Zero-based column index to indicate whether the item or one of its sub-items should be retrieved.
  9266.         lvs.col = col; // Struct copy, which should enhance sorting performance over a pointer.
  9267.         lvs.incoming_is_index = true;
  9268.         lvs.lvi.pszText = NULL; // Serves to detect whether the sort-proc actually ran (it won't if this is Win95 or some other OS that lacks SortEx).
  9269.         lvs.lvi.mask = LVIF_TEXT;
  9270.         SendMessage(aControl.hwnd, LVM_SORTITEMSEX, (WPARAM)&lvs, (LPARAM)LV_GeneralSort);
  9271.         if (!lvs.lvi.pszText)
  9272.         {
  9273.             // Since SortEx() didn't run (above has already ensured that this wasn't because the control is empty),
  9274.             // fall back to Sort() method.
  9275.             // Use a simple sequential lParam to guarantee uniqueness. This must be done every time in case
  9276.             // rows have been inserted/deleted since the last time, in which case uniqueness would not be
  9277.             // certain otherwise:
  9278.             lvs.lvi.iSubItem = 0; // Indicate that an item vs. subitem is to be updated (subitems can't have an lParam).
  9279.             lvs.lvi.mask = LVIF_PARAM; // Indicate which member is to be updated.
  9280.             for (lvs.lvi.lParam = 0, lvs.lvi.iItem = 0; lvs.lvi.iItem < item_count; ++lvs.lvi.lParam, ++lvs.lvi.iItem)
  9281.                 ListView_SetItem(aControl.hwnd, &lvs.lvi);
  9282.             // Initialize struct members as much as possible so that the sort callback function doesn't have to do it
  9283.             // each time it's called.   Some of the others were already initialized higher above for internal use here.
  9284.             lvs.incoming_is_index = false;
  9285.             lvs.lvfi.flags = LVFI_PARAM; // This is the find-method; i.e. the sort function will find each item based on its LPARAM.
  9286.             lvs.lvi.mask = LVIF_TEXT; // Sort proc. uses LVIF_TEXT internally because the PARAM mask only applies to lvfi vs. lvi.
  9287.             lvs.lvi.iSubItem = aColumnIndex; // Zero-based column index to indicate whether the item or one of its sub-items should be retrieved.
  9288.             SendMessage(aControl.hwnd, LVM_SORTITEMS, (WPARAM)&lvs, (LPARAM)LV_GeneralSort);
  9289.         }
  9290.     }
  9291.  
  9292.     // For simplicity, ListView_SortItems()'s return value (TRUE/FALSE) is ignored since it shouldn't
  9293.     // realistically fail.  Just update things to indicate the current sort-column and direction:
  9294.     lv_attrib.sorted_by_col = aColumnIndex;
  9295.     lv_attrib.is_now_sorted_ascending = lvs.sort_ascending;
  9296. }
  9297.  
  9298.  
  9299.  
  9300. DWORD GuiType::ControlGetListViewMode(HWND aWnd)
  9301. // Caller has ensured that aWnd is non-NULL and a valid ListView control.
  9302. // Returns one of the following:
  9303. // LV_VIEW_ICON        0x0000 (LVS_ICON also equals 0x0000)
  9304. // LV_VIEW_DETAILS     0x0001 (LVS_REPORT also equals 0x0001)
  9305. // LV_VIEW_SMALLICON   0x0002 (LVS_SMALLICON also equals 0x0002)
  9306. // LV_VIEW_LIST        0x0003 (LVS_LIST also equals 0x0003)
  9307. // LV_VIEW_TILE        0x0004
  9308. {
  9309.     // On XP or later, use the new method of finding the view so that tile-view can be detected.
  9310.     // Also, the following relies on the fact that LV_VIEW_ICON==LVS_ICON, LV_VIEW_DETAILS==LVS_REPORT,
  9311.     // LVS_SMALLICON==LV_VIEW_SMALLICON, and LVS_LIST==LV_VIEW_LIST.
  9312.     return g_os.IsWinXPorLater() ? ListView_GetView(aWnd) : (GetWindowLong(aWnd, GWL_STYLE) & LVS_TYPEMASK);
  9313. }
  9314.