Notify.ahk 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469
  1. /********************************************************************************************
  2. * Notify - Simplifies the creation and display of notification GUIs.
  3. * @author Martin Chartier (XMCQCX)
  4. * @date 2024/11/20
  5. * @version 1.7.0
  6. * @see {@link https://github.com/XMCQCX/NotifyClass-NotifyCreator GitHub}
  7. * @see {@link https://www.autohotkey.com/boards/viewtopic.php?f=83&t=129635 AHK Forum}
  8. * @license MIT license
  9. * @credits
  10. * - JSON by thqby, HotKeyIt. {@link https://github.com/thqby/ahk2_lib/blob/master/JSON.ahk GitHub}
  11. * - FrameShadow by Klark92. {@link https://www.autohotkey.com/boards/viewtopic.php?f=6&t=29117&hilit=FrameShadow AHK Forum}
  12. * - DrawBorder by ericreeves. {@link https://gist.github.com/ericreeves/fd426cc0457a5a47058e1ad1a29d9bd6 GitHub}
  13. * - CalculatePopupWindowPosition by lexikos {@link https://www.autohotkey.com/boards/viewtopic.php?t=103459 AHK Forum}
  14. * - Notify by gwarble. {@link https://www.autohotkey.com/board/topic/44870-notify-multiple-easy-tray-area-notifications-v04991/ AHK Forum}
  15. * - Notify by the-Automator. {@link https://www.the-automator.com/downloads/maestrith-notify-class-v2/ the-automator.com}
  16. * - WiseGui by SKAN. {@link https://www.autohotkey.com/boards/viewtopic.php?t=94044 AHK Forum}
  17. * @features
  18. * - Customize various parameters such as text, font, color, image, sound, animation, and more.
  19. * - Choose from built-in themes or create your own custom themes with Notify Creator.
  20. * - Rounded or edged corners.
  21. * - Position at different locations on the screen.
  22. * - GUI stacking and repositioning.
  23. * - Multi-Monitor support.
  24. * - Multi-Script support.
  25. * - Call a function when clicking on it.
  26. * @methods
  27. * - Show(title, msg, image, sound, callback, options) - Builds and displays a notification GUI.
  28. * - Destroy(param) - Destroys GUIs.
  29. * - Window handle (hwnd) - Destroys the GUI with the specified window handle.
  30. * - tag - Destroys every GUI containing this tag across all scripts.
  31. * - 'oldest' or no param - Destroys the oldest GUI.
  32. * - 'latest' - Destroys the most recent GUI.
  33. * - DestroyAllOnMonitorAtPosition(monitorNumber, position) - Destroys all GUIs on a specific monitor at a given position.
  34. * - DestroyAllOnAllMonitorAtPosition(position) - Destroys all GUIs on all monitors at a specific position.
  35. * - DestroyAllOnMonitor(monitorNumber) - Destroys all GUIs on a specific monitor.
  36. * - DestroyAll() - Destroys all GUIs.
  37. * - Exist(tag) - Checks if a GUI with the specified tag exists and returns the unique ID (HWND) of the first matching GUI.
  38. * - SetDefaultTheme(theme) - Set a different theme as the default.
  39. ********************************************************************************************/
  40. Class Notify {
  41. /********************************************************************************************
  42. * @method Show(title, msg, image, sound, callback, options)
  43. * @description Builds and displays a notification GUI.
  44. * @param title Title
  45. * @param msg Message
  46. * @param image
  47. * - The path of an image.
  48. * - String: `'icon!'`, `'icon?'`, `'iconx'`, `'iconi'`
  49. * - Icon from dll. For example: `'C:\Windows\System32\imageres.dll|icon19'`
  50. * - Image Handle. For example: 'HICON:' hwnd
  51. * - Filename of image located in "Pictures\Notify".
  52. * - Supported file types: `.ico, .dll, .exe, .cpl, .png, .jpeg, .jpg, .gif, .bmp, .tif`
  53. * @param sound
  54. * - The path of the WAV file to be played.
  55. * - String: `'soundx'`, `'soundi'`
  56. * - Filename of WAV file located in "C:\Windows\Media" or "Music\Sounds". For example: `'Ding'`, `'tada'`, `'Windows Error'` etc.
  57. * - Use Notify Creator to view and play all available notification sounds.
  58. * @param callback Function object to call when left-clicking on the GUI.
  59. * @param options For example: `'POS=TL DUR=6 IW=70 TF=Impact TS=42 TC=GREEN MC=blue BC=Silver STYLE=edge SHOW=Fade Hide=Fade@250'`
  60. * - The string is case-insensitive.
  61. * - The asterisk (*) indicates the default option.
  62. * - `THEME` - Built-in themes and user-created themes.
  63. * - Use Notify Creator to view all themes and their visual appearance.
  64. * - `POS` - Position
  65. * - `BR` - Bottom right*
  66. * - `BC` - Bottom center
  67. * - `BL` - Bottom left
  68. * - `TL` - Top left
  69. * - `TC` - Top center
  70. * - `TR` - Top right
  71. * - `CT` - Center
  72. * - `CTL` - Center left
  73. * - `CTR` - Center right
  74. * - `Mouse` - Near the cursor.
  75. * - `DUR` - Display duration (in seconds). Set to 0 to keep it on the screen until left-clicking on the GUI or programmatically destroying it. `*8`
  76. * - `MON` - Monitor number to display the GUI. AutoHotkey monitor numbers differ from those in Windows Display settings or NVIDIA Control Panel.
  77. * - `Primary`* - The primary monitor.
  78. * - `Active` - The monitor on which the active window is displayed.
  79. * - `Mouse` - The monitor on which the mouse is currently positioned.
  80. * - `IW` - Image width - `*32` If only one dimension is specified, the other dimension will automatically adjust to preserve the aspect ratio.
  81. * - `IH` - Image height `*-1`
  82. * - `iw=0` or `ih=0` - Uses the actual dimensions of the image.
  83. * - `TF` - Title font `*Segoe UI`
  84. * - `TFO` - Title font options. `*Bold` For example: `tfo=underline italic strike`
  85. * - `TS` - Title size `*15`
  86. * - `TC` - Title color `*White`
  87. * - `TALI` - Title alignment
  88. * - `LEFT`*
  89. * - `RIGHT`
  90. * - `CENTER`
  91. * - `MF` - Message font `*Segoe UI`
  92. * - `MFO` - Message font options. For example: `mfo=underline italic strike`
  93. * - `MS` - Message size `*12`
  94. * - `MC` - Message color `*0xEAEAEA`
  95. * - `MALI` - Message alignment
  96. * - `LEFT`*
  97. * - `RIGHT`
  98. * - `CENTER`
  99. * - `PROG` - Progress bar. For example: `prog=1`, `prog=h40 cGreen`, `prog=w400`, {@link https://www.autohotkey.com/docs/v2/lib/GuiControls.htm#Progress Progress Options}
  100. * - `BC` - Background color `*0x1F1F1F`
  101. * - `STYLE` - Notification Appearance
  102. * - `ROUND` - Rounded corners*
  103. * - `EDGE` - Edged corners
  104. * - `BDR` - Border. For example: `bdr=Aqua`,`bdr=Red,4`
  105. * - The round style's maximum border width is limited to 1 pixel, while the edge style allows up to 5 pixels.
  106. * - If the theme includes a border and the style is set to edge, you can specify only the border width like this: `bdr=,3`
  107. * - `0` - No border
  108. * - `1` - Border
  109. * - `Default`
  110. * - `Color`
  111. * - `Color,border width` - Specify color and width, separated by a comma.
  112. * - `PAD` - Comma-separated values. Can range from 0 to 25. For example: `pad=0,0,15,15,5,10`, `pad=,10`
  113. * - If values aren’t specified, the default padding settings for the style will be set.
  114. * - PadX - Padding between the left or right edge of the GUI and the screen's edge.
  115. * - PadY - Padding between the top or bottom edge of the GUI and the screen's edge.
  116. * - GMX - Left/right margins of the GUI.
  117. * - GMY - Top/bottom margins of the GUI.
  118. * - SpX - Horizontal spacing between the right side of the image and other controls.
  119. * - SpY - Vertical spacing between the title, message, and progress bar.
  120. * - `MAXW` - Maximum width of the GUI (excluding image width and margins).
  121. * - `WSTC` - WinSetTransColor. Not compatible with the round style, fade animation. For example: `style=edge bdr=0 bc=black WSTC=black` {@link https://www.autohotkey.com/docs/v2/lib/WinSetTransColor.htm WinSetTransColor}
  122. * - `WSTP` - WinSetTransparent. Not compatible with the round style, fade animation. For example: `style=edge wstp=120` {@link https://www.autohotkey.com/docs/v2/lib/WinSetTransparent.htm WinSetTransparent}
  123. * - `SHOW` and `HIDE` - Animation when showing and destroying the GUI. The duration, which is optional, can range from 1 to 2500 milliseconds. For example: `STYLE=EDGE SHOW=SlideNorth HIDE=SlideSouth@250`
  124. * - The round style is not compatible with most animations. It renders only the fade-in (Show=fade) animation correctly. If the round style and fade-out (Hide=fade) are used, the corners become edged during the animation.
  125. * - If animations aren’t specified, the default animations for the style and position will be set.
  126. * - `None`
  127. * - `Fade`
  128. * - `Expand`
  129. * - `SlideEast`
  130. * - `SlideWest`
  131. * - `SlideNorth`
  132. * - `SlideSouth`
  133. * - `SlideNorthEast`
  134. * - `SlideNorthWest`
  135. * - `SlideSouthEast`
  136. * - `SlideSouthWest`
  137. * - `RollEast`
  138. * - `RollWest`
  139. * - `RollNorth`
  140. * - `RollSouth`
  141. * - `RollNorthEast`
  142. * - `RollNorthWest`
  143. * - `RollSouthEast`
  144. * - `RollSouthWest`
  145. * - `TAG` - Marker to identify a GUI. The Destroy method accepts a handle or a tag, it destroys every GUI containing this tag across all scripts.
  146. * - `DGC` - Destroy GUI click. Allow or prevent the GUI from being destroyed when clicked.
  147. * - `0` - Clicking on the GUI does not destroy it.
  148. * - `1` - Clicking on the GUI destroys it.*
  149. * - `DG` - Destroy GUIs before creating the new GUI.
  150. * - `0` - Do not destroy GUIs.*
  151. * - `1` - Destroy all GUIs on the monitor option at the position option.
  152. * - `2` - Destroy all GUIs on all monitors at the position option.
  153. * - `3` - Destroy all GUIs on the monitor option.
  154. * - `4` - Destroy all GUIs.
  155. * - `5` - Destroy all GUIs containing the tag. For example: `dg=5 tag=myTAG`
  156. * - `OPT` - Sets various options and styles for the appearance and behavior of the window. `*+Owner -Caption +AlwaysOnTop` {@link https://www.autohotkey.com/docs/v2/lib/Gui.htm#Opt GUI Opt}
  157. * @returns Map object
  158. ********************************************************************************************/
  159. static Show(title:='', msg:='', image:='', sound:='', callback:='', options:='') => this._Show(title, msg, image, sound, callback, options)
  160. static __New()
  161. {
  162. this.mNotifyGUIs := this.MapCI()
  163. this.mThemesStrings := this.MapCI().Set(
  164. 'Light', 'tc=Black mc=Black bc=White',
  165. 'Dark', 'tc=White mc=0xEAEAEA bc=0x1F1F1F',
  166. 'Matrix', 'tc=Lime mc=0x00FF7F bc=Black bdr=0x00FF7F tf=Consolas mf=Lucida Console',
  167. 'Cyberpunk', 'tc=0xFF005F mc=Aqua bc=0x0D0D0D bdr=Aqua tf=Consolas mf=Lucida Console',
  168. 'Cybernetic', 'tc=Aqua mc=0xFF005F bc=0x1A1A1A bdr=0xFF005F tf=Lucida Console mf=Consolas',
  169. 'Synthwave', 'tc=Fuchsia mc=Aqua bc=0x1A0E2F bdr=Aqua tf=Consolas mf=Arial',
  170. 'Dracula', 'tc=0xFF79C6 mc=0x8BE9FD bc=0x282A36 bdr=0x8BE9FD tf=Consolas mf=Arial',
  171. 'Monokai', 'tc=0xF8F8F2 mc=0xA6E22E bc=0x272822 bdr=0xE8F7C8 tf=Lucida Console mf=Tahoma',
  172. 'Solarized Dark', 'tc=0xB58900 mc=0x839496 bc=0x002B36 bdr=0x839496 tf=Consolas mf=Calibri',
  173. 'Atomic', 'tc=0xE49013 mc=0xDFCA9B bc=0x1F1F1F bdr=0xDFCA9B tf=Consolas mf=Lucida Console',
  174. 'PCB', 'tc=0xCCAA00 mc=0x00CC00 bc=0x002200 bdr=0x00CC00 tf=Consolas mf=Arial',
  175. 'Aurora', 'tc=0x47F0AC mc=0xEAEAEA bc=0x0C1631 bdr=0x47F0AC',
  176. 'Milky Way', 'tc=0x9370DB mc=0xE0E1DD bc=0x0D1B2A bdr=0xE0E1DD tf=Trebuchet MS mf=Calibri',
  177. 'Venom', 'tc=0xF9EA2C mc=0xFAF2A4 bc=0x317140 tf=Segoe UI mf=Segoe UI bdr=0x86EE99',
  178. 'Gator', 'tc=0xADFF2F mc=0xF0FFF0 bc=0x006400 bdr=0x7CCD7C',
  179. 'Forum', 'tc=0x3F5770 mc=0x272727 bc=0xDFDFDF bdr=0x686868',
  180. 'Cappuccino', 'tc=0x6F4E37 mc=0x886434 bc=0xFFF8DC bdr=0x886434 tf=Trebuchet MS mf=Times New Roman',
  181. 'Earthy', 'tc=0xF5FFFA mc=0x4DCA22 bc=0x3E2723 bdr=0x41A91D tf=Lucida Console mf=Arial',
  182. 'Rust', 'tc=0xFFC107 mc=0xF5DEB3 bc=0x8F3209 bdr=0xF5DEB3 tf=Georgia mf=Arial',
  183. 'Galactic', 'tc=0xFFD700 mc=White bc=Black bdr=White tf=Verdana mf=Arial',
  184. 'Steampunk', 'tc=0xFFD700 mc=0xB87333 bc=0x3E2723 bdr=0xFFD700 tf=Trebuchet MS mf=Times New Roman',
  185. 'Pastel', 'tc=0xFF69B4 mc=0x0072E3 bc=0xFFF0F5 bdr=0x0072E3 tf=Calibri',
  186. 'Nature', 'tc=0x2E8B57 mc=0x4A5E4A bc=0xE8F3E8 bdr=0x4A5E4A tf=Trebuchet MS',
  187. 'Pink Light', 'tc=0xFF1493 mc=0xFF69B4 bc=0xFFE4E1 bdr=0xFF69B4 tf=Comic Sans MS mf=Verdana',
  188. 'Pink Dark', 'tc=0xFF1493 mc=0xFF69B4 bc=0x1F1F1F bdr=0xFF69B4 tf=Comic Sans MS mf=Verdana',
  189. 'Sticky', 'tc=Black mc=0x333333 bc=0xF9E15B bdr=0x5F5103 tf=Arial mf=Verdana',
  190. 'OK', 'tc=Black mc=Black bc=0x49C149 bdr=0x336F50',
  191. 'OKLight', 'tc=0x52CB43 mc=Black bc=0xF1F8F4 bdr=0x52CB43',
  192. 'OKDark', 'tc=0x52CB43 mc=0xEAEAEA bc=0x1F1F1F bdr=0x52CB43 ',
  193. 'x', 'tc=White mc=0xEAEAEA bc=0xC61111 bdr=0xEAEAEA image=iconx',
  194. 'i', 'tc=White mc=0xEAEAEA bc=0x4682B4 bdr=0xEAEAEA image=iconi',
  195. '!', 'tc=Black mc=Black bc=0xFFD953 bdr=0x6F5600 image=icon!',
  196. '?', 'tc=White mc=0xEAEAEA bc=0x4682B4 bdr=0xEAEAEA image=icon?',
  197. 'xLight', 'tc=0xC61111 mc=Black bc=0xFBEFEB bdr=0xC61111 image=iconx',
  198. '!Light', 'tc=0xE1AA04 mc=Black bc=0xFEF8EB bdr=0xE1AA04 image=icon!',
  199. 'iLight', 'tc=0x2543AC mc=Black bc=0xE7EFFA bdr=0x2543AC image=iconi',
  200. '?Light', 'tc=0x2543AC mc=Black bc=0xE7EFFA bdr=0x2543AC image=icon?',
  201. 'xDark', 'tc=0xC61111 mc=0xEAEAEA bc=0x1F1F1F bdr=0xC61111 image=iconx',
  202. '!Dark', 'tc=0xDEA309 mc=0xEAEAEA bc=0x1F1F1F bdr=0xDEA309 image=icon!',
  203. 'iDark', 'tc=0x41A5EE mc=0xEAEAEA bc=0x1F1F1F bdr=0x41A5EE image=iconi',
  204. '?Dark', 'tc=0x41A5EE mc=0xEAEAEA bc=0x1F1F1F bdr=0x41A5EE image=icon?',
  205. )
  206. this.mDefaults := this.MapCI().Set(
  207. 'theme', 'Default',
  208. 'style', 'Round',
  209. 'mon', 'Primary', ; Monitor
  210. 'pos', 'BR', ; Position
  211. 'dur', 8, ; Duration
  212. 'iw', 32, ; Image width
  213. 'ih', -1, ; Image height
  214. 'tf', 'Segoe UI', ; Title font
  215. 'tfo', 'norm Bold', ; Title font options
  216. 'ts', 15, ; Title size
  217. 'tc', 'White', ; Title color
  218. 'tali', 'Left', ; Title alignment
  219. 'mf', 'Segoe UI', ; Message font
  220. 'mfo', 'norm', ; Message font options
  221. 'ms', 12, ; Message size
  222. 'mc', '0xEAEAEA', ; Message color
  223. 'mali', 'Left', ; Message alignment
  224. 'bc', '0x1F1F1F', ; Background color
  225. 'dg', 0, ; Destroy GUIs
  226. 'dgc', 1, ; Destroy GUI click
  227. 'bdr', 'Default', ; Border
  228. 'prog', '', ; Progress bar
  229. 'wstc', '', ; WinSetTransColor
  230. 'wstp', '', ; WinSetTransparent
  231. 'maxW', '', ; Maximum width
  232. 'tag', '', ; GUI window title identifying marker
  233. 'opt', '+Owner -Caption +AlwaysOnTop',
  234. 'image', 'None',
  235. 'sound', 'None',
  236. 'pad', ',,16,16,8,10'
  237. )
  238. this.padG := 10 ; Pad between GUIs
  239. this.bdrWdefaultEdge := 2
  240. this.arrBdrWrange := [1,5]
  241. this.arrPadRange := [0,25]
  242. this.ParsePadOption(this.mDefaults)
  243. this.arrFonts := Array()
  244. this.isTooManyFonts := false
  245. this.mAHKcolors := this.MapCI().Set(
  246. 'Black', '0x000000', 'Silver', '0xC0C0C0',
  247. 'Gray', '0x808080', 'White', '0xFFFFFF',
  248. 'Maroon', '0x800000', 'Red', '0xFF0000',
  249. 'Purple', '0x800080', 'Fuchsia','0xFF00FF',
  250. 'Green', '0x008000', 'Lime', '0x00FF00',
  251. 'Olive', '0x808000', 'Yellow', '0xFFFF00',
  252. 'Navy', '0x000080', 'Blue', '0x0000FF',
  253. 'Teal', '0x008080', 'Aqua', '0x00FFFF'
  254. )
  255. this.mAW := this.MapCI().Set(
  256. 'none', '',
  257. 'fade', '0x80000', ; AW_BLEND
  258. 'expand', '0x00010', ; AW_CENTER
  259. 'slideEast', '0x40001', ; AW_SLIDE | AW_HOR_POSITIVE
  260. 'slideWest', '0x40002', ; AW_SLIDE | AW_HOR_NEGATIVE
  261. 'slideNorth', '0x40008', ; AW_SLIDE | AW_VER_NEGATIVE
  262. 'slideSouth', '0x40004', ; AW_SLIDE | AW_VER_POSITIVE
  263. 'slideNorthEast', '0x40009', ; AW_SLIDE | AW_VER_NEGATIVE | AW_HOR_POSITIVE
  264. 'slideNorthWest', '0x4000A', ; AW_SLIDE | AW_VER_NEGATIVE | AW_HOR_NEGATIVE
  265. 'slideSouthEast', '0x40005', ; AW_SLIDE | AW_VER_POSITIVE | AW_HOR_POSITIVE
  266. 'slideSouthWest', '0x40006', ; AW_SLIDE | AW_VER_POSITIVE | AW_HOR_NEGATIVE
  267. 'rollEast', '0x00001', ; AW_HOR_POSITIVE
  268. 'rollWest', '0x00002', ; AW_HOR_NEGATIVE
  269. 'rollNorth', '0x00008', ; AW_VER_NEGATIVE
  270. 'rollSouth', '0x00004', ; AW_VER_POSITIVE
  271. 'rollNorthEast', '0x00009', ; ROLL_DIAG_BL_TO_TR
  272. 'rollNorthWest', '0x0000a', ; ROLL_DIAG_BR_TO_TL
  273. 'rollSouthEast', '0x00005', ; ROLL_DIAG_TL_TO_BR
  274. 'rollSouthWest', '0x00006' ; ROLL_DIAG_TR_TO_BL
  275. )
  276. this.pathImagesFolder := RegRead('HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders', 'My Pictures') '\Notify'
  277. this.arrImageExt := ['ico', 'dll', 'exe', 'cpl', 'png', 'jpeg', 'jpg', 'gif', 'bmp', 'tif']
  278. this.strImageExt := this.ArrayToString(this.arrImageExt, '|')
  279. this.mImages := this.MapCI().Set('icon!', 2, 'icon?', 3, 'iconx', 4, 'iconi', 5)
  280. Loop Files this.pathImagesFolder '\*.*'
  281. if RegExMatch(A_LoopFileExt, 'i)^(' this.strImageExt ')$')
  282. SplitPath(A_LoopFilePath,,,, &fileName), this.mImages[fileName] := A_LoopFilePath
  283. ;==============================================
  284. this.pathSoundsFolder := RegRead('HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders', 'My Music') '\Sounds'
  285. this.mSounds := this.MapCI().Set('soundx', '*16', 'soundi', '*64')
  286. for path in [A_WinDir '\Media', this.pathSoundsFolder]
  287. Loop Files path '\*.wav'
  288. SplitPath(A_LoopFilePath,,,, &fileName), this.mSounds[fileName] := A_LoopFilePath
  289. ;==============================================
  290. this.mThemes := this.MapCI()
  291. for theme, str in this.mThemesStrings
  292. this.OptionsStringToMap(this.mThemes[theme] := this.MapCI(), str)
  293. ;==============================================
  294. this.mOrig_mDefaults := this.MapCI()
  295. for key, value in this.mDefaults
  296. this.mOrig_mDefaults[key] := value
  297. ;==============================================
  298. this.mOrig_mThemes := this.MapCI()
  299. for theme, mTheme in this.mThemes {
  300. this.mOrig_mThemes[theme] := this.MapCI()
  301. for key, value in mTheme
  302. this.mOrig_mThemes[theme][key] := value
  303. }
  304. ;==============================================
  305. sourceFile := A_IsCompiled ? A_ScriptFullPath : A_LineFile
  306. SplitPath(sourceFile,, &pathDir)
  307. if (FileExist(pathDir '\Preferences.json')) {
  308. objFile := FileOpen(pathDir '\Preferences.json', 'r', 'UTF-8')
  309. mJSON := _JSON_thqby.parse(objFile.Read(), keepbooltype := false, as_map := true)
  310. objFile.Close()
  311. if (mJSON.Has('mDefaults')) {
  312. this.mThemes['Default'] := this.MapCI()
  313. for key, value in mJSON['mDefaults']
  314. this.mThemes['Default'][key] := value
  315. for key, value in this.mDefaults
  316. if this.mThemes['Default'].Has(key)
  317. this.mDefaults[key] := this.mThemes['Default'][key]
  318. for key in ['padX', 'padY', 'gmX', 'gmY', 'spX', 'spY']
  319. if this.mDefaults.Has(key)
  320. this.mDefaults.Delete(key)
  321. this.ParsePadOption(this.mDefaults)
  322. }
  323. if (mJSON.Has('mThemes')) {
  324. for key, value in mJSON['mThemes'] {
  325. this.mThemes[key] := this.MapCI()
  326. for k, v in value
  327. this.mThemes[key][k] := v
  328. }
  329. }
  330. }
  331. ;==============================================
  332. this.mThemes['Default'] := this.MapCI()
  333. if !this.mThemes.Has(this.mDefaults['theme'])
  334. this.mDefaults['theme'] := 'Default'
  335. ;==============================================
  336. for value in ['mThemes', 'mOrig_mThemes'] {
  337. for theme, mTheme in this.%value% {
  338. if (theme != 'default') {
  339. arrKeyDefined := Array()
  340. for key, v in mTheme
  341. arrKeyDefined.Push(key)
  342. mTheme['arrKeyDefined'] := arrKeyDefined
  343. }
  344. }
  345. }
  346. ;==============================================
  347. for theme, mTheme in this.mThemes {
  348. this.ParseBorderOption(mTheme)
  349. for value in ['show', 'hide']
  350. if mtheme.Has(value)
  351. mtheme.Delete(value)
  352. }
  353. }
  354. ;============================================================================================
  355. static _Show(title:='', msg:='', image:='', sound:='', callback:='', options:='')
  356. {
  357. static gIndex := 0
  358. this.OptionsStringToMap(m := this.MapCI(), options)
  359. if !m.Has('theme') || !this.mThemes.Has(m['theme'])
  360. m['theme'] := this.mDefaults['theme']
  361. this.SetThemeSettings(m, this.mThemes[m['theme']])
  362. this.SetDefault_MiscValues(m)
  363. this.ParseAnimationOption(m)
  364. this.SetAnimationDefault(m)
  365. this.ParsePadOption(m)
  366. this.SetPadDefault(m)
  367. this.ParseBorderOption(m)
  368. this.SetBorderOption(m)
  369. if image
  370. m['image'] := image
  371. if sound
  372. m['sound'] := sound
  373. if (!title && !msg && (m['image'] = '' || m['image'] = 'none'))
  374. return
  375. ;==============================================
  376. switch {
  377. case (m['mon'] = 'mouse' || m['pos'] = 'mouse'): m['mon'] := this.MonitorGetMouseIsIn()
  378. case m['mon'] = 'active': m['mon'] := this.MonitorGetWindowIsIn('A')
  379. case (m['mon'] = 'primary' || m['mon'] < 1 || m['mon'] > MonitorGetCount()) : m['mon'] := MonitorGetPrimary()
  380. }
  381. switch m['dg'] {
  382. case 1: this.DestroyAllOnMonitorAtPosition(m['mon'], m['pos'])
  383. case 2: this.DestroyAllOnAllMonitorAtPosition(m['pos'])
  384. case 3: this.DestroyAllOnMonitor(m['mon'])
  385. case 4: this.DestroyAll()
  386. case 5: m['tag'] && this.Destroy(m['tag'])
  387. }
  388. ;==============================================
  389. g := Gui(m['opt'], 'NotifyGUI_' m['mon'] '_' m['pos'] '_' m['style'] '_' m['bdrC'] '_' m['bdrW'] '_' m['padY'] '_' A_Now A_MSec (m['tag'] && '_' m['tag']))
  390. g.BackColor := m['bc']
  391. g.MarginX := m['gmX'] + m['bdrW']
  392. g.MarginY := m['gmY'] + m['bdrW']
  393. g.gIndex := ++gIndex
  394. m['hwnd'] := g.handle := g.hwnd
  395. for value in ['pos', 'mon', 'hideHex', 'hideDur', 'tag']
  396. g.%value% := m[value]
  397. ;==============================================
  398. switch {
  399. case RegExMatch(m['image'], 'i)^(icon!|icon\?|iconx|iconi)$'):
  400. try m['pic'] := g.Add('Picture', 'w' m['iw'] ' h' m['ih'] ' Icon' this.mImages[m['image']], A_WinDir '\system32\user32.dll')
  401. case this.mImages.Has(m['image']) && FileExist(this.mImages[m['image']]):
  402. try m['pic'] := g.Add('Picture', 'w' m['iw'] ' h' m['ih'], this.mImages[m['image']])
  403. case RegExMatch(m['image'], 'i)^(.+?\.(?:dll|exe|cpl))\|icon(\d+)$', &matchIcon) && FileExist(matchIcon[1]):
  404. try m['pic'] := g.Add('Picture', 'w' m['iw'] ' h' m['ih'] ' Icon' matchIcon[2], matchIcon[1])
  405. case FileExist(m['image']) || RegExMatch(m['image'], 'i)^h(icon|bitmap).*\d+'):
  406. try m['pic'] := g.Add('Picture', 'w' m['iw'] ' h' m['ih'], m['image'])
  407. }
  408. ;==============================================
  409. MonitorGetWorkArea(m['mon'], &monWALeft, &monWATop, &monWARight, &monWABottom)
  410. monWAwidth := Abs(monWARight - monWALeft)
  411. monWAheight := Abs(monWABottom - monWATop)
  412. visibleScreenWidth := monWAwidth / (A_ScreenDPI / 96)
  413. if m.Has('pic')
  414. picWidth := this.GetPicWidth(m['pic'], monWALeft, monWATop) + m['spX'] + g.MarginX*2
  415. if title
  416. titleCtrlW := this.GetTextWidth(title, m['tf'], m['ts'], m['tfo'], monWALeft, monWATop)
  417. if msg
  418. msgCtrlW := this.GetTextWidth(msg, m['mf'], m['ms'], m['mfo'], monWALeft, monWATop)
  419. if title && (titleCtrlW + (picWidth ?? g.MarginX*2)) > visibleScreenWidth
  420. titleWidth := visibleScreenWidth - m['padX']*2 - (picWidth ?? g.MarginX*2)
  421. if msg && (msgCtrlW + (picWidth ?? g.MarginX*2)) > visibleScreenWidth
  422. msgWidth := visibleScreenWidth - m['padX']*2 - (picWidth ?? g.MarginX*2)
  423. if m['prog'] && RegExMatch(m['prog'], 'i)\bw(\d+)\b', &match_width)
  424. progUserW := match_width[1]
  425. if (m['prog'] && IsSet(progUserW)) && ((progUserW + (picWidth ?? g.MarginX*2)) > (visibleScreenWidth))
  426. progWidth := visibleScreenWidth - m['padX']*2 - (picWidth ?? g.MarginX*2)
  427. maxWidth := Max(
  428. (title ? (titleWidth ?? titleCtrlW ?? 0) : 0),
  429. (msg ? (msgWidth ?? msgCtrlW ?? 0) : 0),
  430. (m['prog'] ? (progWidth ?? progUserW ?? 0) : 0)
  431. )
  432. if m['maxW'] && (m['maxW'] < maxWidth)
  433. maxWidth := m['maxW']
  434. ;==============================================
  435. if (title) {
  436. this.SetFont(g, 's' m['ts'] ' c' m['tc'] ' ' m['tfo'], m['tf'])
  437. m['title'] := g.Add('Text', m['tali'] (IsSet(picWidth) ? ' x+' m['spX'] : '') ' w' maxWidth, title)
  438. }
  439. if (m['prog']) {
  440. switch {
  441. case !IsSet(progUserW): m['prog'] := m['prog'] ' w' maxWidth
  442. case IsSet(progUserW): m['prog'] := progUserW > maxWidth ? RegExReplace(m['prog'], 'w\d+', 'w' maxWidth) : m['prog']
  443. }
  444. g.MarginY := title ? m['spY'] : m['gmY'] + m['bdrW']
  445. m['prog'] := g.Add('Progress', (!title && IsSet(picWidth) ? ' x+' m['spX'] : '') ' ' m['prog'])
  446. }
  447. if (msg) {
  448. g.MarginY := title || m['prog'] ? m['spY'] : m['gmY'] + m['bdrW']
  449. this.SetFont(g, 's' m['ms'] ' c' m['mc'] ' ' m['mfo'], m['mf'])
  450. m['msg'] := g.Add('Text', m['mali'] ((!title && !m['prog']) && IsSet(picWidth) ? ' x+' m['spX'] : '') ' w' maxWidth, msg)
  451. }
  452. g.MarginY := m['gmY'] + m['bdrW']
  453. g.Show('Hide')
  454. WinGetPos(,, &gW, &gH, g)
  455. clickArea := g.Add('Text', 'x0 y0 w' gW ' h' gH ' BackgroundTrans')
  456. if callback
  457. clickArea.OnEvent('Click', callback)
  458. if m['dgc']
  459. clickArea.OnEvent('Click', this.gDestroy.Bind(this, g, 'clickArea'))
  460. g.OnEvent('Close', this.gDestroy.Bind(this, g, 'close'))
  461. g.boundFuncTimer := this.gDestroy.Bind(this, g, 'timer')
  462. if m['sound']
  463. this.Sound(m['sound'])
  464. ;==============================================
  465. switch m['pos'], false {
  466. case 'br', 'bc', 'bl': minMaxPosY := monWABottom
  467. case 'tr', 'tc', 'tl', 'ct', 'ctl', 'ctr': minMaxPosY := monWATop
  468. }
  469. mDhwTmm := this.Set_DHWindows_TMMode(0, 'RegEx')
  470. for id in WinGetList('i)^NotifyGUI_' m['mon'] '_' m['pos'] '_ ahk_class AutoHotkeyGUI') {
  471. try {
  472. WinGetPos(, &guiY,, &guiH, 'ahk_id ' id)
  473. switch m['pos'], false {
  474. case 'br', 'bc', 'bl': minMaxPosY := Min(minMaxPosY, guiY)
  475. case 'tr', 'tc', 'tl', 'ct', 'ctl', 'ctr': minMaxPosY := Max(minMaxPosY, guiY + guiH)
  476. }
  477. } catch
  478. break
  479. }
  480. this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev'])
  481. switch m['pos'], false {
  482. case 'br': gPos := 'x' monWARight - gW - m['padX'] ' y' ((minMaxPosY = monWABottom) ? monWABottom - gH - m['padY'] : minMaxPosY - gH - this.padG)
  483. case 'bc': gPos := 'x' monWARight - monWAwidth/2 - gW/2 ' y' ((minMaxPosY = monWABottom) ? monWABottom - gH - m['padY'] : minMaxPosY - gH - this.padG)
  484. case 'bl': gPos := 'x' monWALeft + m['padX'] ' y' ((minMaxPosY = monWABottom) ? monWABottom - gH - m['padY'] : minMaxPosY - gH - this.padG)
  485. case 'tl': gPos := 'x' monWALeft + m['padX'] ' y' ((minMaxPosY = monWATop) ? monWATop + m['padY'] : minMaxPosY + this.padG)
  486. case 'tc': gPos := 'x' monWARight - monWAwidth/2 - gW/2 ' y' ((minMaxPosY = monWATop) ? monWATop + m['padY'] : minMaxPosY + this.padG)
  487. case 'tr': gPos := 'x' monWARight - m['padX'] - gW ' y' ((minMaxPosY = monWATop) ? monWATop + m['padY'] : minMaxPosY + this.padG)
  488. case 'ct': gPos := 'x' monWARight - monWAwidth/2 - gW/2 ' y' ((minMaxPosY = monWATop) ? monWATop + monWAheight/2 - gH/2 : minMaxPosY + this.padG)
  489. case 'ctl': gPos := 'x' monWALeft + m['padX'] ' y' ((minMaxPosY = monWATop) ? monWATop + monWAheight/2 - gH/2 : minMaxPosY + this.padG)
  490. case 'ctr': gPos := 'x' monWARight - m['padX'] - gW ' y' ((minMaxPosY = monWATop) ? monWATop + monWAheight/2 - gH/2 : minMaxPosY + this.padG)
  491. case 'mouse': gPos := this.CalculatePopupWindowPosition(g.hwnd)
  492. }
  493. switch g.pos, false {
  494. case 'br', 'bc', 'bl': outOfWorkArea := (minMaxPosY < (monWATop + gH + this.padG))
  495. case 'tr', 'tc', 'tl', 'ct', 'ctl', 'ctr': outOfWorkArea := (minMaxPosY > (monWABottom - gH - this.padG))
  496. case 'mouse': outOfWorkArea := false
  497. }
  498. ;==============================================
  499. this.mNotifyGUIs[gIndex] := g
  500. switch m['style'], false {
  501. case 'round': this.FrameShadow(g.hwnd), !RegExMatch(m['bdrC'], 'i)^(default|1|0)$') && this.DrawBorderRound(g.hwnd, m['bdrC'])
  502. case 'edge': RegExMatch(m['bdrC'], 'i)^(default|1)$') && g.Opt('+Border')
  503. }
  504. if m['wstp'] || m['wstp'] = 0
  505. WinSetTransparent(m['wstp'], g)
  506. if m['wstc']
  507. WinSetTransColor(m['wstc'], g)
  508. if m['showHex']
  509. g.Show(gPos ' NoActivate Hide'), DllCall('AnimateWindow', 'Ptr', g.hwnd, 'Int', m['showDur'], 'Int', m['showHex'])
  510. else
  511. g.Show(gPos ' NoActivate')
  512. if m['style'] = 'edge' && !RegExMatch(m['bdrC'], 'i)^(default|1|0)$')
  513. try this.DrawBorderEdge(g.Hwnd, m['bdrC'], m['bdrW'])
  514. if m['dur']
  515. SetTimer(g.boundFuncTimer, - ((m['dur'] + (outOfWorkArea ? 8 : 0)) * 1000))
  516. return m
  517. }
  518. ;============================================================================================
  519. static gDestroy(g, fromMethod:='', *)
  520. {
  521. SetTimer(g.boundFuncTimer, 0)
  522. if g.hideHex && !RegExMatch(fromMethod, 'i)^(destroy|close)')
  523. try DllCall('AnimateWindow', 'Ptr', g.hwnd, 'Int', g.hideDur, 'Int', Format("{:#X}", g.hideHex + 0x10000))
  524. g.Destroy()
  525. if this.mNotifyGUIs.Has(g.gIndex)
  526. this.mNotifyGUIs.Delete(g.gIndex)
  527. ;==============================================
  528. Sleep(25)
  529. arrGUIs := Array()
  530. mDhwTmm := this.Set_DHWindows_TMMode(0, 'RegEx')
  531. for id in WinGetList('i)^NotifyGUI_' g.mon '_' g.pos '_ ahk_class AutoHotkeyGUI') {
  532. try {
  533. WinGetPos(, &gY,, &gH, 'ahk_id ' id)
  534. RegExMatch(WinGetTitle('ahk_id ' id), 'i)^NotifyGUI_\d+_([a-z]+)_([a-z]+)_(\w+)_(\d+)_(\d+)_\d+', &match)
  535. if match[1] = 'mouse'
  536. continue
  537. arrGUIs.Push(this.MapCI().Set('gY', gY, 'gH', gH, 'id', id, 'style', match[2], 'bdrC', match[3], 'bdrW', match[4], 'padY', match[5]))
  538. } catch {
  539. arrGUIs := Array()
  540. break
  541. }
  542. }
  543. if (arrGUIs.Length) {
  544. try MonitorGetWorkArea(g.mon,, &monWATop,, &monWABottom)
  545. catch {
  546. this.RedrawAllBorderEdge()
  547. this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev'])
  548. return
  549. }
  550. monWAheight := Abs(monWABottom - monWATop)
  551. SetWinDelay(0)
  552. switch g.pos, false {
  553. case 'br', 'bc', 'bl': arrGUIs := this.SortArrayGUIPosY(arrGUIs, true), posY := monWABottom - arrGUIs[1]['padY']
  554. case 'tr', 'tc', 'tl', 'ct', 'ctl', 'ctr': arrGUIs := this.SortArrayGUIPosY(arrGUIs), posY := monWATop + arrGUIs[1]['padY']
  555. }
  556. for value in arrGUIs {
  557. switch g.pos, false{
  558. case 'br', 'bc', 'bl': posY -= value['gH']
  559. case 'ct', 'ctl', 'ctr': (A_Index = 1 && posY := monWATop + monWAheight/2 - value['gH']/2)
  560. }
  561. if (Abs(posY - value['gY']) > 10) {
  562. try {
  563. WinMove(, posY,,, 'ahk_id ' value['id'])
  564. this.ReDrawBorderEdge(value['id'], value['style'], value['bdrC'], value['bdrW'])
  565. }
  566. catch
  567. break
  568. }
  569. switch g.pos, false {
  570. case 'br', 'bc', 'bl': posY -= this.padG
  571. case 'tr', 'tc', 'tl', 'ct', 'ctl', 'ctr': posY += value['gH'] + this.padG
  572. }
  573. }
  574. }
  575. this.RedrawAllBorderEdge()
  576. this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev'])
  577. }
  578. /********************************************************************************************
  579. * Destroys GUIs.
  580. * @param {integer|string} param
  581. * - Window handle (hwnd) - Destroys the GUI with the specified window handle.
  582. * - Tag - destroys every GUI containing this tag across all scripts.
  583. * - 'oldest' or no param - Destroys the oldest GUI.
  584. * - 'latest' - Destroys the most recent GUI.
  585. */
  586. static Destroy(param:='')
  587. {
  588. mDhwTmm := this.Set_DHWindows_TMMode(0, A_TitleMatchMode)
  589. SetWinDelay(25)
  590. if (WinExist('ahk_id ' param)) {
  591. for gIndex, value in this.mNotifyGUIs.Clone() {
  592. if (param = value.handle && this.mNotifyGUIs.Has(gIndex)) {
  593. this.gDestroy(this.mNotifyGUIs[gIndex], 'destroy')
  594. break
  595. }
  596. }
  597. SetTitleMatchMode(1)
  598. for id in WinGetList('NotifyGUI_ ahk_class AutoHotkeyGUI') {
  599. if (param = id) {
  600. try WinClose('ahk_id ' id)
  601. break
  602. }
  603. }
  604. }
  605. ;==============================================
  606. if (param) {
  607. for gIndex, value in this.mNotifyGUIs.Clone()
  608. if param = value.tag && this.mNotifyGUIs.Has(gIndex)
  609. this.gDestroy(this.mNotifyGUIs[gIndex], 'destroy')
  610. SetTitleMatchMode('RegEx')
  611. for id in WinGetList('i)^NotifyGUI_\d+_[a-z]+_[a-z]+_\w+_\d+_\d+_\d+_\Q' param '\E$ ahk_class AutoHotkeyGUI')
  612. try WinClose('ahk_id ' id)
  613. }
  614. ;==============================================
  615. if (param = 'oldest' || param = 'latest' || param = '') {
  616. m := Map()
  617. SetTitleMatchMode(1)
  618. for id in WinGetList('NotifyGUI_ ahk_class AutoHotkeyGUI') {
  619. try {
  620. RegExMatch(WinGetTitle('ahk_id ' id), 'i)^NotifyGUI_\d+_[a-z]+_[a-z]+_\w+_\d+_\d+_(\d+)', &match)
  621. m[match[1]] := id
  622. }
  623. }
  624. if (param = 'latest') {
  625. for timestamp, id in m
  626. destroyId := id
  627. } else {
  628. for timestamp, id in m {
  629. destroyId := id
  630. break
  631. }
  632. }
  633. if IsSet(destroyId)
  634. try WinClose('ahk_id ' destroyId)
  635. }
  636. ;==============================================
  637. this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev'])
  638. }
  639. ;============================================================================================
  640. static DestroyAllOnMonitorAtPosition(monNum, position)
  641. {
  642. for gIndex, value in this.mNotifyGUIs.Clone()
  643. if value.mon = monNum && value.pos = position && this.mNotifyGUIs.Has(gIndex)
  644. this.gDestroy(this.mNotifyGUIs[gIndex], 'destroyAllOnMonitorAtPosition')
  645. this.WinGetList_WinClose('i)^NotifyGUI_' monNum '_' position '_ ahk_class AutoHotkeyGUI', 0, 'RegEx')
  646. }
  647. ;============================================================================================
  648. static DestroyAllOnAllMonitorAtPosition(position)
  649. {
  650. for gIndex, value in this.mNotifyGUIs.Clone()
  651. if value.pos = position && this.mNotifyGUIs.Has(gIndex)
  652. this.gDestroy(this.mNotifyGUIs[gIndex], 'destroyAllOnAllMonitorAtPosition')
  653. this.WinGetList_WinClose('i)^NotifyGUI_\d+_' position '_ ahk_class AutoHotkeyGUI', 0, 'RegEx')
  654. }
  655. ;============================================================================================
  656. static DestroyAllOnMonitor(monNum)
  657. {
  658. for gIndex, value in this.mNotifyGUIs.Clone()
  659. if value.mon = monNum && this.mNotifyGUIs.Has(gIndex)
  660. this.gDestroy(this.mNotifyGUIs[gIndex], 'destroyAllOnMonitor')
  661. this.WinGetList_WinClose('i)NotifyGUI_' monNum '_ ahk_class AutoHotkeyGUI', 0, 'RegEx')
  662. }
  663. ;============================================================================================
  664. static DestroyAll()
  665. {
  666. for gIndex, value in this.mNotifyGUIs.Clone()
  667. if this.mNotifyGUIs.Has(gIndex)
  668. this.gDestroy(this.mNotifyGUIs[gIndex], 'destroyAll')
  669. this.WinGetList_WinClose('NotifyGUI_ ahk_class AutoHotkeyGUI', 0, 1)
  670. }
  671. ;============================================================================================
  672. static WinGetList_WinClose(winTitle, dhWindows, tmMode)
  673. {
  674. mDhwTmm := this.Set_DHWindows_TMMode(dhWindows, tmMode)
  675. SetWinDelay(25)
  676. for id in WinGetList(winTitle)
  677. try WinClose('ahk_id ' id)
  678. this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev'])
  679. }
  680. ;============================================================================================
  681. static Exist(tag)
  682. {
  683. mDhwTmm := this.Set_DHWindows_TMMode(0, 'RegEx')
  684. for id in WinGetList('i)^NotifyGUI_\d+_[a-z]+_[a-z]+_\w+_\d+_\d+_\d+_\Q' tag '\E$ ahk_class AutoHotkeyGUI') {
  685. idFound := id
  686. break
  687. }
  688. this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev'])
  689. return idFound ?? 0
  690. }
  691. ;============================================================================================
  692. static SetDefaultTheme(theme:='')
  693. {
  694. switch {
  695. case this.mThemes.Has(theme): this.mDefaults['theme'] := theme
  696. case !theme: this.mDefaults['theme'] := 'default'
  697. }
  698. }
  699. ;============================================================================================
  700. static SetDefault_MiscValues(m)
  701. {
  702. switch {
  703. case (m.has('iw') && !m.has('ih')) : m['ih'] := -1
  704. case (m.has('ih') && !m.has('iw')) : m['iw'] := -1
  705. }
  706. for key, value in this.mDefaults
  707. if !m.has(key)
  708. m[key] := value
  709. if !RegExMatch(m['style'], 'i)^(round|edge)$')
  710. m['style'] := this.mDefaults['style']
  711. for value in ['tfo', 'mfo'] {
  712. m['arr' value] := Array()
  713. for v in ['bold', 'italic', 'strike', 'underline']
  714. if InStr(m[value], v)
  715. m['arr' value].Push(v)
  716. if !InStr(m[value], 'norm')
  717. m[value] := Trim('norm ' m[value])
  718. }
  719. }
  720. ;============================================================================================
  721. static ParseAnimationOption(m)
  722. {
  723. for value in ['show', 'hide'] {
  724. if (m.has(value)) {
  725. arrAnim := StrSplit(m[value], '@', A_Space)
  726. m[value 'Hex'] := this.mAW[arrAnim[1] = 0 ? 'none' : arrAnim[1]]
  727. arrAnim.Has(2) && (m[value 'Dur'] := Min(2500, Max(1, integer(arrAnim[2]))))
  728. }
  729. }
  730. }
  731. ;============================================================================================
  732. static SetAnimationDefault(m)
  733. {
  734. switch m['style'], false {
  735. case 'edge':
  736. {
  737. if !m.has('showHex') {
  738. switch m['pos'], false {
  739. case 'br', 'tr', 'ctr': m['showHex'] := this.mAW['slideWest']
  740. case 'bl', 'tl', 'ctl': m['showHex'] := this.mAW['slideEast']
  741. case 'bc': m['showHex'] := this.mAW['slideNorth']
  742. case 'tc': m['showHex'] := this.mAW['slideSouth']
  743. case 'ct': m['showHex'] := this.mAW['expand']
  744. case 'mouse': m['showHex'] := this.mAW['none']
  745. }
  746. }
  747. if !m.has('hideHex') {
  748. switch m['pos'], false {
  749. case 'br', 'tr', 'ctr': m['hideHex'] := this.mAW['slideEast']
  750. case 'bl', 'tl', 'ctl': m['hideHex'] := this.mAW['slideWest']
  751. case 'bc': m['hideHex'] := this.mAW['slideSouth']
  752. case 'tc': m['hideHex'] := this.mAW['slideNorth']
  753. case 'ct': m['hideHex'] := this.mAW['expand']
  754. case 'mouse': m['hideHex'] := this.mAW['none']
  755. }
  756. }
  757. m['showDur'] := m.Get('showDur', 75)
  758. m['hideDur'] := m.Get('hideDur', 100)
  759. }
  760. case 'round':
  761. {
  762. m['showHex'] := m.Get('showHex', this.mAW['fade'])
  763. m['hideHex'] := m.Get('hideHex', this.mAW['none'])
  764. m['showDur'] := m.Get('showDur', 1)
  765. m['hideDur'] := m.Get('hideDur', 1)
  766. }
  767. }
  768. }
  769. ;============================================================================================
  770. static ParsePadOption(m)
  771. {
  772. if (m.has('pad')) {
  773. arrPad := StrSplit(m['pad'], ',', A_Space)
  774. for index, value in ['padX', 'padY', 'gmX', 'gmY', 'spX', 'spY']
  775. if arrPad.Has(index) && arrPad[index] != ''
  776. m[value] := Min(this.arrPadRange[2], Max(this.arrPadRange[1], Integer(arrPad[index])))
  777. }
  778. }
  779. ;============================================================================================
  780. static SetPadDefault(m)
  781. {
  782. m['padX'] := m.Has('padX') ? m['padX'] : (m['style'] = 'edge' ? 0 : 10)
  783. m['padY'] := m.Has('padY') ? m['padY'] : (m['style'] = 'edge' ? 0 : 10)
  784. for key in ['gmX', 'gmY', 'spX', 'spY']
  785. if !m.has(key)
  786. m[key] := this.mDefaults[key]
  787. }
  788. ;============================================================================================
  789. static ParseBorderOption(m)
  790. {
  791. if (m.has('bdr')) {
  792. arrBdr := StrSplit(m['bdr'], ',', A_Space)
  793. m['bdr'] := this.NormAHKColor(m['bdr'])
  794. m['bdrC'] := this.NormAHKColor(arrBdr[1])
  795. arrBdr.Has(2) && (m['bdrW'] := this.SetValidBorderWidth(arrBdr[2]))
  796. }
  797. }
  798. ;============================================================================================
  799. static SetBorderOption(m)
  800. {
  801. mTheme := this.mThemes.Has(m['theme']) ? this.mThemes[m['theme']] : this.MapCI()
  802. switch {
  803. case m['bdr'] = 0: m['bdrC'] := 0
  804. case m.Has('bdrC') && m['bdrC']: m['bdrC'] := m['bdrC']
  805. case (!m.Has('bdrC') || !m['bdrC']) && mTheme.Has('bdrC'): m['bdrC'] := mTheme['bdrC']
  806. case (!m.Has('bdrC') || !m['bdrC']) && !mTheme.Has('bdrC'): m['bdrC'] := 'default'
  807. }
  808. switch {
  809. case RegExMatch(m['bdrC'], 'i)^(default|1|0)$'): m['bdrW'] := 0
  810. case !m.Has('bdrW'): m['bdrW'] := (m['style'] = 'edge' ? this.bdrWdefaultEdge : 0)
  811. case m.Has('bdrW'):
  812. switch m['style'], false {
  813. case 'edge': m['bdrW'] := this.SetValidBorderWidth(m['bdrW'])
  814. case 'round': m['bdrW'] := 0
  815. }
  816. }
  817. }
  818. ;============================================================================================
  819. static SetValidBorderWidth(width) => Min(this.arrBdrWrange[2], Max(this.arrBdrWrange[1], Integer(width)))
  820. ;============================================================================================
  821. static SetFont(g, options, fontName)
  822. {
  823. g.SetFont(options, fontName)
  824. if !this.HasVal(strFont := options ' ' fontName, this.arrFonts)
  825. this.arrFonts.Push(strFont)
  826. if (this.arrFonts.Length >= 190) {
  827. this.isTooManyFonts := true
  828. return false
  829. }
  830. return true
  831. }
  832. ;============================================================================================
  833. static Set_DHWindows_TMMode(dhw, tmm)
  834. {
  835. dhwPrev := A_DetectHiddenWindows
  836. tmmPrev := A_TitleMatchMode
  837. DetectHiddenWindows(dhw)
  838. SetTitleMatchMode(tmm)
  839. return Map('dhwPrev', dhwPrev, 'tmmPrev', tmmPrev)
  840. }
  841. ;============================================================================================
  842. static Sound(sound)
  843. {
  844. if this.mSounds.Has(sound)
  845. sound := this.mSounds[sound]
  846. if FileExist(sound) || RegExMatch(sound,'^\*\-?\d+')
  847. try Soundplay(sound)
  848. }
  849. ;============================================================================================
  850. static OptionsStringToMap(m, haystack)
  851. {
  852. pos := 1
  853. while (pos := RegExMatch(haystack, 'i)(\w+)\s*=\s*(.*?)\s*(?=\s*\w+\s*=|$)', &match, pos))
  854. m[match[1]] := match[2], pos += StrLen(match[0])
  855. }
  856. ;============================================================================================
  857. static SetThemeSettings(m, mTheme)
  858. {
  859. if (m['theme'] != 'default')
  860. for key in mTheme['arrKeyDefined']
  861. if !m.Has(key)
  862. m[key] := mTheme[key]
  863. }
  864. ;============================================================================================
  865. static GetTextWidth(str:='', font:='', fontSize:='', fontOption:='', monWALeft:='', monWATop:='')
  866. {
  867. g := Gui()
  868. g.SetFont('s' fontSize ' ' fontOption, font)
  869. g.txt := g.Add('Text',, str)
  870. g.Show('x' monWALeft ' y' monWATop ' Hide')
  871. g.txt.GetPos(,, &ctrlWidth)
  872. g.Destroy()
  873. return ctrlWidth
  874. }
  875. ;============================================================================================
  876. static GetPicWidth(picCtrl, monWALeft:='', monWATop:='')
  877. {
  878. g := Gui()
  879. g.pic := picCtrl
  880. g.Show('x' monWALeft ' y' monWATop ' Hide')
  881. g.pic.GetPos(,, &ctrlWidth)
  882. g.Destroy()
  883. return ctrlWidth
  884. }
  885. ;============================================================================================
  886. static SortArrayGUIPosY(arr, sortReverse := false)
  887. {
  888. for value in arr
  889. listValueY .= value['gY'] ','
  890. listSortValueY := Sort(RTrim(listValueY, ','), (sortReverse ? 'RN' : 'N') ' D,')
  891. sortArray := Array()
  892. for value in StrSplit(listSortValueY, ',')
  893. for v in arr
  894. if v['gY'] = value
  895. sortArray.Push(v)
  896. return sortArray
  897. }
  898. ;=============================================================================================
  899. static HasVal(needle, haystack, caseSensitive := false)
  900. {
  901. for index, value in haystack
  902. if (caseSensitive && value == needle) || (!caseSensitive && value = needle)
  903. return index
  904. return false
  905. }
  906. ;============================================================================================
  907. static ArrayToString(arr, delim)
  908. {
  909. for value in arr
  910. str .= value delim
  911. return RTrim(str, delim)
  912. }
  913. ;============================================================================================
  914. static NormAllColors(m)
  915. {
  916. for key in ['tc', 'mc', 'bc']
  917. if m.Has(key)
  918. m[key] := NormColor(m[key])
  919. for key in ['bdr', 'bdrC']
  920. if m.Has(key) && !RegExMatch(m[key], 'i)^(1|0|default)$')
  921. m[key] := NormColor(m[key])
  922. NormColor(key) => this.NormAHKColor(this.NormHexClrCode(key))
  923. }
  924. ;============================================================================================
  925. static NormAHKColor(color)
  926. {
  927. if RegExMatch(color, '^(0|1)$')
  928. return color
  929. for colorName, colorValue in this.mAHKcolors {
  930. if (colorValue = color) {
  931. color := colorName
  932. break
  933. }
  934. }
  935. return color
  936. }
  937. ;============================================================================================
  938. static NormHexClrCode(color)
  939. {
  940. if this.mAHKcolors.Has(color)
  941. color := this.mAHKcolors[color]
  942. if RegExMatch(Color, '^[0-9A-Fa-f]{1,6}$') && SubStr(color, 1, 2) != '0x'
  943. color := '0x' color
  944. if (RegExMatch(color, '^0x[0-9A-Fa-f]{1,6}$')) {
  945. hexPart := SubStr(color, 3)
  946. while StrLen(hexPart) < 6
  947. hexPart := '0' hexPart
  948. color := '0x' hexPart
  949. }
  950. else color := '0xFFFFFF'
  951. return color
  952. }
  953. /********************************************************************************************
  954. * @credits Klark92 (original author), XMCQCX (v2 conversion)
  955. * @see {@link https://www.autohotkey.com/boards/viewtopic.php?f=6&t=29117&hilit=FrameShadow AHK Forum}
  956. */
  957. static FrameShadow(hwnd)
  958. {
  959. DllCall("dwmapi.dll\DwmIsCompositionEnabled", "int*", &dwmEnabled:=0)
  960. if !dwmEnabled {
  961. DllCall("user32.dll\SetClassLongPtr", "ptr", hwnd, "int", -26, "ptr", DllCall("user32.dll\GetClassLongPtr", "ptr", hwnd, "int", -26) | 0x20000)
  962. }
  963. else {
  964. margins := Buffer(16, 0)
  965. NumPut("int", 1, "int", 1, "int", 1, "int", 1, margins)
  966. DllCall("dwmapi.dll\DwmSetWindowAttribute", "ptr", hwnd, "Int", 2, "Int*", 2, "Int", 4)
  967. DllCall("dwmapi.dll\DwmExtendFrameIntoClientArea", "ptr", hwnd, "ptr", margins)
  968. }
  969. }
  970. /********************************************************************************************
  971. * @credits ericreeves
  972. * @see {@link https://gist.github.com/ericreeves/fd426cc0457a5a47058e1ad1a29d9bd6 GitHub Gist}
  973. */
  974. static DrawBorderRound(hwnd, color)
  975. {
  976. color := this.RGB_BGR(this.NormHexClrCode(color))
  977. DllCall("dwmapi\DwmSetWindowAttribute", "ptr", hwnd, "int", DWMWA_BORDER_COLOR := 34, "int*", color, "int", 4)
  978. }
  979. ;============================================================================================
  980. static DrawBorderEdge(hwnd, color, width)
  981. {
  982. color := this.RGB_BGR(this.NormHexClrCode(color))
  983. rect := Buffer(16)
  984. DllCall("GetClientRect", "ptr", hwnd, "ptr", rect)
  985. left := NumGet(rect, 0, "int")
  986. top := NumGet(rect, 4, "int")
  987. right := NumGet(rect, 8, "int")
  988. bottom := NumGet(rect, 12, "int")
  989. hdc := DllCall("GetDC", "ptr", hwnd)
  990. hBrush := DllCall("gdi32\CreateSolidBrush", "uint", color, "ptr")
  991. hOldBrush := DllCall("gdi32\SelectObject", "ptr", hdc, "ptr", hBrush, "ptr")
  992. DllCall("gdi32\PatBlt", "ptr", hdc, "int", left, "int", top, "int", right - left, "int", width, "uint", 0x00F00021) ; Top border
  993. DllCall("gdi32\PatBlt", "ptr", hdc, "int", left, "int", bottom - width, "int", right - left, "int", width, "uint", 0x00F00021) ; Bottom border
  994. DllCall("gdi32\PatBlt", "ptr", hdc, "int", left, "int", top, "int", width, "int", bottom - top, "uint", 0x00F00021) ; Left border
  995. DllCall("gdi32\PatBlt", "ptr", hdc, "int", right - width, "int", top, "int", width, "int", bottom - top, "uint", 0x00F00021) ; Right border
  996. DllCall("gdi32\SelectObject", "ptr", hdc, "ptr", hOldBrush)
  997. DllCall("gdi32\DeleteObject", "ptr", hBrush)
  998. DllCall("ReleaseDC", "ptr", hwnd, "ptr", hdc)
  999. }
  1000. ;============================================================================================
  1001. static ReDrawBorderEdge(hwnd, style, color, width)
  1002. {
  1003. if style = 'edge' && !RegExMatch(color, 'i)^(default|1|0)$')
  1004. this.DrawBorderEdge(hwnd, color, width)
  1005. }
  1006. ;============================================================================================
  1007. static RedrawAllBorderEdge()
  1008. {
  1009. mDhwTmm := this.Set_DHWindows_TMMode(0, 1)
  1010. for id in WinGetList('NotifyGUI_ ahk_class AutoHotkeyGUI') {
  1011. try {
  1012. RegExMatch(WinGetTitle('ahk_id ' id), 'i)^NotifyGUI_\d+_([a-z]+)_([a-z]+)_(\w+)_(\d+)_(\d+)_\d+', &match)
  1013. this.ReDrawBorderEdge(id, match[2], match[3], match[4])
  1014. }
  1015. }
  1016. this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev'])
  1017. }
  1018. ;============================================================================================
  1019. static MonitorGetMouseIsIn()
  1020. {
  1021. point := Buffer(8)
  1022. DllCall("user32\GetCursorPos", "Ptr", point)
  1023. x := NumGet(point, 0, "Int")
  1024. y := NumGet(point, 4, "Int")
  1025. hMonitor := DllCall("user32\MonitorFromPoint", "Int64", (y << 32) | (x & 0xFFFFFFFF), "UInt", 0x2, "Ptr")
  1026. return hMonitor ? this.MonitorGetNumberFromHandle(hMonitor) : MonitorGetPrimary()
  1027. }
  1028. ;============================================================================================
  1029. static MonitorGetWindowIsIn(winTitle)
  1030. {
  1031. hMonitor := DllCall("user32\MonitorFromWindow", "Ptr", WinExist(winTitle), "UInt", 0x2, "Ptr")
  1032. return hMonitor ? this.MonitorGetNumberFromHandle(hMonitor) : MonitorGetPrimary()
  1033. }
  1034. ;============================================================================================
  1035. static MonitorGetNumberFromHandle(hMonitor)
  1036. {
  1037. NumPut("UInt", 104, MONITORINFOEX := Buffer(104), 0)
  1038. return DllCall("user32\GetMonitorInfo", "Ptr", hMonitor, "Ptr", MONITORINFOEX)
  1039. ? RegExReplace( StrGet(MONITORINFOEX.Ptr + 40, 32) , ".*(\d+)$", "$1")
  1040. : MonitorGetPrimary()
  1041. }
  1042. /********************************************************************************************
  1043. * @credits lexikos
  1044. * @see {@link https://www.autohotkey.com/boards/viewtopic.php?t=103459 AHK Forum}
  1045. */
  1046. static CalculatePopupWindowPosition(hwnd)
  1047. {
  1048. CoordMode('Mouse', 'Screen')
  1049. MouseGetPos(&x, &y)
  1050. anchorPt := Buffer(8)
  1051. windowRect := Buffer(16), windowSize := windowRect.ptr + 8
  1052. excludeRect := Buffer(16)
  1053. outRect := Buffer(16)
  1054. DllCall("GetClientRect", "ptr", hwnd, "ptr", windowRect)
  1055. /*
  1056. Windows 7 permits overlap with the taskbar, whereas Windows 10 requires the
  1057. tooltip to be within the work area (WinMove can subvert that, so this is just
  1058. for consistency with the normal behaviour).
  1059. */
  1060. flags := VerCompare(A_OSVersion, "6.2") < 0 ? 0 : 0x10000 ; TPM_WORKAREA
  1061. NumPut("int", x+16, "int", y+16, anchorPt) ; ToolTip normally shows at an offset of 16,16 from the cursor.
  1062. NumPut("int", x-3, "int", y-3, "int", x+3, "int", y+3, excludeRect) ; Avoid the area around the mouse pointer.
  1063. DllCall("CalculatePopupWindowPosition", "ptr", anchorPt, "ptr", windowSize, "uint", flags, "ptr", excludeRect, "ptr", outRect)
  1064. return 'x' NumGet(outRect, 0, 'int') ' y' NumGet(outRect, 4, 'int')
  1065. }
  1066. ;============================================================================================
  1067. static RGB_BGR(c) => ((c & 0xFF) << 16 | c & 0xFF00 | c >> 16)
  1068. ;============================================================================================
  1069. static MapCI() => (m := Map(), m.CaseSense := false, m)
  1070. }
  1071. /****************************************************************************************************************************************
  1072. * @description: JSON格式字符串序列化和反序列化, 修改自[HotKeyIt/Yaml](https://github.com/HotKeyIt/Yaml)
  1073. * 增加了对true/false/null类型的支持, 保留了数值的类型
  1074. * @author thqby, HotKeyIt
  1075. * @date 2024/02/24
  1076. * @version 1.0.7
  1077. ************************************************************************************************/
  1078. class _JSON_thqby {
  1079. static null := ComValue(1, 0), true := ComValue(0xB, 1), false := ComValue(0xB, 0)
  1080. /**
  1081. * Converts a AutoHotkey Object Notation JSON string into an object.
  1082. * @param text A valid JSON string.
  1083. * @param keepbooltype convert true/false/null to JSON.true / JSON.false / JSON.null where it's true, otherwise 1 / 0 / ''
  1084. * @param as_map object literals are converted to map, otherwise to object
  1085. */
  1086. static parse(text, keepbooltype := false, as_map := true) {
  1087. keepbooltype ? (_true := this.true, _false := this.false, _null := this.null) : (_true := true, _false := false, _null := "")
  1088. as_map ? (map_set := (maptype := Map).Prototype.Set) : (map_set := (obj, key, val) => obj.%key% := val, maptype := Object)
  1089. NQ := "", LF := "", LP := 0, P := "", R := ""
  1090. D := [C := (A := InStr(text := LTrim(text, " `t`r`n"), "[") = 1) ? [] : maptype()], text := LTrim(SubStr(text, 2), " `t`r`n"), L := 1, N := 0, V := K := "", J := C, !(Q := InStr(text, '"') != 1) ? text := LTrim(text, '"') : ""
  1091. Loop Parse text, '"' {
  1092. Q := NQ ? 1 : !Q
  1093. NQ := Q && RegExMatch(A_LoopField, '(^|[^\\])(\\\\)*\\$')
  1094. if !Q {
  1095. if (t := Trim(A_LoopField, " `t`r`n")) = "," || (t = ":" && V := 1)
  1096. continue
  1097. else if t && (InStr("{[]},:", SubStr(t, 1, 1)) || A && RegExMatch(t, "m)^(null|false|true|-?\d+(\.\d*(e[-+]\d+)?)?)\s*[,}\]\r\n]")) {
  1098. Loop Parse t {
  1099. if N && N--
  1100. continue
  1101. if InStr("`n`r `t", A_LoopField)
  1102. continue
  1103. else if InStr("{[", A_LoopField) {
  1104. if !A && !V
  1105. throw Error("Malformed JSON - missing key.", 0, t)
  1106. C := A_LoopField = "[" ? [] : maptype(), A ? D[L].Push(C) : map_set(D[L], K, C), D.Has(++L) ? D[L] := C : D.Push(C), V := "", A := Type(C) = "Array"
  1107. continue
  1108. } else if InStr("]}", A_LoopField) {
  1109. if !A && V
  1110. throw Error("Malformed JSON - missing value.", 0, t)
  1111. else if L = 0
  1112. throw Error("Malformed JSON - to many closing brackets.", 0, t)
  1113. else C := --L = 0 ? "" : D[L], A := Type(C) = "Array"
  1114. } else if !(InStr(" `t`r,", A_LoopField) || (A_LoopField = ":" && V := 1)) {
  1115. if RegExMatch(SubStr(t, A_Index), "m)^(null|false|true|-?\d+(\.\d*(e[-+]\d+)?)?)\s*[,}\]\r\n]", &R) && (N := R.Len(0) - 2, R := R.1, 1) {
  1116. if A
  1117. C.Push(R = "null" ? _null : R = "true" ? _true : R = "false" ? _false : IsNumber(R) ? R + 0 : R)
  1118. else if V
  1119. map_set(C, K, R = "null" ? _null : R = "true" ? _true : R = "false" ? _false : IsNumber(R) ? R + 0 : R), K := V := ""
  1120. else throw Error("Malformed JSON - missing key.", 0, t)
  1121. } else {
  1122. ; Added support for comments without '"'
  1123. if A_LoopField == '/' {
  1124. nt := SubStr(t, A_Index + 1, 1), N := 0
  1125. if nt == '/' {
  1126. if nt := InStr(t, '`n', , A_Index + 2)
  1127. N := nt - A_Index - 1
  1128. } else if nt == '*' {
  1129. if nt := InStr(t, '*/', , A_Index + 2)
  1130. N := nt + 1 - A_Index
  1131. } else nt := 0
  1132. if N
  1133. continue
  1134. }
  1135. throw Error("Malformed JSON - unrecognized character.", 0, A_LoopField " in " t)
  1136. }
  1137. }
  1138. }
  1139. } else if A || InStr(t, ':') > 1
  1140. throw Error("Malformed JSON - unrecognized character.", 0, SubStr(t, 1, 1) " in " t)
  1141. } else if NQ && (P .= A_LoopField '"', 1)
  1142. continue
  1143. else if A
  1144. LF := P A_LoopField, C.Push(InStr(LF, "\") ? UC(LF) : LF), P := ""
  1145. else if V
  1146. LF := P A_LoopField, map_set(C, K, InStr(LF, "\") ? UC(LF) : LF), K := V := P := ""
  1147. else
  1148. LF := P A_LoopField, K := InStr(LF, "\") ? UC(LF) : LF, P := ""
  1149. }
  1150. return J
  1151. UC(S, e := 1) {
  1152. static m := Map('"', '"', "a", "`a", "b", "`b", "t", "`t", "n", "`n", "v", "`v", "f", "`f", "r", "`r")
  1153. local v := ""
  1154. Loop Parse S, "\"
  1155. if !((e := !e) && A_LoopField = "" ? v .= "\" : !e ? (v .= A_LoopField, 1) : 0)
  1156. v .= (t := m.Get(SubStr(A_LoopField, 1, 1), 0)) ? t SubStr(A_LoopField, 2) :
  1157. (t := RegExMatch(A_LoopField, "i)^(u[\da-f]{4}|x[\da-f]{2})\K")) ?
  1158. Chr("0x" SubStr(A_LoopField, 2, t - 2)) SubStr(A_LoopField, t) : "\" A_LoopField,
  1159. e := A_LoopField = "" ? e : !e
  1160. return v
  1161. }
  1162. }
  1163. /**
  1164. * Converts a AutoHotkey Array/Map/Object to a Object Notation JSON string.
  1165. * @param obj A AutoHotkey value, usually an object or array or map, to be converted.
  1166. * @param expandlevel The level of JSON string need to expand, by default expand all.
  1167. * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
  1168. */
  1169. static stringify(obj, expandlevel := unset, space := " ") {
  1170. expandlevel := IsSet(expandlevel) ? Abs(expandlevel) : 10000000
  1171. return Trim(CO(obj, expandlevel))
  1172. CO(O, J := 0, R := 0, Q := 0) {
  1173. static M1 := "{", M2 := "}", S1 := "[", S2 := "]", N := "`n", C := ",", S := "- ", E := "", K := ":"
  1174. if (OT := Type(O)) = "Array" {
  1175. D := !R ? S1 : ""
  1176. for key, value in O {
  1177. F := (VT := Type(value)) = "Array" ? "S" : InStr("Map,Object", VT) ? "M" : E
  1178. Z := VT = "Array" && value.Length = 0 ? "[]" : ((VT = "Map" && value.count = 0) || (VT = "Object" && ObjOwnPropCount(value) = 0)) ? "{}" : ""
  1179. D .= (J > R ? "`n" CL(R + 2) : "") (F ? (%F%1 (Z ? "" : CO(value, J, R + 1, F)) %F%2) : ES(value)) (OT = "Array" && O.Length = A_Index ? E : C)
  1180. }
  1181. } else {
  1182. D := !R ? M1 : ""
  1183. for key, value in (OT := Type(O)) = "Map" ? (Y := 1, O) : (Y := 0, O.OwnProps()) {
  1184. F := (VT := Type(value)) = "Array" ? "S" : InStr("Map,Object", VT) ? "M" : E
  1185. Z := VT = "Array" && value.Length = 0 ? "[]" : ((VT = "Map" && value.count = 0) || (VT = "Object" && ObjOwnPropCount(value) = 0)) ? "{}" : ""
  1186. D .= (J > R ? "`n" CL(R + 2) : "") (Q = "S" && A_Index = 1 ? M1 : E) ES(key) K (F ? (%F%1 (Z ? "" : CO(value, J, R + 1, F)) %F%2) : ES(value)) (Q = "S" && A_Index = (Y ? O.count : ObjOwnPropCount(O)) ? M2 : E) (J != 0 || R ? (A_Index = (Y ? O.count : ObjOwnPropCount(O)) ? E : C) : E)
  1187. if J = 0 && !R
  1188. D .= (A_Index < (Y ? O.count : ObjOwnPropCount(O)) ? C : E)
  1189. }
  1190. }
  1191. if J > R
  1192. D .= "`n" CL(R + 1)
  1193. if R = 0
  1194. D := RegExReplace(D, "^\R+") (OT = "Array" ? S2 : M2)
  1195. return D
  1196. }
  1197. ES(S) {
  1198. switch Type(S) {
  1199. case "Float":
  1200. if (v := '', d := InStr(S, 'e'))
  1201. v := SubStr(S, d), S := SubStr(S, 1, d - 1)
  1202. if ((StrLen(S) > 17) && (d := RegExMatch(S, "(99999+|00000+)\d{0,3}$")))
  1203. S := Round(S, Max(1, d - InStr(S, ".") - 1))
  1204. return S v
  1205. case "Integer":
  1206. return S
  1207. case "String":
  1208. S := StrReplace(S, "\", "\\")
  1209. S := StrReplace(S, "`t", "\t")
  1210. S := StrReplace(S, "`r", "\r")
  1211. S := StrReplace(S, "`n", "\n")
  1212. S := StrReplace(S, "`b", "\b")
  1213. S := StrReplace(S, "`f", "\f")
  1214. S := StrReplace(S, "`v", "\v")
  1215. S := StrReplace(S, '"', '\"')
  1216. return '"' S '"'
  1217. default:
  1218. return S == this.true ? "true" : S == this.false ? "false" : "null"
  1219. }
  1220. }
  1221. CL(i) {
  1222. Loop (s := "", space ? i - 1 : 0)
  1223. s .= space
  1224. return s
  1225. }
  1226. }
  1227. }