diff --git a/resource/tool/bullet_point.png b/resource/tool/bullet_point.png new file mode 100644 index 00000000..583d36e7 Binary files /dev/null and b/resource/tool/bullet_point.png differ diff --git a/resource/tool/check_updates.png b/resource/tool/check_updates.png new file mode 100644 index 00000000..56e3adf5 Binary files /dev/null and b/resource/tool/check_updates.png differ diff --git a/resource/tool/color_text.png b/resource/tool/color_text.png new file mode 100644 index 00000000..841c2624 Binary files /dev/null and b/resource/tool/color_text.png differ diff --git a/resource/tool/dark_bullet_point.png b/resource/tool/dark_bullet_point.png new file mode 100644 index 00000000..d1caa7ab Binary files /dev/null and b/resource/tool/dark_bullet_point.png differ diff --git a/resource/tool/dark_check_updates.png b/resource/tool/dark_check_updates.png new file mode 100644 index 00000000..8c4351cf Binary files /dev/null and b/resource/tool/dark_check_updates.png differ diff --git a/resource/tool/dark_font.png b/resource/tool/dark_font.png new file mode 100644 index 00000000..48d0b654 Binary files /dev/null and b/resource/tool/dark_font.png differ diff --git a/resource/tool/dark_preferences.png b/resource/tool/dark_preferences.png new file mode 100644 index 00000000..417e2a18 Binary files /dev/null and b/resource/tool/dark_preferences.png differ diff --git a/resource/tool/dark_reload_data.png b/resource/tool/dark_reload_data.png new file mode 100644 index 00000000..d6043b6c Binary files /dev/null and b/resource/tool/dark_reload_data.png differ diff --git a/resource/tool/font.png b/resource/tool/font.png new file mode 100644 index 00000000..4950944c Binary files /dev/null and b/resource/tool/font.png differ diff --git a/resource/tool/preferences.png b/resource/tool/preferences.png new file mode 100644 index 00000000..8a4012fe Binary files /dev/null and b/resource/tool/preferences.png differ diff --git a/resource/tool/reload_data.png b/resource/tool/reload_data.png new file mode 100644 index 00000000..fecb2722 Binary files /dev/null and b/resource/tool/reload_data.png differ diff --git a/resource/win32_res.rc b/resource/win32_res.rc index fda47d01..aabd5a9e 100644 --- a/resource/win32_res.rc +++ b/resource/win32_res.rc @@ -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" diff --git a/src/data/action/value.cpp b/src/data/action/value.cpp index 1d50d07f..56be57e5 100644 --- a/src/data/action/value.cpp +++ b/src/data/action/value.cpp @@ -102,40 +102,202 @@ TextValue& TextValueAction::value() const { } +unique_ptr toggle_format_action(const TextValueP& value, vector& 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 , 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(value, start+offset, end+offset, end+offset, new_value, action_name); + } +} unique_ptr 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 += _(""); - new_value += substr(str, end_i); - } else { - // we are inside this tag, 'remove' it - new_value = substr(str, 0, start_i); - new_value += _(""); - 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 , 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(value, start, end, end, new_value, action_name); + return make_unique(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 tag_list; + map 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 = _("") + selection + _(""); + 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(_(""), 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, _("")), _("")); + selection = remove_tag(selection, _("
  • ")); + offset -= 1; + return prefix + selection + suffix; } unique_ptr 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) { diff --git a/src/data/action/value.hpp b/src/data/action/value.hpp index c8938782..3477ab56 100644 --- a/src/data/action/value.hpp +++ b/src/data/action/value.hpp @@ -127,8 +127,16 @@ private: String name; }; -/// Action for toggling some formating tag on or off in some range -unique_ptr 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 toggle_format_action(const TextValueP& value, vector& tags, size_t start_i, size_t end_i, size_t start, size_t end, const String& action_name); +unique_ptr 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 (, , 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 (, , 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 (
  • ) +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*/ diff --git a/src/data/font.cpp b/src/data/font.cpp index a99ee3de..23155e2f 100644 --- a/src/data/font.cpp +++ b/src/data/font.cpp @@ -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(); diff --git a/src/data/font.hpp b/src/data/font.hpp index 32d33b68..a1734c1e 100644 --- a/src/data/font.hpp +++ b/src/data/font.hpp @@ -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 diff --git a/src/data/settings.cpp b/src/data/settings.cpp index c493c267..cff49b1e 100644 --- a/src/data/settings.cpp +++ b/src/data/settings.cpp @@ -124,6 +124,7 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(GameSettings) { REFLECT(pack_amounts); REFLECT(pack_seed_random); REFLECT(pack_seed); + REFLECT(custom_colors); } diff --git a/src/data/settings.hpp b/src/data/settings.hpp index 0359623c..522108f2 100644 --- a/src/data/settings.hpp +++ b/src/data/settings.hpp @@ -85,7 +85,9 @@ public: map pack_amounts; bool pack_seed_random; int pack_seed; - + vector custom_colors; + static const size_t max_custom_colors = 16; // store this many custom colors for the color picker + DECLARE_REFLECTION(); private: bool initialized; diff --git a/src/gui/set/cards_panel.cpp b/src/gui/set/cards_panel.cpp index d37ab099..6259313c 100644 --- a/src/gui/set/cards_panel.cpp +++ b/src/gui/set/cards_panel.cpp @@ -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())); @@ -473,9 +483,11 @@ void CardsPanel::onCommand(int id) { break; } 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) { editor->doFormat(id); } diff --git a/src/gui/set/set_info_panel.cpp b/src/gui/set/set_info_panel.cpp index 557f8cce..7381ea93 100644 --- a/src/gui/set/set_info_panel.cpp +++ b/src/gui/set/set_info_panel.cpp @@ -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; } diff --git a/src/gui/set/window.cpp b/src/gui/set/window.cpp index 916949e5..626fbc1c 100644 --- a/src/gui/set/window.cpp +++ b/src/gui/set/window.cpp @@ -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(); diff --git a/src/gui/value/text.cpp b/src/gui/value/text.cpp index 5a325f31..4a1af6d4 100644 --- a/src/gui/value/text.cpp +++ b/src/gui/value/text.cpp @@ -17,7 +17,9 @@ #include #include #include -#include +#include +#include +#include #include #include @@ -473,12 +475,19 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) { replaceSelection(_(""), _ACTION_("delete")); return true; case WXK_RETURN: - if (field().multi_line) { + if (field().multi_line) { + String prefix; + String suffix; + if (is_in_tag(value().value(), _(""); + suffix = _("
  • "); + } if (ev.ShiftDown()) { // soft line break - replaceSelection(_("\n"), _ACTION_("soft line break")); - } else { - replaceSelection(_("\n"), _ACTION_("enter")); + replaceSelection(prefix + _("\n") + suffix, _ACTION_("soft line break")); + } else { + // 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(), _("(getStyle().get()); + if (style) { + data.SetInitialFont(style->font.toWxFont(1.0)); + } + wxFontDialog dial(nullptr, data); + if (dial.ShowModal() == wxID_OK) { + data = dial.GetFontData(); + vector 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(); } diff --git a/src/render/text/element.cpp b/src/render/text/element.cpp index ebdfc1b0..7ac1108a 100644 --- a/src/render/text/element.cpp +++ b/src/render/text/element.cpp @@ -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, _(" outside
  • tag")); + } + } else if (is_tag(text, tag_start, _(" outside
  • tag")); } - paragraphs.back().margin_end_char = pos; } else if (is_tag(text, tag_start, _(":"), 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(), diff --git a/src/render/text/element.hpp b/src/render/text/element.hpp index ac036111..f9d726fe 100644 --- a/src/render/text/element.hpp +++ b/src/render/text/element.hpp @@ -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 diff --git a/src/render/text/font.cpp b/src/render/text/font.cpp index 61eebf65..4f6f0f0d 100644 --- a/src/render/text/font.cpp +++ b/src/render/text/font.cpp @@ -48,7 +48,8 @@ void FontTextElement::getCharInfo(RotatedDC& dc, double scale, vector& out.push_back(CharInfo( RealSize(s.width - prev_width, s.height), c == _(' ') ? LineBreak::SPACE : LineBreak::MAYBE, - draw_as == DRAW_ACTIVE // from tag + draw_as == DRAW_ACTIVE, // from tag + c == _('•') )); prev_width = s.width; } diff --git a/src/render/text/viewer.cpp b/src/render/text/viewer.cpp index 3c95549f..dbc644fa 100644 --- a/src/render/text/viewer.cpp +++ b/src/render/text/viewer.cpp @@ -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 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 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 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 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 @@ -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 -RealSize TextViewer::fitLineWidth(Line& line, RotatedDC& dc, const TextStyle& style) const { - RealSize line_size(line.margin_left + lineLeft(dc, style, line.top), 0); +RealSize TextViewer::fitLineWidth(Line& line, RotatedDC& dc, const TextStyle& style) const { + 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& 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,10 +578,10 @@ bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector& char // The word we are currently reading RealSize word_size; - vector positions_word; // positios for this word + vector positions_word; // positions for this word size_t word_end_or_soft = 0; size_t word_start = 0; - // For each character ... + // For each character ... for (size_t i = 0 ; i < chars.size() ; ++i) { const CharInfo& c = chars[i]; assert(i_para < elements.paragraphs.size()); @@ -608,8 +612,11 @@ bool TextViewer::prepareLinesAtScale(RotatedDC& dc, const vector& 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) { - line.margin_left += c.size.width; // character in left margin + 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& 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 @@ -830,9 +846,10 @@ void TextViewer::alignParagraph(size_t start_line, size_t end_line, const vector // TODO : work well with mask } -void TextViewer::Line::alignHorizontal(const vector& chars, const TextStyle& style, const RealRect& s) { - double width = this->width() - margin_left; - double target_width = s.width - margin_left - margin_right; +void TextViewer::Line::alignHorizontal(const vector& chars, const TextStyle& style, const RealRect& s) { + 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); diff --git a/src/util/error.cpp b/src/util/error.cpp index d3beb13a..9f48dfb7 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -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()); diff --git a/src/util/error.hpp b/src/util/error.hpp index db3dbdc1..ed613a11 100644 --- a/src/util/error.hpp +++ b/src/util/error.hpp @@ -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 diff --git a/src/util/tagged_string.cpp b/src/util/tagged_string.cpp index af06bc65..01168cbb 100644 --- a/src/util/tagged_string.cpp +++ b/src/util/tagged_string.cpp @@ -25,8 +25,8 @@ wxUniChar tag_char(wxUniChar c) { // Is a character the "end" of the tag name? // don't mistake as , only , and are considered -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(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(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(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(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, _("= 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, _(" is in stack remove it and returns true -// otherwise appends and returns fales +// otherwise appends and returns false // (where 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 is always removed // but 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 diff --git a/src/util/tagged_string.hpp b/src/util/tagged_string.hpp index 860d7a6a..0f7fd1ea 100644 --- a/src/util/tagged_string.hpp +++ b/src/util/tagged_string.hpp @@ -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 x * 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,16 +124,25 @@ 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 "abc",_) = 3 [[nodiscard]] size_t untagged_length(String::const_iterator it, String::const_iterator end); // ----------------------------------------------------------------------------- : 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 @@ -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, ... becomes a single character. /** 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); // ----------------------------------------------------------------------------- : 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, "" --> "" * - * @param all Merge all tags, if false only merges b,i,sym, and pairs. But not . + * @param all Merge all tags, if false only merges b,i,u,strike,color,size,font,sym, and pairs. + * But not , as this could lead to structural changes. For example, merging
  • would + * fuse two bullet points together. */ String simplify_tagged_merge(const String& str, bool all = false); diff --git a/src/util/window_id.hpp b/src/util/window_id.hpp index 79f72302..69f51581 100644 --- a/src/util/window_id.hpp +++ b/src/util/window_id.hpp @@ -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,