├── Autorun.ahk ├── COMMANDER_PATH.bat ├── ReadMe.md ├── TMD.ahk ├── TMD.exe ├── cm.ini ├── lib ├── BTT.ahk ├── Gdip_All.ahk ├── NonNull.ahk ├── class_easyini.ahk ├── class_listbox.ahk ├── delSymbol.ahk └── tc.ahk ├── settings.ini ├── tc.ico ├── 按键列表(键盘, 鼠标和操纵杆) - AutoHotkey.url └── 路径自动找先不要填了 /Autorun.ahk: -------------------------------------------------------------------------------- 1 | if !FileExist("..\Plugins\WDX\Autorun\autorun.cfg"){ 2 | MsgBox,你解压错地方了 3 | ExitApp 4 | } 5 | open= 6 | ( 7 | ShellExec "`%COMMANDER_PATH`%\TMD\TMD.exe" 8 | ) 9 | FileRead,auto,..\Plugins\WDX\Autorun\autorun.cfg 10 | if (InStr(auto,open)){ 11 | MsgBox,自启打开`,想关掉手动关掉去哈哈哈 12 | } 13 | else{ 14 | open:="`n" . open 15 | FileAppend,%open%,..\Plugins\WDX\Autorun\autorun.cfg 16 | MsgBox,自启成功 17 | } 18 | ExitApp -------------------------------------------------------------------------------- /COMMANDER_PATH.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set ENV_Path=%COMMANDER_PATH% 3 | setx /M test1 "" 4 | paus 5 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzncpa/tmd/bb51e5929e0076b2f877cf8a5d5f2a766a257115/ReadMe.md -------------------------------------------------------------------------------- /TMD.ahk: -------------------------------------------------------------------------------- 1 | ;************************************* admin *********************** 2 | loop, %0% 3 | { 4 | param := %A_Index% ; Fetch the contents of the variable whose name is contained in A_Index. 5 | params .= A_Space . param 6 | } 7 | ShellExecute := A_IsUnicode ? "shell32\ShellExecute":"shell32\ShellExecuteA" 8 | if not A_IsAdmin 9 | { 10 | if A_IsCompiled 11 | DllCall(ShellExecute, uint, 0, str, "RunAs", str, A_ScriptFullPath, str, params , str, A_WorkingDir, int, 1) 12 | else 13 | DllCall(ShellExecute, uint, 0, str, "RunAs", str, A_AhkPath, str, """" . A_ScriptFullPath . """" . A_Space . params, str, A_WorkingDir, int, 1) 14 | ExitApp 15 | } 16 | global TC_Quick_Command_update_version:="2.3.0" ;Version 17 | global TC_Quick_Command_update_time:="2022.09.22" ;update date 18 | global Showarr := {} 19 | ;************************************* exec *********************** 20 | #SingleInstance Force 21 | #MaxMem 640 22 | #KeyHistory 0 23 | #Persistent 24 | SetBatchLines -1 25 | DetectHiddenWindows On 26 | SetWinDelay -1 27 | SetControlDelay -1 28 | SetWorkingDir %A_ScriptDir% 29 | CoordMode, ToolTip, Screen 30 | CoordMode, Caret , Screen 31 | CoordMode, Mouse, Screen 32 | ListLines Off 33 | #Include %A_ScriptDir% 34 | #Include 35 | #Include 36 | #Include 37 | ; OnMessage(0x020A,"Mouse_MButton_W1") 38 | ; OnMessage(0x0201,"Mouse_LButton_D1") 39 | ;icon 40 | Menu, Tray, Icon, tc.ico 41 | ;if ini change reload 42 | time:=A_Now 43 | #Persistent 44 | SetTimer,re_index,3000 45 | 46 | index: 47 | st:=class_EasyIni("settings.ini") ;Read settings 48 | for stkey,stv in st["settings"] 49 | %stkey%:=stv 50 | ;************************************* btt style *********************** 51 | BttStyle := {Margin:10 ; If omitted, 5 will be used. Range 0-30. 52 | , TabStops:[50, 80, 100] ; If omitted, [50] will be used. This value must be an array. 53 | , TextColor:"0xff" . 预览字体颜色 ; ARGB 54 | , BackgroundColor:"0xff" . 预览背景颜色 ; ARGB 55 | , Font:预览字体名称 ; If omitted, ToolTip's Font will be used. Can specify the font file path. 56 | , FontSize:预览字体大小 ; If omitted, 12 will be used. 57 | , FontRender:5 ; If omitted, 5 will be used. Range 0-5. 58 | , FontStyle:"Regular"} 59 | Btt(启动词, 0, 0, ,"BttStyle") 60 | SetTimer, Gui_Destroy, -1000 61 | 62 | ;************************************* keyboard *********************** 63 | ; Hotkey,F10,reload ;重启脚本 64 | Hotkey, IfWinActive, ahk_class TTOTAL_CMD 65 | if (键盘呼出搜索框) 66 | Hotkey,% st.settings.键盘呼出搜索框, searchBar_keyboard ;快捷键激活搜索框by光标 67 | if (控件呼出搜索框) 68 | Hotkey,% st.settings.控件呼出搜索框, searchBar_control ;快捷键激活搜索框by控件 69 | if (鼠标呼出搜索框) 70 | Hotkey,% st.settings.鼠标呼出搜索框, searchBar_Mouse ;鼠标激活搜索框 71 | if (重复命令) 72 | Hotkey,% st.settings.重复命令,reexec ;重复上一条命令 73 | Hotkey, if, (WinActive("ahk_id " MyGuiHwnd) && (Showstr)) 74 | Hotkey, Enter, exec ;防止误触可以改成下面的ctrl Enter执行 75 | ; Hotkey, ^Enter, exec 76 | Hotkey, esc,Gui_Destroy 77 | Hotkey,% st.settings.复制全部内容, copy_all ;ctrl c copy all 78 | Hotkey,% st.settings.复制名字, copy_name ;ctrl 1复制em or cm name 79 | Hotkey,% st.settings.复制号码或命令, copy_num_cmd ;ctrl 2 copy cmd or num 80 | Hotkey,% st.settings.复制Menu, copy_menu ;ctrl 3 copy em or cm Menu 81 | Hotkey,% st.settings.开关预览,open_or_hide 82 | Hotkey,% st.settings.编辑em,edit_command 83 | Hotkey,% st.settings.ding命令,ding 84 | Hotkey, if 85 | 86 | ;*************************************get the user and win ini path *********************** 87 | 88 | 89 | 90 | ;************form caps*********************************************** 91 | COMMANDER_PATH := % GF_GetSysVar("COMMANDER_PATH") 92 | WinGet,TcExeFullPath,ProcessPath,ahk_class TTOTAL_CMD 93 | if !TcExeFullPath ;没tc在运行 94 | { 95 | if A_Is64bitOS { 96 | if FileExist(A_WorkingDir . "\" . "TOTALCMD64.EXE") { 97 | TcExeFullPath := % A_WorkingDir . "\" . "TOTALCMD64.EXE" 98 | COMMANDER_PATH := % A_WorkingDir 99 | COMMANDER_NAME := "TOTALCMD64.EXE" 100 | COMMANDER_EXE := COMMANDER_PATH . "\" . "TOTALCMD64.EXE" 101 | EnvSet,COMMANDER_PATH, %COMMANDER_PATH% 102 | } else if FileExist(A_WorkingDir . "\" . "TOTALCMD.EXE") { 103 | TcExeFullPath := % A_WorkingDir . "\" . "TOTALCMD.EXE" 104 | COMMANDER_PATH := % A_WorkingDir 105 | COMMANDER_EXE := COMMANDER_PATH . "\" . "TOTALCMD.EXE" 106 | EnvSet,COMMANDER_PATH, %COMMANDER_PATH% 107 | } else{ 108 | ;toolTip 当前目录下没Totalcmd程序 109 | ;sleep 2000 110 | ;tooltip 111 | } 112 | } 113 | else { 114 | if FileExist(A_WorkingDir . "\" . "TOTALCMD.EXE") { 115 | TcExeFullPath := A_WorkingDir . "\" . "TOTALCMD.EXE" 116 | COMMANDER_PATH := % A_WorkingDir 117 | EnvSet,COMMANDER_PATH, %COMMANDER_PATH% 118 | } else { 119 | ;toolTip 当前目录下没Totalcmd程序 120 | ;sleep 2000 121 | ;tooltip 122 | } 123 | } 124 | } 125 | else{ ;有tc在运行 126 | if(COMMANDER_PATH == A_WorkingDir) { 127 | EnvSet,COMMANDER_PATH, %COMMANDER_PATH% 128 | } 129 | else if !COMMANDER_PATH ;但脚本先启动,比如随系统自启动,所以并没有COMMANDER_PATH变量 130 | { 131 | WinGet,TcExeName,ProcessName,ahk_class TTOTAL_CMD 132 | StringTrimRight, COMMANDER_PATH, TcExeFullPath, StrLen(TcExeName)+1 133 | EnvSet,COMMANDER_PATH, %COMMANDER_PATH% 134 | } 135 | } 136 | ;********************************************************************* 137 | if (!wincmd路径){ 138 | if (FileExist(COMMANDER_PATH "\wincmd.ini")) 139 | wincmd路径:=COMMANDER_PATH "\wincmd.ini" 140 | else if (FileExist(appdata "\ghisler\wincmd.ini") ) 141 | wincmd路径:=appdata "\ghisler\wincmd.ini" 142 | else{ 143 | MsgBox,wincmd路径没填且不在常见路径 144 | run "settings.ini" 145 | ExitApp 146 | } 147 | } 148 | 149 | if (!usercmd路径){ 150 | if (FileExist(COMMANDER_PATH "\usercmd.ini")) 151 | usercmd路径:=COMMANDER_PATH "\usercmd.ini" 152 | else if (FileExist(appdata "\ghisler\usercmd.ini") ) 153 | usercmd路径:=appdata "\ghisler\usercmd.ini" 154 | else{ 155 | MsgBox,usercmd路径没填且不在常见路径 156 | run "settings.ini" 157 | ExitApp 158 | } 159 | } 160 | ;************************************* *********************** 161 | usercmd:=class_EasyIni(usercmd路径) 162 | win:=class_EasyIni(wincmd路径) 163 | cm:=class_EasyIni("cm.ini") 164 | if (LanguageEM!="") 165 | tz:=class_EasyIni(LanguageEM) 166 | if (tz) 167 | usercmd.merge(tz) 168 | if (shortcuts路径!="") 169 | sc:=class_EasyIni(shortcuts路径) 170 | else{ 171 | if (win.FindKeys("Shortcuts","RedirectSection").1="RedirectSection"){ 172 | sc:=class_EasyIni(str2env(win.Shortcuts.RedirectSection)) 173 | sc_sigh:="re" 174 | } 175 | else 176 | sc_sigh:="win" ;win and re 177 | } 178 | 179 | ;tcmatch path 180 | 181 | ;32 Default 182 | if (A_PtrSize==4){ 183 | 184 | Loop Files,%COMMANDER_PATH%\*.dll 185 | { 186 | if (A_LoopFileName="tcmatch.dll"){ 187 | dllPath :=A_LoopFileFullPath 188 | ;IniWrite,%dllPath%, %A_Temp%\tmdhis.ini,settings,dllPath 189 | MatchFileW :=A_LoopFileDir "\TCMatch\MatchFileW" 190 | ;IniWrite,%MatchFileW%, %A_Temp%\tmdhis.ini,settings,MatchFileW 191 | } 192 | } 193 | if (!FileExist(dllPath)){ 194 | MsgBox,缺少32位的TCMatchdll 195 | ExitApp 196 | } 197 | } 198 | 199 | if (A_PtrSize==8){ 200 | 201 | Loop Files,%COMMANDER_PATH%\*.dll 202 | { 203 | if (A_LoopFileName="tcmatch64.dll"){ 204 | dllPath :=A_LoopFileFullPath 205 | ;IniWrite,%dllPath%, %A_Temp%\tmdhis.ini,settings,dllPath 206 | MatchFileW :=A_LoopFileDir "\TCMatch64\MatchFileW" 207 | ;IniWrite,%MatchFileW%, %A_Temp%\tmdhis.ini,settings,MatchFileW 208 | } 209 | } 210 | if (!FileExist(dllPath)){ 211 | MsgBox,缺少64位的TCMatchdll 212 | ExitApp 213 | } 214 | } 215 | 216 | 217 | OnMessage(0x5555, "MsgMonitor") 218 | return 219 | 220 | MsgMonitor(wParam, lParam, msg){ 221 | WinGetClass, cls, A 222 | if( cls = "TTOTAL_CMD" ) 223 | Gosub, searchBar_keyboard 224 | } 225 | 226 | match_cm_arr: 227 | for sec,kv in cm{ 228 | if (cm[sec]["Menu"]!="" && !RegExMatch(cm[sec]["Menu"],隐藏菜单规则)){ 229 | cm_want_all:=sec " " cm[sec]["Menu"] 230 | cm_num:=cm_want_all . cm[sec]["num"] 231 | Query:=RegExReplace(Query,"^[:;',./?,。:;“‘?、]") ;del c: 232 | if (InStr(Query," ")){ 233 | Query_cm_arr := StrSplit(Query, " ") 234 | for query_a,query_b in Query_cm_arr{ 235 | if (!TCMatch(cm_num,query_b)){ 236 | find_cm_sign := false 237 | break 238 | } 239 | if (TCMatch(cm_num,query_b)) 240 | find_cm_sign := true 241 | } 242 | if (find_cm_sign = true) 243 | Showarr.Push(cm_want_all) 244 | }else{ 245 | if (TCMatch(cm_num,Query)) 246 | Showarr.Push(cm_want_all) 247 | } 248 | } 249 | 250 | } 251 | 252 | return 253 | 254 | match_em_arr: 255 | For k,v in usercmd 256 | { 257 | if (usercmd[k]["Menu"]!="" && !RegExMatch(usercmd[k]["Menu"],隐藏菜单规则)){ 258 | em_want_all:=k " " usercmd[k]["Menu"] 259 | ; if \s spilt Query to arr 260 | if (InStr(Query," ")){ 261 | Query_em_arr := StrSplit(Query, " ") 262 | for a,b in Query_em_arr{ 263 | if (!TCMatch(em_want_all,b)){ 264 | find_em_sign := false 265 | break 266 | } 267 | if (TCMatch(em_want_all,b)) 268 | find_em_sign := true 269 | } 270 | if (find_em_sign = true) 271 | Showarr.Push(em_want_all) 272 | }else{ 273 | if (TCMatch(em_want_all,Query)){ 274 | Showarr.Push(em_want_all) 275 | } 276 | } 277 | } 278 | 279 | } 280 | return 281 | 282 | reload: 283 | Reload 284 | return 285 | 286 | Refresh: 287 | Showstr :=cm_mode_sigh:= "" 288 | Showarr := {} 289 | ControlGetText, Query, , ahk_id %SSK% 290 | if (Query!=""){ 291 | ;first try to match cm 292 | if (RegExMatch(Query, "^[:;',./?,。:;“‘?、]")){ ;the sigh of match cm 293 | cm_mode_sigh:=1 294 | gosub match_cm_arr 295 | gosub update_listbox 296 | return 297 | } 298 | cm_mode_sigh:=0 ;then we match em 299 | gosub match_em_arr 300 | gosub update_listbox 301 | if (焦点从零开始=1){ 302 | if (CListBox.GetCurrentSel(LIST)=1) 303 | GuiControl, ss:Choose, command,0 304 | } 305 | }else if (Query=""){ 306 | IniRead,displaycmd,%temp%\tmdhis.ini,history,last 307 | ;Showarr.Push(st.history.last A_Space "**the last command**") 308 | Showarr.Push(displaycmd A_Space "**the last command**") 309 | for k,v in st.ding{ 310 | Showarr.Push(k A_Space v) 311 | } 312 | gosub update_listbox 313 | } 314 | return 315 | 316 | update_listbox: 317 | GuiControl, ss:, -Redraw, Command 318 | For Showstrk,Showstrv in Showarr 319 | { 320 | Showstr .= "|" Showstrk " " Showstrv 321 | n := A_Index 322 | } 323 | ; Showstr := (Query) ? Showstr : "" 324 | gosub updateList 325 | GuiControl, ss:, +Redraw, Command 326 | return 327 | 328 | Preview: ;remove preview 329 | if (A_GuiEvent = "DoubleClick") 330 | gosub exec 331 | Gui, ss:Submit, NoHide 332 | if (显示预览=1){ 333 | gosub cmem_preview 334 | btt_which(预览位置) 335 | } 336 | return 337 | 338 | btt_which(which){ 339 | global 340 | if (which="右") 341 | btt_pos := Btt(Bttstr, xpos+列表宽度+5, ypos, ,"BttStyle") 342 | else if (which="左") 343 | btt_pos := Btt(Bttstr, xpos-btt_pos.w, ypos, ,"BttStyle") 344 | Else if (which="上") 345 | btt_pos := Btt(Bttstr, xpos, ypos-250, ,"BttStyle"), btt_pos := Btt(Bttstr, xpos, ypos-btt_pos.h, ,"BttStyle") 346 | return 347 | } 348 | 349 | shortcuts_search: 350 | if (sc_sigh="re"){ 351 | for sec,kv in sc{ 352 | for k,v in sc[sec]{ 353 | if (v=command){ 354 | gosub v_command 355 | Return 356 | } 357 | } 358 | } 359 | shortcuts:="null" 360 | }else if (sc_sigh="win"){ 361 | for k,v in win.Shortcuts{ 362 | if (v=command){ 363 | gosub v_command 364 | Return 365 | } 366 | } 367 | for k,v in win.ShortcutsWin{ 368 | if (v=command){ 369 | gosub v_command 370 | } 371 | Return 372 | } 373 | shortcuts:="null" 374 | } 375 | return 376 | 377 | v_command: 378 | shortcuts:="" 379 | shortcuts:=RegExReplace(k, "^A\+","Alt+") 380 | shortcuts:=RegExReplace(shortcuts, "^C\+","Ctrl+") 381 | shortcuts:=RegExReplace(shortcuts, "^CS\+","Ctrl+Shift+") 382 | shortcuts:=RegExReplace(shortcuts, "^AS\+","Alt+Shift+") 383 | shortcuts:=RegExReplace(shortcuts, "^CA\+","Ctrl+Alt+") 384 | shortcuts:=RegExReplace(shortcuts, "^CAS\+","Ctrl+Alt+Shift+") 385 | shortcuts:=RegExReplace(shortcuts, "^S\+","Shift+") 386 | return 387 | 388 | cmem_preview: 389 | arr_command:=StrSplit(command, " ") 390 | command:=arr_command[2] , Bttstr :="" 391 | if (RegExMatch(command, "^cm")){ 392 | cm_num:=cm[command]["num"] 393 | cm_menu:=cm[command]["Menu"] 394 | gosub shortcuts_search 395 | Bttstr= 396 | ( 397 | [%command%] 398 | num=%cm_num% 399 | menu=%cm_menu% 400 | shortcuts=%shortcuts% 401 | ) 402 | }else if (RegExMatch(command, "^em")){ 403 | em_menu:=usercmd[command]["Menu"] 404 | em_cmd:=usercmd[command]["cmd"] 405 | em_param:=usercmd[command]["param"] 406 | em_button:=usercmd[command]["button"] 407 | gosub shortcuts_search 408 | Bttstr= 409 | ( 410 | [%command%] 411 | cmd=%em_cmd% 412 | menu=%em_menu% 413 | param=%em_param% 414 | button=%em_button% 415 | shortcuts=%shortcuts% 416 | ) 417 | } 418 | return 419 | 420 | 421 | searchBar: 422 | gosub Gui_Destroy 423 | Gui, ss:Margin, 0, 0 424 | Gui, ss:-Caption +Border 425 | Gui, ss:+AlwaysOnTop -DPIScale +ToolWindow +HwndMyGuiHwnd +E0x02000000 +E0x00080000 426 | Gui, ss:Font, s%字体大小%, %字体名称% 427 | Gui, ss:Add, Edit, gRefresh vsearchBar HwndSSK w%列表宽度% -E0x200 428 | SetEditCueBanner(SSK,搜索框提示) 429 | Gui, ss:Font, s%字体大小%, %字体名称% 430 | Gui, ss:Add, ListBox, hwndLIST h0 vCommand -HScroll -E0x200 w%列表宽度% gPreview 431 | 432 | Gui ss:+LastFound ; Make the Gui window the last found window for use by the line below. 433 | GuiControl, ss:Move, Command, h0 434 | ControlColor(SSK, MyGuiHwnd, "0x" 搜索框背景颜色, "0x" 字体颜色) 435 | ControlColor(LIST, MyGuiHwnd, "0x" 列表背景颜色, "0x" 字体颜色) 436 | xpos:=ypos:="" 437 | ; switchime(1) ;测试输入法切英文 438 | return 439 | shellMessage(wParam, lParam) { ;接受系统窗口回调消息, 第一次是实时,第二次是保障 440 | if ( wParam=1 || wParam=32772 || wParam=5 || wParam=4) { 441 | WinGetClass, cls, A 442 | if( cls != "TTOTAL_CMD" ) 443 | gosub Gui_Destroy 444 | } 445 | } 446 | 447 | show_hook: ;add windows hook 448 | xpos:=xpos+搜索框右移 , ypos:=ypos+搜索框下移 449 | Gui, ss:show, AutoSize x%xpos% y%ypos% 450 | ;CoordMode,Mouse, Screen 451 | MouseMove, -10, 0, 0,R 452 | if (输入法英文) 453 | switchime(1) 454 | if (窗口切换自动关闭=1){ 455 | Gui ss:+LastFound 456 | hWnd := WinExist() 457 | DllCall( "RegisterShellHookWindow", UInt,hWnd ) 458 | MsgNum := DllCall( "RegisterWindowMessage", Str,"SHELLHOOK" ) 459 | OnMessage( MsgNum, "ShellMessage" ) 460 | ; shellMessage(1,1) 461 | } 462 | return 463 | 464 | 465 | 466 | 467 | searchBar_keyboard: 468 | gosub searchBar 469 | PostMessage,1075, 2914, 0, , ahk_class TTOTAL_CMD 470 | Sleep 50 471 | MouseGetPos,xpos,ypos 472 | gosub show_hook 473 | gosub Refresh 474 | return 475 | searchBar_control: 476 | gosub searchBar 477 | ControlGetFocus,TLB,ahk_class TTOTAL_CMD 478 | ControlGetPos,xpos,ypos,wn,,%TLB%,ahk_class TTOTAL_CMD 479 | gosub show_hook 480 | gosub Refresh 481 | return 482 | searchBar_Mouse: 483 | gosub searchBar 484 | MouseGetPos,xpos,ypos 485 | gosub show_hook 486 | gosub Refresh 487 | return 488 | 489 | go_preview: 490 | GuiControl, ss:Choose, command, % ChooseRow 491 | gosub Preview 492 | return 493 | choose_number(number:="",which:=""){ 494 | global 495 | if (number!=0){ 496 | ChooseRow:=number 497 | GuiControl, ss:Choose, command, % ChooseRow 498 | Sleep 50 499 | gosub Preview 500 | if (数字直接执行=1) 501 | gosub exec 502 | }else{ 503 | if (which="up") 504 | ChooseRow := (ChooseRow > 1) ? ChooseRow - 1 : ChooseRow 505 | else if(which="down") 506 | ChooseRow := (ChooseRow < n) ? ChooseRow + 1 : ChooseRow 507 | else if(which="home") 508 | ChooseRow :=1 509 | else if(which="end") 510 | endLine:="" , ChooseRow :=CListBox.GetCount(LIST) 511 | GuiControl, ss:Choose, command, % ChooseRow 512 | Sleep 50 513 | gosub Preview 514 | } 515 | return 516 | } 517 | 518 | open_or_hide: 519 | if (st["settings"]["显示预览"]=0){ 520 | gosub open_preview 521 | ; gosub go_preview 522 | } 523 | else 524 | gosub hide_preview 525 | return 526 | 527 | hide_preview: 528 | 显示预览:=0 529 | st["settings"]["显示预览"]:=0 530 | st.save() 531 | btt() 532 | return 533 | open_preview: 534 | 显示预览:=1 535 | st["settings"]["显示预览"]:=1 536 | st.save() 537 | return 538 | 539 | ssGuiEscape: 540 | ssGuiClose: 541 | Gui_Destroy: 542 | Btt() 543 | Gui, ss:Destroy 544 | return 545 | 546 | copy_all: 547 | Clipboard:="" , Clipboard:=Bttstr 548 | tips(Bttstr) 549 | return 550 | copy_name: 551 | Clipboard:="" ,Clipboard:=command 552 | tips(command) 553 | return 554 | copy_num_cmd: 555 | if (cm_mode_sigh!=1){ 556 | Clipboard:="" , Clipboard:=em_cmd 557 | tips(em_cmd) 558 | } 559 | else{ 560 | Clipboard:="" , Clipboard:=cm_num 561 | tips(cm_num) 562 | } 563 | return 564 | copy_menu: 565 | if (cm_mode_sigh!=1){ 566 | Clipboard:="" , Clipboard:=em_menu 567 | tips("em_menu") 568 | } 569 | else{ 570 | Clipboard:="" , Clipboard:=cm_menu 571 | tips("cm_menu") 572 | } 573 | return 574 | 575 | exec: 576 | gosub Gui_Destroy 577 | if (cm_mode_sigh!=1){ 578 | TC_EMC(command) 579 | ;st.history.em:=1 580 | ;st.history.last:=command 581 | IniWrite,%command%, %A_Temp%\tmdhis.ini,history,last 582 | IniWrite,1, %A_Temp%\tmdhis.ini,history,em 583 | } 584 | else{ 585 | PostMessage,1075,%cm_num%, 0, , ahk_class TTOTAL_CMD 586 | ;st.history.last:=cm_num 587 | ;st.history.em:=0 588 | IniWrite,%cm_num%, %A_Temp%\tmdhis.ini,history,last 589 | IniWrite,0, %A_Temp%\tmdhis.ini,history,em 590 | } 591 | ;st.save() 592 | gosub Gui_Destroy 593 | WinActivate ahk_class TTOTAL_CMD 594 | return 595 | 596 | reexec: 597 | IniRead,recmd,%temp%\tmdhis.ini,history,last 598 | IniRead,execem,%temp%\tmdhis.ini,history,em 599 | ;recmd:=st.history.last 600 | ;if (st.history.em==1){ 601 | if (execem==1){ 602 | TC_EMC(recmd) 603 | }else if (execem==0){ 604 | PostMessage,1075,%recmd%, 0, , ahk_class TTOTAL_CMD 605 | } 606 | return 607 | 608 | tips(text){ 609 | MouseGetPos,xa,ya 610 | Btt(text,xa,ya,,"BttStyle") 611 | Sleep 500 612 | btt_which(预览位置) 613 | return 614 | } 615 | 616 | ding: 617 | if (st.FindKeys("ding",command).1=command){ 618 | st.RemoveKey("ding",command) 619 | }else{ 620 | if (RegExMatch(command, "^cm")){ 621 | st["ding"][command]:=cm[command]["menu"] 622 | }else if (RegExMatch(command, "^em")){ 623 | st["ding"][command]:=usercmd[command]["menu"] 624 | } 625 | } 626 | st.save() 627 | return 628 | 629 | 630 | edit_command: 631 | if (指定编辑器){ 632 | gosub Gui_Destroy 633 | MouseGetPos,xa,ya 634 | Btt("打开编辑器",xa,ya, ,"BttStyle") 635 | SetTimer, Gui_Destroy, -1000 636 | try 637 | Run %指定编辑器% %usercmd路径% 638 | Catch e 639 | run notepad %usercmd路径% 640 | } 641 | return 642 | 643 | #If (WinActive("ahk_id " MyGuiHwnd) && (Showstr)) 644 | #If 645 | #If (WinExist("ahk_id " MyGuiHwnd)) 646 | #If 647 | 648 | #If (WinActive("ahk_id " MyGuiHwnd) && (Showstr)) 649 | !1::choose_number(1) 650 | !2::choose_number(2) 651 | !3::choose_number(3) 652 | !4::choose_number(4) 653 | !5::choose_number(5) 654 | !6::choose_number(6) 655 | !7::choose_number(7) 656 | !8::choose_number(8) 657 | !9::choose_number(9) 658 | up::choose_number(0,"up") 659 | Down::choose_number(0,"down") 660 | home::choose_number(0,"home") 661 | end::choose_number(0,"end") 662 | Left::btt_which("左") 663 | right::btt_which("右") 664 | 665 | F1::choose_number(1) 666 | F2::choose_number(2) 667 | F3::choose_number(3) 668 | F4::choose_number(4) 669 | F5::choose_number(5) 670 | F6::choose_number(6) 671 | F7::choose_number(7) 672 | F8::choose_number(8) 673 | F9::choose_number(9) 674 | F10::choose_number(10) 675 | F11::choose_number(11) 676 | F12::choose_number(12) 677 | #If 678 | 679 | updateList: 680 | GuiControl, ss:, Command, % (Showstr) ? Showstr : "" 681 | if (是否显示所有em=0) 682 | ListN := ((n < 最大显示行) ? n : 最大显示行) * CListBox.GetItemHeight(LIST) 683 | else 684 | ListN := n * CListBox.GetItemHeight(LIST) 685 | ListN := !(Showstr) ? 0 : ListN 686 | GuiControl, ss:Move, Command, % "h" ListN 687 | ChooseRow := 1 688 | GuiControl, ss:Choose, command, % ChooseRow 689 | gosub Preview 690 | Gui, ss:show, AutoSize 691 | if !(Showstr) { 692 | Btt() 693 | GuiControl, ss:, Command, | 694 | } 695 | return 696 | 697 | re_index: 698 | FileGetTime,last_user, %usercmd路径%, M 699 | if (last_user-time>0){ 700 | time:=last_user 701 | gosub index 702 | return 703 | } 704 | FileGetTime,last_st,settings.ini, M 705 | if (last_st-time>0){ 706 | time:=last_st 707 | gosub index 708 | return 709 | } 710 | 711 | if (tz){ 712 | FileGetTime,last_tz, %LanguageEM%, M 713 | if (last_tz-time>0){ 714 | time:=last_tz 715 | gosub index 716 | return 717 | } 718 | } 719 | if (sc){ 720 | sc_path:=str2env(win.Shortcuts.RedirectSection) 721 | FileGetTime,last_sc, %sc_path%, M 722 | if (last_sc-time>0){ 723 | time:=last_sc 724 | gosub index 725 | return 726 | } 727 | } 728 | if (win){ 729 | FileGetTime,last_sc, %wincmd路径%, M 730 | if (last_sc-time>0){ 731 | time:=last_sc 732 | gosub index 733 | return 734 | } 735 | } 736 | return 737 | 738 | ControlColor(Control, Window, bc := "", tc := "", Redraw := True) { 739 | local a := {} 740 | a["c"] := Control 741 | a["g"] := Window 742 | a["bc"] := (bc == "") ? "" : (((bc & 255) << 16) + (((bc >> 8) & 255) << 8) + (bc >> 16)) 743 | a["tc"] := (tc == "") ? "" : (((tc & 255) << 16) + (((tc >> 8) & 255) << 8) + (tc >> 16)) 744 | 745 | CC_WindowProc("Set", a, "", "") 746 | 747 | if (Redraw) { 748 | WinSet Redraw,, ahk_id %Control% 749 | } 750 | } 751 | CC_WindowProc(hWnd, uMsg, wParam, lParam) { 752 | local tc, bc, a 753 | static Win := {} 754 | ; Critical 755 | 756 | if uMsg Between 0x132 And 0x138 ; WM_CTLCOLOR(MsgBox|Edit|LISTBOX|BTN|DLG|SCROLLBAR|static) 757 | if (Win[hWnd].HasKey(lParam)) { 758 | if (tc := Win[hWnd, lParam, "tc"]) { 759 | DllCall("gdi32.dll\SetTextColor", "Ptr", wParam, "UInt", tc) 760 | } 761 | 762 | if (bc := Win[hWnd, lParam, "bc"]) { 763 | DllCall("gdi32.dll\SetBkColor", "Ptr", wParam, "UInt", bc) 764 | } 765 | 766 | return Win[hWnd, lParam, "Brush"] ; return the HBRUSH to notify the OS that we altered the HDC. 767 | } 768 | 769 | if (hWnd == "Set") { 770 | a := uMsg 771 | Win[a.g, a.c] := a 772 | 773 | if ((Win[a.g, a.c, "tc"] == "") && (Win[a.g, a.c, "bc"] == "")) { 774 | Win[a.g].Remove(a.c, "") 775 | } 776 | 777 | if (!Win[a.g, "WindowProcOld"]) { 778 | Win[a.g,"WindowProcOld"] := DllCall("SetWindowLong" . (A_PtrSize == 8 ? "Ptr" : "") 779 | , "Ptr", a.g, "Int", -4, "Ptr", RegisterCallback("CC_WindowProc", "", 4), "UPtr") 780 | } 781 | 782 | if (Win[a.g, a.c, "bc"] != "") { 783 | Win[a.g, a.c, "Brush"] := DllCall("gdi32.dll\CreateSolidBrush", "UInt", a.bc, "UPtr") 784 | } 785 | 786 | return 787 | } 788 | 789 | return DllCall("CallWindowProc", "Ptr", Win[hWnd, "WindowProcOld"], "Ptr", hWnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam, "Ptr") 790 | } 791 | 792 | SetEditCueBanner(HWND, Cue) { ; requires AHL_L 793 | Static EM_SETCUEBANNER := (0x1500 + 1) 794 | Return DllCall("User32.dll\SendMessageW", "Ptr", HWND, "Uint", EM_SETCUEBANNER, "Ptr", True, "WStr", Cue) 795 | } 796 | str2env(str){ ;full path 797 | if (!RegExMatch(str,"%(.+)%")) 798 | return str 799 | else{ 800 | EnvGet, OutputVar,% RegExReplace(str, "%(.+)%(.+)","$1") 801 | return OutputVar . RegExReplace(str, "%(.+)%(.+)","$2") 802 | } 803 | } 804 | 805 | TCMatchOn(dllPath = "") { 806 | if(g_TCMatchModule) 807 | return g_TCMatchModule 808 | g_TCMatchModule := DllCall("LoadLibrary", "Str", dllPath, "Ptr") 809 | return g_TCMatchModule 810 | } 811 | 812 | TCMatchOff() { 813 | DllCall("FreeLibrary", "Ptr", g_TCMatchModule) 814 | g_TCMatchModule := "" 815 | } 816 | 817 | TCMatch(aHaystack, aNeedle) { 818 | global 819 | static g_TCMatchModule 820 | ; MatchFileW := (A_PtrSize == 8 ) ? "TCMatch64\MatchFileW" : "TCMatch\MatchFileW" 821 | ; dllPath := A_ScriptDir "\Lib\tcmatch\" ((A_PtrSize == 8 ) ? "TCMatch64" : "TCMatch") ".dll" 822 | ; MatchFileW :=A_ScriptDir "\Lib\tcmatch\TCMatch\MatchFileW" 823 | ; dllPath := A_ScriptDir "\Lib\tcmatch\TCMatch" 824 | g_TCMatchModule := TCMatchOn(dllPath) 825 | Return DllCall(MatchFileW, "WStr", aNeedle, "WStr", aHaystack) 826 | } 827 | 828 | GF_GetSysVar(sys_var_name) 829 | { 830 | EnvGet, sv,% sys_var_name 831 | return % sv 832 | } 833 | switchime(ime := "A") 834 | { 835 | if (ime = 1) 836 | DllCall("SendMessage", UInt, WinActive("A"), UInt, 80, UInt, 1, UInt, DllCall("LoadKeyboardLayout", Str,"00000804", UInt, 1)) 837 | else if (ime = 0) 838 | DllCall("SendMessage", UInt, WinActive("A"), UInt, 80, UInt, 1, UInt, DllCall("LoadKeyboardLayout", Str,, UInt, 1)) 839 | else if (ime = "A") 840 | Send, #{Space} 841 | } 842 | 843 | -------------------------------------------------------------------------------- /TMD.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzncpa/tmd/bb51e5929e0076b2f877cf8a5d5f2a766a257115/TMD.exe -------------------------------------------------------------------------------- /cm.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzncpa/tmd/bb51e5929e0076b2f877cf8a5d5f2a766a257115/cm.ini -------------------------------------------------------------------------------- /lib/BTT.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | https://github.com/telppa/BeautifulToolTip 3 | 4 | If you want to add your own style to the built-in style, you can add it directly in btt(). 5 | 6 | version: 7 | 2021.10.4 8 | 9 | changelog: 10 | 2021.10.03 11 | 改变 Include 方式,降低库冲突的可能性。 12 | 2021.09.29 13 | 支持设置 TabStops 。 14 | 除 GDIP 库外所有函数内置到 Class 中,降低库冲突的可能性。 15 | 2021.04.30 16 | 修复 Win7 下不能运行的 bug 。(2021.04.20 引起) 17 | 2021.04.20 18 | 支持根据显示器 DPI 缩放比例自动缩放,与 ToolTip 特性保持一致。 19 | 支持直接使用未安装的本地字体。 20 | Options 增加 JustCalculateSize 参数,可在不绘制内容的前提下直接返回尺寸。 21 | 2021.03.07 22 | 增加 4 种渐变模式。 23 | 2021.03.05 24 | 删除渐变方向参数。 25 | 增加渐变角度参数,支持 360° 渐变。 26 | 增加渐变模式参数,支持 4 种模式。 27 | 2021.03.03 28 | 文本色支持渐变。 29 | 增加 Style8 。 30 | 增加 3 个渐变方向。 31 | 2021.03.02 32 | 细边框色支持渐变。 33 | 增加 Style7 。 34 | 增加 2 个渐变方向。 35 | 2021.03.01 36 | BTT 的总在最上级别现在跟 ToolTip 一样高了。 37 | 解决 BTT 被不明原因置底导致误以为没显示的问题。 38 | 增加 Style6 。 39 | 文字显示更加居中。 40 | 2021.02.22 41 | 增加返回值。 42 | Options 增加 Transparent 参数,可直接设置整体透明度。 43 | 44 | todo: 45 | 指定高宽 46 | 文字可获取 47 | 降低内存消耗 48 | 阴影 49 | ANSI版本的支持 50 | 文字太多导致没有显示完全的情况下给予明显提示(例如闪烁) 51 | 52 | 优势: 53 | *高性能 是内置 ToolTip 2-1000 倍性能(文本越大性能对比越大,多数普通应用场景2-5倍性能) 54 | *高兼容 兼容内置 ToolTip 的一切,包括语法、WhichToolTip、A_CoordModeToolTip、自动换行等等 55 | *简单易用 一行代码即使用 无需手动创建释放资源 56 | *多套内置风格 可通过模板快速实现自定义风格 57 | *可自定义风格 细边框(颜色、大小) 圆角 边距 文字(颜色、字体、字号、渲染方式、样式) 背景色 所有颜色支持360°渐变与透明 58 | *可自定义参数 贴附目标句柄 坐标模式 整体透明度 文本框永不被遮挡 跟随鼠标距离 不绘制内容直接返回尺寸 59 | *不闪烁不乱跳 60 | *多显示器支持 61 | *缩放显示支持 62 | *跟随鼠标不出界 63 | *可贴附指定目标 64 | */ 65 | global g_btt_X, g_btt_y, g_btt_w, g_btt_h 66 | btt(Text:="", X:="", Y:="", WhichToolTip:="", BulitInStyleOrStyles:="", BulitInOptionOrOptions:="") 67 | { 68 | static BTT 69 | ; You can customize your own style. 70 | ; All supported parameters are listed below. All parameters can be omitted. 71 | ; Please share your custom style and include a screenshot. It will help a lot of people. 72 | ; Attention: 73 | ; Color => ARGB => Alpha Red Green Blue => 0x ff aa bb cc => 0xffaabbcc 74 | , Style99 := {Border:20 ; If omitted, 1 will be used. Range 0-20. 75 | , Rounded:30 ; If omitted, 3 will be used. Range 0-30. 76 | , Margin:30 ; If omitted, 5 will be used. Range 0-30. 77 | , TabStops:[50, 80, 100] ; If omitted, [50] will be used. This value must be an array. 78 | , BorderColor:0xffaabbcc ; ARGB 79 | , BorderColorLinearGradientStart:0xff16a085 ; ARGB 80 | , BorderColorLinearGradientEnd:0xfff4d03f ; ARGB 81 | , BorderColorLinearGradientAngle:45 ; Mode=8 Angle 0(L to R) 90(U to D) 180(R to L) 270(D to U) 82 | , BorderColorLinearGradientMode:1 ; Mode=4 Angle 0(L to R) 90(D to U), Range 1-8. 83 | , TextColor:0xff112233 ; ARGB 84 | , TextColorLinearGradientStart:0xff00416a ; ARGB 85 | , TextColorLinearGradientEnd:0xffe4e5e6 ; ARGB 86 | , TextColorLinearGradientAngle:90 ; Mode=8 Angle 0(L to R) 90(U to D) 180(R to L) 270(D to U) 87 | , TextColorLinearGradientMode:1 ; Mode=4 Angle 0(L to R) 90(D to U), Range 1-8. 88 | , BackgroundColor:0xff778899 ; ARGB 89 | , BackgroundColorLinearGradientStart:0xff8DA5D3 ; ARGB 90 | , BackgroundColorLinearGradientEnd:0xffF4CFC9 ; ARGB 91 | , BackgroundColorLinearGradientAngle:135 ; Mode=8 Angle 0(L to R) 90(U to D) 180(R to L) 270(D to U) 92 | , BackgroundColorLinearGradientMode:1 ; Mode=4 Angle 0(L to R) 90(D to U), Range 1-8. 93 | , Font:"Font Name" ; If omitted, ToolTip's Font will be used. Can specify the font file path. 94 | , FontSize:20 ; If omitted, 12 will be used. 95 | , FontRender:5 ; If omitted, 5 will be used. Range 0-5. 96 | , FontStyle:"Regular Bold Italic BoldItalic Underline Strikeout"} 97 | 98 | , Option99 := {TargetHWND:"" ; If omitted, active window will be used. 99 | , CoordMode:"Screen Relative Window Client" ; If omitted, A_CoordModeToolTip will be used. 100 | , Transparent:"50" ; If omitted, 255 will be used. 101 | , MouseNeverCoverToolTip:"" ; If omitted, 1 will be used. 102 | , DistanceBetweenMouseXAndToolTip:"" ; If omitted, 16 will be used. This value can be negative. 103 | , DistanceBetweenMouseYAndToolTip:"" ; If omitted, 16 will be used. This value can be negative. 104 | , JustCalculateSize:""} ; Set to 1, no content will be displayed, just calculate size and return. 105 | 106 | , Style1 := {TextColor:0xffeef8f6 107 | , Transparent:50 ; If omitted, 255 will be used. 108 | , BackgroundColor:0xff1b8dff 109 | , FontSize:14} 110 | 111 | , Style2 := {Border:5 112 | , Rounded:0 113 | , TextColor:0xfff4f4f4 114 | , BackgroundColor:0xaa3e3d45 115 | , FontSize:14} 116 | 117 | , Style3 := {Border:2 118 | , Rounded:0 119 | , TextColor:0xffF15839 120 | , BackgroundColor:0xffFCEDE6 121 | , FontSize:14} 122 | 123 | , Style4 := {Border:10 124 | , Rounded:20 125 | , BorderColor:0xff604a78 126 | , TextColor:0xffF3AE00 127 | , BackgroundColor:0xff6A537F 128 | , FontSize:15 129 | , FontStyle:"Bold Italic"} 130 | 131 | , Style5 := {Border:0 132 | , Rounded:5 133 | , TextColor:0xffeeeeee 134 | , BackgroundColorLinearGradientStart:0xff134E5E 135 | , BackgroundColorLinearGradientEnd:0xff326f69 136 | , BackgroundColorLinearGradientAngle:0 137 | , BackgroundColorLinearGradientMode:1} 138 | 139 | , Style6 := {Border:2 140 | , Rounded:5 141 | , TextColor:0xffCAE682 142 | , BackgroundColor:0xff434343 143 | , FontSize:14} 144 | 145 | , Style7 := {Border:20 146 | , Rounded:30 147 | , Margin:30 148 | , BorderColor:0xffaabbcc 149 | , TextColor:0xff112233 150 | , BackgroundColorLinearGradientStart:0xffF4CFC9 151 | , BackgroundColorLinearGradientEnd:0xff8DA5D3 152 | , BackgroundColorLinearGradientAngle:0 153 | , BackgroundColorLinearGradientMode:1 154 | , FontStyle:"BoldItalic"} 155 | 156 | , Style8 := {Border:3 157 | , Rounded:30 158 | , Margin:30 159 | , BorderColorLinearGradientStart:0xffb7407c 160 | , BorderColorLinearGradientEnd:0xff3881a7 161 | , BorderColorLinearGradientAngle:45 162 | , BorderColorLinearGradientMode:1 163 | , TextColor:0xffd9d9db 164 | , BackgroundColor:0xff26293a} 165 | 166 | ; 直接在 static 中初始化 BTT 会报错,所以只能这样写 167 | if (BTT="") 168 | BTT := new BeautifulToolTip() 169 | 170 | return, BTT.ToolTip(Text, X, Y, WhichToolTip 171 | ; 如果 Style 是一个内置预设的名称,则使用对应内置预设的值,否则使用 Styles 本身的值。 Options 同理。 172 | , %BulitInStyleOrStyles%="" ? BulitInStyleOrStyles : %BulitInStyleOrStyles% 173 | , %BulitInOptionOrOptions%="" ? BulitInOptionOrOptions : %BulitInOptionOrOptions%) 174 | } 175 | 176 | Class BeautifulToolTip 177 | { 178 | ; 以下这些是类中静态变量。末尾带数字1的,表明最多存在1-20这样的变体。例如 _BTT1 就有 _BTT1 ... _BTT20 共20个类似变量。 179 | ; pToken, Monitors, ToolTipFontName, DIBWidth, DIBHeight 180 | ; MouseNeverCoverToolTip, DistanceBetweenMouseXAndToolTip, DistanceBetweenMouseYAndToolTip 181 | ; hBTT1(GUI句柄), hbm1, hdc1, obm1, G1 182 | ; SavedText1, SavedOptions1, SavedX1, SavedY1, SavedW1, SavedH1, SavedCoordMode1, SavedTargetHWND1 183 | static DebugMode:=0 184 | 185 | __New() 186 | { 187 | if (!this.pToken) 188 | { 189 | ; 加速 190 | SavedBatchLines:=A_BatchLines 191 | SetBatchLines, -1 192 | 193 | ; 多实例启动 gdi+ 。避免有些人修改内部代码时与他自己的 gdi+ 冲突。 194 | this.pToken := Gdip_Startup(1) 195 | if (!this.pToken) 196 | { 197 | MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system 198 | ExitApp 199 | } 200 | 201 | ; 多显示器支持 202 | this.Monitors := MDMF_Enum() 203 | ; 获取多显示器各自 DPI 缩放比例 204 | for hMonitor, v in this.Monitors.Clone() 205 | { 206 | if (hMonitor="TotalCount" or hMonitor="Primary") 207 | continue 208 | ; https://github.com/Ixiko/AHK-libs-and-classes-collection/blob/e421acb801867edb659a54b7473e6e617f2b267b/libs/g-n/Monitor.ahk 209 | ; ahk 源码里 A_ScreenDPI 就是只获取了 dpiX ,所以这里保持一致 210 | osv := StrSplit(A_OSVersion, ".") ; https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw 211 | if (osv[1] < 6 || (osv[1] == 6 && osv[2] < 3)) ; WIN_8- 。Win7 必须用这种方式,否则会出错 212 | { 213 | hDC := DllCall("Gdi32.dll\CreateDC", "Str", hMonitor.name, "Ptr", 0, "Ptr", 0, "Ptr", 0, "Ptr") 214 | dpiX := DllCall("Gdi32.dll\GetDeviceCaps", "Ptr", hDC, "Int", 88) ; LOGPIXELSX = 88 215 | DllCall("Gdi32.dll\DeleteDC", "Ptr", hDC) 216 | } 217 | else 218 | DllCall("Shcore.dll\GetDpiForMonitor", "Ptr", hMonitor, "Int", Type, "UIntP", dpiX, "UIntP", dpiY, "UInt") 219 | this.Monitors[hMonitor].DPIScale := this.NonNull_Ret(dpiX, A_ScreenDPI)/96 220 | } 221 | 222 | ; 获取整个桌面的分辨率,即使跨显示器 223 | SysGet, VirtualWidth, 78 224 | SysGet, VirtualHeight, 79 225 | this.DIBWidth := VirtualWidth 226 | this.DIBHeight := VirtualHeight 227 | 228 | ; 获取 ToolTip 的默认字体 229 | this.ToolTipFontName := this.Fnt_GetTooltipFontName() 230 | 231 | ; create 20 guis for gdi+ 232 | ; 最多20个 ToolTip ,与原版对应。 233 | loop, 20 234 | { 235 | ; _BTT1(GUI 名称) 与 _hBTT1(GUI 句柄) 都是临时变量,后者被储存了。 236 | Gui, _BTT%A_Index%: +E0x80000 -Caption +ToolWindow +LastFound +AlwaysOnTop +Hwnd_hBTT%A_Index% 237 | Gui, _BTT%A_Index%: Show, NA 238 | 239 | this["hBTT" A_Index] := _hBTT%A_Index% 240 | , this["hbm" A_Index] := CreateDIBSection(this.DIBWidth, this.DIBHeight) 241 | , this["hdc" A_Index] := CreateCompatibleDC() 242 | , this["obm" A_Index] := SelectObject(this["hdc" A_Index], this["hbm" A_Index]) 243 | , this["G" A_Index] := Gdip_GraphicsFromHDC(this["hdc" A_Index]) 244 | , Gdip_SetSmoothingMode(this["G" A_Index], 4) 245 | , Gdip_SetPixelOffsetMode(this["G" A_Index], 2) ; 此参数是画出完美圆角矩形的关键 246 | } 247 | SetBatchLines, %SavedBatchLines% 248 | } 249 | else 250 | return 251 | } 252 | 253 | ; new 后得到的变量在销毁后会自动跳这里来运行,因此很适合做自动资源回收。 254 | __Delete() 255 | { 256 | loop, 20 257 | { 258 | Gdip_DeleteGraphics(this["G" A_Index]) 259 | , SelectObject(this["hdc" A_Index], this["obm" A_Index]) 260 | , DeleteObject(this["hbm" A_Index]) 261 | , DeleteDC(this["hdc" A_Index]) 262 | } 263 | Gdip_Shutdown(this.pToken) 264 | } 265 | 266 | ; 参数默认全部为空只是为了让清空 ToolTip 的语法简洁而已。 267 | ToolTip(Text:="", X:="", Y:="", WhichToolTip:="", Styles:="", Options:="") 268 | { 269 | ; 给出 WhichToolTip 的默认值1,并限制 WhichToolTip 的范围为 1-20 270 | this.NonNull(WhichToolTip, 1, 1, 20) 271 | ; 检查并解析 Styles 与 Options 。无论不传参、部分传参、完全传参,此函数均能正确返回所需参数。 272 | O:=this._CheckStylesAndOptions(Styles, Options) 273 | 274 | ; 判断显示内容是否发生变化。由于前面给了 Options 一个默认值,所以首次进来时下面的 O.Options!=SavedOptions 是必然成立的。 275 | FirstCallOrNeedToUpdate:=(Text != this["SavedText" WhichToolTip] 276 | or O.Checksum != this["SavedOptions" WhichToolTip]) 277 | 278 | if (Text="") 279 | { 280 | ; 清空 ToolTip 281 | Gdip_GraphicsClear(this["G" WhichToolTip]) 282 | UpdateLayeredWindow(this["hBTT" WhichToolTip], this["hdc" WhichToolTip]) 283 | ; 清空变量 284 | this["SavedText" WhichToolTip] := "" 285 | , this["SavedOptions" WhichToolTip] := "" 286 | , this["SavedX" WhichToolTip] := "" 287 | , this["SavedY" WhichToolTip] := "" 288 | , this["SavedW" WhichToolTip] := "" 289 | , this["SavedH" WhichToolTip] := "" 290 | , this["SavedTargetHWND" WhichToolTip] := "" 291 | , this["SavedCoordMode" WhichToolTip] := "" 292 | , this["SavedTransparent" WhichToolTip] := "" 293 | 294 | return 295 | } 296 | else if (FirstCallOrNeedToUpdate) ; First Call or NeedToUpdate 297 | { 298 | ; 加速 299 | SavedBatchLines:=A_BatchLines 300 | SetBatchLines, -1 301 | 302 | ; 获取目标尺寸,用于计算文本大小时加上宽高限制,否则目标为屏幕时,可能计算出超宽超高的大小,导致无法显示。 303 | TargetSize:=this._CalculateDisplayPosition(X, Y, "", "", O, GetTargetSize:=1) 304 | ; 使得 文字区域+边距+细边框 不会超过目标宽度。 305 | , MaxTextWidth:=TargetSize.W - O.Margin*2 - O.Border*2 306 | ; 使得 文字区域+边距+细边框 不会超过目标高度的90%。 307 | ; 之所以高度限制90%是因为两个原因,1是留出一些上下的空白,避免占满全屏,鼠标点不了其它地方,难以退出。 308 | ; 2是在计算文字区域时,即使已经给出了宽高度限制,且因为自动换行的原因,宽度的返回值通常在范围内,但高度的返回值偶尔还是会超过1行,所以提前留个余量。 309 | , MaxTextHeight:=(TargetSize.H*90)//100 - O.Margin*2 - O.Border*2 310 | ; 为 _TextToGraphics 计算区域提供高宽限制。 311 | , O.Width:=MaxTextWidth, O.Height:=MaxTextHeight 312 | ; 计算文字显示区域 TextArea = x|y|width|height|chars|lines 313 | , TextArea:=StrSplit(this._TextToGraphics(this["G" WhichToolTip], Text, O, Measure:=1), "|") 314 | ; 这里务必向上取整。 315 | ; 向上的原因是,例如 1.2 如果四舍五入为 1,那么最右边的字符可能会显示不全。 316 | ; 取整的原因是,不取整无法画出完美的圆角矩形。 317 | ; 当使用 AutoTrim 选项,即自动将超出范围的文字显示为 “...” 时,此时返回的宽高度值是不包含 “...” 的。 318 | ; 所以加上 “...” 的宽高度后,仍然可能超限。故需要再次检查并限制。 319 | ; 一旦宽高度超过了限制(CreateDIBSection() 时创建的大小),会导致 UpdateLayeredWindow() 画不出图像来。 320 | , TextWidth:=Min(Ceil(TextArea[3]), MaxTextWidth) 321 | , TextHeight:=Min(Ceil(TextArea[4]), MaxTextHeight) 322 | 323 | , RectWidth:=TextWidth+O.Margin*2 ; 文本+边距。 324 | , RectHeight:=TextHeight+O.Margin*2 325 | , RectWithBorderWidth:=RectWidth+O.Border*2 ; 文本+边距+细边框。 326 | , RectWithBorderHeight:=RectHeight+O.Border*2 327 | ; 圆角超过矩形宽或高的一半时,会画出畸形的圆,所以这里验证并限制一下。 328 | , R:=(O.Rounded>Min(RectWidth, RectHeight)//2) ? Min(RectWidth, RectHeight)//2 : O.Rounded 329 | 330 | if (O.JustCalculateSize!=1) 331 | { 332 | ; 画之前务必清空画布,否则会出现异常。 333 | Gdip_GraphicsClear(this["G" WhichToolTip]) 334 | 335 | ; 准备细边框画刷 336 | if (O.BCLGA!="" and O.BCLGM and O.BCLGS and O.BCLGE) ; 渐变画刷 画细边框 337 | pBrushBorder := this._CreateLinearGrBrush(O.BCLGA, O.BCLGM, O.BCLGS, O.BCLGE 338 | , 0, 0, RectWithBorderWidth, RectWithBorderHeight) 339 | else 340 | pBrushBorder := Gdip_BrushCreateSolid(O.BorderColor) ; 纯色画刷 画细边框 341 | 342 | if (O.Border>0) 343 | switch, R 344 | { 345 | ; 圆角为0则使用矩形画。不单独处理,会画出显示异常的图案。 346 | case, "0": Gdip_FillRectangle(this["G" WhichToolTip] ; 矩形细边框 347 | , pBrushBorder, 0, 0, RectWithBorderWidth, RectWithBorderHeight) 348 | Default : Gdip_FillRoundedRectanglePath(this["G" WhichToolTip] ; 圆角细边框 349 | , pBrushBorder, 0, 0, RectWithBorderWidth, RectWithBorderHeight, R) 350 | } 351 | 352 | ; 准备文本框画刷 353 | if (O.BGCLGA!="" and O.BGCLGM and O.BGCLGS and O.BGCLGE) ; 渐变画刷 画文本框 354 | pBrushBackground := this._CreateLinearGrBrush(O.BGCLGA, O.BGCLGM, O.BGCLGS, O.BGCLGE 355 | , O.Border, O.Border, RectWidth, RectHeight) 356 | else 357 | pBrushBackground := Gdip_BrushCreateSolid(O.BackgroundColor) ; 纯色画刷 画文本框 358 | 359 | switch, R 360 | { 361 | case, "0": Gdip_FillRectangle(this["G" WhichToolTip] ; 矩形文本框 362 | , pBrushBackground, O.Border, O.Border, RectWidth, RectHeight) 363 | Default : Gdip_FillRoundedRectanglePath(this["G" WhichToolTip] ; 圆角文本框 364 | , pBrushBackground, O.Border, O.Border, RectWidth, RectHeight 365 | , (R>O.Border) ? R-O.Border : R) ; 确保内外圆弧看起来同心 366 | } 367 | 368 | ; 清理画刷 369 | Gdip_DeleteBrush(pBrushBorder) 370 | Gdip_DeleteBrush(pBrushBackground) 371 | 372 | ; 计算居中显示坐标。由于 TextArea 返回的文字范围右边有很多空白,所以这里的居中坐标并不精确。 373 | O.X:=O.Border+O.Margin, O.Y:=O.Border+O.Margin, O.Width:=TextWidth, O.Height:=TextHeight 374 | 375 | ; 如果显示区域过小,文字无法完全显示,则将待显示文本最后4个字符替换为4个英文省略号,表示显示不完全。 376 | ; 虽然有 GdipSetStringFormatTrimming 函数可以设置末尾显示省略号,但它偶尔需要增加额外的宽度才能显示出来。 377 | ; 由于难以判断是否需要增加额外宽度,以及需要增加多少等等问题,所以直接用这种方式自己实现省略号的显示。 378 | ; 之所以选择替换最后4个字符,是因为一般替换掉最后2个字符,才能确保至少一个省略号显示出来。 379 | ; 为了应对意外情况,以及让省略号更加明显一点,所以选择替换最后4个。 380 | ; 原始的 Text 需要用于显示前的比对,所以不能改原始值,必须用 TempText 。 381 | if (TextArea[5]4 ? SubStr(Text, 1 ,TextArea[5]-4) "…………" : SubStr(Text, 1 ,1) "…………" 383 | else 384 | TempText:=Text 385 | 386 | ; 写字到框上。这个函数使用 O 中的 X,Y 去调整文字的位置。 387 | this._TextToGraphics(this["G" WhichToolTip], TempText, O) 388 | 389 | ; 调试用,可显示计算得到的文字范围。 390 | if (this.DebugMode) 391 | { 392 | pBrush := Gdip_BrushCreateSolid(0x20ff0000) 393 | Gdip_FillRectangle(this["G" WhichToolTip], pBrush, O.Border+O.Margin, O.Border+O.Margin, TextWidth, TextHeight) 394 | Gdip_DeleteBrush(pBrush) 395 | } 396 | 397 | ; 返回文本框不超出目标范围(比如屏幕范围)的最佳坐标。 398 | this._CalculateDisplayPosition(X, Y, RectWithBorderWidth, RectWithBorderHeight, O) 399 | 400 | ; 显示 401 | UpdateLayeredWindow(this["hBTT" WhichToolTip], this["hdc" WhichToolTip] 402 | , X, Y, RectWithBorderWidth, RectWithBorderHeight, O.Transparent) 403 | 404 | ; 因为 BTT 总会在使用一段时间后,被不明原因的置底,导致显示内容被其它窗口遮挡,以为没有正常显示,所以这里提升Z序到最上面! 405 | ; 已测试过,此方法效率极高,远超 WinSet, Top 命令。 406 | ; hWndInsertAfter 407 | ; HWND_TOPMOST:=-1 408 | ; uFlags 409 | ; SWP_NOSIZE:=0x0001 410 | ; SWP_NOMOVE:=0x0002 411 | ; SWP_NOREDRAW:=0x0008 412 | ; SWP_NOACTIVATE:=0x0010 413 | ; SWP_NOOWNERZORDER:=0x0200 414 | ; SWP_NOSENDCHANGING:=0x0400 415 | ; SWP_DEFERERASE:=0x2000 416 | ; SWP_ASYNCWINDOWPOS:=0x4000 417 | DllCall("SetWindowPos", "ptr", this["hBTT" WhichToolTip], "ptr", -1, "int", 0, "int", 0, "int", 0, "int", 0, "uint", 26139) 418 | } 419 | 420 | ; 保存参数值,以便之后比对参数值是否改变 421 | this["SavedText" WhichToolTip] := Text 422 | , this["SavedOptions" WhichToolTip] := O.Checksum 423 | , this["SavedX" WhichToolTip] := X ; 这里的 X,Y 是经过 _CalculateDisplayPosition() 计算后的新 X,Y 424 | , this["SavedY" WhichToolTip] := Y 425 | , this["SavedW" WhichToolTip] := RectWithBorderWidth 426 | , this["SavedH" WhichToolTip] := RectWithBorderHeight 427 | , this["SavedTargetHWND" WhichToolTip] := O.TargetHWND 428 | , this["SavedCoordMode" WhichToolTip] := O.CoordMode 429 | , this["SavedTransparent" WhichToolTip] := O.Transparent 430 | 431 | SetBatchLines, %SavedBatchLines% 432 | } 433 | ; x,y 任意一个跟随鼠标位置 或 使用窗口或客户区模式(窗口大小可能发生改变或者窗口发生移动) 434 | ; 或 目标窗口发生变化 或 坐标模式发生变化 435 | ; 或 整体透明度发生变化 这5种情况可能需要移动位置,需要进行坐标计算。 436 | else if ((X="" or Y="") or O.CoordMode!="Screen" 437 | or O.TargetHWND!=this.SavedTargetHWND or O.CoordMode!=this.SavedCoordMode 438 | or O.Transparent!=this.SavedTransparent) 439 | { 440 | ; 返回文本框不超出目标范围(比如屏幕范围)的最佳坐标。 441 | this._CalculateDisplayPosition(X, Y, this["SavedW" WhichToolTip], this["SavedH" WhichToolTip], O) 442 | ; 判断文本框 显示位置 443 | ; 或 显示透明度 是否发生改变 444 | if (X!=this["SavedX" WhichToolTip] or Y!=this["SavedY" WhichToolTip] 445 | or O.Transparent!=this.SavedTransparent) 446 | { 447 | ; 显示 448 | UpdateLayeredWindow(this["hBTT" WhichToolTip], this["hdc" WhichToolTip] 449 | , X, Y, this["SavedW" WhichToolTip], this["SavedH" WhichToolTip], O.Transparent) 450 | 451 | ; 保存新的位置 452 | this["SavedX" WhichToolTip] := X 453 | , this["SavedY" WhichToolTip] := Y 454 | , this["SavedTargetHWND" WhichToolTip] := O.TargetHWND 455 | , this["SavedCoordMode" WhichToolTip] := O.CoordMode 456 | , this["SavedTransparent" WhichToolTip] := O.Transparent 457 | } 458 | } 459 | 460 | ret:={Hwnd : this["hBTT" WhichToolTip] 461 | , X : X 462 | , Y : Y 463 | , W : this["SavedW" WhichToolTip] 464 | , H : this["SavedH" WhichToolTip]} 465 | return, ret 466 | } 467 | 468 | ; 为了统一参数的传输,以及特殊模式的设置,修改了 gdip 库的 Gdip_TextToGraphics() 函数。 469 | _TextToGraphics(pGraphics, Text, Options, Measure:=0) 470 | { 471 | static Styles := "Regular|Bold|Italic|BoldItalic|Underline|Strikeout" 472 | 473 | ; 设置字体样式 474 | Style := 0 475 | for eachStyle, valStyle in StrSplit(Styles, "|") 476 | { 477 | if InStr(Options.FontStyle, valStyle) 478 | Style |= (valStyle != "StrikeOut") ? (A_Index-1) : 8 479 | } 480 | 481 | if (FileExist(Options.Font)) ; 加载未安装的本地字体 482 | { 483 | hFontCollection := Gdip_NewPrivateFontCollection() 484 | hFontFamily := Gdip_CreateFontFamilyFromFile(Options.Font, hFontCollection) 485 | } 486 | if !hFontFamily ; 加载已安装的字体 487 | hFontFamily := Gdip_FontFamilyCreate(Options.Font) 488 | if !hFontFamily ; 加载默认字体 489 | hFontFamily := Gdip_FontFamilyCreateGeneric(1) 490 | ; 根据 DPI 缩放比例自动调整字号 491 | hFont := Gdip_FontCreate(hFontFamily, Options.FontSize * Options.DPIScale, Style, Unit:=0) 492 | 493 | ; 设置文字格式化样式,LineLimit = 0x00002000 只显示完整的行。 494 | ; 比如最后一行,因为布局高度有限,只能显示出一半,此时就会让它完全不显示。 495 | ; 直接使用 Gdip_StringFormatGetGeneric(1) 包含 LineLimit 设置,同时可以实现左右空白区域最小化。 496 | ; 但这样有个副作用,那就是无法精确的设置文本框的宽度了,同时最右边文字的间距会被压缩。 497 | ; 例如指定宽度800,可能返回的宽度是793,因为右边没有用空白补上。 498 | ; 好处是右边几乎没有空白区域,左边也没有,所以接近完美的实现文字居中了。 499 | ; hStringFormat := Gdip_StringFormatCreate(0x00002000) 500 | ; if !hStringFormat 501 | hStringFormat := Gdip_StringFormatGetGeneric(1) 502 | 503 | ; 准备文本画刷 504 | if (Options.TCLGA!="" and Options.TCLGM and Options.TCLGS and Options.TCLGE 505 | and Options.Width and Options.Height) ; 渐变画刷 506 | { 507 | pBrush := this._CreateLinearGrBrush(Options.TCLGA, Options.TCLGM, Options.TCLGS, Options.TCLGE 508 | , this.NonNull_Ret(Options.X, 0), this.NonNull_Ret(Options.Y, 0) 509 | , Options.Width, Options.Height) 510 | } 511 | else 512 | pBrush := Gdip_BrushCreateSolid(Options.TextColor) ; 纯色画刷 513 | 514 | ; 检查参数是否齐全 515 | if !(hFontFamily && hFont && hStringFormat && pBrush && pGraphics) 516 | { 517 | E := !pGraphics ? -2 : !hFontFamily ? -3 : !hFont ? -4 : !hStringFormat ? -5 : !pBrush ? -6 : 0 518 | if pBrush 519 | Gdip_DeleteBrush(pBrush) 520 | if hStringFormat 521 | Gdip_DeleteStringFormat(hStringFormat) 522 | if hFont 523 | Gdip_DeleteFont(hFont) 524 | if hFontFamily 525 | Gdip_DeleteFontFamily(hFontFamily) 526 | if hFontCollection 527 | Gdip_DeletePrivateFontCollection(hFontCollection) 528 | return E 529 | } 530 | 531 | TabStops := [] 532 | for k, v in Options.TabStops 533 | TabStops.Push(v * Options.DPIScale) 534 | Gdip_SetStringFormatTabStops(hStringFormat, TabStops) ; 设置 TabStops 535 | Gdip_SetStringFormatAlign(hStringFormat, Align:=0) ; 设置左对齐 536 | Gdip_SetTextRenderingHint(pGraphics, Options.FontRender) ; 设置渲染模式 537 | CreateRectF(RC 538 | , this.NonNull_Ret(Options.X, 0) ; x,y 需要至少为0 539 | , this.NonNull_Ret(Options.Y, 0) 540 | , Options.Width, Options.Height) ; 宽高可以为空 541 | returnRC := Gdip_MeasureString(pGraphics, Text, hFont, hStringFormat, RC) ; 计算大小 542 | 543 | if !Measure 544 | _E := Gdip_DrawString(pGraphics, Text, hFont, hStringFormat, pBrush, RC) 545 | 546 | Gdip_DeleteBrush(pBrush) 547 | Gdip_DeleteFont(hFont) 548 | Gdip_DeleteStringFormat(hStringFormat) 549 | Gdip_DeleteFontFamily(hFontFamily) 550 | if hFontCollection 551 | Gdip_DeletePrivateFontCollection(hFontCollection) 552 | return _E ? _E : returnRC 553 | } 554 | 555 | _CreateLinearGrBrush(Angle, Mode, StartColor, EndColor, x, y, w, h) 556 | { 557 | ; Mode=8 Angle 0=左到右 90=上到下 180=右到左 270=下到上 558 | ; Mode=3 Angle 0=左到右 90=近似上到下 559 | ; Mode=4 Angle 0=左到右 90=下到上 560 | switch, Mode 561 | { 562 | case, 1,3,5,7:pBrush:=Gdip_CreateLinearGrBrush(x, y, x+w, y, StartColor, EndColor) 563 | case, 2,4,6,8:pBrush:=Gdip_CreateLinearGrBrush(x, y+h//2, x+w, y+h//2, StartColor, EndColor) 564 | } 565 | switch, Mode 566 | { 567 | case, 1,2: Gdip_RotateLinearGrBrushTransform(pBrush, Angle, 0) ; 性能比模式3、4高10倍左右 568 | case, 3,4: Gdip_RotateLinearGrBrushTransform(pBrush, Angle, 1) 569 | case, 5,6: Gdip_RotateLinearGrBrushAtCenter(pBrush, Angle, 0) 570 | case, 7,8: Gdip_RotateLinearGrBrushAtCenter(pBrush, Angle, 1) ; 可绕中心旋转 571 | } 572 | return, pBrush 573 | } 574 | 575 | ; 此函数确保传入空值或者错误值均可返回正确值。 576 | _CheckStylesAndOptions(Styles, Options) 577 | { 578 | O := {} 579 | , O.Border := this.NonNull_Ret(Styles.Border , 1 , 0 , 20) ; 细边框 默认1 0-20 580 | , O.Rounded := this.NonNull_Ret(Styles.Rounded , 3 , 0 , 30) ; 圆角 默认3 0-30 581 | , O.Margin := this.NonNull_Ret(Styles.Margin , 5 , 0 , 30) ; 边距 默认5 0-30 582 | , O.TabStops := this.NonNull_Ret(Styles.TabStops , [50] , "", "") ; 制表符宽 默认[50] 583 | , O.TextColor := this.NonNull_Ret(Styles.TextColor , 0xff575757 , "", "") ; 文本色 默认0xff575757 584 | , O.BackgroundColor := this.NonNull_Ret(Styles.BackgroundColor, 0xffffffff , "", "") ; 背景色 默认0xffffffff 585 | , O.Font := this.NonNull_Ret(Styles.Font , this.ToolTipFontName, "", "") ; 字体 默认与 ToolTip 一致 586 | , O.FontSize := this.NonNull_Ret(Styles.FontSize , 12 , "", "") ; 字号 默认12 587 | , O.FontRender := this.NonNull_Ret(Styles.FontRender , 5 , 0 , 5 ) ; 渲染模式 默认5 0-5 588 | , O.FontStyle := Styles.FontStyle ; 字体样式 默认无 589 | 590 | ; 名字太长,建个缩写副本。 591 | , O.BCLGS := Styles.BorderColorLinearGradientStart ; 细边框渐变色 默认无 592 | , O.BCLGE := Styles.BorderColorLinearGradientEnd ; 细边框渐变色 默认无 593 | , O.BCLGA := Styles.BorderColorLinearGradientAngle ; 细边框渐变角度 默认无 594 | , O.BCLGM := this.NonNull_Ret(Styles.BorderColorLinearGradientMode, "", 1, 8) ; 细边框渐变模式 默认无 1-8 595 | 596 | ; 名字太长,建个缩写副本。 597 | , O.TCLGS := Styles.TextColorLinearGradientStart ; 文本渐变色 默认无 598 | , O.TCLGE := Styles.TextColorLinearGradientEnd ; 文本渐变色 默认无 599 | , O.TCLGA := Styles.TextColorLinearGradientAngle ; 文本渐变角度 默认无 600 | , O.TCLGM := this.NonNull_Ret(Styles.TextColorLinearGradientMode, "", 1, 8) ; 文本渐变模式 默认无 1-8 601 | 602 | ; 名字太长,建个缩写副本。 603 | , O.BGCLGS := Styles.BackgroundColorLinearGradientStart ; 背景渐变色 默认无 604 | , O.BGCLGE := Styles.BackgroundColorLinearGradientEnd ; 背景渐变色 默认无 605 | , O.BGCLGA := Styles.BackgroundColorLinearGradientAngle ; 背景渐变角度 默认无 606 | , O.BGCLGM := this.NonNull_Ret(Styles.BackgroundColorLinearGradientMode, "", 1, 8) ; 背景渐变模式 默认无 1-8 607 | 608 | ; a:=0xaabbccdd 下面是运算规则 609 | ; a>>16 = 0xaabb 610 | ; a>>24 = 0xaa 611 | ; a&0xffff = 0xccdd 612 | ; a&0xff = 0xdd 613 | ; 0x88<<16 = 0x880000 614 | ; 0x880000+0xbbcc = 0x88bbcc 615 | , BlendedColor2 := (O.TCLGS and O.TCLGE and O.TCLGD) ? O.TCLGS : O.TextColor ; 使用文本渐变色替换文本色用于混合 616 | , BlendedColor := ((O.BackgroundColor>>24)<<24) + (BlendedColor2&0xffffff) ; 混合色 背景色的透明度与文本色混合 617 | , O.BorderColor := this.NonNull_Ret(Styles.BorderColor , BlendedColor , "", "") ; 细边框色 默认混合色 618 | 619 | , O.TargetHWND := this.NonNull_Ret(Options.TargetHWND , WinExist("A") , "", "") ; 目标句柄 默认活动窗口 620 | , O.CoordMode := this.NonNull_Ret(Options.CoordMode , A_CoordModeToolTip, "", "") ; 坐标模式 默认与 ToolTip 一致 621 | , O.Transparent := this.NonNull_Ret(Options.Transparent, 255 , 0 , 255) ; 整体透明度 默认255 622 | , O.MouseNeverCoverToolTip := this.NonNull_Ret(Options.MouseNeverCoverToolTip , 1 , 0 , 1 ) ; 鼠标永不遮挡文本框 623 | , O.DistanceBetweenMouseXAndToolTip := this.NonNull_Ret(Options.DistanceBetweenMouseXAndToolTip, 16, "", "") ; 鼠标与文本框的X距离 624 | , O.DistanceBetweenMouseYAndToolTip := this.NonNull_Ret(Options.DistanceBetweenMouseYAndToolTip, 16, "", "") ; 鼠标与文本框的Y距离 625 | , O.JustCalculateSize := Options.JustCalculateSize ; 仅计算显示尺寸并返回 626 | 627 | ; 难以比对两个对象是否一致,所以造一个变量比对。 628 | ; 这里的校验因素,必须是那些改变后会使画面内容也产生变化的因素。 629 | ; 所以没有 TargetHWND 和 CoordMode 和 Transparent ,因为这三个因素只影响位置。 630 | for k, v in O.TabStops 631 | TabStops .= v "," 632 | O.Checksum := O.Border "|" O.Rounded "|" O.Margin "|" TabStops "|" 633 | . O.BorderColor "|" O.BCLGS "|" O.BCLGE "|" O.BCLGA "|" O.BCLGM "|" 634 | . O.TextColor "|" O.TCLGS "|" O.TCLGE "|" O.TCLGA "|" O.TCLGM "|" 635 | . O.BackgroundColor "|" O.BGCLGS "|" O.BGCLGE "|" O.BGCLGA "|" O.BGCLGM "|" 636 | . O.Font "|" O.FontSize "|" O.FontRender "|" O.FontStyle 637 | return, O 638 | } 639 | 640 | ; 此函数确保文本框显示位置不会超出目标范围。 641 | ; 使用 ByRef X, ByRef Y 返回不超限的位置。 642 | _CalculateDisplayPosition(ByRef X, ByRef Y, W, H, Options, GetTargetSize:=0) 643 | { 644 | VarSetCapacity(Point, 8, 0) 645 | ; 获取鼠标位置 646 | , DllCall("GetCursorPos", "Ptr", &Point) 647 | , MouseX := NumGet(Point, 0, "Int"), MouseY := NumGet(Point, 4, "Int") 648 | 649 | ; x,y 即 ToolTip 显示的位置。 650 | ; x,y 同时为空表明完全跟随鼠标。 651 | ; x,y 单个为空表明只跟随鼠标横向或纵向移动。 652 | ; x,y 都有值,则说明被钉在屏幕或窗口或客户区的某个位置。 653 | ; MouseX,MouseY 是鼠标的屏幕坐标。 654 | ; DisplayX,DisplayY 是 x,y 经过转换后的屏幕坐标。 655 | ; 以下过程 x,y 不发生变化, DisplayX,DisplayY 储存转换好的屏幕坐标。 656 | ; 不要尝试合并分支 (X="" and Y="") 与 (A_CoordModeToolTip = "Screen")。 657 | ; 因为存在把坐标模式设为 Window 或 Client 但又同时不给出 x,y 的情况!!!!!! 658 | if (X="" and Y="") 659 | { ; 没有给出 x,y 则使用鼠标坐标 660 | DisplayX := MouseX 661 | , DisplayY := MouseY 662 | ; 根据坐标判断在第几个屏幕里,并获得对应屏幕边界。 663 | ; 使用 MONITOR_DEFAULTTONEAREST 设置,可以在给出的点不在任何显示器内时,返回距离最近的显示器。 664 | ; 这样可以修正使用 1920,1080 这种错误的坐标,导致返回空值,导致画图失败的问题。 665 | ; 为什么 1920,1080 是错误的呢?因为 1920 是宽度,而坐标起点是0,所以最右边坐标值是 1919,最下面是 1079。 666 | , hMonitor := MDMF_FromPoint(DisplayX, DisplayY, MONITOR_DEFAULTTONEAREST:=2) 667 | , TargetLeft := this.Monitors[hMonitor].Left 668 | , TargetTop := this.Monitors[hMonitor].Top 669 | , TargetRight := this.Monitors[hMonitor].Right 670 | , TargetBottom := this.Monitors[hMonitor].Bottom 671 | , TargetWidth := TargetRight-TargetLeft 672 | , TargetHeight := TargetBottom-TargetTop 673 | ; 将对应屏幕的 DPIScale 存入 Options 中。 674 | , Options.DPIScale := this.Monitors[hMonitor].DPIScale 675 | } 676 | ; 已给出 x和y 或x 或y,都会走到下面3个分支去。 677 | else if (Options.CoordMode = "Window" or Options.CoordMode = "Relative") 678 | { ; 已给出 x或y 且使用窗口坐标 679 | WinGetPos, WinX, WinY, WinW, WinH, % "ahk_id " Options.TargetHWND 680 | 681 | XInScreen := WinX+X 682 | , YInScreen := WinY+Y 683 | , TargetLeft := WinX 684 | , TargetTop := WinY 685 | , TargetWidth := WinW 686 | , TargetHeight := WinH 687 | , TargetRight := TargetLeft+TargetWidth 688 | , TargetBottom := TargetTop+TargetHeight 689 | , DisplayX := (X="") ? MouseX : XInScreen 690 | , DisplayY := (Y="") ? MouseY : YInScreen 691 | , hMonitor := MDMF_FromPoint(DisplayX, DisplayY, MONITOR_DEFAULTTONEAREST:=2) 692 | , Options.DPIScale := this.Monitors[hMonitor].DPIScale 693 | } 694 | else if (Options.CoordMode = "Client") 695 | { ; 已给出 x或y 且使用客户区坐标 696 | VarSetCapacity(ClientArea, 16, 0) 697 | , DllCall("GetClientRect", "Ptr", Options.TargetHWND, "Ptr", &ClientArea) 698 | , DllCall("ClientToScreen", "Ptr", Options.TargetHWND, "Ptr", &ClientArea) 699 | , ClientX := NumGet(ClientArea, 0, "Int") 700 | , ClientY := NumGet(ClientArea, 4, "Int") 701 | , ClientW := NumGet(ClientArea, 8, "Int") 702 | , ClientH := NumGet(ClientArea, 12, "Int") 703 | 704 | XInScreen := ClientX+X 705 | , YInScreen := ClientY+Y 706 | , TargetLeft := ClientX 707 | , TargetTop := ClientY 708 | , TargetWidth := ClientW 709 | , TargetHeight := ClientH 710 | , TargetRight := TargetLeft+TargetWidth 711 | , TargetBottom := TargetTop+TargetHeight 712 | , DisplayX := (X="") ? MouseX : XInScreen 713 | , DisplayY := (Y="") ? MouseY : YInScreen 714 | , hMonitor := MDMF_FromPoint(DisplayX, DisplayY, MONITOR_DEFAULTTONEAREST:=2) 715 | , Options.DPIScale := this.Monitors[hMonitor].DPIScale 716 | } 717 | else ; 这里必然 A_CoordModeToolTip = "Screen" 718 | { ; 已给出 x或y 且使用屏幕坐标 719 | DisplayX := (X="") ? MouseX : X 720 | , DisplayY := (Y="") ? MouseY : Y 721 | , hMonitor := MDMF_FromPoint(DisplayX, DisplayY, MONITOR_DEFAULTTONEAREST:=2) 722 | , TargetLeft := this.Monitors[hMonitor].Left 723 | , TargetTop := this.Monitors[hMonitor].Top 724 | , TargetRight := this.Monitors[hMonitor].Right 725 | , TargetBottom := this.Monitors[hMonitor].Bottom 726 | , TargetWidth := TargetRight-TargetLeft 727 | , TargetHeight := TargetBottom-TargetTop 728 | , Options.DPIScale := this.Monitors[hMonitor].DPIScale 729 | } 730 | 731 | if (GetTargetSize=1) 732 | { 733 | TargetSize := [] 734 | , TargetSize.X := TargetLeft 735 | , TargetSize.Y := TargetTop 736 | ; 一个窗口,有各种各样的方式可以让自己的高宽超过屏幕高宽。 737 | ; 例如最大化的时候,看起来刚好填满了屏幕,应该是1920*1080,但实际获取会发现是1936*1096。 738 | ; 还可以通过拖动窗口边缘调整大小的方式,让它变1924*1084。 739 | ; 还可以直接在创建窗口的时候,指定一个数值,例如3000*3000。 740 | ; 由于设计的时候, DIB 最大就是多个屏幕大小的总和。 741 | ; 当造出一个超过屏幕大小总和的窗口,又使用了 A_CoordModeToolTip = "Window" 之类的参数,同时待显示文本单行又超级长。 742 | ; 此时 (显示宽高 = 窗口宽高) > DIB宽高,会导致 UpdateLayeredWindow() 显示失败。 743 | ; 所以这里做一下限制。 744 | , TargetSize.W := Min(TargetWidth, this.DIBWidth) 745 | , TargetSize.H := Min(TargetHeight, this.DIBHeight) 746 | return, TargetSize 747 | } 748 | 749 | DPIScale := Options.DPIScale 750 | ; 为跟随鼠标显示的文本框增加一个距离,避免鼠标和文本框挤一起发生遮挡。 751 | ; 因为前面需要用到原始的 DisplayX 和 DisplayY 进行计算,所以在这里才增加距离。 752 | , DisplayX := (X="") ? DisplayX+Options.DistanceBetweenMouseXAndToolTip*DPIScale : DisplayX 753 | , DisplayY := (Y="") ? DisplayY+Options.DistanceBetweenMouseYAndToolTip*DPIScale : DisplayY 754 | 755 | ; 处理目标边缘(右和下)的情况,让文本框可以贴边显示,不会超出目标外。 756 | , DisplayX := (DisplayX+W>=TargetRight) ? TargetRight-W : DisplayX 757 | , DisplayY := (DisplayY+H>=TargetBottom) ? TargetBottom-H : DisplayY 758 | ; 处理目标边缘(左和上)的情况,让文本框可以贴边显示,不会超出目标外。 759 | ; 不建议合并代码,理解会变得困难。 760 | , DisplayX := (DisplayX=DisplayX and MouseY>=DisplayY and MouseX<=DisplayX+W and MouseY<=DisplayY+H) 771 | { 772 | ; MouseY-H-16 是往上弹,应对在左下角和右下角的情况。 773 | ; MouseY+H+16 是往下弹,应对在右上角和左上角的情况。 774 | ; 这里不要去用 Abs(Options.DistanceBetweenMouseYAndToolTip) 替代 16。因为当前者很大时,显示效果不好。 775 | ; 优先往上弹,如果不超限,则上弹。如果超限则往下弹,下弹超限,则不弹。 776 | DisplayY := MouseY-H-16>=TargetTop ? MouseY-H-16 : MouseY+H+16<=TargetBottom ? MouseY+16 : DisplayY 777 | } 778 | 779 | ; 使用 ByRef 变量特性返回计算得到的 X和Y 780 | X := DisplayX , Y := DisplayY 781 | g_btt_X := X 782 | g_btt_Y := Y 783 | g_btt_W := W 784 | g_btt_H := H 785 | } 786 | 787 | ; https://autohotkey.com/boards/viewtopic.php?f=6&t=4379 788 | ; jballi's Fnt Library 789 | Fnt_GetTooltipFontName() 790 | { 791 | static LF_FACESIZE:=32 ;-- In TCHARS 792 | return StrGet(this.Fnt_GetNonClientMetrics()+(A_IsUnicode ? 316:220)+28,LF_FACESIZE) 793 | } 794 | 795 | Fnt_GetNonClientMetrics() 796 | { 797 | static Dummy15105062 798 | ,SPI_GETNONCLIENTMETRICS:=0x29 799 | ,NONCLIENTMETRICS 800 | 801 | ;-- Set the size of NONCLIENTMETRICS structure 802 | cbSize:=A_IsUnicode ? 500:340 803 | if (((GV:=DllCall("GetVersion"))&0xFF . "." . GV>>8&0xFF)>=6.0) ;-- Vista+ 804 | cbSize+=4 805 | 806 | ;-- Create and initialize NONCLIENTMETRICS structure 807 | VarSetCapacity(NONCLIENTMETRICS,cbSize,0) 808 | NumPut(cbSize,NONCLIENTMETRICS,0,"UInt") 809 | 810 | ;-- Get nonclient metrics parameter 811 | if !DllCall("SystemParametersInfo" 812 | ,"UInt",SPI_GETNONCLIENTMETRICS 813 | ,"UInt",cbSize 814 | ,"Ptr",&NONCLIENTMETRICS 815 | ,"UInt",0) 816 | return false 817 | 818 | ;-- Return to sender 819 | return &NONCLIENTMETRICS 820 | } 821 | 822 | #IncludeAgain %A_LineFile%\..\NonNull.ahk 823 | } 824 | 825 | #Include %A_LineFile%\..\Gdip_All.ahk -------------------------------------------------------------------------------- /lib/NonNull.ahk: -------------------------------------------------------------------------------- 1 | ; 变量为空,则使用默认值。变量不为空,则使用变量值。 2 | ; 同时可以检查变量是否超出最大最小范围。 3 | ; 注意,默认值不受最大最小范围的限制。 4 | ; 也就是说 5 | ; 当变量值为"",默认值为8,范围为2-5,此时变量值会是8。 6 | ; 当变量值为10,默认值为8,范围为2-5,此时变量值会是5。 7 | NonNull(ByRef var, DefaultValue, MinValue:="", MaxValue:="") ; 237ms 8 | { 9 | var:= var="" ? DefaultValue : MinValue="" ? (MaxValue="" ? var : Min(var, MaxValue)) : (MaxValue!="" ? Max(Min(var, MaxValue), MinValue) : Max(var, MinValue)) 10 | } 11 | 12 | ; 与 NonNull 一致,区别是通过 return 返回值,而不是 ByRef。 13 | NonNull_Ret(var, DefaultValue, MinValue:="", MaxValue:="") ; 237ms 14 | { 15 | return, var="" ? DefaultValue : MinValue="" ? (MaxValue="" ? var : Min(var, MaxValue)) : (MaxValue!="" ? Max(Min(var, MaxValue), MinValue) : Max(var, MinValue)) 16 | /* 17 | ; 下面的 if 版本与上面的三元版本是等价的 18 | ; 只是16w次循环的速度是 270ms,慢了13% 19 | if (var="") 20 | return, DefaultValue ; 变量为空,则返回默认值 21 | else 22 | { 23 | if (MinValue="") 24 | { 25 | if (MaxValue="") 26 | return, var ; 变量有值,且不检查最大最小范围,则直接返回变量值 27 | else 28 | return, Min(var, MaxValue) ; 变量有值,且只检查最大值,则返回不大于最大值的变量值 29 | } 30 | else 31 | { 32 | if (MaxValue!="") 33 | ; 三元的写法不会更快 34 | return, Max(Min(var, MaxValue), MinValue) ; 变量有值,且检查最大最小范围,则返回最大最小范围内的变量值 35 | else 36 | return, Max(var, MinValue) ; 变量有值,且只检查最小值,则返回不小于最小值的变量值 37 | } 38 | } 39 | */ 40 | } 41 | 42 | /* 单元测试 43 | 计时() 44 | loop,1000 45 | { 46 | gosub, UnitTest1 47 | gosub, UnitTest2 48 | } 49 | 计时() 50 | 51 | ; ByRef 版本的测试 52 | UnitTest1: 53 | v:="" 54 | NonNull(v, 8, 2, 10) 55 | if v!=8 56 | MsgBox, wrong 57 | v:="" 58 | NonNull(v, 8, "", "") 59 | if v!=8 60 | MsgBox, wrong 61 | v:="" 62 | NonNull(v, 8, 2, "") 63 | if v!=8 64 | MsgBox, wrong 65 | v:="" 66 | NonNull(v, 8, "", 10) 67 | if v!=8 68 | MsgBox, wrong 69 | 70 | v:=5 71 | NonNull(v, 8, 2, 10) 72 | if v!=5 73 | MsgBox, wrong 74 | v:=5 75 | NonNull(v, 8, "", "") 76 | if v!=5 77 | MsgBox, wrong 78 | v:=5 79 | NonNull(v, 8, 2, "") 80 | if v!=5 81 | MsgBox, wrong 82 | v:=5 83 | NonNull(v, 8, "", 10) 84 | if v!=5 85 | MsgBox, wrong 86 | 87 | v:=15 88 | NonNull(v, 8, 2, 10) 89 | if v!=10 90 | MsgBox, wrong 91 | v:=15 92 | NonNull(v, 8, "", "") 93 | if v!=15 94 | MsgBox, wrong 95 | v:=15 96 | NonNull(v, 8, 2, "") 97 | if v!=15 98 | MsgBox, wrong 99 | v:=15 100 | NonNull(v, 8, "", 10) 101 | if v!=10 102 | MsgBox, wrong 103 | 104 | v:=1 105 | NonNull(v, 8, 2, 10) 106 | if v!=2 107 | MsgBox, wrong 108 | v:=1 109 | NonNull(v, 8, "", "") 110 | if v!=1 111 | MsgBox, wrong 112 | v:=1 113 | NonNull(v, 8, 2, "") 114 | if v!=2 115 | MsgBox, wrong 116 | v:=1 117 | NonNull(v, 8, "", 10) 118 | if v!=1 119 | MsgBox, wrong 120 | return 121 | 122 | ; return 版本的测试 123 | UnitTest2: 124 | v:="" 125 | if NonNull_Ret(v, 8, 2, 10)!=8 126 | MsgBox, wrong 127 | v:="" 128 | if NonNull_Ret(v, 8, "", "")!=8 129 | MsgBox, wrong 130 | v:="" 131 | if NonNull_Ret(v, 8, 2, "")!=8 132 | MsgBox, wrong 133 | v:="" 134 | if NonNull_Ret(v, 8, "", 10)!=8 135 | MsgBox, wrong 136 | 137 | v:=5 138 | if NonNull_Ret(v, 8, 2, 10)!=5 139 | MsgBox, wrong 140 | v:=5 141 | if NonNull_Ret(v, 8, "", "")!=5 142 | MsgBox, wrong 143 | v:=5 144 | if NonNull_Ret(v, 8, 2, "")!=5 145 | MsgBox, wrong 146 | v:=5 147 | if NonNull_Ret(v, 8, "", 10)!=5 148 | MsgBox, wrong 149 | 150 | v:=15 151 | if NonNull_Ret(v, 8, 2, 10)!=10 152 | MsgBox, wrong 153 | v:=15 154 | if NonNull_Ret(v, 8, "", "")!=15 155 | MsgBox, wrong 156 | v:=15 157 | if NonNull_Ret(v, 8, 2, "")!=15 158 | MsgBox, wrong 159 | v:=15 160 | if NonNull_Ret(v, 8, "", 10)!=10 161 | MsgBox, wrong 162 | 163 | v:=1 164 | if NonNull_Ret(v, 8, 2, 10)!=2 165 | MsgBox, wrong 166 | v:=1 167 | if NonNull_Ret(v, 8, "", "")!=1 168 | MsgBox, wrong 169 | v:=1 170 | if NonNull_Ret(v, 8, 2, "")!=2 171 | MsgBox, wrong 172 | v:=1 173 | if NonNull_Ret(v, 8, "", 10)!=1 174 | MsgBox, wrong 175 | return 176 | */ -------------------------------------------------------------------------------- /lib/class_easyini.ahk: -------------------------------------------------------------------------------- 1 | ; ############################################################################################################# 2 | ; # Verdlin's INI library 3 | ; # Original thread: https://autohotkey.com/board/topic/91578-class-easyini-native-syntax-inisectionkey-val-formatting-retained/ 4 | ; # Original source code: https://github.com/Aatoz/AutoHotKey/blob/master/Lib/class_EasyIni.ahk 5 | ; ############################################################################################################# 6 | ; ############################################################################################################# 7 | ; # Modified by dein0s 8 | ; # Source code: https://github.com/dein0s/AHK_Snippets/blob/master/EasyIni.ahk 9 | ; # List of all functions and class methods at the end of this file 10 | ; # 11 | ; # Github: https://github.com/dein0s 12 | ; # Twitter: https://twitter.com/dein0s 13 | ; # Discord: dein0s#2248 14 | ; # 15 | ; # Modified parts marked as following: 16 | ; # --- MODIFICATION START (dein0s) --- 17 | ; # --- MODIFICATION END (dein0s) ---; 18 | ; ############################################################################################################# 19 | class_EasyIni(sFile="", sLoadFromStr="") 20 | { 21 | return new EasyIni(sFile, sLoadFromStr) 22 | } 23 | 24 | class EasyIni 25 | { 26 | __New(sFile="", sLoadFromStr="") ; Loads ths file into memory. 27 | { 28 | this := this.CreateIniObj("EasyIni_ReservedFor_m_sFile", sFile 29 | , "EasyIni_ReservedFor_TopComments", Object()) ; Top comments can be stored in linear array because order will simply be numeric 30 | 31 | if (sFile == A_Blank && sLoadFromStr == A_Blank) 32 | return this 33 | 34 | ; Append ".ini" if it is not already there. 35 | if (SubStr(sFile, StrLen(sFile)-3, 4) != ".ini") 36 | this.EasyIni_ReservedFor_m_sFile := sFile := (sFile . ".ini") 37 | 38 | sIni := sLoadFromStr 39 | if (sIni == A_Blank) 40 | FileRead, sIni, %sFile% 41 | 42 | /* 43 | Current design (not fully implemented): 44 | --------------------------------------------------------------------------------------------------------------------------------------------------- 45 | Comments at the top of the section apply to the file as a whole. They are keyed off an internal section called "EasyIni_ReservedFor_TopComments." 46 | Comments above section headers apply to the the last key of the previous section. 47 | If a comment appears between two keys, then it will apply to the key above it -- this is consistent with the solution for comments above section headers. 48 | Newlines will be stored in similar fashion to comments. 49 | --------------------------------------------------------------------------------------------------------------------------------------------------- 50 | 51 | --------------------------------------------------------------------------------------------------------------------------------------------------- 52 | If full-support for comments needs to be added, then the design below should supersede the design above. 53 | By saying, "Full-support" I mean a way to directly access these comments based upon sections and keys. 54 | --------------------------------------------------------------------------------------------------------------------------------------------------- 55 | 56 | --------------------------------------------------------------------------------------------------------------------------------------------------- 57 | Comments at the top of the section apply to the file as a whole. They are keyed off an internal section called "EasyIni_ReservedFor_TopComments." 58 | Comments above section headers apply to the section header. If people dislike this, I may instead chose to make the comments apply to the last key of the previous section if there a newline in-between the comment in question and the next section. I may come up with some decent solution as I experiment 59 | If a comment appears between two keys, then it will apply to the key below it -- this is consistent with the solution for comments above section headers. 60 | Newlines will be stored in similar fashion to comments. 61 | --------------------------------------------------------------------------------------------------------------------------------------------------- 62 | */ 63 | ;~ FileRead, vText,%sIni% 64 | ;~ oArray := StrSplit(vText, "`n", "`r") 65 | ;~ for,k,v in oArray 66 | ;~ { 67 | Loop, Parse, sIni, `n, `r 68 | { 69 | sTrimmedLine := Trim(A_LoopField) 70 | ;~ sTrimmedLine := Trim(v) 71 | ; Comments or newlines within the ini 72 | if (SubStr(sTrimmedLine, 1, 1) == ";" || sTrimmedLine == A_Blank) ; A_Blank would be a newline 73 | { 74 | ; Chr(14) is just the magical char to indicate that this line should only be a newline "`n" 75 | LoopField := A_LoopField == A_Blank ? Chr(14) : A_LoopField 76 | ;~ LoopField :=v== A_Blank ? Chr(14) : v 77 | 78 | if (sCurSec == A_Blank) 79 | this.EasyIni_ReservedFor_TopComments.Insert(A_Index, LoopField) ; not using sTrimmedLine so as to keep comment formatting 80 | else 81 | { 82 | if (sPrevKeyForThisSec == A_Blank) ; This happens when there is a comment in the section before the first key, if any 83 | sPrevKeyForThisSec := "SectionComment" 84 | 85 | if (IsObject(this[sCurSec].EasyIni_ReservedFor_Comments)) 86 | { 87 | if (this[sCurSec].EasyIni_ReservedFor_Comments.HasKey(sPrevKeyForThisSec)) 88 | this[sCurSec].EasyIni_ReservedFor_Comments[sPrevKeyForThisSec] .= "`n" LoopField 89 | else this[sCurSec].EasyIni_ReservedFor_Comments.Insert(sPrevKeyForThisSec, LoopField) 90 | } 91 | else 92 | { 93 | if (IsObject(this[sCurSec])) 94 | this[sCurSec].EasyIni_ReservedFor_Comments := {(sPrevKeyForThisSec):LoopField} 95 | else this[sCurSec, "EasyIni_ReservedFor_Comments"] := {(sPrevKeyForThisSec):LoopField} 96 | } 97 | } 98 | continue 99 | } 100 | 101 | ; [Section] 102 | if (SubStr(sTrimmedLine, 1, 1) = "[" && InStr(sTrimmedLine, "]")) ; need to be sure that this isn't just a key starting with "[" 103 | { 104 | if (sCurSec != A_Blank && !this.HasKey(sCurSec)) 105 | this[sCurSec] := EasyIni_CreateBaseObj() 106 | sCurSec := SubStr(sTrimmedLine, 2, InStr(sTrimmedLine, "]", false, 0) - 2) ; 0 search right to left. We want to trim the *last* occurence of "]" 107 | sPrevKeyForThisSec := "" 108 | continue 109 | } 110 | 111 | ; key=val 112 | iPosOfEquals := InStr(sTrimmedLine, "=") 113 | if (iPosOfEquals) 114 | { 115 | sPrevKeyForThisSec := SubStr(sTrimmedLine, 1, iPosOfEquals - 1) ; so it's not the previous key yet...but it will be on next iteration :P 116 | val := SubStr(sTrimmedLine, iPosOfEquals + 1) 117 | StringReplace, val, val , `%A_ScriptDir`%, %A_ScriptDir%, All 118 | StringReplace, val, val , `%A_WorkingDir`%, %A_ScriptDir%, All 119 | this[sCurSec, sPrevKeyForThisSec] := val 120 | } 121 | else ; at this point, we know it isn't a comment, or newline, it isn't a section, and it isn't a conventional key-val pair. Treat this line as a key with no val 122 | { 123 | sPrevKeyForThisSec := sTrimmedLine 124 | this[sCurSec, sPrevKeyForThisSec] := "" 125 | } 126 | } 127 | ; if there is a section with no keys and it is at the bottom of the file, then we missed it 128 | if (sCurSec != A_Blank && !this.HasKey(sCurSec)) 129 | this[sCurSec] := EasyIni_CreateBaseObj() 130 | 131 | return this 132 | } 133 | 134 | CreateIniObj(parms*) 135 | { 136 | ; Define prototype object for ini arrays: 137 | ; --- MODIFICATION START (dein0s) --- 138 | static base := {__Set: "EasyIni_Set", _NewEnum: "EasyIni_NewEnum", Delete: "Delete", Remove: "EasyIni_Remove", Insert: "EasyIni_Insert", InsertBefore: "EasyIni_InsertBefore", AddSection: "EasyIni.AddSection", RenameSection: "EasyIni.RenameSection", DeleteSection: "EasyIni.DeleteSection", GetSections: "EasyIni.GetSections", FindSecs: "EasyIni.FindSecs", AddKey: "EasyIni.AddKey", RenameKey: "EasyIni.RenameKey", DeleteKey: "EasyIni.DeleteKey", RemoveKey: "EasyIni.RemoveKey", GetKeys: "EasyIni.GetKeys", FindKeys: "EasyIni.FindKeys", GetVals: "EasyIni.GetVals", FindVals: "EasyIni.FindVals", HasVal: "EasyIni.HasVal", SetKeyVal: "EasyIni.SetKeyVal", GetCommentContent: "EasyIni.GetCommentContent", GetTopComments: "EasyIni.GetTopComments", GetSectionComments: "EasyIni.GetSectionComments", GetKeyComments: "EasyIni.GetKeyComments", AddComment: "EasyIni.AddComment", AddTopComment: "EasyIni.AddTopComment", AddSectionComment: "EasyIni.AddSectionComment", AddKeyComment: "EasyIni.AddKeyComment", DeleteComment: "EasyIni.DeleteComment", Update: "EasyIni.Update", Compare: "EasyIni.Compare", Copy: "EasyIni.Copy", Merge: "EasyIni.Merge", GetFileName: "EasyIni.GetFileName", GetOnlyIniFileName:"EasyIni.GetOnlyIniFileName", IsEmpty:"EasyIni.IsEmpty", Reload: "EasyIni.Reload", GetIsSaved: "EasyIni.GetIsSaved", Save: "EasyIni.Save", ToVar: "EasyIni.ToVar", GetValue: "EasyIni.GetValue"} 139 | ; --- MODIFICATION END (dein0s) --- 140 | ; Create and return new object: 141 | return Object("_keys", Object(), "base", base, parms*) 142 | } 143 | 144 | AddSection(sec, key="", val="", ByRef rsError="") 145 | { 146 | if (this.HasKey(sec)) 147 | { 148 | rsError := "Error! Cannot add new section [" sec "], because it already exists." 149 | MsgBox, %rsError% 150 | return false 151 | } 152 | 153 | if (key == A_Blank) 154 | this[sec] := EasyIni_CreateBaseObj() 155 | else this[sec, key] := val 156 | 157 | return true 158 | } 159 | 160 | RenameSection(sOldSec, sNewSec, ByRef rsError="") 161 | { 162 | if (!this.HasKey(sOldSec)) 163 | { 164 | rsError := "Error! Could not rename section [" sOldSec "], because it does not exist." 165 | MsgBox, %rsError% 166 | return false 167 | } 168 | if (sOldSec = sNewSec) ; EasyIni is case-insensitve. 169 | return true ; true because the rename is harmless. 170 | 171 | this[sNewSec] := this[sOldSec] 172 | this.DeleteSection(sOldSec) 173 | 174 | return true 175 | } 176 | 177 | DeleteSection(sec) 178 | { 179 | r := this.Remove(sec) 180 | return r 181 | } 182 | 183 | GetSections(sDelim="`n", sSort="") 184 | { 185 | for sec in this 186 | secs .= (A_Index == 1 ? sec : sDelim sec) 187 | 188 | if (sSort) 189 | Sort, secs, D%sDelim% %sSort% 190 | 191 | return secs 192 | } 193 | 194 | FindSecs(sExp, iMaxSecs="") 195 | { 196 | aSecs := [] 197 | for sec in this 198 | { 199 | if (RegExMatch(sec, sExp)) 200 | { 201 | aSecs.Insert(sec) 202 | if (iMaxSecs&& aSecs.MaxIndex() == iMaxSecs) 203 | return aSecs 204 | } 205 | } 206 | return aSecs 207 | } 208 | 209 | AddKey(sec, key, val="", ByRef rsError="") 210 | { 211 | if (this.HasKey(sec)) 212 | { 213 | if (this[sec].HasKey(key)) 214 | { 215 | rsError := "Error! Could not add key, " key " because there is a key in the same section:`nSection: " sec "`nKey: " key 216 | MsgBox, %rsError% 217 | return false 218 | } 219 | } 220 | else 221 | { 222 | rsError := "Error! Could not add key, " key " because Section, " sec " does not exist." 223 | MsgBox, %rsError% 224 | return false 225 | } 226 | this[sec, key] := val 227 | return true 228 | } 229 | 230 | RenameKey(sec, OldKey, NewKey, ByRef rsError="") 231 | { 232 | if (!this[sec].HasKey(OldKey)) 233 | { 234 | rsError := "Error! The specified key " OldKey " could not be modified because it does not exist." 235 | MsgBox, %rsError% 236 | return false 237 | } 238 | 239 | ValCopy := this[sec][OldKey] 240 | ; --- MODIFICATION START (dein0s) --- 241 | CommentCopy := this.GetKeyComments(sec, OldKey) 242 | this.RemoveKey(sec, OldKey) 243 | this.AddKey(sec, NewKey) 244 | if (!IsStringEmpty(CommentCopy)) { 245 | this.AddKeyComment(sec, NewKey, CommentCopy) 246 | } 247 | ; --- MODIFICATION END (dein0s) --- 248 | this[sec][NewKey] := ValCopy 249 | return true 250 | } 251 | 252 | DeleteKey(sec, key) 253 | { 254 | this[sec].Delete(key) 255 | return 256 | } 257 | 258 | ; --- MODIFICATION START (dein0s) --- 259 | ; DeleteKey() provides some inconsistency (key is still saved into the file, but with empty value) 260 | RemoveKey(sec, key) 261 | { 262 | this[sec].Remove(key) 263 | return 264 | } 265 | ; --- MODIFICATION END (dein0s) --- 266 | 267 | GetKeys(sec, sDelim="`n", sSort="") 268 | { 269 | for key in this[sec] 270 | keys .= A_Index == 1 ? key : sDelim key 271 | 272 | if (sSort) 273 | Sort, keys, D%sDelim% %sSort% 274 | 275 | return keys 276 | } 277 | 278 | FindKeys(sec, sExp, iMaxKeys="") 279 | { 280 | aKeys := [] 281 | for key in this[sec] 282 | { 283 | if (RegExMatch(key, sExp)) 284 | { 285 | aKeys.Insert(key) 286 | if (iMaxKeys && aKeys.MaxIndex() == iMaxKeys) 287 | return aKeys 288 | } 289 | } 290 | return aKeys 291 | } 292 | 293 | ; Non-regex, exact match on key 294 | ; returns key(s) and their assocationed section(s) 295 | FindExactKeys(key, iMaxKeys="") 296 | { 297 | aKeys := {} 298 | for sec, aData in this 299 | { 300 | if (aData.HasKey(key)) 301 | { 302 | aKeys.Insert(sec, key) 303 | if (iMaxKeys && aKeys.MaxIndex() == iMaxKeys) 304 | return aKeys 305 | } 306 | } 307 | return aKeys 308 | } 309 | 310 | GetVals(sec, sDelim="`n", sSort="") 311 | { 312 | for key, val in this[sec] 313 | vals .= A_Index == 1 ? val : sDelim val 314 | 315 | if (sSort) 316 | Sort, vals, D%sDelim% %sSort% 317 | 318 | return vals 319 | } 320 | 321 | FindVals(sec, sExp, iMaxVals="") 322 | { 323 | aVals := [] 324 | for key, val in this[sec] 325 | { 326 | if (RegExMatch(val, sExp)) 327 | { 328 | aVals.Insert(val) 329 | if (iMaxVals && aVals.MaxIndex() == iMaxVals) 330 | break 331 | } 332 | } 333 | return aVals 334 | } 335 | 336 | HasVal(sec, FindVal) 337 | { 338 | for k, val in this[sec] 339 | if (FindVal = val) 340 | return true 341 | return false 342 | } 343 | 344 | ; --- MODIFICATION START (dein0s) --- 345 | SetKeyVal(sec, key, val, ByRef rsError="") 346 | { 347 | if (!this.HasKey(sec)) { 348 | rsError := "Error! Could not set value '" val "' for key '" key "' because Section [" sec "] does not exist." 349 | MsgBox, %rsError% 350 | return false 351 | } 352 | if (!this[sec].HasKey(key)) { 353 | rsError := "Error! Could not set value '" val "' for key '" key "' because key does not exist in Section [" sec "]." 354 | MsgBox, %rsError% 355 | return false 356 | } 357 | this[sec, key] := val 358 | return true 359 | } 360 | 361 | GetCommentContent(sec="", key="", topComment=false) 362 | { 363 | if (topComment) { 364 | commentsObj := this.EasyIni_ReservedFor_TopComments 365 | } 366 | else { 367 | commentsObj := StrSplit(this[sec].EasyIni_ReservedFor_Comments[key], "`n") 368 | } 369 | for commentIndex, commentContent in commentsObj { 370 | if (!IsStringEmpty(commentContent)) { 371 | sComments .= commentContent "`n" 372 | } 373 | } 374 | return sComments 375 | } 376 | 377 | GetTopComments() 378 | { 379 | return this.GetCommentContent( , , true) 380 | } 381 | 382 | GetSectionComments(sec) 383 | { 384 | return this.GetCommentContent(sec, "SectionComment") 385 | } 386 | 387 | GetKeyComments(sec, key) 388 | { 389 | return this.GetCommentContent(sec, key) 390 | } 391 | 392 | AddComment(sec="", key="", comment="", topComment=false, ByRef rsError="") 393 | { 394 | for commentIndex, commentContent in StrSplit(comment, "`n") { 395 | if (!IsStringEmpty(commentContent)) { 396 | if (InStr(commentContent, ";") != 1) { 397 | commentContent := "; " commentContent 398 | } 399 | if (topComment) { 400 | if (!this.HasKey("EasyIni_ReservedFor_TopComments")) { 401 | this.Insert("EasyIni_ReservedFor_TopComments", []) 402 | } 403 | this.EasyIni_ReservedFor_TopComments.Insert(commentContent) 404 | } 405 | else { 406 | if (!this.HasKey(sec)) { 407 | if (key == "SectionComment") { 408 | rsError := "Error! Could not add comment to Section [" sec "] because it does not exist." 409 | } 410 | else { 411 | rsError := "Error! Could not add comment to key '" key "' because Section [" sec "] does not exist." 412 | } 413 | MsgBox, %rsError% 414 | return false 415 | } 416 | if (key != "SectionComment" and !this[sec].HasKey(key)) { 417 | rsError := "Error! Could not add comment to key '" key "' because this key does not exist in Section [" sec "]." 418 | MsgBox, %rsError% 419 | return false 420 | } 421 | if (!IsObject(this[sec].EasyIni_ReservedFor_Comments)) { 422 | this[sec].EasyIni_ReservedFor_Comments := {} 423 | } 424 | commentCurrent := this[sec].EasyIni_ReservedFor_Comments[key] 425 | if (IsStringEmpty(commentCurrent)) { 426 | this[sec].EasyIni_ReservedFor_Comments.Insert(key, commentContent) 427 | } 428 | else { 429 | this[sec].EasyIni_ReservedFor_Comments.Insert(key, commentCurrent "`n" commentContent) 430 | } 431 | } 432 | } 433 | } 434 | return true 435 | } 436 | 437 | AddTopComment(comment, ByRef rsError="") 438 | { 439 | return this.AddComment( , , comment, true, rsError) 440 | } 441 | 442 | AddSectionComment(sec, comment, ByRef rsError="") 443 | { 444 | return this.AddComment(sec, "SectionComment", comment, , rsError) 445 | } 446 | 447 | AddKeyComment(sec, key, comment, ByRef rsError="") 448 | { 449 | return this.AddComment(sec, key, comment, , rsError) 450 | } 451 | 452 | DeleteComment(sec="", key="", comment="", topComment=false, ByRef rsError="") 453 | { 454 | for commentIndex, commentContent in StrSplit(comment, "`n") { 455 | if (!IsStringEmpty(commentContent)) { 456 | if (topComment) { 457 | for commentIndexTop, commentContentTop in this.EasyIni_ReservedFor_TopComments { 458 | if (commentContentTop ~= commentContent) { 459 | this.EasyIni_ReservedFor_TopComments.Delete(commentIndexTop) 460 | } 461 | } 462 | } 463 | else { 464 | if (!this.HasKey(sec)) { 465 | if (key == "SectionComment") { 466 | rsError := "Error! Could not delete comment from Section [" sec "] because it does not exist." 467 | } 468 | else { 469 | rsError := "Error! Could not remove comment from key '" key "' because Section [" sec "] does not exist." 470 | } 471 | MsgBox, %rsError% 472 | return false 473 | } 474 | else { 475 | if (key != "SectionComment" and !this[sec].HasKey(key)) { 476 | rsError := "Error! Could not delete comment from key '" key "' because this key does not exist in Section [" sec "]." 477 | MsgBox, %rsError% 478 | return false 479 | } 480 | for commentIndexCurrent, commentContentCurrent in StrSplit(this[sec].EasyIni_ReservedFor_Comments[key], "`n") { 481 | if (commentContentCurrent ~= commentContent) { 482 | continue 483 | } 484 | else { 485 | commentCurrentStr .= commentContentCurrent "`n" 486 | } 487 | } 488 | if (!IsStringEmpty(commentCurrentStr)) { 489 | this[sec].EasyIni_ReservedFor_Comments.Insert(key, commentCurrentStr) 490 | } 491 | else { 492 | this[sec].EasyIni_ReservedFor_Comments.Delete(key) 493 | } 494 | } 495 | } 496 | } 497 | } 498 | return true 499 | } 500 | 501 | DeleteTopComment(comment, ByRef rsError="") 502 | { 503 | return this.DeleteComment( , , comment, true, rsError) 504 | } 505 | 506 | DeleteSectionComment(sec, comment, ByRef rsError="") 507 | { 508 | return this.DeleteComment(sec, "SectionComment", comment, , rsError) 509 | } 510 | 511 | DeleteKeyComment(sec, key, comment, ByRef rsError="") 512 | { 513 | return this.DeleteComment(sec, key, comment, , rsError) 514 | } 515 | 516 | Update(SourceIni, sections=true, keys=true, values=false, top_comments=false, section_comments=true, key_comments=true, repeatedRecursions=0) 517 | ; TODO: add docstring 518 | { 519 | if (!IsObject(SourceIni)) { 520 | SourceIni := class_EasyIni(SourceIni) 521 | } 522 | if (SourceIni.IsEmpty()) { 523 | return 524 | } 525 | ; Add new items from SourceIni object 526 | if (top_comments) { 527 | for commentIndex, commentContent in StrSplit(SourceIni.GetTopComments(), "`n") { 528 | ; Add new top comment 529 | if (!InStr(this.GetTopComments(), commentContent)) { 530 | this.AddTopComment(commentContent) 531 | } 532 | } 533 | } 534 | for sectionName, sectionKeys in SourceIni { 535 | ; Add new section 536 | if (sections and !this.HasKey(sectionName)) { 537 | this.AddSection(sectionName) 538 | } 539 | ; Add new section comment 540 | if (section_comments and this.HasKey(sectionName)) { 541 | for commentIndex, commentContent in StrSplit(SourceIni.GetSectionComments(sectionName), "`n") { 542 | if (!InStr(this.GetSectionComments(sectionName), commentContent)) { 543 | this.AddSectionComment(sectionName, commentContent) 544 | } 545 | } 546 | } 547 | for keyName, keyVal in sectionKeys { 548 | ; Add new key 549 | if (keys and !this[sectionName].HasKey(keyName)) { 550 | this.AddKey(sectionName, keyName, keyVal) 551 | } 552 | if (this[sectionName].HasKey(keyName)) { 553 | ; Set new key value 554 | if (values) { 555 | this.SetKeyVal(sectionName, keyName, keyVal) 556 | } 557 | ; Add new key comment 558 | if (key_comments) { 559 | for commentIndex, commentContent in StrSplit(SourceIni.GetKeyComments(sectionName, keyName), "`n") { 560 | if (!InStr(this.GetKeyComments(sectionName, keyName), commentContent)) { 561 | this.AddKeyComment(sectionName, keyName, commentContent) 562 | } 563 | } 564 | } 565 | } 566 | } 567 | } 568 | ; Remove old items from current EasyIni object 569 | if (top_comments) { 570 | for commentIndex, commentContent in StrSplit(this.GetTopComments(), "`n") { 571 | ; Remove old top comment 572 | if (!InStr(SourceIni.GetTopComments(), commentContent)) { 573 | this.DeleteTopComment(commentContent) 574 | } 575 | } 576 | } 577 | 578 | removeSectionsList := [] 579 | for sectionName, sectionKeys in this { 580 | ; Remove old section, remember Section, remove later to not mess up the object index. 581 | if (sections and !SourceIni.HasKey(sectionName)) { 582 | removeSectionsList.push(sectionName) 583 | } 584 | ; Remove old section comment 585 | if (section_comments and SourceIni.HasKey(sectionName)) { 586 | for commentIndex, commentContent in StrSplit(this.GetSectionComments(sectionName), "`n") { 587 | if (!InStr(SourceIni.GetSectionComments(sectionName), commentContent)) { 588 | this.DeleteSection(sectionName, commentContent) 589 | } 590 | } 591 | } 592 | for keyName, keyVal in sectionKeys { 593 | ; Remove old key 594 | if (keys and !SourceIni[sectionName].HasKey(keyName)) { 595 | this.RemoveKey(sectionName, keyName) 596 | } 597 | ; Remove old key comment 598 | if (key_comments and SourceIni[sectionName].HasKey(keyName)){ 599 | for commentIndex, commentContent in StrSplit(this.GetKeyComments(sectionName, keyName), "`n") { 600 | if (!InStr(SourceIni.GetKeyComments(sectionName, keyName), commentContent)) { 601 | this.DeleteKeyComment(sectionName, keyName, commentContent) 602 | } 603 | } 604 | } 605 | } 606 | } 607 | 608 | Loop, % removeSectionsList.MaxIndex() { 609 | this.DeleteSection(removeSectionsList[A_Index]) 610 | } 611 | 612 | return 613 | } 614 | 615 | Compare(SourceIni, sections=true, keys=true, values=false, comments=false) 616 | ; TODO: add docstring 617 | { 618 | if (!IsObject(SourceIni)) { 619 | SourceIni := class_EasyIni(SourceIni) 620 | } 621 | if (sections) { 622 | if (this.GetSections("|", "C") != SourceIni.GetSections("|", "C")) { 623 | return false 624 | } 625 | } 626 | if (keys) { 627 | for sectionIndex, sectionName in StrSplit(this.GetSections("|", "C"), "|") { 628 | if (this.GetKeys(sectionName, "|", "C") != SourceIni.GetKeys(sectionName, "|", "C")) { 629 | return false 630 | } 631 | } 632 | } 633 | if (comments) { 634 | for commentIndex, commentContent in this.EasyIni_ReservedFor_TopComments { 635 | sTopComments .= (A_Index == 1) ? commentContent : "|" commentContent 636 | Sort, sTopComments, "|" "C" 637 | } 638 | for commentIndex, commentContent in SourceIni.EasyIni_ReservedFor_TopComments { 639 | sTopCommentsSource .= (A_Index == 1) ? commentContent : "|" commentContent 640 | Sort, sTopCommentsSource, "|" "C" 641 | } 642 | if (sTopComments != sTopCommentsSource) { 643 | return false 644 | } 645 | for sectionIndex, sectionName in StrSplit(this.GetSections("|", "C"), "|") { 646 | for commentKey, commentContent in this[sectionName].EasyIni_ReservedFor_Comments { 647 | sAllSectionComments .= (A_Index == 1) ? commentContent : "|" commentContent 648 | Sort, sAllSectionComments, "|" "C" 649 | } 650 | for commentKey, commentContent in SourceIni[sectionName].EasyIni_ReservedFor_Comments { 651 | sAllSectionCommentsSource .= (A_Index == 1) ? commentContent : "|" commentContent 652 | Sort, sAllSectionCommentsSource, "|" "C" 653 | } 654 | if (sAllSectionComments != sAllSectionCommentsSource) { 655 | return false 656 | } 657 | } 658 | } 659 | return true 660 | } 661 | ; --- MODIFICATION END (dein0s) --- 662 | 663 | ; SourceIni: May be EasyIni object or simply a path to an ini file. 664 | ; bCopyFileName = true: Allow copying of data without copying the file name. 665 | Copy(SourceIni, bCopyFileName = true) 666 | { 667 | ; Get ini as string. 668 | if (IsObject(SourceIni)) 669 | sIniString := SourceIni.ToVar() 670 | else FileRead, sIniString, %SourceIni% 671 | 672 | ; Effectively make this function static by allowing calls via EasyIni.Copy. 673 | if (IsObject(this)) 674 | { 675 | if (bCopyFileName) 676 | sOldFileName := this.GetFileName() 677 | this := A_Blank ; avoid any copy constructor issues. 678 | 679 | ; ObjClone doesn't work consistently. It's likely a problem with the meta-function overrides, 680 | ; but this is a nice, quick hack. 681 | this := class_EasyIni(SourceIni.GetFileName(), sIniString) 682 | 683 | ; Restore file name. 684 | this.EasyIni_ReservedFor_m_sFile := sOldFileName 685 | } 686 | else 687 | return class_EasyIni(bCopyFileName ? SourceIni.GetFileName() : "", sIniString) 688 | 689 | return this 690 | } 691 | 692 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 693 | /* 694 | Author: Verdlin 695 | STILL UNDER CONSTRUCTION. Need to handle comments, and then this will be complete. 696 | Function: Merge 697 | Purpose: Merge two EasyIni objects. 698 | Parameters 699 | vOtherIni: Other EasyIni object to merge with this. 700 | bRemoveNonMatching: If true, removes sections and keys that do not exist in both inis. 701 | bOverwriteMatching: If true, any key that exists in both objects will use the val from vOtherIni. 702 | vExceptionsIni: class_Easy ini object full of exceptions keys for secs. Any matching key will remain unchanged. 703 | */ 704 | Merge(vOtherIni, bRemoveNonMatching = false, bOverwriteMatching = false, vExceptionsIni = "") 705 | { 706 | ; TODO: Perhaps just save one ini, read it back in, and then perform merging? I think this would help with formatting. 707 | ; [Sections] 708 | for sec, aKeysToVals in vOtherIni 709 | { 710 | if (!this.HasKey(sec)) 711 | if (bRemoveNonMatching) 712 | this.DeleteSection(sec) 713 | else this.AddSection(sec) 714 | 715 | ; key=val 716 | for key, val in aKeysToVals 717 | { 718 | bMakeException := vExceptionsIni[sec].HasKey(key) 719 | 720 | if (this[sec].HasKey(key)) 721 | { 722 | if (bOverwriteMatching && !bMakeException) 723 | this[sec, key] := val 724 | } 725 | else 726 | { 727 | if (bRemoveNonMatching && !bMakeException) 728 | this.DeleteKey(sec, key) 729 | else if (!bRemoveNonMatching) 730 | this.AddKey(sec, key, val) 731 | } 732 | } 733 | } 734 | return 735 | } 736 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 737 | 738 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 739 | /* 740 | Author: Verdlin 741 | Function: GetFileName 742 | Purpose: Wrapper to return the extremely long named member var, EasyIni_ReservedFor_m_sFile 743 | Parameters 744 | None 745 | */ 746 | GetFileName() 747 | { 748 | return this.EasyIni_ReservedFor_m_sFile 749 | } 750 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 751 | 752 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 753 | /* 754 | Author: Verdlin 755 | Function: GetFileName 756 | Purpose: Wrapper to return just the .ini name without the path. 757 | Parameters 758 | None 759 | */ 760 | GetOnlyIniFileName() 761 | { 762 | return SubStr(this.EasyIni_ReservedFor_m_sFile, InStr(this.EasyIni_ReservedFor_m_sFile,"\", false, -1)+1) 763 | } 764 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 765 | 766 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 767 | /* 768 | Author: Verdlin 769 | Function: IsEmpty 770 | Purpose: To indicate whether or not this ini has data 771 | Parameters 772 | None 773 | */ 774 | IsEmpty() 775 | { 776 | return (this.GetSections() == A_Blank ; No sections. 777 | && !this.EasyIni_ReservedFor_TopComments.HasKey(1)) ; and no comments. 778 | } 779 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 780 | 781 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 782 | /* 783 | Author: Verdlin 784 | Function: Reload 785 | Purpose: Reloads object from ini file. This is necessary when other routines may be modifying the same ini file. 786 | Parameters 787 | None 788 | */ 789 | test() 790 | { 791 | if (FileExist(this.GetFileName())) 792 | this := class_EasyIni(this.GetFileName()) ; else nothing to reload. 793 | return this 794 | } 795 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 796 | 797 | ; TODO: Add option to store load and save times in comment at bottom of ini? 798 | Save(sSaveAs="", bWarnIfExist=false) 799 | { 800 | if (sSaveAs == A_Blank) 801 | sFile := this.GetFileName() 802 | else 803 | { 804 | sFile := sSaveAs 805 | 806 | ; Append ".ini" if it is not already there. 807 | if (SubStr(sFile, StrLen(sFile)-3, 4) != ".ini") 808 | sFile .= ".ini" 809 | 810 | if (bWarnIfExist && FileExist(sFile)) 811 | { 812 | MsgBox, 4,, The file "%sFile%" already exists.`n`nAre you sure that you want to overwrite it? 813 | IfMsgBox, No 814 | return false 815 | } 816 | } 817 | 818 | ; Formatting is preserved in ini object. 819 | ; FileDelete, %sFile% 820 | initext:="" 821 | bIsFirstLine := true 822 | for k, v in this.EasyIni_ReservedFor_TopComments 823 | { 824 | ; --- MODIFICATION START (dein0s) --- 825 | sLastAddedLine := (A_Index == 1 ? "" : "`n") (v == Chr(14) ? "" : v) 826 | ; FileAppend, %sLastAddedLine%, %sFile%, UTF-8 827 | initext .= sLastAddedLine 828 | bIsFirstLine := false 829 | } 830 | 831 | for section, aKeysToVals in this 832 | { 833 | sLastAddedLine := (bIsFirstLine ? "[" : "`n[") section "]" 834 | ; FileAppend, %sLastAddedLine%, %sFile%, UTF-8 835 | initext .= sLastAddedLine 836 | bIsFirstLine := false 837 | ; Add the comment(s) for this section 838 | sComments := this[section].EasyIni_ReservedFor_Comments["SectionComment"] 839 | Loop, Parse, sComments, `n 840 | { 841 | sLastAddedLine := "`n" (A_LoopField == Chr(14) ? "" : A_LoopField) 842 | ; FileAppend, %sLastAddedLine%, %sFile%, UTF-8 843 | initext .= sLastAddedLine 844 | } 845 | 846 | bEmptySection := true 847 | for key, val in aKeysToVals 848 | { 849 | bEmptySection := false 850 | sLastAddedLine := "`n" key "=" val 851 | ; FileAppend, %sLastAddedLine%, %sFile%, UTF-8 852 | initext .= sLastAddedLine 853 | 854 | ; Add the comment(s) for this key 855 | sComments := this[section].EasyIni_ReservedFor_Comments[key] 856 | Loop, Parse, sComments, `n 857 | { 858 | sLastAddedLine := "`n" (A_LoopField == Chr(14) ? "" : A_LoopField) 859 | ; FileAppend, %sLastAddedLine%, %sFile%, UTF-8 860 | initext .= sLastAddedLine 861 | } 862 | } 863 | if (bEmptySection) 864 | { 865 | ; An empy section may contain comments... 866 | sComments := this[section].EasyIni_ReservedFor_Comments["SectionComment"] 867 | Loop, Parse, sComments, `n 868 | { 869 | sLastAddedLine := "`n" (A_LoopField == Chr(14) ? "" : A_LoopField) 870 | ; FileAppend, %sLastAddedLine%, %sFile%, UTF-8 871 | initext .= sLastAddedLine 872 | } 873 | } 874 | initext := Trim(initext,"`n") "`n" 875 | initext := RegExReplace(initext, "\s{2,}(?=[^\[])", "`n") 876 | file := FileOpen(sFile, "w"), file.Write(initext), file.Close() 877 | ; --- MODIFICATION END (dein0s) --- 878 | } 879 | return true 880 | } 881 | 882 | ToVar() 883 | { 884 | sTmpFile := "$$$EasyIni_Temp.ini" 885 | this.Save(sTmpFile, !A_IsCompiled) 886 | FileRead, sIniAsVar, %sTmpFile% 887 | FileDelete, %sTmpFile% 888 | return sIniAsVar 889 | } 890 | } 891 | 892 | ; For all of the EasyIni_* functions below, much credit is due to Lexikos and Rbrtryn for their work with ordered arrays 893 | ; See http://www.autohotkey.com/board/topic/61792-ahk-l-for-loop-in-order-of-key-value-pair-creation/?p=389662 for Lexikos's initial work with ordered arrays 894 | ; See http://www.autohotkey.com/board/topic/94043-ordered-array/#entry592333 for Rbrtryn's OrderedArray lib 895 | EasyIni_CreateBaseObj(parms*) 896 | { 897 | ; Define prototype object for ordered arrays: 898 | static base := {__Set: "EasyIni_Set", _NewEnum: "EasyIni_NewEnum", Delete: "Delete", Remove: "EasyIni_Remove", Insert: "EasyIni_Insert", InsertBefore: "EasyIni_InsertBefore"} 899 | ; Create and return new base object: 900 | return Object("_keys", Object(), "base", base, parms*) 901 | } 902 | 903 | EasyIni_Set(obj, parms*) 904 | { 905 | ; If this function is called, the key must not already exist. 906 | ; Sub-class array if necessary then add this new key to the key list, if it doesn't begin with "EasyIni_ReservedFor_" 907 | if parms.maxindex() > 2 908 | ObjInsert(obj, parms[1], EasyIni_CreateBaseObj()) 909 | 910 | ; Skip over member variables 911 | if (SubStr(parms[1], 1, 20) <> "EasyIni_ReservedFor_") 912 | ObjInsert(obj._keys, parms[1]) 913 | ; Since we don't return a value, the default behaviour takes effect. 914 | ; That is, a new key-value pair is created and stored in the object. 915 | } 916 | 917 | EasyIni_NewEnum(obj) 918 | { 919 | ; Define prototype object for custom enumerator: 920 | static base := Object("Next", "EasyIni_EnumNext") 921 | ; Return an enumerator wrapping our _keys array's enumerator: 922 | return Object("obj", obj, "enum", obj._keys._NewEnum(), "base", base) 923 | } 924 | 925 | EasyIni_EnumNext(e, ByRef k, ByRef v="") 926 | { 927 | ; If Enum.Next() returns a "true" value, it has stored a key and 928 | ; value in the provided variables. In this case, "i" receives the 929 | ; current index in the _keys array and "k" receives the value at 930 | ; that index, which is a key in the original object: 931 | if r := e.enum.Next(i,k) 932 | ; We want it to appear as though the user is simply enumerating 933 | ; the key-value pairs of the original object, so store the value 934 | ; associated with this key in the second output variable: 935 | v := e.obj[k] 936 | return r 937 | } 938 | 939 | EasyIni_Remove(obj, parms*) 940 | { 941 | r := ObjRemove(obj, parms*) ; Remove keys from main object 942 | Removed := [] 943 | for k, v in obj._keys ; Get each index key pair 944 | if not ObjHasKey(obj, v) ; if key is not in main object 945 | Removed.Insert(k) ; Store that keys index to be removed later 946 | for k, v in Removed ; For each key to be removed 947 | ObjRemove(obj._keys, v, "") ; remove that key from key list 948 | 949 | return r 950 | } 951 | 952 | EasyIni_Insert(obj, parms*) 953 | { 954 | r := ObjInsert(obj, parms*) ; Insert keys into main object 955 | enum := ObjNewEnum(obj) ; Can't use for-loop because it would invoke EasyIni_NewEnum 956 | while enum[k] { ; For each key in main object 957 | for i, kv in obj._keys ; Search for key in obj._keys 958 | if (k = "_keys" || k = kv || SubStr(k, 1, 20) = "EasyIni_ReservedFor_" || SubStr(kv, 1, 20) = "EasyIni_ReservedFor_") ; If found... 959 | continue 2 ; Get next key in main object 960 | ObjInsert(obj._keys, k) ; Else insert key into obj._keys 961 | } 962 | 963 | return r 964 | } 965 | 966 | EasyIni_InsertBefore(obj, key, parms*) 967 | { 968 | OldKeys := obj._keys ; Save key list 969 | obj._keys := [] ; Clear key list 970 | for idx, k in OldKeys { ; Put the keys before key 971 | if (k = key) ; back into key list 972 | break 973 | obj._keys.Insert(k) 974 | } 975 | 976 | r := ObjInsert(obj, parms*) ; Insert keys into main object 977 | enum := ObjNewEnum(obj) ; Can't use for-loop because it would invoke EasyIni_NewEnum 978 | while enum[k] { ; For each key in main object 979 | for i, kv in OldKeys ; Search for key in OldKeys 980 | if (k = "_keys" || k = kv) ; If found... 981 | continue 2 ; Get next key in main object 982 | ObjInsert(obj._keys, k) ; Else insert key into obj._keys 983 | } 984 | 985 | for i, k in OldKeys { ; Put the keys after key 986 | if (i < idx) ; back into key list 987 | continue 988 | obj._keys.Insert(k) 989 | } 990 | 991 | return r 992 | } 993 | 994 | ; --- MODIFICATION START (dein0s) --- 995 | IsStringEmpty(string) 996 | { 997 | if (string == Chr(14) or string == "`n" or string == "`r" or string == "`r`n" or string == "`n`r" or string == "") { 998 | return true 999 | } 1000 | return false 1001 | } 1002 | 1003 | CalcStringMD5(string, case=true) 1004 | { 1005 | static MD5_DIGEST_LENGTH := 16 1006 | hModule := DllCall("LoadLibrary", "Str", "advapi32.dll", "Ptr") 1007 | VarSetCapacity(MD5_CTX, 104, 0) 1008 | DllCall("advapi32\MD5Init", "Ptr", &MD5_CTX) 1009 | DllCall("advapi32\MD5Update", "Ptr", &MD5_CTX, "AStr", string, "UInt", StrLen(string)) 1010 | DllCall("advapi32\MD5Final", "Ptr", &MD5_CTX) 1011 | Loop % MD5_DIGEST_LENGTH 1012 | outStr .= Format("{:02" (case ? "X" : "x") "}", NumGet(MD5_CTX, 87 + A_Index, "UChar")) 1013 | return outStr, DllCall("FreeLibrary", "Ptr", hModule) 1014 | } 1015 | ;觉得原函数不是很好用,进行改造,实现设置默认值的功能 1016 | GetValue(sec, key,default) 1017 | { 1018 | IniVal:=this[sec][ key] 1019 | 1020 | if !IniVal 1021 | { 1022 | this[sec][ key]:=default 1023 | IniVal := default 1024 | } 1025 | return IniVal 1026 | } 1027 | 1028 | /* 1029 | List of functions: 1030 | class_EasyIni(sFile="", sLoadFromStr="") 1031 | EasyIni_CreateBaseObj(parms*) 1032 | EasyIni_Set(obj, parms*) 1033 | EasyIni_NewEnum(obj) 1034 | EasyIni_EnumNext(e, ByRef k, ByRef v="") 1035 | EasyIni_Remove(obj, parms*) 1036 | EasyIni_Insert(obj, parms*) 1037 | EasyIni_InsertBefore(obj, key, parms*) 1038 | IsStringEmpty(string) 1039 | CalcStringMD5(string, case=true) 1040 | GetValue(sec, key,default) 1041 | 1042 | List of EasyIni class methods: 1043 | __New(sFile="", sLoadFromStr="") 1044 | CreateIniObj(parms*) 1045 | AddSection(sec, key="", val="", ByRef rsError="") 1046 | RenameSection(sOldSec, sNewSec, ByRef rsError="") 1047 | DeleteSection(sec) 1048 | GetSections(sDelim="`n", sSort="") 1049 | FindSecs(sExp, iMaxSecs="") 1050 | AddKey(sec, key, val="", ByRef rsError="") 1051 | RenameKey(sec, OldKey, NewKey, ByRef rsError="") 1052 | DeleteKey(sec, key) 1053 | RemoveKey(sec, key) 1054 | GetKeys(sec, sDelim="`n", sSort="") 1055 | FindKeys(sec, sExp, iMaxKeys="") 1056 | FindExactKeys(key, iMaxKeys="") 1057 | GetVals(sec, sDelim="`n", sSort="") 1058 | FindVals(sec, sExp, iMaxVals="") 1059 | HasVal(sec, FindVal) 1060 | SetKeyVal(sec, key, val, ByRef rsError="") 1061 | GetCommentContent(sec="", key="", topComment=false) 1062 | GetTopComments() 1063 | GetSectionComments(sec) 1064 | GetKeyComments(sec, key) 1065 | AddComment(sec="", key="", comment="", topComment=false, ByRef rsError="") 1066 | AddTopComment(comment, ByRef rsError="") 1067 | AddSectionComment(sec, comment, ByRef rsError="") 1068 | AddKeyComment(sec, key, comment, ByRef rsError="") 1069 | DeleteComment(sec="", key="", comment="", topComment=false, ByRef rsError="") 1070 | DeleteTopComment(comment, ByRef rsError="") 1071 | DeleteSectionComment(sec, comment, ByRef rsError="") 1072 | DeleteKeyComment(sec, key, comment, ByRef rsError="") 1073 | Update(SourceIni, sections=true, keys=true, values=false, top_comments=false, section_comments=true, key_comments=true) 1074 | Compare(SourceIni, sections=true, keys=true, comments=false) 1075 | Copy(SourceIni, bCopyFileName = true) 1076 | Merge(vOtherIni, bRemoveNonMatching = false, bOverwriteMatching = false, vExceptionsIni = "") 1077 | GetFileName() 1078 | GetOnlyIniFileName() 1079 | IsEmpty() 1080 | Reload() 1081 | Save(sSaveAs="", bWarnIfExist=false) 1082 | ToVar() 1083 | */ 1084 | ; --- MODIFICATION END (dein0s) --- 1085 | -------------------------------------------------------------------------------- /lib/class_listbox.ahk: -------------------------------------------------------------------------------- 1 | Class CListBox 2 | { 3 | /* 4 | 新增(追加)listbox项 5 | HWND:ListBox控件句柄 6 | Text:要追加字符 7 | 成功返回总行数否则返回false 8 | */ 9 | Add(HWND,Text){ 10 | Static LB_ADDSTRING := 0x0180 11 | VarSetCapacity(String,StrPut(Text,"utf-16")*4),StrPut(Text, &String, "utf-16") 12 | Index:=DllCall("User32\SendMessage", "Ptr", HWND, "UInt", LB_ADDSTRING, "Ptr", 0, "Ptr", &String)+1 13 | Count:=this.GetCount(HWND),this.SetCurSel(HWND, Count) 14 | Return Count=Index?Count:False 15 | } 16 | /* 17 | 指定位置插入listbox项 18 | HWND:ListBox控件句柄 19 | Pos:指定插入的行号,Pos=0时追加插入 20 | Text:要追加字符 21 | 成功返回行号否则返回false 22 | */ 23 | Insert(HWND,Text,Pos:=1){ 24 | Static LB_INSERTSTRING := 0x0181 25 | VarSetCapacity(String,StrPut(Text,"utf-16")*4),StrPut(Text, &String, "utf-16") 26 | Index:=DllCall("User32\SendMessage", "Ptr", HWND, "UInt", LB_INSERTSTRING, "UInt", Pos-1, "Ptr", &String)+1 27 | if (Index>=Pos) 28 | this.SetCurSel(HWND, Index) 29 | Return Index>=Pos?Index:False 30 | } 31 | /* 32 | 获取listbox总行数 33 | 成功返回总行数 34 | */ 35 | GetCount(HWND){ 36 | Static LB_GETCOUNT := 0x018B 37 | Return DllCall("User32.dll\SendMessage", "Ptr", HWND, "UInt", LB_GETCOUNT, "Ptr", 0, "Ptr", 0, "Ptr") 38 | } 39 | /* 40 | 删除listbox指定行 41 | HWND:ListBox控件句柄 42 | Pos:指定删除的行号 43 | 成功返回True否则返回false 44 | */ 45 | Delete(HWND,Pos){ 46 | Static LB_DELETESTRING := 0x0182 47 | i:=this.GetCount(HWND) 48 | Index:=DllCall("User32\SendMessage", "Ptr", HWND, "UInt", LB_DELETESTRING, "UInt", Pos-1, "Ptr", 0, "Ptr") 49 | Count:=this.GetCount(HWND) 50 | Return Index=Count&&Count") 43 | content := StrReplace(content,"/") 44 | content := StrReplace(content,"\") 45 | content := StrReplace(content,"|") 46 | content := StrReplace(content,";") 47 | content := StrReplace(content,"'") 48 | content := StrReplace(content,"·") 49 | content := StrReplace(content,"~") 50 | Return Content 51 | } -------------------------------------------------------------------------------- /lib/tc.ahk: -------------------------------------------------------------------------------- 1 | ; #IfWinActive, ahk_class TTOTAL_CMD 2 | ; ^8:: 3 | ; ControlGetText, test, TPathPanel2, ahk_exe TOTALCMD.EXE 4 | ; if (RegExMatch(test, "\\>.+")) 5 | ; PostMessage,1075, 312, 0, , ahk_class TTOTAL_CMD 6 | ; Else 7 | ; TC_EMC("em_showAV") 8 | ; Return 9 | 10 | 11 | ; #IfWinActive 12 | TC_CM(num,param:=""){ 13 | SendMessage 1075,%num%,%param%, ,ahk_class TTOTAL_CMD 14 | return 1 15 | } 16 | 17 | TC_EMC( cmd, wID="ahk_class TTOTAL_CMD", activateWin=FALSE, showMsg=FALSE ) { 18 | TC_Activate( wID, activateWin, showMsg, cmd ) 19 | TC_SendWMCopyData( "EM", cmd, params:="", wID ) 20 | Return 21 | } 22 | 23 | 24 | TC_CD@( wID, src="", trg="", params="", activateWin=TRUE ) { 25 | if( activateWin ) 26 | WinActivate, % ( wID+0 ? "ahk_id " wID : wID ) 27 | TC_SendWMCopyData( "CD", cmd:=(src " `r" trg " "), params, wID ) 28 | Return 29 | } 30 | 31 | TC_Activate( byRef wID, activateWin=TRUE, showMsg=TRUE, cmd="" ) { 32 | wID:=QueryWinID(wID, TRUE) 33 | if(!activateWin ) 34 | Return FALSE 35 | if( showMsg ) 36 | MsgBox,,%A_ThisFunc%, % "Activating TC" ( cmd ? ", for command: " cmd "`n" : "`n"), 1 37 | WinActivate, ahk_id %wID% 38 | Return TRUE 39 | } 40 | 41 | QueryWinID( aWin="A", canExist=FALSE, winText="", notTitle="", notText="" ) { 42 | if( !(retVal:=WinActiveA( aWin, winText, notTitle, notText )) ) 43 | retVal:=( !canExist ? 0 : WinExistA( aWin, winText, notTitle, notText )) 44 | Return retVal 45 | } 46 | 47 | WinActiveA( aWin="", winText="", notTitle="", notText="" ) { 48 | Return WinActive( (aWin+0 ? "ahk_id " aWin : aWin), winText, notTitle, notText ) 49 | } 50 | 51 | WinExistA( aWin="", winText="", notTitle="", notText="" ) { 52 | Return WinExist( (aWin+0 ? "ahk_id " aWin : aWin), winText, notTitle, notText ) 53 | } 54 | 55 | ; TC_CD(src := "", target := ""){ 56 | ; srcLen := StrLen(src), tgtLen := StrLen(target) 57 | ; cmd := srcLen ? src "`r" (tgtLen ? target : "") 58 | ; : "`r" target 59 | ; ; clipboard := cmd 60 | ; TC_SendWMCopyData( "CD", cmd, "", "ahk_class TTOTAL_CMD" ) 61 | ; } 62 | 63 | 64 | ;; 65 | ;; AutoHotkey_L Function 66 | ;; cmdType: "CD" or "EM" 67 | ;; cmd(1): name of user command, e.g. em_FOO 68 | ;; cmd(2): formatted string with path's to CD to, 69 | ;; e.g. "C:\`rC:\Users" 70 | ;; addParams: for CD only, e.g. ST, S, T 71 | 72 | TC_SendWMCopyData( cmdType, byRef cmd, byRef addParams="", aWin="A" ) { 73 | aWin := (aWin+0) ? aWin : WinExist(aWin) 74 | ; aWin := aWin ? aWin : WinExist("ahk_class TTOTAL_CMD") 75 | 76 | Critical 77 | 78 | VarSetCapacity( CopyDataStruct, A_PtrSize * 3 ) 79 | if( A_IsUnicode ) 80 | { 81 | VarSetCapacity( cmdA, StrPut(cmd, "cp0") + 1) 82 | Loop, % StrLen(cmd) 83 | NumPut( Asc(SubStr(cmd, A_Index, 1)), cmdA, A_Index - 1, "Char") 84 | NumPut( 0, cmdA, StrLen(cmd), "Char") ; Add the '\0' char to the end of the string and terminate it, otherwise, changes are that unwanted char present. 85 | } 86 | NumPut( Asc(SubStr(cmdType,1,1)) + 256 * Asc(SubStr(cmdType,2,1)), CopyDataStruct ) 87 | NumPut( StrLen(cmd) + (cmdType="CD" ? 5 : 1), CopyDataStruct, A_PtrSize ) 88 | NumPut((A_IsUnicode ? &cmdA : &cmd), CopyDataStruct, A_PtrSize * 2) 89 | NumPut( &cmdA, CopyDataStruct, A_PtrSize * 2) 90 | 91 | 92 | Loop, % (cmdType=="CD" ? 2 : 0) 93 | NumPut( Asc(SubStr(addParams, A_Index, 1)), (A_IsUnicode ? cmdA : cmd), (StrLen(cmd) + A_Index), "Char" ) 94 | SendMessage, 0x4A,, &CopyDataStruct,, ahk_id %aWin% 95 | Return 96 | } -------------------------------------------------------------------------------- /settings.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzncpa/tmd/bb51e5929e0076b2f877cf8a5d5f2a766a257115/settings.ini -------------------------------------------------------------------------------- /tc.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzncpa/tmd/bb51e5929e0076b2f877cf8a5d5f2a766a257115/tc.ico -------------------------------------------------------------------------------- /按键列表(键盘, 鼠标和操纵杆) - AutoHotkey.url: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=https://www.autoahk.com/help/autohotkey/zh-cn/docs/KeyList.htm 3 | -------------------------------------------------------------------------------- /路径自动找先不要填了: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hzncpa/tmd/bb51e5929e0076b2f877cf8a5d5f2a766a257115/路径自动找先不要填了 --------------------------------------------------------------------------------