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