AutoHotkey Community

It is currently May 26th, 2012, 5:28 am

All times are UTC [ DST ]




Post new topic Reply to topic  [ 5 posts ] 
Author Message
 Post subject: Hotkey_IfControlActive()
PostPosted: September 3rd, 2007, 4:15 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7501
Location: Australia
Hotkey_IfControlActive()
Description:
Facilitates making hotkeys specific to a control or class of control.

Compatibility:
Windows 98* minimum, tested on Vista with AutoHotkey v1.0.47.04.
* Based on minimum requirements of SetWinEventHook and GetGUIThreadInfo.

It still needs more testing, and perhaps feature improvements.

How it works:
SetWinEventHook is used to hook focus events. Hotkeys are enabled via the Hotkey command when their associated control or type of control is focused, and disabled on unfocus.
Code:
;
; AutoHotkey Version: 1.0.47
; Language:       English
; Platform:       Win98/NT
; Author:         Lexikos
;
; Script Function:
;   Facilitates making hotkeys specific to a control or class of control.
;
; Version History:
;   --      Initial release (no version number.)
;   1.1     Function now sets the initial state of the hotkey correctly.
;

; ControlDesc:
;   Control, WinTitle   -- ala ControlGet. Currently, Control cannot contain a comma.
;   Control             -- ClassNN, or any control of this class.
;
; KeyName:
;   Name of the hotkey.
;
; VariantType:
;   Any sub-command supported by Hotkey,IfWin..  ("IfWinActive", "IfWinExist", etc.)
;
; VariantTitle, VariantText:
;   The parameters of the Hotkey,IfWin.. sub-command.
;
; If VariantType, etc. are omitted, the default variant is used.
; If VariantType is an empty string, which variant is used is undefined.
Hotkey_IfControlActive(ControlDesc, KeyName, VariantType="IfWinActive", VariantTitle="", VariantText="")
{
    global H_ICA_List
    static hFocusHook
   
    d1:=chr(1), d2:=chr(2), d3:=chr(3) ; delimiters

    if (!KeyName
        or Hotkey_IfControlActive_InStrAny(ControlDesc KeyName VariantType VariantTitle VariantText, d1 d2 d3))
    {   ; Above: Ensures none of the parameters include any of the delimiters.
        ErrorLevel = Params
        return false
    }
    ; Note: ControlDesc may be blank, indicating IfControlActive should
    ;       no longer be applied to it.

    if (!hFocusHook)
    {   ; Hook focus changes system-wide.
        hFocusHook := DllCall("SetWinEventHook"
            , "uint", 0x8005, "uint", 0x8005  ; EVENT_OBJECT_FOCUS
            , "uint", 0
            , "uint", RegisterCallback("Hotkey_IfControlActive_FocusHook")
            , "uint", 0  ; idProcess (0=all)
            , "uint", 0
            , "uint", 0) ; dwflags = WINEVENT_OUTOFCONTEXT
        if (!hFocusHook) {
            ErrorLevel = Hook
            return false
        }
    }

    if (focus_hwnd := Hotkey_IfControlActive_GetFocus())
    {   ; Set initial state of hotkey based on focused control.
        focus_CNN := Hotkey_IfControlActive_GetClassNN(focus_hwnd)
        is_match := Hotkey_IfControlActive_Match(ControlDesc, focus_hwnd, focus_CNN)
        if VariantType
            Hotkey, %VariantType%, %VariantTitle%, %VariantText%
        Hotkey, %KeyName%, % is_match ? "On" : "Off"
    }

    header := d1 ControlDesc d2
    item := KeyName d3 VariantType d3 VariantTitle d3 VariantText

    if (i := InStr(H_ICA_List, header, true))
    {
        i += StrLen(header)
        H_ICA_List := SubStr(H_ICA_List, 1, i-1)
            . item d2
            . SubStr(H_ICA_List, i)
    }
    else
        H_ICA_List .= header . item
   
    return true
}

Hotkey_IfControlActive_FocusChanged(focus, focus_CNN, from, from_CNN)
{
    global H_ICA_List
   
    d1:=chr(1), d2:=chr(2), d3:=chr(3) ; delimiters
   
    Loop, Parse, H_ICA_List, %d1%  ; for each unique ControlDesc
    {
        if (A_LoopField="") ; initial delimiter
            continue
       
        i := InStr(A_LoopField, d2)
        if (!i)
            continue
       
        c := SubStr(A_LoopField, 1, i-1)
        is_match := Hotkey_IfControlActive_Match(c, focus, focus_CNN)
        was_match := Hotkey_IfControlActive_Match(c, from, from_CNN)
       
        if (was_match != is_match)
        {
            s := SubStr(A_LoopField, i+1)
            Loop, Parse, s, %d2%  ; for each hotkey
            {
                ; KeyName d3 VariantType d3 VariantTitle d3 VariantText
                StringSplit, s, A_LoopField, %d3%
               
                ; Hotkey, IfWin..
                if s2
                    Hotkey, %s2%, %s3%, %s4%
               
                Hotkey, %s1%, % is_match ? "On" : "Off"
            }
        }
    }
}

Hotkey_IfControlActive_Match(ControlDesc, hwnd, ClassNN)
{
    if !(hwnd or classNN)
        return false

    ; ClassNN
    if (ControlDesc = ClassNN)
        return true
   
    ; Exact Class
    WinGetClass, Class, ahk_id %hwnd%
    if (ControlDesc == Class)
        return true
   
    ; Control, WinTitle
    StringSplit, s, ControlDesc, `, %A_Space%%A_Tab%
    ControlGet, this, Hwnd,, %s1%, %s2%
    return (this = hwnd)
}


Hotkey_IfControlActive_InStrAny(str, any)
{
    Loop, Parse, str
        if InStr(any, A_LoopField)
            return true
    return false
}

; Occurs when focusing a window, control, menu item, ListView item, etc.
Hotkey_IfControlActive_FocusHook(hHook, event, hwnd, idObject, idChild, evtThread, evtTime)
{
    static last_focus, last_focus_CNN
   
    ; Active window may be hidden. For example, when AHK shows a menu,
    ; the AutoHotkey main window is "Active", but still hidden.
    DetectHiddenWindows, On

    if (WinExist("A"))
    {
        ; ControlGetFocus causes problems, maybe because of AttachThreadInput()?
        ;ControlGetFocus, focus_CNN
       
        ; 'hwnd' is sometimes the parent/ancestor of the control with focus.
        if !(focus_hwnd := Hotkey_IfControlActive_GetFocus())
            focus_hwnd := hwnd
        focus_CNN  := Hotkey_IfControlActive_GetClassNN(focus_hwnd)
           
        if (last_focus != focus_hwnd) {
            Hotkey_IfControlActive_FocusChanged(focus_hwnd, focus_CNN, last_focus, last_focus_CNN)
            last_focus := focus_hwnd
            last_focus_CNN := focus_CNN
        }
    }
}

Hotkey_IfControlActive_GetClassNN(hwnd)
{
    WinGet, list, ControlList
    Loop, Parse, list, `n
    {
        ControlGet, this, Hwnd,, %A_LoopField%
        if (this = hwnd)
            return A_LoopField
    }
}

; Min OS: Windows 98, Windows NT 4.0 SP3
; (SetWinEventHook requires minimum Windows 98 anyway.)
Hotkey_IfControlActive_GetFocus()
{
    VarSetCapacity(info, 48, 0)
    NumPut(48, info, 0)
    return (DllCall("GetGUIThreadInfo", "uint", 0, "uint", &info)
            && !(NumGet(info, 4) & 4)) ; ! GUI_INMENUMODE
        ? NumGet(info, 12) : 0  ; hwndFocus
}
Implementation Notes:
Since the hwnd of the focus event isn't always the focused control (sometimes it's an ancestor), I originally used ControlGetFocus. This caused some odd behaviour with Explorer - essentially preventing double-click from working with previously unfocused items. I believe this is because ControlGetFocus uses AttachThreadInput() (and GetFocus()) internally - using GetGUIThreadInfo() instead of ControlGetFocus solved this issue.

Example:
This example uses my Edit Control Functions (required for the example to work) to implement a few features I often missed when using standard Edit controls/Notepad. (Straight from my hotkey script):
Code:
; Hotkey_IfControlActive(ControlDesc, KeyName [, VariantType, VariantTitle, VariantText])

; hkEditInit is called by my main hotkey script.
hkEditInit:
    Hotkey_IfControlActive("Edit", "^BS")
    Hotkey_IfControlActive("Edit", "^Delete")
    Hotkey_IfControlActive("Edit", "+Delete")
    Hotkey_IfControlActive("Edit", "$^c")
    Hotkey_IfControlActive("Edit", "$^x")
    Hotkey_IfControlActive("Edit", "$^v")
return

; NOTE: This even works on Edit controls super-imposed on ListViews!
;   (e.g. when renaming a file in Explorer)

; TODO: Improve word selection (to be more 'selective')

; Delete word left/right.
^BS::       Send % Edit_TextIsSelected("A") ? "{Delete}" : "^+{Left}{Delete}"
^Delete::   Send % Edit_TextIsSelected("A") ? "{Delete}" : "^+{Right}{Delete}"

; Delete line.
+Delete::
    if (Edit_TextIsSelected("A"))
        Send {Delete}
    else
        Edit_DeleteLine(0, "A")
return

; Copy line.
$^c::
    if (Edit_TextIsSelected("A"))
        Send ^c
    else
        Edit_CopyLine()
return

; Cut line.
$^x::
    if (Edit_TextIsSelected("A") or Edit_SelectLine(0, true, "A"))
        Send ^x
return

; Paste line.
$^v::   Send % (SubStr(Clipboard,0)="`n") ? "{Home}^v" : "^v"

Edit_CopyLine()
{
    ControlGetFocus, focus, A
    if (SubStr(focus,1,4)="Edit") {
        ControlGet, line, CurrentLine,, %focus%, A
        ControlGet, line, Line, %line%, %focus%, A
        Clipboard := line "`r`n"
    }
}
Variant example:
Note that "IfWinActive" doesn't affect the behaviour of the hotkey, it only specifies which variant of the hotkey to affect. This example causes ^BS (Notepad variant) to be enabled only while Edit1 is focused.
Code:
Hotkey_IfControlActive("Edit1", "^BS", "IfWinActive", "ahk_class Notepad")

#IfWinActive ahk_class Notepad
^BS::Send ^+{Left}{Delete}
If you're thinking that isn't useful, you're probably right. ;) However, the hotkey would be disabled when Notepad's menu is active (or the Font dialog, etc.).


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: January 4th, 2008, 11:47 am 
Offline

Joined: May 24th, 2006, 2:49 pm
Posts: 4511
Location: Belgrade
I didn't know about this. Thanks. I added it to Custom GUI Controls sticky along with Edit functions and some other things.

_________________
Image


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: April 8th, 2008, 2:12 am 
Offline

Joined: June 1st, 2006, 7:39 am
Posts: 14
I really like this idea since I also need to setup some hotkeys when only a specific control is focused.

However, the example code about setting ^BS in notepad crashes on my win2003, any idea?

call stack shows:

> 0005b6e0()
user32.dll!___ClientCallWinEventProc@4() + 0x2a bytes
ntdll.dll!_KiUserCallbackDispatcher@12() + 0x2e bytes
ntdll.dll!_KiUserCallbackDispatcher@12() + 0x2e bytes
user32.dll!_NtUserPeekMessage@20() + 0xc bytes


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: April 8th, 2008, 3:29 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7501
Location: Australia
I suppose 0005b6e0 is the address of the callback function. Do you have DEP enabled? It has been recently confirmed that DEP prevents AutoHotkey's callbacks from working. I have suggested a workaround, but afaik it hasn't been tested yet.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: April 8th, 2008, 3:37 am 
Offline

Joined: June 1st, 2006, 7:39 am
Posts: 14
Thank you!

Yes DEP is the root cause of this issue. I disabled DEP for autohotkey.exe and it now works correctly.


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 posts ] 

All times are UTC [ DST ]


Who is online

Users browsing this forum: Google [Bot], Google Feedfetcher, Stigg and 12 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group