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  */
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  */
177  LightReMutexHolder holder(_lock);
178  PGItem::cull_callback(trav, data);
180  update_text();
181  update_cursor();
182  }
183 
184  // Now render the text.
185  CullTraverserData next_data(data, _text_render_root.node());
186  trav->traverse(next_data);
187 
188  // Now continue to render everything else below this node.
189  return true;
190 }
191 
192 /**
193  * This is a callback hook function, called whenever a mouse or keyboard entry
194  * is depressed while the mouse is within the region.
195  */
197 press(const MouseWatcherParameter &param, bool background) {
198  LightReMutexHolder holder(_lock);
199  if (get_active()) {
200  if (param.has_button()) {
201  // Make sure _text is initialized properly.
202  update_text();
203 
204  bool overflow_mode = get_overflow_mode() && _num_lines == 1;
205 
206  ButtonHandle button = param.get_button();
207 
208  if (button == MouseButton::one() ||
209  button == MouseButton::two() ||
210  button == MouseButton::three() ||
211  button == MouseButton::four() ||
212  button == MouseButton::five()) {
213  // Mouse button; set focus.
214  set_focus(true);
215 
216  } else if ((!background && get_focus()) ||
217  (background && get_background_focus())) {
218  // Keyboard button.
219  if (!_candidate_wtext.empty()) {
220  _candidate_wtext = wstring();
221  _text_geom_stale = true;
222  }
223 
224  _cursor_position = min(_cursor_position, _text.get_num_characters());
225  _blink_start = ClockObject::get_global_clock()->get_frame_time();
226  if (button == KeyboardButton::enter()) {
227  // Enter. Accept the entry.
228  if (_accept_enabled) {
229  accept(param);
230  }
231  else {
232  accept_failed(param);
233  }
234 
235  } else if (button == KeyboardButton::backspace()) {
236  // Backspace. Remove the character to the left of the cursor.
237  if (_cursor_position > 0) {
238  _text.set_wsubstr(wstring(), _cursor_position - 1, 1);
239  _cursor_position--;
240  _cursor_stale = true;
241  _text_geom_stale = true;
242  erase(param);
243  }
244 
245  } else if (button == KeyboardButton::del()) {
246  // Delete. Remove the character to the right of the cursor.
247  if (_cursor_position < _text.get_num_characters()) {
248  _text.set_wsubstr(wstring(), _cursor_position, 1);
249  _text_geom_stale = true;
250  erase(param);
251  }
252 
253  } else if (button == KeyboardButton::left()) {
254  if (_cursor_keys_active) {
255  // Left arrow. Move the cursor position to the left.
256  --_cursor_position;
257  if (_cursor_position < 0) {
258  _cursor_position = 0;
259  overflow(param);
260  } else {
261  type(param);
262  }
263  _cursor_stale = true;
264  if (overflow_mode){
265  _text_geom_stale = true;
266  }
267  }
268 
269  } else if (button == KeyboardButton::right()) {
270  if (_cursor_keys_active) {
271  // Right arrow. Move the cursor position to the right.
272  ++_cursor_position;
273  if (_cursor_position > _text.get_num_characters()) {
274  _cursor_position = _text.get_num_characters();
275  overflow(param);
276  } else {
277  type(param);
278  }
279  _cursor_stale = true;
280  if (overflow_mode){
281  _text_geom_stale = true;
282  }
283  }
284 
285  } else if (button == KeyboardButton::home()) {
286  if (_cursor_keys_active) {
287  // Home. Move the cursor position to the beginning.
288  _cursor_position = 0;
289  _cursor_stale = true;
290  if (overflow_mode){
291  _text_geom_stale = true;
292  }
293  type(param);
294  }
295 
296  } else if (button == KeyboardButton::end()) {
297  if (_cursor_keys_active) {
298  // End. Move the cursor position to the end.
299  _cursor_position = _text.get_num_characters();
300  _cursor_stale = true;
301  if (overflow_mode){
302  _text_geom_stale = true;
303  }
304  type(param);
305  }
306  }
307  }
308  }
309  }
310  PGItem::press(param, background);
311 
312 #ifdef THREADED_PIPELINE
313  if (Pipeline::get_render_pipeline()->get_num_stages() > 1) {
314  if (_text_geom_stale) {
315  update_text();
316  }
317  if (_cursor_stale) {
318  update_cursor();
319  }
320  }
321 #endif
322 }
323 
324 /**
325  * This is a callback hook function, called whenever the user types a key.
326  */
328 keystroke(const MouseWatcherParameter &param, bool background) {
329  LightReMutexHolder holder(_lock);
330  if (get_active()) {
331  if (param.has_keycode()) {
332  // Make sure _text is initialized properly.
333  update_text();
334 
335  int keycode = param.get_keycode();
336 
337  if ((!isascii(keycode) || isprint(keycode)) && keycode != '\t') {
338  // A normal visible character. Add a new character to the text entry,
339  // if there's room.
340  if (!_candidate_wtext.empty()) {
341  _candidate_wtext = wstring();
342  _text_geom_stale = true;
343  }
344  wstring new_char(1, (wchar_t)keycode);
345 
346  if (get_max_chars() > 0 && _text.get_num_characters() >= get_max_chars()) {
347  // In max_chars mode, we consider it an overflow after we have
348  // exceeded a fixed number of characters, irrespective of the
349  // formatted width of those characters.
350  overflow(param);
351 
352  } else {
353  _cursor_position = min(_cursor_position, _text.get_num_characters());
354  bool too_long = !_text.set_wsubstr(new_char, _cursor_position, 0);
355  bool overflow_mode = get_overflow_mode() && _num_lines == 1;
356  if(overflow_mode){
357  too_long = false;
358  }
359  if (_obscure_mode) {
360  too_long = !_obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
361  } else {
362  if (!too_long && (_text.get_num_rows() == _num_lines) && !overflow_mode) {
363  // If we've filled up all of the available lines, we must also
364  // ensure that the last line is not too long (it might be,
365  // because of additional whitespace on the end).
366  int r = _num_lines - 1;
367  int c = _text.get_num_cols(r);
368  PN_stdfloat last_line_width =
369  _text.get_xpos(r, c) - _text.get_xpos(r, 0);
370  too_long = (last_line_width > _max_width);
371  }
372 
373  if (!too_long && keycode == ' ' && !overflow_mode) {
374  // Even if we haven't filled up all of the available lines, we
375  // should reject a space that's typed at the end of the current
376  // line if it would make that line exceed the maximum width,
377  // just so we don't allow an infinite number of spaces to
378  // accumulate.
379  int r, c;
380  _text.calc_r_c(r, c, _cursor_position);
381  if (_text.get_num_cols(r) == c + 1) {
382  // The user is typing at the end of the line. But we must
383  // allow at least one space at the end of the line, so we only
384  // make any of the following checks if there are already
385  // multiple spaces at the end of the line.
386  if (c - 1 >= 0 && _text.get_character(r, c - 1) == ' ') {
387  // Ok, the user is putting multiple spaces on the end of a
388  // line; we need to make sure the line does not grow too
389  // wide. Measure the line's width.
390  PN_stdfloat current_line_width =
391  _text.get_xpos(r, c + 1) - _text.get_xpos(r, 0);
392  if (current_line_width > _max_width) {
393  // We have to reject the space, but we don't treat it as
394  // an overflow condition.
395  _text.set_wsubstr(wstring(), _cursor_position, 1);
396  // If the user is typing over existing space characters,
397  // we act as if the right-arrow key were pressed instead,
398  // and advance the cursor to the next position.
399  // Otherwise, we just quietly eat the space character.
400  if (_cursor_position < _text.get_num_characters() &&
401  _text.get_character(_cursor_position) == ' ') {
402  _cursor_position++;
403  _cursor_stale = true;
404  }
405  return;
406  }
407  }
408  }
409  }
410  }
411 
412  if (too_long) {
413  _text.set_wsubstr(wstring(), _cursor_position, 1);
414  overflow(param);
415 
416  } else {
417  _cursor_position += new_char.length();
418  _cursor_stale = true;
419  _text_geom_stale = true;
420  type(param);
421  }
422  }
423  }
424  }
425  }
426  PGItem::keystroke(param, background);
427 
428 #ifdef THREADED_PIPELINE
429  if (Pipeline::get_render_pipeline()->get_num_stages() > 1) {
430  if (_text_geom_stale) {
431  update_text();
432  }
433  if (_cursor_stale) {
434  update_cursor();
435  }
436  }
437 #endif
438 }
439 
440 /**
441  * This is a callback hook function, called whenever the user selects an item
442  * from the IME menu.
443  */
445 candidate(const MouseWatcherParameter &param, bool background) {
446  LightReMutexHolder holder(_lock);
447  if (get_active()) {
448  if (param.has_candidate()) {
449  // Save the candidate string so it can be displayed.
450  _candidate_wtext = param.get_candidate_string();
451  _candidate_highlight_start = param.get_highlight_start();
452  _candidate_highlight_end = param.get_highlight_end();
453  _candidate_cursor_pos = param.get_cursor_pos();
454  _text_geom_stale = true;
455  if (!_candidate_wtext.empty()) {
456  type(param);
457  }
458  }
459  }
460  PGItem::candidate(param, background);
461 
462 #ifdef THREADED_PIPELINE
463  if (Pipeline::get_render_pipeline()->get_num_stages() > 1) {
464  if (_text_geom_stale) {
465  update_text();
466  }
467  if (_cursor_stale) {
468  update_cursor();
469  }
470  }
471 #endif
472 }
473 
474 /**
475  * This is a callback hook function, called whenever the entry is accepted by
476  * the user pressing Enter normally.
477  */
479 accept(const MouseWatcherParameter &param) {
480  LightReMutexHolder holder(_lock);
482  string event = get_accept_event(param.get_button());
483  play_sound(event);
484  throw_event(event, EventParameter(ep));
485  set_focus(false);
486 }
487 
488 /**
489  * This is a callback hook function, called whenever the user presses Enter
490  * but we can't accept the input.
491  */
493 accept_failed(const MouseWatcherParameter &param) {
494  LightReMutexHolder holder(_lock);
496  string event = get_accept_failed_event(param.get_button());
497  play_sound(event);
498  throw_event(event, EventParameter(ep));
499  // set_focus(false);
500 }
501 
502 /**
503  * This is a callback hook function, called whenever the entry is overflowed
504  * because the user attempts to type too many characters, exceeding either
505  * set_max_chars() or set_max_width().
506  */
508 overflow(const MouseWatcherParameter &param) {
509  LightReMutexHolder holder(_lock);
511  string event = get_overflow_event();
512  play_sound(event);
513  throw_event(event, EventParameter(ep));
514 }
515 
516 /**
517  * This is a callback hook function, called whenever the user extends the text
518  * by typing.
519  */
521 type(const MouseWatcherParameter &param) {
522  LightReMutexHolder holder(_lock);
524  string event = get_type_event();
525  play_sound(event);
526  throw_event(event, EventParameter(ep));
527 }
528 
529 /**
530  * This is a callback hook function, called whenever the user erase characters
531  * in the text.
532  */
534 erase(const MouseWatcherParameter &param) {
535  LightReMutexHolder holder(_lock);
537  string event = get_erase_event();
538  play_sound(event);
539  throw_event(event, EventParameter(ep));
540 }
541 
542 /**
543  * This is a callback hook function, called whenever the cursor moves.
544  */
546 cursormove() {
547  LightReMutexHolder holder(_lock);
548  string event = get_cursormove_event();
549  throw_event(event, EventParameter(_cursor_def.get_x()), EventParameter(_cursor_def.get_y()));
550 }
551 
552 /**
553  * Sets up the entry for normal use. The width is the maximum width of
554  * characters that will be typed, and num_lines is the integer number of lines
555  * of text of the entry. Both of these together determine the size of the
556  * entry, based on the TextNode in effect.
557  */
559 setup(PN_stdfloat width, int num_lines) {
560  LightReMutexHolder holder(_lock);
561  setup_minimal(width, num_lines);
562 
563  TextNode *text_node = get_text_def(S_focus);
564  PN_stdfloat line_height = text_node->get_line_height();
565 
566  // Determine the four corners of the frame.
567  float bottom = -0.3f * line_height - (line_height * (num_lines - 1));
568  // Transform each corner by the TextNode's transform.
569  LMatrix4 mat = text_node->get_transform();
570  LPoint3 ll = LPoint3::rfu(0.0f, 0.0f, bottom) * mat;
571  LPoint3 ur = LPoint3::rfu(width, 0.0f, line_height) * mat;
572  LPoint3 lr = LPoint3::rfu(width, 0.0f, bottom) * mat;
573  LPoint3 ul = LPoint3::rfu(0.0f, 0.0f, line_height) * mat;
574 
575  LVector3 up = LVector3::up();
576  int up_axis;
577  if (up[1]) {
578  up_axis = 1;
579  }
580  else if (up[2]) {
581  up_axis = 2;
582  }
583  else {
584  up_axis = 0;
585  }
586  LVector3 right = LVector3::right();
587  int right_axis;
588  if (right[0]) {
589  right_axis = 0;
590  }
591  else if (right[2]) {
592  right_axis = 2;
593  }
594  else {
595  right_axis = 1;
596  }
597 
598  // And get the new minmax to define the frame. We do all this work instead
599  // of just using the lower-left and upper-right corners, just in case the
600  // text was rotated.
601  LVecBase4 frame;
602  frame[0] = min(min(ll[right_axis], ur[right_axis]), min(lr[right_axis], ul[right_axis]));
603  frame[1] = max(max(ll[right_axis], ur[right_axis]), max(lr[right_axis], ul[right_axis]));
604  frame[2] = min(min(ll[up_axis], ur[up_axis]), min(lr[up_axis], ul[up_axis]));
605  frame[3] = max(max(ll[up_axis], ur[up_axis]), max(lr[up_axis], ul[up_axis]));
606 
607  switch (text_node->get_align()) {
608  case TextNode::A_left:
609  case TextNode::A_boxed_left:
610  // The default case.
611  break;
612 
613  case TextNode::A_center:
614  case TextNode::A_boxed_center:
615  frame[0] = -width / 2.0;
616  frame[1] = width / 2.0;
617  break;
618 
619  case TextNode::A_right:
620  case TextNode::A_boxed_right:
621  frame[0] = -width;
622  frame[1] = 0.0f;
623  break;
624  }
625 
626  set_frame(frame[0] - 0.15f, frame[1] + 0.15f, frame[2], frame[3]);
627 
628  PGFrameStyle style;
629  style.set_width(0.1f, 0.1f);
630  style.set_type(PGFrameStyle::T_bevel_in);
631  style.set_color(0.8f, 0.8f, 0.8f, 1.0f);
632 
633  set_frame_style(S_no_focus, style);
634 
635  style.set_color(0.9f, 0.9f, 0.9f, 1.0f);
636  set_frame_style(S_focus, style);
637 
638  style.set_color(0.6f, 0.6f, 0.6f, 1.0f);
639  set_frame_style(S_inactive, style);
640 }
641 
642 /**
643  * Sets up the entry without creating any frame or other decoration.
644  */
646 setup_minimal(PN_stdfloat width, int num_lines) {
647  LightReMutexHolder holder(_lock);
648  set_text(string());
649  _cursor_position = 0;
650  set_max_chars(0);
651  set_max_width(width);
652  set_num_lines(num_lines);
653  update_text();
654 
655  _accept_enabled = true;
656 
657  TextNode *text_node = get_text_def(S_focus);
658  PN_stdfloat line_height = text_node->get_line_height();
659 
660  // Set up a default cursor: a vertical bar.
662 
663  LineSegs ls;
664  ls.set_color(text_node->get_text_color());
665  ls.move_to(0.0f, 0.0f, -0.15f * line_height);
666  ls.draw_to(0.0f, 0.0f, 0.70f * line_height);
668 
669  /*
670  // An underscore cursor would work too.
671  text_node->set_text("_");
672  get_cursor_def().attach_new_node(text_node->generate());
673  */
674 }
675 
676 /**
677  * Changes the TextNode that will be used to render the text within the entry
678  * when the entry is in the indicated state. The default if nothing is
679  * specified is the same TextNode returned by PGItem::get_text_node().
680  */
682 set_text_def(int state, TextNode *node) {
683  LightReMutexHolder holder(_lock);
684  nassertv(state >= 0 && state < 1000); // Sanity check.
685  if (node == nullptr && state >= (int)_text_defs.size()) {
686  // If we're setting it to NULL, we don't need to slot a new one.
687  return;
688  }
689  slot_text_def(state);
690 
691  _text_defs[state] = node;
692 }
693 
694 /**
695  * Returns the TextNode that will be used to render the text within the entry
696  * when the entry is in the indicated state. See set_text_def().
697  */
699 get_text_def(int state) const {
700  LightReMutexHolder holder(_lock);
701  if (state < 0 || state >= (int)_text_defs.size()) {
702  // If we don't have a definition, use the global one.
703  return get_text_node();
704  }
705  if (_text_defs[state] == nullptr) {
706  return get_text_node();
707  }
708  return _text_defs[state];
709 }
710 
711 /**
712  * Toggles the active/inactive state of the entry. In the case of a PGEntry,
713  * this also changes its visual appearance.
714  */
716 set_active(bool active) {
717  LightReMutexHolder holder(_lock);
718  PGItem::set_active(active);
719  update_state();
720 }
721 
722 /**
723  * Toggles the focus state of the entry. In the case of a PGEntry, this also
724  * changes its visual appearance.
725  */
727 set_focus(bool focus) {
728  LightReMutexHolder holder(_lock);
729  PGItem::set_focus(focus);
730  _blink_start = ClockObject::get_global_clock()->get_frame_time();
731  update_state();
732 }
733 
734 /**
735  * Returns true if any of the characters in the string returned by get_wtext()
736  * are out of the range of an ASCII character (and, therefore, get_wtext()
737  * should be called in preference to get_text()).
738  */
740 is_wtext() const {
741  LightReMutexHolder holder(_lock);
742  for (int i = 0; i < _text.get_num_characters(); ++i) {
743  wchar_t ch = _text.get_character(i);
744  if ((ch & ~0x7f) != 0) {
745  return true;
746  }
747  }
748 
749  return false;
750 }
751 
752 /**
753  * Ensures there is a slot in the array for the given text definition.
754  */
755 void PGEntry::
756 slot_text_def(int state) {
757  while (state >= (int)_text_defs.size()) {
758  _text_defs.push_back(nullptr);
759  }
760 }
761 
762 /**
763  * Causes the PGEntry to recompute its text, if necessary.
764  */
765 void PGEntry::
766 update_text() {
767  TextNode *node = get_text_def(get_state());
768  nassertv(node != nullptr);
769 
770  if (_text_geom_stale || node != _last_text_def) {
771  TextProperties props = *node;
772  props.set_wordwrap(_max_width);
774  _text.set_properties(props);
775  _text.set_max_rows(_num_lines);
776 
777  if (node != _last_text_def) {
778  // Make sure the default properties are applied to all the characters in
779  // the text.
780  _text.set_wtext(_text.get_wtext());
781  _last_text_def = node;
782  }
783 
784  _text.set_multiline_mode (!(get_overflow_mode() && _num_lines == 1));
785 
786  PT(PandaNode) assembled;
787  if (_obscure_mode) {
788  _obscure_text.set_properties(props);
789  _obscure_text.set_max_rows(_num_lines);
790  _obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
791  assembled = _obscure_text.assemble_text();
792 
793  } else if (_candidate_wtext.empty()) {
794  // If we're not trying to display a candidate string, it's easy: just
795  // display the current text contents.
796  assembled = _text.assemble_text();
797 
798  } else {
800  TextProperties inactive = tp_mgr->get_properties(_candidate_inactive);
801  TextProperties active = tp_mgr->get_properties(_candidate_active);
802 
803  // Insert the complex sequence of characters required to show the
804  // candidate string in a different color. This gets inserted at the
805  // current cursor position.
806  wstring cseq;
807  cseq += wstring(1, (wchar_t)text_push_properties_key);
808  cseq += node->decode_text(_candidate_inactive);
809  cseq += wstring(1, (wchar_t)text_push_properties_key);
810  cseq += _candidate_wtext.substr(0, _candidate_highlight_start);
811  cseq += wstring(1, (wchar_t)text_push_properties_key);
812  cseq += node->decode_text(_candidate_active);
813  cseq += wstring(1, (wchar_t)text_push_properties_key);
814  cseq += _candidate_wtext.substr(_candidate_highlight_start,
815  _candidate_highlight_end - _candidate_highlight_start);
816  cseq += wstring(1, (wchar_t)text_pop_properties_key);
817  cseq += _candidate_wtext.substr(_candidate_highlight_end);
818  cseq += wstring(1, (wchar_t)text_pop_properties_key);
819 
820  // Create a special TextAssembler to insert the candidate string.
821  TextAssembler ctext(_text);
822  ctext.set_wsubstr(cseq, _cursor_position, 0);
823  assembled = ctext.assemble_text();
824  }
825 
826  if (!_current_text.is_empty()) {
827  _current_text.remove_node();
828  }
829 
830  _current_text =
831  _text_render_root.attach_new_node(assembled);
832 
833  _current_text.set_mat(node->get_transform());
834 
835  if (get_overflow_mode() && _num_lines == 1){
836  // We determine the minimum required padding:
837  PN_stdfloat cursor_graphic_pos = _text.get_xpos(0, _cursor_position);
838  PN_stdfloat min_padding = (cursor_graphic_pos - _max_width);
839 
840 /*
841  * If the current padding would produce a caret outside the text entry, we
842  * relocate it. Here we also have to make a jump towards the center when the
843  * caret is going outside the visual area and there's enough text ahead for
844  * increased usability. The amount that the caret is moved for hinting
845  * depends on the OS, and the specific behavior under certain circunstances in
846  * different Operating Systems is very complicated (the implementation would
847  * need to "remember" the original typing starting point). For the moment we
848  * are gonna use an unconditional 50% jump, this behavior is found in some Mac
849  * dialogs, and it's the easiest to implement by far, while providing proven
850  * usability. PROS: Reduces the amount of scrolling while both writing and
851  * navigating with arrow keys, which is desirable. CONS: The user needs to
852  * remember that heshe has exceeded the boundaries, but this happens with all
853  * implementations to some degree.
854  */
855 
856  if (_current_padding < min_padding || _current_padding > cursor_graphic_pos){
857  _current_padding = min_padding + (cursor_graphic_pos - min_padding) * 0.5;
858  }
859 
860  if (_current_padding < 0){ // Caret virtual position doesn't exceed boundaries
861  _current_padding = 0;
862  }
863 
864  _current_text.set_x(_current_text.get_x() - _current_padding);
865  _current_text.set_scissor(NodePath(this),
866  LPoint3::rfu(0, 0, -0.5), LPoint3::rfu(_max_width, 0, -0.5),
867  LPoint3::rfu(_max_width, 0, 1.5), LPoint3::rfu(0, 0, 1.5));
868  }
869 
870  _text_geom_stale = false;
871  _text_geom_flattened = false;
872  _cursor_stale = true;
873  }
874 
875  // We'll flatten the text geometry only if we don't have focus. Otherwise,
876  // we assume the user may be changing it frequently.
877  if (!get_focus() && !_text_geom_flattened) {
878  _current_text.flatten_strong();
879  _text_geom_flattened = true;
880  }
881 }
882 
883 /**
884  * Moves the cursor to its correct position.
885  */
886 void PGEntry::
887 update_cursor() {
888  TextNode *node = get_text_def(get_state());
889  nassertv(node != nullptr);
890  _cursor_scale.set_mat(node->get_transform());
891  _cursor_scale.set_color(node->get_text_color());
892 
893  if (_cursor_stale || node != _last_text_def) {
894  update_text();
895 
896  _cursor_position = min(_cursor_position, _text.get_num_characters());
897 
898  // Determine the row and column of the cursor.
899  int row, column;
900  PN_stdfloat xpos, ypos;
901  if (_obscure_mode) {
902  _obscure_text.calc_r_c(row, column, _cursor_position);
903  xpos = _obscure_text.get_xpos(row, column);
904  ypos = _obscure_text.get_ypos(row, column);
905  } else {
906  _text.calc_r_c(row, column, _cursor_position);
907  if (_cursor_position > 0 && _text.get_character(_cursor_position - 1) == '\n') {
908  row += 1;
909  column = 0;
910  }
911  xpos = _text.get_xpos(row, column);
912  ypos = _text.get_ypos(row, column);
913  }
914 
915  _cursor_def.set_pos(xpos - _current_padding, 0.0f, ypos);
916  _cursor_stale = false;
917  cursormove();
918 
919  }
920 
921  // Should the cursor be visible?
922  if (!get_focus() || !_candidate_wtext.empty()) {
923  show_hide_cursor(false);
924  } else {
925  double elapsed_time =
926  ClockObject::get_global_clock()->get_frame_time() - _blink_start;
927  int cycle = (int)(elapsed_time * _blink_rate * 2.0f);
928  bool visible = ((cycle & 1) == 0);
929  show_hide_cursor(visible);
930  }
931 }
932 
933 /**
934  * Makes the cursor visible or invisible, e.g. during a blink cycle.
935  */
936 void PGEntry::
937 show_hide_cursor(bool visible) {
938  if (visible != _cursor_visible) {
939  if (visible) {
940  _cursor_scale.show();
941  } else {
942  _cursor_scale.hide();
943  }
944  _cursor_visible = visible;
945  }
946 }
947 
948 /**
949  * Determines what the correct state for the PGEntry should be.
950  */
951 void PGEntry::
952 update_state() {
953  if (get_active()) {
954  if (get_focus()) {
955  set_state(S_focus);
956  } else {
957  set_state(S_no_focus);
958  }
959  } else {
960  set_state(S_inactive);
961  }
962 #ifdef THREADED_PIPELINE
963  if (Pipeline::get_render_pipeline()->get_num_stages() > 1) {
964  update_text();
965  update_cursor();
966  }
967 #endif
968 }
A ButtonHandle represents a single button from any device, including keyboard buttons and mouse butto...
Definition: buttonHandle.h:26
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
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
This collects together the pieces of data that are accumulated for each node while walking the scene ...
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
Definition: cullTraverser.h:45
void traverse(const NodePath &root)
Begins the traversal from the indicated node.
An optional parameter associated with an event.
Similar to MutexHolder, but for a light reentrant mutex.
Encapsulates creation of a series of connected or disconnected line segments or points,...
Definition: lineSegs.h:33
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
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
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
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
static ButtonHandle four()
Returns the ButtonHandle associated with the fourth mouse button.
Definition: mouseButton.cxx:67
static ButtonHandle one()
Returns the ButtonHandle associated with the first mouse button.
Definition: mouseButton.cxx:43
static ButtonHandle five()
Returns the ButtonHandle associated with the fifth mouse button.
Definition: mouseButton.cxx:75
static ButtonHandle two()
Returns the ButtonHandle associated with the second mouse button.
Definition: mouseButton.cxx:51
static ButtonHandle three()
Returns the ButtonHandle associated with the third mouse button.
Definition: mouseButton.cxx:59
This is sent along as a parameter to most events generated for a region to indicate the mouse and but...
bool has_candidate() const
Returns true if this parameter has an associated candidate string, false otherwise.
const std::wstring & get_candidate_string() const
Returns the candidate string associated with this event.
ButtonHandle get_button() const
Returns the mouse or keyboard button associated with this event.
size_t get_highlight_end() const
Returns one more than the last highlighted character in the candidate string.
bool has_keycode() const
Returns true if this parameter has an associated keycode, false otherwise.
size_t get_highlight_start() const
Returns the first highlighted character in the candidate string.
size_t get_cursor_pos() const
Returns the position of the user's edit cursor within the candidate string.
bool has_button() const
Returns true if this parameter has an associated mouse or keyboard button, false otherwise.
int get_keycode() const
Returns the keycode associated with this event.
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:159
void set_x(PN_stdfloat x)
Sets the X component of the position transform, leaving other components untouched.
Definition: nodePath.cxx:972
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:539
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:2776
void show()
Undoes the effect of a previous hide() on this node: makes the referenced node (and the entire subgra...
Definition: nodePath.I:1796
void set_mat(const LMatrix4 &mat)
Directly sets an arbitrary 4x4 transform matrix.
Definition: nodePath.cxx:1359
void hide()
Makes the referenced node (and the entire subgraph below this node) invisible to all cameras.
Definition: nodePath.I:1853
void remove_node(Thread *current_thread=Thread::get_current_thread())
Disconnects the referenced node from the scene graph.
Definition: nodePath.cxx:628
bool is_empty() const
Returns true if the NodePath contains no nodes.
Definition: nodePath.I:188
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
PandaNode * node() const
Returns the referenced node of the path.
Definition: nodePath.I:227
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:2019
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:600
const TransformState * get_transform(Thread *current_thread=Thread::get_current_thread()) const
Returns the complete transform object set on this node.
Definition: nodePath.cxx:795
int flatten_strong()
The strongest possible flattening.
Definition: nodePath.cxx:5635
This is a particular kind of PGItem that handles simple one-line or short multi-line text entries,...
Definition: pgEntry.h:38
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:197
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:512
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
virtual void type(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the user extends the text by typing.
Definition: pgEntry.cxx:521
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:699
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:521
NodePath get_cursor_def()
Returns the Node that will be rendered to represent the cursor.
Definition: pgEntry.I:272
void set_max_chars(int max_chars)
Sets the maximum number of characters that may be typed into the entry.
Definition: pgEntry.I:163
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:740
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:502
void set_num_lines(int num_lines)
Sets the number of lines of text the PGEntry will use.
Definition: pgEntry.I:219
void setup_minimal(PN_stdfloat width, int num_lines)
Sets up the entry without creating any frame or other decoration.
Definition: pgEntry.cxx:646
virtual void erase(const MouseWatcherParameter &param)
This is a callback hook function, called whenever the user erase characters in the text.
Definition: pgEntry.cxx:534
std::string get_cursormove_event() const
Returns the event name that will be thrown whenever the cursor moves.
Definition: pgEntry.I:538
virtual void cursormove()
This is a callback hook function, called whenever the cursor moves.
Definition: pgEntry.cxx:546
virtual void set_active(bool active)
Toggles the active/inactive state of the entry.
Definition: pgEntry.cxx:716
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:445
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:190
virtual PandaNode * make_copy() const
Returns a newly-allocated Node that is a shallow copy of this one.
Definition: pgEntry.cxx:141
void setup(PN_stdfloat width, int num_lines)
Sets up the entry for normal use.
Definition: pgEntry.cxx:559
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:479
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:530
bool get_overflow_mode() const
Specifies whether overflow mode is enabled.
Definition: pgEntry.I:370
bool set_text(const std::string &text)
Changes the text currently displayed within the entry.
Definition: pgEntry.I:23
virtual void set_focus(bool focus)
Toggles the focus state of the entry.
Definition: pgEntry.cxx:727
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:682
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:508
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:493
virtual void keystroke(const MouseWatcherParameter &param, bool background)
This is a callback hook function, called whenever the user types a key.
Definition: pgEntry.cxx:328
int get_max_chars() const
Returns the current maximum number of characters that may be typed into the entry,...
Definition: pgEntry.I:173
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
void clear_cursor_def()
Removes all the children from the cursor_def node, in preparation for adding a new definition.
Definition: pgEntry.I:282
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:493
void set_type(Type type)
Sets the basic type of frame.
Definition: pgFrameStyle.I:64
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_width(PN_stdfloat x, PN_stdfloat y)
Sets the width parameter, which has meaning only for certain frame types.
Definition: pgFrameStyle.I:139
This is the base class for all the various kinds of gui widget objects.
Definition: pgItem.h:53
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:674
bool get_active() const
Returns whether the PGItem is currently active for mouse events.
Definition: pgItem.I:160
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:1007
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
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:1076
bool get_focus() const
Returns whether the PGItem currently has focus for keyboard events.
Definition: pgItem.I:170
bool get_background_focus() const
Returns whether background_focus is currently enabled.
Definition: pgItem.I:180
int get_state() const
Returns the "state" of this particular PGItem.
Definition: pgItem.I:150
virtual void keystroke(const MouseWatcherParameter &param, bool background)
This is a callback hook function, called whenever the user presses a key.
Definition: pgItem.cxx:726
virtual void set_focus(bool focus)
Sets whether the PGItem currently has keyboard focus.
Definition: pgItem.cxx:860
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:750
virtual void set_active(bool active)
Sets whether the PGItem is active for mouse watching.
Definition: pgItem.cxx:837
This specialization on MouseWatcherParameter allows us to tag on additional elements to events for th...
A basic node of the scene graph or data graph.
Definition: pandaNode.h:65
set_state
Sets the complete RenderState that will be applied to all nodes at this level and below.
Definition: pandaNode.h:173
static Pipeline * get_render_pipeline()
Returns a pointer to the global render pipeline.
Definition: pipeline.I:18
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
bool set_wtext(const std::wstring &wtext)
Accepts a new text string and associated properties structure, and precomputes the wordwrapping layou...
set_multiline_mode
Sets the multiline mode flag.
int get_num_rows() const
Returns the number of rows of text after it has all been wordwrapped and assembled.
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.
set_max_rows
If max_rows is greater than zero, no more than max_rows will be accepted.
std::wstring get_wtext() const
Returns a wstring that represents the contents of the text.
int get_num_cols(int r) const
Returns the number of characters and/or graphic objects in the nth row.
int get_num_characters() const
Returns the number of characters of text, before wordwrapping.
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 ...
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...
wchar_t get_character(int n) const
Returns the character at the indicated position in the pre-wordwrapped string.
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.
set_properties
Specifies the default TextProperties that are applied to the text in the absence of any nested proper...
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
The primary interface to this module.
Definition: textNode.h:48
PN_stdfloat get_line_height() const
Returns the number of units high each line of text is.
Definition: textNode.I:20
This defines all of the TextProperties structures that might be referenced by name from an embedded t...
TextProperties get_properties(const std::string &name)
Returns the TextProperties associated with the indicated name.
static TextPropertiesManager * get_global_ptr()
Returns the pointer to the global TextPropertiesManager object.
This defines the set of visual properties that may be assigned to the individual characters of the te...
set_wordwrap
Sets the text up to automatically wordwrap when it exceeds the indicated width.
set_preserve_trailing_whitespace
Sets the preserve_trailing_whitespace flag.
get_current_pipeline_stage
Returns the integer pipeline stage associated with the current thread.
Definition: thread.h:110
Indicates a coordinate-system transform on vertices.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.