Updating my current script which modifies multiple lines into a single one - autohotkey

My current script copies text like this with a shortcut:
:WiltedFlower: aetheryxflower ─ 4
:Alcohol: alcohol ─ 3,709
:Ant: ant ─ 11,924
:Apple: apple ─ 15
:ArmpitHair: armpithair ─ 2
and pastes it modified into a single line
Pls trade 4 aetheryxflower 3 alcohol 11 ant 15 apple 2 armpithair <#id>
As you can see there are already two little problems, the first one is that it copies only the number/s before a comma if one existed instead of ignoring it. The second is that I always need to also copy before hitting the hotkey and start re/start the script, I've thought of modifying the script so that it uses the already selected text instead of the copied one so that I can bind it with a single hotkey.
That is my current script, it would be cool if anyone can also tell me what they used and why exactly, so that I also get better with ahk
!q::
list =
While pos := RegExMatch(Clipboard, "(\w*) ─ (\d*)", m, pos ? pos + StrLen(m) : 1)
list .= m2 " " m1 " "
Clipboard := "", Clipboard := "Pls trade " list " <#951737931159187457>"
ClipWait, 0
If ErrorLevel
MsgBox, 48, Error, An error occurred while waiting for the clipboard.
return

If the pattern of your copied text dont change, you can use something like this:
#Persistent
OnClipboardChange:
list =
a := StrSplit(StrReplace(Clipboard, "`r"), "`n")
Loop,% a.Count() {
b := StrSplit( a[A_Index], ": " )
c := StrSplit( b[2], " - " )
list .= Trim( c[2] ) " " Trim( c[1] ) " "
}
Clipboard := "Pls trade " list " <#951737931159187457>"]
ToolTip % Clipboard ; just for debug
return
With your example text, the output will be:
Pls trade aetheryxflower ─ 4 alcohol ─ 3,709 ant ─ 11,924 apple ─ 15 armpithair ─ 2 <#951737931159187457>
And this will run EVERY TIME your clipboard changes, to avoid this, you can add at the top of the script #IfWinActive, WinTitle or #IfWinExist, WinTitle depending of your need.

The answer given would solve the problem, assuming that it never changes pattern as Diesson mentions.
I did the explanation of the code you provided with comments in the code below:
!q::
list = ; initalize a blank variable
; regexMatch(Haystack, regexNeedle, OutputVar, startPos)
; just for frame of reference in explanation of regexMatch
While ; loop while 'pos' <> 0
pos := RegExMatch(Clipboard ; Haystack is the string to be searched,
in this case the Clipboard
, "(\w*) ─ (\d*)" ; regex needle in this case "capture word characters
(a-z OR A-Z OR 0-9 OR _) any number of times, space dash space
then capture any number of digits (0-9)"
, m ; output var array base name, ie first capture will be in m1
second m2 and so on.
, pos ? pos + StrLen(m) : 1) ; starting position for search
"? :"used in this way is called a ternary operator, what is saying
is "if pos<>0 then length of string+pos is start position, otherwise
start at 1". Based on the docs, this shouldn't actually work well
since 'm' in this case should be left blank
list .= m2 " " m1 " " ; append the results to the 'list' variable
followed with a space
Clipboard := "" ; clear the clipboard.
Clipboard := "Pls trade " list " <#951737931159187457>"
ClipWait, 0 ; wait zero seconds for the clipboard to change
If ErrorLevel ; if waiting zero seconds for the clipboard to change
doesn't work, give error msg to user.
MsgBox, 48, Error, An error occurred while waiting for the clipboard.
return
Frankly this code is what I would call quick and dirty, and seems unlikely to work well all the time.

Related

Script in AutoHotkey to Press Windows button and Left Arrow simultaneously

I want to write a script in autohotkey so that every time I open my dictionary application on PC, keys Windows+LeftArrow being pressed at the same time and as the result, it snaps the windows on the left side of monitor.
I tried this:
#IfWinActive Oxford Advanced Learner's Dictionary
Send, #{Left}
return
Also this one:
#IfWinActive Oxford Advanced Learner's Dictionary
Send, {LWinDown}{Left}{LWinup}
return
But for either of them noting happened when I opened the application.
EDIT:
As suggested by #Charlie Armstrong the real question is: How do I make a block of code run every time I start a certain program? So #IfWinActive might not be useful for.
One way is periodically check if new process/window is created and to check if that is a process/window we want to interact with.
This first example is based on COM notifications when a process has been created/destroyed.
; help for question: https://stackoverflow.com/q/66394326/883015
; by joedf (16:04 2021/02/28)
MyWatchedWindowTitle := "Oxford Advanced Learner's Dictionary"
NewProcess_CheckInterval := 1 ; in seconds
SetTitleMatchMode, 2 ;this might not be needed, makes the check for "contains" instead of "same" winTitle
hWnds := []
gosub, initialize_NewProcessNotification
return
; Called when a new process is detected
On_NewProcess(proc) {
global hWnds
global MyWatchedWindowTitle
; get the window handle, if possible
if (hwnd:=WinExist("ahk_pid " proc.ProcessID)) {
WinGetTitle, wTitle, ahk_id %hwnd%
; check if there is a visible window
if (wTitle)
{
; if so, check if it's a window we want to interact with
if (InStr(wTitle,MyWatchedWindowTitle))
{
; check if we've interacted with this specific window before
if (!ArrayContains(hWnds, hwnd)) {
; we havent, so we do something with it
hWnds.push(hwnd) ; keep in memory that we have interacted with this window ID before.
DoSomething(hwnd) ; the keys we want to send to it
}
}
}
}
}
DoSomething(hwnd) {
; size and move window to the left
SysGet, MonitorWorkArea, MonitorWorkArea
posY := 0
posX := 0
width := A_ScreenWidth // 2
height := MonitorWorkAreaBottom
WinMove, ahk_id %hwnd% ,,%posX%,%posY%,%width%,%height%
; multi-montitor support, more examples, and more complete snapping functions can be found here:
; https://gist.github.com/AWMooreCO/1ef708055a11862ca9dc
}
ArrayContains(haystack, needle) {
for k, v in haystack
{
if (v == needle)
return true
}
return false
}
initialize_NewProcessNotification:
;////////////////////////////// New Process notificaton ////////////////////////
; from Lexikos' example
; https://autohotkey.com/board/topic/56984-new-process-notifier/#entry358038
; Get WMI service object.
winmgmts := ComObjGet("winmgmts:")
; Create sink objects for receiving event noficiations.
ComObjConnect(createSink := ComObjCreate("WbemScripting.SWbemSink"), "ProcessCreate_")
ComObjConnect(deleteSink := ComObjCreate("WbemScripting.SWbemSink"), "ProcessDelete_")
; Set event polling interval, in seconds.
interval := NewProcess_CheckInterval
; Register for process creation notifications:
winmgmts.ExecNotificationQueryAsync(createSink
, "Select * from __InstanceCreationEvent"
. " within " interval
. " where TargetInstance isa 'Win32_Process'")
; Register for process deletion notifications:
winmgmts.ExecNotificationQueryAsync(deleteSink
, "Select * from __InstanceDeletionEvent"
. " within " interval
. " where TargetInstance isa 'Win32_Process'")
; Don't exit automatically.
#Persistent
return
; Called when a new process is detected:
ProcessCreate_OnObjectReady(obj) {
proc := obj.TargetInstance
/*
TrayTip New Process Detected, % "
(LTrim
ID:`t" proc.ProcessID "
Parent:`t" proc.ParentProcessID "
Name:`t" proc.Name "
Path:`t" proc.ExecutablePath "
Command line (requires XP or later):
" proc.CommandLine
)
*/
On_NewProcess(proc)
}
; Called when a process terminates:
ProcessDelete_OnObjectReady(prm) {
/*
obj := COM_DispGetParam(prm, 0, 9)
proc := COM_Invoke(obj, "TargetInstance")
COM_Release(obj)
TrayTip Process Terminated, % "
(LTrim
ID:`t" COM_Invoke(proc, "Handle") "
Name:`t" COM_Invoke(proc, "Name")
)
COM_Release(proc)
*/
}
This second example, which is perhaps a bit simpler, checks periodically for new windows that match the searched WinTitle.
; help for question: https://stackoverflow.com/q/66394326/883015
; by joedf (16:17 2021/02/28)
#Persistent
MyWatchedWindowTitle := "Oxford Advanced Learner's Dictionary"
SetTitleMatchMode, 2 ;this might not be needed, makes the check for "contains" instead of "same" winTitle
SetTimer, checkForNewWindow, 1000 ;ms
hWnds := []
return
checkForNewWindow() {
global hWnds
global MyWatchedWindowTitle
; first check if there is at least one window that matches our winTitle
if (hwnd:=WinExist(MyWatchedWindowTitle)) {
; get all window matches
WinGet, wArray, List , %MyWatchedWindowTitle%
; loop through all windows that matched
loop % wArray
{
hWnd := wArray%A_Index%
; check if we've interacted with this specific window before
if (!ArrayContains(hWnds, hwnd)) {
; we havent, so we do something with it
hWnds.push(hwnd) ; keep in memory that we have interacted with this window ID before.
DoSomething(hwnd) ; the keys we want to send to it
}
}
}
}
DoSomething(hwnd) {
; size and move window to the left
SysGet, MonitorWorkArea, MonitorWorkArea
posY := 0
posX := 0
width := A_ScreenWidth // 2
height := MonitorWorkAreaBottom
WinMove, ahk_id %hwnd% ,,%posX%,%posY%,%width%,%height%
; multi-montitor support, more examples, and more complete snapping functions can be found here:
; https://gist.github.com/AWMooreCO/1ef708055a11862ca9dc
}
ArrayContains(haystack, needle) {
for k, v in haystack
{
if (v == needle)
return true
}
return false
}
I think your biggest issue is that AHK doesn't seem to work well for snapping windows (according to my quick research and testing). What does work well, though, is WinMove.
I assume you're launching the program from a shortcut icon, but I would suggest using a keyboard shortcut that launches the program and then positions the window from the script. Here is some sample code that opens Notepad2.exe, waits for 200 milliseconds, and then moves the window and resizes it:
^+!n:: ; Control+Shift+Alt+N to Open Notepad
Run C:\Program Files\Notepad2\Notepad2.exe
sleep, 200
WinMove, Notepad2,, 10, 20, 800, 600
return

Autohotkey - Copy from HTML and paste in Word

I am new to Autohotkey, so need your patience.
I have a folder with 6236 individual HTML files; each contains one sentence.
The names of these files go like this: 001001, 001002, 001003,..., 001007, 002001,002002, 002003 and so on. Of course, with .html file format.
I want to write a script with hotstrings such as:
:*:001001::
:*:001002::
and so on, so that when I type 001001 in Word, have the content of 001001.html pasted into the word. And when I type 001002 it pastes the content of 001002.html into the word. This way for all the 6236 files.
Can you help me with this?
Main code
loop,6236
{
number := 001000 + A_Index
number := "00" . number
temptext1 :=":*:" . number . "::"
temptext2 := "FileRead, orgtext, C:\" . number . ".html"
temptext3 := "SendInput % orgtext"
temptext4 := "return"
temptext := temptext . temptext1 . "`n" . temptext2 . "`n" . temptext3 . "`n" . temptext4 . "`n`n"
}
FileAppend, %temptext%, C:\test.ahk
Loop 1 will create following text
:*:001001::
FileRead, orgtext, C:\001001.html
SendInput % orgtext
return
Loop 2 will create
:*:001002::
FileRead, orgtext, C:\001002.html
SendInput % orgtext
return
And so on until 007236. You need to change where your html files are located. I assumed C:\001001.html. If for example files are in C:\Program Files\Myhtmlfiles\001001.html then edit line 6 into:
temptext2 := "FileRead, orgtext, C:\Program Files\Myhtmlfiles\" . number . ".html"
This is quite a long loop so it takes a couple of seconds to finish. After the loop is finished result gets saved into test.ahk located in C:\

Join lines automatically using autohotkey

Long paragraphs in Adobe Acrobat results in a line break when text is copied to clipboard and pasted in Word.
To deal with this, I manually paste the copied-text in Notepad ++ > select all > ctrl+J (join lines) > select all > Copy...... and then paste it into my Word document.
I would like to automate this join line using autohotkey as I have a large body of documents to go through. I can't seem to find any examples online in dealing with this directly within autohotkey script.
Ex.
Data structures with labeled axes supporting automatic or explicit data alignment.
This prevents common errors resulting from misaligned data and working with
differently-indexed data coming from different sources.
After manually joining in Notepad ++
Data structures with labeled axes supporting automatic or explicit data alignment. This prevents common errors resulting from misaligned data and working with differently-indexed data coming from different sources.
This works:
#Persistent
OnClipboardChange("ClipChanged")
return
ClipChanged() {
If (WinActive("ahk_exe AcroRd32.exe")) {
For e, v in StrSplit(clipboard, "`n", "`r")
x .= v " "
clipboard := trim(x)
}
}
Try this:
#Persistent
return
OnClipboardChange:
If WinActive("ahk_exe AcroRd32.exe")
{
clipboard =
Send, {Ctrl down}c{Ctrl up}{Esc}
ClipWait
clip := RegExReplace(clipboard, "(\S.*?)\R(.*?\S)", "$1 $2") ; strip line breaks and replace them with spaces
clipboard = %clip%
StringReplace clipboard, clipboard, % " ", % " ", A ; replace double spaces with single spaces
ClipWait
ControlClick, x0 y0, A
}
return
EDIT
Or this:
#Persistent
return
OnClipboardChange:
If WinActive("ahk_class AcrobatSDIWindow")
{
clipboard := ""
Send, {Ctrl down}c{Ctrl up}{Esc}
ClipWait
clip := RegExReplace(clipboard, "(\S.*?)\R(.*?\S)", "$1 $2") ; strip line breaks and replace them with spaces
StringReplace clip, clip, % " ", % " ", A ; replace double spaces with single spaces
clipboard := ""
clipboard = %clip%
ClipWait 2
If !(ErrorLevel)
{
ToolTip, lines joined
Sleep 500
ToolTip
}
}
return
I added functionality considering lines ending with a hyphen.
Before:
This occurs in elec-
tricity generation
systems.
After:
This occurs in electricity generation systems.
Code:
#Persistent
return
OnClipboardChange:
If WinActive("ahk_exe AcroRd32.exe")
{
clipboard := ""
Send, {Ctrl down}c{Ctrl up}{Esc}
ClipWait
clip := RegExReplace(clipboard, "(\S.*?)-\R(.*?\S)", "$1$2") ; strip line breaks with hyphen
clip := RegExReplace(clip, "(\S.*?)\R(.*?\S)", "$1 $2") ; strip line breaks and replace them with spaces
StringReplace clip, clip, % " ", % " ", A ; replace double spaces with single spaces
clipboard := ""
clipboard = %clip%
ClipWait 2
If !(ErrorLevel)
{
ToolTip, lines joined
Sleep 500
ToolTip
}
}
return

SendEvent ^{ins} isn't copying content to the clipboard

!c::
file_name = footnote.ini
restore_original_clipBoard := clipboard
clipboard =
KeyWait, Alt
KeyWait, c ;small c
BlockInput, on
SendEvent, ^{ins} ;^c doesn't work
ClipWait, 2 ; Wait for the clipboard to contain text.
if ErrorLevel
{
MsgBox Failed to save the selection: %clipboard%
exit
}
BlockInput, off
save_selection := clipboard
Problem: Despite a selection being made, Sendevent ^{ins} does not save it to the clipboard. Sometimes I have to repeat my hotkey, alt + c several times before the selection is being copied to the clipboard. The KeyWait should ensure me that only ^{ins} is being processed without any additional keys. What am I doing wrong here?
UPDATE
One of the ways I tried to force copy a selection to the clipboard was by using a while loop. I got it to work through the post: Looping clipboard and errorlevel evaluation not working as expected
PROBLEM
When I make a selection and press alt + c it sometimes gets stuck in the infinite loop that I implemented. But as you can see from that code:
clipboard := ""
while( StrLen(clipboard) < 1 )
{
Send, ^{ins}
Sleep, 50
}
MsgBox % ClipBoard
The infinite loop incorporates within itself a continues resending of ^{ins}. For some reason, my selection is not being recognized as a selection. Whilst it is in that infinite loop, I try to reselect the text. It then recognizes it instantly and copies my selection to the clipboard. But alas! The selection is incomplete because it goes so quick.
This problem is not always like that. Sometimes it recognizes the selection first spot on! So sometimes it copies my selection to my clipboard sometimes not. When it does not, then a resending of a ^{ins} does not seem to work. I do not want to the user to reselect his selection. Is that possible to do?
Send {Ctrl Down}{c}{Ctrl Up}
That presses Ctrl+C, you must do it instantly as one command apposed to pressing Ctrl waiting then pressing C.
Never seen Insert key used for copying text.
Also found this sends Ctrl+C as well.
Send, ^c
To send insert key use
{Insert}
This way works for me:
!vk43:: ; alt+c
clipContent:=ClipboardAll
Clipboard:=""
SendEvent, ^{Ins}
ClipWait, .75
MsgBox, % 262 . (ErrorLevel ? 160:208)
, % ErrorLevel ? "Period expired:":"Result:"
, % ErrorLevel ? "Failed to save the selection.":Clipboard
, % (ErrorLevel ? 0:2) . .5
Clipboard:=clipContent
KeyWait, vk43
Return
!vk43:: ; alt+c
clipContent:=ClipboardAll ; backup clipboard content (if needed)
Clipboard:="" ; no comment :)
Loop
{
SendEvent, ^{Ins}
ClipWait, .75 ; means 750ms, same if write 0.75
; assign value of "ErrorLevel" an variable for further usage
errLvl:=ErrorLevel
; monitoring current action (for debugging purpose only)
TrayTip, % "attempt: #"A_Index
, % """ErrorLevel"" of ""ClipWait"" command is: "errLvl
}
; here you can set the condition of ending the cycle: either...
; ...variable errLvl has not a true value,...
; ...or the number of attempts is equal 5
Until, Not errLvl Or A_Index=5
; value of each field of the command "MsgBox"...
; ...are set depending on the value of errLvl variable...
; ...using a ternary expression
; means if errLvl is a true, "options" field is 262160
MsgBox, % 262 . (errLvl ? 160:208)
; means that "title" has a couple variants
, % errLvl ? "Period expired:":"Result:"
; means that "text" has a couple variants
, % errLvl ? "Failed to save the selection.":Clipboard
; means if errLvl is a true, "timeout" field is 0.5 (500ms)
, % (errLvl ? 0:2) . .5
/* same that and above:
IfEqual, errLvl, % True, MsgBox, 262160
, % "Period expired:"
, % "Failed to save the selection."
, 0.5
Else MsgBox, 262208, % "Result:", % Clipboard, 2.5
*/
TrayTip ; remove "TrayTip" (for debugging purpose only)
; save an positive result (if needed)
IfEqual, errLvl, 0, Sleep, -1, someVar:=Clipboard
; return a temporarily saved data into clipboard (if needed)
Clipboard:=clipContent
KeyWait, % "vk43"
Return
From my experience whenever keystrokes are not recognized reliably it's due to either the system or the targeted program not keeping up with the speed at which those keys are sent.
For SendEvent you could try something like SetKeyDelay, 1000, 1000 and see if this improves things. The other option would be to send explicit down and up keys with intermittent sleep calls as outlined in this answer.

Autohotkey - Replace different words or sentences

in this script that I use i can replace only one word teams--> milan, juventus, inter..
But i want replace many words (not only one word) for example:
[simply word replacement jack-->beta]
alfa-->beta
[sentence replacement jack-->jack,john,alfa]
jack
jack
john
alfa
This is actually code that i use
loop {
While !RegexMatch(strCapture, "teams$") {
Input, strUserInput, V L1, {BackSpace} ; V: visible, L1: Character length 1
If ErrorLevel = Endkey:BackSpace
strCapture := SubStr(strCapture, 1, StrLen(strUserInput) - 1)
else
strCapture .= strUserInput
; tooltip % ErrorLevel "`n" strUserInput "`n" strCapture "`n" ; enable this to see what actually happens
}
SendInput,
(Ltrim
{Backspace 5}
milan
juventus
inter
roma
lazio
napoli
mantova
)
strCapture := ""
}
How can I modify the code?
It is also possible to run the script integrating copy-paste?
You could use a For loop to replace the contents, but this would become increasingly tedious to maintain at scale, so YMMV.
The following will use the clipboard contents as the to/from variables, but you can swap it out with a variable as desired (replace "clipboard" with your string variable).
F3::
{
replace := {"alfa":"beta", "gamma":"delta"}
For start, end in replace {
StringReplace, clipboard, clipboard, %start%, %end%, All
}}
Return