"Fossies" - the Fresh Open Source Software Archive

Member "AutoHotkey_L-1.1.33.09/source/script_menu.cpp" (8 May 2021, 74854 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_menu.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 
   23 
   24 ResultType Script::MenuError(LPTSTR aMessage, LPTSTR aInfo)
   25 // Displays an error or sets ErrorLevel as appropriate.  Defining this
   26 // as a function vs. a straight macro reduces code size considerably.
   27 {
   28     return mMenuUseErrorLevel ? g_ErrorLevel->Assign(ERRORLEVEL_ERROR)
   29         : ScriptError(aMessage, aInfo);
   30 }
   31 
   32 
   33 ResultType Script::PerformMenu(LPTSTR aMenu, LPTSTR aCommand, LPTSTR aParam3, LPTSTR aParam4, LPTSTR aOptions, LPTSTR aOptions2, Var *aParam4Var, Var *aParam5Var)
   34 {
   35     if (mMenuUseErrorLevel)
   36         g_ErrorLevel->Assign(ERRORLEVEL_NONE);  // Set default, which is "none" for the Menu command.
   37 
   38     #define RETURN_MENU_ERROR(msg, info) return MenuError(msg, info)
   39     #define RETURN_IF_NOT_TRAY if (!is_tray) RETURN_MENU_ERROR(ERR_MENUTRAY, aMenu)
   40 
   41     MenuCommands menu_command = Line::ConvertMenuCommand(aCommand);
   42     if (menu_command == MENU_CMD_INVALID)
   43         RETURN_MENU_ERROR(ERR_PARAM2_INVALID, aCommand);
   44 
   45     bool is_tray = !_tcsicmp(aMenu, _T("tray"));
   46 
   47     // Handle early on anything that doesn't require the menu to be found or created:
   48     switch(menu_command)
   49     {
   50     case MENU_CMD_USEERRORLEVEL:
   51         mMenuUseErrorLevel = (Line::ConvertOnOff(aParam3) != TOGGLED_OFF);
   52         // Even though the state may have changed by the above, it doesn't seem necessary
   53         // to adjust on the fly for the purpose of this particular return.  In other words,
   54         // the old mode will be in effect for this one return:
   55         return OK;
   56 
   57     case MENU_CMD_TIP:
   58         RETURN_IF_NOT_TRAY;
   59         if (*aParam3)
   60         {
   61             if (!mTrayIconTip)
   62                 mTrayIconTip = (LPTSTR) SimpleHeap::Malloc(sizeof(mNIC.szTip)); // SimpleHeap improves avg. case mem load.
   63             if (mTrayIconTip)
   64                 tcslcpy(mTrayIconTip, aParam3, _countof(mNIC.szTip));
   65         }
   66         else // Restore tip to default.
   67             if (mTrayIconTip)
   68                 *mTrayIconTip = '\0';
   69         if (mNIC.hWnd) // i.e. only update the tip if the tray icon exists (can't work otherwise).
   70         {
   71             UPDATE_TIP_FIELD
   72             Shell_NotifyIcon(NIM_MODIFY, &mNIC);  // Currently not checking its result (e.g. in case a shell other than Explorer is running).
   73         }
   74         return OK;
   75 
   76     case MENU_CMD_ICON:
   77         // L17: If is_tray and aParam3 is omitted or aParam4 is an integer, set the tray icon. Otherwise set a menu item icon.
   78         if (is_tray)
   79         {
   80             bool mIconFrozen_prev = mIconFrozen;
   81             // Lexikos: aOptions still ambiguous with menu item icon number at this point.
   82             //if (*aOptions) // i.e. if it's blank, don't change the current setting of mIconFrozen.
   83             //  mIconFrozen = (ATOI(aOptions) == 1);
   84 
   85             if (!*aParam3)
   86             {
   87                 // Lexikos: MenuItemName omitted, therefore no conflict. mIconFrozen may now be set.
   88                 if (*aOptions) // i.e. if it's blank, don't change the current setting of mIconFrozen.
   89                     mIconFrozen = (ATOI(aOptions) == 1);
   90 
   91                 g_NoTrayIcon = false;
   92                 if (!mNIC.hWnd) // The icon doesn't exist, so create it.
   93                 {
   94                     CreateTrayIcon();
   95                     UpdateTrayIcon(true);  // Force the icon into the correct pause/suspend state.
   96                 }
   97                 else if (!mIconFrozen && mIconFrozen_prev) // To cause "Menu Tray, Icon,,, 0" to update the icon while the script is suspended.
   98                     UpdateTrayIcon(true);
   99                 return OK;
  100             }
  101 
  102             // Otherwise, user has specified a custom icon:
  103             if (*aParam3 == '*' && !*(aParam3 + 1)) // Restore the standard icon.
  104             {
  105                 // Lexikos: For compatibility with older scripts, "Menu, Tray, Icon, *" must reset tray to default icon, even if an item "*" exists. mIconFrozen may now be set.
  106                 if (*aOptions) // i.e. if it's blank, don't change the current setting of mIconFrozen.
  107                     mIconFrozen = (ATOI(aOptions) == 1);
  108 
  109                 if (mCustomIcon)
  110                 {
  111                     GuiType::DestroyIconsIfUnused(mCustomIcon, mCustomIconSmall); // v1.0.37.07: Solves reports of Gui windows losing their icons.
  112                     // If the above doesn't destroy the icon, the GUI window(s) still using it are responsible for
  113                     // destroying it later.
  114                     mCustomIcon = NULL;  // To indicate that there is no custom icon.
  115                     mCustomIconSmall = NULL;
  116                     free(mCustomIconFile);
  117                     mCustomIconFile = NULL;
  118                     mCustomIconNumber = 0;
  119                     UpdateTrayIcon(true);  // Need to use true in this case too.
  120                 }
  121                 return OK;
  122             }
  123 
  124             if (IsPureNumeric(aParam4, true)) // pure integer or empty/whitespace
  125             {
  126                 // Lexikos: We are unconditionally treating this as a request to set the tray icon, so mIconFrozen may now be set.
  127                 if (*aOptions) // i.e. if it's blank, don't change the current setting of mIconFrozen.
  128                     mIconFrozen = (ATOI(aOptions) == 1);
  129 
  130                 // v1.0.43.03: Load via LoadPicture() vs. ExtractIcon() because ExtractIcon harms the quality
  131                 // of 16x16 icons inside .ico files by first scaling them to 32x32 (which then has to be scaled
  132                 // back to 16x16 for the tray and for the SysMenu icon). I've visually confirmed that the
  133                 // distortion occurs at least when a 16x16 icon is loaded by ExtractIcon() then put into the
  134                 // tray.  It might not be the scaling itself that distorts the icon: the pixels are all in the
  135                 // right places, it's just that some are the wrong color/shade. This implies that some kind of
  136                 // unwanted interpolation or color tweaking is being done by ExtractIcon (and probably LoadIcon),
  137                 // but not by LoadImage.
  138                 // Also, load the icon at actual size so that when/if this icon is used for a GUI window, its
  139                 // appearance in the alt-tab menu won't be unexpectedly poor due to having been scaled from its
  140                 // native size down to 16x16.
  141                 int icon_number;
  142                 if (*aParam4)
  143                 {
  144                     icon_number = ATOI(aParam4);
  145                     if (icon_number == 0) // Must validate for use in two places below.
  146                         icon_number = 1; // Must be != 0 to tell LoadPicture that "icon must be loaded, never a bitmap".
  147                 }
  148                 else
  149                     icon_number = 1; // One vs. Zero tells LoadIcon: "must load icon, never a bitmap (e.g. no gif/jpg/png)".
  150 
  151                 int image_type;
  152                 // L17: For best results, load separate small and large icons.
  153                 HICON new_icon_small;
  154                 HICON new_icon = NULL; // Initialize to detect failure to load either icon.
  155                 HMODULE icon_module = NULL; // Must initialize because it's not always set by LoadPicture().
  156                 if (!_tcsnicmp(aParam3, _T("HICON:"), 6) && aParam3[6] != '*')
  157                 {
  158                     // Handle this here rather than in LoadPicture() because the first call would destroy the
  159                     // original icon (due to specifying the width and height), causing the second call to fail.
  160                     // Keep the original size for both icons since that sometimes produces better results than
  161                     // CopyImage(), and it keeps the code smaller.
  162                     new_icon_small = (HICON)(UINT_PTR)ATOI64(aParam3 + 6);
  163                     new_icon = new_icon_small; // DestroyIconsIfUnused() handles this case by calling DestroyIcon() only once.
  164                 }
  165                 else if ( new_icon_small = (HICON)LoadPicture(aParam3, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), image_type, icon_number, false) ) // Called with icon_number > 0, it guarantees return of an HICON/HCURSOR, never an HBITMAP.
  166                     if ( !(new_icon = (HICON)LoadPicture(aParam3, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), image_type, icon_number, false, NULL, &icon_module)) )
  167                         DestroyIcon(new_icon_small);
  168                 if ( !new_icon )
  169                     RETURN_MENU_ERROR(_T("Can't load icon."), aParam3);
  170 
  171                 GuiType::DestroyIconsIfUnused(mCustomIcon, mCustomIconSmall); // This destroys it if non-NULL and it's not used by an GUI windows.
  172 
  173                 mCustomIcon = new_icon;
  174                 mCustomIconSmall = new_icon_small;
  175                 mCustomIconNumber = icon_number;
  176 
  177                 TCHAR full_path[T_MAX_PATH], *filename_marker;
  178                 // If the icon was loaded from a DLL, relative->absolute conversion below may produce the
  179                 // wrong result (i.e. in the typical case where the DLL is not in the working directory).
  180                 // So in that case, get the path of the module which contained the icon (if available).
  181                 // Get the full path in case it's a relative path.  This is documented and it's done in case
  182                 // the script ever changes its working directory:
  183                 if (   icon_module && GetModuleFileName(icon_module, full_path, _countof(full_path))
  184                     || GetFullPathName(aParam3, _countof(full_path) - 1, full_path, &filename_marker)   )
  185                     aParam3 = full_path;
  186                 free(mCustomIconFile);
  187                 mCustomIconFile = _tcsdup(aParam3); // Failure isn't checked due to rarity and for simplicity; it'll be reported as empty in that case.
  188 
  189                 if (icon_module)
  190                     FreeLibrary(icon_module);
  191 
  192                 if (!g_NoTrayIcon)
  193                     UpdateTrayIcon(true);  // Need to use true in this case too.
  194                 return OK;
  195             }
  196         }
  197         break;
  198 
  199     case MENU_CMD_NOICON:
  200         if (is_tray && !*aParam3) // L17: "Menu, Tray, NoIcon, xxx" removes icon from tray menu item xxx.
  201         {
  202             g_NoTrayIcon = true;
  203             if (mNIC.hWnd) // Since it exists, destroy it.
  204             {
  205                 Shell_NotifyIcon(NIM_DELETE, &mNIC); // Remove it.
  206                 mNIC.hWnd = NULL;  // Set this as an indicator that tray icon is not installed.
  207                 // but don't do DestroyMenu() on mTrayMenu->mMenu (if non-NULL) since it may have been
  208                 // changed by the user to have the custom items on top of the standard items,
  209                 // for example, and we don't want to lose that ordering in case the script turns
  210                 // the icon back on at some future time during this session.
  211             }
  212             return OK;
  213         }
  214         // else: this request to remove a menu item's icon will be processed below.
  215         break;
  216 
  217     case MENU_CMD_CLICK:
  218         RETURN_IF_NOT_TRAY;
  219         mTrayMenu->mClickCount = ATOI(aParam3);
  220         if (mTrayMenu->mClickCount < 1)
  221             mTrayMenu->mClickCount = 1;  // Single-click to activate menu's default item.
  222         else if (mTrayMenu->mClickCount > 2)
  223             mTrayMenu->mClickCount = 2;  // Double-click.
  224         return OK;
  225 
  226     case MENU_CMD_MAINWINDOW:
  227         RETURN_IF_NOT_TRAY;
  228 #ifdef AUTOHOTKEYSC
  229         if (!g_AllowMainWindow)
  230         {
  231             g_AllowMainWindow = true;
  232             EnableOrDisableViewMenuItems(GetMenu(g_hWnd), MF_ENABLED); // Added as a fix in v1.0.47.06.
  233             // Rather than using InsertMenu() to insert the item in the right position,
  234             // which makes the code rather unmaintainable, it seems best just to recreate
  235             // the entire menu.  This will result in the standard menu items going back
  236             // up to the top of the menu if the user previously had them at the bottom,
  237             // but it seems too rare to worry about, especially since it's easy to
  238             // work around that:
  239             if (mTrayMenu->mIncludeStandardItems)
  240                 mTrayMenu->Destroy(); // It will be recreated automatically the next time the user displays it.
  241             // else there's no need.
  242         }
  243 #endif
  244         return OK;
  245 
  246     case MENU_CMD_NOMAINWINDOW:
  247         RETURN_IF_NOT_TRAY;
  248 #ifdef AUTOHOTKEYSC
  249         if (g_AllowMainWindow)
  250         {
  251             g_AllowMainWindow = false;
  252             EnableOrDisableViewMenuItems(GetMenu(g_hWnd), MF_DISABLED | MF_GRAYED); // Added as a fix in v1.0.47.06.
  253             // See comments in the prior case above for why it's done this way vs. using DeleteMenu():
  254             if (mTrayMenu->mIncludeStandardItems)
  255                 mTrayMenu->Destroy(); // It will be recreated automatically the next time the user displays it.
  256             // else there's no need.
  257         }
  258 #endif
  259         return OK;
  260     } // switch()
  261 
  262 
  263     // Now that most opportunities to return an error have passed, find or create the menu, since
  264     // all the commands that haven't already been fully handled above will need it:
  265     UserMenu *menu = FindMenu(aMenu);
  266 
  267     // Handle the Insert command like a sub-mode of the Add command, for simplicity:
  268     bool ignore_existing_items = false;
  269     UserMenuItem **insert_at = NULL;
  270     if (menu_command == MENU_CMD_INSERT && (menu || !*aParam3 || !_tcscmp(aParam3, _T("1&"))))
  271     {
  272         // If the menu doesn't exist yet, allow it to be created only if the item is being
  273         // appended; i.e. aParam3 is either "" or "1&".
  274         if (menu && *aParam3)
  275         {
  276             bool search_by_pos;
  277             UserMenuItem *insert_before, *prev_item;
  278             if (  !(insert_before = menu->FindItem(aParam3, prev_item, search_by_pos))  )
  279             {
  280                 // The item wasn't found.  Treat it as an error unless it is the position
  281                 // immediately after the last item.
  282                 if (  !(search_by_pos && ATOI(aParam3) == (int)menu->mMenuItemCount + 1)  )
  283                     RETURN_MENU_ERROR(_T("Nonexistent menu item."), aParam3);
  284             }
  285             // To simplify insertion, give AddItem() a pointer to the variable within the
  286             // linked-list which points to the item, rather than a pointer to the item itself:
  287             insert_at = prev_item ? &prev_item->mNextMenuItem : &menu->mFirstMenuItem;
  288         }
  289         menu_command = MENU_CMD_ADD;
  290         ignore_existing_items = true;
  291         aParam3 = aParam4;
  292         aParam4 = aOptions;
  293         aParam4Var = aParam5Var;
  294         aOptions = aOptions2;
  295     }
  296     
  297     if (!menu)
  298     {
  299         // Menus can be created only in conjunction with the ADD command. Update: As of v1.0.25.12, they can
  300         // also be created with the "Menu, MyMenu, Standard" command.
  301         if (menu_command != MENU_CMD_ADD && menu_command != MENU_CMD_STANDARD)
  302             RETURN_MENU_ERROR(ERR_MENU, aMenu);
  303         if (   !(menu = AddMenu(aMenu))   )
  304             RETURN_MENU_ERROR(_T("Menu name too long."), aMenu); // Could also be "out of mem" but that's too rare to display.
  305     }
  306 
  307     // The above has found or added the menu for use below.
  308 
  309     switch(menu_command)
  310     {
  311     case MENU_CMD_SHOW:
  312         return menu->Display(true, *aParam3 ? ATOI(aParam3) : COORD_UNSPECIFIED, *aParam4 ? ATOI(aParam4) : COORD_UNSPECIFIED);
  313     case MENU_CMD_ADD:
  314         if (*aParam3) // Since a menu item name was given, it's not a separator line.
  315             break;    // Let a later switch() handle it.
  316         if (!menu->AddItem(_T(""), GetFreeMenuItemID(), NULL, NULL, _T(""), insert_at)) // Even separators get an ID, so that they can be modified later using the position& notation.
  317             RETURN_MENU_ERROR(ERR_OUTOFMEM, _T(""));  // Out of mem should be the only possibility in this case.
  318         return OK;
  319     case MENU_CMD_DELETE:
  320         if (*aParam3) // Since a menu item name was given, an item is being deleted, not the whole menu.
  321             break;    // Let a later switch() handle it.
  322         if (menu == mTrayMenu)
  323             RETURN_MENU_ERROR(_T("Tray menu must not be deleted."), _T(""));
  324         if (!ScriptDeleteMenu(menu))
  325             RETURN_MENU_ERROR(_T("Can't delete menu (in use?)."), menu->mName); // Possibly in use as a menu bar.
  326         return OK;
  327     case MENU_CMD_DELETEALL:
  328         if (!menu->DeleteAllItems())
  329             RETURN_MENU_ERROR(_T("Can't delete items (in use?)."), menu->mName); // Possibly in use as a menu bar.
  330         return OK;
  331     case MENU_CMD_DEFAULT:
  332         if (*aParam3) // Since a menu item has been specified, let a later switch() handle it.
  333             break;
  334         //else no menu item, so it's the same as NoDefault: fall through to the next case.
  335     case MENU_CMD_NODEFAULT:
  336         return menu->SetDefault();
  337     case MENU_CMD_STANDARD:
  338         menu->IncludeStandardItems(); // Since failure is very rare, no check of its return value is done.
  339         return OK;
  340     case MENU_CMD_NOSTANDARD:
  341         menu->ExcludeStandardItems(); // Since failure is very rare, no check of its return value is done.
  342         return OK;
  343     case MENU_CMD_COLOR:
  344         menu->SetColor(aParam3, _tcsicmp(aParam4, _T("Single")));
  345         return OK;
  346     }
  347 
  348     // All the remaining commands need a menu item to operate upon, or some other requirement met below.
  349 
  350     LPTSTR new_name = _T("");
  351     if (menu_command == MENU_CMD_RENAME) // aParam4 contains the menu item's new name in this case.
  352     {
  353         new_name = aParam4;
  354         aParam4 = _T("");
  355     }
  356 
  357     // The above has handled all cases that don't require a menu item to be found or added,
  358     // including the adding separator lines.  So at the point, it is necessary to either find
  359     // or create a menu item.  The latter only occurs for the ADD command.
  360     if (!*aParam3)
  361         RETURN_MENU_ERROR(ERR_PARAM3_MUST_NOT_BE_BLANK, _T(""));
  362 
  363     // Find the menu item name AND its previous item (needed for the DELETE command) in the linked list:
  364     UserMenuItem *menu_item = NULL, *menu_item_prev = NULL; // Set defaults.
  365     bool search_by_pos = false;
  366     if (!ignore_existing_items) // i.e. Insert always inserts a new item.
  367         menu_item = menu->FindItem(aParam3, menu_item_prev, search_by_pos);
  368 
  369     // Whether an existing menu item's options should be updated without updating its submenu or label:
  370     bool update_exiting_item_options = (menu_command == MENU_CMD_ADD && menu_item && !*aParam4 && *aOptions);
  371 
  372     // Seems best to avoid performance enhancers such as (Label *)mAttribute here, since the "Menu"
  373     // command has so many modes of operation that would be difficult to parse at load-time:
  374     IObject *target_label = NULL;  // Set default.
  375     UserMenu *submenu = NULL;    // Set default.
  376     if (menu_command == MENU_CMD_ADD && !update_exiting_item_options) // Labels and submenus are only used in conjunction with the ADD command.
  377     {
  378         if (aParam4Var && aParam4Var->HasObject()) // This must take precedence over the next check below.
  379             target_label = aParam4Var->Object();
  380         else if (!*aParam4) // Allow the label/submenu to default to the menu name.
  381             aParam4 = aParam3; // Note that aParam3 will be blank in the case of a separator line.
  382         if (*aParam4) // It's not a separator line and no object was given.
  383         {
  384             if (*aParam4 == ':') // It's a submenu.
  385             {
  386                 ++aParam4;
  387                 if (   !(submenu = FindMenu(aParam4))   )
  388                     RETURN_MENU_ERROR(ERR_SUBMENU, aParam4);
  389                 // Before going further: since a submenu has been specified, make sure that the parent
  390                 // menu is not included anywhere in the nested hierarchy of that submenu's submenus.
  391                 // The OS doesn't seem to like that, creating empty or strange menus if it's attempted:
  392                 if (   submenu && (submenu == menu || submenu->ContainsMenu(menu))   )
  393                     RETURN_MENU_ERROR(_T("Submenu must not contain its parent menu."), aParam4);
  394             }
  395             else // It's a label.
  396                 if (   !(target_label = FindCallable(aParam4, NULL, 3))   )
  397                     RETURN_MENU_ERROR(ERR_NO_LABEL, aParam4);
  398         }
  399     }
  400 
  401     if (!menu_item)  // menu item doesn't exist, so create it (but only if the command is ADD).
  402     {
  403         if (menu_command != MENU_CMD_ADD || search_by_pos)
  404             // Seems best not to create menu items on-demand like this because they might get put into
  405             // an incorrect position (i.e. it seems better than menu changes be kept separate from
  406             // menu additions):
  407             RETURN_MENU_ERROR(_T("Nonexistent menu item."), aParam3);
  408 
  409         // Otherwise: Adding a new item that doesn't yet exist.
  410         UINT item_id = GetFreeMenuItemID();
  411         if (!item_id) // All ~64000 IDs are in use!
  412             RETURN_MENU_ERROR(_T("Too many menu items."), aParam3); // Short msg since so rare.
  413         if (!menu->AddItem(aParam3, item_id, target_label, submenu, aOptions, insert_at))
  414             RETURN_MENU_ERROR(_T("Menu item name too long."), aParam3); // Can also happen due to out-of-mem, but that's too rare to display.
  415         return OK;  // Item has been successfully added with the correct properties.
  416     } // if (!menu_item)
  417 
  418     // Above has found the correct menu_item to operate upon (it already returned if
  419     // the item was just created).  Since the item was found, the UserMenu's popup
  420     // menu must already exist because a UserMenu object can't have menu items unless
  421     // its menu exists.
  422 
  423     switch (menu_command)
  424     {
  425     case MENU_CMD_ADD:
  426         // This is only reached if the ADD command is being used to update the label, submenu, or
  427         // options of an existing menu item (since it would have returned above if the item was
  428         // just newly created).
  429         return menu->ModifyItem(menu_item, target_label, submenu, aOptions);
  430     case MENU_CMD_RENAME:
  431         if (!menu->RenameItem(menu_item, new_name))
  432             RETURN_MENU_ERROR(_T("Rename failed (name too long?)."), new_name);
  433         return OK;
  434     case MENU_CMD_CHECK:
  435         return menu->CheckItem(menu_item);
  436     case MENU_CMD_UNCHECK:
  437         return menu->UncheckItem(menu_item);
  438     case MENU_CMD_TOGGLECHECK:
  439         return menu->ToggleCheckItem(menu_item);
  440     case MENU_CMD_ENABLE:
  441         return menu->EnableItem(menu_item);
  442     case MENU_CMD_DISABLE: // Disables and grays the item.
  443         return menu->DisableItem(menu_item);
  444     case MENU_CMD_TOGGLEENABLE:
  445         return menu->ToggleEnableItem(menu_item);
  446     case MENU_CMD_DEFAULT:
  447         return menu->SetDefault(menu_item);
  448     case MENU_CMD_DELETE:
  449         return menu->DeleteItem(menu_item, menu_item_prev);
  450     // L17: Set or remove a menu item's icon.
  451     case MENU_CMD_ICON:
  452         // aOptions2: Icon width if specified. Defaults to system small icon size; original icon size will be used if aOptions2 is "0".
  453         if (!menu->SetItemIcon(menu_item, aParam4, ATOI(aOptions), !*aOptions2 ? GetSystemMetrics(SM_CXSMICON) : ATOI(aOptions2)))
  454             RETURN_MENU_ERROR(_T("Can't load icon."), aParam4);
  455         return OK;
  456     case MENU_CMD_NOICON:
  457         return menu->RemoveItemIcon(menu_item);
  458     } // switch()
  459     return FAIL;  // Should never be reached, but avoids compiler warning and improves bug detection.
  460 }
  461 
  462 
  463 
  464 UserMenu *Script::FindMenu(LPTSTR aMenuName)
  465 // Returns the UserMenu whose name matches aMenuName, or NULL if not found.
  466 {
  467     if (!aMenuName || !*aMenuName) return NULL;
  468     for (UserMenu *menu = mFirstMenu; menu != NULL; menu = menu->mNextMenu)
  469         if (!lstrcmpi(menu->mName, aMenuName)) // Match found.
  470             return menu;
  471     return NULL; // No match found.
  472 }
  473 
  474 
  475 
  476 UserMenu *Script::FindMenu(HMENU aMenuHandle)
  477 {
  478     if (!aMenuHandle) return NULL;
  479     for (UserMenu *menu = mFirstMenu; menu != NULL; menu = menu->mNextMenu)
  480         if (menu->mMenu == aMenuHandle)
  481             return menu;
  482     return NULL; // No match found.
  483 }
  484 
  485 
  486 
  487 UserMenu *Script::AddMenu(LPTSTR aMenuName)
  488 // Caller must have already ensured aMenuName doesn't exist yet in the list.
  489 // Returns the newly created UserMenu object.
  490 {
  491     if (!aMenuName || !*aMenuName) return NULL;
  492     size_t length = _tcslen(aMenuName);
  493     if (length > MAX_MENU_NAME_LENGTH)
  494         return NULL;  // Caller should show error if desired.
  495     // After mem is allocated, the object takes charge of its later deletion:
  496     LPTSTR name_dynamic = tmalloc(length + 1);  // +1 for terminator.
  497     if (!name_dynamic)
  498         return NULL;  // Caller should show error if desired.
  499     _tcscpy(name_dynamic, aMenuName);
  500     UserMenu *menu = new UserMenu(name_dynamic);
  501     if (!menu)
  502     {
  503         free(name_dynamic);
  504         return NULL;  // Caller should show error if desired.
  505     }
  506     if (!mFirstMenu)
  507         mFirstMenu = mLastMenu = menu;
  508     else
  509     {
  510         mLastMenu->mNextMenu = menu;
  511         // This must be done after the above:
  512         mLastMenu = menu;
  513     }
  514     ++mMenuCount;  // Only after memory has been successfully allocated.
  515     return menu;
  516 }
  517 
  518 
  519 
  520 ResultType Script::ScriptDeleteMenu(UserMenu *aMenu)
  521 // Deletes a UserMenu object and all the UserMenuItem objects that belong to it.
  522 // Any UserMenuItem object that has a submenu attached to it does not result in
  523 // that submenu being deleted, even if no other menus are using that submenu
  524 // (i.e. the user must delete all menus individually).  Any menus which have
  525 // aMenu as one of their submenus will have that menu item deleted from their
  526 // menus to avoid any chance of problems due to non-existent or NULL submenus.
  527 {
  528     // Delete any other menu's menu item that has aMenu as its attached submenu:
  529     UserMenuItem *mi, *mi_prev, *mi_to_delete;
  530     for (UserMenu *m = mFirstMenu; m; m = m->mNextMenu)
  531         if (m != aMenu) // Don't bother with this menu even if it's submenu of itself, since it will be destroyed anyway.
  532             for (mi = m->mFirstMenuItem, mi_prev = NULL; mi;)
  533             {
  534                 mi_to_delete = mi;
  535                 mi = mi->mNextMenuItem;
  536                 if (mi_to_delete->mSubmenu == aMenu)
  537                     m->DeleteItem(mi_to_delete, mi_prev);
  538                 else
  539                     mi_prev = mi_to_delete;
  540             }
  541     // Remove aMenu from the linked list.  First find the item that occurs prior the aMenu in the list:
  542     UserMenu *aMenu_prev;
  543     for (aMenu_prev = mFirstMenu; aMenu_prev; aMenu_prev = aMenu_prev->mNextMenu)
  544         if (aMenu_prev->mNextMenu == aMenu)
  545             break;
  546     if (aMenu == mLastMenu)
  547         mLastMenu = aMenu_prev; // Can be NULL if the list will now be empty.
  548     if (aMenu_prev) // there is another item prior to aMenu in the linked list.
  549         aMenu_prev->mNextMenu = aMenu->mNextMenu; // Can be NULL if aMenu was the last one.
  550     else // aMenu was the first one in the list.
  551         mFirstMenu = aMenu->mNextMenu; // Can be NULL if the list will now be empty.
  552     aMenu->Destroy(); // Destroy the OS menu.
  553     aMenu->DeleteAllItems();
  554     if (aMenu->mBrush) // Free the brush used for the menu's background color.
  555         DeleteObject(aMenu->mBrush);
  556     free(aMenu->mName); // Since it was separately allocated.
  557     delete aMenu;
  558     --mMenuCount;
  559     return OK;
  560 }
  561 
  562 
  563 
  564 UINT Script::GetFreeMenuItemID()
  565 // Returns an unused menu item ID, or 0 if all IDs are used.
  566 {
  567     // Need to find a menuID that isn't already in use by one of the other menu items.
  568     // But also need to conserve menu items since only a relatively small number of IDs is available.
  569     // Can't simply use ID_USER_FIRST + mMenuItemCount because: 1) There might be more than one
  570     // user defined menu; 2) a menu item in the middle of the list may have been deleted,
  571     // in which case that value would already be in use by the last item.
  572     // Update: Now using caching of last successfully found free-ID to greatly improve avg.
  573     // performance, especially for menus that contain thousands of items and submenus, such as
  574     // ones that are built to mirror an entire nested directory structure.  Caching should
  575     // improve performance even after all menu IDs within the available range have been
  576     // allocated once (via adding and deleting menus + menu items) since large blocks of free IDs
  577     // should be free, and on average, the caching will exploit these large free blocks.  However,
  578     // if large amounts of menus and menu items are continually deleted and re-added by a script,
  579     // the pool of free IDs will become fragmented over time, which will reduce performance.
  580     // Since that kind of script behavior seems very rare, no attempt is made to "defragment".
  581     // If more performance is needed in the future (seems unlikely for 99.9999% of scripts),
  582     // could maintain an field of ~64000 bits, each bit representing whether a menu item ID is
  583     // free.  Then, every time a menu or one or more of its IDs is deleted or added, the corresponding
  584     // ID could be marked as free/taken.  That would add quite a bit of complexity to the menu
  585     // delete code, however, and it would reduce the overall maintainability.  So it definitely
  586     // doesn't seem worth it, especially since Windows XP seems to have trouble even displaying
  587     // menus larger than around 15000-25000 items.
  588     static UINT sLastFreeID = ID_USER_FIRST - 1;
  589     // Increment by one for each new search, both due to the above line and because the
  590     // last-found free ID has a high likelihood of still being in use:
  591     ++sLastFreeID;
  592     bool id_in_use;
  593     // Note that the i variable is used to force the loop to complete exactly one full
  594     // circuit through all available IDs, regardless of where the starting/cached value:
  595     for (int i = 0; i < (ID_USER_LAST - ID_USER_FIRST + 1); ++i, ++sLastFreeID) // FOR EACH ID
  596     {
  597         if (sLastFreeID > ID_USER_LAST)
  598             sLastFreeID = ID_USER_FIRST;  // Wrap around to the beginning so that one complete circuit is made.
  599         id_in_use = false;  // Reset the default each iteration (overridden if the below finds a match).
  600         for (UserMenu *m = mFirstMenu; m; m = m->mNextMenu) // FOR EACH MENU
  601         {
  602             for (UserMenuItem *mi = m->mFirstMenuItem; mi; mi = mi->mNextMenuItem) // FOR EACH MENU ITEM
  603             {
  604                 if (mi->mMenuID == sLastFreeID)
  605                 {
  606                     id_in_use = true;
  607                     break;
  608                 }
  609             }
  610             if (id_in_use) // No point in searching the other menus, since it's now known to be in use.
  611                 break;
  612         }
  613         if (!id_in_use) // Break before the loop increments sLastFreeID.
  614             break;
  615     }
  616     return id_in_use ? 0 : sLastFreeID;
  617 }
  618 
  619 
  620 
  621 UserMenuItem *UserMenu::FindItem(LPTSTR aNameOrPos, UserMenuItem *&aPrevItem, bool &aByPos)
  622 {
  623     int index_to_find = -1;
  624     size_t length = _tcslen(aNameOrPos);
  625     // Check if the caller identified the menu item by position/index rather than by name.
  626     // This should be reasonably backwards-compatible, as any scripts that want literally
  627     // "1&" as menu item text would have to actually write "1&&".
  628     if (length > 1
  629         && aNameOrPos[length - 1] == '&' // Use the same convention as WinMenuSelectItem: 1&, 2&, 3&...
  630         && aNameOrPos[length - 2] != '&') // Not &&, which means one literal &.
  631         index_to_find = ATOI(aNameOrPos) - 1; // Yields -1 if aParam3 doesn't start with a number.
  632     aByPos = index_to_find > -1;
  633     // Find the item.
  634     int current_index = 0;
  635     UserMenuItem *menu_item_prev = NULL, *menu_item;
  636     for (menu_item = mFirstMenuItem
  637         ; menu_item
  638         ; menu_item_prev = menu_item, menu_item = menu_item->mNextMenuItem, ++current_index)
  639         if (current_index == index_to_find // Found by index.
  640             || !lstrcmpi(menu_item->mName, aNameOrPos)) // Found by case-insensitive text match.
  641             break;
  642     aPrevItem = menu_item_prev;
  643     return menu_item;
  644 }
  645 
  646 
  647 
  648 // Macros for use with the below methods (in previous versions, submenus were identified by position):
  649 #define aMenuItem_ID        aMenuItem->mMenuID
  650 #define aMenuItem_MF_BY     MF_BYCOMMAND
  651 #define UPDATE_GUI_MENU_BARS(menu_type, hmenu) \
  652     if (menu_type == MENU_TYPE_BAR && g_guiCount)\
  653         GuiType::UpdateMenuBars(hmenu); // Above: If it's not a popup, it's probably a menu bar.
  654 
  655 
  656 #ifdef AUTOHOTKEYSC
  657 #define CHANGE_DEFAULT_IF_NEEDED \
  658     if (mDefault == aMenuItem)\
  659     {\
  660         if (mMenu)\
  661         {\
  662             if (this == g_script.mTrayMenu)\
  663                 SetMenuDefaultItem(mMenu, mIncludeStandardItems && g_AllowMainWindow ? ID_TRAY_OPEN : -1, FALSE);\
  664             else\
  665                 SetMenuDefaultItem(mMenu, -1, FALSE);\
  666         }\
  667         mDefault = NULL;\
  668     }
  669 #else
  670 #define CHANGE_DEFAULT_IF_NEEDED \
  671     if (mDefault == aMenuItem)\
  672     {\
  673         if (mMenu)\
  674         {\
  675             if (this == g_script.mTrayMenu)\
  676                 SetMenuDefaultItem(mMenu, mIncludeStandardItems ? ID_TRAY_OPEN : -1, FALSE);\
  677             else\
  678                 SetMenuDefaultItem(mMenu, -1, FALSE);\
  679         }\
  680         mDefault = NULL;\
  681     }
  682 #endif
  683 
  684 
  685 
  686 ResultType UserMenu::AddItem(LPTSTR aName, UINT aMenuID, IObject *aLabel, UserMenu *aSubmenu, LPTSTR aOptions
  687     , UserMenuItem **aInsertAt)
  688 // Caller must have already ensured that aName does not yet exist as a user-defined menu item
  689 // in this->mMenu.
  690 {
  691     size_t length = _tcslen(aName);
  692     if (length > MAX_MENU_NAME_LENGTH)
  693         return FAIL;  // Caller should show error if desired.
  694     // After mem is allocated, the object takes charge of its later deletion:
  695     LPTSTR name_dynamic;
  696     if (length)
  697     {
  698         if (   !(name_dynamic = tmalloc(length + 1))   )  // +1 for terminator.
  699             return FAIL;  // Caller should show error if desired.
  700         _tcscpy(name_dynamic, aName);
  701     }
  702     else
  703         name_dynamic = Var::sEmptyString; // So that it can be detected as a non-allocated empty string.
  704     UserMenuItem *menu_item = new UserMenuItem(name_dynamic, length + 1, aMenuID, aLabel, aSubmenu, this);
  705     if (!menu_item) // Should also be very rare.
  706     {
  707         if (name_dynamic != Var::sEmptyString)
  708             free(name_dynamic);
  709         return FAIL;  // Caller should show error if desired.
  710     }
  711     if (mMenu)
  712     {
  713         InternalAppendMenu(menu_item, aInsertAt ? *aInsertAt : NULL);
  714         UPDATE_GUI_MENU_BARS(mMenuType, mMenu)
  715     }
  716     if (aInsertAt)
  717     {
  718         // Caller has passed a pointer to the variable in the linked list which should
  719         // hold this new item; either &mFirstMenuItem or &previous_item->mNextMenuItem.
  720         menu_item->mNextMenuItem = *aInsertAt;
  721         // This must be done after the above:
  722         *aInsertAt = menu_item;
  723     }
  724     else
  725     {
  726         // Append the item.
  727         if (!mFirstMenuItem)
  728             mFirstMenuItem = menu_item;
  729         else
  730             mLastMenuItem->mNextMenuItem = menu_item;
  731         // This must be done after the above:
  732         mLastMenuItem = menu_item;
  733     }
  734     ++mMenuItemCount;  // Only after memory has been successfully allocated.
  735     if (*aOptions)
  736         UpdateOptions(menu_item, aOptions);
  737     if (_tcschr(aName, '\t')) // v1.1.04: The new item has a keyboard accelerator.
  738         UpdateAccelerators();
  739     return OK;
  740 }
  741 
  742 
  743 
  744 ResultType UserMenu::InternalAppendMenu(UserMenuItem *mi, UserMenuItem *aInsertBefore)
  745 // Appends an item to mMenu and and ensures the new item's ID is set.
  746 {
  747     MENUITEMINFO mii;
  748     mii.cbSize = sizeof(mii);
  749     mii.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STRING | MIIM_STATE;
  750     mii.wID = mi->mMenuID;
  751     mii.fType = mi->mMenuType;
  752     mii.dwTypeData = mi->mName;
  753     mii.fState = mi->mMenuState;
  754     if (mi->mSubmenu)
  755     {
  756         // Ensure submenu is created so that its handle can be used below.
  757         if (!mi->mSubmenu->Create())
  758             return FAIL;
  759         mii.fMask |= MIIM_SUBMENU;
  760         mii.hSubMenu = mi->mSubmenu->mMenu;
  761     }
  762     if (mi->mIcon)
  763     {
  764         mii.fMask |= MIIM_BITMAP;
  765         mii.hbmpItem = g_os.IsWinVistaOrLater() ? mi->mBitmap : HBMMENU_CALLBACK;
  766     }
  767     UINT insert_at;
  768     BOOL by_position;
  769     if (aInsertBefore)
  770         insert_at = aInsertBefore->mMenuID, by_position = FALSE;
  771     else
  772         insert_at = GetMenuItemCount(mMenu), by_position = TRUE;
  773     // Although AppendMenu() ignores the ID when adding a separator and provides no way to
  774     // specify the ID when adding a submenu, that is purely a limitation of AppendMenu().
  775     // Using InsertMenuItem() instead allows us to always set the ID, which simplifies
  776     // identifying separator and submenu items later on.
  777     return InsertMenuItem(mMenu, insert_at, by_position, &mii) ? OK : FAIL;
  778 }
  779 
  780 
  781 
  782 UserMenuItem::UserMenuItem(LPTSTR aName, size_t aNameCapacity, UINT aMenuID, IObject *aLabel, UserMenu *aSubmenu, UserMenu *aMenu)
  783 // UserMenuItem Constructor.
  784     : mName(aName), mNameCapacity(aNameCapacity), mMenuID(aMenuID), mLabel(aLabel), mSubmenu(aSubmenu), mMenu(aMenu)
  785     , mPriority(0) // default priority = 0
  786     , mMenuState(MFS_ENABLED | MFS_UNCHECKED), mMenuType(*aName ? MFT_STRING : MFT_SEPARATOR)
  787     , mNextMenuItem(NULL)
  788     , mIcon(NULL) // L17: Initialize mIcon/mBitmap union.
  789 {
  790 }
  791 
  792 
  793 
  794 ResultType UserMenu::DeleteItem(UserMenuItem *aMenuItem, UserMenuItem *aMenuItemPrev)
  795 {
  796     // Remove this menu item from the linked list:
  797     if (aMenuItem == mLastMenuItem)
  798         mLastMenuItem = aMenuItemPrev; // Can be NULL if the list will now be empty.
  799     if (aMenuItemPrev) // there is another item prior to aMenuItem in the linked list.
  800         aMenuItemPrev->mNextMenuItem = aMenuItem->mNextMenuItem; // Can be NULL if aMenuItem was the last one.
  801     else // aMenuItem was the first one in the list.
  802         mFirstMenuItem = aMenuItem->mNextMenuItem; // Can be NULL if the list will now be empty.
  803     CHANGE_DEFAULT_IF_NEEDED  // Should do this before freeing aMenuItem's memory.
  804     if (g_script.mThisMenuItem == aMenuItem)
  805         g_script.mThisMenuItem = NULL;
  806     if (mMenu) // Delete the item from the menu.
  807         RemoveMenu(mMenu, aMenuItem_ID, aMenuItem_MF_BY); // v1.0.48: Lexikos: DeleteMenu() destroys any sub-menu handle associated with the item, so use RemoveMenu. Otherwise the submenu handle stored somewhere else in memory would suddenly become invalid.
  808     RemoveItemIcon(aMenuItem); // L17: Free icon or bitmap.
  809     if (aMenuItem->mName != Var::sEmptyString)
  810         free(aMenuItem->mName); // Since it was separately allocated.
  811     delete aMenuItem; // Do this last when its contents are no longer needed.
  812     --mMenuItemCount;
  813     UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
  814     return OK;
  815 }
  816 
  817 
  818 
  819 ResultType UserMenu::DeleteAllItems()
  820 // Remove all menu items from the linked list and from the menu.
  821 {
  822     // Fixed for v1.1.27.03: Don't attempt to take a shortcut by calling Destroy(), as it
  823     // will fail if this is a sub-menu of a menu bar.  Removing the items individually will
  824     // do exactly what the user expects.  The following old comment indicates one reason
  825     // Destroy() was used; that reason is now obsolete since submenus are given IDs:
  826     // "In addition, this avoids the need to find any submenus by position:"
  827     if (!mFirstMenuItem)
  828         return OK;  // If there are no user-defined menu items, it's already in the correct state.
  829     UserMenuItem *menu_item_to_delete;
  830     for (UserMenuItem *mi = mFirstMenuItem; mi;)
  831     {
  832         if (mMenu)
  833             RemoveMenu(mMenu, mi->mMenuID, MF_BYCOMMAND);
  834         menu_item_to_delete = mi;
  835         mi = mi->mNextMenuItem;
  836         if (g_script.mThisMenuItem == menu_item_to_delete)
  837             g_script.mThisMenuItem = NULL;
  838         RemoveItemIcon(menu_item_to_delete); // L26: Free icon or bitmap!
  839         if (menu_item_to_delete->mName != Var::sEmptyString)
  840             delete menu_item_to_delete->mName; // Since it was separately allocated.
  841         delete menu_item_to_delete;
  842     }
  843     mFirstMenuItem = mLastMenuItem = NULL;
  844     mMenuItemCount = 0;
  845     mDefault = NULL;  // i.e. there can't be a *user-defined* default item anymore, even if this is the tray.
  846     UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
  847     return OK;
  848 }
  849 
  850 
  851 
  852 ResultType UserMenu::ModifyItem(UserMenuItem *aMenuItem, IObject *aLabel, UserMenu *aSubmenu, LPTSTR aOptions)
  853 // Modify the label, submenu, or options of a menu item (exactly one of these should be NULL and the
  854 // other not except when updating only the options).
  855 // If a menu item becomes a submenu, we don't relinquish its ID in case it's ever made a normal item
  856 // again (avoids the need to re-lookup a unique ID).
  857 {
  858     if (*aOptions)
  859         UpdateOptions(aMenuItem, aOptions);
  860     if (!aLabel && !aSubmenu) // We were called only to update this item's options.
  861         return OK;
  862 
  863     aMenuItem->mLabel = aLabel;  // This will be NULL if this menu item is a separator or submenu.
  864     if (aMenuItem->mSubmenu == aSubmenu) // Below relies on this check.
  865         return OK;
  866     if (!mMenu)
  867     {
  868         aMenuItem->mSubmenu = aSubmenu;  // Just set the indicator for when the menu is later created.
  869         return OK;
  870     }
  871 
  872     // Otherwise, since the OS menu exists, one of these is to be done to aMenuItem in it:
  873     // 1) Change a submenu to point to a different menu.
  874     // 2) Change a submenu so that it becomes a normal menu item.
  875     // 3) Change a normal menu item into a submenu.
  876 
  877     // Since Create() ensures that aSubmenu is non-null whenever this->mMenu is non-null, this is just
  878     // an extra safety check in case some other method destroyed aSubmenu since then:
  879     if (aSubmenu)
  880         if (!aSubmenu->Create()) // Create if needed.  No error msg since so rare.
  881             return FAIL;
  882 
  883     MENUITEMINFO mii;
  884     mii.cbSize = sizeof(mii);
  885     mii.fMask = MIIM_SUBMENU;
  886     mii.hSubMenu = aSubmenu ? aSubmenu->mMenu : NULL;
  887     if (SetMenuItemInfo(mMenu, aMenuItem->mMenuID, FALSE, &mii))
  888     {
  889         // Submenu was just made into a different submenu or converted into a normal menu item.
  890         // Since the OS (as an undocumented side effect) sometimes destroys the menu itself when
  891         // a submenu is changed in this way, update our state to indicate that the menu handle
  892         // is no longer valid:
  893         if (aMenuItem->mSubmenu && aMenuItem->mSubmenu->mMenu && !IsMenu(aMenuItem->mSubmenu->mMenu))
  894         {
  895             UserMenu *temp = aMenuItem->mSubmenu;
  896             aMenuItem->mSubmenu = aSubmenu; // Should be done before the below so that Destroy() sees the change.
  897             // The following shouldn't fail because submenus are popup menus, and popup menus can't be
  898             // menu bars. Update: Even if it does fail due to causing a cascade-destroy upward toward any
  899             // menu bar that happens to own it, it seems okay because the real purpose here is simply to
  900             // update that fact that "temp" was already destroyed indirectly by the OS, as evidenced by
  901             // the fact that IsMenu() returned FALSE above.
  902             temp->Destroy();
  903         }
  904         else
  905             aMenuItem->mSubmenu = aSubmenu;
  906     }
  907     // else no error msg and return OK so that the thread will continue.  This may help catch
  908     // bugs in the course of normal use of this feature.
  909     return OK;
  910 }
  911 
  912 
  913 
  914 void UserMenu::UpdateOptions(UserMenuItem *aMenuItem, LPTSTR aOptions)
  915 {
  916     UINT new_type = aMenuItem->mMenuType; // Set default.
  917 
  918     LPTSTR next_option, option_end;
  919     bool adding;
  920     TCHAR orig_char;
  921 
  922     // See GuiType::ControlParseOptions() for comments about how the options are parsed.
  923     for (next_option = aOptions; *next_option; next_option = option_end)
  924     {
  925         next_option = omit_leading_whitespace(next_option);
  926         if (*next_option == '-')
  927         {
  928             adding = false;
  929             ++next_option;
  930         }
  931         else
  932         {
  933             adding = true;
  934             if (*next_option == '+')
  935                 ++next_option;
  936         }
  937 
  938         if (!*next_option)
  939             break;
  940         if (   !(option_end = StrChrAny(next_option, _T(" \t")))   )
  941             option_end = next_option + _tcslen(next_option);
  942         if (option_end == next_option)
  943             continue;
  944 
  945         orig_char = *option_end;
  946         *option_end = '\0';
  947         // End generic option-parsing code; begin menu options.
  948 
  949              if (!_tcsicmp(next_option, _T("Radio"))) if (adding) new_type |= MFT_RADIOCHECK; else new_type &= ~MFT_RADIOCHECK;
  950         else if (!_tcsicmp(next_option, _T("Right"))) if (adding) new_type |= MFT_RIGHTJUSTIFY; else new_type &= ~MFT_RIGHTJUSTIFY;
  951         else if (!_tcsicmp(next_option, _T("Break"))) if (adding) new_type |= MFT_MENUBREAK; else new_type &= ~MFT_MENUBREAK;
  952         else if (!_tcsicmp(next_option, _T("BarBreak"))) if (adding) new_type |= MFT_MENUBARBREAK; else new_type &= ~MFT_MENUBARBREAK;
  953         else if (ctoupper(*next_option) == 'P')
  954             aMenuItem->mPriority = ATOI(next_option + 1);
  955 
  956         *option_end = orig_char;
  957     }
  958 
  959     if (new_type != aMenuItem->mMenuType)
  960     {
  961         if (mMenu)
  962         {
  963             MENUITEMINFO mii;
  964             mii.cbSize = sizeof(mii);
  965             mii.fMask = MIIM_FTYPE;
  966             mii.fType = new_type;
  967             SetMenuItemInfo(mMenu, aMenuItem->mMenuID, FALSE, &mii);
  968         }
  969         aMenuItem->mMenuType = (WORD)new_type;
  970     }
  971 }
  972 
  973 
  974 
  975 ResultType UserMenu::RenameItem(UserMenuItem *aMenuItem, LPTSTR aNewName)
  976 // Caller should specify "" for aNewName to convert aMenuItem into a separator.
  977 // Returns FAIL if the new name conflicts with an existing name.
  978 {
  979     if (_tcslen(aNewName) > MAX_MENU_NAME_LENGTH)
  980         return FAIL; // Caller should display error if desired.
  981 
  982     // Preserve any additional type flags set by options, but exclude the main type bits.
  983     // Also clear MFT_OWNERDRAW (if set by the script), since it changes the meaning of dwTypeData.
  984     // MSDN: "The MFT_BITMAP, MFT_SEPARATOR, and MFT_STRING values cannot be combined with one another."
  985     UINT new_type = (aMenuItem->mMenuType & ~(MFT_BITMAP | MFT_SEPARATOR | MFT_STRING | MFT_OWNERDRAW))
  986         | (*aNewName ? MFT_STRING : MFT_SEPARATOR);
  987 
  988     if (!mMenu) // Just update the member variables for later use when the menu is created.
  989     {
  990         aMenuItem->mMenuType = (WORD)new_type;
  991         return UpdateName(aMenuItem, aNewName);
  992     }
  993 
  994     MENUITEMINFO mii;
  995     mii.cbSize = sizeof(mii);
  996     mii.fMask = 0;
  997 
  998     if (*aNewName)
  999     {
 1000         if (aMenuItem->mMenuType & MFT_SEPARATOR)
 1001         {
 1002             // Since this item is currently a separator, the system will have disabled it.
 1003             // Set the item's state to what it should be:
 1004             mii.fMask |= MIIM_STATE;
 1005             mii.fState = aMenuItem->mMenuState;
 1006         }
 1007     }
 1008     else // converting into a separator
 1009     {
 1010         // Notes about the below macro:
 1011         // ID_TRAY_OPEN is not set to be the default for the self-contained version, since it lacks that menu item.
 1012         CHANGE_DEFAULT_IF_NEEDED
 1013         // Testing shows that if an item is converted into a separator and back into a
 1014         // normal item, it retains its submenu.  So don't set the submenu to NULL, since
 1015         // it's not necessary and would result in the OS destroying the submenu:
 1016         //if (aMenuItem->mSubmenu)  // Converting submenu into a separator.
 1017         //{
 1018         //  mii.fMask |= MIIM_SUBMENU;
 1019         //  mii.hSubMenu = NULL;
 1020         //}
 1021     }
 1022 
 1023     mii.fMask |= MIIM_TYPE;
 1024     mii.fType = new_type;
 1025     mii.dwTypeData = aNewName;
 1026 
 1027     // v1.1.04: If the new and old names both have accelerators, call UpdateAccelerators() if they
 1028     // are different. Otherwise call it if only one is NULL (i.e. accelerator was added or removed).
 1029     LPTSTR old_accel = _tcschr(aMenuItem->mName, '\t'), new_accel = _tcschr(aNewName, '\t');
 1030     bool update_accel = old_accel && new_accel ? _tcsicmp(old_accel, new_accel) : old_accel != new_accel;
 1031 
 1032     // Failure is rare enough in the below that no attempt is made to undo the above:
 1033     BOOL result = SetMenuItemInfo(mMenu, aMenuItem->mMenuID, FALSE, &mii);
 1034     UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
 1035     if (  !(result && UpdateName(aMenuItem, aNewName))  )
 1036         return FAIL;
 1037     aMenuItem->mMenuType = (WORD)mii.fType; // Update this in case the menu is destroyed/recreated.
 1038     if (update_accel) // v1.1.04: Above determined this item's accelerator was changed.
 1039         UpdateAccelerators(); // Must not be done until after mName is updated.
 1040     if (*aNewName)
 1041         ApplyItemIcon(aMenuItem); // If any.  Simpler to call this than combine it into the logic above.
 1042     return OK;
 1043 }
 1044 
 1045 
 1046 
 1047 ResultType UserMenu::UpdateName(UserMenuItem *aMenuItem, LPTSTR aNewName)
 1048 // Caller should already have ensured that aMenuItem is not too long.
 1049 {
 1050     size_t new_length = _tcslen(aNewName);
 1051     if (new_length)
 1052     {
 1053         if (new_length >= aMenuItem->mNameCapacity) // Too small, so reallocate.
 1054         {
 1055             // Use a temp var. so that mName will never wind up being NULL (relied on by other things).
 1056             // This also retains the original menu name if the allocation fails:
 1057             LPTSTR temp = tmalloc(new_length + 1);  // +1 for terminator.
 1058             if (!temp)
 1059                 return FAIL;
 1060             // Otherwise:
 1061             if (aMenuItem->mName != Var::sEmptyString) // Since it was previously allocated, free it.
 1062                 free(aMenuItem->mName);
 1063             aMenuItem->mName = temp;
 1064             aMenuItem->mNameCapacity = new_length + 1;
 1065         }
 1066         _tcscpy(aMenuItem->mName, aNewName);
 1067     }
 1068     else // It will become a separator.
 1069     {
 1070         *aMenuItem->mName = '\0'; // Safe because even if it's capacity is 1 byte, it's a writable byte.
 1071     }
 1072     return OK;
 1073 }
 1074 
 1075 
 1076 
 1077 ResultType UserMenu::SetItemState(UserMenuItem *aMenuItem, UINT aState, UINT aStateMask)
 1078 {
 1079     if (mMenu)
 1080     {
 1081         MENUITEMINFO mii;
 1082         mii.cbSize = sizeof(mii);
 1083         mii.fMask = MIIM_STATE;
 1084         // Retrieve the current state from the menu rather than using mMenuState,
 1085         // in case the script has modified the state via DllCall.
 1086         if (GetMenuItemInfo(mMenu, aMenuItem->mMenuID, FALSE, &mii))
 1087         {
 1088             mii.fState = (mii.fState & ~aStateMask) | aState;
 1089             // Update our state in case the menu gets destroyed/recreated.
 1090             aMenuItem->mMenuState = (WORD)mii.fState;
 1091             // Set the new state.
 1092             SetMenuItemInfo(mMenu, aMenuItem->mMenuID, FALSE, &mii);
 1093             if (aStateMask & MFS_DISABLED) // i.e. enabling or disabling, which would affect a menu bar.
 1094                 UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
 1095             return OK;
 1096         }
 1097     }
 1098     aMenuItem->mMenuState = (WORD)((aMenuItem->mMenuState & ~aStateMask) | aState);
 1099     return OK;
 1100 }
 1101 
 1102 ResultType UserMenu::CheckItem(UserMenuItem *aMenuItem)
 1103 {
 1104     return SetItemState(aMenuItem, MFS_CHECKED, MFS_CHECKED);
 1105 }
 1106 
 1107 ResultType UserMenu::UncheckItem(UserMenuItem *aMenuItem)
 1108 {
 1109     return SetItemState(aMenuItem, MFS_UNCHECKED, MFS_CHECKED);
 1110 }
 1111 
 1112 ResultType UserMenu::ToggleCheckItem(UserMenuItem *aMenuItem)
 1113 {
 1114     return SetItemState(aMenuItem, (aMenuItem->mMenuState & MFS_CHECKED) ^ MFS_CHECKED, MFS_CHECKED);
 1115 }
 1116 
 1117 ResultType UserMenu::EnableItem(UserMenuItem *aMenuItem)
 1118 {
 1119     return SetItemState(aMenuItem, MFS_ENABLED, MFS_DISABLED);
 1120 }
 1121 
 1122 ResultType UserMenu::DisableItem(UserMenuItem *aMenuItem)
 1123 {
 1124     return SetItemState(aMenuItem, MFS_DISABLED, MFS_DISABLED);
 1125 }
 1126 
 1127 ResultType UserMenu::ToggleEnableItem(UserMenuItem *aMenuItem)
 1128 {
 1129     return SetItemState(aMenuItem, (aMenuItem->mMenuState & MFS_DISABLED) ^ MFS_DISABLED, MFS_DISABLED);
 1130 }
 1131 
 1132 
 1133 
 1134 ResultType UserMenu::SetDefault(UserMenuItem *aMenuItem)
 1135 {
 1136     if (mDefault == aMenuItem)
 1137         return OK;
 1138     mDefault = aMenuItem;
 1139     if (!mMenu) // No further action required: the new setting will be in effect when the menu is created.
 1140         return OK;
 1141     if (aMenuItem) // A user-defined menu item is being made the default.
 1142         SetMenuDefaultItem(mMenu, aMenuItem->mMenuID, FALSE); // This also ensures that only one is default at a time.
 1143     else
 1144     {
 1145         // Otherwise, a user-defined item that was previously the default is no longer the default.
 1146         // Provide a new default if this is the tray menu, the standard items are present, and a default
 1147         // action is called for:
 1148         if (this == g_script.mTrayMenu) // Necessary for proper operation of the self-contained version:
 1149 #ifdef AUTOHOTKEYSC
 1150             SetMenuDefaultItem(mMenu, g_AllowMainWindow && mIncludeStandardItems ? ID_TRAY_OPEN : -1, FALSE);
 1151 #else
 1152             SetMenuDefaultItem(mMenu, mIncludeStandardItems ? ID_TRAY_OPEN : -1, FALSE);
 1153 #endif
 1154         else
 1155             SetMenuDefaultItem(mMenu, -1, FALSE);
 1156     }
 1157     UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Testing shows that menu bars themselves can have default items, and that this is necessary.
 1158     return OK;
 1159 }
 1160 
 1161 
 1162 
 1163 ResultType UserMenu::IncludeStandardItems()
 1164 {
 1165     if (mIncludeStandardItems)
 1166         return OK;
 1167     // In this case, immediately create the menu to support having the standard menu items on the
 1168     // bottom or middle rather than at the top (which is the default). Older comment: Only do
 1169     // this if it was false beforehand so that the standard menu items will be appended to whatever
 1170     // the user has already added to the tray menu (increases flexibility).
 1171     if (!Create()) // It may already exist, in which case this returns OK.
 1172         return FAIL; // No error msg since so rare.
 1173     return AppendStandardItems();
 1174 }
 1175 
 1176 
 1177 
 1178 ResultType UserMenu::ExcludeStandardItems()
 1179 {
 1180     if (!mIncludeStandardItems)
 1181         return OK;
 1182     mIncludeStandardItems = false;
 1183     // This method isn't used because it fails on sub-menus of a menu bar:
 1184     //return Destroy(); // It will be recreated automatically the next time the user displays it.
 1185     if (mMenu)
 1186     {
 1187         for (UINT i = ID_TRAY_FIRST; i <= ID_TRAY_LAST; ++i)
 1188             RemoveMenu(mMenu, i, MF_BYCOMMAND);
 1189         UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary (though it's unusual to put the standard items on a menu bar).
 1190     }
 1191     return OK;
 1192 }
 1193 
 1194 
 1195 
 1196 ResultType UserMenu::Create(MenuTypeType aMenuType)
 1197 // Menu bars require non-popup menus (CreateMenu vs. CreatePopupMenu).  Rather than maintain two
 1198 // different types of HMENUs on the rare chance that a script might try to use a menu both as
 1199 // a popup and a menu bar, it seems best to have only one type to keep the code simple and reduce
 1200 // resources used for the menu.  This has been documented in the help file.
 1201 // Note that a menu bar's submenus can be (perhaps must be) of the popup type, so we only need
 1202 // to worry about the distinction for the menu bar itself.  The caller tells us which is which.
 1203 {
 1204     if (mMenu)
 1205     {
 1206         // Since menu already exists, check if it's the right type.  If caller left the type unspecified,
 1207         // assume it is the right type:
 1208         if (aMenuType == MENU_TYPE_NONE || aMenuType == mMenuType)
 1209             return OK;
 1210         else // It exists but it's the wrong type.  Destroy and recreate it (but keep TRAY always as popup type).
 1211             if (!_tcsicmp(mName, _T("tray")) || !Destroy()) // Could not be destroyed, perhaps because it is attached to a window as a menu bar.
 1212                 return FAIL;
 1213     }
 1214     if (aMenuType == MENU_TYPE_NONE) // Since caller didn't specify and it's about to be (re)created, assume popup.
 1215         aMenuType = MENU_TYPE_POPUP;
 1216     if (   !(mMenu = (aMenuType == MENU_TYPE_BAR) ? CreateMenu() : CreatePopupMenu())   )
 1217         // Failure is rare, so no error msg here (caller can, if it wants).
 1218         return FAIL;
 1219 
 1220     mMenuType = aMenuType;  // We have to track its type since I don't think there's any way to find out via API.
 1221 
 1222     // It seems best not to have a mandatory EXIT item added to the bottom of the tray menu
 1223     // for these reasons:
 1224     // 1) Allows the tray icon to be shown even at time when the user wants it to have no menu at all
 1225     //    (i.e. avoids the need for #NoTrayIcon just to disable the showing of the menu).
 1226     // 2) Avoids complexity because there would be a 3rd state: Standard, NoStandard, and
 1227     //    NoStandardWithExit.  This might be inconsequential, but would require testing.
 1228     //if (!mIncludeStandardItems && !mMenuItemCount)
 1229     //{
 1230     //  AppendMenu(mTrayMenu->mMenu, MF_STRING, ID_TRAY_EXIT, "E&xit");
 1231     //  return OK;
 1232     //}
 1233 
 1234     // By default, the standard menu items are added first, since the users would probably want
 1235     // their own user defined menus at the bottom where they're easier to reach:
 1236     if (mIncludeStandardItems)
 1237         AppendStandardItems();
 1238 
 1239     // Now append all of the user defined items:
 1240     UserMenuItem *mi;
 1241     for (mi = mFirstMenuItem; mi; mi = mi->mNextMenuItem)
 1242         InternalAppendMenu(mi);
 1243 
 1244     if (mDefault)
 1245         // This also automatically ensures that only one is default at a time:
 1246         SetMenuDefaultItem(mMenu, mDefault->mMenuID, FALSE);
 1247 
 1248     // Apply background color if this menu has a non-standard one.  If this menu has submenus,
 1249     // they will be individually given their own background color when created via Create(),
 1250     // which is why false is passed:
 1251     ApplyColor(false);
 1252 
 1253     // L17: Apply default style to merge checkmark/icon columns in menu.
 1254     MENUINFO menu_info;
 1255     menu_info.cbSize = sizeof(MENUINFO);
 1256     menu_info.fMask = MIM_STYLE;
 1257     menu_info.dwStyle = MNS_CHECKORBMP;
 1258     SetMenuInfo(mMenu, &menu_info);
 1259 
 1260     return OK;
 1261 }
 1262 
 1263 
 1264 
 1265 void UserMenu::SetColor(LPTSTR aColorName, bool aApplyToSubmenus)
 1266 {
 1267     // Avoid the overhead of creating HBRUSH's on OSes that don't support SetMenuInfo().
 1268     // Perhaps there is some other way to change menu background color on Win95/NT?
 1269     AssignColor(aColorName, mColor, mBrush);  // Takes care of deleting old brush, etc.
 1270     // To avoid complications, such as a submenu being detached from its parent and then its parent
 1271     // later being deleted (which causes the HBRUSH to get deleted too), give each submenu it's
 1272     // own HBRUSH handle by calling AssignColor() for each:
 1273     if (aApplyToSubmenus)
 1274         for (UserMenuItem *mi = mFirstMenuItem; mi; mi = mi->mNextMenuItem)
 1275             if (mi->mSubmenu)
 1276                 AssignColor(aColorName, mi->mSubmenu->mColor, mi->mSubmenu->mBrush);
 1277     if (mMenu)
 1278     {
 1279         ApplyColor(aApplyToSubmenus);
 1280         UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
 1281     }
 1282 }
 1283 
 1284 
 1285 
 1286 void UserMenu::ApplyColor(bool aApplyToSubmenus)
 1287 // Caller has ensured that mMenu is not NULL.
 1288 // The below should be done even if the default color is being (re)applied because
 1289 // testing shows that the OS sets the color to white if the HBRUSH becomes invalid.
 1290 // The caller is also responsible for calling UPDATE_GUI_MENU_BARS if desired.
 1291 {
 1292     MENUINFO mi = {0}; 
 1293     mi.cbSize = sizeof(MENUINFO);
 1294     mi.fMask = MIM_BACKGROUND|(aApplyToSubmenus ? MIM_APPLYTOSUBMENUS : 0);
 1295     mi.hbrBack = mBrush;
 1296     SetMenuInfo(mMenu, &mi);
 1297 }
 1298 
 1299 
 1300 
 1301 ResultType UserMenu::AppendStandardItems()
 1302 // Caller must ensure that this->mMenu exists if it wants the items to be added immediately.
 1303 {
 1304     mIncludeStandardItems = true; // even if the menu doesn't exist.
 1305     if (!mMenu)
 1306         return OK;
 1307 #ifdef AUTOHOTKEYSC
 1308     if (g_AllowMainWindow)
 1309     {
 1310         AppendMenu(mMenu, MF_STRING, ID_TRAY_OPEN, _T("&Open"));
 1311         if (this == g_script.mTrayMenu && !mDefault) // No user-defined default menu item, so use the standard one.
 1312             SetMenuDefaultItem(mMenu, ID_TRAY_OPEN, FALSE); // Seems to have no function other than appearance.
 1313     }
 1314 #else
 1315     AppendMenu(mMenu, MF_STRING, ID_TRAY_OPEN, _T("&Open"));
 1316     AppendMenu(mMenu, MF_STRING, ID_TRAY_HELP, _T("&Help"));
 1317     AppendMenu(mMenu, MF_SEPARATOR, ID_TRAY_SEP1, NULL); // The separators are given IDs to simplify removal.
 1318     AppendMenu(mMenu, MF_STRING, ID_TRAY_WINDOWSPY, _T("&Window Spy"));
 1319     AppendMenu(mMenu, MF_STRING, ID_TRAY_RELOADSCRIPT, _T("&Reload This Script"));
 1320     AppendMenu(mMenu, MF_STRING, ID_TRAY_EDITSCRIPT, _T("&Edit This Script"));
 1321     AppendMenu(mMenu, MF_SEPARATOR, ID_TRAY_SEP2, NULL);
 1322     if (this == g_script.mTrayMenu && !mDefault) // No user-defined default menu item, so use the standard one.
 1323         SetMenuDefaultItem(mMenu, ID_TRAY_OPEN, FALSE); // Seems to have no function other than appearance.
 1324 #endif
 1325     AppendMenu(mMenu, MF_STRING, ID_TRAY_SUSPEND, _T("&Suspend Hotkeys"));
 1326     AppendMenu(mMenu, MF_STRING, ID_TRAY_PAUSE, _T("&Pause Script"));
 1327     AppendMenu(mMenu, MF_STRING, ID_TRAY_EXIT, _T("E&xit"));
 1328     UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary (though it would be rare anyone would want the menu bar containing the std items).
 1329     return OK;  // For caller convenience.
 1330 }
 1331 
 1332 
 1333 
 1334 ResultType UserMenu::Destroy()
 1335 // Returns OK upon complete success or FAIL otherwise.  For example, even if this's menu
 1336 // is successfully destroyed, if the indirect destructions resulting from it don't succeed, this
 1337 // method returns FAIL.
 1338 {
 1339     if (!mMenu)  // For performance.
 1340         return OK;
 1341     // I think DestroyMenu() can fail if an attempt is made to destroy the menu while it is being
 1342     // displayed (but even if it doesn't fail, it seems very bad to try to destroy it then, which
 1343     // is why g_MenuIsVisible is checked just to be sure).
 1344     // But this all should be impossible in our case because the script is in an uninterruptible state
 1345     // while the menu is displayed, which in addition to pausing the current thread (which happens
 1346     // anyway), no new timed or hotkey subroutines can be launched.  Thus, this should rarely if
 1347     // ever happen, which is why no error message is given here:
 1348     //if (g_MenuIsVisible)
 1349     //  return FAIL;
 1350 
 1351     // DestroyMenu fails (GetLastError() == ERROR_INVALID_MENU_HANDLE) if a parent menu that contained
 1352     // mMenu as one of its submenus was destroyed above.  This seems to indicate that submenus are
 1353     // destroyed whenever a parent menu is destroyed.  Therefore, don't check failure on the below,
 1354     // just assume that afterward, the menu is gone.  IsMenu() is checked because the handle can be
 1355     // invalid if the OS already destroyed it behind-the-scenes (this happens to a submenu whenever
 1356     // its parent menu is destroyed, or whenever a submenu is converted back into a normal menu item):
 1357     if (IsMenu(mMenu))
 1358     {
 1359         // As a precaution, don't allow a menu to be destroyed if a window is using it as its
 1360         // menu bar. That might have bad side-effects on some OSes, especially older ones:
 1361         if (mMenuType == MENU_TYPE_BAR)
 1362             for (int i = 0; i < g_guiCount; ++i)
 1363                 if (GetMenu(g_gui[i]->mHwnd) == mMenu) // mHwnd is always non-NULL for any item in g_gui.
 1364                     return FAIL; // A GUI window is using this menu, so don't destroy the menu.
 1365         if (!DestroyMenu(mMenu)) // v1.0.30.01: Doesn't seem to be a reason *not* to check the return value and return FAIL if it failed.
 1366             return FAIL;
 1367     }
 1368     mMenu = NULL; // This must be done immediately after destroying the menu to prevent recursion problems below.
 1369     
 1370     ResultType result = OK;
 1371     UserMenuItem *mi;
 1372 
 1373     // Destroy any menu that contains this menu as a submenu.  This is done so that such
 1374     // menus will be automatically recreated the next time they are used, which is necessary
 1375     // because otherwise when such a menu is displayed the next time, the OS will show its
 1376     // old contents even though the menu is gone.  Thus, those old menu items will be
 1377     // selectable but will have no effect.  In addition, sometimes our caller plans to
 1378     // recreate this->mMenu (or have it recreated automatically upon first use) and thus
 1379     // we don't want to use DeleteMenu() because that would require having to detect whether
 1380     // the menu needs updating (to reflect whether the submenu has been recreated) every
 1381     // time we display it.  Another drawback to DeleteMenu() is that it would change the
 1382     // order of the menu items to something other than what the user originally specified
 1383     // unless InsertMenu() was woven in during the update:
 1384     for (UserMenu *m = g_script.mFirstMenu; m; m = m->mNextMenu)
 1385         if (m->mMenu)
 1386             for (mi = m->mFirstMenuItem; mi; mi = mi->mNextMenuItem)
 1387                 if (mi->mSubmenu == this)
 1388                     if (!m->Destroy())  // Attempt to destroy any menu that contains this menu as a submenu (will fail if m is a menu bar).
 1389                         result = FAIL; // Seems best to consider even one failure is considered a total failure.
 1390 
 1391     // Bug-fix for v1.1.23: Destroying sub-menus after (rather than before) the parent menu appears
 1392     // to solve an issue where a sub-menu was not marked as destroyed because IsMenu() returned TRUE
 1393     // after its parent was destroyed but only until its grandparent was destroyed.
 1394     // Bug-fix for v1.0.19: The below is now done OUTSIDE the above block because the moment a
 1395     // parent menu is deleted all its submenus AND SUB-SUB-SUB...MENUS become invalid menu handles.
 1396     // But even though the OS has done this, Destroy() must still be called recursively from here
 1397     // so that the menu handles will be set to NULL.  This is because other functions -- such as
 1398     // Display() -- do not do the IsMenu() check, relying instead on whether the handle is NULL to
 1399     // determine whether the menu physically exists.
 1400     // The moment the above is done, any submenus that were attached to mMenu are also destroyed
 1401     // by the OS.  So mark them as destroyed in our bookkeeping also:
 1402     for (mi = mFirstMenuItem; mi ; mi = mi->mNextMenuItem)
 1403         if (mi->mSubmenu && mi->mSubmenu->mMenu && !IsMenu(mi->mSubmenu->mMenu))
 1404             mi->mSubmenu->Destroy(); // Its return value isn't checked since there doesn't seem to be anything that can/should be done if it fails.
 1405 
 1406     return result;
 1407 }
 1408 
 1409 
 1410 
 1411 ResultType UserMenu::Display(bool aForceToForeground, int aX, int aY)
 1412 // aForceToForeground defaults to true because when a menu is displayed spontaneously rather than
 1413 // in response to the user right-clicking the tray icon, I believe that the OS will revert to its
 1414 // behavior of "resisting" a window that tries to "steal focus".  I believe this resistance does
 1415 // not occur when the user clicks the icon because that click causes the task bar to get focus,
 1416 // and it is likely that the OS allows other windows to steal focus from the task bar without
 1417 // resistance.  This is done because if the main window is *not* successfully activated prior to
 1418 // displaying the menu, it might be impossible to dismiss the menu by clicking outside of it.
 1419 {
 1420     if (!mMenuItemCount && !mIncludeStandardItems)
 1421         return OK;  // Consider the display of an empty menu to be a success.
 1422     //if (!IsMenu(mMenu))
 1423     //  mMenu = NULL;
 1424     if (!mMenu) // i.e. because this is the first time the user has opened the menu.
 1425         if (!Create()) // no error msg since so rare
 1426             return FAIL;
 1427     if (this == g_script.mTrayMenu)
 1428     {
 1429         // These are okay even if the menu items don't exist (perhaps because the user customized the menu):
 1430         CheckMenuItem(mMenu, ID_TRAY_SUSPEND, g_IsSuspended ? MF_CHECKED : MF_UNCHECKED);
 1431         CheckMenuItem(mMenu, ID_TRAY_PAUSE, g->IsPaused ? MF_CHECKED : MF_UNCHECKED);
 1432     }
 1433 
 1434     POINT pt;
 1435     if (aX == COORD_UNSPECIFIED || aY == COORD_UNSPECIFIED)
 1436         GetCursorPos(&pt);
 1437     if (!(aX == COORD_UNSPECIFIED && aY == COORD_UNSPECIFIED)) // At least one was specified.
 1438     {
 1439         // If one coordinate was omitted, pt contains the cursor position in SCREEN COORDINATES.
 1440         // So don't do something like this, which would incorrectly offset the cursor position
 1441         // by the window position if CoordMode != Screen:
 1442         //CoordToScreen(pt, COORD_MODE_MENU);
 1443         POINT origin = {0};
 1444         CoordToScreen(origin, COORD_MODE_MENU);
 1445         if (aX != COORD_UNSPECIFIED)
 1446             pt.x = aX + origin.x;
 1447         if (aY != COORD_UNSPECIFIED)
 1448             pt.y = aY + origin.y;
 1449     }
 1450 
 1451     // UPDATE: For v1.0.35.14, must ensure one of the script's windows is active before showing the menu
 1452     // because otherwise the menu cannot be dismissed via the escape key or by clicking outside the menu.
 1453     // Testing shows that ensuring any of our thread's windows is active allows both the tray menu and
 1454     // any popup or context menus to work correctly.
 1455     // UPDATE: For v1.0.35.12, the script's main window (g_hWnd) is activated only for the tray menu because:
 1456     // 1) Doing so for GUI context menus seems to prevent mouse clicks in the menu or elsewhere in the window.
 1457     // 2) It would probably have other side effects for other uses of popup menus.
 1458     HWND fore_win = GetForegroundWindow();
 1459     bool change_fore;
 1460     if (change_fore = (!fore_win || GetWindowThreadProcessId(fore_win, NULL) != g_MainThreadID))
 1461     {
 1462         // Always bring main window to foreground right before TrackPopupMenu(), even if window is hidden.
 1463         // UPDATE: This is a problem because SetForegroundWindowEx() will restore the window if it's hidden,
 1464         // but restoring also shows the window if it's hidden.  Could re-hide it... but the question here
 1465         // is can a minimized window be the foreground window?  If not, how to explain why
 1466         // SetForegroundWindow() always seems to work for the purpose of the tray menu?
 1467         //if (aForceToForeground)
 1468         //{
 1469         //  // Seems best to avoid using the script's current setting of #WinActivateForce.  Instead, always
 1470         //  // try the gentle approach first since it is unlikely that displaying a menu will cause the
 1471         //  // "flashing task bar button" problem?
 1472         //  bool original_setting = g_WinActivateForce;
 1473         //  g_WinActivateForce = false;
 1474         //  SetForegroundWindowEx(g_hWnd);
 1475         //  g_WinActivateForce = original_setting;
 1476         //}
 1477         //else
 1478         if (!SetForegroundWindow(g_hWnd))
 1479         {
 1480             // The below fixes the problem where the menu cannot be canceled by clicking outside of
 1481             // it (due to the main window not being active).  That usually happens the first time the
 1482             // menu is displayed after the script launches.  0 is not enough sleep time, but 10 is:
 1483             SLEEP_WITHOUT_INTERRUPTION(10);
 1484             SetForegroundWindow(g_hWnd);  // 2nd time always seems to work for this particular window.
 1485             // OLDER NOTES:
 1486             // Always bring main window to foreground right before TrackPopupMenu(), even if window is hidden.
 1487             // UPDATE: This is a problem because SetForegroundWindowEx() will restore the window if it's hidden,
 1488             // but restoring also shows the window if it's hidden.  Could re-hide it... but the question here
 1489             // is can a minimized window be the foreground window?  If not, how to explain why
 1490             // SetForegroundWindow() always seems to work for the purpose of displaying the tray menu?
 1491             //if (aForceToForeground)
 1492             //{
 1493             //  // Seems best to avoid using the script's current setting of #WinActivateForce.  Instead, always
 1494             //  // try the gentle approach first since it is unlikely that displaying a menu will cause the
 1495             //  // "flashing task bar button" problem?
 1496             //  bool original_setting = g_WinActivateForce;
 1497             //  g_WinActivateForce = false;
 1498             //  SetForegroundWindowEx(g_hWnd);
 1499             //  g_WinActivateForce = original_setting;
 1500             //}
 1501             //else
 1502             //...
 1503         }
 1504     }
 1505     // Apparently, the HWND parameter of TrackPopupMenuEx() can be g_hWnd even if one of the script's
 1506     // other (non-main) windows is foreground. The menu still seems to operate correctly.
 1507     g_MenuIsVisible = MENU_TYPE_POPUP; // It seems this is also set by WM_ENTERMENULOOP because apparently, TrackPopupMenuEx generates WM_ENTERMENULOOP. So it's done here just for added safety in case WM_ENTERMENULOOP isn't ALWAYS generated.
 1508     TrackPopupMenuEx(mMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON, pt.x, pt.y, g_hWnd, NULL);
 1509     g_MenuIsVisible = MENU_TYPE_NONE;
 1510     // MSDN recommends this to prevent menu from closing on 2nd click.  MSDN also says that it's only
 1511     // necessary to do this "for a notification icon". So to to avoid unnecessary launches of MsgSleep(),
 1512     // its done only for the tray menu in v1.0.35.12:
 1513     if (this == g_script.mTrayMenu)
 1514         PostMessage(g_hWnd, WM_NULL, 0, 0);
 1515     else // Seems best to avoid the following for the tray menu since it doesn't seem work and might produce side-effects in some cases.
 1516     {
 1517         if (change_fore && fore_win && GetForegroundWindow() == g_hWnd)
 1518         {
 1519             // The last of the conditions above is checked in case the user clicked the taskbar or some
 1520             // other window to dismiss the menu.  In that case, the following isn't done because it typically
 1521             // steals focus from the user's intended window, and this attempt usually fails due to the OS's
 1522             // anti-focus-stealing measure, which in turn would cause fore_win's taskbar button to flash annoyingly.
 1523             SetForegroundWindow(fore_win); // See comments above for why SetForegroundWindowEx() isn't used.
 1524             // The following resolves the issue where the window would not have enough time to become active
 1525             // before we continued using our timeslice to return to our caller and launch our new thread.
 1526             // In other words, the menu thread would launch before SetForegroundWindow() actually had a chance
 1527             // to take effect:
 1528             // 0 is exactly the amount of time (-1 is not enough because it doesn't yield) needed for that
 1529             // other process to actually ack/perform the activation of its window and clean out its queue using
 1530             // one timeslice.  This has been tested even when the CPU is maxed from some third-party process.
 1531             // For performance and code simplicity, it seems best not to do a GetForegroundWindow() loop that
 1532             // waits for it to become active (unless others report that this method is significantly unreliable):
 1533             SLEEP_WITHOUT_INTERRUPTION(0);
 1534         }
 1535     }
 1536     // Fix for v1.0.38.05: If the current thread is interruptible (which it should be since a menu was just
 1537     // displayed, which almost certainly timed out the default Thread Interrupt setting), the following
 1538     // MsgSleep() will launch the selected menu item's subroutine.  This fix is needed because of a change
 1539     // in v1.0.38.04, namely the line "g_script.mLastPeekTime = tick_now;" in IsCycleComplete().
 1540     // The root problem here is that it would not be intuitive to allow the command after
 1541     // "Menu, MyMenu, Show" should to run before the menu item's subroutine launches as a new thread.
 1542     // 
 1543     // You could argue that selecting a menu item should immediately Gosub the selected menu item's
 1544     // subroutine rather than queuing it up as a new thread.  However, even if that is a better method,
 1545     // it would break existing scripts that rely on new-thread behavior (such as fresh default for
 1546     // SetKeyDelay).
 1547     //
 1548     // Without this fix, a script such as the following (and many other things similar) would
 1549     // counterintuitively fail to launch the selected item's subroutine:
 1550     // Menu, MyMenu, Add, NOTEPAD
 1551     // Menu, MyMenu, Show
 1552     // ; Sleep 0  ; Uncommenting this line was necessary in v1.0.38.04 but not any other versions.
 1553     // ExitApp
 1554     MsgSleep(-1);
 1555     return OK;
 1556 }
 1557 
 1558 
 1559 
 1560 UINT UserMenu::GetItemPos(LPTSTR aMenuItemName)
 1561 // aMenuItemName will be searched for in this menu.
 1562 // Returns UINT_MAX if aMenuItemName can't be found.
 1563 {
 1564     int i = 0;
 1565     // It seems more proper to use the original menu item name as set by the Menu command
 1566     // rather than GetMenuString() as in v1.1.19 and earlier since our only caller always
 1567     // passes an item name which originally came from item->mName.  If the item names are
 1568     // out of sync (i.e. the user modified the item via the Win32 API), this method may
 1569     // be more reliable.  It should also be faster and smaller.
 1570     for (UserMenuItem *item = mFirstMenuItem; item; item = item->mNextMenuItem, ++i)
 1571         if (!lstrcmpi(item->mName, aMenuItemName))
 1572             return i;
 1573     return UINT_MAX;  // No match found.
 1574 }
 1575 
 1576 
 1577 
 1578 bool UserMenu::ContainsMenu(UserMenu *aMenu)
 1579 {
 1580     if (!aMenu)
 1581         return false;
 1582     // For each submenu in mMenu: Check if it or any of its submenus equals aMenu.
 1583     for (UserMenuItem *mi = mFirstMenuItem; mi; mi = mi->mNextMenuItem)
 1584         if (mi->mSubmenu)
 1585             if (mi->mSubmenu == aMenu || mi->mSubmenu->ContainsMenu(aMenu)) // recursive
 1586                 return true;
 1587             //else keep searching
 1588     return false;
 1589 }
 1590 
 1591 
 1592 
 1593 void UserMenu::UpdateAccelerators()
 1594 {
 1595     if (!mMenu)
 1596         // Menu doesn't exist yet, so can't be attached (directly or indirectly) to any GUIs.
 1597         return;
 1598 
 1599     if (mMenuType == MENU_TYPE_BAR)
 1600     {
 1601         for (int i = 0; i < g_guiCount; ++i)
 1602             if (GetMenu(g_gui[i]->mHwnd) == mMenu)
 1603             {
 1604                 g_gui[i]->UpdateAccelerators(*this);
 1605                 // Continue in case there are other GUIs using this menu.
 1606                 //break;
 1607             }
 1608     }
 1609     else
 1610     {
 1611         // This menu isn't a menu bar, but perhaps it is contained by one.
 1612         for (UserMenu *menu = g_script.mFirstMenu; menu; menu = menu->mNextMenu)
 1613             if (menu->mMenuType == MENU_TYPE_BAR && menu->ContainsMenu(this))
 1614             {
 1615                 menu->UpdateAccelerators();
 1616                 // Continue in case there are other menus which contain this submenu.
 1617                 //break;
 1618             }
 1619         return;
 1620     }
 1621 }
 1622 
 1623 
 1624 
 1625 //
 1626 // L17: Menu-item icon functions.
 1627 //
 1628 
 1629 
 1630 ResultType UserMenu::SetItemIcon(UserMenuItem *aMenuItem, LPTSTR aFilename, int aIconNumber, int aWidth)
 1631 {
 1632     if (!*aFilename || (*aFilename == '*' && !aFilename[1]))
 1633         return RemoveItemIcon(aMenuItem);
 1634 
 1635     if (aIconNumber == 0 && !g_os.IsWinVistaOrLater()) // The owner-draw method used on XP and older expects an icon.
 1636         aIconNumber = 1; // Must be != 0 to tell LoadPicture to return an icon, converting from bitmap if necessary.
 1637 
 1638     int image_type;
 1639     HICON new_icon;
 1640     // Currently height is always -1 and cannot be overridden. -1 means maintain aspect ratio, usually 1:1 for icons.
 1641     if ( !(new_icon = (HICON)LoadPicture(aFilename, aWidth, -1, image_type, aIconNumber, false)) )
 1642         return FAIL;
 1643 
 1644     HBITMAP new_copy;
 1645 
 1646     if (g_os.IsWinVistaOrLater())
 1647     {
 1648         if (image_type != IMAGE_BITMAP) // Convert to 32-bit bitmap:
 1649         {
 1650             new_copy = IconToBitmap32(new_icon, true);
 1651             // Even if conversion failed, we have no further use for the icon:
 1652             DestroyIcon(new_icon);
 1653             if (!new_copy)
 1654                 return FAIL;
 1655             new_icon = (HICON)new_copy;
 1656         }
 1657 
 1658         if (aMenuItem->mBitmap) // Delete previous bitmap.
 1659             DeleteObject(aMenuItem->mBitmap);
 1660     }
 1661     else
 1662     {
 1663         // LoadPicture already converted to icon if needed, due to aIconNumber > 0.
 1664         if (aMenuItem->mIcon) // Delete previous icon.
 1665             DestroyIcon(aMenuItem->mIcon);
 1666     }
 1667     // Also sets mBitmap via union:
 1668     aMenuItem->mIcon = new_icon;
 1669 
 1670     if (mMenu)
 1671         ApplyItemIcon(aMenuItem);
 1672 
 1673     return aMenuItem->mIcon ? OK : FAIL;
 1674 }
 1675 
 1676 
 1677 // Caller has ensured mMenu is non-NULL.
 1678 ResultType UserMenu::ApplyItemIcon(UserMenuItem *aMenuItem)
 1679 {
 1680     if (aMenuItem->mIcon) // Check mIcon/mBitmap union.
 1681     {
 1682         MENUITEMINFO item_info;
 1683         item_info.cbSize = sizeof(MENUITEMINFO);
 1684         item_info.fMask = MIIM_BITMAP;
 1685         // Set HBMMENU_CALLBACK or 32-bit bitmap as appropriate.
 1686         item_info.hbmpItem = g_os.IsWinVistaOrLater() ? aMenuItem->mBitmap : HBMMENU_CALLBACK;
 1687         SetMenuItemInfo(mMenu, aMenuItem_ID, aMenuItem_MF_BY, &item_info);
 1688     }
 1689     return OK;
 1690 }
 1691 
 1692 
 1693 ResultType UserMenu::RemoveItemIcon(UserMenuItem *aMenuItem)
 1694 {
 1695     if (aMenuItem->mIcon) // Check mIcon/mBitmap union.
 1696     {
 1697         if (mMenu)
 1698         {
 1699             MENUITEMINFO item_info;
 1700             item_info.cbSize = sizeof(MENUITEMINFO);
 1701             item_info.fMask = MIIM_BITMAP;
 1702             item_info.hbmpItem = NULL;
 1703             // If g_os.IsWinVistaOrLater(), this removes the bitmap we set. Otherwise it removes HBMMENU_CALLBACK, therefore disabling owner-drawing.
 1704             SetMenuItemInfo(mMenu, aMenuItem_ID, aMenuItem_MF_BY, &item_info);
 1705         }
 1706         if (g_os.IsWinVistaOrLater()) // Free the appropriate union member.
 1707             DeleteObject(aMenuItem->mBitmap);
 1708         else
 1709             DestroyIcon(aMenuItem->mIcon);
 1710         aMenuItem->mIcon = NULL; // Clear mIcon/mBitmap union.
 1711     }
 1712     return OK;
 1713 }
 1714 
 1715 
 1716 BOOL UserMenu::OwnerMeasureItem(LPMEASUREITEMSTRUCT aParam)
 1717 {
 1718     UserMenuItem *menu_item = g_script.FindMenuItemByID(aParam->itemID);
 1719     if (!menu_item) // L26: Check if the menu item is one with a submenu.
 1720         menu_item = g_script.FindMenuItemBySubmenu((HMENU)(UINT_PTR)aParam->itemID); // Extra cast avoids warning C4312.
 1721 
 1722     if (!menu_item || !menu_item->mIcon)
 1723         return FALSE;
 1724 
 1725     BOOL size_is_valid = FALSE;
 1726     ICONINFO icon_info;
 1727     if (GetIconInfo(menu_item->mIcon, &icon_info))
 1728     {
 1729         BITMAP icon_bitmap;
 1730         if (GetObject(icon_info.hbmColor, sizeof(BITMAP), &icon_bitmap))
 1731         {
 1732             // Return size of icon.
 1733             aParam->itemWidth = icon_bitmap.bmWidth;
 1734             aParam->itemHeight = icon_bitmap.bmHeight;
 1735             size_is_valid = TRUE;
 1736         }
 1737         DeleteObject(icon_info.hbmColor);
 1738         DeleteObject(icon_info.hbmMask);
 1739     }
 1740     return size_is_valid;
 1741 }
 1742 
 1743 
 1744 BOOL UserMenu::OwnerDrawItem(LPDRAWITEMSTRUCT aParam)
 1745 {
 1746     UserMenuItem *menu_item = g_script.FindMenuItemByID(aParam->itemID);
 1747     if (!menu_item) // L26: Check if the menu item is one with a submenu.
 1748         menu_item = g_script.FindMenuItemBySubmenu((HMENU)(UINT_PTR)aParam->itemID); // Extra cast avoids warning C4312.
 1749 
 1750     if (!menu_item || !menu_item->mIcon)
 1751         return FALSE;
 1752 
 1753     // Draw icon at actual size at requested position.
 1754     return DrawIconEx(aParam->hDC
 1755                 , aParam->rcItem.left, aParam->rcItem.top
 1756                 , menu_item->mIcon, 0, 0, 0, NULL, DI_NORMAL);
 1757 }