"Fossies" - the Fresh Open Source Software Archive

Member "AutoHotkey_L-1.1.33.09/source/script_gui.cpp" (8 May 2021, 557574 Bytes) of package /windows/misc/AutoHotkey_L-1.1.33.09.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "script_gui.cpp" see the Fossies "Dox" file reference documentation.

    1 /*
    2 AutoHotkey
    3 
    4 Copyright 2003-2009 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 GuiType *Script::ResolveGui(LPTSTR aBuf, LPTSTR &aCommand, LPTSTR *aName, size_t *aNameLength, LPTSTR aControlID)
   26 {
   27     LPTSTR name_marker = NULL;
   28     size_t name_length = 0;
   29     Line::ConvertGuiName(aBuf, aCommand, &name_marker, &name_length);
   30     
   31     if (!name_marker) // i.e. no name was specified.
   32     {
   33         // v1.1.20: Support omitting the GUI name when specifying a control by HWND.
   34         if (aControlID && IsPureNumeric(aControlID, TRUE, FALSE) == PURE_INTEGER)
   35         {
   36             HWND control_hwnd = (HWND)ATOU64(aControlID);
   37             if (GuiType *pgui = GuiType::FindGuiParent(control_hwnd))
   38                 return pgui;
   39         }
   40 
   41         if (g->GuiDefaultWindowValid())
   42             return g->GuiDefaultWindow;
   43         else if (g->GuiDefaultWindow) // i.e. it contains a Gui name but no valid Gui.
   44             name_marker = g->GuiDefaultWindow->mName;
   45         else
   46             name_marker = _T("1"); // For backward-compatibility.
   47         // Return the default Gui name to our caller in case it wants to create a Gui.
   48         if (aName)
   49             *aName = name_marker; // Caller must copy the name before releasing g->GuiDefaultWindow.
   50         if (aNameLength)
   51             *aNameLength = _tcslen(name_marker);
   52         // GuiDefaultWindowValid() has already searched and determined the Gui does not exist.
   53         return NULL;
   54     }
   55     
   56     // Set defaults: indicate this name can't be used to create a new Gui.
   57     if (aName)
   58         *aName = NULL;
   59     if (aNameLength)
   60         *aNameLength = 0;
   61 
   62     if (!name_length || name_length > MAX_VAR_NAME_LENGTH)
   63         return NULL; // Invalid name.
   64     
   65     // Make a temporary null-terminated copy of the name for use below.
   66     TCHAR name[MAX_VAR_NAME_LENGTH + 1];
   67     tmemcpy(name, name_marker, name_length);
   68     name[name_length] = '\0';
   69     
   70     if (IsPureNumeric(name, TRUE, FALSE) == PURE_INTEGER) // Allow negatives, for flexibility.
   71     {
   72         unsigned __int64 gui_num = ATOU64(name); // Must use ATOU64 because ATOI64 can't handle values larger than _I64_MAX.
   73         if (gui_num < 1 || gui_num > 99 // The range of valid Gui numbers prior to v1.1.03.
   74             || name_length > 2) // Length is also checked because that's how it used to be.
   75         {
   76             // *aName is left as NULL in this case to prevent a new Gui from being created
   77             // with this number as its name if below fails to find a Gui.  Otherwise, it
   78             // might be possible for that Gui's name to conflict with a future Gui's HWND.
   79             return GuiType::FindGui((HWND)gui_num);
   80         }
   81         // Otherwise, it's a number between 1 and 99 which is composed of no more than two
   82         // characters; i.e. it must be treated as a name for backward compatibility reasons.
   83         // ConvertGuiName() already stripped the leading 0 out of something like "01", for
   84         // backward-compatibility.
   85     }
   86 
   87     // Search for the Gui!
   88     GuiType *found_gui = GuiType::FindGui(name);
   89     
   90     // If no Gui with this name exists and aName is not NULL, our caller wants to know what
   91     // name to give a new Gui.  Before returning the name, ensure it is valid.  At the very
   92     // least, space must be outlawed for Gui label names and +OwnerGUINAME.  Requiring the
   93     // name to be valid as a variable name allows for possible future use of the Gui name
   94     // as part of a variable or function name.
   95     if (aName)
   96     {
   97         // found_gui: *aName must be set for GUI_CMD_NEW even if a GUI was found, since
   98         // found_gui will be destroyed (along with found_gui->mName) and recreated. If a
   99         // GUI was found, obviously the name is valid and ValidateName() can be skipped.
  100         if (found_gui || Var::ValidateName(name, false))
  101         {
  102             // This name is okay.
  103             *aName = name_marker;
  104             if (aNameLength)
  105                 *aNameLength = name_length;
  106         }
  107         // Otherwise, leave it set to NULL so our caller knows it is invalid.
  108     }
  109 
  110     return found_gui;
  111 }
  112 
  113 
  114 GuiType *GuiType::FindGui(LPTSTR aName)
  115 {
  116     for (int i = 0; i < g_guiCount; ++i)
  117         if (!_tcsicmp(g_gui[i]->mName, aName))
  118             return g_gui[i];
  119     return NULL;
  120 }
  121 
  122 
  123 GuiType *GuiType::FindGui(HWND aHwnd)
  124 {
  125     // The loop will usually find it on the first iteration since
  126     // the #1 window is default and thus most commonly used.
  127     for (int i = 0; i < g_guiCount; ++i)
  128         if (g_gui[i]->mHwnd == aHwnd)
  129             return g_gui[i];
  130     return NULL;
  131 }
  132 
  133 
  134 GuiType *GuiType::FindGuiParent(HWND aHwnd)
  135 // Returns the GuiType of aHwnd or its closest ancestor which is a Gui.
  136 {
  137     for ( ; aHwnd; aHwnd = GetParent(aHwnd))
  138     {
  139         if (GuiType *gui = FindGui(aHwnd))
  140             return gui;
  141         if (!(GetWindowLong(aHwnd, GWL_STYLE) & WS_CHILD))
  142             break;
  143     }
  144     return NULL;
  145 }
  146 
  147 
  148 GuiType *global_struct::GuiDefaultWindowValid()
  149 {
  150     if (!GuiDefaultWindow)
  151     {
  152         // Default Gui hasn't been set yet for this thread, so find Gui 1.
  153         if (GuiDefaultWindow = GuiType::FindGui(_T("1"))) // Assignment.
  154             GuiDefaultWindow->AddRef();
  155         return GuiDefaultWindow;
  156     }
  157     // Update GuiDefaultWindow if it has been destroyed and recreated.
  158     return GuiType::ValidGui(GuiDefaultWindow);
  159 }
  160 
  161 
  162 GuiType *GuiType::ValidGui(GuiType *&aGuiRef)
  163 {
  164     if (aGuiRef && !aGuiRef->mHwnd) // Gui existed but has been destroyed.
  165     {
  166         if (!*aGuiRef->mName) // v1.1.04: It was an anonymous GUI, so no point keeping it around.
  167         {
  168             aGuiRef->Release();
  169             aGuiRef = NULL;
  170             return NULL;
  171         }
  172         GuiType *recreated_gui;
  173         if (   !(recreated_gui = GuiType::FindGui(aGuiRef->mName))   )
  174             return NULL; // Gui is not valid, so return NULL.
  175         // Gui has been recreated, so update the reference:
  176         recreated_gui->AddRef();
  177         aGuiRef->Release();
  178         aGuiRef = recreated_gui;
  179     }
  180     // Above verified it either points to a valid Gui or is NULL.
  181     return aGuiRef;
  182 }
  183 
  184 
  185 ResultType Script::PerformGui(LPTSTR aBuf, LPTSTR aParam2, LPTSTR aParam3, LPTSTR aParam4)
  186 {
  187     LPTSTR aCommand;    // Set by ResolveGui().
  188     LPTSTR name;        // Set by ResolveGui() if it returns NULL.
  189     size_t name_length; //
  190     GuiType *pgui = ResolveGui(aBuf, aCommand, &name, &name_length);
  191     if (!pgui && !name)
  192         return ScriptError(ERR_INVALID_GUI_NAME, aBuf);
  193     
  194     GuiCommands gui_command = Line::ConvertGuiCommand(aCommand);
  195     if (gui_command == GUI_CMD_INVALID)
  196         // This is caught at load-time 99% of the time and can only occur here if the sub-command name
  197         // or Gui name is contained in a variable reference.
  198         return ScriptError(ERR_PARAM1_INVALID, aCommand);
  199 
  200     PRIVATIZE_S_DEREF_BUF;  // See comments in GuiControl() about this.
  201     ResultType result = OK; // Set default return value for use with all instances of "goto" further below.
  202     // EVERYTHING below this point should use "result" and "goto return_the_result" instead of "return".
  203 
  204     // First completely handle any sub-command that doesn't require the window to exist.
  205     // In other words, don't auto-create the window before doing this command like we do
  206     // for the others:
  207     switch(gui_command)
  208     {
  209     case GUI_CMD_DESTROY:
  210         if (pgui)
  211             result = GuiType::Destroy(*pgui);
  212         goto return_the_result;
  213 
  214     case GUI_CMD_DEFAULT:
  215         if (!pgui)
  216         {
  217             // Create a dummy structure to hold the name.  For simplicity and maintainability,
  218             // a full GuiType structure is constructed.  We can't actually create the Gui yet,
  219             // since that would prevent +Owner%N% from working and possibly break other scripts
  220             // which rely on the old behaviour.
  221             if (  (GuiType::sFont || (GuiType::sFont = (FontType *)malloc(sizeof(FontType) * MAX_GUI_FONTS)))  ) // See similar line below for comments regarding sFont.
  222             if (pgui = new GuiType())
  223             {
  224                 if (pgui->mName = tmalloc(name_length + 1))
  225                 {
  226                     tmemcpy(pgui->mName, name, name_length);
  227                     pgui->mName[name_length] = '\0';
  228                 }
  229                 else
  230                 {
  231                     delete pgui;
  232                     pgui = NULL;
  233                 }
  234             }
  235         }
  236         // Change the "default" member, not g->GuiWindow because that contains the original window
  237         // responsible for launching this thread, which should not be changed because it is used to
  238         // produce the contents of A_Gui.
  239         if (pgui)
  240         {
  241             if (g->GuiDefaultWindow)
  242                 g->GuiDefaultWindow->Release();
  243             pgui->AddRef();
  244             g->GuiDefaultWindow = pgui;
  245         }
  246         else
  247             result = ScriptError(ERR_OUTOFMEM);
  248         goto return_the_result;
  249 
  250     case GUI_CMD_OPTIONS:
  251         // v1.0.43.09:
  252         // Don't overload "+LastFound" because it would break existing scripts that rely on the window
  253         // being created by +LastFound.
  254         if (!_tcsicmp(aCommand, _T("+LastFoundExist")))
  255         {
  256             g->hWndLastUsed = pgui ? pgui->mHwnd : NULL;
  257             goto return_the_result;
  258         }
  259         break;
  260 
  261     case GUI_CMD_NEW: // v1.1.04: Gui, New.
  262         if (_tcschr(aBuf, ':'))
  263         {
  264             if (pgui)
  265             {
  266                 // Caller explicitly asked for a "new" Gui, so destroy this one:
  267                 GuiType::Destroy(*pgui);
  268                 pgui = NULL;
  269             }
  270         }
  271         else
  272         {
  273             // In this case, caller has omitted the name and wants to create an "anonymous" Gui.
  274             pgui = NULL; // Override pgui, which was set to the default Gui (if it exists).
  275             name_length = 0; // Override name_length, which was set to the length of the default Gui name.
  276             //name = _T(""); // Unnecessary since name is not expected to be null-terminated.
  277             // Below will allocate an empty name, for simplicity and maintainability --
  278             // this way, mName is always non-NULL and points to malloc'd memory.
  279         }
  280         break;
  281     }
  282 
  283 
  284     // If the window doesn't currently exist, don't auto-create it for those commands for
  285     // which it wouldn't make sense. Note that things like FONT and COLOR are allowed to
  286     // auto-create the window, since those commands can be legitimately used prior to the
  287     // first "Gui Add" command.  Also, it seems best to allow SHOW even though all it will
  288     // do is create and display an empty window.
  289     if (!pgui)
  290     {
  291         switch(gui_command)
  292         {
  293         case GUI_CMD_SUBMIT:
  294         case GUI_CMD_CANCEL:
  295         case GUI_CMD_FLASH:
  296         case GUI_CMD_MINIMIZE:
  297         case GUI_CMD_MAXIMIZE:
  298         case GUI_CMD_RESTORE:
  299             goto return_the_result; // Nothing needs to be done since the window object doesn't exist.
  300         }
  301 
  302         if (g_guiCount == g_guiCountMax)
  303         {
  304             // g_gui is full or hasn't been allocated yet, so allocate or expand it.   Start at a low
  305             // number since most scripts don't use many Gui windows, and double each time for simplicity
  306             // and to avoid lots of intermediate reallocations if the script creates many Gui windows.
  307             int new_max = g_guiCountMax ? g_guiCountMax * 2 : 8;
  308             GuiType **new_gui_array = (GuiType **)realloc(g_gui, new_max * sizeof(GuiType *));
  309             if (!new_gui_array)
  310             {
  311                 result = FAIL; // No error displayed since extremely rare.
  312                 goto return_the_result;
  313             }
  314             g_gui = new_gui_array;
  315             g_guiCountMax = new_max;
  316         }
  317 
  318         // Otherwise: Create the object and (later) its window, since all the other sub-commands below need it:
  319         for (;;) // For break, to reduce repetition of cleanup-on-failure code.
  320         {
  321             // v1.0.44.14: sFont is created upon first use to conserve ~14 KB memory in non-GUI scripts.
  322             // v1.1.29.00: sFont is created here rather than in FindOrCreateFont(), which is called by
  323             // the constructor below, to avoid the need to add extra logic in several places to detect
  324             // a failed/NULL array.  Previously that was done by simply terminating the script.
  325             if (  (GuiType::sFont || (GuiType::sFont = (FontType *)malloc(sizeof(FontType) * MAX_GUI_FONTS)))  )
  326             if (pgui = new GuiType())
  327             {
  328                 if (pgui->mControl = (GuiControlType *)malloc(GUI_CONTROL_BLOCK_SIZE * sizeof(GuiControlType)))
  329                 {
  330                     if (pgui->mName = tmalloc(name_length + 1))
  331                     {
  332                         tmemcpy(pgui->mName, name, name_length);
  333                         pgui->mName[name_length] = '\0';
  334                         pgui->mControlCapacity = GUI_CONTROL_BLOCK_SIZE;
  335                         g_gui[g_guiCount++] = pgui;
  336                         break;
  337                     }
  338                     free(pgui->mControl);
  339                 }
  340                 delete pgui;
  341             }
  342             result = ScriptError(ERR_OUTOFMEM);
  343             goto return_the_result;
  344         }
  345     }
  346 
  347     GuiType &gui = *pgui;  // For performance.
  348 
  349     // Now handle any commands that should be handled prior to creation of the window in the case
  350     // where the window doesn't already exist:
  351     
  352     LPTSTR options;
  353     if (gui_command == GUI_CMD_OPTIONS)
  354         options = aCommand;
  355     else if (gui_command == GUI_CMD_NEW)
  356         options = aParam2;
  357     else
  358         options = NULL;
  359     
  360     bool set_last_found_window = false;
  361     ToggleValueType own_dialogs = TOGGLE_INVALID;
  362     Var *hwnd_var = NULL;
  363     if (options)
  364         if (!gui.ParseOptions(options, set_last_found_window, own_dialogs, hwnd_var))
  365         {
  366             result = FAIL; // It already displayed the error.
  367             goto return_the_result;
  368         }
  369 
  370     // Create the window if needed.  Since it should not be possible for our window to get destroyed
  371     // without our knowing about it (via the explicit handling in its window proc), it shouldn't
  372     // be necessary to check the result of IsWindow(gui.mHwnd):
  373     if (!gui.mHwnd && !gui.Create())
  374     {
  375         GuiType::Destroy(gui); // Get rid of the object so that it stays in sync with the window's existence.
  376         result = ScriptError(_T("Could not create window."));
  377         goto return_the_result;
  378     }
  379 
  380     if (gui_command == GUI_CMD_NEW) // v1.1.04: Gui, New.  v1.1.08: now also applies to Gui, Name:New.
  381     {
  382         // The following comments are only applicable to Gui, New (anonymous Gui):
  383         // Now that the HWND is known, we could use it as the Gui's name.  However, that isn't
  384         // done because it would allow a Gui to be created using an invalid HWND as a name
  385         // (and that invalid HWND could become valid for some other window, later):
  386         //
  387         //    Gui, New  ; Creates a new Gui and sets it as default.
  388         //    Gui, Destroy  ; Destroys the Gui but does not affect g->GuiDefaultWindow or g->DialogOwner.
  389         //    Gui, +LastFound  ; This would create a Gui using the HWND of the previous one as a name.
  390         //
  391         // Instead, A_Gui returns the HWND when mName is an empty string.
  392 
  393         // Make the new window the default, for convenience:
  394         if (g->GuiDefaultWindow)
  395             g->GuiDefaultWindow->Release();
  396         pgui->AddRef();
  397         g->GuiDefaultWindow = pgui;
  398     }
  399 
  400     if (hwnd_var) // v1.1.04: +HwndVarName option.
  401         hwnd_var->AssignHWND(gui.mHwnd);
  402 
  403     if (options)
  404     {
  405         // After creating the window, apply remaining options:
  406         if (set_last_found_window)
  407             g->hWndLastUsed = gui.mHwnd;
  408         if (own_dialogs != TOGGLE_INVALID) // v1.0.35.06: Plus or minus "OwnDialogs" was present rather than being entirely absent.
  409         {
  410             if (g->DialogOwner)
  411                 g->DialogOwner->Release();
  412             if (own_dialogs == TOGGLED_ON)
  413             {
  414                 gui.AddRef();
  415                 g->DialogOwner = &gui;
  416             }
  417             else
  418                 g->DialogOwner = NULL; // Reset to NULL when "-OwnDialogs" is present.
  419         }
  420         if (gui_command == GUI_CMD_NEW && *aParam3)
  421             SetWindowText(gui.mHwnd, aParam3);
  422         goto return_the_result;
  423     }
  424 
  425     GuiControls gui_control_type = GUI_CONTROL_INVALID;
  426     int index;
  427 
  428     switch (gui_command)
  429     {
  430     case GUI_CMD_ADD:
  431         if (   !(gui_control_type = Line::ConvertGuiControl(aParam2))   )
  432         {
  433             result = ScriptError(ERR_PARAM2_INVALID, aParam2);
  434             goto return_the_result;
  435         }
  436         result = gui.AddControl(gui_control_type, aParam3, aParam4); // It already displayed any error.
  437         goto return_the_result;
  438 
  439     case GUI_CMD_MARGIN:
  440         if (*aParam2)
  441             gui.mMarginX = gui.Scale(ATOI(aParam2)); // Seems okay to allow negative margins.
  442         if (*aParam3)
  443             gui.mMarginY = gui.Scale(ATOI(aParam3)); // Seems okay to allow negative margins.
  444         goto return_the_result;
  445         
  446     case GUI_CMD_MENU:
  447         UserMenu *menu;
  448         if (*aParam2)
  449         {
  450             // By design, the below will give a slightly misleading error if the specified menu is the
  451             // TRAY menu, since it should be obvious that it cannot be used as a menu bar (since it
  452             // must always be of the popup type):
  453             if (   !(menu = FindMenu(aParam2)) || menu == g_script.mTrayMenu   ) // Relies on short-circuit boolean.
  454             {
  455                 result = ScriptError(ERR_MENU, aParam2);
  456                 goto return_the_result;
  457             }
  458             menu->Create(MENU_TYPE_BAR);  // Ensure the menu physically exists and is the "non-popup" type (for a menu bar).
  459         }
  460         else
  461             menu = NULL;
  462         SetMenu(gui.mHwnd, menu ? menu->mMenu : NULL);  // Add or remove the menu.
  463         if (menu) // v1.1.04: Keyboard accelerators.
  464             gui.UpdateAccelerators(*menu);
  465         else
  466             gui.RemoveAccelerators();
  467         goto return_the_result;
  468 
  469     case GUI_CMD_SHOW:
  470         result = gui.Show(aParam2, aParam3);
  471         goto return_the_result;
  472 
  473     case GUI_CMD_SUBMIT:
  474         result = gui.Submit(_tcsicmp(aParam2, _T("NoHide")));
  475         goto return_the_result;
  476 
  477     case GUI_CMD_CANCEL:
  478         result = gui.Cancel();
  479         goto return_the_result;
  480 
  481     case GUI_CMD_MINIMIZE:
  482         // If the window is hidden, it is unhidden as a side-effect (this happens even for SW_SHOWMINNOACTIVE).
  483         ShowWindow(gui.mHwnd, SW_MINIMIZE);
  484         goto return_the_result;
  485 
  486     case GUI_CMD_MAXIMIZE:
  487         ShowWindow(gui.mHwnd, SW_MAXIMIZE); // If the window is hidden, it is unhidden as a side-effect.
  488         goto return_the_result;
  489 
  490     case GUI_CMD_RESTORE:
  491         ShowWindow(gui.mHwnd, SW_RESTORE); // If the window is hidden, it is unhidden as a side-effect.
  492         goto return_the_result;
  493 
  494     case GUI_CMD_FONT:
  495         result = gui.SetCurrentFont(aParam2, aParam3);
  496         goto return_the_result;
  497 
  498     case GUI_CMD_LISTVIEW:
  499     case GUI_CMD_TREEVIEW:
  500         if (*aParam2)
  501         {
  502             GuiIndexType control_index = gui.FindControl(aParam2); // Search on either the control's variable name or its ClassNN.
  503             if (control_index < gui.mControlCount)
  504             {
  505                 GuiControlType &control = gui.mControl[control_index]; // For maintainability, and might slightly reduce code size.
  506                 if (gui_command == GUI_CMD_LISTVIEW)
  507                 {
  508                     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.
  509                         gui.mCurrentListView = &control;
  510                     //else mismatched control type, so just leave it unchanged.
  511                 }
  512                 else // GUI_CMD_TREEVIEW
  513                 {
  514                     if (control.type == GUI_CONTROL_TREEVIEW)
  515                         gui.mCurrentTreeView = &control;
  516                     //else mismatched control type, so just leave it unchanged.
  517                 }
  518             }
  519             //else it seems best never to change it to be "no control" since it doesn't seem to have much use.
  520         }
  521         goto return_the_result;
  522 
  523     case GUI_CMD_TAB:
  524     {
  525         if (*aParam3 // Which tab control. Must be processed prior to Param2 since it might change mCurrentTabControlIndex.
  526             || !*aParam2) // Both the tab control number and the tab number were omitted.
  527         {
  528             if (*aParam3)
  529             {
  530                 index = ATOI(aParam3) - 1;
  531                 if (index < 0 || index > MAX_TAB_CONTROLS - 1)
  532                 {
  533                     result = ScriptError(ERR_PARAM3_INVALID, aParam3);
  534                     goto return_the_result;
  535                 }
  536             }
  537             else
  538                 index = MAX_TAB_CONTROLS; // i.e. "no tab"
  539             
  540             if (gui.mCurrentTabControlIndex != index) // This is checked early in case of early return in the next section due to error.
  541             {
  542                 if (GuiControlType *tab_control = gui.FindTabControl(gui.mCurrentTabControlIndex))
  543                 {
  544                     // Autosize the previous tab control.  Has no effect if it is not a Tab3 control or has
  545                     // already been autosized.  Doing it at this point allows the script to set different
  546                     // margins for inside and outside the tab control, and is simpler than the alternative:
  547                     // waiting until the next external control is added.  The main drawback is that the
  548                     // script is unable to alternate between tab controls and still utilize autosizing.
  549                     // On the flip side, scripts can use this to their advantage -- to add controls which
  550                     // should not affect the tab control's size.
  551                     gui.AutoSizeTabControl(*tab_control);
  552                 }
  553                 gui.mCurrentTabControlIndex = index;
  554                 // Fix for v1.1.23.00: This section was restructured so that the following is done even
  555                 // if both parameters are omitted (fixes the "none at all" condition mentioned below).
  556                 // Fix for v1.0.38.02: Changing to a different tab control (or none at all when there
  557                 // was one before, or vice versa) should start a new radio group:
  558                 gui.mInRadioGroup = false;
  559             }
  560         }
  561         if (*aParam2) // Index or name of a particular tab inside a control.
  562         {
  563             if (!*aParam3 && gui.mCurrentTabControlIndex == MAX_TAB_CONTROLS)
  564                 // Provide a default: the most recently added tab control.  If there are no
  565                 // tab controls, assume the index is the first tab control (i.e. a tab control
  566                 // to be created in the future).  Fix for v1.0.46.16: This section must be done
  567                 // prior to gui.FindTabControl() below because otherwise, a script that does
  568                 // "Gui Tab" will find that a later use of "Gui Tab, TabName" won't work unless
  569                 // the third parameter (which tab control) is explicitly specified.
  570                 gui.mCurrentTabControlIndex = gui.mTabControlCount ? gui.mTabControlCount - 1 : 0;
  571             bool exact_match = !_tcsicmp(aParam4, _T("Exact")); // v1.0.37.03.
  572             // Unlike "GuiControl, Choose", in this case, don't allow negatives since that would just
  573             // generate an error msg further below:
  574             if (!exact_match && IsPureNumeric(aParam2, false, false))
  575             {
  576                 index = ATOI(aParam2) - 1;
  577                 if (index < 0 || index > MAX_TABS_PER_CONTROL - 1)
  578                 {
  579                     result = ScriptError(ERR_PARAM2_INVALID, aParam2);
  580                     goto return_the_result;
  581                 }
  582             }
  583             else
  584             {
  585                 index = -1;  // Set default to be "failure".
  586                 GuiControlType *tab_control = gui.FindTabControl(gui.mCurrentTabControlIndex);
  587                 if (tab_control)
  588                     index = gui.FindTabIndexByName(*tab_control, aParam2, exact_match); // Returns -1 on failure.
  589                 if (index == -1)
  590                 {
  591                     result = ScriptError(_T("Tab name doesn't exist yet."), aParam2);
  592                     goto return_the_result;
  593                 }
  594             }
  595             if (gui.mCurrentTabIndex != index)
  596             {
  597                 gui.mCurrentTabIndex = index;
  598                 gui.mInRadioGroup = false; // A fix for v1.0.38.02, see comments at similar line above.
  599             }
  600         }
  601         goto return_the_result;
  602     }
  603         
  604     case GUI_CMD_COLOR:
  605         // AssignColor() takes care of deleting old brush, etc.
  606         // In this case, a blank for either param means "leaving existing color alone", in which
  607         // case AssignColor() is not called since it would assume CLR_NONE then.
  608         if (*aParam2)
  609             AssignColor(aParam2, gui.mBackgroundColorWin, gui.mBackgroundBrushWin);
  610         if (*aParam3)
  611         {
  612             AssignColor(aParam3, gui.mBackgroundColorCtl, gui.mBackgroundBrushCtl);
  613             // As documented, the following is not done.  Primary reasons:
  614             // 1) Allows any custom color that was explicitly specified via "Gui, Add, ListView, BackgroundGreen"
  615             //    to stay in effect rather than being overridden by this change.  You could argue that this
  616             //    could be detected by asking the control its background color and if it matches the previous
  617             //    mBackgroundColorCtl (which might be CLR_DEFAULT?), it's 99% likely it was not an
  618             //    individual/explicit custom color and thus should be changed here.  But that would be even
  619             //    more complexity so it seems better to keep it simple.
  620             // 2) Reduce code size.
  621             //for (GuiIndexType u = 0; u < gui.mControlCount; ++u)
  622             //  if (gui.mControl[u].type == GUI_CONTROL_LISTVIEW && ListView_GetTextBkColor(..) != prev_bk_color_ctl)
  623             //  {
  624             //      ListView_SetTextBkColor(gui.mControl[u].hwnd, gui.mBackgroundColorCtl);
  625             //      ListView_SetBkColor(gui.mControl[u].hwnd, gui.mBackgroundColorCtl);
  626             //  }
  627             //  ... and probably similar for TREEVIEW.
  628         }
  629         if (IsWindowVisible(gui.mHwnd))
  630             // Force the window to repaint so that colors take effect immediately.
  631             // UpdateWindow() isn't enough sometimes/always, so do something more aggressive:
  632             InvalidateRect(gui.mHwnd, NULL, TRUE);
  633         goto return_the_result;
  634 
  635     case GUI_CMD_FLASH:
  636         // Note that FlashWindowEx() could enable parameters for flashing the window a given number of times
  637         // and at a certain frequency, and other options such as only-taskbar-button or only-caption.
  638         // Set FlashWindowEx() for more ideas:
  639         FlashWindow(gui.mHwnd, _tcsicmp(aParam2, _T("Off")) ? TRUE : FALSE);
  640         goto return_the_result;
  641     
  642     } // switch()
  643 
  644     result = FAIL;  // Should never be reached, but avoids compiler warning and improves bug detection.
  645 
  646 return_the_result:
  647     DEPRIVATIZE_S_DEREF_BUF;
  648     return result;
  649 }
  650 
  651 
  652 
  653 ResultType Line::GuiControl(LPTSTR aCommand, LPTSTR aControlID, LPTSTR aParam3, Var *aParam3Var)
  654 {
  655     GuiType *pgui = Script::ResolveGui(aCommand, aCommand, NULL, 0, aControlID);
  656     GuiControlCmds guicontrol_cmd = Line::ConvertGuiControlCmd(aCommand);
  657     if (guicontrol_cmd == GUICONTROL_CMD_INVALID)
  658         // This is caught at load-time 99% of the time and can only occur here if the sub-command name
  659         // or Gui name is contained in a variable reference.  Since it's so rare, the handling of it is
  660         // debatable, but to keep it simple just set ErrorLevel:
  661         return SetErrorLevelOrThrow();
  662     if (!pgui)
  663         // This departs from the tradition used by PerformGui() but since this type of error is rare,
  664         // and since use ErrorLevel adds a little bit of flexibility (since the script's current thread
  665         // is not unconditionally aborted), this seems best:
  666         return SetErrorLevelOrThrow();
  667 
  668     GuiType &gui = *pgui;  // For performance.
  669     GuiIndexType control_index = gui.FindControl(aControlID);
  670     if (control_index >= gui.mControlCount) // Not found.
  671         return SetErrorLevelOrThrow();
  672     GuiControlType &control = gui.mControl[control_index];   // For performance and convenience.
  673 
  674     // Beyond this point, errors are rare so set the default to "no error":
  675     g_ErrorLevel->Assign(ERRORLEVEL_NONE);
  676 
  677     // Fixed for v1.0.48.04: Some operations on a GUI control can trigger a callback or OnMessage function;
  678     // e.g. SendMessage(control.hwnd, STM_SETIMAGE, ...). Such a function is then likely to change the contents
  679     // of the deref buffer, which would then alter the contents of the parameters used by commands like
  680     // GuiControl.  To prevent that, make the current deref buffer private until this function returns. That
  681     // forces any newly launched callback or OnMessage function to create a new deref buffer if it needs one.
  682     // The main alternative to this method is to make copies of all the parameters and point the parameters to
  683     // the copies.  But since the parameters might be very large, that method could perform much worse and would
  684     // be more complicated, especially since 99.9% of the time, the copies would turn out to be unnecessary
  685     // because the action doesn't wind up triggering any callback or OnMessage function.
  686     PRIVATIZE_S_DEREF_BUF;
  687     ResultType result = OK; // Set default return value for use with all instances of "goto" further below.
  688     // EVERYTHING below this point should use "result" and "goto return_the_result" instead of "return".
  689 
  690     LPTSTR malloc_buf;
  691     RECT rect;
  692     WPARAM checked;
  693     GuiControlType *tab_control;
  694     int new_pos;
  695     SYSTEMTIME st[2];
  696     int selection_index;
  697     bool do_redraw_if_in_tab = false;
  698     bool do_redraw_unconditionally = false;
  699 
  700     switch (guicontrol_cmd)
  701     {
  702 
  703     case GUICONTROL_CMD_OPTIONS:
  704     {
  705         GuiControlOptionsType go; // Its contents not currently used here, but it might be in the future.
  706         gui.ControlInitOptions(go, control);
  707         result = gui.ControlParseOptions(aCommand, go, control, control_index, aParam3Var);
  708         goto return_the_result;
  709     }
  710 
  711     case GUICONTROL_CMD_CONTENTS:
  712     case GUICONTROL_CMD_TEXT:
  713         switch (control.type)
  714         {
  715         case GUI_CONTROL_TEXT:
  716         case GUI_CONTROL_LINK:
  717         case GUI_CONTROL_GROUPBOX:
  718             do_redraw_unconditionally = (control.attrib & GUI_CONTROL_ATTRIB_BACKGROUND_TRANS); // v1.0.40.01.
  719             // Note that it isn't sufficient in this case to do InvalidateRect(control.hwnd, ...).
  720             break;
  721 
  722         case GUI_CONTROL_PIC:
  723         {
  724             // Update: The below doesn't work, so it will be documented that a picture control
  725             // should be always be referred to by its original filename even if the picture changes.
  726             // Set the text unconditionally even if the picture can't be loaded.  This text must
  727             // be set to allow GuiControl(Get) to be able to operate upon the picture without
  728             // needing to identify it via something like "Static14".
  729             //SetWindowText(control.hwnd, aParam3);
  730             //SendMessage(control.hwnd, WM_SETTEXT, 0, (LPARAM)aParam3);
  731 
  732             // Set default options, to be possibly overridden by any options actually present:
  733             // Fixed for v1.0.23: Below should use GetClientRect() vs. GetWindowRect(), otherwise
  734             // a size too large will be returned if the control has a border:
  735             GetClientRect(control.hwnd, &rect);
  736             int width = rect.right - rect.left;
  737             int height = rect.bottom - rect.top;
  738             int icon_number = 0; // Zero means "load icon or bitmap (doesn't matter)".
  739             // Note that setting the control's picture handle to NULL sometimes or always shrinks
  740             // the control down to zero dimensions, so must be done only after the above.
  741 
  742             // Parse any options that are present in front of the filename:
  743             LPTSTR next_option = omit_leading_whitespace(aParam3);
  744             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.
  745             {
  746                 LPTSTR option_end;
  747                 TCHAR orig_char;
  748                 for (; *next_option == '*'; next_option = omit_leading_whitespace(option_end))
  749                 {
  750                     // Find the end of this option item:
  751                     if (   !(option_end = StrChrAny(next_option, _T(" \t")))   )  // Space or tab.
  752                         option_end = next_option + _tcslen(next_option); // Set to position of zero terminator instead.
  753                     // Permanently terminate in between options to help eliminate ambiguity for words contained
  754                     // inside other words, and increase confidence in decimal and hexadecimal conversion.
  755                     orig_char = *option_end;
  756                     *option_end = '\0';
  757                     ++next_option; // Skip over the asterisk.  It might point to a zero terminator now.
  758                     if (!_tcsnicmp(next_option, _T("Icon"), 4))
  759                         icon_number = ATOI(next_option + 4); // LoadPicture() correctly handles any negative value.
  760                     else
  761                     {
  762                         switch (ctoupper(*next_option))
  763                         {
  764                         case 'W':
  765                             width = ATOI(next_option + 1);
  766                             break;
  767                         case 'H':
  768                             height = ATOI(next_option + 1);
  769                             break;
  770                         // If not one of the above, such as zero terminator or a number, just ignore it.
  771                         }
  772                     }
  773 
  774                     *option_end = orig_char; // Undo the temporary termination so that loop's omit_leading() will work.
  775                 } // for() each item in option list
  776 
  777                 // The below assigns option_end + 1 vs. next_option in case the filename is contained in a
  778                 // variable ref and/ that filename contains leading spaces.  Example:
  779                 // GuiControl,, MyPic, *w100 *h-1 %FilenameWithLeadingSpaces%
  780                 // Update: Windows XP and perhaps other OSes will load filenames-containing-leading-spaces
  781                 // even if those spaces are omitted.  However, I'm not sure whether all API calls that
  782                 // use filenames do this, so it seems best to include those spaces whenever possible.
  783                 aParam3 = *option_end ? option_end + 1 : option_end; // Set aParam3 to the start of the image's filespec.
  784             } 
  785             //else options are not present, so do not set aParam3 to be next_option because that would
  786             // omit legitimate spaces and tabs that might exist at the beginning of a real filename (file
  787             // names can start with spaces).
  788 
  789             // See comments in ControlLoadPicture():
  790             if (!gui.ControlLoadPicture(control, aParam3, width, height, icon_number))
  791                 goto error;
  792             
  793             // Fix for v1.0.33.02: If this control belongs to a tab control and is visible (i.e. its page
  794             // in the tab control is the current page), must redraw the tab control to get the picture/icon
  795             // to update correctly.  v1.0.40.01: Pictures such as .Gif sometimes disappear (even if they're
  796             // not in a tab control):
  797             //do_redraw_if_in_tab = true;
  798             do_redraw_unconditionally = true;
  799             break; // Rather than return, continue on to do the redraw.
  800         }
  801 
  802         case GUI_CONTROL_BUTTON:
  803             break;
  804 
  805         case GUI_CONTROL_CHECKBOX:
  806         case GUI_CONTROL_RADIO:
  807             if (guicontrol_cmd == GUICONTROL_CMD_CONTENTS && IsPureNumeric(aParam3, true, false))
  808             {
  809                 checked = ATOI(aParam3);
  810                 if (!checked || checked == 1 || (control.type == GUI_CONTROL_CHECKBOX && checked == -1))
  811                 {
  812                     if (checked == -1)
  813                         checked = BST_INDETERMINATE;
  814                     //else the "checked" var is already set correctly.
  815                     if (control.type == GUI_CONTROL_RADIO)
  816                     {
  817                         gui.ControlCheckRadioButton(control, control_index, checked);
  818                         goto return_the_result;
  819                     }
  820                     // Otherwise, we're operating upon a checkbox.
  821                     SendMessage(control.hwnd, BM_SETCHECK, checked, 0);
  822                     goto return_the_result;
  823                 }
  824                 //else the default SetWindowText() action will be taken below.
  825             }
  826             // else assume it's the text/caption for the item, so the default SetWindowText() action will be taken below.
  827             break; // Fix for v1.0.35.01: Don't return, continue onward.
  828 
  829         case GUI_CONTROL_LISTVIEW:
  830         case GUI_CONTROL_TREEVIEW:
  831             // Due to the fact that an LV's first col. can't be directly deleted and other complexities,
  832             // this is not currently supported (also helps reduce code size).  The built-in function
  833             // for modifying columns should be used instead.  Similar for TreeView.
  834             goto return_the_result;
  835 
  836         case GUI_CONTROL_EDIT:
  837         case GUI_CONTROL_CUSTOM: // Make it edit the default window text
  838             // Note that TranslateLFtoCRLF() will return the original buffer we gave it if no translation
  839             // is needed.  Otherwise, it will return a new buffer which we are responsible for freeing
  840             // when done (or NULL if it failed to allocate the memory).
  841             malloc_buf = (*aParam3 && (GetWindowLong(control.hwnd, GWL_STYLE) & ES_MULTILINE))
  842                 ? TranslateLFtoCRLF(aParam3) : aParam3; // Automatic translation, as documented.
  843             SetWindowText(control.hwnd,  malloc_buf ? malloc_buf : aParam3); // malloc_buf is checked again in case the mem alloc failed.
  844             if (malloc_buf && malloc_buf != aParam3)
  845                 free(malloc_buf);
  846             goto return_the_result;
  847 
  848         case GUI_CONTROL_DATETIME:
  849             if (guicontrol_cmd == GUICONTROL_CMD_CONTENTS)
  850             {
  851                 if (*aParam3)
  852                 {
  853                     if (YYYYMMDDToSystemTime(aParam3, st[0], true))
  854                         DateTime_SetSystemtime(control.hwnd, GDT_VALID, st);
  855                     //else invalid, so leave current sel. unchanged.
  856                 }
  857                 else // User wants there to be no date selection.
  858                 {
  859                     // Ensure the DTS_SHOWNONE style is present, otherwise it won't work.  However,
  860                     // it appears that this style cannot be applied after the control is created, so
  861                     // this line is commented out:
  862                     //SetWindowLong(control.hwnd, GWL_STYLE, GetWindowLong(control.hwnd, GWL_STYLE) | DTS_SHOWNONE);
  863                     DateTime_SetSystemtime(control.hwnd, GDT_NONE, st);  // Contents of st are ignored in this mode.
  864                 }
  865             }
  866             else // GUICONTROL_CMD_TEXT
  867             {
  868                 bool use_custom_format = false; // Set default.
  869                 // Reset style to "pure" so that new style (or custom format) can take effect.
  870                 DWORD style = GetWindowLong(control.hwnd, GWL_STYLE) // DTS_SHORTDATEFORMAT==0 so can be omitted below.
  871                     & ~(DTS_LONGDATEFORMAT | DTS_SHORTDATECENTURYFORMAT | DTS_TIMEFORMAT);
  872                 if (*aParam3)
  873                 {
  874                     // DTS_SHORTDATEFORMAT and DTS_SHORTDATECENTURYFORMAT
  875                     // seem to produce identical results (both display 4-digit year), at least on XP.  Perhaps
  876                     // DTS_SHORTDATECENTURYFORMAT is obsolete.  In any case, it's uncommon so for simplicity, is
  877                     // not a named style.  It can always be applied numerically if desired.  Update:
  878                     // DTS_SHORTDATECENTURYFORMAT is now applied by default upon creation, which can be overridden
  879                     // explicitly via -0x0C in the control's options.
  880                     if (!_tcsicmp(aParam3, _T("LongDate"))) // LongDate seems more readable than "Long".  It also matches the keyword used by FormatTime.
  881                         style |= DTS_LONGDATEFORMAT; // Competing styles were already purged above.
  882                     else if (!_tcsicmp(aParam3, _T("Time")))
  883                         style |= DTS_TIMEFORMAT; // Competing styles were already purged above.
  884                     else // Custom format.
  885                         use_custom_format = true;
  886                 }
  887                 //else aText is blank and use_custom_format==false, which will put DTS_SHORTDATEFORMAT into effect.
  888                 if (!use_custom_format)
  889                     SetWindowLong(control.hwnd, GWL_STYLE, style);
  890                 //else leave style unchanged so that if format is later removed, the underlying named style will
  891                 // not have been altered.
  892                 // This both adds and removes the custom format depending on aParma3:
  893                 DateTime_SetFormat(control.hwnd, use_custom_format ? aParam3 : NULL); // NULL removes any custom format so that the underlying style format is revealed.
  894             }
  895             goto return_the_result;
  896 
  897         case GUI_CONTROL_MONTHCAL:
  898             if (*aParam3)
  899             {
  900                 DWORD gdtr = YYYYMMDDToSystemTime2(aParam3, st);
  901                 if (!gdtr) // Neither min nor max is present (or both are invalid).
  902                     break; // Leave current sel. unchanged.
  903                 if (GetWindowLong(control.hwnd, GWL_STYLE) & MCS_MULTISELECT) // Must use range-selection even if selection is only one date.
  904                 {
  905                     if (gdtr == GDTR_MIN) // No maximum is present, so set maximum to minimum.
  906                         st[1] = st[0];
  907                     //else just max, or both are present.  Assume both for code simplicity.
  908                     MonthCal_SetSelRange(control.hwnd, st);
  909                 }
  910                 else
  911                     MonthCal_SetCurSel(control.hwnd, st);
  912                 //else invalid, so leave current sel. unchanged.
  913                 do_redraw_if_in_tab = true; // Confirmed necessary.
  914                 break;
  915             }
  916             //else blank, so do nothing (control does not support having "no selection").
  917             goto return_the_result; // Don't break since don't the other actions below to be taken.
  918 
  919         case GUI_CONTROL_HOTKEY:
  920             SendMessage(control.hwnd, HKM_SETHOTKEY, gui.TextToHotkey(aParam3), 0); // This will set it to "None" if aParam3 is blank.
  921             goto return_the_result; // Don't break since don't the other actions below to be taken.
  922         
  923         case GUI_CONTROL_UPDOWN:
  924             if (*aParam3 == '+') // Apply as delta from its current position.
  925             {
  926                 new_pos = ATOI(aParam3 + 1);
  927                 // Any out of range or non-numeric value in the buddy is ignored since error reporting is
  928                 // left up to the script, which can compare contents of buddy to those of UpDown to check
  929                 // validity if it wants.
  930                 if (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) // It has a 32-bit vs. 16-bit range.
  931                     new_pos += (int)SendMessage(control.hwnd, UDM_GETPOS32, 0, 0);
  932                 else // 16-bit.  Must cast to short to omit the error portion (see comment above).
  933                     new_pos += (short)SendMessage(control.hwnd, UDM_GETPOS, 0, 0);
  934                 // Above uses +1 to omit the plus sign, which allows a negative delta via +-5.
  935                 // -5 is not treated as a delta because that would be ambiguous with an absolute position.
  936                 // In any case, it seems like too much code to be justified.
  937             }
  938             else
  939                 new_pos = ATOI(aParam3);
  940             // MSDN: "If the parameter is outside the control's specified range, nPos will be set to the nearest
  941             // valid value."
  942             SendMessage(control.hwnd, (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) ? UDM_SETPOS32 : UDM_SETPOS
  943                 , 0, new_pos); // Unnecessary to cast to short in the case of UDM_SETPOS, since it ignores the high-order word.
  944             goto return_the_result; // Don't break since don't the other actions below to be taken.
  945 
  946         case GUI_CONTROL_SLIDER:
  947             // Confirmed this fact from MSDN: That the control automatically deals with out-of-range values
  948             // by setting slider to min or max:
  949             if (*aParam3 == '+') // Apply as delta from its current position.
  950             {
  951                 new_pos = ATOI(aParam3 + 1);
  952                 if (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR)
  953                     new_pos = -new_pos;  // Delta moves to opposite direction if control is inverted.
  954                 SendMessage(control.hwnd, TBM_SETPOS, TRUE
  955                     , SendMessage(control.hwnd, TBM_GETPOS, 0, 0) + new_pos);
  956                 // Above uses +1 to omit the plus sign, which allows a negative delta via +-5.
  957                 // -5 is not treated as a delta because that would be ambiguous with an absolute position.
  958                 // In any case, it seems like too much code to be justified.
  959             }
  960             else
  961                 SendMessage(control.hwnd, TBM_SETPOS, TRUE, gui.ControlInvertSliderIfNeeded(control, ATOI(aParam3)));
  962                 // Above msg has no return value.
  963             goto return_the_result; // Don't break since don't the other actions below to be taken.
  964 
  965         case GUI_CONTROL_PROGRESS:
  966             // Confirmed through testing (PBM_DELTAPOS was also tested): The control automatically deals
  967             // with out-of-range values by setting bar to min or max.  
  968             if (*aParam3 == '+')
  969                 // This allows a negative delta, e.g. via +-5.  Nothing fancier is done since the need
  970                 // to go backwards in a progress bar is rare.
  971                 SendMessage(control.hwnd, PBM_DELTAPOS, ATOI(aParam3 + 1), 0);
  972             else
  973                 SendMessage(control.hwnd, PBM_SETPOS, ATOI(aParam3), 0);
  974             goto return_the_result; // Don't break since don't the other actions below to be taken.
  975             
  976         case GUI_CONTROL_ACTIVEX:
  977             // Don't do anything.
  978             goto return_the_result;
  979 
  980         case GUI_CONTROL_STATUSBAR:
  981             SetWindowText(control.hwnd, aParam3);
  982             goto return_the_result;
  983 
  984         default: // Namely the following:
  985         //case GUI_CONTROL_DROPDOWNLIST:
  986         //case GUI_CONTROL_COMBOBOX:
  987         //case GUI_CONTROL_LISTBOX:
  988         //case GUI_CONTROL_TAB:
  989             if (control.type == GUI_CONTROL_COMBOBOX && guicontrol_cmd == GUICONTROL_CMD_TEXT)
  990             {
  991                 // Fix for v1.0.40.08: Must clear the current selection to avoid Submit/GuiControlGet
  992                 // retrieving it instead of the text that's about to be put into the Edit field.  Note that
  993                 // whatever changes are done here should tested to work with ComboBox's AltSubmit option also.
  994                 // After the next text is added to the Edit field, upon GuiControlGet or "Gui Submit", that
  995                 // text will be checked against the drop-list to see if it matches any of the selections
  996                 // It's done at that stage rather than here because doing it there also solves the issue
  997                 // of the user manually entering a selection into the Edit field and then failing to get
  998                 // the position of the matching item when the ComboBox is set to AltSubmit mode.
  999                 SendMessage(control.hwnd, CB_SETCURSEL, -1, 0);
 1000                 break; // v1.0.38: Fall through to the SetWindowText() method, which works to set combo's edit field.
 1001             }
 1002             // Seems best not to do the below due to the extreme rarity of anyone wanting to change a
 1003             // ListBox or ComboBox's hidden caption.  That can be done via ControlSetText if it is
 1004             // ever needed.  The advantage of not doing this is that the "TEXT" command can be used
 1005             // as a gentle, slight-variant of GUICONTROL_CMDCONTENTS, i.e. without needing to worry
 1006             // what the target control's type is:
 1007             //if (guicontrol_cmd == GUICONTROL_CMD_TEXT)
 1008             //  break;
 1009             if (*aParam3 == gui.mDelimiter) // The signal to overwrite rather than append to the list.
 1010             {
 1011                 ++aParam3;  // Exclude the initial pipe from further consideration.
 1012                 int msg;
 1013                 switch (control.type)
 1014                 {
 1015                 case GUI_CONTROL_TAB: msg = TCM_DELETEALLITEMS; break; // Same as TabCtrl_DeleteAllItems().
 1016                 case GUI_CONTROL_LISTBOX: msg = LB_RESETCONTENT; break;
 1017                 default: // DropDownList or ComboBox
 1018                     msg = CB_RESETCONTENT;
 1019                 }
 1020                 SendMessage(control.hwnd, msg, 0, 0);  // Delete all items currently in the list.
 1021             }
 1022             gui.ControlAddContents(control, aParam3, 0);
 1023             if (control.type == GUI_CONTROL_TAB)
 1024             {
 1025                 // In case the active tab has changed or been deleted, update the tab.
 1026                 // First, update the tab control's dialog (if any) to match its display area.
 1027                 gui.UpdateTabDialog(control.hwnd);
 1028                 // The "false" param will cause focus to jump to first item in z-order if
 1029                 // the control that previously had focus was inside a tab that was just
 1030                 // deleted (seems okay since this kind of operation is fairly rare):
 1031                 gui.ControlUpdateCurrentTab(control, false);
 1032                 // Must invalidate part of parent window to get controls to redraw correctly, at least
 1033                 // in the following case: Tab that is currently active still exists and is still active
 1034                 // after the tab-rebuild done above.  Currently ControlUpdateCurrentTab already does
 1035                 // this, even if the selected tab has not changed or if there is now no tab selected.
 1036                 //InvalidateRect(gui.mHwnd, NULL, TRUE); // TRUE = Seems safer to erase, not knowing all possible overlaps.
 1037             }
 1038             goto return_the_result; // Don't break since don't the other actions below to be taken.
 1039         } // inner switch() for control's type for contents/txt sub-commands.
 1040 
 1041         if (do_redraw_if_in_tab) // Excludes the SetWindowText() below, but might need changing for future control types.
 1042             break;
 1043         // Otherwise:
 1044         // The only other reason it wouldn't have already returned is to fall back to SetWindowText() here.
 1045         // Since above didn't return or break, it's either:
 1046         // 1) A control that uses the standard SetWindowText() method such as GUI_CONTROL_TEXT,
 1047         //    GUI_CONTROL_GROUPBOX, or GUI_CONTROL_BUTTON.
 1048         // 2) A radio or checkbox whose caption is being changed instead of its checked state.
 1049         SetWindowText(control.hwnd, aParam3); // Seems more reliable to set text before doing the redraw, plus it saves code size.
 1050         if (do_redraw_unconditionally)
 1051             break;
 1052         goto return_the_result;
 1053 
 1054     case GUICONTROL_CMD_MOVE:
 1055     case GUICONTROL_CMD_MOVEDRAW:
 1056     {
 1057         int xpos = COORD_UNSPECIFIED;
 1058         int ypos = COORD_UNSPECIFIED;
 1059         int width = COORD_UNSPECIFIED;
 1060         int height = COORD_UNSPECIFIED;
 1061 
 1062         for (LPTSTR cp = aParam3; *cp; ++cp)
 1063         {
 1064             switch(ctoupper(*cp))
 1065             {
 1066             // For options such as W, H, X and Y:
 1067             // Use _ttoi() vs. ATOI() to avoid interpreting something like 0x01B as hex when in fact
 1068             // the B was meant to be an option letter (though in this case, none of the hex digits are
 1069             // currently used as option letters):
 1070             case 'W':
 1071                 width = gui.Scale(_ttoi(cp + 1));
 1072                 break;
 1073             case 'H':
 1074                 height = gui.Scale(_ttoi(cp + 1));
 1075                 break;
 1076             case 'X':
 1077                 xpos = gui.Scale(_ttoi(cp + 1));
 1078                 break;
 1079             case 'Y':
 1080                 ypos = gui.Scale(_ttoi(cp + 1));
 1081                 break;
 1082             }
 1083         }
 1084 
 1085         GetWindowRect(control.hwnd, &rect); // Failure seems too rare to check for.
 1086         POINT dest_pt = {rect.left, rect.top};
 1087         ScreenToClient(GetParent(control.hwnd), &dest_pt); // Set default x/y target position, to be possibly overridden below.
 1088         if (xpos != COORD_UNSPECIFIED)
 1089             dest_pt.x = xpos;
 1090         if (ypos != COORD_UNSPECIFIED)
 1091             dest_pt.y = ypos;
 1092 
 1093         if (!MoveWindow(control.hwnd, dest_pt.x, dest_pt.y
 1094             , width == COORD_UNSPECIFIED ? rect.right - rect.left : width
 1095             , height == COORD_UNSPECIFIED ? rect.bottom - rect.top : height
 1096             , TRUE))  // Do repaint.
 1097             goto error;
 1098 
 1099         // Note that GUI_CONTROL_UPDOWN has no special handling here.  This is because unlike slider buddies,
 1100         // whose only purpose is to label the control, an up-down's is also content-linked to it, so the
 1101         // inability to move the up-down to separate it from its buddy would be a loss of flexibility.  For
 1102         // this reason and also to reduce code size, the control is not re-buddied to snap them together.
 1103         if (control.type == GUI_CONTROL_SLIDER) // It seems buddies don't move automatically, so trigger the move.
 1104         {
 1105             HWND buddy1 = (HWND)SendMessage(control.hwnd, TBM_GETBUDDY, TRUE, 0);
 1106             HWND buddy2 = (HWND)SendMessage(control.hwnd, TBM_GETBUDDY, FALSE, 0);
 1107             if (buddy1)
 1108             {
 1109                 SendMessage(control.hwnd, TBM_SETBUDDY, TRUE, (LPARAM)buddy1);
 1110                 // It doesn't always redraw the buddies correctly, at least on XP, so do it manually:
 1111                 InvalidateRect(buddy1, NULL, TRUE);
 1112             }
 1113             if (buddy2)
 1114             {
 1115                 SendMessage(control.hwnd, TBM_SETBUDDY, FALSE, (LPARAM)buddy2);
 1116                 InvalidateRect(buddy2, NULL, TRUE);
 1117             }
 1118         }
 1119         else if (control.type == GUI_CONTROL_TAB)
 1120         {
 1121             // Check for autosizing flags on this Tab control.
 1122             int mask = (width == COORD_UNSPECIFIED ? 0 : TAB3_AUTOWIDTH)
 1123                 | (height == COORD_UNSPECIFIED ? 0 : TAB3_AUTOHEIGHT);
 1124             int autosize = (int)(INT_PTR)GetProp(control.hwnd, _T("ahk_autosize"));
 1125             if (autosize & mask) // There are autosize flags to unset (implies this is a Tab3 control).
 1126             {
 1127                 autosize &= ~mask; // Unset the flags corresponding to dimensions we've just set.
 1128                 if (autosize)
 1129                     SetProp(control.hwnd, _T("ahk_autosize"), (HANDLE)(INT_PTR)autosize);
 1130                 else
 1131                     RemoveProp(control.hwnd, _T("ahk_autosize"));
 1132             }
 1133         }
 1134 
 1135         // v1.0.41.02: To prevent severe flickering when resizing ListViews and other controls,
 1136         // the MOVE mode now avoids doing the invalidate-rect, but the MOVEDRAW mode does do it.
 1137         if (guicontrol_cmd == GUICONTROL_CMD_MOVEDRAW)
 1138         {
 1139             // This must be done, at least in cases such as GroupBox under certain themes/conditions.
 1140             // More than just control.hwnd must be invalided, otherwise the interior of the GroupBox retains
 1141             // a ghost image of whatever was in it before the move:
 1142             GetWindowRect(control.hwnd, &rect); // Limit it to only that part of the client area that is receiving the rect.
 1143             MapWindowPoints(NULL, gui.mHwnd, (LPPOINT)&rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
 1144             InvalidateRect(gui.mHwnd, &rect, TRUE); // Seems safer to use TRUE, not knowing all possible overlaps, etc.
 1145         }
 1146         goto return_the_result;
 1147     }
 1148 
 1149     case GUICONTROL_CMD_FOCUS:
 1150         if (SetFocus(control.hwnd))
 1151         {
 1152             result = OK;
 1153             goto return_the_result;
 1154         } // else
 1155         goto error;
 1156 
 1157     case GUICONTROL_CMD_ENABLE:
 1158     case GUICONTROL_CMD_DISABLE:
 1159     {
 1160         // GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED is maintained for use with tab controls.  It allows controls
 1161         // on inactive tabs to be marked for later enabling.  It also allows explicitly disabled controls to
 1162         // stay disabled even when their tab/page becomes active. It is updated unconditionally for simplicity
 1163         // and maintainability.  
 1164         if (guicontrol_cmd == GUICONTROL_CMD_ENABLE)
 1165             control.attrib &= ~GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED;
 1166         else
 1167             control.attrib |= GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED;
 1168         if (tab_control = gui.FindTabControl(control.tab_control_index)) // It belongs to a tab control that already exists.
 1169         {
 1170             
 1171             if (GetWindowLong(tab_control->hwnd, GWL_STYLE) & WS_DISABLED) // But its tab control is disabled...
 1172                 goto return_the_result;
 1173             selection_index = TabCtrl_GetCurSel(tab_control->hwnd);
 1174             if (selection_index != control.tab_index && selection_index != -1)
 1175                 // There is no current tab/page or the one selected is not this control's:
 1176                 // Do not disable or re-enable the control in this case.
 1177                 // v1.0.48.04: Above now also checks for -1, which is a tab control containing zero tabs/pages.
 1178                 // The controls on such a tab control might be wrongly/inadvertently visible because
 1179                 // ControlUpdateCurrentTab() isn't capable of handling that situation.  Since fixing
 1180                 // ControlUpdateCurrentTab() would reduce backward compatibility -- and in case anyone is
 1181                 // using tabless tab controls for anything -- it seems best to allow these "wrongly visible"
 1182                 // controls to be explicitly manipulated by GuiControl Enable/Disable and Hide/Show.
 1183                 // UPDATE: ControlUpdateCurrentTab() has been fixed, so the controls won't be visible
 1184                 // unless the script specifically made them so.  The check is kept in case it is of
 1185                 // some use.  Prior to the fix, the controls would have only been visible if the tab
 1186                 // control previously had a tab but it was deleted, since controls are created in a
 1187                 // hidden state if the tab they are on is not selected.
 1188                 goto return_the_result;
 1189         }
 1190         
 1191         // L23: Restrict focus workaround to when the control is/was actually focused. Fixes a bug introduced by L13: enabling or disabling a control caused the active Edit control to reselect its text.
 1192         bool gui_control_was_focused = GetForegroundWindow() == gui.mHwnd && GetFocus() == control.hwnd;
 1193 
 1194         // Since above didn't return, act upon the enabled/disable:
 1195         EnableWindow(control.hwnd, guicontrol_cmd == GUICONTROL_CMD_ENABLE);
 1196         
 1197         // L23: Only if EnableWindow removed the keyboard focus entirely, reset the focus.
 1198         if (gui_control_was_focused && !GetFocus())
 1199             SetFocus(gui.mHwnd);
 1200         
 1201         if (control.type == GUI_CONTROL_TAB) // This control is a tab control.
 1202             // Update the control so that its current tab's controls will all be enabled or disabled (now
 1203             // that the tab control itself has just been enabled or disabled):
 1204             gui.ControlUpdateCurrentTab(control, false);
 1205         goto return_the_result;
 1206     }
 1207 
 1208     case GUICONTROL_CMD_SHOW:
 1209     case GUICONTROL_CMD_HIDE:
 1210         // GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN is maintained for use with tab controls.  It allows controls
 1211         // on inactive tabs to be marked for later showing.  It also allows explicitly hidden controls to
 1212         // stay hidden even when their tab/page becomes active. It is updated unconditionally for simplicity
 1213         // and maintainability.
 1214         if (guicontrol_cmd == GUICONTROL_CMD_SHOW)
 1215             control.attrib &= ~GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN;
 1216         else
 1217             control.attrib |= GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN;
 1218         if (tab_control = gui.FindTabControl(control.tab_control_index)) // It belongs to a tab control that already exists.
 1219         {
 1220             if (!(GetWindowLong(tab_control->hwnd, GWL_STYLE) & WS_VISIBLE)) // But its tab control is hidden...
 1221                 goto return_the_result;
 1222             selection_index = TabCtrl_GetCurSel(tab_control->hwnd);
 1223             if (selection_index != control.tab_index && selection_index != -1)
 1224                 goto return_the_result; // v1.0.48.04: Concerning the line above, see comments in GUICONTROL_CMD_DISABLE.
 1225         }
 1226         // Since above didn't return, act upon the show/hide:
 1227         ShowWindow(control.hwnd, guicontrol_cmd == GUICONTROL_CMD_SHOW ? SW_SHOWNOACTIVATE : SW_HIDE);
 1228         if (control.type == GUI_CONTROL_TAB) // This control is a tab control.
 1229             // Update the control so that its current tab's controls will all be shown or hidden (now
 1230             // that the tab control itself has just been shown or hidden):
 1231             gui.ControlUpdateCurrentTab(control, false);
 1232         goto return_the_result;
 1233 
 1234     case GUICONTROL_CMD_CHOOSE:
 1235     case GUICONTROL_CMD_CHOOSESTRING:
 1236     {
 1237         int extra_actions = 0; // Set default.
 1238         if (*aParam3 == gui.mDelimiter) // First extra action.
 1239         {
 1240             ++aParam3; // Omit this pipe char from further consideration below.
 1241             ++extra_actions;
 1242         }
 1243         if (control.type == GUI_CONTROL_TAB)
 1244         {
 1245             // Generating the TCN_SELCHANGING and TCN_SELCHANGE messages manually is fairly complex since they
 1246             // need a struct and who knows whether it's even valid for sources other than the tab controls
 1247             // themselves to generate them.  I would use TabCtrl_SetCurFocus(), but that is shot down by
 1248             // the fact that it only generates TCN_SELCHANGING and TCN_SELCHANGE if the tab control lacks
 1249             // the TCS_BUTTONS style, which would make it an incomplete/inconsistent solution.  But I guess
 1250             // it's better than nothing as long as it's documented.
 1251             // MSDN: "If the tab control does not have the TCS_BUTTONS style, changing the focus also changes
 1252             // selected tab. In this case, the tab control sends the TCN_SELCHANGING and TCN_SELCHANGE
 1253             // notification messages to its parent window. 
 1254             // Automatically switch to CHOOSESTRING if parameter isn't numeric:
 1255             if (guicontrol_cmd == GUICONTROL_CMD_CHOOSE && !IsPureNumeric(aParam3, true, false))
 1256                 guicontrol_cmd = GUICONTROL_CMD_CHOOSESTRING;
 1257             if (guicontrol_cmd == GUICONTROL_CMD_CHOOSESTRING)
 1258                 selection_index = gui.FindTabIndexByName(control, aParam3); // Returns -1 on failure.
 1259             else
 1260                 selection_index = ATOI(aParam3) - 1;
 1261             if (selection_index < 0 || selection_index > MAX_TABS_PER_CONTROL - 1)
 1262                 goto error;
 1263             int previous_selection_index = TabCtrl_GetCurSel(control.hwnd);
 1264             if (!extra_actions || (GetWindowLong(control.hwnd, GWL_STYLE) & TCS_BUTTONS))
 1265             {
 1266                 if (TabCtrl_SetCurSel(control.hwnd, selection_index) == -1)
 1267                     goto error;
 1268                 // In this case but not the "else" below, must update the tab to show the proper controls:
 1269                 if (previous_selection_index != selection_index)
 1270                     gui.ControlUpdateCurrentTab(control, extra_actions > 0); // And set focus if the more forceful extra_actions was done.
 1271             }
 1272             else // There is an extra_action and it's not TCS_BUTTONS, so extra_action is possible via TabCtrl_SetCurFocus.
 1273             {
 1274                 TabCtrl_SetCurFocus(control.hwnd, selection_index); // No return value, so check for success below.
 1275                 if (TabCtrl_GetCurSel(control.hwnd) != selection_index)
 1276                     goto error;
 1277             }
 1278             goto return_the_result;
 1279         }
 1280         // Otherwise, it's not a tab control, but a ListBox/DropDownList/Combo or other control:
 1281         if (*aParam3 == gui.mDelimiter && control.type != GUI_CONTROL_TAB) // Second extra action.
 1282         {
 1283             ++aParam3; // Omit this pipe char from further consideration below.
 1284             ++extra_actions;
 1285         }
 1286         if (guicontrol_cmd == GUICONTROL_CMD_CHOOSE && !IsPureNumeric(aParam3, true, false)) // Must be done only after the above.
 1287             guicontrol_cmd = GUICONTROL_CMD_CHOOSESTRING;
 1288         UINT msg, x_msg, y_msg;
 1289         switch(control.type)
 1290         {
 1291         case GUI_CONTROL_DROPDOWNLIST:
 1292         case GUI_CONTROL_COMBOBOX:
 1293             msg = (guicontrol_cmd == GUICONTROL_CMD_CHOOSE) ? CB_SETCURSEL : CB_SELECTSTRING;
 1294             x_msg = CBN_SELCHANGE;
 1295             y_msg = CBN_SELENDOK;
 1296             break;
 1297         case GUI_CONTROL_LISTBOX:
 1298             if (GetWindowLong(control.hwnd, GWL_STYLE) & (LBS_EXTENDEDSEL|LBS_MULTIPLESEL))
 1299             {
 1300                 if (guicontrol_cmd == GUICONTROL_CMD_CHOOSE)
 1301                     msg = LB_SETSEL;
 1302                 else
 1303                     // MSDN: Do not use [LB_SELECTSTRING] with a list box that has the LBS_MULTIPLESEL or the
 1304                     // LBS_EXTENDEDSEL styles:
 1305                     msg = LB_FINDSTRING;
 1306             }
 1307             else // single-select listbox
 1308                 if (guicontrol_cmd == GUICONTROL_CMD_CHOOSE)
 1309                     msg = LB_SETCURSEL;
 1310                 else
 1311                     msg = LB_SELECTSTRING;
 1312             x_msg = LBN_SELCHANGE;
 1313             y_msg = LBN_DBLCLK;
 1314             break;
 1315         default:  // Not a supported control type.
 1316             goto error;
 1317         } // switch(control.type)
 1318 
 1319         if (guicontrol_cmd == GUICONTROL_CMD_CHOOSESTRING)
 1320         {
 1321             if (msg == LB_FINDSTRING)
 1322             {
 1323                 // This msg is needed for multi-select listbox because LB_SELECTSTRING is not supported
 1324                 // in this case.
 1325                 LRESULT found_item = SendMessage(control.hwnd, msg, -1, (LPARAM)aParam3);
 1326                 if (found_item == CB_ERR) // CB_ERR == LB_ERR
 1327                     goto error;
 1328                 if (SendMessage(control.hwnd, LB_SETSEL, TRUE, found_item) == CB_ERR) // CB_ERR == LB_ERR
 1329                     goto error;
 1330             }
 1331             else // Fixed 1 to be -1 in v1.0.35.05:
 1332                 if (SendMessage(control.hwnd, msg, -1, (LPARAM)aParam3) == CB_ERR) // CB_ERR == LB_ERR
 1333                     goto error;
 1334         }
 1335         else // Choose by position vs. string.
 1336         {
 1337             selection_index = ATOI(aParam3) - 1;
 1338             if (selection_index < -1)
 1339                 goto error;
 1340             if (msg == LB_SETSEL) // Multi-select, so use the cumulative method.
 1341             {
 1342                 if (SendMessage(control.hwnd, msg, selection_index >= 0, selection_index) == CB_ERR) // CB_ERR == LB_ERR
 1343                     goto error;
 1344             }
 1345             else
 1346                 if (SendMessage(control.hwnd, msg, selection_index, 0) == CB_ERR && selection_index != -1) // CB_ERR == LB_ERR
 1347                     goto error;
 1348         }
 1349         int control_id = GUI_INDEX_TO_ID(control_index);
 1350         if (extra_actions > 0)
 1351             SendMessage(gui.mHwnd, WM_COMMAND, (WPARAM)MAKELONG(control_id, x_msg), (LPARAM)control.hwnd);
 1352         if (extra_actions > 1)
 1353             SendMessage(gui.mHwnd, WM_COMMAND, (WPARAM)MAKELONG(control_id, y_msg), (LPARAM)control.hwnd);
 1354         goto return_the_result;
 1355     } // case
 1356 
 1357     case GUICONTROL_CMD_FONT:
 1358         // Done regardless of USES_FONT_AND_TEXT_COLOR to allow future OSes or common control updates
 1359         // to be given an explicit font, even though it would have no effect currently:
 1360         SendMessage(control.hwnd, WM_SETFONT, (WPARAM)gui.sFont[gui.mCurrentFontIndex].hfont, 0);
 1361         if (USES_FONT_AND_TEXT_COLOR(control.type)) // Must check this to avoid corrupting union_hbitmap.
 1362         {
 1363             if (control.type != GUI_CONTROL_LISTVIEW) // Must check this to avoid corrupting union col attribs.
 1364                 control.union_color = gui.mCurrentColor; // Used by WM_CTLCOLORSTATIC et. al. for some types of controls.
 1365             switch (control.type)
 1366             {
 1367             case GUI_CONTROL_LISTVIEW:
 1368                 ListView_SetTextColor(control.hwnd, gui.mCurrentColor); // Must use gui.mCurrentColor not control.union_color, see above.
 1369                 break;
 1370             case GUI_CONTROL_TREEVIEW:
 1371                 TreeView_SetTextColor(control.hwnd, gui.mCurrentColor);
 1372                 break;
 1373             case GUI_CONTROL_DATETIME:
 1374                 // Since message MCM_SETCOLOR != DTM_SETMCCOLOR, can't combine the two types:
 1375                 DateTime_SetMonthCalColor(control.hwnd, MCSC_TEXT, gui.mCurrentColor); // Hopefully below will revert to default if color is CLR_DEFAULT.
 1376                 break;
 1377             case GUI_CONTROL_MONTHCAL:
 1378                 MonthCal_SetColor(control.hwnd, MCSC_TEXT, gui.mCurrentColor); // Hopefully below will revert to default if color is CLR_DEFAULT.
 1379                 break;
 1380             }
 1381         }
 1382         InvalidateRect(control.hwnd, NULL, TRUE); // Required for refresh, at least for edit controls, probably some others.
 1383         // Note: The DateTime_SetMonthCalFont() macro is not used for GUI_CONTROL_DATETIME because
 1384         // WM_SETFONT+InvalidateRect() above appear to be sufficient for it too.
 1385         goto return_the_result;
 1386     } // switch()
 1387 
 1388     // If the above didn't return, it wants this check:
 1389     if (   do_redraw_unconditionally
 1390         || (tab_control = gui.FindTabControl(control.tab_control_index)) && IsWindowVisible(control.hwnd)   )
 1391     {
 1392         GetWindowRect(control.hwnd, &rect); // Limit it to only that part of the client area that is receiving the rect.
 1393         MapWindowPoints(NULL, gui.mHwnd, (LPPOINT)&rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
 1394         InvalidateRect(gui.mHwnd, &rect, TRUE); // Seems safer to use TRUE, not knowing all possible overlaps, etc.
 1395         //Overkill: InvalidateRect(gui.mHwnd, NULL, FALSE); // Erase doesn't seem to be necessary.
 1396         // None of the following is enough:
 1397         //Changes focused control, so no good: gui.ControlUpdateCurrentTab(*tab_control, false);
 1398         //RedrawWindow(tab_control->hwnd, NULL, NULL, 0 ..or.. RDW_INVALIDATE);
 1399         //InvalidateRect(control.hwnd, NULL, TRUE);
 1400         //InvalidateRect(tab_control->hwnd, NULL, TRUE);
 1401     }
 1402 
 1403 return_the_result:
 1404     DEPRIVATIZE_S_DEREF_BUF;
 1405     return result;
 1406 
 1407 error:
 1408     result = SetErrorLevelOrThrow();
 1409     goto return_the_result;
 1410 }
 1411 
 1412 
 1413 
 1414 ResultType Line::GuiControlGet(LPTSTR aCommand, LPTSTR aControlID, LPTSTR aParam3)
 1415 {
 1416     Var &output_var = *OUTPUT_VAR;
 1417     GuiType *pgui = Script::ResolveGui(aCommand, aCommand, NULL, 0, aControlID);
 1418     GuiControlGetCmds guicontrolget_cmd = Line::ConvertGuiControlGetCmd(aCommand);
 1419     if (guicontrolget_cmd == GUICONTROLGET_CMD_INVALID)
 1420         // This is caught at load-time 99% of the time and can only occur here if the sub-command name
 1421         // or Gui name is contained in a variable reference.  Since it's so rare, the handling of it is
 1422         // debatable, but to keep it simple just set ErrorLevel:
 1423         return SetErrorLevelOrThrow();
 1424     if (!pgui)
 1425         // This departs from the tradition used by PerformGui() but since this type of error is rare,
 1426         // and since use ErrorLevel adds a little bit of flexibility (since the script's current thread
 1427         // is not unconditionally aborted), this seems best:
 1428         return SetErrorLevelOrThrow();
 1429     
 1430     GuiType &gui = *pgui;  // For performance.
 1431     if (!*aControlID) // In this case, default to the name of the output variable, as documented.
 1432         aControlID = output_var.mName;
 1433 
 1434     // Beyond this point, errors are rare so set the default to "no error":
 1435     g_ErrorLevel->Assign(ERRORLEVEL_NONE);
 1436 
 1437     PRIVATIZE_S_DEREF_BUF;  // GuiControlGet() needs this in case it triggers a callback in the script (e.g. subclassing). See also the comments in GuiControl().
 1438     ResultType result = OK; // Set default return value for use with all instances of "goto" further below.
 1439     // EVERYTHING below this point should use "result" and "goto return_the_result" instead of "return".
 1440 
 1441     // Handle GUICONTROLGET_CMD_FOCUS(V) early since it doesn't need a specified ControlID:
 1442     if (guicontrolget_cmd == GUICONTROLGET_CMD_FOCUS || guicontrolget_cmd == GUICONTROLGET_CMD_FOCUSV)
 1443     {
 1444         output_var.Assign(); // Set default to be blank (in case of failure).
 1445         class_and_hwnd_type cah;
 1446         cah.hwnd = GetFocus();
 1447         GuiControlType *pcontrol;
 1448         if (!cah.hwnd || !(pcontrol = gui.FindControl(cah.hwnd))) // Relies on short-circuit boolean order.
 1449             goto error;
 1450         TCHAR focused_control[WINDOW_CLASS_SIZE];
 1451         if (guicontrolget_cmd == GUICONTROLGET_CMD_FOCUSV) // v1.0.43.06.
 1452             // GUI_HWND_TO_INDEX vs FindControl() is enough because FindControl() was already called above:
 1453             GuiType::ControlGetName(pgui, GUI_HWND_TO_INDEX(pcontrol->hwnd), focused_control);
 1454         else // GUICONTROLGET_CMD_FOCUS (ClassNN mode)
 1455         {
 1456             // This section is the same as that in ControlGetFocus():
 1457             cah.class_name = focused_control;
 1458             if (!GetClassName(cah.hwnd, focused_control, _countof(focused_control) - 5)) // -5 to allow room for sequence number.
 1459                 goto error;
 1460             cah.class_count = 0;  // Init for the below.
 1461             cah.is_found = false; // Same.
 1462             EnumChildWindows(gui.mHwnd, EnumChildFindSeqNum, (LPARAM)&cah);
 1463             if (!cah.is_found) // Should be impossible due to FindControl() having already found it above.
 1464                 goto error;
 1465             // Append the class sequence number onto the class name set the output param to be that value:
 1466             sntprintfcat(focused_control, _countof(focused_control), _T("%d"), cah.class_count);
 1467         }
 1468         result = output_var.Assign(focused_control); // And leave ErrorLevel set to NONE.
 1469         goto return_the_result;
 1470     }
 1471 
 1472     GuiIndexType control_index = gui.FindControl(aControlID);
 1473 
 1474     // Fix for v1.1.03: Set output_var only after calling FindControl() so that something like the following will work:  ControlGet Control, Hwnd,, %Control%
 1475     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.
 1476         output_var.Assign(); // Set default to be blank for all commands except POS, for consistency.
 1477 
 1478     if (control_index >= gui.mControlCount) // Not found.
 1479         goto error;
 1480     GuiControlType &control = gui.mControl[control_index];   // For performance and convenience.
 1481 
 1482     switch(guicontrolget_cmd)
 1483     {
 1484     case GUICONTROLGET_CMD_CONTENTS:
 1485         // Because the below returns FAIL only if a critical error occurred, g_ErrorLevel is
 1486         // left at NONE as set above for all cases.
 1487         result = gui.ControlGetContents(output_var, control, aParam3);
 1488         goto return_the_result;
 1489 
 1490     case GUICONTROLGET_CMD_POS:
 1491     {
 1492         // In this case, output_var itself is not used directly, but is instead used to:
 1493         // 1) Help performance by giving us the location in the linked list of variables of
 1494         //    where to find the X/Y/W/H "array elements".
 1495         // 2) Simplify the code by avoiding the need to classify GuiControlGet's param #1
 1496         //    as something that is only sometimes a variable.
 1497         RECT rect;
 1498         GetWindowRect(control.hwnd, &rect);
 1499         POINT pt = {rect.left, rect.top};
 1500         ScreenToClient(gui.mHwnd, &pt);  // Failure seems too rare to check for.
 1501         // Make it longer than Max var name so that FindOrAddVar() will be able to spot and report
 1502         // var names that are too long:
 1503         TCHAR var_name[MAX_VAR_NAME_LENGTH + 20];
 1504         Var *var;
 1505         int always_use = FINDVAR_FOR_PSEUDO_ARRAY(output_var);
 1506         if (   !(var = g_script.FindOrAddVar(var_name
 1507             , sntprintf(var_name, _countof(var_name), _T("%sX"), output_var.mName)
 1508             , always_use))   )
 1509         {
 1510             result = FAIL; // It will have already displayed the error.
 1511             goto return_the_result;
 1512         }
 1513         var->Assign(gui.Unscale(pt.x));
 1514         if (   !(var = g_script.FindOrAddVar(var_name
 1515             , sntprintf(var_name, _countof(var_name), _T("%sY"), output_var.mName)
 1516             , always_use))   )
 1517         {
 1518             result = FAIL; // It will have already displayed the error.
 1519             goto return_the_result;
 1520         }
 1521         var->Assign(gui.Unscale(pt.y));
 1522         if (   !(var = g_script.FindOrAddVar(var_name
 1523             , sntprintf(var_name, _countof(var_name), _T("%sW"), output_var.mName)
 1524             , always_use))   )
 1525         {
 1526             result = FAIL; // It will have already displayed the error.
 1527             goto return_the_result;
 1528         }
 1529         var->Assign(gui.Unscale(rect.right - rect.left));
 1530         if (   !(var = g_script.FindOrAddVar(var_name
 1531             , sntprintf(var_name, _countof(var_name), _T("%sH"), output_var.mName)
 1532             , always_use))   )
 1533         {
 1534             result = FAIL; // It will have already displayed the error.
 1535             goto return_the_result;
 1536         }
 1537         result = var->Assign(gui.Unscale(rect.bottom - rect.top));
 1538         goto return_the_result;
 1539     }
 1540 
 1541     case GUICONTROLGET_CMD_ENABLED:
 1542         // See comment below.
 1543         result = output_var.Assign(IsWindowEnabled(control.hwnd) ? _T("1") : _T("0"));
 1544         goto return_the_result;
 1545 
 1546     case GUICONTROLGET_CMD_VISIBLE:
 1547         // From working on Window Spy, I seem to remember that IsWindowVisible() uses different standards
 1548         // for determining visibility than simply checking for WS_VISIBLE is the control and its parent
 1549         // window.  If so, it might be undocumented in MSDN.  It is mentioned here to explain why
 1550         // this "visible" sub-cmd is kept separate from some figure command such as "GuiControlGet, Out, Style":
 1551         // 1) The style method is cumbersome to script with since it requires bitwise operates afterward.
 1552         // 2) IsVisible() uses a different standard of detection than simply checking WS_VISIBLE.
 1553         result = output_var.Assign(IsWindowVisible(control.hwnd) ? _T("1") : _T("0"));
 1554         goto return_the_result;
 1555 
 1556     case GUICONTROLGET_CMD_HWND: // v1.0.46.16: Although it overlaps with HwndOutputVar, Majkinetor wanted this to help with encapsulation/modularization.
 1557         result = output_var.AssignHWND(control.hwnd); // See also: CONTROLGET_CMD_HWND
 1558         goto return_the_result;
 1559 
 1560     case GUICONTROLGET_CMD_NAME:
 1561     {
 1562         // Assign only a variable name rather than falling back to the control's text like ControlGetName,
 1563         // otherwise the script mightn't be able to distinguish between a control with associated variable
 1564         // and one which merely contains text similar to a variable name:
 1565         if (control.output_var)
 1566             result = output_var.Assign(control.output_var->mName);
 1567         // Otherwise: leave ErrorLevel 0 and output_var blank, indicating this control exists but has no var.
 1568         goto return_the_result;
 1569     }
 1570     } // switch()
 1571 
 1572     result = FAIL;  // Should never be reached, but avoids compiler warning and improves bug detection.
 1573 
 1574 return_the_result:
 1575     DEPRIVATIZE_S_DEREF_BUF;
 1576     return result;
 1577 
 1578 error:
 1579     result = SetErrorLevelOrThrow();
 1580     goto return_the_result;
 1581 }
 1582 
 1583 
 1584 
 1585 /////////////////
 1586 // Static members
 1587 /////////////////
 1588 FontType *GuiType::sFont = NULL; // An array of structs, allocated upon first use.
 1589 int GuiType::sFontCount = 0;
 1590 HWND GuiType::sTreeWithEditInProgress = NULL;
 1591 
 1592 
 1593 
 1594 ResultType GuiType::Destroy(GuiType &gui)
 1595 // Rather than deal with the confusion of an object destroying itself, this method is static
 1596 // and designed to deal with one particular window index in the g_gui array.
 1597 {
 1598     GuiIndexType u;
 1599     int i;
 1600 
 1601     if (gui.mHwnd)
 1602     {
 1603         // First destroy any windows owned by this window, since they will be auto-destroyed
 1604         // anyway due to their being owned.  By destroying them explicitly, the Destroy()
 1605         // function is called recursively which keeps everything relatively neat.
 1606         // Search right to left since Destroy() shifts items to the right of i left by 1:
 1607         for (i = g_guiCount; --i >= 0; )
 1608             if (g_gui[i]->mOwner == gui.mHwnd)
 1609                 GuiType::Destroy(*g_gui[i]);
 1610         // Testing shows that this must be done prior to calling DestroyWindow() later below, presumably
 1611         // because the destruction immediately destroys the status bar, or prevents it from answering messages.
 1612         // This seems at odds with MSDN's comment: "During the processing of [WM_DESTROY], it can be assumed
 1613         // that all child windows still exist".
 1614         if (gui.mStatusBarHwnd) // IsWindow(gui.mStatusBarHwnd) isn't called because even if possible for it to have been destroyed, SendMessage below should return 0.
 1615         {
 1616             // This is done because the vast majority of people wouldn't want to have to worry about it.
 1617             // They can always use DllCall() if they want to share the same HICON among multiple parts of
 1618             // the same bar, or among different windows (fairly rare).
 1619             HICON hicon;
 1620             int part_count = (int)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]".
 1621             for (i = 0; i < part_count; ++i)
 1622                 if (hicon = (HICON)SendMessage(gui.mStatusBarHwnd, SB_GETICON, i, 0))
 1623                     DestroyIcon(hicon);
 1624         }
 1625         if (IsWindow(gui.mHwnd)) // If WM_DESTROY called us, the window might already be partially destroyed.
 1626         {
 1627             // If this window is using a menu bar but that menu is also used by some other window, first
 1628             // detach the menu so that it doesn't get auto-destroyed with the window.  This is done
 1629             // unconditionally since such a menu will be automatically destroyed when the script exits
 1630             // or when the menu is destroyed explicitly via the Menu command.  It also prevents any
 1631             // submenus attached to the menu bar from being destroyed, since those submenus might be
 1632             // also used by other menus (however, this is not really an issue since menus destroyed
 1633             // would be automatically re-created upon next use).  But in the case of a window that
 1634             // is currently using a menu bar, destroying that bar in conjunction with the destruction
 1635             // of some other window might cause bad side effects on some/all OSes.
 1636             ShowWindow(gui.mHwnd, SW_HIDE);  // Hide it to prevent re-drawing due to menu removal.
 1637             SetMenu(gui.mHwnd, NULL);
 1638             if (!gui.mDestroyWindowHasBeenCalled)
 1639             {
 1640                 gui.mDestroyWindowHasBeenCalled = true;  // Signal the WM_DESTROY routine not to call us.
 1641                 DestroyWindow(gui.mHwnd);  // The WindowProc is immediately called and it now destroys the window.
 1642             }
 1643             // else WM_DESTROY was called by a function other than this one (possibly auto-destruct due to
 1644             // being owned by script's main window), so it would be bad to call DestroyWindow() again since
 1645             // it's already in progress.
 1646         }
 1647     } // if (gui.mHwnd)
 1648 
 1649     // Although it is tempting to do this earlier so that any message monitors or window
 1650     // procedure subclasses which might have been triggered above could operate on a new
 1651     // Gui using the same name as this one that's being destroyed, that could break some
 1652     // scripts since A_Gui and A_GuiControl would not be set correctly:
 1653     for (i = g_guiCount; --i >= 0; ) // Search right to left (newest first).
 1654     {
 1655         if (g_gui[i] == &gui)
 1656         {
 1657             // Remove this item by shifting items right of it to the left by 1:
 1658             while (++i < g_guiCount)
 1659                 g_gui[i-1] = g_gui[i];
 1660             --g_guiCount;
 1661             break;
 1662         }
 1663     }
 1664 
 1665     if (gui.mBackgroundBrushWin)
 1666         DeleteObject(gui.mBackgroundBrushWin);
 1667     if (gui.mBackgroundBrushCtl)
 1668         DeleteObject(gui.mBackgroundBrushCtl);
 1669     if (gui.mHdrop)
 1670         DragFinish(gui.mHdrop);
 1671 
 1672     // It seems best to delete the bitmaps whenever the control changes to a new image or
 1673     // whenever the control is destroyed.  Otherwise, if a control or its parent window is
 1674     // destroyed and recreated many times, memory allocation would continue to grow from
 1675     // all the abandoned pointers:
 1676     for (u = 0; u < gui.mControlCount; ++u)
 1677     {
 1678         GuiControlType &control = gui.mControl[u];
 1679         if (control.type == GUI_CONTROL_PIC && control.union_hbitmap)
 1680         {
 1681             if (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR)
 1682                 DestroyIcon((HICON)control.union_hbitmap); // Works on cursors too.  See notes in LoadPicture().
 1683             else // union_hbitmap is a bitmap rather than an icon or cursor.
 1684                 DeleteObject(control.union_hbitmap);
 1685             //else do nothing, since it isn't the right type to have a valid union_hbitmap member.
 1686         }
 1687         else if (control.type == GUI_CONTROL_LISTVIEW) // It was ensured at an earlier stage that union_lv_attrib != NULL.
 1688             free(control.union_lv_attrib);
 1689         control.jump_to_label = NULL; // Release any user-defined object/BoundFunc used as a g-label.
 1690     }
 1691     HICON icon_eligible_for_destruction = gui.mIconEligibleForDestruction;
 1692     HICON icon_eligible_for_destruction_small = gui.mIconEligibleForDestructionSmall;
 1693     if (icon_eligible_for_destruction && icon_eligible_for_destruction != g_script.mCustomIcon) // v1.0.37.07.
 1694         DestroyIconsIfUnused(icon_eligible_for_destruction, icon_eligible_for_destruction_small); // Must be done only after removal from g_gui.
 1695     // For simplicity and performance, any fonts used *solely* by a destroyed window are destroyed
 1696     // only when the program terminates.  Another reason for this is that sometimes a destroyed window
 1697     // is soon recreated to use the same fonts it did before.
 1698     gui.RemoveAccelerators();
 1699     gui.mHwnd = NULL;
 1700     gui.mControlCount = 0; // All child windows (controls) are automatically destroyed with parent.
 1701     free(gui.mControl); // Free the control array, which was previously malloc'd.
 1702     gui.Release(); // After this, the var "gui" is invalid so should not be referenced.
 1703     return OK;
 1704 }
 1705 
 1706 void GuiType::AddRef()
 1707 // Keeps the GuiType structure in memory so that Gui destruction can be detected
 1708 // reliably, but does NOT guarantee that the structure's members will remain valid.
 1709 {
 1710     ++mReferenceCount;
 1711 }
 1712 
 1713 void GuiType::Release()
 1714 {
 1715     if (--mReferenceCount == 0)
 1716     {
 1717         // This should only ever happen if Destroy() has been called and has freed
 1718         // this structure's contents, although Destroy() mightn't necessarily be
 1719         // the current/last caller of this function.
 1720         free(mName); // Free the name, which was previously malloc'd.
 1721         delete this;
 1722     }
 1723 }
 1724 
 1725 
 1726 
 1727 void GuiType::DestroyIconsIfUnused(HICON ahIcon, HICON ahIconSmall)
 1728 // Caller has ensured that the GUI window previously using ahIcon has been destroyed prior to calling
 1729 // this function.
 1730 {
 1731     if (!ahIcon) // Caller relies on this check.
 1732         return;
 1733     for (int i = 0; i < g_guiCount; ++i)
 1734     {
 1735         // If another window is using this icon, don't destroy the because that has been reported to disrupt
 1736         // the window's display of the icon in some cases (apparently WM_SETICON doesn't make a copy of the
 1737         // icon).  The windows still using the icon will be responsible for destroying it later.
 1738         if (g_gui[i]->mIconEligibleForDestruction == ahIcon)
 1739             return;
 1740     }
 1741     // Since above didn't return, this icon is not currently in use by a GUI window.  The caller has
 1742     // authorized us to destroy it.
 1743     DestroyIcon(ahIcon);
 1744     // L17: Small icon should always also be unused at this point.
 1745     if (ahIconSmall != ahIcon)
 1746         DestroyIcon(ahIconSmall);
 1747 }
 1748 
 1749 
 1750 
 1751 ResultType GuiType::Create()
 1752 {
 1753     if (mHwnd) // It already exists
 1754         return FAIL;  // Seems best for now, since it shouldn't really be called this way.
 1755 
 1756     // Use a separate class for GUI, which gives it a separate WindowProc and allows it to be more
 1757     // distinct when used with the ahk_class method of addressing windows.
 1758     static bool sGuiInitialized = false;
 1759     if (!sGuiInitialized)
 1760     {
 1761         WNDCLASSEX wc = {0};
 1762         wc.cbSize = sizeof(wc);
 1763         wc.lpszClassName = WINDOW_CLASS_GUI;
 1764         wc.hInstance = g_hInstance;
 1765         wc.lpfnWndProc = GuiWindowProc;
 1766         wc.hIcon = g_IconLarge;
 1767         wc.hIconSm = g_IconSmall;
 1768         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).
 1769             // 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.
 1770         wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
 1771         wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
 1772         wc.cbWndExtra = DLGWINDOWEXTRA;  // So that it will be the type that uses DefDlgProc() vs. DefWindowProc().
 1773         if (!RegisterClassEx(&wc))
 1774         {
 1775             MsgBox(_T("RegClass")); // Short/generic msg since so rare.
 1776             return FAIL;
 1777         }
 1778         sGuiInitialized = true;
 1779     }
 1780 
 1781     if (!mLabelsHaveBeenSet) // i.e. don't set the defaults if the labels were set prior to the creation of the window.
 1782         SetLabels(NULL);
 1783     // The above is done prior to creating the window so that mLabelForDropFiles can determine
 1784     // whether to add the WS_EX_ACCEPTFILES style.
 1785 
 1786     if (   !(mHwnd = CreateWindowEx(mExStyle, WINDOW_CLASS_GUI, g_script.mFileName, mStyle, 0, 0, 0, 0
 1787         , mOwner, NULL, g_hInstance, NULL))   )
 1788         return FAIL;
 1789 
 1790     // L17: Use separate big/small icons for best results.
 1791     HICON big_icon, small_icon;
 1792     if (g_script.mCustomIcon)
 1793     {
 1794         mIconEligibleForDestruction = big_icon = g_script.mCustomIcon;
 1795         mIconEligibleForDestructionSmall = small_icon = g_script.mCustomIconSmall; // Should always be non-NULL if mCustomIcon is non-NULL.
 1796     }
 1797     else
 1798     {
 1799         big_icon = g_IconLarge;
 1800         small_icon = g_IconSmall;
 1801         // Unlike mCustomIcon, leave mIconEligibleForDestruction NULL because these
 1802         // icons are used for various other purposes and should never be destroyed.
 1803     }
 1804     // Setting the small icon puts it in the upper left corner of the dialog window.
 1805     // Setting the big icon makes the dialog show up correctly in the Alt-Tab menu (but big seems to
 1806     // have no effect unless the window is unowned, i.e. it has a button on the task bar).
 1807     // Change for v1.0.37.07: Set both icons unconditionally for code simplicity, and also in case
 1808     // it's possible for the window to change after creation in a way that would make a custom icon
 1809     // become relevant.  Set the big icon even if it's owned because there might be ways
 1810     // an owned window can have an entry in the alt-tab menu.  The following ways come close
 1811     // but don't actually succeed:
 1812     // 1) It's owned by the main window but the main window isn't visible: It acquires the main window's icon
 1813     //    in the alt-tab menu regardless of whether it was given a big icon of its own.
 1814     // 2) It's owned by another GUI window but it has the WS_EX_APPWINDOW style (might force a taskbar button):
 1815     //    Same effect as in #1.
 1816     // 3) Possibly other ways.
 1817     SendMessage(mHwnd, WM_SETICON, ICON_SMALL, (LPARAM)small_icon); // Testing shows that a zero is returned for both;
 1818     SendMessage(mHwnd, WM_SETICON, ICON_BIG, (LPARAM)big_icon);   // i.e. there is no previous icon to destroy in this case.
 1819 
 1820     return OK;
 1821 }
 1822 
 1823 
 1824 
 1825 LPTSTR GuiType::ConvertEvent(GuiEventType evt)
 1826 {
 1827     static LPTSTR sNames[] = GUI_EVENT_NAMES;
 1828     static TCHAR sBuf[2] = { 0, 0 };
 1829 
 1830     if (evt < GUI_EVENT_FIRST_UNNAMED)
 1831         return sNames[evt];
 1832 
 1833     // Else it's a character code - convert it to a string
 1834     sBuf[0] = (TCHAR)(UCHAR)evt;
 1835     return sBuf;
 1836 }
 1837 
 1838 
 1839 
 1840 IObject* GuiType::CreateDropArray(HDROP hDrop)
 1841 {
 1842     TCHAR buf[T_MAX_PATH]; // T_MAX_PATH vs. MAX_PATH is unlikely to matter if the source of hDrop is the shell as of 2018, but may allow long paths in future OS versions.
 1843     UINT file_count = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
 1844     Object* obj = Object::Create();
 1845     ExprTokenType tok(buf);
 1846     ExprTokenType* pTok = &tok;
 1847 
 1848     for (UINT u = 0; u < file_count; u++)
 1849     {
 1850         DragQueryFile(hDrop, u, buf, _countof(buf));
 1851         obj->InsertAt(u, u+1, &pTok, 1);
 1852     }
 1853 
 1854     return obj;
 1855 }
 1856 
 1857 
 1858 
 1859 void GuiType::SetLabels(LPTSTR aLabelPrefix)
 1860 // v1.0.44.09: Allow custom label prefix to be set; e.g. MyGUI vs. "5Gui" or "2Gui".  This increases flexibility
 1861 // for scripts that dynamically create a varying number of windows, and also allows multiple windows to call the
 1862 // same set of subroutines.
 1863 // This function mustn't assume that mHwnd is a valid window because it might not have been created yet.
 1864 // Caller passes NULL to indicate "use default label prefix" (i.e. the WindowNumber followed by the string "Gui").
 1865 // Caller is responsible for checking mLabelsHaveBeenSet as a pre-condition to calling us, if desired.
 1866 // Caller must ensure that mExStyle is up-to-date if mHwnd is an existing window.  In addition, caller must
 1867 // apply any changes to mExStyle that we make here.
 1868 {
 1869     mLabelsHaveBeenSet = true; // Although it's value only matters in some contexts, it's set unconditionally for simplicity.
 1870 
 1871     #define MAX_GUI_PREFIX_LENGTH 255
 1872     LPTSTR label_suffix;
 1873     TCHAR 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).
 1874     if (aLabelPrefix)
 1875         tcslcpy(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.
 1876     else // Caller is indicating that the defaults should be used.
 1877     {
 1878         if (*mName != '1' || mName[1]) // Prepend the window number for windows other than the first.
 1879             _stprintf(label_name, _T("%sGui"), mName);
 1880         else
 1881             _tcscpy(label_name, _T("Gui"));
 1882     }
 1883     label_suffix = label_name + _tcslen(label_name); // This is the position at which the rest of the label name will be copied.
 1884 
 1885     // Find the label to run automatically when the form closes (if any):
 1886     _tcscpy(label_suffix, _T("Close"));
 1887     mLabelForClose = g_script.FindCallable(label_name, NULL, 1);  // OK if NULL (closing the window is the same as "gui, cancel").
 1888 
 1889     // Find the label to run automatically when the user presses Escape (if any):
 1890     _tcscpy(label_suffix, _T("Escape"));
 1891     mLabelForEscape = g_script.FindCallable(label_name, NULL, 1);  // OK if NULL (pressing ESCAPE does nothing).
 1892 
 1893     // Find the label to run automatically when the user resizes the window (if any):
 1894     _tcscpy(label_suffix, _T("Size"));
 1895     mLabelForSize = g_script.FindCallable(label_name, NULL, 4);  // OK if NULL.
 1896 
 1897     // Find the label to run automatically when the user invokes context menu via AppsKey, Rightclick, or Shift-F10:
 1898     _tcscpy(label_suffix, _T("ContextMenu"));
 1899     mLabelForContextMenu = g_script.FindCallable(label_name, NULL, 6);  // OK if NULL (leaves context menu unhandled).
 1900 
 1901     // Find the label to run automatically when files are dropped onto the window:
 1902     _tcscpy(label_suffix, _T("DropFiles"));
 1903     if ((mLabelForDropFiles = g_script.FindCallable(label_name, NULL, 5))  // OK if NULL (dropping files is disallowed).
 1904         && !mHdrop) // i.e. don't allow user to visibly drop files onto window if a drop is already queued or running.
 1905         mExStyle |= WS_EX_ACCEPTFILES; // Makes the window accept drops. Otherwise, the WM_DROPFILES msg is not received.
 1906     else
 1907         mExStyle &= ~WS_EX_ACCEPTFILES;
 1908     // It is not necessary to apply any style change made above because the caller detects changes and applies them.
 1909 }
 1910 
 1911 
 1912 
 1913 void GuiType::UpdateMenuBars(HMENU aMenu)
 1914 // Caller has changed aMenu and wants the change visibly reflected in any windows that that
 1915 // use aMenu as a menu bar.  For example, if a menu item has been disabled, the grey-color
 1916 // won't show up immediately unless the window is refreshed.
 1917 {
 1918     for (int i = 0; i < g_guiCount; ++i)
 1919     {
 1920         //if (g_gui[i]->mHwnd) // Always non-NULL for any item in g_gui.
 1921         if (GetMenu(g_gui[i]->mHwnd) == aMenu && IsWindowVisible(g_gui[i]->mHwnd))
 1922         {
 1923             // Neither of the below two calls by itself is enough for all types of changes.
 1924             // Thought it's possible that every type of change only needs one or the other, both
 1925             // are done for simplicity:
 1926             // This first line is necessary at least for cases where the height of the menu bar
 1927             // (the number of rows needed to display all its items) has changed as a result
 1928             // of the caller's change.  In addition, I believe SetWindowPos() must be called
 1929             // before RedrawWindow() to prevent artifacts in some cases:
 1930             SetWindowPos(g_gui[i]->mHwnd, NULL, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
 1931             // This line is necessary at least when a single new menu item has been added:
 1932             RedrawWindow(g_gui[i]->mHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW);
 1933             // RDW_UPDATENOW: Seems best so that the window is in an visible updated state when function
 1934             // returns.  This is because if the menu bar happens to be two rows or its size is changed
 1935             // in any other way, the window dimensions themselves might change, and the caller might
 1936             // rely on such a change being visibly finished for PixelGetColor, etc.
 1937             //Not enough: UpdateWindow(g_gui[i]->mHwnd);
 1938         }
 1939     }
 1940 }
 1941 
 1942 
 1943 
 1944 ResultType GuiType::AddControl(GuiControls aControlType, LPTSTR aOptions, LPTSTR aText)
 1945 // Caller must have ensured that mHwnd is non-NULL (i.e. that the parent window already exists).
 1946 {
 1947     #define TOO_MANY_CONTROLS _T("Too many controls.") // Short msg since so rare.
 1948     if (mControlCount >= MAX_CONTROLS_PER_GUI)
 1949         return g_script.ScriptError(TOO_MANY_CONTROLS);
 1950     if (mControlCount >= mControlCapacity) // The section below on the above check already having been done.
 1951     {
 1952         // realloc() to keep the array contiguous, which allows better-performing methods to be
 1953         // used to access the list of controls in various places.
 1954         // Expand the array by one block:
 1955         GuiControlType *realloc_temp;  // Needed since realloc returns NULL on failure but leaves original block allocated.
 1956         if (   !(realloc_temp = (GuiControlType *)realloc(mControl  // If passed NULL, realloc() will do a malloc().
 1957             , (mControlCapacity + GUI_CONTROL_BLOCK_SIZE) * sizeof(GuiControlType)))   ) 
 1958             return g_script.ScriptError(TOO_MANY_CONTROLS); // A non-specific msg since this error is so rare.
 1959         mControl = realloc_temp;
 1960         mControlCapacity += GUI_CONTROL_BLOCK_SIZE;
 1961     }
 1962 
 1963     ////////////////////////////////////////////////////////////////////////////////////////
 1964     // Set defaults for the various options, to be overridden individually by any specified.
 1965     ////////////////////////////////////////////////////////////////////////////////////////
 1966     GuiControlType &control = mControl[mControlCount];
 1967     ZeroMemory(&control, sizeof(GuiControlType));
 1968     
 1969     GuiControlOptionsType opt;
 1970     ControlInitOptions(opt, control);
 1971     // aOpt.checked is already okay since BST_UNCHECKED == 0
 1972     // Similarly, the zero-init of "control" higher above set the right values for password_char, new_section, etc.
 1973 
 1974     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.
 1975     {
 1976         aControlType = GUI_CONTROL_TAB;
 1977         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.
 1978     }
 1979     else if (aControlType == GUI_CONTROL_TAB3)
 1980     {
 1981         aControlType = GUI_CONTROL_TAB;
 1982         opt.tab_control_uses_dialog = true;
 1983         opt.tab_control_autosize = TAB3_AUTOWIDTH | TAB3_AUTOHEIGHT;
 1984     }
 1985     if (aControlType == GUI_CONTROL_TAB)
 1986     {
 1987         if (mTabControlCount == MAX_TAB_CONTROLS)
 1988             return g_script.ScriptError(_T("Too many tab controls.")); // Short msg since so rare.
 1989         // For now, don't allow a tab control to be create inside another tab control because it raises
 1990         // doubt and probably would create complications.  If it ever is allowed, note that
 1991         // control.tab_index stores this tab control's index (0 for the first tab control, 1 for the
 1992         // second, etc.) -- this is done for performance reasons.
 1993         control.tab_control_index = MAX_TAB_CONTROLS;
 1994         control.tab_index = mTabControlCount; // Store its control-index to help look-up performance in other sections.
 1995         // Autosize any previous tab control, since this new one is going to become current.
 1996         // This is done here rather than when mCurrentTabControlIndex is actually changed later,
 1997         // because this new control's positioning might be dependent on the previous control's size.
 1998         if (GuiControlType *previous_tab_control = FindTabControl(mCurrentTabControlIndex))
 1999         {
 2000             // See "case GUI_CMD_TAB:" for more comments.
 2001             AutoSizeTabControl(*previous_tab_control);
 2002         }
 2003     }
 2004     else if (aControlType == GUI_CONTROL_STATUSBAR)
 2005     {
 2006         if (mStatusBarHwnd)
 2007             return g_script.ScriptError(_T("Too many status bars.")); // Short msg since so rare.
 2008         control.tab_control_index = MAX_TAB_CONTROLS; // Indicate that bar isn't owned by any tab control.
 2009         // No need to do the following because ZeroMem did it:
 2010         //control.tab_index = 0; // Ignored but set for maintainability/consistency.
 2011     }
 2012     else
 2013     {
 2014         control.tab_control_index = mCurrentTabControlIndex;
 2015         control.tab_index = mCurrentTabIndex;
 2016     }
 2017 
 2018     // If this is the first control, set the default margin for the window based on the size
 2019     // of the current font, but only if the margins haven't already been set:
 2020     if (!mControlCount)
 2021     {
 2022         if (mMarginX == COORD_UNSPECIFIED)
 2023             mMarginX = DPIScale((int)(1.25 * sFont[mCurrentFontIndex].point_size));  // Seems to be a good rule of thumb.
 2024         if (mMarginY == COORD_UNSPECIFIED)
 2025             mMarginY = DPIScale((int)(0.75 * sFont[mCurrentFontIndex].point_size));  // Also seems good.
 2026         mPrevX = mMarginX;  // This makes first control be positioned correctly if it lacks both X & Y coords.
 2027     }
 2028 
 2029     control.type = aControlType; // Improves maintainability to do this early, but must be done after TAB2 vs. TAB is resolved higher above.
 2030 
 2031     /////////////////////////////////////////////////
 2032     // Set control-specific defaults for any options.
 2033     /////////////////////////////////////////////////
 2034     opt.style_add |= WS_VISIBLE;  // Starting default for all control types.
 2035     opt.use_theme = mUseTheme; // Set default.
 2036 
 2037     // Radio buttons are handled separately here, outside the switch() further below:
 2038     if (aControlType == GUI_CONTROL_RADIO)
 2039     {
 2040         // The BS_NOTIFY style is probably better not applied by default to radios because although it
 2041         // causes the control to send BN_DBLCLK messages, each double-click by the user is seen only
 2042         // as one click for the purpose of cosmetically making the button appear like it is being
 2043         // clicked rapidly.  Update: the usefulness of double-clicking a radio button seems to
 2044         // outweigh the rare cosmetic deficiency of rapidly clicking a radio button, so it seems
 2045         // better to provide it as a default that can be overridden via explicit option.
 2046         // v1.0.47.04: Removed BS_MULTILINE from default because it is conditionally applied later below.
 2047         opt.style_add |= BS_NOTIFY;  // No WS_TABSTOP here since that is applied elsewhere depending on radio group nature.
 2048         if (!mInRadioGroup)
 2049             opt.style_add |= WS_GROUP; // Tabstop must be handled later below.
 2050             // The mInRadioGroup flag will be changed accordingly after the control is successfully created.
 2051         //else by default, no WS_TABSTOP or WS_GROUP.  However, WS_GROUP can be applied manually via the
 2052         // options list to split this radio group off from one immediately prior to it.
 2053     }
 2054     else // Not a radio.
 2055         if (mInRadioGroup) // Close out the prior radio group by giving this control the WS_GROUP style.
 2056             opt.style_add |= WS_GROUP; // This might not be necessary on all OSes, but it seems traditional / best-practice.
 2057 
 2058     // Set control's default text color:
 2059     bool uses_font_and_text_color = USES_FONT_AND_TEXT_COLOR(aControlType); // Resolve macro only once.
 2060     if (uses_font_and_text_color) // Must check this to avoid corrupting union_hbitmap for PIC controls.
 2061     {
 2062         if (control.type != GUI_CONTROL_LISTVIEW) // Must check this to avoid corrupting union_lv_attrib.
 2063             control.union_color = mCurrentColor; // Default to the most recently set color.
 2064         opt.color_listview = mCurrentColor;  // v1.0.44: Added so that ListViews start off with current font color unless overridden in their options.
 2065     }
 2066     else if (aControlType == GUI_CONTROL_PROGRESS) // This must be done to detect custom Progress color.
 2067         control.union_color = CLR_DEFAULT; // Set progress to default color avoids unnecessary stripping of theme.
 2068     //else don't change union_color since it shares the same address as union_hbitmap & union_col.
 2069 
 2070     switch (aControlType) // Set starting defaults based on control type (the above also does some of that).
 2071     {
 2072     // Some controls also have the WS_EX_CLIENTEDGE exstyle by default because they look pretty strange
 2073     // without them.  This seems to be the standard default used by most applications.
 2074     // Note: It seems that WS_BORDER is hardly ever used in practice with controls, just parent windows.
 2075     case GUI_CONTROL_DROPDOWNLIST:
 2076         opt.style_add |= WS_TABSTOP|WS_VSCROLL;  // CBS_DROPDOWNLIST is forcibly applied later. WS_VSCROLL is necessary.
 2077         break;
 2078     case GUI_CONTROL_COMBOBOX:
 2079         // CBS_DROPDOWN is set as the default here to allow the flexibility for it to be changed to
 2080         // CBS_SIMPLE.  CBS_SIMPLE is allowed for ComboBox but not DropDownList because CBS_SIMPLE
 2081         // has an edit control just like a combo, which DropDownList isn't equipped to handle via Submit().
 2082         // Also, if CBS_AUTOHSCROLL is omitted, typed text cannot go beyond the visible width of the
 2083         // edit control, so it seems best to have that as a default also:
 2084         opt.style_add |= WS_TABSTOP|WS_VSCROLL|CBS_AUTOHSCROLL|CBS_DROPDOWN;  // WS_VSCROLL is necessary.
 2085         break;
 2086     case GUI_CONTROL_LISTBOX:
 2087         // Omit LBS_STANDARD because it includes LBS_SORT, which we don't want as a default style.
 2088         // However, as of v1.0.30.03, LBS_USETABSTOPS is included by default because:
 2089         // 1) Not doing so seems to make it impossible to apply tab stops after the control has been created.
 2090         // 2) Without this style, tabs appears as empty squares in the text, which seems undesirable for
 2091         //    99.9% of applications.
 2092         // 3) LBS_USETABSTOPS can be explicitly removed by specifying -0x80 in the options of "Gui Add".
 2093         opt.style_add |= WS_TABSTOP|WS_VSCROLL|LBS_USETABSTOPS;  // WS_VSCROLL seems the most desirable default.
 2094         opt.exstyle_add |= WS_EX_CLIENTEDGE;
 2095         break;
 2096     case GUI_CONTROL_LISTVIEW:
 2097         // The ListView extended styles are actually an entirely separate class of styles that exist
 2098         // separately from ExStyles.  This explains why Get/SetWindowLong doesn't work on them.
 2099         // But keep in mind that some of the normal/classic extended styles can still be applied
 2100         // to a ListView via Get/SetWindowLong.
 2101         // The listview extended styles all require at least ComCtl32.dll 4.70 (some might require more)
 2102         // and thus will have no effect in Win 95/NT unless they have MSIE 3.x or similar patch installed.
 2103         // Thus, things like LVS_EX_FULLROWSELECT and LVS_EX_HEADERDRAGDROP will have no effect on those systems.
 2104         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.
 2105         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.
 2106         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.
 2107         opt.listview_view = LVS_REPORT; // Improves maintainability by avoiding the need to check if it's -1 in other places.
 2108         break;
 2109     case GUI_CONTROL_TREEVIEW:
 2110         // Default style is somewhat debatable, but the familiarity of Explorer's own defaults seems best.
 2111         // TVS_SHOWSELALWAYS seems preferable by most people, and is also consistent to what is used for ListView.
 2112         // Lines and buttons also seem preferable because the main feature of a tree is its hierarchical nature,
 2113         // and that nature isn't well revealed without buttons, and buttons can't be shown at the root level
 2114         // without TVS_LINESATROOT, which in turn can't be active without TVS_HASLINES.
 2115         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.
 2116         opt.exstyle_add |= WS_EX_CLIENTEDGE; // Debatable, but seems best for consistency with ListView.
 2117         break;
 2118     case GUI_CONTROL_EDIT:
 2119         opt.style_add |= WS_TABSTOP;
 2120         opt.exstyle_add |= WS_EX_CLIENTEDGE;
 2121         break;      
 2122     case GUI_CONTROL_LINK:
 2123         opt.style_add |= WS_TABSTOP;
 2124         break;
 2125     case GUI_CONTROL_UPDOWN:
 2126         // UDS_NOTHOUSANDS is debatable:
 2127         // 1) The primary means by which a script validates whether the buddy contains an invalid
 2128         //    or out-of-range value for its UpDown is to compare the contents of the two.  If one
 2129         //    has commas and the other doesn't, the commas must first be removed before comparing.
 2130         // 2) Presence of commas in numeric data is going to be a source of script bugs for those
 2131         //    who take the buddy's contents rather than the UpDown's contents as the user input.
 2132         //    However, you could argue that script is not proper if it does this blindly because
 2133         //    the buddy could contain an out-of-range or non-numeric value.
 2134         // 3) Display is more ergonomic if it has commas in it.
 2135         // The above make it pretty hard to decide, so sticking with the default of have commas
 2136         // seems ok.  Also, UDS_ALIGNRIGHT must be present by default because otherwise buddying
 2137         // will not take effect correctly.
 2138         opt.style_add |= UDS_SETBUDDYINT|UDS_ALIGNRIGHT|UDS_AUTOBUDDY|UDS_ARROWKEYS;
 2139         break;
 2140     case GUI_CONTROL_DATETIME: // Gets a tabstop even when it contains an empty checkbox indicating "no date".
 2141         // DTS_SHORTDATECENTURYFORMAT is applied by default because it should make results more consistent
 2142         // across old and new systems.  This is because new systems display a 4-digit year even without
 2143         // this style, but older ones might display a two digit year.  This should make any system capable
 2144         // of displaying a 4-digit year display it in the locale's customary format.  On systems that don't
 2145         // support DTS_SHORTDATECENTURYFORMAT, it should be ignored, resulting in DTS_SHORTDATEFORMAT taking
 2146         // effect automatically (untested).
 2147         opt.style_add |= WS_TABSTOP|DTS_SHORTDATECENTURYFORMAT;
 2148         break;
 2149     case GUI_CONTROL_BUTTON: // v1.0.45: Removed BS_MULTILINE from default because it is conditionally applied later below.
 2150     case GUI_CONTROL_CHECKBOX: // v1.0.47.04: Removed BS_MULTILINE from default because it is conditionally applied later below.
 2151     case GUI_CONTROL_HOTKEY:
 2152     case GUI_CONTROL_SLIDER:
 2153         opt.style_add |= WS_TABSTOP;
 2154         break;
 2155     case GUI_CONTROL_PROGRESS:
 2156         opt.style_add |= PBS_SMOOTH; // The smooth ones seem preferable as a default.  Theme is removed later below.
 2157         break;
 2158     case GUI_CONTROL_TAB:
 2159         // Override the normal default, requiring a manual +Theme in the control's options.  This is done
 2160         // because themed tabs have a gradient background that is currently not well supported by the method
 2161         // used here (controls' backgrounds do not match the gradient):
 2162         if (!opt.tab_control_uses_dialog)
 2163             opt.use_theme = false;
 2164         opt.style_add |= WS_TABSTOP|TCS_MULTILINE;
 2165         break;
 2166     case GUI_CONTROL_ACTIVEX:
 2167         opt.style_add |= WS_CLIPSIBLINGS;
 2168         break;
 2169     case GUI_CONTROL_CUSTOM:
 2170         opt.style_add |= WS_TABSTOP;
 2171         break;
 2172     case GUI_CONTROL_STATUSBAR:
 2173         // Although the following appears unnecessary, at least on XP, there's a good chance it's required
 2174         // on older OSes such as Win 95/NT.  On newer OSes, apparently the control shows a grip on
 2175         // its own even though it doesn't even give itself the SBARS_SIZEGRIP style.
 2176         if (mStyle & WS_SIZEBOX) // Parent window is resizable.
 2177             opt.style_add |= SBARS_SIZEGRIP; // Provide a grip by default.
 2178         // Below: Seems best to provide SBARS_TOOLTIPS by default, since we're not bound by backward compatibility
 2179         // like the OS is.  In theory, tips should be displayed only when the script has actually set some tip text
 2180         // (e.g. via SendMessage).  In practice, tips are never displayed, even under the precise conditions
 2181         // described at MSDN's SB_SETTIPTEXT, perhaps under certain OS versions and themes.  See bottom of
 2182         // BIF_StatusBar() for more comments.
 2183         opt.style_add |= SBARS_TOOLTIPS;
 2184         break;
 2185     case GUI_CONTROL_MONTHCAL:
 2186         // Testing indicates that although the MonthCal attached to a DateTime control can be navigated using
 2187         // the keyboard on Win2k/XP, standalone MonthCal controls don't support it, except on Vista and later.
 2188         if (g_os.IsWinVistaOrLater())
 2189             opt.style_add |= WS_TABSTOP;
 2190         break;
 2191     // Nothing extra for these currently:
 2192     //case GUI_CONTROL_RADIO: This one is handled separately above the switch().
 2193     //case GUI_CONTROL_TEXT:
 2194     //case GUI_CONTROL_PIC:
 2195     //case GUI_CONTROL_GROUPBOX:
 2196         // v1.0.44.11: The following was commented out for GROUPBOX to avoid unwanted wrapping of last letter when
 2197         // the font is bold on XP Classic theme (other font styles and desktop themes may also be cause this).
 2198         // Avoiding this problem seems to outweigh the breaking of old scripts that use GroupBoxes with more than
 2199         // one line of text (which are likely to be very rare).
 2200         //opt.style_add |= BS_MULTILINE;
 2201         break;
 2202     }
 2203 
 2204     /////////////////////////////
 2205     // Parse the list of options.
 2206     /////////////////////////////
 2207     if (!ControlParseOptions(aOptions, opt, control))
 2208         return FAIL;  // It already displayed the error.
 2209 
 2210     // The following is needed by ControlSetListViewOptions/ControlSetTreeViewOptions, and possibly others
 2211     // in the future. It must be done only after ControlParseOptions() so that cases where mCurrentColor
 2212     // is not CLR_DEFAULT but the options contained cDefault are handled properly.
 2213     // The following will set opt.color_changed to an invalid value for GUI_CONTROL_PIC (which stores something
 2214     // else in the union_color's union) and other types that don't even use union_color for anything yet.  But
 2215     // that should be okay because those types should never consult opt.color_changed.
 2216     opt.color_changed = CLR_DEFAULT != (aControlType == GUI_CONTROL_LISTVIEW ? opt.color_listview : control.union_color);
 2217     if (opt.color_bk == CLR_DEFAULT) // i.e. the options list must have explicitly specified BackgroundDefault.
 2218     {
 2219         if (aControlType != GUI_CONTROL_TREEVIEW) // v1.1.08: Always set the back-color of a TreeView, otherwise it sends WM_CTLCOLOREDIT on Win2k/XP.
 2220             opt.color_bk = CLR_INVALID; // Tell things like ControlSetListViewOptions "no color change needed".
 2221     }
 2222     else if (opt.color_bk == CLR_INVALID && mBackgroundColorCtl != CLR_DEFAULT // No bk color was specified in options param.
 2223         && 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.
 2224         // Since bkgnd color was not explicitly specified in options, use the current background color (except progress bars, which do their own thing).
 2225         opt.color_bk = mBackgroundColorCtl; // Use window's global custom, control background.
 2226     //else leave it as invalid so that ControlSetListView/TreeView/ProgressOptions() etc. won't bother changing it.
 2227 
 2228     // Change for v1.0.45 (buttons) and v1.0.47.04 (checkboxes and radios): Under some desktop themes and
 2229     // unusual DPI settings, it has been reported that the last letter of the control's text gets truncated
 2230     // and/or causes an unwanted wrap that prevents proper display of the text.  To solve this, default to
 2231     // "wrapping enabled" only when necessary.  One case it's usually necessary is when there's an explicit
 2232     // width present because then the text can automatically word-wrap to the next line if it contains any
 2233     // spaces/tabs/dashes (this also improves backward compatibility).
 2234     DWORD contains_bs_multiline_if_applicable =
 2235         (opt.width != COORD_UNSPECIFIED || opt.height != COORD_UNSPECIFIED
 2236             || opt.row_count > 1.5 || StrChrAny(aText, _T("\n\r"))) // Both LF and CR can start new lines.
 2237         ? (BS_MULTILINE & ~opt.style_remove) // Add BS_MULTILINE unless it was explicitly removed.
 2238         : 0; // Otherwise: Omit BS_MULTILINE (unless it was explicitly added [the "0" is verified correct]) because on some unusual 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.
 2239 
 2240     DWORD style = opt.style_add & ~opt.style_remove;
 2241     DWORD exstyle = opt.exstyle_add & ~opt.exstyle_remove;
 2242 
 2243     //////////////////////////////////////////
 2244     // Force any mandatory styles into effect.
 2245     //////////////////////////////////////////
 2246     style |= WS_CHILD;  // All control types must have this, even if script attempted to remove it explicitly.
 2247     switch (aControlType)
 2248     {
 2249     case GUI_CONTROL_GROUPBOX:
 2250         // There doesn't seem to be any flexibility lost by forcing the buttons to be the right type,
 2251         // and doing so improves maintainability and peace-of-mind:
 2252         style = (style & ~BS_TYPEMASK) | BS_GROUPBOX;  // Force it to be the right type of button.
 2253         break;
 2254     case GUI_CONTROL_BUTTON:
 2255         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.
 2256             style = (style & ~BS_TYPEMASK) | BS_DEFPUSHBUTTON; // Done to ensure the lowest four bits are pure.
 2257         else
 2258             style &= ~BS_TYPEMASK;  // Force it to be the right type of button --> BS_PUSHBUTTON == 0
 2259         style |= contains_bs_multiline_if_applicable;
 2260         break;
 2261     case GUI_CONTROL_CHECKBOX:
 2262         // Note: BS_AUTO3STATE and BS_AUTOCHECKBOX are mutually exclusive due to their overlap within
 2263         // the bit field:
 2264         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.
 2265             style = (style & ~BS_TYPEMASK) | BS_AUTO3STATE; // Done to ensure the lowest four bits are pure.
 2266         else
 2267             style = (style & ~BS_TYPEMASK) | BS_AUTOCHECKBOX;  // Force it to be the right type of button.
 2268         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()).
 2269         break;
 2270     case GUI_CONTROL_RADIO:
 2271         style = (style & ~BS_TYPEMASK) | BS_AUTORADIOBUTTON;  // Force it to be the right type of button.
 2272         // This below must be handled here rather than in the set-defaults section because this
 2273         // radio might be the first of its group due to the script having explicitly specified the word
 2274         // Group in options (useful to make two adjacent radio groups).
 2275         if (style & WS_GROUP && !(opt.style_remove & WS_TABSTOP))
 2276             style |= WS_TABSTOP;
 2277         // Otherwise it lacks a tabstop by default.
 2278         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()).
 2279         break;
 2280     case GUI_CONTROL_DROPDOWNLIST:
 2281         style |= CBS_DROPDOWNLIST;  // This works because CBS_DROPDOWNLIST == CBS_SIMPLE|CBS_DROPDOWN
 2282         break;
 2283     case GUI_CONTROL_COMBOBOX:
 2284         if (style & CBS_SIMPLE) // i.e. CBS_SIMPLE has been added to the original default, so assume it is SIMPLE.
 2285             style = (style & ~0x0F) | CBS_SIMPLE; // Done to ensure the lowest four bits are pure.
 2286         else
 2287             style = (style & ~0x0F) | CBS_DROPDOWN; // Done to ensure the lowest four bits are pure.
 2288         break;
 2289     case GUI_CONTROL_LISTBOX:
 2290         style |= LBS_NOTIFY;  // There doesn't seem to be any flexibility lost by forcing this style.
 2291         break;
 2292     case GUI_CONTROL_EDIT:
 2293         // This is done for maintainability and peace-of-mind, though it might not strictly be required
 2294         // to be done at this stage:
 2295         if (opt.row_count > 1.5 || _tcschr(aText, '\n')) // Multiple rows or contents contain newline.
 2296             style |= (ES_MULTILINE & ~opt.style_remove); // Add multiline unless it was explicitly removed.
 2297         // This next check is relied upon by other things.  If this edit has the multiline style either
 2298         // due to the above check or any other reason, provide other default styles if those styles
 2299         // weren't explicitly removed in the options list:
 2300         if (style & ES_MULTILINE) // If allowed, enable vertical scrollbar and capturing of ENTER keystrokes.
 2301             // Safest to include ES_AUTOVSCROLL, though it appears to have no effect on XP.  See also notes below:
 2302             #define EDIT_MULTILINE_DEFAULT (WS_VSCROLL|ES_WANTRETURN|ES_AUTOVSCROLL)
 2303             style |= EDIT_MULTILINE_DEFAULT & ~opt.style_remove;
 2304             // In addition, word-wrapping is implied unless explicitly disabled via -wrap in options.
 2305             // This is because -wrap adds the ES_AUTOHSCROLL style.
 2306         // else: Single-line edit.  ES_AUTOHSCROLL will be applied later below if all the other checks
 2307         // fail to auto-detect this edit as a multi-line edit.
 2308         // Notes: ES_MULTILINE is required for any CRLFs in the default value to display correctly.
 2309         // If ES_MULTILINE is in effect: "If you do not specify ES_AUTOHSCROLL, the control automatically
 2310         // wraps words to the beginning of the next line when necessary."
 2311         // Also, ES_AUTOVSCROLL seems to have no additional effect, perhaps because this window type
 2312         // is considered to be a dialog. MSDN: "When the multiline edit control is not in a dialog box
 2313         // and the ES_AUTOVSCROLL style is specified, the edit control shows as many lines as possible
 2314         // and scrolls vertically when the user presses the ENTER key. If you do not specify ES_AUTOVSCROLL,
 2315         // the edit control shows as many lines as possible and beeps if the user presses the ENTER key when
 2316         // no more lines can be displayed."
 2317         break;
 2318     case GUI_CONTROL_TAB:
 2319         style |= WS_CLIPSIBLINGS; // MSDN: Both the parent window and the tab control must have the WS_CLIPSIBLINGS window style.
 2320         // TCS_OWNERDRAWFIXED is required to implement custom Text color in the tabs.
 2321         // For some reason, it's also required for TabWindowProc's WM_ERASEBKGND to be able to
 2322         // override the background color of the control's interior, at least when an XP theme is in effect.
 2323         if (mBackgroundBrushWin && !(control.attrib & GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT)
 2324             || control.union_color != CLR_DEFAULT)
 2325         {
 2326             style |= TCS_OWNERDRAWFIXED;
 2327             // Even if use_theme is true, the theme won't be applied due to the above style.
 2328             opt.use_theme = false; // Let CreateTabDialog() know not to enable the theme.
 2329         }
 2330         else
 2331             style &= ~TCS_OWNERDRAWFIXED;
 2332         if (opt.width != COORD_UNSPECIFIED) // If a width was specified, don't autosize horizontally.
 2333             opt.tab_control_autosize &= ~TAB3_AUTOWIDTH;
 2334         if (opt.height != COORD_UNSPECIFIED)
 2335             opt.tab_control_autosize &= ~TAB3_AUTOHEIGHT;
 2336         break;
 2337 
 2338     // Nothing extra for these currently:
 2339     //case GUI_CONTROL_TEXT:  Ensuring SS_BITMAP and such are absent seems too over-protective.
 2340     //case GUI_CONTROL_LINK:
 2341     //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.
 2342     //case GUI_CONTROL_LISTVIEW:
 2343     //case GUI_CONTROL_TREEVIEW:
 2344     //case GUI_CONTROL_DATETIME:
 2345     //case GUI_CONTROL_MONTHCAL:
 2346     //case GUI_CONTROL_HOTKEY:
 2347     //case GUI_CONTROL_UPDOWN:
 2348     //case GUI_CONTROL_SLIDER:
 2349     //case GUI_CONTROL_PROGRESS:
 2350     //case GUI_CONTROL_ACTIVEX:
 2351     //case GUI_CONTROL_STATUSBAR:
 2352     }
 2353 
 2354     ////////////////////////////////////////////////////////////////////////////////////////////
 2355     // If the above didn't already set a label for this control and this control type qualifies,
 2356     // check if an automatic/implicit label exists for it in the script.
 2357     ////////////////////////////////////////////////////////////////////////////////////////////
 2358     if (aControlType == GUI_CONTROL_BUTTON
 2359         && !control.jump_to_label && !(control.attrib & GUI_CONTROL_ATTRIB_IMPLICIT_CANCEL))
 2360     {
 2361         TCHAR label_name[1024]; // Subroutine labels are nearly unlimited in length, so use a size to cover anything realistic.
 2362         if (*mName != '1' || mName[1]) // Prepend the window number for windows other than the first.
 2363             _tcscpy(label_name, mName);
 2364         else
 2365             *label_name = '\0';
 2366         sntprintfcat(label_name, _countof(label_name), _T("Button%s"), aText);
 2367         // Remove spaces and ampersands.  Although ampersands are legal in labels, it seems
 2368         // more friendly to omit them in the automatic-label label name.  Note that a button
 2369         // or menu item can contain a literal ampersand by using two ampersands, such as
 2370         // "Save && Exit" (in this example, the auto-label would be named "ButtonSaveExit").
 2371         // v1.0.46.01: tabs and accents are also removed since labels can't contain them.
 2372         // However, colons are NOT removed because labels CAN contain them (except at the very end;
 2373         // but due to rarity and backward compatibility, it doesn't seem worth adding code size for that).
 2374         StrReplace(label_name, _T("\r"), _T(""), SCS_SENSITIVE);
 2375         StrReplace(label_name, _T("\n"), _T(""), SCS_SENSITIVE);
 2376         StrReplace(label_name, _T("\t"), _T(""), SCS_SENSITIVE);
 2377         StrReplace(label_name, _T(" "), _T(""), SCS_SENSITIVE);
 2378         StrReplace(label_name, _T("&"), _T(""), SCS_SENSITIVE);
 2379         StrReplace(label_name, _T("`"), _T(""), SCS_SENSITIVE);
 2380         // Alternate method, but seems considerably larger in code size based on OBJ size:
 2381         //LPTSTR string_list[] = {_T("\r"), _T("\n"), _T(" "), _T("\t"), _T("&"), _T("`"), NULL}; // \r is separate from \n in case they're ever unpaired. Last char must be NULL to terminate the list.
 2382         //for (LPTSTR *cp = string_list; *cp; ++cp)
 2383         //  StrReplace(label_name, *cp, _T(""), SCS_SENSITIVE);
 2384         control.jump_to_label = g_script.FindCallable(label_name, NULL, 4);  // OK if NULL (the button will do nothing).
 2385     }
 2386 
 2387     // The below will yield NULL for GUI_CONTROL_STATUSBAR because control.tab_control_index==OutOfBounds for it.
 2388     GuiControlType *owning_tab_control = FindTabControl(control.tab_control_index); // For use in various places.
 2389     GuiControlType control_temp; // Contents are unused (left uninitialized for performance and to help catch bugs).
 2390     GuiControlType &prev_control = mControlCount ? mControl[mControlCount - 1] : control_temp; // For code size reduction, performance, and maintainability.
 2391 
 2392     ////////////////////////////////////////////////////////////////////////////////////////////
 2393     // Automatically set the control's position in the client area if no position was specified.
 2394     ////////////////////////////////////////////////////////////////////////////////////////////
 2395     if (opt.x == COORD_UNSPECIFIED && opt.y == COORD_UNSPECIFIED)
 2396     {
 2397         if (owning_tab_control && !GetControlCountOnTabPage(control.tab_control_index, control.tab_index)) // Relies on short-circuit boolean.
 2398         {
 2399             // Since this control belongs to a tab control and that tab control already exists,
 2400             // Position it relative to the tab control's client area upper-left corner if this
 2401             // is the first control on this particular tab/page:
 2402             POINT pt = GetPositionOfTabDisplayArea(*owning_tab_control);
 2403             // Since both coords were unspecified, position this control at the upper left corner of its page.
 2404             opt.x = pt.x + mMarginX;
 2405             opt.y = pt.y + mMarginY;
 2406         }
 2407         else
 2408         {
 2409             // GUI_CONTROL_STATUSBAR ignores these:
 2410             // Since both coords were unspecified, proceed downward from the previous control, using a default margin.
 2411             opt.x = mPrevX;
 2412             opt.y = mPrevY + mPrevHeight + mMarginY;  // Don't use mMaxExtentDown in this is a new column.
 2413         }
 2414         if ((aControlType == GUI_CONTROL_TEXT || aControlType == GUI_CONTROL_LINK) && mControlCount // This is a text control and there is a previous control before it.
 2415             && (prev_control.type == GUI_CONTROL_TEXT || prev_control.type == GUI_CONTROL_LINK)
 2416             && prev_control.tab_control_index == control.tab_control_index  // v1.0.44.03: Don't do the adjustment if
 2417             && prev_control.tab_index == control.tab_index)                 // it's on another page or in another tab control.
 2418             // Since this text control is being auto-positioned immediately below another, provide extra
 2419             // margin space so that any edit control, dropdownlist, or other "tall input" control later added
 2420             // to its right in "vertical progression" mode will line up with it.
 2421             // v1.0.44: For code simplicity, this doesn't handle any status bar that might be present in between,
 2422             // since that seems too rare and the consequences too mild.
 2423             opt.y += GUI_CTL_VERTICAL_DEADSPACE;
 2424     }
 2425     // Can't happen due to the logic in the options-parsing section:
 2426     //else if (opt.x == COORD_UNSPECIFIED)
 2427     //  opt.x = mPrevX;
 2428     //else if (y == COORD_UNSPECIFIED)
 2429     //  opt.y = mPrevY;
 2430 
 2431 
 2432     /////////////////////////////////////////////////////////////////////////////////////
 2433     // For certain types of controls, provide a standard row_count if none was specified.
 2434     /////////////////////////////////////////////////////////////////////////////////////
 2435 
 2436     // Set default for all control types.  GUI_CONTROL_MONTHCAL must be set up here because
 2437     // an explicitly specified row-count must also avoid calculating height from row count
 2438     // in the standard way.
 2439     bool calc_height_later = aControlType == GUI_CONTROL_MONTHCAL || aControlType == GUI_CONTROL_LISTVIEW
 2440          || aControlType == GUI_CONTROL_TREEVIEW;
 2441     bool calc_control_height_from_row_count = !calc_height_later; // Set default.
 2442 
 2443     if (opt.height == COORD_UNSPECIFIED && opt.row_count < 1)
 2444     {
 2445         switch(aControlType)
 2446         {
 2447         case GUI_CONTROL_DROPDOWNLIST:
 2448         case GUI_CONTROL_COMBOBOX: // For these 2, row-count is defined as the number of items to display in the list.
 2449             // Update: Unfortunately, heights taller than the desktop do not work: pre-v6 common controls
 2450             // misbehave when the height is too tall to fit on the screen.  So the below comment is
 2451             // obsolete and kept only for reference:
 2452             // Since no height or row-count was given, make the control very tall so that OSes older than
 2453             // XP will behavior similar to XP: they will let the desktop height determine how tall the
 2454             // control can be. One exception to this is a true CBS_SIMPLE combo, which has appearance
 2455             // and functionality similar to a ListBox.  In that case, a default row-count is provided
 2456             // since that is more appropriate than having a really tall box hogging the window.
 2457             // Because CBS_DROPDOWNLIST includes both CBS_SIMPLE and CBS_DROPDOWN, a true "simple"
 2458             // (which is similar to a listbox) must omit CBS_DROPDOWN:
 2459             opt.row_count = 3;  // Actual height will be calculated below using this.
 2460             // Avoid doing various calculations later below if the XP+ will ignore the height anyway.
 2461             // CBS_NOINTEGRALHEIGHT is checked in case that style was explicitly applied to the control
 2462             // by the script. This is because on XP+ it will cause the combo/DropDownList to obey the
 2463             // specified default height set above.  Also, for a pure CBS_SIMPLE combo, the OS always
 2464             // obeys height:
 2465             if ((!(style & CBS_SIMPLE) || (style & CBS_DROPDOWN)) // Not a pure CBS_SIMPLE.
 2466                 && g_os.IsWinXPorLater() // ... and the OS is XP+.
 2467                 && !(style & CBS_NOINTEGRALHEIGHT)) // ... and XP won't obey the height.
 2468                 calc_control_height_from_row_count = false; // Don't bother calculating the height (i.e. override the default).
 2469             break;
 2470         case GUI_CONTROL_LISTBOX:
 2471             opt.row_count = 3;  // Actual height will be calculated below using this.
 2472             break;
 2473         case GUI_CONTROL_LISTVIEW:
 2474         case GUI_CONTROL_TREEVIEW:
 2475         case GUI_CONTROL_CUSTOM:
 2476             opt.row_count = 5;  // Actual height will be calculated below using this.
 2477             break;
 2478         case GUI_CONTROL_GROUPBOX:
 2479             // Seems more appropriate to give GUI_CONTROL_GROUPBOX exactly two rows: the first for the
 2480             // title of the group-box and the second for its content (since it might contain controls
 2481             // placed horizontally end-to-end, and thus only need one row).
 2482             opt.row_count = 2;
 2483             break;
 2484         case GUI_CONTROL_EDIT:
 2485             // If there's no default text in the control from which to later calc the height, use a basic default.
 2486             if (!*aText)
 2487                 opt.row_count = (style & ES_MULTILINE) ? 3.0F : 1.0F;
 2488             break;
 2489         case GUI_CONTROL_DATETIME:
 2490         case GUI_CONTROL_HOTKEY:
 2491             opt.row_count = 1;
 2492             break;
 2493         case GUI_CONTROL_UPDOWN: // A somewhat arbitrary default in case it will lack a buddy to "snap to".
 2494             // Make vertical up-downs tall by default.  If their width has also been omitted, they
 2495             // will be made narrow by default in a later section.
 2496             if (style & UDS_HORZ)
 2497                 // Height vs. row_count is specified to ensure the same thickness for both vertical
 2498                 // and horizontal up-downs:
 2499                 opt.height = PROGRESS_DEFAULT_THICKNESS; // Seems okay for up-down to use Progress's thickness.
 2500             else
 2501                 opt.row_count = 5.0F;
 2502             break;
 2503         case GUI_CONTROL_SLIDER:
 2504             // Make vertical trackbars tall by default.  If their width has also been omitted, they
 2505             // will be made narrow by default in a later section.
 2506             if (style & TBS_VERT)
 2507                 opt.row_count = 5.0F;
 2508             else
 2509                 opt.height = ControlGetDefaultSliderThickness(style, opt.thickness);
 2510             break;
 2511         case GUI_CONTROL_PROGRESS:
 2512             // Make vertical progress bars tall by default.  If their width has also been omitted, they
 2513             // will be made narrow by default in a later section.
 2514             if (style & PBS_VERTICAL)
 2515                 opt.row_count = 5.0F;
 2516             else
 2517                 // Height vs. row_count is specified to ensure the same thickness for both vertical
 2518                 // and horizontal progress bars:
 2519                 opt.height = PROGRESS_DEFAULT_THICKNESS;
 2520             break;
 2521         case GUI_CONTROL_TAB:
 2522             opt.row_count = 10;
 2523             break;
 2524         // Types not included
 2525         // ------------------
 2526         //case GUI_CONTROL_TEXT:      Rows are based on control's contents.
 2527         //case GUI_CONTROL_LINK:      Rows are based on control's contents.
 2528         //case GUI_CONTROL_PIC:       N/A
 2529         //case GUI_CONTROL_BUTTON:    Rows are based on control's contents.
 2530         //case GUI_CONTROL_CHECKBOX:  Same
 2531         //case GUI_CONTROL_RADIO:     Same
 2532         //case GUI_CONTROL_MONTHCAL:  Leave row-count unspecified so that an explicit r1 can be distinguished from "unspecified".
 2533         //case GUI_CONTROL_ACTIVEX:   N/A
 2534         //case GUI_CONTROL_STATUSBAR: For now, row-count is ignored/unused.
 2535         }
 2536     }
 2537     else // Either a row_count or a height was explicitly specified.
 2538         // If OS is XP+, must apply the CBS_NOINTEGRALHEIGHT style for these reasons:
 2539         // 1) The app now has a manifest, which tells OS to use common controls v6.
 2540         // 2) Common controls v6 will not obey the the user's specified height for the control's
 2541         //    list portion unless the CBS_NOINTEGRALHEIGHT style is present.
 2542         if ((aControlType == GUI_CONTROL_DROPDOWNLIST || aControlType == GUI_CONTROL_COMBOBOX) && g_os.IsWinXPorLater())
 2543             style |= CBS_NOINTEGRALHEIGHT; // Forcibly applied, even if removed in options.
 2544 
 2545     ////////////////////////////////////////////////////////////////////////////////////////////
 2546     // In case the control being added requires an HDC to calculate its size, provide the means.
 2547     ////////////////////////////////////////////////////////////////////////////////////////////
 2548     HDC hdc = NULL;
 2549     HFONT hfont_old = NULL;
 2550     TEXTMETRIC tm; // Used in more than one place.
 2551     // To improve maintainability, always use this macro to deal with the above.
 2552     // HDC will be released much further below when it is no longer needed.
 2553     // Remember to release DC if ever need to return FAIL in the middle of auto-sizing/positioning.
 2554     #define GUI_SET_HDC \
 2555         if (!hdc)\
 2556         {\
 2557             hdc = GetDC(mHwnd);\
 2558             hfont_old = (HFONT)SelectObject(hdc, sFont[mCurrentFontIndex].hfont);\
 2559         }
 2560 
 2561     //////////////////////////////////////////////////////////////////////////////////////
 2562     // If a row-count was specified or made available by the above defaults, calculate the
 2563     // control's actual height (to be used when creating the window).  Note: If both
 2564     // row_count and height were explicitly specified, row_count takes precedence.
 2565     //////////////////////////////////////////////////////////////////////////////////////
 2566     if (opt.row_count > 0)
 2567     {
 2568         // For GroupBoxes, add 1 to any row_count greater than 1 so that the title itself is
 2569         // already included automatically.  In other words, the R-value specified by the user
 2570         // should be the number of rows available INSIDE the box.
 2571         // For DropDownLists and ComboBoxes, 1 is added because row_count is defined as the
 2572         // number of rows shown in the drop-down portion of the control, so we need one extra
 2573         // (used in later calculations) for the always visible portion of the control.
 2574         switch (aControlType)
 2575         {
 2576         case GUI_CONTROL_GROUPBOX:
 2577         case GUI_CONTROL_DROPDOWNLIST:
 2578         case GUI_CONTROL_COMBOBOX: // LISTVIEW has custom handling later on, so isn't listed here.
 2579             ++opt.row_count;
 2580             break;
 2581         }
 2582         if (calc_control_height_from_row_count)
 2583         {
 2584             GUI_SET_HDC
 2585             GetTextMetrics(hdc, &tm);
 2586             // Calc the height by adding up the font height for each row, and including the space between lines
 2587             // (tmExternalLeading) if there is more than one line.  0.5 is used in two places to prevent
 2588             // negatives in one, and round the overall result in the other.
 2589             opt.height = (int)((tm.tmHeight * opt.row_count) + (tm.tmExternalLeading * ((int)(opt.row_count + 0.5) - 1)) + 0.5);
 2590             switch (aControlType)
 2591             {
 2592             case GUI_CONTROL_DROPDOWNLIST:
 2593             case GUI_CONTROL_COMBOBOX:
 2594             case GUI_CONTROL_LISTBOX:
 2595             case GUI_CONTROL_EDIT:
 2596             case GUI_CONTROL_DATETIME:
 2597             case GUI_CONTROL_HOTKEY:
 2598             case GUI_CONTROL_CUSTOM:
 2599                 opt.height += GUI_CTL_VERTICAL_DEADSPACE;
 2600                 if (style & WS_HSCROLL)
 2601                     opt.height += GetSystemMetrics(SM_CYHSCROLL);
 2602                 break;
 2603             case GUI_CONTROL_BUTTON:
 2604                 // Provide a extra space for top/bottom margin together, proportional to the current font
 2605                 // size so that it looks better with very large or small fonts.  The +2 seems to make
 2606                 // it look just right on all font sizes, especially the default GUI size of 8 where the
 2607                 // height should be about 23 to be standard(?)
 2608                 opt.height += DPIScale(sFont[mCurrentFontIndex].point_size + 2);
 2609                 break;
 2610             case GUI_CONTROL_GROUPBOX: // Since groups usually contain other controls, the below sizing seems best.
 2611                 // Use row_count-2 because of the +1 added above for GUI_CONTROL_GROUPBOX.
 2612                 // The current font's height is added in to provide an upper/lower margin in the box
 2613                 // proportional to the current font size, which makes it look better in most cases:
 2614                 opt.height += mMarginY * (2 + ((int)(opt.row_count + 0.5) - 2));
 2615                 break;
 2616             case GUI_CONTROL_TAB:
 2617                 opt.height += mMarginY * (2 + ((int)(opt.row_count + 0.5) - 1)); // -1 vs. the -2 used above.
 2618                 break;
 2619             // Types not included
 2620             // ------------------
 2621             //case GUI_CONTROL_TEXT:     Uses basic height calculated above the switch().
 2622             //case GUI_CONTROL_LINK:     Uses basic height calculated above the switch().
 2623             //case GUI_CONTROL_PIC:      Uses basic height calculated above the switch() (seems OK even for pic).
 2624             //case GUI_CONTROL_CHECKBOX: Uses basic height calculated above the switch().
 2625             //case GUI_CONTROL_RADIO:    Same.
 2626             //case GUI_CONTROL_UPDOWN:   Same.
 2627             //case GUI_CONTROL_SLIDER:   Same.
 2628             //case GUI_CONTROL_PROGRESS: Same.
 2629             //case GUI_CONTROL_MONTHCAL: Not included at all in this section because it treats "rows" differently.
 2630             //case GUI_CONTROL_ACTIVEX:  N/A
 2631             //case GUI_CONTROL_STATUSBAR: N/A
 2632             } // switch
 2633         }
 2634         else // calc_control_height_from_row_count == false
 2635             // Assign a default just to allow the control to be created successfully. 13 is the default
 2636             // height of a text/radio control for the typical 8 point font size, but the exact value
 2637             // shouldn't matter (within reason) since calc_control_height_from_row_count is telling us this type of
 2638             // control will not obey the height anyway.  Update: It seems better to use a small constant
 2639             // value to help catch bugs while still allowing the control to be created:
 2640             if (!calc_height_later)
 2641                 opt.height = DPIScale(30);
 2642             //else MONTHCAL and others must keep their "unspecified height" value for later detection.
 2643     }
 2644 
 2645     bool control_width_was_set_by_contents = false;
 2646 
 2647     if (opt.height == COORD_UNSPECIFIED || opt.width == COORD_UNSPECIFIED)
 2648     {
 2649         // Set defaults:
 2650         int extra_width = 0, extra_height = 0;
 2651         UINT draw_format = DT_CALCRECT;
 2652 
 2653         switch (aControlType)
 2654         {
 2655         case GUI_CONTROL_EDIT:
 2656             if (!*aText) // Only auto-calculate edit's dimensions if there is text to do it with.
 2657                 break;
 2658             // Since edit controls leave approximate 1 avg-char-width margin on the right side,
 2659             // and probably exactly 4 pixels on the left counting its border and the internal
 2660             // margin), adjust accordingly so that DrawText() will calculate the correct
 2661             // control height based on word-wrapping.  Note: Can't use EM_GETRECT because
 2662             // control doesn't exist yet (though that might be an alternative approach for
 2663             // the future):
 2664             GUI_SET_HDC
 2665             GetTextMetrics(hdc, &tm);
 2666             extra_width += 4 + tm.tmAveCharWidth;
 2667             // Determine whether there will be a vertical scrollbar present.  If ES_MULTILINE hasn't
 2668             // already been applied or auto-detected above, it's possible that a scrollbar will be
 2669             // added later due to the text auto-wrapping.  In that case, the calculated height may
 2670             // be incorrect due to the additional wrapping caused by the width taken up by the
 2671             // scrollbar.  Since this combination of circumstances is rare, and since there are easy
 2672             // workarounds, it's just documented here as a limitation:
 2673             if (style & WS_VSCROLL)
 2674                 extra_width += GetSystemMetrics(SM_CXVSCROLL);
 2675             // DT_EDITCONTROL: "the average character width is calculated in the same manner as for an edit control"
 2676             // Although it's hard to say exactly what the quote above means, DT_EDITCONTROL definitely
 2677             // affects the width of tab stops (making this consistent with Edit controls) and therefore
 2678             // must be included.  It also excludes the last line if it is empty, which is undesirable,
 2679             // so we need to compensate for that:
 2680             if (*aText && aText[_tcslen(aText)-1] == '\n')
 2681                 extra_height += tm.tmHeight + tm.tmExternalLeading;
 2682             // Also include DT_EXPANDTABS under the assumption that if there are tabs present, the user
 2683             // intended for them to be there because a multiline edit would expand them (rather than trying
 2684             // to worry about whether this control *might* become auto-multiline after this point.
 2685             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.
 2686             // and now fall through and have the dimensions calculated based on what's in the control.
 2687             // ABOVE FALLS THROUGH TO BELOW
 2688         case GUI_CONTROL_TEXT:
 2689         case GUI_CONTROL_BUTTON:
 2690         case GUI_CONTROL_CHECKBOX:
 2691         case GUI_CONTROL_RADIO:
 2692         case GUI_CONTROL_LINK:
 2693         {
 2694             GUI_SET_HDC
 2695             if (aControlType == GUI_CONTROL_TEXT)
 2696             {
 2697                 draw_format |= DT_EXPANDTABS; // Buttons can't expand tabs, so don't add this for them.
 2698                 if (style & SS_NOPREFIX) // v1.0.44.10: This is necessary to auto-width the control properly if its contents include any ampersands.
 2699                     draw_format |= DT_NOPREFIX;
 2700             }
 2701             else if (aControlType == GUI_CONTROL_CHECKBOX || aControlType == GUI_CONTROL_RADIO)
 2702             {
 2703                 // Both Checkbox and Radio seem to have the same spacing characteristics:
 2704                 // Expand to allow room for button itself, its border, and the space between
 2705                 // the button and the first character of its label (this space seems to
 2706                 // be the same as tmAveCharWidth).  +2 seems to be needed to make it work
 2707                 // for the various sizes of Courier New vs. Verdana that I tested.  The
 2708                 // alternative, (2 * GetSystemMetrics(SM_CXEDGE)), seems to add a little
 2709                 // too much width (namely 4 vs. 2).
 2710                 GetTextMetrics(hdc, &tm);
 2711                 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).
 2712             }
 2713             if ((style & WS_BORDER) && (aControlType == GUI_CONTROL_TEXT || aControlType == GUI_CONTROL_LINK))
 2714             {
 2715                 // This seems to be necessary only for Text and Link controls:
 2716                 extra_width = 2 * GetSystemMetrics(SM_CXBORDER);
 2717                 extra_height = 2 * GetSystemMetrics(SM_CYBORDER);
 2718             }
 2719             if (   opt.width != COORD_UNSPECIFIED // Since a width was given, auto-expand the height via word-wrapping,
 2720                 && ( !(aControlType == GUI_CONTROL_BUTTON || aControlType == GUI_CONTROL_CHECKBOX || aControlType == GUI_CONTROL_RADIO)
 2721                     || (style & BS_MULTILINE) )   ) // except when -Wrap is used on a Button, Checkbox or Radio.
 2722                 draw_format |= DT_WORDBREAK;
 2723 
 2724             RECT draw_rect;
 2725             draw_rect.left = 0;
 2726             draw_rect.top = 0;
 2727             draw_rect.right = (opt.width == COORD_UNSPECIFIED) ? 0 : opt.width - extra_width; // extra_width
 2728             draw_rect.bottom = (opt.height == COORD_UNSPECIFIED) ? 0 : opt.height;
 2729 
 2730             int draw_height;
 2731             TCHAR last_char = 0;
 2732 
 2733             // Since a Link control's text contains markup which isn't actually rendered, we need
 2734             // to strip it out before calling DrawText or it will put out the size calculation:
 2735             if (aControlType == GUI_CONTROL_LINK)
 2736             {
 2737                 // Unless the control has the LWS_NOPREFIX style, the first ampersand causes the character
 2738                 // following it to be rendered with an underline, and the ampersand itself is not rendered.
 2739                 // Since DrawText has this same behaviour by default, we can simply set DT_NOPREFIX when
 2740                 // appropriate and it should handle the ampersands correctly.
 2741                 if (style & LWS_NOPREFIX)
 2742                     draw_format |= DT_NOPREFIX;
 2743 
 2744                 LPTSTR aTextCopy = new TCHAR[_tcslen(aText) + 1];
 2745 
 2746                 TCHAR *src, *dst;
 2747                 for (src = aText, dst = aTextCopy; *src; ++src)
 2748                 {
 2749                     if (*src == '<' && ctoupper(src[1]) == 'A')
 2750                     {
 2751                         TCHAR *linkText = NULL;
 2752                         TCHAR *cp = omit_leading_whitespace(src + 2);
 2753 
 2754                         // It is important to note that while the SysLink control's syntax is similar to HTML,
 2755                         // it is not HTML.  Some valid HTML won't work, and some invalid HTML will.  Testing
 2756                         // indicates that whitespace is not required between "<A" and the attribute, so there
 2757                         // is no check to see if the above actually omitted whitespace.
 2758                         for (;;)
 2759                         {
 2760                             if (*cp == '>')
 2761                             {
 2762                                 linkText = cp + 1;
 2763                                 break;
 2764                             }
 2765 
 2766                             // Testing indicates that attribute names can only be alphanumeric chars.  This
 2767                             // method of testing for the attribute name does not treat <a=""> as an error,
 2768                             // which is perfect since the control tolerates it (the attribute is ignored).
 2769                             while (cisalnum(*cp)) 
 2770                                 cp++;
 2771                             
 2772                             // What follows next must be the attribute value.  Spaces are not tolerated.
 2773                             if (*cp != '=' || '"' != cp[1])
 2774                                 break; // Not valid.
 2775                                 
 2776                             // Testing indicates that the attribute value can contain virtually anything,
 2777                             // including </a> but not quotation marks.
 2778                             cp = _tcschr(cp + 2, '"');
 2779                             if (!cp)
 2780                                 break; // Not valid.
 2781                             
 2782                             // Allow whitespace after the attribute (before '>' or the next attribute).
 2783                             cp = omit_leading_whitespace(cp + 1);
 2784                         } // for (attribute-parsing loop)
 2785 
 2786                         if (linkText)
 2787                         {
 2788                             // Testing indicates that spaces in the end tag are not tolerated:
 2789                             if (LPTSTR endTag = tcscasestr(linkText, _T("</a>")))
 2790                             {
 2791                                 // Copy the link text and skip over the end tag.
 2792                                 for (cp = linkText; cp < endTag; *dst++ = *cp++);
 2793                                 src = endTag + 3; // The outer loop's increment makes it + 4.
 2794                                 continue;
 2795                             }
 2796                             // Otherwise, there's no end tag so the begin tag is treated as text.
 2797                         }
 2798                         // Otherwise, it wasn't a valid tag, so it is treated as text.
 2799                     } // if (found a tag)
 2800                     *dst++ = *src;
 2801                 } // for (searching for tag, copying text)
 2802                 
 2803                 *dst = '\0';
 2804 
 2805                 if (dst > aTextCopy)
 2806                     last_char = dst[-1];
 2807 
 2808                 // If no text, "H" is used in case the function requires a non-empty string to give consistent results:
 2809                 draw_height = DrawText(hdc, *aTextCopy ? aTextCopy : _T("H"), -1, &draw_rect, draw_format);
 2810                 
 2811                 delete[] aTextCopy;
 2812             }
 2813             else
 2814             {
 2815                 // If no text, "H" is used in case the function requires a non-empty string to give consistent results:
 2816                 draw_height = DrawText(hdc, *aText ? aText : _T("H"), -1, &draw_rect, draw_format);
 2817                 if (*aText)
 2818                     last_char = aText[_tcslen(aText)-1];
 2819             }
 2820             
 2821             int draw_width = draw_rect.right - draw_rect.left;
 2822 
 2823             switch (aControlType)
 2824             {
 2825             case GUI_CONTROL_BUTTON:
 2826                 if (contains_bs_multiline_if_applicable) // BS_MULTILINE style prevents the control from utilizing the extra space.
 2827                     break;
 2828             case GUI_CONTROL_TEXT:
 2829             case GUI_CONTROL_EDIT:
 2830                 if (!last_char)
 2831                     break;
 2832                 // draw_rect doesn't include the overhang of the last character, so adjust for that
 2833                 // to avoid clipping of italic fonts and other fonts where characters overlap.
 2834                 // This is currently only done for Text, Edit and Button controls because the other
 2835                 // control types won't utilize the extra space (they are apparently clipped according
 2836                 // to what the OS erroneously calculates the width of the text to be).
 2837                 ABC abc;
 2838                 if (GetCharABCWidths(hdc, last_char, last_char, &abc))
 2839                     if (abc.abcC < 0)
 2840                         draw_width -= abc.abcC;
 2841             }
 2842 
 2843             // Even if either height or width was already explicitly specified above, it seems best to
 2844             // override it if DrawText() says it's not big enough.  REASONING: It seems too rare that
 2845             // someone would want to use an explicit height/width to selectively hide part of a control's
 2846             // contents, presumably for revelation later.  If that is truly desired, ControlMove or
 2847             // similar can be used to resize the control afterward.  In addition, by specifying BOTH
 2848             // width and height/rows, none of these calculations happens anyway, so that's another way
 2849             // this override can be overridden.  UPDATE for v1.0.44.10: The override is now not done for Edit
 2850             // controls because unlike the other control types enumerated above, it is much more common to
 2851             // have an Edit not be tall enough to show all of it's initial text.  This fixes the following:
 2852             //   Gui, Add, Edit, r2 ,Line1`nLine2`nLine3`nLine4
 2853             // Since there's no explicit width above, the r2 option (or even an H option) would otherwise
 2854             // be overridden in favor of making the edit tall enough to hold all 4 lines.
 2855             // Another reason for not changing the other control types to be like Edit is that backward
 2856             // compatibility probably outweighs any value added by changing them (and the added value is dubious
 2857             // when the comments above are carefully considered).
 2858             if (opt.height == COORD_UNSPECIFIED || (draw_height > opt.height && aControlType != GUI_CONTROL_EDIT))
 2859             {
 2860                 opt.height = draw_height + extra_height;
 2861                 if (aControlType == GUI_CONTROL_EDIT)
 2862                 {
 2863                     opt.height += GUI_CTL_VERTICAL_DEADSPACE;
 2864                     if (style & WS_HSCROLL)
 2865                         opt.height += GetSystemMetrics(SM_CYHSCROLL);
 2866                 }
 2867                 else if (aControlType == GUI_CONTROL_BUTTON)
 2868                     opt.height += sFont[mCurrentFontIndex].point_size + 2;  // +2 makes it standard height.
 2869             }
 2870             if (opt.width == COORD_UNSPECIFIED || draw_width > opt.width)
 2871             {
 2872                 // v1.0.44.08: Fixed the following line by moving it into this IF block.  This prevents
 2873                 // an up-down from widening its edit control when that edit control had an explicit width.
 2874                 control_width_was_set_by_contents = true; // Indicate that this control was auto-width'd.
 2875                 opt.width = draw_width + extra_width;  // See comments above for why specified width is overridden.
 2876                 if (aControlType == GUI_CONTROL_BUTTON)
 2877                     // Allow room for border and an internal margin proportional to the font height.
 2878                     // Button's border is 3D by default, so SM_CXEDGE vs. SM_CXBORDER is used?
 2879                     opt.width += 2 * GetSystemMetrics(SM_CXEDGE) + sFont[mCurrentFontIndex].point_size;
 2880             }
 2881             break;
 2882         } // case for text/button/checkbox/radio/link
 2883 
 2884         // Types not included
 2885         // ------------------
 2886         //case GUI_CONTROL_PIC:           If needed, it is given some default dimensions at the time of creation.
 2887         //case GUI_CONTROL_GROUPBOX:      Seems too rare than anyone would want its width determined by its text.
 2888         //case GUI_CONTROL_EDIT:          It is included, but only if it has default text inside it.
 2889         //case GUI_CONTROL_TAB:           Seems too rare than anyone would want its width determined by tab-count.
 2890 
 2891         //case GUI_CONTROL_LISTVIEW:      Has custom handling later below.
 2892         //case GUI_CONTROL_TREEVIEW:      Same.
 2893         //case GUI_CONTROL_MONTHCAL:      Same.
 2894         //case GUI_CONTROL_ACTIVEX:       N/A?
 2895         //case GUI_CONTROL_STATUSBAR:     Ignores width/height, so no need to handle here.
 2896 
 2897         //case GUI_CONTROL_DROPDOWNLIST:  These last ones are given (later below) a standard width based on font size.
 2898         //case GUI_CONTROL_COMBOBOX:      In addition, their height has already been determined further above.
 2899         //case GUI_CONTROL_LISTBOX:
 2900         //case GUI_CONTROL_DATETIME:
 2901         //case GUI_CONTROL_HOTKEY:
 2902         //case GUI_CONTROL_UPDOWN:
 2903         //case GUI_CONTROL_SLIDER:
 2904         //case GUI_CONTROL_PROGRESS:
 2905         } // switch()
 2906     }
 2907 
 2908     //////////////////////////////////////////////////////////////////////////////////////////
 2909     // If the width was not specified and the above did not already determine it (which should
 2910     // only be possible for the cases contained in the switch-stmt below), provide a default.
 2911     //////////////////////////////////////////////////////////////////////////////////////////
 2912     if (opt.width == COORD_UNSPECIFIED)
 2913     {
 2914         int gui_standard_width = GUI_STANDARD_WIDTH; // Resolve macro only once for performance/code size.
 2915         switch(aControlType)
 2916         {
 2917         case GUI_CONTROL_DROPDOWNLIST:
 2918         case GUI_CONTROL_COMBOBOX:
 2919         case GUI_CONTROL_LISTBOX:
 2920         case GUI_CONTROL_HOTKEY:
 2921         case GUI_CONTROL_EDIT:
 2922             opt.width = gui_standard_width;
 2923             break;
 2924         case GUI_CONTROL_LISTVIEW:
 2925         case GUI_CONTROL_TREEVIEW:
 2926         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).
 2927         case GUI_CONTROL_CUSTOM:
 2928             opt.width = gui_standard_width * 2;
 2929             break;
 2930         case GUI_CONTROL_UPDOWN: // Iffy, but needs some kind of default?
 2931             opt.width = (style & UDS_HORZ) ? gui_standard_width : PROGRESS_DEFAULT_THICKNESS; // Progress's default seems ok for up-down too.
 2932             break;
 2933         case GUI_CONTROL_SLIDER:
 2934             // Make vertical trackbars narrow by default.  For vertical trackbars: there doesn't seem
 2935             // to be much point in defaulting the width to something proportional to font size because
 2936             // the thumb only seems to have two sizes and doesn't auto-grow any larger than that.
 2937             opt.width = (style & TBS_VERT) ? ControlGetDefaultSliderThickness(style, opt.thickness) : gui_standard_width;
 2938             break;
 2939         case GUI_CONTROL_PROGRESS:
 2940             opt.width = (style & PBS_VERTICAL) ? PROGRESS_DEFAULT_THICKNESS : gui_standard_width;
 2941             break;
 2942         case GUI_CONTROL_GROUPBOX:
 2943             // Since groups and tabs contain other controls, allow room inside them for a margin based
 2944             // on current font size.
 2945             opt.width = gui_standard_width + (2 * mMarginX);
 2946             break;
 2947         case GUI_CONTROL_TAB:
 2948             // Tabs tend to be wide so that that tabs can all fit on the top row, and because they
 2949             // are usually used to fill up the entire window.  Therefore, default them to the ability
 2950             // to hold two columns of standard-width controls:
 2951             opt.width = (2 * gui_standard_width) + (3 * mMarginX);  // 3 vs. 2 to allow space in between columns.
 2952             break;
 2953         // Types not included
 2954         // ------------------
 2955         //case GUI_CONTROL_TEXT:      Exact width should already have been calculated based on contents.
 2956         //case GUI_CONTROL_LINK:      Exact width should already have been calculated based on contents.
 2957         //case GUI_CONTROL_PIC:       Calculated based on actual pic size if no explicit width was given.
 2958         //case GUI_CONTROL_BUTTON:    Exact width should already have been calculated based on contents.
 2959         //case GUI_CONTROL_CHECKBOX:  Same.
 2960         //case GUI_CONTROL_RADIO:     Same.
 2961         //case GUI_CONTROL_MONTHCAL:  Exact width will be calculated after the control is created (size to fit month).
 2962         //case GUI_CONTROL_ACTIVEX:   Defaults to zero width/height, which could be useful in some cases.
 2963         //case GUI_CONTROL_STATUSBAR: Ignores width, so no need to handle here.
 2964         }
 2965     }
 2966 
 2967     /////////////////////////////////////////////////////////////////////////////////////////
 2968     // For edit controls: If the above didn't already determine how many rows it should have,
 2969     // auto-detect that by comparing the current font size with the specified height. At this
 2970     // stage, the above has already ensured that an Edit has at least a height or a row_count.
 2971     /////////////////////////////////////////////////////////////////////////////////////////
 2972     if (aControlType == GUI_CONTROL_EDIT && !(style & ES_MULTILINE))
 2973     {
 2974         if (opt.row_count < 1) // Determine the row-count to auto-detect multi-line vs. single-line.
 2975         {
 2976             GUI_SET_HDC
 2977             GetTextMetrics(hdc, &tm);
 2978             int height_beyond_first_row = opt.height - GUI_CTL_VERTICAL_DEADSPACE - tm.tmHeight;
 2979             if (style & WS_HSCROLL)
 2980                 height_beyond_first_row -= GetSystemMetrics(SM_CYHSCROLL);
 2981             if (height_beyond_first_row > 0)
 2982             {
 2983                 opt.row_count = 1 + ((float)height_beyond_first_row / (tm.tmHeight + tm.tmExternalLeading));
 2984                 // This section is a near exact match for one higher above.  Search for comment
 2985                 // "Add multiline unless it was explicitly removed" for a full explanation and keep
 2986                 // the below in sync with that section above:
 2987                 if (opt.row_count > 1.5)
 2988                 {
 2989                     style |= (ES_MULTILINE & ~opt.style_remove); // Add multiline unless it was explicitly removed.
 2990                     // Do the below only if the above actually added multiline:
 2991                     if (style & ES_MULTILINE) // If allowed, enable vertical scrollbar and capturing of ENTER keystrokes.
 2992                         style |= EDIT_MULTILINE_DEFAULT & ~opt.style_remove;
 2993                     // else: Single-line edit.  ES_AUTOHSCROLL will be applied later below if all the other checks
 2994                     // fail to auto-detect this edit as a multi-line edit.
 2995                 }
 2996             }
 2997             else // there appears to be only one row.
 2998                 opt.row_count = 1;
 2999                 // And ES_AUTOHSCROLL will be applied later below if all the other checks
 3000                 // fail to auto-detect this edit as a multi-line edit.
 3001         }
 3002     }
 3003 
 3004     // If either height or width is still undetermined, leave it set to COORD_UNSPECIFIED since that
 3005     // is a large negative number and should thus help catch bugs.  In other words, the above
 3006     // heuristics should be designed to handle all cases and always resolve height/width to something,
 3007     // with the possible exception of things that auto-size based on external content such as
 3008     // GUI_CONTROL_PIC.
 3009 
 3010     //////////////////////
 3011     //
 3012     // CREATE THE CONTROL.
 3013     //
 3014     //////////////////////
 3015     bool do_strip_theme = !opt.use_theme;   // Set defaults.
 3016     bool retrieve_dimensions = false;       //
 3017     int item_height, min_list_height;
 3018     RECT rect;
 3019     LPTSTR malloc_buf;
 3020     HMENU control_id = (HMENU)(size_t)GUI_INDEX_TO_ID(mControlCount); // Cast to size_t avoids compiler warning.
 3021     HWND parent_hwnd = mHwnd;
 3022 
 3023     bool font_was_set = false;          // "
 3024     bool is_parent_visible = IsWindowVisible(mHwnd) && !IsIconic(mHwnd);
 3025     #define GUI_SETFONT \
 3026     {\
 3027         SendMessage(control.hwnd, WM_SETFONT, (WPARAM)sFont[mCurrentFontIndex].hfont, is_parent_visible);\
 3028         font_was_set = true;\
 3029     }
 3030 
 3031     // If a control is being added to a tab, even if the parent window is hidden (since it might
 3032     // have been hidden by Gui, Cancel), make sure the control isn't visible unless it's on a
 3033     // visible tab.
 3034     // The below alters style vs. style_remove, since later below style_remove is checked to
 3035     // find out if the control was explicitly hidden vs. hidden by the automatic action here:
 3036     bool on_visible_page_of_tab_control = false;
 3037     if (control.tab_control_index < MAX_TAB_CONTROLS) // This control belongs to a tab control (must check this even though FindTabControl() does too).
 3038     {
 3039         if (owning_tab_control) // Its tab control exists...
 3040         {
 3041             HWND tab_dialog = (HWND)GetProp(owning_tab_control->hwnd, _T("ahk_dlg"));
 3042             if (tab_dialog)
 3043             {
 3044                 // Controls in a Tab3 control are created as children of an invisible dialog window
 3045                 // rather than as siblings of the tab control.  This solves some visual glitches.
 3046                 parent_hwnd = tab_dialog;
 3047                 POINT pt = { opt.x, opt.y };
 3048                 MapWindowPoints(mHwnd, parent_hwnd, &pt, 1); // Translate from GUI client area to tab client area.
 3049                 opt.x = pt.x, opt.y = pt.y;
 3050             }
 3051             if (!(GetWindowLong(owning_tab_control->hwnd, GWL_STYLE) & WS_VISIBLE) // Don't use IsWindowVisible().
 3052                 || TabCtrl_GetCurSel(owning_tab_control->hwnd) != control.tab_index)
 3053                 // ... but it's not set to the page/tab that contains this control, or the entire tab control is hidden.
 3054                 style &= ~WS_VISIBLE;
 3055             else // Make the following true as long as the parent is also visible.
 3056                 on_visible_page_of_tab_control = is_parent_visible;  // For use later below.
 3057         }
 3058         else // Its tab control does not exist, so this control is kept hidden until such time that it does.
 3059             style &= ~WS_VISIBLE;
 3060     }
 3061     // else do nothing.
 3062 
 3063     switch(aControlType)
 3064     {
 3065     case GUI_CONTROL_TEXT:
 3066         // Seems best to omit SS_NOPREFIX by default so that ampersand can be used to create shortcut keys.
 3067         control.hwnd = CreateWindowEx(exstyle, _T("static"), aText, style
 3068             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL);
 3069         break;
 3070 
 3071     case GUI_CONTROL_LINK:
 3072         // Seems best to omit LWS_NOPREFIX by default so that ampersand can be used to create shortcut keys.
 3073         control.hwnd = CreateWindowEx(exstyle, _T("SysLink"), aText, style
 3074             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL);
 3075         break;
 3076 
 3077     case GUI_CONTROL_PIC:
 3078         if (opt.width == COORD_UNSPECIFIED)
 3079             opt.width = 0;  // Use zero to tell LoadPicture() to keep original width.
 3080         if (opt.height == COORD_UNSPECIFIED)
 3081             opt.height = 0;  // Use zero to tell LoadPicture() to keep original height.
 3082         // Must set its caption to aText so that documented ability to refer to a picture by its original
 3083         // filename is possible:
 3084         if (control.hwnd = CreateWindowEx(exstyle, _T("static"), aText, style
 3085             , opt.x, opt.y, opt.width, opt.height  // OK if zero, control creation should still succeed.
 3086             , parent_hwnd, control_id, g_hInstance, NULL))
 3087         {
 3088             if (!ControlLoadPicture(control, aText, opt.width, opt.height, opt.icon_number))
 3089                 break;
 3090             // UPDATE ABOUT THE BELOW: Rajat says he can't get the Smart GUI working without
 3091             // the controls retaining their original numbering/z-order.  This has to do with the fact
 3092             // that TEXT controls and PIC controls are both static.  If only PIC controls were reordered,
 3093             // that's not enough to make things work for him.  If only TEXT controls were ALSO reordered
 3094             // to be at the top of the list (so that all statics retain their original ordering with
 3095             // respect to each other) I have a 90% expectation (based on testing) that prefix/shortcut
 3096             // keys inside static text controls would jump to the wrong control because their associated
 3097             // control seems to be based solely on z-order.  ANOTHER REASON NOT to ever change the z-order
 3098             // of controls automatically: Every time a picture is added, it would increment the z-order
 3099             // number of all other control types by 1.  This is bad especially for text controls because
 3100             // they are static just like pictures, and thus their Class+NN "unique ID" would change (as
 3101             // seen by the ControlXXX commands) if a picture were ever added after a window was shown.
 3102             // Older note: calling SetWindowPos() with HWND_TOPMOST doesn't seem to provide any useful
 3103             // effect that I could discern (by contrast, HWND_TOP does actually move a control to the
 3104             // top of the z-order).
 3105             // The below is OBSOLETE and its code further below is commented out:
 3106             // Facts about how overlapping controls are drawn vs. which one receives mouse clicks:
 3107             // 1) The first control created is at the top of the Z-order (i.e. the lowest z-order number
 3108             //    and the first in tab navigation), the second is next, and so on.
 3109             // 2) Controls get drawn in ascending Z-order (i.e. the first control is drawn first
 3110             //    and any later controls that overlap are drawn on top of it, except for controls that
 3111             //    have WS_CLIPSIBLINGS).
 3112             // 3) When a user clicks a point that contains two overlapping controls and each control is
 3113             //    capable of capturing clicks, the one closer to the top captures the click even though it
 3114             //    was drawn beneath (overlapped by) the other control.
 3115             // Because of this behavior, the following policy seems best:
 3116             // 1) Move all static images to the top of the Z-order so that other controls are always
 3117             //    drawn on top of them.  This is done because it seems to be the behavior that would
 3118             //    be desired at least 90% of the time.
 3119             // 2) Do not do the same for static text and GroupBoxes because it seems too rare that
 3120             //    overlapping would be done in such cases, and even if it is done it seems more
 3121             //    flexible to allow the order in which the controls were created to determine how they
 3122             //    overlap and which one get the clicks.
 3123             //
 3124             // Rather than push static pictures to the top in the reverse order they were created -- 
 3125             // which might be a little more intuitive since the ones created first would then always
 3126             // be "behind" ones created later -- for simplicity, we do it here at the time the control
 3127             // is created.  This avoids complications such as a picture being added after the
 3128             // window is shown for the first time and then not getting sent to the top where it
 3129             // should be.  Update: The control is now kept in its original order for two reasons:
 3130             // 1) The reason mentioned above (that later images should overlap earlier images).
 3131             // 2) Allows Rajat's SmartGUI Creator to support picture controls (due to its grid background pic).
 3132             // First find the last picture control in the array.  The last one should already be beneath
 3133             // all the others in the z-order due to having been originally added using the below method.
 3134             // Might eventually need to switch to EnumWindows() if it's possible for Z-order to be
 3135             // altered, or controls to be inserted between others, by any commands in the future.
 3136             //GuiIndexType index_of_last_picture = UINT_MAX;
 3137             //for (u = 0; u < mControlCount; ++u)
 3138             //{
 3139             //  if (mControl[u].type == GUI_CONTROL_PIC)
 3140             //      index_of_last_picture = u;
 3141             //}
 3142             //if (index_of_last_picture == UINT_MAX) // There are no other pictures, so put this one on top.
 3143             //  SetWindowPos(control.hwnd, HWND_TOP, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_NOMOVE|SWP_NOSIZE);
 3144             //else // Put this picture after the last picture in the z-order.
 3145             //  SetWindowPos(control.hwnd, mControl[index_of_last_picture].hwnd, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_NOMOVE|SWP_NOSIZE);
 3146             //// Adjust to control's actual size in case it changed for any reason (failure to load picture, etc.)
 3147             retrieve_dimensions = true;
 3148         }
 3149         break;
 3150 
 3151     case GUI_CONTROL_GROUPBOX:
 3152         // In this case, BS_MULTILINE will obey literal newlines in the text, but it does not automatically
 3153         // wrap the text, at least on XP.  Since it's strange-looking to have multiple lines, newlines
 3154         // should be rarely present anyway.  Also, BS_NOTIFY seems to have no effect on GroupBoxes (it
 3155         // never sends any BN_CLICKED/BN_DBLCLK messages).  This has been verified twice.
 3156         control.hwnd = CreateWindowEx(exstyle, _T("button"), aText, style
 3157             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL);
 3158         break;
 3159 
 3160     case GUI_CONTROL_BUTTON:
 3161         // For all "button" type controls, BS_MULTILINE is included by default so that any literal
 3162         // newlines in the button's name will start a new line of text as the user intended.
 3163         // In addition, this causes automatic wrapping to occur if the user specified a width
 3164         // too small to fit the entire line.
 3165         if (control.hwnd = CreateWindowEx(exstyle, _T("button"), aText, style
 3166             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3167         {
 3168             if (style & BS_DEFPUSHBUTTON)
 3169             {
 3170                 // First remove the style from the old default button, if there is one:
 3171                 if (mDefaultButtonIndex < mControlCount)
 3172                 {
 3173                     // MSDN says this is necessary in some cases:
 3174                     // Since the window might be visible at this point, send BM_SETSTYLE rather than
 3175                     // SetWindowLong() so that the button will get redrawn.  Update: The redraw doesn't
 3176                     // actually seem to happen (both the old and the new buttons retain the default-button
 3177                     // appearance until the window gets entirely redraw such as via alt-tab).  This is fairly
 3178                     // inexplicable since the exact same technique works with "GuiControl +Default".  In any
 3179                     // case, this is kept because it also serves to change the default button appearance later,
 3180                     // which is received in the WindowProc via WM_COMMAND:
 3181                     SendMessage(mControl[mDefaultButtonIndex].hwnd, BM_SETSTYLE
 3182                         , (WPARAM)LOWORD((GetWindowLong(mControl[mDefaultButtonIndex].hwnd, GWL_STYLE) & ~BS_DEFPUSHBUTTON))
 3183                         , MAKELPARAM(TRUE, 0)); // Redraw = yes. It's probably smart enough not to do it if the window is hidden.
 3184                     // The below attempts to get the old button to lose its default-border failed.  This might
 3185                     // be due to the fact that if the window hasn't yet been shown for the first time, its
 3186                     // client area isn't yet the right size, so the OS decides that no update is needed since
 3187                     // the control is probably outside the boundaries of the window:
 3188                     //InvalidateRect(mHwnd, NULL, TRUE);
 3189                     //GetClientRect(mControl[mDefaultButtonIndex].hwnd, &client_rect);
 3190                     //InvalidateRect(mControl[mDefaultButtonIndex].hwnd, &client_rect, TRUE);
 3191                     //ShowWindow(mHwnd, SW_SHOWNOACTIVATE); // i.e. don't activate it if it wasn't before.
 3192                     //ShowWindow(mHwnd, SW_HIDE);
 3193                     //UpdateWindow(mHwnd);
 3194                     //SendMessage(mHwnd, WM_NCPAINT, 1, 0);
 3195                     //RedrawWindow(mHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW);
 3196                     //SetWindowPos(mHwnd, NULL, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
 3197                 }
 3198                 mDefaultButtonIndex = mControlCount;
 3199                 SendMessage(mHwnd, DM_SETDEFID, (WPARAM)GUI_INDEX_TO_ID(mDefaultButtonIndex), 0);
 3200                 // Strangely, in spite of the control having been created with the BS_DEFPUSHBUTTON style,
 3201                 // need to send BM_SETSTYLE or else the default button will lack its visual style when the
 3202                 // dialog is first shown.  Also strange is that the following must be done *after*
 3203                 // removing the visual/default style from the old default button and/or after doing
 3204                 // DM_SETDEFID above.
 3205                 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.
 3206             }
 3207         }
 3208         break;
 3209 
 3210     case GUI_CONTROL_CHECKBOX:
 3211         // The BS_NOTIFY style is not a good idea for checkboxes because although it causes the control
 3212         // to send BN_DBLCLK messages, any rapid clicks by the user on (for example) a tri-state checkbox
 3213         // are seen only as one click for the purpose of changing the box's state.
 3214         if (control.hwnd = CreateWindowEx(exstyle, _T("button"), aText, style
 3215             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3216         {
 3217             if (opt.checked != BST_UNCHECKED) // Set the specified state.
 3218                 SendMessage(control.hwnd, BM_SETCHECK, opt.checked, 0);
 3219         }
 3220         break;
 3221 
 3222     case GUI_CONTROL_RADIO:
 3223         control.hwnd = CreateWindowEx(exstyle, _T("button"), aText, style
 3224             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL);
 3225         // opt.checked is handled later below.
 3226         break;
 3227 
 3228     case GUI_CONTROL_DROPDOWNLIST:
 3229     case GUI_CONTROL_COMBOBOX:
 3230         // It has been verified that that EM_LIMITTEXT has no effect when sent directly
 3231         // to a ComboBox hwnd; however, it might work if sent to its edit-child. But for now,
 3232         // a Combobox can only be limited to its visible width.  Later, there might
 3233         // be a way to send a message to its child control to limit its width directly.
 3234         if (opt.limit && control.type == GUI_CONTROL_COMBOBOX)
 3235             style &= ~CBS_AUTOHSCROLL;
 3236         // Since the control's variable can change, it seems best to pass in the empty string
 3237         // as the control's caption, rather than the name of the variable.  The name of the variable
 3238         // isn't that useful anymore anyway since GuiControl(Get) can access controls directly by
 3239         // their current output-var names:
 3240         if (control.hwnd = CreateWindowEx(exstyle, _T("Combobox"), _T(""), style
 3241             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3242         {
 3243             // Set font unconditionally to simplify calculations, which help ensure that at least one item
 3244             // in the DropDownList/Combo is visible when the list drops down:
 3245             GUI_SETFONT // Set font in preparation for asking it how tall each item is.
 3246             if (calc_control_height_from_row_count)
 3247             {
 3248                 item_height = (int)SendMessage(control.hwnd, CB_GETITEMHEIGHT, 0, 0);
 3249                 // Note that at this stage, height should contain a explicitly-specified height or height
 3250                 // estimate from the above, even if row_count is greater than 0.
 3251                 // The below calculation may need some fine tuning:
 3252                 int cbs_extra_height = ((style & CBS_SIMPLE) && !(style & CBS_DROPDOWN)) ? 4 : 2;
 3253                 min_list_height = (2 * item_height) + GUI_CTL_VERTICAL_DEADSPACE + cbs_extra_height;
 3254                 if (opt.height < min_list_height) // Adjust so that at least 1 item can be shown.
 3255                     opt.height = min_list_height;
 3256                 else if (opt.row_count > 0)
 3257                     // Now that we know the true item height (since the control has been created and we asked
 3258                     // it), resize the control to try to get it to the match the specified number of rows.
 3259                     // +2 seems to be the exact amount needed to prevent partial rows from showing
 3260                     // on all font sizes and themes when NOINTEGRALHEIGHT is in effect:
 3261                     opt.height = (int)(opt.row_count * item_height) + GUI_CTL_VERTICAL_DEADSPACE + cbs_extra_height;
 3262             }
 3263             MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint, since it might be visible.
 3264             // Since combo's size is created to accommodate its drop-down height, adjust our true height
 3265             // to its actual collapsed size.  This true height is used for auto-positioning the next
 3266             // control, if it uses auto-positioning.  It might be possible for it's width to be different
 3267             // also, such as if it snaps to a certain minimize width if one too small was specified,
 3268             // so that is recalculated too:
 3269             retrieve_dimensions = true;
 3270         }
 3271         break;
 3272 
 3273     case GUI_CONTROL_LISTBOX:
 3274         // See GUI_CONTROL_COMBOBOX above for why empty string is passed in as the caption:
 3275         if (control.hwnd = CreateWindowEx(exstyle, _T("Listbox"), _T(""), style
 3276             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3277         {
 3278             if (opt.tabstop_count)
 3279                 SendMessage(control.hwnd, LB_SETTABSTOPS, opt.tabstop_count, (LPARAM)opt.tabstop);
 3280             // For now, it seems best to always override a height that would cause zero items to be
 3281             // displayed.  This is because there is a very thin control visible even if the height
 3282             // is explicitly set to zero, which seems pointless (there are other ways to draw thin
 3283             // looking objects for unusual purposes anyway).
 3284             // Set font unconditionally to simplify calculations, which help ensure that at least one item
 3285             // in the DropDownList/Combo is visible when the list drops down:
 3286             GUI_SETFONT // Set font in preparation for asking it how tall each item is.
 3287             item_height = (int)SendMessage(control.hwnd, LB_GETITEMHEIGHT, 0, 0);
 3288             // Note that at this stage, height should contain a explicitly-specified height or height
 3289             // estimate from the above, even if opt.row_count is greater than 0.
 3290             min_list_height = item_height + GUI_CTL_VERTICAL_DEADSPACE;
 3291             if (style & WS_HSCROLL)
 3292                 // Assume bar will be actually appear even though it won't in the rare case where
 3293                 // its specified pixel-width is smaller than the width of the window:
 3294                 min_list_height += GetSystemMetrics(SM_CYHSCROLL);
 3295             if (opt.height < min_list_height) // Adjust so that at least 1 item can be shown.
 3296                 opt.height = min_list_height;
 3297             else if (opt.row_count > 0)
 3298             {
 3299                 // Now that we know the true item height (since the control has been created and we asked
 3300                 // it), resize the control to try to get it to the match the specified number of rows.
 3301                 opt.height = (int)(opt.row_count * item_height) + GUI_CTL_VERTICAL_DEADSPACE;
 3302                 if (style & WS_HSCROLL)
 3303                     // Assume bar will be actually appear even though it won't in the rare case where
 3304                     // its specified pixel-width is smaller than the width of the window:
 3305                 opt.height += GetSystemMetrics(SM_CYHSCROLL);
 3306             }
 3307             MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint, since it might be visible.
 3308             // Since by default, the OS adjusts list's height to prevent a partial item from showing
 3309             // (LBS_NOINTEGRALHEIGHT), fetch the actual height for possible use in positioning the
 3310             // next control:
 3311             retrieve_dimensions = true;
 3312         }
 3313         break;
 3314 
 3315     case GUI_CONTROL_LISTVIEW:
 3316         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.
 3317             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).
 3318         if (control.hwnd = CreateWindowEx(exstyle, WC_LISTVIEW, _T(""), style, opt.x, opt.y // exstyle does apply to ListViews.
 3319             , opt.width, opt.height == COORD_UNSPECIFIED ? 200 : opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3320         {
 3321             if (   !(control.union_lv_attrib = (lv_attrib_type *)malloc(sizeof(lv_attrib_type)))   )
 3322             {
 3323                 // Since mem alloc problem is so rare just get rid of the control and flag it to be reported
 3324                 // later below as "cannot create control".  Doing this avoids the need to every worry whether
 3325                 // control.union_lv_attrib is NULL in other places.
 3326                 DestroyWindow(control.hwnd);
 3327                 control.hwnd = NULL;
 3328                 break;
 3329             }
 3330             // Otherwise:
 3331             mCurrentListView = &control;
 3332             ZeroMemory(control.union_lv_attrib, sizeof(lv_attrib_type));
 3333             control.union_lv_attrib->sorted_by_col = -1; // Indicate that there is currently no sort order.
 3334             control.union_lv_attrib->no_auto_sort = opt.listview_no_auto_sort;
 3335 
 3336             // v1.0.36.06: If this ListView is owned by a tab control, flag that tab control as needing
 3337             // to stay after all of its controls in the z-order.  This solves ListView-inside-Tab redrawing
 3338             // problems, namely the disappearance of the ListView or an incomplete drawing of it.
 3339             if (owning_tab_control)
 3340                 owning_tab_control->attrib |= GUI_CONTROL_ATTRIB_ALTBEHAVIOR;
 3341 
 3342             // Seems best to put tile view into effect before applying any styles that might be dependent upon it:
 3343             if (opt.listview_view == LV_VIEW_TILE) // An earlier stage has verified that this is true only if OS is XP or later.
 3344                 SendMessage(control.hwnd, LVM_SETVIEW, LV_VIEW_TILE, 0);
 3345             if (opt.listview_style) // This is a third set of styles that exist in addition to normal & extended.
 3346                 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.
 3347             ControlSetListViewOptions(control, opt); // Relies on adjustments to opt.color_changed and color_bk done higher above.
 3348 
 3349             if (opt.height == COORD_UNSPECIFIED) // Adjust the control's size to fit opt.row_count rows.
 3350             {
 3351                 // Known limitation: This will be inaccurate if an ImageList is later assigned to the
 3352                 // ListView because that increases the height of each row slightly (or a lot if
 3353                 // a large-icon list is forced into Details/Report view and other views that are
 3354                 // traditionally small-icon).  The code size and complexity of trying to compensate
 3355                 // for this doesn't seem likely to be worth it.
 3356                 GUI_SETFONT  // Required before asking it for a height estimate.
 3357                 switch (opt.listview_view)
 3358                 {
 3359                 case LVS_REPORT:
 3360                     // The following formula has been tested on XP with the point sizes 8, 9, 10, 12, 14, and 18 for:
 3361                     // Verdana
 3362                     // Courier New
 3363                     // Gui Default Font
 3364                     // Times New Roman
 3365                     opt.height = 4 + HIWORD(ListView_ApproximateViewRect(control.hwnd, -1, -1
 3366                         , (WPARAM)opt.row_count - 1)); // -1 seems to be needed to make it calculate right for LVS_REPORT.
 3367                     // Above: It seems best to exclude any horiz. scroll bar from consideration, even though it will
 3368                     // block the last row if bar is present.  The bar can be dismissed by manually dragging the
 3369                     // column dividers or using the GuiControl auto-size methods.
 3370                     // Note that ListView_ApproximateViewRect() is not available on 95/NT4 that lack
 3371                     // comctl32.dll 4.70+ distributed with MSIE 3.x  Therefore, rather than having a possibly-
 3372                     // complicated work around in the code to detect DLL version, it will be documented in
 3373                     // the help file that the "rows" method will produce an incorrect height on those platforms.
 3374                     break;
 3375 
 3376                 case LV_VIEW_TILE: // This one can be safely integrated with the LVS ones because it doesn't overlap with them.
 3377                     // The following approach doesn't seem to give back useful info about the total height of a
 3378                     // tile and the border beneath it, so it isn't used:
 3379                     //LVTILEVIEWINFO tvi;
 3380                     //tvi.cbSize = sizeof(LVTILEVIEWINFO);
 3381                     //tvi.dwMask = LVTVIM_TILESIZE | LVTVIM_LABELMARGIN;
 3382                     //ListView_GetTileViewInfo(control.hwnd, &tvi);
 3383                     // The following might not be perfect for integral scrolling purposes, but it does seem
 3384                     // correct in terms of allowing exactly the right number of rows to be visible when the
 3385                     // control is scrolled all the way to the top. It's also correct for eliminating a
 3386                     // vertical scroll bar if the icons all fit into the specified number of rows. Tested on
 3387                     // XP Theme and Classic theme.
 3388                     opt.height = 7 + (int)((HIWORD(ListView_GetItemSpacing(control.hwnd, FALSE)) - 3) * opt.row_count);
 3389                     break;
 3390 
 3391                 default: // Namely the following:
 3392                 //case LVS_ICON:
 3393                 //case LVS_SMALLICON:
 3394                 //case LVS_LIST:
 3395                     // For these non-report views, it seems far better to define row_count as the number of
 3396                     // icons that can fit vertically rather than as the total number of icons, because the
 3397                     // latter can result in heights that vary based on too many factors, resulting in too
 3398                     // much inconsistency.
 3399                     GUI_SET_HDC
 3400                     GetTextMetrics(hdc, &tm);
 3401                     if (opt.listview_view == LVS_ICON)
 3402                     {
 3403                         // The vertical space between icons is not dependent upon font size.  In other words,
 3404                         // the control's total height to fit exactly N rows would be icon_height*N plus
 3405                         // icon_spacing*(N-1).  However, the font height is added so that the last row has
 3406                         // enough extra room to display one line of text beneath the icon.  The first/constant
 3407                         // number below is a combination of two components: 1) The control's internal margin
 3408                         // that it maintains to decide when to display scroll bars (perhaps 3 above and 3 below).
 3409                         // The space between the icon and its first line of text (which seems constant, perhaps 10).
 3410                         opt.height = 16 + tm.tmHeight + (int)(GetSystemMetrics(SM_CYICON) * opt.row_count
 3411                             + HIWORD(ListView_GetItemSpacing(control.hwnd, FALSE) * (opt.row_count - 1)));
 3412                         // More complex and doesn't seem as accurate:
 3413                         //float half_icon_spacing = 0.5F * HIWORD(ListView_GetItemSpacing(control.hwnd, FALSE));
 3414                         //opt.height = (int)(((HIWORD(ListView_ApproximateViewRect(control.hwnd, 5, 5, 1)) 
 3415                         //  + half_icon_spacing + 4) * opt.row_count) + ((half_icon_spacing - 17) * (opt.row_count - 1)));
 3416                     }
 3417                     else // SMALLICON or LIST. For simplicity, it's done the same way for both, though it doesn't work as well for LIST.
 3418                     {
 3419                         // Seems way too high even with "TRUE": HIWORD(ListView_GetItemSpacing(control.hwnd, TRUE)
 3420                         int cy_smicon = GetSystemMetrics(SM_CYSMICON);
 3421                         // 11 seems to be the right value to prevent unwanted vertical scroll bar in SMALLICON view:
 3422                         opt.height = 11 + (int)((cy_smicon > tm.tmHeight ? cy_smicon : tm.tmHeight) * opt.row_count
 3423                             + 1 * (opt.row_count - 1));
 3424                         break;
 3425                     }
 3426                     break;
 3427                 } // switch()
 3428                 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.
 3429             } // if (opt.height == COORD_UNSPECIFIED)
 3430         } // CreateWindowEx() succeeded.
 3431         break;
 3432 
 3433     case GUI_CONTROL_TREEVIEW:
 3434         if (control.hwnd = CreateWindowEx(exstyle, WC_TREEVIEW, _T(""), style, opt.x, opt.y
 3435             , opt.width, opt.height == COORD_UNSPECIFIED ? 200 : opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3436         {
 3437             mCurrentTreeView = &control;
 3438             if (opt.checked)
 3439                 // Testing confirms that unless the following advice is applied, an item's checkbox cannot
 3440                 // be checked immediately after the item is created:
 3441                 // MSDN: If you want to use the checkbox style, you must set the TVS_CHECKBOXES style (with
 3442                 // SetWindowLong) after you create the tree-view control and before you populate the tree.
 3443                 // Otherwise, the checkboxes might appear unchecked, depending on timing issues.
 3444                 SetWindowLong(control.hwnd, GWL_STYLE, style | TVS_CHECKBOXES);
 3445             ControlSetTreeViewOptions(control, opt); // Relies on adjustments to opt.color_changed and color_bk done higher above.
 3446             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.
 3447                 TreeView_SetImageList(control.hwnd, opt.himagelist, TVSIL_NORMAL); // Currently no error reporting.
 3448 
 3449             if (opt.height == COORD_UNSPECIFIED) // Adjust the control's size to fit opt.row_count rows.
 3450             {
 3451                 // Known limitation (may exist for TreeViews the same as it does for ListViews):
 3452                 // The follow might be inaccurate if an ImageList is later assigned to the TreeView because
 3453                 // that may increase the height of each row. The code size and complexity of trying to
 3454                 // compensate for this doesn't seem likely to be worth it.
 3455                 GUI_SETFONT  // Required before asking it for a height estimate.
 3456                 opt.height = TreeView_GetItemHeight(control.hwnd);
 3457                 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.
 3458                     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
 3459                 // The following formula has been tested on XP fonts DefaultGUI, Verdana, Courier (for a few
 3460                 // point sizes).
 3461                 opt.height = 4 + (int)(opt.row_count * opt.height);
 3462                 // Above: It seems best to exclude any horiz. scroll bar from consideration, even though it will
 3463                 // block the last row if bar is present.  The bar can be dismissed by manually dragging the
 3464                 // column dividers or using the GuiControl auto-size methods.
 3465                 // Note that ListView_ApproximateViewRect() is not available on 95/NT4 that lack
 3466                 // comctl32.dll 4.70+ distributed with MSIE 3.x  Therefore, rather than having a possibly-
 3467                 // complicated work around in the code to detect DLL version, it will be documented in
 3468                 // the help file that the "rows" method will produce an incorrect height on those platforms.
 3469                 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.
 3470             } // if (opt.height == COORD_UNSPECIFIED)
 3471         } // CreateWindowEx() succeeded.
 3472     break;
 3473 
 3474     case GUI_CONTROL_EDIT:
 3475         if (!(style & ES_MULTILINE)) // ES_MULTILINE was not explicitly or automatically specified.
 3476         {
 3477             if (opt.limit < 0) // This is the signal to limit input length to visible width of field.
 3478                 // But it can only work if the Edit isn't a multiline.
 3479                 style &= ~(WS_HSCROLL|ES_AUTOHSCROLL); // Enable the limiting style.
 3480             else // Since this is a single-line edit, add AutoHScroll if it wasn't explicitly removed.
 3481                 style |= ES_AUTOHSCROLL & ~opt.style_remove;
 3482                 // But no attempt is made to turn off WS_VSCROLL or ES_WANTRETURN since those might have some
 3483                 // usefulness even in a single-line edit?  In any case, it seems too overprotective to do so.
 3484         }
 3485         // malloc() is done because edit controls in NT/2k/XP support more than 64K.
 3486         // Mem alloc errors are so rare (since the text is usually less than 32K/64K) that no error is displayed.
 3487         // Instead, the un-translated text is put in directly.  Also, translation is not done for
 3488         // single-line edits since they can't display line breaks correctly anyway.
 3489         // Note that TranslateLFtoCRLF() will return the original buffer we gave it if no translation
 3490         // is needed.  Otherwise, it will return a new buffer which we are responsible for freeing
 3491         // when done (or NULL if it failed to allocate the memory).
 3492         malloc_buf = (*aText && (style & ES_MULTILINE)) ? TranslateLFtoCRLF(aText) : aText;
 3493         if (control.hwnd = CreateWindowEx(exstyle, _T("edit"), malloc_buf ? malloc_buf : aText, style  // malloc_buf is checked again in case mem alloc failed.
 3494             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3495         {
 3496             // As documented in MSDN, setting a password char will have no effect for multi-line edits
 3497             // since they do not support password/mask char.
 3498             // It seems best to allow password_char to be a literal asterisk so that there's a way to
 3499             // have asterisk vs. bullet/closed-circle on OSes that default to bullet.
 3500             if ((style & ES_PASSWORD) && opt.password_char) // Override default.
 3501                 SendMessage(control.hwnd, EM_SETPASSWORDCHAR, (WPARAM)opt.password_char, 0);
 3502             if (opt.limit < 0)
 3503                 opt.limit = 0;
 3504             //else leave it as the zero (unlimited) or positive (restricted) limit already set.
 3505             // Now set the limit. Specifying a limit of zero opens the control to its maximum text capacity,
 3506             // which removes the 32K size restriction.  Testing shows that this does not increase the actual
 3507             // amount of memory used for controls containing small amounts of text.  All it does is allow
 3508             // the control to allocate more memory as the user enters text.
 3509             SendMessage(control.hwnd, EM_LIMITTEXT, (WPARAM)opt.limit, 0); // EM_LIMITTEXT == EM_SETLIMITTEXT
 3510             if (opt.tabstop_count)
 3511                 SendMessage(control.hwnd, EM_SETTABSTOPS, opt.tabstop_count, (LPARAM)opt.tabstop);
 3512         }
 3513         if (malloc_buf && malloc_buf != aText)
 3514             free(malloc_buf);
 3515         break;
 3516 
 3517     case GUI_CONTROL_DATETIME:
 3518     {
 3519         bool use_custom_format = false;
 3520         if (*aText)
 3521         {
 3522             // DTS_SHORTDATEFORMAT and DTS_SHORTDATECENTURYFORMAT seem to produce identical results
 3523             // (both display 4-digit year), at least on XP.  Perhaps DTS_SHORTDATECENTURYFORMAT is
 3524             // obsolete.  In any case, it's uncommon so for simplicity, is not a named style.  It
 3525             // can always be applied numerically if desired. Update: DTS_SHORTDATECENTURYFORMAT is
 3526             // now applied by default higher above, which can be overridden explicitly via -0x0C
 3527             // in the control's options.
 3528             if (!_tcsicmp(aText, _T("LongDate"))) // LongDate seems more readable than "Long".  It also matches the keyword used by FormatTime.
 3529                 style = (style & ~(DTS_SHORTDATECENTURYFORMAT | DTS_TIMEFORMAT)) | DTS_LONGDATEFORMAT; // Purify.
 3530             else if (!_tcsicmp(aText, _T("Time")))
 3531                 style = (style & ~(DTS_SHORTDATECENTURYFORMAT | DTS_LONGDATEFORMAT)) | DTS_TIMEFORMAT;  // Purify.
 3532             else // Custom format. Don't purify (to retain the underlying default in case custom format is ever removed).
 3533                 use_custom_format = true;
 3534         }
 3535         if (opt.choice == 2) // "ChooseNone" was present, so ensure DTS_SHOWNONE is present to allow it.
 3536             style |= DTS_SHOWNONE;
 3537         //else it's blank, so retain the default DTS_SHORTDATEFORMAT (0x0000).
 3538         if (control.hwnd = CreateWindowEx(exstyle, DATETIMEPICK_CLASS, _T(""), style
 3539             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3540         {
 3541             if (use_custom_format)
 3542                 DateTime_SetFormat(control.hwnd, aText);
 3543             //else keep the default or the format set via style higher above.
 3544             // Feels safer to do range prior to selection even though unlike GUI_CONTROL_MONTHCAL,
 3545             // GUI_CONTROL_DATETIME tolerates them in the reverse order when one doesn't fit the other.
 3546             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.
 3547                 DateTime_SetRange(control.hwnd, opt.gdtr_range, opt.sys_time_range);
 3548             //else keep default range, which is "unrestricted range".
 3549             if (opt.choice) // The option "ChooseYYYYMMDD" was present and valid (or ChooseNone was present, choice==2)
 3550                 DateTime_SetSystemtime(control.hwnd, opt.choice == 1 ? GDT_VALID : GDT_NONE, opt.sys_time);
 3551             //else keep default, which is although undocumented appears to be today's date+time, which certainly is the expected default.
 3552             if (control.union_color != CLR_DEFAULT)
 3553                 DateTime_SetMonthCalColor(control.hwnd, MCSC_TEXT, control.union_color);
 3554             // Note: The DateTime_SetMonthCalFont() macro is never used because apparently it's not required
 3555             // to set the font, or even to repaint.
 3556         }
 3557         break;
 3558     }
 3559 
 3560     case GUI_CONTROL_MONTHCAL:
 3561         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.)
 3562         {
 3563             opt.gdtr = YYYYMMDDToSystemTime2(aText, opt.sys_time);
 3564             if (opt.gdtr == (GDTR_MIN | GDTR_MAX)) // When range is present, multi-select is automatically put into effect.
 3565                 style |= MCS_MULTISELECT;  // Must be applied during control creation since it can't be changed afterward.
 3566         }
 3567         // Create the control with arbitrary width/height if no width/height were explicitly specified.
 3568         // It will be resized after creation by querying the control:
 3569         if (control.hwnd = CreateWindowEx(exstyle, MONTHCAL_CLASS, _T(""), style, opt.x, opt.y
 3570             , opt.width < 0 ? 100 : opt.width  // Negative width has special meaning upon creation (see below).
 3571             , opt.height == COORD_UNSPECIFIED ? 100 : opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3572         {
 3573             if (style & MCS_MULTISELECT) // Must do this prior to setting initial contents in case contents is a range greater than 7 days.
 3574                 MonthCal_SetMaxSelCount(control.hwnd, 366); // 7 days seems too restrictive a default, so expand.
 3575             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.
 3576                 MonthCal_SetRange(control.hwnd, opt.gdtr_range, opt.sys_time_range);
 3577             //else keep default range, which is "unrestricted range".
 3578             if (opt.gdtr) // An explicit selection, either a range or single date, is present.
 3579             {
 3580                 if (style & MCS_MULTISELECT) // Must use range-selection even if selection is only one date.
 3581                 {
 3582                     if (opt.gdtr == GDTR_MIN) // No maximum is present, so set maximum to minimum.
 3583                         opt.sys_time[1] = opt.sys_time[0];
 3584                     //else just max, or both are present.  Assume both for code simplicity.
 3585                     MonthCal_SetSelRange(control.hwnd, opt.sys_time);
 3586                 }
 3587                 else
 3588                     MonthCal_SetCurSel(control.hwnd, opt.sys_time);
 3589             }
 3590             //else keep default, which is although undocumented appears to be today's date+time, which certainly is the expected default.
 3591             if (control.union_color != CLR_DEFAULT)
 3592                 MonthCal_SetColor(control.hwnd, MCSC_TEXT, control.union_color);
 3593             GUI_SETFONT  // Required before asking it about its month size.
 3594             if ((opt.width == COORD_UNSPECIFIED || opt.height == COORD_UNSPECIFIED)
 3595                 && MonthCal_GetMinReqRect(control.hwnd, &rect))
 3596             {
 3597                 // Autosize width and/or height by asking the control how big each month is.
 3598                 // MSDN: "The top and left members of lpRectInfo will always be zero. The right and bottom
 3599                 // members represent the minimum cx and cy required for the control."
 3600                 if (opt.width < 0) // Negative width vs. COORD_UNSPECIFIED are different in this case.
 3601                 {
 3602                     // MSDN: "The rectangle returned by MonthCal_GetMinReqRect does not include the width
 3603                     // of the "Today" string, if it is present. If the MCS_NOTODAY style is not set,
 3604                     // retrieve the rectangle that defines the "Today" string width by calling the
 3605                     // MonthCal_GetMaxTodayWidth macro. Use the larger of the two rectangles to ensure
 3606                     // that the "Today" string is not clipped.
 3607                     int month_width;
 3608                     if (style & MCS_NOTODAY) // No today-string, so width is always that from GetMinReqRect.
 3609                         month_width = rect.right;
 3610                     else // There will be a today-string present, so use the greater of the two widths.
 3611                     {
 3612                         month_width = MonthCal_GetMaxTodayWidth(control.hwnd);
 3613                         if (month_width < rect.right)
 3614                             month_width = rect.right;
 3615                     }
 3616                     if (opt.width == COORD_UNSPECIFIED) // Use default, which is to provide room for a single month.
 3617                         opt.width = month_width;
 3618                     else // It's some explicit negative number.  Use it as a multiplier to provide multiple months.
 3619                     {
 3620                         // Multiple months must need a little extra room for border between: 0.02 but 0.03 is okay.
 3621                         // For safety, a larger value is used.
 3622                         opt.width = -opt.width;
 3623                         // Provide room for each separator.  There's one separator for each month after the
 3624                         // first, and the separator always seems to be exactly 6 regardless of font face/size.
 3625                         // This has been tested on both Classic and XP themes.
 3626                         opt.width = opt.width*month_width + (opt.width - 1)*6;
 3627                     }
 3628                 }
 3629                 if (opt.height == COORD_UNSPECIFIED)
 3630                 {
 3631                     opt.height = rect.bottom; // Init for default and for use below (room for only a single month's height).
 3632                     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).
 3633                     {
 3634                         // Unlike horizontally stacked calendars, vertically stacking them produces no separator
 3635                         // between them.
 3636                         GUI_SET_HDC
 3637                         GetTextMetrics(hdc, &tm);
 3638                         // If there will be no today string, the height reported by MonthCal_GetMinReqRect
 3639                         // is not correct for use in calculating the height of more than one month stacked
 3640                         // vertically.  Must adjust it to make it display properly.
 3641                         if (style & MCS_NOTODAY) // No today string, but space is still reserved for it, so must compensate for that.
 3642                             opt.height += tm.tmHeight + 4; // Formula tested with Courier New and Verdana 8/10/12 with row counts between 1 and 5.
 3643                         opt.height = (int)(opt.height * opt.row_count); // Calculate height of all months.
 3644                         // Regardless of whether MCS_NOTODAY is present, the below is still the right formula.
 3645                         // Room for the today-string is reserved only once at the bottom (even without MCS_NOTODAY),
 3646                         // so need to subtract that (also note that some months have 6 rows and others only 5,
 3647                         // but there is whitespace padding in the case of 5 to make all months the same height).
 3648                         opt.height = (int)(opt.height - ((opt.row_count - 1) * (tm.tmHeight - 2)));
 3649                         // Above: -2 seems to work for Verdana and Courier 8/10/12/14/18.
 3650                     }
 3651                     //else opt.row_count was unspecified, so stay with the default set above of exactly
 3652                     // one month tall.
 3653                 }
 3654                 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.
 3655             } // Width or height was unspecified.
 3656         } // Control created OK.
 3657         break;
 3658 
 3659     case GUI_CONTROL_HOTKEY:
 3660         // In this case, not only doesn't the caption appear anywhere, it's not set either (or at least
 3661         // not retrievable via GetWindowText()):
 3662         if (control.hwnd = CreateWindowEx(exstyle, HOTKEY_CLASS, _T(""), style
 3663             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3664         {
 3665             if (*aText)
 3666                 SendMessage(control.hwnd, HKM_SETHOTKEY, TextToHotkey(aText), 0);
 3667             if (opt.limit > 0)
 3668                 SendMessage(control.hwnd, HKM_SETRULES, opt.limit, MAKELPARAM(HOTKEYF_CONTROL|HOTKEYF_ALT, 0));
 3669                 // Above must also specify Ctrl+Alt or some other default, otherwise the restriction will have
 3670                 // no effect.
 3671         }
 3672         break;
 3673 
 3674     case GUI_CONTROL_UPDOWN:
 3675         // The buddy of an up-down can meaningfully be one of the following:
 3676         //case GUI_CONTROL_EDIT:
 3677         //case GUI_CONTROL_TEXT:
 3678         //case GUI_CONTROL_GROUPBOX:
 3679         //case GUI_CONTROL_BUTTON:
 3680         //case GUI_CONTROL_CHECKBOX:
 3681         //case GUI_CONTROL_RADIO:
 3682         //case GUI_CONTROL_LISTBOX:
 3683         // (But testing shows, not these):
 3684         //case GUI_CONTROL_UPDOWN: An up-down will snap onto another up-down, but not have any meaningful effect.
 3685         //case GUI_CONTROL_DROPDOWNLIST:
 3686         //case GUI_CONTROL_COMBOBOX:
 3687         //case GUI_CONTROL_LISTVIEW:
 3688         //case GUI_CONTROL_TREEVIEW:
 3689         //case GUI_CONTROL_HOTKEY:
 3690         //case GUI_CONTROL_UPDOWN:
 3691         //case GUI_CONTROL_SLIDER:
 3692         //case GUI_CONTROL_PROGRESS:
 3693         //case GUI_CONTROL_TAB:
 3694         //case GUI_CONTROL_STATUSBAR: As expected, it doesn't work properly.
 3695 
 3696         // v1.0.44: Don't allow buddying of UpDown to StatusBar (this must be done prior to the next section).
 3697         // UPDATE: Due to rarity and user-should-know-better, this is not checked for (to reduce code size):
 3698         //if (mControlCount && prev_control.type == GUI_CONTROL_STATUSBAR)
 3699         //  style &= ~UDS_AUTOBUDDY;
 3700         // v1.0.42.02: The below is a fix for tab controls that contain a ListView so that up-downs in the
 3701         // tab control don't snap onto the tab control (due to the z-order change done by the ListView creation
 3702         // section whenever a ListView exists inside a tab control).
 3703         bool provide_buddy_manually;
 3704         if (   provide_buddy_manually = (style & UDS_AUTOBUDDY)
 3705             && (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).
 3706             || (owning_tab_control // mControlCount is greater than zero whenever owning_tab_control!=NULL
 3707                 && (owning_tab_control->attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR)))   )
 3708             style &= ~UDS_AUTOBUDDY; // Remove it during control creation to avoid up-down snapping onto tab control.
 3709 
 3710         // The control is created unconditionally because if UDS_AUTOBUDDY is in effect, need to create the
 3711         // control to find out its position and size (since it snaps to its buddy).  That size can then be
 3712         // retrieved and used to figure out how to resize the buddy in cases where its width-set-automatically
 3713         // -based-on-contents should not be squished as a result of buddying.
 3714         if (control.hwnd = CreateWindowEx(exstyle, UPDOWN_CLASS, _T(""), style
 3715             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3716         {
 3717             if (provide_buddy_manually) // v1.0.42.02 (see comment where provide_buddy_manually is initialized).
 3718                 SendMessage(control.hwnd, UDM_SETBUDDY, (WPARAM)prev_control.hwnd, 0); // See StatusBar notes above.  Also, mControlCount>0 whenever provide_buddy_manually==true.
 3719             if (   mControlCount // Ensure there is a previous control to snap onto (below relies on this check).
 3720                 && ((style & UDS_AUTOBUDDY) || provide_buddy_manually)   )
 3721             {
 3722                 // Since creation of a buddied up-down ignored the specified x/y and width/height, update them
 3723                 // for use here and also later below for updating mMaxExtentRight, etc.
 3724                 GetWindowRect(control.hwnd, &rect);
 3725                 MapWindowPoints(NULL, parent_hwnd, (LPPOINT)&rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
 3726                 opt.x = rect.left;
 3727                 opt.y = rect.top;
 3728                 opt.width = rect.right - rect.left;
 3729                 opt.height = rect.bottom - rect.top;
 3730                 // Get its buddy's rectangle for use in two places:
 3731                 RECT buddy_rect;
 3732                 GetWindowRect(prev_control.hwnd, &buddy_rect);
 3733                 MapWindowPoints(NULL, parent_hwnd, (LPPOINT)&buddy_rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
 3734                 // Note: It does not matter if UDS_HORZ is in effect because strangely, the up-down still
 3735                 // winds up on the left or right side of the buddy, not the top/bottom.
 3736                 if (mControlWidthWasSetByContents)
 3737                 {
 3738                     // Since the previous control's width was determined solely by the size of its contents,
 3739                     // enlarge the control to undo the narrowing just done by the buddying process.
 3740                     // This relies on the fact that during buddying, the UpDown was auto-sized and positioned
 3741                     // to fit its buddy.
 3742                     if (style & UDS_ALIGNRIGHT)
 3743                     {
 3744                         // Since moving an up-down's buddy is not enough to move the up-down,
 3745                         // so that must be shifted too:
 3746                         opt.x += opt.width;
 3747                         rect.right += opt.width; // Updated for use further below.
 3748                         MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint this control separately from its buddy below.
 3749                     }
 3750                     // Enlarge the buddy control to restore it to the size it had prior to being reduced by the
 3751                     // buddying process:
 3752                     buddy_rect.right += opt.width; // Must be updated for use in two places.
 3753                     MoveWindow(prev_control.hwnd, buddy_rect.left, buddy_rect.top
 3754                         , buddy_rect.right - buddy_rect.left, buddy_rect.bottom - buddy_rect.top, TRUE);
 3755                 }
 3756                 // Set x/y and width/height to be that of combined/buddied control so that auto-positioning
 3757                 // of future controls will see it as a single control:
 3758                 if (style & UDS_ALIGNRIGHT)
 3759                 {
 3760                     opt.x = buddy_rect.left;
 3761                     // Must calculate the total width of the combined control not as a sum of their two widths,
 3762                     // but as the different between right and left.  Otherwise, the width will be off by either
 3763                     // 2 or 3 because of the slight overlap between the two controls.
 3764                     opt.width = rect.right - buddy_rect.left;
 3765                 }
 3766                 else
 3767                     opt.width = buddy_rect.right - rect.left;
 3768                     //and opt.x set to the x position of the up-down, since it's on the leftmost side.
 3769                 // Leave opt.y and opt.height as-is.
 3770                 if (!opt.range_changed && prev_control.type == GUI_CONTROL_LISTBOX)
 3771                 {
 3772                     // ListBox buddy needs an inverted UpDown (if the UpDown is vertical) to work the way
 3773                     // you'd expect.
 3774                     opt.range_changed = true;
 3775                     if (style & UDS_HORZ) // Use MAXVAL because otherwise ListBox select will be restricted to the first 100 entries.
 3776                         opt.range_max = UD_MAXVAL;
 3777                     else
 3778                         opt.range_min = UD_MAXVAL;  // ListBox needs an inverted UpDown to work the way you'd expect.
 3779                 }
 3780             } // The up-down snapped onto a buddy control.
 3781             if (!opt.range_changed) // Control's default is wacky inverted negative, so provide 0-100 as a better/traditional default.
 3782             {
 3783                 opt.range_changed = true;
 3784                 opt.range_max = 100;
 3785             }
 3786             ControlSetUpDownOptions(control, opt); // This must be done prior to the below.
 3787             // Set the position unconditionally, even if aText is blank.  This causes a blank aText to be
 3788             // see as zero, which ensures the buddy has a legal starting value (which won't happen if the
 3789             // range does not include zero, since it would default to zero then).
 3790             // MSDN: "If the parameter is outside the control's specified range, nPos will be set to the nearest
 3791             // valid value."
 3792             SendMessage(control.hwnd, (control.attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) ? UDM_SETPOS32 : UDM_SETPOS
 3793                 , 0, ATOI(aText)); // Unnecessary to cast to short in the case of UDM_SETPOS, since it ignores the high-order word.
 3794         } // Control was successfully created.
 3795         break;
 3796 
 3797     case GUI_CONTROL_SLIDER:
 3798         if (control.hwnd = CreateWindowEx(exstyle, TRACKBAR_CLASS, _T(""), style
 3799             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3800         {
 3801             ControlSetSliderOptions(control, opt); // Fix for v1.0.25.08: This must be done prior to the below.
 3802             // The control automatically deals with out-of-range values by setting slider to min or max.
 3803             // MSDN: "If this value is outside the control's maximum and minimum range, the position
 3804             // is set to the maximum or minimum value."
 3805             if (*aText)
 3806                 SendMessage(control.hwnd, TBM_SETPOS, TRUE, ControlInvertSliderIfNeeded(control, ATOI(aText)));
 3807                 // Above msg has no return value.
 3808             //else leave it at the OS's default starting position (probably always the far left or top of the range).
 3809         }
 3810         break;
 3811 
 3812     case GUI_CONTROL_PROGRESS:
 3813         if (control.hwnd = CreateWindowEx(exstyle, PROGRESS_CLASS, _T(""), style
 3814             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3815         {
 3816             // Progress bars don't default to mBackgroundColorCtl for their background color because it
 3817             // would be undesired by the user 99% of the time (it usually would look bad since the bar's
 3818             // bk-color almost always matches that of its parent window).
 3819             ControlSetProgressOptions(control, opt, style); // Fix for v1.0.27.01: This must be done prior to the below.
 3820             // This has been confirmed though testing, even when the range is dynamically changed
 3821             // after the control is created to something that no longer includes the bar's current
 3822             // position: The control automatically deals with out-of-range values by setting bar to
 3823             // min or max.
 3824             if (*aText)
 3825                 SendMessage(control.hwnd, PBM_SETPOS, ATOI(aText), 0);
 3826             //else leave it at the OS's default starting position (probably always the far left or top of the range).
 3827             do_strip_theme = false;  // The above would have already stripped it if needed, so don't do it again.
 3828         }
 3829         break;
 3830 
 3831     case GUI_CONTROL_TAB:
 3832         if (control.hwnd = CreateWindowEx(exstyle, WC_TABCONTROL, _T(""), style
 3833             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3834         {
 3835             if (opt.tab_control_uses_dialog) // It's a Tab3 control.
 3836             {
 3837                 // Create a child dialog to host the controls for all tabs.
 3838                 if (!CreateTabDialog(control, opt))
 3839                 {
 3840                     DestroyWindow(control.hwnd);
 3841                     control.hwnd = NULL;
 3842                     break;
 3843                 }
 3844             }
 3845             if (opt.tab_control_autosize)
 3846                 SetProp(control.hwnd, _T("ahk_autosize"), (HANDLE)opt.tab_control_autosize);
 3847             // After a new tab control is created, default all subsequently created controls to belonging
 3848             // to the first tab of this tab control: 
 3849             mCurrentTabControlIndex = mTabControlCount;
 3850             mCurrentTabIndex = 0;
 3851             ++mTabControlCount;
 3852             // Override the tab's window-proc so that custom background color becomes possible:
 3853             g_TabClassProc = (WNDPROC)SetWindowLongPtr(control.hwnd, GWLP_WNDPROC, (LONG_PTR)TabWindowProc);
 3854             // Doesn't work to remove theme background from tab:
 3855             //MyEnableThemeDialogTexture(control.hwnd, ETDT_DISABLE);
 3856             // The above require the following line:
 3857             //#include <uxtheme.h> // For EnableThemeDialogTexture()'s constants.
 3858         }
 3859         break;
 3860         
 3861     case GUI_CONTROL_ACTIVEX:
 3862     {
 3863         static bool sAtlAxInitialized = false;
 3864         if (!sAtlAxInitialized)
 3865         {
 3866             typedef BOOL (WINAPI *MyAtlAxWinInit)();
 3867             if (HMODULE hmodAtl = LoadLibrary(_T("atl")))
 3868             {
 3869                 if (MyAtlAxWinInit fnAtlAxWinInit = (MyAtlAxWinInit)GetProcAddress(hmodAtl, "AtlAxWinInit"))
 3870                     sAtlAxInitialized = fnAtlAxWinInit();
 3871                 if (!sAtlAxInitialized)
 3872                     FreeLibrary(hmodAtl);
 3873                 // Otherwise, init was successful so don't unload it.
 3874             }
 3875             // If any of the above calls failed, attempt to create the window anyway:
 3876         }
 3877         if (control.hwnd = CreateWindowEx(exstyle, _T("AtlAxWin"), aText, style
 3878             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL))
 3879         {
 3880             IObject *activex_obj;
 3881             // This is done even if no output_var, to ensure the control was successfully created:
 3882             if (  !(activex_obj = ControlGetActiveX(control.hwnd))  )
 3883             {
 3884                 DestroyWindow(control.hwnd);
 3885                 control.hwnd = NULL;
 3886                 break;
 3887             }
 3888             if (control.output_var)
 3889                 control.output_var->AssignSkipAddRef(activex_obj); // Let the var take ownership.
 3890             else
 3891                 activex_obj->Release(); // The script can retrieve it later via GuiControlGet.
 3892         }
 3893         break;
 3894     }
 3895 
 3896     case GUI_CONTROL_CUSTOM:
 3897         if (opt.customClassAtom == 0)
 3898             return g_script.ScriptError(_T("A window class is required."));
 3899         control.hwnd = CreateWindowEx(exstyle, MAKEINTATOM(opt.customClassAtom), aText, style
 3900             , opt.x, opt.y, opt.width, opt.height, parent_hwnd, control_id, g_hInstance, NULL);
 3901         break;
 3902 
 3903     case GUI_CONTROL_STATUSBAR:
 3904         if (control.hwnd = CreateStatusWindow(style, aText, mHwnd, (UINT)(size_t)control_id))
 3905         {
 3906             mStatusBarHwnd = control.hwnd;
 3907             if (opt.color_bk != CLR_INVALID) // Explicit color change was requested.
 3908                 SendMessage(mStatusBarHwnd, SB_SETBKCOLOR, 0, opt.color_bk);
 3909         }
 3910         break;
 3911     } // switch() for control creation.
 3912 
 3913     ////////////////////////////////
 3914     // Release the HDC if necessary.
 3915     ////////////////////////////////
 3916     if (hdc)
 3917     {
 3918         if (hfont_old)
 3919         {
 3920             SelectObject(hdc, hfont_old); // Necessary to avoid memory leak.
 3921             hfont_old = NULL;
 3922         }
 3923         ReleaseDC(mHwnd, hdc);
 3924         hdc = NULL;
 3925     }
 3926 
 3927     // Below also serves as a bug check, i.e. GUI_CONTROL_INVALID or some unknown type.
 3928     if (!control.hwnd)
 3929         return g_script.ScriptError(_T("Can't create control."));
 3930     // Otherwise the above control creation succeeded.
 3931     ++mControlCount;
 3932     mControlWidthWasSetByContents = control_width_was_set_by_contents; // Set for use by next control, if any.
 3933     if (opt.hwnd_output_var) // v1.0.46.01.
 3934         opt.hwnd_output_var->AssignHWND(control.hwnd);
 3935 
 3936     if (control.type == GUI_CONTROL_RADIO)
 3937     {
 3938         if (opt.checked != BST_UNCHECKED)
 3939             ControlCheckRadioButton(control, mControlCount - 1, opt.checked); // Also handles alteration of the group's tabstop, etc.
 3940         //else since the control has just been created, there's no need to uncheck it or do any actions
 3941         // related to unchecking, such as tabstop adjustment.
 3942         mInRadioGroup = true; // Set here, only after creation was successful.
 3943     }
 3944     else // For code simplicity and due to rarity, even GUI_CONTROL_STATUSBAR starts a new radio group.
 3945         mInRadioGroup = false;
 3946 
 3947     // Check style_remove vs. style because this control might be hidden just because it was added
 3948     // to a tab that isn't active:
 3949     if (opt.style_remove & WS_VISIBLE)
 3950         control.attrib |= GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN;  // For use with tab controls.
 3951     if (opt.style_add & WS_DISABLED)
 3952         control.attrib |= GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED;
 3953 
 3954     // Strip theme from the control if called for:
 3955     // It is stripped for radios, checkboxes, and groupboxes if they have a custom text color.
 3956     // Otherwise the transparency and/or custom text color will not be obeyed on XP, at least when
 3957     // a non-Classic theme is active.  For GroupBoxes, when a theme is active, it will obey 
 3958     // custom background color but not a custom text color.  The same is true for radios and checkboxes.
 3959     if (do_strip_theme || (control.union_color != CLR_DEFAULT && (control.type == GUI_CONTROL_CHECKBOX
 3960         || control.type == GUI_CONTROL_RADIO || control.type == GUI_CONTROL_GROUPBOX)) // GroupBox needs it too.
 3961         || (control.type == GUI_CONTROL_GROUPBOX && (control.attrib & GUI_CONTROL_ATTRIB_BACKGROUND_TRANS))   ) // Tested and found to be necessary.)
 3962         MySetWindowTheme(control.hwnd, L"", L"");
 3963 
 3964     // Must set the font even if mCurrentFontIndex > 0, otherwise the bold SYSTEM_FONT will be used.
 3965     // Note: Neither the slider's buddies nor itself are affected by the font setting, so it's not applied.
 3966     // However, the buddies are affected at the time they are created if they are a type that uses a font.
 3967     if (!font_was_set && uses_font_and_text_color)
 3968         GUI_SETFONT
 3969 
 3970     if (opt.redraw == CONDITION_FALSE)
 3971         SendMessage(control.hwnd, WM_SETREDRAW, FALSE, 0); // Disable redrawing for this control to allow contents to be added to it more quickly.
 3972         // It's not necessary to do the following because by definition the control has just been created
 3973         // and thus redraw can't have been off for it previously:
 3974         //if (opt.redraw == CONDITION_TRUE) // Since redrawing is being turned back on, invalidate the control so that it updates itself.
 3975         //  InvalidateRect(control.hwnd, NULL, TRUE);
 3976 
 3977     ///////////////////////////////////////////////////
 3978     // Add any content to the control and set its font.
 3979     ///////////////////////////////////////////////////
 3980     ControlAddContents(control, aText, opt.choice); // Must be done after font-set above so that ListView columns can be auto-sized to fit their text.
 3981 
 3982     if (control.type == GUI_CONTROL_TAB && opt.row_count > 0)
 3983     {
 3984         // Now that the tabs have been added (possibly creating more than one row of tabs), resize so that
 3985         // the interior of the control has the actual number of rows specified.
 3986         GetClientRect(control.hwnd, &rect); // MSDN: "the coordinates of the upper-left corner are (0,0)"
 3987         // This is a workaround for the fact that TabCtrl_AdjustRect() seems to give an invalid
 3988         // height when the tabs are at the bottom, at least on XP.  Unfortunately, this workaround
 3989         // does not work when the tabs or on the left or right side, so don't even bother with that
 3990         // adjustment (it's very rare that a tab control would have an unspecified width anyway).
 3991         bool bottom_is_in_effect = (style & TCS_BOTTOM) && !(style & TCS_VERTICAL);
 3992         if (bottom_is_in_effect)
 3993             SetWindowLong(control.hwnd, GWL_STYLE, style & ~TCS_BOTTOM);
 3994         // Insist on a taller client area (or same height in the case of TCS_VERTICAL):
 3995         TabCtrl_AdjustRect(control.hwnd, TRUE, &rect); // Calculate new window height.
 3996         if (bottom_is_in_effect)
 3997             SetWindowLong(control.hwnd, GWL_STYLE, style);
 3998         opt.height = rect.bottom - rect.top;  // Update opt.height for here and for later use below.
 3999         // The below is commented out because TabCtrl_AdjustRect() is unable to cope with tabs on
 4000         // the left or right sides.  It would be rarely used anyway.
 4001         //if (style & TCS_VERTICAL && width_was_originally_unspecified)
 4002         //  // Also make the interior wider in this case, to make the interior as large as intended.
 4003         //  // It is a known limitation that this adjustment does not occur when the script did not
 4004         //  // specify a row_count or omitted height and row_count.
 4005         //  opt.width = rect.right - rect.left;
 4006         MoveWindow(control.hwnd, opt.x, opt.y, opt.width, opt.height, TRUE); // Repaint, since parent might be visible.
 4007     }
 4008     else if (control.type == GUI_CONTROL_TAB && opt.tab_control_uses_dialog)
 4009     {
 4010         // Update this tab control's dialog to match the tab's display area.  This should be done
 4011         // whenever the display area changes, such as when tabs are added for the first time or the
 4012         // number of tab rows changes.  It's not done if (opt.row_count > 0), because the section
 4013         // above would have already triggered an update via MoveWindow/WM_WINDOWPOSCHANGED.
 4014         UpdateTabDialog(control.hwnd);
 4015     }
 4016 
 4017     if (retrieve_dimensions) // Update to actual size for use later below.
 4018     {
 4019         GetWindowRect(control.hwnd, &rect);
 4020         opt.height = rect.bottom - rect.top;
 4021         opt.width = rect.right - rect.left;
 4022 
 4023         if (aControlType == GUI_CONTROL_LISTBOX && (style & WS_HSCROLL))
 4024         {
 4025             if (opt.hscroll_pixels < 0) // Calculate a default based on control's width.
 4026                 // Since horizontal scrollbar is relatively rarely used, no fancy method
 4027                 // such as calculating scrolling-width via LB_GETTEXTLEN & current font's
 4028                 // average width is used.
 4029                 opt.hscroll_pixels = 3 * opt.width;
 4030             // If hscroll_pixels is now zero or smaller than the width of the control, the
 4031             // scrollbar will not be shown.  But the message is still sent unconditionally
 4032             // in case it has some desirable side-effects:
 4033             SendMessage(control.hwnd, LB_SETHORIZONTALEXTENT, (WPARAM)opt.hscroll_pixels, 0);
 4034         }
 4035     }
 4036 
 4037     // v1.0.36.06: If this tab control contains a ListView, keep the tab control after all of its controls
 4038     // in the z-order.  This solves ListView-inside-Tab redrawing problems, namely the disappearance of
 4039     // the ListView or an incomplete drawing of it.  Doing it this way preserves the tab-navigation
 4040     // order of controls inside the tab control, both those above and those beneath the ListView.
 4041     // The only thing it alters is the tab navigation to the tab control itself, which will now occur
 4042     // after rather than before all the controls inside it. For most uses, this a very minor difference,
 4043     // especially given the rarity of having ListViews inside tab controls.  If this solution ever
 4044     // proves undesirable, one alternative is to somehow force the ListView to properly redraw whenever
 4045     // its inside a tab control.  Perhaps this could be done by subclassing the ListView or Tab control
 4046     // and having it do something different or additional in response to WM_ERASEBKGND.  It might
 4047     // also be done in the parent window's proc in response to WM_ERASEBKGND.
 4048     if (owning_tab_control && parent_hwnd == mHwnd) // The control is on a tab control which is its sibling.
 4049     {
 4050         // Fix for v1.0.35: Probably due to clip-siblings, adding a control within the area of a tab control
 4051         // does not properly draw the control.  This seems to apply to most/all control types.
 4052         if (on_visible_page_of_tab_control)
 4053         {
 4054             // Not enough for GUI_CONTROL_DATETIME (it's border is not drawn):
 4055             //InvalidateRect(control.hwnd, NULL, TRUE);  // TRUE is required, at least for GUI_CONTROL_DATETIME.
 4056             GetWindowRect(control.hwnd, &rect);
 4057             MapWindowPoints(NULL, mHwnd, (LPPOINT)&rect, 2); // Convert rect to client coordinates (not the same as GetClientRect()).
 4058             InvalidateRect(mHwnd, &rect, FALSE);
 4059         }
 4060         if (owning_tab_control->attrib & GUI_CONTROL_ATTRIB_ALTBEHAVIOR) // Put the tab control after the newly added control. See comment higher above.
 4061             SetWindowPos(owning_tab_control->hwnd, control.hwnd, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
 4062     }
 4063 
 4064     // v1.0.44: Must keep status bar at the bottom of the z-order so that it gets drawn last.  This alleviates
 4065     // (but does not completely prevent) other controls from overlapping it and getting drawn on top. This is
 4066     // done each time a control is added -- rather than at some single time such as when the parent window is
 4067     // first shown -- in case the script adds more controls later.
 4068     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.
 4069         SetWindowPos(mStatusBarHwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
 4070 
 4071     if (aControlType != GUI_CONTROL_STATUSBAR) // i.e. don't let status bar affect positioning of controls relative to each other.
 4072     {
 4073         if (parent_hwnd != mHwnd) // This control is in a tab dialog.
 4074         {
 4075             // Translate the coordinates back to Gui-relative client coordinates.
 4076             POINT pt = { opt.x, opt.y };
 4077             MapWindowPoints(parent_hwnd, mHwnd, &pt, 1);
 4078             opt.x = pt.x, opt.y = pt.y;
 4079         }
 4080 
 4081         ////////////////////////////////////////////////////////////////////////////////////////////////////
 4082         // Save the details of this control's position for possible use in auto-positioning the next control.
 4083         ////////////////////////////////////////////////////////////////////////////////////////////////////
 4084         mPrevX = opt.x;
 4085         mPrevY = opt.y;
 4086         mPrevWidth = opt.width;
 4087         mPrevHeight = opt.height;
 4088         int right = opt.x + opt.width;
 4089         int bottom = opt.y + opt.height;
 4090         if (parent_hwnd == mHwnd // Skip this for controls which are clipped by a parent control.
 4091             && !opt.tab_control_autosize) // For auto-sizing Tab3, don't adjust mMaxExtent until later.
 4092         {
 4093             if (right > mMaxExtentRight)
 4094                 mMaxExtentRight = right;
 4095             if (bottom > mMaxExtentDown)
 4096                 mMaxExtentDown = bottom;
 4097         }
 4098 
 4099         // As documented, always start new section for very first control, but never if this control is GUI_CONTROL_STATUSBAR.
 4100         if (opt.start_new_section || mControlCount == 1 // aControlType!=GUI_CONTROL_STATUSBAR due to check higher above.
 4101             || (mControlCount == 2 && mControl[0].type == GUI_CONTROL_STATUSBAR)) // This is the first non-statusbar control.
 4102         {
 4103             mSectionX = opt.x;
 4104             mSectionY = opt.y;
 4105             mMaxExtentRightSection = right;
 4106             mMaxExtentDownSection = bottom;
 4107         }
 4108         else
 4109         {
 4110             if (right > mMaxExtentRightSection)
 4111                 mMaxExtentRightSection = right;
 4112             if (bottom > mMaxExtentDownSection)
 4113                 mMaxExtentDownSection = bottom;
 4114         }
 4115     }
 4116 
 4117     return OK;
 4118 }
 4119 
 4120 
 4121 
 4122 ResultType GuiType::ParseOptions(LPTSTR aOptions, bool &aSetLastFoundWindow, ToggleValueType &aOwnDialogs, Var *&aHwndVar)
 4123 // This function is similar to ControlParseOptions() further below, so should be maintained alongside it.
 4124 // Caller must have already initialized aSetLastFoundWindow/, bool &aOwnDialogs with desired starting values.
 4125 // Caller must ensure that aOptions is a modifiable string, since this method temporarily alters it.
 4126 {
 4127     LONG nc_width, nc_height;
 4128 
 4129     if (mHwnd)
 4130     {
 4131         // Since window already exists, its mStyle and mExStyle members might be out-of-date due to
 4132         // "WinSet Transparent", etc.  So update them:
 4133         mStyle = GetWindowLong(mHwnd, GWL_STYLE);
 4134         mExStyle = GetWindowLong(mHwnd, GWL_EXSTYLE);
 4135     }
 4136     DWORD style_orig = mStyle;
 4137     DWORD exstyle_orig = mExStyle;
 4138 
 4139     LPTSTR pos_of_the_x, next_option, option_end;
 4140     TCHAR orig_char;
 4141     bool adding; // Whether this option is being added (+) or removed (-).
 4142 
 4143     for (next_option = aOptions; *next_option; next_option = omit_leading_whitespace(option_end))
 4144     {
 4145         if (*next_option == '-')
 4146         {
 4147             adding = false;
 4148             // omit_leading_whitespace() is not called, which enforces the fact that the option word must
 4149             // immediately follow the +/- sign.  This is done to allow the flexibility to have options
 4150             // omit the plus/minus sign, and also to reserve more flexibility for future option formats.
 4151             ++next_option;  // Point it to the option word itself.
 4152         }
 4153         else
 4154         {
 4155             // Assume option is being added in the absence of either sign.  However, when we were
 4156             // called by GuiControl(), the first option in the list must begin with +/- otherwise the cmd
 4157             // would never have been properly detected as GUICONTROL_CMD_OPTIONS in the first place.
 4158             adding = true;
 4159             if (*next_option == '+')
 4160                 ++next_option;  // Point it to the option word itself.
 4161             //else do not increment, under the assumption that the plus has been omitted from a valid
 4162             // option word and is thus an implicit plus.
 4163         }
 4164 
 4165         if (!*next_option) // In case the entire option string ends in a naked + or -.
 4166             break;
 4167         // Find the end of this option item:
 4168         if (   !(option_end = StrChrAny(next_option, _T(" \t")))   )  // Space or tab.
 4169             option_end = next_option + _tcslen(next_option); // Set to position of zero terminator instead.
 4170         if (option_end == next_option)
 4171             continue; // i.e. the string contains a + or - with a space or tab after it, which is intentionally ignored.
 4172 
 4173         // Temporarily terminate to help eliminate ambiguity for words contained inside other words,
 4174         // such as "Checked" inside of "CheckedGray":
 4175         orig_char = *option_end;
 4176         *option_end = '\0';
 4177 
 4178         // Attributes and option words:
 4179 
 4180         bool set_owner;
 4181         if ((set_owner = !_tcsnicmp(next_option, _T("Owner"), 5))
 4182               || !_tcsnicmp(next_option, _T("Parent"), 6))
 4183         {
 4184             if (!adding)
 4185                 mOwner = NULL;
 4186             else
 4187             {
 4188                 LPTSTR name = next_option + 5 + !set_owner; // 6 for "Parent"
 4189                 if (*name || !set_owner) // i.e. "+Parent" on its own is invalid (and should not default to g_hWnd).
 4190                 {
 4191                     HWND new_owner = NULL;
 4192                     if (IsPureNumeric(name, TRUE, FALSE) == PURE_INTEGER) // Allow negatives, for flexibility.
 4193                     {
 4194                         __int64 gui_num = ATOI64(name);
 4195                         if (gui_num < 1 || gui_num > 99 || (option_end - name) > 2) // See similar checks in ResolveGui() for comments.
 4196                         {
 4197                             // Something like +Owner%Hwnd%, where Hwnd may or may not be a Gui.
 4198                             if (IsWindow((HWND)gui_num))
 4199                                 new_owner = (HWND)gui_num;
 4200                         }
 4201                     }
 4202                     if (!new_owner)
 4203                     {
 4204                         // Something like +OwnerMyGui or +Owner1.
 4205                         if (GuiType *owner_gui = FindGui(name))
 4206                             new_owner = owner_gui->mHwnd;
 4207                     }
 4208                     if (new_owner && new_owner != mHwnd) // Window can't own itself!
 4209                         mOwner = new_owner;
 4210                     else
 4211                         return g_script.ScriptError(_T("Invalid or nonexistent owner or parent window."), next_option);
 4212                 }
 4213                 else
 4214                     mOwner = g_hWnd; // Make a window owned (by script's main window) omits its taskbar button.
 4215             }
 4216             if (set_owner) // +/-Owner
 4217             {
 4218                 if (mStyle & WS_CHILD)
 4219                 {
 4220                     // Since Owner and Parent are mutually-exclusive by nature, it seems appropriate for
 4221                     // +Owner to also apply -Parent.  If this wasn't done, +Owner would merely change the
 4222                     // parent window (see comments further below).
 4223                     mStyle = mStyle & ~WS_CHILD | WS_POPUP;
 4224                     if (mHwnd)
 4225                     {
 4226                         // This seems to be necessary even if SetWindowLong() is called to update the
 4227                         // style before attempting to change the owner.  By contrast, there doesn't seem
 4228                         // to be any problem with delaying the style update until after all of the other
 4229                         // options are parsed.
 4230                         SetParent(mHwnd, NULL);
 4231                     }
 4232                 }
 4233                 // Although MSDN doesn't explicitly document any way to change the owner of an existing
 4234                 // window, the following method was mentioned in a PDC talk by Raymond Chen.  MSDN does
 4235                 // say it shouldn't be used to change the parent of a child window, but maybe what it
 4236                 // actually means is that "HWNDPARENT" is a misnomer; it should've been "HWNDOWNER".
 4237                 // On the other hand, this method ACTUALLY DOES CHANGE THE PARENT WINDOW if mHwnd is a
 4238                 // child window and the check above is disabled (at least it did during testing).
 4239                 if (mHwnd)
 4240                     SetWindowLongPtr(mHwnd, GWLP_HWNDPARENT, (LONG_PTR)mOwner);
 4241             }
 4242             else // +/-Parent
 4243             {
 4244                 if (mHwnd)
 4245                     SetParent(mHwnd, mOwner);
 4246                 if (mOwner)
 4247                     mStyle = mStyle & ~WS_POPUP | WS_CHILD;
 4248                 else
 4249                     mStyle = mStyle & ~WS_CHILD | WS_POPUP;
 4250                 // The new style will be applied by a later section.
 4251             }
 4252         }
 4253 
 4254         else if (!_tcsicmp(next_option, _T("AlwaysOnTop")))
 4255         {
 4256             // If the window already exists, SetWindowLong() isn't enough.  Must use SetWindowPos()
 4257             // to make it take effect.
 4258             if (mHwnd)
 4259             {
 4260                 SetWindowPos(mHwnd, adding ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0
 4261                     , 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.
 4262                 // Fix for v1.0.41.01: Update the original style too, so that the call to SetWindowLong() later below
 4263                 // is made only if multiple styles are being changed on the same line, e.g. Gui +Disabled -SysMenu
 4264                 if (adding) exstyle_orig |= WS_EX_TOPMOST; else exstyle_orig &= ~WS_EX_TOPMOST;
 4265             }
 4266             // Fix for v1.0.41.01: The following line is now done unconditionally.  Previously, it wasn't
 4267             // done if the window already existed, which caused an example such as the following to first
 4268             // set the window always on top and then immediately afterward try to unset it via SetWindowLong
 4269             // (because mExStyle hadn't been updated to reflect the change made by SetWindowPos):
 4270             // Gui, +AlwaysOnTop +Disabled -SysMenu
 4271             if (adding) mExStyle |= WS_EX_TOPMOST; else mExStyle &= ~WS_EX_TOPMOST;
 4272         }
 4273 
 4274         else if (!_tcsicmp(next_option, _T("Border")))
 4275             if (adding) mStyle |= WS_BORDER; else mStyle &= ~WS_BORDER;
 4276 
 4277         else if (!_tcsicmp(next_option, _T("Caption")))
 4278             if (adding) mStyle |= WS_CAPTION; else mStyle = mStyle & ~WS_CAPTION;
 4279 
 4280         else if (!_tcsnicmp(next_option, _T("Delimiter"), 9))
 4281         {
 4282             next_option += 9;
 4283             // For simplicity, the value of "adding" is ignored since no use is foreseeable for "-Delimiter".
 4284             if (!_tcsicmp(next_option, _T("Tab")))
 4285                 mDelimiter = '\t';
 4286             else if (!_tcsicmp(next_option, _T("Space")))
 4287                 mDelimiter = ' ';
 4288             else
 4289                 mDelimiter = *next_option ? *next_option : '|';
 4290         }
 4291 
 4292         else if (!_tcsicmp(next_option, _T("Disabled")))
 4293         {
 4294             if (mHwnd)
 4295             {
 4296                 EnableWindow(mHwnd, adding ? FALSE : TRUE);  // Must not not apply WS_DISABLED directly because that breaks the window.
 4297                 // Fix for v1.0.41.01: Update the original style too, so that the call to SetWindowLong() later below
 4298                 // is made only if multiple styles are being changed on the same line, e.g. Gui +Disabled -SysMenu
 4299                 if (adding) style_orig |= WS_DISABLED; else style_orig &= ~WS_DISABLED;
 4300             }
 4301             // Fix for v1.0.41.01: The following line is now done unconditionally.  Previously, it wasn't
 4302             // done if the window already existed, which caused an example such as the following to first
 4303             // disable the window and then immediately afterward try to enable it via SetWindowLong
 4304             // (because mStyle hadn't been updated to reflect the change made by SetWindowPos):
 4305             // Gui, +AlwaysOnTop +Disabled -SysMenu
 4306             if (adding) mStyle |= WS_DISABLED; else mStyle &= ~WS_DISABLED;
 4307         }
 4308         
 4309         else if (!_tcsnicmp(next_option, _T("Hwnd"), 4))
 4310             aHwndVar = g_script.FindOrAddVar(next_option + 4); // ALWAYS_PREFER_LOCAL is debatable, but for simplicity it seems best since it causes HwndOutputVar to behave the same as the vVar option.
 4311 
 4312         else if (!_tcsnicmp(next_option, _T("Label"), 5)) // v1.0.44.09: Allow custom label prefix for the reasons described in SetLabels().
 4313         {
 4314             if (adding)
 4315                 SetLabels(next_option + 5);
 4316             //else !adding (-Label), which currently does nothing.  Potential future uses include:
 4317             // Disable all labels (seems too rare to be useful).
 4318             // Revert to defaults (e.g. 2GuiSize): Doesn't seem to be of much value because the caller will likely
 4319             // always know the number of the window in question (if nothing else, than via A_Gui) and can thus revert
 4320             // to defaults via something like +Label%A_Gui%Gui.
 4321             // Alternative: Could also use some char that's illegal in labels to indicate one or more of the above.
 4322         }
 4323 
 4324         else if (!_tcsicmp(next_option, _T("LastFound")))
 4325             aSetLastFoundWindow = true; // Regardless of whether "adding" is true or false.
 4326 
 4327         else if (!_tcsicmp(next_option, _T("MaximizeBox"))) // See above comment.
 4328             if (adding) mStyle |= WS_MAXIMIZEBOX|WS_SYSMENU; else mStyle &= ~WS_MAXIMIZEBOX;
 4329 
 4330         else if (!_tcsicmp(next_option, _T("MinimizeBox")))
 4331             // WS_MINIMIZEBOX requires WS_SYSMENU to take effect.  It can be explicitly omitted
 4332             // via "+MinimizeBox -SysMenu" if that functionality is ever needed.
 4333             if (adding) mStyle |= WS_MINIMIZEBOX|WS_SYSMENU; else mStyle &= ~WS_MINIMIZEBOX;
 4334 
 4335         else if (!_tcsnicmp(next_option, _T("MinSize"), 7)) // v1.0.44.13: Added for use with WM_GETMINMAXINFO.
 4336         {
 4337             next_option += 7;
 4338             if (adding)
 4339             {
 4340                 if (*next_option)
 4341                 {
 4342                     // The following will retrieve zeros if window hasn't yet been shown for the first time,
 4343                     // in which case the first showing will do the NC adjustment for us.  The overall approach
 4344                     // used here was chose to avoid any chance for Min/MaxSize to be adjusted more than once
 4345                     // to convert client size to entire-size, which would be wrong since the adjustment must be
 4346                     // applied only once.  Examples of such situations are when one of the coordinates is omitted,
 4347                     // or when +MinSize is specified prior to the first "Gui Show" but +MaxSize is specified after.
 4348                     GetNonClientArea(nc_width, nc_height);
 4349                     // _ttoi() vs. ATOI() is used below to avoid ambiguity of "x" being hex 0x vs. a delimiter.
 4350                     if ((pos_of_the_x = StrChrAny(next_option, _T("Xx"))) && pos_of_the_x[1]) // Kept simple due to rarity of transgressions and their being inconsequential.
 4351                         mMinHeight = Scale(_ttoi(pos_of_the_x + 1)) + nc_height;
 4352                     //else it's "MinSize333" or "MinSize333x", so leave height unchanged as documented.
 4353                     if (pos_of_the_x != next_option) // There's no 'x' or it lies to the right of next_option.
 4354                         mMinWidth = Scale(_ttoi(next_option)) + nc_width; // _ttoi() automatically stops converting when it reaches non-numeric character.
 4355                     //else it's "MinSizeX333", so leave width unchanged as documented.
 4356                 }
 4357                 else // Since no width or height was specified:
 4358                     // Use the window's current size. But if window hasn't yet been shown for the
 4359                     // first time, this will set the values to COORD_CENTERED, which tells the
 4360                     // first-show routine to get the total width/height upon first showing (since
 4361                     // that's where the window's initial size is determined).
 4362                     GetTotalWidthAndHeight(mMinWidth, mMinHeight);
 4363             }
 4364             else // "-MinSize", so tell the WM_GETMINMAXINFO handler to use system defaults.
 4365             {
 4366                 mMinWidth = COORD_UNSPECIFIED;
 4367                 mMinHeight = COORD_UNSPECIFIED;
 4368             }
 4369         }
 4370 
 4371         else if (!_tcsnicmp(next_option, _T("MaxSize"), 7)) // v1.0.44.13: Added for use with WM_GETMINMAXINFO.
 4372         {
 4373             // SEE "MinSize" section above for more comments because the section below is nearly identical to it.
 4374             next_option += 7;
 4375             if (adding)
 4376             {
 4377                 if (*next_option)
 4378                 {
 4379                     GetNonClientArea(nc_width, nc_height);
 4380                     if ((pos_of_the_x = StrChrAny(next_option, _T("Xx"))) && pos_of_the_x[1]) // Kept simple due to rarity of transgressions and their being inconsequential.
 4381                         mMaxHeight = Scale(_ttoi(pos_of_the_x + 1)) + nc_height;
 4382                     if (pos_of_the_x != next_option) // There's no 'x' or it lies to the right of next_option.
 4383                         mMaxWidth = Scale(_ttoi(next_option)) + nc_width; // _ttoi() automatically stops converting when it reaches non-numeric character.
 4384                 }
 4385                 else // No width or height was specified. See comment in "MinSize" for details about this.
 4386                     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.
 4387             }
 4388             else // "-MaxSize", so tell the WM_GETMINMAXINFO handler to use system defaults.
 4389             {
 4390                 mMaxWidth = COORD_UNSPECIFIED;
 4391                 mMaxHeight = COORD_UNSPECIFIED;
 4392             }
 4393         }
 4394 
 4395         else if (!_tcsicmp(next_option, _T("OwnDialogs")))
 4396             aOwnDialogs = (adding ? TOGGLED_ON : TOGGLED_OFF);
 4397 
 4398         else if (!_tcsicmp(next_option, _T("Resize"))) // Minus removes either or both.
 4399             if (adding) mStyle |= WS_SIZEBOX|WS_MAXIMIZEBOX; else mStyle &= ~(WS_SIZEBOX|WS_MAXIMIZEBOX);
 4400 
 4401         else if (!_tcsicmp(next_option, _T("SysMenu")))
 4402             if (adding) mStyle |= WS_SYSMENU; else mStyle &= ~WS_SYSMENU;
 4403 
 4404         else if (!_tcsicmp(next_option, _T("Theme")))
 4405             mUseTheme = adding;
 4406             // But don't apply/remove theme from parent window because that is usually undesirable.
 4407             // This is because even old apps running on XP still have the new parent window theme,
 4408             // at least for their title bar and title bar buttons (except console apps, maybe).
 4409 
 4410         else if (!_tcsicmp(next_option, _T("ToolWindow")))
 4411             // WS_EX_TOOLWINDOW provides narrower title bar, omits task bar button, and omits
 4412             // entry in the alt-tab menu.
 4413             if (adding) mExStyle |= WS_EX_TOOLWINDOW; else mExStyle &= ~WS_EX_TOOLWINDOW;
 4414 
 4415         else if (!_tcsicmp(next_option, _T("DPIScale")))
 4416             mUsesDPIScaling = adding;
 4417 
 4418         // This one should be near the bottom since "E" is fairly vague and might be contained at the start
 4419         // of future option words such as Edge, Exit, etc.
 4420         else if (ctoupper(*next_option) == 'E') // Extended style
 4421         {
 4422             ++next_option; // Skip over the E itself.
 4423             if (IsPureNumeric(next_option, false, false)) // Disallow whitespace in case option string ends in naked "E".
 4424             {
 4425                 // Pure numbers are assumed to be style additions or removals:
 4426                 DWORD given_exstyle = ATOU(next_option); // ATOU() for unsigned.
 4427                 if (adding)
 4428                     mExStyle |= given_exstyle;
 4429                 else
 4430                     mExStyle &= ~given_exstyle;
 4431             }
 4432         }
 4433 
 4434         else // Handle things that are more general than the above, such as single letter options and pure numbers:
 4435         {
 4436             if (IsPureNumeric(next_option)) // Above has already verified that *next_option can't be whitespace.
 4437             {
 4438                 // Pure numbers are assumed to be style additions or removals:
 4439                 DWORD given_style = ATOU(next_option); // ATOU() for unsigned.
 4440                 if (adding)
 4441                     mStyle |= given_style;
 4442                 else
 4443                     mStyle &= ~given_style;
 4444             }
 4445             else // v1.1.04: Validate Gui options.
 4446                 return g_script.ScriptError(ERR_INVALID_OPTION, next_option);
 4447         }
 4448 
 4449         *option_end = orig_char; // Undo the temporary termination because the caller needs aOptions to be unaltered.
 4450 
 4451     } // for() each item in option list
 4452 
 4453     // Besides reducing the code size and complexity, another reason all changes to style are made
 4454     // here rather than above is that multiple changes might have been made above to the style,
 4455     // and there's no point in redrawing/updating the window for each one:
 4456     if (mHwnd && (mStyle != style_orig || mExStyle != exstyle_orig))
 4457     {
 4458         // v1.0.27.01: Must do this prior to SetWindowLong() because sometimes SetWindowLong()
 4459         // traumatizes the window (such as "Gui -Caption"), making it effectively invisible
 4460         // even though its non-functional remnant is still on the screen:
 4461         bool is_visible = IsWindowVisible(mHwnd) && !IsIconic(mHwnd);
 4462 
 4463         // Since window already exists but its style has changed, attempt to update it dynamically.
 4464         if (mStyle != style_orig)
 4465             SetWindowLong(mHwnd, GWL_STYLE, mStyle);
 4466         if (mExStyle != exstyle_orig)
 4467             SetWindowLong(mHwnd, GWL_EXSTYLE, mExStyle);
 4468 
 4469         if (is_visible)
 4470         {
 4471             // Hiding then showing is the only way I've discovered to make it update.  If the window
 4472             // is not updated, a strange effect occurs where the window is still visible but can no
 4473             // longer be used at all (clicks pass right through it).  This show/hide method is less
 4474             // desirable due to possible side effects caused to any script that happens to be watching
 4475             // for its existence/non-existence, so it would be nice if some better way can be discovered
 4476             // to do this.
 4477             // Update by Lexikos: I've been unable to produce the "strange effect" described above using
 4478             // WinSet on Win2k/XP/7, so changing this section to use the same method as WinSet. If there
 4479             // are any problems, at least WinSet and Gui will be consistent.
 4480             // SetWindowPos is also necessary, otherwise the frame thickness entirely around the window
 4481             // does not get updated (just parts of it):
 4482             SetWindowPos(mHwnd, NULL, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
 4483             // ShowWindow(mHwnd, SW_HIDE);
 4484             // 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).
 4485             InvalidateRect(mHwnd, NULL, TRUE); // Quite a few styles require this to become visibly manifest.
 4486             // None of the following methods alone is enough, at least not when the window is currently active:
 4487             // 1) InvalidateRect(mHwnd, NULL, TRUE);
 4488             // 2) SendMessage(mHwnd, WM_NCPAINT, 1, 0);  // 1 = Repaint entire frame.
 4489             // 3) RedrawWindow(mHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW);
 4490             // 4) SetWindowPos(mHwnd, NULL, 0, 0, 0, 0, SWP_DRAWFRAME|SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
 4491         }
 4492         // Continue on to create the window so that code is simplified in other places by
 4493         // using the assumption that "if gui[i] object exists, so does its window".
 4494         // Another important reason this is done is that if an owner window were to be destroyed
 4495         // before the window it owns is actually created, the WM_DESTROY logic would have to check
 4496         // for any windows owned by the window being destroyed and update them.
 4497     }
 4498 
 4499     return OK;
 4500 }
 4501 
 4502 
 4503 
 4504 void GuiType::GetNonClientArea(LONG &aWidth, LONG &aHeight)
 4505 // Added for v1.0.44.13.
 4506 // Yields only the *extra* width/height added by the windows non-client area.
 4507 // If the window hasn't been shown for the first time, the caller wants zeros.
 4508 // The reason for making the script specify size of client area rather than entire window is that it
 4509 // seems far more useful.  For example, a script might know exactly how much minimum height its
 4510 // controls require in the client area, but would find it inconvenient to have to take into account
 4511 // the height of the title bar and menu bar (which vary depending on theme and other settings).
 4512 {
 4513     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.
 4514     {
 4515         aWidth = 0;
 4516         aHeight = 0;
 4517         return;
 4518     }
 4519     // Otherwise, mGuiShowHasNeverBeenDone==false, which should mean that mHwnd!=NULL.
 4520     RECT rect, client_rect;
 4521     GetWindowRect(mHwnd, &rect);
 4522     GetClientRect(mHwnd, &client_rect); // Client rect's left & top are always zero.
 4523     aWidth = (rect.right - rect.left) - client_rect.right;
 4524     aHeight = (rect.bottom - rect.top) - client_rect.bottom;
 4525 }
 4526 
 4527 
 4528 
 4529 void GuiType::GetTotalWidthAndHeight(LONG &aWidth, LONG &aHeight)
 4530 // Added for v1.0.44.13.
 4531 // Yields total width and height of entire window.
 4532 // If the window hasn't been shown for the first time, the caller wants COORD_CENTERED.
 4533 {
 4534     if (mGuiShowHasNeverBeenDone)
 4535     {
 4536         aWidth = COORD_CENTERED;
 4537         aHeight = COORD_CENTERED;
 4538         return;
 4539     }
 4540     // Otherwise, mGuiShowHasNeverBeenDone==false, which should mean that mHwnd!=NULL.
 4541     RECT rect;
 4542     GetWindowRect(mHwnd, &rect);
 4543     aWidth = rect.right - rect.left;
 4544     aHeight = rect.bottom - rect.top;
 4545 }
 4546 
 4547 
 4548 
 4549 ResultType GuiType::ControlParseOptions(LPTSTR aOptions, GuiControlOptionsType &aOpt, GuiControlType &aControl
 4550     , GuiIndexType aControlIndex, Var *aParam3Var)
 4551 // Caller must have already initialized aOpt with zeroes or any other desired starting values.
 4552 // Caller must ensure that aOptions is a modifiable string, since this method temporarily alters it.
 4553 {
 4554     // If control type uses aControl's union for something other than color, communicate the chosen color
 4555     // back through a means that doesn't corrupt the union:
 4556     COLORREF &color_main = (aControl.type == GUI_CONTROL_LISTVIEW || aControl.type == GUI_CONTROL_PIC)
 4557         ? aOpt.color_listview : aControl.union_color;
 4558     LPTSTR next_option, option_end;
 4559     LPTSTR error_message; // Used by "return_error:" when aControl.hwnd == NULL.
 4560     TCHAR orig_char;
 4561     bool adding; // Whether this option is being added (+) or removed (-).
 4562     GuiControlType *tab_control;
 4563     RECT rect;
 4564     POINT pt;
 4565     bool do_invalidate_rect = false; // Set default.
 4566 
 4567     for (next_option = aOptions; *next_option; next_option = omit_leading_whitespace(option_end))
 4568     {
 4569         if (*next_option == '-')
 4570         {
 4571             adding = false;
 4572             // omit_leading_whitespace() is not called, which enforces the fact that the option word must
 4573             // immediately follow the +/- sign.  This is done to allow the flexibility to have options
 4574             // omit the plus/minus sign, and also to reserve more flexibility for future option formats.
 4575             ++next_option;  // Point it to the option word itself.
 4576         }
 4577         else
 4578         {
 4579             // Assume option is being added in the absence of either sign.  However, when we were
 4580             // called by GuiControl(), the first option in the list must begin with +/- otherwise the cmd
 4581             // would never have been properly detected as GUICONTROL_CMD_OPTIONS in the first place.
 4582             adding = true;
 4583             if (*next_option == '+')
 4584                 ++next_option;  // Point it to the option word itself.
 4585             //else do not increment, under the assumption that the plus has been omitted from a valid
 4586             // option word and is thus an implicit plus.
 4587         }
 4588 
 4589         if (!*next_option) // In case the entire option string ends in a naked + or -.
 4590             break;
 4591         // Find the end of this option item:
 4592         if (   !(option_end = StrChrAny(next_option, _T(" \t")))   )  // Space or tab.
 4593             option_end = next_option + _tcslen(next_option); // Set to position of zero terminator instead.
 4594         if (option_end == next_option)
 4595             continue; // i.e. the string contains a + or - with a space or tab after it, which is intentionally ignored.
 4596 
 4597         // Temporarily terminate to help eliminate ambiguity for words contained inside other words,
 4598         // such as "Checked" inside of "CheckedGray":
 4599         orig_char = *option_end;
 4600         *option_end = '\0';
 4601 
 4602         // Attributes:
 4603         if (!_tcsicmp(next_option, _T("Section"))) // Adding and removing are treated the same in this case.
 4604             aOpt.start_new_section = true;    // Ignored by caller when control already exists.
 4605         else if (!_tcsicmp(next_option, _T("AltSubmit")) && aControl.type != GUI_CONTROL_EDIT)
 4606         {
 4607             // v1.0.44: Don't allow control's AltSubmit bit to be set unless it's valid option for
 4608             // that type.  This protects the GUI_CONTROL_ATTRIB_ALTSUBMIT bit from being corrupted
 4609             // in control types that use it for other/internal purposes.  Update: For code size reduction
 4610             // and performance, only exclude control types that use the ALTSUBMIT bit for an internal
 4611             // purpose vs. allowing the script to set it via "AltSubmit".
 4612             if (adding) aControl.attrib |= GUI_CONTROL_ATTRIB_ALTSUBMIT; else aControl.attrib &= ~GUI_CONTROL_ATTRIB_ALTSUBMIT;
 4613             //switch(aControl.type)
 4614             //{
 4615             //case GUI_CONTROL_TAB:
 4616             //case GUI_CONTROL_PIC:
 4617             //case GUI_CONTROL_DROPDOWNLIST:
 4618             //case GUI_CONTROL_COMBOBOX:
 4619             //case GUI_CONTROL_LISTBOX:
 4620             //case GUI_CONTROL_LISTVIEW:
 4621             //case GUI_CONTROL_TREEVIEW:
 4622             //case GUI_CONTROL_MONTHCAL:
 4623             //case GUI_CONTROL_SLIDER:
 4624             //  if (adding) aControl.attrib |= GUI_CONTROL_ATTRIB_ALTSUBMIT; else aControl.attrib &= ~GUI_CONTROL_ATTRIB_ALTSUBMIT;
 4625             //  break;
 4626             //// All other types either use the bit for some internal purpose or want it reserved for possible
 4627             //// future use.  So don't allow the presence of "AltSubmit" to change the bit.
 4628             //}
 4629         }
 4630 
 4631         // Content of control (these are currently only effective if the control is being newly created):
 4632         else if (!_tcsnicmp(next_option, _T("Checked"), 7)) // Caller knows to ignore if inapplicable. Applicable for ListView too.
 4633         {
 4634             next_option += 7;
 4635             if (!_tcsicmp(next_option, _T("Gray"))) // Radios can't have the 3rd/gray state, but for simplicity it's permitted.
 4636                 if (adding) aOpt.checked = BST_INDETERMINATE; else aOpt.checked = BST_UNCHECKED;
 4637             else
 4638             {
 4639                 if (aControl.type == GUI_CONTROL_LISTVIEW)
 4640                     if (adding) aOpt.listview_style |= LVS_EX_CHECKBOXES; else aOpt.listview_style &= ~LVS_EX_CHECKBOXES;
 4641                 else
 4642                 {
 4643                     // As of v1.0.26, Checked/Hidden/Disabled can be followed by an optional 1/0/-1 so that
 4644                     // there is a way for a script to set the starting state by reading from an INI or registry
 4645                     // entry that contains 1 or 0 instead of needing the literal word "checked" stored in there.
 4646                     // Otherwise, a script would have to do something like the following before every "Gui Add":
 4647                     // if Box1Enabled
 4648                     //    Enable = Enabled
 4649                     // else
 4650                     //    Enable =
 4651                     // Gui Add, checkbox, %Enable%, My checkbox.
 4652                     if (*next_option) // There's more after the word, namely a 1, 0, or -1.
 4653                     {
 4654                         aOpt.checked = ATOI(next_option);
 4655                         if (aOpt.checked == -1)
 4656                             aOpt.checked = BST_INDETERMINATE;
 4657                     }
 4658                     else // Below is also used for GUI_CONTROL_TREEVIEW creation because its checkboxes must be added AFTER the control is created.
 4659                         aOpt.checked = adding; // BST_CHECKED == 1, BST_UNCHECKED == 0
 4660                 }
 4661             } // Non-checkedGRAY
 4662         } // Checked.
 4663         else if (!_tcsnicmp(next_option, _T("Choose"), 6))
 4664         {
 4665             // "CHOOSE" provides an easier way to conditionally select a different item at the time
 4666             // the control is added.  Example: gui, add, ListBox, vMyList Choose%choice%, %MyItemList%
 4667             // Caller should ignore aOpt.choice if it isn't applicable for this control type.
 4668             if (adding)
 4669             {
 4670                 next_option += 6;
 4671                 switch (aControl.type)
 4672                 {
 4673                 case GUI_CONTROL_DATETIME:
 4674                     if (!_tcsicmp(next_option, _T("None")))
 4675                         aOpt.choice = 2; // Special flag value to indicate "none".
 4676                     else // See if it's a valid date-time.
 4677                         if (YYYYMMDDToSystemTime(next_option, aOpt.sys_time[0], true)) // Date string is valid.
 4678                             aOpt.choice = 1; // Overwrite 0 to flag sys_time as both present and valid.
 4679                         //else leave choice at its 0 default to indicate no valid Choose option was present.
 4680                     break;
 4681                 case GUI_CONTROL_MONTHCAL:
 4682                     aOpt.gdtr = YYYYMMDDToSystemTime2(next_option, aOpt.sys_time);
 4683                     // For code simplicity, both min and max must be present to enable a selected-range.
 4684                     if (aOpt.gdtr == (GDTR_MIN | GDTR_MAX))
 4685                         aOpt.style_add |= MCS_MULTISELECT;
 4686                     //else never remove the style since it's valid to create a range-capable control via
 4687                     // "Multi" that has only a single date selected (or none).  Also, if the control already
 4688                     // exists, MSDN says that MCS_MULTISELECT cannot be added or removed.
 4689                     break;
 4690                 default:
 4691                     aOpt.choice = ATOI(next_option);
 4692                     if (aOpt.choice < 1) // Invalid: number should be 1 or greater.
 4693                         aOpt.choice = 0; // Flag it as invalid.
 4694                 }
 4695             }
 4696             //else do nothing (not currently implemented)
 4697         }
 4698 
 4699         // Styles (general):
 4700         else if (!_tcsicmp(next_option, _T("Border")))
 4701             if (adding) aOpt.style_add |= WS_BORDER; else aOpt.style_remove |= WS_BORDER;
 4702         else if (!_tcsicmp(next_option, _T("VScroll"))) // Seems harmless in this case not to check aControl.type to ensure it's an input-capable control.
 4703             if (adding) aOpt.style_add |= WS_VSCROLL; else aOpt.style_remove |= WS_VSCROLL;
 4704         else if (!_tcsnicmp(next_option, _T("HScroll"), 7)) // Seems harmless in this case not to check aControl.type to ensure it's an input-capable control.
 4705         {
 4706             if (aControl.type == GUI_CONTROL_TREEVIEW)
 4707                 // Testing shows that Tree doesn't seem to fully support removal of hscroll bar after creation.
 4708                 if (adding) aOpt.style_remove |= TVS_NOHSCROLL; else aOpt.style_add |= TVS_NOHSCROLL;
 4709             else
 4710                 if (adding)
 4711                 {
 4712                     // MSDN: "To respond to the LB_SETHORIZONTALEXTENT message, the list box must have
 4713                     // been defined with the WS_HSCROLL style."
 4714                     aOpt.style_add |= WS_HSCROLL;
 4715                     next_option += 7;
 4716                     aOpt.hscroll_pixels = *next_option ? ATOI(next_option) : -1;  // -1 signals it to use a default based on control's width.
 4717                 }
 4718                 else
 4719                     aOpt.style_remove |= WS_HSCROLL;
 4720         }
 4721         else if (!_tcsicmp(next_option, _T("Tabstop"))) // Seems harmless in this case not to check aControl.type to ensure it's an input-capable control.
 4722             if (adding) aOpt.style_add |= WS_TABSTOP; else aOpt.style_remove |= WS_TABSTOP;
 4723         else if (!_tcsicmp(next_option, _T("NoTab"))) // Supported for backward compatibility and it might be more ergonomic for "Gui Add".
 4724             if (adding) aOpt.style_remove |= WS_TABSTOP; else aOpt.style_add |= WS_TABSTOP;
 4725         else if (!_tcsicmp(next_option, _T("Group"))) // Because it starts with 'G', this overlaps with g-label, but seems well worth it in this case.
 4726             if (adding) aOpt.style_add |= WS_GROUP; else aOpt.style_remove |= WS_GROUP;
 4727         else if (!_tcsicmp(next_option, _T("Redraw")))  // Seems a little more intuitive/memorable than "Draw".
 4728             aOpt.redraw = adding ? CONDITION_TRUE : CONDITION_FALSE; // Otherwise leave it at its default of 0.
 4729         else if (!_tcsnicmp(next_option, _T("Disabled"), 8))
 4730         {
 4731             // As of v1.0.26, Checked/Hidden/Disabled can be followed by an optional 1/0/-1 so that
 4732             // there is a way for a script to set the starting state by reading from an INI or registry
 4733             // entry that contains 1 or 0 instead of needing the literal word "checked" stored in there.
 4734             // Otherwise, a script would have to do something like the following before every "Gui Add":
 4735             // if Box1Enabled
 4736             //    Enable = Enabled
 4737             // else
 4738             //    Enable =
 4739             // Gui Add, checkbox, %Enable%, My checkbox.
 4740             if (next_option[8] && !ATOI(next_option + 8)) // If it's Disabled0, invert the mode to become "enabled".
 4741                 adding = !adding;
 4742             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.
 4743                 EnableWindow(aControl.hwnd, adding ? FALSE : TRUE);
 4744             else
 4745                 if (adding) aOpt.style_add |= WS_DISABLED; else aOpt.style_remove |= WS_DISABLED;
 4746             // Update the "explicitly" flags so that switching tabs won't reset the control's state.
 4747             if (adding)
 4748                 aControl.attrib |= GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED;
 4749             else
 4750                 aControl.attrib &= ~GUI_CONTROL_ATTRIB_EXPLICITLY_DISABLED;
 4751         }
 4752         else if (!_tcsnicmp(next_option, _T("Hidden"), 6))
 4753         {
 4754             // As of v1.0.26, Checked/Hidden/Disabled can be followed by an optional 1/0/-1 so that
 4755             // there is a way for a script to set the starting state by reading from an INI or registry
 4756             // entry that contains 1 or 0 instead of needing the literal word "checked" stored in there.
 4757             // Otherwise, a script would have to do something like the following before every "Gui Add":
 4758             // if Box1Enabled
 4759             //    Enable = Enabled
 4760             // else
 4761             //    Enable =
 4762             // Gui Add, checkbox, %Enable%, My checkbox.
 4763             if (next_option[6] && !ATOI(next_option + 6)) // If it's Hidden0, invert the mode to become "show".
 4764                 adding = !adding;
 4765             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.
 4766                 ShowWindow(aControl.hwnd, adding ? SW_HIDE : SW_SHOWNOACTIVATE);
 4767             else
 4768                 if (adding) aOpt.style_remove |= WS_VISIBLE; else aOpt.style_add |= WS_VISIBLE;
 4769             // Update the "explicitly" flags so that switching tabs won't reset the control's state.
 4770             if (adding)
 4771                 aControl.attrib |= GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN;
 4772             else
 4773                 aControl.attrib &= ~GUI_CONTROL_ATTRIB_EXPLICITLY_HIDDEN;
 4774         }
 4775         else if (!_tcsicmp(next_option, _T("Wrap")))
 4776         {
 4777             switch(aControl.type)
 4778             {
 4779             case GUI_CONTROL_TEXT: // This one is a little tricky but the below should be appropriate in most cases:
 4780                 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.
 4781                 break;
 4782             case GUI_CONTROL_GROUPBOX:
 4783             case GUI_CONTROL_BUTTON:
 4784             case GUI_CONTROL_CHECKBOX:
 4785             case GUI_CONTROL_RADIO:
 4786                 if (adding) aOpt.style_add |= BS_MULTILINE; else aOpt.style_remove |= BS_MULTILINE;
 4787                 break;
 4788             case GUI_CONTROL_UPDOWN:
 4789                 if (adding) aOpt.style_add |= UDS_WRAP; else aOpt.style_remove |= UDS_WRAP;
 4790                 break;
 4791             case GUI_CONTROL_EDIT: // Must be a multi-line now or shortly in the future or these will have no effect.
 4792                 if (adding) aOpt.style_remove |= WS_HSCROLL|ES_AUTOHSCROLL; else aOpt.style_add |= ES_AUTOHSCROLL;
 4793                 // WS_HSCROLL is removed because with it, wrapping is automatically off.
 4794                 break;
 4795             case GUI_CONTROL_TAB:
 4796                 if (adding) aOpt.style_add |= TCS_MULTILINE; else aOpt.style_remove |= TCS_MULTILINE;
 4797                 // WS_HSCROLL is removed because with it, wrapping is automatically off.
 4798                 break;
 4799             // N/A for these:
 4800             //case GUI_CONTROL_PIC:
 4801             //case GUI_CONTROL_DROPDOWNLIST:
 4802             //case GUI_CONTROL_COMBOBOX:
 4803             //case GUI_CONTROL_LISTBOX:
 4804             //case GUI_CONTROL_LISTVIEW:
 4805             //case GUI_CONTROL_TREEVIEW:
 4806             //case GUI_CONTROL_DATETIME:
 4807             //case GUI_CONTROL_MONTHCAL:
 4808             //case GUI_CONTROL_HOTKEY:
 4809             //case GUI_CONTROL_SLIDER:
 4810             //case GUI_CONTROL_PROGRESS:
 4811             //case GUI_CONTROL_LINK:
 4812             }
 4813         }
 4814         else if (!_tcsnicmp(next_option, _T("Background"), 10))
 4815         {
 4816             next_option += 10;  // To help maintainability, point it to the optional suffix here.
 4817             switch(aControl.type)
 4818             {
 4819             case GUI_CONTROL_PROGRESS:
 4820             case GUI_CONTROL_LISTVIEW:
 4821             case GUI_CONTROL_TREEVIEW:
 4822             case GUI_CONTROL_STATUSBAR:
 4823                 // Note that GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT and GUI_CONTROL_ATTRIB_BACKGROUND_TRANS
 4824                 // don't apply to Progress or ListView controls because the window proc never receives
 4825                 // CTLCOLOR messages for them.
 4826                 if (adding)
 4827                 {
 4828                     aOpt.color_bk = ColorNameToBGR(next_option);
 4829                     if (aOpt.color_bk == CLR_NONE) // A matching color name was not found, so assume it's in hex format.
 4830                         // It seems _tcstol() automatically handles the optional leading "0x" if present:
 4831                         aOpt.color_bk = rgb_to_bgr(_tcstol(next_option, NULL, 16));
 4832                         // if next_option did not contain something hex-numeric, black (0x00) will be assumed,
 4833                         // which seems okay given how rare such a problem would be.
 4834                 }
 4835                 else // Removing
 4836                     aOpt.color_bk = CLR_DEFAULT;
 4837                 break;
 4838             default: // Other control types don't yet support custom colors other than TRANS.
 4839                 if (adding)
 4840                 {
 4841                     aControl.attrib &= ~GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT;
 4842                     if (!_tcsicmp(next_option, _T("Trans")))
 4843                         aControl.attrib |= GUI_CONTROL_ATTRIB_BACKGROUND_TRANS; // This is mutually exclusive of the above anyway.
 4844                     else
 4845                         aControl.attrib &= ~GUI_CONTROL_ATTRIB_BACKGROUND_TRANS;
 4846                     // In the future, something like the below can help support background colors for individual controls.
 4847                     //COLORREF background_color = ColorNameToBGR(next_option + 10);
 4848                     //if (background_color == CLR_NONE) // A matching color name was not found, so assume it's in hex format.
 4849                     //  // It seems _tcstol() automatically handles the optional leading "0x" if present:
 4850                     //  background_color = rgb_to_bgr(_tcstol(next_option, NULL, 16));
 4851                     //  // if next_option did not contain something hex-numeric, black (0x00) will be assumed,
 4852                     //  // which seems okay given how rare such a problem would be.
 4853                 }
 4854                 else
 4855                 {
 4856                     // Note that "-BackgroundTrans" is not supported, since Trans is considered to be
 4857                     // a color value for the purpose of expanding this feature in the future to support
 4858                     // custom background colors on a per-control basis.  In other words, the trans factor
 4859                     // can be turned off by using "-Background" or "+BackgroundBlue", etc.
 4860                     aControl.attrib &= ~GUI_CONTROL_ATTRIB_BACKGROUND_TRANS;
 4861                     aControl.attrib |= GUI_CONTROL_ATTRIB_BACKGROUND_DEFAULT;
 4862                 }
 4863             } // switch(aControl.type)
 4864         } // Option "Background".
 4865         else if (!_tcsicmp(next_option, _T("Group"))) // This overlaps with g-label, but seems well worth it in this case.
 4866             if (adding) aOpt.style_add |= WS_GROUP; else aOpt