Jelajahi Sumber

Add support for paste events

rdb 6 tahun lalu
induk
melakukan
0ad86f74d2

+ 11 - 0
panda/src/display/graphicsWindowInputDevice.cxx

@@ -109,6 +109,17 @@ keystroke(int keycode, double time) {
   _button_events->add_event(ButtonEvent(keycode, time));
 }
 
+/**
+ * Records that the indicated string has been pasted.
+ */
+void GraphicsWindowInputDevice::
+paste(const std::wstring &text) {
+  LightMutexHolder holder(_lock);
+  ButtonEvent event(text, 0, 0, 0);
+  event._type = ButtonEvent::T_paste;
+  _button_events->add_event(std::move(event));
+}
+
 /**
  * Records that the indicated candidate string has been highlighted.  This is
  * used to implement IME support for typing in international languages,

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

@@ -45,6 +45,7 @@ PUBLISHED:
   void button_up(ButtonHandle button, double time = ClockObject::get_global_clock()->get_frame_time());
 
   void keystroke(int keycode, double time = ClockObject::get_global_clock()->get_frame_time());
+  void paste(const std::wstring &text);
   void candidate(const std::wstring &candidate_string, size_t highlight_start,
                  size_t highlight_end, size_t cursor_pos);
 

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

@@ -60,6 +60,12 @@ output(std::ostream &out) const {
   case T_raw_up:
     out << "raw button " << _button << " up";
     break;
+
+  case T_paste:
+    out << "paste "
+        << TextEncoder::encode_wtext(_candidate_string,
+                                     TextEncoder::get_default_encoding());
+    break;
   }
 }
 
@@ -96,9 +102,15 @@ write_datagram(Datagram &dg) const {
     dg.add_uint16(_highlight_start);
     dg.add_uint16(_highlight_end);
     dg.add_uint16(_cursor_pos);
+    break;
 
   case T_move:
     break;
+
+  case T_paste:
+    dg.add_string(TextEncoder::encode_wtext(_candidate_string,
+                                            TextEncoder::get_default_encoding()));
+    break;
   }
 }
 
@@ -128,8 +140,14 @@ read_datagram(DatagramIterator &scan) {
     _highlight_start = scan.get_uint16();
     _highlight_end = scan.get_uint16();
     _cursor_pos = scan.get_uint16();
+    break;
 
   case T_move:
     break;
+
+  case T_paste:
+    _candidate_string = TextEncoder::decode_text(scan.get_string(),
+                                                 TextEncoder::get_default_encoding());
+    break;
   }
 }

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

@@ -82,6 +82,10 @@ public:
     // (qwerty) keyboard layout.
     T_raw_down,
     T_raw_up,
+
+    // This event can be sent by the operating system to indicate that text is
+    // pasted into the window.
+    T_paste,
   };
 
   INLINE ButtonEvent();

+ 125 - 77
panda/src/pgui/pgEntry.cxx

@@ -324,93 +324,50 @@ keystroke(const MouseWatcherParameter &param, bool background) {
       if (!isascii(keycode) || isprint(keycode)) {
         // A normal visible character.  Add a new character to the text entry,
         // if there's room.
-        if (!_candidate_wtext.empty()) {
-          _candidate_wtext = wstring();
-          _text_geom_stale = true;
+        if (do_add_character(keycode)) {
+          type(param);
+        } else {
+          overflow(param);
         }
-        wstring new_char(1, (wchar_t)keycode);
+      }
+    }
+  }
+  PGItem::keystroke(param, background);
+}
 
-        if (get_max_chars() > 0 && _text.get_num_characters() >= get_max_chars()) {
-          // In max_chars mode, we consider it an overflow after we have
-          // exceeded a fixed number of characters, irrespective of the
-          // formatted width of those characters.
-          overflow(param);
+/**
+ * This is a callback hook function, called whenever the user pastes text.
+ */
+void PGEntry::
+paste(const MouseWatcherParameter &param, bool background) {
+  LightReMutexHolder holder(_lock);
+  if (get_active()) {
+    if (param.has_candidate()) {
+      // Make sure _text is initialized properly.
+      update_text();
 
+      bool typed_any = false;
+      wstring str = param.get_candidate_string();
+      for (wchar_t keycode : str) {
+        if (do_add_character(keycode)) {
+          typed_any = true;
         } else {
-          _cursor_position = min(_cursor_position, _text.get_num_characters());
-          bool too_long = !_text.set_wsubstr(new_char, _cursor_position, 0);
-          bool overflow_mode = get_overflow_mode() && _num_lines == 1;
-          if(overflow_mode){
-            too_long = false;
-          }
-          if (_obscure_mode) {
-            too_long = !_obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
-          } else {
-            if (!too_long && (_text.get_num_rows() == _num_lines) && !overflow_mode) {
-              // If we've filled up all of the available lines, we must also
-              // ensure that the last line is not too long (it might be,
-              // because of additional whitespace on the end).
-              int r = _num_lines - 1;
-              int c = _text.get_num_cols(r);
-              PN_stdfloat last_line_width =
-                _text.get_xpos(r, c) - _text.get_xpos(r, 0);
-              too_long = (last_line_width > _max_width);
-            }
-
-            if (!too_long && keycode == ' ' && !overflow_mode) {
-              // Even if we haven't filled up all of the available lines, we
-              // should reject a space that's typed at the end of the current
-              // line if it would make that line exceed the maximum width,
-              // just so we don't allow an infinite number of spaces to
-              // accumulate.
-              int r, c;
-              _text.calc_r_c(r, c, _cursor_position);
-              if (_text.get_num_cols(r) == c + 1) {
-                // The user is typing at the end of the line.  But we must
-                // allow at least one space at the end of the line, so we only
-                // make any of the following checks if there are already
-                // multiple spaces at the end of the line.
-                if (c - 1 >= 0 && _text.get_character(r, c - 1) == ' ') {
-                  // Ok, the user is putting multiple spaces on the end of a
-                  // line; we need to make sure the line does not grow too
-                  // wide.  Measure the line's width.
-                  PN_stdfloat current_line_width =
-                    _text.get_xpos(r, c + 1) - _text.get_xpos(r, 0);
-                  if (current_line_width > _max_width) {
-                    // We have to reject the space, but we don't treat it as
-                    // an overflow condition.
-                    _text.set_wsubstr(wstring(), _cursor_position, 1);
-                    // If the user is typing over existing space characters,
-                    // we act as if the right-arrow key were pressed instead,
-                    // and advance the cursor to the next position.
-                    // Otherwise, we just quietly eat the space character.
-                    if (_cursor_position < _text.get_num_characters() &&
-                        _text.get_character(_cursor_position) == ' ') {
-                      _cursor_position++;
-                      _cursor_stale = true;
-                    }
-                    return;
-                  }
-                }
-              }
-            }
-          }
-
-          if (too_long) {
-            _text.set_wsubstr(wstring(), _cursor_position, 1);
-            overflow(param);
-
-          } else {
-            _cursor_position += new_char.length();
-            _cursor_stale = true;
-            _text_geom_stale = true;
+          if (typed_any) {
+            // Send type event first.
             type(param);
+            typed_any = false;
           }
+          overflow(param);
+          break;
         }
       }
+
+      if (typed_any) {
+        type(param);
+      }
     }
   }
-  PGItem::keystroke(param, background);
+  PGItem::paste(param, background);
 }
 
 /**
@@ -714,6 +671,97 @@ is_wtext() const {
   return false;
 }
 
+/**
+ * Adds a character to the entry.  Returns true if it was added, false if
+ * there was an overflow.  Assumes the lock is held.
+ */
+bool PGEntry::
+do_add_character(wchar_t keycode) {
+  if (!_candidate_wtext.empty()) {
+    _candidate_wtext = wstring();
+    _text_geom_stale = true;
+  }
+  wstring new_char(1, (wchar_t)keycode);
+
+  if (get_max_chars() > 0 && _text.get_num_characters() >= get_max_chars()) {
+    // In max_chars mode, we consider it an overflow after we have
+    // exceeded a fixed number of characters, irrespective of the
+    // formatted width of those characters.
+    return false;
+
+  } else {
+    _cursor_position = min(_cursor_position, _text.get_num_characters());
+    bool too_long = !_text.set_wsubstr(new_char, _cursor_position, 0);
+    bool overflow_mode = get_overflow_mode() && _num_lines == 1;
+    if(overflow_mode){
+      too_long = false;
+    }
+    if (_obscure_mode) {
+      too_long = !_obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
+    } else {
+      if (!too_long && (_text.get_num_rows() == _num_lines) && !overflow_mode) {
+        // If we've filled up all of the available lines, we must also
+        // ensure that the last line is not too long (it might be,
+        // because of additional whitespace on the end).
+        int r = _num_lines - 1;
+        int c = _text.get_num_cols(r);
+        PN_stdfloat last_line_width =
+          _text.get_xpos(r, c) - _text.get_xpos(r, 0);
+        too_long = (last_line_width > _max_width);
+      }
+
+      if (!too_long && keycode == ' ' && !overflow_mode) {
+        // Even if we haven't filled up all of the available lines, we
+        // should reject a space that's typed at the end of the current
+        // line if it would make that line exceed the maximum width,
+        // just so we don't allow an infinite number of spaces to
+        // accumulate.
+        int r, c;
+        _text.calc_r_c(r, c, _cursor_position);
+        if (_text.get_num_cols(r) == c + 1) {
+          // The user is typing at the end of the line.  But we must
+          // allow at least one space at the end of the line, so we only
+          // make any of the following checks if there are already
+          // multiple spaces at the end of the line.
+          if (c - 1 >= 0 && _text.get_character(r, c - 1) == ' ') {
+            // Ok, the user is putting multiple spaces on the end of a
+            // line; we need to make sure the line does not grow too
+            // wide.  Measure the line's width.
+            PN_stdfloat current_line_width =
+              _text.get_xpos(r, c + 1) - _text.get_xpos(r, 0);
+            if (current_line_width > _max_width) {
+              // We have to reject the space, but we don't treat it as
+              // an overflow condition.
+              _text.set_wsubstr(wstring(), _cursor_position, 1);
+              // If the user is typing over existing space characters,
+              // we act as if the right-arrow key were pressed instead,
+              // and advance the cursor to the next position.
+              // Otherwise, we just quietly eat the space character.
+              if (_cursor_position < _text.get_num_characters() &&
+                  _text.get_character(_cursor_position) == ' ') {
+                _cursor_position++;
+                _cursor_stale = true;
+              }
+              return true;
+            }
+          }
+        }
+      }
+    }
+
+    if (too_long) {
+      _text.set_wsubstr(wstring(), _cursor_position, 1);
+      return false;
+
+    } else {
+      _cursor_position += new_char.length();
+      _cursor_stale = true;
+      _text_geom_stale = true;
+      return true;
+    }
+  }
+}
+
 /**
  * Ensures there is a slot in the array for the given text definition.
  */

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

@@ -49,6 +49,7 @@ public:
 
   virtual void press(const MouseWatcherParameter &param, bool background);
   virtual void keystroke(const MouseWatcherParameter &param, bool background);
+  virtual void paste(const MouseWatcherParameter &param, bool background);
   virtual void candidate(const MouseWatcherParameter &param, bool background);
 
   virtual void accept(const MouseWatcherParameter &param);
@@ -140,6 +141,8 @@ PUBLISHED:
 
 
 private:
+  bool do_add_character(wchar_t keycode);
+
   void slot_text_def(int state);
   void update_text();
   void update_cursor();

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

@@ -341,6 +341,16 @@ get_keystroke_prefix() {
   return "keystroke-";
 }
 
+/**
+ * Returns the prefix that is used to define the paste event for all
+ * PGItems.  The paste event is the concatenation of this string followed
+ * by a hyphen and get_id().
+ */
+INLINE std::string PGItem::
+get_paste_prefix() {
+  return "paste-";
+}
+
 /**
  * Returns the event name that will be thrown when the item is active and the
  * mouse enters its frame, but not any nested frames.
@@ -448,6 +458,16 @@ get_keystroke_event() const {
   return get_keystroke_prefix() + get_id();
 }
 
+/**
+ * Returns the event name that will be thrown when the item is active and text
+ * is pasted.
+ */
+INLINE std::string PGItem::
+get_paste_event() const {
+  LightReMutexHolder holder(_lock);
+  return get_paste_prefix() + get_id();
+}
+
 /**
  * Changes the TextNode object that will be used by all PGItems to generate
  * default labels given a string.  This can be loaded with the default font,

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

@@ -725,6 +725,24 @@ keystroke(const MouseWatcherParameter &param, bool background) {
   }
 }
 
+/**
+ * This is a callback hook function, called whenever the user pastes text.
+ */
+void PGItem::
+paste(const MouseWatcherParameter &param, bool background) {
+  LightReMutexHolder holder(_lock);
+  if (pgui_cat.is_debug()) {
+    pgui_cat.debug()
+      << *this << "::paste(" << param << ", " << background << ")\n";
+  }
+
+  if (!background) {
+    if (has_notify()) {
+      get_notify()->item_paste(this, param);
+    }
+  }
+}
+
 /**
  * This is a callback hook function, called whenever the user highlights an
  * option in the IME window.
@@ -804,6 +822,20 @@ background_keystroke(const MouseWatcherParameter &param) {
   }
 }
 
+/**
+ * Calls paste() on all the PGItems with background focus.
+ */
+void PGItem::
+background_paste(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->paste(param, true);
+    }
+  }
+}
+
 /**
  * Calls candidate() on all the PGItems with background focus.
  */

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

@@ -94,12 +94,14 @@ public:
   virtual void press(const MouseWatcherParameter &param, bool background);
   virtual void release(const MouseWatcherParameter &param, bool background);
   virtual void keystroke(const MouseWatcherParameter &param, bool background);
+  virtual void paste(const MouseWatcherParameter &param, bool background);
   virtual void candidate(const MouseWatcherParameter &param, bool background);
   virtual void move(const MouseWatcherParameter &param);
 
   static void background_press(const MouseWatcherParameter &param);
   static void background_release(const MouseWatcherParameter &param);
   static void background_keystroke(const MouseWatcherParameter &param);
+  static void background_paste(const MouseWatcherParameter &param);
   static void background_candidate(const MouseWatcherParameter &param);
 
   INLINE void set_notify(PGItemNotify *notify);
@@ -151,6 +153,7 @@ PUBLISHED:
   INLINE static std::string get_repeat_prefix();
   INLINE static std::string get_release_prefix();
   INLINE static std::string get_keystroke_prefix();
+  INLINE static std::string get_paste_prefix();
 
   INLINE std::string get_enter_event() const;
   INLINE std::string get_exit_event() const;
@@ -162,6 +165,7 @@ PUBLISHED:
   INLINE std::string get_repeat_event(const ButtonHandle &button) const;
   INLINE std::string get_release_event(const ButtonHandle &button) const;
   INLINE std::string get_keystroke_event() const;
+  INLINE std::string get_paste_event() const;
 
   INLINE LMatrix4 get_frame_inv_xform() const;
 

+ 8 - 0
panda/src/pgui/pgItemNotify.cxx

@@ -128,6 +128,14 @@ void PGItemNotify::
 item_candidate(PGItem *, const MouseWatcherParameter &) {
 }
 
+/**
+ * Called whenever the "paste" event is triggered on a watched PGItem.
+ * See PGItem::paste().
+ */
+void PGItemNotify::
+item_paste(PGItem *, const MouseWatcherParameter &) {
+}
+
 /**
  * Called whenever the "move" event is triggered on a watched PGItem.  See
  * PGItem::move().

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

@@ -44,6 +44,7 @@ protected:
   virtual void item_release(PGItem *item, const MouseWatcherParameter &param);
   virtual void item_keystroke(PGItem *item, const MouseWatcherParameter &param);
   virtual void item_candidate(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_paste(PGItem *item, const MouseWatcherParameter &param);
   virtual void item_move(PGItem *item, const MouseWatcherParameter &param);
 
 protected:

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

@@ -113,6 +113,24 @@ get_keystroke_event() const {
   return _keystroke_event;
 }
 
+/**
+ * Specifies the event that is generated (if any) for each paste event that is
+ * received.
+ */
+INLINE void ButtonThrower::
+set_paste_event(const std::string &paste_event) {
+  _paste_event = paste_event;
+}
+
+/**
+ * Returns the paste_event that has been set on this ButtonThrower.  See
+ * set_paste_event().
+ */
+INLINE const std::string &ButtonThrower::
+get_paste_event() const {
+  return _paste_event;
+}
+
 /**
  * Specifies the event that is generated (if any) for each IME candidate
  * string event received.  Events of this nature are received only when the

+ 8 - 0
panda/src/tform/buttonThrower.cxx

@@ -288,6 +288,10 @@ do_general_event(const ButtonEvent &button_event, const string &button_name) {
   case ButtonEvent::T_raw_up:
     event_name = _raw_button_up_event;
     break;
+
+  case ButtonEvent::T_paste:
+    event_name = _paste_event;
+    break;
   }
   if (event_name.empty()) {
     // This general event is not configured.
@@ -319,6 +323,10 @@ do_general_event(const ButtonEvent &button_event, const string &button_name) {
     event->add_parameter(button_event._candidate_string);
     break;
 
+  case ButtonEvent::T_paste:
+    event->add_parameter(button_event._candidate_string);
+    break;
+
   case ButtonEvent::T_move:
     event_name = _move_event;
     break;

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

@@ -45,6 +45,8 @@ PUBLISHED:
   INLINE const std::string &get_button_repeat_event() const;
   INLINE void set_keystroke_event(const std::string &keystroke_event);
   INLINE const std::string &get_keystroke_event() const;
+  INLINE void set_paste_event(const std::string &paste_event);
+  INLINE const std::string &get_paste_event() const;
   INLINE void set_candidate_event(const std::string &candidate_event);
   INLINE const std::string &get_candidate_event() const;
   INLINE void set_move_event(const std::string &move_event);
@@ -57,6 +59,7 @@ PUBLISHED:
   MAKE_PROPERTY(button_up_event, get_button_up_event, set_button_up_event);
   MAKE_PROPERTY(button_repeat_event, get_button_repeat_event, set_button_repeat_event);
   MAKE_PROPERTY(keystroke_event, get_keystroke_event, set_keystroke_event);
+  MAKE_PROPERTY(paste_event, get_paste_event, set_paste_event);
   MAKE_PROPERTY(candidate_event, get_candidate_event, set_candidate_event);
   MAKE_PROPERTY(move_event, get_move_event, set_move_event);
   MAKE_PROPERTY(raw_button_down_event, get_raw_button_down_event, set_raw_button_down_event);
@@ -106,6 +109,7 @@ private:
   std::string _button_up_event;
   std::string _button_repeat_event;
   std::string _keystroke_event;
+  std::string _paste_event;
   std::string _candidate_event;
   std::string _move_event;
   std::string _raw_button_up_event;

+ 46 - 0
panda/src/tform/mouseWatcher.cxx

@@ -1056,6 +1056,46 @@ keystroke(int keycode) {
   }
 }
 
+/**
+ * Records that the indicated string has been pasted.
+ */
+void MouseWatcher::
+paste(const std::wstring &text) {
+  nassertv(_lock.debug_is_locked());
+
+  MouseWatcherParameter param;
+  param.set_candidate(text, 0, 0, 0);
+  param.set_modifier_buttons(_mods);
+  param.set_mouse(_mouse);
+
+  // Make sure there are no duplicates in the regions vector.
+  if (!_sorted) {
+    ((MouseWatcher *)this)->do_sort_regions();
+  }
+
+  // Pastes go to all those regions that want keyboard events, exactly like
+  // keystrokes, above.
+
+  for (MouseWatcherRegion *region : _regions) {
+    if (region->get_keyboard()) {
+      param.set_outside(region != _preferred_region);
+      region->paste(param);
+    }
+  }
+
+  // Also check all of our sub-groups.
+  for (MouseWatcherGroup *group : _groups) {
+    group->sort_regions();
+
+    for (MouseWatcherRegion *region : group->_regions) {
+      if (region->get_keyboard()) {
+        param.set_outside(region != _preferred_region);
+        region->paste(param);
+      }
+    }
+  }
+}
+
 /**
  * Records that the indicated candidate string has been highlighted in the
  * IME.
@@ -1417,6 +1457,12 @@ do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input,
         // These are passed through.
         new_button_events.add_event(be);
         break;
+
+      case ButtonEvent::T_paste:
+        activity = true;
+        paste(be._candidate_string);
+        new_button_events.add_event(be);
+        break;
       }
     }
   }

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

@@ -181,6 +181,7 @@ protected:
   void press(ButtonHandle button, bool keyrepeat);
   void release(ButtonHandle button);
   void keystroke(int keycode);
+  void paste(const std::wstring &text);
   void candidate(const std::wstring &candidate, size_t highlight_start,
                  size_t highlight_end, size_t cursor_pos);
 

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

@@ -98,6 +98,14 @@ void MouseWatcherRegion::
 keystroke(const MouseWatcherParameter &) {
 }
 
+/**
+ * This is a callback hook function, called whenever something is pasted by
+ * the user.
+ */
+void MouseWatcherRegion::
+paste(const MouseWatcherParameter &) {
+}
+
 /**
  * This is a callback hook function, called whenever an IME candidate is
  * highlighted by the user.

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

@@ -80,6 +80,7 @@ public:
   virtual void press(const MouseWatcherParameter &param);
   virtual void release(const MouseWatcherParameter &param);
   virtual void keystroke(const MouseWatcherParameter &param);
+  virtual void paste(const MouseWatcherParameter &param);
   virtual void candidate(const MouseWatcherParameter &param);
   virtual void move(const MouseWatcherParameter &param);