Last active
August 12, 2024 01:56
-
-
Save anonymous1184/9a44da7393dd269060b76c238ae2e3f5 to your computer and use it in GitHub Desktop.
Clipboard History Manager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| |
; Version: 2022.06.30.1 | |
; Usages and examples: https://redd.it/mq9m58 | |
ClipHistory(Ini, Monitoring := true) | |
{ | |
static instance := false | |
if (!instance) | |
instance := new ClipHistory(Ini, Monitoring) | |
return instance | |
} | |
#Include <Clip> | |
class ClipHistory extends Clip | |
{ | |
; Properties | |
Monitor[] | |
{ | |
get { | |
return this._active | |
} | |
set { | |
return this.Toggle(value) | |
} | |
} | |
Skip[] | |
{ | |
get { | |
return this._skips | |
} | |
set { | |
this._skips := Format("{:d}", value) | |
return this._skips | |
} | |
} | |
; Public methods | |
__New(Ini, Monitoring) | |
{ | |
this._skips := 0 | |
this._last := Clipboard | |
loop files, % Ini | |
Ini := A_LoopFileLongPath | |
if (!FileExist(Ini)) | |
throw Exception("File '" Ini "' not found.", -1) | |
Monitoring := !!Monitoring | |
IniRead path, % Ini, CLIPBOARD, path | |
attributes := FileExist(path) | |
if (!InStr(attributes, "D")) | |
throw Exception("Bad path, check '" Ini "'.", -1) | |
this._path := path | |
IniRead key1, % Ini, CLIPBOARD, key1, % false | |
IniRead key2, % Ini, CLIPBOARD, key2, % false | |
if (!key1 && !key2) | |
throw Exception("No keys to bind.", -1) | |
this._data := {} | |
if (key1) | |
this._MenuHistoryBind(key1, Monitoring) | |
if (key2) | |
this._MenuSnippetsBind(key2, Ini) | |
IniRead size, % Ini, CLIPBOARD, size | |
this._size := size > 99 ? 99 : size < 1 ? 49 : size | |
} | |
Get(Backup := true, Wait := 5, Skip := 0) | |
{ | |
this._skips += Skip | |
parent := ObjGetBase(this.Base) | |
parent.Get(Backup, Wait) | |
} | |
Previous() | |
{ | |
this.Set(this._prev, false, false) | |
this.Paste() | |
} | |
Toggle(State := -1) | |
{ | |
if (State = -1) | |
this._active ^= 1 | |
else | |
this._active := State | |
OnClipboardChange(this._monitorBind, this._active) | |
return this._active | |
} | |
; Private | |
_Crc32(String) | |
{ | |
return DllCall("Ntdll\RtlComputeCrc32", "UInt",0, "Ptr",&String | |
, "UInt",StrLen(String) * 2, "UInt") | |
} | |
_Delete(Path) | |
{ | |
Clipboard := "" | |
FileDelete % Path | |
} | |
_DeleteAll() | |
{ | |
MsgBox 0x40024, > ClipHistory, Delete all Clipboard History? | |
IfMsgBox Yes | |
{ | |
Clipboard := "" | |
FileDelete % this._path "\*.clip" | |
} | |
} | |
_MenuHistory() | |
{ | |
files := [] | |
loop files, % this._path "\*.clip" | |
files[A_LoopFileTimeModified] := A_LoopFileLongPath | |
; No History | |
if (!files.Count()) | |
return | |
; Max History, FIFO mode. | |
loop % files.Count() - this._size { | |
last := files.MinIndex() | |
FileDelete % files.RemoveAt(last) | |
} | |
; (re)Set | |
this._data["hist"] := [] | |
last := files[files.MaxIndex()] | |
while (file := files.Pop()) { | |
; Read | |
FileRead contents, % "*P1200 " file | |
this._data["hist"].Push(contents) | |
contents := LTrim(contents, "`t`n`r ") | |
; Number of lines | |
StrReplace(contents, "`n",, numLines) | |
; 0-padded index | |
index := Format("{:02}", A_Index) | |
; First LF occurrence | |
firstLF := InStr(contents, "`n") | |
; Limit to first LF or 30 chars | |
size := firstLF && firstLF < 30 ? firstLF : 30 | |
; Cut | |
title := SubStr(contents, 1, size) | |
; Ellipsis if needed | |
title .= StrLen(contents) > 30 ? "..." : "" | |
; Put number of lines at the right | |
title .= numLines > 1 ? "`t+" numLines : "" | |
this._MenuHistoryBuild(index, title) | |
} | |
noOp := {} | |
; Always bind, faster than store/retrieve | |
fnToggle := ObjBindMethod(this, "Toggle", -1) | |
fnDeleteAll := ObjBindMethod(this, "_DeleteAll") | |
fnDeleteLast := ObjBindMethod(this, "_Delete", last) | |
Menu History, Add | |
Menu History, Add, &Monitor, % fnToggle | |
Menu History, Add | |
Menu History, Add, Delete &All, % fnDeleteAll | |
Menu History, Add, Delete &Last, % fnDeleteLast | |
Menu History, Add, &Cancel, % noOp | |
if (this._active) | |
Menu History, Check, &Monitor | |
Menu History, Show | |
; Cleanup | |
loop % index // 10 ; Sub-menu number | |
Menu % "clipSub" A_Index, DeleteAll | |
Menu History, DeleteAll | |
} | |
_MenuHistoryBind(Key, Monitoring) | |
{ | |
fnObj := ObjBindMethod(this, "_MenuHistory") | |
Hotkey % Key, % fnObj, UseErrorLevel | |
if (ErrorLevel) | |
throw Exception("Couldn't bind '" Key "'.", -1) | |
this._active := Monitoring | |
this._monitorBind := ObjBindMethod(this, "_Monitor") | |
OnClipboardChange(this._monitorBind, this._active) | |
} | |
_MenuHistoryBuild(index, item) | |
{ | |
tens := SubStr(index, 1, 1) | |
unit := SubStr(index, 0, 1) | |
item := StrReplace(item, "&", "&&") | |
fnObj := ObjBindMethod(this, "_Paste", "hist", "", index) | |
if (!tens) ; Top level menu | |
Menu History, Add, % "&" unit ") " item, % fnObj | |
else if (index = 10) ; Separator | |
Menu History, Add | |
if (tens) { ; Sub menu items | |
index := SubStr(index, 1, 1) "&" SubStr(index, 2) | |
Menu % "clipSub" tens, Add, % index ") " item, % fnObj | |
} | |
if (!unit) ; Sub menu headers | |
Menu History, Add, % tens "0 - " tens "9", % ":clipSub" tens | |
} | |
_MenuSnippets() | |
{ | |
Menu Snippets, Show | |
} | |
_MenuSnippetsBind(Key, Ini) | |
{ | |
fnObj := ObjBindMethod(this, "_MenuSnippets") | |
Hotkey % Key, % fnObj, UseErrorLevel | |
if (ErrorLevel) | |
throw Exception("Couldn't bind '" Key "'.", -1) | |
IniRead snippets, % Ini, SNIPPETS | |
if (!snippets) | |
throw Exception("No snippets defined.", -1) | |
loop parse, snippets, `n | |
this._snips .= A_Index < 10 ? A_LoopField "`n" : "" | |
this._snips := RTrim(this._snips, "`n") | |
this._MenuSnippetsBuild() | |
} | |
_MenuSnippetsBuild() | |
{ | |
fnObj := ObjBindMethod(this, "_Paste", "snip") | |
loop parse, % this._snips, `n | |
{ | |
snip := StrSplit(A_LoopField, "=",, 2) | |
this._data["snip", A_Index] := snip[2] | |
Menu Snippets, Add, % "&" A_Index ") " snip[1], % fnObj | |
} | |
noOp := {} | |
Menu Snippets, Add | |
Menu Snippets, Add, &Cancel, % noOp | |
} | |
_Monitor(Type) | |
{ | |
static format := DllCall("User32\RegisterClipboardFormat" | |
, "Str","ExcludeClipboardContentFromMonitorProcessing") | |
if (Type != 1) | |
|| (DllCall("User32\IsClipboardFormatAvailable", "UInt",format)) | |
return | |
if (this._skips) { | |
this._skips-- | |
return | |
} | |
crcCurrent := this._Crc32(Clipboard) | |
if (this._crcLast = crcCurrent) | |
return | |
this._crcLast := crcCurrent | |
; Set previous | |
this._prev := this._last | |
this._last := Clipboard | |
; Save as UTF-16 | |
FileOpen(this._path "\" crcCurrent ".clip", 0x1, "CP1200").Write(Clipboard) | |
} | |
_Paste(DataType, _, Index) | |
{ | |
skip := (DataType = "snip") | |
this.Set(this._data[DataType, Index],,, skip) | |
this.Paste(false) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment