/*
* -----
* Screenshot Sender - __menu.js
* -----
* Menu class to create Windows menus.
* -----
*/
//Message constants for menu
var MF_CHECKED = 0x8;
var MF_UNCHECKED = 0x0;
var MF_ENABLED = 0x0;
var MF_DISABLED = 0x2;
var MF_GRAYED = 0x1;
var MF_APPEND = 0x100;
var MF_SEPARATOR = 0x800;
var MF_STRING = 0x0;
var MF_POPUP = 0x10;
var MF_BYPOSITION = 0x400;
var MF_BITMAP = 0x4;
var TPM_LEFTALIGN = 0x0;
var TPM_RIGHTALIGN = 0x8;
var TPM_RETURNCMD = 0x0100;
var TPM_VERNEGANIMATION = 0x2000;
var TPM_LAYOUTRTL = 0x8000;
var MIIM_STRING = 0x40;
var MIIM_SUBMENU = 0x4;
var MIM_APPLYTOSUBMENUS = 0x80000000;
var MIM_BACKGROUND = 0x2;
var MIM_STYLE = 0x10;
var MNS_CHECKORBMP = 0x4000000;
//Other menu globals
var MENU_ID_BASE = 0x1337;
var subclass;
/**
* MENU CLASS - by Mattias Buelens
* This class lets you create and open Windows menus. You can even add submenus and images to the menu.
* Requirements:
* The Gdi.js file of Countdown Live 2 for the bitmap functions. Note that this file contains Trace() function calls. You can
* either replace these calls by Debug.Trace() calls and remove the last parameter, or you can simply remove these lines or copy
* the Trace() function into your script. I really don't care. :-P
* Usage:
* 1. Create a new instance with "new Menu() " with the fCallback parameter, which should be a function to receive the menu clicks.
* 2. Now, you can:
* - Add menu items with "Menu.AddMenuItem() ".
* - Add submenus:
* 1. Create a new submenu with "Menu.CreateSubMenu() ".
* 2. Add stuff in this submenu, for example by using "SubMenu.AddMenuItem() ". You can even add sub-submenus!
* 3. Add the submenu to its parent with "Menu.AddSubMenu() ".
* - Add menu separators with "Menu.AddSeparator() ".
* 3. You can make the menu pop up anywhere you want by calling "Menu.Open() ".
* 4. The callback function will receive up to 2 parameters:
* - A string specifying the ID of the menu item clicked. This is the ID given to an item in "Menu.AddMenuItem() ".
* - An extra parameter passed through "Menu.Open() ".
* It can be anything you want and may be useful if you use the same menu on various places.
* 5. When the menu isn't needed anymore, call "Menu.Destroy() " to destroy it along with any bitmaps or submenus that were created.
* You can use and modify this class in any way you like, as long as you keep the appropriate credits in the script.
* - The GDI and GDI+ functions for the bitmaps were originally written by Matty(Screenshot Sender developer) and further
* modified by myself.
* - The class was written by me. You don't need to place this whole header, just a small note will do just fine. :-)
* Other than that, feel free to do whatever you want with this. Hopefully this class can be useful to you one day and will let you
* create beautiful menus for your scripts. :-)
*
* - Mattias Buelens.
*/
var Menu = function (fCallback, oMenu) {
this.Owner = (typeof oMenu == "object") ? oMenu : false;
this.Callback = (typeof fCallback == "function") ? fCallback : function () { };
this.Index = (this.Owner != false) ? this.Owner.Index : 0;
this.ItemIDs = [], this.Bitmaps = {}, this.SubMenus = [];
this.Handle = Interop.Call("user32", "CreatePopupMenu");
this.SetMenuInfo();
}
Menu.prototype = {
/**
* Adds a menu item to the menu.
* sId Unique string identifier for this menu item, used to identify it when clicked.
* sLabel The label for the new menu item. Ignored when nState is MF_SEPARATOR. Optional.
* nState The state of the menu item. Optional.
* Possible values: MF_CHECKED, MF_UNCHECKED, MF_ENABLED, MF_DISABLED, MF_GRAYED, MF_SEPARATOR
* sBitmapPath The path to an image file to add to the menu item. Optional.
* bIsDefault Boolean defining whether this is the default menu item. Optional.
* Return value: True indicates success, otherwise false.
*/
"AddMenuItem" : function (sId, sLabel, nState, sBitmapPath, bIsDefault) {
sId = (typeof sId != "undefined") ? sId : 0
sLabel = (typeof sLabel == "string") ? sLabel : "";
nState = (typeof nState == "number") ? nState : 0;
sBitmapPath = (typeof sBitmapPath == "string") ? sBitmapPath : "";
bIsDefault = (bIsDefault == true);
var nIndex = (this.Owner == false) ? this.Index : this.Owner.Index - this.Index;
//When this is a separator, add it and stop.
if (nState & MF_SEPARATOR) {
var bResult = Interop.Call("user32", "AppendMenuW", this.Handle, MF_SEPARATOR, MENU_ID_BASE + this.Index, 0);
if (bResult == 0) return false;
this.IndexInc(sId);
return true;
}
//Add the menu item as a string.
Interop.Call("user32", "AppendMenuW", this.Handle, MF_STRING, MENU_ID_BASE + this.Index, sLabel);
if (bResult == 0) return false;
//Sets the menu item to default when asked.
if (bIsDefault) Interop.Call("user32", "SetMenuDefaultItem", this.Handle, nIndex, true);
//Add a bitmap if one is specified.
if (sBitmapPath.length > 0 && FileExists(sBitmapPath) ) {
var bResult = this.SetBitmap(sBitmapPath, nIndex);
}
//Sets the item states
if (typeof nState == "number" && nState > 0) {
if (nState & MF_CHECKED) Interop.Call("user32", "CheckMenuItem", this.Handle, nIndex, MF_BYPOSITION | MF_CHECKED);
if (nState & MF_DISABLED) Interop.Call("user32", "EnableMenuItem", this.Handle, nIndex, MF_BYPOSITION | MF_DISABLED);
if (nState & MF_GRAYED) Interop.Call("user32", "EnableMenuItem", this.Handle, nIndex, MF_BYPOSITION | MF_GRAYED);
}
this.IndexInc(sId);
return true;
},
/**
* Adds a separator to the menu.
* Return value: True indicates success, otherwise false.
*/
"AddSeparator" : function () {
return this.AddMenuItem("", "", MF_SEPARATOR, "", false);
},
/**
* Creates a new submenu.
* Return value: The new submenu.
*/
"CreateSubMenu" : function () {
var _this = this;
var SubMenu = new Menu(0, _this);
return SubMenu;
},
/**
* Adds a submenu to the menu.
* SubMenu The menu object to add as submenu.
* sLabel The label for the submenu. Optional.
* nState The state of the submenu. See AddMenuItem. Optional.
* sBitmapPath The path to an image file to add to the submenu. Optional.
* Return value: True indicates success, otherwise false.
*/
"AddSubMenu" : function (SubMenu, sLabel, nState, sBitmapPath) {
sLabel = (typeof sLabel == "string") ? sLabel : "";
nState = (typeof nState == "number") ? nState : 0;
sBitmapPath = (typeof sBitmapPath == "string") ? sBitmapPath : "";
//Add the submenu
var bResult = Interop.Call("user32", "AppendMenuW", this.Handle, MF_POPUP | MF_STRING, SubMenu.Handle, sLabel);
if (bResult == 0) return false;
//Set the owner of the submenu
var _this = this;
SubMenu.Owner = _this;
//Add the submenu to the list of submenus
this.SubMenus.push(SubMenu);
//Add a bitmap if one is specified.
if (sBitmapPath.length > 0 && FileExists(sBitmapPath) ) {
var bResult = this.SetBitmap(sBitmapPath, this.Index);
}
//Sets the item states
if (typeof nState == "number" && nState > 0) {
if (nState & MF_CHECKED) Interop.Call("user32", "CheckMenuItem", this.Handle, this.Index, MF_BYPOSITION | MF_CHECKED);
if (nState & MF_DISABLED) Interop.Call("user32", "EnableMenuItem", this.Handle, this.Index, MF_BYPOSITION | MF_DISABLED);
if (nState & MF_GRAYED) Interop.Call("user32", "EnableMenuItem", this.Handle, this.Index, MF_BYPOSITION | MF_GRAYED);
}
this.IndexInc("");
return true;
},
/**
* Sets the state of the specified menu item's check-mark attribute.
* Id Menu item position(number) or menu item ID(string) to change.
* bCheck Boolean defining the new check state.
* Return value: None(void)
*/
"CheckMenuItem" : function (Id, bCheck) {
bCheck = (typeof bCheck == "undefined") ? true : (bCheck != false);
var nId = 0;
if (typeof Id == "string") {
//Look up Id in ItemIDs
var i = this.ItemIDs.length;
while (i--) {
if (Id == this.ItemIDs[i]) {
nId = i;
break;
}
}
} else nId = 1*Id;
Interop.Call("user32", "CheckMenuItem", this.Handle, nId, MF_BYPOSITION | (bCheck ? MF_CHECKED : 0) );
},
/**
* Opens the menu.
* oPt Array of the form[x,y]where x and y are the coordinates to open the menu. Optional.
* oParam Parameter to pass to the callback function with the menu item ID. Optional.
* bRTL Boolean specifying if the menu should be positioned for an RTL window. Optional.
* nColor COLORREF(0x00bbggrr) defining the background color of the menu. Optional.
* Return value: True indicates success, otherwise false.
*/
"Open" : function (oPt, oParam, bRTL, nColor) {
oParam = (typeof oParam != "undefined") ? oParam : undefined;
if (!this.IsMenuValid() ) return false;
//Set the position
var POINTAPI = Interop.Allocate(8);
if (typeof oPt == "object") {
//Set given position
POINTAPI.WriteDWORD(0, oPt[0]);
POINTAPI.WriteDWORD(4, oPt[1]);
} else {
//Set current cursor position
Interop.Call("user32", "GetCursorPos", POINTAPI);
}
//Get the subclass window
if (!subclass) subclass = MsgPlus.CreateWnd('Languages\\en\\Subclass.xml', 'Subclass', /*WNDOPT_INVISIBLE*/ 2);
//Set the background color of the menu
if (typeof nColor == "number") {
var hBrush = Interop.Call("gdi32", "CreateSolidBrush", nColor);
var MENUINFO = Interop.Allocate(28);
MENUINFO.WriteDWORD(0, Size); //cbSize
MENUINFO.WriteDWORD(4, MIM_APPLYTOSUBMENUS | MIM_BACKGROUND); //fMask
MENUINFO.WriteDWORD(16, hBrush); //hBrush
var Result = Interop.Call("user32", "SetMenuInfo", this.Handle, MENUINFO);
MENUINFO.Size = 0;
Interop.Call("gdi32", "DeleteObject", hBrush);
}
//Open and retrieve clicked item
var Flags = TPM_RETURNCMD | TPM_VERNEGANIMATION;
if (typeof bRTL != "undefined") Flags |= bRTL ? TPM_RIGHTALIGN | TPM_LAYOUTRTL : TPM_LEFTALIGN;
var Result = Interop.Call("user32", "TrackPopupMenu", this.Handle, Flags, POINTAPI.ReadDWORD(0), POINTAPI.ReadDWORD(4), 0, subclass.Handle, 0);
POINTAPI.Size = 0;
this.Callback( (Result == 0) ? -1 : this.ItemIDs[(Result - MENU_ID_BASE) ], oParam);
return true;
},
/**
* Destroys the menu.
* Return value: True indicates success, otherwise false.
*/
"Destroy" : function () {
//Destroy submenus first.
if (this.SubMenus.length > 0) {
var i = this.SubMenus.length;
while (i--) this.SubMenus[i].Destroy();
}
//Check if this menu can be destroyed.
if (!this.IsMenuValid()) return false;
//Destroy the menu.
Interop.Call("user32", "DestroyMenu", this.Handle);
this.Handle = 0;
//Destroys all bitmaps associated with menu items.
if (typeof this.Bitmaps === "object") {
var i = this.Bitmaps.length;
while (i--) DestroyBitmap(this.Bitmaps[i]);
}
return true;
},
/**
* Increases the menu's index and the index of its owner.
*/
"IndexInc" : function (sItemID) {
var nIndex = (this.Owner == false) ? this.Index : this.Owner.Index - this.Index;
this.ItemIDs[nIndex] = sItemID;
this.Index++;
if (this.Owner != false) this.Owner.IndexInc(sItemID);
},
/**
* Creates and sets the bitmap to a menu item. Used internally.
* sBitmapPath The path to an image file to add to the item.
* uItem The numeric identifier of the item.
* Return value: True indicates success, otherwise false.
*/
"SetBitmap" : function (sBitmapPath, uItem) {
//Create the bitmap if it doesn't exist yet
var hBitmap = 0;
if (typeof this.Bitmaps[sBitmapPath] === "number") {
hBitmap = this.Bitmaps[sBitmapPath];
} else {
hBitmap = GdiCreateHBITMAPFromFile(sBitmapPath);
if (hBitmap == false) {
DestroyBitmap(hBitmap);
this.Bitmaps[sBitmapPath] = false;
return false;
} else {
this.Bitmaps[sBitmapPath] = hBitmap;
}
}
//Set the bitmap to the menu item
var MIIM_BITMAP = 0x80;
var MENUITEMINFO = Interop.Allocate(48);
MENUITEMINFO.WriteDWORD(0, Size);
MENUITEMINFO.WriteDWORD(4, MIIM_BITMAP);
MENUITEMINFO.WriteDWORD(44, hBitmap);
var Result = Interop.Call("user32", "SetMenuItemInfoW", this.Handle, uItem, true, MENUITEMINFO);
if (Result == 0) Debug.Trace("!!! Error occured while setting bitmap for item "+uItem+".");
MENUITEMINFO.Size = 0;
return(Result != 0);
},
/**
* Sets the necessary properties of the menu. Used internally.
* Return value: True indicates success, otherwise false.
*/
"SetMenuInfo" : function () {
var MENUINFO = Interop.Allocate(28);
MENUINFO.WriteDWORD(0, MENUINFO.Size);
var Result = Interop.Call("user32", "GetMenuInfo", this.Handle, MENUINFO);
if (Result == 0) {
MENUINFO.Size = 0;
return false;
}
with (MENUINFO) {
WriteDWORD(4, MIM_STYLE);
WriteDWORD(8, ReadDWORD(8) | MNS_CHECKORBMP);
}
var Result = Interop.Call("user32", "SetMenuInfo", this.Handle, MENUINFO);
MENUINFO.Size = 0;
return(Result == 0);
},
/**
* Checks if the current menu handle is valid. Used internally.
*/
"IsMenuValid" : function () {
return(typeof this.Handle == "number" && Interop.Call("user32", "IsMenu", this.Handle) != 0);
}
}