Jump to content

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

Incrementally switch between windows


  • Please log in to reply
153 replies to this topic
keyboardfreak
  • Members
  • 217 posts
  • Last active: Sep 27 2010 07:21 PM
  • Joined: 09 Oct 2004
Note: I don't use this script anymore, so there will be no more updates, but ezuk created a modifed version which is much better looking, so I suggest you to try that instead.


This is my second day using AutoHotkey and I'm wasting way too much time playing with it. :)

Here's my first more serious script. Maybe others also find it useful. It's a window switcher which shows the matching window titles as you type in your search string incrementally. If only one window is left it is activated immediately (configurable). Otherwise, you can type in more characters (or delete some with backspace) or select between the matching windows using cursor up/down/enter or you can cancel the window with esc.

You can use any substring of any window. For example, if you want to switch to word then you can type rd and there is a good chance word is selected immediately. Or type "notepad" and select quickly between the notepad windows with the cursor.

The idea comes from Emacs where it is used to switch between opened files. After a while it gets addictive, it's so efficient. At least it is my experience with emacs.

Enough talk. Here's the script. There are still some issues with it. I listed them in the TODO section. If you have ideas how to improve the script I'm interested. I'm still a clueless newbie. :D

--

Edit: From now on the latest version of the script will always be here.

Change log:

2006-04-20: Fixed handling of down-arrow and up-arrow keys which was broken by AHK v1.0.38.04.

2005-04-20

[*:dnrinmah]Fixed handling of windows with apostrophe character in the title. Thanks twwilliams!2005-02-18

[*:dnrinmah]Added option to update the list of windows every time the contents of the listbox is updated. This is usually not necessary and it is an overhead which slows down the update of the listbox, so this feature is disabled by default.2005-01-21

[*:dnrinmah]Added option to filter out titles from the window list.
Thanks to Serenity for the idea.2005-01-16

[*:dnrinmah]Windows can be selected with a single click instead of double click.
Thanks Serenity!
[*:dnrinmah]The switcher cannot be moved out of the screen with the left/right arrow keys.
Thanks jtran21!
[*:dnrinmah]Added option for closing the switcher window automatically if the user activates an other window.
It does not work well if activateselectioninbg is enabled, so currently they cannot be enabled together.
Thanks to Serenity for the idea.2005-01-06

[*:dnrinmah]Fixed unwanted selection change when the switcher window is moved horizontally with the arrow keys. Thanks Chris!
[*:dnrinmah]Added required AutoHotkey version
[*:dnrinmah]Added option to show the process name before the window title. The extension (.EXE) is trimmed from the process name. The width of the switcher window has been increased a bit, so that the titles are visible when this option is enabled.

Thanks to mo for the idea.2005-01-02

[*:dnrinmah]The currently selected window can also be activated with a double click. Mouse users can change the activation key to a mouse button (e.g. middle button) and this way the switcher can be operated with the mouse only.
[*:dnrinmah]The switcher window can be moved horizontally with the left/right arrow keys if it blocks the view of windows under it. If you hold the arrow key down instead of pressing it several times the current selection changes in the listbox. I think it's a limitation of AutoHotkey (but Chris is the authority in this question), because the script cannot move the window fast enough and the arrow key presses reach the listbox while the result of the previous Input command is being processed. It's not a big deal, I usually press the right/left arrow key 2-3 times. There is no need to hold it down.2004-12-19

[*:dnrinmah] Windows are activated by id, not by title. No more messing with title match mode, no more problems with windows having the same title, etc.
[*:dnrinmah]Windows with pipe (|) character are handled properly. The pipe characters are simply replaced with dash (-) characters. It causes no problems, since windows are not activated by title anymore.
[*:dnrinmah]If activation in background is enabled the selected window is not activated when the switcher is invoked. Sometimes it was confusing when the active window was changed right after the switcher window appeared.2004-12-12

[*:dnrinmah] The switcher window is always on top. The keyboard focus is grabbed when the switcher is activated and it can be confusing that the keyboard seemingly does not work when the switcher window is behind other windows.
[*:dnrinmah] GUI close event is handled to avoid leaving the keyboard grabbed when the switcher is closed with the mouse.
[*:dnrinmah] Added first letter match mode where the typed search string must match the first letter of words in the window title (only alphanumeric characters are taken into account)

For example, the search string "ad" matches both of these titles:

AutoHotkey - Documentation
Anne's Diary

Thanks to Soapspoon for the idea.
[*:dnrinmah] Added option for activating the currently selected window in the background, so that the user can see which window will be activated. The background activation can be immediate or delayed (by default 300 ms).2004-12-05

[*:dnrinmah]Using exact title match setting to avoid faulty activation when a window's title contains the title of an other window.
Thanks Sandeep!
[*:dnrinmah]Added option for digit shortcuts if there are ten or less windows in the list.
Thanks to Guest for the suggestion.2004-11-27

[*:dnrinmah]autoactiveifonlyone is disabled by default. It takes a bit of time to get used to, so it is disabled for novice users.
[*:dnrinmah]If enabled possible completions are offered when the same unique substring is found in the title of more than one window.

For example, the user typed the string "co" and the list is narrowed to two windows: "Windows Commander" and "Command Prompt". In this case the "command" substring can be completed automatically, so the script offers this completion in square brackets which the user can accept with the TAB key:

co[mmand]

This feature can be confusing for novice users, so it is disabled by default.2004-10-17

[*:dnrinmah] A configurable sound is played if the substring the user typed in does not match any windows
[*:dnrinmah] Cleaned up the script a bit2004-10-13

[*:dnrinmah] Handled case when there is no window on the screen.
[*:dnrinmah] The gui is shown only if necessary.
[*:dnrinmah] Fixed input hanging if there is only one window and autoactivateifonlyone is set.
; 
; iswitchw - Incrementally switch between windows using substrings 
; 
; Required AutoHotkey version: 1.0.25+ 
; 
; When this script is triggered via its hotkey the list of titles of 
; all visible windows appears. The list can be narrowed quickly to a 
; particular window by typing a substring of a window title. 
; 
; When the list is narrowed the desired window can be selected using 
; the cursor keys and Enter. If the substring matches exactly one 
; window that window is activated immediately (configurable, see the 
; "autoactivateifonlyone" variable). 
; 
; The window selection can be cancelled with Esc. 
; 
; The switcher window can be moved horizontally with the left/right 
; arrow keys if it blocks the view of windows under it. 
; 
; The switcher can also be operated with the mouse, although it is 
; meant to be used from the keyboard. A mouse click activates the 
; currently selected window. Mouse users may want to change the 
; activation key to one of the mouse keys. 
; 
; If enabled possible completions are offered when the same unique 
; substring is found in the title of more than one window. 
; 
; For example, the user typed the string "co" and the list is 
; narrowed to two windows: "Windows Commander" and "Command Prompt". 
; In this case the "command" substring can be completed automatically, 
; so the script offers this completion in square brackets which the 
; user can accept with the TAB key: 
; 
;     co[mmand] 
; 
; This feature can be confusing for novice users, so it is disabled 
; by default. 
; 
; 
; For the idea of this script the credit goes to the creators of the 
; iswitchb package for the Emacs editor 
; 
; 
;---------------------------------------------------------------------- 
; 
; User configuration 
; 

; set this to yes if you want to select the only matching window 
; automatically 
autoactivateifonlyone = 

; set this to yes if you want to enable tab completion (see above) 
; it has no effect if firstlettermatch (see below) is enabled 
tabcompletion = 

; set this to yes to enable digit shortcuts when there are ten or 
; less items in the list 
digitshortcuts = 

; set this to yes to enable first letter match mode where the typed 
; search string must match the first letter of words in the 
; window title (only alphanumeric characters are taken into account) 
; 
; For example, the search string "ad" matches both of these titles: 
; 
;  AutoHotkey - Documentation 
;  Anne's Diary 
; 
firstlettermatch = 

; set this to yes to enable activating the currently selected 
; window in the background 
activateselectioninbg = 

; number of milliseconds to wait for the user become idle, before 
; activating the currently selected window in the background 
; 
; it has no effect if activateselectioninbg is off 
; 
; if set to blank the current selection is activated immediately 
; without delay 
bgactivationdelay = 300 

; show process name before window title. 
showprocessname = 

; Close switcher window if the user activates an other window. 
; It does not work well if activateselectioninbg is enabled, so 
; currently they cannot be enabled together. 
closeifinactivated = 

if activateselectioninbg <> 
    if closeifinactivated <> 
    { 
        msgbox, activateselectioninbg and closeifinactivated cannot be enabled together 
        exitapp 
    } 

; List of subtsrings separated with pipe (|) characters (e.g. carpe|diem). 
; Window titles containing any of the listed substrings are filtered out 
; from the list of windows. 
filterlist = 

; Set this yes to update the list of windows every time the contents of the 
; listbox is updated. This is usually not necessary and it is an overhead which 
; slows down the update of the listbox, so this feature is disabled by default. 
dynamicwindowlist = 

; path to sound file played when the user types a substring which 
; does not match any of the windows 
; 
; set this to blank if you don't want a sound 
; 
nomatchsound = %windir%\Media\ding.wav 

if nomatchsound <> 
    ifnotexist, %nomatchsound% 
        msgbox, Sound file %nomatchsound% not found. No sound will be played. 

;---------------------------------------------------------------------- 
; 
; Global variables 
; 
;     numallwin      - the number of windows on the desktop 
;     allwinarray    - array containing the titles of windows on the desktop 
;                      dynamicwindowlist is disabled 
;     allwinidarray  - window ids corresponding to the titles in allwinarray 
;     numwin         - the number of windows in the listbox 
;     idarray        - array containing window ids for the listbox items 
;     orig_active_id - the window ID of the originally active window 
;                      (when the switcher is activated) 
;     prev_active_id - the window ID of the last window activated in the 
;                      background (only if activateselectioninbg is enabled) 
;     switcher_id    - the window ID of the switcher window 
;     filters        - array of filters for filtering out titles 
;                      from the window list 
; 
;---------------------------------------------------------------------- 

AutoTrim, off 

Gui, Add, ListBox, vindex gListBoxClick x6 y11 w300 h250 AltSubmit 
Gui, Add, Text, x6 y264 w50 h20, Search`: 
Gui, Add, Edit, x66 y261 w240 h20, 

if filterlist <> 
{ 
    loop, parse, filterlist, | 
    { 
        filters%a_index% = %A_LoopField% 
    } 
} 

;---------------------------------------------------------------------- 
; 
; I never use the CapsLock key, that's why I chose it. 
; 
CapsLock:: 

search = 
numallwin = 0 
GuiControl,, Edit1 
GoSub, RefreshWindowList 

WinGet, orig_active_id, ID, A 
prev_active_id = %orig_active_id% 

Gui, Show, Center h294 w313, Window Switcher 

; If we determine the ID of the switcher window here then 
; why doesn't it appear in the window list when the script is 
; run the first time? (Note that RefreshWindowList has already 
; been called above). 
; Answer: Because when this code runs first the switcher window 
; does not exist yet when RefreshWindowList is called. 
WinGet, switcher_id, ID, A 
WinSet, AlwaysOnTop, On, ahk_id %switcher_id% 

Loop 
{ 
    if closeifinactivated <> 
        settimer, CloseIfInactive, 200 

    Input, input, L1, {enter}{esc}{backspace}{up}{down}{pgup}{pgdn}{tab}{left}{right} 

    if closeifinactivated <> 
        settimer, CloseIfInactive, off 

    if ErrorLevel = EndKey:enter 
    { 
        GoSub, ActivateWindow 
        break 
    } 

    if ErrorLevel = EndKey:escape 
    { 
        Gui, cancel 

        ; restore the originally active window if 
        ; activateselectioninbg is enabled 
        if activateselectioninbg <> 
            WinActivate, ahk_id %orig_active_id% 

        break 
    } 

    if ErrorLevel = EndKey:backspace 
    { 
        GoSub, DeleteSearchChar 
        continue 
    } 

    if ErrorLevel = EndKey:tab 
        if completion = 
            continue 
        else 
            input = %completion% 

    ; pass these keys to the selector window 

    if ErrorLevel = EndKey:up 
    { 
        Send, {up} 
        GoSuB ActivateWindowInBackgroundIfEnabled 
        continue 
    } 

    if ErrorLevel = EndKey:down 
    { 
        Send, {down} 
        GoSuB ActivateWindowInBackgroundIfEnabled 
        continue 
    } 

    if ErrorLevel = EndKey:pgup 
    { 
        Send, {pgup} 
        GoSuB ActivateWindowInBackgroundIfEnabled 
        continue 
    } 

    if ErrorLevel = EndKey:pgdn 
    { 
        Send, {pgdn} 
        GoSuB ActivateWindowInBackgroundIfEnabled 
        continue 
    } 

    if ErrorLevel = EndKey:left 
    { 
        direction = -1 
        GoSuB MoveSwitcher 
        continue 
    } 

    if ErrorLevel = EndKey:right 
    { 
        direction = 1 
        GoSuB MoveSwitcher 
        continue 
    } 

    ; FIXME: probably other error level cases 
    ; should be handled here (interruption?) 

    ; invoke digit shortcuts if applicable 
    if digitshortcuts <> 
        if numwin <= 10 
            if input in 1,2,3,4,5,6,7,8,9,0 
            { 
                if input = 0 
                    input = 10 

                if numwin < %input% 
                { 
                    if nomatchsound <> 
                        SoundPlay, %nomatchsound% 
                    continue 
                } 

                GuiControl, choose, ListBox1, %input% 
                GoSub, ActivateWindow 
                break 
            } 

    ; process typed character 

    search = %search%%input% 
    GuiControl,, Edit1, %search% 
    GoSub, RefreshWindowList 
} 

Gosub, CleanExit 

return 

;---------------------------------------------------------------------- 
; 
; Refresh the list of windows according to the search criteria 
; 
; Sets: numwin  - see the documentation of global variables 
;       idarray - see the documentation of global variables 
; 
RefreshWindowList: 
    ; refresh the list of windows if necessary 

    if ( dynamicwindowlist = "yes" or numallwin = 0 ) 
    { 
        numallwin = 0 

        WinGet, id, list, , , Program Manager 
        Loop, %id% 
        { 
            StringTrimRight, this_id, id%a_index%, 0 
            WinGetTitle, title, ahk_id %this_id% 

            ; FIXME: windows with empty titles? 
            if title = 
                continue 

            ; don't add the switcher window 
            if switcher_id = %this_id% 
                continue 

            ; show process name if enabled 
            if showprocessname <> 
            { 
                WinGet, procname, ProcessName, ahk_id %this_id% 

                stringgetpos, pos, procname, . 
                if ErrorLevel <> 1 
                { 
                    stringleft, procname, procname, %pos% 
                } 

                stringupper, procname, procname 
                title = %procname%: %title% 
            } 

            ; don't add titles which match any of the filters 
            if filterlist <> 
            { 
                filtered = 

                loop 
                { 
                    stringtrimright, filter, filters%a_index%, 0 
                    if filter = 
                      break 
                    else 
                        ifinstring, title, %filter% 
                        { 
                           filtered = yes 
                           break 
                        } 
                } 

                if filtered = yes 
                    continue 
            } 

            ; replace pipe (|) characters in the window title, 
            ; because Gui Add uses it for separating listbox items 
            StringReplace, title, title, |, -, all 

            numallwin += 1 
            allwinarray%numallwin% = %title% 
            allwinidarray%numallwin% = %this_id% 
        } 
    } 

    ; filter the window list according to the search criteria 

    winlist = 
    numwin = 0 

    Loop, %numallwin% 
    { 
        StringTrimRight, title, allwinarray%a_index%, 0 
        StringTrimRight, this_id, allwinidarray%a_index%, 0 

        ; don't add the windows not matching the search string 
        ; if there is a search string 
        if search <> 
            if firstlettermatch = 
            { 
                if title not contains %search%, 
                    continue 
            } 
            else 
            { 
                stringlen, search_len, search 

                index = 1 
                match = 

                loop, parse, title, %A_Space% 
                {                    
                    stringleft, first_letter, A_LoopField, 1 

                    ; only words beginning with an alphanumeric 
                    ; character are taken into account 
                    if first_letter not in 1,2,3,4,5,6,7,8,9,0,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z 
                        continue 

                    stringmid, search_char, search, %index%, 1 

                    if first_letter <> %search_char% 
                        break 

                    index += 1 

                    ; no more search characters 
                    if index > %search_len% 
                    { 
                        match = yes 
                        break 
                    } 
                } 

                if match = 
                    continue    ; no match 
            } 

        if winlist <> 
            winlist = %winlist%| 
        winlist = %winlist%%title%`r%this_id% 

        numwin += 1 
        winarray%numwin% = %title% 
    } 

    ; if the pattern didn't match any window 
    if numwin = 0 
        ; if the search string is empty then we can't do much 
        if search = 
        { 
            Gui, cancel 
            Gosub, CleanExit 
        } 
        ; delete the last character 
        else 
        { 
            if nomatchsound <> 
                SoundPlay, %nomatchsound% 

            GoSub, DeleteSearchChar 
            return 
        } 

    ; sort the list alphabetically 
    Sort, winlist, D| 

    ; add digit shortcuts if there are ten or less windows 
    ; in the list and digit shortcuts are enabled 
    if digitshortcuts <> 
        if numwin <= 10 
        { 
            digitlist = 
            digit = 1 
            loop, parse, winlist, | 
            { 
                ; FIXME: windows with empty title? 
                if A_LoopField <> 
                { 
                    if digitlist <> 
                        digitlist = %digitlist%| 
                    digitlist = %digitlist%%digit%%A_Space%%A_Space%%A_Space%%A_LoopField% 

                    digit += 1 
                    if digit = 10 
                        digit = 0 
                } 
            } 
            winlist = %digitlist% 
        } 

    ; strip window IDs from the sorted list 
    titlelist = 
    arrayindex = 1 

    loop, parse, winlist, | 
    { 
        stringgetpos, pos, A_LoopField, `r 

        stringleft, title, A_LoopField, %pos% 
        titlelist = %titlelist%|%title% 

        pos += 2 ; skip the separator char 
        stringmid, id, A_LoopField, %pos%, 10000 
        idarray%arrayindex% = %id% 
        ++arrayindex 
    } 

    ; show the list 
    GuiControl,, ListBox1, %titlelist% 
    GuiControl, Choose, ListBox1, 1 

    if numwin = 1 
        if autoactivateifonlyone <> 
        { 
            GoSub, ActivateWindow 
            Gosub, CleanExit 
        } 

    GoSub ActivateWindowInBackgroundIfEnabled 

    completion = 

    if tabcompletion = 
        return 

    ; completion is not implemented for first letter match mode 
    if firstlettermatch <> 
        return 

    ; determine possible completion if there is 
    ; a search string and there are more than one 
    ; window in the list 

    if search = 
        return 
    
    if numwin = 1 
        return 

    loop 
    { 
        nextchar = 

        loop, %numwin% 
        { 
            stringtrimleft, title, winarray%a_index%, 0 

            if nextchar = 
            { 
                substr = %search%%completion% 
                stringlen, substr_len, substr 
                stringgetpos, pos, title, %substr% 

                if pos = -1 
                    break 

                pos += %substr_len% 

                ; if the substring matches the end of the 
                ; string then no more characters can be completed 
                stringlen, title_len, title 
                if pos >= %title_len% 
                { 
                    pos = -1 
                    break 
                } 

                ; stringmid has different position semantics 
                ; than stringgetpos. strange... 
                pos += 1 
                stringmid, nextchar, title, %pos%, 1 
                substr = %substr%%nextchar% 
             } 
             else 
             { 
                stringgetpos, pos, title, %substr% 
                if pos = -1 
                    break 
             } 
        } 

        if pos = -1 
            break 
        else 
            completion = %completion%%nextchar% 
    } 

    if completion <> 
        GuiControl,, Edit1, %search%[%completion%] 

return 

;---------------------------------------------------------------------- 
; 
; Delete last search char and update the window list 
; 
DeleteSearchChar: 

if search = 
    return 

StringTrimRight, search, search, 1 
GuiControl,, Edit1, %search% 
GoSub, RefreshWindowList 

return 

;---------------------------------------------------------------------- 
; 
; Activate selected window 
; 
ActivateWindow: 

Gui, submit 
stringtrimleft, window_id, idarray%index%, 0 
WinActivate, ahk_id %window_id% 

return 

;---------------------------------------------------------------------- 
; 
; Activate selected window in the background 
; 
ActivateWindowInBackground: 

guicontrolget, index,, ListBox1 
stringtrimleft, window_id, idarray%index%, 0 

if prev_active_id <> %window_id% 
{ 
    WinActivate, ahk_id %window_id% 
    WinActivate, ahk_id %switcher_id% 
    prev_active_id = %window_id% 
} 

return 

;---------------------------------------------------------------------- 
; 
; Activate selected window in the background if the option is enabled. 
; If an activation delay is set then a timer is started instead of 
; activating the window immediately. 
; 
ActivateWindowInBackgroundIfEnabled: 

if activateselectioninbg = 
    return 

; Don't do it just after the switcher is activated. It is confusing 
; if active window is changed immediately. 
WinGet, id, ID, ahk_id %switcher_id% 
if id = 
    return 

if bgactivationdelay = 
    GoSub ActivateWindowInBackground 
else 
    settimer, BgActivationTimer, %bgactivationdelay% 

return 

;---------------------------------------------------------------------- 
; 
; Check if the user is idle and if so activate the currently selected 
; window in the background 
; 
BgActivationTimer: 

settimer, BgActivationTimer, off 

GoSub ActivateWindowInBackground 

return 

;---------------------------------------------------------------------- 
; 
; Stop background window activation timer if necessary and exit 
; 
CleanExit: 

settimer, BgActivationTimer, off 

exit 

;---------------------------------------------------------------------- 
; 
; Cancel keyboard input if GUI is closed. 
; 
GuiClose: 

send, {esc} 

return 

;---------------------------------------------------------------------- 
; 
; Handle mouse click events on the list box 
; 
ListBoxClick: 
if (A_GuiControlEvent = "Normal"
    and !GetKeyState("Down", "P") and !GetKeyState("Up", "P"))
    send, {enter} 
return 

;---------------------------------------------------------------------- 
; 
; Move switcher window horizontally 
; 
; Input: direction - 1 for right, -1 for left 
; 
MoveSwitcher: 

direction *= 100 
WinGetPos, x, y, width, , ahk_id %switcher_id% 
x += %direction% 

if x < 0 
    x = 0 
else 
{ 
   SysGet screensize, MonitorWorkArea 
   screensizeRight -= %width% 
   if x > %screensizeRight% 
      x = %screensizeRight% 
} 

prevdelay = %A_WinDelay% 
SetWinDelay, -1 
WinMove, ahk_id %switcher_id%, , %x%, %y% 
SetWinDelay, %prevdelay% 

return 

;---------------------------------------------------------------------- 
; 
; Close the switcher window if the user activated an other window 
; 
CloseIfInactive: 

ifwinnotactive, ahk_id %switcher_id% 
    send, {esc} 

return
[/b]

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Very nice. Here are a few suggestions, some of which address your comments and questions:

Consider allowing a doubleclick on an item in the box to activate that window. Rarely used, but maybe nice to have. This can be done via A_GuiControlEvent

As you asked, I think think there are much better ways to gather input than making all those letter/number hotkeys. The best way is probably have an Edit control at the top of the list. Then have a timer that checks its contents periodically:
SetTimer, WatchEdit, 100
....
return

WatchEdit:
GuiControlGet, MyEdit   ; MyEdit is the name of the var associated with the edit.
if MyEdit <> %MyEditPrev%  ; Edit has changed
{
     MyEditPrev = %MyEdit%
     ... take action to update list or activate window ...
}
return
If the above isn't suitable, you could try using the Input command instead.

Finally, as you know, it is probably better to pick a better default hotkey than capslock.

Thanks for sharing this wonderful script.

  • Guests
  • Last active:
  • Joined: --

Consider allowing a doubleclick on an item in the box to activate that window. Rarely used, but maybe nice to have.

Added to the TODO list. A bit reluctantly though, because this thing is meant to be used via the keyboard.

As you asked, I think think there are much better ways to gather input than making all those letter/number hotkeys. The best way is probably have an Edit control at the top of the list. Then have a timer that checks its contents periodically

The main problem with this is that the Edit control has the focus, so the user can't navigate the listbox easily without swithcing the focus manually first.

If the above isn't suitable, you could try using the Input command instead.

Input is just what I needed. Works fine.

Finally, as you know, it is probably better to pick a better default hotkey than capslock.

Any suggestion? By the way, I think the golden rule of configuring the keyboard is to make frequent operations simple (single keybindings).

The CapsLock is an underused key. I don't use it, noone I know uses it for its original purpose. It's only function is being hit accidentally which causes problems when entering passwords. It's a big, easy to hit button which I never use, that's why I chose it.

Thanks for sharing this wonderful script.

Thanks for creating AutoHotkey. It's a dream come true. :)

I still have issues with the script. Since I switched to using Input updating of the text label with the current search string does not work. Any idea why?
;
; iswitchw - Incrementally switch between windows using substrings
;
;
; TODO:
;
;    - Why doesn't setting the search text with ControlSetText work?
;
;    - Why doesn't getting the search text with ControlGetText work?
;      if fixed there is no need to pass the search param to RefreshWindowList
;
;    - Relying on generated control names (e.g. Static1) is not robust
;      Can we specify them?
;
;    - Handle doubleclick selection on the listbox
;

SetTitleMatchMode, 2
wintitle = Window Switcher

; set this to blank if you don't want to select the only matching window
; automatically
autoswitchifonlyone = yes

Gui, Add, ListBox, vWindow x6 y11 w270 h250,
Gui, Add, Text, x6 y261 w50 h20, Search`:
Gui, Add, Text, x66 y261 w210 h20, 

;----------------------------------------------------------------------

CapsLock::

Gui, Show, x428 y215 h294 w285, %wintitle%
search = 
ControlSetText, Static2, 
GoSub, RefreshWindowList

Loop
{
    Input, input, L1, {enter}{esc}{backspace}{up}{down}{pgup}{pgdn}

    If ErrorLevel = EndKey:enter
    {
        Gui, submit
        WinActivate, %window%
        Break
    }

    If ErrorLevel = EndKey:escape
    {
        Gui, cancel
        Break
    }

    If ErrorLevel = EndKey:backspace
    {
        IfNotEqual, search
            GoSub, DeleteSearchChar
        Continue
    }

    ; pass these keys to the listbox control 

    If ErrorLevel = EndKey:up
    {
        Send, {up}
        Continue
    }

    If ErrorLevel = EndKey:down
    {
        Send, {down}
        Continue
    }

    If ErrorLevel = EndKey:pgup
    {
        Send, {pgup}
        Continue
    }

    If ErrorLevel = EndKey:pgdn
    {
        Send, {pgdn}
        Continue
    }

    ; FIXME: probably other error level cases
    ; should be handled here (interruption?)

    search = %search%%input%
    ControlSetText, Static2, %search%
    GoSub, RefreshWindowList

    IfEqual, continue
        Break
}

return

;----------------------------------------------------------------------
;
; Refresh the list of windows according to the search criteria
;
; Parameter:  search - the current search string
;
; Return value: continue - continue getting more characters
;
RefreshWindowList:
    ; FIXME: why doesn't it work?
    ;ControlGetText, search2, Static2

    winlist = |
    onlyone =

    WinGet, id, list, , , Program Manager
    Loop, %id%
    {
        StringTrimRight, this_id, id%a_index%, 0
        WinGetTitle, title, ahk_id %this_id%

        ; don't add windows with empty titles
        IfEqual, title
            Continue

        ; don't add the switcher window
        If title contains %wintitle%
            Continue

        ; don't add the windows not matching the search string
        ; if there is a search string
        IfNotEqual, search
            If title not contains %search%
                Continue

        ; store if it is the only window in the list
        ; FIXME: this is nasty, need a simpler solution
        IfEqual, winlist, |
            onlyone = %title%
        else
            onlyone = 

        winlist = %winlist%%title%|
    }

    ; if the pattern didn't match any window, delete the last char
    IfEqual, winlist, |
        GoSub, DeleteSearchChar

    ; show the list
    Sort, winlist, D|
    GuiControl,, ListBox1, %winlist%
    GuiControl, Choose, ListBox1, 1

    continue = yes

    ; select the only window if configured
    IfNotEqual, winlist, |
        IfNotEqual, onlyone
            IfNotEqual, autoswitchifonlyone, 
            {
                Gui, submit
                WinActivate, %window%
                continue =
            }
return

;----------------------------------------------------------------------
;
; Delete last search char and update the window list
;
DeleteSearchChar:
StringTrimRight, search, search, 1
ControlSetText, Static2, %search%
GoSub, RefreshWindowList
return


Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004

The main problem with this is that the Edit control has the focus, so the user can't navigate the listbox easily without swithcing the focus manually first.
...
Input is just what I needed. Works fine.

Oh I see. I didn't realize you intended the listbox to be naviated via the arrow keys. Good point.

The CapsLock is an underused key. I don't use it, noone I know uses it for its original purpose. It's only function is being hit accidentally which causes problems when entering passwords. It's a big, easy to hit button which I never use, that's why I chose it.

I do use capslock periodically for its original purpose and didn't realize it was common for some users never to use it. By the way, I do use Capslock as prefix for hotkeys, which doesn't disrupt it's original function. Example: Capslock & A::MsgBox You pressed A while holding down Capslock. Thanks for explaining why you chose Capslock.


updating of the text label with the current search string does not work. Any idea why?

That happens because the last found window is not set like it was in version 1. I suggest the following replacement (in two places) since it's probably easier to use than ControlSetText:
GuiControl,, Static2, %search%

I tried the new version and it's a nice improvement because it does not auto-close when the window is submitted like the first version. This allows it to be used repeatedly.

By the way, if you want me to include this in the script showcase, I'll make a note to do so (though I'm a little backlogged in that department currently).

keyboardfreak
  • Members
  • 217 posts
  • Last active: Sep 27 2010 07:21 PM
  • Joined: 09 Oct 2004

Oh I see. I didn't realize you intended the listbox to be naviated via the arrow keys.

That's the point. Narrowing down the list of windows with a substring and either hitting the desired window immediately or quickly select a window from the short list.

I tried the new version and it's a nice improvement because it does not auto-close when the window is submitted like the first version. This allows it to be used repeatedly.

I don't really follow you here. :) What do you mean by no "auto-close"?

By default, the script activates the matching window immediately if there is exactly one match and hides the selector window. The idea behind this is that I want to switch to an other window quickly and don't want to deal with the selector window manually.

You can switch it off if you don't like it setting by the autoactivateifonlyone variable to blank.

Did you mean this or something else?

By the way, if you want me to include this in the script showcase, I'll make a note to do so (though I'm a little backlogged in that department currently).

I'm a newbie here, so you should know better than me if a script is worthful for inclusion in the showcase. I certainly don't mind. It's an honour. :)

Here's the updated script. Thanks to you the todo list is much shorter. I think the first TODO entry (generated control names) cannot be fixed in the script, so that entry can be deleted. Do you agree?

Edit: See the latest version of the script in the first post.

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004

I don't really follow you here. :) What do you mean by no "auto-close"?

My mistake. I thought the first version closed after one usage, requiring you to restart the script. I see that this isn't the case.

I'm a newbie here, so you should know better than me if a script is worthful for inclusion in the showcase. I certainly don't mind. It's an honour. :)

I think it's pretty slick. And if you keep improving it, you might have to patent it :)

the first TODO entry (generated control names) cannot be fixed in the script, so that entry can be deleted. Do you agree?

That can be changed with tomorrow's release, which will allow a unique variable to be assigned to a control upon creation. Example:

Gui, add, text, vMyText, Its Contents.
...
GuiControl,, MyText, Here are the new contents.

savage
  • Members
  • 207 posts
  • Last active: Jul 03 2008 03:12 AM
  • Joined: 02 Jul 2004
OOOH NEATO! This looks pretty cool, I'm big on quick window management. I'll have to try this out.

jack
  • Members
  • 75 posts
  • Last active: Oct 29 2010 08:33 PM
  • Joined: 04 Sep 2004
it seems to work better if i add a winrestore to catch those windows that are minimized:

If ErrorLevel = EndKey:enter 
    { 
        Gui, submit 
        WinRestore, %window% 
        WinActivate, %window% 
        Break 
    }



jack
we don't believe in the if and maybe scenario

jack
  • Members
  • 75 posts
  • Last active: Oct 29 2010 08:33 PM
  • Joined: 04 Sep 2004
nope, i take it back. it's fine as it is.

jack
The Author asserts the moral right to be indentified as the author of this work

keyboardfreak
  • Members
  • 217 posts
  • Last active: Sep 27 2010 07:21 PM
  • Joined: 09 Oct 2004
A few changes:
* Handled case when there is no window on the screen.
* The gui is shown only if necessary.
* Fixed input hanging if there is only one window and autoactivateifonlyone is set.I was thinking about creating automatic unit tests in the form of an other autohotkey script to run common test cases automatically, so that I can see if anything breaks when I change something in the script, but I managed to resist the temptation. :)

Edit: See the latest version of the script in the first post.

BoBo
  • Guests
  • Last active:
  • Joined: --

I managed to resist the temptation


For the first time I thought about to get a Tatoo :D :D :D

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
It's looking great. One small problem is that you might have forgotten braces {} around the block here:
if search <> 

    StringTrimRight, search, search, 1 

    GuiControl,, Edit1, %search% 

    GoSub, RefreshWindowList


keyboardfreak
  • Members
  • 217 posts
  • Last active: Sep 27 2010 07:21 PM
  • Joined: 09 Oct 2004
Yes, I have. It might be the python influence. :) I saw your answer in the bug report forum, but made other changes to the script and forgot this. It does not cause an infinite loop anymore, so it's not a big deal. I'll fix it in the next version.

keyboardfreak
  • Members
  • 217 posts
  • Last active: Sep 27 2010 07:21 PM
  • Joined: 09 Oct 2004
A new version. Changes:

* A configurable sound is played if the substring the user typed in does not match any windows
* Cleaned up the script a bit
Edit: See the latest version of the script in the first post.

dijiyd
  • Members
  • 87 posts
  • Last active: Oct 22 2005 01:29 PM
  • Joined: 31 Mar 2004
Very nice. Man, my integrated scripts file's getting big.

I have something to say though.. (might be moot) I was kind of confused at first, because I clicked the edit control. After that, I couldn't use the up/down key to switch between entries.

And after I think about it, that is the point eh? Not to use the mouse? So, never mind.