Commit be344ca6 authored by twanvl's avatar twanvl

improved cursor motion for up/down/home/end; text editor now remembers cursor...

improved cursor motion for up/down/home/end; text editor now remembers cursor positions (instead of indices) when applying scripts.
parent 794fa8a2
......@@ -130,10 +130,10 @@ TextValueAction* toggle_format_action(const TextValueP& value, const String& tag
}
}
TextValueAction* typing_action(const TextValueP& value, size_t start, size_t end, const String& replacement, const String& action_name) {
TextValueAction* typing_action(const TextValueP& value, size_t start_i, size_t end_i, size_t start, size_t end, const String& replacement, const String& action_name) {
bool reverse = start > end;
if (reverse) swap(start, end);
String new_value = tagged_substr_replace(value->value(), start, end, replacement);
String new_value = tagged_substr_replace(value->value(), start_i, end_i, replacement);
if (value->value() == new_value) {
// no change
return nullptr;
......@@ -141,9 +141,9 @@ TextValueAction* typing_action(const TextValueP& value, size_t start, size_t end
// if (name == _("Backspace")) {
// // HACK: put start after end
if (reverse) {
return new TextValueAction(value, end, start, start+replacement.size(), new_value, action_name);
return new TextValueAction(value, end, start, start+untag(replacement).size(), new_value, action_name);
} else {
return new TextValueAction(value, start, end, start+replacement.size(), new_value, action_name);
return new TextValueAction(value, start, end, start+untag(replacement).size(), new_value, action_name);
}
}
}
......
......@@ -75,7 +75,8 @@ class TextValueAction : public ValueAction {
TextValueAction* toggle_format_action(const TextValueP& value, const String& tag, size_t start, size_t end, const String& action_name);
/// Typing in a TextValue, replace the selection [start...end) with replacement
TextValueAction* typing_action(const TextValueP& value, size_t start, size_t end, const String& replacement, const String& action_name);
/** start and end are cursor positions, start_i and end_i are indices*/
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);
// ----------------------------------------------------------------------------- : Event
......
......@@ -143,25 +143,25 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) {
}
break;
case WXK_UP:
moveSelection(TYPE_INDEX, v.moveLine(selection_end_i, -1), !ev.ShiftDown(), MOVE_LEFT);
moveSelection(TYPE_INDEX, v.moveLine(selection_end_i, -1), !ev.ShiftDown(), MOVE_LEFT_OPT);
break;
case WXK_DOWN:
moveSelection(TYPE_INDEX, v.moveLine(selection_end_i, +1), !ev.ShiftDown(), MOVE_RIGHT);
moveSelection(TYPE_INDEX, v.moveLine(selection_end_i, +1), !ev.ShiftDown(), MOVE_RIGHT_OPT);
break;
case WXK_HOME:
// move to begining of line / all (if control)
if (ev.ControlDown()) {
moveSelection(TYPE_INDEX, 0, !ev.ShiftDown(), MOVE_LEFT);
moveSelection(TYPE_INDEX, 0, !ev.ShiftDown(), MOVE_LEFT_OPT);
} else {
moveSelection(TYPE_INDEX, v.lineStart(selection_end_i), !ev.ShiftDown(), MOVE_LEFT);
moveSelection(TYPE_INDEX, v.lineStart(selection_end_i), !ev.ShiftDown(), MOVE_LEFT_OPT);
}
break;
case WXK_END:
// move to end of line / all (if control)
if (ev.ControlDown()) {
moveSelection(TYPE_INDEX, value().value().size(), !ev.ShiftDown(), MOVE_RIGHT);
moveSelection(TYPE_INDEX, value().value().size(), !ev.ShiftDown(), MOVE_RIGHT_OPT);
} else {
moveSelection(TYPE_INDEX, v.lineEnd(selection_end_i), !ev.ShiftDown(), MOVE_RIGHT);
moveSelection(TYPE_INDEX, v.lineEnd(selection_end_i), !ev.ShiftDown(), MOVE_RIGHT_OPT);
}
break;
case WXK_BACK:
......@@ -306,11 +306,11 @@ void TextValueEditor::onValueChange() {
}
void TextValueEditor::onAction(const Action& action, bool undone) {
TextValueViewer::onAction(action, undone);
TextValueViewer::onValueChange();
TYPE_CASE(action, TextValueAction) {
selection_start_i = action.selection_start;
selection_end_i = action.selection_end;
fixSelection(TYPE_INDEX);
selection_start = action.selection_start;
selection_end = action.selection_end;
fixSelection(TYPE_CURSOR);
}
}
......@@ -475,7 +475,7 @@ 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_i, selection_end_i, replacement, name);
ValueAction* action = typing_action(valueP(), selection_start_i, selection_end_i, selection_start, selection_end, replacement, name);
if (!action) {
// nothing changes, but move the selection anyway
moveSelection(TYPE_CURSOR, selection_start);
......@@ -571,8 +571,8 @@ void TextValueEditor::fixSelection(IndexType t, Movement 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_start == selection_end ? MOVE_MID : MOVE_RIGHT);
selection_end_i = cursor_to_index(val, selection_end, selection_start == selection_end ? MOVE_MID : MOVE_LEFT);
selection_start_i = cursor_to_index(val, selection_start, selection_start == selection_end ? MOVE_MID : MOVE_RIGHT_OPT);
selection_end_i = cursor_to_index(val, selection_end, selection_start == selection_end ? MOVE_MID : MOVE_LEFT_OPT);
// start and end must be on the same side of separators
size_t seppos = val.find(_("<sep"));
while (seppos != String::npos) {
......@@ -580,11 +580,11 @@ void TextValueEditor::fixSelection(IndexType t, Movement dir) {
if (selection_start_i <= seppos && selection_end_i > seppos) {
// not on same side, move selection end before sep
selection_end = index_to_cursor(val, seppos, dir);
selection_end_i = cursor_to_index(val, selection_end, selection_start == selection_end ? MOVE_MID : MOVE_LEFT);
selection_end_i = cursor_to_index(val, selection_end, selection_start == selection_end ? MOVE_MID : MOVE_LEFT_OPT);
} else if (selection_start_i >= sepend && selection_end_i < sepend) {
// not on same side, move selection end after sep
selection_end = index_to_cursor(val, sepend, dir);
selection_end_i = cursor_to_index(val, selection_end, selection_start == selection_end ? MOVE_MID : MOVE_LEFT);
selection_end_i = cursor_to_index(val, selection_end, selection_start == selection_end ? MOVE_MID : MOVE_LEFT_OPT);
}
// find next separator
seppos = val.find(_("<sep"), seppos + 1);
......@@ -622,10 +622,10 @@ void TextValueEditor::select(size_t start, size_t end) {
}
size_t TextValueEditor::move(size_t pos, size_t start, size_t end, Movement dir) {
if (dir == MOVE_LEFT) return start;
if (dir == MOVE_RIGHT) return end;
if (pos * 2 > start + end) return end; // past the middle
else return start;
if (dir < 0 /*MOVE_LEFT*/) return start;
if (dir > 0 /*MOVE_RIGHT*/) return end;
if (pos * 2 > start + end) return end; // past the middle
else return start;
}
// ----------------------------------------------------------------------------- : Native look / scrollbar
......
......@@ -182,14 +182,46 @@ size_t index_to_cursor(const String& str, size_t index, Movement dir) {
if (is_substr(str, i, _("<atom")) || is_substr(str, i, _("<sep"))) {
// skip tag contents, tag counts as a single 'character'
size_t before = i;
size_t after = match_close_tag_end(str, i);
size_t close = match_close_tag(str, i);
size_t after = skip_tag(str, close);
if (index > before && index < after) {
// index is inside an atom, determine on which side we want the cursor
if (dir == MOVE_RIGHT) {
// Index is inside an atom, determine on which side we want the cursor
// This is the only place where MOVE_LEFT/RIGHT and MOVE_*_OPT differ
// for the OPT version we must check if we are actually past any real characters
// but, if the atom is empty, it still counts as a single character!
if (dir == MOVE_LEFT) {
return cursor;
} else if (dir == MOVE_RIGHT) {
return cursor + 1;
} else if (dir == MOVE_MID) {
// take the closest side
return cursor + ((int)(after - index) < (int)(index - before));
} else if (dir == MOVE_LEFT_OPT) {
// is there any non-tag after index?
bool empty = true;
while (i < close) {
c = str.GetChar(i);
if (c == _('<')) {
i = skip_tag(str, i);
} else if (i >= index) {
return cursor; // this is a non-tag character after index
} else {
empty = false;
++i;
}
}
return empty ? cursor : cursor + 1; // still didn't pass any
} else if (dir == MOVE_RIGHT_OPT) {
// is index actually past any non-tag?
while (i < close) {
if (i >= index) {
return cursor; // we didn't pass any non-tag stuff
}
c = str.GetChar(i);
if (c != _('<')) break;
i = skip_tag(str, i);
}
return cursor + 1; // yes it is
}
}
i = after;
......@@ -260,7 +292,7 @@ size_t cursor_to_index(const String& str, size_t cursor, Movement dir) {
}
}
// This allows formating to be enabled without a selection
return dir == MOVE_RIGHT ? end - 1 : start;
return dir <= 0 /*MOVE_LEFT*/ ? start : end - 1;
}
// ----------------------------------------------------------------------------- : Untagged position
......
......@@ -90,9 +90,11 @@ String anti_tag(const String& tag);
/// 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
{ MOVE_LEFT = -2 ///< Always move the cursor to the left
, MOVE_LEFT_OPT = -1 ///< Move the cursor to the left, but a position inside a tag is the same as that before
, MOVE_MID = 0 ///< Move in whichever direction the distance to move is shorter (TODO: define shorter)
, MOVE_RIGHT_OPT = 1 ///< Move the cursor to the left, but a position inside a tag is the same as that after
, MOVE_RIGHT = 2 ///< Always move the cursor to the right
};
/// Find the cursor position corresponding to the given character index.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment