Jump to content

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

Yet another hotkey multiple press function


  • Please log in to reply
8 replies to this topic
Relayer
  • Members
  • 122 posts
  • Last active: Jun 22 2015 09:21 PM
  • Joined: 24 Nov 2008
I know this has been done before. For whatever reason I really liked what I came up with here and thought I'd share it.

Relayer

/*
	Function: MultiPress(actionList = "", delay = 500)

A function to cleanly implement the response to more than one pressing of a hotkey.
A "clean" approach because:
	- only one static variable is maintained by the function
	- the function returns promptly because it does not manage the delay period
	- one timer subroutine in the calling script services all instances of use
	- the timer is transient and does not consume resources when not needed
	- coding for a given hotkey is compact and very readable
	
Limitation:
	- cannot "interlace" key presses, i.e. have one hotkey with a very long delay and then
	expect a second hotkey with a short delay to function in between presses of the first
	
How it works:
	- Invoke the function with a list of labels corresponding to all service routines for
	that particular hotkey.  This is just a comma separated list as a string in the order
	corresponding to number of presses (first position for 1 press, second position for
	2 presses and so on).
	- Default delay period to allow between key presses is 500 msec, but can be as long
	as you want.
	- Each press of the hotkey calls the function which counts the number of key
	presses of the same key honoring the delay period.  Exceeding the delay period between
	presses restarts the counter at 1.
	- The function returns the label that corresponds to the number of times the hotkey
	was pressed and at the same time starts a timer whose routine is in the main script.
	- The timer routine checks to make sure the label exists, calls that label and turns
	itself OFF.
	- Optionally, the action list can be empty in which case the function simply returns the
	number of hotkey presses made honoring the delay period.  The timer is not started.

	
Example 1:

This is the timer subroutine.  Its name must match the name in the function.  The variable
"Action" is the variable that is equated to the function call that will contain the label of
the routine to execute for that number of key presses. Place one instance of this in the
calling script.

Hotkey subroutines invoke the function passing a list of subroutine names corresponding
to the services for the number of key presses made inside of the delay period.  Note
the first parameter is a non-existing label that will do nothing.  In this example the
result is to render one press of F6 to do nothing but it will respond to 2, 3, & 4 presses.

	TakeAction:
		SetTimer, TakeAction, Off
		if (IsLabel(Action))
			Gosub, %Action%
	Return


	F6::
		Action := MultiPress("null, Manny, Moe, Jack")
	Return
		Manny:
			Tooltip, This is Manny!
		Return
		Moe:
			Tooltip, Moe here!
		Return
		Jack:
			Tooltip, Jack present and accounted for!
		Return

	
Example 2:

In this example the hotkey routine simply asks for the number of presses made that honor
a two second delay period and takes appropriate action on its own.  Note that this only
works when there is a single definition of N presses for the hotkey behavior.
Use the previous example when multiple behaviors are required for a given hotkey.

	F7::
	if (MultiPress("", 2000) = 3)
		Msgbox, holy keypresses Batman!
	Return

*/


MultiPress(actionList = "", delay = 500)
{
	Static pressCount := 0

	pressCount := ( ((A_PriorHotKey = "") || (A_ThisHotKey = A_PriorHotKey))
					&& (A_TimeSincePriorHotkey < delay) ) ? (pressCount + 1) : (1)
				
	if (actionList = "")  ;this option flags to just return count to caller
		Return pressCount

	SetTimer, TakeAction, % delay
	Loop, Parse, actionList, `,, %A_Space%
		if (A_Index = pressCount)
			Return A_LoopField

	Return False
}



klownboy
  • Members
  • 87 posts
  • Last active: May 24 2014 11:21 AM
  • Joined: 20 Jul 2012
Hey Relayer,
Nice script! The F7 example worked fine, but Example 1 for the F6 multi-press script did not work until I changed the names of the sub routines labels to: Manny, Moe, and Jack, instead of Tom, Dick and Harry. I'm so new to AHK I wasn't sure initially if it was intentional. If not, you may want to edit the example. I realize you said to call out the timer subroutine "takeaction:" in the example, but if it's always necessary, why not remove the comment from that section so the not so gifted won't be confused on how to use your script. :)
Thanks for the nice script,
Ken

Relayer
  • Members
  • 122 posts
  • Last active: Jun 22 2015 09:21 PM
  • Joined: 24 Nov 2008
Ooooops...

Thanks. I changed the names just before posting it. I changed the messages and my brain said "I'm done".

I'm glad you like the script. It's not rocket science as they say but it allows me to quickly set up hotkeys with a multitude of functions easily.

I added one small thing the you may find useful. For multiple presses of greater than 3 I sometimes lose track of how many. So, I added a tooltip that presents the count briefly. Since the function also has the list of service routines, you could alternatively pop the name of the service up as the key is pressed.

MultiPress(actionList = "", delay = 500)
{
	Static pressCount := 0

	pressCount := ( ((A_PriorHotKey = "") || (A_ThisHotKey = A_PriorHotKey))
					&& (A_TimeSincePriorHotkey < delay) ) ? (pressCount + 1) : (1)

	if (actionList = "")  ;this option flags to just return count to caller
		Return pressCount

	Tooltip, %pressCount%
	SetTimer, TakeAction, % delay
	Loop, Parse, actionList, `,, %A_Space%
		if (A_Index = pressCount)
			Return A_LoopField

	Return False
}

The tooltip is then cleared by the global service in the calling script:
TakeAction:
	SetTimer, TakeAction, Off
	Tooltip
	if (IsLabel(Action))
		Gosub, %Action%
Return


klownboy
  • Members
  • 87 posts
  • Last active: May 24 2014 11:21 AM
  • Joined: 20 Jul 2012
Hey again Relayer,
Adding the Tooltip was a nice idea. I played with the times a bit (i.e., two seconds down to one) along with the "default delay period to allow between key presses" from 500 to 300 msec . I noticed the F7 example (single definition of N presses) is much more responsive than the F6 example with multiple behaviors multiple, but taking the times down seems to help that quite a bit. I personnaly would just as soon hit the keys 2 or 3 times as fast as possible anyway.
Thanks again,
Ken

Relayer
  • Members
  • 122 posts
  • Last active: Jun 22 2015 09:21 PM
  • Joined: 24 Nov 2008
klownboy:

Yes, the F7 example will be much more responsive because it triggers the service as soon as it hits the number of required presses. The F6 example needs to wait to see if there will be an additional press before triggering the appropriate service routine. That delay is set by the "TakeAction" timer.

So, the only function of the delay parameter in the F7 example is to disqualify a series of presses that is too slow to be deemed valid. In contrast the F6 delay parameter does the same thing but enforces the need of a delay expiration before triggering.

Relayer

klownboy
  • Members
  • 87 posts
  • Last active: May 24 2014 11:21 AM
  • Joined: 20 Jul 2012
Hi Relayer,
I tested your "yet another hotkey" against some other similar hotkery functions like Morse http://www.autohotke...pic.php?t=16951 and your apprears to be faster performing similar actions.
By the way, when you edited your script I think the tooltip for the following portion of the code (actually just an example) is not working correctly. The tooltip (e.g., "This is Manny" or "Moe here!" stays on the screen after the script finishes until you manually exit the script. I suppose it needs another call of tooltip? The press count tooltip does work and close as it should.
Manny:
         Tooltip, This is Manny!
      Return
      Moe:
         Tooltip, Moe here!
      Return
      Jack:
         Tooltip, Jack present and accounted for!
      Return
Didn't you originally use a Message Box instead of tooltip in this example where you had to turn press OK to close the box?
Thanks again,
Ken

Relayer
  • Members
  • 122 posts
  • Last active: Jun 22 2015 09:21 PM
  • Joined: 24 Nov 2008
klownboy,

Yes, I forgot to add the command "tooltip" (with no parameters) to the TakeAction subroutine to remove the tooltip in my example.

Relayer

P.S. I like a lot of things I've written in AHK but I have to blow my own horn here for this function and construct for multiple presses of the same hotkey. I've been using it since the original post and now I can add functionality to my scripts at the speed of light with this. I'm loving it more and more each day.

klownboy
  • Members
  • 87 posts
  • Last active: May 24 2014 11:21 AM
  • Joined: 20 Jul 2012
Hey Relayer, I've been getting some good use out of your MultiPress. Thanks. As I said, before it's quite robust as compared to some of the others out there and there are certainly plenty of them. I'm still relatively new to AHK, but I've experimented on numerous multiple click or press functions.

I've also experimented with your MultiPress checking the speed diferences between double clicking only with a key to perform actions, but retain the original function of the key in this case Numpad0 when pressed once. My first example is using Multipress with a single definition of key presses (as in your F7 example) which as we discussed, would be quicker method if that's all we were defining for that key. Using this method, this was the only way I could get the Numpad0 key, when pressed just once, to just send "0" and at the same time NOT get a "0" in a text editor when I double clicked. I couldn't get it to work if I simply sent Numpad0 using send or send input Numpad0. Obviously this method is basically invoking Multipress twice, once for each method, which is probably not the best idea... but then again it works. Disregard the times I've been playing with a range of them.
      $Numpad0::
        if (MultiPress("", 300) = 2)
             {
               ;bunch of actions based on whether a program winexists and/or winactive...
             }
              else
              Action := MultiPress("1press, ")
              ;send {Numpad0}         just sending a Numpad0 here would not work.... 
         Return
      
      1press:     
            send {Numpad0}
            return
The other method I experimented with was similar to your F6 example where I wanted to maintain the function of the Numpad0 (in this case) when it was pressed just once. It worked just fine, though as discussed, may not be as robust for a simple double click definition.
$Numpad0::
   Action := MultiPress("1press, 2press, ")
   Return

1press:     
             sendInput {Numpad0}
             return

2press:
             {
               ;bunch of actions based on whether a program winexists and/or winactive...
             }
      return

Have you done any experimenting with the above first example (your F7 example in your original post) with a double click action and yet have the key retain it's original function/action and at the same time NOT send a Numpad0 when you double click (i.e., perform the action in the hotkey assignment only).

Thanks again,
Ken

SouzaRM
  • Members
  • 12 posts
  • Last active: Jul 26 2018 01:32 PM
  • Joined: 26 Nov 2008

Thanks ReLayer!


Fight, Fight and Fight