├── .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 | #  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 | 
47 | 
48 | 
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 | }
--------------------------------------------------------------------------------