I wanted to leave the following code to help someone else in the future.
It is a v1 script that does not use UIA, but will:
- monitor and record open tabs
- open previously used tabs
- open saved favorites tabs
- activate an already open tab (if you try to open it)
- really only works if 1 FE window is open (gets confused with 2+ FE windows)
It used v1 because my IT department isn't upgrading to v2.
Also I know it could use a lot of polish, but I am limited to the IDE I have at work
Thanks for the core work that others had done, and I am sorry if I forgot to credit anyone.
Code: Select all
#Requires AutoHotkey v1.1.33
#NoEnv
#SingleInstance Force
; #Persistent
; #InstallKeybdHook ; needed for $ HotKey modifier (no trigger self) and others (e.g, Inactivity)
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%
/*
;==============================================================================
;========== What This Script Does ============================================
;==============================================================================
; Script that monitors what Tabs are open in File Explorer in Win 11
; NOTE: Only works if 1 FE window is open (not 2+ windows).
;==============================================================================
*/
; =========================================
; ===== Includes
; =========================================
;BeginRegion
#Include %A_ScriptDir%\Library\SettingsClass.ahk
#Include %A_ScriptDir%\Library\getSupportFilename.ahk
;EndRegion
;==============================================================================
;========== Pre-Main Script ==================================================
;==============================================================================
------------------------------------------------------------------------
; Files to include during a compile
;BeginRegion
imageDir := A_ScriptDir "\Script_Images"
IconName := getSupportFilename("ico") ; use the older simple filename version of getSupportFilename("ico")
FileInstall, .\Script_Images\Icons\File_Explorer_Manager.ico, %imageDir%\Icons\%IconName%
if(A_IsCompiled)
{
IconName = %imageDir%\Icons\%IconName%
}
Else
{
IconName := getSupportFilename2("ico") ; now use the updated version of getSupportFilename2("ico")
}
;EndRegion
; ------------------------------------------------------------------------
; setup Settings variable
;BeginRegion
settingsArray := []
; new SettingsProperty(propertyName, iniName="", defaultValue="", isWriteOnClose=false, isArray=false, arraySplitChar="", isBoolean=false)
settingsArray.push( new SettingsProperty("Save_Every_X_Minutes", , 2, false) )
settingsArray.push( new SettingsProperty("TabList_OnClose", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_Favourites", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_1", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_2", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_3", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_4", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_5", , "", false, true, "|") )
settingsArray.push( new SettingsProperty("TabList_Preserved", , "", false, true, "|") )
global Settings := new SettingsClass(settingsArray)
;EndRegion
; ------------------------------------------------------------------------
; Set up SystemTray Menu
;BeginRegion
FileGetTime, ScriptLastModified, %A_ScriptFullPath%, M
FormatTime, ScriptLastModifiedX, %ScriptLastModified%
TrayTipString =
( LTrim
---- %A_ScriptName% ----
Script last modified:
%ScriptLastModifiedX%
)
Menu, Tray, Tip, %TrayTipString% ; Create system tray Mouse-over ToolTip
IfExist %IconName%
{
Menu, Tray, Icon, %IconName%
}
Menu, tray, NoStandard ; Remove all standard menu items
GoSub Menu_Create_SubMenu_Favourites
Menu, tray, add, Favourite tabs, :SubMenu_FavouriteTabsMenuItems
Menu, Tray, add ; Creates a separator line.
GoSub Menu_Create_SubMenu_OpenTabs
Menu, tray, add, Open tabs, :SubMenu_OpenTabsMenuItems
Menu, Tray, add ; Creates a separator line.
Menu, Tray, add, Open all tabs that were open when Explorer was closed , TabList_Open_Last
Menu, tray, default, Open all tabs that were open when Explorer was closed
SubMenuNumber := 0
GoSub Menu_Create_SubMenu_PriorTabs
Menu, Tray, add, Preserved tab list, :SubMenu_PriorTabsMenuItems_0
SubMenuNumber := 1
GoSub Menu_Create_SubMenu_PriorTabs
Menu, SubMenu_PriorTabListsMenuItems, add, 1st most recent tab list, :SubMenu_PriorTabsMenuItems_1
SubMenuNumber := 2
GoSub Menu_Create_SubMenu_PriorTabs
Menu, SubMenu_PriorTabListsMenuItems, add, 2nd most recent tab list, :SubMenu_PriorTabsMenuItems_2
SubMenuNumber := 3
GoSub Menu_Create_SubMenu_PriorTabs
Menu, SubMenu_PriorTabListsMenuItems, add, 3rd most recent tab list, :SubMenu_PriorTabsMenuItems_3
SubMenuNumber := 4
GoSub Menu_Create_SubMenu_PriorTabs
Menu, SubMenu_PriorTabListsMenuItems, add, 4th most recent tab list, :SubMenu_PriorTabsMenuItems_4
SubMenuNumber := 5
GoSub Menu_Create_SubMenu_PriorTabs
Menu, SubMenu_PriorTabListsMenuItems, add, 5th most recent tab list, :SubMenu_PriorTabsMenuItems_5
Menu, tray, add, Prior tab lists, :SubMenu_PriorTabListsMenuItems
Menu, Tray, add ; Creates a separator line.
Menu, SubMenu_StandardMenuItems, Standard
Menu, tray, add, Standard menu, :SubMenu_StandardMenuItems
Menu, Tray, add ; Creates a separator line.
Menu, Tray, add, Open INI file, Menu_OpenINI
; GoSub Menu_Create_SubMenu_PriorTabList
;Menu, tray, add, Test, :SubMenu_PriorTabs1MenuItems
; Menu, Tray, add, About..., TabList_Open
;EndRegion
; ------------------------------------------------------------------------
; Setup the Log file
; global Log := createLogInstance()
; Log.isLogClassTurnedOff := true
; ------------------------------------------------------------------------
; Setup the ability to receive messages from other programs/scripts
;BeginRegion
RegisterShellHook() ; if FE is NOT running or for when it closes
;EndRegion
; When the script ends, perform this function
; OnExit("ExitFunction")
;EndRegion
;==============================================================================
;========== Main Script ======================================================
;==============================================================================
;BeginRegion
Settings.Update_Tab_Menu_Every_X_Minutes := 0.5
if ( getFileExplorerHWND() )
{ ; if File Explorer is already running
; update periodically
timerPeriod := Settings.Save_Every_X_Minutes * 60 * 1000
SetTimer, TabList_Update, %timerPeriod%
timerPeriod2 := Settings.Update_Tab_Menu_Every_X_Minutes * 60 * 1000
SetTimer, Menu_Create_SubMenu_OpenTabs, %timerPeriod2%
}
return
;EndRegion
; =========================================
; ===== Menu Subroutines / Labels
; =========================================
;BeginRegion
TabList_Update:
{
newPathArray := getAllTabLocationsFromFileExplorer()
oldPathArray := Settings.TabList_1
if (arrayToString(newPathArray) != arrayToString(oldPathArray))
{
Settings.TabList_5 := Settings.TabList_4
Settings.TabList_4 := Settings.TabList_3
Settings.TabList_3 := Settings.TabList_2
Settings.TabList_2 := Settings.TabList_1
Settings.TabList_1 := newPathArray
Settings.writeINIFile()
}
; MsgBox % arrayToString(newPathArray) . "`n`n`n" . arrayToString(oldPathArray)
}
return
TabList_Save_Preserved:
{
newPathArray := getAllTabLocationsFromFileExplorer()
oldPathArray := Settings.TabList_Preserved
if (arrayToString(newPathArray) != arrayToString(oldPathArray))
{
Settings.TabList_Preserved := newPathArray
Settings.writeINIFile()
; rederaw the menu
SubMenuNumber := 0
GoSub Menu_Create_SubMenu_PriorTabs
}
}
return
TabList_Open_Last:
reopenTabArray(Settings.TabList_OnClose)
return
Menu_OpenINI:
{ ; opens an INI file for editing.
IniName := Settings.iniFile
IfExist %IniName%
{
Run, open %IniName%
}
}
return
Menu_Create_SubMenu_PriorTabs:
{
; SubMenuNumber needs to be defined prior to entering the subroutine (valid values are 0-5, 0 is the Preserved tab list)
SubMenuName := "SubMenu_PriorTabsMenuItems_" . SubMenuNumber
PriorTabsArray := getTheRightTabListArray(SubMenuNumber)
Menu, %SubMenuName%, add
Menu, %SubMenuName%, DeleteAll
Menu, %SubMenuName%, add, Open All, Menu_Open_PriorTabsArray
Menu, %SubMenuName%, add, ; Separator
if (PriorTabsArray.Length() < 1)
{
Menu, %SubMenuName%, add, No Tabs in this list yet, TabList_Open_Last ; just a dummy target label
Menu, %SubMenuName%, disable, No Tabs in this list yet
}
else
{
for index, value in PriorTabsArray
{
; the Tab_Open_Favourite subroutine works just fine here
Menu, %SubMenuName%, add, %value% , Tab_Open_Favourite
}
}
if(SubMenuNumber == 0)
{
Menu, %SubMenuName%, add ; Creates a separator line.
Menu, %SubMenuName%, add, Set Current tab list as the Preserved tab list, TabList_Save_Preserved
}
}
return
Menu_Open_PriorTabsArray:
{
SubMenuNumber := SubStr(A_ThisMenu , 0, 1)
PriorTabsArray := getTheRightTabListArray(SubMenuNumber)
reopenTabArray(PriorTabsArray)
}
return
Menu_Create_SubMenu_Favourites:
{
Menu, SubMenu_FavouriteTabsMenuItems, add
Menu, SubMenu_FavouriteTabsMenuItems, DeleteAll
Menu, SubMenu_FavouriteTabsMenuItems, add, Add Active Tab as Favourite tab (Ctrl+Shift+F), Tab_Save_Favourite
Menu, SubMenu_FavouriteTabsMenuItems, add,
if (Settings.TabList_Favourites.Length() < 1)
{
Menu, SubMenu_FavouriteTabsMenuItems, add, No Favourites yet, TabList_Open_Last ; just a dummy target label
Menu, SubMenu_FavouriteTabsMenuItems, disable, No Favourites yet
}
else
{
for index, value in Settings.TabList_Favourites
{
; BoundMenuFunction := Func("Explorer_Navigate_Tab").Bind(value)
Menu, SubMenu_FavouriteTabsMenuItems, add, %value% , Tab_Open_Favourite
}
}
}
return
Tab_Save_Favourite:
{
activePath := GetActiveExplorerTab()
; msgbox, % activePath
wasAddedToArray := false
if (Settings.TabList_Favourites.Length() < 1)
{
Settings.TabList_Favourites := [activePath]
wasAddedToArray := true
; msgbox, "nothing"
}
else
{
; msgbox, "something"
isInArray := false
for index, value in Settings.TabList_Favourites
{
if(value == activePath)
{
isInArray := true
}
}
if (!isInArray)
{
; msgbox, "not in array"
wasAddedToArray := true
Settings.TabList_Favourites.push(activePath)
}
}
if (wasAddedToArray)
{
; msgbox, "writing"
Settings.writeINIFile()
GoSub Menu_Create_SubMenu_Favourites
; notify the user of a change in status
TrayTip, File Explorer Tab Manager, New Favourite folder saved.`n%activePath%,, 16
SetTimer, HideTrayTip, -4000
}
}
return
Tab_Open_Favourite:
reopenTabArray([A_ThisMenuItem])
return
Menu_Create_SubMenu_OpenTabs:
{
Menu, SubMenu_OpenTabsMenuItems, add
Menu, SubMenu_OpenTabsMenuItems, DeleteAll
Menu, SubMenu_OpenTabsMenuItems, add, Set Current tab list as the Preserved tab list, TabList_Save_Preserved
Menu, SubMenu_OpenTabsMenuItems, add ; Creates a separator line.
openTabsArray := getAllTabLocationsFromFileExplorer()
Settings.OpenTabs := openTabsArray
if (openTabsArray.Length() < 1)
{
Menu, SubMenu_OpenTabsMenuItems, add, No Open Tabs, TabList_Open_Last ; just a dummy target label
Menu, SubMenu_OpenTabsMenuItems, disable, No Open Tabs
}
else
{
for index, value in openTabsArray
{
Menu, SubMenu_OpenTabsMenuItems, add, %value% , Menu_ActivateAnOpenTab
}
}
}
return
Timer_Create_SubMenu_OpenTabs:
{
newPathArray := getAllTabLocationsFromFileExplorer()
oldPathArray := Settings.OpenTabs
if (arrayToString(newPathArray) != arrayToString(oldPathArray))
{
GoSub Menu_Create_SubMenu_OpenTabs
}
}
return
Menu_ActivateAnOpenTab:
activateAnOpenTab(A_ThisMenuItem)
return
;EndRegion
; =========================================
; ===== Functions
; =========================================
;BeginRegion
ShellMessage( wParam, lParam )
{
static WindowHandle
if ( wParam = (HSHELL_WINDOWCREATED:=1) )
{
; WinGet, WindowProcessName, ProcessName, ahk_id %lParam%
if ( lParam = getFileExplorerHWND() )
{
WindowHandle := lParam
OnExplorerOpen(lParam)
}
}
else if ( wParam = (HSHELL_WINDOWDESTROYED:=2) )
{
if ( lParam = WindowHandle )
{
OnExplorerExit(lParam)
}
}
return
}
RegisterShellHook()
{
Gui, +LastFound
ScriptHandle := WinExist()
DllCall( "RegisterShellHookWindow", "UInt", ScriptHandle )
ShellHookMessageIdentifier := DllCall( "RegisterWindowMessage", "Str", "SHELLHOOK" )
OnMessage( ShellHookMessageIdentifier, "ShellMessage" )
return
}
OnExplorerOpen(winHWHD)
{
; notify the user of a change in status
TrayTip, File Explorer Tab Manager, The File Explorer app has opened.`nThe helper program will start to monitor the open tabs.
SetTimer, HideTrayTip, -3000
; update periodically
timerPeriod := Settings.Save_Every_X_Minutes * 60 * 1000
SetTimer, TabList_Update, %timerPeriod%
timerPeriod2 := Settings.Update_Tab_Menu_Every_X_Minutes * 60 * 1000
SetTimer, Menu_Create_SubMenu_OpenTabs, %timerPeriod2%
return winHWHD
}
OnExplorerExit(winHWHD)
{
; save the last open tabs
if(Settings.TabList_1.Length() >= 1)
{
Settings.TabList_OnClose := Settings.TabList_1
}
else
{
Settings.TabList_OnClose := Settings.TabList_2
}
Settings.writeINIFile()
if (getFileExplorerHWND())
{ ; only if there are no more open FE windows
; turn off TabList updates
SetTimer, TabList_Update, Off
SetTimer, Menu_Create_SubMenu_OpenTabs, Off
; notify the user of a change in status
tmpStr := "The File Explorer app has exited." . "`n"
tmpStr .= "The helper program will no longer monitor the open tabs."
TrayTip, File Explorer Tab Manager, %tmpStr%
SetTimer, HideTrayTip, -3000
}
else
{ ; if another FE window is open (not that this program handles multiple FE windows well at all)
; notify the user of a change in status
tmpStr := "A File Explorer window has closed." . "`n"
tmpStr .= "But, at least one File Explorer window is still open." . "`n"
tmpStr .= "The helper program will continue to monitor the open tabs."
TrayTip, File Explorer Tab Manager, %tmpStr%
SetTimer, HideTrayTip, -3000
}
return
}
HideTrayTip()
{ ; from https://www.autohotkey.com/docs/v1/lib/TrayTip.htm
TrayTip ; Attempt to hide it the normal way.
if SubStr(A_OSVersion,1,3) = "10." {
Menu Tray, NoIcon
Sleep 200 ; It may be necessary to adjust this sleep.
Menu Tray, Icon
}
}
; =========================================
activateAnOpenTab(targetFullPath)
{
hwnd := openFileExplorer()
; hwnd := getFileExplorerHWND()
; WinActivate, ahk_id %hwnd%
if ( isPathInFileExplorer(targetFullPath, hwnd) )
{
; OK this is painful but the best I can do. Tab through the open tabs until you hit the desired one
openTabsArray := getAllTabLocationsFromFileExplorer() ; I need to know how many tabs are open
Loop, % openTabsArray.Length()
{
; move to next tab
Send, ^{Tab} ; move to newxt tab (cntl+Tab)
; Note 1: This hotkey works in English , don't know for other localizations
; Note 2: AFAIK, this hotkey does nothing in earlier versions of Windows Explorer but it would be safer to check the version before sending it
Sleep, 100 ; for safety
canidateFullPath := GetActiveExplorerTab(hwnd)
tmpStr := "A_Index := " . A_Index . "`n"
tmpStr .= "targetFullPath := " . targetFullPath . "`n"
tmpStr .= "canidateFullPath := " . canidateFullPath . "`n"
; msgbox, % tmpStr
if (canidateFullPath == targetFullPath)
{
return 0
}
}
}
; if we can't find the Tab open it anew
reopenTabArray([targetFullPath])
return 1
}
reopenTabArray(pathArray)
{
hwnd := openFileExplorer()
; hwnd := getFileExplorerHWND()
; WinActivate, ahk_id %hwnd%
for index, pathCurrent in pathArray
{
; MsgBox, % pathCurrent
if ! isPathInFileExplorer(pathCurrent)
{
WinActivate, ahk_id %hwnd%
Explorer_Navigate_New_Tab(pathCurrent)
}
}
; update the menu
GoSub Menu_Create_SubMenu_OpenTabs
return
}
getTheRightTabListArray(whichNumber)
{
switch whichNumber
{
case 0:
tmpArr := Settings.TabList_Preserved
case 1:
tmpArr := Settings.TabList_1
case 2:
tmpArr := Settings.TabList_2
case 3:
tmpArr := Settings.TabList_3
case 4:
tmpArr := Settings.TabList_4
case 5:
tmpArr := Settings.TabList_5
default:
tmpArr := Settings.TabList_1
}
return tmpArr
}
getAllTabLocationsFromFileExplorer()
{ ; selects the FileExplorer window(s) and lists all open tabs
tabPaths := []
; https://www.autohotkey.com/boards/viewtopic.php?p=28761&sid=bf16c7462b523f4f0944d69383ea9a0e#p28761
For Window in ComObjCreate("Shell.Application").Windows ; see http://msdn.microsoft.com/en-us/library/windows/desktop/aa752084(v=vs.85).aspx
{
if (Folder := Window.Document.Folder.Self) ; && Window.Visible
{
tabPaths.Push(Folder.Path)
; MsgBox, 0, Shell Windows
; , % ++Count . "`n"
; . "Window.HWND: !" . Window.HWND . "!`n"
; . "Window.Visible: !" . Window.Visible . "!`n"
; . "Folder.Path: !" . Folder.Path . "!`n" ; <--- this is what you want
; . "Folder.Name: !" . Folder.Name . "!`n"
; . "Folder.Type: !" . Folder.Type . "!"
}
}
return tabPaths
}
isPathInFileExplorer(targetFullPath, targetHWND="")
{
_targetHWND := targetHWND ; create a stable version that ignores changes we make in the loop
tabNumber := 0
For Window in ComObjCreate("Shell.Application").Windows ; see http://msdn.microsoft.com/en-us/library/windows/desktop/aa752084(v=vs.85).aspx
{
if (Folder := Window.Document.Folder.Self) ; && Window.Visible
{
tabNumber := tabNumber + 1
; MsgBox, 0, Shell Windows
; , % ++Count . "`n"
; . "Window.HWND: !" . Window.HWND . "!`n"
; . "Window.Visible: !" . Window.Visible . "!`n"
; . "Folder.Path: !" . Folder.Path . "!`n" ; <--- this is what you want
; . "Folder.Name: !" . Folder.Name . "!`n"
; . "Folder.Type: !" . Folder.Type . "!"
canidateFullPath := Folder.Path
canidateHWND := Window.HWND
if (_targetHWND == "")
targetHWND := canidateHWND
if (canidateFullPath == targetFullPath) && (canidateHWND == targetHWND)
return tabNumber
}
}
return 0
}
Explorer_Navigate_New_Tab(FullPath, hwnd="")
{ ; opens a new tab and then sets that tab's path to FullPath
; error checking / hwnd == "" try to make Active Window
hwnd := (hwnd="") ? getFileExplorerHWND("") : hwnd ; if omitted, use active window
if !(getFileExplorerHWND(hwnd))
{ ; exit if no Explorer window
ErrorLevel := 1
return 0
}
WinWaitActive, ahk_id %hwnd%
; Windows Explorer is the active window
Send, ^t ; add a new tab
; Note 1: This hotkey works in English and French, don't know for other localizations
; Note 2: AFAIK, this hotkey does nothing in earlier versions of Windows Explorer but
; it would be safer to check the version before sending it
Sleep, 100 ; for safety
; the new tab is the active tab
return Explorer_Navigate_Tab(FullPath, hwnd)
}
Explorer_Navigate_Tab(FullPath, hwnd="")
{ ; changes the active tab to FullPath (over-writing the existing value)
; see https://www.autohotkey.com/boards/viewtopic.php?p=489575#p489575
; originally from Learning one (https://www.autohotkey.com/boards/viewtopic.php?p=4480#p4480)
; adapted by JnLlnd for tabbed browsing new to Windows 11 version 22H2 (build 22621.675)
; with code from ntepa (https://www.autohotkey.com/boards/viewtopic.php?p=488735#p488735)
; also works with previous versions of Windows Explorer
; error checking / hwnd == "" try to make Active Window
hwnd := (hwnd="") ? getFileExplorerHWND("") : hwnd ; if omitted, use active window
if !(getFileExplorerHWND(hwnd))
{ ; exit if no Explorer window
ErrorLevel := 1
return 0
}
; msgbox, % "Explorer_Navigate_Tab(" . FullPath . ", " . hwnd . ")"
For pExp in ComObjCreate("Shell.Application").Windows
{
if (pExp.hwnd = hwnd)
{ ; matching window found
activeTab := 0
try ControlGet, activeTab, Hwnd,, % "ShellTabWindowClass1", % "ahk_id" hwnd
if activeTab {
static IID_IShellBrowser := "{000214E2-0000-0000-C000-000000000046}"
shellBrowser := ComObjQuery(pExp, IID_IShellBrowser, IID_IShellBrowser)
DllCall(NumGet(NumGet(shellBrowser+0)+3*A_PtrSize), "Ptr", shellBrowser, "UInt*", thisTab)
tmpStr := "HWND:`t" . hwnd . "`n"
tmpStr .= "thisTab:`t" . thisTab . "`n"
tmpStr .= "activeTab:`t" . activeTab . "`n"
tmpStr .= "Loc:`t" . pExp.LocationURL . "`n"
tmpStr .= "Loc2:`t" . GetFullPathName(pExp.LocationURL) . "`n"
; msgbox, % tmpStr
if (thisTab != activeTab) ; matching active tab
continue
ObjRelease(shellBrowser)
}
pExp.Navigate("file:///" FullPath)
return 1
}
}
return 0 ; window not found
}
openFileExplorer()
{ ; if a window is already open does nothing (but activates the window)
hwnd := getFileExplorerHWND()
if( hwnd == 0)
{ ; no window open
Run, Explorer.exe /n`,/e`
Sleep, 1000
hwnd := getFileExplorerHWND()
}
WinActivate, ahk_id %hwnd%
return hwnd
}
getFileExplorerHWND(hwnd="")
{ ; get a File Explorer window OR error check that HWND is a File Explorer window
if (hwnd == "")
{ ; get a File Explorer window (preference for the Active Window)
hwnd := WinExist("A")
if ( getFileExplorerHWND(hwnd) )
{ ; see if Active Window is a file explorer window
ErrorLevel := 0
return hwnd
}
else if WinExist("ahk_Class CabinetWClass")
{ ; now grab any File Explorer window (explorer.exe is the Desktop process too. So you cannot check ahk_exe)
hwnd := WinExist("ahk_Class CabinetWClass")
ErrorLevel := 0
return hwnd
}
else
{ ; fail
ErrorLevel := 1
return 0
}
}
else
{ ; perform error checking
WinGet, ProcessName, ProcessName, % "ahk_id " hwnd
WinGetClass, ClassName, % "ahk_id " hwnd
if ((ProcessName == "explorer.exe") AND (ClassName == "CabinetWClass") )
{
ErrorLevel := 0
return hwnd
}
else
{
ErrorLevel := 1
return 0
}
}
}
GetFullPathName(path) {
cc := DllCall("GetFullPathName", "str", path, "uint", 0, "ptr", 0, "ptr", 0, "uint")
VarSetCapacity(buf, cc*(A_IsUnicode?2:1))
DllCall("GetFullPathName", "str", path, "uint", cc, "str", buf, "ptr", 0, "uint")
return buf
}
GetActiveExplorerTab(hwnd:="")
{ ; https://www.autohotkey.com/boards/viewtopic.php?p=544495#p544495 added case where hwnd==""
; does both File Explorer & IE
static IID_IShellBrowser := "{000214E2-0000-0000-C000-000000000046}"
if(hwnd == "")
{
hwnd := getFileExplorerHWND()
}
; msgbox, % "HWND=" . hwnd
activeTab := 0, hwnd := hwnd ? hwnd : WinExist("A")
try
ControlGet, activeTab, Hwnd,, ShellTabWindowClass1, ahk_id %hwnd% ; File Explorer (Windows 11)
catch
try
ControlGet, activeTab, Hwnd,, TabWindowClass1, ahk_id %hwnd% ; IE
for w in ComObjCreate("Shell.Application").Windows
{
; msgbox, % "looking for w=" . w.hwnd . "==" . hwnd
if (w.hwnd != hwnd)
continue
if (activeTab)
{ ; The window has tabs, so make sure this is the right one.
shellBrowser := ComObjQuery(w, IID_IShellBrowser, IID_IShellBrowser)
DllCall(NumGet(NumGet(shellBrowser+0), 3*A_PtrSize), "UPtr",shellBrowser, "UIntP",thisTab:=0)
if (thisTab != activeTab)
continue
}
; return w ; you can just return the window object
tab := w
switch ComObjType(tab.Document, "Class")
{
case "ShellFolderView":
; msgbox, % tab.Document.Folder.Self.Path
return tab.Document.Folder.Self.Path
default: ;case "HTMLDocument":
return tab.LocationURL
}
}
}
arrayToString(array, arraySplitChar="|")
{
tmpVal := ""
For Index, Value In array
tmpVal .= arraySplitChar . Value ; Remove leading delimiters when the loop is done
tmpVal := LTrim(tmpVal, arraySplitChar) ; Remove leading delimiters (,)
return tmpVal
}
;EndRegion
; =========================================
; ===== HotKeys
; =========================================
;BeginRegion
; -------- Hotkey Definitions Reminder ----------
; # - Win
; ! - Alt
; ^ - Control
; + - Shift
; ~ - pass key command to OS
; example -- $>!v:: ; $ (no trigger self) > (right key of a pair) ---- Alt (!) + V
^+F:: ; Ctrl+Shft+F
GoSub Tab_Save_Favourite
return
;EndRegion
; =========================================
; ===== Classes
; =========================================
;BeginRegion
;EndRegion
/*
;==============================================================================
;========== Version History ===================================================
;==============================================================================
;
; v0.9 2024-01-05
; * First proof of concept
;
; v1.0 2024-01-07
; * working functionality
;
; v1.1 2024-01-23
; * added a submenu to list all open tabs
; * added function to active an open tab
; * fixed the bug where OnClose was empty
; * opening a tab now starts FE if it isn't already running
;
; v1.2 2024-01-28
; * added a submenus for each prior tab list (including Preserved)
; * closing an FEno longer stops all monitoring, checks if another FE is open
;
;==============================================================================
*/
And the support files
SettingsClass.ahk
Code: Select all
; =========================================
; ===== Classes
; =========================================
;BeginRegion
class SettingsClass
{
__New(aArrayOfProperties, aIniFile="", aSectionName="")
{
; Input variables
; aArrayOfProperties -> an array of objects or associative arrays with the following keys: propertyName, iniName, defaultValue, isWriteOnClose (class SettingsProperty, at the end of this file). Properties are dynamically created from this array.
; aIniFile -> the INI file to read/write (default is A_Scriptname.INI)
; aSectionName -> the section of the INI file to use (default is A_ComputerName)
; dynamically create the properties
; this.settingsConfigutations := {}
Loop, % aArrayOfProperties.Length()
{
currentSetting := aArrayOfProperties[A_Index]
propertyName := currentSetting.propertyName
this[propertyName] := currentSetting.defaultValue
this.settingsConfigurations[propertyName] := currentSetting
; tmpStr := "propertyName=" propertyName
; msgbox, % tmpStr
}
; set the non-dynamic/universal properties
if(aIniFile="")
{
aIniFile := this.getSupportFilename("ini")
}
this.iniFile := aIniFile
if(aSectionName="")
{
this.SectionName := A_ComputerName . "-" . A_UserName
}
else
{
this.SectionName := aSectionName
}
; load the values of the settings from the INI file
this.readINIFile()
return this
}
; Properties
; ---------------------------------------------
; Properties are dynamically created from __New(aArrayOfProperties)
; the few that are universally defined in all version of the Class
; this.iniFile -> the INI file name to be read/written
; this.SectionName -> the section in the INI to read/write to
; Properties hidden from the user
; --------------------------------
; this.settingsConfigurations[propertyName] -> a version of aArrayOfProperties keyed by the propertyName. Used in the read/write methods.
; Methods (of suitable importance)
; ---------------------------------------------
readINIFile(aIniFile="")
{ ; get the INI variables from the file (various default values)
; Input variables
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
if(aIniFile="")
{
aIniFile := this.iniFile
}
retValF := FileExist(aIniFile) ; indicate if the INI file already existed (and hopefully the default values where NOT used)
IniRead, OutputVarSectionNames, %aIniFile%
secVar := this.SectionName
retValS := InStr(OutputVarSectionNames, secVar) ; is there the Section we need?
retVal := (retValF AND retValS)
if (!retVal) ; write an INI file if we don't have one with the needed Section
{
this.writeDefaultINIFile(aIniFile)
}
for propertyName, propertyConfig in this.settingsConfigurations
{
; tmpStr := "propertyName = " propertyName "`n"
; tmpStr .= "propertyConfig.iniName = " propertyConfig.iniName "`n"
; tmpStr .= "propertyConfig.defaultValue = " propertyConfig.defaultValue "`n"
; tmpStr .= "propertyConfig.isArray = " propertyConfig.isArray "`n"
; tmpStr .= "propertyConfig.arraySplitChar = " propertyConfig.arraySplitChar "`n"
; tmpStr .= "propertyConfig.isBoolean = " propertyConfig.isBoolean "`n"
; msgbox, % tmpStr
iniName := propertyConfig.iniName
defaultValue := propertyConfig.defaultValue
propertyValue := this.readINIFileValue(iniName, defaultValue, aIniFile)
if (propertyConfig.isBoolean)
{
if (propertyConfig.isArray)
{
defaultValueArray := StrSplit(defaultValue, propertyConfig.arraySplitChar)
for index, element in propertyValue
{
propertyValueTmp := this.validateBoolean(element, defaultValueArray[index])
propertyValue[index] := propertyValueTmp
}
}
else
{
propertyValue := this.validateBoolean(propertyValue, defaultValue)
}
}
else if (propertyConfig.isArray)
{
propertyValue := StrSplit(propertyValue, propertyConfig.arraySplitChar)
}
else if (propertyConfig.isObject)
{
propertyValueTmp := this.jsonToObject(propertyValue)
propertyValue := propertyValueTmp
}
this[propertyName] := propertyValue
}
return retVal
}
_writeINIFile(aIniFile="", isOnClose=false, isWriteDefaultValues=false, aSection="")
{ ; write user changeable settings to the INI file
; Input variables
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; Output variables
; errorLevel (Boolean) -> did an error occur while writing to the INI file?
if(aIniFile="")
{
aIniFile := this.iniFile
}
errLvl := false
for propertyName, propertyConfig in this.settingsConfigurations
{
; tmpStr := "----- Write ----" isOnClose "-" isWriteDefaultValues "-`n"
; tmpStr .= "aIniFile = " aIniFile "`n"
; tmpStr .= "propertyName = " propertyName "`n"
; tmpStr .= "propertyValue = " this[propertyName] "`n"
; tmpStr .= "propertyConfig.iniName = " propertyConfig.iniName "`n"
; tmpStr .= "propertyConfig.defaultValue = " propertyConfig.defaultValue "`n"
; tmpStr .= "propertyConfig.isWriteOnClose = " propertyConfig.isWriteOnClose "`n"
; tmpStr .= "propertyConfig.isArray = " propertyConfig.isArray "`n"
; tmpStr .= "propertyConfig.arraySplitChar = " propertyConfig.arraySplitChar "`n"
; tmpStr .= "propertyConfig.isBoolean = " propertyConfig.isBoolean "`n"
; msgbox, % tmpStr
tmpStr := "propertyName = " propertyName "`n"
iniName := propertyConfig.iniName
if(isWriteDefaultValues)
{ ; if creating a default INI file
errLvl_new := this.writeINIFileValue(iniName, propertyConfig.defaultValue, aIniFile, aSection)
tmpStr .= "written=" errLvl_new "`n"
}
else if (!isOnClose OR (isOnClose AND propertyConfig.isWriteOnClose))
{ ; if a normal write, or writing on Close and allowed to write OnClose.
if (propertyConfig.isArray)
{
tmpVal := ""
arraySplitChar := propertyConfig.arraySplitChar
For Index, Value In this[propertyName]
tmpVal .= arraySplitChar . Value ; Remove leading delimiters when the loop is done
tmpVal := LTrim(tmpVal, arraySplitChar) ; Remove leading delimiters (,)
}
else if (propertyConfig.isObject)
{
tmpVal := this.objectToJson(this[propertyName])
}
else
{
tmpVal := this[propertyName]
}
errLvl_new := this.writeINIFileValue(iniName, tmpVal, aIniFile, aSection)
tmpStr .= "written=" errLvl_new "`n"
}
else
{
errLvl_new := false
}
errLvl := (errLvl_new OR errLvl)
; msgbox, % tmpStr
}
return errLvl
}
writeINIFile(aIniFile="")
{ ; write user changeable settings to the INI file
; Input variables
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; Output variables
; errorLevel (Boolean) -> did an error occur while writing to the INI file?
return this._writeINIFile(aIniFile, false, false)
}
writeINIFileOnClose(aIniFile="")
{ ; write non-user editable values on the close of the script.
; Input variables
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; Output variables
; errorLevel (Boolean) -> did an error occur while writing to the INI file?
return this._writeINIFile(aIniFile, true, false)
}
writeDefaultINIFile(aIniFile="", aSection="")
{ ; write default values ot the INI file, including the non-user changeable ones (i.e., installed_on)
; Input variables
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; Output variables
; errorLevel (Boolean) -> did an error occur while writing to the INI file?
return this._writeINIFile(aIniFile, false, true, aSection)
}
; Methods (helpers)
; ---------------------------------------------
writeINIFileValue(aKey, aValue, aIniFile="", aSection="")
{ ; a wrapper to IniWrite
; Input variables
; aKey/aValue -> the key/value to write to the INI file
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; aSectionName -> the section of the INI file to use (default is A_ComputerName)
; Output variables
; errorLevel (Boolean) -> did an error occur while writing to the INI file?
if(aIniFile="")
{
aIniFile := this.iniFile
}
if(aSection="")
{
aSection := this.SectionName
}
IniWrite, %aValue%, %aIniFile%, %aSection%, %aKey%
return ErrorLevel
}
readINIFileValue(aKey, aDefValue, aIniFile="", aSection="")
{ ; a wrapper to IniRead (mostly a workaround for the fact that you can't use obj.var syntax in IniRead)
; Input variables
; aKey -> the key to read from the INI file
; aDefValue -> if the aKey cannot be read, supply this as the returned value
; aIniFile -> the INI file to read/write (default is this.iniFile set in _New())
; aSectionName -> the section of the INI file to use (default is A_ComputerName)
; Output variables
; Value -> the value associated with the aKey
if(aIniFile="")
{
aIniFile := this.iniFile
}
if(aSection="")
{
aSection := this.SectionName
}
IniRead, OutVar, %aIniFile%, %aSection%, %aKey%, %aDefValue%
return OutVar
}
getSupportFilename_old(fileExten, addDot=true)
{ ; provides a filename of a support file (e.g., ini, ico), regradless of whether the script is compiled or not
; Input variables
; fileExten -> the file extension of the support file (place in quotes)
; addDot -> should a '.' be added before fileExten? (Default=true)
; Output variables
; a filename of the script with the extension replaced with fileExten
return this.getSupportFilename(fileExten, addDot, "", False, "", False)
}
getSupportFilename(fileExten, addDot=true, parentFolder="", useDefaultFolder=True, filenameSuffix="", useFullPath=true)
{ ; provides a filename of a support file (e.g., ini, ico), regradless of whether the script is compiled or not
; Input variables
; fileExten -> the file extension of the support file (place in quotes)
; addDot -> should a '.' be added before fileExten? Allows you to use "log" instead of ".log" (Default=true)
; parentFolder -> directory where the file should be located (Default="")
; useDefaultFolder -> if parentFolder="" will place the file in a default folder based on fileExten (Default={"ini": "INI_Files", "ico": "Script_Images\Icons", "log": "Logs"})
; filenameSuffix -> a string added between the filenameNoExt and the Exten (e.g., "_" . A_ComputerName) (Default="")
; useFullPath -> return a FullPath or not. If parentFolder isa fullpath this will be ignored. (Default=true)
; Output variables
; a filename of the script with the extension replaced with fileExten
defaultArray := {"ini": "INI_Files", "ico": "Script_Images\Icons", "log": "Logs"}
if(fileExten != "")
{
if(addDot)
{
replacementStr = .%fileExten%
}
else
{
replacementStr = %fileExten%
}
}
else
{
replacementStr := ""
}
if(filenameSuffix != "")
{
replacementStr := filenameSuffix . replacementStr
}
if(A_IsCompiled)
{
StringReplace, fileName, A_ScriptName,.exe,%replacementStr%, All
}
else
{
StringReplace, fileName, A_ScriptName,.ahk,%replacementStr%, All
}
if(parentFolder == "")
{
if(useDefaultFolder)
{
1stChar := SubStr(fileExten, 1, 1)
if (1stChar = ".")
{
fileExten1 := SubStr(fileExten, 2)
; MsgBox, "dot: " . %fileExten1%
}
Else
{
fileExten1 := fileExten
; MsgBox, "nul: " . %fileExten1%
}
For key, value in defaultArray
{
if( InStr(key, fileExten1) )
{
parentFolder := value
}
}
}
}
if(parentFolder != "")
{
returnName := parentFolder . "\" . fileName
}
else
{
returnName := fileName
}
if(useFullPath)
{
; see https://www.autohotkey.com/boards/viewtopic.php?p=289536#p289536
path := returnName
cc := DllCall("GetFullPathName", "str", path, "uint", 0, "ptr", 0, "ptr", 0, "uint")
VarSetCapacity(buf, cc*(A_IsUnicode?2:1))
DllCall("GetFullPathName", "str", path, "uint", cc, "str", buf, "ptr", 0, "uint")
returnName := buf
; SplitPath, returnName, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
; if(OutDrive = "") ; is it already a FullPath?
; {
; SplitPath, A_ScriptFullPath, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
; returnName := OutDir . "\" . returnName ; OutDir includes OutDrive
; }
}
return returnName
}
validateBoolean(boolAsStr, defaultVal=false)
{ ; checks a String for true/false, Yes/No, or a number ("" is false)
; Input variables
; boolAsStr -> a string representation of a Boolean (e.g. from an INI file/STDIN)
; defaultVal -> a default Boolean that is returned if all else fails (default: false)
; Output variables
; boolAsBoolean -> a Boolean (true or false)
if(boolAsStr = "true")
{
return true
}
else if(boolAsStr = "false")
{
return false
}
else if(boolAsStr = "")
{
return false
}
else if(SubStr(boolAsStr,1,1) = "t") ; "=" is case in-sensitive ; "==" is case sensitive
{
return true
}
else if(SubStr(boolAsStr,1,1) = "f")
{
return false
}
else if(SubStr(boolAsStr,1,1) = "y")
{
return true
}
else if(SubStr(boolAsStr,1,1) = "n")
{
return false
}
else if boolAsStr is number
{
if (boolAsStr != 0)
return true
else
return false
}
; else
if (defaultVal) ; very harsh error checking for defaultVal
{
return true
}
else
{
return false
}
}
jsonToObject(ByRef src, args*) ; Jxon_Load()
{ ; copied from jxon.ahk (https://github.com/cocobelgica/AutoHotkey-JSON/tree/master) in order to not have another file to include
; Deserialize src (a JSON formatted string) to an AutoHotkey object
; Syntax:
; obj := Jxon_Load( ByRef src [ , object_base := "", array_base := "" ] )
; Parameter(s):
; src [in, ByRef] - JSON formatted string or path to the file containing JSON formatted string.
; object_base [in, opt] - an object to use as prototype for objects( {} ) created during parsing.
; array_base [in, opt] - an object to use as prototype for arrays( [] ) created during parsing.
static q := Chr(34)
key := "", is_key := false
stack := [ tree := [] ]
is_arr := { (tree): 1 }
next := q . "{[01234567890-tfn"
pos := 0
while ( (ch := SubStr(src, ++pos, 1)) != "" )
{
if InStr(" `t`n`r", ch)
continue
if !InStr(next, ch, true)
{
ln := ObjLength(StrSplit(SubStr(src, 1, pos), "`n"))
col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1))
msg := Format("{}: line {} col {} (char {})"
, (next == "") ? ["Extra data", ch := SubStr(src, pos)][1]
: (next == "'") ? "Unterminated string starting at"
: (next == "\") ? "Invalid \escape"
: (next == ":") ? "Expecting ':' delimiter"
: (next == q) ? "Expecting object key enclosed in double quotes"
: (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'"
: (next == ",}") ? "Expecting ',' delimiter or object closing '}'"
: (next == ",]") ? "Expecting ',' delimiter or array closing ']'"
: [ "Expecting JSON value(string, number, [true, false, null], object or array)"
, ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1]
, ln, col, pos)
throw Exception(msg, -1, ch)
}
is_array := is_arr[obj := stack[1]]
if i := InStr("{[", ch)
{
val := (proto := args[i]) ? new proto : {}
is_array? ObjPush(obj, val) : obj[key] := val
ObjInsertAt(stack, 1, val)
is_arr[val] := !(is_key := ch == "{")
next := q . (is_key ? "}" : "{[]0123456789-tfn")
}
else if InStr("}]", ch)
{
ObjRemoveAt(stack, 1)
next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}"
}
else if InStr(",:", ch)
{
is_key := (!is_array && ch == ",")
next := is_key ? q : q . "{[0123456789-tfn"
}
else ; string | number | true | false | null
{
if (ch == q) ; string
{
i := pos
while i := InStr(src, q,, i+1)
{
val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C")
static end := A_AhkVersion<"2" ? 0 : -1
if (SubStr(val, end) != "\")
break
}
if !i ? (pos--, next := "'") : 0
continue
pos := i ; update pos
val := StrReplace(val, "\/", "/")
, val := StrReplace(val, "\" . q, q)
, val := StrReplace(val, "\b", "`b")
, val := StrReplace(val, "\f", "`f")
, val := StrReplace(val, "\n", "`n")
, val := StrReplace(val, "\r", "`r")
, val := StrReplace(val, "\t", "`t")
i := 0
while i := InStr(val, "\",, i+1)
{
if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0
continue 2
; \uXXXX - JSON unicode escape sequence
xxxx := Abs("0x" . SubStr(val, i+2, 4))
if (A_IsUnicode || xxxx < 0x100)
val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6)
}
if is_key
{
key := val, next := ":"
continue
}
}
else ; number | true | false | null
{
val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos)
; For numerical values, numerify integers and keep floats as is.
; I'm not yet sure if I should numerify floats in v2.0-a ...
static number := "number", integer := "integer"
if val is %number%
{
if val is %integer%
val += 0
}
; in v1.1, true,false,A_PtrSize,A_IsUnicode,A_Index,A_EventInfo,
; SOMETIMES return strings due to certain optimizations. Since it
; is just 'SOMETIMES', numerify to be consistent w/ v2.0-a
else if (val == "true" || val == "false")
val := %value% + 0
; AHK_H has built-in null, can't do 'val := %value%' where value == "null"
; as it would raise an exception in AHK_H(overriding built-in var)
else if (val == "null")
val := ""
; any other values are invalid, continue to trigger error
else if (pos--, next := "#")
continue
pos += i-1
}
is_array? ObjPush(obj, val) : obj[key] := val
next := obj==tree ? "" : is_array ? ",]" : ",}"
}
}
return tree[1]
}
objectToJson(obj, indent:="", lvl:=1) ; Jxon_Dump(obj, indent:="", lvl:=1)
{ ; copied from jxon.ahk (https://github.com/cocobelgica/AutoHotkey-JSON/tree/master) in order to not have another file to include
; Serialize obj to a JSON formatted string
; Syntax:
; str := Jxon_Dump( obj [ , indent := "" ] )
; Return Value:
; A JSON formatted string.
; Parameter(s):
; obj [in] - this argument has the same meaning as in JSON.Dump()
; indent [in, opt] - this argument has the same meaning as in JSON.Dump()
static q := Chr(34)
if IsObject(obj)
{
static Type := Func("Type")
if Type ? (Type.Call(obj) != "Object") : (ObjGetCapacity(obj) == "")
throw Exception("Object type not supported.", -1, Format("<Object at 0x{:p}>", &obj))
is_array := 0
for k in obj
is_array := k == A_Index
until !is_array
static integer := "integer"
if indent is %integer%
{
if (indent < 0)
throw Exception("Indent parameter must be a postive integer.", -1, indent)
spaces := indent, indent := ""
Loop % spaces
indent .= " "
}
indt := ""
Loop, % indent ? lvl : 0
indt .= indent
lvl += 1, out := "" ; Make #Warn happy
for k, v in obj
{
if IsObject(k) || (k == "")
throw Exception("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", &obj) : "<blank>")
if !is_array
out .= ( ObjGetCapacity([k], 1) ? this.objectToJson(k) : q . k . q ) ;// key
. ( indent ? ": " : ":" ) ; token + padding
out .= this.objectToJson(v, indent, lvl) ; value
. ( indent ? ",`n" . indt : "," ) ; token + indent
}
if (out != "")
{
out := Trim(out, ",`n" . indent)
if (indent != "")
out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1)
}
return is_array ? "[" . out . "]" : "{" . out . "}"
}
; Number
else if (ObjGetCapacity([obj], 1) == "")
return obj
; String (null -> not supported by AHK)
if (obj != "")
{
obj := StrReplace(obj, "\", "\\")
, obj := StrReplace(obj, "/", "\/")
, obj := StrReplace(obj, q, "\" . q)
, obj := StrReplace(obj, "`b", "\b")
, obj := StrReplace(obj, "`f", "\f")
, obj := StrReplace(obj, "`n", "\n")
, obj := StrReplace(obj, "`r", "\r")
, obj := StrReplace(obj, "`t", "\t")
static needle := (A_AhkVersion<"2" ? "O)" : "") . "[^\x20-\x7e]"
while RegExMatch(obj, needle, m)
obj := StrReplace(obj, m[0], Format("\u{:04X}", Ord(m[0])))
}
return q . obj . q
}
}
class SettingsProperty
{
__New(propertyName, iniName="", defaultValue="", isWriteOnClose=false, isArray=false, arraySplitChar="", isBoolean=false, isObject=false)
{
this.propertyName := propertyName
if(iniName="")
{
this.iniName := propertyName
}
else
{
this.iniName := iniName
}
this.defaultValue := defaultValue
this.isWriteOnClose := isWriteOnClose
this.isArray := isArray ; for backwards compatibility
this.arraySplitChar := arraySplitChar ; for backwards compatibility
this.isBoolean := isBoolean
this.isObject := isObject
}
}
;EndRegion
/*
;==============================================================================
;========== Version History ===================================================
;==============================================================================
;
; v1.0 2022-01-23
; * initial draft
;
; v1.1 2022-11-12
; * changed writeDefaultINIFile(..., aSection="") and _writeINIFile(..., aSection="") so that you can write to any section (e.g., "DEFAULT_SETTINGS") and not just the Settings.SectionName value.
;
; v1.2 2023-11-18
; * updated getSupportFilename("ico"), which sets default sub-directories for filetypes, and getSupportFilename_old("ico"), which is the old version
;
; v1.3 2024-01-08
; * added ability to store objects (via jxon.ahk functions), not tested
;
;==============================================================================
*/
getSupportFilename.ahk
Code: Select all
getSupportFilename(fileExten, addDot=true)
{ ; provides a filename of a support file (e.g., ini, ico), regradless of whether the script is compiled or not
; Input variables
; fileExten -> the file extension of the support file (place in quotes)
; addDot -> should a '.' be added before fileExten? (Default=true)
; Output variables
; a filename of the script with the extension replaced with fileExten
return getSupportFilename2(fileExten, addDot, "", False, "", False)
}
getSupportFilename2(fileExten, addDot=true, parentFolder="", useDefaultFolder=True, filenameSuffix="", useFullPath=true)
{ ; provides a filename of a support file (e.g., ini, ico), regradless of whether the script is compiled or not
; Input variables
; fileExten -> the file extension of the support file (place in quotes)
; addDot -> should a '.' be added before fileExten? Allows you to use "log" instead of ".log" (Default=true)
; parentFolder -> directory where the file should be located (Default="")
; useDefaultFolder -> if parentFolder="" will place the file in a default folder based on fileExten (Default={"ini": "INI_Files", "ico": "Script_Images\Icons", "log": "Logs"})
; filenameSuffix -> a string added between the filenameNoExt and the Exten (e.g., "_" . A_ComputerName) (Default="")
; useFullPath -> return a FullPath or not. If parentFolder isa fullpath this will be ignored. (Default=true)
; Output variables
; a filename of the script with the extension replaced with fileExten
defaultArray := {"ini": "INI_Files", "ico": "Script_Images\Icons", "log": "Logs"}
if(fileExten != "")
{
if(addDot)
{
replacementStr = .%fileExten%
}
else
{
replacementStr = %fileExten%
}
}
else
{
replacementStr := ""
}
if(filenameSuffix != "")
{
replacementStr := filenameSuffix . replacementStr
}
if(A_IsCompiled)
{
StringReplace, fileName, A_ScriptName,.exe,%replacementStr%, All
}
else
{
StringReplace, fileName, A_ScriptName,.ahk,%replacementStr%, All
}
if(parentFolder == "")
{
if(useDefaultFolder)
{
1stChar := SubStr(fileExten, 1, 1)
if (1stChar = ".")
{
fileExten1 := SubStr(fileExten, 2)
; MsgBox, "dot: " . %fileExten1%
}
Else
{
fileExten1 := fileExten
; MsgBox, "nul: " . %fileExten1%
}
For key, value in defaultArray
{
if( InStr(key, fileExten1) )
{
parentFolder := value
}
}
}
}
if(parentFolder != "")
{
returnName := parentFolder . "\" . fileName
}
else
{
returnName := fileName
}
if(useFullPath)
{
; see https://www.autohotkey.com/boards/viewtopic.php?p=289536#p289536
path := returnName
cc := DllCall("GetFullPathName", "str", path, "uint", 0, "ptr", 0, "ptr", 0, "uint")
VarSetCapacity(buf, cc*(A_IsUnicode?2:1))
DllCall("GetFullPathName", "str", path, "uint", cc, "str", buf, "ptr", 0, "uint")
returnName := buf
; SplitPath, returnName, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
; if(OutDrive = "") ; is it already a FullPath?
; {
; SplitPath, A_ScriptFullPath, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
; returnName := OutDir . "\" . returnName ; OutDir includes OutDrive
; }
}
return returnName
}
getTempFilename(shortFilename)
{ ; creates a full path filename based on the Windows Temp directory
; Input variables
; shortFilename -> the short filename of the temp file you wish to use (e.g., demo.htm)
; Output variables
; fullFileName -> the full path filename of the temp file you wish to use (e.g., C:\Temp\demo.htm)
SplitPath, shortFilename, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
if(OutDrive != "") ; is it already a FullPath?
{
fullFilename := OutFileName
}
fullFilename = %A_Temp%\%fullFilename%
return fullFilename
}
;==============================================================================
;========== Version History ===================================================
;==============================================================================
;BeginRegion
/*
; 1.0 2023-11-18
; * moved getSupportFilename(...), getSupportFilename2(...), and getTempFilename(filename) from Libray.ahk
;
;==============================================================================
*/
;EndRegion