Skip to content

Instantly share code, notes, and snippets.

@cocobelgica
Created October 20, 2012 09:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cocobelgica/3922802 to your computer and use it in GitHub Desktop.
Save cocobelgica/3922802 to your computer and use it in GitHub Desktop.
Class: Menu (AutoHotkey_L)
/*;=======================================================================================
class: Menu
;=======================================================================================
*/
class Menu
{
static MenuList := [], MenuCount := 0, RetVal := ""
__New(MenuName) { ; Creates a new menu
this.Instance := true, this.Name := MenuName, this.ItemCount := 0, this.MenuItems := []
if (this.Name <> "Tray")
this.AddSeparator(), this.RemoveAll()
this.IsStandard := this.Name == "Tray" ? true : false
Menu.MenuCount++
}
__Delete() {
}
AppendItem(MenuItemName, LabelorFunction="", mode=true) {
if !this.IsInstance()
return
if (this.ItemCount >= 1)
for k, v in this.MenuItems
if (v["MenuItem"] == MenuItemName) {
MsgBox, Menu item "%MenuItemName%" already exists!
return
}
this.SetItemAction(MenuItemName, LabelorFunction, mode)
this.ItemCount := DllCall("GetMenuItemCount", "Int", this.GetMenuHandle(this.Name))
this.AppendToList(MenuItemName, LabelorFunction <> "" ? LabelorFunction : MenuItemName, mode)
}
AddSubMenu(MenuName, MenuItemName) {
if !this.IsInstance()
return
Menu, % this.Name, Add, % MenuItemName, % IsObject(MenuName) ? ":" MenuName.Name : ":" MenuName
this.ItemCount := DllCall("GetMenuItemCount", "Int", this.GetMenuHandle(this.Name))
this.AppendToList(MenuItemName, IsObject(MenuName) ? this.GetMenuHandle(MenuName.Name) : this.GetMenuHandle(MenuName))
}
AddSeparator() {
if !this.IsInstance()
return
Menu, % this.Name, Add
this.ItemCount := DllCall("GetMenuItemCount", "Int", this.GetMenuHandle(this.Name))
this.AppendToList("", "")
}
ModifyItem(MenuItemName, Param="", mode=true) {
if !this.IsInstance()
return
if (IsObject(Param) || InStr(Param, ":") == 1) { ; Converted to submenu
Menu, % this.Name, Add, % MenuItemName, % IsObject(Param) ? ":" Param.Name : Param
this.MenuItems[this.GetItemPos(MenuItemName), "LabelorFunction"] := IsObject(Param) ?this.GetMenuHandle(Param.Name) : this.GetMenuHandle(SubStr(Param, 2))
this.MenuItems[this.GetItemPos(MenuItemName), "Mode"] := true ; reset mode to "true" if item is converted to submenu
} else { ; New label or function
this.SetItemAction(MenuItemName, Param, mode)
this.MenuItems[this.GetItemPos(MenuItemName), "LabelorFunction"] := Param <> "" ? Param : MenuItemName
this.MenuItems[this.GetItemPos(MenuItemName), "Mode"] := mode
}
}
RenameItem(MenuItemName, NewName="") { ; if blank, MenuItemName will be converted to a separator. This action cannot be undone.
if !this.IsInstance()
return
Menu, % this.Name, Rename, % MenuItemName, % NewName
NewName <> "" ? this.MenuItems[this.GetItemPos(MenuItemName), "MenuItem"] := NewName : this.MenuItems[this.GetItemPos(MenuItemName), "MenuItem"] := "", this.MenuItems[this.GetItemPos(MenuItemName), "LabelorFunction"] := "", this.MenuItems[this.GetItemPos(MenuItemName), "Mode"] := true ; reset mode to "true" if item is converted to a separator
}
RemoveItem(MenuItemNameorPos, ByName=true, Occurrence=1) { ; If item name is blank(""), this method will remove a separator. By default it will remove the first. Specify "2" for the "Occurrence" parameter to remove the second, "3" for third, and so on... Set "ByName" parameter to "false" if you want to specify the menu item's position instead for the "MenuItemNameorPos" parameter.
if !this.IsInstance()
return
ByName := ByName == "" ? true : ByName ; set to "true"(default) if parameter is blank("")
if (ByName && MenuItemNameorPos <> "") ; Normal item
Menu, % this.Name, Delete, % MenuItemName
else if (ByName && MenuItemNameorPos == "") ; Separator
DllCall("RemoveMenu", Int, this.GetMenuHandle(this.Name), UInt, this.GetItemPos(MenuItemNameorPos, Occurrence)-1, UInt, "0x00000400L")
else { ; By position
if (this.MenuItems[MenuItemNameorPos, "MenuItem"] <> "") ; Normal Item
Menu, % this.Name, Delete, % this.MenuItems[MenuItemNameorPos, "MenuItem"]
else ; Separator
DllCall("RemoveMenu", Int, this.GetMenuHandle(this.Name), UInt, MenuItemNameorPos-1, UInt, "0x00000400L")
}
this.MenuItems.Remove(ByName ? this.GetItemPos(MenuItemNameorPos, MenuItemNameorPos == "" ? Occurrence : 1) : MenuItemNameorPos)
this.ItemCount := DllCall("GetMenuItemCount", "Int", this.GetMenuHandle(this.Name))
}
RemoveAll() {
if !this.IsInstance()
return
Menu, % this.Name, DeleteAll
this.IsStandard ? (this.Standard(false), this.MenuItems.Remove(1, this.MenuItems.MaxIndex()), this.Standard()) : this.MenuItems.Remove(1, this.MenuItems.MaxIndex()) ; if Menu contains the standard items, remove them temporarily, clear the list, and add them back again
this.ItemCount := DllCall("GetMenuItemCount", "Int", this.GetMenuHandle(this.Name))
}
SetItem(MenuItemName, Options="") { ; "Options" can be any of the following(space-delimited): Check, Uncheck, ToggleCheck, Enable, Disable, ToggleEnable
if !this.IsInstance()
return
if (Options <> "") { ; nothing is altered if omitted
Options := RegExReplace(Options, "S) +", A_Space) ; replace multiple spaces with a single space
Loop, Parse, Options, % A_Space
if A_LoopField in Check,Uncheck,ToggleCheck,Enable,Disable,ToggleEnable ; check if option is valid, if not, nothing is altered
Menu, % this.Name, % A_LoopField, % MenuItemName
}
}
Default(MenuItemName="") {
if !this.IsInstance()
return
Menu, % this.Name, Default, % MenuItemName
}
Standard(param=true) {
if !this.IsInstance()
return
this.ItemCount := DllCall("GetMenuItemCount", "Int", this.GetMenuHandle(this.Name)) ; get menu item count prior to adding the standard items
Menu, % this.Name, % param ? "Standard" : "NoStandard"
if param {
for k, v in ["Open", "Help", "", "Window Spy", "Reload This Script", "Edit This Script", "", "Suspend Hotkeys", "Pause Script", "Exit"]
this.ItemCount++, this.AppendToList(v, "`nStdItem")
} else {
a := "", b := 0 ; counters: a=index of the first standard item | b=index of the last standard item
for k, v in this.MenuItems
if (v["LabelorFunction"] == "`nStdItem")
a := b <= 0 ? k : a, b := b <= 0 ? k : b+1
this.MenuItems.Remove(a, b) ; remove the standard items from array
}
this.ItemCount := DllCall("GetMenuItemCount", "Int", this.GetMenuHandle(this.Name)) ; Get the new item count
this.IsStandard := param
}
SetIcon(MenuItemName, FileName, IconNumber="", IconWidth="") {
if !this.IsInstance()
return
Menu, % this.Name, Icon, % MenuItemName, % FileName, % IconNumber, % IconWidth
}
RemoveIcon(MenuItemName) {
if !this.IsInstance()
return
Menu, % this.Name, NoIcon, % MenuItemName
}
Destroy() {
if !this.IsInstance()
return
for k, v in Menu.MenuList
for a, b in v
if (b["LabelorFunction"] == this.GetMenuHandle(this.Name)) ; Remove item entries in list for other items that is using the currently destoyed menu as a submenu.
v.Remove(a)
Menu, % this.Name, Delete
Menu.MenuList.Remove(this.Name)
this.Instance := false, this.Name := "", this.ItemCount := "", this.MenuItems := ""
Menu.MenuCount--
}
Show(X="", Y="") {
if !this.IsInstance()
return
Menu, % this.Name, Show, % X, % Y
}
InsertItem(MenuItemName, Pos, Param="", mode=true) {
if !this.IsInstance()
return
this.AppendItem(MenuItemName, Param, mode)
count := this.IsStandard ? this.ItemCount - 9 : this.ItemCount
Loop, % count - Pos
this.MoveItem(MenuItemName)
}
InsertSubMenu(MenuName, MenuItemName, Pos) {
if !this.IsInstance()
return
this.AddSubMenu(MenuName, MenuItemName)
count := this.IsStandard ? this.ItemCount - 9 : this.ItemCount
Loop, % count - Pos
this.MoveItem(MenuItemName)
}
InsertSeparator(Pos) {
if !this.IsInstance()
return
this.AddSeparator()
xpos := this.ItemCount ; get the newly appended separator's position
count := this.IsStandard ? this.ItemCount - 9 : this.ItemCount
Loop, % count - Pos
xpos := this.MoveItem(xpos, true, false)
}
MoveItem(MenuItemNameorPos, up=true, ByName=true) {
if !this.IsInstance()
return
up := up == "" ? true : up ; set to "true"(default) if parameter is blank("")
items := [], oldh := [] ; Objects to temporarily hold menus/menu items properties after removal
pos := ByName ? this.GetItemPos(MenuItemNameorPos) : MenuItemNameorPos
xpos := up ? pos-1 : pos+1
if (up && pos <= 1 || !up && pos >= this.ItemCount) ; Unable to move item due to its postition and the associated direction
return
if (this.MenuItems[xpos, "LabelorFunction"] == "`nStdItem")
xpos := up ? xpos-9 : xpos+9
for k, v in this.MenuItems
items[k] := {name: v["MenuItem"], action: v["LabelorFunction"], mode: v["Mode"]}
items.Insert(xpos, items.Remove(pos))
for m in Menu.MenuList ; Retrieve handle of menus, menu(s) which are being used as submenu(s) gets a new handle if the item that opens it is removed
oldh[m] := this.GetMenuHandle(m) ; store handle(s) in object for later comparison
this.IsStandard ? (this.Standard(false), RemoveStd := true, this.RemoveAll()) : this.RemoveAll()
for a, b in items {
item := b["name"], action := b["action"], mode := b["mode"]
if (action <> "`nStdItem") {
if (item <> "") { ; Item is not a separator
if (!IsLabel(action) && !IsFunc(action)) { ; Item opens a submenu
for m in Menu.MenuList ; Retrieve new menu handle(s)
if (oldh[m] <> this.GetMenuHandle(m) && oldh[m] == action) ; compare handle changes and filter out the new handle
this.AddSubMenu(m, item)
} else if (IsLabel(action) || IsFunc(action)) ; Normal item
this.AppendItem(item, action, mode)
} else ; Item is a separator
this.AddSeparator()
} else if (action == "`nStdItem" && RemoveStd == true)
this.Standard(), RemoveStd := false
}
return xpos ; return the new position of the item
}
GetItemPos(MenuItemName, Occurrence=1) {
if !this.IsInstance()
return
i := 0
for k, v in this.MenuItems
if (MenuItemName == v["MenuItem"] && MenuItemName <> "")
return k
else if (MenuItemName == v["MenuItem"] && MenuItemName == "" && v["LabelorFunction"] <> "`nStdItem") {
i++
if (i == Occurrence)
return k
}
}
;~ Miscellaneous Commands
SetColor(ColorValue="Default", Single=false) {
if !this.IsInstance()
return
Menu, % this.Name, Color, % ColorValue, % Single ? "Single" : ""
}
UseErrorLevel(param=false) {
if !this.IsInstance()
return
Menu, % this.Name, UseErrorLevel, % param ? "" : "Off"
}
;~==========================================
;~ Tray-specific methods/functions - MenuName must be "Tray"
;~==========================================
TrayIcon(FileName="", IconNumber="", state=0) { ; Omit all parameters to create the tray icon if it isn't already present
if !this.IsInstance()
return
if (this.Name == "Tray") ; verify if the "Tray" is the Menu name
Menu, % this.Name, Icon, % FileName, % IconNumber, % (FileName == "" && IconNumber == "") ? "" : state
}
TrayNoIcon() {
if !this.IsInstance()
return
if (this.Name == "Tray")
Menu, % this.Name, NoIcon
}
TrayTip(Text="") {
if !this.IsInstance()
return
if (this.Name == "Tray")
Menu, % this.Name, Tip, % Text
}
TrayClick(ClickCount=2) {
if !this.IsInstance()
return
if (this.Name == "Tray")
Menu, % this.Name, Click, % ClickCount
}
TrayMainWindow(default=true) {
if !this.IsInstance()
return
if (this.Name == "Tray")
Menu, % this.Name, % default ? "NoMainWindow" : "MainWindow"
}
;~=======================================
; The following methods are for internal use only, Do not use.
;~=======================================
SetItemAction(MenuItemName, LabelorFunction="", mode=true) { ; Internal use only
if (LabelorFunction <> "") {
x := [LabelorFunction, "MenuItemFunctionHandlerLabel", mode ? LabelorFunction : "MenuItemFunctionHandlerLabel"]
Menu, % this.Name, Add, % MenuItemName, % x[this.GetActionType(LabelorFunction)]
} else {
x := ["", "MenuItemFunctionHandlerLabel", mode ? "" : "MenuItemFunctionHandlerLabel"]
Menu, % this.Name, Add, % MenuItemName, % x[this.GetActionType(MenuItemName)]
}
}
GetActionType(Action) { ; 1=Label, 2=Function, 3=Both | Internal use only
if (IsLabel(Action) && !IsFunc(Action))
return 1
else if (IsFunc(Action) && !IsLabel(Action))
return 2
else if (IsLabel(Action) && IsFunc(Action))
return 3
}
MenuItemFunctionHandler() { ; This method is for internal use only
return ; return when this method is called
MenuItemFunctionHandlerLabel: ; This label is for internal use only
if (Menu.MenuList[A_ThisMenu][A_ThisMenuItemPos, "MenuItem"] == A_ThisMenuItem)
ItemFunc := Func(Menu.MenuList[A_ThisMenu][A_ThisMenuItemPos, "LabelorFunction"]), Menu.RetVal := ItemFunc.()
return
}
AppendToList(MenuItemName, LabelorFunction, mode=true) { ; Internal use only
this.MenuItems[this.ItemCount] := {MenuItem: MenuItemName, LabelorFunction: LabelorFunction, Mode: mode}
Menu.MenuList[this.Name] := this.MenuItems
}
GetMenuHandle(MenuName) {
static h_menuDummy
If !h_menuDummy { ; v2.2: Check for !h_menuDummy instead of h_menuDummy="" in case init failed last time.
Menu, menuDummy, Add
Menu, menuDummy, DeleteAll
Gui, 99:Menu, menuDummy
Gui, 99:+LastFound ; v2.2: Use LastFound method instead of window title. [Thanks animeaime.]
h_menuDummy := DllCall("GetMenu", "uint", WinExist())
Gui, 99:Menu
Gui, 99:Destroy
if !h_menuDummy ; v2.2: Return only after cleaning up. [Thanks animeaime.]
return 0
}
Menu, menuDummy, Add, :%MenuName%
h_menu := DllCall( "GetSubMenu", "uint", h_menuDummy, "int", 0 )
DllCall( "RemoveMenu", "uint", h_menuDummy, "uint", 0, "uint", 0x400 )
Menu, menuDummy, Delete, :%MenuName%
return h_menu
}
GetMenuName(MenuHandle) {
for m in Menu.MenuList
if (this.GetMenuHandle(m) == MenuHandle)
return m
}
IsInstance() {
if (!this.Instance)
MsgBox, 16, % A_ScriptName, Menu or menu object does not exist! Please create a new instance using "__New".
return this.Instance
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment