Browse Source

implement MouseWatcher::inactivity_timeout

David Rose 19 years ago
parent
commit
e6077a603c

+ 6 - 0
panda/src/event/buttonEvent.cxx

@@ -42,6 +42,10 @@ output(ostream &out) const {
     out << "button " << _button << " up";
     out << "button " << _button << " up";
     break;
     break;
 
 
+  case T_repeat:
+    out << "button " << _button << " repeat";
+    break;
+
   case T_keystroke:
   case T_keystroke:
     out << "keystroke " << _keycode;
     out << "keystroke " << _keycode;
     break;
     break;
@@ -70,6 +74,7 @@ write_datagram(Datagram &dg) const {
   case T_down:
   case T_down:
   case T_resume_down:
   case T_resume_down:
   case T_up:
   case T_up:
+  case T_repeat:
     // We write the button name.  This is not particularly compact, but
     // We write the button name.  This is not particularly compact, but
     // presumably we don't get thousands of button events per frame, and
     // presumably we don't get thousands of button events per frame, and
     // it is robust as the button index may change between sessions but
     // it is robust as the button index may change between sessions but
@@ -108,6 +113,7 @@ read_datagram(DatagramIterator &scan) {
   case T_down:
   case T_down:
   case T_resume_down:
   case T_resume_down:
   case T_up:
   case T_up:
+  case T_repeat:
     _button = ButtonRegistry::ptr()->get_button(scan.get_string());
     _button = ButtonRegistry::ptr()->get_button(scan.get_string());
     break;
     break;
 
 

+ 6 - 0
panda/src/event/buttonEvent.h

@@ -70,6 +70,12 @@ public:
     T_resume_down,
     T_resume_down,
     T_up,
     T_up,
 
 
+    // T_repeat is sent for each a keyrepeat event generated by the
+    // system, for a button that is continually held down.  If you
+    // want to respect keyrepeat, treat T_down and T_repeat
+    // equivalently.
+    T_repeat,
+
     // T_keystroke is a special keystroke event, and is sent along
     // T_keystroke is a special keystroke event, and is sent along
     // with a Unicode keycode value, not a ButtonHandle.
     // with a Unicode keycode value, not a ButtonHandle.
     T_keystroke,
     T_keystroke,

+ 21 - 0
panda/src/event/buttonEventList.I

@@ -26,6 +26,27 @@ INLINE ButtonEventList::
 ButtonEventList() {
 ButtonEventList() {
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEventList::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE ButtonEventList::
+ButtonEventList(const ButtonEventList &copy) :
+  _events(copy._events)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEventList::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void ButtonEventList::
+operator = (const ButtonEventList &copy) {
+  _events = copy._events;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonEventList::add_event
 //     Function: ButtonEventList::add_event
 //       Access: Public
 //       Access: Public

+ 2 - 0
panda/src/event/buttonEventList.h

@@ -41,6 +41,8 @@ class DatagramIterator;
 class EXPCL_PANDA ButtonEventList : public EventStoreValueBase {
 class EXPCL_PANDA ButtonEventList : public EventStoreValueBase {
 public:
 public:
   INLINE ButtonEventList();
   INLINE ButtonEventList();
+  INLINE ButtonEventList(const ButtonEventList &copy);
+  INLINE void operator = (const ButtonEventList &copy);
 
 
   INLINE void add_event(ButtonEvent event);
   INLINE void add_event(ButtonEvent event);
   INLINE int get_num_events() const;
   INLINE int get_num_events() const;

+ 26 - 0
panda/src/pgui/pgItem.I

@@ -351,6 +351,19 @@ get_press_prefix() {
   return "press-";
   return "press-";
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_repeat_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the repeat
+//               event for all PGItems.  The repeat event is the
+//               concatenation of this string followed by a button
+//               name, followed by a hyphen and get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string PGItem::
+get_repeat_prefix() {
+  return "repeat-";
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::get_release_prefix
 //     Function: PGItem::get_release_prefix
 //       Access: Published, Static
 //       Access: Published, Static
@@ -465,6 +478,19 @@ get_press_event(const ButtonHandle &button) const {
   return get_press_prefix() + button.get_name() + "-" + get_id();
   return get_press_prefix() + button.get_name() + "-" + get_id();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_repeat_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item is active and the indicated mouse or keyboard
+//               button is continuously held down while the mouse is
+//               within the frame.
+////////////////////////////////////////////////////////////////////
+INLINE string PGItem::
+get_repeat_event(const ButtonHandle &button) const {
+  return get_repeat_prefix() + button.get_name() + "-" + get_id();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::get_release_event
 //     Function: PGItem::get_release_event
 //       Access: Published
 //       Access: Published

+ 6 - 1
panda/src/pgui/pgItem.cxx

@@ -583,7 +583,12 @@ void PGItem::
 press(const MouseWatcherParameter &param, bool background) {
 press(const MouseWatcherParameter &param, bool background) {
   if (!background) {
   if (!background) {
     PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
     PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
-    string event = get_press_event(param.get_button());
+    string event;
+    if (param.is_keyrepeat()) {
+      event = get_repeat_event(param.get_button());
+    } else {
+      event = get_press_event(param.get_button());
+    }
     play_sound(event);
     play_sound(event);
     throw_event(event, EventParameter(ep));
     throw_event(event, EventParameter(ep));
   }
   }

+ 2 - 0
panda/src/pgui/pgItem.h

@@ -139,6 +139,7 @@ PUBLISHED:
   INLINE static string get_focus_in_prefix();
   INLINE static string get_focus_in_prefix();
   INLINE static string get_focus_out_prefix();
   INLINE static string get_focus_out_prefix();
   INLINE static string get_press_prefix();
   INLINE static string get_press_prefix();
+  INLINE static string get_repeat_prefix();
   INLINE static string get_release_prefix();
   INLINE static string get_release_prefix();
   INLINE static string get_keystroke_prefix();
   INLINE static string get_keystroke_prefix();
 
 
@@ -149,6 +150,7 @@ PUBLISHED:
   INLINE string get_focus_in_event() const;
   INLINE string get_focus_in_event() const;
   INLINE string get_focus_out_event() const;
   INLINE string get_focus_out_event() const;
   INLINE string get_press_event(const ButtonHandle &button) const;
   INLINE string get_press_event(const ButtonHandle &button) const;
+  INLINE string get_repeat_event(const ButtonHandle &button) const;
   INLINE string get_release_event(const ButtonHandle &button) const;
   INLINE string get_release_event(const ButtonHandle &button) const;
   INLINE string get_keystroke_event() const;
   INLINE string get_keystroke_event() const;
 
 

+ 11 - 0
panda/src/putil/buttonHandle.I

@@ -30,6 +30,17 @@ INLINE ButtonHandle::
 ButtonHandle() {
 ButtonHandle() {
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonHandle::Constructor
+//       Access: Published
+//  Description: Constructs a ButtonHandle with the corresponding
+//               index number, which may have been returned by an
+//               earlier call to ButtonHandle::get_index().
+////////////////////////////////////////////////////////////////////
+INLINE ButtonHandle::
+ButtonHandle(int index) : _index(index) {
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonHandle::Copy Constructor
 //     Function: ButtonHandle::Copy Constructor
 //       Access: Published
 //       Access: Published

+ 1 - 0
panda/src/putil/buttonHandle.h

@@ -30,6 +30,7 @@
 class EXPCL_PANDA ButtonHandle {
 class EXPCL_PANDA ButtonHandle {
 PUBLISHED:
 PUBLISHED:
   INLINE ButtonHandle();
   INLINE ButtonHandle();
+  INLINE ButtonHandle(int index);
 
 
 public:
 public:
   INLINE ButtonHandle(const ButtonHandle &copy);
   INLINE ButtonHandle(const ButtonHandle &copy);

+ 38 - 0
panda/src/tform/buttonThrower.I

@@ -72,6 +72,38 @@ get_button_up_event() const {
   return _button_up_event;
   return _button_up_event;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonThrower::set_button_repeat_event
+//       Access: Published
+//  Description: Specifies the generic event that is generated (if
+//               any) repeatedly while a key or button is held down.
+//               Unlike the specific events that are unique to each
+//               key, this same event name is used for *all* button
+//               events, and the name of the button pressed (possibly
+//               with modifier prefixes) will be sent as a parameter.
+//
+//               If this string is empty, no event is generated.  It
+//               is possible to generate both generic events and
+//               specific events for the same button.
+//
+//               See also set_keystroke_event().
+////////////////////////////////////////////////////////////////////
+INLINE void ButtonThrower::
+set_button_repeat_event(const string &button_repeat_event) {
+  _button_repeat_event = button_repeat_event;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonThrower::get_button_repeat_event
+//       Access: Published
+//  Description: Returns the button_repeat_event that has been set on
+//               this ButtonThrower.  See set_button_repeat_event().
+////////////////////////////////////////////////////////////////////
+INLINE const string &ButtonThrower::
+get_button_repeat_event() const {
+  return _button_repeat_event;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonThrower::set_keystroke_event
 //     Function: ButtonThrower::set_keystroke_event
 //       Access: Published
 //       Access: Published
@@ -83,6 +115,12 @@ get_button_up_event() const {
 //               together will generate the button event "shift-4",
 //               together will generate the button event "shift-4",
 //               but it will generate the keystroke "$".
 //               but it will generate the keystroke "$".
 //
 //
+//               If a key is held down, keyrepeat will cause the same
+//               keystroke event to be generated repeatedly.  This is
+//               different from the corresponding down event, which
+//               will only be generated once, followed by a number of
+//               button repeat events.
+//
 //               This event is generated with a single wstring
 //               This event is generated with a single wstring
 //               parameter, which is a one-character string that
 //               parameter, which is a one-character string that
 //               contains the keystroke generated.  If this event
 //               contains the keystroke generated.  If this event

+ 10 - 2
panda/src/tform/buttonThrower.cxx

@@ -307,6 +307,10 @@ do_general_event(const ButtonEvent &button_event, const string &button_name) {
     event_name = _button_up_event;
     event_name = _button_up_event;
     break;
     break;
 
 
+  case ButtonEvent::T_repeat:
+    event_name = _button_repeat_event;
+    break;
+
   case ButtonEvent::T_keystroke:
   case ButtonEvent::T_keystroke:
     event_name = _keystroke_event;
     event_name = _keystroke_event;
     break;
     break;
@@ -388,7 +392,7 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &input,
       const ButtonEvent &be = button_events->get_event(i);
       const ButtonEvent &be = button_events->get_event(i);
       string event_name = be._button.get_name();
       string event_name = be._button.get_name();
 
 
-      if (be._type == ButtonEvent::T_down) {
+      if (be._type == ButtonEvent::T_down || be._type == ButtonEvent::T_repeat) {
         // Button down.
         // Button down.
         if (!_mods.button_down(be._button)) {
         if (!_mods.button_down(be._button)) {
           // We only prepend modifier names on the button-down events,
           // We only prepend modifier names on the button-down events,
@@ -398,7 +402,11 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &input,
 
 
         if (!_throw_buttons_active || has_throw_button(_mods, be._button)) {
         if (!_throw_buttons_active || has_throw_button(_mods, be._button)) {
           // Process this button.
           // Process this button.
-          do_specific_event(event_name, be._time);
+          if (be._type == ButtonEvent::T_repeat) {
+            do_specific_event(event_name + "-repeat", be._time);
+          } else {
+            do_specific_event(event_name, be._time);
+          }
           do_general_event(be, event_name);
           do_general_event(be, event_name);
           
           
         } else {
         } else {

+ 3 - 0
panda/src/tform/buttonThrower.h

@@ -49,6 +49,8 @@ PUBLISHED:
   INLINE const string &get_button_down_event() const;
   INLINE const string &get_button_down_event() const;
   INLINE void set_button_up_event(const string &button_up_event);
   INLINE void set_button_up_event(const string &button_up_event);
   INLINE const string &get_button_up_event() const;
   INLINE const string &get_button_up_event() const;
+  INLINE void set_button_repeat_event(const string &button_repeat_event);
+  INLINE const string &get_button_repeat_event() const;
   INLINE void set_keystroke_event(const string &keystroke_event);
   INLINE void set_keystroke_event(const string &keystroke_event);
   INLINE const string &get_keystroke_event() const;
   INLINE const string &get_keystroke_event() const;
   INLINE void set_candidate_event(const string &candidate_event);
   INLINE void set_candidate_event(const string &candidate_event);
@@ -92,6 +94,7 @@ private:
 private:
 private:
   string _button_down_event;
   string _button_down_event;
   string _button_up_event;
   string _button_up_event;
+  string _button_repeat_event;
   string _keystroke_event;
   string _keystroke_event;
   string _candidate_event;
   string _candidate_event;
   string _move_event;
   string _move_event;

+ 1 - 1
panda/src/tform/driveInterface.cxx

@@ -422,7 +422,7 @@ do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &input,
     for (int i = 0; i < num_events; i++) {
     for (int i = 0; i < num_events; i++) {
       const ButtonEvent &be = button_events->get_event(i);
       const ButtonEvent &be = button_events->get_event(i);
       if (be._type != ButtonEvent::T_keystroke) {
       if (be._type != ButtonEvent::T_keystroke) {
-        bool down = (be._type == ButtonEvent::T_down);
+        bool down = (be._type != ButtonEvent::T_up);
         
         
         if (be._button == KeyboardButton::up()) {
         if (be._button == KeyboardButton::up()) {
           _up_arrow.set_key(down);
           _up_arrow.set_key(down);

+ 100 - 0
panda/src/tform/mouseWatcher.I

@@ -138,6 +138,17 @@ get_over_region(float x, float y) const {
   return get_over_region(LPoint2f(x, y));
   return get_over_region(LPoint2f(x, y));
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::is_button_down
+//       Access: Published
+//  Description: Returns true if the indicated button is currently
+//               being held down, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool MouseWatcher::
+is_button_down(ButtonHandle button) const {
+  return _inactivity_state != IS_inactive && _current_buttons_down.get_bit(button.get_index());
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::set_button_down_pattern
 //     Function: MouseWatcher::set_button_down_pattern
 //       Access: Published
 //       Access: Published
@@ -193,6 +204,39 @@ get_button_up_pattern() const {
   return _button_up_pattern;
   return _button_up_pattern;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::set_button_repeat_pattern
+//       Access: Published
+//  Description: Sets the pattern string that indicates how the event
+//               names are generated when a button is continuously
+//               held and generates keyrepeat "down" events.  This is
+//               a string that may contain any of the following:
+//
+//                  %r  - the name of the region the mouse is over
+//                  %b  - the name of the button pressed.
+//
+//               The event name will be based on the in_pattern
+//               string specified here, with all occurrences of the
+//               above strings replaced with the corresponding values.
+////////////////////////////////////////////////////////////////////
+INLINE void MouseWatcher::
+set_button_repeat_pattern(const string &pattern) {
+  _button_repeat_pattern = pattern;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::get_button_repeat_pattern
+//       Access: Published
+//  Description: Returns the string that indicates how event names are
+//               names are generated when a button is continuously
+//               held and generates keyrepeat "down" events.  See
+//               set_button_repeat_pattern().
+////////////////////////////////////////////////////////////////////
+INLINE const string &MouseWatcher::
+get_button_repeat_pattern() const {
+  return _button_repeat_pattern;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::set_enter_pattern
 //     Function: MouseWatcher::set_enter_pattern
 //       Access: Published
 //       Access: Published
@@ -463,6 +507,62 @@ has_display_region() const {
   return (_display_region != (DisplayRegion *)NULL);
   return (_display_region != (DisplayRegion *)NULL);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::set_inactivity_timeout
+//       Access: Published
+//  Description: Sets an inactivity timeout on the mouse activity.
+//               When this timeout (in seconds) is exceed with no
+//               keyboard or mouse activity, all currently-held
+//               buttons are automatically released.  This is intended
+//               to help protect against people who inadvertently (or
+//               intentionally) leave a keyboard key stuck down and
+//               then wander away from the keyboard.
+////////////////////////////////////////////////////////////////////
+INLINE void MouseWatcher::
+set_inactivity_timeout(double timeout) {
+  _has_inactivity_timeout = true;
+  _inactivity_timeout = timeout;
+  note_activity();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::has_inactivity_timeout
+//       Access: Published
+//  Description: Returns true if an inactivity timeout has been set,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool MouseWatcher::
+has_inactivity_timeout() const {
+  return _has_inactivity_timeout;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::get_inactivity_timeout
+//       Access: Published
+//  Description: Returns the inactivity timeout that has been set.
+//               It is an error to call this if
+//               has_inactivity_timeout() returns false.
+////////////////////////////////////////////////////////////////////
+INLINE double MouseWatcher::
+get_inactivity_timeout() const {
+  nassertr(_has_inactivity_timeout, 0.0);
+  return _inactivity_timeout;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::clear_inactivity_timeout
+//       Access: Published
+//  Description: Removes the inactivity timeout and restores the
+//               MouseWatcher to its default behavior of allowing a
+//               key to be held indefinitely.
+////////////////////////////////////////////////////////////////////
+INLINE void MouseWatcher::
+clear_inactivity_timeout() {
+  _has_inactivity_timeout = false;
+  _inactivity_timeout = 0.0;
+  note_activity();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::within_region
 //     Function: MouseWatcher::within_region
 //       Access: Protected
 //       Access: Protected

+ 175 - 41
panda/src/tform/mouseWatcher.cxx

@@ -44,7 +44,7 @@ TypeHandle MouseWatcher::_type_handle;
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 MouseWatcher::
 MouseWatcher::
 MouseWatcher(const string &name) : 
 MouseWatcher(const string &name) : 
-  DataNode(name) 
+  DataNode(name)
 {
 {
   _pixel_xy_input = define_input("pixel_xy", EventStoreVec2::get_class_type());
   _pixel_xy_input = define_input("pixel_xy", EventStoreVec2::get_class_type());
   _pixel_size_input = define_input("pixel_size", EventStoreVec2::get_class_type());
   _pixel_size_input = define_input("pixel_size", EventStoreVec2::get_class_type());
@@ -67,6 +67,10 @@ MouseWatcher(const string &name) :
   _button_down = false;
   _button_down = false;
   _eh = (EventHandler *)NULL;
   _eh = (EventHandler *)NULL;
   _display_region = (DisplayRegion *)NULL;
   _display_region = (DisplayRegion *)NULL;
+  _has_inactivity_timeout = false;
+  _inactivity_timeout = 0.0;
+  _last_activity = 0.0;
+  _inactivity_state = IS_active;
 
 
   // When this flag is true, the mouse pointer is allowed to be
   // When this flag is true, the mouse pointer is allowed to be
   // "entered" into multiple regions simultaneously; when false, it
   // "entered" into multiple regions simultaneously; when false, it
@@ -336,6 +340,41 @@ get_group(int n) const {
   return _groups[n];
   return _groups[n];
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::note_activity
+//       Access: Published
+//  Description: Can be used in conjunction with the inactivity
+//               timeout to inform the MouseWatcher that the user has
+//               just performed some action which proves he/she is
+//               present.  It may be necessary to call this for
+//               external events, such as joystick action, that the
+//               MouseWatcher might otherwise not know about.  This
+//               will reset the current inactivity timer.  When the
+//               inactivity timer reaches the length of time specified
+//               by set_inactivity_timeout(), with no keyboard or
+//               mouse activity and no calls to note_activity(), then
+//               any buttons held will be automatically released.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+note_activity() {
+  _last_activity = ClockObject::get_global_clock()->get_frame_time();
+  switch (_inactivity_state) {
+  case IS_active:
+    break;
+
+  case IS_inactive:
+    _inactivity_state = IS_inactive_to_active;
+    break;
+
+  case IS_active_to_inactive:
+    _inactivity_state = IS_active;
+    break;
+
+  case IS_inactive_to_active:
+    break;
+  }
+}
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::output
 //     Function: MouseWatcher::output
@@ -810,11 +849,12 @@ move() {
 //               being depressed.
 //               being depressed.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void MouseWatcher::
 void MouseWatcher::
-press(ButtonHandle button) {
+press(ButtonHandle button, bool keyrepeat) {
   nassertv(_lock.debug_is_locked());
   nassertv(_lock.debug_is_locked());
 
 
   MouseWatcherParameter param;
   MouseWatcherParameter param;
   param.set_button(button);
   param.set_button(button);
+  param.set_keyrepeat(keyrepeat);
   param.set_modifier_buttons(_mods);
   param.set_modifier_buttons(_mods);
   param.set_mouse(_mouse);
   param.set_mouse(_mouse);
 
 
@@ -828,8 +868,13 @@ press(ButtonHandle button) {
 
 
     if (_preferred_button_down_region != (MouseWatcherRegion *)NULL) {
     if (_preferred_button_down_region != (MouseWatcherRegion *)NULL) {
       _preferred_button_down_region->press(param);
       _preferred_button_down_region->press(param);
-      throw_event_pattern(_button_down_pattern,
-                          _preferred_button_down_region, button);
+      if (keyrepeat) {
+        throw_event_pattern(_button_repeat_pattern,
+                            _preferred_button_down_region, button);
+      } else {
+        throw_event_pattern(_button_down_pattern,
+                            _preferred_button_down_region, button);
+      }
     }
     }
     
     
   } else {
   } else {
@@ -1186,6 +1231,8 @@ do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input,
   Thread *current_thread = trav->get_current_thread();
   Thread *current_thread = trav->get_current_thread();
   MutexHolder holder(_lock);
   MutexHolder holder(_lock);
 
 
+  bool activity = false;
+
   // Initially, we do not suppress any events to objects below us in
   // Initially, we do not suppress any events to objects below us in
   // the data graph.
   // the data graph.
   _internal_suppress = 0;
   _internal_suppress = 0;
@@ -1207,6 +1254,7 @@ do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input,
     // Asad: determine if mouse moved from last position
     // Asad: determine if mouse moved from last position
     const LVecBase2f &last_f = _xy->get_value();
     const LVecBase2f &last_f = _xy->get_value();
     if (f != last_f) {
     if (f != last_f) {
+      activity = true;
       move();
       move();
     }
     }
 
 
@@ -1260,34 +1308,63 @@ do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input,
     _internal_suppress |= _preferred_region->get_suppress_flags();
     _internal_suppress |= _preferred_region->get_suppress_flags();
   }
   }
 
 
-  // Look for button events.
+  ButtonEventList new_button_events;
+
+  // Look for new button events.
   if (input.has_data(_button_events_input)) {
   if (input.has_data(_button_events_input)) {
-    const ButtonEventList *button_events;
-    DCAST_INTO_V(button_events, input.get_data(_button_events_input).get_ptr());
-    int num_events = button_events->get_num_events();
+    const ButtonEventList *this_button_events;
+    DCAST_INTO_V(this_button_events, input.get_data(_button_events_input).get_ptr());
+    int num_events = this_button_events->get_num_events();
     for (int i = 0; i < num_events; i++) {
     for (int i = 0; i < num_events; i++) {
-      const ButtonEvent &be = button_events->get_event(i);
+      const ButtonEvent &be = this_button_events->get_event(i);
       be.update_mods(_mods);
       be.update_mods(_mods);
 
 
       switch (be._type) {
       switch (be._type) {
       case ButtonEvent::T_down:
       case ButtonEvent::T_down:
-        press(be._button);
+        if (!_current_buttons_down.get_bit(be._button.get_index())) {
+          // The button was not already depressed; thus, this is not
+          // keyrepeat.
+          activity = true;
+          _current_buttons_down.set_bit(be._button.get_index());
+          press(be._button, false);
+          new_button_events.add_event(be);
+          break;
+        }
+        // The button was already depressed, so this is really just
+        // keyrepeat.  Fall through.
+
+      case ButtonEvent::T_repeat:
+        _current_buttons_down.set_bit(be._button.get_index());
+        press(be._button, true);
+        new_button_events.add_event(ButtonEvent(be._button, ButtonEvent::T_repeat,
+                                                be._time));
         break;
         break;
 
 
       case ButtonEvent::T_up:
       case ButtonEvent::T_up:
+        activity = true;
+        _current_buttons_down.clear_bit(be._button.get_index());
         release(be._button);
         release(be._button);
+        new_button_events.add_event(be);
         break;
         break;
 
 
       case ButtonEvent::T_keystroke:
       case ButtonEvent::T_keystroke:
+        // We don't consider "keystroke" an activity event, because it
+        // might be just keyrepeat.
         keystroke(be._keycode);
         keystroke(be._keycode);
+        new_button_events.add_event(be);
         break;
         break;
 
 
       case ButtonEvent::T_candidate:
       case ButtonEvent::T_candidate:
+        activity = true;
         candidate(be._candidate_string, be._highlight_start, be._highlight_end, be._cursor_pos);
         candidate(be._candidate_string, be._highlight_start, be._highlight_end, be._cursor_pos);
+        new_button_events.add_event(be);
         break;
         break;
 
 
       case ButtonEvent::T_resume_down:
       case ButtonEvent::T_resume_down:
-        // Ignore this, since the button wasn't pressed just now.
+        _current_buttons_down.set_bit(be._button.get_index());
+        // Don't call press(), since the button wasn't actually
+        // pressed just now.
+        new_button_events.add_event(be);
         break;
         break;
 
 
       case ButtonEvent::T_move:
       case ButtonEvent::T_move:
@@ -1297,6 +1374,73 @@ do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input,
     }
     }
   }
   }
 
 
+  // Now check the inactivity timer.
+  if (_has_inactivity_timeout) {
+    if (activity) {
+      note_activity();
+      
+    } else {
+      double now = ClockObject::get_global_clock()->get_frame_time();
+      double elapsed = now - _last_activity;
+
+      // Toggle the inactivity state to inactive.
+      if (elapsed > _inactivity_timeout) {
+        switch (_inactivity_state) {
+        case IS_active:
+          _inactivity_state = IS_active_to_inactive;
+          break;
+
+        case IS_inactive:
+          break;
+          
+        case IS_active_to_inactive:
+          break;
+          
+        case IS_inactive_to_active:
+          _inactivity_state = IS_inactive;
+          break;
+        }
+      }
+    }
+  }
+
+  switch (_inactivity_state) {
+  case IS_active:
+  case IS_inactive:
+    break;
+    
+  case IS_active_to_inactive:
+    // "Release" all of the currently-held buttons.
+    if (tform_cat.is_debug()) {
+      tform_cat.info()
+        << "MouseWatcher detected " << _inactivity_timeout
+        << " seconds of inactivity; releasing held buttons.\n";
+    }
+    {
+      for (int i = 0; i < _current_buttons_down.get_num_bits(); ++i) {
+        if (_current_buttons_down.get_bit(i)) {
+          release(ButtonHandle(i));
+          new_button_events.add_event(ButtonEvent(ButtonHandle(i), ButtonEvent::T_up));
+        }
+      }
+    }
+    _inactivity_state = IS_inactive;
+    break;
+    
+  case IS_inactive_to_active:
+    // "Press" all of the buttons we "released" before.
+    {
+      for (int i = 0; i < _current_buttons_down.get_num_bits(); ++i) {
+        if (_current_buttons_down.get_bit(i)) {
+          press(ButtonHandle(i), false);
+          new_button_events.add_event(ButtonEvent(ButtonHandle(i), ButtonEvent::T_down));
+        }
+      }
+    }
+    _inactivity_state = IS_active;
+    break;
+  }
+
   if (_has_mouse &&
   if (_has_mouse &&
       (_internal_suppress & MouseWatcherRegion::SF_mouse_position) == 0) {
       (_internal_suppress & MouseWatcherRegion::SF_mouse_position) == 0) {
     // Transmit the mouse position.
     // Transmit the mouse position.
@@ -1306,41 +1450,31 @@ do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input,
     output.set_data(_pixel_xy_output, EventParameter(_pixel_xy));
     output.set_data(_pixel_xy_output, EventParameter(_pixel_xy));
   }
   }
 
 
+  // Now transmit the buttons events down the graph.
   int suppress_buttons = ((_internal_suppress | _external_suppress) & MouseWatcherRegion::SF_any_button);
   int suppress_buttons = ((_internal_suppress | _external_suppress) & MouseWatcherRegion::SF_any_button);
 
 
-  if (suppress_buttons != 0) {
-    // Suppress some buttons.
-    _button_events->clear();
-
-    if (input.has_data(_button_events_input)) {
-      const ButtonEventList *button_events;
-      DCAST_INTO_V(button_events, input.get_data(_button_events_input).get_ptr());
-      int num_events = button_events->get_num_events();
-      for (int i = 0; i < num_events; i++) {
-        const ButtonEvent &be = button_events->get_event(i);
-        bool suppress = true;
-        
-        if (be._type != ButtonEvent::T_keystroke && 
-            MouseButton::is_mouse_button(be._button)) {
-          suppress = ((suppress_buttons & MouseWatcherRegion::SF_mouse_button) != 0);
-        } else {
-          suppress = ((suppress_buttons & MouseWatcherRegion::SF_other_button) != 0);
-        }
+  _button_events->clear();
 
 
-        if (!suppress || be._type == ButtonEvent::T_up) {
-          // Don't suppress this button event; pass it through.
-          _button_events->add_event(be);
-        }
-      }
+  int num_events = new_button_events.get_num_events();
+  for (int i = 0; i < num_events; i++) {
+    const ButtonEvent &be = new_button_events.get_event(i);
+    bool suppress = true;
+    
+    if (be._type != ButtonEvent::T_keystroke && 
+        MouseButton::is_mouse_button(be._button)) {
+      suppress = ((suppress_buttons & MouseWatcherRegion::SF_mouse_button) != 0);
+    } else {
+      suppress = ((suppress_buttons & MouseWatcherRegion::SF_other_button) != 0);
     }
     }
-
-    if (_button_events->get_num_events() != 0) {
-      output.set_data(_button_events_output, EventParameter(_button_events));
+    
+    if (!suppress || be._type == ButtonEvent::T_up) {
+      // Don't suppress this button event; pass it through.
+      _button_events->add_event(be);
     }
     }
-
-  } else {
-    // Transmit all buttons.
-    output.set_data(_button_events_output, input.get_data(_button_events_input));
+  }
+  
+  if (_button_events->get_num_events() != 0) {
+    output.set_data(_button_events_output, EventParameter(_button_events));
   }
   }
 
 
   // We always pass the pixel_size data through.
   // We always pass the pixel_size data through.

+ 28 - 1
panda/src/tform/mouseWatcher.h

@@ -31,6 +31,8 @@
 #include "buttonHandle.h"
 #include "buttonHandle.h"
 #include "buttonEventList.h"
 #include "buttonEventList.h"
 #include "linmath_events.h"
 #include "linmath_events.h"
+#include "bitArray.h"
+#include "clockObject.h"
 #include "pvector.h"
 #include "pvector.h"
 
 
 class MouseWatcherParameter;
 class MouseWatcherParameter;
@@ -75,12 +77,17 @@ PUBLISHED:
   INLINE MouseWatcherRegion *get_over_region(float x, float y) const;
   INLINE MouseWatcherRegion *get_over_region(float x, float y) const;
   MouseWatcherRegion *get_over_region(const LPoint2f &pos) const;
   MouseWatcherRegion *get_over_region(const LPoint2f &pos) const;
 
 
+  INLINE bool is_button_down(ButtonHandle button) const;
+
   INLINE void set_button_down_pattern(const string &pattern);
   INLINE void set_button_down_pattern(const string &pattern);
   INLINE const string &get_button_down_pattern() const;
   INLINE const string &get_button_down_pattern() const;
 
 
   INLINE void set_button_up_pattern(const string &pattern);
   INLINE void set_button_up_pattern(const string &pattern);
   INLINE const string &get_button_up_pattern() const;
   INLINE const string &get_button_up_pattern() const;
 
 
+  INLINE void set_button_repeat_pattern(const string &pattern);
+  INLINE const string &get_button_repeat_pattern() const;
+
   INLINE void set_enter_pattern(const string &pattern);
   INLINE void set_enter_pattern(const string &pattern);
   INLINE const string &get_enter_pattern() const;
   INLINE const string &get_enter_pattern() const;
 
 
@@ -115,6 +122,12 @@ PUBLISHED:
   int get_num_groups() const;
   int get_num_groups() const;
   MouseWatcherGroup *get_group(int n) const;
   MouseWatcherGroup *get_group(int n) const;
 
 
+  INLINE void set_inactivity_timeout(double timeout);
+  INLINE bool has_inactivity_timeout() const;
+  INLINE double get_inactivity_timeout() const;
+  INLINE void clear_inactivity_timeout();
+  void note_activity();
+
 public:
 public:
   virtual void output(ostream &out) const;
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level = 0) const;
   virtual void write(ostream &out, int indent_level = 0) const;
@@ -147,7 +160,7 @@ protected:
                            const ButtonHandle &button);
                            const ButtonHandle &button);
 
 
   void move();
   void move();
-  void press(ButtonHandle button);
+  void press(ButtonHandle button, bool keyrepeat);
   void release(ButtonHandle button);
   void release(ButtonHandle button);
   void keystroke(int keycode);
   void keystroke(int keycode);
   void candidate(const wstring &candidate, size_t highlight_start, 
   void candidate(const wstring &candidate, size_t highlight_start, 
@@ -178,6 +191,7 @@ private:
   int _external_suppress;
   int _external_suppress;
   LPoint2f _mouse;
   LPoint2f _mouse;
   LPoint2f _mouse_pixel;
   LPoint2f _mouse_pixel;
+  BitArray _current_buttons_down;
 
 
   Regions _current_regions;
   Regions _current_regions;
   PT(MouseWatcherRegion) _preferred_region;
   PT(MouseWatcherRegion) _preferred_region;
@@ -189,6 +203,7 @@ private:
 
 
   string _button_down_pattern;
   string _button_down_pattern;
   string _button_up_pattern;
   string _button_up_pattern;
+  string _button_repeat_pattern;
   string _enter_pattern;
   string _enter_pattern;
   string _leave_pattern;
   string _leave_pattern;
   string _within_pattern;
   string _within_pattern;
@@ -200,6 +215,18 @@ private:
   ModifierButtons _mods;
   ModifierButtons _mods;
   DisplayRegion *_display_region;
   DisplayRegion *_display_region;
 
 
+  bool _has_inactivity_timeout;
+  double _inactivity_timeout;
+  double _last_activity;
+
+  enum InactivityState {
+    IS_active,
+    IS_inactive,
+    IS_active_to_inactive,
+    IS_inactive_to_active,
+  };
+  InactivityState _inactivity_state;
+
 #ifndef NDEBUG
 #ifndef NDEBUG
   NodePath _show_regions_render2d;
   NodePath _show_regions_render2d;
   string _show_regions_bin_name;
   string _show_regions_bin_name;

+ 29 - 0
panda/src/tform/mouseWatcherParameter.I

@@ -77,6 +77,23 @@ set_button(const ButtonHandle &button) {
   _flags |= F_has_button;
   _flags |= F_has_button;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherParameter::set_keyrepeat
+//       Access: Public
+//  Description: Sets the state of the "keyrepeat" flag.  This is true
+//               if a button-press event was generated due to
+//               keyrepeat, or false if it is an original button
+//               press.
+////////////////////////////////////////////////////////////////////
+INLINE void MouseWatcherParameter::
+set_keyrepeat(bool flag) {
+  if (flag) {
+    _flags |= F_is_keyrepeat;
+  } else {
+    _flags &= ~F_is_keyrepeat;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcherParameter::set_keycode
 //     Function: MouseWatcherParameter::set_keycode
 //       Access: Public
 //       Access: Public
@@ -168,6 +185,18 @@ get_button() const {
   return _button;
   return _button;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherParameter::is_keyrepeat
+//       Access: Published
+//  Description: Returns true if the button-down even was generated
+//               due to keyrepeat, or false if it was an original
+//               button down.
+////////////////////////////////////////////////////////////////////
+INLINE bool MouseWatcherParameter::
+is_keyrepeat() const {
+  return (_flags & F_is_keyrepeat) != 0;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcherParameter::has_keycode
 //     Function: MouseWatcherParameter::has_keycode
 //       Access: Published
 //       Access: Published

+ 3 - 0
panda/src/tform/mouseWatcherParameter.h

@@ -40,6 +40,7 @@ public:
   INLINE ~MouseWatcherParameter();
   INLINE ~MouseWatcherParameter();
 
 
   INLINE void set_button(const ButtonHandle &button);
   INLINE void set_button(const ButtonHandle &button);
+  INLINE void set_keyrepeat(bool flag);
   INLINE void set_keycode(int keycode);
   INLINE void set_keycode(int keycode);
   INLINE void set_candidate(const wstring &candidate_string,
   INLINE void set_candidate(const wstring &candidate_string,
                             size_t highlight_start, 
                             size_t highlight_start, 
@@ -52,6 +53,7 @@ public:
 PUBLISHED:
 PUBLISHED:
   INLINE bool has_button() const;
   INLINE bool has_button() const;
   INLINE ButtonHandle get_button() const;
   INLINE ButtonHandle get_button() const;
+  INLINE bool is_keyrepeat() const;
 
 
   INLINE bool has_keycode() const;
   INLINE bool has_keycode() const;
   INLINE int get_keycode() const;
   INLINE int get_keycode() const;
@@ -93,6 +95,7 @@ public:
     F_is_outside    = 0x004,
     F_is_outside    = 0x004,
     F_has_keycode   = 0x008,
     F_has_keycode   = 0x008,
     F_has_candidate = 0x010,
     F_has_candidate = 0x010,
+    F_is_keyrepeat  = 0x020,
   };
   };
   int _flags;
   int _flags;
 };
 };