add font picker, color picker and bullet point ui tools

This commit is contained in:
GenevensiS
2026-02-05 16:02:55 +01:00
parent 11e506739a
commit e9abd1fb77
31 changed files with 519 additions and 134 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

+11
View File
@@ -27,6 +27,10 @@ tool/export_image IMAGE "tool/export_image.png"
tool/export_images IMAGE "tool/export_images.png" tool/export_images IMAGE "tool/export_images.png"
tool/export_mws IMAGE "tool/export_mws.png" tool/export_mws IMAGE "tool/export_mws.png"
tool/export_apprentice IMAGE "tool/export_apprentice.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 IMAGE "tool/print.png"
tool/print_preview IMAGE "tool/print_preview.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/paste IMAGE "tool/paste.png"
tool/find IMAGE "tool/find.png" tool/find IMAGE "tool/find.png"
tool/dark_find IMAGE "tool/dark_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/bold IMAGE "tool/bold.png"
tool/dark_bold IMAGE "tool/dark_bold.png" tool/dark_bold IMAGE "tool/dark_bold.png"
tool/italic IMAGE "tool/italic.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/dark_underline IMAGE "tool/dark_underline.png"
tool/strikethrough IMAGE "tool/strikethrough.png" tool/strikethrough IMAGE "tool/strikethrough.png"
tool/dark_strikethrough IMAGE "tool/dark_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/symbol IMAGE "tool/symbol.png"
tool/dark_symbol IMAGE "tool/dark_symbol.png" tool/dark_symbol IMAGE "tool/dark_symbol.png"
tool/reminder IMAGE "tool/reminder.png" tool/reminder IMAGE "tool/reminder.png"
+183 -21
View File
@@ -102,40 +102,202 @@ 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) { 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) { if (start > end) {
swap(start, end); swap(start, end);
swap(start_i, end_i); swap(start_i, end_i);
} }
String new_value; String new_value = value->value();
const String& str = value->value(); int offset = 0;
// Are we inside the tag we are toggling? // Compute the changes
if (!is_in_tag(str, _("<") + tag, start_i, end_i)) { new_value = tag.Contains(":") ? compute_new_variable_value(new_value, tag, start_i, end_i):
// we are not inside this tag, add it tag == _("li") ? compute_new_bullet_value (new_value, offset, start_i, end_i):
new_value = substr(str, 0, start_i); compute_new_simple_value (new_value, tag, start_i, end_i);
new_value += _("<") + tag + _(">"); // Erase redundant tags
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
if (start != end) { if (start != end) {
// don't simplify if start == end, this way we insert <b></b>, allowing the // don't simplify if start == end, this way we insert <b></b>, allowing the
// user to press Ctrl+B and start typing bold text // user to press Ctrl+B and start typing bold text
new_value = simplify_tagged(new_value); new_value = simplify_tagged(new_value);
} }
// Build action
if (value->value() == new_value) { if (value->value() == new_value) {
return nullptr; // no changes return nullptr; // no changes
} else { } 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) { 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) {
+10 -2
View File
@@ -127,8 +127,16 @@ private:
String name; String name;
}; };
/// Action for toggling some formating tag on or off in some range /// Action for toggling some formating tag(s) 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); 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 /// Typing in a TextValue, replace the selection [start...end) with replacement
/** start and end are cursor positions, start_i and end_i are indices*/ /** start and end are cursor positions, start_i and end_i are indices*/
+1 -1
View File
@@ -183,7 +183,7 @@ wxFont Font::toWxFont(double scale) const {
font.SetPointSize(size > 1 ? size_i : int(scale * font.GetPointSize())); font.SetPointSize(size > 1 ? size_i : int(scale * font.GetPointSize()));
if (strikethrough()) font.MakeStrikethrough(); if (strikethrough()) font.MakeStrikethrough();
return font; 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()); font = wxFont(size_i, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, weight_i, underline(), italic_name());
} else { } else {
String familyName = name(); String familyName = name();
+1
View File
@@ -27,6 +27,7 @@ enum FontFlags
, FONT_CODE_STRING = 0x20 // syntax highlighting , FONT_CODE_STRING = 0x20 // syntax highlighting
, FONT_CODE_NUMBER = 0x40 // syntax highlighting , FONT_CODE_NUMBER = 0x40 // syntax highlighting
, FONT_CODE_OPER = 0x80 // 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 /// A reference to a font for rendering text
+1
View File
@@ -124,6 +124,7 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(GameSettings) {
REFLECT(pack_amounts); REFLECT(pack_amounts);
REFLECT(pack_seed_random); REFLECT(pack_seed_random);
REFLECT(pack_seed); REFLECT(pack_seed);
REFLECT(custom_colors);
} }
+3 -1
View File
@@ -85,7 +85,9 @@ public:
map<String, int> pack_amounts; map<String, int> pack_amounts;
bool pack_seed_random; bool pack_seed_random;
int pack_seed; 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(); DECLARE_REFLECTION();
private: private:
bool initialized; bool initialized;
+25 -13
View File
@@ -151,12 +151,15 @@ CardsPanel::CardsPanel(Window* parent, int id)
add_menu_item_tr(menuCard, ID_SELECT_COLUMNS, nullptr, "card_list_columns"); add_menu_item_tr(menuCard, ID_SELECT_COLUMNS, nullptr, "card_list_columns");
menuFormat = new wxMenu(); menuFormat = new wxMenu();
add_menu_item_tr(menuFormat, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", wxITEM_CHECK); add_menu_item_tr(menuFormat, ID_FORMAT_FONT, settings.darkModePrefix() + "font", "font", wxITEM_CHECK);
add_menu_item_tr(menuFormat, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", wxITEM_CHECK); add_menu_item_tr(menuFormat, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", wxITEM_CHECK);
add_menu_item_tr(menuFormat, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", 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_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_COLOR, "color_text", "color_text", wxITEM_CHECK);
add_menu_item_tr(menuFormat, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_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(); menuFormat->AppendSeparator();
insertSymbolMenu = new wxMenuItem(menuFormat, ID_INSERT_SYMBOL, _MENU_("insert symbol")); insertSymbolMenu = new wxMenuItem(menuFormat, ID_INSERT_SYMBOL, _MENU_("insert symbol"));
menuFormat->Append(insertSymbolMenu); menuFormat->Append(insertSymbolMenu);
@@ -285,12 +288,15 @@ wxMenu* CardsPanel::makeAddCardsSubmenu(bool add_single_card_option) {
void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) { void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
// Toolbar // Toolbar
add_tool_tr(tb, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", false, wxITEM_CHECK); add_tool_tr(tb, ID_FORMAT_FONT, settings.darkModePrefix() + "font", "font", false, wxITEM_CHECK);
add_tool_tr(tb, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", false, wxITEM_CHECK); add_tool_tr(tb, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", false, wxITEM_CHECK);
add_tool_tr(tb, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", 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_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_COLOR, "color_text", "color_text", false, wxITEM_CHECK);
add_tool_tr(tb, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_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(); tb->AddSeparator();
toolAddCard = add_tool_tr(tb, ID_CARD_ADD, "card_add", "add_card", false, wxITEM_DROPDOWN); toolAddCard = add_tool_tr(tb, ID_CARD_ADD, "card_add", "add_card", false, wxITEM_DROPDOWN);
tb->SetDropdownMenu(ID_CARD_ADD, makeAddCardsSubmenu(true)); 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) { void CardsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
// Toolbar // Toolbar
tb->DeleteTool(ID_FORMAT_FONT);
tb->DeleteTool(ID_FORMAT_BOLD); tb->DeleteTool(ID_FORMAT_BOLD);
tb->DeleteTool(ID_FORMAT_ITALIC); tb->DeleteTool(ID_FORMAT_ITALIC);
tb->DeleteTool(ID_FORMAT_UNDERLINE); tb->DeleteTool(ID_FORMAT_UNDERLINE);
tb->DeleteTool(ID_FORMAT_STRIKETHROUGH); tb->DeleteTool(ID_FORMAT_STRIKETHROUGH);
tb->DeleteTool(ID_FORMAT_COLOR);
tb->DeleteTool(ID_FORMAT_BULLETPOINT);
tb->DeleteTool(ID_FORMAT_SYMBOL); tb->DeleteTool(ID_FORMAT_SYMBOL);
tb->DeleteTool(ID_FORMAT_REMINDER); tb->DeleteTool(ID_FORMAT_REMINDER);
tb->DeleteTool(ID_CARD_ADD); 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_REMOVE: ev.Enable(card_list->canDelete()); break;
case ID_CARD_LINK: ev.Enable(card_list->canLink()); 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_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) { if (focused_control(this) == ID_EDITOR) {
ev.Enable(editor->canFormat(ev.GetId())); ev.Enable(editor->canFormat(ev.GetId()));
ev.Check (editor->hasFormat(ev.GetId())); ev.Check (editor->hasFormat(ev.GetId()));
@@ -473,9 +483,11 @@ void CardsPanel::onCommand(int id) {
break; break;
} }
case ID_SELECT_COLUMNS: { case ID_SELECT_COLUMNS: {
card_list->selectColumns(); 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) { if (focused_control(this) == ID_EDITOR) {
editor->doFormat(id); editor->doFormat(id);
} }
+23 -12
View File
@@ -34,21 +34,27 @@ void SetInfoPanel::onChangeSet() {
void SetInfoPanel::initUI(wxToolBar* tb, wxMenuBar* mb) { void SetInfoPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
// Toolbar // Toolbar
add_tool_tr(tb, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", false, wxITEM_CHECK); add_tool_tr(tb, ID_FORMAT_FONT, settings.darkModePrefix() + "font", "font", false, wxITEM_CHECK);
add_tool_tr(tb, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", false, wxITEM_CHECK); add_tool_tr(tb, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", false, wxITEM_CHECK);
add_tool_tr(tb, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", 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_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_COLOR, "color_text", "color_text", false, wxITEM_CHECK);
add_tool_tr(tb, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_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(); tb->Realize();
// Menus // Menus
auto menuFormat = new wxMenu(); 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_FONT, settings.darkModePrefix() + "font", "font", wxITEM_CHECK);
add_menu_item_tr(menuFormat, ID_FORMAT_ITALIC, settings.darkModePrefix() + "italic", "italic", wxITEM_CHECK); add_menu_item_tr(menuFormat, ID_FORMAT_BOLD, settings.darkModePrefix() + "bold", "bold", wxITEM_CHECK);
add_menu_item_tr(menuFormat, ID_FORMAT_UNDERLINE, settings.darkModePrefix() + "underline", "underline", 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_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_COLOR, "color_text", "color_text", wxITEM_CHECK);
add_menu_item_tr(menuFormat, ID_FORMAT_REMINDER, settings.darkModePrefix() + "reminder", "reminder_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")); mb->Insert(2, menuFormat, _MENU_("format"));
// focus on editor // focus on editor
editor->SetFocus(); editor->SetFocus();
@@ -56,10 +62,13 @@ void SetInfoPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
void SetInfoPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) { void SetInfoPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
// Toolbar // Toolbar
tb->DeleteTool(ID_FORMAT_FONT);
tb->DeleteTool(ID_FORMAT_BOLD); tb->DeleteTool(ID_FORMAT_BOLD);
tb->DeleteTool(ID_FORMAT_ITALIC); tb->DeleteTool(ID_FORMAT_ITALIC);
tb->DeleteTool(ID_FORMAT_UNDERLINE); tb->DeleteTool(ID_FORMAT_UNDERLINE);
tb->DeleteTool(ID_FORMAT_STRIKETHROUGH); tb->DeleteTool(ID_FORMAT_STRIKETHROUGH);
tb->DeleteTool(ID_FORMAT_COLOR);
tb->DeleteTool(ID_FORMAT_BULLETPOINT);
tb->DeleteTool(ID_FORMAT_SYMBOL); tb->DeleteTool(ID_FORMAT_SYMBOL);
tb->DeleteTool(ID_FORMAT_REMINDER); tb->DeleteTool(ID_FORMAT_REMINDER);
// Menus // Menus
@@ -68,7 +77,8 @@ void SetInfoPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
void SetInfoPanel::onUpdateUI(wxUpdateUIEvent& ev) { void SetInfoPanel::onUpdateUI(wxUpdateUIEvent& ev) {
switch (ev.GetId()) { 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.Enable(editor->canFormat(ev.GetId()));
ev.Check (editor->hasFormat(ev.GetId())); ev.Check (editor->hasFormat(ev.GetId()));
break; break;
@@ -78,7 +88,8 @@ void SetInfoPanel::onUpdateUI(wxUpdateUIEvent& ev) {
void SetInfoPanel::onCommand(int id) { void SetInfoPanel::onCommand(int id) {
switch (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); editor->doFormat(id);
break; break;
} }
+9 -9
View File
@@ -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_NEW, "new", "new_set");
add_menu_item_tr(menuFile, ID_FILE_OPEN, "open", "open_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, "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, "save", "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_DIRECTORY, "save", "save_set_as_directory");
add_menu_item_tr(menuFile, wxID_ANY, "export", "export", wxITEM_NORMAL, makeExportMenu()); add_menu_item_tr(menuFile, wxID_ANY, "export", "export", wxITEM_NORMAL, makeExportMenu());
menuFile->AppendSeparator(); 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 #if USE_SCRIPT_PROFILING
add_menu_item_tr(menuFile, ID_FILE_PROFILER, nullptr, "show_profiler"); add_menu_item_tr(menuFile, ID_FILE_PROFILER, nullptr, "show_profiler");
#endif #endif
// menuFile->Append(ID_FILE_INSPECT, _("Inspect Internal Data..."), _("Shows a the data in the set using a tree structure")); // menuFile->Append(ID_FILE_INSPECT, _("Inspect Internal Data..."), _("Shows a the data in the set using a tree structure"));
// menuFile->AppendSeparator(); // 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(); menuFile->AppendSeparator();
add_menu_item_tr(menuFile, ID_FILE_PRINT_PREVIEW, "print_preview", "print_preview"); add_menu_item_tr(menuFile, ID_FILE_PRINT_PREVIEW, "print_preview", "print_preview");
add_menu_item_tr(menuFile, ID_FILE_PRINT, "print", "print"); add_menu_item_tr(menuFile, ID_FILE_PRINT, "print", "print");
@@ -88,12 +88,12 @@ SetWindow::SetWindow(Window* parent, const SetP& set)
menuEdit->AppendSeparator(); menuEdit->AppendSeparator();
add_menu_item_tr(menuEdit, ID_EDIT_SELECT_ALL, nullptr, "select_all"); add_menu_item_tr(menuEdit, ID_EDIT_SELECT_ALL, nullptr, "select_all");
menuEdit->AppendSeparator(); menuEdit->AppendSeparator();
add_menu_item_tr(menuEdit, ID_EDIT_FIND, settings.darkModePrefix() + "find", "find"); 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_FIND_NEXT, settings.darkModePrefix() + "find", "find_next");
add_menu_item_tr(menuEdit, ID_EDIT_REPLACE, nullptr, "replace"); add_menu_item_tr(menuEdit, ID_EDIT_REPLACE, settings.darkModePrefix() + "find", "replace");
add_menu_item_tr(menuEdit, ID_EDIT_AUTO_REPLACE, nullptr, "auto_replace"); add_menu_item_tr(menuEdit, ID_EDIT_AUTO_REPLACE, settings.darkModePrefix() + "find", "auto_replace");
menuEdit->AppendSeparator(); 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")); menuBar->Append(menuEdit, _MENU_("edit"));
auto menuWindow = new wxMenu(); auto menuWindow = new wxMenu();
+74 -13
View File
@@ -17,7 +17,9 @@
#include <util/tagged_string.hpp> #include <util/tagged_string.hpp>
#include <util/find_replace.hpp> #include <util/find_replace.hpp>
#include <util/spell_checker.hpp> #include <util/spell_checker.hpp>
#include <util/window_id.hpp> #include <util/window_id.hpp>
#include <wx/fontdlg.h>
#include <wx/colordlg.h>
#include <wx/clipbrd.h> #include <wx/clipbrd.h>
#include <wx/caret.h> #include <wx/caret.h>
@@ -473,12 +475,19 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) {
replaceSelection(_(""), _ACTION_("delete")); replaceSelection(_(""), _ACTION_("delete"));
return true; return true;
case WXK_RETURN: case WXK_RETURN:
if (field().multi_line) { 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()) { if (ev.ShiftDown()) {
// soft line break // 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 { } else {
replaceSelection(_("\n"), _ACTION_("enter")); // hard line break
replaceSelection(prefix + _("\n") + suffix, _ACTION_("enter"));
} }
} }
return true; return true;
@@ -805,7 +814,8 @@ bool TextValueEditor::doDelete() {
bool TextValueEditor::canFormat(int type) const { bool TextValueEditor::canFormat(int type) const {
switch (type) { 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; return !style().always_symbol && style().allow_formating;
case ID_FORMAT_SYMBOL: case ID_FORMAT_SYMBOL:
return !style().always_symbol && style().allow_formating && style().symbol_font.valid(); 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 { bool TextValueEditor::hasFormat(int type) const {
switch (type) { switch (type) {
case ID_FORMAT_FONT:
return is_in_tag(value().value(), _("<font"), selection_start_i, selection_end_i);
case ID_FORMAT_BOLD: 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: 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: 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: case ID_FORMAT_STRIKETHROUGH:
return is_in_tag(value().value(), _("<strike"), selection_start_i, selection_end_i); 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: 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: { case ID_FORMAT_REMINDER: {
const String& v = value().value(); const String& v = value().value();
size_t tag = in_tag(v, _("<kw"), selection_start_i, selection_start_i); 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) { void TextValueEditor::doFormat(int type) {
size_t ss = selection_start, se = selection_end;
switch (type) { 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: { case ID_FORMAT_BOLD: {
addAction(toggle_format_action(valueP(), _("b"), selection_start_i, selection_end_i, selection_start, selection_end, _("Bold"))); addAction(toggle_format_action(valueP(), _("b"), selection_start_i, selection_end_i, selection_start, selection_end, _("Bold")));
break; 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"))); addAction(toggle_format_action(valueP(), _("strike"), selection_start_i, selection_end_i, selection_start, selection_end, _("Strikethrough")));
break; 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: { case ID_FORMAT_SYMBOL: {
addAction(toggle_format_action(valueP(), _("sym"), selection_start_i, selection_end_i, selection_start, selection_end, _("Symbols"))); addAction(toggle_format_action(valueP(), _("sym"), selection_start_i, selection_end_i, selection_start, selection_end, _("Symbols")));
break; break;
@@ -870,8 +933,6 @@ void TextValueEditor::doFormat(int type) {
break; break;
} }
} }
selection_start = ss;
selection_end = se;
fixSelection(); fixSelection();
} }
+19 -5
View File
@@ -172,12 +172,24 @@ private:
fromString(e->children, text, pos, end_tag); fromString(e->children, text, pos, end_tag);
elements.push_back(e); elements.push_back(e);
pos = skip_tag(text, end_tag); pos = skip_tag(text, end_tag);
} else if (is_tag(text, tag_start, _("</bullet"))) { } else if (is_tag(text, tag_start, _("<bullet"))) {
// end of bullet point, set margin here // 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) { if (li <= 0) {
queue_message(MESSAGE_WARNING, _("<bullet> outside <li> tag")); queue_message(MESSAGE_WARNING, _("<bullet> outside <li> tag"));
} }
paragraphs.back().margin_end_char = pos;
} else if (is_tag(text, tag_start, _("<margin"))) { } else if (is_tag(text, tag_start, _("<margin"))) {
size_t colon = text.find_first_of(_(">:"), tag_start); size_t colon = text.find_first_of(_(">:"), tag_start);
if (colon < pos - 1 && text.GetChar(colon) == _(':')) { if (colon < pos - 1 && text.GetChar(colon) == _(':')) {
@@ -282,7 +294,8 @@ private:
paragraphs.back().end = i + 1; paragraphs.back().end = i + 1;
paragraphs.emplace_back(); paragraphs.emplace_back();
paragraphs.back().start = i + 1; 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()) { if (!margins.empty()) {
paragraphs.back().margin_left = margins.back().left; paragraphs.back().margin_left = margins.back().left;
paragraphs.back().margin_right = margins.back().right; paragraphs.back().margin_right = margins.back().right;
@@ -303,7 +316,8 @@ private:
(kwpph > 0 ? FONT_SOFT : FONT_NORMAL) | (kwpph > 0 ? FONT_SOFT : FONT_NORMAL) |
(code > 0 ? FONT_CODE : FONT_NORMAL) | (code > 0 ? FONT_CODE : FONT_NORMAL) |
(code_kw > 0 ? FONT_CODE_KW : 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, underline > 0,
strikethrough > 0, strikethrough > 0,
fonts.empty() ? nullptr : &fonts.back(), fonts.empty() ? nullptr : &fonts.back(),
+6 -4
View File
@@ -37,12 +37,13 @@ struct CharInfo {
RealSize size; ///< Size of this character RealSize size; ///< Size of this character
LineBreak break_after : 16; ///< How/when to break after it? 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 soft : 1; ///< Is this a 'soft' character? soft characters are ignored for alignment
bool bullet : 1; ///< Is this a bullet point?
explicit CharInfo() 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) inline CharInfo(RealSize size, LineBreak break_after, bool soft = false, bool bullet = false)
: size(size), break_after(break_after), soft(soft) : size(size), break_after(break_after), soft(soft), bullet(bullet)
{} {}
}; };
@@ -151,7 +152,8 @@ public:
double margin_left = 0., margin_right = 0.; double margin_left = 0., margin_right = 0.;
double margin_top = 0.; //, margin_bottom = 0.; // TODO: more margin options? double margin_top = 0.; //, margin_bottom = 0.; // TODO: more margin options?
size_t start = String::npos, end = String::npos; 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 /// A list of text elements extracted from a string
+2 -1
View File
@@ -48,7 +48,8 @@ void FontTextElement::getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>&
out.push_back(CharInfo( out.push_back(CharInfo(
RealSize(s.width - prev_width, s.height), RealSize(s.width - prev_width, s.height),
c == _(' ') ? LineBreak::SPACE : LineBreak::MAYBE, c == _(' ') ? LineBreak::SPACE : LineBreak::MAYBE,
draw_as == DRAW_ACTIVE // from <soft> tag draw_as == DRAW_ACTIVE, // from <soft> tag
c == _('')
)); ));
prev_width = s.width; prev_width = s.width;
} }
+39 -22
View File
@@ -13,20 +13,22 @@
// ----------------------------------------------------------------------------- : Line // ----------------------------------------------------------------------------- : Line
struct TextViewer::Line { struct TextViewer::Line {
size_t start; ///< Index of the first character in this 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 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 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 top; ///< y position of (the top of) this line
double line_height; ///< The height of this line in pixels double line_height; ///< The height of this line in pixels
LineBreak break_after; ///< Is there a saparator after this line? LineBreak break_after; ///< Is there a saparator after this line?
optional<Alignment> alignment; ///< Alignment of this line optional<Alignment> alignment; ///< Alignment of this line
bool justifying; ///< Is the text justified? Only true when *really* justifying. bool justifying; ///< Is the text justified? Only true when *really* justifying.
double margin_left; ///< Left margin double margin_left; ///< Left margin including the margin tag and bullet point
double margin_right;///< Rightmargin 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() Line()
: start(0), end_or_soft(0), top(0), line_height(0) : 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 /// The position (just beyond) the bottom of this line
@@ -544,12 +546,13 @@ void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const
} }
// Try to fit a blank line in the masked image, move down until it fits // 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 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) { 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 // nothing fits on this line, move down one pixel
line.top += 1; 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; return line_size;
} }
@@ -565,7 +568,8 @@ bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector<CharInfo>& char
// first line // first line
Line line; Line line;
line.top = style.padding_top; 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.margin_right = elements.paragraphs[0].margin_right;
line.alignment = elements.paragraphs[0].alignment; line.alignment = elements.paragraphs[0].alignment;
// size of the line so far // size of the line so far
@@ -574,10 +578,10 @@ bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector<CharInfo>& char
// The word we are currently reading // The word we are currently reading
RealSize word_size; 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_end_or_soft = 0;
size_t word_start = 0; size_t word_start = 0;
// For each character ... // For each character ...
for (size_t i = 0 ; i < chars.size() ; ++i) { for (size_t i = 0 ; i < chars.size() ; ++i) {
const CharInfo& c = chars[i]; const CharInfo& c = chars[i];
assert(i_para < elements.paragraphs.size()); assert(i_para < elements.paragraphs.size());
@@ -608,8 +612,11 @@ bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector<CharInfo>& char
} }
positions_word.push_back(word_size.width); positions_word.push_back(word_size.width);
if (!c.soft) word_end_or_soft = i + 1; 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 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? // Did the word become too long?
if (!break_now) { 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; if (i_para+1 < elements.paragraphs.size()) ++i_para;
assert(elements.paragraphs[i_para].start == i + 1); assert(elements.paragraphs[i_para].start == i + 1);
line.margin_left = elements.paragraphs[i_para].margin_left; 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.margin_right = elements.paragraphs[i_para].margin_right;
line.top += elements.paragraphs[i_para].margin_top; line.top += elements.paragraphs[i_para].margin_top;
line.alignment = elements.paragraphs[i_para].alignment; line.alignment = elements.paragraphs[i_para].alignment;
} }
line.break_after = LineBreak::NO; 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 // reset line_size
line_size = fitLineWidth(line, dc, style); line_size = fitLineWidth(line, dc, style);
line.positions.push_back(line_size.width); // start position line.positions.push_back(line_size.width); // start position
@@ -830,9 +846,10 @@ void TextViewer::alignParagraph(size_t start_line, size_t end_line, const vector
// TODO : work well with mask // TODO : work well with mask
} }
void TextViewer::Line::alignHorizontal(const vector<CharInfo>& chars, const TextStyle& style, const RealRect& s) { void TextViewer::Line::alignHorizontal(const vector<CharInfo>& chars, const TextStyle& style, const RealRect& s) {
double width = this->width() - margin_left; double margin_bullet = bullet ? margin_left_before_bullet : margin_left;
double target_width = s.width - margin_left - margin_right; double width = this->width() - margin_bullet;
double target_width = s.width - margin_bullet - margin_right;
Alignment alignment = this->alignment.value_or(style.alignment); Alignment alignment = this->alignment.value_or(style.alignment);
bool should_fill = (alignment & ALIGN_IF_OVERFLOW ? width > target_width : true) bool should_fill = (alignment & ALIGN_IF_OVERFLOW ? width > target_width : true)
&& (alignment & ALIGN_IF_SOFTBREAK ? break_after == LineBreak::SOFT || !style.field().multi_line : true); && (alignment & ALIGN_IF_SOFTBREAK ? break_after == LineBreak::SOFT || !style.field().multi_line : true);
+3
View File
@@ -180,6 +180,9 @@ void queue_message(MessageType type, String const& msg) {
// Only show errors in the main thread // Only show errors in the main thread
message_queue.push_front(make_pair(type,msg)); message_queue.push_front(make_pair(type,msg));
} }
void qm(String const& msg) {
queue_message(MESSAGE_ERROR, msg);
}
void handle_error(const Error& e) { void handle_error(const Error& e) {
queue_message(e.is_fatal() ? MESSAGE_FATAL_ERROR : MESSAGE_ERROR, e.what()); queue_message(e.is_fatal() ? MESSAGE_FATAL_ERROR : MESSAGE_ERROR, e.what());
+1
View File
@@ -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 /** 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 queue_message(MessageType type, String const& msg);
void qm(String const& msg);
/// Handle an error by queuing a message /// Handle an error by queuing a message
void handle_error(const Error& e); void handle_error(const Error& e);
/// Handle an error by showing a message box /// Handle an error by showing a message box
+78 -24
View File
@@ -25,8 +25,8 @@ wxUniChar tag_char(wxUniChar c) {
// Is a character the "end" of the tag name? // 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> // don't mistake <tag> as <t>, only <t>, <t-stuff> and <t:stuff> are considered <t>
bool is_tag_end_char(Char c) { bool is_tag_end_char(Char c, bool strict) {
return c == '>' || c == '-' || c == ':' || c == ' '; 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); 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) { for (; *tag; ++it, ++tag) {
if (it == end || *it != *tag) return false; 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; return true;
} }
@@ -267,11 +267,25 @@ String::const_iterator find_close_tag(String::const_iterator tag, String::const_
return String::npos; return String::npos;
} }
bool is_tag(const String& str, size_t pos, const String& tag) { 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()]); 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 last_start = String::npos;
size_t size = str.size(); size_t size = str.size();
int taglevel = 0; 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 ; ) { for (size_t pos = 0 ; pos < end ; ) {
Char c = str.GetChar(pos); Char c = str.GetChar(pos);
if (c == _('<')) { 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; if (pos < start) last_start = pos;
++taglevel; ++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 --taglevel; // close tag
} }
pos = skip_tag(str,pos); 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; return taglevel < 1 ? String::npos : last_start;
} }
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) {
return in_tag(str,tag,start,end) != String::npos; return in_tag(str,tag,start,end,strict) != String::npos;
} }
@@ -325,6 +339,17 @@ String anti_tag(const String& tag) {
// ----------------------------------------------------------------------------- : Cursor position // ----------------------------------------------------------------------------- : 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 index_to_cursor(const String& str, size_t index, Movement dir) {
size_t cursor = 0; size_t cursor = 0;
index = min(index, str.size()); 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; bool has_width = true;
if (c == _('<')) { if (c == _('<')) {
// a tag // 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' // skip tag contents, tag counts as a single 'character'
size_t before = i; size_t before = i;
size_t close = match_close_tag(str, 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; bool has_width = true;
if (c == _('<')) { if (c == _('<')) {
// a tag // 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 // never move the end over an atom/sep
if (cur >= cursor) { ++i; break; } if (cur >= cursor) { ++i; break; }
// skip tag contents, tag counts as a single 'character' // 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"))) { } else if (is_substr(str, i, _("<sep"))) {
i = match_close_tag_end(str, i); i = match_close_tag_end(str, i);
ret += UNTAG_SEP; 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"))) { } else if (i == 0 && is_substr(str, i, _("<prefix"))) {
// prefix at start of string, skip contents, index never before // prefix at start of string, skip contents, index never before
i = match_close_tag_end(str,i); i = match_close_tag_end(str,i);
@@ -555,7 +597,14 @@ size_t index_to_untagged(const String& str, size_t index) {
return p; return p;
} }
// ----------------------------------------------------------------------------- : Global operations // ----------------------------------------------------------------------------- : 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) { String remove_tag(const String& str, const String& tag) {
if (tag.size() < 1) return str; if (tag.size() < 1) return str;
@@ -641,14 +690,18 @@ String simplify_tagged(const String& str) {
// Add a tag to a stack of tags, try to cancel it out // Add a tag to a stack of tags, try to cancel it out
// If </tag> is in stack remove it and returns true // 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) // (where </tag> is the negation of tag)
bool add_or_cancel_tag(const String& tag, String& stack, bool all = false) { bool add_or_cancel_tag(const String& tag, String& stack, bool all = false) {
if (all || starts_with(tag, _("/")) || if (all || starts_with(tag, _("/")) ||
starts_with(tag, _("b")) || starts_with(tag, _("b")) ||
starts_with(tag, _("i")) || starts_with(tag, _("i")) ||
starts_with(tag, _("sym")) ||
starts_with(tag, _("u")) || 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, // cancel out all close tags, but not all open tags,
// so <xx></xx> is always removed // so <xx></xx> is always removed
// but </xx><xx> is not // 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 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(); 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) { for (size_t i = 0 ; i < size ; ++i) {
Char c = str.GetChar(i); Char c = str.GetChar(i);
if (c == _('<')) { if (c == _('<')) {
@@ -688,17 +741,18 @@ String simplify_tagged_merge(const String& str, bool all) {
} }
String simplify_tagged_overlap(const String& str) { 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(); 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) { for (size_t i = 0 ; i < size ; ++i) {
Char c = str.GetChar(i); Char c = str.GetChar(i);
if (c == _('<')) { if (c == _('<')) {
String tag = tag_at(str, i); String tag = tag_at(str, i);
if (starts_with(tag, _("b")) || starts_with(tag, _("/b")) || if (starts_with(tag, _("b")) || starts_with(tag, _("/b")) ||
starts_with(tag, _("i")) || starts_with(tag, _("/i")) || starts_with(tag, _("i")) || starts_with(tag, _("/i")) ||
starts_with(tag, _("u")) || starts_with(tag, _("/u")) || starts_with(tag, _("sym")) || starts_with(tag, _("/sym")) ||
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 // optimize this tag
if (open_tags.find(_("<") + tag + _(">")) == String::npos) { if (open_tags.find(_("<") + tag + _(">")) == String::npos) {
// we are not already inside this tag // we are not already inside this tag
+27 -6
View File
@@ -74,7 +74,11 @@ String fix_old_tags(const String&);
/// Does a string contain a tag at the given location? /// Does a string contain a tag at the given location?
/** Only matches if the tag ends one of ">-: " */ /** 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? /// Is the given range entirely contained in a given tagged block?
/** If so: return the start position of that tag, otherwise returns String::npos /** 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> * <tag><tag></tag>x</tag>
* the x is in_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 /// 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 <>) /// Return the tag at the given position (without the <>)
String tag_at(const String& str, size_t pos); String tag_at(const String& str, size_t pos);
@@ -120,16 +124,25 @@ String anti_tag(const String& tag);
[[nodiscard]] String::const_iterator find_close_tag(String::const_iterator it, String::const_iterator end); [[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? /// 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" * 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 // Length of a string when not counting tags
// For example: untagged_length("<b>abc</b>",_) = 3 // For example: untagged_length("<b>abc</b>",_) = 3
[[nodiscard]] size_t untagged_length(String::const_iterator it, String::const_iterator end); [[nodiscard]] size_t untagged_length(String::const_iterator it, String::const_iterator end);
// ----------------------------------------------------------------------------- : Cursor position // ----------------------------------------------------------------------------- : 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 /// Directions of cursor movement
enum Movement enum Movement
@@ -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_ATOM = _('\2');
const Char UNTAG_SEP = _('\3'); const Char UNTAG_SEP = _('\3');
const Char UNTAG_ATOM_KWPPH = _('\4'); const Char UNTAG_ATOM_KWPPH = _('\4');
const Char UNTAG_BULLET = _('\5');
/// Untag a string for use with cursors, <atom>...</atom> becomes a single character. /// Untag a string for use with cursors, <atom>...</atom> becomes a single character.
/** This string should only be used for cursor position calculations. */ /** This string should only be used for cursor position calculations. */
@@ -179,6 +193,11 @@ size_t untagged_to_index(const String& str, size_t pos, bool inside, size_t star
size_t index_to_untagged(const String& str, size_t index); size_t index_to_untagged(const String& str, size_t index);
// ----------------------------------------------------------------------------- : Global operations // ----------------------------------------------------------------------------- : 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. /// 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-") /** tag doesn't have to be a complete tag, for example remove_tag(str, "<kw-")
@@ -244,7 +263,9 @@ String simplify_tagged(const String& str);
/// Simplify a tagged string by merging adjecent open/close tags /// Simplify a tagged string by merging adjecent open/close tags
/** e.g. "<tag></tag>" --> "" /** 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); String simplify_tagged_merge(const String& str, bool all = false);
+3
View File
@@ -134,9 +134,12 @@ enum ChildMenuID {
ID_FORMAT_ITALIC, ID_FORMAT_ITALIC,
ID_FORMAT_UNDERLINE, ID_FORMAT_UNDERLINE,
ID_FORMAT_STRIKETHROUGH, ID_FORMAT_STRIKETHROUGH,
ID_FORMAT_COLOR,
ID_FORMAT_BULLETPOINT,
ID_FORMAT_SYMBOL, ID_FORMAT_SYMBOL,
ID_FORMAT_REMINDER, ID_FORMAT_REMINDER,
ID_INSERT_SYMBOL, ID_INSERT_SYMBOL,
ID_FORMAT_FONT,
// Spelling errors // Spelling errors
ID_SPELLING_ADD_TO_DICT = 6301, ID_SPELLING_ADD_TO_DICT = 6301,