Ver Fonte

support keystrokes in addition to button up/down

David Rose há 24 anos atrás
pai
commit
6739d86101

+ 1 - 1
panda/src/device/clientButtonDevice.cxx

@@ -53,7 +53,7 @@ set_button_state(int index, bool down) {
 
   ButtonHandle handle = _buttons[index]._handle;
   if (handle != ButtonHandle::none()) {
-    _button_events->push_back(ButtonEvent(handle, down));
+    _button_events->push_back(ButtonEvent(handle, down ? ButtonEvent::T_down : ButtonEvent::T_up));
   }
 }
 

+ 2 - 2
panda/src/device/virtualMouse.cxx

@@ -101,7 +101,7 @@ set_mouse_on(bool flag) {
 ////////////////////////////////////////////////////////////////////
 void VirtualMouse::
 press_button(ButtonHandle button) {
-  _next_button_events->push_back(ButtonEvent(button, true));
+  _next_button_events->push_back(ButtonEvent(button, ButtonEvent::T_down));
 }
   
 ////////////////////////////////////////////////////////////////////
@@ -112,7 +112,7 @@ press_button(ButtonHandle button) {
 ////////////////////////////////////////////////////////////////////
 void VirtualMouse::
 release_button(ButtonHandle button) {
-  _next_button_events->push_back(ButtonEvent(button, false));
+  _next_button_events->push_back(ButtonEvent(button, ButtonEvent::T_up));
 }
 
 ////////////////////////////////////////////////////////////////////

+ 13 - 2
panda/src/display/graphicsWindowInputDevice.cxx

@@ -155,7 +155,7 @@ get_button_event() {
 ////////////////////////////////////////////////////////////////////
 void GraphicsWindowInputDevice::
 button_down(ButtonHandle button) {
-  _button_events.push_back(ButtonEvent(button, true));
+  _button_events.push_back(ButtonEvent(button, ButtonEvent::T_down));
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -165,5 +165,16 @@ button_down(ButtonHandle button) {
 ////////////////////////////////////////////////////////////////////
 void GraphicsWindowInputDevice::
 button_up(ButtonHandle button) {
-  _button_events.push_back(ButtonEvent(button, false));
+  _button_events.push_back(ButtonEvent(button, ButtonEvent::T_up));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsWindowInputDevice::keystroke
+//       Access: Public
+//  Description: Records that the indicated keystroke has been
+//               generated.
+////////////////////////////////////////////////////////////////////
+void GraphicsWindowInputDevice::
+keystroke(int keycode) {
+  _button_events.push_back(ButtonEvent(keycode));
 }

+ 1 - 0
panda/src/display/graphicsWindowInputDevice.h

@@ -63,6 +63,7 @@ public:
   // GraphicsWindows to record the data incoming on the device.
   void button_down(ButtonHandle button);
   void button_up(ButtonHandle button);
+  void keystroke(int keycode);
   INLINE void set_pointer_in_window(int x, int y);
   INLINE void set_pointer_out_of_window();
 

+ 3 - 0
panda/src/glxdisplay/glxGraphicsWindow.cxx

@@ -735,6 +735,9 @@ handle_keypress(ButtonHandle key, int x, int y) {
   _input_devices[0].set_pointer_in_window(x, y);
   if (key != ButtonHandle::none()) {
     _input_devices[0].button_down(key);
+    if (key.has_ascii_equivalent()) {
+      _input_devices[0].keystroke(key.get_ascii_equivalent());
+    }
   }
 }
 

+ 7 - 0
panda/src/pgui/config_pgui.cxx

@@ -36,8 +36,15 @@ ConfigureFn(config_pgui) {
   init_libpgui();
 }
 
+// If this is true, uses a QuickRenderTraverser to render PGItems;
+// otherwise, uses a normal DirectRenderTraverser, which is more
+// powerful but somewhat slower.
 const bool pgui_quick = config_pgui.GetBool("pgui-quick", true);
 
+// Temporary variable to support old-style button press/release for
+// pgentries, before keystrokes were implemented.
+const bool use_keystrokes = config_pgui.GetBool("use-keystrokes", false);
+
 
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libpgui

+ 1 - 0
panda/src/pgui/config_pgui.h

@@ -26,6 +26,7 @@ NotifyCategoryDecl(pgui, EXPCL_PANDA, EXPTP_PANDA);
 
 // Configure variables for pgui package.
 extern const bool pgui_quick;
+extern const bool use_keystrokes;
 
 extern EXPCL_PANDA void init_libpgui();
 

+ 116 - 1
panda/src/pgui/pgEntry.cxx

@@ -237,7 +237,7 @@ press(const MouseWatcherParameter &param, bool background) {
             _cursor_stale = true;
           }
           
-        } else if (button.has_ascii_equivalent()) {
+        } else if (!use_keystrokes && button.has_ascii_equivalent()) {
           char key = button.get_ascii_equivalent();
           if (isprint(key)) {
             // A normal visible character.  Add a new character to the
@@ -340,6 +340,121 @@ press(const MouseWatcherParameter &param, bool background) {
   PGItem::press(param, background);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGEntry::keystroke
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever
+//               the user types a key.
+////////////////////////////////////////////////////////////////////
+void PGEntry::
+keystroke(const MouseWatcherParameter &param, bool background) {
+  if (get_active()) {
+    if (param.has_keycode()) {
+      int keycode = param.get_keycode();
+          
+      if (use_keystrokes) {
+        if (!isascii(keycode) || isprint(keycode)) {
+          // A normal visible character.  Add a new character to the
+          // text entry, if there's room.
+
+          string new_char(1, (char)keycode);
+            
+          if (get_max_chars() > 0 && (int)_text.length() >= get_max_chars()) {
+            overflow(param);
+          } else {
+            string new_text = 
+              _text.substr(0, _cursor_position) + new_char +
+              _text.substr(_cursor_position);
+            
+            // Get a string to measure its length.  In normal mode,
+            // we measure the text itself.  In obscure mode, we
+            // measure a string of n asterisks.
+            string measure_text;
+            if (_obscure_mode) {
+              measure_text = get_display_text() + '*';
+            } else {
+              measure_text = new_text;
+            }
+            
+            // Check the length.
+            bool too_long = false;
+            if (_max_width > 0.0f) {
+              TextNode *text_node = get_text_def(S_focus);
+              if (_num_lines <= 1) {
+                // If we have only one line, we can check the length
+                // by simply measuring the width of the text.
+                too_long = (text_node->calc_width(measure_text) > _max_width);
+                
+              } else {
+                // If we have multiple lines, we have to check the
+                // length by wordwrapping it and counting up the
+                // number of lines.
+                string ww_text = text_node->wordwrap_to(measure_text, _max_width, true);
+                int num_lines = 1;
+                size_t last_line_start = 0;
+                for (size_t p = 0;
+                     p < ww_text.length() && !too_long;
+                     ++p) {
+                  if (ww_text[p] == '\n') {
+                    last_line_start = p + 1;
+                    num_lines++;
+                    too_long = (num_lines > _num_lines);
+                  }
+                }
+                
+                if (!too_long) {
+                  // We must also ensure that the last line is not too
+                  // long (it might be, because of additional
+                  // whitespace on the end).
+                  string last_line = ww_text.substr(last_line_start);
+                  float last_line_width = text_node->calc_width(last_line);
+                  if (num_lines == _num_lines) {
+                    // Mainly we only care about this if we're on
+                    // the very last line.
+                    too_long = (last_line_width > _max_width);
+                    
+                  } else {
+                    // If we're not on the very last line, the width
+                    // is still important, just so we don't allow an
+                    // infinite number of spaces to accumulate.
+                    // However, we must allow at least *one* space
+                    // on the end of a line.
+                    if (_text.length() >= 1 && 
+                        _text[_text.length() - 1] == ' ') {
+                      if (last_line_width > _max_width) {
+                        // In this case, however, it's not exactly
+                        // an overflow; we just want to reject the
+                        // space.
+                        return;
+                      }
+                    }
+                  }
+                }
+              }
+            }
+            
+            if (too_long) {
+              overflow(param);
+              
+            } else {
+              _text = new_text;
+              if (_obscure_mode) {
+                _obscured_text = measure_text;
+              }
+              
+              _cursor_position += new_char.size();
+              _cursor_stale = true;
+              _text_geom_stale = true;
+              type(param);
+            }
+          }
+        }
+      }
+    }
+  }
+  PGItem::keystroke(param, background);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGEntry::accept
 //       Access: Public, Virtual

+ 1 - 0
panda/src/pgui/pgEntry.h

@@ -49,6 +49,7 @@ public:
                          const AllTransitionsWrapper &trans);
 
   virtual void press(const MouseWatcherParameter &param, bool background);
+  virtual void keystroke(const MouseWatcherParameter &param, bool background);
 
   virtual void accept(const MouseWatcherParameter &param);
   virtual void overflow(const MouseWatcherParameter &param);

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

@@ -317,6 +317,19 @@ get_release_prefix() {
   return "release-";
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_keystroke_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the
+//               keystroke event for all PGItems.  The keystroke event
+//               is the concatenation of this string followed by a
+//               hyphen and get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string PGItem::
+get_keystroke_prefix() {
+  return "keystroke-";
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::get_enter_event
 //       Access: Published
@@ -418,6 +431,17 @@ get_release_event(const ButtonHandle &button) const {
   return get_release_prefix() + button.get_name() + "-" + get_id();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_keystroke_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item is active and any key is pressed by the user.
+////////////////////////////////////////////////////////////////////
+INLINE string PGItem::
+get_keystroke_event() const {
+  return get_keystroke_prefix() + "-" + get_id();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::set_text_node
 //       Access: Published, Static

+ 33 - 0
panda/src/pgui/pgItem.cxx

@@ -349,6 +349,22 @@ release(const MouseWatcherParameter &param, bool background) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::keystroke
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever
+//               the user presses a key.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+keystroke(const MouseWatcherParameter &param, bool background) {
+  if (!background) {
+    PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+    string event = get_keystroke_event();
+    play_sound(event);
+    throw_event(event, EventParameter(ep));
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::background_press
 //       Access: Public, Static
@@ -383,6 +399,23 @@ background_release(const MouseWatcherParameter &param) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::background_keystroke
+//       Access: Public, Static
+//  Description: Calls keystroke() on all the PGItems with background
+//               focus.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+background_keystroke(const MouseWatcherParameter &param) {
+  BackgroundFocus::const_iterator fi;
+  for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) {
+    PGItem *item = *fi;
+    if (!item->get_focus()) {
+      item->keystroke(param, true);
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::set_active
 //       Access: Published, Virtual

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

@@ -81,9 +81,11 @@ public:
   virtual void focus_out();
   virtual void press(const MouseWatcherParameter &param, bool background);
   virtual void release(const MouseWatcherParameter &param, bool background);
+  virtual void keystroke(const MouseWatcherParameter &param, bool background);
 
   static void background_press(const MouseWatcherParameter &param);
   static void background_release(const MouseWatcherParameter &param);
+  static void background_keystroke(const MouseWatcherParameter &param);
 
 PUBLISHED:
   INLINE void set_frame(float left, float right, float bottom, float top);
@@ -127,6 +129,7 @@ PUBLISHED:
   INLINE static string get_focus_out_prefix();
   INLINE static string get_press_prefix();
   INLINE static string get_release_prefix();
+  INLINE static string get_keystroke_prefix();
 
   INLINE string get_enter_event() const;
   INLINE string get_exit_event() const;
@@ -136,6 +139,7 @@ PUBLISHED:
   INLINE string get_focus_out_event() const;
   INLINE string get_press_event(const ButtonHandle &button) const;
   INLINE string get_release_event(const ButtonHandle &button) const;
+  INLINE string get_keystroke_event() const;
 
 #ifdef HAVE_AUDIO
   void set_sound(const string &event, AudioSound *sound);

+ 11 - 0
panda/src/pgui/pgMouseWatcherBackground.cxx

@@ -66,3 +66,14 @@ void PGMouseWatcherBackground::
 release(const MouseWatcherParameter &param) {
   PGItem::background_release(param);
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGMouseWatcherBackground::keystroke
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever
+//               the user presses a key.
+////////////////////////////////////////////////////////////////////
+void PGMouseWatcherBackground::
+keystroke(const MouseWatcherParameter &param) {
+  PGItem::background_keystroke(param);
+}

+ 1 - 0
panda/src/pgui/pgMouseWatcherBackground.h

@@ -37,6 +37,7 @@ PUBLISHED:
 
   virtual void press(const MouseWatcherParameter &param);
   virtual void release(const MouseWatcherParameter &param);
+  virtual void keystroke(const MouseWatcherParameter &param);
 
 public:
   static TypeHandle get_class_type() {

+ 13 - 0
panda/src/pgui/pgMouseWatcherRegion.cxx

@@ -138,3 +138,16 @@ release(const MouseWatcherParameter &param) {
     _item->release(param, false);
   }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGMouseWatcherRegion::keystroke
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever 
+//               the user presses a key.
+////////////////////////////////////////////////////////////////////
+void PGMouseWatcherRegion::
+keystroke(const MouseWatcherParameter &param) {
+  if (_item != (PGItem *)NULL) {
+    _item->keystroke(param, false);
+  }
+}

+ 1 - 0
panda/src/pgui/pgMouseWatcherRegion.h

@@ -43,6 +43,7 @@ public:
   virtual void without(const MouseWatcherParameter &param);
   virtual void press(const MouseWatcherParameter &param);
   virtual void release(const MouseWatcherParameter &param);
+  virtual void keystroke(const MouseWatcherParameter &param);
 
 private:
   PGItem *_item;

+ 28 - 7
panda/src/putil/buttonEvent.I

@@ -25,7 +25,8 @@
 INLINE ButtonEvent::
 ButtonEvent() :
   _button(ButtonHandle::none()),
-  _down(false)
+  _keycode(0),
+  _type(T_down)
 {
 }
 
@@ -35,9 +36,23 @@ ButtonEvent() :
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE ButtonEvent::
-ButtonEvent(ButtonHandle button, bool down) :
+ButtonEvent(ButtonHandle button, ButtonEvent::Type type) :
   _button(button),
-  _down(down)
+  _keycode(0),
+  _type(type)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonEvent::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE ButtonEvent::
+ButtonEvent(short keycode) :
+  _button(ButtonHandle::none()),
+  _keycode(keycode),
+  _type(T_keystroke)
 {
 }
 
@@ -49,7 +64,8 @@ ButtonEvent(ButtonHandle button, bool down) :
 INLINE ButtonEvent::
 ButtonEvent(const ButtonEvent &copy) :
   _button(copy._button),
-  _down(copy._down)
+  _keycode(copy._keycode),
+  _type(copy._type)
 {
 }
 
@@ -61,7 +77,8 @@ ButtonEvent(const ButtonEvent &copy) :
 INLINE void ButtonEvent::
 operator = (const ButtonEvent &copy) {
   _button = copy._button;
-  _down = copy._down;
+  _keycode = copy._keycode;
+  _type = copy._type;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -72,7 +89,8 @@ operator = (const ButtonEvent &copy) {
 INLINE bool ButtonEvent::
 operator == (const ButtonEvent &other) const {
   return (_button == other._button &&
-          _down == other._down);
+          _keycode == other._keycode &&
+          _type == other._type);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -95,6 +113,9 @@ operator < (const ButtonEvent &other) const {
   if (_button != other._button) {
     return _button < other._button;
   }
+  if (_keycode != other._keycode) {
+    return _keycode < other._keycode;
+  }
 
-  return _down < other._down;
+  return _type < other._type;
 }

+ 12 - 5
panda/src/putil/buttonEvent.cxx

@@ -25,10 +25,17 @@
 ////////////////////////////////////////////////////////////////////
 void ButtonEvent::
 output(ostream &out) const {
-  out << "button " << _button;
-  if (_down) {
-    out << " down";
-  } else {
-    out << " up";
+  switch (_type) {
+  case T_down:
+    out << "button " << _button << " down";
+    break;
+
+  case T_up:
+    out << "button " << _button << " up";
+    break;
+
+  case T_keystroke:
+    out << "keystroke " << _keycode;
+    break;
   }
 }

+ 39 - 5
panda/src/putil/buttonEvent.h

@@ -19,19 +19,47 @@
 #ifndef BUTTONEVENT_H
 #define BUTTONEVENT_H
 
-#include <pandabase.h>
+#include "pandabase.h"
 
 #include "buttonHandle.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : ButtonEvent
-// Description : Records a transition of one button from up to down or
-//               vice-versa.
+// Description : Records a button event of some kind.  This is either
+//               a keyboard or mouse button (or some other kind of
+//               button) changing state from up to down, or
+//               vice-versa, or it is a single "keystroke".
+//
+//               A keystroke is different than a button event in that
+//               (a) it does not necessarily correspond to a physical
+//               button on a keyboard, but might be the result of a
+//               combination of buttons (e.g. "A" is the result of
+//               shift + "a"); and (b) it does not manage separate
+//               "up" and "down" events, but is itself an
+//               instantaneous event.  
+//
+//               Normal up/down button events can be used to track the
+//               state of a particular button on the keyboard, while
+//               keystroke events are best used to monitor what a user
+//               is attempting to type.
+//
+//               Button up/down events are defined across all the
+//               physical keys on the keyboard (and other buttons for
+//               which there is a corresponding ButtonHandle object),
+//               while keystroke events are defined across the entire
+//               Unicode character set.
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA ButtonEvent {
 public:
+  enum Type {
+    T_down,
+    T_up,
+    T_keystroke
+  };
+
   INLINE ButtonEvent();
-  INLINE ButtonEvent(ButtonHandle button, bool down);
+  INLINE ButtonEvent(ButtonHandle button, Type type);
+  INLINE ButtonEvent(short keycode);
   INLINE ButtonEvent(const ButtonEvent &copy);
   INLINE void operator = (const ButtonEvent &copy);
 
@@ -41,8 +69,14 @@ public:
 
   void output(ostream &out) const;
 
+  // _button will be filled in if type is T_down or T_up.
   ButtonHandle _button;
-  bool _down;
+
+  // _keycode will be filled in if type is T_keystroke.  It will be
+  // the Unicode character that was typed.
+  short _keycode;
+
+  Type _type;
 };
 
 INLINE ostream &operator << (ostream &out, const ButtonEvent &be) {

+ 7 - 2
panda/src/putil/modifierButtons.I

@@ -100,10 +100,15 @@ get_button(int index) const {
 ////////////////////////////////////////////////////////////////////
 INLINE bool ModifierButtons::
 add_event(const ButtonEvent &event) {
-  if (event._down) {
+  switch (event._type) {
+  case ButtonEvent::T_down:
     return button_down(event._button);
-  } else {
+
+  case ButtonEvent::T_up:
     return button_up(event._button);
+
+  default:
+    return false;
   }
 }
 

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

@@ -335,7 +335,7 @@ transmit_data(AllTransitionsWrapper &data) {
       const ButtonEvent &be = (*bi);
       string event_name = be._button.get_name();
 
-      if (be._down) {
+      if (be._type == ButtonEvent::T_down) {
         // Button down.
         if (!_mods.button_down(be._button)) {
           // We only prepend modifier names on the button-down events,
@@ -356,7 +356,7 @@ transmit_data(AllTransitionsWrapper &data) {
           new_b->push_back(be);
         }
           
-      } else {
+      } else if (be._type == ButtonEvent::T_up) {
         // Button up.
         _mods.button_up(be._button);
 
@@ -376,6 +376,14 @@ transmit_data(AllTransitionsWrapper &data) {
           }
           new_b->push_back(be);
         }
+
+      } else {
+        // Some other kind of button event (e.g. keypress).  Don't
+        // throw an event for this, but do pass it down.
+        if (new_b == (ButtonEventDataTransition *)NULL) {
+          new_b = new ButtonEventDataTransition;
+        }
+        new_b->push_back(be);
       }
     }
   }

+ 23 - 19
panda/src/tform/driveInterface.cxx

@@ -435,27 +435,31 @@ transmit_data(AllTransitionsWrapper &data) {
     ButtonEventDataTransition::const_iterator bi;
     for (bi = b->begin(); bi != b->end(); ++bi) {
       const ButtonEvent &be = (*bi);
-      if (be._down) {
-        // We only trap button down events if (a) the mouse is in the
-        // window, and (b) we aren't set to ignore the mouse.
-        if (got_mouse && !_ignore_mouse) {
+      if (be._type != ButtonEvent::T_keystroke) {
+        bool down = (be._type == ButtonEvent::T_down);
+
+        if (down) {
+          // We only trap button down events if (a) the mouse is in the
+          // window, and (b) we aren't set to ignore the mouse.
+          if (got_mouse && !_ignore_mouse) {
+            _mods.add_event(be);
+          }
+        } else {
+          // However, we always trap button up events, so we don't get
+          // confused and believe a button is still being held down when
+          // it is not.
           _mods.add_event(be);
         }
-      } else {
-        // However, we always trap button up events, so we don't get
-        // confused and believe a button is still being held down when
-        // it is not.
-        _mods.add_event(be);
-      }
-
-      if (be._button == KeyboardButton::up()) {
-        _up_arrow.set_key(be._down);
-      } else if (be._button == KeyboardButton::down()) {
-        _down_arrow.set_key(be._down);
-      } else if (be._button == KeyboardButton::left()) {
-        _left_arrow.set_key(be._down);
-      } else if (be._button == KeyboardButton::right()) {
-        _right_arrow.set_key(be._down);
+        
+        if (be._button == KeyboardButton::up()) {
+          _up_arrow.set_key(down);
+        } else if (be._button == KeyboardButton::down()) {
+          _down_arrow.set_key(down);
+        } else if (be._button == KeyboardButton::left()) {
+          _left_arrow.set_key(down);
+        } else if (be._button == KeyboardButton::right()) {
+          _right_arrow.set_key(down);
+        }
       }
     }
   }

+ 75 - 4
panda/src/tform/mouseWatcher.cxx

@@ -681,6 +681,37 @@ release(ButtonHandle button) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::keystroke
+//       Access: Protected
+//  Description: Records that the indicated keystroke has been
+//               generated.
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+keystroke(int keycode) {
+  MouseWatcherParameter param;
+  param.set_keycode(keycode);
+  param.set_modifier_buttons(_mods);
+  param.set_mouse(_mouse);
+
+  // Send the event to every region that wants keyboard buttons,
+  // regardless of the mouse position.
+  if (_preferred_region != (MouseWatcherRegion *)NULL) {
+    // Our current region, the one under the mouse, always get
+    // all the keyboard events, even if it doesn't set its
+    // keyboard flag.
+    _preferred_region->keystroke(param);
+  }
+
+  if ((_suppress_flags & MouseWatcherRegion::SF_other_button) == 0) {
+    // All the other regions only get the keyboard events if they
+    // set their global keyboard flag, *and* the current region does
+    // not suppress keyboard buttons.
+    param.set_outside(true);
+    global_keystroke(param);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::global_keyboard_press
 //       Access: Protected
@@ -712,7 +743,6 @@ global_keyboard_press(const MouseWatcherParameter &param) {
     }
   }
 }
-
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::global_keyboard_release
 //       Access: Protected
@@ -745,6 +775,38 @@ global_keyboard_release(const MouseWatcherParameter &param) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::global_keystroke
+//       Access: Protected
+//  Description: Calls keystroke() on all regions that are interested
+//               in receiving global keyboard events, except for the
+//               current region (which already received this one).
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+global_keystroke(const MouseWatcherParameter &param) {
+  Regions::const_iterator ri;
+  for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
+    MouseWatcherRegion *region = (*ri);
+
+    if (region != _preferred_region && region->get_keyboard()) {
+      region->keystroke(param);
+    }
+  }
+
+  // Also check all of our sub-groups.
+  Groups::const_iterator gi;
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    MouseWatcherGroup *group = (*gi);
+    for (ri = group->_regions.begin(); ri != group->_regions.end(); ++ri) {
+      MouseWatcherRegion *region = (*ri);
+
+      if (region != _preferred_region && region->get_keyboard()) {
+        region->keystroke(param);
+      }
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::enter_region
 //       Access: Protected
@@ -835,10 +897,18 @@ transmit_data(AllTransitionsWrapper &data) {
       const ButtonEvent &be = (*bi);
       _mods.add_event(be);
 
-      if (be._down) {
+      switch (be._type) {
+      case ButtonEvent::T_down:
         press(be._button);
-      } else {
+        break;
+
+      case ButtonEvent::T_up:
         release(be._button);
+        break;
+
+      case ButtonEvent::T_keystroke:
+        keystroke(be._keycode);
+        break;
       }
     }
   }
@@ -864,7 +934,8 @@ transmit_data(AllTransitionsWrapper &data) {
         const ButtonEvent &be = (*bi);
         bool suppress = true;
 
-        if (MouseButton::is_mouse_button(be._button)) {
+        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);

+ 2 - 0
panda/src/tform/mouseWatcher.h

@@ -135,8 +135,10 @@ protected:
 
   void press(ButtonHandle button);
   void release(ButtonHandle button);
+  void keystroke(int keycode);
   void global_keyboard_press(const MouseWatcherParameter &param);
   void global_keyboard_release(const MouseWatcherParameter &param);
+  void global_keystroke(const MouseWatcherParameter &param);
 
   INLINE void within_region(MouseWatcherRegion *region, const MouseWatcherParameter &param);
   INLINE void without_region(MouseWatcherRegion *region, const MouseWatcherParameter &param);

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

@@ -35,6 +35,7 @@ MouseWatcherParameter() {
 INLINE MouseWatcherParameter::
 MouseWatcherParameter(const MouseWatcherParameter &copy) :
   _button(copy._button),
+  _keycode(copy._keycode),
   _mods(copy._mods),
   _mouse(copy._mouse),
   _flags(copy._flags)
@@ -49,6 +50,7 @@ MouseWatcherParameter(const MouseWatcherParameter &copy) :
 INLINE void MouseWatcherParameter::
 operator = (const MouseWatcherParameter &copy) {
   _button = copy._button;
+  _keycode = copy._keycode;
   _mods = copy._mods;
   _mouse = copy._mouse;
   _flags = copy._flags;
@@ -75,6 +77,17 @@ set_button(const ButtonHandle &button) {
   _flags |= F_has_button;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherParameter::set_keycode
+//       Access: Public
+//  Description: Sets the keycode associated with this event, if any.
+////////////////////////////////////////////////////////////////////
+INLINE void MouseWatcherParameter::
+set_keycode(int keycode) {
+  _keycode = keycode;
+  _flags |= F_has_keycode;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcherParameter::set_modifier_buttons
 //       Access: Public
@@ -138,6 +151,28 @@ get_button() const {
   return _button;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherParameter::has_keycode
+//       Access: Published
+//  Description: Returns true if this parameter has an associated
+//               keycode, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool MouseWatcherParameter::
+has_keycode() const {
+  return (_flags & F_has_keycode) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherParameter::get_keycode
+//       Access: Published
+//  Description: Returns the keycode associated with this event.  If
+//               has_keycode(), above, returns false, this returns 0.
+////////////////////////////////////////////////////////////////////
+INLINE int MouseWatcherParameter::
+get_keycode() const {
+  return _keycode;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcherParameter::get_modifier_buttons
 //       Access: Published

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

@@ -39,6 +39,7 @@ public:
   INLINE ~MouseWatcherParameter();
 
   INLINE void set_button(const ButtonHandle &button);
+  INLINE void set_keycode(int keycode);
   INLINE void set_modifier_buttons(const ModifierButtons &mods);
   INLINE void set_mouse(const LPoint2f &mouse);
   INLINE void set_outside(bool flag);
@@ -47,6 +48,9 @@ PUBLISHED:
   INLINE bool has_button() const;
   INLINE ButtonHandle get_button() const;
 
+  INLINE bool has_keycode() const;
+  INLINE int get_keycode() const;
+
   INLINE const ModifierButtons &get_modifier_buttons() const;
 
   INLINE bool has_mouse() const;
@@ -58,6 +62,7 @@ PUBLISHED:
 
 public:
   ButtonHandle _button;
+  short _keycode;
   ModifierButtons _mods;
   LPoint2f _mouse;
 
@@ -65,6 +70,7 @@ public:
     F_has_button  = 0x001,
     F_has_mouse   = 0x002,
     F_is_outside  = 0x004,
+    F_has_keycode = 0x008,
   };
   int _flags;
 };

+ 10 - 0
panda/src/tform/mouseWatcherRegion.cxx

@@ -117,3 +117,13 @@ press(const MouseWatcherParameter &) {
 void MouseWatcherRegion::
 release(const MouseWatcherParameter &) {
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherRegion::keystroke
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever a
+//               keystroke is generated by the user.
+////////////////////////////////////////////////////////////////////
+void MouseWatcherRegion::
+keystroke(const MouseWatcherParameter &) {
+}

+ 1 - 0
panda/src/tform/mouseWatcherRegion.h

@@ -76,6 +76,7 @@ public:
   virtual void without(const MouseWatcherParameter &param);
   virtual void press(const MouseWatcherParameter &param);
   virtual void release(const MouseWatcherParameter &param);
+  virtual void keystroke(const MouseWatcherParameter &param);
 
 private:
   LVecBase4f _frame;