00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "pgEntry.h"
00020 #include "pgMouseWatcherParameter.h"
00021
00022 #include "cullTraverser.h"
00023 #include "cullTraverserData.h"
00024 #include "throw_event.h"
00025 #include "transformState.h"
00026 #include "mouseWatcherParameter.h"
00027 #include "keyboardButton.h"
00028 #include "mouseButton.h"
00029 #include "lineSegs.h"
00030
00031 #include <math.h>
00032
00033 TypeHandle PGEntry::_type_handle;
00034
00035
00036
00037
00038
00039
00040 PGEntry::
00041 PGEntry(const string &name) :
00042 PGItem(name)
00043 {
00044 _cursor_position = 0;
00045 _cursor_stale = true;
00046 _max_chars = 0;
00047 _max_width = 0.0f;
00048 _num_lines = 1;
00049 _last_text_def = (TextNode *)NULL;
00050 _text_geom_stale = true;
00051 _blink_start = 0.0f;
00052 _blink_rate = 1.0f;
00053
00054 _text_render_root = NodePath("text_root");
00055 _cursor_def = _text_render_root.attach_new_node("cursor");
00056 _cursor_visible = true;
00057
00058 _cursor_keys_active = true;
00059 _obscure_mode = false;
00060
00061 set_active(true);
00062 update_state();
00063 }
00064
00065
00066
00067
00068
00069
00070 PGEntry::
00071 ~PGEntry() {
00072 }
00073
00074
00075
00076
00077
00078
00079 PGEntry::
00080 PGEntry(const PGEntry ©) :
00081 PGItem(copy),
00082 _wtext(copy._wtext),
00083 _obscured_wtext(copy._obscured_wtext),
00084 _cursor_position(copy._cursor_position),
00085 _max_chars(copy._max_chars),
00086 _max_width(copy._max_width),
00087 _text_defs(copy._text_defs),
00088 _blink_start(copy._blink_start),
00089 _blink_rate(copy._blink_rate),
00090 _cursor_keys_active(copy._cursor_keys_active),
00091 _obscure_mode(copy._obscure_mode)
00092 {
00093 _cursor_stale = true;
00094 _last_text_def = (TextNode *)NULL;
00095 _text_geom_stale = true;
00096 }
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106 PandaNode *PGEntry::
00107 make_copy() const {
00108 return new PGEntry(*this);
00109 }
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120 bool PGEntry::
00121 has_cull_callback() const {
00122 return true;
00123 }
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144 bool PGEntry::
00145 cull_callback(CullTraverser *trav, CullTraverserData &data) {
00146 PGItem::cull_callback(trav, data);
00147 update_text();
00148 update_cursor();
00149
00150
00151 CullTraverserData next_data(data, _text_render_root.node());
00152 trav->traverse(next_data);
00153
00154
00155 return true;
00156 }
00157
00158
00159
00160
00161
00162
00163
00164
00165 void PGEntry::
00166 press(const MouseWatcherParameter ¶m, bool background) {
00167 if (get_active()) {
00168 if (param.has_button()) {
00169 ButtonHandle button = param.get_button();
00170
00171 if (button == MouseButton::one() ||
00172 button == MouseButton::two() ||
00173 button == MouseButton::three()) {
00174
00175 set_focus(true);
00176
00177 } else if ((!background && get_focus()) ||
00178 (background && get_background_focus())) {
00179
00180 _cursor_position = min(_cursor_position, (int)_wtext.length());
00181 _blink_start = ClockObject::get_global_clock()->get_frame_time();
00182 if (button == KeyboardButton::enter()) {
00183
00184 accept(param);
00185
00186 } else if (button == KeyboardButton::backspace()) {
00187
00188 if (_cursor_position > 0) {
00189 if (_obscure_mode && _obscured_wtext.length() == _wtext.length()) {
00190 _obscured_wtext.erase(_obscured_wtext.begin() + _obscured_wtext.length() - 1);
00191 }
00192 _wtext.erase(_wtext.begin() + _cursor_position - 1);
00193 _cursor_position--;
00194 _cursor_stale = true;
00195 _text_geom_stale = true;
00196 erase(param);
00197 }
00198
00199 } else if (button == KeyboardButton::del()) {
00200
00201 if (_cursor_position < (int)_wtext.length()) {
00202 if (_obscure_mode && _obscured_wtext.length() == _wtext.length()) {
00203 _obscured_wtext.erase(_obscured_wtext.begin() + _obscured_wtext.length() - 1);
00204 }
00205 _wtext.erase(_wtext.begin() + _cursor_position);
00206 _text_geom_stale = true;
00207 erase(param);
00208 }
00209
00210 } else if (button == KeyboardButton::left()) {
00211 if (_cursor_keys_active) {
00212
00213 _cursor_position = max(_cursor_position - 1, 0);
00214 _cursor_stale = true;
00215 }
00216
00217 } else if (button == KeyboardButton::right()) {
00218 if (_cursor_keys_active) {
00219
00220 _cursor_position = min(_cursor_position + 1, (int)_wtext.length());
00221 _cursor_stale = true;
00222 }
00223
00224 } else if (button == KeyboardButton::home()) {
00225 if (_cursor_keys_active) {
00226
00227 _cursor_position = 0;
00228 _cursor_stale = true;
00229 }
00230
00231 } else if (button == KeyboardButton::end()) {
00232 if (_cursor_keys_active) {
00233
00234 _cursor_position = _wtext.length();
00235 _cursor_stale = true;
00236 }
00237
00238 } else if (!use_keystrokes && button.has_ascii_equivalent()) {
00239
00240
00241
00242 wchar_t key = button.get_ascii_equivalent();
00243 if (isprint(key)) {
00244
00245
00246
00247 if (get_max_chars() > 0 && (int)_wtext.length() >= get_max_chars()) {
00248 overflow(param);
00249 } else {
00250 wstring new_text =
00251 _wtext.substr(0, _cursor_position) + key +
00252 _wtext.substr(_cursor_position);
00253
00254
00255
00256
00257 wstring measure_text;
00258 if (_obscure_mode) {
00259 measure_text = get_display_wtext() + (wchar_t)'*';
00260 } else {
00261 measure_text = new_text;
00262 }
00263
00264
00265 bool too_long = false;
00266 if (_max_width > 0.0f) {
00267 TextNode *text_node = get_text_def(S_focus);
00268 if (_num_lines <= 1) {
00269
00270
00271 too_long = (text_node->calc_width(measure_text) > _max_width);
00272
00273 } else {
00274
00275
00276
00277 wstring ww_text = text_node->wordwrap_to(measure_text, _max_width, true);
00278 int num_lines = 1;
00279 size_t last_line_start = 0;
00280 for (size_t p = 0;
00281 p < ww_text.length() && !too_long;
00282 ++p) {
00283 if (ww_text[p] == '\n') {
00284 last_line_start = p + 1;
00285 num_lines++;
00286 too_long = (num_lines > _num_lines);
00287 }
00288 }
00289
00290 if (!too_long) {
00291
00292
00293
00294 wstring last_line = ww_text.substr(last_line_start);
00295 float last_line_width = text_node->calc_width(last_line);
00296 if (num_lines == _num_lines) {
00297
00298
00299 too_long = (last_line_width > _max_width);
00300
00301 } else {
00302
00303
00304
00305
00306
00307 if (_wtext.length() >= 1 &&
00308 _wtext[_wtext.length() - 1] == ' ') {
00309 if (last_line_width > _max_width) {
00310
00311
00312
00313 return;
00314 }
00315 }
00316 }
00317 }
00318 }
00319 }
00320
00321 if (too_long) {
00322 overflow(param);
00323
00324 } else {
00325 _wtext = new_text;
00326 if (_obscure_mode) {
00327 _obscured_wtext = measure_text;
00328 }
00329
00330 _cursor_position++;
00331 _cursor_stale = true;
00332 _text_geom_stale = true;
00333 type(param);
00334 }
00335 }
00336 }
00337 }
00338 }
00339 }
00340 }
00341 PGItem::press(param, background);
00342 }
00343
00344
00345
00346
00347
00348
00349
00350 void PGEntry::
00351 keystroke(const MouseWatcherParameter ¶m, bool background) {
00352 if (get_active()) {
00353 if (param.has_keycode()) {
00354 int keycode = param.get_keycode();
00355
00356 if (use_keystrokes) {
00357 if (!isascii(keycode) || isprint(keycode)) {
00358
00359
00360 wstring new_char(1, (wchar_t)keycode);
00361
00362 if (get_max_chars() > 0 && (int)_wtext.length() >= get_max_chars()) {
00363 overflow(param);
00364 } else {
00365 _cursor_position = min(_cursor_position, (int)_wtext.length());
00366 wstring new_text =
00367 _wtext.substr(0, _cursor_position) + new_char +
00368 _wtext.substr(_cursor_position);
00369
00370
00371
00372
00373 wstring measure_text;
00374 if (_obscure_mode) {
00375 measure_text = get_display_wtext() + (wchar_t)'*';
00376 } else {
00377 measure_text = new_text;
00378 }
00379
00380
00381 bool too_long = false;
00382 if (_max_width > 0.0f) {
00383 TextNode *text_node = get_text_def(S_focus);
00384 if (_num_lines <= 1) {
00385
00386
00387 too_long = (text_node->calc_width(measure_text) > _max_width);
00388
00389 } else {
00390
00391
00392
00393 wstring ww_text = text_node->wordwrap_to(measure_text, _max_width, true);
00394 int num_lines = 1;
00395 size_t last_line_start = 0;
00396 for (size_t p = 0;
00397 p < ww_text.length() && !too_long;
00398 ++p) {
00399 if (ww_text[p] == '\n') {
00400 last_line_start = p + 1;
00401 num_lines++;
00402 too_long = (num_lines > _num_lines);
00403 }
00404 }
00405
00406 if (!too_long) {
00407
00408
00409
00410 wstring last_line = ww_text.substr(last_line_start);
00411 float last_line_width = text_node->calc_width(last_line);
00412 if (num_lines == _num_lines) {
00413
00414
00415 too_long = (last_line_width > _max_width);
00416
00417 } else {
00418
00419
00420
00421
00422
00423 if (_wtext.length() >= 1 &&
00424 _wtext[_wtext.length() - 1] == ' ') {
00425 if (last_line_width > _max_width) {
00426
00427
00428
00429 return;
00430 }
00431 }
00432 }
00433 }
00434 }
00435 }
00436
00437 if (too_long) {
00438 overflow(param);
00439
00440 } else {
00441 _wtext = new_text;
00442 if (_obscure_mode) {
00443 _obscured_wtext = measure_text;
00444 }
00445
00446 _cursor_position += new_char.length();
00447 _cursor_stale = true;
00448 _text_geom_stale = true;
00449 type(param);
00450 }
00451 }
00452 }
00453 }
00454 }
00455 }
00456 PGItem::keystroke(param, background);
00457 }
00458
00459
00460
00461
00462
00463
00464
00465 void PGEntry::
00466 accept(const MouseWatcherParameter ¶m) {
00467 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00468 string event = get_accept_event(param.get_button());
00469 play_sound(event);
00470 throw_event(event, EventParameter(ep));
00471 set_focus(false);
00472 }
00473
00474
00475
00476
00477
00478
00479
00480
00481
00482 void PGEntry::
00483 overflow(const MouseWatcherParameter ¶m) {
00484 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00485 string event = get_overflow_event();
00486 play_sound(event);
00487 throw_event(event, EventParameter(ep));
00488 }
00489
00490
00491
00492
00493
00494
00495
00496 void PGEntry::
00497 type(const MouseWatcherParameter ¶m) {
00498 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00499 string event = get_type_event();
00500 play_sound(event);
00501 throw_event(event, EventParameter(ep));
00502 }
00503
00504
00505
00506
00507
00508
00509
00510 void PGEntry::
00511 erase(const MouseWatcherParameter ¶m) {
00512 PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
00513 string event = get_erase_event();
00514 play_sound(event);
00515 throw_event(event, EventParameter(ep));
00516 }
00517
00518
00519
00520
00521
00522
00523
00524
00525
00526
00527 void PGEntry::
00528 setup(float width, int num_lines) {
00529 set_text(string());
00530 _cursor_position = 0;
00531 set_max_chars(0);
00532 set_max_width(width);
00533 set_num_lines(num_lines);
00534
00535 TextNode *text_node = get_text_def(S_focus);
00536 float line_height = text_node->get_line_height();
00537
00538
00539 LPoint3f ll(0.0f, 0.0f, -0.3f * line_height - (line_height * (num_lines - 1)));
00540 LPoint3f ur(width, 0.0f, line_height);
00541 LPoint3f lr(ur[0], 0.0f, ll[2]);
00542 LPoint3f ul(ll[0], 0.0f, ur[2]);
00543
00544
00545 LMatrix4f mat = text_node->get_transform();
00546 ll = ll * mat;
00547 ur = ur * mat;
00548 lr = lr * mat;
00549 ul = ul * mat;
00550
00551
00552
00553
00554 LVecBase4f frame;
00555 frame[0] = min(min(ll[0], ur[0]), min(lr[0], ul[0]));
00556 frame[1] = max(max(ll[0], ur[0]), max(lr[0], ul[0]));
00557 frame[2] = min(min(ll[2], ur[2]), min(lr[2], ul[2]));
00558 frame[3] = max(max(ll[2], ur[2]), max(lr[2], ul[2]));
00559
00560 switch (text_node->get_align()) {
00561 case TextNode::A_left:
00562
00563 break;
00564
00565 case TextNode::A_center:
00566 frame[0] = -width / 2.0;
00567 frame[1] = width / 2.0;
00568 break;
00569
00570 case TextNode::A_right:
00571 frame[0] = -width;
00572 frame[1] = 0.0f;
00573 break;
00574 }
00575
00576 set_frame(frame[0] - 0.15f, frame[1] + 0.15f, frame[2], frame[3]);
00577
00578 PGFrameStyle style;
00579 style.set_width(0.1f, 0.1f);
00580 style.set_type(PGFrameStyle::T_bevel_in);
00581 style.set_color(0.8f, 0.8f, 0.8f, 1.0f);
00582
00583 set_frame_style(S_no_focus, style);
00584
00585 style.set_color(0.9f, 0.9f, 0.9f, 1.0f);
00586 set_frame_style(S_focus, style);
00587
00588 style.set_color(0.6f, 0.6f, 0.6f, 1.0f);
00589 set_frame_style(S_inactive, style);
00590
00591
00592 clear_cursor_def();
00593
00594 LineSegs ls;
00595 ls.set_color(0.0f, 0.0f, 0.0f, 1.0f);
00596 ls.move_to(0.0f, 0.0f, -0.15f * line_height);
00597 ls.draw_to(0.0f, 0.0f, 0.85f * line_height);
00598 get_cursor_def().attach_new_node(ls.create());
00599
00600
00601
00602
00603
00604
00605 }
00606
00607
00608
00609
00610
00611
00612
00613
00614
00615
00616
00617
00618
00619
00620
00621 void PGEntry::
00622 set_text_def(int state, TextNode *node) {
00623 nassertv(state >= 0 && state < 1000);
00624 if (node == (TextNode *)NULL && state >= (int)_text_defs.size()) {
00625
00626 return;
00627 }
00628 slot_text_def(state);
00629
00630 _text_defs[state] = node;
00631 }
00632
00633
00634
00635
00636
00637
00638
00639
00640 TextNode *PGEntry::
00641 get_text_def(int state) const {
00642 if (state < 0 || state >= (int)_text_defs.size()) {
00643
00644 return get_text_node();
00645 }
00646 if (_text_defs[state] == (TextNode *)NULL) {
00647 return get_text_node();
00648 }
00649 return _text_defs[state];
00650 }
00651
00652
00653
00654
00655
00656
00657
00658
00659 void PGEntry::
00660 set_active(bool active) {
00661 PGItem::set_active(active);
00662 update_state();
00663 }
00664
00665
00666
00667
00668
00669
00670
00671 void PGEntry::
00672 set_focus(bool focus) {
00673 PGItem::set_focus(focus);
00674 _blink_start = ClockObject::get_global_clock()->get_frame_time();
00675 update_state();
00676 }
00677
00678
00679
00680
00681
00682
00683
00684
00685 const wstring &PGEntry::
00686 get_display_wtext() {
00687 if (_obscure_mode) {
00688
00689
00690 if (_obscured_wtext.length() != _wtext.length()) {
00691 _obscured_wtext = wstring();
00692 wstring::const_iterator ti;
00693 for (ti = _wtext.begin(); ti != _wtext.end(); ++ti) {
00694 _obscured_wtext += (wchar_t)'*';
00695 }
00696 }
00697
00698 return _obscured_wtext;
00699
00700 } else {
00701
00702 return _wtext;
00703 }
00704 }
00705
00706
00707
00708
00709
00710
00711
00712 void PGEntry::
00713 slot_text_def(int state) {
00714 while (state >= (int)_text_defs.size()) {
00715 _text_defs.push_back((TextNode *)NULL);
00716 }
00717 }
00718
00719
00720
00721
00722
00723
00724
00725 void PGEntry::
00726 update_text() {
00727 TextNode *node = get_text_def(get_state());
00728 nassertv(node != (TextNode *)NULL);
00729
00730 if (_text_geom_stale || node != _last_text_def) {
00731 const wstring &display_wtext = get_display_wtext();
00732
00733
00734 _last_text_def = node;
00735
00736 if (_max_width > 0.0f && _num_lines > 1) {
00737
00738 wstring ww_text =
00739 _last_text_def->wordwrap_to(display_wtext, _max_width, true);
00740
00741
00742 _ww_lines.clear();
00743 size_t p = 0;
00744 size_t q = ww_text.find((wchar_t)'\n');
00745 while (q != string::npos) {
00746 _ww_lines.push_back(WWLine());
00747 WWLine &line = _ww_lines.back();
00748 line._str = ww_text.substr(p, q - p);
00749
00750
00751 line._left = 0.0f;
00752 if (_last_text_def->get_align() != TextNode::A_left) {
00753 _last_text_def->set_wtext(line._str);
00754 line._left = _last_text_def->get_left();
00755 }
00756
00757 p = q + 1;
00758 q = ww_text.find('\n', p);
00759 }
00760 _ww_lines.push_back(WWLine());
00761 WWLine &line = _ww_lines.back();
00762 line._str = ww_text.substr(p);
00763
00764
00765 line._left = 0.0f;
00766 if (_last_text_def->get_align() != TextNode::A_left) {
00767 _last_text_def->set_wtext(line._str);
00768 line._left = _last_text_def->get_left();
00769 }
00770
00771 _last_text_def->set_wtext(ww_text);
00772
00773 } else {
00774
00775 _ww_lines.clear();
00776 _ww_lines.push_back(WWLine());
00777 WWLine &line = _ww_lines.back();
00778 line._str = display_wtext;
00779
00780 _last_text_def->set_wtext(display_wtext);
00781 line._left = _last_text_def->get_left();
00782 }
00783
00784 if (!_current_text.is_empty()) {
00785 _current_text.remove_node();
00786 }
00787 _current_text =
00788 _text_render_root.attach_new_node(_last_text_def->generate());
00789 _text_geom_stale = false;
00790 _cursor_stale = true;
00791 }
00792 }
00793
00794
00795
00796
00797
00798
00799 void PGEntry::
00800 update_cursor() {
00801 TextNode *node = get_text_def(get_state());
00802 nassertv(node != (TextNode *)NULL);
00803
00804 if (_cursor_stale || node != _last_text_def) {
00805 update_text();
00806
00807 _cursor_position = min(_cursor_position, (int)_wtext.length());
00808
00809
00810 int row = 0;
00811 int column = _cursor_position;
00812 while (row + 1 < (int)_ww_lines.size() &&
00813 column > (int)_ww_lines[row]._str.length()) {
00814 column -= _ww_lines[row]._str.length();
00815 row++;
00816 }
00817
00818 nassertv(row >= 0 && row < (int)_ww_lines.size());
00819 nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length());
00820
00821 float width =
00822 _last_text_def->calc_width(_ww_lines[row]._str.substr(0, column));
00823 float line_height = _last_text_def->get_line_height();
00824
00825 LVecBase3f trans(_ww_lines[row]._left + width, 0.0f, -line_height * row);
00826 _cursor_def.set_pos(trans);
00827
00828 _cursor_stale = false;
00829 }
00830
00831
00832 if (!get_focus()) {
00833 show_hide_cursor(false);
00834 } else {
00835 double elapsed_time =
00836 ClockObject::get_global_clock()->get_frame_time() - _blink_start;
00837 int cycle = (int)(elapsed_time * _blink_rate * 2.0f);
00838 bool visible = ((cycle & 1) == 0);
00839 show_hide_cursor(visible);
00840 }
00841 }
00842
00843
00844
00845
00846
00847
00848
00849 void PGEntry::
00850 show_hide_cursor(bool visible) {
00851 if (visible != _cursor_visible) {
00852 if (visible) {
00853 _cursor_def.show();
00854 } else {
00855 _cursor_def.hide();
00856 }
00857 _cursor_visible = visible;
00858 }
00859 }
00860
00861
00862
00863
00864
00865
00866
00867 void PGEntry::
00868 update_state() {
00869 if (get_active()) {
00870 if (get_focus()) {
00871 set_state(S_focus);
00872 } else {
00873 set_state(S_no_focus);
00874 }
00875 } else {
00876 set_state(S_inactive);
00877 }
00878 }