Panda3D
pgEntry.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file pgEntry.cxx
10  * @author drose
11  * @date 2002-03-13
12  */
13 
14 #include "pgEntry.h"
16 
17 #include "cullTraverser.h"
18 #include "cullTraverserData.h"
19 #include "throw_event.h"
20 #include "transformState.h"
21 #include "mouseWatcherParameter.h"
22 #include "keyboardButton.h"
23 #include "mouseButton.h"
24 #include "lineSegs.h"
25 #include "textEncoder.h"
26 #include "config_text.h"
27 
28 #include <math.h>
29 
30 using std::max;
31 using std::min;
32 using std::string;
33 using std::wstring;
34 
35 TypeHandle PGEntry::_type_handle;
36 
37 /**
38  *
39  */
40 PGEntry::
41 PGEntry(const string &name) :
42  PGItem(name),
43  _text(get_text_node()),
44  _obscure_text(get_text_node())
45 {
46  set_cull_callback();
47 
48  _cursor_position = 0;
49  _cursor_stale = true;
50  _candidate_highlight_start = 0;
51  _candidate_highlight_end = 0;
52  _candidate_cursor_pos = 0;
53  _max_chars = 0;
54  _max_width = 0.0f;
55  _num_lines = 1;
56  _accept_enabled = true;
57  _last_text_def = nullptr;
58  _text_geom_stale = true;
59  _text_geom_flattened = true;
60  _blink_start = 0.0f;
61  _blink_rate = 1.0f;
62 
63  _text_render_root = NodePath("text_root");
64 
65  CPT(TransformState) transform = TransformState::make_mat(LMatrix4::convert_mat(CS_default, CS_zup_right));
66  _text_render_root.set_transform(transform);
67 
68  _cursor_scale = _text_render_root.attach_new_node("cursor_scale");
69  _cursor_def = _cursor_scale.attach_new_node("cursor");
70  _cursor_visible = true;
71 
72  // These strings are used to specify the TextProperties to apply to
73  // candidate strings generated from the IME (for entering text in an east
74  // Asian language).
75  _candidate_active = "candidate_active";
76  _candidate_inactive = "candidate_inactive";
77 
78  _cursor_keys_active = true;
79  _obscure_mode = false;
80  _overflow_mode = false;
81 
82  _current_padding = 0.0f;
83 
84  set_active(true);
85  update_state();
86 
87  // Some default parameters so it doesn't crash hard if no one calls setup().
88  setup_minimal(10, 1);
89 }
90 
91 /**
92  *
93  */
94 PGEntry::
95 ~PGEntry() {
96 }
97 
98 /**
99  *
100  */
101 PGEntry::
102 PGEntry(const PGEntry &copy) :
103  PGItem(copy),
104  _text(copy._text),
105  _obscure_text(copy._obscure_text),
106  _cursor_position(copy._cursor_position),
107  _cursor_visible(copy._cursor_visible),
108  _candidate_highlight_start(copy._candidate_highlight_start),
109  _candidate_highlight_end(copy._candidate_highlight_end),
110  _candidate_cursor_pos(copy._candidate_cursor_pos),
111  _max_chars(copy._max_chars),
112  _max_width(copy._max_width),
113  _num_lines(copy._num_lines),
114  _accept_enabled(copy._accept_enabled),
115  _candidate_active(copy._candidate_active),
116  _candidate_inactive(copy._candidate_inactive),
117  _text_defs(copy._text_defs),
118  _blink_start(copy._blink_start),
119  _blink_rate(copy._blink_rate),
120  _cursor_keys_active(copy._cursor_keys_active),
121  _obscure_mode(copy._obscure_mode),
122  _overflow_mode(copy._overflow_mode)
123 {
124  _cursor_stale = true;
125  _last_text_def = nullptr;
126  _text_geom_stale = true;
127  _text_geom_flattened = true;
128 
129  _text_render_root = NodePath("text_root");
130  _cursor_scale = _text_render_root.attach_new_node("cursor_scale");
131  _cursor_scale.set_transform(copy._cursor_scale.get_transform());
132  _cursor_def = copy._cursor_def.copy_to(_cursor_scale);
133 }
134 
135 /**
136  * Returns a newly-allocated Node that is a shallow copy of this one. It will
137  * be a different Node pointer, but its internal data may or may not be shared
138  * with that of the original Node.
139  */
141 make_copy() const {
142  LightReMutexHolder holder(_lock);
143  return new PGEntry(*this);
144 }
145 
146 /**
147  * Transforms the contents of this node by the indicated matrix, if it means
148  * anything to do so. For most kinds of nodes, this does nothing.
149  */
150 void PGEntry::
151 xform(const LMatrix4 &mat) {
152  LightReMutexHolder holder(_lock);
153  PGItem::xform(mat);
154  _text_render_root.set_mat(_text_render_root.get_mat() * mat);
155 }
156 
157 /**
158  * This function will be called during the cull traversal to perform any
159  * additional operations that should be performed at cull time. This may
160  * include additional manipulation of render state or additional
161  * visible/invisible decisions, or any other arbitrary operation.
162  *
163  * Note that this function will *not* be called unless set_cull_callback() is
164  * called in the constructor of the derived class. It is necessary to call
165  * set_cull_callback() to indicated that we require cull_callback() to be
166  * called.
167  *
168  * By the time this function is called, the node has already passed the
169  * bounding-volume test for the viewing frustum, and the node's transform and
170  * state have already been applied to the indicated CullTraverserData object.
171  *
172  * The return value is true if this node should be visible, or false if it
173  * should be culled.
174  */
175 bool PGEntry::
177  LightReMutexHolder holder(_lock);
178  PGItem::cull_callback(trav, data);
179  update_text();
180  update_cursor();
181 
182  // Now render the text.
183  CullTraverserData next_data(data, _text_render_root.node());
184  trav->traverse(next_data);
185 
186  // Now continue to render everything else below this node.
187  return true;
188 }
189 
190 /**
191  * This is a callback hook function, called whenever a mouse or keyboard entry
192  * is depressed while the mouse is within the region.
193  */
194 void PGEntry::
195 press(const MouseWatcherParameter &param, bool background) {
196  LightReMutexHolder holder(_lock);
197  if (get_active()) {
198  if (param.has_button()) {
199  // Make sure _text is initialized properly.
200  update_text();
201 
202  bool overflow_mode = get_overflow_mode() && _num_lines == 1;
203 
204  ButtonHandle button = param.get_button();
205 
206  if (button == MouseButton::one() ||
207  button == MouseButton::two() ||
208  button == MouseButton::three() ||
209  button == MouseButton::four() ||
210  button == MouseButton::five()) {
211  // Mouse button; set focus.
212  set_focus(true);
213 
214  } else if ((!background && get_focus()) ||
215  (background && get_background_focus())) {
216  // Keyboard button.
217  if (!_candidate_wtext.empty()) {
218  _candidate_wtext = wstring();
219  _text_geom_stale = true;
220  }
221 
222  _cursor_position = min(_cursor_position, _text.get_num_characters());
223  _blink_start = ClockObject::get_global_clock()->get_frame_time();
224  if (button == KeyboardButton::enter()) {
225  // Enter. Accept the entry.
226  if (_accept_enabled) {
227  accept(param);
228  }
229  else {
230  accept_failed(param);
231  }
232 
233  } else if (button == KeyboardButton::backspace()) {
234  // Backspace. Remove the character to the left of the cursor.
235  if (_cursor_position > 0) {
236  _text.set_wsubstr(wstring(), _cursor_position - 1, 1);
237  _cursor_position--;
238  _cursor_stale = true;
239  _text_geom_stale = true;
240  erase(param);
241  }
242 
243  } else if (button == KeyboardButton::del()) {
244  // Delete. Remove the character to the right of the cursor.
245  if (_cursor_position < _text.get_num_characters()) {
246  _text.set_wsubstr(wstring(), _cursor_position, 1);
247  _text_geom_stale = true;
248  erase(param);
249  }
250 
251  } else if (button == KeyboardButton::left()) {
252  if (_cursor_keys_active) {
253  // Left arrow. Move the cursor position to the left.
254  --_cursor_position;
255  if (_cursor_position < 0) {
256  _cursor_position = 0;
257  overflow(param);
258  } else {
259  type(param);
260  }
261  _cursor_stale = true;
262  if (overflow_mode){
263  _text_geom_stale = true;
264  }
265  }
266 
267  } else if (button == KeyboardButton::right()) {
268  if (_cursor_keys_active) {
269  // Right arrow. Move the cursor position to the right.
270  ++_cursor_position;
271  if (_cursor_position > _text.get_num_characters()) {
272  _cursor_position = _text.get_num_characters();
273  overflow(param);
274  } else {
275  type(param);
276  }
277  _cursor_stale = true;
278  if (overflow_mode){
279  _text_geom_stale = true;
280  }
281  }
282 
283  } else if (button == KeyboardButton::home()) {
284  if (_cursor_keys_active) {
285  // Home. Move the cursor position to the beginning.
286  _cursor_position = 0;
287  _cursor_stale = true;
288  if (overflow_mode){
289  _text_geom_stale = true;
290  }
291  type(param);
292  }
293 
294  } else if (button == KeyboardButton::end()) {
295  if (_cursor_keys_active) {
296  // End. Move the cursor position to the end.
297  _cursor_position = _text.get_num_characters();
298  _cursor_stale = true;
299  if (overflow_mode){
300  _text_geom_stale = true;
301  }
302  type(param);
303  }
304  }
305  }
306  }
307  }
308  PGItem::press(param, background);
309 }
310 
311 /**
312  * This is a callback hook function, called whenever the user types a key.
313  */
314 void PGEntry::
315 keystroke(const MouseWatcherParameter &param, bool background) {
316  LightReMutexHolder holder(_lock);
317  if (get_active()) {
318  if (param.has_keycode()) {
319  // Make sure _text is initialized properly.
320  update_text();
321 
322  int keycode = param.get_keycode();
323 
324  if (!isascii(keycode) || isprint(keycode)) {
325  // A normal visible character. Add a new character to the text entry,
326  // if there's room.
327  if (!_candidate_wtext.empty()) {
328  _candidate_wtext = wstring();
329  _text_geom_stale = true;
330  }
331  wstring new_char(1, (wchar_t)keycode);
332 
333  if (get_max_chars() > 0 && _text.get_num_characters() >= get_max_chars()) {
334  // In max_chars mode, we consider it an overflow after we have
335  // exceeded a fixed number of characters, irrespective of the
336  // formatted width of those characters.
337  overflow(param);
338 
339  } else {
340  _cursor_position = min(_cursor_position, _text.get_num_characters());
341  bool too_long = !_text.set_wsubstr(new_char, _cursor_position, 0);
342  bool overflow_mode = get_overflow_mode() && _num_lines == 1;
343  if(overflow_mode){
344  too_long = false;
345  }
346  if (_obscure_mode) {
347  too_long = !_obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
348  } else {
349  if (!too_long && (_text.get_num_rows() == _num_lines) && !overflow_mode) {
350  // If we've filled up all of the available lines, we must also
351  // ensure that the last line is not too long (it might be,
352  // because of additional whitespace on the end).
353  int r = _num_lines - 1;
354  int c = _text.get_num_cols(r);
355  PN_stdfloat last_line_width =
356  _text.get_xpos(r, c) - _text.get_xpos(r, 0);
357  too_long = (last_line_width > _max_width);
358  }
359 
360  if (!too_long && keycode == ' ' && !overflow_mode) {
361  // Even if we haven't filled up all of the available lines, we
362  // should reject a space that's typed at the end of the current
363  // line if it would make that line exceed the maximum width,
364  // just so we don't allow an infinite number of spaces to
365  // accumulate.
366  int r, c;
367  _text.calc_r_c(r, c, _cursor_position);
368  if (_text.get_num_cols(r) == c + 1) {
369  // The user is typing at the end of the line. But we must
370  // allow at least one space at the end of the line, so we only
371  // make any of the following checks if there are already
372  // multiple spaces at the end of the line.
373  if (c - 1 >= 0 && _text.get_character(r, c - 1) == ' ') {
374  // Ok, the user is putting multiple spaces on the end of a
375  // line; we need to make sure the line does not grow too
376  // wide. Measure the line's width.
377  PN_stdfloat current_line_width =
378  _text.get_xpos(r, c + 1) - _text.get_xpos(r, 0);
379  if (current_line_width > _max_width) {
380  // We have to reject the space, but we don't treat it as
381  // an overflow condition.
382  _text.set_wsubstr(wstring(), _cursor_position, 1);
383  // If the user is typing over existing space characters,
384  // we act as if the right-arrow key were pressed instead,
385  // and advance the cursor to the next position.
386  // Otherwise, we just quietly eat the space character.
387  if (_cursor_position < _text.get_num_characters() &&
388  _text.get_character(_cursor_position) == ' ') {
389  _cursor_position++;
390  _cursor_stale = true;
391  }
392  return;
393  }
394  }
395  }
396  }
397  }
398 
399  if (too_long) {
400  _text.set_wsubstr(wstring(), _cursor_position, 1);
401  overflow(param);
402 
403  } else {
404  _cursor_position += new_char.length();
405  _cursor_stale = true;
406  _text_geom_stale = true;
407  type(param);
408  }
409  }
410  }
411  }
412  }
413  PGItem::keystroke(param, background);
414 }
415 
416 /**
417  * This is a callback hook function, called whenever the user selects an item
418  * from the IME menu.
419  */
420 void PGEntry::
421 candidate(const MouseWatcherParameter &param, bool background) {
422  LightReMutexHolder holder(_lock);
423  if (get_active()) {
424  if (param.has_candidate()) {
425  // Save the candidate string so it can be displayed.
426  _candidate_wtext = param.get_candidate_string();
427  _candidate_highlight_start = param.get_highlight_start();
428  _candidate_highlight_end = param.get_highlight_end();
429  _candidate_cursor_pos = param.get_cursor_pos();
430  _text_geom_stale = true;
431  if (!_candidate_wtext.empty()) {
432  type(param);
433  }
434  }
435  }
436  PGItem::candidate(param, background);
437 }
438 
439 /**
440  * This is a callback hook function, called whenever the entry is accepted by
441  * the user pressing Enter normally.
442  */
443 void PGEntry::
445  LightReMutexHolder holder(_lock);
447  string event = get_accept_event(param.get_button());
448  play_sound(event);
449  throw_event(event, EventParameter(ep));
450  set_focus(false);
451 }
452 
453 /**
454  * This is a callback hook function, called whenever the user presses Enter
455  * but we can't accept the input.
456  */
457 void PGEntry::
459  LightReMutexHolder holder(_lock);
461  string event = get_accept_failed_event(param.get_button());
462  play_sound(event);
463  throw_event(event, EventParameter(ep));
464  // set_focus(false);
465 }
466 
467 /**
468  * This is a callback hook function, called whenever the entry is overflowed
469  * because the user attempts to type too many characters, exceeding either
470  * set_max_chars() or set_max_width().
471  */
472 void PGEntry::
474  LightReMutexHolder holder(_lock);
476  string event = get_overflow_event();
477  play_sound(event);
478  throw_event(event, EventParameter(ep));
479 }
480 
481 /**
482  * This is a callback hook function, called whenever the user extends the text
483  * by typing.
484  */
485 void PGEntry::
487  LightReMutexHolder holder(_lock);
489  string event = get_type_event();
490  play_sound(event);
491  throw_event(event, EventParameter(ep));
492 }
493 
494 /**
495  * This is a callback hook function, called whenever the user erase characters
496  * in the text.
497  */
498 void PGEntry::
500  LightReMutexHolder holder(_lock);
502  string event = get_erase_event();
503  play_sound(event);
504  throw_event(event, EventParameter(ep));
505 }
506 
507 /**
508  * This is a callback hook function, called whenever the cursor moves.
509  */
510 void PGEntry::
512  LightReMutexHolder holder(_lock);
513  string event = get_cursormove_event();
514  throw_event(event, EventParameter(_cursor_def.get_x()), EventParameter(_cursor_def.get_y()));
515 }
516 
517 /**
518  * Sets up the entry for normal use. The width is the maximum width of
519  * characters that will be typed, and num_lines is the integer number of lines
520  * of text of the entry. Both of these together determine the size of the
521  * entry, based on the TextNode in effect.
522  */
523 void PGEntry::
524 setup(PN_stdfloat width, int num_lines) {
525  LightReMutexHolder holder(_lock);
526  setup_minimal(width, num_lines);
527 
528  TextNode *text_node = get_text_def(S_focus);
529  PN_stdfloat line_height = text_node->get_line_height();
530 
531  // Determine the four corners of the frame.
532  float bottom = -0.3f * line_height - (line_height * (num_lines - 1));
533  // Transform each corner by the TextNode's transform.
534  LMatrix4 mat = text_node->get_transform();
535  LPoint3 ll = LPoint3::rfu(0.0f, 0.0f, bottom) * mat;
536  LPoint3 ur = LPoint3::rfu(width, 0.0f, line_height) * mat;
537  LPoint3 lr = LPoint3::rfu(width, 0.0f, bottom) * mat;
538  LPoint3 ul = LPoint3::rfu(0.0f, 0.0f, line_height) * mat;
539 
540  LVector3 up = LVector3::up();
541  int up_axis;
542  if (up[1]) {
543  up_axis = 1;
544  }
545  else if (up[2]) {
546  up_axis = 2;
547  }
548  else {
549  up_axis = 0;
550  }
551  LVector3 right = LVector3::right();
552  int right_axis;
553  if (right[0]) {
554  right_axis = 0;
555  }
556  else if (right[2]) {
557  right_axis = 2;
558  }
559  else {
560  right_axis = 1;
561  }
562 
563  // And get the new minmax to define the frame. We do all this work instead
564  // of just using the lower-left and upper-right corners, just in case the
565  // text was rotated.
566  LVecBase4 frame;
567  frame[0] = min(min(ll[right_axis], ur[right_axis]), min(lr[right_axis], ul[right_axis]));
568  frame[1] = max(max(ll[right_axis], ur[right_axis]), max(lr[right_axis], ul[right_axis]));
569  frame[2] = min(min(ll[up_axis], ur[up_axis]), min(lr[up_axis], ul[up_axis]));
570  frame[3] = max(max(ll[up_axis], ur[up_axis]), max(lr[up_axis], ul[up_axis]));
571 
572  switch (text_node->get_align()) {
573  case TextNode::A_left:
574  case TextNode::A_boxed_left:
575  // The default case.
576  break;
577 
578  case TextNode::A_center:
579  case TextNode::A_boxed_center:
580  frame[0] = -width / 2.0;
581  frame[1] = width / 2.0;
582  break;
583 
584  case TextNode::A_right:
585  case TextNode::A_boxed_right:
586  frame[0] = -width;
587  frame[1] = 0.0f;
588  break;
589  }
590 
591  set_frame(frame[0] - 0.15f, frame[1] + 0.15f, frame[2], frame[3]);
592 
593  PGFrameStyle style;
594  style.set_width(0.1f, 0.1f);
595  style.set_type(PGFrameStyle::T_bevel_in);
596  style.set_color(0.8f, 0.8f, 0.8f, 1.0f);
597 
598  set_frame_style(S_no_focus, style);
599 
600  style.set_color(0.9f, 0.9f, 0.9f, 1.0f);
601  set_frame_style(S_focus, style);
602 
603  style.set_color(0.6f, 0.6f, 0.6f, 1.0f);
604  set_frame_style(S_inactive, style);
605 }
606 
607 /**
608  * Sets up the entry without creating any frame or other decoration.
609  */
610 void PGEntry::
611 setup_minimal(PN_stdfloat width, int num_lines) {
612  LightReMutexHolder holder(_lock);
613  set_text(string());
614  _cursor_position = 0;
615  set_max_chars(0);
616  set_max_width(width);
617  set_num_lines(num_lines);
618  update_text();
619 
620  _accept_enabled = true;
621 
622  TextNode *text_node = get_text_def(S_focus);
623  PN_stdfloat line_height = text_node->get_line_height();
624 
625  // Set up a default cursor: a vertical bar.
627 
628  LineSegs ls;
629  ls.set_color(text_node->get_text_color());
630  ls.move_to(0.0f, 0.0f, -0.15f * line_height);
631  ls.draw_to(0.0f, 0.0f, 0.70f * line_height);
633 
634  /*
635  // An underscore cursor would work too.
636  text_node->set_text("_");
637  get_cursor_def().attach_new_node(text_node->generate());
638  */
639 }
640 
641 /**
642  * Changes the TextNode that will be used to render the text within the entry
643  * when the entry is in the indicated state. The default if nothing is
644  * specified is the same TextNode returned by PGItem::get_text_node().
645  */
646 void PGEntry::
647 set_text_def(int state, TextNode *node) {
648  LightReMutexHolder holder(_lock);
649  nassertv(state >= 0 && state < 1000); // Sanity check.
650  if (node == nullptr && state >= (int)_text_defs.size()) {
651  // If we're setting it to NULL, we don't need to slot a new one.
652  return;
653  }
654  slot_text_def(state);
655 
656  _text_defs[state] = node;
657 }
658 
659 /**
660  * Returns the TextNode that will be used to render the text within the entry
661  * when the entry is in the indicated state. See set_text_def().
662  */
664 get_text_def(int state) const {
665  LightReMutexHolder holder(_lock);
666  if (state < 0 || state >= (int)_text_defs.size()) {
667  // If we don't have a definition, use the global one.
668  return get_text_node();
669  }
670  if (_text_defs[state] == nullptr) {
671  return get_text_node();
672  }
673  return _text_defs[state];
674 }
675 
676 /**
677  * Toggles the active/inactive state of the entry. In the case of a PGEntry,
678  * this also changes its visual appearance.
679  */
680 void PGEntry::
681 set_active(bool active) {
682  LightReMutexHolder holder(_lock);
683  PGItem::set_active(active);
684  update_state();
685 }
686 
687 /**
688  * Toggles the focus state of the entry. In the case of a PGEntry, this also
689  * changes its visual appearance.
690  */
691 void PGEntry::
692 set_focus(bool focus) {
693  LightReMutexHolder holder(_lock);
694  PGItem::set_focus(focus);
695  _blink_start = ClockObject::get_global_clock()->get_frame_time();
696  update_state();
697 }
698 
699 /**
700  * Returns true if any of the characters in the string returned by get_wtext()
701  * are out of the range of an ASCII character (and, therefore, get_wtext()
702  * should be called in preference to get_text()).
703  */
704 bool PGEntry::
705 is_wtext() const {
706  LightReMutexHolder holder(_lock);
707  for (int i = 0; i < _text.get_num_characters(); ++i) {
708  wchar_t ch = _text.get_character(i);
709  if ((ch & ~0x7f) != 0) {
710  return true;
711  }
712  }
713 
714  return false;
715 }
716 
717 /**
718  * Ensures there is a slot in the array for the given text definition.
719  */
720 void PGEntry::
721 slot_text_def(int state) {
722  while (state >= (int)_text_defs.size()) {
723  _text_defs.push_back(nullptr);
724  }
725 }
726 
727 /**
728  * Causes the PGEntry to recompute its text, if necessary.
729  */
730 void PGEntry::
731 update_text() {
732  TextNode *node = get_text_def(get_state());
733  nassertv(node != nullptr);
734 
735  if (_text_geom_stale || node != _last_text_def) {
736  TextProperties props = *node;
737  props.set_wordwrap(_max_width);
739  _text.set_properties(props);
740  _text.set_max_rows(_num_lines);
741 
742  if (node != _last_text_def) {
743  // Make sure the default properties are applied to all the characters in
744  // the text.
745  _text.set_wtext(_text.get_wtext());
746  _last_text_def = node;
747  }
748 
749  _text.set_multiline_mode (!(get_overflow_mode() && _num_lines == 1));
750 
751  PT(PandaNode) assembled;
752  if (_obscure_mode) {
753  _obscure_text.set_properties(props);
754  _obscure_text.set_max_rows(_num_lines);
755  _obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
756  assembled = _obscure_text.assemble_text();
757 
758  } else if (_candidate_wtext.empty()) {
759  // If we're not trying to display a candidate string, it's easy: just
760  // display the current text contents.
761  assembled = _text.assemble_text();
762 
763  } else {
765  TextProperties inactive = tp_mgr->get_properties(_candidate_inactive);
766  TextProperties active = tp_mgr->get_properties(_candidate_active);
767 
768  // Insert the complex sequence of characters required to show the
769  // candidate string in a different color. This gets inserted at the
770  // current cursor position.
771  wstring cseq;
772  cseq += wstring(1, (wchar_t)text_push_properties_key);
773  cseq += node->decode_text(_candidate_inactive);
774  cseq += wstring(1, (wchar_t)text_push_properties_key);
775  cseq += _candidate_wtext.substr(0, _candidate_highlight_start);
776  cseq += wstring(1, (wchar_t)text_push_properties_key);
777  cseq += node->decode_text(_candidate_active);
778  cseq += wstring(1, (wchar_t)text_push_properties_key);
779  cseq += _candidate_wtext.substr(_candidate_highlight_start,
780  _candidate_highlight_end - _candidate_highlight_start);
781  cseq += wstring(1, (wchar_t)text_pop_properties_key);
782  cseq += _candidate_wtext.substr(_candidate_highlight_end);
783  cseq += wstring(1, (wchar_t)text_pop_properties_key);
784 
785  // Create a special TextAssembler to insert the candidate string.
786  TextAssembler ctext(_text);
787  ctext.set_wsubstr(cseq, _cursor_position, 0);
788  assembled = ctext.assemble_text();
789  }
790 
791  if (!_current_text.is_empty()) {
792  _current_text.remove_node();
793  }
794 
795  _current_text =
796  _text_render_root.attach_new_node(assembled);
797 
798  _current_text.set_mat(node->get_transform());
799 
800  if (get_overflow_mode() && _num_lines == 1){
801  // We determine the minimum required padding:
802  PN_stdfloat cursor_graphic_pos = _text.get_xpos(0, _cursor_position);
803  PN_stdfloat min_padding = (cursor_graphic_pos - _max_width);
804 
805 /*
806  * If the current padding would produce a caret outside the text entry, we
807  * relocate it. Here we also have to make a jump towards the center when the
808  * caret is going outside the visual area and there's enough text ahead for
809  * increased usability. The amount that the caret is moved for hinting
810  * depends on the OS, and the specific behavior under certain circunstances in
811  * different Operating Systems is very complicated (the implementation would
812  * need to "remember" the original typing starting point). For the moment we
813  * are gonna use an unconditional 50% jump, this behavior is found in some Mac
814  * dialogs, and it's the easiest to implement by far, while providing proven
815  * usability. PROS: Reduces the amount of scrolling while both writing and
816  * navigating with arrow keys, which is desirable. CONS: The user needs to
817  * remember that heshe has exceeded the boundaries, but this happens with all
818  * implementations to some degree.
819  */
820 
821  if (_current_padding < min_padding || _current_padding > cursor_graphic_pos){
822  _current_padding = min_padding + (cursor_graphic_pos - min_padding) * 0.5;
823  }
824 
825  if (_current_padding < 0){ // Caret virtual position doesn't exceed boundaries
826  _current_padding = 0;
827  }
828 
829  _current_text.set_x(_current_text.get_x() - _current_padding);
830  _current_text.set_scissor(NodePath(this),
831  LPoint3::rfu(0, 0, -0.5), LPoint3::rfu(_max_width, 0, -0.5),
832  LPoint3::rfu(_max_width, 0, 1.5), LPoint3::rfu(0, 0, 1.5));
833  }
834 
835  _text_geom_stale = false;
836  _text_geom_flattened = false;
837  _cursor_stale = true;
838  }
839 
840  // We'll flatten the text geometry only if we don't have focus. Otherwise,
841  // we assume the user may be changing it frequently.
842  if (!get_focus() && !_text_geom_flattened) {
843  _current_text.flatten_strong();
844  _text_geom_flattened = true;
845  }
846 }
847 
848 /**
849  * Moves the cursor to its correct position.
850  */
851 void PGEntry::
852 update_cursor() {
853  TextNode *node = get_text_def(get_state());
854  nassertv(node != nullptr);
855  _cursor_scale.set_mat(node->get_transform());
856  _cursor_scale.set_color(node->get_text_color());
857 
858  if (_cursor_stale || node != _last_text_def) {
859  update_text();
860 
861  _cursor_position = min(_cursor_position, _text.get_num_characters());
862 
863  // Determine the row and column of the cursor.
864  int row, column;
865  PN_stdfloat xpos, ypos;
866  if (_obscure_mode) {
867  _obscure_text.calc_r_c(row, column, _cursor_position);
868  xpos = _obscure_text.get_xpos(row, column);
869  ypos = _obscure_text.get_ypos(row, column);
870  } else {
871  _text.calc_r_c(row, column, _cursor_position);
872  if (_cursor_position > 0 && _text.get_character(_cursor_position - 1) == '\n') {
873  row += 1;
874  column = 0;
875  }
876  xpos = _text.get_xpos(row, column);
877  ypos = _text.get_ypos(row, column);
878  }
879 
880  _cursor_def.set_pos(xpos - _current_padding, 0.0f, ypos);
881  _cursor_stale = false;
882  cursormove();
883 
884  }
885 
886  // Should the cursor be visible?
887  if (!get_focus() || !_candidate_wtext.empty()) {
888  show_hide_cursor(false);
889  } else {
890  double elapsed_time =
891  ClockObject::get_global_clock()->get_frame_time() - _blink_start;
892  int cycle = (int)(elapsed_time * _blink_rate * 2.0f);
893  bool visible = ((cycle & 1) == 0);
894  show_hide_cursor(visible);
895  }
896 }
897 
898 /**
899  * Makes the cursor visible or invisible, e.g. during a blink cycle.
900  */
901 void PGEntry::
902 show_hide_cursor(bool visible) {
903  if (visible != _cursor_visible) {
904  if (visible) {
905  _cursor_scale.show();
906  } else {
907  _cursor_scale.hide();
908  }
909  _cursor_visible = visible;
910  }
911 }
912 
913 /**
914  * Determines what the correct state for the PGEntry should be.
915  */
916 void PGEntry::
917 update_state() {
918  if (get_active()) {
919  if (get_focus()) {
920  set_state(S_focus);
921  } else {
922  set_state(S_no_focus);
923  }
924  } else {
925  set_state(S_inactive);
926  }
927 }
set_max_rows
If max_rows is greater than zero, no more than max_rows will be accepted.
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
void setup_minimal(PN_stdfloat width, int num_lines)
Sets up the entry without creating any frame or other decoration.
Definition: pgEntry.cxx:611
int get_num_cols(int r) const
Returns the number of characters and/or graphic objects in the nth row.
bool get_background_focus() const
Returns whether background_focus is currently enabled.
Definition: pgItem.I:180
A basic node of the scene graph or data graph.
Definition: pandaNode.h:64
void set_max_width(PN_stdfloat max_width)
Sets the maximum width of all characters that may be typed into the entry.
Definition: pgEntry.I:184
std::string get_cursormove_event() const
Returns the event name that will be thrown whenever the cursor moves.
Definition: pgEntry.I:503
This specialization on MouseWatcherParameter allows us to tag on additional elements to events for th...
void set_width(PN_stdfloat x, PN_stdfloat y)
Sets the width parameter, which has meaning only for certain frame types.
Definition: pgFrameStyle.I:139
int get_num_characters() const
Returns the number of characters of text, before wordwrapping.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Indicates a coordinate-system transform on vertices.
static ButtonHandle three()
Returns the ButtonHandle associated with the third mouse button.
Definition: mouseButton.cxx:59
static ButtonHandle two()
Returns the ButtonHandle associated with the second mouse button.
Definition: mouseButton.cxx:51
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
set_multiline_mode
Sets the multiline mode flag.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void overflow(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the entry is overflowed because the user attempts t...
Definition: pgEntry.cxx:473
std::string get_accept_failed_event(const ButtonHandle &button) const
Returns the event name that will be thrown when the entry cannot accept an input.
Definition: pgEntry.I:467
This is the base class for all the various kinds of gui widget objects.
Definition: pgItem.h:53
virtual PandaNode * make_copy() const
Returns a newly-allocated Node that is a shallow copy of this one.
Definition: pgEntry.cxx:141
virtual void xform(const LMatrix4 &mat)
Transforms the contents of this node by the indicated matrix, if it means anything to do so.
Definition: pgEntry.cxx:151
An optional parameter associated with an event.
void clear_cursor_def()
Removes all the children from the cursor_def node, in preparation for adding a new definition.
Definition: pgEntry.I:261
int get_max_chars() const
Returns the current maximum number of characters that may be typed into the entry,...
Definition: pgEntry.I:167
int get_state() const
Returns the "state" of this particular PGItem.
Definition: pgItem.I:150
bool is_empty() const
Returns true if the NodePath contains no nodes.
Definition: nodePath.I:188
bool get_active() const
Returns whether the PGItem is currently active for mouse events.
Definition: pgItem.I:160
int get_keycode() const
Returns the keycode associated with this event.
This is a particular kind of PGItem that handles simple one-line or short multi-line text entries,...
Definition: pgEntry.h:37
bool set_wsubstr(const std::wstring &wtext, int start, int count)
Replaces the 'count' characters from 'start' of the current text with the indicated replacement text.
wchar_t get_character(int n) const
Returns the character at the indicated position in the pre-wordwrapped string.
void set_type(Type type)
Sets the basic type of frame.
Definition: pgFrameStyle.I:64
void set_frame(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top)
Sets the bounding rectangle of the item, in local coordinates.
Definition: pgItem.I:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data)
This function will be called during the cull traversal to perform any additional operations that shou...
Definition: pgEntry.cxx:176
This collects together the pieces of data that are accumulated for each node while walking the scene ...
static ButtonHandle one()
Returns the ButtonHandle associated with the first mouse button.
Definition: mouseButton.cxx:43
bool set_wtext(const std::wstring &wtext)
Accepts a new text string and associated properties structure, and precomputes the wordwrapping layou...
PN_stdfloat get_ypos(int r, int c) const
Returns the y position of the origin of all of the characters or graphic objects in the indicated row...
GeomNode * create(bool dynamic=false)
Creates a new GeomNode that will render the series of line segments and points described via calls to...
Definition: lineSegs.I:108
virtual void press(const MouseWatcherParameter &param, bool background)
This is a callback hook function, called whenever a mouse or keyboard entry is depressed while the mo...
Definition: pgEntry.cxx:195
int get_num_rows() const
Returns the number of rows of text after it has all been wordwrapped and assembled.
void set_color(PN_stdfloat r, PN_stdfloat g, PN_stdfloat b, PN_stdfloat a=1.0, int priority=0)
Applies a scene-graph color to the referenced node.
Definition: nodePath.cxx:1952
set_preserve_trailing_whitespace
Sets the preserve_trailing_whitespace flag.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void show()
Undoes the effect of a previous hide() on this node: makes the referenced node (and the entire subgra...
Definition: nodePath.I:1788
static TextPropertiesManager * get_global_ptr()
Returns the pointer to the global TextPropertiesManager object.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void traverse(const NodePath &root)
Begins the traversal from the indicated node.
static ButtonHandle four()
Returns the ButtonHandle associated with the fourth mouse button.
Definition: mouseButton.cxx:67
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:26
bool is_wtext() const
Returns true if any of the characters in the string returned by get_wtext() are out of the range of a...
Definition: pgEntry.cxx:705
void set_color(PN_stdfloat r, PN_stdfloat g, PN_stdfloat b, PN_stdfloat a=1.0f)
Establishes the color that will be assigned to all vertices created by future calls to move_to() and ...
Definition: lineSegs.I:56
bool get_overflow_mode() const
Specifies whether overflow mode is enabled.
Definition: pgEntry.I:335
virtual void erase(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the user erase characters in the text.
Definition: pgEntry.cxx:499
void move_to(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z)
Moves the pen to the given point without drawing a line.
Definition: lineSegs.I:84
virtual void accept_failed(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the user presses Enter but we can't accept the inpu...
Definition: pgEntry.cxx:458
bool has_button() const
Returns true if this parameter has an associated mouse or keyboard button, false otherwise.
Encapsulates creation of a series of connected or disconnected line segments or points,...
Definition: lineSegs.h:33
void set_frame_style(int state, const PGFrameStyle &style)
Changes the kind of frame that will be drawn behind the item when it is in the indicated state.
Definition: pgItem.cxx:992
ButtonHandle get_button() const
Returns the mouse or keyboard button associated with this event.
virtual void set_focus(bool focus)
Sets whether the PGItem currently has keyboard focus.
Definition: pgItem.cxx:851
get_frame_time
Returns the time in seconds as of the last time tick() was called (typically, this will be as of the ...
Definition: clockObject.h:91
void set_scissor(PN_stdfloat left, PN_stdfloat right, PN_stdfloat bottom, PN_stdfloat top)
Sets up a scissor region on the nodes rendered at this level and below.
Definition: nodePath.cxx:2709
NodePath attach_new_node(PandaNode *node, int sort=0, Thread *current_thread=Thread::get_current_thread()) const
Attaches a new node, with or without existing parents, to the scene graph below the referenced node o...
Definition: nodePath.cxx:563
set_wordwrap
Sets the text up to automatically wordwrap when it exceeds the indicated width.
std::string get_accept_event(const ButtonHandle &button) const
Returns the event name that will be thrown when the entry is accepted normally.
Definition: pgEntry.I:458
void set_color(PN_stdfloat r, PN_stdfloat g, PN_stdfloat b, PN_stdfloat a)
Sets the dominant color of the frame.
Definition: pgFrameStyle.I:80
void set_num_lines(int num_lines)
Sets the number of lines of text the PGEntry will use.
Definition: pgEntry.I:206
virtual void cursormove()
This is a callback hook function, called whenever the cursor moves.
Definition: pgEntry.cxx:511
This class is not normally used directly by user code, but is used by the TextNode to lay out a block...
Definition: textAssembler.h:43
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
size_t get_cursor_pos() const
Returns the position of the user's edit cursor within the candidate string.
static TextNode * get_text_node()
Returns the TextNode object that will be used by all PGItems to generate default labels given a strin...
Definition: pgItem.cxx:1055
PN_stdfloat get_xpos(int r, int c) const
Returns the x position of the origin of the character or graphic object at the indicated position in ...
bool get_focus() const
Returns whether the PGItem currently has focus for keyboard events.
Definition: pgItem.I:170
NodePath copy_to(const NodePath &other, int sort=0, Thread *current_thread=Thread::get_current_thread()) const
Functions like instance_to(), except a deep copy is made of the referenced node and all of its descen...
Definition: nodePath.cxx:535
static ButtonHandle five()
Returns the ButtonHandle associated with the fifth mouse button.
Definition: mouseButton.cxx:75
set_properties
Specifies the default TextProperties that are applied to the text in the absence of any nested proper...
virtual void keystroke(const MouseWatcherParameter &param, bool background)
This is a callback hook function, called whenever the user types a key.
Definition: pgEntry.cxx:315
Similar to MutexHolder, but for a light reentrant mutex.
virtual void set_focus(bool focus)
Toggles the focus state of the entry.
Definition: pgEntry.cxx:692
void draw_to(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z)
Draws a line segment from the pen's last position (the last call to move_to or draw_to) to the indica...
Definition: lineSegs.I:94
bool has_candidate() const
Returns true if this parameter has an associated candidate string, false otherwise.
void setup(PN_stdfloat width, int num_lines)
Sets up the entry for normal use.
Definition: pgEntry.cxx:524
virtual void candidate(const MouseWatcherParameter &param, bool background)
This is a callback hook function, called whenever the user selects an item from the IME menu.
Definition: pgEntry.cxx:421
void set_state(int state)
Sets the "state" of this particular PGItem.
Definition: pgItem.I:141
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int flatten_strong()
The strongest possible flattening.
Definition: nodePath.cxx:5543
bool calc_r_c(int &r, int &c, int n) const
Computes the row and column index of the nth character or graphic object in the text.
void set_text_def(int state, TextNode *node)
Changes the TextNode that will be used to render the text within the entry when the entry is in the i...
Definition: pgEntry.cxx:647
NodePath get_cursor_def()
Returns the Node that will be rendered to represent the cursor.
Definition: pgEntry.I:251
PandaNode * node() const
Returns the referenced node of the path.
Definition: nodePath.I:227
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void remove_node(Thread *current_thread=Thread::get_current_thread())
Disconnects the referenced node from the scene graph.
Definition: nodePath.cxx:591
size_t get_highlight_start() const
Returns the first highlighted character in the candidate string.
The primary interface to this module.
Definition: textNode.h:48
This defines the set of visual properties that may be assigned to the individual characters of the te...
void set_mat(const LMatrix4 &mat)
Directly sets an arbitrary 4x4 transform matrix.
Definition: nodePath.cxx:1292
virtual void candidate(const MouseWatcherParameter &param, bool background)
This is a callback hook function, called whenever the user highlights an option in the IME window.
Definition: pgItem.cxx:733
std::string get_erase_event() const
Returns the event name that will be thrown whenever the user erases characters in the text.
Definition: pgEntry.I:495
virtual void keystroke(const MouseWatcherParameter &param, bool background)
This is a callback hook function, called whenever the user presses a key.
Definition: pgItem.cxx:709
const LMatrix4 & get_mat() const
Returns the transform matrix that has been applied to the referenced node, or the identity matrix if ...
Definition: nodePath.I:776
std::wstring get_wtext() const
Returns a wstring that represents the contents of the text.
PN_stdfloat get_line_height() const
Returns the number of units high each line of text is.
Definition: textNode.I:20
virtual void type(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the user extends the text by typing.
Definition: pgEntry.cxx:486
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
virtual void set_active(bool active)
Toggles the active/inactive state of the entry.
Definition: pgEntry.cxx:681
This is sent along as a parameter to most events generated for a region to indicate the mouse and but...
const TransformState * get_transform(Thread *current_thread=Thread::get_current_thread()) const
Returns the complete transform object set on this node.
Definition: nodePath.cxx:758
std::wstring decode_text(const std::string &text) const
Returns the given wstring decoded to a single-byte string, via the current encoding system.
Definition: textEncoder.I:490
TextNode * get_text_def(int state) const
Returns the TextNode that will be used to render the text within the entry when the entry is in the i...
Definition: pgEntry.cxx:664
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This defines all of the TextProperties structures that might be referenced by name from an embedded t...
void hide()
Makes the referenced node (and the entire subgraph below this node) invisible to all cameras.
Definition: nodePath.I:1843
virtual void accept(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the entry is accepted by the user pressing Enter no...
Definition: pgEntry.cxx:444
TextProperties get_properties(const std::string &name)
Returns the TextProperties associated with the indicated name.
void set_max_chars(int max_chars)
Sets the maximum number of characters that may be typed into the entry.
Definition: pgEntry.I:157
bool set_text(const std::string &text)
Changes the text currently displayed within the entry.
Definition: pgEntry.I:23
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:161
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
Definition: cullTraverser.h:45
size_t get_highlight_end() const
Returns one more than the last highlighted character in the candidate string.
virtual void set_active(bool active)
Sets whether the PGItem is active for mouse watching.
Definition: pgItem.cxx:828
const std::wstring & get_candidate_string() const
Returns the candidate string associated with this event.
std::string get_overflow_event() const
Returns the event name that will be thrown when too much text is attempted to be entered into the PGE...
Definition: pgEntry.I:477
virtual void press(const MouseWatcherParameter &param, bool background)
This is a callback hook function, called whenever a mouse or keyboard button is depressed while the m...
Definition: pgItem.cxx:657
bool has_keycode() const
Returns true if this parameter has an associated keycode, false otherwise.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::string get_type_event() const
Returns the event name that will be thrown whenever the user extends the text by typing.
Definition: pgEntry.I:486
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.