PGEntry supports embedded mode changes

This commit is contained in:
David Rose 2007-07-12 00:19:18 +00:00
parent 504df9ac3c
commit 278fe85c1a
12 changed files with 1286 additions and 468 deletions

View File

@ -32,6 +32,23 @@ set_text(const string &text) {
set_wtext(text_node->decode_text(text));
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::get_plain_text
// Access: Published
// Description: Returns the text currently displayed within the
// entry, without any embedded properties characters.
//
// This uses the Unicode encoding currently specified
// for the "focus" TextNode; therefore, the TextNode
// must exist before calling get_text().
////////////////////////////////////////////////////////////////////
INLINE string PGEntry::
get_plain_text() const {
TextNode *text_node = get_text_def(S_focus);
nassertr(text_node != (TextNode *)NULL, string());
return text_node->encode_wtext(get_plain_wtext());
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::get_text
// Access: Published
@ -47,6 +64,61 @@ get_text() const {
return text_node->encode_wtext(get_wtext());
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::get_num_characters
// Access: Published
// Description: Returns the number of characters of text in the
// entry. This is the actual number of visible
// characters, not counting implicit newlines due to
// wordwrapping, or formatted characters for text
// properties changes. If there is an embedded
// TextGraphic object, it counts as one character.
//
// This is also the length of the string returned by
// get_plain_text().
////////////////////////////////////////////////////////////////////
INLINE int PGEntry::
get_num_characters() const {
return _text.get_num_characters();
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::get_character
// Access: Published
// Description: Returns the character at the indicated position in
// the entry. If the object at this position is a
// graphic object instead of a character, returns 0.
////////////////////////////////////////////////////////////////////
INLINE wchar_t PGEntry::
get_character(int n) const {
return _text.get_character(n);
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::get_graphic
// Access: Published
// Description: Returns the graphic object at the indicated position
// in the pre-wordwrapped string. If the object at this
// position is a character instead of a graphic object,
// returns NULL.
////////////////////////////////////////////////////////////////////
INLINE const TextGraphic *PGEntry::
get_graphic(int n) const {
return _text.get_graphic(n);
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::get_properties
// Access: Published
// Description: Returns the TextProperties in effect for the object
// at the indicated position in the pre-wordwrapped
// string.
////////////////////////////////////////////////////////////////////
INLINE const TextProperties &PGEntry::
get_properties(int n) const {
return _text.get_properties(n);
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::set_cursor_position
// Access: Published
@ -431,11 +503,25 @@ get_erase_event() const {
////////////////////////////////////////////////////////////////////
INLINE void PGEntry::
set_wtext(const wstring &wtext) {
_wtext = wtext;
_text.set_wtext(wtext);
if (_obscure_mode) {
_obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
}
_text_geom_stale = true;
_cursor_stale = true;
_blink_start = ClockObject::get_global_clock()->get_frame_time();
set_cursor_position(_wtext.size());
set_cursor_position(_text.get_num_characters());
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::get_plain_wtext
// Access: Published
// Description: Returns the text currently displayed within the
// entry, without any embedded properties characters.
////////////////////////////////////////////////////////////////////
INLINE wstring PGEntry::
get_plain_wtext() const {
return _text.get_plain_wtext();
}
////////////////////////////////////////////////////////////////////
@ -444,9 +530,9 @@ set_wtext(const wstring &wtext) {
// Description: Returns the text currently displayed within the
// entry.
////////////////////////////////////////////////////////////////////
INLINE const wstring &PGEntry::
INLINE wstring PGEntry::
get_wtext() const {
return _wtext;
return _text.get_wtext();
}
////////////////////////////////////////////////////////////////////

View File

@ -41,7 +41,9 @@ TypeHandle PGEntry::_type_handle;
////////////////////////////////////////////////////////////////////
PGEntry::
PGEntry(const string &name) :
PGItem(name)
PGItem(name),
_text(get_text_node()),
_obscure_text(get_text_node())
{
set_cull_callback();
@ -53,6 +55,7 @@ PGEntry(const string &name) :
_max_chars = 0;
_max_width = 0.0f;
_num_lines = 1;
_accept_enabled = true;
_last_text_def = (TextNode *)NULL;
_text_geom_stale = true;
_blink_start = 0.0f;
@ -93,8 +96,8 @@ PGEntry::
PGEntry::
PGEntry(const PGEntry &copy) :
PGItem(copy),
_wtext(copy._wtext),
_obscured_wtext(copy._obscured_wtext),
_text(copy._text),
_obscure_text(copy._obscure_text),
_cursor_position(copy._cursor_position),
_max_chars(copy._max_chars),
_max_width(copy._max_width),
@ -203,7 +206,7 @@ press(const MouseWatcherParameter &param, bool background) {
_text_geom_stale = true;
}
_cursor_position = min(_cursor_position, (int)_wtext.length());
_cursor_position = min(_cursor_position, _text.get_num_characters());
_blink_start = ClockObject::get_global_clock()->get_frame_time();
if (button == KeyboardButton::enter()) {
// Enter. Accept the entry.
@ -214,10 +217,7 @@ press(const MouseWatcherParameter &param, bool background) {
} else if (button == KeyboardButton::backspace()) {
// Backspace. Remove the character to the left of the cursor.
if (_cursor_position > 0) {
if (_obscure_mode && _obscured_wtext.length() == _wtext.length()) {
_obscured_wtext.erase(_obscured_wtext.begin() + _obscured_wtext.length() - 1);
}
_wtext.erase(_wtext.begin() + _cursor_position - 1);
_text.set_wsubstr(wstring(), _cursor_position - 1, 1);
_cursor_position--;
_cursor_stale = true;
_text_geom_stale = true;
@ -226,11 +226,8 @@ press(const MouseWatcherParameter &param, bool background) {
} else if (button == KeyboardButton::del()) {
// Delete. Remove the character to the right of the cursor.
if (_cursor_position < (int)_wtext.length()) {
if (_obscure_mode && _obscured_wtext.length() == _wtext.length()) {
_obscured_wtext.erase(_obscured_wtext.begin() + _obscured_wtext.length() - 1);
}
_wtext.erase(_wtext.begin() + _cursor_position);
if (_cursor_position < _text.get_num_characters()) {
_text.set_wsubstr(wstring(), _cursor_position, 1);
_text_geom_stale = true;
erase(param);
}
@ -245,7 +242,7 @@ press(const MouseWatcherParameter &param, bool background) {
} else if (button == KeyboardButton::right()) {
if (_cursor_keys_active) {
// Right arrow. Move the cursor position to the right.
_cursor_position = min(_cursor_position + 1, (int)_wtext.length());
_cursor_position = min(_cursor_position + 1, _text.get_num_characters());
_cursor_stale = true;
}
@ -259,7 +256,7 @@ press(const MouseWatcherParameter &param, bool background) {
} else if (button == KeyboardButton::end()) {
if (_cursor_keys_active) {
// End. Move the cursor position to the end.
_cursor_position = _wtext.length();
_cursor_position = _text.get_num_characters();
_cursor_stale = true;
}
}
@ -290,52 +287,27 @@ keystroke(const MouseWatcherParameter &param, bool background) {
}
wstring new_char(1, (wchar_t)keycode);
if (get_max_chars() > 0 && (int)_wtext.length() >= get_max_chars()) {
if (get_max_chars() > 0 && _text.get_num_characters() >= get_max_chars()) {
// In max_chars mode, we consider it an overflow after we
// have exceeded a fixed number of characters, irrespective
// of the formatted width of those characters.
overflow(param);
} else {
_cursor_position = min(_cursor_position, (int)_wtext.length());
wstring new_text =
_wtext.substr(0, _cursor_position) + new_char +
_wtext.substr(_cursor_position);
// Get the new string to measure its length. In normal
// mode, we measure the text itself. In obscure mode, we
// measure a string of n asterisks.
wstring measure_text;
_cursor_position = min(_cursor_position, _text.get_num_characters());
bool too_long = !_text.set_wsubstr(new_char, _cursor_position, 0);
if (_obscure_mode) {
measure_text = get_display_wtext() + (wchar_t)'*';
too_long = !_obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
} else {
measure_text = new_text;
}
// Now check the length.
bool too_long = false;
if (_max_width > 0.0f) {
TextNode *text_node = get_text_def(S_focus);
text_node->set_wtext(measure_text);
text_node->set_wordwrap(_max_width);
text_node->set_preserve_trailing_whitespace(true);
text_node->set_max_rows(_num_lines);
too_long = text_node->has_overflow();
if (!too_long && (text_node->get_num_rows() == _num_lines)) {
if (!too_long && (_text.get_num_rows() == _num_lines)) {
// If we've filled up all of the available lines, we
// must also ensure that the last line is not too long
// (it might be, because of additional whitespace on
// the end).
wstring ww_text = text_node->get_wordwrapped_wtext();
size_t last_line_start = ww_text.rfind('\n');
if (last_line_start == string::npos) {
last_line_start = 0;
}
wstring last_line = ww_text.substr(last_line_start);
float last_line_width = text_node->calc_width(last_line);
int r = _num_lines - 1;
int c = _text.get_num_cols(r);
float last_line_width =
_text.get_xpos(r, c) - _text.get_xpos(r, 0);
too_long = (last_line_width > _max_width);
}
@ -345,59 +317,31 @@ keystroke(const MouseWatcherParameter &param, bool background) {
// end of the current line if it would make that line
// exceed the maximum width, just so we don't allow an
// infinite number of spaces to accumulate.
// First, we need to figure out our current position
// within the wordwrapped text, by skipping past the
// newlines.
wstring ww_text = text_node->get_wordwrapped_wtext();
int i = 0;
int current_pos = 0;
while (i < _cursor_position) {
nassertv(current_pos < (int)ww_text.length());
if (ww_text[current_pos] != '\n') {
i++;
}
current_pos++;
}
// Is the user typing at the end of the line? Scan for
// the next character that's not a space following the
// current position.
int p = current_pos + 1;
while (p < (int)ww_text.length() && ww_text[p] == ' ') {
p++;
}
if (p >= (int)ww_text.length() || ww_text[p] == '\n') {
int r, c;
_text.calc_r_c(r, c, _cursor_position);
if (_text.get_num_cols(r) == c + 1) {
// The user is typing at the end of the line. But we
// must allow at least one space at the end of the
// line, so we only make any of the following checks
// if there are already multiple spaces at the end of
// the line.
if (p - 2 >= 0 && ww_text[p - 2] == ' ') {
if (c - 1 >= 0 && _text.get_character(r, c - 1) == ' ') {
// Ok, the user is putting multiple spaces on the
// end of a line; we need to make sure the line does
// not grow too wide. Get the beginning of the line
// and measure its width.
int q = current_pos;
while (q >= 0 && ww_text[q] != '\n') {
q--;
}
wstring current_line = ww_text.substr(q + 1, p - (q + 1));
float current_line_width = text_node->calc_width(current_line);
// not grow too wide. Measure the line's width.
float current_line_width =
_text.get_xpos(r, c + 1) - _text.get_xpos(r, 0);
if (current_line_width > _max_width) {
// We have to reject the space, but we don't treat
// it as an overflow condition.
_text.set_wsubstr(wstring(), _cursor_position, 1);
// If the user is typing over existing space
// characters, we act as if the right-arrow key
// were pressed instead, and advance the cursor to
// the next position. Otherwise, we just quietly
// eat the space character.
if (_cursor_position < (int)_wtext.length() &&
_wtext[_cursor_position] == ' ') {
if (_cursor_position < _text.get_num_characters() &&
_text.get_character(_cursor_position) == ' ') {
_cursor_position++;
_cursor_stale = true;
}
@ -409,14 +353,10 @@ keystroke(const MouseWatcherParameter &param, bool background) {
}
if (too_long) {
_text.set_wsubstr(wstring(), _cursor_position, 1);
overflow(param);
} else {
_wtext = new_text;
if (_obscure_mode) {
_obscured_wtext = measure_text;
}
_cursor_position += new_char.length();
_cursor_stale = true;
_text_geom_stale = true;
@ -445,10 +385,11 @@ candidate(const MouseWatcherParameter &param, bool background) {
_candidate_highlight_end = param.get_highlight_end();
_candidate_cursor_pos = param.get_cursor_pos();
_text_geom_stale = true;
if (!_candidate_wtext.empty())
if (!_candidate_wtext.empty()) {
type(param);
}
}
}
PGItem::candidate(param, background);
}
@ -678,9 +619,9 @@ set_focus(bool focus) {
////////////////////////////////////////////////////////////////////
bool PGEntry::
is_wtext() const {
wstring::const_iterator ti;
for (ti = _wtext.begin(); ti != _wtext.end(); ++ti) {
if (((*ti) & ~0x7f) != 0) {
for (int i = 0; i < _text.get_num_characters(); ++i) {
wchar_t ch = _text.get_character(i);
if ((ch & ~0x7f) != 0) {
return true;
}
}
@ -688,34 +629,6 @@ is_wtext() const {
return false;
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::get_display_wtext
// Access: Private
// Description: Returns the string that should be displayed within
// the entry. This is normally _wtext, but it may be
// _obscured_wtext.
////////////////////////////////////////////////////////////////////
const wstring &PGEntry::
get_display_wtext() {
if (_obscure_mode) {
// If obscure mode is enabled, we should just display a bunch of
// asterisks.
if (_obscured_wtext.length() != _wtext.length()) {
_obscured_wtext = wstring();
wstring::const_iterator ti;
for (ti = _wtext.begin(); ti != _wtext.end(); ++ti) {
_obscured_wtext += (wchar_t)'*';
}
}
return _obscured_wtext;
} else {
// In normal, non-obscure mode, we display the actual text
return _wtext;
}
}
////////////////////////////////////////////////////////////////////
// Function: PGEntry::slot_text_def
// Access: Private
@ -741,89 +654,56 @@ update_text() {
nassertv(node != (TextNode *)NULL);
if (_text_geom_stale || node != _last_text_def) {
wstring display_wtext;
TextProperties props = *node;
props.set_wordwrap(_max_width);
props.set_preserve_trailing_whitespace(true);
_text.set_properties(props);
_text.set_max_rows(_num_lines);
if (_candidate_wtext.empty() || _obscure_mode) {
if (node != _last_text_def) {
// Make sure the default properties are applied to all the
// characters in the text.
_text.set_wtext(_text.get_wtext());
_last_text_def = node;
}
PT(PandaNode) assembled;
if (_obscure_mode) {
_obscure_text.set_properties(props);
_obscure_text.set_max_rows(_num_lines);
_obscure_text.set_wtext(wstring(_text.get_num_characters(), '*'));
assembled = _obscure_text.assemble_text();
} else if (_candidate_wtext.empty()) {
// If we're not trying to display a candidate string, it's easy:
// just display the current text contents.
display_wtext = get_display_wtext();
assembled = _text.assemble_text();
} else {
// Insert the complex sequence of characters required to show
// the candidate string in a different color. This gets
// inserted at the current cursor position.
wstring source_wtext = get_display_wtext();
TextPropertiesManager *tp_mgr = TextPropertiesManager::get_global_ptr();
TextProperties inactive = tp_mgr->get_properties(_candidate_inactive);
TextProperties active = tp_mgr->get_properties(_candidate_active);
display_wtext = source_wtext.substr(0, _cursor_position);
display_wtext += wstring(1, (wchar_t)text_push_properties_key);
display_wtext += node->decode_text(_candidate_inactive);
display_wtext += wstring(1, (wchar_t)text_push_properties_key);
display_wtext += _candidate_wtext.substr(0, _candidate_highlight_start);
display_wtext += wstring(1, (wchar_t)text_push_properties_key);
display_wtext += node->decode_text(_candidate_active);
display_wtext += wstring(1, (wchar_t)text_push_properties_key);
display_wtext += _candidate_wtext.substr(_candidate_highlight_start,
_candidate_highlight_end - _candidate_highlight_start);
display_wtext += wstring(1, (wchar_t)text_pop_properties_key);
display_wtext += _candidate_wtext.substr(_candidate_highlight_end);
display_wtext += wstring(1, (wchar_t)text_pop_properties_key);
// Create a special TextAssembler to insert the candidate string
// in its own special colors.
display_wtext += source_wtext.substr(_cursor_position);
}
// We need to regenerate.
_last_text_def = node;
_last_text_def->set_wtext(display_wtext);
_last_text_def->set_wordwrap(_max_width);
_last_text_def->set_preserve_trailing_whitespace(true);
_last_text_def->set_max_rows(_num_lines);
// Check for multiple lines.
wstring ww_text = _last_text_def->get_wordwrapped_wtext();
// And chop the lines up into pieces.
_ww_lines.clear();
size_t p = 0;
size_t q = ww_text.find((wchar_t)'\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.0f;
if (_last_text_def->get_align() != TextNode::A_left) {
// Temporarily set this line's text in the TextNode, just so
// we can measure the left margin. (If align is A_left, we
// know that the left margin is 0.0, so we don't need to do
// this.)
_last_text_def->set_wtext(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.0f;
if (_last_text_def->get_align() != TextNode::A_left) {
_last_text_def->set_wtext(line._str);
line._left = _last_text_def->get_left();
TextAssembler ctext(_text);
ctext.set_wsubstr(_candidate_wtext.substr(_candidate_highlight_end),
_cursor_position, 0, inactive);
ctext.set_wsubstr(_candidate_wtext.substr(_candidate_highlight_start,
_candidate_highlight_end - _candidate_highlight_start),
_cursor_position, 0, active);
ctext.set_wsubstr(_candidate_wtext.substr(0, _candidate_highlight_start),
_cursor_position, 0, inactive);
assembled = ctext.assemble_text();
}
if (!_current_text.is_empty()) {
_current_text.remove_node();
}
// We need to reset the text, since we might have changed it
// temporarily in the above when align is not A_left.
_last_text_def->set_wtext(display_wtext);
_current_text =
_text_render_root.attach_new_node(_last_text_def->generate());
_text_render_root.attach_new_node(assembled);
_text_geom_stale = false;
_cursor_stale = true;
}
@ -844,32 +724,23 @@ update_cursor() {
if (_cursor_stale || node != _last_text_def) {
update_text();
_cursor_position = min(_cursor_position, (int)_wtext.length());
_cursor_position = min(_cursor_position, _text.get_num_characters());
_candidate_cursor_pos = min(_candidate_cursor_pos, _candidate_wtext.length());
// Determine the row and column of the cursor.
int row = 0;
int column = _cursor_position + _candidate_cursor_pos;
while (row + 1 < (int)_ww_lines.size() &&
column > (int)_ww_lines[row]._str.length()) {
column -= _ww_lines[row]._str.length();
row++;
int row, column;
float xpos, ypos;
if (_obscure_mode) {
_obscure_text.calc_r_c(row, column, _cursor_position + _candidate_cursor_pos);
xpos = _obscure_text.get_xpos(row, column);
ypos = _obscure_text.get_ypos(row, column);
} else {
_text.calc_r_c(row, column, _cursor_position + _candidate_cursor_pos);
xpos = _text.get_xpos(row, column);
ypos = _text.get_ypos(row, column);
}
nassertv(row >= 0 && row < (int)_ww_lines.size());
// It is possible for this to become untrue legitimately, if due
// to a candidate string we have wordwrapped down the last part of
// the line containing the cursor.
//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.0f, -line_height * row);
_cursor_def.set_pos(trans);
_cursor_def.set_pos(xpos, 0.0f, ypos);
_cursor_stale = false;
}

View File

@ -27,6 +27,7 @@
#include "pointerTo.h"
#include "pvector.h"
#include "clockObject.h"
#include "textAssembler.h"
////////////////////////////////////////////////////////////////////
// Class : PGEntry
@ -74,8 +75,14 @@ PUBLISHED:
void setup(float width, int num_lines);
INLINE void set_text(const string &text);
INLINE string get_plain_text() const;
INLINE string get_text() const;
INLINE int get_num_characters() const;
INLINE wchar_t get_character(int n) const;
INLINE const TextGraphic *get_graphic(int n) const;
INLINE const TextProperties &get_properties(int n) const;
INLINE void set_cursor_position(int position);
INLINE int get_cursor_position() const;
@ -121,21 +128,21 @@ PUBLISHED:
INLINE string get_erase_event() const;
INLINE void set_wtext(const wstring &wtext);
INLINE const wstring &get_wtext() const;
INLINE wstring get_plain_wtext() const;
INLINE wstring get_wtext() const;
INLINE void set_accept_enabled(bool enabled);
bool is_wtext() const;
private:
const wstring &get_display_wtext();
void slot_text_def(int state);
void update_text();
void update_cursor();
void show_hide_cursor(bool visible);
void update_state();
wstring _wtext;
wstring _obscured_wtext;
TextAssembler _text;
TextAssembler _obscure_text;
int _cursor_position;
bool _cursor_stale;
bool _cursor_visible;
@ -166,17 +173,6 @@ private:
TextNode *_last_text_def;
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:
wstring _str;
float _left;
};
typedef pvector<WWLine> WWLines;
WWLines _ww_lines;
// This is 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 position and blink state.

View File

@ -43,9 +43,61 @@ get_usage_hint() const {
return _usage_hint;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::set_max_rows
// Access: Published
// Description: If max_rows is greater than zero, no more than
// max_rows will be accepted. Text beyond that will be
// truncated.
//
// The return value is true if all the text is accepted,
// or false if some was truncated.
////////////////////////////////////////////////////////////////////
INLINE bool TextAssembler::
set_max_rows(int max_rows) {
_max_rows = max_rows;
return wordwrap_text();
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_max_rows
// Access: Published
// Description: If max_rows is greater than zero, no more than
// max_rows will be accepted. Text beyond that will be
// truncated.
////////////////////////////////////////////////////////////////////
INLINE int TextAssembler::
get_max_rows() const {
return _max_rows;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::set_properties
// Access: Published
// Description: Specifies the default TextProperties that are applied
// to the text in the absence of any nested property
// change sequences.
////////////////////////////////////////////////////////////////////
INLINE void TextAssembler::
set_properties(const TextProperties &properties) {
_initial_cprops = new ComputedProperties(properties);
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_properties
// Access: Published
// Description: Returns the default TextProperties that are applied
// to the text in the absence of any nested property
// change sequences.
////////////////////////////////////////////////////////////////////
INLINE const TextProperties &TextAssembler::
get_properties() const {
return _initial_cprops->_properties;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_ul
// Access: Public
// Access: Published
// Description: Returns the upper-left corner of the assembled text,
// in 2-d text coordinates.
////////////////////////////////////////////////////////////////////
@ -56,7 +108,7 @@ get_ul() const {
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_lr
// Access: Public
// Access: Published
// Description: Returns the lower-right corner of the assembled text,
// in 2-d text coordinates.
////////////////////////////////////////////////////////////////////
@ -65,15 +117,212 @@ get_lr() const {
return _lr;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::calc_r
// Access: Published
// Description: Computes the row index of the nth character or
// graphic object in the text and returns it.
//
// If the nth character is not a normal printable
// character with a position in the wordwrapped string,
// returns -1 (for instance, a soft-hyphen character, or
// a newline character, may not have a corresponding
// position).
////////////////////////////////////////////////////////////////////
int TextAssembler::
calc_r(int n) const {
int r, c;
if (calc_r_c(r, c, n)) {
return r;
}
return -1;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::calc_c
// Access: Published
// Description: Computes the column index of the nth character or
// graphic object in the text and returns it.
//
// If the nth character is not a normal printable
// character with a position in the wordwrapped string,
// returns -1 (for instance, a soft-hyphen character, or
// a newline character, may not have a corresponding
// position).
////////////////////////////////////////////////////////////////////
int TextAssembler::
calc_c(int n) const {
int r, c;
if (calc_r_c(r, c, n)) {
return c;
}
return -1;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_num_characters
// Access: Published
// Description: Returns the number of characters of text, before
// wordwrapping.
////////////////////////////////////////////////////////////////////
INLINE int TextAssembler::
get_num_characters() const {
return _text_string.size();
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_character
// Access: Published
// Description: Returns the character at the indicated position in
// the pre-wordwrapped string. If the object at this
// position is a graphic object instead of a character,
// returns 0.
////////////////////////////////////////////////////////////////////
INLINE wchar_t TextAssembler::
get_character(int n) const {
nassertr(n >= 0 && n < (int)_text_string.size(), 0);
return _text_string[n]._character;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_graphic
// Access: Published
// Description: Returns the graphic object at the indicated position
// in the pre-wordwrapped string. If the object at this
// position is a character instead of a graphic object,
// returns NULL.
////////////////////////////////////////////////////////////////////
INLINE const TextGraphic *TextAssembler::
get_graphic(int n) const {
nassertr(n >= 0 && n < (int)_text_string.size(), 0);
return _text_string[n]._graphic;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_properties
// Access: Published
// Description: Returns the TextProperties in effect for the object
// at the indicated position in the pre-wordwrapped
// string.
////////////////////////////////////////////////////////////////////
INLINE const TextProperties &TextAssembler::
get_properties(int n) const {
nassertr(n >= 0 && n < (int)_text_string.size(), *(new TextProperties()));
return _text_string[n]._cprops->_properties;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_width
// Access: Published
// Description: Returns the width of the character or object at the
// indicated position in the pre-wordwrapped string.
////////////////////////////////////////////////////////////////////
INLINE float TextAssembler::
get_width(int n) const {
nassertr(n >= 0 && n < (int)_text_string.size(), 0.0f);
return calc_width(_text_string[n]);
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_num_rows
// Access: Public
// Access: Published
// Description: Returns the number of rows of text after it has all
// been wordwrapped and assembled.
////////////////////////////////////////////////////////////////////
INLINE int TextAssembler::
get_num_rows() const {
return _num_rows;
return _text_block.size();
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_num_cols
// Access: Published
// Description: Returns the number of characters and/or graphic
// objects in the nth row.
////////////////////////////////////////////////////////////////////
INLINE int TextAssembler::
get_num_cols(int r) const {
nassertr(r >= 0 && r <= (int)_text_block.size(), 0);
if (r == _text_block.size()) {
return 0;
}
return _text_block[r]._string.size();
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_character
// Access: Published
// Description: Returns the character at the indicated position in
// the indicated row. If the object at this position is
// a graphic object instead of a character, returns 0.
////////////////////////////////////////////////////////////////////
INLINE wchar_t TextAssembler::
get_character(int r, int c) const {
nassertr(r >= 0 && r < (int)_text_block.size(), 0);
nassertr(c >= 0 && c < (int)_text_block[r]._string.size(), 0);
return _text_block[r]._string[c]._character;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_graphic
// Access: Published
// Description: Returns the graphic object at the indicated position
// in the indicated row. If the object at this position
// is a character instead of a graphic object, returns
// NULL.
////////////////////////////////////////////////////////////////////
INLINE const TextGraphic *TextAssembler::
get_graphic(int r, int c) const {
nassertr(r >= 0 && r < (int)_text_block.size(), 0);
nassertr(c >= 0 && c < (int)_text_block[r]._string.size(), 0);
return _text_block[r]._string[c]._graphic;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_properties
// Access: Published
// Description: Returns the TextProperties in effect for the object
// at the indicated position in the indicated row.
////////////////////////////////////////////////////////////////////
INLINE const TextProperties &TextAssembler::
get_properties(int r, int c) const {
nassertr(r >= 0 && r < (int)_text_block.size(), *(new TextProperties()));
nassertr(c >= 0 && c < (int)_text_block[r]._string.size(), *(new TextProperties()));
return _text_block[r]._string[c]._cprops->_properties;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_width
// Access: Published
// Description: Returns the width of the character or object at the
// indicated position in the indicated row.
////////////////////////////////////////////////////////////////////
INLINE float TextAssembler::
get_width(int r, int c) const {
nassertr(r >= 0 && r < (int)_text_block.size(), 0.0f);
nassertr(c >= 0 && c < (int)_text_block[r]._string.size(), 0.0f);
return calc_width(_text_block[r]._string[c]);
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::get_ypos
// Access: Published
// Description: Returns the y position of the origin of all of the
// characters or graphic objects in the indicated row.
//
// It is legal for r to exceed the index number of the
// last row by 1. The value of c is presently ignored.
////////////////////////////////////////////////////////////////////
INLINE float TextAssembler::
get_ypos(int r, int) const {
nassertr(r >= 0 && r <= (int)_text_block.size(), 0.0f);
if (r == _text_block.size()) {
return _next_row_ypos;
} else {
return _text_block[r]._ypos;
}
}
////////////////////////////////////////////////////////////////////
@ -85,9 +334,9 @@ get_num_rows() const {
INLINE float TextAssembler::
calc_width(const TextCharacter &tch) {
if (tch._graphic != (TextGraphic *)NULL) {
return calc_width(tch._graphic, *tch._properties);
return calc_width(tch._graphic, tch._cprops->_properties);
} else {
return calc_width(tch._character, *tch._properties);
return calc_width(tch._character, tch._cprops->_properties);
}
}
@ -97,10 +346,11 @@ calc_width(const TextCharacter &tch) {
// Description:
////////////////////////////////////////////////////////////////////
INLINE TextAssembler::TextCharacter::
TextCharacter(wchar_t character, const TextProperties *properties) :
TextCharacter(wchar_t character,
TextAssembler::ComputedProperties *cprops) :
_character(character),
_graphic(NULL),
_properties(properties)
_cprops(cprops)
{
}
@ -110,13 +360,126 @@ TextCharacter(wchar_t character, const TextProperties *properties) :
// Description:
////////////////////////////////////////////////////////////////////
INLINE TextAssembler::TextCharacter::
TextCharacter(const TextGraphic *graphic, const TextProperties *properties) :
TextCharacter(const TextGraphic *graphic, const wstring &graphic_wname,
TextAssembler::ComputedProperties *cprops) :
_character(0),
_graphic(graphic),
_properties(properties)
_graphic_wname(graphic_wname),
_cprops(cprops)
{
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::TextCharacter::Copy Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE TextAssembler::TextCharacter::
TextCharacter(const TextAssembler::TextCharacter &copy) :
_character(copy._character),
_graphic(copy._graphic),
_graphic_wname(copy._graphic_wname),
_cprops(copy._cprops)
{
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::TextCharacter::Copy Assignment
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE void TextAssembler::TextCharacter::
operator = (const TextAssembler::TextCharacter &copy) {
_character = copy._character;
_graphic = copy._graphic;
_graphic_wname = copy._graphic_wname;
_cprops = copy._cprops;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::TextRow::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE TextAssembler::TextRow::
TextRow(int row_start) :
_row_start(row_start),
_got_soft_hyphens(false)
{
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::TextRow::Copy Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE TextAssembler::TextRow::
TextRow(const TextAssembler::TextRow &copy) :
_string(copy._string),
_row_start(copy._row_start),
_got_soft_hyphens(copy._got_soft_hyphens),
_xpos(copy._xpos),
_ypos(copy._ypos)
{
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::TextRow::Copy Assignment
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE void TextAssembler::TextRow::
operator = (const TextAssembler::TextRow &copy) {
_string = copy._string;
_row_start = copy._row_start;
_got_soft_hyphens = copy._got_soft_hyphens;
_xpos = copy._xpos;
_ypos = copy._ypos;
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::ComputedProperties::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE TextAssembler::ComputedProperties::
ComputedProperties(const TextProperties &orig_properties) :
_based_on(NULL),
_depth(0),
_properties(orig_properties)
{
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::ComputedProperties::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE TextAssembler::ComputedProperties::
ComputedProperties(ComputedProperties *based_on, const wstring &wname,
TextEncoder *encoder) :
_based_on(based_on),
_depth(_based_on->_depth + 1),
_wname(wname),
_properties(based_on->_properties)
{
TextPropertiesManager *manager =
TextPropertiesManager::get_global_ptr();
// Now we have to encode the wstring into a string, for lookup
// in the TextPropertiesManager.
string name = encoder->encode_wtext(wname);
const TextProperties *named_props = manager->get_properties_ptr(name);
if (named_props != (TextProperties *)NULL) {
_properties.add_properties(*named_props);
} else {
text_cat.warning()
<< "Unknown TextProperties: " << name << "\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: TextAssembler::GlyphPlacement::add_piece
// Access: Public

File diff suppressed because it is too large Load Diff

View File

@ -19,8 +19,6 @@
#ifndef TEXTASSEMBLER_H
#define TEXTASSEMBLER_H
#ifndef CPPPARSER // interrogate has a bit of trouble with wstring iterators.
#include "pandabase.h"
#include "textProperties.h"
@ -29,23 +27,27 @@
#include "geomNode.h"
#include "pointerTo.h"
#include "geom.h"
#include "textPropertiesManager.h"
#include "textEncoder.h"
class TextEncoder;
class TextGraphic;
class TextAssembler;
////////////////////////////////////////////////////////////////////
// Class : TextAssembler
// Description : This class is not intended to be used directly by
// user code, but is used by the TextNode to lay out a
// block of text and convert it into rows of Geoms
// according to the TextProperties.
//
// It is not exported from the DLL since it is not
// intended to be used outside of this module.
// Description : This class is not normally used directly by user
// code, but is used by the TextNode to lay out a block
// of text and convert it into rows of Geoms according
// to the TextProperties. However, user code may take
// advantage of it, if desired, for very low-level text
// operations.
////////////////////////////////////////////////////////////////////
class TextAssembler {
public:
class EXPCL_PANDA TextAssembler {
PUBLISHED:
TextAssembler(TextEncoder *encoder);
TextAssembler(const TextAssembler &copy);
void operator = (const TextAssembler &copy);
~TextAssembler();
void clear();
@ -53,11 +55,41 @@ public:
INLINE void set_usage_hint(Geom::UsageHint usage_hint);
INLINE Geom::UsageHint get_usage_hint() const;
bool set_wtext(const wstring &wtext, const TextProperties &properties,
int max_rows = 0);
INLINE int get_num_rows() const;
INLINE bool set_max_rows(int max_rows);
INLINE int get_max_rows() const;
INLINE void set_properties(const TextProperties &properties);
INLINE const TextProperties &get_properties() const;
bool set_wtext(const wstring &wtext);
bool set_wsubstr(const wstring &wtext, int start, int count,
const TextProperties &properties = TextProperties());
wstring get_plain_wtext() const;
wstring get_wordwrapped_plain_wtext() const;
wstring get_wtext() const;
wstring get_wordwrapped_wtext() const;
bool calc_r_c(int &r, int &c, int n) const;
INLINE int calc_r(int n) const;
INLINE int calc_c(int n) const;
int calc_index(int r, int c) const;
INLINE int get_num_characters() const;
INLINE wchar_t get_character(int n) const;
INLINE const TextGraphic *get_graphic(int n) const;
INLINE const TextProperties &get_properties(int n) const;
INLINE float get_width(int n) const;
INLINE int get_num_rows() const;
INLINE int get_num_cols(int r) const;
INLINE wchar_t get_character(int r, int c) const;
INLINE const TextGraphic *get_graphic(int r, int c) const;
INLINE const TextProperties &get_properties(int r, int c) const;
INLINE float get_width(int r, int c) const;
float get_xpos(int r, int c) const;
INLINE float get_ypos(int r, int c) const;
PT(PandaNode) assemble_text();
INLINE const LVector2f &get_ul() const;
@ -67,33 +99,69 @@ public:
static float calc_width(const TextGraphic *graphic, const TextProperties &properties);
private:
class ComputedProperties : public ReferenceCount {
public:
INLINE ComputedProperties(const TextProperties &orig_properties);
INLINE ComputedProperties(ComputedProperties *based_on,
const wstring &wname, TextEncoder *encoder);
void append_delta(wstring &wtext, ComputedProperties *other);
PT(ComputedProperties) _based_on;
int _depth;
wstring _wname;
TextProperties _properties;
};
// These structures are built up and operated on by scan_wtext() and
// wordwrap_text(). It represents the unrolling of the embedded \1
// .. \2 sequences embedded in the string into a TextProperties
// pointer associated with each character.
typedef pvector<TextProperties *> PropertiesList;
class TextCharacter {
public:
INLINE TextCharacter(wchar_t character, const TextProperties *properties);
INLINE TextCharacter(const TextGraphic *graphic, const TextProperties *properties);
INLINE TextCharacter(wchar_t character, ComputedProperties *cprops);
INLINE TextCharacter(const TextGraphic *graphic,
const wstring &graphic_wname,
ComputedProperties *cprops);
INLINE TextCharacter(const TextCharacter &copy);
INLINE void operator = (const TextCharacter &copy);
wchar_t _character;
const TextGraphic *_graphic;
const TextProperties *_properties;
wstring _graphic_wname;
PT(ComputedProperties) _cprops;
};
typedef pvector<TextCharacter> TextString;
PropertiesList _properties_list;
TextString _wordwrapped_string;
int _num_rows;
class TextRow {
public:
INLINE TextRow(int row_start);
INLINE TextRow(const TextRow &copy);
INLINE void operator = (const TextRow &copy);
void scan_wtext(wstring::const_iterator &si,
TextString _string;
int _row_start;
bool _got_soft_hyphens;
float _xpos;
float _ypos;
};
typedef pvector<TextRow> TextBlock;
PT(ComputedProperties) _initial_cprops;
// This is the string, unwordwrapped.
TextString _text_string;
// And here it is, wordwrapped.
TextBlock _text_block;
#ifndef CPPPARSER // interrogate has a bit of trouble with wstring iterators.
void scan_wtext(TextString &output_string,
wstring::const_iterator &si,
const wstring::const_iterator &send,
const TextProperties *current_properties,
TextString &text_string);
bool wordwrap_text(const TextAssembler::TextString &text,
TextAssembler::TextString &output_text,
int max_rows);
ComputedProperties *current_cprops);
#endif // CPPPARSER
bool wordwrap_text();
INLINE static float calc_width(const TextCharacter &tch);
static float calc_hyphen_width(const TextCharacter &tch);
@ -127,11 +195,8 @@ private:
};
typedef pvector<GlyphPlacement *> PlacedGlyphs;
void assemble_paragraph(TextString::const_iterator si,
const TextString::const_iterator &send,
PlacedGlyphs &placed_glyphs);
void assemble_row(TextString::const_iterator &si,
const TextString::const_iterator &send,
void assemble_paragraph(PlacedGlyphs &placed_glyphs);
void assemble_row(TextRow &row,
PlacedGlyphs &row_placed_glyphs,
float &row_width, float &line_height,
TextProperties::Alignment &align);
@ -188,14 +253,14 @@ private:
// These are filled in by assemble_paragraph().
LVector2f _ul;
LVector2f _lr;
float _next_row_ypos;
TextEncoder *_encoder;
Geom::UsageHint _usage_hint;
int _max_rows;
};
#include "textAssembler.I"
#endif // CPPPARSER
#endif

View File

@ -47,6 +47,7 @@ get_line_height() const {
INLINE void TextNode::
set_max_rows(int max_rows) {
_max_rows = max_rows;
_assembler.set_max_rows(_max_rows);
invalidate_with_measure();
}
@ -59,6 +60,7 @@ set_max_rows(int max_rows) {
INLINE void TextNode::
clear_max_rows() {
_max_rows = 0;
_assembler.set_max_rows(_max_rows);
invalidate_with_measure();
}
@ -1183,8 +1185,10 @@ append_unicode_char(int character) {
// Access: Public
// Description: Returns a string that represents the contents of the
// text, as it has been formatted by wordwrap rules.
// This will not contain any embedded special characters
// like \1 or \3.
//
// In earlier versions, this did not contain any
// embedded special characters like \1 or \3; now it
// does.
////////////////////////////////////////////////////////////////////
INLINE string TextNode::
get_wordwrapped_text() const {
@ -1234,8 +1238,10 @@ append_wtext(const wstring &wtext) {
// Access: Published
// Description: Returns a wstring that represents the contents of the
// text, as it has been formatted by wordwrap rules.
// This will not contain any embedded special characters
// like \1 or \3.
//
// In earlier versions, this did not contain any
// embedded special characters like \1 or \3; now it
// does.
////////////////////////////////////////////////////////////////////
INLINE wstring TextNode::
get_wordwrapped_wtext() const {

View File

@ -302,7 +302,8 @@ generate() {
wstring wtext = get_wtext();
// Assemble the text.
bool all_set = _assembler.set_wtext(wtext, *this, _max_rows);
_assembler.set_properties(*this);
bool all_set = _assembler.set_wtext(wtext);
if (all_set) {
// No overflow.
_flags &= ~F_has_overflow;

View File

@ -296,9 +296,7 @@ private:
LPoint3f _ul3d, _lr3d;
#ifndef CPPPARSER
TextAssembler _assembler;
#endif
public:
static TypeHandle get_class_type() {

View File

@ -17,6 +17,16 @@
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
// Function: TextProperties::operator !=
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
INLINE bool TextProperties::
operator != (const TextProperties &other) const {
return !operator == (other);
}
////////////////////////////////////////////////////////////////////
// Function: TextProperties::is_any_specified
// Access: Published

View File

@ -91,6 +91,71 @@ operator = (const TextProperties &copy) {
_glyph_shift = copy._glyph_shift;
}
////////////////////////////////////////////////////////////////////
// Function: TextProperties::operator ==
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
bool TextProperties::
operator == (const TextProperties &other) const {
if (_specified != other._specified) {
return false;
}
if ((_specified & F_has_font) && _font != other._font) {
return false;
}
if ((_specified & F_has_small_caps) && _small_caps != other._small_caps) {
return false;
}
if ((_specified & F_has_small_caps_scale) && _small_caps_scale != other._small_caps_scale) {
return false;
}
if ((_specified & F_has_slant) && _slant != other._slant) {
return false;
}
if ((_specified & F_has_align) && _align != other._align) {
return false;
}
if ((_specified & F_has_indent) && _indent_width != other._indent_width) {
return false;
}
if ((_specified & F_has_wordwrap) && _wordwrap_width != other._wordwrap_width) {
return false;
}
if ((_specified & F_has_preserve_trailing_whitespace) && _preserve_trailing_whitespace != other._preserve_trailing_whitespace) {
return false;
}
if ((_specified & F_has_text_color) && _text_color != other._text_color) {
return false;
}
if ((_specified & F_has_text_color) && _text_color != other._text_color) {
return false;
}
if ((_specified & F_has_shadow_color) && _shadow_color != other._shadow_color) {
return false;
}
if ((_specified & F_has_shadow) && _shadow_offset != other._shadow_offset) {
return false;
}
if ((_specified & F_has_bin) && _bin != other._bin) {
return false;
}
if ((_specified & F_has_draw_order) && _draw_order != other._draw_order) {
return false;
}
if ((_specified & F_has_tab_width) && _tab_width != other._tab_width) {
return false;
}
if ((_specified & F_has_glyph_scale) && _glyph_scale != other._glyph_scale) {
return false;
}
if ((_specified & F_has_glyph_shift) && _glyph_shift != other._glyph_shift) {
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: TextProperties::clear
// Access: Published

View File

@ -59,6 +59,9 @@ PUBLISHED:
TextProperties(const TextProperties &copy);
void operator = (const TextProperties &copy);
bool operator == (const TextProperties &other) const;
INLINE bool operator != (const TextProperties &other) const;
void clear();
INLINE bool is_any_specified() const;