From 5285800a3687c412319ca36637c71d9f8cc7ff28 Mon Sep 17 00:00:00 2001 From: David Rose Date: Wed, 11 Jul 2001 18:46:17 +0000 Subject: [PATCH] support multi-line PGEntries --- panda/src/pgui/pgEntry.I | 31 ++++++++ panda/src/pgui/pgEntry.cxx | 145 ++++++++++++++++++++++++++++++++----- panda/src/pgui/pgEntry.h | 17 ++++- panda/src/pgui/pgItem.I | 18 +++++ panda/src/pgui/pgItem.h | 2 + 5 files changed, 194 insertions(+), 19 deletions(-) diff --git a/panda/src/pgui/pgEntry.I b/panda/src/pgui/pgEntry.I index 957c8d3296..4b98083576 100644 --- a/panda/src/pgui/pgEntry.I +++ b/panda/src/pgui/pgEntry.I @@ -105,10 +105,16 @@ get_max_chars() const { // number of characters; also set_max_chars(). // // If this is 0, there is no limit. +// +// If _num_lines is more than 1, rather than being a +// fixed width on the whole entry, this becomes instead +// the wordwrap width (and the width limit on the entry +// is essentially _max_width * _num_lines). //////////////////////////////////////////////////////////////////// INLINE void PGEntry:: set_max_width(float max_width) { _max_width = max_width; + _text_geom_stale = true; } //////////////////////////////////////////////////////////////////// @@ -123,6 +129,31 @@ get_max_width() const { return _max_width; } +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::set_num_lines +// Access: Published +// Description: Sets the number of lines of text the PGEntry will +// use. This only has meaning if _max_width is not 0; +// _max_width indicates the wordwrap width of each line. +//////////////////////////////////////////////////////////////////// +INLINE void PGEntry:: +set_num_lines(int num_lines) { + nassertv(num_lines >= 1); + _num_lines = num_lines; + _text_geom_stale = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: PGEntry::get_num_lines +// Access: Published +// Description: Returns the number of lines of text the PGEntry will +// use, if _max_width is not 0. See set_num_lines(). +//////////////////////////////////////////////////////////////////// +INLINE int PGEntry:: +get_num_lines() const { + return _num_lines; +} + //////////////////////////////////////////////////////////////////// // Function: PGEntry::set_blink_rate // Access: Published diff --git a/panda/src/pgui/pgEntry.cxx b/panda/src/pgui/pgEntry.cxx index 10a60cb835..8548d05fd8 100644 --- a/panda/src/pgui/pgEntry.cxx +++ b/panda/src/pgui/pgEntry.cxx @@ -46,6 +46,7 @@ PGEntry(const string &name) : PGItem(name) _cursor_stale = true; _max_chars = 0; _max_width = 0.0; + _num_lines = 1; _last_text_def = (TextNode *)NULL; _text_geom_stale = true; _blink_start = 0.0; @@ -220,14 +221,64 @@ press(const MouseWatcherParameter ¶m) { _text.substr(0, _cursor_position) + key + _text.substr(_cursor_position); - // Check the width. - bool too_wide = false; - if (get_max_width() > 0.0) { + // Check the length. + bool too_long = false; + if (_max_width > 0.0) { TextNode *text_node = get_text_def(S_focus); - too_wide = (text_node->calc_width(new_text) > get_max_width()); + if (_num_lines <= 1) { + // If we have only one line, we can check the length + // by simply measuring the width of the text. + too_long = (text_node->calc_width(new_text) > _max_width); + + } else { + // If we have multiple lines, we have to check the + // length by wordwrapping it and counting up the + // number of lines. + string ww_text = text_node->wordwrap_to(new_text, _max_width, true); + int num_lines = 1; + size_t last_line_start = 0; + for (size_t p = 0; + p < ww_text.length() && !too_long; + ++p) { + if (ww_text[p] == '\n') { + last_line_start = p + 1; + num_lines++; + too_long = (num_lines > _num_lines); + } + } + + if (!too_long) { + // We must also ensure that the last line is not too + // long (it might be, because of additional + // whitespace on the end). + string last_line = ww_text.substr(last_line_start); + float last_line_width = text_node->calc_width(last_line); + if (num_lines == _num_lines) { + // Mainly we only care about this if we're on + // the very last line. + too_long = (last_line_width > _max_width); + + } else { + // If we're not on the very last line, the width + // is still important, just so we don't allow an + // infinite number of spaces to accumulate. + // However, we must allow at least *one* space + // on the end of a line. + if (_text.length() >= 1 && + _text[_text.length() - 1] == ' ') { + if (last_line_width > _max_width) { + // In this case, however, it's not exactly + // an overflow; we just want to reject the + // space. + return; + } + } + } + } + } } - if (too_wide) { + if (too_long) { overflow(param); } else { @@ -280,20 +331,22 @@ overflow(const MouseWatcherParameter ¶m) { // Access: Published // Description: Sets up the entry for normal use. The width is the // maximum width of characters that will be typed, and -// determines the size of the entry, based on the -// TextNode in effect. +// num_lines is the integer number of lines of text of +// the entry. Both of these together determine the size +// of the entry, based on the TextNode in effect. //////////////////////////////////////////////////////////////////// void PGEntry:: -setup(float width) { +setup(float width, int num_lines) { set_text(string()); _cursor_position = 0; set_max_chars(0); set_max_width(width); + set_num_lines(num_lines); TextNode *text_node = get_text_def(S_focus); - float height = text_node->get_line_height(); + float line_height = text_node->get_line_height(); - LVecBase4f frame(0.0, width, -0.3 * height, height); + LVecBase4f frame(0.0, width, -0.3 * line_height - (line_height * (num_lines - 1)), line_height); switch (text_node->get_align()) { case TM_ALIGN_LEFT: // The default case. @@ -329,8 +382,8 @@ setup(float width) { clear_cursor_def(); LineSegs ls; ls.set_color(0.0, 0.0, 0.0, 1.0); - ls.move_to(0.0, 0.0, -0.15 * height); - ls.draw_to(0.0, 0.0, 0.85 * height); + ls.move_to(0.0, 0.0, -0.15 * line_height); + ls.draw_to(0.0, 0.0, 0.85 * line_height); new RenderRelation(get_cursor_def(), ls.create()); // An underscore cursor would work too. @@ -437,7 +490,53 @@ update_text() { if (_text_geom_stale || node != _last_text_def) { // We need to regenerate. _last_text_def = node; - _last_text_def->set_text(_text); + + if (_max_width > 0.0 && _num_lines > 1) { + // Fold the text into multiple lines. + string ww_text = _last_text_def->wordwrap_to(_text, _max_width, true); + + // And chop the lines up into pieces. + _ww_lines.clear(); + size_t p = 0; + size_t q = ww_text.find('\n'); + while (q != string::npos) { + _ww_lines.push_back(WWLine()); + WWLine &line = _ww_lines.back(); + line._str = ww_text.substr(p, q - p); + + // Get the left edge of the text at this line. + line._left = 0.0; + if (_last_text_def->get_align() != TM_ALIGN_LEFT) { + _last_text_def->set_text(line._str); + line._left = _last_text_def->get_left(); + } + + p = q + 1; + q = ww_text.find('\n', p); + } + _ww_lines.push_back(WWLine()); + WWLine &line = _ww_lines.back(); + line._str = ww_text.substr(p); + + // Get the left edge of the text at this line. + line._left = 0.0; + if (_last_text_def->get_align() != TM_ALIGN_LEFT) { + _last_text_def->set_text(line._str); + line._left = _last_text_def->get_left(); + } + + _last_text_def->set_text(ww_text); + + } else { + // Only one line. + _ww_lines.clear(); + _ww_lines.push_back(WWLine()); + WWLine &line = _ww_lines.back(); + line._str = _text; + + _last_text_def->set_text(_text); + line._left = _last_text_def->get_left(); + } if (_current_text_arc != (NodeRelation *)NULL) { remove_arc(_current_text_arc); @@ -445,7 +544,6 @@ update_text() { PT_Node text = _last_text_def->generate(); _current_text_arc = new RenderRelation(_text_render_root, text); _text_geom_stale = false; - _text_left = _last_text_def->get_left(); _cursor_stale = true; } } @@ -465,10 +563,23 @@ update_cursor() { _cursor_position = min(_cursor_position, (int)_text.length()); - float width = - _last_text_def->calc_width(_text.substr(0, _cursor_position)); + // Determine the row and column of the cursor. + int row = 0; + int column = _cursor_position; + while (row + 1 < (int)_ww_lines.size() && + column > (int)_ww_lines[row]._str.length()) { + column -= _ww_lines[row]._str.length(); + row++; + } - LVecBase3f trans(_text_left + width, 0.0, 0.0); + nassertv(row >= 0 && row < (int)_ww_lines.size()); + nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length()); + + float width = + _last_text_def->calc_width(_ww_lines[row]._str.substr(0, column)); + float line_height = _last_text_def->get_line_height(); + + LVecBase3f trans(_ww_lines[row]._left + width, 0.0, -line_height * row); LMatrix4f pos = LMatrix4f::translate_mat(trans); _cursor_def->set_transition(new TransformTransition(pos)); diff --git a/panda/src/pgui/pgEntry.h b/panda/src/pgui/pgEntry.h index 35023c8400..65975a0f1a 100644 --- a/panda/src/pgui/pgEntry.h +++ b/panda/src/pgui/pgEntry.h @@ -60,7 +60,7 @@ PUBLISHED: S_inactive }; - void setup(float width); + void setup(float width, int num_lines); INLINE void set_text(const string &text); INLINE const string &get_text() const; @@ -72,6 +72,8 @@ PUBLISHED: INLINE int get_max_chars() const; INLINE void set_max_width(float max_width); INLINE float get_max_width() const; + INLINE void set_num_lines(int num_lines); + INLINE int get_num_lines() const; INLINE void set_blink_rate(float blink_rate); INLINE float get_blink_rate() const; @@ -104,6 +106,7 @@ private: int _max_chars; float _max_width; + int _num_lines; typedef pvector< PT(TextNode) > TextDefs; TextDefs _text_defs; @@ -116,9 +119,19 @@ private: // is generated. NodeRelation *_current_text_arc; TextNode *_last_text_def; - float _text_left; bool _text_geom_stale; + // This is a list of each row of text in the entry, after it has + // been wordwrapped by update_text(). It's used by update_cursor() + // to compute the correct cursor position. + class WWLine { + public: + string _str; + float _left; + }; + typedef pvector WWLines; + WWLines _ww_lines; + // This is the arc above the node that represents the cursor // geometry. It is also attached to the above node, and is // transformed around and/or hidden according to the cursor's diff --git a/panda/src/pgui/pgItem.I b/panda/src/pgui/pgItem.I index 498f9c2d1c..8d17d44a9b 100644 --- a/panda/src/pgui/pgItem.I +++ b/panda/src/pgui/pgItem.I @@ -160,6 +160,24 @@ get_id() const { return _region->get_name(); } +//////////////////////////////////////////////////////////////////// +// Function: PGItem::set_id +// Access: Published +// Description: Set the unique ID assigned to this PGItem. It is the +// user's responsibility to ensure that this ID is +// unique. +// +// Normally, this should not need to be called, as the +// PGItem will assign itself an ID when it is created, +// but this function allows the user to decide to +// redefine the ID to be something possibly more +// meaningful. +//////////////////////////////////////////////////////////////////// +INLINE void PGItem:: +set_id(const string &id) { + _region->set_name(id); +} + //////////////////////////////////////////////////////////////////// // Function: PGItem::get_enter_prefix // Access: Published, Static diff --git a/panda/src/pgui/pgItem.h b/panda/src/pgui/pgItem.h index 7f34e8b889..7c119ee1f5 100644 --- a/panda/src/pgui/pgItem.h +++ b/panda/src/pgui/pgItem.h @@ -102,6 +102,8 @@ PUBLISHED: void set_frame_style(int state, const PGFrameStyle &style); INLINE const string &get_id() const; + INLINE void set_id(const string &id); + INLINE static string get_enter_prefix(); INLINE static string get_exit_prefix(); INLINE static string get_press_prefix();