Detecting key held down without keyup/down events

I don't want to bind key up/down to my Frame, as I found the events
aren't always triggered.
Instead I observed that a combination of wx.EVT_CHAR_HOOK and an
accelerator table (on the Frame) has provided a cross-platform way of
doing hotkey events that are always triggered regardless of which
control in the Frame has focus.

I want to detect a user holding a key down for a length of time, so that
I can perform an action the first time that the event is triggered.

To be clearer, in my drawing program, I'm allowing the user to move
selected shapes with the keyboard arrow keys, and want to add an undo
point only at the beginning of the "held down" sequence. When a key is
held down, my hotkey event is called many times.
Assuming the hotkey event has code like this:

       x, y = self.board.GetViewStart()
       x2, y2 = self.board.GetClientSizeTuple()

       map = { wx.WXK_UP: (-1, y - SCROLL_AMOUNT),
                      wx.WXK_DOWN: (-1, y + SCROLL_AMOUNT),
                      wx.WXK_LEFT: (x - SCROLL_AMOUNT, -1),
                      wx.WXK_RIGHT: (x + SCROLL_AMOUNT, -1) }

       x, y = map.get(keycode)[0], map.get(keycode)[1]
      self.board.Scroll(x, y)

      if not self.has_set_undo:
          self.has_set_undo = True
          self.add_undo()

I basically want to add an undo point once per "stream" of events. So I
need to do this with some "guard" boolean flag to True so that the undo
method call is not called each time.
But, I'm having troubling figuring out when to reset the guard flag.

If the user holds down left to move a shape by 200px, waits a second and
then moves it right 50px, that should cause two undo events. But, how? I
figured a timer is involved but I'm a bit unsure how to do it

Cheers all,

···

--
Steven Sproat, BSc
http://www.launchpad.net/whyteboard

Here's the solution I came up with, in the end:

    def __init__(self):
        self.hotkey_pressed = False # for hotkey timer
        self.hotkey_timer = None

   def hotkey(self, event):
        if not self.hotkey_pressed:
            self.hotkey_pressed = True
            self.board.add_undo()
            shape.start_select_action(0)
            self.hotkey_timer = wx.CallLater(300, self.reset_hotkey)
        else:
            self.hotkey_timer.Restart(300)

        shape.move(map.get(code)[0], map.get(code)[1], offset=(0, 0))
        self.board.draw_shape(shape)

    def reset_hotkey(self):
        """Reset the system for the next stream of hotkey up/down events"""
        self.hotkey_pressed = False
        self.board.selected.sort_handles()
        self.board.selected.end_select_action(0)

···

On 5 April 2010 00:51, Steven Sproat <sproaty@gmail.com> wrote:

I don't want to bind key up/down to my Frame, as I found the events
aren't always triggered.
Instead I observed that a combination of wx.EVT_CHAR_HOOK and an
accelerator table (on the Frame) has provided a cross-platform way of
doing hotkey events that are always triggered regardless of which
control in the Frame has focus.

I want to detect a user holding a key down for a length of time, so that
I can perform an action the first time that the event is triggered.

To be clearer, in my drawing program, I'm allowing the user to move
selected shapes with the keyboard arrow keys, and want to add an undo
point only at the beginning of the "held down" sequence. When a key is
held down, my hotkey event is called many times.
Assuming the hotkey event has code like this:

  x, y = self\.board\.GetViewStart\(\)
  x2, y2 = self\.board\.GetClientSizeTuple\(\)

  map = \{ wx\.WXK\_UP: \(\-1, y \- SCROLL\_AMOUNT\),
                 wx\.WXK\_DOWN: \(\-1, y \+ SCROLL\_AMOUNT\),
                 wx\.WXK\_LEFT: \(x \- SCROLL\_AMOUNT, \-1\),
                 wx\.WXK\_RIGHT: \(x \+ SCROLL\_AMOUNT, \-1\) \}

  x, y = map\.get\(keycode\)\[0\], map\.get\(keycode\)\[1\]
 self\.board\.Scroll\(x, y\)

 if not self\.has\_set\_undo:
     self\.has\_set\_undo = True
     self\.add\_undo\(\)

I basically want to add an undo point once per "stream" of events. So I
need to do this with some "guard" boolean flag to True so that the undo
method call is not called each time.
But, I'm having troubling figuring out when to reset the guard flag.

If the user holds down left to move a shape by 200px, waits a second and
then moves it right 50px, that should cause two undo events. But, how? I
figured a timer is involved but I'm a bit unsure how to do it

Cheers all,

--
Steven Sproat, BSc
Whyteboard in Launchpad

--
Steven Sproat, BSc
http://www.launchpad.net/whyteboard

Like I mentioned the other day in another thread, call the timer's Start method each time you get an event that you want to be grouped into a single undo/redo item. That will reset the timer to be N milliseconds in the future each time. When there is finally N milliseconds without one of these events then the timer will expire and the timer event will be sent, and in that handler you can do whatever housekeeping you need.

···

On 4/4/10 4:51 PM, Steven Sproat wrote:

If the user holds down left to move a shape by 200px, waits a second and
then moves it right 50px, that should cause two undo events. But, how? I
figured a timer is involved but I'm a bit unsure how to do it

--
Robin Dunn
Software Craftsman