Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Command Object function library NEW [Replace functions]


  • Please log in to reply
20 replies to this topic
sinkfaze
  • Moderators
  • 6367 posts
  • Last active: Nov 30 2018 08:50 PM
  • Joined: 18 Mar 2008
Note: Please see this post for updated versions of several of the below functions.

In accordance with my wish I decided to put my money where my mouth is and start a library of functions dedicated to retrieving all available information about certain items based on existing commands and lumping them into an object to be returned. Once returned the user can choose which information they would like to use from the object. I think the exercise of creating a library like this may have future implications for AHK; if there is no significant time loss to retrieve all available information about an item it may prove more satisfactory to return all data in objects in the future rather than a single piece in a variable.

For example, a function DriveInfo() to retrieve all information about all available drives, which can be limited by specifying a type of drive as a parameter:

drives :=	DriveInfo()
For k,v in drives
{
	dk :=	k, dr :=	v
	For k,v in dr
		res .=	(v ? ((!res ? "" : "`n") k ": " v) : "")
	MsgBox, Information for drive %dk%:`n`n%res%
	res := dr :=	""
}
; separate example
MsgBox %	DriveInfo("C").Label
return

Or FileInfo(), which will retrieve all information about a file:

file :=	FileInfo(filename :=	"C:\Program Files\AutoHotkey\AutoHotkey.exe")
For k,v in file
	res .=	(!res ? "" : "`n") k ": " v
MsgBox, Information for %filename%`n`n%res%
return

Bear in mind this is only a preliminary list, and any additions would be welcome.


DriveInfo(drive="") {
	cmd=Capacity,Filesystem,Label,Serial,Type,Status,StatusCD
	if	(drive && !FileExist(RegExReplace(drive,"\W") ":\")) {
		MsgBox, 48, Warning, Invalid parameter for drive, please select an existing drive on your machine.
		return	False
	}
	if	drive {
		res :=	Object("FreeSpace",DriveSpaceFree(drive))
		Loop, parse, cmd, `,
			res[A_LoopField] :=	DriveGet(A_LoopField,drive)
		return	res
	}
	res :=	Object(), dlist :=	DriveGet("List")
	Loop, parse, dlist 
	{
		tmp :=	A_LoopField, obj :=	Object("FreeSpace",DriveSpaceFree(tmp)) 
		Loop, parse, cmd, `, 
			obj[A_LoopField] :=	DriveGet(A_LoopField,tmp) 
		res[tmp] :=	obj 
	}
	return	res
}

DriveGet(cmd,value="") {	; helper function for DriveObj() 
	DriveGet, res, %cmd%, %	value ? RegExReplace(value,"\W") ":\" : ""
	return	res 
}

DriveSpaceFree(path) {	; helper function for DriveObj() 
	DriveSpaceFree, res, %	RegExReplace(path,"\W") ":\" 
	return	res
}
FileInfo(fpath) {
	FileGetTime, ftm, %fpath%, M
	FileGetTime, ftc, %fpath%, C
	FileGetTime, fta, %fpath%, A
	FileGetVersion, fv, %fpath%
	return	Object("Attributes",FileExist(fpath)
	 ,"Modified",ftm,"Created",ftc,"Last Access",fta,"Version",fv)
}

MouseObj(opt="") { ; opt may only be 1,2 or 3
	MouseGetPos, x, y, w, c, %opt%
	return	Object("x",x,"y",y,"window",w,"control",c)
}

WinGetActiveStats() {
	WinGetActiveStats, t, w, h, x, y
	return	Object("title",t,"width",w,"height",h,"x",x,"y",y)
}

WinInfo(win="A",text="",extitle="",extext="") {
	cmd := "PID,ProcessName,MinMax,ControlList,ControlListHwnd,"
	 . "Transparent,TransColor,Style,ExStyle"
	WinGetTitle, t, %win%, %text%, %extitle%, %extext%
	WinGetPos, x, y, w, h, %win%, %text%, %extitle%, %extext%
	WinGetText, text, %win%, %text%, %extitle%, %extext%
	WinGetClass, class, %win%, %text%, %extitle%, %extext%
	res := Object("title",t,"width",w,"height",h,"x",x,"y",y,"text",text,"class",class)
	Loop, parse, cmd, `,
		res[A_LoopField] :=	WinGet(win,A_LoopField)
	return	res
}

WinGet(win,cmd,text="",extitle="",extext="") { ; helper function for WinObj()
	WinGet, res, %cmd%, %win%, %text%, %extitle%, %extext%
	return	res
}

PathInfo(ByRef InputVar) {
	SplitPath, InputVar, f, d, e, n, dr
	return	Object("FileName",f,"Dir",d,"Extension",e,"NameNoExt",n,"Drive",dr)
}

MonitorInfo() {
	cmd=Monitor,MonitorWorkArea,MonitorName
	c :=	SysGet("MonitorCount")
	res :=	Object("Count",c,"PrimaryMonitor",SysGet("MonitorPrimary"))
	if	(c > 1) {
		Loop %	c {
			m :=	A_Index, %m% :=	Object()	
			Loop,parse,cmd, `,
				%m%[(A_LoopField="Monitor") ? "Coords" : SubStr(A_LoopField,8)] :=	SysGet(A_LoopField,m)
			res[A_Index] :=	%m%
		}
	}
	return	res
}

SysGet(Subcmd,Param3="") {
	SysGet, r, %Subcmd%, %Param3%
	Return	In(subcmd,"Monitor,MonitorWorkArea") ? rLeft "," rTop "," rRight "," rBottom
	 : r
}

ImageSearch(X1,Y1,X2,Y2,ImageFile) {
	ImageSearch, X, Y, %X1%, %Y1%, %X2%, %Y2%, %ImageFile%
	return	Object("x",X,"y",Y)
}

PixelSearch(X1,Y1,X2,Y2,ColorID,Variation = "",Mode = "") {
	PixelSearch, X, Y, %X1%, %Y1%, %X2%, %Y2%, %ColorID%, %Variation%, %Mode%
	return	Object("x",X,"y",Y)
}

In(ByRef var, MatchList) {
	If var in %MatchList%
		Return	True
}

WinGetList(win="A",type="List",text="",extitle="",extext="") {
	if	!In(type,"List,Control") {
		MsgBox, 48, Warning, Invalid type parameter, only List or Control are accepted.
		return	False
	}
	res :=   Object()
	If	(type = "List") {
		WinGet, out, List, %win%, %text%, %extitle%, %extext%
		Loop %	out {
			WinGetTitle, t, %	"ahk_id " out%A_Index%
			res[A_Index] :=	Object("Title",t,"hWnd",out%A_Index%)
		}
	} else {
		WinGet, cout, ControlList, %win%, %text%, %extitle%, %extext%
		WinGet, hout, ControlListHwnd, %win%, %text%, %extitle%, %extext%
		StringSplit, n, cout, `n
		StringSplit, h, hout, `n
		Loop %	n0
			res[A_Index] :=	Object("Name",n%A_Index%,"hWnd",h%A_Index%)
	}
	return	res
}

20101230
[*:gpb7c0or]changed the object var format in the first example and removed 'Obj' from applicable function names, replaced by 'Info'.
[*:gpb7c0or]Updated the DriveInfo function to accomodate a call to a single drive, updated and added helper functions for DriveInfo
[*:gpb7c0or]Added additional functions
20110102 - Added RegExM() and WhileRegExM().

20110106 - Modified WinGetList() to accept only 'List' and 'Control' in the type parameter; 'List' now returns the title and hWnd for each matching window and 'Control' now returns the name and hWnd of each control in the window.


Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
I'm curious about your use of dynamic variables in the drive loop. Why not use ordinary variables, thus avoiding unnecessary overhead and the possibility of overwriting global variables?
[color=darkred]drv[/color] := A_LoopField, [color=darkred]obj[/color] := Object()
      Loop, parse, cmd, `,
         [color=darkred]obj[/color][A_LoopField] := DriveGet(A_LoopField, [color=darkred]drv[/color] ":\")
      res[A_LoopField] := [color=darkred]obj[/color]
IMO, v.Capacity is shorter and more readable than v["Capacity"] and therefore more appealing, which could be important if you want the idea to gather support.

I would expect DriveObj() to represent a drive, not a collection of drives, especially since FileObj() represents a single file. Given that a "File object" as returned by FileOpen() allows read/write access to a file, I think "FileInfo" would be more appropriate in this case. If objects are meant to be a natural part of the language - i.e. just the means of returning the information rather than the purpose of the functions - perhaps the names should not include "Obj" at all. For instance,
drive_c := DriveInfo("C")
removable_drives := DriveInfo("removable")
ver := FileInfo(A_AhkPath).Version
Updating info could be done through the same object:
FileInfo(A_ScriptFullPath).Attributes := "AR"


fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009
It's a nice idea for sure. However, the part where I see the most use would be GUIs. The concept of GUI variables is a bit outdated IMO now that there is object support. It would be nice if it was possible to place all GUI variables belonging to one GUI in a single object and access some properties through it.

sinkfaze
  • Moderators
  • 6367 posts
  • Last active: Nov 30 2018 08:50 PM
  • Joined: 18 Mar 2008

I'm curious about your use of dynamic variables in the drive loop. Why not use ordinary variables, thus avoiding unnecessary overhead and the possibility of overwriting global variables?


Well I was trying to create objects with the same name as the drive they'd be holding information for so you could call for information from a particular drive, like this:

drive := DriveInfo()
MsgBox % drive.c.Capacity

Hence I needed the value of the variable for the object name and not the variable. Maybe I wrote it wrong, worked when I wrote it anyway. Believe you me I expect to find plenty of things I don't know or overlooked, kind of another reason for taking on the project.

IMO, v.Capacity is shorter and more readable than v["Capacity"] and therefore more appealing, which could be important if you want the idea to gather support.


Fair enough.

I would expect DriveObj() to represent a drive, not a collection of drives, especially since FileObj() represents a single file.


I thought about that while I was creating it and came to the conclusion that if there isn't a significant extra cost in time or resources to retrieve all information about all drives at once, why not? It would be easy enough to get what you need from one drive given how I planned for the object to work. Again, could be wrong on that.

Given that a "File object" as returned by FileOpen() allows read/write access to a file, I think "FileInfo" would be more appropriate in this case. If objects are meant to be a natural part of the language - i.e. just the means of returning the information rather than the purpose of the functions - perhaps the names should not include "Obj" at all.


Interesting point.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

Well I was trying to create objects with the same name as the drive they'd be holding information for so you could call for information from a particular drive, like this:

drive := DriveInfo()
MsgBox % drive.c.Capacity

I wasn't questioning the names in the array object, but the intermediate dynamic variables.
D := "Danger!"
DriveInfo()
MsgBox % C.Label  ; This should NOT work, but it currently does.
MsgBox % D        ; Where'd my value go??

I thought about that while I was creating it and came to the conclusion that if there isn't a significant extra cost in time or resources to retrieve all information about all drives at once, why not?

I was merely objecting to the choice of name. However, I also think DriveInfo("C") is more natural than DriveInfo().C. It could support both.

sinkfaze
  • Moderators
  • 6367 posts
  • Last active: Nov 30 2018 08:50 PM
  • Joined: 18 Mar 2008
I updated the DriveInfo() function to accomodate Lexikos' idea for added functionality and added a few more functions to the library.

sinkfaze
  • Moderators
  • 6367 posts
  • Last active: Nov 30 2018 08:50 PM
  • Joined: 18 Mar 2008
Below are two additional functions for the library which are essentially a proof of concept for the wish mentioned in the original post. The first function is RegExM(), which will return the matching string, the string's length and the string's position in a single object. Example:

html=<a href="http://www.abc.def/ghi=Word">blah</a>
n=i)href="\K.*?\w+">\w+

re :=	RegExM(html,n)
For k,v in re
	match .=	(!match ? "" : "`n") "Whole " k ":  " v
MsgBox % match
return

It can also handle named and unnamed subpatterns:

html=<a href="http://www.abc.def/ghi=Word">blah</a>
n=href="\K(?P<URL>.*?(\w+))">(?=\w+)
re :=	RegExM(html,n)
For k,v in re {
	if	IsObject(v) {
		tk :=	k, tv :=	v
		For k,v in tv
			sb .= (!sb ? "" : "`n") "Subpattern " tk " " k ":  " v
		sb .= "`n"
	}
	else	whole .= (!whole ? "" : "`n") "Whole " k ":  " v
}
MsgBox, %whole%`n`n%sb%
return

Taking this a step further is WhileRegExM(), which utilizes the functionality of RegExM() to traverse a given string and return multiple matches (if any):

; example without subpatterns
h=That is not a thoughtful thing to throw at the President's throne.
n=\bth\w+\b
wre :=	WhileRegExM(h,n)
MsgBox %	"There were " wre.Count " total matches of the pattern."
For each, match in wre
	if	(each="Count")
		continue
	else	MsgBox, %	"Match " A_Index ": " match.Str
; last match
MsgBox %	"Last match: " wre[wre.Count].Str
return

With subpatterns WhileRegExM() proves more handy as you can parse out individual subpatterns for use if you don't need the others:

html=
(
<a href="http://www.abc.def/ghi=Word">blah</a>
<a href="http://www.abc.def/ghi=Excel">narf</a>
<a href="http://www.abc.def/ghi=Outlook">ponk</a>
<a href="http://www.abc.def/ghi=Access">zoff</a>
)
n=href="\K(?P<URL>.*?(\w+))">(?=\w+)

wre :=	WhileRegExM(html,n)
For each, match in wre
	if	(each="Count")
		continue
	else	MsgBox %	match.URL.Str
;	MsgBox %	match[2].Str
return

Both functions are provided below. I've tried to be as thorough in testing these functions as possible but I make room for the fact that there will probably be inconsistencies. Please let me know of any problems you find using the functions, and any other comments or suggestions are welcome:

RegExM()

RegExM(haystack,needle,startpos=1) {

	Sub := Pos := 1
	 , opts := "^[^\\(]+(?=\))", spop :=	"[^\\]\K\((?!\?<?(:|=|!))"

	if	!RegExMatch(haystack,needle,"",startpos)
		return	False
; 	>> create two needles, one exclusively for string/length match, other exclusively for position match
	if	RegExMatch(needle,opts,o)	; check needle for options
		if	InStr(o,"P")	; if 'P' option is present in the original needle
;			save the original needle as the position needle and strip out the 'P' option for the string/length needle
			pneedle :=	needle, needle :=	RegExReplace(needle,o,RegExReplace(o,"P"))
;		else add 'P' to the existing options for the position needle
		else	pneedle :=	RegExReplace(needle,o,o "P")
;	else add the 'P)' option and save to the position needle
	else	pneedle :=	"P)" needle
	if	!RegExMatch(needle,spop) {	; if no subpatterns exist
		p :=	RegExMatch(haystack,needle,m,startpos)
		return	Object("Str",m,"Pos",p,"Len",StrLen(m))
	}
;	>> parse subpattern names/numbers out of needle and save to list
	While	Pos :=	RegExMatch(needle,spop,sp,Pos+StrLen(sp)) {
		if	(SubStr(needle,pos+1,3) = "?P<")	; if subpattern has name flag
			sv .=	(!sv ? "" : ",") SubStr(needle,Pos+4,InStr(needle,">",False,Pos+1)-Pos-4)
		else	sv .=	(!sv ? "" : ",") Sub
		Sub++
	}
	mp :=	RegExMatch(haystack,needle,m,startpos)
	 , t :=	RegExMatch(haystack,pneedle,p,startpos)
	 , res :=	Object("Str",m,"Pos",mp,"Len",StrLen(m))
;	>> parse subpattern list to save to appropriate subpattern object
	Loop, parse, sv, `,
	{
		tm :=	m%A_LoopField%, tp :=	pPos%A_LoopField%
		res[A_LoopField] :=	Object("Str",tm,"Pos",tp,"Len",StrLen(tm))
	}
	return	res
}

WhileRegExM()

WhileRegExM(haystack,needle,startpos=1) {

	static	opts := "^[^\\(]+(?=\))", spop :=   "[^\\]\K\((?!\?<?(:|=|!))"
	Sub := Pos := 1

	if	!RegExMatch(haystack,needle,"",startpos)
		return	False
;	>> create two needles, one exclusively for string/length match, other exclusively for position match
	if	RegExMatch(needle,opts,o)	; check needle for options
		if	InStr(o,"P")	; if 'P' option is present in the original needle
;					  save the original needle as the position needle and strip out the 'P' option for the string/length needle
			pneedle :=   needle, needle :=   RegExReplace(needle,o,RegExReplace(o,"P"))
;					  else add 'P' to the existing options for the position needle
		else	pneedle :=   RegExReplace(needle,o,o "P")
;	else add the 'P)' option and save to the position needle
	else	pneedle :=   "P)" needle
	res :=	Object()
	if	!RegExMatch(needle,spop) {
		While	startpos :=   RegExMatch(haystack,needle,m,startpos+StrLen(m))
			res[(Count:=   A_Index)] :=	{Str:m,Pos:startpos,Len:StrLen(m)}
		res["Count"] :=	Count
		return	res
	}
;	>> parse subpattern names/numbers out of needle and save to list
	While	Pos :=   RegExMatch(needle,spop,sp,Pos+StrLen(sp)) {
		if	(SubStr(needle,pos+1,3) = "?P<")	; if subpattern has name flag
			sv .=	(!sv ? "" : ",") SubStr(needle,Pos+4,InStr(needle,">",False,Pos+1)-Pos-4)
		else	sv .=	(!sv ? "" : ",") Sub
		Sub++
	}
	pstartpos :=	startpos
	While	startpos :=	RegExMatch(haystack,needle,m,startpos+StrLen(m)) {
		obj :=	{Str:m,Pos:startpos,Len:StrLen(m)}
		pstartpos :=	RegExMatch(haystack,pneedle,p,pstartpos+StrLen(p))
		Loop, parse, sv, `,
		{
			tm :=	m%A_LoopField%, tp :=	pPos%A_LoopField%
			obj[A_LoopField] :=	{Str:tm,Pos:tp,Len:StrLen(tm)}
		}
		res[(Count :=	A_Index)] :=	obj 
	}
	res["Count"] :=	Count
	return	res   
}


sinkfaze
  • Moderators
  • 6367 posts
  • Last active: Nov 30 2018 08:50 PM
  • Joined: 18 Mar 2008
Here are a couple more simple functions for strings. The first is Case() which is used to convert strings to lower, upper or title case:

MsgBox % Case("hello world!").T
MsgBox % Case("no typing in all caps byotch!!!").U
return

Case()

Case(text) {
	StringLower, l, text 
	StringUpper, u, text
	StringUpper, t, text, T
	return	Object("L",l,"U",u,"T",t)
}

The other is StrMatch(), which will return the number of matches of the search text in a string and return the position of all of the matches:

t=The quick brown fox jumped over the lazy dog and the cat ran away with the spoon.
s=the
m :=	StrMatch(t,s)
res :=	m.Count " instances of " s " were found:`n"
For k,v in m
	if	k is digit
		res .=	"`nPosition of Match " A_Index ":`t" v
MsgBox %	res
res=
return

StrMatch()

StrMatch(text,searchtext,CaseSensitive=False,startpos=1) {
	StringReplace, text, text, %searchtext%, %searchtext%, UseErrorLevel
	res :=	Object("Count",ErrorLevel)
	Loop %	res.Count
		res[A_Index] :=	InStr(text,searchtext,CaseSensitive,startpos,A_Index)
	return	res
}

20110104 - Corrected typo in Case() function (thanks hoppfrosch)


hoppfrosch
  • Members
  • 399 posts
  • Last active: Feb 26 2016 05:31 AM
  • Joined: 25 Jan 2006
There is a typo in your Case()-function:

Case(text) {
   StringLower, l, text
   StringUpper, [color=red]u[/color], text
   StringUpper, t, text, T
   return   Object("L",l,"U",u,"T",t)
}


sinkfaze
  • Moderators
  • 6367 posts
  • Last active: Nov 30 2018 08:50 PM
  • Joined: 18 Mar 2008
Corrected and noted, thanks.

hoppfrosch
  • Members
  • 399 posts
  • Last active: Feb 26 2016 05:31 AM
  • Joined: 25 Jan 2006
BTW: Great work ...

I've got to look into objects more detailled to get full possibilities they offer . :)

sinkfaze
  • Moderators
  • 6367 posts
  • Last active: Nov 30 2018 08:50 PM
  • Joined: 18 Mar 2008
Here's another function Control() to retrieve basic information about a control from a window. Example usage:

ctrl := Control("Edit1","ahk_class Notepad") ; make sure you have Notepad open 
For k,v in ctrl
	if	v
		res .=   (!res ? "" : "`n") k ": " v
MsgBox, Control information for Edit1:`n`n%res% 
return

MsgBox %	Control("Edit1","ahk_class Notepad").ExStyle
return

Control()

Control(ctrl,wintitle="",wintext="",extitle="",extext="") {   ; ctrl will accept either control's name or hWnd

	if	ctrl is not integer
		ControlGet, ctrlhWnd, Hwnd, , %ctrl%, %wintitle%, %wintext%, %extitle%, %extitle%
	else	ctrlhWnd :=	ctrl
	ControlGetFocus, focus, %wintitle%, %wintext%, %extitle%, %extitle%
	ControlGetPos, x, y, w, h, , ahk_id %ctrlhWnd%
	res :=	{x:x,y:y,Width:w,Height:h,Focus:(focus=ctrl) ? 1 : 0,hWnd:ctrlHwnd}
	For each, attr in ["Enabled","Visible","Style","ExStyle"]
	{
		ControlGet, out, %attr%, , , ahk_id %ctrlhWnd%
		res.Insert(attr,out)
	}
	return	res
}

I'm a bit undecided on what to do with control commands specifically related to ListBox/ComboBox/DropDownList/Checkbox/Radio button/Edit controls. It would seem logical to lump them into the same function but it just feels impractical. I'm thinking towards an object which would retrieve all information about all controls in a particular window, but I can't think of any particularly reliable way to identify these "special" controls in advance so they can retrieve the additional information. Any advice or input from users interested in these functions about the above mentioned issues would be more than welcome.

20110105 - changed 'Checked' to 'Visible' (thanks Lexikos)

20110107 - Corrected example (thanks Learning One) and modified function to accept the control's hWnd as well as name/ClassNN

20110415 - Updated internal structure of function.


Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

ControlGet, visible, Checked ...

Did you mean Visible?

Btw, it might save a bit of code and time to get the hwnd of the control first, then use ahk_id for the remaining lines. That also guarantees that it won't retrieve information from two different controls, even in the off chance the window happens to change while the script is executing.

fragman
  • Members
  • 1591 posts
  • Last active: Nov 12 2012 08:51 PM
  • Joined: 13 Oct 2009
It would be quite nice to work with GUIs in a more object-oriented manner!

However, I think for Case() it would be more useful to add functions like toLower(), toUpper(), etc. to string objects so you could simply do something like this:
msgbox % "This is a string".toUpper()
I think I have already seen this somewhere on the forums though.

sinkfaze
  • Moderators
  • 6367 posts
  • Last active: Nov 30 2018 08:50 PM
  • Joined: 18 Mar 2008
I decided to go with a change I've been contemplating for the last few days with the WinGetList() function. The function now accepts only 'List' and 'Control' for the type parameter. If 'List' is specified the object now returns the title and hWnd for each matching window; if 'Control' is specified the object now returns the name (aka ClassNN) and hWnd of each control in the selected window. Just as before, 'List' is the default for the type parameter. Example usage:

win := WinGetList("ahk_class Notepad")
MsgBox % "There are " win.MaxIndex() " matching windows."
For k,v in win
  MsgBox % "Window " k " Title: " v.Title "`nWindow " k " hWnd: " v.hWnd
return

ctrl := WinGetList("ahk_class Notepad","Control")
MsgBox % "There are " ctrl.MaxIndex() " controls in the window."
For k,v in ctrl
  MsgBox % "Control " k " Name: " v.Name "`nControl " k " hWnd: " v.hWnd
return