add font picker, color picker and bullet point ui tools
|
After Width: | Height: | Size: 578 B |
|
After Width: | Height: | Size: 734 B |
|
After Width: | Height: | Size: 1019 B |
|
After Width: | Height: | Size: 587 B |
|
After Width: | Height: | Size: 754 B |
|
After Width: | Height: | Size: 797 B |
|
After Width: | Height: | Size: 767 B |
|
After Width: | Height: | Size: 944 B |
|
After Width: | Height: | Size: 771 B |
|
After Width: | Height: | Size: 747 B |
|
After Width: | Height: | Size: 903 B |
@@ -27,6 +27,10 @@ tool/export_image IMAGE "tool/export_image.png"
|
||||
tool/export_images IMAGE "tool/export_images.png"
|
||||
tool/export_mws IMAGE "tool/export_mws.png"
|
||||
tool/export_apprentice IMAGE "tool/export_apprentice.png"
|
||||
tool/reload_data IMAGE "tool/reload_data.png"
|
||||
tool/dark_reload_data IMAGE "tool/dark_reload_data.png"
|
||||
tool/check_updates IMAGE "tool/check_updates.png"
|
||||
tool/dark_check_updates IMAGE "tool/dark_check_updates.png"
|
||||
tool/print IMAGE "tool/print.png"
|
||||
tool/print_preview IMAGE "tool/print_preview.png"
|
||||
|
||||
@@ -40,7 +44,11 @@ tool/copy IMAGE "tool/copy.png"
|
||||
tool/paste IMAGE "tool/paste.png"
|
||||
tool/find IMAGE "tool/find.png"
|
||||
tool/dark_find IMAGE "tool/dark_find.png"
|
||||
tool/preferences IMAGE "tool/preferences.png"
|
||||
tool/dark_preferences IMAGE "tool/dark_preferences.png"
|
||||
|
||||
tool/font IMAGE "tool/font.png"
|
||||
tool/dark_font IMAGE "tool/dark_font.png"
|
||||
tool/bold IMAGE "tool/bold.png"
|
||||
tool/dark_bold IMAGE "tool/dark_bold.png"
|
||||
tool/italic IMAGE "tool/italic.png"
|
||||
@@ -49,6 +57,9 @@ tool/underline IMAGE "tool/underline.png"
|
||||
tool/dark_underline IMAGE "tool/dark_underline.png"
|
||||
tool/strikethrough IMAGE "tool/strikethrough.png"
|
||||
tool/dark_strikethrough IMAGE "tool/dark_strikethrough.png"
|
||||
tool/color_text IMAGE "tool/color_text.png"
|
||||
tool/bullet_point IMAGE "tool/bullet_point.png"
|
||||
tool/dark_bullet_point IMAGE "tool/dark_bullet_point.png"
|
||||
tool/symbol IMAGE "tool/symbol.png"
|
||||
tool/dark_symbol IMAGE "tool/dark_symbol.png"
|
||||
tool/reminder IMAGE "tool/reminder.png"
|
||||
|
||||
@@ -102,42 +102,204 @@ TextValue& TextValueAction::value() const {
|
||||
}
|
||||
|
||||
|
||||
unique_ptr<TextValueAction> toggle_format_action(const TextValueP& value, vector<String>& tags, size_t start_i, size_t end_i, size_t start, size_t end, const String& action_name) {
|
||||
if (start > end) {
|
||||
swap(start, end);
|
||||
swap(start_i, end_i);
|
||||
}
|
||||
// Find absolute position of the selection
|
||||
String new_value = value->value();
|
||||
size_t untagged_start_i = to_untagged_pos(new_value, start_i);
|
||||
size_t untagged_end_i = to_untagged_pos(new_value, end_i);
|
||||
int offset = 0;
|
||||
// Compute the changes
|
||||
for (int i = 0; i < tags.size() ; ++i) {
|
||||
const String& tag = tags[i];
|
||||
new_value = tag.Contains(":") ? compute_new_variable_value(new_value, tag, start_i, end_i):
|
||||
tag == _("li") ? compute_new_bullet_value (new_value, offset, start_i, end_i):
|
||||
compute_new_simple_value (new_value, tag, start_i, end_i);
|
||||
// Erase redundant tags
|
||||
if (start != end) {
|
||||
// don't simplify if start == end, this way we insert <b></b>, allowing the
|
||||
// user to press Ctrl+B and start typing bold text
|
||||
new_value = simplify_tagged(new_value);
|
||||
}
|
||||
// Adjust selection
|
||||
start_i = to_tagged_pos(new_value, untagged_start_i, true, false);
|
||||
end_i = to_tagged_pos(new_value, untagged_end_i, true, false);
|
||||
}
|
||||
// Build action
|
||||
if (value->value() == new_value) {
|
||||
return nullptr; // no changes
|
||||
} else {
|
||||
return make_unique<TextValueAction>(value, start+offset, end+offset, end+offset, new_value, action_name);
|
||||
}
|
||||
}
|
||||
unique_ptr<TextValueAction> toggle_format_action(const TextValueP& value, const String& tag, size_t start_i, size_t end_i, size_t start, size_t end, const String& action_name) {
|
||||
if (start > end) {
|
||||
swap(start, end);
|
||||
swap(start_i, end_i);
|
||||
}
|
||||
String new_value;
|
||||
const String& str = value->value();
|
||||
// Are we inside the tag we are toggling?
|
||||
if (!is_in_tag(str, _("<") + tag, start_i, end_i)) {
|
||||
// we are not inside this tag, add it
|
||||
new_value = substr(str, 0, start_i);
|
||||
new_value += _("<") + tag + _(">");
|
||||
new_value += substr(str, start_i, end_i - start_i);
|
||||
new_value += _("</") + tag + _(">");
|
||||
new_value += substr(str, end_i);
|
||||
} else {
|
||||
// we are inside this tag, 'remove' it
|
||||
new_value = substr(str, 0, start_i);
|
||||
new_value += _("</") + tag + _(">");
|
||||
new_value += substr(str, start_i, end_i - start_i);
|
||||
new_value += _("<") + tag + _(">");
|
||||
new_value += substr(str, end_i);
|
||||
}
|
||||
// Build action
|
||||
String new_value = value->value();
|
||||
int offset = 0;
|
||||
// Compute the changes
|
||||
new_value = tag.Contains(":") ? compute_new_variable_value(new_value, tag, start_i, end_i):
|
||||
tag == _("li") ? compute_new_bullet_value (new_value, offset, start_i, end_i):
|
||||
compute_new_simple_value (new_value, tag, start_i, end_i);
|
||||
// Erase redundant tags
|
||||
if (start != end) {
|
||||
// don't simplify if start == end, this way we insert <b></b>, allowing the
|
||||
// user to press Ctrl+B and start typing bold text
|
||||
new_value = simplify_tagged(new_value);
|
||||
}
|
||||
// Build action
|
||||
if (value->value() == new_value) {
|
||||
return nullptr; // no changes
|
||||
} else {
|
||||
return make_unique<TextValueAction>(value, start, end, end, new_value, action_name);
|
||||
return make_unique<TextValueAction>(value, start+offset, end+offset, end+offset, new_value, action_name);
|
||||
}
|
||||
}
|
||||
|
||||
String compute_new_simple_value(const String& str, const String& tag, size_t start_i, size_t end_i) {
|
||||
String prefix(substr(str, 0, start_i));
|
||||
String suffix(substr(str, end_i));
|
||||
String selection(substr(str, start_i, end_i - start_i));
|
||||
// if we are inside the tag we are toggling, 'remove' it
|
||||
if (in_tag(str, _("<") + tag, start_i, end_i) != String::npos) selection = anti_wrap_tag(selection, _("<") + tag + _(">"));
|
||||
// otherwise add it
|
||||
else selection = wrap_tag(selection, _("<") + tag + _(">"));
|
||||
return prefix + selection + suffix;
|
||||
}
|
||||
|
||||
String compute_new_variable_value(const String& str, const String& tag, size_t start_i, size_t end_i) {
|
||||
size_t start_tag = in_tag(str, _("<") + tag, start_i, end_i, true);
|
||||
// if we are inside the tag we are toggling, 'remove' it
|
||||
if (start_tag != String::npos) {
|
||||
String prefix(substr(str, 0, start_i));
|
||||
String suffix(substr(str, end_i));
|
||||
String selection(substr(str, start_i, end_i - start_i));
|
||||
selection = anti_wrap_tag(selection, _("<") + tag + _(">"));
|
||||
return prefix + selection + suffix;
|
||||
}
|
||||
// we are not inside this tag, add it around the selection, and
|
||||
// move all instances of variants of this tag to the sides of the selection
|
||||
|
||||
// first, include surrounding formatting tags to the selection
|
||||
int start_temp = str.find_last_of("<", start_i - 1);
|
||||
while (start_temp != String::npos &&
|
||||
str.GetChar(start_i - 1) == '>' &&
|
||||
is_formatting_tag(str, start_temp)) {
|
||||
start_i = start_temp;
|
||||
start_temp = str.find_last_of("<", start_i - 1);
|
||||
}
|
||||
while (end_i < str.size() &&
|
||||
is_formatting_tag(str, end_i)) {
|
||||
end_i = str.find_first_of(">", end_i) + 1;
|
||||
}
|
||||
String prefix(substr(str, 0, start_i));
|
||||
String suffix(substr(str, end_i));
|
||||
String selection(substr(str, start_i, end_i - start_i));
|
||||
|
||||
// tally open tags that are variants of this tag
|
||||
vector<String> tag_list;
|
||||
map<String, int> tag_map;
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
String tag_variants = _("<") + tag.substr(0, tag.find_first_of(":"));
|
||||
size_t size = tag_variants.size();
|
||||
String temp; temp.reserve(selection.size());
|
||||
for (; end < selection.size() ; ++end) {
|
||||
if (is_substr(selection, end, tag_variants)) {
|
||||
temp += selection.substr(start, end - start);
|
||||
start = end + 1;
|
||||
end = selection.find(">", end + size);
|
||||
String new_tag = selection.substr(start, end+1 - start);
|
||||
if (tag_map.find(new_tag) != tag_map.end()) {
|
||||
tag_map[new_tag] = tag_map[new_tag]+1;
|
||||
}
|
||||
else {
|
||||
tag_map[new_tag] = 1;
|
||||
tag_list.push_back(new_tag);
|
||||
}
|
||||
start = end+1;
|
||||
}
|
||||
}
|
||||
temp += selection.substr(start, end - start);
|
||||
|
||||
// tally close tags
|
||||
selection.Clear(); selection.reserve(temp.size());
|
||||
String cejected_tag = close_tag(tag_variants);
|
||||
size_t csize = size + 1;
|
||||
for (start = 0, end = 0; end < temp.size() ; ++end) {
|
||||
if (is_substr(temp, end, cejected_tag)) {
|
||||
selection += temp.substr(start, end - start);
|
||||
start = end + 2;
|
||||
end = temp.find(">", end + csize);
|
||||
String new_tag = temp.substr(start, end+1 - start);
|
||||
if (tag_map.find(new_tag) != tag_map.end()) {
|
||||
tag_map[new_tag] = tag_map[new_tag]-1;
|
||||
}
|
||||
else {
|
||||
tag_map[new_tag] = -1;
|
||||
tag_list.push_back(new_tag);
|
||||
}
|
||||
start = end+1;
|
||||
}
|
||||
}
|
||||
selection += temp.substr(start, end - start);
|
||||
|
||||
// add the actual tag we are toggling
|
||||
selection = wrap_tag(selection, _("<") + tag + _(">"));
|
||||
|
||||
// add the tallied open and close tags
|
||||
for (int i = 0; i < tag_list.size() ; ++i) {
|
||||
String& new_tag = tag_list[i];
|
||||
int count = tag_map[new_tag];
|
||||
if (count > 0) {
|
||||
selection = selection + _("<") + new_tag;
|
||||
}
|
||||
else if (count < 0) {
|
||||
selection = _("</") + new_tag + selection;
|
||||
}
|
||||
}
|
||||
|
||||
// add the stuff around the selection
|
||||
return prefix + selection + suffix;
|
||||
}
|
||||
|
||||
String compute_new_bullet_value(const String& str, int& offset, size_t start_i, size_t end_i) {
|
||||
// Are we inside the tag we are toggling?
|
||||
size_t start_tag = in_tag(str, _("<li"), start_i, end_i);
|
||||
if (start_tag == String::npos) {
|
||||
// we are not inside this tag, add it
|
||||
// first, expand the selection to the end of the line
|
||||
end_i = min(str.find(_("<li"), end_i),
|
||||
min(str.find(_("<sep"), end_i),
|
||||
min(str.find(_("<suffix"), end_i),
|
||||
min(str.find(_("\n"), end_i),
|
||||
min(str.find(_("<line"), end_i),
|
||||
str.find(_("<soft-line"), end_i))))));
|
||||
String prefix(substr(str, 0, start_i));
|
||||
String suffix = end_i == String::npos ? String() : substr(str, end_i);
|
||||
String selection(substr(str, start_i, end_i - start_i));
|
||||
selection = _("<li><bullet>• </bullet>") + selection + _("</li>");
|
||||
offset += 1;
|
||||
return prefix + selection + suffix;
|
||||
}
|
||||
// we are inside this tag, 'remove' it
|
||||
// first, expand the selection to include the tags
|
||||
start_i = start_tag;
|
||||
end_i = str.find(_("</li>"), start_tag);
|
||||
if (end_i != String::npos) end_i += 5;
|
||||
String prefix(substr(str, 0, start_i));
|
||||
String suffix = end_i == String::npos ? String() : substr(str, end_i);
|
||||
String selection(substr(str, start_i, end_i - start_i));
|
||||
selection = remove_tag(remove_tag_contents(selection, _("<bullet>")), _("<bullet>"));
|
||||
selection = remove_tag(selection, _("<li>"));
|
||||
offset -= 1;
|
||||
return prefix + selection + suffix;
|
||||
}
|
||||
|
||||
unique_ptr<TextValueAction> typing_action(const TextValueP& value, size_t start_i, size_t end_i, size_t start, size_t end, const String& replacement, const String& action_name) {
|
||||
bool reverse = start > end;
|
||||
if (reverse) {
|
||||
|
||||
@@ -127,8 +127,16 @@ private:
|
||||
String name;
|
||||
};
|
||||
|
||||
/// Action for toggling some formating tag on or off in some range
|
||||
unique_ptr<TextValueAction> toggle_format_action(const TextValueP& value, const String& tag, size_t start_i, size_t end_i, size_t start, size_t end, const String& action_name);
|
||||
/// Action for toggling some formating tag(s) on or off in some range.
|
||||
unique_ptr<TextValueAction> toggle_format_action(const TextValueP& value, vector<String>& tags, size_t start_i, size_t end_i, size_t start, size_t end, const String& action_name);
|
||||
unique_ptr<TextValueAction> toggle_format_action(const TextValueP& value, const String& tag, size_t start_i, size_t end_i, size_t start, size_t end, const String& action_name);
|
||||
|
||||
/// New value obtained by toggling a simple tag (<b>, <i>, etc...)
|
||||
String compute_new_simple_value(const String& str, const String& tag, size_t start_i, size_t end_i);
|
||||
/// New value obtained by toggling a tag that has variations (<color:#000000>, <color:#FFFFFF>, etc...)
|
||||
String compute_new_variable_value(const String& str, const String& tag, size_t start_i, size_t end_i);
|
||||
/// New value obtained by toggling a bullet point tag (<li>)
|
||||
String compute_new_bullet_value(const String& str, int& offset, size_t start_i, size_t end_i);
|
||||
|
||||
/// Typing in a TextValue, replace the selection [start...end) with replacement
|
||||
/** start and end are cursor positions, start_i and end_i are indices*/
|
||||
|
||||
@@ -183,7 +183,7 @@ wxFont Font::toWxFont(double scale) const {
|
||||
font.SetPointSize(size > 1 ? size_i : int(scale * font.GetPointSize()));
|
||||
if (strikethrough()) font.MakeStrikethrough();
|
||||
return font;
|
||||
} else if (flags & FONT_ITALIC && !italic_name().empty()) {
|
||||
} else if (!(flags & FONT_FROM_TAG) && (flags & FONT_ITALIC) && !italic_name().empty()) {
|
||||
font = wxFont(size_i, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, weight_i, underline(), italic_name());
|
||||
} else {
|
||||
String familyName = name();
|
||||
|
||||
@@ -27,6 +27,7 @@ enum FontFlags
|
||||
, FONT_CODE_STRING = 0x20 // syntax highlighting
|
||||
, FONT_CODE_NUMBER = 0x40 // syntax highlighting
|
||||
, FONT_CODE_OPER = 0x80 // syntax highlighting
|
||||
, FONT_FROM_TAG = 0x100 // this font is defined by a markup tag
|
||||
};
|
||||
|
||||
/// A reference to a font for rendering text
|
||||
|
||||
@@ -124,6 +124,7 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(GameSettings) {
|
||||
REFLECT(pack_amounts);
|
||||
REFLECT(pack_seed_random);
|
||||
REFLECT(pack_seed);
|
||||
REFLECT(custom_colors);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -85,6 +85,8 @@ public:
|
||||
map<String, int> pack_amounts;
|
||||
bool pack_seed_random;
|
||||
int pack_seed;
|
||||
vector<Color> custom_colors;
|
||||
static const size_t max_custom_colors = 16; // store this many custom colors for the color picker
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
private:
|
||||
|
||||
@@ -151,12 +151,15 @@ CardsPanel::CardsPanel(Window* parent, int id)
|
||||
add_menu_item_tr(menuCard, ID_SELECT_COLUMNS, nullptr, "card_list_columns");
|
||||
|
||||
menuFormat = new wxMenu();
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_FONT, settings.darkModePrefix() + "font", "font", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_STRIKETHROUGH, settings.darkModePrefix() + "strikethrough", "strikethrough", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_SYMBOL, settings.darkModePrefix() + "symbol", "symbols", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_text", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_COLOR, "color_text", "color_text", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_BULLETPOINT, settings.darkModePrefix() + "bullet_point", "bullet_point", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_SYMBOL, settings.darkModePrefix() + "symbol", "symbols", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_text", wxITEM_CHECK);
|
||||
menuFormat->AppendSeparator();
|
||||
insertSymbolMenu = new wxMenuItem(menuFormat, ID_INSERT_SYMBOL, _MENU_("insert symbol"));
|
||||
menuFormat->Append(insertSymbolMenu);
|
||||
@@ -285,12 +288,15 @@ wxMenu* CardsPanel::makeAddCardsSubmenu(bool add_single_card_option) {
|
||||
|
||||
void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
// Toolbar
|
||||
add_tool_tr(tb, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_FONT, settings.darkModePrefix() + "font", "font", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_STRIKETHROUGH, settings.darkModePrefix() + "strikethrough", "strikethrough", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_SYMBOL, settings.darkModePrefix() + "symbol", "symbols", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_text", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_COLOR, "color_text", "color_text", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_BULLETPOINT, settings.darkModePrefix() + "bullet_point", "bullet_point", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_SYMBOL, settings.darkModePrefix() + "symbol", "symbols", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_text", false, wxITEM_CHECK);
|
||||
tb->AddSeparator();
|
||||
toolAddCard = add_tool_tr(tb, ID_CARD_ADD, "card_add", "add_card", false, wxITEM_DROPDOWN);
|
||||
tb->SetDropdownMenu(ID_CARD_ADD, makeAddCardsSubmenu(true));
|
||||
@@ -321,10 +327,13 @@ void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
|
||||
void CardsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
// Toolbar
|
||||
tb->DeleteTool(ID_FORMAT_FONT);
|
||||
tb->DeleteTool(ID_FORMAT_BOLD);
|
||||
tb->DeleteTool(ID_FORMAT_ITALIC);
|
||||
tb->DeleteTool(ID_FORMAT_UNDERLINE);
|
||||
tb->DeleteTool(ID_FORMAT_STRIKETHROUGH);
|
||||
tb->DeleteTool(ID_FORMAT_COLOR);
|
||||
tb->DeleteTool(ID_FORMAT_BULLETPOINT);
|
||||
tb->DeleteTool(ID_FORMAT_SYMBOL);
|
||||
tb->DeleteTool(ID_FORMAT_REMINDER);
|
||||
tb->DeleteTool(ID_CARD_ADD);
|
||||
@@ -367,7 +376,8 @@ void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
case ID_CARD_REMOVE: ev.Enable(card_list->canDelete()); break;
|
||||
case ID_CARD_LINK: ev.Enable(card_list->canLink()); break;
|
||||
case ID_CARD_AND_LINK_COPY: ev.Enable(card_list->canCopy()); break;
|
||||
case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE: case ID_FORMAT_STRIKETHROUGH: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
|
||||
case ID_FORMAT_FONT: case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE: case ID_FORMAT_STRIKETHROUGH:
|
||||
case ID_FORMAT_COLOR: case ID_FORMAT_BULLETPOINT: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
|
||||
if (focused_control(this) == ID_EDITOR) {
|
||||
ev.Enable(editor->canFormat(ev.GetId()));
|
||||
ev.Check (editor->hasFormat(ev.GetId()));
|
||||
@@ -474,8 +484,10 @@ void CardsPanel::onCommand(int id) {
|
||||
}
|
||||
case ID_SELECT_COLUMNS: {
|
||||
card_list->selectColumns();
|
||||
break;
|
||||
}
|
||||
case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE: case ID_FORMAT_STRIKETHROUGH: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
|
||||
case ID_FORMAT_FONT: case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE: case ID_FORMAT_STRIKETHROUGH:
|
||||
case ID_FORMAT_COLOR: case ID_FORMAT_BULLETPOINT: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
|
||||
if (focused_control(this) == ID_EDITOR) {
|
||||
editor->doFormat(id);
|
||||
}
|
||||
|
||||
@@ -34,21 +34,27 @@ void SetInfoPanel::onChangeSet() {
|
||||
|
||||
void SetInfoPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
// Toolbar
|
||||
add_tool_tr(tb, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_FONT, settings.darkModePrefix() + "font", "font", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_STRIKETHROUGH, settings.darkModePrefix() + "strikethrough", "strikethrough", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_SYMBOL, settings.darkModePrefix() + "symbol", "symbols", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_text", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_COLOR, "color_text", "color_text", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_BULLETPOINT, settings.darkModePrefix() + "bullet_point", "bullet_point", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_SYMBOL, settings.darkModePrefix() + "symbol", "symbols", false, wxITEM_CHECK);
|
||||
add_tool_tr(tb, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_text", false, wxITEM_CHECK);
|
||||
tb->Realize();
|
||||
// Menus
|
||||
auto menuFormat = new wxMenu();
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_FONT, settings.darkModePrefix() + "font", "font", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_STRIKETHROUGH, settings.darkModePrefix() + "strikethrough", "strikethrough", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_SYMBOL, settings.darkModePrefix() + "symbol", "symbols", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_text", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_COLOR, "color_text", "color_text", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_BULLETPOINT, settings.darkModePrefix() + "bullet_point", "bullet_point", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_SYMBOL, settings.darkModePrefix() + "symbol", "symbols", wxITEM_CHECK);
|
||||
add_menu_item_tr(menuFormat, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_text", wxITEM_CHECK);
|
||||
mb->Insert(2, menuFormat, _MENU_("format"));
|
||||
// focus on editor
|
||||
editor->SetFocus();
|
||||
@@ -56,10 +62,13 @@ void SetInfoPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
|
||||
void SetInfoPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
// Toolbar
|
||||
tb->DeleteTool(ID_FORMAT_FONT);
|
||||
tb->DeleteTool(ID_FORMAT_BOLD);
|
||||
tb->DeleteTool(ID_FORMAT_ITALIC);
|
||||
tb->DeleteTool(ID_FORMAT_UNDERLINE);
|
||||
tb->DeleteTool(ID_FORMAT_STRIKETHROUGH);
|
||||
tb->DeleteTool(ID_FORMAT_COLOR);
|
||||
tb->DeleteTool(ID_FORMAT_BULLETPOINT);
|
||||
tb->DeleteTool(ID_FORMAT_SYMBOL);
|
||||
tb->DeleteTool(ID_FORMAT_REMINDER);
|
||||
// Menus
|
||||
@@ -68,7 +77,8 @@ void SetInfoPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
|
||||
|
||||
void SetInfoPanel::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
switch (ev.GetId()) {
|
||||
case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE: case ID_FORMAT_STRIKETHROUGH: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
|
||||
case ID_FORMAT_FONT: case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE: case ID_FORMAT_STRIKETHROUGH:
|
||||
case ID_FORMAT_COLOR: case ID_FORMAT_BULLETPOINT: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
|
||||
ev.Enable(editor->canFormat(ev.GetId()));
|
||||
ev.Check (editor->hasFormat(ev.GetId()));
|
||||
break;
|
||||
@@ -78,7 +88,8 @@ void SetInfoPanel::onUpdateUI(wxUpdateUIEvent& ev) {
|
||||
|
||||
void SetInfoPanel::onCommand(int id) {
|
||||
switch (id) {
|
||||
case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE: case ID_FORMAT_STRIKETHROUGH: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
|
||||
case ID_FORMAT_FONT: case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE: case ID_FORMAT_STRIKETHROUGH:
|
||||
case ID_FORMAT_COLOR: case ID_FORMAT_BULLETPOINT: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
|
||||
editor->doFormat(id);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -58,17 +58,17 @@ SetWindow::SetWindow(Window* parent, const SetP& set)
|
||||
add_menu_item_tr(menuFile, ID_FILE_NEW, "new", "new_set");
|
||||
add_menu_item_tr(menuFile, ID_FILE_OPEN, "open", "open_set");
|
||||
add_menu_item_tr(menuFile, ID_FILE_SAVE, "save", "save_set");
|
||||
add_menu_item_tr(menuFile, ID_FILE_SAVE_AS, nullptr, "save_set_as");
|
||||
add_menu_item_tr(menuFile, ID_FILE_SAVE_AS_DIRECTORY, nullptr, "save_set_as_directory");
|
||||
add_menu_item_tr(menuFile, ID_FILE_SAVE_AS, "save", "save_set_as");
|
||||
add_menu_item_tr(menuFile, ID_FILE_SAVE_AS_DIRECTORY, "save", "save_set_as_directory");
|
||||
add_menu_item_tr(menuFile, wxID_ANY, "export", "export", wxITEM_NORMAL, makeExportMenu());
|
||||
menuFile->AppendSeparator();
|
||||
add_menu_item_tr(menuFile, ID_FILE_CHECK_UPDATES, nullptr, "check_updates");
|
||||
add_menu_item_tr(menuFile, ID_FILE_CHECK_UPDATES, settings.darkModePrefix() + "check_updates", "check_updates");
|
||||
#if USE_SCRIPT_PROFILING
|
||||
add_menu_item_tr(menuFile, ID_FILE_PROFILER, nullptr, "show_profiler");
|
||||
#endif
|
||||
// menuFile->Append(ID_FILE_INSPECT, _("Inspect Internal Data..."), _("Shows a the data in the set using a tree structure"));
|
||||
// menuFile->AppendSeparator();
|
||||
add_menu_item_tr(menuFile, ID_FILE_RELOAD, nullptr, "reload_data");
|
||||
add_menu_item_tr(menuFile, ID_FILE_RELOAD, settings.darkModePrefix() + "reload_data", "reload_data");
|
||||
menuFile->AppendSeparator();
|
||||
add_menu_item_tr(menuFile, ID_FILE_PRINT_PREVIEW, "print_preview", "print_preview");
|
||||
add_menu_item_tr(menuFile, ID_FILE_PRINT, "print", "print");
|
||||
@@ -88,12 +88,12 @@ SetWindow::SetWindow(Window* parent, const SetP& set)
|
||||
menuEdit->AppendSeparator();
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_SELECT_ALL, nullptr, "select_all");
|
||||
menuEdit->AppendSeparator();
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_FIND, settings.darkModePrefix() + "find", "find");
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_FIND_NEXT, settings.darkModePrefix() + "find", "find_next");
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_REPLACE, nullptr, "replace");
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_AUTO_REPLACE, nullptr, "auto_replace");
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_FIND, settings.darkModePrefix() + "find", "find");
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_FIND_NEXT, settings.darkModePrefix() + "find", "find_next");
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_REPLACE, settings.darkModePrefix() + "find", "replace");
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_AUTO_REPLACE, settings.darkModePrefix() + "find", "auto_replace");
|
||||
menuEdit->AppendSeparator();
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_PREFERENCES, nullptr, "preferences");
|
||||
add_menu_item_tr(menuEdit, ID_EDIT_PREFERENCES, settings.darkModePrefix() + "preferences", "preferences");
|
||||
menuBar->Append(menuEdit, _MENU_("edit"));
|
||||
|
||||
auto menuWindow = new wxMenu();
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <util/find_replace.hpp>
|
||||
#include <util/spell_checker.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
#include <wx/fontdlg.h>
|
||||
#include <wx/colordlg.h>
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/caret.h>
|
||||
|
||||
@@ -474,11 +476,18 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) {
|
||||
return true;
|
||||
case WXK_RETURN:
|
||||
if (field().multi_line) {
|
||||
String prefix;
|
||||
String suffix;
|
||||
if (is_in_tag(value().value(), _("<li"), selection_start_i, selection_end_i)) {
|
||||
prefix = _("</li>");
|
||||
suffix = _("<li><bullet>• </bullet>");
|
||||
}
|
||||
if (ev.ShiftDown()) {
|
||||
// soft line break
|
||||
replaceSelection(_("<soft-line>\n</soft-line>"), _ACTION_("soft line break"));
|
||||
replaceSelection(prefix + _("<soft-line>\n</soft-line>") + suffix, _ACTION_("soft line break"));
|
||||
} else {
|
||||
replaceSelection(_("\n"), _ACTION_("enter"));
|
||||
// hard line break
|
||||
replaceSelection(prefix + _("\n") + suffix, _ACTION_("enter"));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -805,7 +814,8 @@ bool TextValueEditor::doDelete() {
|
||||
|
||||
bool TextValueEditor::canFormat(int type) const {
|
||||
switch (type) {
|
||||
case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE: case ID_FORMAT_STRIKETHROUGH:
|
||||
case ID_FORMAT_FONT: case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE:
|
||||
case ID_FORMAT_STRIKETHROUGH: case ID_FORMAT_COLOR: case ID_FORMAT_BULLETPOINT:
|
||||
return !style().always_symbol && style().allow_formating;
|
||||
case ID_FORMAT_SYMBOL:
|
||||
return !style().always_symbol && style().allow_formating && style().symbol_font.valid();
|
||||
@@ -819,16 +829,22 @@ bool TextValueEditor::canFormat(int type) const {
|
||||
|
||||
bool TextValueEditor::hasFormat(int type) const {
|
||||
switch (type) {
|
||||
case ID_FORMAT_FONT:
|
||||
return is_in_tag(value().value(), _("<font"), selection_start_i, selection_end_i);
|
||||
case ID_FORMAT_BOLD:
|
||||
return is_in_tag(value().value(), _("<b"), selection_start_i, selection_end_i);
|
||||
return is_in_tag(value().value(), _("<b"), selection_start_i, selection_end_i);
|
||||
case ID_FORMAT_ITALIC:
|
||||
return is_in_tag(value().value(), _("<i"), selection_start_i, selection_end_i);
|
||||
return is_in_tag(value().value(), _("<i"), selection_start_i, selection_end_i);
|
||||
case ID_FORMAT_UNDERLINE:
|
||||
return is_in_tag(value().value(), _("<u"), selection_start_i, selection_end_i);
|
||||
return is_in_tag(value().value(), _("<u"), selection_start_i, selection_end_i);
|
||||
case ID_FORMAT_STRIKETHROUGH:
|
||||
return is_in_tag(value().value(), _("<strike"), selection_start_i, selection_end_i);
|
||||
case ID_FORMAT_COLOR:
|
||||
return is_in_tag(value().value(), _("<color"), selection_start_i, selection_end_i);
|
||||
case ID_FORMAT_BULLETPOINT:
|
||||
return is_in_tag(value().value(), _("<li"), selection_start_i, selection_end_i);
|
||||
case ID_FORMAT_SYMBOL:
|
||||
return is_in_tag(value().value(), _("<sym"), selection_start_i, selection_end_i);
|
||||
return is_in_tag(value().value(), _("<sym"), selection_start_i, selection_end_i);
|
||||
case ID_FORMAT_REMINDER: {
|
||||
const String& v = value().value();
|
||||
size_t tag = in_tag(v, _("<kw"), selection_start_i, selection_start_i);
|
||||
@@ -843,8 +859,30 @@ bool TextValueEditor::hasFormat(int type) const {
|
||||
}
|
||||
|
||||
void TextValueEditor::doFormat(int type) {
|
||||
size_t ss = selection_start, se = selection_end;
|
||||
switch (type) {
|
||||
case ID_FORMAT_FONT: {
|
||||
wxFontData data;
|
||||
data.EnableEffects(false);
|
||||
TextStyle* style = dynamic_cast<TextStyle*>(getStyle().get());
|
||||
if (style) {
|
||||
data.SetInitialFont(style->font.toWxFont(1.0));
|
||||
}
|
||||
wxFontDialog dial(nullptr, data);
|
||||
if (dial.ShowModal() == wxID_OK) {
|
||||
data = dial.GetFontData();
|
||||
vector<String> tags;
|
||||
wxFont font = data.GetChosenFont();
|
||||
tags.push_back(_("font:") + font.GetFaceName());
|
||||
tags.push_back(_("size:") + wxString::Format(wxT("%i"),font.GetPointSize()));
|
||||
if (font.GetWeight() >= wxFONTWEIGHT_BOLD) tags.push_back(_("b"));
|
||||
if (font.GetStyle() >= wxFONTSTYLE_ITALIC) tags.push_back(_("i"));
|
||||
//tags.push_back(_("color:") + data.GetColour().GetAsString(wxC2S_HTML_SYNTAX)); // no need since EnableEffects(false)
|
||||
//if (font.GetUnderlined()) tags.push_back(_("u")); // no need since EnableEffects(false)
|
||||
//if (font.GetStrikethrough()) tags.push_back(_("strike")); // no need since EnableEffects(false)
|
||||
addAction(toggle_format_action(valueP(), tags, selection_start_i, selection_end_i, selection_start, selection_end, _("Font")));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ID_FORMAT_BOLD: {
|
||||
addAction(toggle_format_action(valueP(), _("b"), selection_start_i, selection_end_i, selection_start, selection_end, _("Bold")));
|
||||
break;
|
||||
@@ -861,6 +899,31 @@ void TextValueEditor::doFormat(int type) {
|
||||
addAction(toggle_format_action(valueP(), _("strike"), selection_start_i, selection_end_i, selection_start, selection_end, _("Strikethrough")));
|
||||
break;
|
||||
}
|
||||
case ID_FORMAT_COLOR: {
|
||||
wxColourData data;
|
||||
data.SetColour(wxColour(0, 0, 0));
|
||||
GameSettings& gs = settings.gameSettingsFor(parent.getGame());
|
||||
for (size_t i = 0 ; i < min(gs.custom_colors.size(),gs.max_custom_colors) ; ++i) {
|
||||
data.SetCustomColour(i, gs.custom_colors[i]);
|
||||
}
|
||||
wxColourDialog dial = wxColourDialog(nullptr, &data);
|
||||
if (dial.ShowModal() == wxID_OK) {
|
||||
data = dial.GetColourData();
|
||||
wxColour color = data.GetColour();
|
||||
String tag = _("color:") + color.GetAsString(wxC2S_HTML_SYNTAX);
|
||||
addAction(toggle_format_action(valueP(), tag, selection_start_i, selection_end_i, selection_start, selection_end, _("Color Text")));
|
||||
gs.custom_colors.clear();
|
||||
for (size_t i = 0 ; i < gs.max_custom_colors ; ++i) {
|
||||
wxColour custom_color = data.GetCustomColour(i);
|
||||
if (custom_color.IsOk()) gs.custom_colors.push_back(custom_color);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ID_FORMAT_BULLETPOINT: {
|
||||
addAction(toggle_format_action(valueP(), _("li"), selection_start_i, selection_end_i, selection_start, selection_end, _("Bullet Point")));
|
||||
break;
|
||||
}
|
||||
case ID_FORMAT_SYMBOL: {
|
||||
addAction(toggle_format_action(valueP(), _("sym"), selection_start_i, selection_end_i, selection_start, selection_end, _("Symbols")));
|
||||
break;
|
||||
@@ -870,8 +933,6 @@ void TextValueEditor::doFormat(int type) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
selection_start = ss;
|
||||
selection_end = se;
|
||||
fixSelection();
|
||||
}
|
||||
|
||||
|
||||
@@ -172,12 +172,24 @@ private:
|
||||
fromString(e->children, text, pos, end_tag);
|
||||
elements.push_back(e);
|
||||
pos = skip_tag(text, end_tag);
|
||||
} else if (is_tag(text, tag_start, _("</bullet"))) {
|
||||
// end of bullet point, set margin here
|
||||
} else if (is_tag(text, tag_start, _("<bullet"))) {
|
||||
// start of bullet point, set margin before bullet here, but only once
|
||||
// subsequent times will align to this one
|
||||
if (paragraphs.back().margin_before_bullet == paragraphs.back().start) {
|
||||
paragraphs.back().margin_before_bullet = tag_start;
|
||||
}
|
||||
if (li <= 0) {
|
||||
queue_message(MESSAGE_WARNING, _("<bullet> outside <li> tag"));
|
||||
}
|
||||
} else if (is_tag(text, tag_start, _("</bullet"))) {
|
||||
// end of bullet point, set margin after bullet here, but only once
|
||||
// subsequent times will align to this one
|
||||
if (paragraphs.back().margin_after_bullet == paragraphs.back().start) {
|
||||
paragraphs.back().margin_after_bullet = pos;
|
||||
}
|
||||
if (li <= 0) {
|
||||
queue_message(MESSAGE_WARNING, _("<bullet> outside <li> tag"));
|
||||
}
|
||||
paragraphs.back().margin_end_char = pos;
|
||||
} else if (is_tag(text, tag_start, _("<margin"))) {
|
||||
size_t colon = text.find_first_of(_(">:"), tag_start);
|
||||
if (colon < pos - 1 && text.GetChar(colon) == _(':')) {
|
||||
@@ -282,7 +294,8 @@ private:
|
||||
paragraphs.back().end = i + 1;
|
||||
paragraphs.emplace_back();
|
||||
paragraphs.back().start = i + 1;
|
||||
paragraphs.back().margin_end_char = i + 1;
|
||||
paragraphs.back().margin_before_bullet = i + 1;
|
||||
paragraphs.back().margin_after_bullet = i + 1;
|
||||
if (!margins.empty()) {
|
||||
paragraphs.back().margin_left = margins.back().left;
|
||||
paragraphs.back().margin_right = margins.back().right;
|
||||
@@ -303,7 +316,8 @@ private:
|
||||
(kwpph > 0 ? FONT_SOFT : FONT_NORMAL) |
|
||||
(code > 0 ? FONT_CODE : FONT_NORMAL) |
|
||||
(code_kw > 0 ? FONT_CODE_KW : FONT_NORMAL) |
|
||||
(code_string > 0 ? FONT_CODE_STRING : FONT_NORMAL),
|
||||
(code_string > 0 ? FONT_CODE_STRING : FONT_NORMAL) |
|
||||
(!fonts.empty() ? FONT_FROM_TAG : FONT_NORMAL),
|
||||
underline > 0,
|
||||
strikethrough > 0,
|
||||
fonts.empty() ? nullptr : &fonts.back(),
|
||||
|
||||
@@ -37,12 +37,13 @@ struct CharInfo {
|
||||
RealSize size; ///< Size of this character
|
||||
LineBreak break_after : 16; ///< How/when to break after it?
|
||||
bool soft : 1; ///< Is this a 'soft' character? soft characters are ignored for alignment
|
||||
bool bullet : 1; ///< Is this a bullet point?
|
||||
|
||||
explicit CharInfo()
|
||||
: break_after(LineBreak::NO), soft(true)
|
||||
: break_after(LineBreak::NO), soft(true), bullet(false)
|
||||
{}
|
||||
inline CharInfo(RealSize size, LineBreak break_after, bool soft = false)
|
||||
: size(size), break_after(break_after), soft(soft)
|
||||
inline CharInfo(RealSize size, LineBreak break_after, bool soft = false, bool bullet = false)
|
||||
: size(size), break_after(break_after), soft(soft), bullet(bullet)
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -151,7 +152,8 @@ public:
|
||||
double margin_left = 0., margin_right = 0.;
|
||||
double margin_top = 0.; //, margin_bottom = 0.; // TODO: more margin options?
|
||||
size_t start = String::npos, end = String::npos;
|
||||
size_t margin_end_char = 0; // end position of characters that are added to the margin (i.e. bullet points)
|
||||
size_t margin_before_bullet = 0; // position of the bullet tag
|
||||
size_t margin_after_bullet = 0; // position of the first character after the bullet tag
|
||||
};
|
||||
|
||||
/// A list of text elements extracted from a string
|
||||
|
||||
@@ -48,7 +48,8 @@ void FontTextElement::getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>&
|
||||
out.push_back(CharInfo(
|
||||
RealSize(s.width - prev_width, s.height),
|
||||
c == _(' ') ? LineBreak::SPACE : LineBreak::MAYBE,
|
||||
draw_as == DRAW_ACTIVE // from <soft> tag
|
||||
draw_as == DRAW_ACTIVE, // from <soft> tag
|
||||
c == _('•')
|
||||
));
|
||||
prev_width = s.width;
|
||||
}
|
||||
|
||||
@@ -13,20 +13,22 @@
|
||||
// ----------------------------------------------------------------------------- : Line
|
||||
|
||||
struct TextViewer::Line {
|
||||
size_t start; ///< Index of the first character in this line
|
||||
size_t end_or_soft; ///< Index just beyond the last non-soft character
|
||||
vector<double> positions; ///< x position of each character in this line, gives the number of characters + 1, never empty
|
||||
double top; ///< y position of (the top of) this line
|
||||
double line_height; ///< The height of this line in pixels
|
||||
LineBreak break_after; ///< Is there a saparator after this line?
|
||||
optional<Alignment> alignment; ///< Alignment of this line
|
||||
bool justifying; ///< Is the text justified? Only true when *really* justifying.
|
||||
double margin_left; ///< Left margin
|
||||
double margin_right;///< Rightmargin
|
||||
size_t start; ///< Index of the first character in this line
|
||||
size_t end_or_soft; ///< Index just beyond the last non-soft character
|
||||
vector<double> positions; ///< x position of each character in this line, gives the number of characters + 1, never empty
|
||||
double top; ///< y position of (the top of) this line
|
||||
double line_height; ///< The height of this line in pixels
|
||||
LineBreak break_after; ///< Is there a saparator after this line?
|
||||
optional<Alignment> alignment; ///< Alignment of this line
|
||||
bool justifying; ///< Is the text justified? Only true when *really* justifying.
|
||||
double margin_left; ///< Left margin including the margin tag and bullet point
|
||||
double margin_left_before_bullet; ///< Left margin but just before the bullet point
|
||||
double margin_right; ///< Rightmargin
|
||||
bool bullet; ///< Does this line start with a bullet point?
|
||||
|
||||
Line()
|
||||
: start(0), end_or_soft(0), top(0), line_height(0)
|
||||
, break_after(LineBreak::NO), justifying(false)
|
||||
, break_after(LineBreak::NO), justifying(false), bullet(false)
|
||||
{}
|
||||
|
||||
/// The position (just beyond) the bottom of this line
|
||||
@@ -545,11 +547,12 @@ void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const
|
||||
|
||||
// Try to fit a blank line in the masked image, move down until it fits
|
||||
RealSize TextViewer::fitLineWidth(Line& line, RotatedDC& dc, const TextStyle& style) const {
|
||||
RealSize line_size(line.margin_left + lineLeft(dc, style, line.top), 0);
|
||||
double margin_left = line.bullet ? line.margin_left_before_bullet : line.margin_left;
|
||||
RealSize line_size(margin_left + lineLeft(dc, style, line.top), 0);
|
||||
while (line.top < dc.getHeight() && line_size.width + 1 >= dc.getWidth() - style.padding_right - line.margin_right) {
|
||||
// nothing fits on this line, move down one pixel
|
||||
line.top += 1;
|
||||
line_size.width = line.margin_left + lineLeft(dc, style, line.top);
|
||||
line_size.width = margin_left + lineLeft(dc, style, line.top);
|
||||
}
|
||||
return line_size;
|
||||
}
|
||||
@@ -565,7 +568,8 @@ bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector<CharInfo>& char
|
||||
// first line
|
||||
Line line;
|
||||
line.top = style.padding_top;
|
||||
line.margin_left = elements.paragraphs[0].margin_left;
|
||||
line.margin_left = elements.paragraphs[0].margin_left;
|
||||
line.margin_left_before_bullet = elements.paragraphs[0].margin_left;
|
||||
line.margin_right = elements.paragraphs[0].margin_right;
|
||||
line.alignment = elements.paragraphs[0].alignment;
|
||||
// size of the line so far
|
||||
@@ -574,7 +578,7 @@ bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector<CharInfo>& char
|
||||
|
||||
// The word we are currently reading
|
||||
RealSize word_size;
|
||||
vector<double> positions_word; // positios for this word
|
||||
vector<double> positions_word; // positions for this word
|
||||
size_t word_end_or_soft = 0;
|
||||
size_t word_start = 0;
|
||||
// For each character ...
|
||||
@@ -608,8 +612,11 @@ bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector<CharInfo>& char
|
||||
}
|
||||
positions_word.push_back(word_size.width);
|
||||
if (!c.soft) word_end_or_soft = i + 1;
|
||||
if (i < elements.paragraphs[i_para].margin_end_char) {
|
||||
if (i < elements.paragraphs[i_para].margin_after_bullet) {
|
||||
line.margin_left += c.size.width; // character in left margin
|
||||
if (i < elements.paragraphs[i_para].margin_before_bullet) {
|
||||
line.margin_left_before_bullet += c.size.width;
|
||||
}
|
||||
}
|
||||
// Did the word become too long?
|
||||
if (!break_now) {
|
||||
@@ -690,11 +697,20 @@ bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector<CharInfo>& char
|
||||
if (i_para+1 < elements.paragraphs.size()) ++i_para;
|
||||
assert(elements.paragraphs[i_para].start == i + 1);
|
||||
line.margin_left = elements.paragraphs[i_para].margin_left;
|
||||
line.margin_left_before_bullet = elements.paragraphs[i_para].margin_left;
|
||||
line.margin_right = elements.paragraphs[i_para].margin_right;
|
||||
line.top += elements.paragraphs[i_para].margin_top;
|
||||
line.alignment = elements.paragraphs[i_para].alignment;
|
||||
}
|
||||
line.break_after = LineBreak::NO;
|
||||
// is the first visible character of the line a bullet point?
|
||||
line.bullet = false;
|
||||
for (size_t j = line.start ; j < chars.size() ; ++j) {
|
||||
if (!chars[j].soft) {
|
||||
line.bullet = chars[j].bullet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// reset line_size
|
||||
line_size = fitLineWidth(line, dc, style);
|
||||
line.positions.push_back(line_size.width); // start position
|
||||
@@ -831,8 +847,9 @@ void TextViewer::alignParagraph(size_t start_line, size_t end_line, const vector
|
||||
}
|
||||
|
||||
void TextViewer::Line::alignHorizontal(const vector<CharInfo>& chars, const TextStyle& style, const RealRect& s) {
|
||||
double width = this->width() - margin_left;
|
||||
double target_width = s.width - margin_left - margin_right;
|
||||
double margin_bullet = bullet ? margin_left_before_bullet : margin_left;
|
||||
double width = this->width() - margin_bullet;
|
||||
double target_width = s.width - margin_bullet - margin_right;
|
||||
Alignment alignment = this->alignment.value_or(style.alignment);
|
||||
bool should_fill = (alignment & ALIGN_IF_OVERFLOW ? width > target_width : true)
|
||||
&& (alignment & ALIGN_IF_SOFTBREAK ? break_after == LineBreak::SOFT || !style.field().multi_line : true);
|
||||
|
||||
@@ -180,6 +180,9 @@ void queue_message(MessageType type, String const& msg) {
|
||||
// Only show errors in the main thread
|
||||
message_queue.push_front(make_pair(type,msg));
|
||||
}
|
||||
void qm(String const& msg) {
|
||||
queue_message(MESSAGE_ERROR, msg);
|
||||
}
|
||||
|
||||
void handle_error(const Error& e) {
|
||||
queue_message(e.is_fatal() ? MESSAGE_FATAL_ERROR : MESSAGE_ERROR, e.what());
|
||||
|
||||
@@ -154,6 +154,7 @@ enum MessageType
|
||||
/** If the message is a MESSAGE_FATAL_ERROR, and show_message_box_for_fatal_errors==true, then a popup is shown
|
||||
*/
|
||||
void queue_message(MessageType type, String const& msg);
|
||||
void qm(String const& msg);
|
||||
/// Handle an error by queuing a message
|
||||
void handle_error(const Error& e);
|
||||
/// Handle an error by showing a message box
|
||||
|
||||
@@ -25,8 +25,8 @@ wxUniChar tag_char(wxUniChar c) {
|
||||
|
||||
// Is a character the "end" of the tag name?
|
||||
// don't mistake <tag> as <t>, only <t>, <t-stuff> and <t:stuff> are considered <t>
|
||||
bool is_tag_end_char(Char c) {
|
||||
return c == '>' || c == '-' || c == ':' || c == ' ';
|
||||
bool is_tag_end_char(Char c, bool strict) {
|
||||
return strict ? c == '>' : c == '>' || c == '-' || c == ':' || c == ' ';
|
||||
}
|
||||
|
||||
|
||||
@@ -162,11 +162,11 @@ String fix_old_tags(const String& str) {
|
||||
return skip_all_tags(it, end, after_open, after_close);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_tag(String::const_iterator it, String::const_iterator end, const char* tag) {
|
||||
[[nodiscard]] bool is_tag(String::const_iterator it, String::const_iterator end, const char* tag, bool strict) {
|
||||
for (; *tag; ++it, ++tag) {
|
||||
if (it == end || *it != *tag) return false;
|
||||
}
|
||||
if (it == end || !is_tag_end_char(*it)) return false;
|
||||
if (it == end || !is_tag_end_char(*it, strict)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -267,11 +267,25 @@ String::const_iterator find_close_tag(String::const_iterator tag, String::const_
|
||||
return String::npos;
|
||||
}
|
||||
|
||||
bool is_tag(const String& str, size_t pos, const String& tag) {
|
||||
return is_substr(str, pos, tag) && pos+tag.size() < str.size() && is_tag_end_char(str[pos+tag.size()]);
|
||||
bool is_tag(const String& str, size_t pos, const String& tag, bool strict) {
|
||||
return is_substr(str, pos, tag) && pos+tag.size() < str.size() && is_tag_end_char(str[pos+tag.size()], strict);
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t in_tag(const String& str, const String& tag, size_t start, size_t end) {
|
||||
bool is_formatting_tag(const String& str, size_t pos) {
|
||||
if (str.size() < pos + 2) return false;
|
||||
if (str.GetChar(pos) != '<') return false;
|
||||
pos++;
|
||||
return is_substr(str, pos, _("b")) || is_substr(str, pos, _("/b")) ||
|
||||
is_substr(str, pos, _("i")) || is_substr(str, pos, _("/i")) ||
|
||||
is_substr(str, pos, _("sym")) || is_substr(str, pos, _("/sym")) ||
|
||||
is_substr(str, pos, _("u")) || is_substr(str, pos, _("/u")) ||
|
||||
is_substr(str, pos, _("strike")) || is_substr(str, pos, _("/strike")) ||
|
||||
is_substr(str, pos, _("font")) || is_substr(str, pos, _("/font")) ||
|
||||
is_substr(str, pos, _("size")) || is_substr(str, pos, _("/size")) ||
|
||||
is_substr(str, pos, _("color")) || is_substr(str, pos, _("/color"));
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t in_tag(const String& str, const String& tag, size_t start, size_t end, bool strict) {
|
||||
size_t last_start = String::npos;
|
||||
size_t size = str.size();
|
||||
int taglevel = 0;
|
||||
@@ -279,10 +293,10 @@ bool is_tag(const String& str, size_t pos, const String& tag) {
|
||||
for (size_t pos = 0 ; pos < end ; ) {
|
||||
Char c = str.GetChar(pos);
|
||||
if (c == _('<')) {
|
||||
if (is_substr(str, pos + 1, static_cast<const Char*>(tag.c_str())+1) && pos+tag.size() < str.size() && is_tag_end_char(str[pos+tag.size()])) {
|
||||
if (is_substr(str, pos + 1, static_cast<const Char*>(tag.c_str())+1) && pos+tag.size() < str.size() && is_tag_end_char(str[pos+tag.size()], strict)) {
|
||||
if (pos < start) last_start = pos;
|
||||
++taglevel;
|
||||
} else if (pos + 2 < size && str.GetChar(pos+1) == _('/') && is_substr(str, pos + 2, static_cast<const Char*>(tag.c_str())+1) && pos+1+tag.size() < str.size() && is_tag_end_char(str[pos+1+tag.size()])) {
|
||||
} else if (pos + 2 < size && str.GetChar(pos+1) == _('/') && is_substr(str, pos + 2, static_cast<const Char*>(tag.c_str())+1) && pos+1+tag.size() < str.size() && is_tag_end_char(str[pos+1+tag.size()], strict)) {
|
||||
--taglevel; // close tag
|
||||
}
|
||||
pos = skip_tag(str,pos);
|
||||
@@ -296,8 +310,8 @@ bool is_tag(const String& str, size_t pos, const String& tag) {
|
||||
}
|
||||
return taglevel < 1 ? String::npos : last_start;
|
||||
}
|
||||
bool is_in_tag(const String& str, const String& tag, size_t start, size_t end) {
|
||||
return in_tag(str,tag,start,end) != String::npos;
|
||||
bool is_in_tag(const String& str, const String& tag, size_t start, size_t end, bool strict) {
|
||||
return in_tag(str,tag,start,end,strict) != String::npos;
|
||||
}
|
||||
|
||||
|
||||
@@ -325,6 +339,17 @@ String anti_tag(const String& tag) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Cursor position
|
||||
|
||||
size_t to_untagged_pos(const String& str, size_t pos) {
|
||||
return untag(str.substr(0, pos)).size();
|
||||
}
|
||||
|
||||
size_t to_tagged_pos(const String& str, size_t pos, bool after_open, bool after_close) {
|
||||
String::const_iterator it = str.begin();
|
||||
const String::const_iterator end = str.end();
|
||||
it = advance_untagged(it, end, pos, after_open, after_close);
|
||||
return std::distance(str.begin(), it);
|
||||
}
|
||||
|
||||
size_t index_to_cursor(const String& str, size_t index, Movement dir) {
|
||||
size_t cursor = 0;
|
||||
index = min(index, str.size());
|
||||
@@ -335,7 +360,11 @@ size_t index_to_cursor(const String& str, size_t index, Movement dir) {
|
||||
bool has_width = true;
|
||||
if (c == _('<')) {
|
||||
// a tag
|
||||
if (is_substr(str, i, _("<atom")) || is_substr(str, i, _("<sep"))) {
|
||||
if (is_substr(str, i, _("<atom")) ||
|
||||
is_substr(str, i, _("<sep")) ||
|
||||
is_substr(str, i, _("<bullet")) ||
|
||||
is_substr(str, i, _("<soft-line")) ||
|
||||
is_substr(str, i, _("<line"))) {
|
||||
// skip tag contents, tag counts as a single 'character'
|
||||
size_t before = i;
|
||||
size_t close = match_close_tag(str, i);
|
||||
@@ -426,7 +455,11 @@ void cursor_to_index_range(const String& str, size_t cursor, size_t& start, size
|
||||
bool has_width = true;
|
||||
if (c == _('<')) {
|
||||
// a tag
|
||||
if (is_substr(str, i, _("<atom")) || is_substr(str, i, _("<sep"))) {
|
||||
if (is_substr(str, i, _("<atom")) ||
|
||||
is_substr(str, i, _("<sep")) ||
|
||||
is_substr(str, i, _("<bullet")) ||
|
||||
is_substr(str, i, _("<soft-line")) ||
|
||||
is_substr(str, i, _("<line"))) {
|
||||
// never move the end over an atom/sep
|
||||
if (cur >= cursor) { ++i; break; }
|
||||
// skip tag contents, tag counts as a single 'character'
|
||||
@@ -504,6 +537,15 @@ String untag_for_cursor(const String& str) {
|
||||
} else if (is_substr(str, i, _("<sep"))) {
|
||||
i = match_close_tag_end(str, i);
|
||||
ret += UNTAG_SEP;
|
||||
} else if (is_substr(str, i, _("<bullet"))) {
|
||||
i = match_close_tag_end(str, i);
|
||||
ret += UNTAG_BULLET;
|
||||
} else if (is_substr(str, i, _("<soft-line"))) {
|
||||
i = match_close_tag_end(str, i);
|
||||
ret += UNTAG_ATOM;
|
||||
} else if (is_substr(str, i, _("<line"))) {
|
||||
i = match_close_tag_end(str, i);
|
||||
ret += UNTAG_ATOM;
|
||||
} 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);
|
||||
@@ -557,6 +599,13 @@ size_t index_to_untagged(const String& str, size_t index) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Global operations
|
||||
|
||||
String wrap_tag(const String& str, const String& tag) {
|
||||
return tag + str + close_tag(tag);
|
||||
}
|
||||
String anti_wrap_tag(const String& str, const String& tag) {
|
||||
return close_tag(tag) + str + tag;
|
||||
}
|
||||
|
||||
String remove_tag(const String& str, const String& tag) {
|
||||
if (tag.size() < 1) return str;
|
||||
String ctag = close_tag(tag);
|
||||
@@ -641,14 +690,18 @@ String simplify_tagged(const String& str) {
|
||||
|
||||
// Add a tag to a stack of tags, try to cancel it out
|
||||
// If </tag> is in stack remove it and returns true
|
||||
// otherwise appends <tag> and returns fales
|
||||
// otherwise appends <tag> and returns false
|
||||
// (where </tag> is the negation of tag)
|
||||
bool add_or_cancel_tag(const String& tag, String& stack, bool all = false) {
|
||||
if (all || starts_with(tag, _("/")) ||
|
||||
starts_with(tag, _("b")) ||
|
||||
starts_with(tag, _("i")) ||
|
||||
starts_with(tag, _("sym")) ||
|
||||
starts_with(tag, _("u")) ||
|
||||
starts_with(tag, _("sym"))) {
|
||||
starts_with(tag, _("strike")) ||
|
||||
starts_with(tag, _("color")) ||
|
||||
starts_with(tag, _("font")) ||
|
||||
starts_with(tag, _("size"))) {
|
||||
// cancel out all close tags, but not all open tags,
|
||||
// so <xx></xx> is always removed
|
||||
// but </xx><xx> is not
|
||||
@@ -669,9 +722,9 @@ bool add_or_cancel_tag(const String& tag, String& stack, bool all = false) {
|
||||
}
|
||||
|
||||
String simplify_tagged_merge(const String& str, bool all) {
|
||||
String ret; ret.reserve(str.size());
|
||||
String waiting_tags; // tags that are waiting to be written to the output
|
||||
size_t size = str.size();
|
||||
String ret; ret.reserve(size);
|
||||
String waiting_tags; // tags that are waiting to be written to the output
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
Char c = str.GetChar(i);
|
||||
if (c == _('<')) {
|
||||
@@ -688,17 +741,18 @@ String simplify_tagged_merge(const String& str, bool all) {
|
||||
}
|
||||
|
||||
String simplify_tagged_overlap(const String& str) {
|
||||
String ret; ret.reserve(str.size());
|
||||
String open_tags; // tags we are in
|
||||
size_t size = str.size();
|
||||
String ret; ret.reserve(size);
|
||||
String open_tags; // tags we are in
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
Char c = str.GetChar(i);
|
||||
if (c == _('<')) {
|
||||
String tag = tag_at(str, i);
|
||||
if (starts_with(tag, _("b")) || starts_with(tag, _("/b")) ||
|
||||
starts_with(tag, _("i")) || starts_with(tag, _("/i")) ||
|
||||
starts_with(tag, _("u")) || starts_with(tag, _("/u")) ||
|
||||
starts_with(tag, _("sym")) || starts_with(tag, _("/sym"))) {
|
||||
if (starts_with(tag, _("b")) || starts_with(tag, _("/b")) ||
|
||||
starts_with(tag, _("i")) || starts_with(tag, _("/i")) ||
|
||||
starts_with(tag, _("sym")) || starts_with(tag, _("/sym")) ||
|
||||
starts_with(tag, _("u")) || starts_with(tag, _("/u")) ||
|
||||
starts_with(tag, _("strike")) || starts_with(tag, _("/strike"))) {
|
||||
// optimize this tag
|
||||
if (open_tags.find(_("<") + tag + _(">")) == String::npos) {
|
||||
// we are not already inside this tag
|
||||
|
||||
@@ -74,7 +74,11 @@ String fix_old_tags(const String&);
|
||||
|
||||
/// Does a string contain a tag at the given location?
|
||||
/** Only matches if the tag ends one of ">-: " */
|
||||
[[nodiscard]] bool is_tag(const String& str, size_t pos, const String& tag);
|
||||
[[nodiscard]] bool is_tag(const String& str, size_t pos, const String& tag, bool strict = false);
|
||||
|
||||
/// Does a string contain a tag at the given location?
|
||||
/** Matches <b <i <u <strike <font <size <color <sym or their anti tags */
|
||||
[[nodiscard]] bool is_formatting_tag(const String& str, size_t pos);
|
||||
|
||||
/// Is the given range entirely contained in a given tagged block?
|
||||
/** If so: return the start position of that tag, otherwise returns String::npos
|
||||
@@ -83,9 +87,9 @@ String fix_old_tags(const String&);
|
||||
* <tag><tag></tag>x</tag>
|
||||
* the x is in_tag
|
||||
*/
|
||||
[[nodiscard]] size_t in_tag(const String& str, const String& tag, size_t start, size_t end);
|
||||
[[nodiscard]] size_t in_tag(const String& str, const String& tag, size_t start, size_t end, bool strict = false);
|
||||
/// Boolean returning version of the above
|
||||
bool is_in_tag(const String& str, const String& tag, size_t start, size_t end);
|
||||
bool is_in_tag(const String& str, const String& tag, size_t start, size_t end, bool strict = false);
|
||||
|
||||
/// Return the tag at the given position (without the <>)
|
||||
String tag_at(const String& str, size_t pos);
|
||||
@@ -120,10 +124,11 @@ String anti_tag(const String& tag);
|
||||
[[nodiscard]] String::const_iterator find_close_tag(String::const_iterator it, String::const_iterator end);
|
||||
|
||||
/// Does a string contain a tag at the given location?
|
||||
/** Only matches if the tag in the text ends one of ">-: "
|
||||
/** Only matches if the tag in the text ends with one of ">-: "
|
||||
* If strict is set to true then it must end with exactly ">"
|
||||
* tag should be "<tag" or "</tag"
|
||||
*/
|
||||
[[nodiscard]] bool is_tag(String::const_iterator it, String::const_iterator end, const char* tag);
|
||||
[[nodiscard]] bool is_tag(String::const_iterator it, String::const_iterator end, const char* tag, bool strict = false);
|
||||
|
||||
// Length of a string when not counting tags
|
||||
// For example: untagged_length("<b>abc</b>",_) = 3
|
||||
@@ -131,6 +136,14 @@ String anti_tag(const String& tag);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Cursor position
|
||||
|
||||
/// Take a tagged string and a position inside it,
|
||||
/// return the position if the string was untagged.
|
||||
size_t to_untagged_pos(const String& str, size_t pos);
|
||||
|
||||
/// Take a tagged string and a position inside the untagged version,
|
||||
/// return the position in the tagged version.
|
||||
size_t to_tagged_pos(const String& str, size_t pos, bool after_open=false, bool after_close=false);
|
||||
|
||||
/// Directions of cursor movement
|
||||
enum Movement
|
||||
{ MOVE_LEFT = -2 ///< Always move the cursor to the left
|
||||
@@ -158,6 +171,7 @@ size_t cursor_to_index(const String& str, size_t cursor, Movement dir = MOVE_MID
|
||||
const Char UNTAG_ATOM = _('\2');
|
||||
const Char UNTAG_SEP = _('\3');
|
||||
const Char UNTAG_ATOM_KWPPH = _('\4');
|
||||
const Char UNTAG_BULLET = _('\5');
|
||||
|
||||
/// Untag a string for use with cursors, <atom>...</atom> becomes a single character.
|
||||
/** This string should only be used for cursor position calculations. */
|
||||
@@ -180,6 +194,11 @@ size_t index_to_untagged(const String& str, size_t index);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Global operations
|
||||
|
||||
/// Add a tag and its close tag around a string. Tag must be complete.
|
||||
String wrap_tag(const String& str, const String& tag);
|
||||
/// Add a tag and its close tag around a string but in the opposite order.
|
||||
String anti_wrap_tag(const String& str, const String& tag);
|
||||
|
||||
/// Remove all instances of a tag and its close tag, but keep the contents.
|
||||
/** tag doesn't have to be a complete tag, for example remove_tag(str, "<kw-")
|
||||
* removes all tags starting with "<kw-".
|
||||
@@ -244,7 +263,9 @@ String simplify_tagged(const String& str);
|
||||
/// Simplify a tagged string by merging adjecent open/close tags
|
||||
/** e.g. "<tag></tag>" --> ""
|
||||
*
|
||||
* @param all Merge all tags, if false only merges b,i,sym, and <tag></tag> pairs. But not </tag><tag>.
|
||||
* @param all Merge all tags, if false only merges b,i,u,strike,color,size,font,sym, and <tag></tag> pairs.
|
||||
* But not </tag><tag>, as this could lead to structural changes. For example, merging </li><li> would
|
||||
* fuse two bullet points together.
|
||||
*/
|
||||
String simplify_tagged_merge(const String& str, bool all = false);
|
||||
|
||||
|
||||
@@ -134,9 +134,12 @@ enum ChildMenuID {
|
||||
ID_FORMAT_ITALIC,
|
||||
ID_FORMAT_UNDERLINE,
|
||||
ID_FORMAT_STRIKETHROUGH,
|
||||
ID_FORMAT_COLOR,
|
||||
ID_FORMAT_BULLETPOINT,
|
||||
ID_FORMAT_SYMBOL,
|
||||
ID_FORMAT_REMINDER,
|
||||
ID_INSERT_SYMBOL,
|
||||
ID_FORMAT_FONT,
|
||||
|
||||
// Spelling errors
|
||||
ID_SPELLING_ADD_TO_DICT = 6301,
|
||||
|
||||