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