; =============================================================================
;
; Find Classes by Text
; Written by Alex Peters, 4/3/2006
;
; Lists the ClassNameNNs of a window grouped by the displayed text. Useful for
; determining the ClassNameNN of e.g. a text control when AutoIt Window Info
; cannot help (due for instance to overlapping controls on the window).
;
; =============================================================================


#Include <GUIConstants.au3>

Opt('GUIOnEventMode', True)
Opt('MustDeclareVars', True)
Opt('WinWaitDelay', 0)

; Variables to be accessed by event-handling functions.
Global Enum $HAN_GUI, $HAN_TREE, $HAN_BTN, $HAN_COUNT
Global $Handles[$HAN_COUNT]
Global $CapturedTitle = '[No window has been captured]'
Global $Capturing = False

; GUI positioning constants.
Global Const $PADDING = 12
Global Const $BTN_HEIGHT = 40


; =============================================================================

PrepareGUI()

While True
	WinWaitNotActive($Handles[$HAN_GUI])
	; Another window is active. Capture it if appropriate.
	If $Capturing Then
		Beep(400, 50)
		; Grab title for display on button.
		$CapturedTitle = WinGetTitle('')
		; Get the information and build a TreeView.
		Local $TextClasses = WinGetClassesByText(WinGetHandle(''))
		BuildTree($TextClasses)
		; Return to normal operation mode.
		ExitCaptureMode()
	EndIf
	WinWaitActive($Handles[$HAN_GUI])
WEnd


; =============================================================================
; PrepareGUI():
;     Creates and shows the GUI and its base controls.
; =============================================================================

Func PrepareGUI()

	; Create the window.
	$Handles[$HAN_GUI] = GUICreate('Find Classes By Text', _
			@DesktopWidth / 2, @DesktopHeight / 2, Default, Default, _
			BitOR($GUI_SS_DEFAULT_GUI, $WS_SIZEBOX, $WS_MAXIMIZEBOX))
	GUISetOnEvent($GUI_EVENT_RESIZED, 'Event_GUIResize')
	GUISetOnEvent($GUI_EVENT_CLOSE, 'Event_GUIClose')

	; Create the Capture button.
	$Handles[$HAN_BTN] = GUICtrlCreateButton($CapturedTitle, _
			Default, Default, Default, Default, $BS_MULTILINE)
	GUICtrlSetResizing($Handles[$HAN_BTN], _
			$GUI_DOCKLEFT + $GUI_DOCKRIGHT + $GUI_DOCKTOP + $GUI_DOCKHEIGHT)
	GUICtrlSetOnEvent($Handles[$HAN_BTN], 'Event_BtnCapture')

	; Arrange everything nicely.
	RepositionControls()

	; Show the GUI.
	GUISetState()

EndFunc

; =============================================================================
; BuildTree():
;   Creates a TreeView control containing the specified text snippets and
;   associated ClassNameNNs.
; =============================================================================

Func BuildTree(Const ByRef $TextClasses)

	; Delete any existing TreeView; this is the easiest way to get rid of all
	; existing window data.
	If $Handles[$HAN_TREE] <> '' Then GUICtrlDelete($Handles[$HAN_TREE])

	; Create a new TreeView.
	$Handles[$HAN_TREE] = GUICtrlCreateTreeView(0, 0, 0, 0, _
			$GUI_SS_DEFAULT_TREEVIEW, $WS_EX_CLIENTEDGE)
	GUICtrlSetResizing($Handles[$HAN_TREE], $GUI_DOCKBORDERS)

	; Keep everything nicely arranged.
	RepositionControls()

	; Populate with text snippets and associated ClassNameNNs.
	For $I = 1 To $TextClasses[0][0]
		Local $TextNode = GUICtrlCreateTreeViewItem( _
				"'" & $TextClasses[$I][0] & "'", $Handles[$HAN_TREE])
		Local $Classes = $TextClasses[$I][1]
		While StringInStr($Classes, @LF)
			GUICtrlCreateTreeViewItem( _
					StringLeft($Classes, StringInStr($Classes, @LF) - 1), _
					$TextNode)
			$Classes = StringTrimLeft($Classes, StringInStr($Classes, @LF))
		WEnd
		GUICtrlCreateTreeViewItem($Classes, $TextNode)
	Next

EndFunc


; =============================================================================
; RepositionControls():
;     Aligns the GUI controls nicely after a resize or after a new control is
;     created.
; =============================================================================

Func RepositionControls()

	Local Const $Area = WinGetClientSize($Handles[$HAN_GUI])
	Local Const $MaxWidth = $Area[0] - 2 * $PADDING
	Local Const $MaxHeight = $Area[1] - 2 * $PADDING

	GUICtrlSetPos($Handles[$HAN_BTN], _
			$PADDING, $PADDING, _
			$MaxWidth, $BTN_HEIGHT)

	If $Handles[$HAN_TREE] <> '' Then GUICtrlSetPos($Handles[$HAN_TREE], _
			$PADDING, 2 * $PADDING + $BTN_HEIGHT, _
			$MaxWidth, $MaxHeight - $BTN_HEIGHT - $PADDING)

EndFunc


; =============================================================================
; Event_GUIClose():
;     Called when the GUI is asked to close (e.g. when the user clicks the X).
; =============================================================================

Func Event_GUIClose()

	Exit

EndFunc


; =============================================================================
; Event_GUIResize():
;     Called after the user has completed a resize operation on the GUI.
; =============================================================================

Func Event_GUIResize()

	RepositionControls()

EndFunc


; =============================================================================
; Event_BtnCapture():
;     Called when the Capture button is clicked, and enters or exits capturing
;     mode as appropriate.
; =============================================================================

Func Event_BtnCapture()

	If $Capturing Then
		ExitCaptureMode()
	Else
		EnterCaptureMode()
	EndIf

EndFunc


; ==============================================================================
; WinGetClassesByText():
;     Returns a text/class list in the form of a two-dimensional array. Element
;     [0][0] contains a count of following text/class pairs. Element [X][0]
;     holds the text and element [X][1] holds an @LF-delimited list of
;     ClassNameNNs sharing that text.
; ==============================================================================

Func WinGetClassesByText($Title, $Text = '')

	Local $Classes = WinGetControlIDs($Title, $Text)
	Local $Texts[$Classes[0] + 1][2]
	$Texts[0][0] = 0

	For $I = 1 To $Classes[0]
		AddClass($Texts, ControlGetText($Title, $Text, $Classes[$I]), $Classes[$I])
	Next

	Return $Texts

EndFunc


; ==============================================================================
; WinGetControlIDs():
;     Returns an array of ClassNameNNs for a window where element 0 is a count.
; ==============================================================================

Func WinGetControlIDs($sTitle, $sText = '')

	Local $avClasses[1], $iCounter, $sClasses, $sClassStub, $sClassStubList

	; Request an unnumbered class list.
	$sClassStubList = WinGetClassList($sTitle, $sText)

	; Return an empty response if no controls exist.
	; Additionally set @Error if the specified window was not found.
	If $sClassStubList = '' Then
		If @Error Then SetError(1)
		$avClasses[0] = 0
		Return $avClasses
	EndIf

	; Prepare an array to hold the numbered classes.
	ReDim $avClasses[StringLen($sClassStubList) - _
			StringLen(StringReplace($sClassStubList, @LF, '')) + 1]

	; The first element will contain a count.
	$avClasses[0] = 0

	; Count each unique class, enumerate them in the array and remove them from
	; the string.
	Do
		$sClassStub = _
				StringLeft($sClassStubList, StringInStr($sClassStubList, @LF))
		$iCounter = 0
		While StringInStr($sClassStubList, $sClassStub)
			$avClasses[0] += 1
			$iCounter += 1
			$avClasses[$avClasses[0]] = _
					StringTrimRight($sClassStub, 1) & $iCounter
			$sClassStubList = _
					StringReplace($sClassStubList, $sClassStub, '', 1)
		WEnd
	Until $sClassStubList = ''

	Return $avClasses

EndFunc


; ==============================================================================
; AddClass():
;     Adds a class to a text entry in the given text/class list. If the given
;     text is not already contained then a new element is created.
; ==============================================================================

Func AddClass(ByRef $Texts, $Text, $Class)

	For $I = 1 To $Texts[0][0]
		If $Text == $Texts[$I][0] Then
			$Texts[$I][1] &= @LF & $Class
			Return
		EndIf
	Next

	; This point is reached if the text doesn't already exist in the list.
	$Texts[0][0] += 1
	$Texts[$Texts[0][0]][0] = $Text
	$Texts[$Texts[0][0]][1] = $Class

EndFunc

; ==============================================================================
; EnterCaptureMode():
;     Performs whatever is necessary when a capture is initiated.
; ==============================================================================

Func EnterCaptureMode()

	$Capturing = True
	GUICtrlSetData($Handles[$HAN_BTN], _
			'[Activate window to be captured or click to cancel]')

EndFunc


; ==============================================================================
; ExitCaptureMode():
;     Performs whatever is necessary when a capture is cancelled or completed.
; ==============================================================================

Func ExitCaptureMode()

	$Capturing = False
	GUICtrlSetData($Handles[$HAN_BTN], $CapturedTitle)

EndFunc