-- Rockstar Util Menu -- Rockstar North -- 1/3/2005 -- by Greg Smith -- by Luke Openshaw -- utility functions for creating menus global RSmacroNameList = undefined global RSmacroCatList = undefined -- Struct used to define menu-item for generating TypeNgo data: struct RsMenuItem ( Name, Macro, Category, Tags, TopMenuName, -- Get hash for struct's data, used to avoid showing multiple copies in TypeNGo: fn GetHash = ( local Vals = (for Item in #(Name, Macro, Category, Tags) collect (Item as string)) GetHashValue Vals 1 ) ) -- Descriptions of menu-items are added to this list by 'RsAddMenuItems' -- This list is used to generate a command-list for the TypeNgo tool global RsMenuItems = #() global RsMenuItems_Hashes = #() -- Sets list of defined macro action names and categories, to let us search for action-categories by name: fn RSgetMacroList nameList:#() catList:#() = ( local ss = stringstream "" macros.list to:ss local macroList = for item in (filterString (ss as string) "\n") collect (filterString item "\"") ::RSmacroNameList = for item in macroList collect (toLower item[2]) ::RSmacroCatList = for item in macroList collect item[4] macroList ) -- Returns category-name for named macro: fn RSgetMacroCategory macroName = ( if (RSmacroNameList == undefined) do ( RSgetMacroList() ) local findNum = findItem RSmacroNameList (toLower macroName) if (findNum == 0) then "" else RSmacroCatList[findNum] ) -- Runs a named macro: (no need for category-name) fn RsRunMacro macroName = ( local macroList = RsGetMacroList() local macroIdx = findItem RSmacroNameList (toLower macroName) if (macroIdx == 0) then (return undefined) else ( local macroLine = macroList[macroIdx] local macroNum = (trimRight macroLine[1]) as integer if (macroNum != undefined) do ( macros.run macroNum ) ) ) -- Remove menu from main menu fn RsEmptyMenu menu firstRun:false = ( for i = menu.numItems() to 1 by -1 do ( local subMenu if not firstRun do ( try (subMenu = (menu.getItem i).getSubMenu()) catch ( format "\nR* MENU-SETUP SYSTEM-ERROR CAUGHT\nI recommend restarting Max\n" undefined ) ) menu.removeItemByPosition i -- If menuitem is a submenu, unregister it: if (subMenu != undefined) do ( local subMenuName = subMenu.getTitle() menuman.unRegisterMenu subMenu -- Clear away any duplicates that weren't unregistered previously: local checkForDupes = true while checkForDupes do ( checkForDupes = false menuExists = (menuMan.findMenu subMenuName) if (menuExists != undefined) do ( format "Removing duplicate menu: %\n" subMenuName menuMan.unRegisterMenu menuExists checkForDupes = true ) ) ) ) ) fn RsAddMenuItems menu items reversed:false TopMenuName:undefined StoreInfo:True = ( -- The quad-menu is upside-down, this allows items to be added in a more sensible order: local addPos = if reversed then 1 else -1 for item in items do ( local MenuI -- If a menu-item has been passed as a RsMenuItem struct (with extra tags, etc) local InfoItem = undefined if (isKindOf Item RsMenuItem) do ( InfoItem = Item Item = InfoItem.Macro ) case of ( -- Add separators: (item == ""): ( menui = MenuMan.CreateSeparatorItem() ) -- Add menu-items, and build TypeNgo data: (isKindOf Item String): ( if (InfoItem == undefined) do ( InfoItem = RsMenuItem Macro:Item ) if (InfoItem.Category == undefined) do ( InfoItem.Category = (RSgetMacroCategory item) ) -- Create menu-item for macro: MenuI = MenuMan.CreateActionItem InfoItem.Macro InfoItem.Category -- Collect info-struct if macro exists, and 'StoreInfo' is True: if (StoreInfo) and (MenuI != undefined) do ( -- Make sure all info-structs are tagged with the top-level menu-name: InfoItem.TopMenuName = TopMenuName -- Get macro's title: InfoItem.Name = MenuI.GetTitle() -- Collect info-struct, for use with TypeNgo tool: append RsMenuItems InfoItem ) ) -- Recursively build menus from DataPairs: (isKindOf Item DataPair): ( ::RsSetMenu Item.MenuName Item.Items menuParent:menu reversed:reversed TopMenuName:TopMenuName IsTopLevel:False ) ) -- Add menuitem, if valid: if (menui != undefined) do ( Menu.AddItem menui addPos ) ) ) -- Add items to quadmenu: fn RsAddQuadMenuSubItems quadmenu submenuname items = ( retMenu = menuman.createmenu submenuname retMenuItem = menuman.createsubmenuitem submenuname retMenu RsAddMenuItems retMenu items reversed:true quadmenu.additem retMenuItem 1 return retMenu ) ------------------------------------------------------------------------------------ -- RsClearMenu: -- Recursively unregisters a given menu-hierarchy ------------------------------------------------------------------------------------ fn RsClearMenu ThisMenu = ( local ItemCount = ThisMenu.NumItems() if (ItemCount != 0) do ( -- Unregister submenus from the bottom up, depth first: for n = ItemCount to 1 by -1 do ( local SubItem = ThisMenu.GetItem n local SubMenu = SubItem.GetSubMenu() if (SubMenu != undefined) do ( RsClearMenu SubMenu MenuMan.UnregisterMenu SubMenu ) ) -- Make sure the menu contains no items: for n = 1 to (ThisMenu.NumItems()) do ( ThisMenu.RemoveItemByPosition 1 ) ) ) ------------------------------------------------------------------------------------ -- Tries to find a root menu with the specified name. -- If menu isnt found, it creates it ------------------------------------------------------------------------------------ fn RsGetNewMenu menuName parentMenu Reversed:False IsTopLevel:False = ( -- Convert non-menu 'parentMenu' values to appropriate menu-value: parentMenu = case (classOf parentMenu) of ( MixinInterface:(parentMenu) String:(MenuMan.FindMenu parentMenu) Default:(MenuMan.GetMainMenuBar()) ) -- Find and unregister existing top-level menu(s) with same name: if IsTopLevel and (isKindOf ParentMenu MixinInterface) do ( for n = (ParentMenu.NumItems()) to 1 by -1 do ( local SubMenu = (ParentMenu.GetItem n).GetSubMenu() if (SubMenu != undefined) and (matchPattern (SubMenu.GetTitle()) pattern:menuName) do ( RsClearMenu SubMenu MenuMan.UnregisterMenu SubMenu ) ) ) -- Create new menu: local retMenu = MenuMan.CreateMenu menuName local retMenuItem = MenuMan.CreateSubMenuItem menuName retMenu -- We add items in reverse for upside-down quadmenus: local addPos = if reversed then 1 else -1 parentMenu.additem retMenuItem addPos return retMenu ) ------------------------------------------------------------------------------------ -- Add the passed in items to the menu. -- The 'items' list will either be a list of macro names, -- or a datapair defining submenus. -- 'Reversed' flag adds items in reverse order, used to add to top-quadrant quad-menus -- 'StoreInfo' flag says whether items will be added to TypeNgo data-list ------------------------------------------------------------------------------------ fn RsSetMenu menuAddTo items menuParent: Reversed:False TopMenuName:undefined StoreInfo:True IsTopLevel:True = ( -- Add new menu or replace old one with same name: if (isKindOf menuAddTo String) do ( -- Replace string 'menuAddTo' with menu generated/found from string: menuAddTo = RsGetNewMenu (menuAddTo) MenuParent Reversed:Reversed IsTopLevel:IsTopLevel ) if (IsTopLevel) do ( TopMenuName = MenuAddTo.GetTitle() -- Clear command-search data from earlier menu-generations: local ValidNums = for n = 1 to RsMenuItems.Count where (RsMenuItems[n].TopMenuName != TopMenuName) collect n RsMenuItems = for n = ValidNums collect RsMenuItems[n] RsMenuItems_Hashes = for n = ValidNums collect RsMenuItems_Hashes[n] ) -- Add menuitems: RsAddMenuItems menuAddTo items reversed:reversed TopMenuName:TopMenuName StoreInfo:StoreInfo return menuAddTo ) ------------------------------------------------------------------------------------ -- unregisters menus with no content ------------------------------------------------------------------------------------ mapped fn RsCleanMenu menu = ( local hasContent = false if (isKindOf menu String) do ( menu = menuman.findmenu menu ) local menuItems = for n = 1 to (menu.numItems()) collect (menu.getItem n) for item in menuItems do ( local subMenu = item.getSubMenu() if (subMenu == undefined) then ( if not item.getIsSeparator() do ( -- Flag this menu as having content if it contains a non-menu non-separator item hasContent = true ) ) else ( -- Flag this menu as having content if it contains a sub-menu with content local subMenuHasContent = RsCleanMenu subMenu if subMenuHasContent do (hasContent = true) ) ) if not hasContent do ( -- Don't bother mentioning missing menus to outsourcers: if (gRsIsOutSource != true) do (format "[Removed Empty Menu: %]\n" menu.getTitle()) -- Remove menus that have no content menuman.unRegisterMenu menu ) hasContent ) ------------------------------------------------------------------------------------ -- unregisters menus that are not used by the main menu or quad-menus, -- and have the same name as another menu. -- Unused non-duplicate menu-names won't be removed. -- WARNING: -- this function triggers unavoidable system-exceptions (inside the Try) -- which cause the Macro Recorder to stop working for some reason ------------------------------------------------------------------------------------ fn RsRemoveUnusedDupeMenus = ( -- Add Root menubar to menu-list: local usedMenus = #(menuMan.getMainMenuBar()) -- Add menus from all quads to menu-list: for quadNum = 1 to menuMan.numQuadMenus() do ( local quad = menuMan.getQuadMenu quadNum for n = 1 to 4 do ( local getMenu = quad.getMenu n if (isKindOf getMenu MixinInterface) do ( appendIfUnique usedMenus (quad.getMenu n) ) ) ) -- Expand menu-list: local menuNum = 0 local failCount = 0 local usedMenuNames = #() local menu, getItem, getMenu while (menuNum < usedMenus.count) do ( menuNum += 1 menu = usedMenus[menuNum] append usedMenuNames (menu.getTitle()) for n = 1 to menu.numItems() do ( getItem = (menu.getItem n) getMenu = try (getItem.getSubMenu()) catch -1 -- Some inbuilt context-menus will fail for getSubMenu case getMenu of ( (-1):(failCount += 1) undefined:() default:(appendIfUnique usedMenus getMenu) ) ) ) -- Get list of menus that aren't on the usedMenus list: local unusedMenus = for n = 1 to menuMan.numMenus() collect ( menu = menuMan.getMenu n if ((findItem usedMenus menu) == 0) then menu else dontCollect ) local unusedMenuNames = for menu in unusedMenus collect (menu.getTitle()) local removeMenus = #{} removeMenus.count = unusedMenus.count -- Mark any unused items that are named more than once - we'll keep the last one mentioned on the list, and remove the duplicates local uniqueUnusedNames = #() local menuName for menuNum = unusedMenus.count to 1 by -1 do ( menuName = unusedMenuNames[menuNum] if ((findItem uniqueUnusedNames menuName) == 0) then ( append uniqueUnusedNames menuName ) else ( removeMenus[menuNum] = true ) ) -- Mark any remaining menus that have matching names in the used-menus list: for menuNum = -removeMenus do ( if ((findItem usedMenuNames unusedMenuNames[menuNum]) != 0) do ( removeMenus[menuNum] = true ) ) --format "% menus, % fails, % unused, %(%) total, % to remove\n" usedMenus.count failCount unusedMenus.count (menuMan.numMenus()) (usedMenus.count + unusedMenus.count) removeMenus.numberSet -- Unregister the menus that have been marked for deletion: if (removeMenus.numberSet != 0) do ( format "Removing % unused duplicate menus\n" removeMenus.numberSet for menuNum = removeMenus do ( --print (unusedMenus[menuNum].getTitle()) menuman.unRegisterMenu unusedMenus[menuNum] ) ) ok )