Panda3D

pgEntry.cxx

00001 // Filename: pgEntry.cxx
00002 // Created by:  drose (13Mar02)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "pgEntry.h"
00016 #include "pgMouseWatcherParameter.h"
00017 
00018 #include "cullTraverser.h"
00019 #include "cullTraverserData.h"
00020 #include "throw_event.h"
00021 #include "transformState.h"
00022 #include "mouseWatcherParameter.h"
00023 #include "keyboardButton.h"
00024 #include "mouseButton.h"
00025 #include "lineSegs.h"
00026 #include "textEncoder.h"
00027 #include "config_text.h"
00028 
00029 #include <math.h>
00030 
00031 TypeHandle PGEntry::_type_handle;
00032 
00033 ////////////////////////////////////////////////////////////////////
00034 //     Function: PGEntry::Constructor
00035 //       Access: Published
00036 //  Description: 
00037 ////////////////////////////////////////////////////////////////////
00038 PGEntry::
00039 PGEntry(const string &name) : 
00040   PGItem(name),
00041   _text(get_text_node()),
00042   _obscure_text(get_text_node())
00043 {
00044   set_cull_callback();
00045 
00046   _cursor_position = 0;
00047   _cursor_stale = true;
00048   _candidate_highlight_start = 0;
00049   _candidate_highlight_end = 0;
00050   _candidate_cursor_pos = 0;
00051   _max_chars = 0;
00052   _max_width = 0.0f;
00053   _num_lines = 1;
00054   _accept_enabled = true;
00055   _last_text_def = (TextNode *)NULL;
00056   _text_geom_stale = true;
00057   _text_geom_flattened = true;
00058   _blink_start = 0.0f;
00059   _blink_rate = 1.0f;
00060 
00061   _text_render_root = NodePath("text_root");
00062   _cursor_scale = _text_render_root.attach_new_node("cursor_scale");
00063   _cursor_def = _cursor_scale.attach_new_node("cursor");
00064   _cursor_visible = true;
00065 
00066   // These strings are used to specify the TextProperties to apply to
00067   // candidate strings generated from the IME (for entering text in an
00068   // east Asian language).
00069   _candidate_active = "candidate_active";
00070   _candidate_inactive = "candidate_inactive";
00071 
00072   _cursor_keys_active = true;
00073   _obscure_mode = false;
00074   _overflow_mode = false;
00075 
00076   _current_padding = 0.0f;
00077 
00078   set_active(true);
00079   update_state();
00080 
00081   // Some default parameters so it doesn't crash hard if no one calls
00082   // setup().
00083   setup_minimal(10, 1);
00084 }
00085 
00086 ////////////////////////////////////////////////////////////////////
00087 //     Function: PGEntry::Destructor
00088 //       Access: Public, Virtual
00089 //  Description: 
00090 ////////////////////////////////////////////////////////////////////
00091 PGEntry::
00092 ~PGEntry() {
00093 }
00094 
00095 ////////////////////////////////////////////////////////////////////
00096 //     Function: PGEntry::Copy Constructor
00097 //       Access: Protected
00098 //  Description: 
00099 ////////////////////////////////////////////////////////////////////
00100 PGEntry::
00101 PGEntry(const PGEntry &copy) :
00102   PGItem(copy),
00103   _text(copy._text),
00104   _obscure_text(copy._obscure_text),
00105   _cursor_position(copy._cursor_position),
00106   _cursor_visible(copy._cursor_visible),
00107   _candidate_highlight_start(copy._candidate_highlight_start),
00108   _candidate_highlight_end(copy._candidate_highlight_end),
00109   _candidate_cursor_pos(copy._candidate_cursor_pos),
00110   _max_chars(copy._max_chars),
00111   _max_width(copy._max_width),
00112   _num_lines(copy._num_lines),
00113   _accept_enabled(copy._accept_enabled),
00114   _candidate_active(copy._candidate_active),
00115   _candidate_inactive(copy._candidate_inactive),
00116   _text_defs(copy._text_defs),
00117   _blink_start(copy._blink_start),
00118   _blink_rate(copy._blink_rate),
00119   _cursor_keys_active(copy._cursor_keys_active),
00120   _obscure_mode(copy._obscure_mode),
00121   _overflow_mode(copy._overflow_mode)
00122 {
00123   _cursor_stale = true;
00124   _last_text_def = (TextNode *)NULL;
00125   _text_geom_stale = true;
00126   _text_geom_flattened = true;
00127 
00128   _text_render_root = NodePath("text_root");
00129   _cursor_scale = _text_render_root.attach_new_node("cursor_scale");
00130   _cursor_scale.set_transform(copy._cursor_scale.get_transform());
00131   _cursor_def = copy._cursor_def.copy_to(_cursor_scale);
00132 }
00133 
00134 ////////////////////////////////////////////////////////////////////
00135 //     Function: PGEntry::make_copy
00136 //       Access: Public, Virtual
00137 //  Description: Returns a newly-allocated Node that is a shallow copy
00138 //               of this one.  It will be a different Node pointer,
00139 //               but its internal data may or may not be shared with
00140 //               that of the original Node.
00141 ////////////////////////////////////////////////////////////////////
00142 PandaNode *PGEntry::
00143 make_copy() const {
00144   LightReMutexHolder holder(_lock);
00145   return new PGEntry(*this);
00146 }
00147 
00148 ////////////////////////////////////////////////////////////////////
00149 //     Function: PGEntry::xform
00150 //       Access: Public, Virtual
00151 //  Description: Transforms the contents of this node by the indicated
00152 //               matrix, if it means anything to do so.  For most
00153 //               kinds of nodes, this does nothing.
00154 ////////////////////////////////////////////////////////////////////
00155 void PGEntry::
00156 xform(const LMatrix4f &mat) {
00157   LightReMutexHolder holder(_lock);
00158   PGItem::xform(mat);
00159   _text_render_root.set_mat(_text_render_root.get_mat() * mat);
00160 }
00161 
00162 ////////////////////////////////////////////////////////////////////
00163 //     Function: PGEntry::cull_callback
00164 //       Access: Protected, Virtual
00165 //  Description: This function will be called during the cull
00166 //               traversal to perform any additional operations that
00167 //               should be performed at cull time.  This may include
00168 //               additional manipulation of render state or additional
00169 //               visible/invisible decisions, or any other arbitrary
00170 //               operation.
00171 //
00172 //               Note that this function will *not* be called unless
00173 //               set_cull_callback() is called in the constructor of
00174 //               the derived class.  It is necessary to call
00175 //               set_cull_callback() to indicated that we require
00176 //               cull_callback() to be called.
00177 //
00178 //               By the time this function is called, the node has
00179 //               already passed the bounding-volume test for the
00180 //               viewing frustum, and the node's transform and state
00181 //               have already been applied to the indicated
00182 //               CullTraverserData object.
00183 //
00184 //               The return value is true if this node should be
00185 //               visible, or false if it should be culled.
00186 ////////////////////////////////////////////////////////////////////
00187 bool PGEntry::
00188 cull_callback(CullTraverser *trav, CullTraverserData &data) {
00189   LightReMutexHolder holder(_lock);
00190   PGItem::cull_callback(trav, data);
00191   update_text();
00192   update_cursor();
00193 
00194   // Now render the text.
00195   CullTraverserData next_data(data, _text_render_root.node());
00196   trav->traverse(next_data);
00197 
00198   // Now continue to render everything else below this node.
00199   return true;
00200 }
00201 
00202 ////////////////////////////////////////////////////////////////////
00203 //     Function: PGEntry::press
00204 //       Access: Public, Virtual
00205 //  Description: This is a callback hook function, called whenever a
00206 //               mouse or keyboard entry is depressed while the mouse
00207 //               is within the region.
00208 ////////////////////////////////////////////////////////////////////
00209 void PGEntry::
00210 press(const MouseWatcherParameter &param, bool background) {
00211   LightReMutexHolder holder(_lock);
00212   if (get_active()) {
00213     if (param.has_button()) {
00214       // Make sure _text is initialized properly.
00215       update_text();
00216 
00217       bool overflow_mode = get_overflow_mode() && _num_lines == 1;
00218 
00219       ButtonHandle button = param.get_button();
00220       
00221       if (button == MouseButton::one() ||
00222           button == MouseButton::two() ||
00223           button == MouseButton::three() ||
00224           button == MouseButton::four() ||
00225           button == MouseButton::five()) {
00226         // Mouse button; set focus.
00227         set_focus(true);
00228         
00229       } else if ((!background && get_focus()) || 
00230                  (background && get_background_focus())) {
00231         // Keyboard button.
00232         if (!_candidate_wtext.empty()) {
00233           _candidate_wtext = wstring();
00234           _text_geom_stale = true;
00235         }
00236 
00237         _cursor_position = min(_cursor_position, _text.get_num_characters());
00238         _blink_start = ClockObject::get_global_clock()->get_frame_time();
00239         if (button == KeyboardButton::enter()) {
00240           // Enter.  Accept the entry.
00241           if (_accept_enabled) {
00242             accept(param);
00243           }
00244           else {
00245             accept_failed(param);
00246           }
00247           
00248         } else if (button == KeyboardButton::backspace()) {
00249           // Backspace.  Remove the character to the left of the cursor.
00250           if (_cursor_position > 0) {
00251             _text.set_wsubstr(wstring(), _cursor_position - 1, 1);
00252             _cursor_position--;
00253             _cursor_stale = true;
00254             _text_geom_stale = true;
00255             erase(param);
00256           }
00257           
00258         } else if (button == KeyboardButton::del()) {
00259           // Delete.  Remove the character to the right of the cursor.
00260           if (_cursor_position < _text.get_num_characters()) {
00261             _text.set_wsubstr(wstring(), _cursor_position, 1);
00262             _text_geom_stale = true;
00263             erase(param);
00264           }
00265           
00266         } else if (button == KeyboardButton::left()) {
00267           if (_cursor_keys_active) {
00268             // Left arrow.  Move the cursor position to the left.
00269             --_cursor_position;
00270             if (_cursor_position < 0) {
00271               _cursor_position = 0;
00272               overflow(param);
00273             } else {
00274               type(param);    
00275             }
00276             _cursor_stale = true;
00277             if (overflow_mode){
00278                 _text_geom_stale = true;
00279             }
00280           }
00281           
00282         } else if (button == KeyboardButton::right()) {
00283           if (_cursor_keys_active) {
00284             // Right arrow.  Move the cursor position to the right.
00285             ++_cursor_position;
00286             if (_cursor_position > _text.get_num_characters()) {
00287               _cursor_position = _text.get_num_characters();
00288               overflow(param);
00289             } else {
00290               type(param);
00291             }
00292             _cursor_stale = true;
00293             if (overflow_mode){
00294                 _text_geom_stale = true;
00295             }
00296           }
00297           
00298         } else if (button == KeyboardButton::home()) {
00299           if (_cursor_keys_active) {
00300             // Home.  Move the cursor position to the beginning.
00301             _cursor_position = 0;
00302             _cursor_stale = true;
00303             if (overflow_mode){
00304                 _text_geom_stale = true;
00305             }
00306             type(param);
00307           }
00308           
00309         } else if (button == KeyboardButton::end()) {
00310           if (_cursor_keys_active) {
00311             // End.  Move the cursor position to the end.
00312             _cursor_position = _text.get_num_characters();
00313             _cursor_stale = true;
00314             if (overflow_mode){
00315                 _text_geom_stale = true;
00316             }
00317             type(param);
00318           }
00319         }          
00320       }
00321     }
00322   }
00323   PGItem::press(param, background);
00324 }
00325 
00326 ////////////////////////////////////////////////////////////////////
00327 //     Function: PGEntry::keystroke
00328 //       Access: Public, Virtual
00329 //  Description: This is a callback hook function, called whenever
00330 //               the user types a key.
00331 ////////////////////////////////////////////////////////////////////
00332 void PGEntry::
00333 keystroke(const MouseWatcherParameter &param, bool background) {
00334   LightReMutexHolder holder(_lock);
00335   if (get_active()) {
00336     if (param.has_keycode()) {
00337       // Make sure _text is initialized properly.
00338       update_text();
00339 
00340       int keycode = param.get_keycode();
00341 
00342       if (!isascii(keycode) || isprint(keycode)) {
00343         // A normal visible character.  Add a new character to the
00344         // text entry, if there's room.
00345         if (!_candidate_wtext.empty()) {
00346           _candidate_wtext = wstring();
00347           _text_geom_stale = true;
00348         }
00349         wstring new_char(1, (wchar_t)keycode);
00350 
00351         if (get_max_chars() > 0 && _text.get_num_characters() >= get_max_chars()) {
00352           // In max_chars mode, we consider it an overflow after we
00353           // have exceeded a fixed number of characters, irrespective
00354           // of the formatted width of those characters.
00355           overflow(param);
00356 
00357         } else {
00358           _cursor_position = min(_cursor_position, _text.get_num_characters());
00359           bool too_long = !_text.set_wsubstr(new_char, _cursor_position, 0);
00360           bool overflow_mode = get_overflow_mode() && _num_lines == 1;
00361           if(overflow_mode){
00362             too_long = false;
00363           }
00364           if (_obscure_mode) {
00365             too_long = !_obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
00366           } else {
00367             if (!too_long && (_text.get_num_rows() == _num_lines) && !overflow_mode) {
00368               // If we've filled up all of the available lines, we
00369               // must also ensure that the last line is not too long
00370               // (it might be, because of additional whitespace on
00371               // the end).
00372               int r = _num_lines - 1;
00373               int c = _text.get_num_cols(r);
00374               float last_line_width = 
00375                 _text.get_xpos(r, c) - _text.get_xpos(r, 0);
00376               too_long = (last_line_width > _max_width);
00377             }
00378             
00379             if (!too_long && keycode == ' ' && !overflow_mode) {
00380               // Even if we haven't filled up all of the available
00381               // lines, we should reject a space that's typed at the
00382               // end of the current line if it would make that line
00383               // exceed the maximum width, just so we don't allow an
00384               // infinite number of spaces to accumulate.
00385               int r, c;
00386               _text.calc_r_c(r, c, _cursor_position);
00387               if (_text.get_num_cols(r) == c + 1) {
00388                 // The user is typing at the end of the line.  But we
00389                 // must allow at least one space at the end of the
00390                 // line, so we only make any of the following checks
00391                 // if there are already multiple spaces at the end of
00392                 // the line.
00393                 if (c - 1 >= 0 && _text.get_character(r, c - 1) == ' ') {
00394                   // Ok, the user is putting multiple spaces on the
00395                   // end of a line; we need to make sure the line does
00396                   // not grow too wide.  Measure the line's width.
00397                   float current_line_width = 
00398                     _text.get_xpos(r, c + 1) - _text.get_xpos(r, 0);
00399                   if (current_line_width > _max_width) {
00400                     // We have to reject the space, but we don't treat
00401                     // it as an overflow condition.  
00402                     _text.set_wsubstr(wstring(), _cursor_position, 1);
00403                     // If the user is typing over existing space
00404                     // characters, we act as if the right-arrow key
00405                     // were pressed instead, and advance the cursor to
00406                     // the next position.  Otherwise, we just quietly
00407                     // eat the space character.
00408                     if (_cursor_position < _text.get_num_characters() && 
00409                         _text.get_character(_cursor_position) == ' ') {
00410                       _cursor_position++;
00411                       _cursor_stale = true;
00412                     }
00413                     return;
00414                   }
00415                 }
00416               }
00417             }
00418           }
00419           
00420           if (too_long) {
00421             _text.set_wsubstr(wstring(), _cursor_position, 1);
00422             overflow(param);
00423               
00424           } else {
00425             _cursor_position += new_char.length();
00426             _cursor_stale = true;
00427             _text_geom_stale = true;
00428             type(param);
00429           }
00430         }
00431       }
00432     }
00433   }
00434   PGItem::keystroke(param, background);
00435 }
00436 
00437 ////////////////////////////////////////////////////////////////////
00438 //     Function: PGEntry::candidate
00439 //       Access: Public, Virtual
00440 //  Description: This is a callback hook function, called whenever
00441 //               the user selects an item from the IME menu.
00442 ////////////////////////////////////////////////////////////////////
00443 void PGEntry::
00444 candidate(const MouseWatcherParameter &param, bool background) {
00445   LightReMutexHolder holder(_lock);
00446   if (get_active()) {
00447     if (param.has_candidate()) {
00448       // Save the candidate string so it can be displayed.
00449       _candidate_wtext = param.get_candidate_string();
00450       _candidate_highlight_start = param.get_highlight_start();
00451       _candidate_highlight_end = param.get_highlight_end();
00452       _candidate_cursor_pos = param.get_cursor_pos();
00453       _text_geom_stale = true;
00454       if (!_candidate_wtext.empty()) {
00455         type(param);
00456       }
00457     }
00458   }
00459   PGItem::candidate(param, background);
00460 }
00461 
00462 ////////////////////////////////////////////////////////////////////
00463 //     Function: PGEntry::accept
00464 //       Access: Public, Virtual
00465 //  Description: This is a callback hook function, called whenever the
00466 //               entry is accepted by the user pressing Enter normally.
00467 ////////////////////////////////////////////////////////////////////
00468 void PGEntry::
00469 accept(const MouseWatcherParameter &param) {
00470   LightReMutexHolder holder(_lock);
00471   PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00472   string event = get_accept_event(param.get_button());
00473   play_sound(event);
00474   throw_event(event, EventParameter(ep));
00475   set_focus(false);
00476 }
00477 
00478 ////////////////////////////////////////////////////////////////////
00479 //     Function: PGEntry::accept_failed
00480 //       Access: Public, Virtual
00481 //  Description: This is a callback hook function, called whenever the
00482 //               user presses Enter but we can't accept the input.
00483 ////////////////////////////////////////////////////////////////////
00484 void PGEntry::
00485 accept_failed(const MouseWatcherParameter &param) {
00486   LightReMutexHolder holder(_lock);
00487   PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00488   string event = get_accept_failed_event(param.get_button());
00489   play_sound(event);
00490   throw_event(event, EventParameter(ep));
00491   //set_focus(false);
00492 }
00493 
00494 ////////////////////////////////////////////////////////////////////
00495 //     Function: PGEntry::overflow
00496 //       Access: Public, Virtual
00497 //  Description: This is a callback hook function, called whenever the
00498 //               entry is overflowed because the user attempts to type
00499 //               too many characters, exceeding either set_max_chars()
00500 //               or set_max_width().
00501 ////////////////////////////////////////////////////////////////////
00502 void PGEntry::
00503 overflow(const MouseWatcherParameter &param) {
00504   LightReMutexHolder holder(_lock);
00505   PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00506   string event = get_overflow_event();
00507   play_sound(event);
00508   throw_event(event, EventParameter(ep));
00509 }
00510 
00511 ////////////////////////////////////////////////////////////////////
00512 //     Function: PGEntry::type
00513 //       Access: Public, Virtual
00514 //  Description: This is a callback hook function, called whenever the
00515 //               user extends the text by typing.
00516 ////////////////////////////////////////////////////////////////////
00517 void PGEntry::
00518 type(const MouseWatcherParameter &param) {
00519   LightReMutexHolder holder(_lock);
00520   PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00521   string event = get_type_event();
00522   play_sound(event);
00523   throw_event(event, EventParameter(ep));
00524 }
00525 
00526 ////////////////////////////////////////////////////////////////////
00527 //     Function: PGEntry::erase
00528 //       Access: Public, Virtual
00529 //  Description: This is a callback hook function, called whenever the
00530 //               user erase characters in the text.
00531 ////////////////////////////////////////////////////////////////////
00532 void PGEntry::
00533 erase(const MouseWatcherParameter &param) {
00534   LightReMutexHolder holder(_lock);
00535   PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00536   string event = get_erase_event();
00537   play_sound(event);
00538   throw_event(event, EventParameter(ep));
00539 }
00540 
00541 ////////////////////////////////////////////////////////////////////
00542 //     Function: PGEntry::cursormove
00543 //       Access: Public, Virtual
00544 //  Description: This is a callback hook function, called whenever the
00545 //               cursor moves.
00546 ////////////////////////////////////////////////////////////////////
00547 void PGEntry::
00548 cursormove() {
00549   LightReMutexHolder holder(_lock);
00550   string event = get_cursormove_event();
00551   throw_event(event, EventParameter(_cursor_def.get_x()), EventParameter(_cursor_def.get_y()));
00552 }
00553 
00554 ////////////////////////////////////////////////////////////////////
00555 //     Function: PGEntry::setup
00556 //       Access: Published
00557 //  Description: Sets up the entry for normal use.  The width is the
00558 //               maximum width of characters that will be typed, and
00559 //               num_lines is the integer number of lines of text of
00560 //               the entry.  Both of these together determine the size
00561 //               of the entry, based on the TextNode in effect.
00562 ////////////////////////////////////////////////////////////////////
00563 void PGEntry::
00564 setup(float width, int num_lines) {
00565   LightReMutexHolder holder(_lock);
00566   setup_minimal(width, num_lines);
00567 
00568   TextNode *text_node = get_text_def(S_focus);
00569   float line_height = text_node->get_line_height();
00570 
00571   // Determine the four corners of the frame.
00572   LPoint3f ll(0.0f, 0.0f, -0.3f * line_height - (line_height * (num_lines - 1)));
00573   LPoint3f ur(width, 0.0f, line_height);
00574   LPoint3f lr(ur[0], 0.0f, ll[2]);
00575   LPoint3f ul(ll[0], 0.0f, ur[2]);
00576 
00577   // Transform each corner by the TextNode's transform.
00578   LMatrix4f mat = text_node->get_transform();
00579   ll = ll * mat;
00580   ur = ur * mat;
00581   lr = lr * mat;
00582   ul = ul * mat;
00583 
00584   // And get the new minmax to define the frame.  We do all this work
00585   // instead of just using the lower-left and upper-right corners,
00586   // just in case the text was rotated.
00587   LVecBase4f frame;
00588   frame[0] = min(min(ll[0], ur[0]), min(lr[0], ul[0]));
00589   frame[1] = max(max(ll[0], ur[0]), max(lr[0], ul[0]));
00590   frame[2] = min(min(ll[2], ur[2]), min(lr[2], ul[2]));
00591   frame[3] = max(max(ll[2], ur[2]), max(lr[2], ul[2]));
00592 
00593   switch (text_node->get_align()) {
00594   case TextNode::A_left:
00595     // The default case.
00596     break;
00597 
00598   case TextNode::A_center:
00599     frame[0] = -width / 2.0;
00600     frame[1] = width / 2.0;
00601     break;
00602 
00603   case TextNode::A_right:
00604     frame[0] = -width;
00605     frame[1] = 0.0f;
00606     break;
00607   }
00608 
00609   set_frame(frame[0] - 0.15f, frame[1] + 0.15f, frame[2], frame[3]);
00610 
00611   PGFrameStyle style;
00612   style.set_width(0.1f, 0.1f);
00613   style.set_type(PGFrameStyle::T_bevel_in);
00614   style.set_color(0.8f, 0.8f, 0.8f, 1.0f);
00615 
00616   set_frame_style(S_no_focus, style);
00617 
00618   style.set_color(0.9f, 0.9f, 0.9f, 1.0f);
00619   set_frame_style(S_focus, style);
00620 
00621   style.set_color(0.6f, 0.6f, 0.6f, 1.0f);
00622   set_frame_style(S_inactive, style);
00623 }
00624 
00625 ////////////////////////////////////////////////////////////////////
00626 //     Function: PGEntry::setup_minimal
00627 //       Access: Published
00628 //  Description: Sets up the entry without creating any frame or other
00629 //               decoration.
00630 ////////////////////////////////////////////////////////////////////
00631 void PGEntry::
00632 setup_minimal(float width, int num_lines) {
00633   LightReMutexHolder holder(_lock);
00634   set_text(string());
00635   _cursor_position = 0;
00636   set_max_chars(0);
00637   set_max_width(width);
00638   set_num_lines(num_lines);
00639   update_text();
00640 
00641   _accept_enabled = true;
00642 
00643   TextNode *text_node = get_text_def(S_focus);
00644   float line_height = text_node->get_line_height();
00645 
00646   // Set up a default cursor: a vertical bar.
00647   clear_cursor_def();
00648 
00649   LineSegs ls;
00650   ls.set_color(text_node->get_text_color());
00651   ls.move_to(0.0f, 0.0f, -0.15f * line_height);
00652   ls.draw_to(0.0f, 0.0f, 0.70f * line_height);
00653   get_cursor_def().attach_new_node(ls.create());
00654   
00655   /*
00656   // An underscore cursor would work too.
00657   text_node->set_text("_");
00658   get_cursor_def().attach_new_node(text_node->generate());
00659   */
00660 }
00661 
00662 ////////////////////////////////////////////////////////////////////
00663 //     Function: PGEntry::set_text_def
00664 //       Access: Published
00665 //  Description: Changes the TextNode that will be used to render the
00666 //               text within the entry when the entry is in the
00667 //               indicated state.  The default if nothing is specified
00668 //               is the same TextNode returned by
00669 //               PGItem::get_text_node().
00670 ////////////////////////////////////////////////////////////////////
00671 void PGEntry::
00672 set_text_def(int state, TextNode *node) {
00673   LightReMutexHolder holder(_lock);
00674   nassertv(state >= 0 && state < 1000);  // Sanity check.
00675   if (node == (TextNode *)NULL && state >= (int)_text_defs.size()) {
00676     // If we're setting it to NULL, we don't need to slot a new one.
00677     return;
00678   }
00679   slot_text_def(state);
00680 
00681   _text_defs[state] = node;
00682 }
00683 
00684 ////////////////////////////////////////////////////////////////////
00685 //     Function: PGEntry::get_text_def
00686 //       Access: Published
00687 //  Description: Returns the TextNode that will be used to render the
00688 //               text within the entry when the entry is in the
00689 //               indicated state.  See set_text_def().
00690 ////////////////////////////////////////////////////////////////////
00691 TextNode *PGEntry:: 
00692 get_text_def(int state) const {
00693   LightReMutexHolder holder(_lock);
00694   if (state < 0 || state >= (int)_text_defs.size()) {
00695     // If we don't have a definition, use the global one.
00696     return get_text_node();
00697   }
00698   if (_text_defs[state] == (TextNode *)NULL) {
00699     return get_text_node();
00700   }
00701   return _text_defs[state];
00702 }
00703 
00704 ////////////////////////////////////////////////////////////////////
00705 //     Function: PGEntry::set_active
00706 //       Access: Published, Virtual
00707 //  Description: Toggles the active/inactive state of the entry.  In
00708 //               the case of a PGEntry, this also changes its visual
00709 //               appearance.
00710 ////////////////////////////////////////////////////////////////////
00711 void PGEntry:: 
00712 set_active(bool active) {
00713   LightReMutexHolder holder(_lock);
00714   PGItem::set_active(active);
00715   update_state();
00716 }
00717 
00718 ////////////////////////////////////////////////////////////////////
00719 //     Function: PGEntry::set_focus
00720 //       Access: Published, Virtual
00721 //  Description: Toggles the focus state of the entry.  In the case of
00722 //               a PGEntry, this also changes its visual appearance.
00723 ////////////////////////////////////////////////////////////////////
00724 void PGEntry:: 
00725 set_focus(bool focus) {
00726   LightReMutexHolder holder(_lock);
00727   PGItem::set_focus(focus);
00728   _blink_start = ClockObject::get_global_clock()->get_frame_time();
00729   update_state();
00730 }
00731 
00732 ////////////////////////////////////////////////////////////////////
00733 //     Function: PGEntry::is_wtext
00734 //       Access: Published
00735 //  Description: Returns true if any of the characters in the string
00736 //               returned by get_wtext() are out of the range of an
00737 //               ASCII character (and, therefore, get_wtext() should
00738 //               be called in preference to get_text()).
00739 ////////////////////////////////////////////////////////////////////
00740 bool PGEntry:: 
00741 is_wtext() const {
00742   LightReMutexHolder holder(_lock);
00743   for (int i = 0; i < _text.get_num_characters(); ++i) {
00744     wchar_t ch = _text.get_character(i);
00745     if ((ch & ~0x7f) != 0) {
00746       return true;
00747     }
00748   }
00749 
00750   return false;
00751 }
00752 
00753 ////////////////////////////////////////////////////////////////////
00754 //     Function: PGEntry::slot_text_def
00755 //       Access: Private
00756 //  Description: Ensures there is a slot in the array for the given
00757 //               text definition.
00758 ////////////////////////////////////////////////////////////////////
00759 void PGEntry::
00760 slot_text_def(int state) {
00761   while (state >= (int)_text_defs.size()) {
00762     _text_defs.push_back((TextNode *)NULL);
00763   }
00764 }
00765 
00766 ////////////////////////////////////////////////////////////////////
00767 //     Function: PGEntry::update_text
00768 //       Access: Private
00769 //  Description: Causes the PGEntry to recompute its text, if
00770 //               necessary.
00771 ////////////////////////////////////////////////////////////////////
00772 void PGEntry:: 
00773 update_text() {
00774   TextNode *node = get_text_def(get_state());
00775   nassertv(node != (TextNode *)NULL);
00776     
00777   if (_text_geom_stale || node != _last_text_def) {
00778     TextProperties props = *node;
00779     props.set_wordwrap(_max_width);
00780     props.set_preserve_trailing_whitespace(true);
00781     _text.set_properties(props);
00782     _text.set_max_rows(_num_lines);
00783 
00784     if (node != _last_text_def) {
00785       // Make sure the default properties are applied to all the
00786       // characters in the text.
00787       _text.set_wtext(_text.get_wtext());
00788       _last_text_def = node;
00789     }
00790 
00791     _text.set_multiline_mode (!(get_overflow_mode() && _num_lines == 1));
00792 
00793     PT(PandaNode) assembled;
00794     if (_obscure_mode) {
00795       _obscure_text.set_properties(props);
00796       _obscure_text.set_max_rows(_num_lines);
00797       _obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
00798       assembled = _obscure_text.assemble_text();
00799 
00800     } else if (_candidate_wtext.empty()) {
00801       // If we're not trying to display a candidate string, it's easy:
00802       // just display the current text contents.
00803       assembled = _text.assemble_text();
00804 
00805     } else {
00806       TextPropertiesManager *tp_mgr = TextPropertiesManager::get_global_ptr();
00807       TextProperties inactive = tp_mgr->get_properties(_candidate_inactive);
00808       TextProperties active = tp_mgr->get_properties(_candidate_active);
00809 
00810       // Insert the complex sequence of characters required to show
00811       // the candidate string in a different color.  This gets
00812       // inserted at the current cursor position.
00813       wstring cseq;
00814       cseq += wstring(1, (wchar_t)text_push_properties_key);
00815       cseq += node->decode_text(_candidate_inactive);
00816       cseq += wstring(1, (wchar_t)text_push_properties_key);
00817       cseq += _candidate_wtext.substr(0, _candidate_highlight_start);
00818       cseq += wstring(1, (wchar_t)text_push_properties_key);
00819       cseq += node->decode_text(_candidate_active);
00820       cseq += wstring(1, (wchar_t)text_push_properties_key);
00821       cseq += _candidate_wtext.substr(_candidate_highlight_start,
00822                                        _candidate_highlight_end - _candidate_highlight_start);
00823       cseq += wstring(1, (wchar_t)text_pop_properties_key);
00824       cseq += _candidate_wtext.substr(_candidate_highlight_end);
00825       cseq += wstring(1, (wchar_t)text_pop_properties_key);
00826 
00827       // Create a special TextAssembler to insert the candidate string.
00828       TextAssembler ctext(_text);
00829       ctext.set_wsubstr(cseq, _cursor_position, 0);
00830       assembled = ctext.assemble_text();
00831     }
00832 
00833     if (!_current_text.is_empty()) {
00834       _current_text.remove_node();
00835     }
00836 
00837     _current_text = 
00838       _text_render_root.attach_new_node(assembled);
00839 
00840     _current_text.set_mat(node->get_transform());
00841 
00842     if (get_overflow_mode() && _num_lines == 1){
00843       // We determine the minimum required padding:
00844       float cursor_graphic_pos = _text.get_xpos(0, _cursor_position);
00845       float min_padding = (cursor_graphic_pos - _max_width);
00846 
00847       // If the current padding would produce a caret outside the text entry,
00848       // we relocate it.
00849       // Here we also have to make a jump towards the center when the caret
00850       // is going outside the visual area and there's enough text ahead for
00851       // increased usability.
00852       //
00853       // The amount that the caret is moved for hinting depends on the OS,
00854       // and the specific behavior under certain circunstances in different
00855       // Operating Systems is very complicated (the implementation would need
00856       // to "remember" the original typing starting point). For the moment we are
00857       // gonna use an unconditional 50% jump, this behavior is found in some
00858       // Mac dialogs, and it's the easiest to implement by far, while
00859       // providing proven usability.
00860       // PROS: Reduces the amount of scrolling while both writing and navigating
00861       // with arrow keys, which is desirable.
00862       // CONS: The user needs to remember that he/she has exceeded the boundaries,
00863       // but this happens with all implementations to some degree.
00864 
00865       if (_current_padding < min_padding || _current_padding > cursor_graphic_pos){
00866         _current_padding = min_padding + (cursor_graphic_pos - min_padding) * 0.5;
00867       }
00868 
00869       if (_current_padding < 0){ // Caret virtual position doesn't exceed boundaries
00870         _current_padding = 0;
00871       }
00872 
00873       _current_text.set_x(_current_text.get_x() - _current_padding);
00874       _current_text.set_scissor(NodePath(this),
00875         LPoint3f(0, 0, -0.5), LPoint3f(_max_width, 0, -0.5),
00876         LPoint3f(_max_width, 0, 1.5), LPoint3f(0, 0, 1.5));
00877     }
00878 
00879     _text_geom_stale = false;
00880     _text_geom_flattened = false;
00881     _cursor_stale = true;
00882   }
00883 
00884   // We'll flatten the text geometry only if we don't have focus.
00885   // Otherwise, we assume the user may be changing it frequently.
00886   if (!get_focus() && !_text_geom_flattened) {
00887     _current_text.flatten_strong();
00888     _text_geom_flattened = true;
00889   }
00890 }
00891 
00892 ////////////////////////////////////////////////////////////////////
00893 //     Function: PGEntry::update_cursor
00894 //       Access: Private
00895 //  Description: Moves the cursor to its correct position.
00896 ////////////////////////////////////////////////////////////////////
00897 void PGEntry:: 
00898 update_cursor() {
00899   TextNode *node = get_text_def(get_state());
00900   nassertv(node != (TextNode *)NULL);
00901   _cursor_scale.set_mat(node->get_transform());
00902   _cursor_scale.set_color(node->get_text_color());
00903 
00904   if (_cursor_stale || node != _last_text_def) {
00905     update_text();
00906 
00907     _cursor_position = min(_cursor_position, _text.get_num_characters());
00908 
00909     // Determine the row and column of the cursor.
00910     int row, column;
00911     float xpos, ypos;
00912     if (_obscure_mode) {
00913       _obscure_text.calc_r_c(row, column, _cursor_position);
00914       xpos = _obscure_text.get_xpos(row, column);
00915       ypos = _obscure_text.get_ypos(row, column);
00916     } else {
00917       _text.calc_r_c(row, column, _cursor_position);
00918       xpos = _text.get_xpos(row, column);
00919       ypos = _text.get_ypos(row, column);
00920     }
00921 
00922     _cursor_def.set_pos(xpos - _current_padding, 0.0f, ypos);
00923     _cursor_stale = false;
00924     cursormove();
00925     
00926   }
00927 
00928   // Should the cursor be visible?
00929   if (!get_focus() || !_candidate_wtext.empty()) {
00930     show_hide_cursor(false);
00931   } else {
00932     double elapsed_time = 
00933       ClockObject::get_global_clock()->get_frame_time() - _blink_start;
00934     int cycle = (int)(elapsed_time * _blink_rate * 2.0f);
00935     bool visible = ((cycle & 1) == 0);
00936     show_hide_cursor(visible);
00937   }
00938 }
00939 
00940 ////////////////////////////////////////////////////////////////////
00941 //     Function: PGEntry::show_hide_cursor
00942 //       Access: Private
00943 //  Description: Makes the cursor visible or invisible, e.g. during a
00944 //               blink cycle.
00945 ////////////////////////////////////////////////////////////////////
00946 void PGEntry:: 
00947 show_hide_cursor(bool visible) {
00948   if (visible != _cursor_visible) {
00949     if (visible) {
00950       _cursor_scale.show();
00951     } else {
00952       _cursor_scale.hide();
00953     }
00954     _cursor_visible = visible;
00955   }
00956 }
00957 
00958 ////////////////////////////////////////////////////////////////////
00959 //     Function: PGEntry::update_state
00960 //       Access: Private
00961 //  Description: Determines what the correct state for the PGEntry
00962 //               should be.
00963 ////////////////////////////////////////////////////////////////////
00964 void PGEntry:: 
00965 update_state() {
00966   if (get_active()) {
00967     if (get_focus()) {
00968       set_state(S_focus);
00969     } else {
00970       set_state(S_no_focus);
00971     }
00972   } else {
00973     set_state(S_inactive);
00974   }
00975 }
 All Classes Functions Variables Enumerations