Browse Source

pgraph pgui

David Rose 24 years ago
parent
commit
d5dce0be83

+ 31 - 0
panda/src/pgui/pgCullTraverser.I

@@ -0,0 +1,31 @@
+// Filename: pgCullTraverser.I
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGCullTraverser::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGCullTraverser::
+PGCullTraverser(qpPGTop *top, qpCullTraverser *trav) :
+  qpCullTraverser(*trav),
+  _top(top)
+{
+  _sort_index = 0;
+}

+ 21 - 0
panda/src/pgui/pgCullTraverser.cxx

@@ -0,0 +1,21 @@
+// Filename: pgCullTraverser.cxx
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pgCullTraverser.h"
+
+TypeHandle PGCullTraverser::_type_handle;

+ 66 - 0
panda/src/pgui/pgCullTraverser.h

@@ -0,0 +1,66 @@
+// Filename: pgCullTraverser.h
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PGCULLTRAVERSER_H
+#define PGCULLTRAVERSER_H
+
+#include "pandabase.h"
+
+#include "qppgTop.h"
+#include "qpcullTraverser.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : PGCullTraverser
+// Description : This is a specialization of CullTraverser for use
+//               within the pgui system.  It is substituted in for the
+//               normal CullTraverser by the PGTop node.  Its purpose
+//               is to carry additional data through the traversal so
+//               that PGItems can know how to register their regions
+//               with the current MouseWatcherGroup.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA PGCullTraverser : public qpCullTraverser {
+public:
+  INLINE PGCullTraverser(qpPGTop *top, qpCullTraverser *trav);
+
+  qpPGTop *_top;
+  int _sort_index;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    qpCullTraverser::init_type();
+    register_type(_type_handle, "PGCullTraverser",
+                  qpCullTraverser::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "pgCullTraverser.I"
+
+#endif
+
+
+  

+ 75 - 0
panda/src/pgui/qppgButton.I

@@ -0,0 +1,75 @@
+// Filename: qppgButton.I
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::setup
+//       Access: Published
+//  Description: Sets up the button using the indicated NodePath as
+//               arbitrary geometry.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGButton::
+setup(const qpNodePath &ready) {
+  setup(ready, ready, ready, ready);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::setup
+//       Access: Published
+//  Description: Sets up the button using the indicated NodePath as
+//               arbitrary geometry.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGButton::
+setup(const qpNodePath &ready, const qpNodePath &depressed) {
+  setup(ready, depressed, ready, ready);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::setup
+//       Access: Published
+//  Description: Sets up the button using the indicated NodePath as
+//               arbitrary geometry.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGButton::
+setup(const qpNodePath &ready, const qpNodePath &depressed, 
+      const qpNodePath &rollover) {
+  setup(ready, depressed, rollover, ready);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::get_click_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the click
+//               event for all qpPGButtons.  The click event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGButton::
+get_click_prefix() {
+  return "click-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::get_click_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               button is clicked normally.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGButton::
+get_click_event(const ButtonHandle &button) const {
+  return "click-" + button.get_name() + "-" + get_id();
+}

+ 291 - 0
panda/src/pgui/qppgButton.cxx

@@ -0,0 +1,291 @@
+// Filename: qppgButton.cxx
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qppgButton.h"
+#include "pgMouseWatcherParameter.h"
+
+#include "throw_event.h"
+#include "mouseButton.h"
+#include "mouseWatcherParameter.h"
+#include "colorAttrib.h"
+#include "transformState.h"
+
+TypeHandle qpPGButton::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGButton::
+qpPGButton(const string &name) : qpPGItem(name)
+{
+  _button_down = false;
+  _click_buttons.insert(MouseButton::one());
+
+  set_active(true);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGButton::
+~qpPGButton() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGButton::
+qpPGButton(const qpPGButton &copy) :
+  qpPGItem(copy)
+{
+  _button_down = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpPGButton::
+make_copy() const {
+  return new qpPGButton(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::enter
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse enters the region.
+////////////////////////////////////////////////////////////////////
+void qpPGButton::
+enter(const MouseWatcherParameter &param) {
+  if (get_active()) {
+    set_state(_button_down ? S_depressed : S_rollover);
+  }
+  qpPGItem::enter(param);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::exit
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse exits the region.
+////////////////////////////////////////////////////////////////////
+void qpPGButton::
+exit(const MouseWatcherParameter &param) {
+  if (get_active()) {
+    set_state(S_ready);
+  }
+  qpPGItem::exit(param);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::press
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever a
+//               mouse or keyboard button is depressed while the mouse
+//               is within the region.
+////////////////////////////////////////////////////////////////////
+void qpPGButton::
+press(const MouseWatcherParameter &param, bool background) {
+  if (has_click_button(param.get_button())) {
+    if (get_active()) {
+      _button_down = true;
+      set_state(S_depressed);
+    }
+  }
+  qpPGItem::press(param, background);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::release
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever a
+//               mouse or keyboard button previously depressed with
+//               press() is released.
+////////////////////////////////////////////////////////////////////
+void qpPGButton::
+release(const MouseWatcherParameter &param, bool background) {
+  if (has_click_button(param.get_button())) {
+    _button_down = false;
+    if (get_active()) {
+      if (param.is_outside()) {
+        set_state(S_ready);
+      } else {
+        set_state(S_rollover);
+        click(param);
+      }
+    }
+  }
+  qpPGItem::release(param, background);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::click
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               button is clicked down-and-up by the user normally.
+////////////////////////////////////////////////////////////////////
+void qpPGButton::
+click(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_click_event(param.get_button());
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::setup
+//       Access: Published
+//  Description: Sets up the button as a default text button using the
+//               indicated label string.  The TextNode defined by
+//               PGItem::get_text_node() will be used to create the
+//               label geometry.  This automatically sets up the frame
+//               according to the size of the text.
+////////////////////////////////////////////////////////////////////
+void qpPGButton::
+setup(const string &label) {
+  clear_state_def(S_ready);
+  clear_state_def(S_depressed);
+  clear_state_def(S_rollover);
+  clear_state_def(S_inactive);
+
+  qpTextNode *text_node = get_text_node();
+  text_node->set_text(label);
+  PT(PandaNode) geom = text_node->generate();
+
+  LVecBase4f frame = text_node->get_card_actual();
+  set_frame(frame[0] - 0.4f, frame[1] + 0.4f, frame[2] - 0.15f, frame[3] + 0.15f);
+
+  PT(PandaNode) ready = new PandaNode("ready");
+  PT(PandaNode) depressed = new PandaNode("depressed");
+  PT(PandaNode) rollover = new PandaNode("rollover");
+  PT(PandaNode) inactive = new PandaNode("inactive");
+
+  get_state_def(S_ready)->add_child(ready);
+  get_state_def(S_depressed)->add_child(depressed);
+  get_state_def(S_rollover)->add_child(rollover);
+  get_state_def(S_inactive)->add_child(inactive);
+
+  ready->add_child(geom);
+  depressed->add_child(geom);
+  rollover->add_child(geom);
+  inactive->add_child(geom);
+
+  PGFrameStyle style;
+  style.set_color(0.8f, 0.8f, 0.8f, 1.0);
+  style.set_width(0.1f, 0.1f);
+
+  style.set_type(PGFrameStyle::T_bevel_out);
+  set_frame_style(S_ready, style);
+
+  style.set_color(0.9f, 0.9f, 0.9f, 1.0);
+  set_frame_style(S_rollover, style);
+
+  inactive->set_attrib(ColorAttrib::make_flat(Colorf(0.8f, 0.8f, 0.8f, 1.0f)));
+  style.set_color(0.6f, 0.6f, 0.6f, 1.0);
+  set_frame_style(S_inactive, style);
+
+  style.set_type(PGFrameStyle::T_bevel_in);
+  style.set_color(0.8f, 0.8f, 0.8f, 1.0);
+  set_frame_style(S_depressed, style);
+  depressed->set_transform(TransformState::make_pos(LVector3f(0.05f, 0.0f, -0.05f)));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::setup
+//       Access: Published
+//  Description: Sets up the button using the indicated NodePath as
+//               arbitrary geometry.
+////////////////////////////////////////////////////////////////////
+void qpPGButton::
+setup(const qpNodePath &ready, const qpNodePath &depressed, 
+      const qpNodePath &rollover, const qpNodePath &inactive) {
+  clear_state_def(S_ready);
+  clear_state_def(S_depressed);
+  clear_state_def(S_rollover);
+  clear_state_def(S_inactive);
+
+  instance_to_state_def(S_ready, ready);
+  instance_to_state_def(S_depressed, depressed);
+  instance_to_state_def(S_rollover, rollover);
+  instance_to_state_def(S_inactive, inactive);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::set_active
+//       Access: Published, Virtual
+//  Description: Toggles the active/inactive state of the button.  In
+//               the case of a qpPGButton, this also changes its visual
+//               appearance.
+////////////////////////////////////////////////////////////////////
+void qpPGButton:: 
+set_active(bool active) {
+  if (active != get_active()) {
+    qpPGItem::set_active(active);
+    set_state(active ? S_ready : S_inactive);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::add_click_button
+//       Access: Published
+//  Description: Adds the indicated button to the set of buttons that
+//               can effectively "click" the qpPGButton.  Normally, this
+//               is just MouseButton::one().  Returns true if the
+//               button was added, or false if it was already there.
+////////////////////////////////////////////////////////////////////
+bool qpPGButton::
+add_click_button(const ButtonHandle &button) {
+  return _click_buttons.insert(button).second;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::remove_click_button
+//       Access: Published
+//  Description: Removes the indicated button from the set of buttons
+//               that can effectively "click" the qpPGButton.  Normally,
+//               this is just MouseButton::one().  Returns true if the
+//               button was removed, or false if it was not in the
+//               set.
+////////////////////////////////////////////////////////////////////
+bool qpPGButton::
+remove_click_button(const ButtonHandle &button) {
+  return (_click_buttons.erase(button) != 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGButton::has_click_button
+//       Access: Published
+//  Description: Returns true if the indicated button is on the set of
+//               buttons that can effectively "click" the qpPGButton.
+//               Normally, this is just MouseButton::one().
+////////////////////////////////////////////////////////////////////
+bool qpPGButton::
+has_click_button(const ButtonHandle &button) {
+  return (_click_buttons.count(button) != 0);
+}

+ 104 - 0
panda/src/pgui/qppgButton.h

@@ -0,0 +1,104 @@
+// Filename: qppgButton.h
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpPGBUTTON_H
+#define qpPGBUTTON_H
+
+#include "pandabase.h"
+
+#include "qppgItem.h"
+#include "qpnodePath.h"
+#include "pset.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : qpPGButton
+// Description : This is a particular kind of PGItem that is
+//               specialized to behave like a normal button object.
+//               It keeps track of its own state, and handles mouse
+//               events sensibly.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpPGButton : public qpPGItem {
+PUBLISHED:
+  qpPGButton(const string &name);
+  virtual ~qpPGButton();
+
+protected:
+  qpPGButton(const qpPGButton &copy);
+
+public:
+  virtual PandaNode *make_copy() const;
+
+  virtual void enter(const MouseWatcherParameter &param);
+  virtual void exit(const MouseWatcherParameter &param);
+  virtual void press(const MouseWatcherParameter &param, bool background);
+  virtual void release(const MouseWatcherParameter &param, bool background);
+
+  virtual void click(const MouseWatcherParameter &param);
+
+PUBLISHED:
+  enum State {
+    S_ready = 0,
+    S_depressed,
+    S_rollover,
+    S_inactive
+  };
+
+  void setup(const string &label);
+  INLINE void setup(const qpNodePath &ready);
+  INLINE void setup(const qpNodePath &ready, const qpNodePath &depressed);
+  INLINE void setup(const qpNodePath &ready, const qpNodePath &depressed, 
+                    const qpNodePath &rollover);
+  void setup(const qpNodePath &ready, const qpNodePath &depressed,
+             const qpNodePath &rollover, const qpNodePath &inactive);
+
+  virtual void set_active(bool active);
+
+  bool add_click_button(const ButtonHandle &button);
+  bool remove_click_button(const ButtonHandle &button);
+  bool has_click_button(const ButtonHandle &button);
+
+  INLINE static string get_click_prefix();
+  INLINE string get_click_event(const ButtonHandle &button) const;
+
+private:
+  typedef pset<ButtonHandle> Buttons;
+  Buttons _click_buttons;
+
+  bool _button_down;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    qpPGItem::init_type();
+    register_type(_type_handle, "qpPGButton",
+                  qpPGItem::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "qppgButton.I"
+
+#endif

+ 384 - 0
panda/src/pgui/qppgEntry.I

@@ -0,0 +1,384 @@
+// Filename: qppgEntry.I
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_text
+//       Access: Published
+//  Description: Changes the text currently displayed within the
+//               entry.  This uses the Unicode encoding currently
+//               specified for the "focus" TextNode; therefore, the
+//               TextNode must exist before calling set_text().
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGEntry:: 
+set_text(const string &text) {
+  qpTextNode *text_node = get_text_def(S_focus);
+  nassertv(text_node != (qpTextNode *)NULL);
+  set_wtext(text_node->decode_text(text));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_text
+//       Access: Published
+//  Description: Returns the text currently displayed within the
+//               entry.  This uses the Unicode encoding currently
+//               specified for the "focus" TextNode; therefore, the
+//               TextNode must exist before calling get_text().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGEntry:: 
+get_text() const {
+  qpTextNode *text_node = get_text_def(S_focus);
+  nassertr(text_node != (qpTextNode *)NULL, string());
+  return text_node->encode_wtext(get_wtext());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_cursor_position
+//       Access: Published
+//  Description: Sets the current position of the cursor.  This is the
+//               position within the text at which the next letter
+//               typed by the user will be inserted; normally it is
+//               the same as the length of the text.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGEntry:: 
+set_cursor_position(int position) {
+  if (_cursor_position != position) {
+    _cursor_position = position;
+    _cursor_stale = true;
+    _blink_start = ClockObject::get_global_clock()->get_frame_time();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_cursor_position
+//       Access: Published
+//  Description: Returns the current position of the cursor.
+////////////////////////////////////////////////////////////////////
+INLINE int qpPGEntry:: 
+get_cursor_position() const {
+  return _cursor_position;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_max_chars
+//       Access: Published
+//  Description: Sets the maximum number of characters that may be
+//               typed into the entry.  This is a limit on the number
+//               of characters, as opposed to the width of the entry;
+//               see also set_max_width().
+//
+//               If this is 0, there is no limit.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGEntry:: 
+set_max_chars(int max_chars) {
+  _max_chars = max_chars;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_max_chars
+//       Access: Published
+//  Description: Returns the current maximum number of characters that
+//               may be typed into the entry, or 0 if there is no
+//               limit.  See set_max_chars().
+////////////////////////////////////////////////////////////////////
+INLINE int qpPGEntry:: 
+get_max_chars() const {
+  return _max_chars;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_max_width
+//       Access: Published
+//  Description: Sets the maximum width of all characters that may be
+//               typed into the entry.  This is a limit on the width
+//               of the formatted text, not a fixed limit on the
+//               number of characters; also set_max_chars().
+//
+//               If this is 0, there is no limit.
+//
+//               If _num_lines is more than 1, rather than being a
+//               fixed width on the whole entry, this becomes instead
+//               the wordwrap width (and the width limit on the entry
+//               is essentially _max_width * _num_lines).
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGEntry:: 
+set_max_width(float max_width) {
+  _max_width = max_width;
+  _text_geom_stale = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_max_width
+//       Access: Published
+//  Description: Returns the current maximum width of the characters
+//               that may be typed into the entry, or 0 if there is no
+//               limit.  See set_max_width().
+////////////////////////////////////////////////////////////////////
+INLINE float qpPGEntry:: 
+get_max_width() const {
+  return _max_width;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_num_lines
+//       Access: Published
+//  Description: Sets the number of lines of text the qpPGEntry will
+//               use.  This only has meaning if _max_width is not 0;
+//               _max_width indicates the wordwrap width of each line.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGEntry:: 
+set_num_lines(int num_lines) {
+  nassertv(num_lines >= 1);
+  _num_lines = num_lines;
+  _text_geom_stale = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_num_lines
+//       Access: Published
+//  Description: Returns the number of lines of text the qpPGEntry will
+//               use, if _max_width is not 0.  See set_num_lines().
+////////////////////////////////////////////////////////////////////
+INLINE int qpPGEntry:: 
+get_num_lines() const {
+  return _num_lines;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_blink_rate
+//       Access: Published
+//  Description: Sets the number of times per second the cursor will
+//               blink while the entry has keyboard focus.
+//
+//               If this is 0, the cursor does not blink, but is held
+//               steady.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGEntry:: 
+set_blink_rate(float blink_rate) {
+  _blink_rate = blink_rate;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_blink_rate
+//       Access: Published
+//  Description: Returns the number of times per second the cursor
+//               will blink, or 0 if the cursor is not to blink.
+////////////////////////////////////////////////////////////////////
+INLINE float qpPGEntry:: 
+get_blink_rate() const {
+  return _blink_rate;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_cursor_def
+//       Access: Published
+//  Description: Returns the Node that will be rendered to represent
+//               the cursor.  You can attach suitable cursor geometry
+//               to this node.
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *qpPGEntry:: 
+get_cursor_def() {
+  return _cursor_def;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::clear_cursor_def
+//       Access: Published
+//  Description: Removes all the children from the cursor_def node, in
+//               preparation for adding a new definition.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGEntry:: 
+clear_cursor_def() {
+  get_cursor_def()->remove_all_children();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_cursor_keys_active
+//       Access: Published
+//  Description: Sets whether the arrow keys (and home/end) control
+//               movement of the cursor.  If true, they are active; if
+//               false, they are ignored.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGEntry:: 
+set_cursor_keys_active(bool flag) {
+  _cursor_keys_active = flag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_cursor_keys_active
+//       Access: Published
+//  Description: Returns whether the arrow keys are currently set to
+//               control movement of the cursor; see
+//               set_cursor_keys_active().
+////////////////////////////////////////////////////////////////////
+INLINE bool qpPGEntry:: 
+get_cursor_keys_active() const {
+  return _cursor_keys_active;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_obscure_mode
+//       Access: Published
+//  Description: Specifies whether obscure mode should be enabled.  In
+//               obscure mode, a string of asterisks is displayed
+//               instead of the literal text, e.g. for entering
+//               passwords.
+//
+//               In obscure mode, the width of the text is computed
+//               based on the width of the string of asterisks, not on
+//               the width of the actual text.  This has implications
+//               on the maximum length of text that may be entered if
+//               max_width is in effect.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGEntry:: 
+set_obscure_mode(bool flag) {
+  if (_obscure_mode != flag) {
+    _obscure_mode = flag;
+    _text_geom_stale = true;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_obscure_mode
+//       Access: Published
+//  Description: Specifies whether obscure mode is enabled.  See
+//               set_obscure_mode().
+////////////////////////////////////////////////////////////////////
+INLINE bool qpPGEntry:: 
+get_obscure_mode() const {
+  return _obscure_mode;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_accept_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the accept
+//               event for all PGEntries.  The accept event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGEntry::
+get_accept_prefix() {
+  return "accept-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_overflow_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the overflow
+//               event for all PGEntries.  The overflow event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGEntry::
+get_overflow_prefix() {
+  return "overflow-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_type_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the type
+//               event for all PGEntries.  The type event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGEntry::
+get_type_prefix() {
+  return "type-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_erase_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the erase
+//               event for all PGEntries.  The erase event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGEntry::
+get_erase_prefix() {
+  return "erase-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_accept_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               entry is accepted normally.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGEntry::
+get_accept_event(const ButtonHandle &button) const {
+  return "accept-" + button.get_name() + "-" + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_overflow_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when too
+//               much text is attempted to be entered into the
+//               qpPGEntry, exceeding either the limit set via
+//               set_max_chars() or via set_max_width().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGEntry::
+get_overflow_event() const {
+  return "overflow-" + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_type_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown whenever
+//               the user extends the text by typing.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGEntry::
+get_type_event() const {
+  return "type-" + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_erase_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown whenever
+//               the user erases characters in the text.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGEntry::
+get_erase_event() const {
+  return "erase-" + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_wtext
+//       Access: Public
+//  Description: Changes the text currently displayed within the
+//               entry.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGEntry:: 
+set_wtext(const wstring &wtext) {
+  _wtext = wtext;
+  _text_geom_stale = true;
+  _cursor_stale = true;
+  _blink_start = ClockObject::get_global_clock()->get_frame_time();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_wtext
+//       Access: Public
+//  Description: Returns the text currently displayed within the
+//               entry.
+////////////////////////////////////////////////////////////////////
+INLINE const wstring &qpPGEntry:: 
+get_wtext() const {
+  return _wtext;
+}

+ 881 - 0
panda/src/pgui/qppgEntry.cxx

@@ -0,0 +1,881 @@
+// Filename: qppgEntry.cxx
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qppgEntry.h"
+#include "pgMouseWatcherParameter.h"
+
+#include "qpcullTraverser.h"
+#include "throw_event.h"
+#include "transformState.h"
+#include "mouseWatcherParameter.h"
+#include "keyboardButton.h"
+#include "mouseButton.h"
+#include "lineSegs.h"
+
+#include "math.h"
+
+TypeHandle qpPGEntry::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGEntry::
+qpPGEntry(const string &name) : 
+  qpPGItem(name)
+{
+  _cursor_position = 0;
+  _cursor_stale = true;
+  _max_chars = 0;
+  _max_width = 0.0f;
+  _num_lines = 1;
+  _last_text_def = (qpTextNode *)NULL;
+  _text_geom_stale = true;
+  _blink_start = 0.0f;
+  _blink_rate = 1.0f;
+
+  _text_render_root = new PandaNode("text_root");
+  _current_text_node = (PandaNode *)NULL;
+  _cursor_def = new PandaNode("cursor");
+  _text_render_root->add_child(_cursor_def);
+  _cursor_visible = true;
+
+  _cursor_keys_active = true;
+  _obscure_mode = false;
+
+  set_active(true);
+  update_state();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGEntry::
+~qpPGEntry() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::Copy Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGEntry::
+qpPGEntry(const qpPGEntry &copy) :
+  qpPGItem(copy),
+  _wtext(copy._wtext),
+  _obscured_wtext(copy._obscured_wtext),
+  _cursor_position(copy._cursor_position),
+  _max_chars(copy._max_chars),
+  _max_width(copy._max_width),
+  _text_defs(copy._text_defs),
+  _blink_start(copy._blink_start),
+  _blink_rate(copy._blink_rate),
+  _cursor_keys_active(copy._cursor_keys_active),
+  _obscure_mode(copy._obscure_mode)
+{
+  _cursor_stale = true;
+  _last_text_def = (qpTextNode *)NULL;
+  _text_geom_stale = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpPGEntry::
+make_copy() const {
+  return new qpPGEntry(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::has_cull_callback
+//       Access: Protected, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this node during the cull
+//               traversal.
+////////////////////////////////////////////////////////////////////
+bool qpPGEntry::
+has_cull_callback() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::cull_callback
+//       Access: Protected, Virtual
+//  Description: If has_cull_callback() returns true, this function
+//               will be called during the cull traversal to perform
+//               any additional operations that should be performed at
+//               cull time.  This may include additional manipulation
+//               of render state or additional visible/invisible
+//               decisions, or any other arbitrary operation.
+//
+//               By the time this function is called, the node has
+//               already passed the bounding-volume test for the
+//               viewing frustum, and the node's transform and state
+//               have already been applied to the indicated
+//               CullTraverserData object.
+//
+//               The return value is true if this node should be
+//               visible, or false if it should be culled.
+////////////////////////////////////////////////////////////////////
+bool qpPGEntry::
+cull_callback(qpCullTraverser *trav, CullTraverserData &data) {
+  qpPGItem::cull_callback(trav, data);
+  update_text();
+  update_cursor();
+
+  // Now render the text.
+  nassertr(_text_render_root != (PandaNode *)NULL, true);
+  trav->traverse(_text_render_root, data);
+
+  // Now continue to render everything else below this node.
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::press
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever a
+//               mouse or keyboard entry is depressed while the mouse
+//               is within the region.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry::
+press(const MouseWatcherParameter &param, bool background) {
+  if (get_active()) {
+    if (param.has_button()) {
+      ButtonHandle button = param.get_button();
+      
+      if (button == MouseButton::one() ||
+          button == MouseButton::two() ||
+          button == MouseButton::three()) {
+        // Mouse button; set focus.
+        set_focus(true);
+        
+      } else if ((!background && get_focus()) || 
+                 (background && get_background_focus())) {
+        // Keyboard button.
+        _cursor_position = min(_cursor_position, (int)_wtext.length());
+        _blink_start = ClockObject::get_global_clock()->get_frame_time();
+        if (button == KeyboardButton::enter()) {
+          // Enter.  Accept the entry.
+          accept(param);
+          
+        } else if (button == KeyboardButton::backspace()) {
+          // Backspace.  Remove the character to the left of the cursor.
+          if (_cursor_position > 0) {
+            if (_obscure_mode && _obscured_wtext.length() == _wtext.length()) {
+              _obscured_wtext.erase(_obscured_wtext.begin() + _obscured_wtext.length() - 1);
+            }
+            _wtext.erase(_wtext.begin() + _cursor_position - 1);
+            _cursor_position--;
+            _cursor_stale = true;
+            _text_geom_stale = true;
+            erase(param);
+          }
+          
+        } else if (button == KeyboardButton::del()) {
+          // Delete.  Remove the character to the right of the cursor.
+          if (_cursor_position < (int)_wtext.length()) {
+            if (_obscure_mode && _obscured_wtext.length() == _wtext.length()) {
+              _obscured_wtext.erase(_obscured_wtext.begin() + _obscured_wtext.length() - 1);
+            }
+            _wtext.erase(_wtext.begin() + _cursor_position);
+            _text_geom_stale = true;
+            erase(param);
+          }
+          
+        } else if (button == KeyboardButton::left()) {
+          if (_cursor_keys_active) {
+            // Left arrow.  Move the cursor position to the left.
+            _cursor_position = max(_cursor_position - 1, 0);
+            _cursor_stale = true;
+          }
+          
+        } else if (button == KeyboardButton::right()) {
+          if (_cursor_keys_active) {
+            // Right arrow.  Move the cursor position to the right.
+            _cursor_position = min(_cursor_position + 1, (int)_wtext.length());
+            _cursor_stale = true;
+          }
+          
+        } else if (button == KeyboardButton::home()) {
+          if (_cursor_keys_active) {
+            // Home.  Move the cursor position to the beginning.
+            _cursor_position = 0;
+            _cursor_stale = true;
+          }
+          
+        } else if (button == KeyboardButton::end()) {
+          if (_cursor_keys_active) {
+            // End.  Move the cursor position to the end.
+            _cursor_position = _wtext.length();
+            _cursor_stale = true;
+          }
+          
+        } else if (!use_keystrokes && button.has_ascii_equivalent()) {
+          // This part of the code is deprecated and will be removed
+          // soon.  It only supports the old button up/down method of
+          // sending keystrokes, instead of the new keystroke method.
+          wchar_t key = button.get_ascii_equivalent();
+          if (isprint(key)) {
+            // A normal visible character.  Add a new character to the
+            // text entry, if there's room.
+            
+            if (get_max_chars() > 0 && (int)_wtext.length() >= get_max_chars()) {
+              overflow(param);
+            } else {
+              wstring new_text = 
+                _wtext.substr(0, _cursor_position) + key +
+                _wtext.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.
+              wstring measure_text;
+              if (_obscure_mode) {
+                measure_text = get_display_wtext() + (wchar_t)'*';
+              } else {
+                measure_text = new_text;
+              }
+              
+              // Check the length.
+              bool too_long = false;
+              if (_max_width > 0.0f) {
+                qpTextNode *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.
+                  wstring 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).
+                    wstring 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 (_wtext.length() >= 1 && 
+                          _wtext[_wtext.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 {
+                _wtext = new_text;
+                if (_obscure_mode) {
+                  _obscured_wtext = measure_text;
+                }
+                
+                _cursor_position++;
+                _cursor_stale = true;
+                _text_geom_stale = true;
+                type(param);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  qpPGItem::press(param, background);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::keystroke
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever
+//               the user types a key.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry::
+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.
+          wstring new_char(1, (wchar_t)keycode);
+
+          if (get_max_chars() > 0 && (int)_wtext.length() >= get_max_chars()) {
+            overflow(param);
+          } else {
+            _cursor_position = min(_cursor_position, (int)_wtext.length());
+            wstring new_text = 
+              _wtext.substr(0, _cursor_position) + new_char +
+              _wtext.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.
+            wstring measure_text;
+            if (_obscure_mode) {
+              measure_text = get_display_wtext() + (wchar_t)'*';
+            } else {
+              measure_text = new_text;
+            }
+
+            // Check the length.
+            bool too_long = false;
+            if (_max_width > 0.0f) {
+              qpTextNode *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.
+                wstring 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).
+                  wstring 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 (_wtext.length() >= 1 && 
+                        _wtext[_wtext.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 {
+              _wtext = new_text;
+              if (_obscure_mode) {
+                _obscured_wtext = measure_text;
+              }
+              
+              _cursor_position += new_char.length();
+              _cursor_stale = true;
+              _text_geom_stale = true;
+              type(param);
+            }
+          }
+        }
+      }
+    }
+  }
+  qpPGItem::keystroke(param, background);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::accept
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               entry is accepted by the user pressing Enter normally.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry::
+accept(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_accept_event(param.get_button());
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+  set_focus(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::overflow
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               entry is overflowed because the user attempts to type
+//               too many characters, exceeding either set_max_chars()
+//               or set_max_width().
+////////////////////////////////////////////////////////////////////
+void qpPGEntry::
+overflow(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_overflow_event();
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::type
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               user extends the text by typing.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry::
+type(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_type_event();
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::erase
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               user erase characters in the text.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry::
+erase(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_erase_event();
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::setup
+//       Access: Published
+//  Description: Sets up the entry for normal use.  The width is the
+//               maximum width of characters that will be typed, and
+//               num_lines is the integer number of lines of text of
+//               the entry.  Both of these together determine the size
+//               of the entry, based on the qpTextNode in effect.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry::
+setup(float width, int num_lines) {
+  set_text(string());
+  _cursor_position = 0;
+  set_max_chars(0);
+  set_max_width(width);
+  set_num_lines(num_lines);
+
+  qpTextNode *text_node = get_text_def(S_focus);
+  float line_height = text_node->get_line_height();
+
+  // Define determine the four corners of the frame.
+  LPoint3f ll(0.0f, 0.0f, -0.3f * line_height - (line_height * (num_lines - 1)));
+  LPoint3f ur(width, 0.0f, line_height);
+  LPoint3f lr(ur[0], 0.0f, ll[2]);
+  LPoint3f ul(ll[0], 0.0f, ur[2]);
+
+  // Transform each corner by the qpTextNode's transform.
+  LMatrix4f mat = text_node->get_transform();
+  ll = ll * mat;
+  ur = ur * mat;
+  lr = lr * mat;
+  ul = ul * mat;
+
+  // And get the new minmax to define the frame.  We do all this work
+  // instead of just using the lower-left and upper-right corners,
+  // just in case the text was rotated.
+  LVecBase4f frame;
+  frame[0] = min(min(ll[0], ur[0]), min(lr[0], ul[0]));
+  frame[1] = max(max(ll[0], ur[0]), max(lr[0], ul[0]));
+  frame[2] = min(min(ll[2], ur[2]), min(lr[2], ul[2]));
+  frame[3] = max(max(ll[2], ur[2]), max(lr[2], ul[2]));
+
+  switch (text_node->get_align()) {
+  case qpTextNode::A_left:
+    // The default case.
+    break;
+
+  case qpTextNode::A_center:
+    frame[0] = -width / 2.0;
+    frame[1] = width / 2.0;
+    break;
+
+  case qpTextNode::A_right:
+    frame[0] = -width;
+    frame[1] = 0.0f;
+    break;
+  }
+
+  set_frame(frame[0] - 0.15f, frame[1] + 0.15f, frame[2], frame[3]);
+
+  PGFrameStyle style;
+  style.set_width(0.1f, 0.1f);
+  style.set_type(PGFrameStyle::T_bevel_in);
+  style.set_color(0.8f, 0.8f, 0.8f, 1.0f);
+
+  set_frame_style(S_no_focus, style);
+
+  style.set_color(0.9f, 0.9f, 0.9f, 1.0f);
+  set_frame_style(S_focus, style);
+
+  style.set_color(0.6f, 0.6f, 0.6f, 1.0f);
+  set_frame_style(S_inactive, style);
+
+  // Set up a default cursor: a vertical bar.
+  clear_cursor_def();
+
+  /*
+  LineSegs ls;
+  ls.set_color(0.0f, 0.0f, 0.0f, 1.0f);
+  ls.move_to(0.0f, 0.0f, -0.15f * line_height);
+  ls.draw_to(0.0f, 0.0f, 0.85f * line_height);
+  new RenderRelation(get_cursor_def(), ls.create());
+  */
+
+  // An underscore cursor would work too.
+  text_node->set_text("_");
+  get_cursor_def()->add_child(text_node->generate());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_text_def
+//       Access: Published
+//  Description: Changes the qpTextNode that will be used to render the
+//               text within the entry when the entry is in the
+//               indicated state.  The default if nothing is specified
+//               is the same qpTextNode returned by
+//               qpPGItem::get_text_node().
+//
+//               It is the responsibility of the user to ensure that
+//               this qpTextNode has been frozen by a call to freeze().
+//               Passing in an unfrozen qpTextNode will result in
+//               needless work.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry::
+set_text_def(int state, qpTextNode *node) {
+  nassertv(state >= 0 && state < 1000);  // Sanity check.
+  if (node == (qpTextNode *)NULL && state >= (int)_text_defs.size()) {
+    // If we're setting it to NULL, we don't need to slot a new one.
+    return;
+  }
+  slot_text_def(state);
+
+  _text_defs[state] = node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_text_def
+//       Access: Published
+//  Description: Returns the qpTextNode that will be used to render the
+//               text within the entry when the entry is in the
+//               indicated state.  See set_text_def().
+////////////////////////////////////////////////////////////////////
+qpTextNode *qpPGEntry:: 
+get_text_def(int state) const {
+  if (state < 0 || state >= (int)_text_defs.size()) {
+    // If we don't have a definition, use the global one.
+    return get_text_node();
+  }
+  if (_text_defs[state] == (qpTextNode *)NULL) {
+    return get_text_node();
+  }
+  return _text_defs[state];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_active
+//       Access: Published, Virtual
+//  Description: Toggles the active/inactive state of the entry.  In
+//               the case of a qpPGEntry, this also changes its visual
+//               appearance.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry:: 
+set_active(bool active) {
+  qpPGItem::set_active(active);
+  update_state();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::set_focus
+//       Access: Published, Virtual
+//  Description: Toggles the focus state of the entry.  In the case of
+//               a qpPGEntry, this also changes its visual appearance.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry:: 
+set_focus(bool focus) {
+  qpPGItem::set_focus(focus);
+  _blink_start = ClockObject::get_global_clock()->get_frame_time();
+  update_state();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::get_display_wtext
+//       Access: Private
+//  Description: Returns the string that should be displayed within
+//               the entry.  This is normally _wtext, but it may be
+//               _obscured_wtext.
+////////////////////////////////////////////////////////////////////
+const wstring &qpPGEntry::
+get_display_wtext() {
+  if (_obscure_mode) {
+    // If obscure mode is enabled, we should just display a bunch of
+    // asterisks.
+    if (_obscured_wtext.length() != _wtext.length()) {
+      _obscured_wtext = wstring();
+      wstring::const_iterator ti;
+      for (ti = _wtext.begin(); ti != _wtext.end(); ++ti) {
+        _obscured_wtext += (wchar_t)'*';
+      }
+    }
+
+    return _obscured_wtext;
+
+  } else {
+    // In normal, non-obscure mode, we display the actual text.
+    return _wtext;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::slot_text_def
+//       Access: Private
+//  Description: Ensures there is a slot in the array for the given
+//               text definition.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry::
+slot_text_def(int state) {
+  while (state >= (int)_text_defs.size()) {
+    _text_defs.push_back((qpTextNode *)NULL);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::update_text
+//       Access: Private
+//  Description: Causes the qpPGEntry to recompute its text, if
+//               necessary.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry:: 
+update_text() {
+  qpTextNode *node = get_text_def(get_state());
+  nassertv(node != (qpTextNode *)NULL);
+
+  if (_text_geom_stale || node != _last_text_def) {
+    const wstring &display_wtext = get_display_wtext();
+
+    // We need to regenerate.
+    _last_text_def = node;
+
+    if (_max_width > 0.0f && _num_lines > 1) {
+      // Fold the text into multiple lines.
+      wstring ww_text = 
+        _last_text_def->wordwrap_to(display_wtext, _max_width, true);
+
+      // And chop the lines up into pieces.
+      _ww_lines.clear();
+      size_t p = 0;
+      size_t q = ww_text.find((wchar_t)'\n');
+      while (q != string::npos) {
+        _ww_lines.push_back(WWLine());
+        WWLine &line = _ww_lines.back();
+        line._str = ww_text.substr(p, q - p);
+
+        // Get the left edge of the text at this line.
+        line._left = 0.0f;
+        if (_last_text_def->get_align() != qpTextNode::A_left) {
+          _last_text_def->set_wtext(line._str);
+          line._left = _last_text_def->get_left();
+        }
+
+        p = q + 1;
+        q = ww_text.find('\n', p);
+      }
+      _ww_lines.push_back(WWLine());
+      WWLine &line = _ww_lines.back();
+      line._str = ww_text.substr(p);
+      
+      // Get the left edge of the text at this line.
+      line._left = 0.0f;
+      if (_last_text_def->get_align() != qpTextNode::A_left) {
+        _last_text_def->set_wtext(line._str);
+        line._left = _last_text_def->get_left();
+      }
+
+      _last_text_def->set_wtext(ww_text);
+
+    } else {
+      // Only one line.
+      _ww_lines.clear();
+      _ww_lines.push_back(WWLine());
+      WWLine &line = _ww_lines.back();
+      line._str = display_wtext;
+
+      _last_text_def->set_wtext(display_wtext);
+      line._left = _last_text_def->get_left();
+    }
+
+    if (_current_text_node != (PandaNode *)NULL) {
+      _text_render_root->remove_child(_current_text_node);
+    }
+    _current_text_node = _last_text_def->generate();
+    _text_render_root->add_child(_current_text_node);
+    _text_geom_stale = false;
+    _cursor_stale = true;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::update_cursor
+//       Access: Private
+//  Description: Moves the cursor to its correct position.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry:: 
+update_cursor() {
+  qpTextNode *node = get_text_def(get_state());
+  nassertv(node != (qpTextNode *)NULL);
+
+  if (_cursor_stale || node != _last_text_def) {
+    update_text();
+
+    _cursor_position = min(_cursor_position, (int)_wtext.length());
+
+    // Determine the row and column of the cursor.
+    int row = 0;
+    int column = _cursor_position;
+    while (row + 1 < (int)_ww_lines.size() &&
+           column > (int)_ww_lines[row]._str.length()) {
+      column -= _ww_lines[row]._str.length();
+      row++;
+    }
+
+    nassertv(row >= 0 && row < (int)_ww_lines.size());
+    nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length());
+
+    float width = 
+      _last_text_def->calc_width(_ww_lines[row]._str.substr(0, column));
+    float line_height = _last_text_def->get_line_height();
+
+    LVecBase3f trans(_ww_lines[row]._left + width, 0.0f, -line_height * row);
+    _cursor_def->set_transform(TransformState::make_pos(trans));
+
+    _cursor_stale = false;
+  }
+
+  // Should the cursor be visible?
+  if (!get_focus()) {
+    show_hide_cursor(false);
+  } else {
+    double elapsed_time = 
+      ClockObject::get_global_clock()->get_frame_time() - _blink_start;
+    int cycle = (int)(elapsed_time * _blink_rate * 2.0f);
+    bool visible = ((cycle & 1) == 0);
+    show_hide_cursor(visible);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::show_hide_cursor
+//       Access: Private
+//  Description: Makes the cursor visible or invisible, e.g. during a
+//               blink cycle.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry:: 
+show_hide_cursor(bool visible) {
+  if (visible != _cursor_visible) {
+    if (visible) {
+      // Reveal the cursor.
+      _cursor_def->set_draw_mask(DrawMask::all_on());
+    } else {
+      // Hide the cursor.
+      _cursor_def->set_draw_mask(DrawMask::all_off());
+    }
+    _cursor_visible = visible;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGEntry::update_state
+//       Access: Private
+//  Description: Determines what the correct state for the qpPGEntry
+//               should be.
+////////////////////////////////////////////////////////////////////
+void qpPGEntry:: 
+update_state() {
+  if (get_active()) {
+    if (get_focus()) {
+      set_state(S_focus);
+    } else {
+      set_state(S_no_focus);
+    }
+  } else {
+    set_state(S_inactive);
+  }
+}

+ 194 - 0
panda/src/pgui/qppgEntry.h

@@ -0,0 +1,194 @@
+// Filename: qppgEntry.h
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpPGENTRY_H
+#define qpPGENTRY_H
+
+#include "pandabase.h"
+
+#include "qppgItem.h"
+
+#include "qptextNode.h"
+#include "pointerTo.h"
+#include "pvector.h"
+#include "clockObject.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : qpPGEntry
+// Description : This is a particular kind of PGItem that handles
+//               simple one-line text entries, of the sort where the
+//               user can type any string.
+//
+//               A qpPGEntry does all of its internal manipulation on a
+//               wide string, so it can store the full Unicode
+//               character set.  The interface can support either the
+//               wide string getters and setters, or the normal 8-bit
+//               string getters and setters, which use whatever
+//               encoding method is specified by the associated
+//               TextNode.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpPGEntry : public qpPGItem {
+PUBLISHED:
+  qpPGEntry(const string &name);
+  virtual ~qpPGEntry();
+
+protected:
+  qpPGEntry(const qpPGEntry &copy);
+
+public:
+  virtual PandaNode *make_copy() const;
+  virtual bool has_cull_callback() const;
+  virtual bool cull_callback(qpCullTraverser *trav, CullTraverserData &data);
+
+  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);
+  virtual void type(const MouseWatcherParameter &param);
+  virtual void erase(const MouseWatcherParameter &param);
+
+PUBLISHED:
+  enum State {
+    S_focus = 0,
+    S_no_focus,
+    S_inactive
+  };
+
+  void setup(float width, int num_lines);
+
+  INLINE void set_text(const string &text);
+  INLINE string get_text() const;
+
+  INLINE void set_cursor_position(int position);
+  INLINE int get_cursor_position() const;
+
+  INLINE void set_max_chars(int max_chars);
+  INLINE int get_max_chars() const;
+  INLINE void set_max_width(float max_width);
+  INLINE float get_max_width() const;
+  INLINE void set_num_lines(int num_lines);
+  INLINE int get_num_lines() const;
+
+  INLINE void set_blink_rate(float blink_rate);
+  INLINE float get_blink_rate() const;
+
+  INLINE PandaNode *get_cursor_def();
+  INLINE void clear_cursor_def();
+
+  INLINE void set_cursor_keys_active(bool flag);
+  INLINE bool get_cursor_keys_active() const;
+
+  INLINE void set_obscure_mode(bool flag);
+  INLINE bool get_obscure_mode() const;
+
+  void set_text_def(int state, qpTextNode *node);
+  qpTextNode *get_text_def(int state) const;
+
+  virtual void set_active(bool active);
+  virtual void set_focus(bool focus);
+
+  INLINE static string get_accept_prefix();
+  INLINE static string get_overflow_prefix();
+  INLINE static string get_type_prefix();
+  INLINE static string get_erase_prefix();
+
+  INLINE string get_accept_event(const ButtonHandle &button) const;
+  INLINE string get_overflow_event() const;
+  INLINE string get_type_event() const;
+  INLINE string get_erase_event() const;
+
+public:
+  INLINE void set_wtext(const wstring &wtext);
+  INLINE const wstring &get_wtext() const;
+
+
+private:
+  const wstring &get_display_wtext();
+  void slot_text_def(int state);
+  void update_text();
+  void update_cursor();
+  void show_hide_cursor(bool visible);
+  void update_state();
+
+  wstring _wtext;
+  wstring _obscured_wtext;
+  int _cursor_position;
+  bool _cursor_stale;
+  bool _cursor_visible;
+
+  int _max_chars;
+  float _max_width;
+  int _num_lines;
+
+  typedef pvector< PT(qpTextNode) > TextDefs;
+  TextDefs _text_defs;
+
+  // This node is the root of the subgraph that renders both the text
+  // and the cursor.
+  PT(PandaNode) _text_render_root;
+
+  // This is the node for rendering the actual text that is parented
+  // to the above node when the text is generated.
+  PT(PandaNode) _current_text_node;
+  qpTextNode *_last_text_def;
+  bool _text_geom_stale;
+
+  // This is a list of each row of text in the entry, after it has
+  // been wordwrapped by update_text().  It's used by update_cursor()
+  // to compute the correct cursor position.
+  class WWLine {
+  public:
+    wstring _str;
+    float _left;
+  };
+  typedef pvector<WWLine> WWLines;
+  WWLines _ww_lines;
+
+  // This is the node that represents the cursor geometry.  It is also
+  // attached to the above node, and is transformed around and/or
+  // hidden according to the cursor's position and blink state.
+  PandaNode *_cursor_def;
+
+  double _blink_start;
+  double _blink_rate;
+
+  bool _cursor_keys_active;
+  bool _obscure_mode;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    qpPGItem::init_type();
+    register_type(_type_handle, "qpPGEntry",
+                  qpPGItem::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "qppgEntry.I"
+
+#endif

+ 468 - 0
panda/src/pgui/qppgItem.I

@@ -0,0 +1,468 @@
+// Filename: qppgItem.I
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_region
+//       Access: Public
+//  Description: Returns the MouseWatcherRegion associated with this
+//               item.  Every qpPGItem has a MouseWatcherRegion
+//               associated with it, that is created when the qpPGItem
+//               is created; it does not change during the lifetime of
+//               the qpPGItem.  Even items that do not have a frame have
+//               an associated MouseWatcherRegion, although it will
+//               not be used in this case.
+////////////////////////////////////////////////////////////////////
+INLINE PGMouseWatcherRegion *qpPGItem:: 
+get_region() const {
+  return _region;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_frame
+//       Access: Published
+//  Description: Sets the bounding rectangle of the item, in local
+//               coordinates.  This is the region on screen within
+//               which the mouse will be considered to be within the
+//               item.  Normally, it should correspond to the bounding
+//               rectangle of the visible geometry of the item.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGItem::
+set_frame(float left, float right, float bottom, float top) {
+  set_frame(LVecBase4f(left, right, bottom, top));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_frame
+//       Access: Published
+//  Description: Sets the bounding rectangle of the item, in local
+//               coordinates.  This is the region on screen within
+//               which the mouse will be considered to be within the
+//               item.  Normally, it should correspond to the bounding
+//               rectangle of the visible geometry of the item.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGItem::
+set_frame(const LVecBase4f &frame) {
+  _has_frame = true;
+  _frame = frame;
+  mark_frames_stale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_frame
+//       Access: Published
+//  Description: Returns the bounding rectangle of the item.  See
+//               set_frame().  It is an error to call this if
+//               has_frame() returns false.
+////////////////////////////////////////////////////////////////////
+INLINE const LVecBase4f &qpPGItem::
+get_frame() const {
+  nassertr(has_frame(), _frame);
+  return _frame;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::has_frame
+//       Access: Published
+//  Description: Returns true if the item has a bounding rectangle;
+//               see set_frame().
+////////////////////////////////////////////////////////////////////
+INLINE bool qpPGItem::
+has_frame() const {
+  return _has_frame;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::clear_frame
+//       Access: Published
+//  Description: Removes the bounding rectangle from the item.  It
+//               will no longer be possible to position the mouse
+//               within the item; see set_frame().
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGItem::
+clear_frame() {
+  _has_frame = false;
+  mark_frames_stale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_state
+//       Access: Published
+//  Description: Sets the "state" of this particular qpPGItem.  
+//
+//               The qpPGItem node will render as if it were the
+//               subgraph assigned to the corresponding index via
+//               set_state_def().
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGItem::
+set_state(int state) {
+  _state = state;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_state
+//       Access: Published
+//  Description: Returns the "state" of this particular qpPGItem.  See
+//               set_state().
+////////////////////////////////////////////////////////////////////
+INLINE int qpPGItem::
+get_state() const {
+  return _state;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_active
+//       Access: Published
+//  Description: Returns whether the qpPGItem is currently active for
+//               mouse events.  See set_active().
+////////////////////////////////////////////////////////////////////
+INLINE bool qpPGItem::
+get_active() const {
+  return (_flags & F_active) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_focus
+//       Access: Published
+//  Description: Returns whether the qpPGItem currently has focus for
+//               keyboard events.  See set_focus().
+////////////////////////////////////////////////////////////////////
+INLINE bool qpPGItem::
+get_focus() const {
+  return (_flags & F_focus) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_background_focus
+//       Access: Published
+//  Description: Returns whether background_focus is currently
+//               enabled.  See set_background_focus().
+////////////////////////////////////////////////////////////////////
+INLINE bool qpPGItem::
+get_background_focus() const {
+  return (_flags & F_background_focus) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_suppress_flags
+//       Access: Published
+//  Description: This is just an interface to set the suppress flags
+//               on the underlying MouseWatcherRegion.  See
+//               MouseWatcherRegion::set_suppress_flags().
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGItem::
+set_suppress_flags(int suppress_flags) {
+  _region->set_suppress_flags(suppress_flags);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_suppress_flags
+//       Access: Published
+//  Description: This is just an interface to get the suppress flags
+//               on the underlying MouseWatcherRegion.  See
+//               MouseWatcherRegion::get_suppress_flags().
+////////////////////////////////////////////////////////////////////
+INLINE int qpPGItem::
+get_suppress_flags() const {
+  return _region->get_suppress_flags();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_id
+//       Access: Published
+//  Description: Returns the unique ID assigned to this qpPGItem.  This
+//               will be assigned to the region created with the
+//               MouseWatcher, and will thus be used to generate event
+//               names.
+////////////////////////////////////////////////////////////////////
+INLINE const string &qpPGItem::
+get_id() const {
+  return _region->get_name();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_id
+//       Access: Published
+//  Description: Set the unique ID assigned to this qpPGItem.  It is the
+//               user's responsibility to ensure that this ID is
+//               unique.
+//
+//               Normally, this should not need to be called, as the
+//               qpPGItem will assign itself an ID when it is created,
+//               but this function allows the user to decide to
+//               redefine the ID to be something possibly more
+//               meaningful.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGItem::
+set_id(const string &id) {
+  _region->set_name(id);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_enter_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the enter
+//               event for all qpPGItems.  The enter event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_enter_prefix() {
+  return "enter-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_exit_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the exit
+//               event for all qpPGItems.  The exit event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_exit_prefix() {
+  return "exit-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_within_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the within
+//               event for all qpPGItems.  The within event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_within_prefix() {
+  return "within-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_without_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the without
+//               event for all qpPGItems.  The without event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_without_prefix() {
+  return "without-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_focus_in_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the focus_in
+//               event for all qpPGItems.  The focus_in event is the
+//               concatenation of this string followed by get_id().
+//
+//               Unlike most item events, this event is thrown with no
+//               parameters.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_focus_in_prefix() {
+  return "fin-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_focus_out_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the focus_out
+//               event for all qpPGItems.  The focus_out event is the
+//               concatenation of this string followed by get_id().
+//
+//               Unlike most item events, this event is thrown with no
+//               parameters.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_focus_out_prefix() {
+  return "fout-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_press_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the press
+//               event for all qpPGItems.  The press event is the
+//               concatenation of this string followed by a button
+//               name, followed by a hyphen and get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_press_prefix() {
+  return "press-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_release_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the release
+//               event for all qpPGItems.  The release event is the
+//               concatenation of this string followed by a button
+//               name, followed by a hyphen and get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_release_prefix() {
+  return "release-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_keystroke_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the
+//               keystroke event for all qpPGItems.  The keystroke event
+//               is the concatenation of this string followed by a
+//               hyphen and get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_keystroke_prefix() {
+  return "keystroke-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_enter_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item is active and the mouse enters its frame, but
+//               not any nested frames.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_enter_event() const {
+  return get_enter_prefix() + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_exit_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item is active and the mouse exits its frame, or
+//               enters a nested frame.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_exit_event() const {
+  return get_exit_prefix() + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_within_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item is active and the mouse moves within the
+//               boundaries of the frame.  This is different from the
+//               enter_event in that the mouse is considered within
+//               the frame even if it is also within a nested frame.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_within_event() const {
+  return get_within_prefix() + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_without_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item is active and the mouse moves completely outside
+//               the boundaries of the frame.  This is different from
+//               the exit_event in that the mouse is considered
+//               within the frame even if it is also within a nested
+//               frame.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_without_event() const {
+  return get_without_prefix() + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_focus_in_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item gets the keyboard focus.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_focus_in_event() const {
+  return get_focus_in_prefix() + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_focus_out_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item loses the keyboard focus.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_focus_out_event() const {
+  return get_focus_out_prefix() + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_press_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 depressed while the mouse is within the
+//               frame.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_press_event(const ButtonHandle &button) const {
+  return get_press_prefix() + button.get_name() + "-" + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_release_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               item is active and the indicated mouse or keyboard
+//               button, formerly clicked down is within the frame, is
+//               released.
+////////////////////////////////////////////////////////////////////
+INLINE string qpPGItem::
+get_release_event(const ButtonHandle &button) const {
+  return get_release_prefix() + button.get_name() + "-" + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::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 qpPGItem::
+get_keystroke_event() const {
+  return get_keystroke_prefix() + get_id();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_text_node
+//       Access: Published, Static
+//  Description: Changes the TextNode object that will be used by all
+//               qpPGItems to generate default labels given a string.
+//               This can be loaded with the default font, etc.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGItem::
+set_text_node(qpTextNode *node) {
+  _text_node = node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_focus_item
+//       Access: Published, Static
+//  Description: Returns the one qpPGItem in the world that currently
+//               has keyboard focus, if any, or NULL if no item has
+//               keyboard focus.  Use qpPGItem::set_focus() to activate
+//               or deactivate keyboard focus on a particular item.
+////////////////////////////////////////////////////////////////////
+INLINE qpPGItem *qpPGItem::
+get_focus_item() {
+  return _focus_item;
+}

+ 849 - 0
panda/src/pgui/qppgItem.cxx

@@ -0,0 +1,849 @@
+// Filename: qppgItem.cxx
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qppgItem.h"
+#include "pgTop.h"
+#include "pgMouseWatcherParameter.h"
+#include "pgCullTraverser.h"
+#include "config_pgui.h"
+
+#include "pandaNode.h"
+#include "throw_event.h"
+#include "string_utils.h"
+#include "qpnodePath.h"
+#include "qpcullTraverser.h"
+
+#ifdef HAVE_AUDIO
+#include "audioSound.h"
+#endif
+
+TypeHandle qpPGItem::_type_handle;
+PT(qpTextNode) qpPGItem::_text_node;
+qpPGItem *qpPGItem::_focus_item = (qpPGItem *)NULL;
+qpPGItem::BackgroundFocus qpPGItem::_background_focus;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGItem::
+qpPGItem(const string &name) : 
+  PandaNode(name)
+{
+  _has_frame = false;
+  _frame.set(0, 0, 0, 0);
+  _region = new PGMouseWatcherRegion(this);
+  _state = 0;
+  _flags = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGItem::
+~qpPGItem() {
+  nassertv(_region->_qpitem == this);
+  _region->_qpitem = (qpPGItem *)NULL;
+
+  set_background_focus(false);
+  if (_focus_item == this) {
+    _focus_item = (qpPGItem *)NULL;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGItem::
+qpPGItem(const qpPGItem &copy) :
+  PandaNode(copy),
+  _has_frame(copy._has_frame),
+  _frame(copy._frame),
+  _state(copy._state),
+  _flags(copy._flags)
+{
+  _region = new PGMouseWatcherRegion(this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::make_copy
+//       Access: Protected, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpPGItem::
+make_copy() const {
+  return new qpPGItem(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::xform
+//       Access: Protected, Virtual
+//  Description: Transforms the contents of this node by the indicated
+//               matrix, if it means anything to do so.  For most
+//               kinds of nodes, this does nothing.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+xform(const LMatrix4f &mat) {
+  // Transform the frame.
+  LPoint3f ll(_frame[0], 0.0, _frame[2]);
+  LPoint3f ur(_frame[1], 0.0, _frame[3]);
+  ll = ll * mat;
+  ur = ur * mat;
+  _frame.set(ll[0], ur[0], ll[2], ur[2]);
+
+  // Transform the individual states and their frame styles.
+  StateDefs::iterator di;
+  for (di = _state_defs.begin(); di != _state_defs.end(); ++di) {
+    PandaNode *node = (*di)._node;
+    if (node != (PandaNode *)NULL) {
+      // Apply the matrix to the previous transform.
+      node->set_transform(node->get_transform()->compose(TransformState::make_mat(mat)));
+
+      /*
+      // Now flatten the transform into the subgraph.
+      SceneGraphReducer gr;
+      gr.apply_transitions(arc);
+      */
+    }
+
+    // Transform the frame style too.
+    if ((*di)._frame_style.xform(mat)) {
+      (*di)._frame_stale = true;
+    }
+  }
+  mark_bound_stale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::has_cull_callback
+//       Access: Protected, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this node during the cull
+//               traversal.
+////////////////////////////////////////////////////////////////////
+bool qpPGItem::
+has_cull_callback() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::cull_callback
+//       Access: Protected, Virtual
+//  Description: If has_cull_callback() returns true, this function
+//               will be called during the cull traversal to perform
+//               any additional operations that should be performed at
+//               cull time.  This may include additional manipulation
+//               of render state or additional visible/invisible
+//               decisions, or any other arbitrary operation.
+//
+//               By the time this function is called, the node has
+//               already passed the bounding-volume test for the
+//               viewing frustum, and the node's transform and state
+//               have already been applied to the indicated
+//               CullTraverserData object.
+//
+//               The return value is true if this node should be
+//               visible, or false if it should be culled.
+////////////////////////////////////////////////////////////////////
+bool qpPGItem::
+cull_callback(qpCullTraverser *trav, CullTraverserData &data) {
+  if (has_frame() && get_active()) {
+    // The item has a frame, so we want to generate a region for it
+    // and update the MouseWatcher.
+
+    // We can only do this if our traverser is a PGCullTraverser
+    // (which will be the case if this node was parented somewhere
+    // under a PGTop node).
+    if (trav->is_exact_type(PGCullTraverser::get_class_type())) {
+      PGCullTraverser *pg_trav;
+      DCAST_INTO_R(pg_trav, trav, true);
+
+      const LMatrix4f &transform = data._net_transform->get_mat();
+      activate_region(transform, pg_trav->_sort_index);
+      pg_trav->_sort_index++;
+
+      pg_trav->_top->add_region(get_region());
+    }
+  }
+
+  if (has_state_def(get_state())) {
+    // This item has a current state definition that we should use
+    // to render the item.
+    PandaNode *def = get_state_def(get_state());
+    trav->traverse(def, data);
+  }
+
+  // Now continue to render everything else below this node.
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::recompute_internal_bound
+//       Access: Protected, Virtual
+//  Description: Called when needed to recompute the node's
+//               _internal_bound object.  Nodes that contain anything
+//               of substance should redefine this to do the right
+//               thing.
+////////////////////////////////////////////////////////////////////
+BoundingVolume *qpPGItem::
+recompute_internal_bound() {
+  // First, get ourselves a fresh, empty bounding volume.
+  BoundingVolume *bound = PandaNode::recompute_internal_bound();
+  nassertr(bound != (BoundingVolume *)NULL, bound);
+
+  // Now actually compute the bounding volume by putting it around all
+  // of our states' bounding volumes.
+  pvector<const BoundingVolume *> child_volumes;
+
+  // We walk through the list of state defs indirectly, calling
+  // get_state_def() on each one, to ensure that the frames are
+  // updated correctly before we measure their bounding volumes.
+  for (int i = 0; i < (int)_state_defs.size(); i++) {
+    PandaNode *node = get_state_def(i);
+    if (node != (PandaNode *)NULL) {
+      child_volumes.push_back(&node->get_bound());
+    }
+  }
+
+  const BoundingVolume **child_begin = &child_volumes[0];
+  const BoundingVolume **child_end = child_begin + child_volumes.size();
+
+  bound->around(child_begin, child_end);
+  return bound;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::activate_region
+//       Access: Public
+//  Description: Applies the indicated scene graph transform and order
+//               as determined by the traversal from PGTop.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+activate_region(const LMatrix4f &transform, int sort) {
+  // Transform all four vertices, and get the new bounding box.  This
+  // way the region works (mostly) even if has been rotated.
+  LPoint3f ll(_frame[0], 0.0, _frame[2]);
+  LPoint3f lr(_frame[1], 0.0, _frame[2]);
+  LPoint3f ul(_frame[0], 0.0, _frame[3]);
+  LPoint3f ur(_frame[1], 0.0, _frame[3]);
+  ll = ll * transform;
+  lr = lr * transform;
+  ul = ul * transform;
+  ur = ur * transform;
+  _region->set_frame(min(min(ll[0], lr[0]), min(ul[0], ur[0])),
+                     max(max(ll[0], lr[0]), max(ul[0], ur[0])),
+                     min(min(ll[2], lr[2]), min(ul[2], ur[2])),
+                     max(max(ll[2], lr[2]), max(ul[2], ur[2])));
+                     
+  _region->set_sort(sort);
+  _region->set_active(true);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::enter
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse enters the region.  The mouse is only
+//               considered to be "entered" in one region at a time;
+//               in the case of nested regions, it exits the outer
+//               region before entering the inner one.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+enter(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_enter_event();
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::exit
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse exits the region.  The mouse is only considered
+//               to be "entered" in one region at a time; in the case
+//               of nested regions, it exits the outer region before
+//               entering the inner one.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+exit(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_exit_event();
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::within
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse moves within the boundaries of the region, even
+//               if it is also within the boundaries of a nested
+//               region.  This is different from "enter", which is
+//               only called whenever the mouse is within only that
+//               region.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+within(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_within_event();
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::without
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               mouse moves completely outside the boundaries of the
+//               region.  See within().
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+without(const MouseWatcherParameter &param) {
+  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+  string event = get_without_event();
+  play_sound(event);
+  throw_event(event, EventParameter(ep));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::focus_in
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               widget gets the keyboard focus.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+focus_in() {
+  string event = get_focus_in_event();
+  play_sound(event);
+  throw_event(event);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::focus_out
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               widget loses the keyboard focus.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+focus_out() {
+  string event = get_focus_out_event();
+  play_sound(event);
+  throw_event(event);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::press
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever a
+//               mouse or keyboard button is depressed while the mouse
+//               is within the region.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+press(const MouseWatcherParameter &param, bool background) {
+  if (!background) {
+    PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+    string event = get_press_event(param.get_button());
+    play_sound(event);
+    throw_event(event, EventParameter(ep));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::release
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever a
+//               mouse or keyboard button previously depressed with
+//               press() is released.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+release(const MouseWatcherParameter &param, bool background) {
+  if (!background) {
+    PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
+    string event = get_release_event(param.get_button());
+    play_sound(event);
+    throw_event(event, EventParameter(ep));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::keystroke
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever
+//               the user presses a key.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+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: qpPGItem::background_press
+//       Access: Public, Static
+//  Description: Calls press() on all the qpPGItems with background
+//               focus.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+background_press(const MouseWatcherParameter &param) {
+  BackgroundFocus::const_iterator fi;
+  for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) {
+    qpPGItem *item = *fi;
+    if (!item->get_focus()) {
+      item->press(param, true);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::background_release
+//       Access: Public, Static
+//  Description: Calls release() on all the qpPGItems with background
+//               focus.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+background_release(const MouseWatcherParameter &param) {
+  BackgroundFocus::const_iterator fi;
+  for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) {
+    qpPGItem *item = *fi;
+    if (!item->get_focus()) {
+      item->release(param, true);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::background_keystroke
+//       Access: Public, Static
+//  Description: Calls keystroke() on all the qpPGItems with background
+//               focus.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+background_keystroke(const MouseWatcherParameter &param) {
+  BackgroundFocus::const_iterator fi;
+  for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) {
+    qpPGItem *item = *fi;
+    if (!item->get_focus()) {
+      item->keystroke(param, true);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_active
+//       Access: Published, Virtual
+//  Description: Sets whether the qpPGItem is active for mouse watching.
+//               This is not necessarily related to the
+//               active/inactive appearance of the item, which is
+//               controlled by set_state(), but it does affect whether
+//               it responds to mouse events.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+set_active(bool active) {
+  if (active) {
+    _flags |= F_active;
+  } else {
+    _flags &= ~F_active;
+    // Deactivating the item automatically defocuses it too.
+    if (get_focus()) {
+      set_focus(false);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_focus
+//       Access: Published, Virtual
+//  Description: Sets whether the qpPGItem currently has keyboard focus.
+//               This simply means that the item may respond to
+//               keyboard events as well as to mouse events; precisely
+//               what this means is up to the individual item.  
+//
+//               Only one qpPGItem in the world is allowed to have focus
+//               at any given time.  Setting the focus on any other
+//               item automatically disables the focus from the
+//               previous item.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+set_focus(bool focus) {
+  if (focus) {
+    if (!get_active()) {
+      // Cannot set focus on an inactive item.
+      return;
+    }
+
+    // Set the keyboard focus to this item.
+    if (_focus_item != this) {
+      if (_focus_item != (qpPGItem *)NULL) {
+        // Clear the focus from whatever item currently has it.
+        _focus_item->set_focus(false);
+      }
+      _focus_item = this;
+    }
+    if (!get_focus()) {
+      focus_in();
+      _flags |= F_focus;
+    }
+
+  } else {
+    if (_focus_item == this) {
+      // Remove this item from the focus.
+      _focus_item = (qpPGItem *)NULL;
+    }
+
+    if (get_focus()) {
+      focus_out();
+      _flags &= ~F_focus;
+    }
+  }
+  _region->set_keyboard(focus);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_background_focus
+//       Access: Published
+//  Description: Sets the background_focus flag for this item.  When
+//               background_focus is enabled, the item will receive
+//               keypress events even if it is not in focus; in fact,
+//               even if it is not onscreen.  Unlike normal focus,
+//               many items may have background_focus simultaneously.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+set_background_focus(bool focus) {
+  if (focus != get_background_focus()) {
+    if (focus) {
+      // Activate background focus.
+      _flags |= F_background_focus;
+      bool inserted = _background_focus.insert(this).second;
+      nassertv(inserted);
+
+    } else {
+      // Deactivate background focus.
+      _flags &= ~F_background_focus;
+      size_t num_erased = _background_focus.erase(this);
+      nassertv(num_erased == 1);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_num_state_defs
+//       Access: Published
+//  Description: Returns one more than the highest-numbered state def
+//               that was ever assigned to the qpPGItem.  The complete
+//               set of state defs assigned may then be retrieved by
+//               indexing from 0 to (get_num_state_defs() - 1).
+//
+//               This is only an upper limit on the actual number of
+//               state defs, since there may be holes in the list.
+////////////////////////////////////////////////////////////////////
+int qpPGItem::
+get_num_state_defs() const {
+  return _state_defs.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::has_state_def
+//       Access: Published
+//  Description: Returns true if get_state_def() has ever been called
+//               for the indicated state (thus defining a render
+//               subgraph for this state index), false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpPGItem::
+has_state_def(int state) const {
+  if (state < 0 || state >= (int)_state_defs.size()) {
+    return false;
+  }
+  return (_state_defs[state]._node != (PandaNode *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::clear_state_def
+//       Access: Published
+//  Description: Resets the NodePath assigned to the indicated state
+//               to its initial default, with only a frame
+//               representation if appropriate.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+clear_state_def(int state) {
+  if (state < 0 || state >= (int)_state_defs.size()) {
+    return;
+  }
+
+  PandaNode *node = _state_defs[state]._node;
+  if (node != (PandaNode *)NULL) {
+    node->remove_all_children();
+  }
+
+  _state_defs[state]._frame_node = (PandaNode *)NULL;
+  _state_defs[state]._frame_stale = true;
+
+  mark_bound_stale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_state_def
+//       Access: Published
+//  Description: Returns the Node that is the root of the subgraph
+//               that will be drawn when the qpPGItem is in the
+//               indicated state.  The first time this is called for a
+//               particular state index, it may create the Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpPGItem::
+get_state_def(int state) {
+  nassertr(state >= 0 && state < 1000, (PandaNode *)NULL);  // Sanity check.
+  slot_state_def(state);
+
+  if (_state_defs[state]._node == (PandaNode *)NULL) {
+    // Create a new node.
+    _state_defs[state]._node = new PandaNode("state_" + format_string(state));
+    _state_defs[state]._frame_stale = true;
+  }
+
+  if (_state_defs[state]._frame_stale) {
+    update_frame(state);
+  }
+
+  return _state_defs[state]._node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::instance_to_state_def
+//       Access: Published
+//  Description: Parents an instance of the bottom node of the
+//               indicated NodePath to the indicated state index.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+instance_to_state_def(int state, const qpNodePath &path) {
+  if (path.is_empty()) {
+    // If the path is empty, quietly do nothing.
+    return;
+  }
+
+  get_state_def(state)->add_child(path.node());
+
+  mark_bound_stale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_frame_style
+//       Access: Published
+//  Description: Returns the kind of frame that will be drawn behind
+//               the item when it is in the indicated state.
+////////////////////////////////////////////////////////////////////
+PGFrameStyle qpPGItem::
+get_frame_style(int state) {
+  if (state < 0 || state >= (int)_state_defs.size()) {
+    return PGFrameStyle();
+  }
+  return _state_defs[state]._frame_style;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_frame_style
+//       Access: Published
+//  Description: Changes the kind of frame that will be drawn behind
+//               the item when it is in the indicated state.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+set_frame_style(int state, const PGFrameStyle &style) {
+  // Get the state def node, mainly to ensure that this state is
+  // slotted and listed as having been defined.
+  PandaNode *def = get_state_def(state);
+  nassertv(def != (PandaNode *)NULL);
+
+  _state_defs[state]._frame_style = style;
+  _state_defs[state]._frame_stale = true;
+
+  mark_bound_stale();
+}
+
+#ifdef HAVE_AUDIO
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::set_sound
+//       Access: Published
+//  Description: Sets the sound that will be played whenever the
+//               indicated event occurs.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+set_sound(const string &event, AudioSound *sound) {
+  _sounds[event] = sound;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::clear_sound
+//       Access: Published
+//  Description: Removes the sound associated with the indicated
+//               event.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+clear_sound(const string &event) {
+  _sounds.erase(event);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_sound
+//       Access: Published
+//  Description: Returns the sound associated with the indicated
+//               event, or NULL if there is no associated sound.
+////////////////////////////////////////////////////////////////////
+AudioSound *qpPGItem::
+get_sound(const string &event) const {
+  Sounds::const_iterator si = _sounds.find(event);
+  if (si != _sounds.end()) {
+    return (*si).second;
+  }
+  return (AudioSound *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::has_sound
+//       Access: Published
+//  Description: Returns true if there is a sound associated with the
+//               indicated event, or false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpPGItem::
+has_sound(const string &event) const {
+  return (_sounds.count(event) != 0);
+}
+#endif  // HAVE_AUDIO
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::get_text_node
+//       Access: Published, Static
+//  Description: Returns the TextNode object that will be used by all
+//               qpPGItems to generate default labels given a string.
+//               This can be loaded with the default font, etc.
+////////////////////////////////////////////////////////////////////
+qpTextNode *qpPGItem::
+get_text_node() {
+  if (_text_node == (qpTextNode *)NULL) {
+    _text_node = new qpTextNode("pguiText");
+    _text_node->freeze();
+    _text_node->set_text_color(0.0, 0.0, 0.0, 1.0);
+
+    // The default TextNode is aligned to the left, for the
+    // convenience of PGEntry.
+    _text_node->set_align(qpTextNode::A_left);
+  }
+  return _text_node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::play_sound
+//       Access: Protected
+//  Description: Plays the sound associated with the indicated event,
+//               if there is one.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+play_sound(const string &event) {
+#ifdef HAVE_AUDIO
+  Sounds::const_iterator si = _sounds.find(event);
+  if (si != _sounds.end()) {
+    AudioSound *sound = (*si).second;
+    sound->play();
+  }
+#endif  // HAVE_AUDIO
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::slot_state_def
+//       Access: Private
+//  Description: Ensures there is a slot in the array for the given
+//               state definition.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+slot_state_def(int state) {
+  while (state >= (int)_state_defs.size()) {
+    StateDef def;
+    def._frame_stale = true;
+    _state_defs.push_back(def);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::update_frame
+//       Access: Private
+//  Description: Generates a new instance of the frame geometry for
+//               the indicated state.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+update_frame(int state) {
+  // First, remove the old frame geometry, if any.
+  if (state >= 0 && state < (int)_state_defs.size()) {
+    PandaNode *old_frame = _state_defs[state]._frame_node;
+    if (old_frame != (PandaNode *)NULL) {
+      PandaNode *node = _state_defs[state]._node;
+      nassertv(node != (PandaNode *)NULL);
+      node->remove_child(old_frame);
+      _state_defs[state]._frame_node = (PandaNode *)NULL;
+    }
+  }
+
+  // We must turn off the stale flag first, before we call
+  // get_state_def(), to prevent get_state_def() from being a
+  // recursive call.
+  _state_defs[state]._frame_stale = false;
+
+  // Now create new frame geometry.
+  if (has_frame()) {
+    PandaNode *node = get_state_def(state);
+    nassertv(node != (PandaNode *)NULL);
+    _state_defs[state]._frame_node = 
+      _state_defs[state]._frame_style.generate_into(node, _frame);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGItem::mark_frames_stale
+//       Access: Private
+//  Description: Marks all the frames in all states stale, so that
+//               they will be regenerated the next time each state is
+//               requested.
+////////////////////////////////////////////////////////////////////
+void qpPGItem::
+mark_frames_stale() {
+  StateDefs::iterator di;
+  for (di = _state_defs.begin(); di != _state_defs.end(); ++di) {
+    // Remove the old frame, if any.
+    PandaNode *old_frame = (*di)._frame_node;
+    if (old_frame != (PandaNode *)NULL) {
+      PandaNode *node = (*di)._node;
+      nassertv(node != (PandaNode *)NULL);
+      node->remove_child(old_frame);
+      (*di)._frame_node = (PandaNode *)NULL;
+    }
+
+    (*di)._frame_stale = true;
+  }
+  mark_bound_stale();
+}

+ 213 - 0
panda/src/pgui/qppgItem.h

@@ -0,0 +1,213 @@
+// Filename: qppgItem.h
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpPGITEM_H
+#define qpPGITEM_H
+
+#include "pandabase.h"
+
+#include "pgMouseWatcherRegion.h"
+#include "pgFrameStyle.h"
+
+#include "pandaNode.h"
+#include "luse.h"
+#include "pointerTo.h"
+#include "qptextNode.h"
+
+#include "pmap.h"
+
+class PGTop;
+class MouseWatcherParameter;
+class AudioSound;
+class qpNodePath;
+
+////////////////////////////////////////////////////////////////////
+//       Class : qpPGItem
+// Description : This is the base class for all the various kinds of
+//               gui widget objects.
+//
+//               It is a Node which corresponds to a rectangular
+//               region on the screen, and it may have any number of
+//               "state" subgraphs, one of which is rendered at any
+//               given time according to its current state.
+//
+//               The qpPGItem node must be parented to the scene graph
+//               somewhere beneath a PGTop node in order for this
+//               behavior to work.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpPGItem : public PandaNode {
+PUBLISHED:
+  qpPGItem(const string &name);
+  virtual ~qpPGItem();
+
+protected:
+  qpPGItem(const qpPGItem &copy);
+
+  virtual PandaNode *make_copy() const;
+  virtual void xform(const LMatrix4f &mat);
+  virtual bool has_cull_callback() const;
+  virtual bool cull_callback(qpCullTraverser *trav, CullTraverserData &data);
+
+  virtual BoundingVolume *recompute_internal_bound();
+
+public:
+  void activate_region(const LMatrix4f &transform, int sort);
+  INLINE PGMouseWatcherRegion *get_region() const;
+
+  virtual void enter(const MouseWatcherParameter &param);
+  virtual void exit(const MouseWatcherParameter &param);
+  virtual void within(const MouseWatcherParameter &param);
+  virtual void without(const MouseWatcherParameter &param);
+  virtual void focus_in();
+  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);
+  INLINE void set_frame(const LVecBase4f &frame);
+  INLINE const LVecBase4f &get_frame() const;
+  INLINE bool has_frame() const;
+  INLINE void clear_frame();
+
+  INLINE void set_state(int state);
+  INLINE int get_state() const;
+
+  virtual void set_active(bool active);
+  INLINE bool get_active() const;
+
+  virtual void set_focus(bool focus);
+  INLINE bool get_focus() const;
+
+  void set_background_focus(bool focus);
+  INLINE bool get_background_focus() const;
+
+  INLINE void set_suppress_flags(int suppress_flags);
+  INLINE int get_suppress_flags() const;
+
+  int get_num_state_defs() const;
+  void clear_state_def(int state);
+  bool has_state_def(int state) const;
+  PandaNode *get_state_def(int state);
+  void instance_to_state_def(int state, const qpNodePath &chain);
+
+  PGFrameStyle get_frame_style(int state);
+  void set_frame_style(int state, const PGFrameStyle &style);
+
+  INLINE const string &get_id() const;
+  INLINE void set_id(const string &id);
+
+  INLINE static string get_enter_prefix();
+  INLINE static string get_exit_prefix();
+  INLINE static string get_within_prefix();
+  INLINE static string get_without_prefix();
+  INLINE static string get_focus_in_prefix();
+  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;
+  INLINE string get_within_event() const;
+  INLINE string get_without_event() const;
+  INLINE string get_focus_in_event() const;
+  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);
+  void clear_sound(const string &event);
+  AudioSound *get_sound(const string &event) const;
+  bool has_sound(const string &event) const;
+#endif
+
+  static qpTextNode *get_text_node();
+  INLINE static void set_text_node(qpTextNode *node);
+
+  INLINE static qpPGItem *get_focus_item();
+
+protected:
+  void play_sound(const string &event);
+
+private:
+  void slot_state_def(int state);
+  void update_frame(int state);
+  void mark_frames_stale();
+
+  bool _has_frame;
+  LVecBase4f _frame;
+  int _state;
+  enum Flags {
+    F_active             = 0x01,
+    F_focus              = 0x02,
+    F_background_focus   = 0x04,
+  };
+  int _flags;
+
+  PT(PGMouseWatcherRegion) _region;
+
+  class StateDef {
+  public:
+    PT(PandaNode) _node;
+    PGFrameStyle _frame_style;
+    PT(PandaNode) _frame_node;
+    bool _frame_stale;
+  };
+  typedef pvector<StateDef> StateDefs;
+  StateDefs _state_defs;
+
+#ifdef HAVE_AUDIO
+  typedef pmap<string, PT(AudioSound) > Sounds;
+  Sounds _sounds;
+#endif
+
+  static PT(qpTextNode) _text_node;
+  static qpPGItem *_focus_item;
+
+  typedef pset<qpPGItem *> BackgroundFocus;
+  static BackgroundFocus _background_focus;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PandaNode::init_type();
+    register_type(_type_handle, "qpPGItem",
+                  PandaNode::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "qppgItem.I"
+
+#endif

+ 70 - 0
panda/src/pgui/qppgTop.I

@@ -0,0 +1,70 @@
+// Filename: qppgTop.I
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGTop::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE qpPGTop::
+qpPGTop(const qpPGTop &copy) :
+  PandaNode(copy),
+  _watcher(copy._watcher)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGTop::get_mouse_watcher
+//       Access: Published
+//  Description: Returns the MouseWatcher pointer that the qpPGTop object
+//               registers its PG items with, or NULL if the
+//               MouseWatcher has not yet been set.
+////////////////////////////////////////////////////////////////////
+INLINE qpMouseWatcher *qpPGTop::
+get_mouse_watcher() const {
+  return _watcher;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGTop::add_region
+//       Access: Public
+//  Description: Adds the indicated region to the set of regions in
+//               the group.  Returns true if it was successfully
+//               added, or false if it was already on the list.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpPGTop::
+add_region(MouseWatcherRegion *region) {
+  if (_watcher_group == (PGMouseWatcherGroup *)NULL) {
+    return false;
+  }
+  return _watcher_group->add_region(region);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGTop::clear_regions
+//       Access: Public
+//  Description: Removes all the regions from the group.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGTop::
+clear_regions() {
+  if (_watcher_group == (PGMouseWatcherGroup *)NULL) {
+    return;
+  }
+  _watcher_group->clear_regions();
+}

+ 173 - 0
panda/src/pgui/qppgTop.cxx

@@ -0,0 +1,173 @@
+// Filename: qppgTop.cxx
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qppgTop.h"
+#include "pgItem.h"
+#include "pgMouseWatcherGroup.h"
+#include "pgCullTraverser.h"
+
+#include "omniBoundingVolume.h"
+
+TypeHandle qpPGTop::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGTop::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGTop::
+qpPGTop(const string &name) : 
+  PandaNode(name)
+{
+  _watcher_group = (PGMouseWatcherGroup *)NULL;
+
+  // A qpPGTop node normally has an infinite bounding volume.  Screw
+  // culling.
+  set_bound(OmniBoundingVolume());
+  set_final(true);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGTop::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGTop::
+~qpPGTop() {
+  set_mouse_watcher((qpMouseWatcher *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGTop::make_copy
+//       Access: Protected, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpPGTop::
+make_copy() const {
+  return new qpPGTop(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGTop::has_cull_callback
+//       Access: Protected, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this node during the cull
+//               traversal.
+////////////////////////////////////////////////////////////////////
+bool qpPGTop::
+has_cull_callback() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGTop::cull_callback
+//       Access: Protected, Virtual
+//  Description: If has_cull_callback() returns true, this function
+//               will be called during the cull traversal to perform
+//               any additional operations that should be performed at
+//               cull time.  This may include additional manipulation
+//               of render state or additional visible/invisible
+//               decisions, or any other arbitrary operation.
+//
+//               By the time this function is called, the node has
+//               already passed the bounding-volume test for the
+//               viewing frustum, and the node's transform and state
+//               have already been applied to the indicated
+//               CullTraverserData object.
+//
+//               The return value is true if this node should be
+//               visible, or false if it should be culled.
+////////////////////////////////////////////////////////////////////
+bool qpPGTop::
+cull_callback(qpCullTraverser *trav, CullTraverserData &data) {
+  // Empty our set of regions in preparation for re-adding whichever
+  // ones we encounter in the traversal that are current.
+  clear_regions();
+
+  // Now subsitute for the normal CullTraverser a special one of our
+  // own choosing.  This just carries around a pointer back to the
+  // PGTop node, for the convenience of PGItems to register themselves
+  // as they are drawn.
+  PGCullTraverser pg_trav(this, trav);
+  pg_trav.traverse_below(this, data);
+
+  // We've taken care of the traversal, thank you.
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGTop::set_mouse_watcher
+//       Access: Published
+//  Description: Sets the MouseWatcher pointer that the qpPGTop object
+//               registers its PG items with.  This must be set before
+//               the PG items are active.
+////////////////////////////////////////////////////////////////////
+void qpPGTop::
+set_mouse_watcher(qpMouseWatcher *watcher) {
+  if (_watcher_group != (PGMouseWatcherGroup *)NULL) {
+    _watcher_group->clear_top(this);
+  }
+  if (_watcher != (qpMouseWatcher *)NULL) {
+    _watcher->remove_group(_watcher_group);
+  }
+
+  _watcher = watcher;
+  _watcher_group = (PGMouseWatcherGroup *)NULL;
+
+  if (_watcher != (qpMouseWatcher *)NULL) {
+    // We create a new PGMouseWatcherGroup, but we don't own the
+    // reference count; the watcher will own this for us.
+    _watcher_group = new PGMouseWatcherGroup(this);
+    _watcher->add_group(_watcher_group);
+  }
+}
+
+/*
+  if (node->is_of_type(PGItem::get_class_type())) {
+    PGItem *pgi = DCAST(PGItem, node);
+
+    if (pgi->has_frame() && pgi->get_active()) {
+      // The item has a frame, so we want to generate a region for it
+      // and update the MouseWatcher.
+
+      // Get the complete net transform to the PGItem from the top.
+      LMatrix4f mat;
+      
+      NodeTransitionWrapper ntw(TransformTransition::get_class_type());
+      wrt(pgi, chain.begin(), chain.end(), (Node *)NULL, 
+          ntw, RenderRelation::get_class_type());
+      const TransformTransition *tt;
+      if (!get_transition_into(tt, ntw)) {
+        // No relative transform.
+        mat = LMatrix4f::ident_mat();
+      } else {
+        mat = tt->get_matrix();
+      }
+      
+      // Now apply this transform to the item's frame.
+      pgi->activate_region(this, mat, _sort_index);
+      _sort_index++;
+
+      add_region(pgi->get_region());
+    }
+*/

+ 95 - 0
panda/src/pgui/qppgTop.h

@@ -0,0 +1,95 @@
+// Filename: qppgTop.h
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpPGTOP_H
+#define qpPGTOP_H
+
+#include "pandabase.h"
+
+#include "pgMouseWatcherGroup.h"
+
+#include "pandaNode.h"
+#include "qpmouseWatcher.h"
+#include "pointerTo.h"
+
+class GraphicsStateGuardian;
+class PGMouseWatcherGroup;
+
+////////////////////////////////////////////////////////////////////
+//       Class : PGTop
+// Description : The "top" node of the new Panda GUI system.  This
+//               node must be parented to the 2-d scene graph, and all
+//               PG objects should be parented to this node or
+//               somewhere below it.  PG objects not parented within
+//               this hierarchy will not be clickable.
+//
+//               This node begins the special traversal of the PG
+//               objects that registers each node within the
+//               MouseWatcher and forces everything to render in a
+//               depth-first, left-to-right order, appropriate for 2-d
+//               objects.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpPGTop : public PandaNode {
+PUBLISHED:
+  qpPGTop(const string &name);
+  virtual ~qpPGTop();
+
+protected:
+  INLINE qpPGTop(const qpPGTop &copy);
+
+public:
+  virtual PandaNode *make_copy() const;
+  virtual bool has_cull_callback() const;
+  virtual bool cull_callback(qpCullTraverser *trav, CullTraverserData &data);
+
+PUBLISHED:
+  void set_mouse_watcher(qpMouseWatcher *watcher);
+  INLINE qpMouseWatcher *get_mouse_watcher() const;
+
+public:
+  // These methods duplicate the functionality of MouseWatcherGroup.
+  INLINE bool add_region(MouseWatcherRegion *region);
+  INLINE void clear_regions();
+
+private:
+  PT(qpMouseWatcher) _watcher;
+  PGMouseWatcherGroup *_watcher_group;
+  
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PandaNode::init_type();
+    register_type(_type_handle, "qpPGTop",
+                  PandaNode::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+
+  friend class PGMouseWatcherGroup;
+};
+
+#include "qppgTop.I"
+
+#endif

+ 94 - 0
panda/src/pgui/qppgWaitBar.I

@@ -0,0 +1,94 @@
+// Filename: qppgWaitBar.I
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::set_range
+//       Access: Published
+//  Description: Sets the value at which the WaitBar indicates 100%.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGWaitBar:: 
+set_range(float range) {
+  _range = range;
+  _bar_state = -1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::get_range
+//       Access: Published
+//  Description: Returns the value at which the WaitBar indicates 100%.
+////////////////////////////////////////////////////////////////////
+INLINE float qpPGWaitBar:: 
+get_range() const {
+  return _range;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::set_value
+//       Access: Published
+//  Description: Sets the current value of the bar.  This should range
+//               between 0 and get_range().
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGWaitBar:: 
+set_value(float value) {
+  _value = value;
+  _bar_state = -1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::get_value
+//       Access: Published
+//  Description: Returns the current value of the bar.
+////////////////////////////////////////////////////////////////////
+INLINE float qpPGWaitBar:: 
+get_value() const {
+  return _value;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::get_percent
+//       Access: Published
+//  Description: Returns the percentage complete.
+////////////////////////////////////////////////////////////////////
+INLINE float qpPGWaitBar:: 
+get_percent() const {
+  return _value / _range * 100.0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::set_bar_style
+//       Access: Published
+//  Description: Sets the kind of frame that is drawn on top of the
+//               WaitBar to represent the amount completed.
+////////////////////////////////////////////////////////////////////
+INLINE void qpPGWaitBar:: 
+set_bar_style(const PGFrameStyle &style) {
+  _bar_style = style;
+  _bar_state = -1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::get_bar_style
+//       Access: Published
+//  Description: Returns the kind of frame that is drawn on top of the
+//               WaitBar to represent the amount completed.
+////////////////////////////////////////////////////////////////////
+INLINE PGFrameStyle qpPGWaitBar:: 
+get_bar_style() const {
+  return _bar_style;
+}

+ 189 - 0
panda/src/pgui/qppgWaitBar.cxx

@@ -0,0 +1,189 @@
+// Filename: qppgWaitBar.cxx
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qppgWaitBar.h"
+#include "pgMouseWatcherParameter.h"
+
+#include "throw_event.h"
+
+TypeHandle qpPGWaitBar::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGWaitBar::
+qpPGWaitBar(const string &name) : qpPGItem(name)
+{
+  _range = 100.0;
+  _value = 0.0;
+  _bar_state = -1;
+  _bar_node = (PandaNode *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGWaitBar::
+~qpPGWaitBar() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::Copy Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+qpPGWaitBar::
+qpPGWaitBar(const qpPGWaitBar &copy) :
+  qpPGItem(copy),
+  _range(copy._range),
+  _value(copy._value)
+{
+  _bar_state = -1;
+  _bar_node = (PandaNode *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpPGWaitBar::
+make_copy() const {
+  return new qpPGWaitBar(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::has_cull_callback
+//       Access: Protected, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this node during the cull
+//               traversal.
+////////////////////////////////////////////////////////////////////
+bool qpPGWaitBar::
+has_cull_callback() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::cull_callback
+//       Access: Protected, Virtual
+//  Description: If has_cull_callback() returns true, this function
+//               will be called during the cull traversal to perform
+//               any additional operations that should be performed at
+//               cull time.  This may include additional manipulation
+//               of render state or additional visible/invisible
+//               decisions, or any other arbitrary operation.
+//
+//               By the time this function is called, the node has
+//               already passed the bounding-volume test for the
+//               viewing frustum, and the node's transform and state
+//               have already been applied to the indicated
+//               CullTraverserData object.
+//
+//               The return value is true if this node should be
+//               visible, or false if it should be culled.
+////////////////////////////////////////////////////////////////////
+bool qpPGWaitBar::
+cull_callback(qpCullTraverser *trav, CullTraverserData &data) {
+  update();
+  return qpPGItem::cull_callback(trav, data);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::setup
+//       Access: Public
+//  Description: Creates a qpPGWaitBar with the indicated dimensions,
+//               with the indicated maximum range.
+////////////////////////////////////////////////////////////////////
+void qpPGWaitBar::
+setup(float width, float height, float range) {
+  set_state(0);
+  clear_state_def(0);
+
+  set_frame(-0.5 * width, 0.5 * width, -0.5 * height, 0.5 * height);
+
+  PGFrameStyle style;
+  style.set_width(0.05, 0.05);
+
+  style.set_color(0.6, 0.6, 0.6, 1.0);
+  style.set_type(PGFrameStyle::T_bevel_in);
+  set_frame_style(0, style);
+
+  style.set_color(0.8, 0.8, 0.8, 1.0);
+  style.set_type(PGFrameStyle::T_bevel_out);
+  set_bar_style(style);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpPGWaitBar::update
+//       Access: Private
+//  Description: Computes the appropriate size of the bar frame
+//               according to the percentage completed.
+////////////////////////////////////////////////////////////////////
+void qpPGWaitBar:: 
+update() {
+  int state = get_state();
+
+  // If the bar was last drawn in this state and is still current, we
+  // don't have to draw it again.
+  if (_bar_state == state) {
+    return;
+  }
+
+  // Remove the old bar geometry, if any.
+  if (_bar_node != (PandaNode *)NULL) {
+    PandaNode *parent_node = _bar_node->get_parent(0);
+    parent_node->remove_child(_bar_node);
+    _bar_node = (PandaNode *)NULL;
+  }
+
+  // Now create new bar geometry.
+  if (_value != 0.0 && _range != 0.0) {
+    PandaNode *node = get_state_def(state);
+    nassertv(node != (PandaNode *)NULL);
+
+    PGFrameStyle style = get_frame_style(state);
+    const LVecBase4f &frame = get_frame();
+    const LVecBase2f &width = style.get_width();
+
+    // Put the bar within the item's frame's border.
+    LVecBase4f bar_frame(frame[0] + width[0],
+                         frame[1] - width[0],
+                         frame[2] + width[1],
+                         frame[3] - width[1]);
+
+    // And scale the bar according to our value.
+    float frac = _value / _range;
+    frac = max(min(frac, 1.0f), 0.0f);
+    bar_frame[1] = bar_frame[0] + frac * (bar_frame[1] - bar_frame[0]);
+    
+    _bar_node = _bar_style.generate_into(node, bar_frame);
+  }
+
+  // Indicate that the bar is current for this state.
+  _bar_state = state;
+}

+ 88 - 0
panda/src/pgui/qppgWaitBar.h

@@ -0,0 +1,88 @@
+// Filename: qppgWaitBar.h
+// Created by:  drose (14Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpPGWAITBAR_H
+#define qpPGWAITBAR_H
+
+#include "pandabase.h"
+
+#include "qppgItem.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : qpPGWaitBar
+// Description : This is a particular kind of qpPGItem that draws a
+//               little bar that fills from left to right to indicate
+//               a slow process gradually completing, like a
+//               traditional "wait, loading" bar.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpPGWaitBar : public qpPGItem {
+PUBLISHED:
+  qpPGWaitBar(const string &name = "");
+  virtual ~qpPGWaitBar();
+
+protected:
+  qpPGWaitBar(const qpPGWaitBar &copy);
+
+public:
+  virtual PandaNode *make_copy() const;
+  virtual bool has_cull_callback() const;
+  virtual bool cull_callback(qpCullTraverser *trav, CullTraverserData &data);
+
+PUBLISHED:
+  void setup(float width, float height, float range);
+
+  INLINE void set_range(float range);
+  INLINE float get_range() const;
+
+  INLINE void set_value(float value);
+  INLINE float get_value() const;
+
+  INLINE float get_percent() const;
+
+  INLINE void set_bar_style(const PGFrameStyle &style);
+  INLINE PGFrameStyle get_bar_style() const;
+
+private:
+  void update();
+
+  float _range, _value;
+  int _bar_state;
+  PGFrameStyle _bar_style;
+  PT(PandaNode) _bar_node;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    qpPGItem::init_type();
+    register_type(_type_handle, "qpPGWaitBar",
+                  qpPGItem::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "qppgWaitBar.I"
+
+#endif