diff --git a/data/en.mse-locale/locale b/data/en.mse-locale/locale index 265a063f..16ef6c70 100644 --- a/data/en.mse-locale/locale +++ b/data/en.mse-locale/locale @@ -195,15 +195,20 @@ button: # Card select select all: Select &All select none: Select &None + + # Update checker + close: &Close ############################################################## Titles in the GUI title: magic set editor: Magic Set Editor about: About Magic Set Editor + symbol editor: Symbol Editor # dialogs open set: Open Set save set: Save Set As save image: Save Image + updates availible: Updates Availible #preferences preferences: Preferences display: Display diff --git a/src/gui/set/window.cpp b/src/gui/set/window.cpp index 2b0aaa17..c52d08c1 100644 --- a/src/gui/set/window.cpp +++ b/src/gui/set/window.cpp @@ -94,11 +94,11 @@ SetWindow::SetWindow(Window* parent, const SetP& set) menuBar->Append(menuHelp, _MENU_("help")); SetMenuBar(menuBar); - + // status bar CreateStatusBar(); SetStatusText(_("Welcome to Magic Set Editor")); - + // tool bar wxToolBar* tb = CreateToolBar(wxTB_FLAT | wxNO_BORDER | wxTB_HORIZONTAL); tb->SetToolBitmapSize(wxSize(18,18)); diff --git a/src/gui/symbol/window.cpp b/src/gui/symbol/window.cpp index 5f437f5c..7054e303 100644 --- a/src/gui/symbol/window.cpp +++ b/src/gui/symbol/window.cpp @@ -48,7 +48,7 @@ SymbolWindow::SymbolWindow(Window* parent, const SymbolValueP& value, const SetP } void SymbolWindow::init(Window* parent, SymbolP symbol) { - Create(parent, wxID_ANY, _("Symbol Editor"), wxDefaultPosition, wxSize(600,600), wxDEFAULT_FRAME_STYLE | wxNO_FULL_REPAINT_ON_RESIZE); + Create(parent, wxID_ANY, _TITLE_("symbol editor"), wxDefaultPosition, wxSize(600,600), wxDEFAULT_FRAME_STYLE | wxNO_FULL_REPAINT_ON_RESIZE); inSelectionEvent = false; // Menu bar diff --git a/src/gui/update_checker.cpp b/src/gui/update_checker.cpp index 4a451121..e641a22c 100644 --- a/src/gui/update_checker.cpp +++ b/src/gui/update_checker.cpp @@ -120,11 +120,11 @@ struct HtmlWindowToBrowser : public wxHtmlWindow { void show_update_dialog(Window* parent) { if (!update_available()) return; // we already have the latest version // Show update dialog - wxDialog* dlg = new wxDialog(parent, wxID_ANY, _("Updates availible"), wxDefaultPosition); + wxDialog* dlg = new wxDialog(parent, wxID_ANY, _TITLE_("updates availible"), wxDefaultPosition); // controls wxHtmlWindow* html = new HtmlWindowToBrowser(dlg, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER); html->SetPage(update_version_data->description); - wxButton* close = new wxButton(dlg, wxID_OK, _("&Close")); + wxButton* close = new wxButton(dlg, wxID_OK, _BUTTON_("close")); close->SetDefault(); // layout wxSizer* s = new wxBoxSizer(wxVERTICAL); diff --git a/src/gui/value/text.cpp b/src/gui/value/text.cpp index 04b46447..4149cb0e 100644 --- a/src/gui/value/text.cpp +++ b/src/gui/value/text.cpp @@ -54,7 +54,8 @@ END_EVENT_TABLE () // ----------------------------------------------------------------------------- : TextValueEditor IMPLEMENT_VALUE_EDITOR(Text) - , selection_start(0), selection_end(0) + , selection_start (0), selection_end (0) + , selection_start_i(0), selection_end_i(0) , select_words(false) , scrollbar(nullptr) {} @@ -67,7 +68,7 @@ TextValueEditor::~TextValueEditor() { void TextValueEditor::onLeftDown(const RealPoint& pos, wxMouseEvent& ev) { select_words = false; - moveSelection(v.indexAt(style().getRotation().trInv(pos)), !ev.ShiftDown(), MOVE_MID); + moveSelection(TYPE_INDEX, v.indexAt(style().getRotation().trInv(pos)), !ev.ShiftDown(), MOVE_MID); } void TextValueEditor::onLeftUp(const RealPoint& pos, wxMouseEvent&) { // TODO: lookup position of click? @@ -78,9 +79,9 @@ void TextValueEditor::onMotion(const RealPoint& pos, wxMouseEvent& ev) { size_t index = v.indexAt(style().getRotation().trInv(pos)); if (select_words) { // TODO: on the left, swap start and end - moveSelection(index < selection_start ? prevWordBoundry(index) : nextWordBoundry(index), false, MOVE_MID); + moveSelection(TYPE_INDEX, index < selection_start_i ? prevWordBoundry(index) : nextWordBoundry(index), false, MOVE_MID); } else { - moveSelection(index, false, MOVE_MID); + moveSelection(TYPE_INDEX, index, false, MOVE_MID); } } } @@ -88,16 +89,16 @@ void TextValueEditor::onMotion(const RealPoint& pos, wxMouseEvent& ev) { void TextValueEditor::onLeftDClick(const RealPoint& pos, wxMouseEvent& ev) { select_words = true; size_t index = v.indexAt(style().getRotation().trInv(pos)); - moveSelection(prevWordBoundry(index), true, MOVE_MID); - moveSelection(nextWordBoundry(index), false, MOVE_MID); + moveSelection(TYPE_INDEX, prevWordBoundry(index), true, MOVE_MID); + moveSelection(TYPE_INDEX, nextWordBoundry(index), false, MOVE_MID); } void TextValueEditor::onRightDown(const RealPoint& pos, wxMouseEvent& ev) { size_t index = v.indexAt(style().getRotation().trInv(pos)); - if (index < min(selection_start, selection_end) || - index > max(selection_start, selection_end)) { + if (index < min(selection_start_i, selection_end_i) || + index > max(selection_start_i, selection_end_i)) { // only move cursor when outside selection - moveSelection(index, !ev.ShiftDown(), MOVE_MID); + moveSelection(TYPE_INDEX, index, !ev.ShiftDown(), MOVE_MID); } } @@ -109,48 +110,48 @@ void TextValueEditor::onChar(wxKeyEvent& ev) { case WXK_LEFT: // move left (selection?) if (ev.ControlDown()) { - moveSelection(prevWordBoundry(selection_end),!ev.ShiftDown(), MOVE_LEFT); + moveSelection(TYPE_INDEX, prevWordBoundry(selection_end_i),!ev.ShiftDown(), MOVE_LEFT); } else { - moveSelection(prevCharBoundry(selection_end),!ev.ShiftDown(), MOVE_LEFT); + moveSelection(TYPE_CURSOR, prevCharBoundry(selection_end), !ev.ShiftDown(), MOVE_LEFT); } break; case WXK_RIGHT: // move left (selection?) if (ev.ControlDown()) { - moveSelection(nextWordBoundry(selection_end),!ev.ShiftDown(), MOVE_RIGHT); + moveSelection(TYPE_INDEX, nextWordBoundry(selection_end_i),!ev.ShiftDown(), MOVE_RIGHT); } else { - moveSelection(nextCharBoundry(selection_end),!ev.ShiftDown(), MOVE_RIGHT); + moveSelection(TYPE_CURSOR, nextCharBoundry(selection_end), !ev.ShiftDown(), MOVE_RIGHT); } break; case WXK_UP: - moveSelection(v.moveLine(selection_end, -1), !ev.ShiftDown(), MOVE_LEFT); + moveSelection(TYPE_INDEX, v.moveLine(selection_end_i, -1), !ev.ShiftDown(), MOVE_LEFT); break; case WXK_DOWN: - moveSelection(v.moveLine(selection_end, +1), !ev.ShiftDown(), MOVE_RIGHT); + moveSelection(TYPE_INDEX, v.moveLine(selection_end_i, +1), !ev.ShiftDown(), MOVE_RIGHT); break; case WXK_HOME: // move to begining of line / all (if control) if (ev.ControlDown()) { - moveSelection(0, !ev.ShiftDown(), MOVE_LEFT); + moveSelection(TYPE_INDEX, 0, !ev.ShiftDown(), MOVE_LEFT); } else { - moveSelection(v.lineStart(selection_end), !ev.ShiftDown(), MOVE_LEFT); + moveSelection(TYPE_INDEX, v.lineStart(selection_end_i), !ev.ShiftDown(), MOVE_LEFT); } break; case WXK_END: // move to end of line / all (if control) if (ev.ControlDown()) { - moveSelection(value().value().size(), !ev.ShiftDown(), MOVE_RIGHT); + moveSelection(TYPE_INDEX, value().value().size(), !ev.ShiftDown(), MOVE_RIGHT); } else { - moveSelection(v.lineEnd(selection_end), !ev.ShiftDown(), MOVE_RIGHT); + moveSelection(TYPE_INDEX, v.lineEnd(selection_end_i), !ev.ShiftDown(), MOVE_RIGHT); } break; case WXK_BACK: if (selection_start == selection_end) { // if no selection, select previous character - moveSelectionNoRedraw(prevCharBoundry(selection_end), false); + moveSelectionNoRedraw(TYPE_CURSOR, prevCharBoundry(selection_end), false); if (selection_start == selection_end) { // Walk over a as if we are the LEFT key - moveSelection(prevCharBoundry(selection_end), true, MOVE_LEFT); + moveSelection(TYPE_CURSOR, prevCharBoundry(selection_end), true, MOVE_LEFT); return; } } @@ -159,10 +160,10 @@ void TextValueEditor::onChar(wxKeyEvent& ev) { case WXK_DELETE: if (selection_start == selection_end) { // if no selection select next - moveSelectionNoRedraw(nextCharBoundry(selection_end), false); + moveSelectionNoRedraw(TYPE_CURSOR, nextCharBoundry(selection_end), false); if (selection_start == selection_end) { // Walk over a as if we are the RIGHT key - moveSelection(nextCharBoundry(selection_end), true, MOVE_RIGHT); + moveSelection(TYPE_CURSOR, nextCharBoundry(selection_end), true, MOVE_RIGHT); } } replaceSelection(wxEmptyString, _("Delete")); @@ -193,12 +194,13 @@ void TextValueEditor::onLoseFocus() { assert(caret); if (caret->IsVisible()) caret->Hide(); // hide selection - selection_start = selection_end = 0; + selection_start = selection_end = 0; + selection_start_i = selection_end_i = 0; } bool TextValueEditor::onContextMenu(wxMenu& m, wxContextMenuEvent& ev) { // in a keword? => "reminder text" option - size_t kwpos = in_tag(value().value(), _(" value().value().size()) selection_start = value().value().size(); - if (selection_end > value().value().size()) selection_end = value().value().size(); - size_t start = min(selection_start, selection_end); - size_t end = max(selection_start, selection_end); + if (selection_start_i > value().value().size()) selection_start_i = value().value().size(); + if (selection_end_i > value().value().size()) selection_end_i = value().value().size(); + size_t start = min(selection_start_i, selection_end_i); + size_t end = max(selection_start_i, selection_end_i); String str = untag(value().value().substr(start, end - start)); if (str.empty()) return false; // no data to copy // set data @@ -340,11 +343,11 @@ bool TextValueEditor::canFormat(int type) const { bool TextValueEditor::hasFormat(int type) const { switch (type) { case ID_FORMAT_BOLD: - return in_tag(value().value(), _(" // it is not 0 for empty text, because TextRenderer handles that case @@ -437,10 +440,10 @@ void TextValueEditor::replaceSelection(const String& replacement, const String& fixSelection(); // execute the action before adding it to the stack, // because we want to run scripts before action listeners see the action - ValueAction* action = typing_action(valueP(), selection_start, selection_end, replacement, name); + ValueAction* action = typing_action(valueP(), selection_start_i, selection_end_i, replacement, name); if (!action) { - // nothing changed, but move the selection anyway - moveSelection(selection_start); + // nothing changes, but move the selection anyway + moveSelection(TYPE_CURSOR, selection_start); return; } // perform the action @@ -451,12 +454,12 @@ void TextValueEditor::replaceSelection(const String& replacement, const String& String val = value().value(); Char typed = replacement.GetChar(0); Char typedU = toUpper(typed); - Char cur = val.GetChar(selection_start); + Char cur = val.GetChar(selection_start_i); // the cursor may have moved because of sorting... // is 'replacement' just after the current cursor? - if (selection_start >= 0 && selection_start < val.size() && (cur == typed || cur == typedU)) { + if (selection_start_i >= 0 && selection_start_i < val.size() && (cur == typed || cur == typedU)) { // no need to move cursor in a special way - selection_end = selection_start = min(selection_end, selection_start) + 1; + selection_end_i = selection_start_i = min(selection_end_i, selection_start_i) + 1; } else { // find the last occurence of 'replacement' in the value size_t pos = val.find_last_of(typed); @@ -465,22 +468,23 @@ void TextValueEditor::replaceSelection(const String& replacement, const String& pos = val.find_last_of(typedU); } if (pos != String::npos) { - selection_end = selection_start = pos + 1; + selection_end_i = selection_start_i = pos + 1; } else { - selection_end = selection_start; + selection_end_i = selection_start_i; } } } else { - selection_end = selection_start = min(selection_end, selection_start) + replacement.size(); + selection_end_i = selection_start_i = min(selection_end_i, selection_start_i) + replacement.size(); } + fixSelection(TYPE_INDEX, MOVE_MID); // scroll with next update // scrollWithCursor = true; } -void TextValueEditor::moveSelection(size_t new_end, bool also_move_start, Movement dir) { +void TextValueEditor::moveSelection(IndexType t, size_t new_end, bool also_move_start, Movement dir) { if (!isCurrent()) { // selection is only visible for curent editor, we can do a move the simple way - moveSelectionNoRedraw(new_end, also_move_start, dir); + moveSelectionNoRedraw(t, new_end, also_move_start, dir); return; } // Hide caret @@ -494,9 +498,9 @@ void TextValueEditor::moveSelection(size_t new_end, bool also_move_start, Moveme rdc.SetClippingRegion(style().getRect()); } // clear old selection by drawing it again - v.drawSelection(rdc, style(), selection_start, selection_end); + v.drawSelection(rdc, style(), selection_start_i, selection_end_i); // move - moveSelectionNoRedraw(new_end, also_move_start, dir); + moveSelectionNoRedraw(t, new_end, also_move_start, dir); // scroll? // scrollWithCursor = true; // if (onMove()) { @@ -518,23 +522,53 @@ void TextValueEditor::moveSelection(size_t new_end, bool also_move_start, Moveme rdc.DrawText(String::Format(_("%d - %d"),selection_start, selection_end), RealPoint(style().width-50,style().height-10)); } -void TextValueEditor::moveSelectionNoRedraw(size_t new_end, bool also_move_start, Movement dir) { - selection_end = new_end; - if (also_move_start) selection_start = selection_end; - fixSelection(dir); +void TextValueEditor::moveSelectionNoRedraw(IndexType t, size_t new_end, bool also_move_start, Movement dir) { + if (t == TYPE_INDEX) { + selection_end_i = new_end; + if (also_move_start) selection_start_i = selection_end_i; + } else { + selection_end = new_end; + if (also_move_start) selection_start = selection_end; + } + fixSelection(t, dir); } -void TextValueEditor::fixSelection(Movement dir) { +void TextValueEditor::fixSelection(IndexType t, Movement dir) { const String& val = value().value(); - // value may have become smaller because of undo/redo - // make sure the selection stays inside the text - size_t size = val.size(); - selection_end = min(size, selection_end); - selection_start = min(size, selection_start); + // Which type takes precedent? + if (t == TYPE_INDEX) { + selection_start = index_to_cursor(value().value(), selection_start_i, dir); + selection_end = index_to_cursor(value().value(), selection_end_i, dir); + } + // make sure the selection is at a valid position inside the text + selection_start_i = cursor_to_index(val, selection_start); + selection_end_i = cursor_to_index(val, selection_end); // start and end must be on the same side of separators size_t seppos = val.find(_(" seppos) || + (selection_start_i >= sepend && selection_end_i < sepend)) { + // not on same side, move selection end before sep + //selection_end = cursor_to_index(val, index_to_cursor(val, seppos)); + selection_end = index_to_cursor(val, seppos, dir); + selection_end_i = cursor_to_index(val, selection_end); + } + // find next separator + seppos = val.find(_(" seppos) selection_end = seppos; // not on same side if (selection_start >= sepend && selection_end < sepend) selection_end = sepend; // not on same side if (selection_start > seppos && selection_start < sepend) { @@ -551,7 +585,7 @@ void TextValueEditor::fixSelection(Movement dir) { // start or end in an ? if so, move them out size_t atompos = val.find(_(" atompos && selection_start < atomend) { // start inside atom selection_start = move(selection_start, atompos, atomend, dir); } @@ -561,10 +595,8 @@ void TextValueEditor::fixSelection(Movement dir) { // find next atom atompos = val.find(_(" +#include // for Movement #include #include @@ -17,11 +18,9 @@ class TextValueEditorScrollBar; // ----------------------------------------------------------------------------- : TextValueEditor -/// Directions of cursor movement -enum Movement -{ MOVE_LEFT ///< Always move the cursor to the left -, MOVE_MID ///< Move in whichever direction the distance to move is shorter (TODO: define shorter) -, MOVE_RIGHT ///< Always move the cursor to the right +enum IndexType +{ TYPE_CURSOR ///< Positions are cursor positions +, TYPE_INDEX ///< Positions are character indices }; /// An editor 'control' for editing TextValues @@ -83,16 +82,19 @@ class TextValueEditor : public TextValueViewer, public ValueEditor { // --------------------------------------------------- : Data private: - size_t selection_start, selection_end; ///< Cursor position/selection (if any) + size_t selection_start, selection_end; ///< Cursor position/selection (if any), cursor positions + size_t selection_start_i, selection_end_i; ///< Cursor position/selection, character indices TextValueEditorScrollBar* scrollbar; ///< Scrollbar for multiline fields in native look bool select_words; ///< Select whole words when dragging the mouse? // --------------------------------------------------- : Selection / movement - /// Move the selection to a new location, clears the previously drawn selection - void moveSelection(size_t new_end, bool also_move_start=true, Movement dir = MOVE_MID); - /// Move the selection to a new location, but does not redraw - void moveSelectionNoRedraw(size_t new_end, bool also_move_start=true, Movement dir = MOVE_MID); + /// Move the selection to a new location, clears the previously drawn selection. + /** t specifies what kind of position new_end is */ + void moveSelection(IndexType t, size_t new_end, bool also_move_start=true, Movement dir = MOVE_MID); + /// Move the selection to a new location, but does not redraw. + /** t specifies what kind of position new_end is */ + void moveSelectionNoRedraw(IndexType t, size_t new_end, bool also_move_start=true, Movement dir = MOVE_MID); /// Replace the current selection with 'replacement', name the action void replaceSelection(const String& replacement, const String& name); @@ -104,7 +106,7 @@ class TextValueEditor : public TextValueViewer, public ValueEditor { * * When correcting the selection, move in the given direction */ - void fixSelection(Movement dir = MOVE_MID); + void fixSelection(IndexType t = TYPE_CURSOR, Movement dir = MOVE_MID); /// Return a position resulting from moving pos outside the range [start...end), in the direction dir static size_t move(size_t pos, size_t start, size_t end, Movement dir); @@ -113,11 +115,13 @@ class TextValueEditor : public TextValueViewer, public ValueEditor { void showCaret(); /// Position of previous visible & selectable character + /** Uses cursor positions */ size_t prevCharBoundry(size_t pos) const; size_t nextCharBoundry(size_t pos) const; /// Front of previous word, used witch Ctrl+Left/right - size_t prevWordBoundry(size_t pos) const; - size_t nextWordBoundry(size_t pos) const; + /** Uses character indices */ + size_t prevWordBoundry(size_t pos_i) const; + size_t nextWordBoundry(size_t pos_i) const; // --------------------------------------------------- : Scrolling diff --git a/src/gui/welcome_window.cpp b/src/gui/welcome_window.cpp index ffb04ebd..064be170 100644 --- a/src/gui/welcome_window.cpp +++ b/src/gui/welcome_window.cpp @@ -64,7 +64,7 @@ void WelcomeWindow::draw(DC& dc) { } void WelcomeWindow::onOpenSet(wxCommandEvent&) { - wxFileDialog dlg(this, _("Open a set"), wxEmptyString, wxEmptyString, import_formats(), wxOPEN); + wxFileDialog dlg(this, _TITLE_("open set"), wxEmptyString, wxEmptyString, import_formats(), wxOPEN); if (dlg.ShowModal() == wxID_OK) { close(import_set(dlg.GetPath())); } diff --git a/src/render/text/element.hpp b/src/render/text/element.hpp index 0fbd1aa1..e971a8ff 100644 --- a/src/render/text/element.hpp +++ b/src/render/text/element.hpp @@ -130,7 +130,7 @@ class FontTextElement : public SimpleTextElement { : SimpleTextElement(text, start, end) , font(font) {} - + virtual void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; virtual void getCharInfo(RotatedDC& dc, double scale, vector& out) const; virtual double minScale() const; @@ -146,7 +146,7 @@ class SymbolTextElement : public SimpleTextElement { : SimpleTextElement(text, start, end) , font(font), ctx(*ctx) {} - + virtual void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; virtual void getCharInfo(RotatedDC& dc, double scale, vector& out) const; virtual double minScale() const; @@ -162,9 +162,22 @@ class CompoundTextElement : public TextElement { public: CompoundTextElement(const String& text, size_t start ,size_t end) : TextElement(text, start, end) {} + virtual void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; + virtual void getCharInfo(RotatedDC& dc, double scale, vector& out) const; + virtual double minScale() const; + TextElements elements; ///< the elements }; +/// A TextElement drawn using a grey background +class AtomTextElement : public CompoundTextElement { + public: + AtomTextElement(const String& text, size_t start ,size_t end) : CompoundTextElement(text, start, end) {} + + virtual void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; +}; + + // ----------------------------------------------------------------------------- : Other text elements /// A text element that displays a horizontal separator line diff --git a/src/render/text/viewer.hpp b/src/render/text/viewer.hpp index 532e04ba..3840ea19 100644 --- a/src/render/text/viewer.hpp +++ b/src/render/text/viewer.hpp @@ -81,7 +81,7 @@ class TextViewer { /// Is the character at the given index visible? bool isVisible(size_t index) const; /// Find the first character index that is at/before/after the given index, and which has a nonzero width - /** More precisely: it returns a position so that no character after it has zero width + /** More precisely: it returns a position so that the character after it in the direction delta has nonzero width */ size_t firstVisibleChar(size_t index, int delta) const; diff --git a/src/util/tagged_string.cpp b/src/util/tagged_string.cpp index 5620c7a3..0f6ca6f2 100644 --- a/src/util/tagged_string.cpp +++ b/src/util/tagged_string.cpp @@ -159,6 +159,80 @@ String anti_tag(const String& tag) { else return _(""); } +// ----------------------------------------------------------------------------- : Cursor position + +size_t index_to_cursor(const String& str, size_t index, Movement dir) { + size_t cursor = 0; + size_t start = 0, end = 0; + index = min(index, str.size()); + // find the range [start...end) with the same cursor value, that contains index + // after the loop, cursor corresponds to index end + for (size_t i = 0 ; i < str.size() ; ) { + Char c = str.GetChar(i); + if (c == _('<')) { + // a tag + if (is_substr(str, i, _(" index) break; + } else { + i = skip_tag(str, i); + end = i; + } + } else { + cursor++; + i++; + start = end; + end = i; + if (end > index) break; + } + } + if (cursor == 0) return 0; + if (i == str.size()) return cursor; + if (dir == MOVE_LEFT) return cursor - 1; + if (dir == MOVE_RIGHT) return cursor - (start == index); + // which is nearer? start or end? + return cursor - ((int)(index - start) <= (int)(end - index)); +} + +void cursor_to_index_range(const String& str, size_t cursor, size_t& start, size_t& end) { + start = end = 0; + size_t cur = 0; + size_t i = 0; + while (cur <= cursor && i < str.size()) { + Char c = str.GetChar(i); + if (c == _('<')) { + // a tag + if (is_substr(str, i, _(" return a position inside the tags + // This allows formating to be enabled without a selection + return start; +} + + // ----------------------------------------------------------------------------- : Global operations String remove_tag(const String& str, const String& tag) { diff --git a/src/util/tagged_string.hpp b/src/util/tagged_string.hpp index 8939c100..47c2cafb 100644 --- a/src/util/tagged_string.hpp +++ b/src/util/tagged_string.hpp @@ -80,6 +80,29 @@ String close_tag(const String& tag); /// The matching close tag for an open tag and vice versa String anti_tag(const String& tag); +// ----------------------------------------------------------------------------- : Cursor position + +/// Directions of cursor movement +enum Movement +{ MOVE_LEFT = -1 ///< Always move the cursor to the left +, MOVE_MID = 0 ///< Move in whichever direction the distance to move is shorter (TODO: define shorter) +, MOVE_RIGHT = 1 ///< Always move the cursor to the right +}; + +/// Find the cursor position corresponding to the given character index. +/** A cursor position always corresponds to a valid place to type text. + * The cursor position is rounded to the direction dir. + */ +size_t index_to_cursor(const String& str, size_t index, Movement dir = MOVE_MID); + +/// Find the range of character indeces corresponding to the given cursor position +/** The output parameters will correspond to the range [start...end) which are all valid character indices. + */ +void cursor_to_index_range(const String& str, size_t cursor, size_t& begin, size_t& end); + +/// Find the character index corresponding to the given cursor position +size_t cursor_to_index(const String& str, size_t cursor); + // ----------------------------------------------------------------------------- : Global operations /// Remove all instances of a tag and its close tag, but keep the contents.