451 lines
12 KiB
Plaintext
Executable File
451 lines
12 KiB
Plaintext
Executable File
-- 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
|
|
)
|