Commit 6cd57ab0 authored by twanvl's avatar twanvl

Added <prefix> and <suffix> tags;

Added prefix and suffix support to combined_editor;
'always symbol' now checks if the symbols are available in the symbol font;
Fixed parser bug in spec_sort;
A first information field is no longer used as set identification
parent 5da31292
......@@ -13,15 +13,17 @@ When the field changes the underlying values are updated and vice versa.
Note: @forward_editor@ and @combined_editor@ are the same function.
--Parameters--
! Parameter Type Description
! Parameter Type Default Description
| @field@ [[type:value#text|text value]] Text value to edit
| @field1@ [[type:value#text|text value]] Another text value to edit
| @field2@ [[type:value#text|text value]] ''etc.''
| @separator@ [[type:string]] Separator between field 1 and 2
| @separator1@ [[type:string]] Multiple separators
| @separator2@ [[type:string]] Next separator, ''etc.''
| @hide when empty@ [[type:boolean]] Don't include separators if the entire value is empty.
| @soft before empty@ [[type:boolean]] Make separators 'soft' when the value folowing it is empty.
| @prefix@ [[type:string]] ''optional'' Prefix before the combined editor; like a separtor between the start and the first field.
| @suffix@ [[type:string]] ''optional'' Suffix after the combined editor; like a separtor between the last field and the end.
| @hide when empty@ [[type:boolean]] @false@ Don't include separators if the entire value is empty.
| @soft before empty@ [[type:boolean]] @false@ Make separators 'soft' when the value folowing it is empty.
Soft separators are hidden by default and shown grayed when the field is selected.
--Examples--
......
......@@ -55,7 +55,8 @@ The rest of the properties depend on the type of [[type:field]] this style is fo
! Type Property Type Default Description
| @"text"@ @font@ [[type:font]] ''Required'' Font to render the text.
| ^^^ @symbol font@ [[type:symbol font]] Font to render symbols in the text (optional).
| ^^^ @always symbol@ [[type:boolean]] @false@ Should all text be rendered with symbols?<br/>If set, @font@ is not needed.
| ^^^ @always symbol@ [[type:boolean]] @false@ Should all text be rendered with symbols?<br/>
Text that is not supported by the symbol font is still rendered as normal text.
| ^^^ @allow formating@ [[type:boolean]] @true@ Is custom formating (bold, italic) allowed?
| ^^^ @alignment@ [[type:scriptable]] [[type:alignment]] @top left@ Alignment of the text.
| ^^^ @angle@ [[type:scriptable]] [[type:int]] @0@ Rotation of the text inside the box, in degrees.
......@@ -72,6 +73,9 @@ The rest of the properties depend on the type of [[type:field]] this style is fo
A line height of @0@ means all lines are in the same position, @1@ is normal behaviour, @2@ skips a line, etc.
| ^^^ @line height hard@ [[type:double]] @1@ Multiplier for the line height of 'hard' line breaks. These are breaks caused by the enter key.
| ^^^ @line height line@ [[type:double]] @1@ Multiplier for the line height of 'soft' line breaks. These are breaks caused by @"<line>\n</line>"@ tags.
| ^^^ @line height soft max@ ^^^ ''disabled'' When there is still vertical room in the text box, increase the line heights to at most these values to spread the text more evenly.
| ^^^ @line height hard max@ ^^^ ^^^ ^^^
| ^^^ @line height line max@ ^^^ ^^^ ^^^
| ^^^ @mask@ [[type:filename]] ''none'' A mask that indicates where in the box text can be placed.<br/>
Text is never put in black areas of the box:<br/>
<img src="style-text-mask.png" alt=""/>
......
......@@ -36,6 +36,8 @@ This is written as the character with code 1 in files.
| @"<sep>"@ A separator between fields. This tag is automatically inserted by the [[fun:combined_editor]] function.<br/>
Inserting this tag manually will confuse that function!<br/>
This tag can never be selected, and its contents can not be edited.
| @"<prefix>"@ At the beginning of a string, indicates a part that can not be selected. This tag is automatically inserted by the [[fun:combined_editor]] function.
| @"<suffix>"@ At the end of a string, indicates a part that can not be selected. This tag is automatically inserted by the [[fun:combined_editor]] function.
| @"<sep-soft>"@ Like @"<sep>"@, only hidden. This is inserted by [[fun:combined_editor]]
| @"<soft>"@ Text who's width is ignored for alignment, similair to @"<sep-soft>"@, but not a separator.
| @"<word-list-?>"@ Indicate that the text inside the tag should be selected from a [[type:word list]].
......
......@@ -13,6 +13,7 @@
#include <data/keyword.hpp>
#include <data/field.hpp>
#include <data/field/text.hpp> // for 0.2.7 fix
#include <data/field/information.hpp>
#include <util/tagged_string.hpp> // for 0.2.7 fix
#include <util/order_cache.hpp>
#include <script/script_manager.hpp>
......@@ -106,12 +107,13 @@ String Set::identification() const {
return v->toString();
}
}
// otherwise the first field
if (!data.empty()) {
return data.at(0)->toString();
} else {
return wxEmptyString;
// otherwise the first non-information field
FOR_EACH_CONST(v, data) {
if (!dynamic_pointer_cast<InfoValue>(v)) {
return v->toString();
}
}
return wxEmptyString;
}
......
......@@ -215,6 +215,46 @@ next_symbol:;
}
}
size_t SymbolFont::recognizePrefix(const String& text, size_t start) const {
size_t pos;
for (pos = start ; pos < text.size() ; ) {
// 1. check merged numbers
if (merge_numbers && pos + 1 < text.size()) {
size_t num_count = text.find_first_not_of(_("0123456789"), pos) - pos;
if (num_count >= 2) {
pos += num_count;
goto next_symbol;
}
}
// 2. check symbol list
FOR_EACH_CONST(sym, symbols) {
if (!sym->code.empty() && sym->enabled && is_substr(text, pos, sym->code)) { // symbol matches
pos += sym->code.size();
goto next_symbol; // continue two levels
}
}
// 3. draw multiple together as text?
if (!as_text.empty()) {
if (!as_text_r.IsValid()) {
as_text_r.Compile(_("^") + as_text, wxRE_ADVANCED);
}
if (as_text_r.IsValid()) {
if (as_text_r.Matches(text.substr(pos))) {
size_t start, len;
if (as_text_r.GetMatch(&start,&len) && start == 0) {
pos += len;
goto next_symbol;
}
}
}
}
// 4. failed
break;
next_symbol:;
}
return pos - start;
}
SymbolInFont* SymbolFont::defaultSymbol() const {
FOR_EACH_CONST(sym, symbols) {
if (sym->code.empty() && sym->enabled) return sym.get();
......
......@@ -51,6 +51,9 @@ class SymbolFont : public Packaged {
/// Split a string into separate symbols for drawing and for determining their size
void split(const String& text, SplitSymbols& out) const;
/// How many consecutive characters of the text, starting at start can be rendered with this symbol font?
size_t recognizePrefix(const String& text, size_t start) const;
/// Draw a piece of text
void draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, const String& text);
/// Get information on characters in a string
......
......@@ -956,6 +956,7 @@ void TextValueEditor::replaceSelection(const String& replacement, const String&
void TextValueEditor::tryAutoReplace() {
size_t end = selection_start_i;
GameSettings& gs = settings.gameSettingsFor(*getSet().game);
if (!gs.use_auto_replace) return;
FOR_EACH(ar, gs.auto_replaces) {
if (ar->enabled && ar->match.size() <= end) {
size_t start = end - ar->match.size();
......
......@@ -75,6 +75,7 @@ struct TextElementsFromString {
int soft, kwpph, param, line, soft_line;
int code, code_kw, code_string, param_ref, error;
int param_id;
/// put angle brackets around the text?
bool bracket;
TextElementsFromString()
......@@ -86,10 +87,15 @@ struct TextElementsFromString {
void fromString(TextElements& te, const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) {
te.elements.clear();
end = min(end, text.size());
size_t text_start = start;
// for each character...
for (size_t pos = start ; pos < end ; ) {
Char c = text.GetChar(pos);
if (c == _('<')) {
if (text_start < pos) {
// text element before this tag?
addText(te, text, text_start, pos, style, ctx);
}
size_t tag_start = pos;
pos = skip_tag(text, tag_start);
if (is_substr(text, tag_start, _( "<b"))) bold += 1;
......@@ -132,39 +138,80 @@ struct TextElementsFromString {
else if (is_substr(text, tag_start, _("<atom"))) {
// 'atomic' indicator
size_t end_tag = min(end, match_close_tag(text, tag_start));
intrusive_ptr<AtomTextElement> e(new AtomTextElement(text, pos, end_tag));
intrusive_ptr<AtomTextElement> e(new AtomTextElement(pos, end_tag));
fromString(e->elements, text, pos, end_tag, style, ctx);
te.elements.push_back(e);
pos = skip_tag(text, end_tag);
} else if (is_substr(text, tag_start, _( "<error"))) {
// error indicator
size_t end_tag = min(end, match_close_tag(text, tag_start));
intrusive_ptr<ErrorTextElement> e(new ErrorTextElement(text, pos, end_tag));
intrusive_ptr<ErrorTextElement> e(new ErrorTextElement(pos, end_tag));
fromString(e->elements, text, pos, end_tag, style, ctx);
te.elements.push_back(e);
pos = skip_tag(text, end_tag);
} else {
// ignore other tags
}
text_start = pos;
} else {
c = untag_char(c); // unescape
// A character of normal text, add to the last text element (if possible)
SimpleTextElement* e = nullptr;
if (!te.elements.empty()) e = dynamic_cast<SimpleTextElement*>(te.elements.back().get());
if (e && e->end == (bracket ? pos + 1 : pos)) {
e->end = bracket ? pos + 2 : pos + 1; // just move the end, no need to make a new element
e->content += c;
if (bracket) {
// content is "<somethin>g" should be "<something>"
swap(e->content[e->content.size() - 2], e->content[e->content.size() - 1]);
pos += 1;
}
} else {
// add a new element for this text
}
if (text_start < end) {
addText(te, text, text_start, end, style, ctx);
}
}
private:
/// Create a text element for a piece of text
void addText(TextElements& te, const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) {
String content = untag(text.substr(start, end - start));
assert(content.size() == end-start);
// use symbol font?
if (symbol > 0 && style.symbol_font.valid()) {
e = new SymbolTextElement(text, pos, pos + 1, style.symbol_font, &ctx);
bracket = false;
te.elements.push_back(new_intrusive5<SymbolTextElement>(content, start, end, style.symbol_font, &ctx));
} else {
// text, possibly mixed with symbols
DrawWhat what = soft > 0 ? DRAW_ACTIVE : DRAW_NORMAL;
LineBreak line_break = line > 0 ? BREAK_LINE :
soft_line > 0 ? BREAK_SOFT : BREAK_HARD;
if (kwpph > 0 || param > 0) {
// bracket the text
content = String(LEFT_ANGLE_BRACKET) + content + RIGHT_ANGLE_BRACKET;
start -= 1;
end += 1;
}
if (style.always_symbol && style.symbol_font.valid()) {
// mixed symbols/text, autodetected by symbol font
size_t text_pos = 0;
size_t pos = 0;
FontP font;
while (pos < end-start) {
if (size_t n = style.symbol_font.font->recognizePrefix(content,pos)) {
// at 'pos' there are n symbol font characters
if (text_pos < pos) {
// text before it?
if (!font) font = makeFont(style);
te.elements.push_back(new_intrusive6<FontTextElement>(content.substr(text_pos, pos-text_pos), start+text_pos, start+pos, font, what, line_break));
}
te.elements.push_back(new_intrusive5<SymbolTextElement>(content.substr(pos,n), start+pos, start+pos+n, style.symbol_font, &ctx));
text_pos = pos += n;
} else {
++pos;
}
}
if (text_pos < pos) {
if (!font) font = makeFont(style);
te.elements.push_back(new_intrusive6<FontTextElement>(content.substr(text_pos), start+text_pos, end, font, what, line_break));
}
} else {
FontP font = style.font.make(
te.elements.push_back(new_intrusive6<FontTextElement>(content, start, end, makeFont(style), what, line_break));
}
}
}
FontP makeFont(const TextStyle& style) {
return style.font.make(
(bold > 0 ? FONT_BOLD : FONT_NORMAL) |
(italic > 0 ? FONT_ITALIC : FONT_NORMAL) |
(soft > 0 ? FONT_SOFT : FONT_NORMAL) |
......@@ -175,26 +222,6 @@ struct TextElementsFromString {
param > 0 || param_ref > 0
? &param_colors[(param_id++) % param_colors_count]
: nullptr);
bracket = kwpph > 0 || param > 0;
e = new FontTextElement(
text,
bracket ? pos - 1 : pos,
bracket ? pos + 2 : pos + 1,
font,
soft > 0 ? DRAW_ACTIVE : DRAW_NORMAL,
line > 0 ? BREAK_LINE :
soft_line > 0 ? BREAK_SOFT : BREAK_HARD);
}
if (bracket) {
e->content = String(LEFT_ANGLE_BRACKET) + c + RIGHT_ANGLE_BRACKET;
} else {
e->content = c;
}
te.elements.push_back(TextElementP(e));
}
pos += 1;
}
}
}
};
......
......@@ -57,12 +57,10 @@ struct CharInfo {
/// A section of text that can be rendered using a TextViewer
class TextElement : public IntrusivePtrBase<TextElement> {
public:
/// The text of which a subsection is drawn
String text;
/// What section of the input string is this element?
size_t start, end;
inline TextElement(const String& text, size_t start ,size_t end) : text(text), start(start), end(end) {}
inline TextElement(size_t start ,size_t end) : start(start), end(end) {}
virtual ~TextElement() {}
/// Draw a subsection section of the text in the given rectangle
......@@ -109,8 +107,8 @@ class TextElements {
/// A text element that just shows text
class SimpleTextElement : public TextElement {
public:
SimpleTextElement(const String& text, size_t start, size_t end)
: TextElement(text, start, end), content(text.substr(start,end-start))
SimpleTextElement(const String& content, size_t start, size_t end)
: TextElement(start, end), content(content)
{}
String content; ///< Text to show
};
......@@ -118,8 +116,8 @@ class SimpleTextElement : public TextElement {
/// A text element that uses a normal font
class FontTextElement : public SimpleTextElement {
public:
FontTextElement(const String& text, size_t start, size_t end, const FontP& font, DrawWhat draw_as, LineBreak break_style)
: SimpleTextElement(text, start, end)
FontTextElement(const String& content, size_t start, size_t end, const FontP& font, DrawWhat draw_as, LineBreak break_style)
: SimpleTextElement(content, start, end)
, font(font), draw_as(draw_as), break_style(break_style)
{}
......@@ -136,8 +134,8 @@ class FontTextElement : public SimpleTextElement {
/// A text element that uses a symbol font
class SymbolTextElement : public SimpleTextElement {
public:
SymbolTextElement(const String& text, size_t start, size_t end, const SymbolFontRef& font, Context* ctx)
: SimpleTextElement(text, start, end)
SymbolTextElement(const String& content, size_t start, size_t end, const SymbolFontRef& font, Context* ctx)
: SimpleTextElement(content, start, end)
, font(font), ctx(*ctx)
{}
......@@ -155,7 +153,7 @@ class SymbolTextElement : public SimpleTextElement {
/// A TextElement consisting of sub elements
class CompoundTextElement : public TextElement {
public:
CompoundTextElement(const String& text, size_t start, size_t end) : TextElement(text, start, end) {}
CompoundTextElement(size_t start, size_t end) : TextElement(start, end) {}
virtual void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const;
virtual void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const;
......@@ -168,7 +166,7 @@ class CompoundTextElement : public TextElement {
/// A TextElement drawn using a grey background
class AtomTextElement : public CompoundTextElement {
public:
AtomTextElement(const String& text, size_t start, size_t end) : CompoundTextElement(text, start, end) {}
AtomTextElement(size_t start, size_t end) : CompoundTextElement(start, end) {}
virtual void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const;
};
......@@ -176,23 +174,10 @@ class AtomTextElement : public CompoundTextElement {
/// A TextElement drawn using a red wavy underline
class ErrorTextElement : public CompoundTextElement {
public:
ErrorTextElement(const String& text, size_t start, size_t end) : CompoundTextElement(text, start, end) {}
ErrorTextElement(size_t start, size_t end) : CompoundTextElement(start, end) {}
virtual void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const;
};
// ----------------------------------------------------------------------------- : Other text elements
/*
/// A text element that displays a horizontal separator line
class HorizontalLineTextElement : public TextElement {
public:
HorizontalLineTextElement(const String& text, size_t start ,size_t end) : TextElement(text, start, end) {}
virtual void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const;
virtual void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const;
virtual double minScale() const;
};
*/
// ----------------------------------------------------------------------------- : EOF
#endif
......@@ -328,12 +328,7 @@ void TextViewer::setExactScrollPosition(double pos) {
// ----------------------------------------------------------------------------- : Elements
void TextViewer::prepareElements(const String& text, const TextStyle& style, Context& ctx) {
if (style.always_symbol) {
elements.elements.clear();
elements.elements.push_back(new_intrusive5<SymbolTextElement>(text, 0, text.size(), style.symbol_font, &ctx));
} else {
elements.fromString(text, 0, text.size(), style, ctx);
}
}
......
......@@ -52,10 +52,21 @@ SCRIPT_FUNCTION_WITH_DEP(combined_editor) {
if (separators.size() < values.size() - 1) {
throw ScriptError(String::Format(_("Not enough separators for combine_editor, expected %d"), values.size()-1));
}
// split the value
// the value
SCRIPT_PARAM(String, value);
// remove suffix/prefix
SCRIPT_OPTIONAL_PARAM_(String, prefix);
SCRIPT_OPTIONAL_PARAM_(String, suffix);
if (is_substr(value,0,_("<prefix"))) {
value = value.substr(min(value.size(), match_close_tag_end(value, 0)));
}
size_t pos = value.rfind(_("<suffix"));
if (pos != String::npos && match_close_tag_end(value,pos) >= value.size()) {
value = value.substr(0, pos);
}
// split the value
vector<pair<String,bool> > value_parts; // (value part, is empty)
size_t pos = value.find(_("<sep"));
pos = value.find(_("<sep"));
while (pos != String::npos) {
String part = value.substr(0, pos);
value_parts.push_back(make_pair(part, false));
......@@ -81,18 +92,48 @@ SCRIPT_FUNCTION_WITH_DEP(combined_editor) {
// recombine the parts
String new_value = value_parts.front().first;
bool new_value_empty = value_parts.front().second;
size_t size_before_last = 0;
for (size_t i = 1 ; i < value_parts.size() ; ++i) {
size_before_last = new_value.size();
if (value_parts[i].second && new_value_empty && hide_when_empty) {
// no separator
} else if (value_parts[i].second && soft_before_empty) {
// soft separator
new_value += _("<sep-soft>") + separators[i - 1] + _("</sep-soft>");
new_value_empty = false;
} else {
// normal separator
new_value += _("<sep>") + separators[i - 1] + _("</sep>");
new_value_empty = false;
}
new_value += value_parts[i].first;
}
if (!new_value_empty || !hide_when_empty) {
if (!suffix.empty()) {
if (is_substr(new_value, size_before_last, _("<sep-soft>")) && value_parts.size() >= 2) {
// If the value ends in a soft separator, we have this situation:
// [blah]<sep-soft>ABC</sep-soft><suffix>XYZ</suffix>
// This renderes as:
// [blah] XYZ
// Which looks bad, so instead change the text to
// [blah]<sep>XYZ<soft>ABC</soft></sep>
// Which might be slightly incorrect, but soft text doesn't matter anyway.
size_t after = min(new_value.size(), match_close_tag_end(new_value, size_before_last));
new_value = new_value.substr(0, size_before_last)
+ _("<sep>")
+ suffix
+ _("<soft>")
+ separators[value_parts.size() - 2]
+ _("</soft></sep>")
+ new_value.substr(after);
} else {
new_value += _("<suffix>") + suffix + _("</suffix>");
}
}
if (!prefix.empty()) {
new_value = _("<prefix>") + prefix + _("</prefix>") + new_value;
}
}
SCRIPT_RETURN(new_value);
}
......
......@@ -239,7 +239,14 @@ void in_place_sort(const String& spec, String& input, String& ret) {
String spec_sort(const String& spec, String& input, String& ret) {
SpecIterator it(spec);
while(it.nextUntil(0)) {
if (it.value == _('<')) { // keep only a single copy
if (it.escaped) { // single character, escaped
FOR_EACH(d, input) {
if (d == it.value) {
ret += d;
d = REMOVED;
}
}
} else if (it.value == _('<')) { // keep only a single copy
while (it.nextUntil(_('>'))) {
size_t pos = input.find_first_of(it.value);
if (pos != String::npos) {
......
......@@ -249,6 +249,13 @@ size_t index_to_cursor(const String& str, size_t index, Movement dir) {
}
}
i = after;
} else if (i == 0 && is_substr(str, i, _("<prefix"))) {
// prefix at start of string, skip contents
i = match_close_tag_end(str, i);
has_width = false;
} else if (is_substr(str, i, _("<suffix")) && match_close_tag_end(str,i) >= str.size()) {
// suffix at end of string
break;
} else {
i = skip_tag(str, i);
has_width = false;
......@@ -268,7 +275,8 @@ void cursor_to_index_range(const String& str, size_t cursor, size_t& start, size
start = end = 0;
size_t cur = 0;
size_t i = 0;
while (cur <= cursor && i < str.size()) {
size_t size = str.size(); // can be changed by <suffix> tags
while (cur <= cursor && i < size) {
Char c = str.GetChar(i);
bool has_width = true;
if (c == _('<')) {
......@@ -278,6 +286,14 @@ void cursor_to_index_range(const String& str, size_t cursor, size_t& start, size
if (cur >= cursor) { ++i; break; }
// skip tag contents, tag counts as a single 'character'
i = match_close_tag_end(str, i);
} else if (i == 0 && is_substr(str, i, _("<prefix"))) {
// prefix at start of string, skip contents, index never before
start = i = match_close_tag_end(str,i);
has_width = false;
} else if (is_substr(str, i, _("<suffix")) && match_close_tag_end(str,i) >= str.size()) {
// suffix at start of string, skip contents
size = i;
has_width = false;
} else {
i = skip_tag(str, i);
has_width = false;
......@@ -290,8 +306,8 @@ void cursor_to_index_range(const String& str, size_t cursor, size_t& start, size
if (cur == cursor) start = i;
}
}
end = min(i, str.size());
if (cur < cursor) start = end = str.size();
end = min(i, size);
if (cur < cursor) start = end = size;
if (end <= start) end = start + 1;
}
......@@ -336,6 +352,12 @@ String untag_for_cursor(const String& str) {
} else if (is_substr(str, i, _("<sep"))) {
i = match_close_tag_end(str, i);
ret += _('\3'); // use a random character here
} else if (i == 0 && is_substr(str, i, _("<prefix"))) {
// prefix at start of string, skip contents, index never before
i = match_close_tag_end(str,i);
} else if (is_substr(str, i, _("<suffix")) && match_close_tag_end(str,i) >= str.size()) {
// suffix at start of string, skip contents
i = str.size();
} else {
i = skip_tag(str, i);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment