diff --git a/Docs/releasenotes.md b/Docs/releasenotes.md index 0199bb5..9245227 100644 --- a/Docs/releasenotes.md +++ b/Docs/releasenotes.md @@ -1,5 +1,9 @@ # Release Notes +## v1.6.5 - 2020-01-15 + +- Support History of Saved Window Positions + ## v1.6.4 - 2020-01-08 - Added command line build command for setup exe diff --git a/Docs/todo.md b/Docs/todo.md index 94924b3..dc698dd 100644 --- a/Docs/todo.md +++ b/Docs/todo.md @@ -1,6 +1,5 @@ # To Do -- Support History of Saved Window Positions - Support History of Saved Desktop Icon Positions - Add icons to other context menu items diff --git a/Lib/ArrayUtils.ahk b/Lib/ArrayUtils.ahk index 80151db..dce171e 100644 --- a/Lib/ArrayUtils.ahk +++ b/Lib/ArrayUtils.ahk @@ -1,6 +1,6 @@ ;-------------------------------------------------------------------------------- -; AutoSort - Sort array (from: https://autohotkey.com/boards/viewtopic.php?f=6&t=3790&p=20122) -AutoSort(Arr) +; SortArray - Sort array (from: https://autohotkey.com/boards/viewtopic.php?f=6&t=3790&p=20122) +SortArray(Arr) { t:=Object() @@ -13,6 +13,16 @@ AutoSort(Arr) return Arr } +ReverseArray(arr) +{ + newArr := [] + + for k, v in arr + newArr.Insert(1, v) + + return newArr +} + ;-------------------------------------------------------------------------------- ; IndexOf - Find the index of an array item IndexOf(array, item) diff --git a/Lib/HashingFunctions.ahk b/Lib/HashingFunctions.ahk new file mode 100644 index 0000000..0d55102 --- /dev/null +++ b/Lib/HashingFunctions.ahk @@ -0,0 +1,64 @@ +; From : https://github.com/jNizM/AHK_Scripts/tree/master/src/hash_checksum + +; =============================================================================================================================== +; Adler-32 is a checksum algorithm +; =============================================================================================================================== + +Adler32(str) +{ + a := 1, b := 0 + loop, parse, str + b := Mod(b + (a := Mod(a + Asc(A_LoopField), 0xFFF1)), 0xFFF1) + return Format("{:#x}", (b << 16) | a) +} + +; =============================================================================================================================== +; In cryptography, SHA-1 (Secure Hash Algorithm 1) is a cryptographic hash function +; =============================================================================================================================== + +SHA1(string, case := 0) +{ + static SHA_DIGEST_LENGTH := 20 + hModule := DllCall("LoadLibrary", "Str", "advapi32.dll", "Ptr") + , VarSetCapacity(SHA_CTX, 136, 0), DllCall("advapi32\A_SHAInit", "Ptr", &SHA_CTX) + , DllCall("advapi32\A_SHAUpdate", "Ptr", &SHA_CTX, "AStr", string, "UInt", StrLen(string)) + , DllCall("advapi32\A_SHAFinal", "Ptr", &SHA_CTX, "UInt", &SHA_CTX + 116) + loop % SHA_DIGEST_LENGTH + o .= Format("{:02" (case ? "X" : "x") "}", NumGet(SHA_CTX, 115 + A_Index, "UChar")) + return o, DllCall("FreeLibrary", "Ptr", hModule) +} + +; =============================================================================================================================== +; The MD5 algorithm is a widely used hash function producing a 128-bit hash value +; =============================================================================================================================== + +MD5(string, case := 0) +{ + static MD5_DIGEST_LENGTH := 16 + hModule := DllCall("LoadLibrary", "Str", "advapi32.dll", "Ptr") + , VarSetCapacity(MD5_CTX, 104, 0), DllCall("advapi32\MD5Init", "Ptr", &MD5_CTX) + , DllCall("advapi32\MD5Update", "Ptr", &MD5_CTX, "AStr", string, "UInt", StrLen(string)) + , DllCall("advapi32\MD5Final", "Ptr", &MD5_CTX) + loop % MD5_DIGEST_LENGTH + o .= Format("{:02" (case ? "X" : "x") "}", NumGet(MD5_CTX, 87 + A_Index, "UChar")) + return o, DllCall("FreeLibrary", "Ptr", hModule) +} + +; =============================================================================================================================== +; CRC32 Implementation in AutoHotkey +; =============================================================================================================================== + +CRC32(str) +{ + static table := [] + loop 256 { + crc := A_Index - 1 + loop 8 + crc := (crc & 1) ? (crc >> 1) ^ 0xEDB88320 : (crc >> 1) + table[A_Index - 1] := crc + } + crc := ~0 + loop, parse, str + crc := table[(crc & 0xFF) ^ Asc(A_LoopField)] ^ (crc >> 8) + return Format("{:#x}", ~crc) +} diff --git a/Lib/IOClasses.ahk b/Lib/IOClasses.ahk new file mode 100644 index 0000000..3d62f58 --- /dev/null +++ b/Lib/IOClasses.ahk @@ -0,0 +1,154 @@ +#include Lib\IOUtils.ahk + +;-------------------------------------------------------------------------------- +; FileNameParser - Class to parse a file into its parts +class FileNameParser +{ +_FullFileName := + + __New(fullFileName) + { + this.FullFileName := fullFileName + } + + FullFileName + { + get + { + return this._FullFileName + } + set + { + this._FullFileName := value + } + } + + Drive + { + get + { + fileName := this.FullFileName + SplitPath, fileName,,,,, drive + + return drive + } + } + + Directory + { + get + { + fileName := this.FullFileName + SplitPath, fileName,, directory + + return directory + } + } + + FileName + { + get + { + fileName := this.FullFileName + SplitPath, fileName, fileName + + return fileName + } + } + + Extension + { + get + { + fileName := this.FullFileName + SplitPath, fileName,,, extension + + return extension + } + } + + FileNameNoExt + { + get + { + fileName := this.FullFileName + SplitPath, fileName,,,, fileNameNoExt + + return fileNameNoExt + } + } +} + +;-------------------------------------------------------------------------------- +; DataFile - Class to represent a data file with a well formed name +class DataFile extends FileNameParser +{ + __New(fullFileName) + { + base.__New(fullFileName) + } + + Content + { + get + { + return FileReadContent(this.FullFileName) + } + } + + ContentLines + { + get + { + return FileReadContentLines(this.FullFileName) + } + } + + LineCount + { + get + { + return this.ContentLines.length() + } + } + + CRC32 + { + get + { + hash := CRC32(this.Content) + + return hash + } + } + + Adler32 + { + get + { + hash := Adler32(this.Content) + + return hash + } + } + + SHA1 + { + get + { + hash := SHA1(this.Content) + + return hash + } + } + + MD5 + { + get + { + hash := MD5(this.Content) + + return hash + } + } +} diff --git a/Lib/IOUtils.ahk b/Lib/IOUtils.ahk index 8e4d16c..8e03f8e 100644 --- a/Lib/IOUtils.ahk +++ b/Lib/IOUtils.ahk @@ -1,4 +1,5 @@ #include Lib\StringUtils.ahk +#include Lib\HashingFunctions.ahk ;-------------------------------------------------------------------------------- ; CombinePaths - Append path2 to path1, ensuring the correct delimiters are in place @@ -9,11 +10,11 @@ CombinePaths(path1, path2) ;-------------------------------------------------------------------------------- ; CombinePaths - Append multiple paths together, ensuring the correct delimiters are in place -CombinePathA(paths*) +CombineAllPaths(paths*) { newPath := - Loop, %path% + For index, path in paths { if (newPath = "") { @@ -28,6 +29,19 @@ CombinePathA(paths*) return newPath } +;-------------------------------------------------------------------------------- +; SetFileExtension - Set the extension on a filename +SetFileExtension(fileName, extension) +{ + pos := InStr(fileName, ".", false, -1) + if (pos) + fileName := SubStr(fileName, 1, pos) + + fileName := EnsureEndsWith(fileName, ".") . extension + + return fileName +} + ;-------------------------------------------------------------------------------- ; FileExists - Determines if a file exists FileExists(fileName) @@ -49,3 +63,26 @@ FolderExists(folderName) return exists } + +;-------------------------------------------------------------------------------- +; FileReadContent - Read the entire contents of a file into a variable +FileReadContent(fileName) +{ + FileRead, content, %fileName% + + return content +} + +;-------------------------------------------------------------------------------- +; FileReadContentLines - Read every line of a file into an array +FileReadContentLines(fileName) +{ + lines := [] + + Loop, Read, %fileName% + { + lines.push(A_LoopReadLine) + } + + return lines +} diff --git a/Lib/ListViewSelector.ahk b/Lib/ListViewSelector.ahk new file mode 100644 index 0000000..d13ae28 --- /dev/null +++ b/Lib/ListViewSelector.ahk @@ -0,0 +1,132 @@ +#Include Lib\ArrayUtils.ahk +#Include Lib\StringUtils.ahk +#Include Lib\MathUtils.ahk + +G_CurrentListViewSelector := "" + +class ListViewSelector +{ +Title := "Select..." +ColumnNames := [] +ColumnOptions := [] +Items := [] +SelectedIndex := 0 +MinRowCountSize := 5 +MaxRowCountSize := 20 +ListViewWidth := 400 +OnSuccess := "" + + __New() + { + this.Title := "Select..." + this.ColumnNames := [] + this.ColumnOptions := [] + this.Items := [] + this.SelectedIndex := 0 + this.MinRowCountSize := 5 + this.MaxRowCountSize := 20 + this.ListViewWidth := 400 + this.OnSuccess := "" + } + + ShowDialog() + { + global G_CurrentListViewSelector + + G_CurrentListViewSelector := this + + this.BuildGui() + + if (this.SelectedIndex) + { + LV_Modify(this.SelectedIndex, "Select") + } + + this.PopulateItems() + + this.SetupColumns() + + Gui, LvwSelector:Show + } + + BuildGui() + { + title := this.Title + columnNameText := JoinItems("|", this.ColumnNames) + rowCount := MinOf(MaxOf(this.Items.length(), this.MinRowCountSize), this.MaxRowCountSize) + + buttonWidth := 80 + buttonLeft := this.ListViewWidth - (buttonWidth * 2) + + listViewWidth := this.ListViewWidth + + Gui, LvwSelector:New, -SysMenu, %title% + Gui, LvwSelector:Add, ListView, r%rowCount% w%listViewWidth% gLvwSelectorListView, %columnNameText% + + Gui, LvwSelector:Add, Button, Section default x%buttonLeft% w80 gLvwSelectorGuiButtonOK, OK + Gui, LvwSelector:Add, Button, ys x+10 w80, Cancel + } + + PopulateItems() + { + LV_Delete() + + for index, item in this.Items + { + LV_Add("", item*) + } + } + + SetupColumns() + { + for index, item in this.ColumnOptions + { + LV_ModifyCol(index, item) + } + } +} + +;-------------------------------------------------------------------------------- +; DestroyConfigGui - Destroy the Config Gui +DestroyLvwSelectorGui() +{ + Gui, LvwSelector:Destroy +} + +LvwSelectorListView() +{ + if (A_GuiEvent = "DoubleClick") + { + LV_Modify(A_EventInfo, "Select") + LvwSelectorGuiButtonOK() + } +} + +LvwSelectorButtonCancel() +{ + DestroyLvwSelectorGui() +} + +LvwSelectorGuiEscape() +{ + LvwSelectorButtonCancel() +} + +LvwSelectorGuiButtonOK() +{ + global G_CurrentListViewSelector + + G_CurrentListViewSelector.SelectedIndex := LV_GetNext() + if (!(G_CurrentListViewSelector.SelectedIndex)) + return + + Gui, LvwSelector:Submit + + DestroyLvwSelectorGui() + + funcName := G_CurrentListViewSelector.OnSuccess + if (funcName) + { + %funcName%(G_CurrentListViewSelector) + } +} diff --git a/Lib/StringUtils.ahk b/Lib/StringUtils.ahk index 01d110c..99c600a 100644 --- a/Lib/StringUtils.ahk +++ b/Lib/StringUtils.ahk @@ -6,18 +6,72 @@ StringRepeat(string, times) Return output } +EnsureStartsWith(text, prefix) +{ + prefixLength := StrLen(prefix) + + if (prefixLength > 0) + { + If (SubStr(text, 1, prefixLength) != prefix) + text := prefix . text + } + + return text +} + EnsureEndsWith(text, suffix) { - suffixLength := suffix.Length + suffixLength := StrLen(suffix) - currentSuffix := SubStr(text, -1 * suffixLength) + if (suffixLength > 0) + { + If (SubStr(text, 1 - suffixLength) != suffix) + text := text . suffix + } + + return text +} + +RemoveStartsWith(text, prefix) +{ + prefixLength := StrLen(prefix) - If (currentSuffix != suffix) - text := text . suffix + if (prefixLength > 0) + { + while(SubStr(text, 1, prefixLength) = prefix) + text := SubStr(text, prefixLength + 1) + } return text } +RemoveEndsWith(text, suffix) +{ + suffixLength := StrLen(suffix) + + if (suffixLength > 0) + { + while(SubStr(text, 1 - suffixLength) = suffix) + text := SubStr(text, 1, StrLen(text) - suffixLength) + } + + return text +} + +ToUpper(text) +{ + StringUpper, result, text + + return result +} + +ToLower(text) +{ + StringLower, result, text + + return result +} + JoinText(sep, params*) { for index, param in params @@ -33,3 +87,14 @@ JoinItems(sep, array) return SubStr(str, 1, -StrLen(sep)) } + +; Tests + +;xx := "This is made of glass" + +;yy := RemoveEndsWith(xx, "s") +;zz := EnsureEndsWith(xx, "s") +;ww := EnsureEndsWith(xx, "x") + +;yy := EnsureStartsWith(xx, ".") +;yy := RemoveStartsWith(xx, "T") diff --git a/Lib/UserDataUtils.ahk b/Lib/UserDataUtils.ahk index 85d30f1..090540f 100644 --- a/Lib/UserDataUtils.ahk +++ b/Lib/UserDataUtils.ahk @@ -1,4 +1,5 @@ #Include Lib\Logging.ahk +#Include Lib\ArrayUtils.ahk #Include Lib\StringUtils.ahk #Include Lib\IOUtils.ahk @@ -34,3 +35,31 @@ GetUserDataFileName(dataFileName) return fileName } + +;-------------------------------------------------------------------------------- +; GetUserDataFileNames : Get a list of appropriate file names for the specified User Data +GetUserDataFileNames(dataFilePattern, sortOrder := 0) +{ + global UserDataPath + + filePattern := CombinePaths(UserDataPath, dataFilePattern) + + files := [] + + Loop, Files, %filePattern% + { + files.push(A_LoopFileFullPath) + } + + if (sortOrder != 0) + { + files := SortArray(files) + + if (sortOrder < 0) + { + files := ReverseArray(files) + } + } + + return files +} diff --git a/Lib/WindowFunctions.ahk b/Lib/WindowFunctions.ahk index c393266..e733ab8 100644 --- a/Lib/WindowFunctions.ahk +++ b/Lib/WindowFunctions.ahk @@ -106,21 +106,21 @@ SetWindowByGutter(theWindow, gutterSize) ;-------------------------------------------------------------------------------- ; SetWindowByColumn - Set the Window position by column, including a gutter -SetWindowByColumn(theWindow, column, maxColumns, gutterSize = 0) +SetWindowByColumn(theWindow, column, maxColumns, gutterSize := 0) { SetWindowByGrid(theWindow, 1, column, 1, maxColumns, gutterSize) } ;-------------------------------------------------------------------------------- ; SetWindowByRow - Set the Window position by row, including a gutter -SetWindowByRow(theWindow, row, maxRows, gutterSize = 0) +SetWindowByRow(theWindow, row, maxRows, gutterSize := 0) { SetWindowByGrid(theWindow, row, 1, maxRows, 1, gutterSize) } ;-------------------------------------------------------------------------------- ; SetWindowByGutter - Set the Window position by column, including a gutter -SetWindowByGrid(theWindow, row, column, maxRows, maxColumns, gutterSize = 0) +SetWindowByGrid(theWindow, row, column, maxRows, maxColumns, gutterSize := 0) { LogText("Row: " . row . " / " . maxRows . ", Column: " . column . " / " . maxColumns . ", gutterSize: " . gutterSize) @@ -138,7 +138,7 @@ SetWindowByGrid(theWindow, row, column, maxRows, maxColumns, gutterSize = 0) ;-------------------------------------------------------------------------------- ; SetWindowByGutter - Set the Window position by column, including a gutter -SetWindowSpanMonitors(theWindow, alignLeft, alignTop, alignRight, alignBottom, gutterSize = 0) +SetWindowSpanMonitors(theWindow, alignLeft, alignTop, alignRight, alignBottom, gutterSize := 0) { windowSpan := new Rectangle2(alignLeft, alignTop, alignRight, alignBottom) @@ -335,7 +335,7 @@ SendWindowToBack(theWindow) ;-------------------------------------------------------------------------------- ; GetMonitorIndexAt - Get the index of the monitor containing the specified x and y co-ordinates. -GetMonitorIndexAt(x, y, defaultMonitor = -1) +GetMonitorIndexAt(x, y, defaultMonitor := -1) { coordinate := new Coordinate(x, y) ;LogText("GetMonitorIndexAt: " . coordinate.Description) @@ -376,3 +376,21 @@ FindCurrentWindowForProcessName(processName) return } + +;-------------------------------------------------------------------------------- +; HasWindowMoved - Check two window positions are equivalent +HasWindowMoved(window1, window2) +{ + if (window1.Left <> window2.Left) + return True + if (window1.Top <> window2.Top) + return True + if (window1.Width <> window2.Width) + return True + if (window1.Height <> window2.Height) + return True + if (window1.WindowStatus <> window2.WindowStatus) + return True + + return False +} diff --git a/Lib/WindowPositions.ahk b/Lib/WindowPositions.ahk index b4663f5..cd29f38 100644 --- a/Lib/WindowPositions.ahk +++ b/Lib/WindowPositions.ahk @@ -1,59 +1,228 @@ #Include Lib\Logging.ahk +#Include Lib\ArrayUtils.ahk #Include Lib\StringUtils.ahk -#Include Lib\IOUtils.ahk +#Include Lib\IOClasses.ahk #Include Lib\WindowObjects.ahk #Include Lib\WindowFunctions.ahk #Include Lib\MathUtils.ahk #Include Lib\IOUtils.ahk #Include Lib\UserDataUtils.ahk +#Include Lib\HashingFunctions.ahk #Include Lib\PleasantNotify.ahk ;-------------------------------------------------------------------------------- ; Globals WindowPositionsBaseFileName := "" +WindowPositionsFileExt := "dat" ;-------------------------------------------------------------------------------- ; Initialisation WindowPositions_OnInit() { global WindowPositionsBaseFileName + global WindowPositionsFileExt WindowPositionsBaseFileName := "WindowPositions" + WindowPositionsFileExt := "dat" + + allFiles := GetWindowPositionsDataFiles("") + for index, item in allFiles + { + if (!item.FileTimestamp) + { + sourceFileName := item.FullFileName + destFileName := item.BuildFileName() + + try FileMove, %sourceFileName%, %destFileName%, 0 + } + } } ;-------------------------------------------------------------------------------- -; HasSavedWindowPositionFile - Is there a file of saved Window Positions -HasSavedWindowPositionFile(desktopSize) +; WindowPositionsDataFile - +class WindowPositionsDataFile extends DataFile { - fileName := GetWindowPositionsDataFileName(desktopSize) + __New(fullFileName) + { + base.__New(fullFileName) + } + + BaseFileName + { + get + { + return StrSplit(this.FileNameNoExt, "-")[1] + } + } + + DesktopSizeDescription + { + get + { + return StrSplit(this.FileNameNoExt, "-")[2] + } + } + + DesktopSizeWidth + { + get + { + return StrSplit(this.DesktopSizeDescription, "x")[1] + } + } + + DesktopSizeHeight + { + get + { + return StrSplit(this.DesktopSizeDescription, "x")[2] + } + } - return FileExists(fileName) + FileTimestamp + { + get + { + return StrSplit(this.FileNameNoExt, "-")[3] + } + } + + FileCreatedTimestamp + { + get + { + fileName := this.FullFileName + + FileGetTime, timestamp, %fileName%, C + + return timestamp + } + } + + Timestamp + { + get + { + return this.FileTimestamp ? this.FileTimestamp : this.FileCreatedTimestamp + } + } + + BuildFileName() + { + fileNameOnly := JoinText("-", this.BaseFileName, this.DesktopSizeDescription, this.Timestamp) + + fileName := CombinePaths(this.Directory, SetFileExtension(fileNameOnly, this.Extension)) + + return fileName + } + + IsValid() + { + global WindowPositionsBaseFileName + global WindowPositionsFileExt + + isValid := (this.BaseFileName && (this.BaseFileName = WindowPositionsBaseFileName)) && (this.DesktopSizeDescription) && (this.Timestamp) && (this.Extension && (this.Extension = WindowPositionsFileExt)) + + return isValid + } } ;-------------------------------------------------------------------------------- -; GetWindowPositionsDataFileName - Get the appropriate saved Window Positions filename -GetWindowPositionsDataFileName(desktopSize) +; BuildWindowPositionsDataFileName - Build the filename for a WindowPositions data file or pattern +BuildWindowPositionsDataFileName(desktopSize, isNew := false, isPattern := false) { global WindowPositionsBaseFileName + global WindowPositionsFileExt dimensions := desktopSize.DimensionsText - fileName := WindowPositionsBaseFileName + fileNamePattern := WindowPositionsBaseFileName if (dimensions != "") { - fileName = %fileName%-%dimensions% + fileNamePattern .= "-" . dimensions + } + + if (isPattern) + { + fileNamePattern .= "*" + } + else if (isNew) + { + dateTime := A_Now + + FormatTime, fileDateTime, dateTime, yyyyMMddHHmmss + fileNamePattern .= "-" . fileDateTime + } + + fileNamePattern .= "." . WindowPositionsFileExt + + return fileNamePattern +} + +;-------------------------------------------------------------------------------- +; GetWindowPositionsDataFileNames - Get all the WindowPositions data files for the desktop size +GetWindowPositionsDataFileNames(desktopSize) +{ + global WindowPositionsBaseFileName + global WindowPositionsFileExt + + pattern := BuildWindowPositionsDataFileName(desktopSize, false, true) + + files := GetUserDataFileNames(pattern, -1) + + return files +} + +;-------------------------------------------------------------------------------- +; GetWindowPositionsDataFiles - Get all the WindowPositions instances for the desktop size +GetWindowPositionsDataFiles(desktopSize) +{ + files := GetWindowPositionsDataFileNames(desktopSize) + + instances := [] + for index, item in files + { + instance := new WindowPositionsDataFile(item) + instances.push(instance) } - fileName = %fileName%.dat + return instances +} - dataFileName := GetUserDataFileName(fileName) +;-------------------------------------------------------------------------------- +; HasSavedWindowPositionFile - Is there a file of saved Window Positions +HasSavedWindowPositionFile(desktopSize) +{ + fileNames := GetWindowPositionsDataFileNames(desktopSize) + + return (fileNames.length() > 0) +} + +;-------------------------------------------------------------------------------- +; HasMultipleSavedWindowPositionFiles - Is there more than 1 file of saved Window Positions +HasMultipleSavedWindowPositionFiles(desktopSize) +{ + fileNames := GetWindowPositionsDataFileNames(desktopSize) + + return (fileNames.length() > 1) +} + +;-------------------------------------------------------------------------------- +; GetLatestWindowPositionsDataFileName - Get the appropriate saved Window Positions filename +GetLatestWindowPositionsDataFileName(desktopSize) +{ + files := GetWindowPositionsDataFileNames(desktopSize) + + dataFileName := files.length() > 0 + ? files[1] + : "" return dataFileName } ;-------------------------------------------------------------------------------- ; BuildWindowFromDefinition - Build a Window Definition from a text string -BuildWindowFromDefinition(text, separator = "|") +BuildWindowFromDefinition(text, separator := "|") { parts := StrSplit(text, separator) @@ -72,7 +241,7 @@ BuildWindowFromDefinition(text, separator = "|") ;-------------------------------------------------------------------------------- ; GetWindowDefinition - Create a text representation of a Window Definition -GetWindowDefinition(window, separator = "|") +GetWindowDefinition(window, separator := "|") { parts := [] parts.Push(window.WindowHandle) @@ -90,37 +259,13 @@ GetWindowDefinition(window, separator = "|") return definition } -;-------------------------------------------------------------------------------- -; HasWindowMoved - Check two window positions are equivalent -HasWindowMoved(window1, window2) -{ - if (window1.Left <> window2.Left) - return True - if (window1.Top <> window2.Top) - return True - if (window1.Width <> window2.Width) - return True - if (window1.Height <> window2.Height) - return True - if (window1.WindowStatus <> window2.WindowStatus) - return True - - return False -} - ;-------------------------------------------------------------------------------- ; SaveWindowPositions - Save all the current window positions to a file SaveWindowPositions(includeOffScreenWindows, notify) { desktopSize := GetDesktopSize() - fileName := GetWindowPositionsDataFileName(desktopSize) - - If (FileExists(fileName)) - { - LogText("Removing old Data File: " . fileName) - FileDelete , %fileName% - } + fileName := GetUserDataFileName(BuildWindowPositionsDataFileName(desktopSize, true)) saveCount := 0 WinGet windows, List @@ -188,15 +333,12 @@ SaveWindowPositions(includeOffScreenWindows, notify) ;-------------------------------------------------------------------------------- ; RestoreWindowPositions - Restores window positions from a file -RestoreWindowPositions(includeOffScreenWindows) +RestoreWindowPositions(fileName, includeOffScreenWindows) { - desktopSize := GetDesktopSize() - - fileName := GetWindowPositionsDataFileName(desktopSize) - + LogText("RestoreWindowPositions: Using fileName: " . fileName) If (!FileExists(fileName)) { - MsgBox , 48, Restore Window Positions, Unable to locate file %fileName% + MsgBox, 48, Restore Window Positions, Unable to locate file %fileName% return } @@ -263,3 +405,50 @@ RestoreWindowPositions(includeOffScreenWindows) new PleasantNotify("Window Positions", JoinItems("`r`n", textParts), 350, 125, "b r") } + +;-------------------------------------------------------------------------------- +; RestoreWindowPositionsFromFile - Show the dialog to select the file to restore window positions from +RestoreWindowPositionsFromFile(desktopSize) +{ + global G_SelectableWindowPositions + + columns := [ "Desktop Resolution", "Date Created", "Hash", "Window Count" ] + columnOptions := [ "AutoHdr NoSort", "Auto NoSort", "Auto NoSort Right", "AutoHdr Integer NoSort" ] + + G_SelectableWindowPositions := GetWindowPositionsDataFiles(desktopSize) + + items := [] + for index, item in G_SelectableWindowPositions + { + itemTimestamp := item.Timestamp + FormatTime, timestamp, %itemTimestamp%, yyyy-MM-dd HH:mm.ss + row := [ item.DesktopSizeDescription, timestamp, item.Adler32, item.LineCount ] + items.push(row) + } + + selector := new ListViewSelector() + selector.Title := "Restore Window Positions..." + selector.ColumnNames := columns + selector.ColumnOptions := columnOptions + selector.Items := items + selector.ListViewWidth := 400 + selector.MinRowCountSize := 6 + selector.SelectedIndex := 1 + selector.OnSuccess := "OnWindowPositionSelected" + + selector.ShowDialog() +} + +;-------------------------------------------------------------------------------- +; OnWindowPositionSelected - Restore selected Window Positions +OnWindowPositionSelected(listViewSelector) +{ + global G_SelectableWindowPositions + + item := G_SelectableWindowPositions[listViewSelector.SelectedIndex] + + if (!item) + return + + RestoreWindowPositions(item.FullFileName, G_UserConfig.WindowPositions_IncludeOffScreenWindows) +} diff --git a/Setup/WindowExtensions_Setup.iss b/Setup/WindowExtensions_Setup.iss index cfe0f5d..9ef36d1 100644 --- a/Setup/WindowExtensions_Setup.iss +++ b/Setup/WindowExtensions_Setup.iss @@ -1,6 +1,6 @@ #define AppName "WindowExtensions" #define AppTitle "Window Extensions" -#define AppVersion "1.6.4" +#define AppVersion "1.6.5" [Setup] AppName={#AppName} diff --git a/Spikes/HashSHA1.ahk b/Spikes/HashSHA1.ahk new file mode 100644 index 0000000..d5f33d3 --- /dev/null +++ b/Spikes/HashSHA1.ahk @@ -0,0 +1,21 @@ +; =============================================================================================================================== +; In cryptography, SHA-1 (Secure Hash Algorithm 1) is a cryptographic hash function +; =============================================================================================================================== + +SHA(string, case := 0) +{ + static SHA_DIGEST_LENGTH := 20 + hModule := DllCall("LoadLibrary", "Str", "advapi32.dll", "Ptr") + , VarSetCapacity(SHA_CTX, 136, 0), DllCall("advapi32\A_SHAInit", "Ptr", &SHA_CTX) + , DllCall("advapi32\A_SHAUpdate", "Ptr", &SHA_CTX, "AStr", string, "UInt", StrLen(string)) + , DllCall("advapi32\A_SHAFinal", "Ptr", &SHA_CTX, "UInt", &SHA_CTX + 116) + loop % SHA_DIGEST_LENGTH + o .= Format("{:02" (case ? "X" : "x") "}", NumGet(SHA_CTX, 115 + A_Index, "UChar")) + return o, DllCall("FreeLibrary", "Ptr", hModule) +} + +; =============================================================================================================================== + +MsgBox % SHA("Hello World") "`n" + . SHA("Hello World", 1) + \ No newline at end of file diff --git a/Spikes/ListViewSpikes.ahk b/Spikes/ListViewSpikes.ahk new file mode 100644 index 0000000..be3d926 --- /dev/null +++ b/Spikes/ListViewSpikes.ahk @@ -0,0 +1,27 @@ +; Create the ListView with two columns, Name and Size: +Gui, Add, ListView, r20 w700 gMyListView, Name|Size (KB) + +; Gather a list of file names from a folder and put them into the ListView: +Loop, %A_MyDocuments%\*.* +{ + crap := [ A_LoopFileName, A_LoopFileSizeKB ] + LV_Add("", crap*) +} + +LV_ModifyCol() ; Auto-size each column to fit its contents. +LV_ModifyCol(2, "Integer") ; For sorting purposes, indicate that column 2 is an integer. + +; Display the window and return. The script will be notified whenever the user double clicks a row. +Gui, Show +return + +MyListView: +if (A_GuiEvent = "DoubleClick") +{ + LV_GetText(RowText, A_EventInfo) ; Get the text from the row's first field. + ToolTip You double-clicked row number %A_EventInfo%. Text: "%RowText%" +} +return + +GuiClose: ; Indicate that the script should exit automatically when the window is closed. +ExitApp diff --git a/TrayMenu.ahk b/TrayMenu.ahk index 6eb7d1d..59e07b0 100644 --- a/TrayMenu.ahk +++ b/TrayMenu.ahk @@ -55,6 +55,13 @@ BuildTrayMenu() restoreEnabled := HasSavedWindowPositionFile(desktopSize) EnableMenuItem(TrayMenuName, restoreTitle, restoreEnabled) + + if (HasMultipleSavedWindowPositionFiles(desktopSize)) + { + restoreTitle := "Restore Window &Positions (" . desktopSize.DimensionsText . ")..." + + menuIndex := AddMenuItemWithIcon(TrayMenuName, restoreTitle, "RestoreMultipleWindowPositionsHandler", IconLibraryFileName, GetIconLibraryIndex("POSITION_RESTORE"), menuIndex) + } } ; Desktop Icons diff --git a/WindowExtensions.ahk b/WindowExtensions.ahk index d838169..c624883 100644 --- a/WindowExtensions.ahk +++ b/WindowExtensions.ahk @@ -15,7 +15,7 @@ AppDescription := "Window Extensions Menu and HotKeys" AppCopyright := "Copyright © 2020 Martin Smith" AppNotes := "Concise and consistent control over Window Positions. Right-click right half of Window Caption bar to invoke, or hit WinKey-W" AppURL := "https://github.com/martinsmith1968/WindowExtensions" -AppVersion := "1.6.4.0" +AppVersion := "1.6.5.0" ;-------------------------------------------------------------------------------- ; Includes diff --git a/WindowExtensionsUserConfig.ahk b/WindowExtensionsUserConfig.ahk index de1fd82..a8a4483 100644 --- a/WindowExtensionsUserConfig.ahk +++ b/WindowExtensionsUserConfig.ahk @@ -63,13 +63,20 @@ WindowExtensionsUserConfig_OnStartup() { global G_UserConfig + desktopSize := GetDesktopSize() + if (G_UserConfig.Startup_RestoreDesktopIcons) { RestoreDesktopIcons() } if (G_UserConfig.Startup_RestoreWindowPositions) { - RestoreWindowPositions(G_UserConfig.WindowPositions_IncludeOffScreenWindows) + if (HasSavedWindowPositionFile(desktopSize)) + { + fileName := GetLatestWindowPositionsDataFileName(desktopSize) + + RestoreWindowPositions(fileName, G_UserConfig.WindowPositions_IncludeOffScreenWindows) + } } WindowExtensionsUserConfig_OnConfigUpdated() diff --git a/WindowMenu.ahk b/WindowMenu.ahk index bde02b2..6e80e16 100644 --- a/WindowMenu.ahk +++ b/WindowMenu.ahk @@ -7,12 +7,14 @@ #Include Lib\WindowFunctions.ahk #Include Lib\WindowPositions.ahk #Include Lib\DesktopIcons.ahk +#Include Lib\ListViewSelector.ahk #Include WindowExtensionsUserConfig.ahk ;-------------------------------------------------------------------------------- ; Globals IconLibrary := [] WindowMenuName := +G_SelectableWindowPositions := [] ;-------------------------------------------------------------------------------- ; Initialisation @@ -95,6 +97,13 @@ BuildWindowMenu() restoreEnabled := HasSavedWindowPositionFile(desktopSize) EnableMenuItem(WindowMenuName, restoreTitle, restoreEnabled) + + if (HasMultipleSavedWindowPositionFiles(desktopSize)) + { + restoreTitle := "Restore Window &Positions (" . desktopSize.DimensionsText . ")..." + + menuIndex := AddMenuItemWithIcon(WindowMenuName, restoreTitle, "RestoreMultipleWindowPositionsHandler", IconLibraryFileName, GetIconLibraryIndex("POSITION_RESTORE"), menuIndex) + } } ; Move to Corners @@ -262,7 +271,11 @@ SaveWindowPositions(G_UserConfig.WindowPositions_IncludeOffScreenWindows, true) return RestoreWindowPositionsHandler: -RestoreWindowPositions(G_UserConfig.WindowPositions_IncludeOffScreenWindows) +RestoreWindowPositions(GetLatestWindowPositionsDataFileName(GetDesktopSize()), G_UserConfig.WindowPositions_IncludeOffScreenWindows) +return + +RestoreMultipleWindowPositionsHandler: +RestoreWindowPositionsFromFile(GetDesktopSize()) return ;--------------------------------------------------------------------------------