Panda3D
|
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 ©) : 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 ¶m, 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 ¶m, 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 ¶m, 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 ¶m) { 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 ¶m) { 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 ¶m) { 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 ¶m) { 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 ¶m) { 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 }