├── .gitignore ├── stahky.ini ├── res ├── app.ico ├── app48.png ├── screenshots │ ├── s1.png │ ├── s2.png │ └── s3.png └── docs │ └── PUM_documentation.pdf ├── LICENSE ├── README.md ├── stahky.ahk └── lib ├── utils.ahk ├── PUM_API.ahk └── PUM.ahk /.gitignore: -------------------------------------------------------------------------------- 1 | ~* 2 | *.exe 3 | *.lnk -------------------------------------------------------------------------------- /stahky.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joedf/stahky/HEAD/stahky.ini -------------------------------------------------------------------------------- /res/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joedf/stahky/HEAD/res/app.ico -------------------------------------------------------------------------------- /res/app48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joedf/stahky/HEAD/res/app48.png -------------------------------------------------------------------------------- /res/screenshots/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joedf/stahky/HEAD/res/screenshots/s1.png -------------------------------------------------------------------------------- /res/screenshots/s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joedf/stahky/HEAD/res/screenshots/s2.png -------------------------------------------------------------------------------- /res/screenshots/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joedf/stahky/HEAD/res/screenshots/s3.png -------------------------------------------------------------------------------- /res/docs/PUM_documentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joedf/stahky/HEAD/res/docs/PUM_documentation.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joe DF 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![*](res/app48.png) stahky 2 | A take on [stacky](https://github.com/pawelt/stacky) in AutoHotkey (AHK) for Windows 10 & 11. 3 | 4 | Lots of customization options, see below! 5 | 6 | Get the latest version [here](https://github.com/joedf/stahky/releases). 7 | 8 | ## Usage 9 | - Drag a folder with shortcuts, programs and files onto Stahky 10 | - A taskbar pinnable icon / shortcut will be created in the same folder as Stahky itself. 11 | - You should edit the shortcut's icon before pinning it. 12 | - Once pinned, you can delete the shortcut file. 13 | - Note: you can set files as hidden if you don't want them to appear in the Stahky menu. 14 | - Note: you can drag a folder along with a stahky ini config file to use separate settings for each shortcut. 15 | - Appearance, DPI, offsets, etc. settings 16 | - Colors are determined automatically on the first run 17 | - Edit stahky.ini for specific colors, offsets (x or y), use DPI for size and position calculation, ... 18 | - The `SortFoldersFirst` option allows you to have folders appear first at the top of each menu accordingly. 19 | - Use `PUM_flags` to use [PUM.ahk's flag options](res/docs/PUM_documentation.pdf), such as `hleft` for horizontally left-aligned, `hcenter` for horizontally centered, or `noanim` for no fade-in animation. You can use multiple flags in the ini config file like so `PUM_flags=hleft noanim` 20 | - Use `ShowAtMousePosition=1` in the ini config file to have the menu show up at the current mouse postion regardless where the taskbar is. However if this mode is used, `PUM_flags` values are ignored. 21 | - The font can customized with the following ini config file options: 22 | - `fontName` (default is `Segoe UI`) 23 | - `fontSize` (default is `9`) 24 | - `fontWeight` (default is `400`) 25 | - `fontItalic` (default is `0` for false) 26 | - `fontStrike` (default is `0` for false) 27 | - `fontUnderline` (default is `0` for false) 28 | - Submenus 29 | - You can use a normal folder and customize it's icon (in properties) 30 | - Use a shortcut to a folder, if you want to be able to open it instead of a having submenu 31 | - You can have stahky shortcuts within your folder for custom submenus. Stahky-licious! 32 | - Stahkys that have circular references will be capped by the recursion depth setting `STAHKY_MAX_DEPTH` (default is 5). 33 | - Opening a folder in a Stahky menu 34 | - Use `ShowOpenCurrentFolder=1` in the ini config file to display a "Open this folder..." option on all folders. 35 | - Press WheelButton / MButton on 36 | - a stahky to open its target folder for easy editing. 37 | - any item to open its parent folder. 38 | - Set `exitAfterFolderOpen=0` in the ini config file to keep Stahky on-screen after opening a folder 39 | - Intended to run as a compiled binary (best experience), but the script can be executed directly. 40 | - There is `/config` argument that can be used to specify a ini config file to use at runtime, e.g. `Stahky.exe /stahky "C:/my/folder" /config "C:/my/stahky/config/file.ini"` 41 | - Press Shift+Win+a or Right-click (RButton) any item in a stacky to show the About/First-time-use dialog to easily check the version and other information. 42 | - If Stahky takes too long to load, it will warn you of including folders that are too large. 43 | - this time limit `STAHKY_MAX_RUN_TIME` can be changed but has a minimum of 1000 ms and maximum of 10 s of wait time (default is 3500 ms). 44 | 45 | ## Screenshots 46 | ![screenshot1](res/screenshots/s1.png) 47 | ![about_dialog](res/screenshots/s3.png) 48 | ![screenshot2](res/screenshots/s2.png) 49 | -------------------------------------------------------------------------------- /stahky.ahk: -------------------------------------------------------------------------------- 1 | ; stahky 2 | ; by joedf - started 2020.07.10 3 | ; 4 | ; inspired from Stacky (by Pawel Turlejski) 5 | ; https://github.com/pawelt/stacky 6 | ; https://web.archive.org/web/20130927190146/http://justafewlines.com/2013/04/stacky/ 7 | 8 | 9 | ; https://www.autohotkey.com/docs/misc/Performance.htm 10 | #NoEnv 11 | SetBatchLines -1 12 | ListLines Off 13 | 14 | #NoTrayIcon 15 | #SingleInstance, Force 16 | 17 | ; Ensure we used the libs from the correct folder if the working dir is different 18 | #Include %A_ScriptDir% 19 | 20 | #Include lib\utils.ahk 21 | 22 | ; uses PUM by Deo 23 | #Include lib\PUM_API.ahk 24 | #Include lib\PUM.ahk 25 | 26 | APP_NAME := "stahky" 27 | APP_VERSION := "0.3.9.1" 28 | APP_REVISION := "2025/03/19" 29 | 30 | ;@Ahk2Exe-SetName stahky 31 | ;@Ahk2Exe-SetVersion 0.3.9.1 32 | ;@Ahk2Exe-SetDescription A take on stacky in AutoHotkey (AHK) for Windows 10 33 | ;@Ahk2Exe-SetCopyright (c) 2025 joedf.github.io 34 | ;@Ahk2Exe-SetCompanyName joedf.github.io 35 | ;@Ahk2Exe-SetMainIcon res\app.ico 36 | 37 | ; Trick to use mpress and throw no error if not available 38 | ;@Ahk2Exe-PostExec cmd /c mpress.exe "%A_WorkFileName%" &rem, 0 39 | 40 | 41 | STAHKY_EXT := APP_NAME . ".lnk" 42 | G_STAHKY_ARG := "/stahky" 43 | G_STAHKY_ARG_CFG := "/config" 44 | StahkyConfigFile := A_ScriptDir "\" APP_NAME ".ini" 45 | 46 | ; AutoHotkey behavioural settings needed 47 | GroupAdd APP_Self_WinGroup, ahk_id %A_ScriptHwnd% 48 | GroupAdd APP_Self_WinGroup, % "ahk_pid " DllCall("GetCurrentProcessId") 49 | CoordMode, Mouse, Screen 50 | CoordMode, Pixel, Screen 51 | MouseGetPos, mouseX, mouseY 52 | 53 | ; ================ [ CREATE a shortcut Stahky ] ================ 54 | 55 | ; Smart auto-create *lnk pinnable shortcut file, when folder dragged-on-top of this app 56 | if ( A_Args[1] != G_STAHKY_ARG && FileExist(A_Args[1]) ) 57 | { 58 | ; if config is unspecified use default 59 | _runPath := "" 60 | _configFile := "" 61 | 62 | ; parse args to see if folder and optinally and ini file was passed 63 | for _n, param in A_Args 64 | { 65 | ; path must exist whether it is a file or folder 66 | if FileExist(param) 67 | { 68 | ; check if we were given a Directory / Folder, create a new stahky if so 69 | FileGetAttrib,_t, % param 70 | if InStr(_t,"D") 71 | { 72 | _runPath := param 73 | } 74 | else { 75 | ; otherwise, we likely have a file... 76 | ; Check if we have a settings / config file specified 77 | if isSettingsFile(param) 78 | { 79 | _configFile := param 80 | } else { 81 | MsgBox, 48, %APP_NAME% - Error: Invalid config file, Error: Could not create stahky shortcut with invalid config file: "%param%" 82 | } 83 | } 84 | } 85 | } 86 | 87 | ; check if we got valid options, create the stahky file if so 88 | if StrLen(_runPath) > 0 { 89 | ; create the stahky shortcut file 90 | makeStahkyFile(_runPath, _configFile) 91 | ; we're done here! don't execute the rest of the program ... arrrgg >_< 92 | ExitApp 93 | } 94 | } 95 | ; otherwise, if we are not in "create mode", proceed as normal... 96 | 97 | ; ======================= [ RUN Stahky ] ======================= 98 | 99 | ; check for first run, if we want to show the intro dialog 100 | G_FirstRun_Trigger := false 101 | if !FileExist(StahkyConfigFile) 102 | FirstRun_Trigger() 103 | 104 | ; get search path 105 | searchPath := A_WorkingDir . "\*" 106 | 107 | ; Parse each parameter to see: 108 | ; 1) If a folder or search path is provided 109 | ; 2) If a custom stahky config/settings ini file is provided 110 | for _n, param in A_Args 111 | { 112 | ; check if we have a switch '/' param 113 | if (SubStr(param, 1, 1) == "/") { 114 | ; and check if followed by a value 115 | if (A_Args.Length() > _n) { 116 | value := A_Args[_n+1] 117 | 118 | ; parse param for search path 119 | if InStr(param, G_STAHKY_ARG) 120 | { 121 | if FileExist(value) { 122 | ; use the Stahky shortcut file's path if available 123 | FileGetAttrib,_t, % value 124 | if InStr(_t,"D") { 125 | searchPath := value . "\*" 126 | } else { 127 | ; warn user and exit if it's not a folder .... wut -,- 128 | MsgBox, 48, %APP_NAME% - Error: Invalid stahky config, Error: Could not launch stahky as the following target folder was not found:`n"%value%" 129 | ExitApp 130 | } 131 | } 132 | } 133 | ; parse param for config file 134 | else if InStr(param, G_STAHKY_ARG_CFG) 135 | { 136 | _cfgPath := NormalizePath(value) 137 | if isSettingsFile(_cfgPath) 138 | { 139 | StahkyConfigFile := _cfgPath 140 | } else { 141 | ; if the config file is invalid, we simply continue execution and 142 | ; ignore the given config. We use the default config file if possible. 143 | } 144 | } 145 | } else { 146 | MsgBox, 48, %APP_NAME% - Error: Invalid stahky parameter, Error: Could not launch stahky with no value for parameter "%param%". 147 | ExitApp 148 | } 149 | } 150 | } 151 | 152 | ; get/update settings, colors, position offsets, ... 153 | loadSettings(StahkyConfigFile) 154 | saveSettings(StahkyConfigFile) 155 | 156 | ; update value for High DPI display 157 | DPIScaleRatio := 1 158 | if (useDPIScaleRatio) { 159 | DPIScaleRatio := (A_ScreenDPI / 96) 160 | icoSize *= DPIScaleRatio 161 | menuTextMargin *= DPIScaleRatio 162 | menuMarginX *= DPIScaleRatio 163 | menuMarginY *= DPIScaleRatio 164 | } 165 | 166 | ; font options for the PUM menu item 167 | fontOptions := {name: fontName 168 | ,height: fontSize 169 | ,Weight: fontWeight 170 | ,Italic: fontItalic 171 | ,strike: fontStrike 172 | ,Underline: fontUnderline} 173 | 174 | ; parameters of the PUM object, the manager of the menus 175 | pumParams := {"SelMethod" : "fill" ;item selection method, may be frame,fill 176 | ,"selTColor" : stextColor ;selection text color 177 | ,"selBGColor" : sbgColor ;selection background color, -1 means invert current color 178 | ,"oninit" : "PUM_out" ;function which will be called when any menu going to be opened 179 | ,"onuninit" : "PUM_out" ;function which will be called when any menu going to be closing 180 | ,"onselect" : "PUM_out" ;function which will be called when any item selected with mouse (hovered) 181 | ,"onrbutton" : "PUM_out" ;function which will be called when any item right clicked 182 | ,"onmbutton" : "PUM_out" ;function which will be called when any item clicked with middle mouse button 183 | ,"onrun" : "PUM_out" ;function which will be called when any item clicked with left mouse button 184 | ,"onshow" : "PUM_out" ;function which will be called before any menu shown using Show method 185 | ,"onclose" : "Pum_out" ;function called just before quitting from Show method 186 | ,"pumfont" : fontOptions ;font options, LOGFONT: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logfonta 187 | ,mnemonicCMD : "select"} 188 | 189 | ; PUM_Menu parameters 190 | menuParams := {"bgcolor" : bgColor ;background color of the menu 191 | , "iconssize" : icoSize ;size of icons in the menu 192 | , "tcolor" : textColor ;text color of the menu items 193 | , "textMargin": menuTextMargin 194 | , "xmargin" : menuMarginX 195 | , "ymargin" : menuMarginY } 196 | 197 | ; create an instance of PUM object, it is best to have only one of such in the program 198 | pm := new PUM( pumParams ) 199 | ; creating popup menu, represented by PUM_Menu object with given parameters 200 | menu := pm.CreateMenu( menuParams ) 201 | 202 | ; Log start time to prevent runs from taking too long with no visual feedback 203 | STAHKY_START_TIME := A_TickCount 204 | 205 | ; Populate Stahkys! 206 | MakeStahkyMenu(menu, searchPath, pm, menuParams ) 207 | 208 | ; Calculate the coordinates to show the menu at 209 | if (ShowAtMousePosition) { 210 | menuPos := {x: mouseX, y: mouseY} 211 | PUM_flags := "" ; ignore flags if this mode is used 212 | } else { 213 | ; Calculate optimal postion for the menu to be, 214 | ; whether near the Taskbar or as a context menu elsewhere 215 | menuPos := getOptimalMenuPos(mouseX, mouseY) 216 | } 217 | 218 | ; Display the PUM menu 219 | item := menu.Show( menuPos.x+offsetX, menuPos.y+offsetY, PUM_flags ) 220 | 221 | ; Destroy everything PUM on program end 222 | pm.Destroy() 223 | 224 | ; First Run triggered - don't auto-exit 225 | if (!G_FirstRun_Trigger) 226 | ExitApp 227 | return 228 | 229 | 230 | ; PUM's right-click / rbutton handler is not reliable 231 | ; do some extra handling here 232 | ; https://autohotkey.com/board/topic/94970-ifwinactive-reference-running-autohotkey-window/#entry598885 233 | ; 234 | ; No need for #If since the app only runs if we have a menu or a window shown 235 | ;#IfWinExist ahk_group APP_Self_WinGroup 236 | +#a:: 237 | ~$*RButton:: 238 | FirstRun_Trigger() 239 | return 240 | ;#IfWinExist 241 | 242 | 243 | ; handle attached PUM events and actions 244 | PUM_out( msg, obj ) { 245 | 246 | ; run item normally 247 | if (msg == "onrun") 248 | { 249 | rPath := obj.path 250 | 251 | ; try a normal run/launch 252 | Run, %rPath%,,UseErrorLevel 253 | 254 | ; if it fails, assume a shortcut and try again 255 | if (ErrorLevel) { 256 | try { 257 | FileGetShortcut,%rPath%,outTarget,outWrkDir,outArgs 258 | Run, "%outTarget%" %outArgs%, %outWrkDir%, UseErrorLevel 259 | 260 | ; Try again if it failed, possibly ProgramFiles x86 vs x64: https://github.com/joedf/stahky/issues/2 261 | if (ErrorLevel) 262 | { 263 | EnvGet, pf64, ProgramW6432 264 | _outTarget64 := StrReplace(outTarget, A_ProgramFiles, pf64, , 1) 265 | Run, "%_outTarget64%" %outArgs%, %outWrkDir% 266 | } 267 | } 268 | catch ; run failed, alert user 269 | { 270 | MsgBox, 48,, Error: Could not launch the following (please verify it exists):`n%outTarget% 271 | } 272 | } 273 | } 274 | 275 | ; On MButton, open the folder if we have a stahky 276 | if (msg == "onmbutton") { 277 | 278 | ; open the stahky's folder 279 | if (_p:=isStahkyFile(obj.path)) { 280 | if FileExist(_p) 281 | Run % _p 282 | } 283 | else ; open the current menu's or submenu's parent folder 284 | { 285 | SplitPath, % obj.path,,_p 286 | Run, % _p 287 | } 288 | 289 | global exitAfterFolderOpen 290 | if (exitAfterFolderOpen) 291 | ExitApp 292 | } 293 | 294 | ; On RButton, open the about/firsttime use dialog 295 | if (msg == "onrbutton") { 296 | FirstRun_Trigger() 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /lib/utils.ahk: -------------------------------------------------------------------------------- 1 | 2 | MakeStahkyMenu( pMenu, searchPath, iPUM, pMenuParams, recursion_CurrentDepth := 0 ) 3 | { 4 | global APP_NAME 5 | global STAHKY_MAX_DEPTH 6 | global STAHKY_START_TIME 7 | global STAHKY_MAX_RUN_TIME 8 | 9 | global ShowOpenCurrentFolder 10 | global SortFoldersFirst 11 | 12 | if (ShowOpenCurrentFolder) 13 | { 14 | ; If we have a folder and the show current folder option is enabled, 15 | ; Show an "open: ..." folder option first. For more info see: 16 | ; https://github.com/joedf/stahky/issues/20 17 | if (SubStr(searchPath, 1-2) == "\*") 18 | { 19 | currentDirItem := { "name": "Open this folder..." 20 | ,"path": SubStr(searchPath, 1, 0-2) 21 | ,"icon": "shell32.dll:4" } 22 | pMenu.Add(currentDirItem) 23 | pMenu.Add() ; add separator 24 | } 25 | } 26 | 27 | if (SortFoldersFirst) 28 | { 29 | ; do folders first 30 | Loop, %searchPath%, 2 31 | { 32 | MakeStahkyMenu_subroutine( pMenu, A_LoopFileFullPath, iPUM, pMenuParams, recursion_CurrentDepth ) 33 | } 34 | 35 | ; do files second 36 | Loop, %searchPath%, 0 37 | { 38 | MakeStahkyMenu_subroutine( pMenu, A_LoopFileFullPath, iPUM, pMenuParams, recursion_CurrentDepth ) 39 | } 40 | } 41 | else 42 | { 43 | ; do normal order, alphabetically/natural order 44 | Loop, %searchPath%, 1 45 | { 46 | MakeStahkyMenu_subroutine( pMenu, A_LoopFileFullPath, iPUM, pMenuParams, recursion_CurrentDepth ) 47 | } 48 | } 49 | 50 | return pMenu 51 | } 52 | 53 | MakeStahkyMenu_subroutine( pMenu, fPath, iPUM, pMenuParams, recursion_CurrentDepth := 0 ) 54 | { 55 | global APP_NAME 56 | global STAHKY_MAX_DEPTH 57 | global STAHKY_START_TIME 58 | global STAHKY_MAX_RUN_TIME 59 | 60 | ; assume we get the full path in fPath 61 | 62 | ; check if the menu creation is taking too long, avoid very large folders! 63 | runtime:=(A_TickCount - STAHKY_START_TIME) 64 | if (runtime > 2000) ; bug? doesn't work unless we have tooltip? 65 | ToolTip %APP_NAME% is loading... %runtime% 66 | if (runtime > STAHKY_MAX_RUN_TIME) { 67 | ToolTip 68 | 69 | MsgBox, 4112, , 70 | (Ltrim Join`s 71 | Stahky has been running for too long! Please ensure to not include any folders that are too large. 72 | Consider including a shortcut to large folders in your stahky folder instead. 73 | `n`nLatest file:`n%fPath%`n`nExecution time: %runtime% ms`nThe program will now terminate. 74 | ) 75 | ExitApp 76 | } 77 | 78 | FileGetAttrib, fileAttrib, % fPath 79 | if InStr(fileAttrib, "H") ; Skip any file that is H (Hidden) 80 | return pMenu ; Skip this file and move on to the next one. 81 | 82 | SplitPath,fPath,,,fExt,fNameNoExt 83 | 84 | ; support filenames like .gitignore, LICENSE 85 | if (!fNameNoExt) 86 | fNameNoExt := "." . fExt 87 | 88 | ; support filenames with `&`, so they don't become ALT+letter shortcut and hide the `&` character 89 | fNameNoExt := StrReplace(fNameNoExt,"&","&&") 90 | 91 | ; automagically get a nice icon accordingly, if possible 92 | OutIconChoice := getItemIcon(fPath) 93 | 94 | ; setup the menu item's metadata 95 | mItem := { "name": fNameNoExt 96 | ,"path": fPath 97 | ,"icon": OutIconChoice } 98 | 99 | ; handle any submenus 100 | if fExt in lnk 101 | { 102 | ; display stahkys as submenus 103 | if (OutTarget := isStahkyFile(fPath)) { 104 | 105 | ; couldnt get from the stahky file config, so assume the target folder using the lnk's args 106 | if !FileExist(OutTarget) { 107 | FileGetShortcut,%fPath%,,,OutArgs 108 | OutTarget := Trim(OutArgs,""""`t) 109 | } 110 | 111 | ; create and attach the stahky submenu, with a cap on recursion depth 112 | if (recursion_CurrentDepth < STAHKY_MAX_DEPTH) 113 | { 114 | ; recurse into sub-stahky-liciousnous 115 | ; Not using "%A_ThisFunc%", to support optional sorting from "MakeStahkyMenu" instead of "MakeStahkyMenu_subroutine" 116 | MakeStahkyMenu( mItem["submenu"] := iPUM.CreateMenu( pMenuParams ) 117 | ,OutTarget . "\*" 118 | ,iPUM 119 | ,pMenuParams 120 | ,recursion_CurrentDepth+1 ) 121 | } else { 122 | maxStahkyWarningMenu := (mItem["submenu"] := iPUM.CreateMenu( pMenuParams )) 123 | maxStahkyWarningMenu.Add({ "name": "Overwhelmingly Stahky-licious! (Max = " . STAHKY_MAX_DEPTH . ")" 124 | ,"disabled": true 125 | ,"icon": "shell32.dll:77" }) 126 | } 127 | } 128 | } 129 | else if (InStr(fileAttrib,"D")) ; display on-shortcut folders as submenus 130 | { 131 | ; recurse into folders 132 | ; Not using "%A_ThisFunc%", to support optional sorting from "MakeStahkyMenu" instead of "MakeStahkyMenu_subroutine" 133 | MakeStahkyMenu( mItem["submenu"] := iPUM.CreateMenu( pMenuParams ) 134 | ,fPath . "\*" 135 | ,iPUM 136 | ,pMenuParams 137 | ,recursion_CurrentDepth ) 138 | } 139 | 140 | ; push the menu item to the parent menu 141 | pMenu.add( mItem ) 142 | 143 | return pMenu 144 | } 145 | 146 | makeStahkyFile(iPath, configFile:="") { 147 | global APP_NAME 148 | global STAHKY_EXT 149 | global G_STAHKY_ARG 150 | global G_STAHKY_ARG_CFG 151 | 152 | ; assume we have a folder and get it's name 153 | SplitPath,iPath,outFolderName 154 | ; create the shortcut in the same folder as Stahky itself 155 | LinkFile := A_ScriptDir . "\" . outFolderName . "." . STAHKY_EXT 156 | 157 | ; check for optional config file param 158 | cfgParam := "" 159 | if (StrLen(configFile) > 0 and isSettingsFile(configFile)) { 160 | cfgFullPath := NormalizePath(configFile) 161 | ; basically: /config "my/config/file/path/here.ini" 162 | cfgParam := G_STAHKY_ARG_CFG . " " . """" . cfgFullPath . """" 163 | } 164 | 165 | ; Compiled vs script (using installed AHK) version shortcuts are different 166 | if (A_IsCompiled) { 167 | FileCreateShortcut, %A_ScriptFullPath%, %LinkFile%, %A_ScriptDir%, %G_STAHKY_ARG% "%iPath%" %cfgParam% 168 | } else { 169 | FileCreateShortcut, %A_AhkPath%, %LinkFile%, %A_ScriptDir%,"%A_ScriptFullPath%" %G_STAHKY_ARG% "%iPath%" %cfgParam% 170 | } 171 | 172 | MsgBox, 64, New Stahky created, A pinnable shortcut was created here: `n%LinkFile% 173 | } 174 | 175 | isStahkyFile(fPath) { 176 | global APP_NAME 177 | global G_STAHKY_ARG 178 | 179 | SplitPath,fPath,,,_ext 180 | if _ext in lnk 181 | { 182 | FileGetShortcut,%fPath%,,,outArgs 183 | args := StrSplit(outArgs,A_Space) 184 | for n, arg in args 185 | { 186 | if (arg == G_STAHKY_ARG) { 187 | ;MsgBox, 48, , STAHKY-LICIOUS! 188 | /* 189 | Running as script example: 190 | "C:\Program Files\AutoHotkey\v1.1.37.02\AutoHotkeyU64.exe" 191 | "C:\Users\joedf\code\stahky\stahky.ahk" 192 | /stahky "C:\Users\joedf\code\stahky\~MY-LI~1" 193 | /config "C:\Users\joedf\code\stahky\~stahky3.ini" 194 | 195 | Running as compiled example: 196 | C:\Users\joedf\code\stahky\stahky.exe 197 | /stahky "C:\Users\joedf\code\stahky\~my-links" 198 | /config "C:\Users\joedf\code\stahky\stahky.ini" 199 | */ 200 | ; okay to assume the path following the argument is wrapped in quotes 201 | ; since we wrap all argument paths in quotes now... 202 | s1 := InStr(outArgs, " " . G_STAHKY_ARG . " """) + StrLen(G_STAHKY_ARG) + 3 203 | s2 := InStr(outArgs, """", false, s1) 204 | path := Trim(SubStr(outArgs,s1, s2-s1),"""") 205 | if FileExist(path) 206 | return path 207 | return true 208 | } 209 | } 210 | } 211 | return false 212 | } 213 | 214 | isSettingsFile(fPath) { 215 | global APP_NAME 216 | if FileExist(fPath) 217 | { 218 | SplitPath, fPath , , , fileExtension 219 | ; check if we got an existing INI file 220 | if InStr(fileExtension, "ini") 221 | { 222 | IniRead, outSection, %fPath%, %APP_NAME% 223 | if StrLen(outSection) > 2 224 | return True 225 | } 226 | } 227 | return False 228 | } 229 | 230 | loadSettings(SCFile) { 231 | global 232 | ; get taskbar colors 233 | TaskbarColor := getTaskbarColor() 234 | 235 | ; calc default colors 236 | TaskbarSColor := lightenColor(TaskbarColor) 237 | TaskbarTColor := contrastBW(TaskbarSColor) 238 | 239 | ; load vals 240 | IniRead, offsetX, %SCFile%,%APP_NAME%,offsetX,0 241 | IniRead, offsetY, %SCFile%,%APP_NAME%,offsetY,0 242 | IniRead, icoSize, %SCFile%,%APP_NAME%,iconSize,24 243 | IniRead, STAHKY_MAX_RUN_TIME, %SCFile%,%APP_NAME%,STAHKY_MAX_RUN_TIME,3500 244 | STAHKY_MAX_RUN_TIME := Max(1000,Min(STAHKY_MAX_RUN_TIME,10000)) ; minimum of 1s, maximum of 10s wait/run time 245 | IniRead, STAHKY_MAX_DEPTH, %SCFile%,%APP_NAME%,STAHKY_MAX_DEPTH,5 246 | IniRead, SortFoldersFirst, %SCFile%,%APP_NAME%,SortFoldersFirst,0 247 | IniRead, useDPIScaleRatio, %SCFile%,%APP_NAME%,useDPIScaleRatio,1 248 | IniRead, exitAfterFolderOpen, %SCFile%,%APP_NAME%,exitAfterFolderOpen,1 249 | IniRead, ShowOpenCurrentFolder, %SCFile%,%APP_NAME%,ShowOpenCurrentFolder,0 250 | IniRead, ShowAtMousePosition, %SCFile%,%APP_NAME%,ShowAtMousePosition,0 251 | IniRead, menuTextMargin, %SCFile%,%APP_NAME%,menuTextMargin,85 252 | IniRead, menuMarginX, %SCFile%,%APP_NAME%,menuMarginX,4 253 | IniRead, menuMarginY, %SCFile%,%APP_NAME%,menuMarginY,4 254 | IniRead, bgColor, %SCFile%,%APP_NAME%,menuBGColor, % TaskbarColor ;0x101010 255 | IniRead, sbgColor, %SCFile%,%APP_NAME%,menuSelectedBGColor, % TaskbarSColor ;0x272727 256 | IniRead, stextColor, %SCFile%,%APP_NAME%,menuSelectedTextColor, % TaskbarTColor ; B/W based on a luma/contrast formula 257 | IniRead, textColor, %SCFile%,%APP_NAME%,menuTextColor, % TaskbarTColor 258 | IniRead, PUM_flags, %SCFile%,%APP_NAME%,PUM_flags,hleft 259 | ; font options 260 | IniRead, fontName, %SCFile%,%APP_NAME%,fontName,Segoe UI 261 | IniRead, fontSize, %SCFile%,%APP_NAME%,fontSize,9 262 | IniRead, fontWeight, %SCFile%,%APP_NAME%,fontWeight,400 263 | IniRead, fontItalic, %SCFile%,%APP_NAME%,fontItalic,0 264 | IniRead, fontStrike, %SCFile%,%APP_NAME%,fontStrike,0 265 | IniRead, fontUnderline, %SCFile%,%APP_NAME%,fontUnderline,0 266 | } 267 | 268 | saveSettings(SCFile) { 269 | global 270 | ; save vals 271 | IniWrite, % offsetX, %SCFile%,%APP_NAME%,offsetX 272 | IniWrite, % offsetY, %SCFile%,%APP_NAME%,offsetY 273 | IniWrite, % icoSize, %SCFile%,%APP_NAME%,iconSize 274 | IniWrite, % STAHKY_MAX_RUN_TIME, %SCFile%,%APP_NAME%,STAHKY_MAX_RUN_TIME 275 | IniWrite, % STAHKY_MAX_DEPTH, %SCFile%,%APP_NAME%,STAHKY_MAX_DEPTH 276 | IniWrite, % SortFoldersFirst, %SCFile%,%APP_NAME%,SortFoldersFirst 277 | IniWrite, % useDPIScaleRatio, %SCFile%,%APP_NAME%,useDPIScaleRatio 278 | IniWrite, % ShowOpenCurrentFolder, %SCFile%,%APP_NAME%,ShowOpenCurrentFolder 279 | IniWrite, % ShowAtMousePosition, %SCFile%,%APP_NAME%,ShowAtMousePosition 280 | IniWrite, % exitAfterFolderOpen, %SCFile%,%APP_NAME%,exitAfterFolderOpen 281 | IniWrite, % menuTextMargin, %SCFile%,%APP_NAME%,menuTextMargin 282 | IniWrite, % menuMarginX, %SCFile%,%APP_NAME%,menuMarginX 283 | IniWrite, % menuMarginY, %SCFile%,%APP_NAME%,menuMarginY 284 | IniWrite, % bgColor, %SCFile%,%APP_NAME%,menuBGColor 285 | IniWrite, % sbgColor, %SCFile%,%APP_NAME%,menuSelectedBGColor 286 | IniWrite, % stextColor, %SCFile%,%APP_NAME%,menuSelectedTextColor 287 | IniWrite, % textColor, %SCFile%,%APP_NAME%,menuTextColor 288 | IniWrite, % PUM_flags, %SCFile%,%APP_NAME%,PUM_flags 289 | ; font options 290 | IniWrite, % fontName, %SCFile%,%APP_NAME%,fontName 291 | IniWrite, % fontSize, %SCFile%,%APP_NAME%,fontSize 292 | IniWrite, % fontWeight, %SCFile%,%APP_NAME%,fontWeight 293 | IniWrite, % fontItalic, %SCFile%,%APP_NAME%,fontItalic 294 | IniWrite, % fontStrike, %SCFile%,%APP_NAME%,fontStrike 295 | IniWrite, % fontUnderline, %SCFile%,%APP_NAME%,fontUnderline 296 | } 297 | 298 | lightenColor(cHex, L:=2.64) { 299 | R := (L * (10+(cHex>>16 & 0xFF))) & 0xFF 300 | G := (L * (10+(cHex>>8 & 0xFF))) & 0xFF 301 | B := (L * (10+(cHex & 0xFF))) & 0xFF 302 | return Format("0x{:X}", (R<<16 | G<<8 | B<<0) ) 303 | } 304 | 305 | contrastBW(c) { ; based on https://gamedev.stackexchange.com/a/38561/48591 306 | R := 0.2126 * (c>>16 & 0xFF) / 0xFF 307 | G := 0.7152 * (c>>8 & 0xFF) / 0xFF 308 | B := 0.0722 * (c & 0xFF) / 0xFF 309 | luma := R+G+B 310 | return (luma > 0.35) ? 0x000000 : 0xFFFFFF 311 | } 312 | 313 | getTaskbarColor() { 314 | ; get task pos/size info 315 | WinGetPos tx, ty, tw, th, ahk_class Shell_TrayWnd 316 | 317 | ; calc pixel position 318 | tPix_x := tx + tw - 2 319 | tPix_y := ty + th - 2 320 | 321 | ; pick the color and return 322 | PixelGetColor, TaskbarColor, % tPix_x, % tPix_y, RGB 323 | return TaskbarColor 324 | } 325 | 326 | GetMonitorMouseIsIn() { 327 | ; code from Maestr0 328 | ; https://www.autohotkey.com/boards/viewtopic.php?p=235163#p235163 329 | 330 | ; get the mouse coordinates first 331 | Coordmode, Mouse, Screen ; use Screen, so we can compare the coords with the sysget information` 332 | MouseGetPos, Mx, My 333 | 334 | SysGet, MonitorCount, 80 ; monitorcount, so we know how many monitors there are, and the number of loops we need to do 335 | Loop, %MonitorCount% 336 | { 337 | SysGet, mon%A_Index%, Monitor, %A_Index% ; "Monitor" will get the total desktop space of the monitor, including taskbars 338 | 339 | if ( Mx >= mon%A_Index%left ) && ( Mx < mon%A_Index%right ) && ( My >= mon%A_Index%top ) && ( My < mon%A_Index%bottom ) 340 | { 341 | ActiveMon := A_Index 342 | break 343 | } 344 | } 345 | return ActiveMon 346 | } 347 | 348 | getOptimalMenuPos(mx, my) { 349 | ; based off of stacky's code, but is multi-monitor aware 350 | ; https://github.com/joedf/stahky/issues/21#issuecomment-2722264863 351 | hMonitor := GetMonitorMouseIsIn() 352 | SysGet, rWorkArea, MonitorWorkArea, %hMonitor% 353 | 354 | pos_x := mx, pos_y := my 355 | if (pos_x < rWorkAreaLeft) { 356 | pos_x := rWorkAreaLeft - 1 357 | } else if (pos_x > rWorkAreaRight) { 358 | pos_x := rWorkAreaRight - 1 359 | } 360 | 361 | if (pos_y < rWorkAreaTop) { 362 | pos_y := rWorkAreaTop - 1 363 | } else if (pos_y > rWorkAreaBottom) { 364 | pos_y := rWorkAreaBottom - 1 365 | } 366 | 367 | ; from testing, these flags don't seem to be needed 368 | ; SysGet, menuDropAlign, % (SM_MENUDROPALIGNMENT := 40) 369 | ; flags := menuDropAlign | (TPM_LEFTBUTTON := 0) 370 | 371 | return { x: pos_x, y: pos_y, flags: flags } 372 | } 373 | 374 | getItemIcon(fPath) { 375 | SplitPath,fPath,,,fExt 376 | FileGetAttrib,fAttr,%fPath% 377 | 378 | OutIconChoice := "" 379 | 380 | ; support executable binaries 381 | if fExt in exe,dll 382 | OutIconChoice := fPath . ":0" 383 | 384 | ; support windows shortcut/link files *.lnk 385 | if fExt in lnk 386 | { 387 | FileGetShortcut, %fPath%, OutTarget,,,, OutIcon, OutIconNum 388 | SplitPath,OutTarget,,,OutTargetExt 389 | if OutTargetExt in exe,dll 390 | OutIconChoice := OutTarget . ":0" 391 | if (OutIcon && OutIconNum) 392 | OutIconChoice := OutIcon . ":" . (OutIconNum-1) 393 | else { 394 | ; Support shortcuts to folders with no custom icon set (default) 395 | FileGetAttrib,_attr,%OutTarget% 396 | if (InStr(_attr,"D")) { 397 | ; display default icon instead of blank file icon 398 | OutIconChoice := "imageres.dll:4" 399 | } 400 | } 401 | } 402 | ; support windows internet shortcut files *.url 403 | else if fExt in url 404 | { 405 | IniRead, OutIcon, %fPath%, InternetShortcut, IconFile 406 | IniRead, OutIconNum, %fPath%, InternetShortcut, IconIndex, 0 407 | if FileExist(OutIcon) 408 | OutIconChoice := OutIcon . ":" . OutIconNum 409 | } 410 | 411 | ; support folder icons 412 | if (InStr(fAttr,"D")) 413 | { 414 | OutIconChoice := "shell32.dll:4" 415 | 416 | ; Customized may contain a hidden system file called desktop.ini 417 | _dini := fPath . "\desktop.ini" 418 | ; https://msdn.microsoft.com/en-us/library/cc144102.aspx 419 | 420 | ; case 1 421 | ; [.ShellClassInfo] 422 | ; IconResource=C:\WINDOWS\System32\SHELL32.dll,130 423 | IniRead,_ico,%_dini%,.ShellClassInfo,IconResource,0 424 | if (_ico) { 425 | lastComma := InStr(_ico,",",0,0) 426 | OutIconChoice := Substr(_ico,1,lastComma-1) . ":" . substr(_ico,lastComma+1) 427 | } else { 428 | ; case 2 429 | ; [.ShellClassInfo] 430 | ; IconFile=C:\WINDOWS\System32\SHELL32.dll 431 | ; IconIndex=130 432 | IniRead,_icoFile,%_dini%,.ShellClassInfo,IconFile,0 433 | IniRead,_icoIdx,%_dini%,.ShellClassInfo,IconIndex,0 434 | if (_icoFile) 435 | OutIconChoice := _icoFile . ":" . _icoIdx 436 | } 437 | } 438 | 439 | ; support associated filetypes 440 | else if (StrLen(OutIconChoice) < 4) 441 | OutIconChoice := getExtIcon(fExt) 442 | 443 | return OutIconChoice 444 | } 445 | 446 | getExtIcon(Ext) { ; modified from AHK_User - https://www.autohotkey.com/boards/viewtopic.php?p=297834#p297834 447 | I1 := I2:= "" 448 | RegRead, from, HKEY_CLASSES_ROOT, .%Ext% 449 | RegRead, DefaultIcon, HKEY_CLASSES_ROOT, %from%\DefaultIcon 450 | StringReplace, DefaultIcon, DefaultIcon, `",,all 451 | StringReplace, DefaultIcon, DefaultIcon, `%SystemRoot`%, %A_WinDir%,all 452 | StringReplace, DefaultIcon, DefaultIcon, `%ProgramFiles`%, %A_ProgramFiles%,all 453 | StringReplace, DefaultIcon, DefaultIcon, `%windir`%, %A_WinDir%,all 454 | StringSplit, I, DefaultIcon, `, 455 | DefaultIcon := I1 ":" RegExReplace(I2, "[^\d-]+") ;clean index number, but support negatives 456 | 457 | if (StrLen(DefaultIcon) < 4) { 458 | ; default file icon, if all else fails 459 | DefaultIcon := "shell32.dll:0" 460 | 461 | ;windows default to the OpenCommand if available 462 | RegRead, OpenCommand, HKEY_CLASSES_ROOT, %from%\shell\open\command 463 | if (OpenCommand) { 464 | OpenCommand := StrSplit(OpenCommand,"""","""`t`n`r")[2] 465 | DefaultIcon := OpenCommand . ":0" 466 | } 467 | } 468 | 469 | return DefaultIcon 470 | } 471 | 472 | NormalizePath(path) { ; from AHK v1.1.37.02 documentation 473 | cc := DllCall("GetFullPathName", "str", path, "uint", 0, "ptr", 0, "ptr", 0, "uint") 474 | VarSetCapacity(buf, cc*2) 475 | DllCall("GetFullPathName", "str", path, "uint", cc, "str", buf, "ptr", 0) 476 | return buf 477 | } 478 | 479 | FirstRun_Trigger() { 480 | global G_FirstRun_Trigger 481 | global APP_NAME 482 | global APP_VERSION 483 | global APP_REVISION 484 | 485 | ; prevent program auto exiting if we are displaying this dialog 486 | G_FirstRun_Trigger := true 487 | 488 | Gui, AboutDialog:New, +LastFound +AlwaysOnTop +ToolWindow 489 | Gui, AboutDialog:Margin, 10, -7 490 | Gui, Color, white 491 | ;@Ahk2Exe-IgnoreBegin 492 | Gui, Add, Picture, x12 y9 w48 h48, %A_ScriptDir%\res\app.ico 493 | ;@Ahk2Exe-IgnoreEnd 494 | /*@Ahk2Exe-Keep 495 | Gui, Add, Picture, x12 y9 w48 h48 Icon1, %A_ScriptFullPath% 496 | */ 497 | Gui, Font, s20 bold, Segoe UI 498 | Gui, Add, Text, x72 y2, %APP_NAME% 499 | Gui, Font, s9 norm 500 | Gui, Add, Text, x+4 yp+15, v%APP_VERSION% 501 | Gui, Add, Text, x72 yp+18 R2, by joedf 502 | Gui, Add, Text, , Revision date: %APP_REVISION% 503 | Gui, Add, Text, R2, Released under the MIT License 504 | Gui, Add, Link, R2, Special thanks to Deo for PUM.ahk 505 | Gui, Add, Text, , First time use? 506 | Gui, Add, Link, , https://github.com/joedf/stahky 507 | Gui, AboutDialog:Margin, , 10 508 | Gui, Show, , About %APP_NAME% 509 | return 510 | 511 | AboutDialogGuiEscape: 512 | AboutDialogGuiClose: 513 | ExitApp 514 | } 515 | -------------------------------------------------------------------------------- /lib/PUM_API.ahk: -------------------------------------------------------------------------------- 1 | class PUM_base 2 | { 3 | __Call( aTarget, aParams* ) { 4 | if ObjHasKey( PUM_base, aTarget ) 5 | return PUM_base[ aTarget ].( this, aParams* ) 6 | throw Exception( "Unknown function '" aTarget "' requested from object '" this.__Class "'", -1 ) 7 | } 8 | 9 | Err( msg ) { 10 | throw Exception( this.__Class " : " msg ( A_LastError != 0 ? "`n" this.ErrorFormat( A_LastError ) : "" ), -2 ) 11 | } 12 | 13 | ErrorFormat( error_id ) { 14 | VarSetCapacity(msg,1000,0) 15 | if !len := DllCall("FormatMessageW" 16 | ,"UInt",FORMAT_MESSAGE_FROM_SYSTEM := 0x00001000 | FORMAT_MESSAGE_IGNORE_INSERTS := 0x00000200 ;dwflags 17 | ,"Ptr",0 ;lpSource 18 | ,"UInt",error_id ;dwMessageId 19 | ,"UInt",0 ;dwLanguageId 20 | ,"Ptr",&msg ;lpBuffer 21 | ,"UInt",500) ;nSize 22 | return 23 | return strget(&msg,len) 24 | } 25 | } 26 | 27 | class pumAPI_base extends PUM_base 28 | { 29 | __Get( name ) { 30 | if !ObjHasKey( this, initialized ) 31 | this.Init() 32 | else 33 | throw Exception( "Unknown field '" name "' requested from object '" this.__Class "'", -1 ) 34 | } 35 | } 36 | 37 | class pumAPI extends pumAPI_base 38 | { 39 | Init() 40 | { 41 | this.LoadDllFunction( "User32.dll", "DrawIconEx" ) 42 | this.LoadDllFunction( "Gdi32.dll", "ExcludeClipRect" ) 43 | this.LoadDllFunction( "Gdi32.dll", "SetBkColor" ) 44 | this.LoadDllFunction( "Gdi32.dll", "SetTextColor" ) 45 | this.LoadDllFunction( "User32.dll", "FrameRect" ) 46 | this.LoadDllFunction( "User32.dll", "InflateRect" ) 47 | this.LoadDllFunction( "User32.dll", "DrawEdge" ) 48 | this.LoadDllFunction( "Gdi32.dll", "DeleteDC" ) 49 | this.LoadDllFunction( "User32.dll", "ReleaseDC" ) 50 | this.LoadDllFunction( "Gdi32.dll", "BitBlt" ) 51 | this.LoadDllFunction( "User32.dll", "FillRect" ) 52 | this.LoadDllFunction( "Gdi32.dll", "CreateCompatibleDC" ) 53 | this.LoadDllFunction( "Gdi32.dll", "CreateCompatibleBitmap" ) 54 | this.LoadDllFunction( "User32.dll", "SetRect" ) 55 | this.LoadDllFunction( "User32.dll", "DrawFrameControl" ) 56 | this.LoadDllFunction( "User32.dll", "GetSysColorBrush" ) 57 | this.LoadDllFunction( "User32.dll", "GetSysColor" ) 58 | this.LoadDllFunction( "Gdi32.dll", "CreateSolidBrush" ) 59 | this.LoadDllFunction( "Gdi32.dll", "DeleteObject" ) 60 | this.LoadDllFunction( "Gdi32.dll", "SelectObject" ) 61 | this.LoadDllFunction( "User32.dll", "GetDC" ) 62 | this.LoadDllFunction( "Gdi32.dll", "GetTextExtentPoint32W" ) 63 | this.LoadDllFunction( "User32.dll", "CopyImage" ) 64 | this.LoadDllFunction( "user32.dll", "PrivateExtractIconsW" ) 65 | this.LoadDllFunction( "user32.dll", "DestroyIcon" ) 66 | this.LoadDllFunction( "user32.dll", "SystemParametersInfoW" ) 67 | this.LoadDllFunction( "Gdi32.dll", "CreateFontIndirectW" ) 68 | this.LoadDllFunction( "gdiplus.dll", "GdiplusStartup" ) 69 | this.LoadDllFunction( "gdiplus.dll", "GdiplusShutdown" ) 70 | this.LoadDllFunction( "gdiplus.dll", "GdipCreateBitmapFromFileICM" ) 71 | this.LoadDllFunction( "gdiplus.dll", "GdipCreateHICONFromBitmap" ) 72 | this.LoadDllFunction( "gdiplus.dll", "GdipDisposeImage" ) 73 | this.LoadDllFunction( "User32.dll", "DrawTextExW" ) 74 | this.LoadDllFunction( "User32.dll", "DestroyWindow" ) 75 | 76 | this.LoadDllFunction( "User32.dll", "GetMenuItemRect" ) 77 | this.LoadDllFunction( "User32.dll", "CreatePopupMenu" ) 78 | this.LoadDllFunction( "User32.dll", "DestroyMenu" ) 79 | this.LoadDllFunction( "User32.dll", "DeleteMenu" ) 80 | this.LoadDllFunction( "User32.dll", "RemoveMenu" ) 81 | this.LoadDllFunction( "User32.dll", "SetMenuInfo" ) 82 | this.LoadDllFunction( "User32.dll", "SetMenuItemInfoW" ) 83 | this.LoadDllFunction( "User32.dll", "GetMenuItemInfoW" ) 84 | this.LoadDllFunction( "User32.dll", "GetMenuInfo" ) 85 | this.LoadDllFunction( "User32.dll", "GetMenuItemCount" ) 86 | this.LoadDllFunction( "User32.dll", "GetMenuItemID" ) 87 | this.LoadDllFunction( "User32.dll", "GetSubMenu" ) 88 | this.LoadDllFunction( "User32.dll", "IsMenu" ) 89 | this.LoadDllFunction( "User32.dll", "EndMenu" ) 90 | this.LoadDllFunction( "User32.dll", "InsertMenuItemW" ) 91 | this.LoadDllFunction( "User32.dll", "TrackPopupMenuEx" ) 92 | this.LoadDllFunction( "Gdi32.dll", "GetDeviceCaps" ) 93 | this.LoadDllFunction( "Kernel32.dll", "MulDiv" ) 94 | this.LoadDllFunction( "Shlwapi.dll", "PathFindExtensionW" ) 95 | this.initialized := 1 96 | } 97 | 98 | LoadDllFunction( file, function ) 99 | { 100 | if !hModule := DllCall( "GetModuleHandleW", "Wstr", file, "UPtr" ) 101 | hModule := DllCall( "LoadLibraryW", "Wstr", file, "UPtr" ) 102 | 103 | ret := DllCall("GetProcAddress", "Ptr", hModule, "AStr", function, "UPtr") 104 | if !ret 105 | this.Err( "Could not load function '" function "'" ) 106 | else 107 | this[ "p" function ] := ret 108 | } 109 | 110 | SetTimer( funcName, time = "" ) 111 | { 112 | static st_timers := object(), st_cb := object() 113 | if !IsFunc( funcName ) 114 | throw Exception( "Non existent function used for timer: " funcName, -1 ) 115 | if st_timers.HasKey( funcName ) 116 | { 117 | if ( time = "" ) ;just return parameters of active timer 118 | return st_timers[ funcName ] 119 | if ( time = "nOFF" && !( st_timers[ funcName ].delay < 0 ) ) 120 | return 121 | DllCall( "KillTimer", "ptr", 0, "uint", st_timers[ funcName ].tID ) 122 | st_timers.Remove( funcName ) 123 | } 124 | if time is integer 125 | if ( time != 0 ) 126 | { 127 | address := st_cb.hasKey( funcName ) ? st_cb[ funcName ] : ( st_cb[ funcName ] := RegisterCallback( funcName, "", 4 ) ) 128 | timerID := DllCall( "SetTimer", "ptr", 0, "UInt", 0, "Uint", abs(time), "ptr", address ) 129 | st_timers[ funcName ] := { tID : timerID, delay : time } 130 | } 131 | return 132 | } 133 | DrawIconEx( hDC, xLeft, yTop, hIcon ) { 134 | return DllCall( this.pDrawIconEx,"Ptr", hDC,"int", xLeft,"int", yTop,"Ptr", hIcon,"int", 0,"int", 0,"uint", 0,"Ptr", 0,"uint", 3 ) 135 | } 136 | ExcludeClipRect( hDC, left, top, right, bottom ) { 137 | return DllCall( this.pExcludeClipRect, "Ptr", hDC, "Int", left, "Int", top, "Int", right, "Int", bottom ) 138 | } 139 | SetBkColor( hDC, clr ) { 140 | return DllCall( this.pSetBkColor, "Ptr", hDC, "UInt", clr ) 141 | } 142 | SetTextColor( hDC, clr ) { 143 | return DllCall( this.pSetTextColor, "Ptr", hDC, "UInt", clr ) 144 | } 145 | FrameRect( hDC, pRECT, clr ) { 146 | ret := DllCall( this.pFrameRect, "Ptr", hDC, "Ptr", pRECT, "Ptr", hBrush := this.CreateSolidBrush( clr ) ) 147 | this.DeleteObject( hBrush ) 148 | return ret 149 | } 150 | InflateRect( pRECT, x, y ) 151 | { 152 | return DllCall( this.pInflateRect, "Ptr", pRECT, "int", x, "int", y ) 153 | } 154 | DrawEdge( hDC, pRECT ) { 155 | return DllCall( this.pDrawEdge, "Ptr", hDC, "Ptr", pRECT, "UInt", (0x0002 | 0x0004), "UInt", 0x0002 ) 156 | } 157 | DeleteDC( hDC ) { 158 | return DllCall( this.pDeleteDC, "Ptr", hDC ) 159 | } 160 | ReleaseDC( hDC, hWnd ) { 161 | return DllCall( this.pReleaseDC, "Ptr", hWnd, "Ptr", hDC ) 162 | } 163 | BitBlt( hdcDest, nXDest, nYDest, nWidth, nHeight, hdcSrc, nXSrc, nYSrc, dwRop ) { 164 | return DllCall( this.pBitBlt, "Ptr", hdcDest, "UInt",nXDest, "UInt",nYDest, "UInt",nWidth, "UInt",nHeight, "Ptr", hdcSrc, "UInt",nXSrc, "UInt",nYSrc, "UInt",dwRop ) 165 | } 166 | FillRect( hDC, pRECT, Clr ) { 167 | ret := DllCall( this.pFillRect, "Ptr", hDC, "Ptr", pRECT, "Ptr", hBrush := this.CreateSolidBrush( Clr ) ) 168 | this.DeleteObject( hBrush ) 169 | return ret 170 | } 171 | CreateCompatibleDC( hDC ) { 172 | return DllCall( this.pCreateCompatibleDC, "Ptr", hDC ) 173 | } 174 | CreateCompatibleBitmap( hDC, w, h ) { 175 | return DllCall( this.pCreateCompatibleBitmap, "Ptr", hDC, "UInt", w, "Uint", h ) 176 | } 177 | SetRect( ByRef rect, left, top, right, bottom ) { 178 | VarSetCapacity( rect, 16, 0 ) 179 | return DllCall( this.pSetRect, "Ptr", &rect, "UInt", left, "UInt", top, "UInt", right, "UInt", bottom ) 180 | } 181 | DrawFrameControl( hDC, pRECT, uType, uState ) { 182 | return DllCall( this.pDrawFrameControl, "Ptr", hDC, "Ptr", pRECT, "UInt", uType, "UInt", uState ) 183 | } 184 | GetSysColorBrush( nIndex ) { 185 | return DllCall( this.pGetSysColorBrush, "UInt", nIndex ) 186 | } 187 | GetSysColor( nIndex ) { 188 | return DllCall( this.pGetSysColor, "UInt", nIndex ) 189 | } 190 | CreateSolidBrush( clr ) { 191 | return DllCall( this.pCreateSolidBrush, "Uint", clr ) 192 | } 193 | DeleteObject( hObj ) { 194 | return DllCall( this.pDeleteObject, "Ptr", hObj ) 195 | } 196 | SelectObject( hDC, hObj ) { 197 | return DllCall( this.pSelectObject, "Ptr", hDC, "Ptr", hObj ) 198 | } 199 | GetDC( hwnd ) { 200 | return DllCall( this.pGetDC, "Ptr", hwnd ) 201 | } 202 | GetTextExtentPoint32( hDC, string ) { 203 | VarSetCapacity( pSize, 8, 0 ) 204 | DllCall( this.pGetTextExtentPoint32W, "Ptr", hDC, "Ptr", &string, "UInt", StrLen( string ), "Ptr", &pSize ) 205 | return { cx : NumGet( &pSize, 0, "UInt" ), cy : NumGet( &pSize, 4, "UInt" ) } 206 | } 207 | max( var1, var2 ) { 208 | return var1>var2?var1:var2 209 | } 210 | IsInteger( var ) { 211 | if var is integer 212 | return True 213 | else 214 | return False 215 | } 216 | isEmpty( var ) { 217 | return ( var = "" ? True : False ) 218 | } 219 | IconGetPath(Ico) { 220 | spec := Ico 221 | pos := InStr(Ico, ":", 0, 0) 222 | if (pos > 4) 223 | spec := substr(Ico,1,pos-1) 224 | return this.PathUnquoteSpaces( spec ) 225 | } 226 | IconGetIndex(Ico) { 227 | pos := InStr(Ico, ":", 0, 0) 228 | if (pos > 4) 229 | { 230 | ind := substr(Ico,pos+1) 231 | if !ind 232 | ind := 0 233 | return ind 234 | } 235 | } 236 | IconCopy(handle,size,type = 1,flags = 0x8) { ;type: 1 - IMAGE_ICON, 2 - IMAGE_CURSOR, 0 - IMAGE_BITMAP, 0x8 = LR_COPYDELETEORG 237 | return DllCall( this.pCopyImage, "Ptr", handle, "uint", type, "int", size, "int", size, "UInt",flags) 238 | } 239 | IconExtract( icoPath, size = 32 ) { 240 | pPath := this.IconGetPath( icoPath ) 241 | pNum := this.IconGetIndex( icoPath ) 242 | pNum := pNum = "" ? 0 : pNum 243 | ;http://msdn.microsoft.com/en-us/library/ms648075%28v=VS.85%29.aspx 244 | DllCall( this.pPrivateExtractIconsW, "Str", pPath, "UInt", pNum, "UInt", size, "UInt", size, "Ptr*", handle, "Ptr", 0,"UInt",1, "UInt", 0 ) 245 | if !handle 246 | { 247 | SplitPath, pPath,,,Ext 248 | if (Ext = "exe") 249 | DllCall( this.pPrivateExtractIconsW, "Str", "shell32.dll", "UInt", 2, "UInt", size, "UInt", size, "Ptr*", handle, "Ptr", 0,"UInt",1, "UInt", 0 ) 250 | } 251 | return handle 252 | } 253 | PathUnquoteSpaces( path ) 254 | { 255 | path := Trim( path ) 256 | regex = O)^\s*"+(.*?)"+\s*$ 257 | if RegExMatch( path, regex, match ) 258 | path := match[1] 259 | return path 260 | } 261 | PathFindExtension( sPath ) 262 | { 263 | return DllCall( this.pPathFindExtensionW, "ptr", &sPath ) 264 | } 265 | PathGetExt( sPath ) 266 | { 267 | sPath := this.PathUnquoteSpaces( sPath ) 268 | if this.IsEmpty( sPath ) 269 | return "" 270 | ext := StrGet( this.PathFindExtension( sPath ), "UTF-16" ) 271 | return SubStr( ext, 2 ) ;getting extension without dot 272 | } 273 | DestroyIcon( hIcon ) { 274 | return DllCall( this.pDestroyIcon, "Ptr", hIcon ) 275 | } 276 | Free(byRef var) { 277 | VarSetCapacity(var,0) 278 | return 279 | } 280 | GetSysFont( ByRef LOGFONT ) { 281 | VarSetCapacity(LOGFONT, 92, 0) 282 | return DllCall( this.pSystemParametersInfoW, "Uint", 0x001F, "UInt", 92, "Ptr", &LOGFONT, "UInt", 0 ) ? &LOGFONT : 0 283 | } 284 | CreateFontIndirect( pLOGFONT ) { 285 | return DllCall( this.pCreateFontIndirectW, "Ptr", pLOGFONT ) 286 | } 287 | StrSplit(str,delim,omit = "") { 288 | if (strlen(delim) > 1) 289 | { 290 | StringReplace,str,str,% delim,ƒ,1 ;■¶╬ 291 | delim = ƒ 292 | } 293 | ra := Array() 294 | loop, parse,str,% delim,% omit 295 | if (A_LoopField != "") 296 | ra.Insert(A_LoopField) 297 | return ra 298 | } 299 | Gdip_Startup() { 300 | VarSetCapacity(GdiplusStartupInput , 3*A_PtrSize, 0), NumPut(1,GdiplusStartupInput ,0,"UInt") ; GdiplusVersion = 1 301 | DllCall( this.pGdiplusStartup, "Ptr*", pToken, "Ptr", &GdiplusStartupInput, "Ptr", 0) 302 | return pToken 303 | } 304 | Gdip_Shutdown( pToken ) { 305 | DllCall( this.pGdiplusShutdown, "Ptr", pToken) 306 | return 0 307 | } 308 | RGBtoBGR( bgr_clr ) { 309 | return ( bgr_clr & 0xFF0000 ) >> 16 | ( bgr_clr & 0x00FF00 ) | ( bgr_clr & 0x0000FF ) << 16 310 | } 311 | DrawText( hDC, text, pRect, flags ) { 312 | return DllCall( this.pDrawTextExW,"Ptr", hDC, "wstr", text, "Uint", -1, "Ptr", pRect, "Uint", flags, "Ptr", 0 ) 313 | } 314 | DestroyWindow( hwnd ) { 315 | return DllCall( this.pDestroyWindow, "Ptr", hwnd ) 316 | } 317 | GetDeviceCaps( hWnd = 0, flags = 90 ) { 318 | return DllCall( this.pGetDeviceCaps, "Ptr", DllCall( "GetDC", "Ptr", hWnd ), "uint", flags ) 319 | } 320 | MulDiv( a, b, c ) { 321 | return DllCall( this.pMulDiv, "int", a, "int", b, "int", c ) 322 | } 323 | obj2LOGFONT( obj, ByRef LOGFONT ) { 324 | if !IsObject( obj ) 325 | obj := this.Str2Dict( obj ) 326 | if ( VarSetCapacity( LOGFONT ) != 92 ) 327 | VarSetCapacity( LOGFONT, 92, 0 ) 328 | if ObjHasKey( obj, "height" ) 329 | { 330 | NumPut( -this.MulDiv( abs(obj["height"]), this.GetDeviceCaps(), 72 ) 331 | , LOGFONT, 0, "Int" ) 332 | } 333 | if ObjHasKey( obj, "width" ) 334 | NumPut( obj["width"], LOGFONT, 4, "UInt" ) 335 | if ObjHasKey( obj, "Escapement" ) 336 | NumPut( obj["Escapement"], LOGFONT, 8, "UInt" ) 337 | if ObjHasKey( obj, "Orientation" ) 338 | NumPut( obj["Orientation"], LOGFONT, 12, "UInt" ) 339 | if ObjHasKey( obj, "Weight" ) 340 | NumPut( obj["Weight"], LOGFONT, 16, "UInt" ) 341 | if ObjHasKey( obj, "Italic" ) 342 | NumPut( obj["Italic"], LOGFONT, 20, "UChar" ) 343 | if ObjHasKey( obj, "Underline" ) 344 | NumPut( obj["Underline"], LOGFONT, 21, "UChar" ) 345 | if ObjHasKey( obj, "strike" ) 346 | NumPut( obj["strike"], LOGFONT, 22, "UChar" ) 347 | if ObjHasKey( obj, "OutPrecision" ) 348 | NumPut( obj["OutPrecision"], LOGFONT, 24, "UChar" ) 349 | if ObjHasKey( obj, "ClipPrecision" ) 350 | NumPut( obj["ClipPrecision"], LOGFONT, 25, "UChar" ) 351 | if ObjHasKey( obj, "PitchAndFamily" ) 352 | NumPut( obj["PitchAndFamily"], LOGFONT, 27, "UChar" ) 353 | if ObjHasKey( obj, "CharSet" ) 354 | NumPut( obj["CharSet"], LOGFONT, 23, "UChar" ) 355 | if ObjHasKey( obj, "Quality" ) 356 | NumPut( obj["Quality"], LOGFONT, 26, "UChar" ) 357 | if ObjHasKey( obj, "name" ) 358 | StrPut( obj["name"], &LOGFONT + 28, 32, "UTF-16" ) 359 | } 360 | LOGFONT2obj( ByRef LOGFONT ) { 361 | if ( VarSetCapacity( LOGFONT ) != 92 ) 362 | return 0 363 | obj := object() 364 | obj["height"] := abs( this.MulDiv( abs( NumGet( LOGFONT, 0, "Int" ) ) 365 | , 72, this.GetDeviceCaps() ) ) 366 | obj["width"] := NumGet( LOGFONT, 4, "Int" ) 367 | obj["Escapement"] := NumGet( LOGFONT, 8, "Int" ) 368 | obj["Orientation"] := NumGet( LOGFONT, 12, "Int" ) 369 | obj["Weight"] := NumGet( LOGFONT, 16, "Int" ) 370 | obj["italic"] := NumGet( LOGFONT, 20, "UChar" ) 371 | obj["underline"] := NumGet( LOGFONT, 21, "UChar" ) 372 | obj["strike"] := NumGet( LOGFONT, 22, "UChar" ) 373 | obj["CharSet"] := NumGet( LOGFONT, 23, "UChar" ) 374 | obj["OutPrecision"] := NumGet( LOGFONT, 24, "UChar" ) 375 | obj["ClipPrecision"] := NumGet( LOGFONT, 25, "UChar" ) 376 | obj["Quality"] := NumGet( LOGFONT, 26, "UChar" ) 377 | obj["PitchAndFamily"] := NumGet( LOGFONT, 27, "UChar" ) 378 | obj["name"] := StrGet( &LOGFONT + 28, 32, "UTF-16" ) 379 | return obj 380 | } 381 | Dict2Str( obj, delim = "|", separ = ":" ) { 382 | fstr := "" 383 | for key,val in obj 384 | fstr .= ( fstr ? delim : "" ) key separ val 385 | return fstr 386 | } 387 | Str2Dict( fstr, delim = "|", separ = ":" ) { 388 | obj := object() 389 | for i,pair in this.StrSplit( fstr, delim, A_Space A_Tab ) 390 | { 391 | ar := this.StrSplit( pair, separ, A_Space A_Tab ) 392 | obj[ ar[1] ] := ar[2] 393 | } 394 | return obj 395 | } 396 | 397 | ;//////////////////////////////////// PUM functions 398 | _GetItemPosByID( hMenu, itemID ) 399 | { 400 | if ( ( nCount := this._GetMenuItemCount( hMenu ) ) != -1 ) 401 | { 402 | loop,% nCount 403 | { 404 | nPos := A_Index-1 ;pos is zero-based 405 | if (( rID := this._GetMenuItemID( hMenu, nPos ) ) == -1 ) ;if this item is submenu :/ 406 | rID := this._GetItem( hMenu, nPos ).id 407 | if ( itemID == rID ) 408 | return nPos 409 | } 410 | } 411 | return -1 412 | } 413 | _GetItemRect( hMenu, nPos ) 414 | { 415 | VarSetCapacity( RECT, 16, 0 ) 416 | if DllCall( this.pGetMenuItemRect, "Ptr", 0, "Ptr", hMenu, "UInt", nPos, "Ptr", &RECT ) 417 | { 418 | objRect := { left : numget( RECT, 0, "UInt" ) 419 | ,top : numget( RECT, 4, "UInt" ) 420 | ,right : numget( RECT, 8, "UInt" ) 421 | ,bottom : numget( RECT, 12, "UInt" ) } 422 | return objRect 423 | } 424 | return 0 425 | } 426 | _GetMenuItems( hMenu ) 427 | { 428 | arrItems := Object() 429 | if ( ( nCount := this._GetMenuItemCount( hMenu ) ) != -1 ) 430 | { 431 | loop,% nCount 432 | { 433 | nPos := A_Index-1 ;pos is zero-based 434 | if ( item := this._GetItem( hMenu, nPos ) ) 435 | arrItems[ A_Index ] := item 436 | } 437 | } 438 | return arrItems 439 | } 440 | _GetMenuFromHandle( hMenu ) { 441 | cbSize := this.MENUINFOsize 442 | fMask := this.MIM_MENUDATA 443 | VarSetCapacity( MENUINFO, cbSize, 0 ) 444 | NumPut( cbSize, MENUINFO, 0, "UInt") 445 | NumPut( fMask, MENUINFO, 4, "UInt") 446 | this._GetMenuInfo( hMenu, MENUINFO ) 447 | if ( objPtr := NumGet( &MENUINFO, 16+2*A_PtrSize, "UPtr" ) ) 448 | obj := object( objPtr ) 449 | return obj 450 | } 451 | 452 | _GetItem( hMenu, nItem, fByPos = True ) { 453 | cbsize := this.MENUITEMINFOsize 454 | VarSetCapacity( MENUITEMINFO, cbsize, 0 ) 455 | fMask := this.MIIM_DATA 456 | NumPut( cbsize, MENUITEMINFO, 0, "UInt" ) 457 | NumPut( fMask, MENUITEMINFO, 4, "UInt" ) 458 | if this._GetMenuItemInfo( hMenu, nItem, fByPos, MENUITEMINFO ) 459 | { 460 | if ( objPtr := NumGet( MENUITEMINFO, 16+4*A_PtrSize, "Ptr" ) ) 461 | return object( objPtr ) 462 | } 463 | } 464 | 465 | _loadIcon( pPath, pSize ) { 466 | if !pPath 467 | return 0 468 | if this.IsInteger( pPath ) ;if icon handle were passed 469 | return this.IconCopy( pPath, pSize, 1, 0 ) ;will not delete original icon handle 470 | if ( this.IconGetIndex( pPath ) = "" 471 | && !( this.PathGetExt( pPath ) ~= "i)^(ico|cur|ani)$" ) ) 472 | { 473 | ;~ gdip_token := Gdip_Startup() 474 | DllCall( this.pGdipCreateBitmapFromFileICM, "wstr", pPath, "Ptr*", pBitmap ) 475 | DllCall( this.pGdipCreateHICONFromBitmap, "Ptr", pBitmap, "Ptr*", hIcon ) 476 | DllCall( this.pGdipDisposeImage, "Ptr", pBitmap ) 477 | ;~ Gdip_Shutdown( gdip_token ) 478 | hIcon := this.IconCopy( hIcon, pSize ) 479 | return hIcon 480 | } 481 | else 482 | return this.IconExtract( pPath, pSize ) 483 | } 484 | 485 | ;////////////////// WINAPI 486 | _CreatePopupMenu() { 487 | return DllCall( this.pCreatePopupMenu, "Ptr" ) 488 | } 489 | _DestroyMenu( hMenu ) { 490 | return DllCall( this.pDestroyMenu, "Ptr", hMenu ) 491 | } 492 | ;deletes item and associated menu 493 | _DeleteItem( hMenu, itemID, flag = 0 ) { ;0 means deletion by ID, otherewise by pos 494 | return DllCall( this.pDeleteMenu, "Ptr", hMenu, "UInt", ItemID, "UInt", flag?0x400:0 ) 495 | } 496 | ;deletes item, but detach associated menu 497 | _RemoveItem( hMenu, itemID, flag = 0 ) { ;0 means deletion by ID, otherewise by pos 498 | return DllCall( this.pRemoveMenu, "Ptr", hMenu, "UInt", ItemID, "UInt", flag?0x400:0 ) 499 | } 500 | _SetMenuInfo( hMenu, MENUINFO_ptr ) { 501 | return DllCall( this.pSetMenuInfo, "Ptr", hMenu, "Ptr", MENUINFO_ptr ) 502 | } 503 | _SetMenuItemInfo( hMenu, itemID, fByPosition, MENUITEMINFO_ptr ) { 504 | return DllCall( this.pSetMenuItemInfoW, "Ptr", hMenu, "uint", itemID, "uint", fByPosition, "Ptr", MENUITEMINFO_ptr) 505 | } 506 | _GetMenuItemInfo( hMenu, uItem, fByPosition, ByRef MENUITEMINFO ) { 507 | return DllCall( this.pGetMenuItemInfoW, "Ptr", hMenu, "uint", uItem, "uint", fByPosition, "Ptr", &MENUITEMINFO ) 508 | } 509 | _GetMenuInfo( hMenu, ByRef MENUINFO ) { 510 | return DllCall( this.pGetMenuInfo, "Ptr", hMenu, "Ptr", &MENUINFO ) 511 | } 512 | _GetMenuItemCount( hMenu ) { 513 | return DllCall( this.pGetMenuItemCount, "Ptr", hMenu ) 514 | } 515 | _GetMenuItemID( hMenu, nPos ) { 516 | return DllCall( this.pGetMenuItemID, "Ptr", hMenu, "Uint", nPos ) 517 | } 518 | _GetSubMenu( hMenu, nPos ) { 519 | return DllCall( this.pGetSubMenu, "Ptr", hMenu, "Uint", nPos ) 520 | } 521 | _IsMenu( hMenu ) { 522 | return DllCall( this.pIsMenu, "Ptr", hMenu ) 523 | } 524 | _EndMenu() { 525 | return DllCall( this.pEndMenu ) 526 | } 527 | _insertMenuItem( hMenu, prevID, fByPos, MENUITEMINFO_ptr ) { 528 | return DllCall( this.pInsertMenuItemW,"Ptr", hMenu,"uint", prevID,"uint", fByPos,"Ptr", MENUITEMINFO_ptr ) 529 | } 530 | _TrackPopupMenuEx( hMenu, uFlags, X, Y, hWnd ) { 531 | return DllCall( this.pTrackPopupMenuEx, "Ptr", hMenu, "uint", uFlags, "int", X, "int", Y, "Ptr", hWnd, "Ptr", 0) 532 | } 533 | 534 | _msgMonitor( state ) 535 | { 536 | static WM_MENUSELECT:= 0x11F 537 | ,WM_MEASUREITEM := 0x2C 538 | ,WM_DRAWITEM := 0x2B 539 | ,WM_ENTERMENULOOP := 0x211 540 | ,WM_INITMENUPOPUP := 0x117 541 | ,WM_UNINITMENUPOPUP := 0x125 542 | ,WM_EXITMENULOOP := 0x212 543 | ,WM_MENUCOMMAND := 0x126 544 | ,WM_MENURBUTTONUP := 0x0122 545 | ,WM_CONTEXTMENU := 0x7b 546 | ,WM_MBUTTONDOWN := 0x207 547 | ,WM_MENUCHAR := 0x120 548 | 549 | static oldMeasure, oldDraw, oldrbutton, oldMButton, oldMenuChar 550 | 551 | if (state) { 552 | OnMessage(WM_ENTERMENULOOP, "PUM_OnEnterLoop") 553 | oldMeasure := OnMessage(WM_MEASUREITEM, "PUM_OnMeasure") 554 | oldDraw := OnMessage(WM_DRAWITEM, "PUM_OnDraw") 555 | OnMessage(WM_MENUSELECT, "PUM_OnSelect") 556 | OnMessage(WM_INITMENUPOPUP, "PUM_OnInit") 557 | OnMessage(WM_UNINITMENUPOPUP,"PUM_OnUninit") 558 | oldrbutton := OnMessage(WM_MENURBUTTONUP, "PUM_OnRButtonUp") 559 | oldMButton := OnMessage(WM_MBUTTONDOWN, "PUM_OnSelect") 560 | oldMenuChar := OnMessage(WM_MENUCHAR, "PUM_OnMenuChar") 561 | OnMessage(WM_EXITMENULOOP, "PUM_OnExitLoop") 562 | } 563 | else { 564 | OnMessage(WM_ENTERMENULOOP, "") 565 | OnMessage(WM_MEASUREITEM, oldMeasure ) 566 | OnMessage(WM_DRAWITEM, oldDraw) 567 | OnMessage(WM_INITMENUPOPUP, "") 568 | OnMessage(WM_MENUSELECT, "") 569 | OnMessage(WM_EXITMENULOOP, "") 570 | OnMessage(WM_UNINITMENUPOPUP, "") 571 | OnMessage(WM_MENURBUTTONUP, oldrbutton ) 572 | OnMessage(WM_MBUTTONDOWN, oldMButton ) 573 | OnMessage(WM_MENUCHAR, oldMenuChar ) 574 | } 575 | } 576 | 577 | ; menu constants 578 | static MNS_AUTODISMISS := 0x10000000 ;Menu automatically ends when mouse is outside the menu for approximately 10 seconds. 579 | ,MNS_CHECKORBMP := 0x04000000 ;The same space is reserved for the check mark and the bitmap. If the check mark is drawn, the bitmap is not. 580 | ,MNS_DRAGDROP := 0x20000000 ;Menu items are OLE drop targets or drag sources. Menu owner receives WM_MENUDRAG and WM_MENUGETOBJECT messages. 581 | ,MNS_MODELESS := 0x40000000 ;Menu is modeless; that is, there is no menu modal message loop while the menu is active. 582 | ,MNS_NOCHECK := 0x80000000 ;No space is reserved to the left of an item for a check mark. 583 | ,MNS_NOTIFYBYPOS := 0x08000000 ;Menu owner receives a WM_MENUCOMMAND message instead of a WM_COMMAND message when the user makes a selection. 584 | ,MFT_MENUBREAK := 0x00000040 585 | ,MFT_MENUBARBREAK := 0x00000020 586 | ,MFT_OWNERDRAW := 0x00000100 587 | ,MFT_SEPARATOR := 0x00000800 588 | ,MFT_RIGHTORDER := 0x00002000 589 | ,MIIM_DATA := 0x00000020 590 | ,MIIM_STRING := 0x00000040 591 | ,MIIM_FTYPE := 0x00000100 592 | ,MIIM_ID := 0x00000002 593 | ,MIIM_STATE := 0x00000001 594 | ,MIIM_SUBMENU := 0x00000004 595 | ,MIIM_BITMAP := 0x00000080 596 | ,MENUITEMINFOsize := 16+8*A_PtrSize 597 | ,MENUINFOsize := 16+3*A_PtrSize 598 | ,HBMMENU_CALLBACK := -1 599 | ,MIM_BACKGROUND := 0x00000002 600 | ,MIM_STYLE := 0x00000010 601 | ,MIM_MAXHEIGHT := 0x00000001 602 | ,MIM_MENUDATA := 0x00000008 603 | ,ODA_DRAWENTIRE := 0x0001 604 | ,ODA_SELECT := 0x0002 605 | ,ODA_FOCUS := 0x0004 606 | ,MFS_DISABLED := 0x3 607 | 608 | } 609 | -------------------------------------------------------------------------------- /lib/PUM.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | PUM class represents popup menu manager 3 | by Deo (slightly modified by joedf) 4 | 5 | Documentation available here 6 | dead link: http://www.autohotkey.net/~Deo/index.html 7 | forum link: https://www.autohotkey.com/board/topic/73599-ahk-l-pum-owner-drawn-object-based-popup-menu/ 8 | */ 9 | class PUM extends PUM_base 10 | { 11 | __New( params = "" ) 12 | { 13 | this.instance := 1 14 | this.SetParams( params ) ;joedf: moved this line before Init(), so pumfont options work 15 | this.Init() 16 | this.gdipToken := pumAPI.Gdip_Startup() 17 | } 18 | 19 | __Get( aName ) 20 | { 21 | if ( aName = "__Class" ) 22 | return PUM.__Class 23 | return PUM._defaults[ aName ] 24 | } 25 | 26 | __Delete() 27 | { 28 | pumAPI.Gdip_Shutdown( this.gdipToken ) 29 | this.Free() 30 | return 31 | } 32 | 33 | Free() 34 | { 35 | for i,menu in this._menus.Clone() 36 | menu.Destroy() 37 | for i,item in this._items.Clone() 38 | item.Destroy() 39 | for i,hBrush in this._brush 40 | pumAPI.DeleteObject( hBrush ) 41 | for i,hFont in this._font 42 | pumAPI.DeleteObject( hFont ) 43 | } 44 | 45 | Init() 46 | { 47 | this._menus := Object() 48 | this._menus.SetCapacity( 20 ) 49 | this._items := Object() 50 | this._items.SetCapacity( 50 ) 51 | this._itemIDbyUID := Object() 52 | this._itemIDbyUID.SetCapacity( 50 ) 53 | this._itemsCount := 0 54 | this._brush := object() 55 | this._font := object() 56 | this.CreateFonts() 57 | } 58 | 59 | Destroy() 60 | { 61 | this.IsInstance() 62 | this.Free() 63 | this.Init() 64 | } 65 | 66 | IsInstance() 67 | { 68 | if this.instance 69 | return 1 70 | else 71 | this.Err( "Object is not an instance.`nUse 'new' to make one" ) 72 | } 73 | 74 | CreateFonts() 75 | { 76 | if !this.pumfont 77 | pumAPI.GetSysFont( LOGFONT ) 78 | else 79 | pumAPI.obj2LOGFONT( this.pumfont, LOGFONT ) 80 | this._font[ "normal" ] := pumAPI.CreateFontIndirect( &LOGFONT ) 81 | pumAPI.obj2LOGFONT( { weight : 700 }, LOGFONT ) 82 | this._font[ "bold" ] := pumAPI.CreateFontIndirect( &LOGFONT ) 83 | } 84 | 85 | GetFontNormal() 86 | { 87 | return this._font[ "normal" ] 88 | } 89 | 90 | GetFontBold() 91 | { 92 | return this._font[ "bold" ] 93 | } 94 | 95 | GetBrush( clr ) 96 | { 97 | this.IsInstance() 98 | return ( this._brush.HasKey( clr ) ? this._brush[ clr ] : ( this._brush[ clr ] := pumAPI.CreateSolidBrush( clr ) ) ) 99 | } 100 | 101 | SetParams( params ) 102 | { 103 | this.IsInstance() 104 | if !IsObject( params ) 105 | return 0 106 | for name, val in params 107 | this[ name ] := val 108 | if ( !pumAPI.isEmpty( params["seltcolor"] ) && this.seltcolor != -1 ) 109 | this.seltcolor := pumAPI.RGBtoBGR( this.seltcolor ) 110 | if ( !pumAPI.isEmpty( params["selbgcolor"] ) && this.selbgcolor != -1 ) 111 | this.selbgcolor := pumAPI.RGBtoBGR( this.selbgcolor ) 112 | } 113 | 114 | GetMenu( menuHandle ) 115 | { 116 | this.IsInstance() 117 | if ObjHasKey( this._menus, menuHandle ) 118 | return this._menus[ menuHandle ] 119 | else 120 | return 0 121 | } 122 | 123 | GetItemByID( id ) 124 | { 125 | this.IsInstance() 126 | if ( id && ObjHasKey( this._items, id ) ) 127 | return this._items[ id ] 128 | else 129 | return 0 130 | } 131 | 132 | GetItemByUID( uid ) 133 | { 134 | this.IsInstance() 135 | return this.GetItemByID( this._itemIDbyUID[ uid ] ) 136 | } 137 | 138 | CreateMenu( params = "" ) 139 | { 140 | this.IsInstance() 141 | handle := pumAPI._CreatePopupMenu() 142 | newmenu := new PUM_Menu( handle, this ) 143 | this._menus[ handle ] := newmenu 144 | newmenu.setParams( params ) 145 | return newmenu 146 | } 147 | 148 | static _defaults := { "selMethod" : "fill" ;may be "frame","fill" 149 | ,"selBGColor" : pumAPI.GetSysColor( 29 ) ;background color of selected item, -1 means invert, default - COLOR_MENUHILIGHT 150 | ,"selTColor" : pumAPI.GetSysColor( 14 ) ;text color of selected item, -1 means invert, default - COLOR_HIGHLIGHTTEXT 151 | ,"frameWidth" : 1 ;width of select frame when selMethod = "frame" 152 | ,"mnemonicCmd" : "run" ;may be "select","run" 153 | ,"oninit" : "" 154 | ,"onuninit" : "" 155 | ,"onselect" : "" 156 | ,"onrbutton" : "" 157 | ,"onmbutton" : "" 158 | ,"onrun" : "" 159 | ,"onshow" : "" 160 | ,"onclose" : "" 161 | ,"pumfont" : "" } 162 | } 163 | 164 | /* 165 | PUM_Item class represent single menu item 166 | */ 167 | 168 | class PUM_Item extends PUM_base 169 | { 170 | __New( id, objMenu ) 171 | { 172 | this.id := id 173 | this.menu := objMenu 174 | this.alive := 1 175 | } 176 | 177 | ; return default value for parameter if it wasn't set 178 | __Get( aName ) 179 | { 180 | if ( aName = "__Class" ) 181 | return PUM_Item.__Class 182 | return PUM_Item._defaults[ aName ] 183 | } 184 | 185 | __Delete() 186 | { 187 | return 188 | } 189 | 190 | GetPos() 191 | { 192 | return pumAPI._GetItemPosByID( this.menu.handle, this.id ) 193 | } 194 | 195 | GetRECT() 196 | { 197 | if (( nPos := this.GetPos() ) != -1 ) 198 | return pumAPI._GetItemRect( this.menu.handle, nPos ) 199 | return 0 200 | } 201 | 202 | GetParent() 203 | { 204 | return this.menu 205 | } 206 | 207 | Detach() 208 | { 209 | this.detachSubMenu := 1 210 | this.Destroy() 211 | } 212 | 213 | ;deletes item from it's own menu and, if associated, submenu this item opens 214 | Destroy() 215 | { 216 | if !this.alive 217 | return 0 218 | this.Free() 219 | if pumAPI.IsInteger( this.uid ) 220 | this.menu.objPUM._itemIDbyUID.Remove( this.uid, "" ) 221 | else 222 | this.menu.objPUM._itemIDbyUID.Remove( this.uid ) 223 | this.menu.objPUM._items.Remove( this.id, "" ) 224 | if this.detachSubMenu 225 | { 226 | this.RemoveSubMenu() 227 | pumAPI._RemoveItem( this.menu.handle, this.id ) 228 | } 229 | else 230 | { 231 | ;PUM menu cleanup should go first, because we will not be able retrieve items from atached menu if we destroy item through API first ( it destroys menu as well ) 232 | this.DestroySubMenu() 233 | pumAPI._DeleteItem( this.menu.handle, this.id ) 234 | } 235 | this.menu := "" 236 | this.submenu := "" 237 | this.alive := 0 238 | } 239 | 240 | Free() 241 | { 242 | ;~ DeleteObject( this.hfont ) 243 | if this.icondestroy 244 | pumAPI.DestroyIcon( this.hIcon ) 245 | this.hotCharCode := "" 246 | } 247 | 248 | DestroySubMenu() 249 | { 250 | if IsObject( this.assocMenu ) 251 | this.assocMenu.Destroy() 252 | } 253 | 254 | RemoveSubMenu() 255 | { 256 | if !this.alive 257 | return 0 258 | if ( this.assocMenu.handle ) 259 | { 260 | fMask := pumAPI.MIIM_SUBMENU 261 | cbsize := pumAPI.MENUITEMINFOsize 262 | VarSetCapacity( struct, cbsize, 0 ) 263 | NumPut( cbsize, struct, 0, "UInt" ) 264 | NumPut( fMask, struct, 4, "UInt" ) 265 | NumPut( 0, struct, 16+A_PtrSize, "Ptr" ) 266 | pumAPI._setMenuItemInfo( this.menu.handle, this.id, False, &struct ) 267 | this.assocMenu.owner := "" 268 | this.assocMenu := "" 269 | this.submenu := "" 270 | pumAPI.free( struct ) 271 | } 272 | } 273 | 274 | SetParams( params, newItemPos = "", fByPos = True ) 275 | { 276 | if !this.alive 277 | return 0 278 | if isObject( params ) 279 | { 280 | for name,val in params 281 | this[ name ] := val 282 | if ( this.uid != "" ) 283 | this.menu.objPUM._itemIDbyUID[ this.uid ] := this.id 284 | } 285 | else if !pumAPI.IsEmpty( params ) 286 | this.name := params 287 | else 288 | this.issep := 1 289 | if !pumAPI.isEmpty( params["tcolor"] ) 290 | this.tcolor := pumAPI.RGBtoBGR( this.tcolor ) 291 | if !pumAPI.isEmpty( params["bgcolor"] ) 292 | this.bgcolor := pumAPI.RGBtoBGR( this.bgcolor ) 293 | this._update( newItemPos, fByPos ) 294 | return 1 295 | } 296 | 297 | Update() 298 | { 299 | this._update() 300 | } 301 | 302 | GetTColor() 303 | { 304 | return pumAPI.RGBtoBGR( this.tcolor ) 305 | } 306 | 307 | GetBGColor() 308 | { 309 | return pumAPI.RGBtoBGR( this.bgcolor ) 310 | } 311 | 312 | GetIconHandle() 313 | { 314 | return this.hicon 315 | } 316 | 317 | _update( newItemPos = "", fByPos = True ) 318 | { 319 | this.Free() 320 | this.hfont := this.bold ? this.menu.objPUM.GetFontBold() : this.menu.objPUM.GetFontNormal() 321 | 322 | if ( mnemPos := InStr( this.name, "&" ) ) 323 | { 324 | hotChar := SubStr( this.name, mnemPos+1, 1 ) 325 | StringLower, hotChar, hotChar 326 | this.hotCharCode := asc( hotChar ) 327 | } 328 | 329 | fMask := 0 330 | 331 | fMask |= pumAPI.MIIM_FTYPE 332 | fMask |= pumAPI.MIIM_ID 333 | fMask |= pumAPI.MIIM_STATE 334 | fMask |= pumAPI.MIIM_SUBMENU 335 | fMask |= pumAPI.MIIM_BITMAP 336 | 337 | fType := 0 338 | fType |= pumAPI.MFT_OWNERDRAW 339 | if this.issep 340 | fType |= pumAPI.MFT_SEPARATOR 341 | else 342 | { 343 | if this.break 344 | fType |= this.break = 2 ? pumAPI.MFT_MENUBARBREAK : pumAPI.MFT_MENUBREAK 345 | } 346 | 347 | fState := 0 348 | if this.disabled 349 | fState |= pumAPI.MFS_DISABLED 350 | wID := this.id 351 | 352 | ownedMenu := IsObject( this.submenu ) ? this.submenu ;if menu object passed 353 | : pumAPI.IsInteger( this.submenu ) ? this.menu.objPUM._menus[ this.submenu ] ;if menu handle passed 354 | : 0 355 | ; check if submenu is valid menu 356 | if ( IsObject( ownedMenu ) && !ownedMenu.owner && pumAPI._isMenu( ownedMenu.handle ) ) 357 | { 358 | ;checking if associated with item submenu changed 359 | if !( this.assocMenu.handle == ownedMenu.handle ) 360 | { 361 | this.DestroySubMenu() 362 | ownedMenu.owner := this 363 | this.assocMenu := ownedMenu 364 | ownedMenu := "" 365 | } 366 | } 367 | 368 | itemNoIcons := this.noicons = -1 ? this.menu.noicons : this.noicons 369 | if !itemNoIcons 370 | { 371 | this.hicon := pumAPI.IsInteger( this.icon ) && this.iconUseHandle ? this.icon : pumAPI._loadIcon( this.icon, this.menu.iconssize ) 372 | this.icondestroy := pumAPI.IsInteger( this.icon ) && this.iconUseHandle ? 0 : 1 373 | } 374 | 375 | fMask |= pumAPI.MIIM_DATA 376 | dwItemData := &this ;storing address to 'this' item object 377 | 378 | ;typedef struct tagMENUITEMINFO { 379 | ; UINT cbSize; 0 380 | ; UINT fMask; 4 381 | ; UINT fType; 8 382 | ; UINT fState; 12 383 | ; UINT wID; 16 384 | ; HMENU hSubMenu; 16+ptr 385 | ; HBITMAP hbmpChecked; 16+2ptr 386 | ; HBITMAP hbmpUnchecked; 16+3ptr 387 | ; ULONG_PTR dwItemData; 16+4ptr 388 | ; LPTSTR dwTypeData; 16+5ptr 389 | ; UINT cch; 16+6ptr 390 | ; HBITMAP hbmpItem; 16+7ptr 391 | ; 16+8ptr 392 | 393 | cbsize := pumAPI.MENUITEMINFOsize 394 | VarSetCapacity( struct, cbsize, 0 ) 395 | NumPut( cbsize, struct, 0, "UInt" ) 396 | NumPut( fMask, struct, 4, "UInt" ) 397 | NumPut( fType, struct, 8, "UInt" ) 398 | NumPut( fState, struct, 12, "UInt" ) 399 | NumPut( wID, struct, 16, "UInt" ) 400 | NumPut( this.assocMenu.handle, struct, 16+A_PtrSize, "Ptr" ) 401 | NumPut( dwItemData, struct, 16+4*A_PtrSize, "Ptr" ) 402 | ;~ NumPut( PUM.HBMMENU_CALLBACK, struct, 16+7*A_PtrSize, "Ptr" ) 403 | 404 | if ( newItemPos != "" ) 405 | pumAPI._insertMenuItem( this.menu.handle, newItemPos, fByPos, &struct ) 406 | else 407 | pumAPI._setMenuItemInfo( this.menu.handle, this.id, False, &struct ) 408 | } 409 | 410 | static _defaults := { "issep" : 0 411 | ,"name" : "" 412 | ,"bold" : 0 413 | ,"icon" : 0 414 | ,"iconUseHandle" : 0 415 | ,"break" : 0 ;0,1,2 416 | ,"submenu": 0 417 | ,"tcolor" : "" 418 | ,"bgcolor": "" 419 | ,"noPrefix" : 0 420 | ,"disabled" : 0 421 | ,"noicons" : -1 ;-1 means use parent menu's setting 422 | ,"notext" : -1 } 423 | } 424 | 425 | /* 426 | PUM_Menu class represent single menu 427 | */ 428 | 429 | class PUM_Menu extends PUM_base 430 | { 431 | static _defaults := { "tcolor" : pumAPI.GetSysColor( 7 ) ;default - COLOR_MENUTEXT 432 | ,"bgcolor" : pumAPI.GetSysColor( 4 ) ;default - COLOR_MENU 433 | ,"nocolors" : 0 434 | ,"noicons" : 0 435 | ,"notext" : 0 436 | ,"iconssize" : 32 437 | ,"textoffset" : 5 438 | ,"maxheight" : 0 439 | ,"xmargin" : 3 440 | ,"ymargin" : 3 441 | ,"textMargin" : 5 } ;this is a pixels zmount which will be added after the text to make menu look pretty 442 | 443 | static _trackConsts := { "context" : 0x0001 ;TPM_RECURSE 444 | ,"hcenter" : 0x4 ;TPM_CENTERALIGN 445 | ,"hleft" : 0x0 ;TPM_LEFTALIGN 446 | ,"hright" : 0x8 ;TPM_RIGHTALIGN 447 | ,"vbottom" : 0x20 ;TPM_BOTTOMALIGN 448 | ,"vtop" : 0x0 ;TPM_TOPALIGN 449 | ,"vcenter" : 0x10 ;TPM_VCENTERALIGN 450 | ,"animlr" : 0x400 ;TPM_HORPOSANIMATION 451 | ,"animrl" : 0x800 ;TPM_HORNEGANIMATION 452 | ,"animtb" : 0x1000 ;TPM_VERPOSANIMATION 453 | ,"animbt" : 0x2000 ;TPM_VERNEGANIMATION 454 | ,"noanim" : 0x4000 } ;TPM_NOANIMATION 455 | 456 | __New( handle, objPUM ) 457 | { 458 | this.handle := handle 459 | this.objPUM := objPUM 460 | this.alive := 1 461 | } 462 | 463 | ; return default value for parameter if it wasn't set 464 | __Get( aName ) 465 | { 466 | if ( aName = "__Class" ) 467 | return PUM_Menu.__Class 468 | return PUM_Menu._defaults[ aName ] 469 | } 470 | 471 | __Delete() 472 | { 473 | ;~ msgbox % "menu destr: " this.handle 474 | return 475 | } 476 | 477 | EndMenu() 478 | { 479 | pumAPI._EndMenu() 480 | } 481 | 482 | Detach() 483 | { 484 | if isObject( this.owner ) 485 | { 486 | this.owner.RemoveSubMenu() 487 | this.owner := "" 488 | } 489 | } 490 | 491 | Destroy() 492 | { 493 | if !this.alive 494 | return 0 495 | this.Free() 496 | if pumAPI.IsInteger( this.handle ) 497 | this.objPUM._menus.Remove( this.handle, "" ) 498 | else 499 | this.objPUM._menus.Remove( this.handle ) 500 | if this.owner 501 | { 502 | this.owner.assocMenu := "" 503 | this.owner.submenu := "" 504 | this.owner := "" 505 | } 506 | this.DestroyItems() 507 | pumAPI._DestroyMenu( this.handle ) 508 | this.objPUM := "" 509 | this.alive := 0 510 | } 511 | 512 | DestroyItems() 513 | { 514 | for i,item in this.GetItems() 515 | item.Destroy() 516 | } 517 | 518 | IsMenu() 519 | { 520 | return pumAPI._isMenu( this.handle ) 521 | } 522 | 523 | GetTColor() 524 | { 525 | return pumAPI.RGBtoBGR( this.tcolor ) 526 | } 527 | 528 | GetBGColor() 529 | { 530 | return pumAPI.RGBtoBGR( this.bgcolor ) 531 | } 532 | 533 | GetParent() 534 | { 535 | return this.owner.GetParent() 536 | } 537 | 538 | Free() 539 | { 540 | ;~ DeleteObject( this.hBrush ) 541 | ;~ this.hBrush := "" 542 | } 543 | 544 | GetItems() 545 | { 546 | if !this.alive 547 | return 0 548 | return pumAPI._GetMenuItems( this.handle ) 549 | } 550 | 551 | GetItemByPos( nPos ) 552 | { 553 | return pumAPI._GetItem( this.handle, nPos, True ) 554 | } 555 | 556 | Count() 557 | { 558 | if !this.alive 559 | return 0 560 | return pumAPI._GetMenuItemCount( this.handle ) 561 | } 562 | 563 | Show( x = 0, y = 0, flags = "" ) 564 | { 565 | if !this.alive 566 | return 0 567 | item := 0 568 | tpmflags := 0x100 569 | for i,v in pumAPI.StrSplit( flags, A_Space, A_Space A_Tab ) 570 | if ( val := PUM_Menu._trackConsts[ v ] ) 571 | tpmflags |= val 572 | isContext := tpmflags & 0x1 573 | if !isContext ;check if menu is not recursed menu ( has TPM_RECURSE flag ) 574 | { 575 | if IsFunc( foo := this.objPum.onshow ) 576 | ret := %foo%( "onshow", this ) 577 | if ( ret = 0 ) 578 | return 579 | Gui PUM_MENU_GUI:+LastFound +ToolWindow +HwndPUMhParent +AlwaysOnTop 580 | Gui, PUM_MENU_GUI:Show 581 | WinActivate,% "ahk_id " PUMhParent 582 | ControlFocus,,ahk_id %PUMhParent% 583 | pumAPI._msgMonitor( 1 ) 584 | pumAPI.SetTimer( "PUM_MenuCheck", 100 ) 585 | } 586 | else 587 | { 588 | Gui PUM_MENU_GUI: +LastFoundExist 589 | PUMhParent := WinExist() 590 | } 591 | this.objPUM.IsMenuShown := True 592 | itemID := pumAPI._TrackPopupMenuEx( this.handle, tpmflags, x, y, PUMhParent ) 593 | this.objPUM.IsMenuShown := False 594 | if itemID 595 | { 596 | if ( item := this.objPUM._items[ itemID ] ) 597 | if isFunc( foo := this.objPUM.onrun ) 598 | %foo%( "onrun", item ) 599 | } 600 | 601 | if !isContext 602 | { 603 | pumAPI._msgMonitor( 0 ) 604 | pumAPI.DestroyWindow( PUMhParent ) 605 | if IsFunc( foo := this.objPum.onclose ) 606 | %foo%( "onclose", this ) 607 | } 608 | return item 609 | } 610 | 611 | Add( params = "", pos = -1, fByPos = True ) 612 | { 613 | if !this.alive 614 | return 0 615 | id := ++this.objPUM._itemsCount 616 | item := new PUM_Item( id, this ) 617 | this.objPUM._items[ id ] := item 618 | item.setParams( params, pos, fByPos ) 619 | return item 620 | } 621 | 622 | SetParams( params ) 623 | { 624 | if !this.alive 625 | return 0 626 | if IsObject( params ) 627 | { 628 | for name,val in params 629 | this[ name ] := val 630 | if !pumAPI.isEmpty( params["tcolor"] ) 631 | this.tcolor := pumAPI.RGBtoBGR( this.tcolor ) 632 | if !pumAPI.isEmpty( params["bgcolor"] ) 633 | this.bgcolor := pumAPI.RGBtoBGR( this.bgcolor ) 634 | } 635 | this._update() 636 | } 637 | 638 | _update() 639 | { 640 | this.Free() 641 | ;typedef struct MENUINFO { 642 | ; DWORD cbSize; 0 643 | ; DWORD fMask; 4 644 | ; DWORD dwStyle; 8 645 | ; UINT cyMax; 12 646 | ; HBRUSH hbrBack; 16 647 | ; DWORD dwContextHelpID; 16+ptr 648 | ; ULONG_PTR dwMenuData; 16+2ptr 649 | ; 16+3ptr 650 | 651 | if ( pumAPI.IsInteger( this.bgcolor ) && !this.nocolors ) 652 | clr := this.bgcolor 653 | else 654 | clr := PUM_Menu._defaults[ "bgcolor" ] 655 | 656 | fMask := 0 657 | fMask |= pumAPI.MIM_BACKGROUND 658 | fMask |= pumAPI.MIM_MAXHEIGHT 659 | fMask |= pumAPI.MIM_MENUDATA 660 | 661 | cbSize := pumAPI.MENUINFOsize 662 | VarSetCapacity( struct, cbSize, 0 ) 663 | NumPut( cbSize, struct, 0, "UInt") 664 | NumPut( fMask, struct, 4, "UInt") 665 | NumPut( this.maxHeight, struct, 12, "UInt") 666 | NumPut( this.objPum.GetBrush( clr ), struct, 16, "UPtr") 667 | NumPut( &this, struct, 16+2*A_PtrSize, "UPtr") 668 | pumAPI._SetMenuInfo( this.handle, &struct ) 669 | } 670 | } 671 | 672 | PUM_MenuCheck( p* ) 673 | { 674 | Gui,PUM_MENU_GUI:+LastFoundExist 675 | IfWinNotActive 676 | { 677 | pumAPI._EndMenu() 678 | pumAPI.SetTimer( A_ThisFunc, "OFF" ) 679 | } 680 | return 681 | } 682 | 683 | ;handler of WM_ENTERMENULOOP message 684 | PUM_OnEnterLoop() 685 | { 686 | return 0 687 | } 688 | 689 | ;handler of WM_EXITMENULOOP message 690 | PUM_OnExitLoop() 691 | { 692 | return 0 693 | } 694 | 695 | ;not currently used 696 | PUM_OnMButtonDown( wParam, lParam, msg, hwnd ) 697 | { 698 | x := lParam & 0xFFFF 699 | y := lParam >> 16 700 | IsCtrl := wParam & 0x0008 701 | IsLMB := wParam & 0x0001 702 | IsMMB := wParam & 0x0010 703 | IsRMB := wParam & 0x0002 704 | IsShift := wParam & 0x0004 705 | IsXB1 := wParam & 0x0020 706 | IsXB2 := wParam & 0x0040 707 | tooltip % x " - " y "`nCtrl " IsCtrl "`nLMB " IsLMB "`nIsMMB " IsMMB "`nIsRMB " IsRMB "`nIsShift " IsShift "`nIsXB1 " IsXB1 "`nIsXB2 " IsXB2 708 | } 709 | 710 | ;handler of WM_CONTEXTMENU message 711 | PUM_OnRButtonUp( wParam, lParam, msg, hwnd ) 712 | { 713 | if ( item := pumAPI._GetItem( lParam, wParam ) ) 714 | { 715 | if IsFunc( foo := item.menu.objPUM.onrbutton ) 716 | %foo%( "onrbutton", item ) 717 | } 718 | return 0 719 | } 720 | 721 | ;handler of WM_INITMENUPOPUP message 722 | PUM_OnInit( wParam, lParam, msg, hwnd ) 723 | { 724 | hMenu := wParam 725 | menu := pumAPI._GetMenuFromHandle( hMenu ) 726 | if isFunc( foo := menu.objPUM.oninit ) 727 | %foo%( "oninit", menu ) 728 | return 0 729 | } 730 | 731 | ;handler of WM_UNINITMENUPOPUP message 732 | PUM_OnUninit( wParam, lParam, msg, hwnd ) 733 | { 734 | hMenu := wParam 735 | menu := pumAPI._GetMenuFromHandle( hMenu ) 736 | if isFunc( foo := menu.objPUM.onuninit ) 737 | %foo%( "onuninit", menu ) 738 | return 0 739 | } 740 | 741 | ;handler of WM_MENUCHAR message 742 | PUM_OnMenuChar( wParam, lParam, msg, hwnd ) 743 | { 744 | charCode := wParam & 0xffff 745 | type := wParam >> 16 746 | hMenu := lParam 747 | itemsList := pumAPI._GetMenuItems( hMenu ) 748 | itemPos := "" 749 | for i,item in itemsList 750 | if ( item.hotCharCode == charCode ) 751 | { 752 | itemPos := i-1 753 | break 754 | } 755 | if( itemPos = "" ) 756 | for i,item in itemsList 757 | { 758 | if pumAPI.isEmpty( hotChar := SubStr( item.name,1,1 ) ) 759 | continue 760 | StringLower, hotChar, hotChar 761 | if ( asc( hotChar ) == charCode ) 762 | { 763 | itemPos := i-1 764 | break 765 | } 766 | } 767 | 768 | if ( itemPos != "" ) 769 | { 770 | mode := item.menu.objPUM.mnemonicCmd = "select" ? 3 : 2 771 | return ( mode << 16 ) | itemPos 772 | } 773 | return 0 774 | } 775 | 776 | ;handler of WM_MENUSELECT, WM_MBUTTONDOWN messages 777 | PUM_OnSelect( wParam, lParam, msg, hwnd ) 778 | { 779 | static hMenu, nItem, fByPosition 780 | if ( msg = 0x207 ) ;MBUTTON 781 | { 782 | if ( item := pumAPI._GetItem( hMenu, nItem, fByPosition ) ) 783 | if isFunc( foo := item.menu.objPUM.onmbutton ) 784 | %foo%( "onmbutton", item ) 785 | return 0 786 | } 787 | nItem := wParam & 0xFFFF 788 | state := wParam >> 16 789 | isSubMenu := state & 0x10 790 | ;~ wm := state & 0x00008000 791 | ;~ ih := state & 0x00000080 792 | ;~ ig := state & 0x00000001 793 | ;~ id := state & 0x00000002 794 | ;~ tooltip % "wm : " wm "`nih : " ih "`nig : " ig "`nid : " id 795 | hMenu := lParam 796 | if isSubMenu 797 | fByPosition := True 798 | else 799 | fByPosition := False 800 | if ( item := pumAPI._GetItem( hMenu, nItem, fByPosition ) ) 801 | { 802 | if isFunc( foo := item.menu.objPUM.onselect ) 803 | %foo%( "onselect", item ) 804 | return 0 805 | } 806 | return 0 807 | } 808 | 809 | ;handler of WM_DRAWITEM message 810 | PUM_OnDraw( wParam, lParam, msg, hwnd ) 811 | { 812 | critical 813 | ;~ typedef struct tagDRAWITEMSTRUCT { 814 | ;~ UINT CtlType; 0 815 | ;~ UINT CtlID; 4 816 | ;~ UINT itemID; 8 817 | ;~ UINT itemAction; 12 818 | ;~ UINT itemState; 16 819 | ;~ HWND hwndItem; 16+ptr 820 | ;~ HDC hDC; 16+2ptr 821 | ;~ RECT rcItem; 822 | ;~ left 16+3ptr 823 | ;~ top 20+3ptr 824 | ;~ right 24+3ptr 825 | ;~ bottom 28+3ptr 826 | ;~ ULONG_PTR itemData; 32+3ptr 827 | ;~ 32+4ptr 828 | if ( wParam != 0 ) ;means the message not for menu 829 | return 830 | ;~ ctlType := NumGet( lParam + 0, 0, "UInt" ) 831 | ;~ if ( ctlType != 1 ) ;ODT_MENU - again check this is menu 832 | ;~ return 833 | itemData := NumGet( lParam + 0, 32 + 3 * A_PtrSize, "UPtr" ) ;pointer on object 834 | item := object( itemData ) ;getting object itself 835 | itemAction := NumGet( lParam + 0, 12, "UInt" ) 836 | itemState := NumGet( lParam + 0, 16, "UInt" ) 837 | hDC := NumGet( lParam + 0, 16 + 2 * A_PtrSize, "UPtr" ) 838 | pRECT := lParam + 16 + 3 * A_PtrSize 839 | left := NumGet( pRECT + 0, 0, "UInt" ) 840 | top := NumGet( pRECT + 0, 4, "UInt" ) 841 | right := NumGet( pRECT + 0, 8, "UInt" ) 842 | bottom := NumGet( pRECT + 0, 12, "UInt" ) 843 | 844 | ;colors definition 845 | tcolor := item.menu.nocolors ? PUM_Menu._defaults[ "tcolor" ] 846 | : item.disabled ? pumAPI.GetSysColor( 17 ) 847 | : ( !pumAPI.IsEmpty( item.tcolor ) ? item.tcolor : item.menu.tcolor ) 848 | bgcolor := !item.menu.nocolors ? ( !pumAPI.IsEmpty( item.bgcolor ) ? item.bgcolor : item.menu.bgcolor ) 849 | : PUM_Menu._defaults[ "bgcolor" ] 850 | selMethod := item.menu.objPUM.selMethod 851 | selBGColor := item.menu.objPUM.selBGColor 852 | selBGColor := selBGColor = -1 ? ~bgcolor & 0xFFFFFF : selBGColor 853 | selTColor := item.disabled ? pumAPI.GetSysColor( 17 ) 854 | : item.menu.objPUM.selTColor 855 | selTColor := selTColor = -1 ? ~tcolor & 0xFFFFFF : selTColor 856 | isItemSelected := ( itemState & 1 ) 857 | 858 | itemNoIcons := item.noicons = -1 ? item.menu.noicons : item.noicons 859 | itemNoText := item.notext = -1 ? item.menu.notext : item.notext 860 | 861 | if ( itemAction = pumAPI.ODA_FOCUS ) 862 | return True 863 | else ;ODA_DRAWENTIRE | ODA_SELECT 864 | { 865 | if item.issep 866 | { 867 | pumAPI.SetRect( sepRect, left, top, right, bottom ) 868 | NumPut( top+1, &sepRect, 4, "UInt" ) 869 | pumAPI.DrawEdge( hDC, &sepRect ) 870 | } 871 | else 872 | { 873 | ;filling item's background in case "fill" selection method 874 | if ( ( itemAction = pumAPI.ODA_SELECT && selMethod = "fill" ) || ( itemAction = pumAPI.ODA_DRAWENTIRE && !pumAPI.IsEmpty( item.bgcolor ) ) ) 875 | { 876 | clr := isItemSelected ? selBGColor : bgcolor 877 | pumAPI.FillRect( hDC, pRect, clr ) 878 | } 879 | 880 | ;filling rect with selection color 881 | if ( selMethod = "fill" && isItemSelected ) 882 | pumAPI.FillRect( hDC, pRect, selBGColor ) 883 | 884 | ;drawing frame selection 885 | if ( selMethod = "frame" && itemAction = PUM.ODA_SELECT ) 886 | { 887 | clr := isItemSelected ? selBGColor : bgcolor 888 | loop, % item.menu.objPUM.frameWidth 889 | { 890 | pumAPI.SetRect( frameRect, left, top, right, bottom ) 891 | infNum := -1 - ( A_Index - 1 ) 892 | pumAPI.InflateRect( &frameRect, infNum, infNum ) 893 | pumAPI.FrameRect( hDC, &frameRect, clr ) 894 | } 895 | } 896 | ;drawing text 897 | if ( !itemNoText && ( ( itemAction = pumAPI.ODA_SELECT && selMethod = "fill" ) || itemAction = pumAPI.ODA_DRAWENTIRE ) ) 898 | { 899 | tClr := isItemSelected ? selTColor : tcolor 900 | bClr := isItemSelected ? selBGColor : bgcolor 901 | pumAPI.SetBkColor( hDC, bClr ) 902 | hfontOld := pumAPI.SelectObject( hDC, item.hfont ) 903 | textFlags := 0x4 | 0x20 | 0x100 | ( item.noPrefix ? 0x800 : 0 ) ; DT_VCENTER | DT_SINGLELINE | DT_NOCLIP | DT_NOPREFIX(?) 904 | tleft := left 905 | + ( itemNoIcons ? 0 : item.menu.iconssize + item.menu.textoffset ) 906 | + item.menu.xmargin 907 | tright := right - ( item.assocMenu ? 15 : 0 ) - item.menu.xmargin 908 | ttop := top + item.menu.ymargin 909 | tbot := bottom - item.menu.ymargin 910 | ;~ if ( item.disabled && !isItemSelected ) 911 | ;~ { 912 | ;~ SetTextColor( hDC, 0xFFFFFF ) 913 | ;~ SetRect( textRect, tleft + 1, ttop + 1, tright + 1, tbot + 1 ) 914 | ;~ DrawText( hDC, item.name, &textRect, textFlags ) 915 | ;~ } 916 | pumAPI.SetTextColor( hDC, tClr ) 917 | pumAPI.SetRect( textRect, tleft, ttop, tright, tbot ) 918 | pumAPI.DrawText( hDC, item.name, &textRect, textFlags ) 919 | pumAPI.SelectObject( hDC, hfontOld ) 920 | } 921 | ;drawing icon 922 | if ( !itemNoIcons && Item.GetIconHandle() 923 | && ( ( itemAction = pumAPI.ODA_SELECT && selMethod = "fill" ) || itemAction = pumAPI.ODA_DRAWENTIRE ) ) 924 | { 925 | pumAPI.DrawIconEx( hDC 926 | , left + item.menu.xmargin 927 | , top + item._y_icon 928 | , item.GetIconHandle() ) 929 | } 930 | ;drawing submenu arrow 931 | if item.assocMenu 932 | && ( ( itemAction = pumAPI.ODA_SELECT && selMethod = "fill" ) || itemAction = pumAPI.ODA_DRAWENTIRE ) 933 | { 934 | ;code took from here: http://www.codeguru.com/cpp/controls/menu/miscellaneous/article.php/c13017/Owner-Drawing-the-Submenu-Arrow.htm 935 | bmWidth := 15 936 | bmHeight := bottom - top 937 | ;calculating it's pos 938 | bmY := round( top - ( ( bottom - top ) - bmHeight )/2 ) 939 | bmX := round( right - 15 ) 940 | 941 | ;drawing arrow in the colors we need 942 | arrowDC := pumAPI.CreateCompatibleDC( hDC ) 943 | fillDC := pumAPI.CreateCompatibleDC( hDC ) 944 | arrowBM := pumAPI.CreateCompatibleBitmap( hDC, bmWidth, bmHeight ) 945 | fillBM := pumAPI.CreateCompatibleBitmap( hDC, bmWidth, bmHeight ) 946 | oldArrowBitmap := pumAPI.SelectObject( arrowDC, arrowBM ) 947 | oldFillBitmap := pumAPI.SelectObject( fillDC, fillBM ) 948 | ;Set the offscreen arrow rect 949 | pumAPI.SetRect( tmpArrowR, 0, 0, bmWidth, bmHeight ) 950 | ;Draw the frame control arrow (The OS draws this as a black on 951 | ; white bitmap mask) 952 | pumAPI.DrawFrameControl( arrowDC, &tmpArrowR, 2, 0 ) 953 | ;Fill the fill bitmap with the arrow color 954 | clr := isItemSelected ? selTColor : tcolor 955 | pumAPI.FillRect( fillDC, &tmpArrowR, clr ) 956 | ;Blit the items in a masking fashion 957 | pumAPI.BitBlt( hDC, bmX, bmY, bmWidth, bmHeight, fillDC, 0, 0, 0x00660046 ) 958 | pumAPI.BitBlt( hDC, bmX, bmY, bmWidth, bmHeight, arrowDC, 0, 0, 0x008800C6 ) 959 | pumAPI.BitBlt( hDC, bmX, bmY, bmWidth, bmHeight, fillDC, 0, 0, 0x00660046 ) 960 | 961 | pumAPI.SelectObject( arrowDC, oldArrowBitmap ) 962 | pumAPI.SelectObject( fillDC, oldFillBitmap ) 963 | pumAPI.DeleteObject( arrowBM ) 964 | pumAPI.DeleteObject( fillBM ) 965 | pumAPI.DeleteDC( arrowDC ) 966 | pumAPI.DeleteDC( fillDC ) 967 | } 968 | } 969 | } 970 | ;This call basically is what stops the OS from drawing the submenu arrow 971 | pumAPI.ExcludeClipRect( hDC, left, top, right, bottom ) ;avoid submenu arrow drawing 972 | return True 973 | } 974 | 975 | ;handler of WM_MEASUREITEM message 976 | PUM_OnMeasure( wParam, lParam, msg, hwnd ) 977 | { 978 | critical 979 | ;~ typedef struct MEASUREITEMSTRUCT { 980 | ;~ UINT CtlType; 0 981 | ;~ UINT CtlID; 4 982 | ;~ UINT itemID; 8 983 | ;~ UINT itemWidth; 12 984 | ;~ UINT itemHeight; 16 985 | ;~ ULONG_PTR itemData; 16+ptr 986 | ; 16+2ptr 987 | if ( wParam != 0 ) ;not a menu 988 | return 989 | ;~ CtlType := NumGet( lParam+0, 0, "UInt" ) 990 | ;~ if ( CtlType != 1 ) ;not a menu 991 | ;~ return 992 | itemData := NumGet( lParam+0, 16+A_PtrSize, "UPtr" ) ;getting stored pointer to item object 993 | item := Object( itemData ) 994 | if item.issep 995 | h := 4, w := 0 996 | else 997 | { 998 | itemNoIcons := item.noicons = -1 ? item.menu.noicons : item.noicons 999 | itemNoText := item.notext = -1 ? item.menu.notext : item.notext 1000 | hDC := pumAPI.GetDC( hwnd ) 1001 | hOldFont := pumAPI.SelectObject( hDC, item.hfont ) 1002 | size := pumAPI.GetTextExtentPoint32( hDC, item.name ) 1003 | w := itemNoIcons ? size.cx 1004 | : itemNoText ? item.menu.iconssize - 11 ;i don't know where this 11 pixels goes from 1005 | : item.menu.iconssize + item.menu.textoffset + size.cx 1006 | w += item.menu.xmargin*2 1007 | if item.submenu 1008 | w += 10 ;15 1009 | else if !itemNoText 1010 | w += item.menu.textMargin 1011 | h := itemNoIcons ? size.cy 1012 | : itemNoText ? item.menu.iconssize 1013 | : pumAPI.max( item.menu.iconssize, size.cy ) 1014 | h += item.menu.ymargin*2 1015 | item._y_icon := round( ( h - item.menu.iconssize )/2 ) 1016 | pumAPI.SelectObject( hDC, hOldFont ) 1017 | pumAPI.ReleaseDC( hDC, hWnd ) 1018 | } 1019 | NumPut( w, lParam+0, 12, "UInt" ) 1020 | NumPut( h, lParam+0, 16, "UInt" ) 1021 | return True 1022 | } --------------------------------------------------------------------------------