Commit cfd59072 authored by twanvl's avatar twanvl

improved cursor handling in text editor

parent 54659c21
...@@ -195,15 +195,20 @@ button: ...@@ -195,15 +195,20 @@ button:
# Card select # Card select
select all: Select &All select all: Select &All
select none: Select &None select none: Select &None
# Update checker
close: &Close
############################################################## Titles in the GUI ############################################################## Titles in the GUI
title: title:
magic set editor: Magic Set Editor magic set editor: Magic Set Editor
about: About Magic Set Editor about: About Magic Set Editor
symbol editor: Symbol Editor
# dialogs # dialogs
open set: Open Set open set: Open Set
save set: Save Set As save set: Save Set As
save image: Save Image save image: Save Image
updates availible: Updates Availible
#preferences #preferences
preferences: Preferences preferences: Preferences
display: Display display: Display
......
...@@ -94,11 +94,11 @@ SetWindow::SetWindow(Window* parent, const SetP& set) ...@@ -94,11 +94,11 @@ SetWindow::SetWindow(Window* parent, const SetP& set)
menuBar->Append(menuHelp, _MENU_("help")); menuBar->Append(menuHelp, _MENU_("help"));
SetMenuBar(menuBar); SetMenuBar(menuBar);
// status bar // status bar
CreateStatusBar(); CreateStatusBar();
SetStatusText(_("Welcome to Magic Set Editor")); SetStatusText(_("Welcome to Magic Set Editor"));
// tool bar // tool bar
wxToolBar* tb = CreateToolBar(wxTB_FLAT | wxNO_BORDER | wxTB_HORIZONTAL); wxToolBar* tb = CreateToolBar(wxTB_FLAT | wxNO_BORDER | wxTB_HORIZONTAL);
tb->SetToolBitmapSize(wxSize(18,18)); tb->SetToolBitmapSize(wxSize(18,18));
......
...@@ -48,7 +48,7 @@ SymbolWindow::SymbolWindow(Window* parent, const SymbolValueP& value, const SetP ...@@ -48,7 +48,7 @@ SymbolWindow::SymbolWindow(Window* parent, const SymbolValueP& value, const SetP
} }
void SymbolWindow::init(Window* parent, SymbolP symbol) { 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; inSelectionEvent = false;
// Menu bar // Menu bar
......
...@@ -120,11 +120,11 @@ struct HtmlWindowToBrowser : public wxHtmlWindow { ...@@ -120,11 +120,11 @@ struct HtmlWindowToBrowser : public wxHtmlWindow {
void show_update_dialog(Window* parent) { void show_update_dialog(Window* parent) {
if (!update_available()) return; // we already have the latest version if (!update_available()) return; // we already have the latest version
// Show update dialog // 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 // controls
wxHtmlWindow* html = new HtmlWindowToBrowser(dlg, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER); wxHtmlWindow* html = new HtmlWindowToBrowser(dlg, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER);
html->SetPage(update_version_data->description); 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(); close->SetDefault();
// layout // layout
wxSizer* s = new wxBoxSizer(wxVERTICAL); wxSizer* s = new wxBoxSizer(wxVERTICAL);
......
...@@ -54,7 +54,8 @@ END_EVENT_TABLE () ...@@ -54,7 +54,8 @@ END_EVENT_TABLE ()
// ----------------------------------------------------------------------------- : TextValueEditor // ----------------------------------------------------------------------------- : TextValueEditor
IMPLEMENT_VALUE_EDITOR(Text) 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) , select_words(false)
, scrollbar(nullptr) , scrollbar(nullptr)
{} {}
...@@ -67,7 +68,7 @@ TextValueEditor::~TextValueEditor() { ...@@ -67,7 +68,7 @@ TextValueEditor::~TextValueEditor() {
void TextValueEditor::onLeftDown(const RealPoint& pos, wxMouseEvent& ev) { void TextValueEditor::onLeftDown(const RealPoint& pos, wxMouseEvent& ev) {
select_words = false; 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&) { void TextValueEditor::onLeftUp(const RealPoint& pos, wxMouseEvent&) {
// TODO: lookup position of click? // TODO: lookup position of click?
...@@ -78,9 +79,9 @@ void TextValueEditor::onMotion(const RealPoint& pos, wxMouseEvent& ev) { ...@@ -78,9 +79,9 @@ void TextValueEditor::onMotion(const RealPoint& pos, wxMouseEvent& ev) {
size_t index = v.indexAt(style().getRotation().trInv(pos)); size_t index = v.indexAt(style().getRotation().trInv(pos));
if (select_words) { if (select_words) {
// TODO: on the left, swap start and end // 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 { } 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) { ...@@ -88,16 +89,16 @@ void TextValueEditor::onMotion(const RealPoint& pos, wxMouseEvent& ev) {
void TextValueEditor::onLeftDClick(const RealPoint& pos, wxMouseEvent& ev) { void TextValueEditor::onLeftDClick(const RealPoint& pos, wxMouseEvent& ev) {
select_words = true; select_words = true;
size_t index = v.indexAt(style().getRotation().trInv(pos)); size_t index = v.indexAt(style().getRotation().trInv(pos));
moveSelection(prevWordBoundry(index), true, MOVE_MID); moveSelection(TYPE_INDEX, prevWordBoundry(index), true, MOVE_MID);
moveSelection(nextWordBoundry(index), false, MOVE_MID); moveSelection(TYPE_INDEX, nextWordBoundry(index), false, MOVE_MID);
} }
void TextValueEditor::onRightDown(const RealPoint& pos, wxMouseEvent& ev) { void TextValueEditor::onRightDown(const RealPoint& pos, wxMouseEvent& ev) {
size_t index = v.indexAt(style().getRotation().trInv(pos)); size_t index = v.indexAt(style().getRotation().trInv(pos));
if (index < min(selection_start, selection_end) || if (index < min(selection_start_i, selection_end_i) ||
index > max(selection_start, selection_end)) { index > max(selection_start_i, selection_end_i)) {
// only move cursor when outside selection // 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) { ...@@ -109,48 +110,48 @@ void TextValueEditor::onChar(wxKeyEvent& ev) {
case WXK_LEFT: case WXK_LEFT:
// move left (selection?) // move left (selection?)
if (ev.ControlDown()) { if (ev.ControlDown()) {
moveSelection(prevWordBoundry(selection_end),!ev.ShiftDown(), MOVE_LEFT); moveSelection(TYPE_INDEX, prevWordBoundry(selection_end_i),!ev.ShiftDown(), MOVE_LEFT);
} else { } else {
moveSelection(prevCharBoundry(selection_end),!ev.ShiftDown(), MOVE_LEFT); moveSelection(TYPE_CURSOR, prevCharBoundry(selection_end), !ev.ShiftDown(), MOVE_LEFT);
} }
break; break;
case WXK_RIGHT: case WXK_RIGHT:
// move left (selection?) // move left (selection?)
if (ev.ControlDown()) { if (ev.ControlDown()) {
moveSelection(nextWordBoundry(selection_end),!ev.ShiftDown(), MOVE_RIGHT); moveSelection(TYPE_INDEX, nextWordBoundry(selection_end_i),!ev.ShiftDown(), MOVE_RIGHT);
} else { } else {
moveSelection(nextCharBoundry(selection_end),!ev.ShiftDown(), MOVE_RIGHT); moveSelection(TYPE_CURSOR, nextCharBoundry(selection_end), !ev.ShiftDown(), MOVE_RIGHT);
} }
break; break;
case WXK_UP: 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; break;
case WXK_DOWN: 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; break;
case WXK_HOME: case WXK_HOME:
// move to begining of line / all (if control) // move to begining of line / all (if control)
if (ev.ControlDown()) { if (ev.ControlDown()) {
moveSelection(0, !ev.ShiftDown(), MOVE_LEFT); moveSelection(TYPE_INDEX, 0, !ev.ShiftDown(), MOVE_LEFT);
} else { } else {
moveSelection(v.lineStart(selection_end), !ev.ShiftDown(), MOVE_LEFT); moveSelection(TYPE_INDEX, v.lineStart(selection_end_i), !ev.ShiftDown(), MOVE_LEFT);
} }
break; break;
case WXK_END: case WXK_END:
// move to end of line / all (if control) // move to end of line / all (if control)
if (ev.ControlDown()) { if (ev.ControlDown()) {
moveSelection(value().value().size(), !ev.ShiftDown(), MOVE_RIGHT); moveSelection(TYPE_INDEX, value().value().size(), !ev.ShiftDown(), MOVE_RIGHT);
} else { } else {
moveSelection(v.lineEnd(selection_end), !ev.ShiftDown(), MOVE_RIGHT); moveSelection(TYPE_INDEX, v.lineEnd(selection_end_i), !ev.ShiftDown(), MOVE_RIGHT);
} }
break; break;
case WXK_BACK: case WXK_BACK:
if (selection_start == selection_end) { if (selection_start == selection_end) {
// if no selection, select previous character // if no selection, select previous character
moveSelectionNoRedraw(prevCharBoundry(selection_end), false); moveSelectionNoRedraw(TYPE_CURSOR, prevCharBoundry(selection_end), false);
if (selection_start == selection_end) { if (selection_start == selection_end) {
// Walk over a <sep> as if we are the LEFT key // Walk over a <sep> as if we are the LEFT key
moveSelection(prevCharBoundry(selection_end), true, MOVE_LEFT); moveSelection(TYPE_CURSOR, prevCharBoundry(selection_end), true, MOVE_LEFT);
return; return;
} }
} }
...@@ -159,10 +160,10 @@ void TextValueEditor::onChar(wxKeyEvent& ev) { ...@@ -159,10 +160,10 @@ void TextValueEditor::onChar(wxKeyEvent& ev) {
case WXK_DELETE: case WXK_DELETE:
if (selection_start == selection_end) { if (selection_start == selection_end) {
// if no selection select next // if no selection select next
moveSelectionNoRedraw(nextCharBoundry(selection_end), false); moveSelectionNoRedraw(TYPE_CURSOR, nextCharBoundry(selection_end), false);
if (selection_start == selection_end) { if (selection_start == selection_end) {
// Walk over a <sep> as if we are the RIGHT key // Walk over a <sep> 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")); replaceSelection(wxEmptyString, _("Delete"));
...@@ -193,12 +194,13 @@ void TextValueEditor::onLoseFocus() { ...@@ -193,12 +194,13 @@ void TextValueEditor::onLoseFocus() {
assert(caret); assert(caret);
if (caret->IsVisible()) caret->Hide(); if (caret->IsVisible()) caret->Hide();
// hide selection // 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) { bool TextValueEditor::onContextMenu(wxMenu& m, wxContextMenuEvent& ev) {
// in a keword? => "reminder text" option // in a keword? => "reminder text" option
size_t kwpos = in_tag(value().value(), _("<kw-"), selection_start, selection_start); size_t kwpos = in_tag(value().value(), _("<kw-"), selection_start_i, selection_start_i);
if (kwpos != String::npos) { if (kwpos != String::npos) {
Char c = String(value().value()).GetChar(kwpos + 4); Char c = String(value().value()).GetChar(kwpos + 4);
m.AppendSeparator(); m.AppendSeparator();
...@@ -211,7 +213,7 @@ bool TextValueEditor::onContextMenu(wxMenu& m, wxContextMenuEvent& ev) { ...@@ -211,7 +213,7 @@ bool TextValueEditor::onContextMenu(wxMenu& m, wxContextMenuEvent& ev) {
void TextValueEditor::onMenu(wxCommandEvent& ev) { void TextValueEditor::onMenu(wxCommandEvent& ev) {
if (ev.GetId() == ID_FORMAT_REMINDER) { if (ev.GetId() == ID_FORMAT_REMINDER) {
// toggle reminder text // toggle reminder text
size_t kwpos = in_tag(value().value(), _("<kw-"), selection_start, selection_start); size_t kwpos = in_tag(value().value(), _("<kw-"), selection_start_i, selection_start_i);
if (kwpos != String::npos) { if (kwpos != String::npos) {
// getSet().actions.add(new TextToggleReminderAction(value, kwpos)); // getSet().actions.add(new TextToggleReminderAction(value, kwpos));
} }
...@@ -225,7 +227,7 @@ void TextValueEditor::onMenu(wxCommandEvent& ev) { ...@@ -225,7 +227,7 @@ void TextValueEditor::onMenu(wxCommandEvent& ev) {
void TextValueEditor::draw(RotatedDC& dc) { void TextValueEditor::draw(RotatedDC& dc) {
TextValueViewer::draw(dc); TextValueViewer::draw(dc);
if (isCurrent()) { if (isCurrent()) {
v.drawSelection(dc, style(), selection_start, selection_end); v.drawSelection(dc, style(), selection_start_i, selection_end_i);
// show caret, onAction() would be a better place // show caret, onAction() would be a better place
// but it has to be done after the viewer has updated the TextViewer // but it has to be done after the viewer has updated the TextViewer
...@@ -268,15 +270,16 @@ wxCursor TextValueEditor::cursor() const { ...@@ -268,15 +270,16 @@ wxCursor TextValueEditor::cursor() const {
void TextValueEditor::onValueChange() { void TextValueEditor::onValueChange() {
TextValueViewer::onValueChange(); TextValueViewer::onValueChange();
selection_start = 0; selection_start = selection_end = 0;
selection_end = 0; selection_start_i = selection_end_i = 0;
} }
void TextValueEditor::onAction(const ValueAction& action, bool undone) { void TextValueEditor::onAction(const ValueAction& action, bool undone) {
TextValueViewer::onAction(action, undone); TextValueViewer::onAction(action, undone);
TYPE_CASE(action, TextValueAction) { TYPE_CASE(action, TextValueAction) {
selection_start = action.selection_start; selection_start_i = action.selection_start;
selection_end = action.selection_end; selection_end_i = action.selection_end;
fixSelection(TYPE_INDEX);
} }
} }
...@@ -304,10 +307,10 @@ bool TextValueEditor::doPaste() { ...@@ -304,10 +307,10 @@ bool TextValueEditor::doPaste() {
bool TextValueEditor::doCopy() { bool TextValueEditor::doCopy() {
// determine string to store // determine string to store
if (selection_start > value().value().size()) selection_start = value().value().size(); if (selection_start_i > value().value().size()) selection_start_i = value().value().size();
if (selection_end > value().value().size()) selection_end = value().value().size(); if (selection_end_i > value().value().size()) selection_end_i = value().value().size();
size_t start = min(selection_start, selection_end); size_t start = min(selection_start_i, selection_end_i);
size_t end = max(selection_start, selection_end); size_t end = max(selection_start_i, selection_end_i);
String str = untag(value().value().substr(start, end - start)); String str = untag(value().value().substr(start, end - start));
if (str.empty()) return false; // no data to copy if (str.empty()) return false; // no data to copy
// set data // set data
...@@ -340,11 +343,11 @@ bool TextValueEditor::canFormat(int type) const { ...@@ -340,11 +343,11 @@ 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_BOLD: case ID_FORMAT_BOLD:
return in_tag(value().value(), _("<b"), selection_start, selection_end) != String::npos; return in_tag(value().value(), _("<b"), selection_start_i, selection_end_i) != String::npos;
case ID_FORMAT_ITALIC: case ID_FORMAT_ITALIC:
return in_tag(value().value(), _("<i"), selection_start, selection_end) != String::npos; return in_tag(value().value(), _("<i"), selection_start_i, selection_end_i) != String::npos;
case ID_FORMAT_SYMBOL: case ID_FORMAT_SYMBOL:
return in_tag(value().value(), _("<sym"), selection_start, selection_end) != String::npos; return in_tag(value().value(), _("<sym"), selection_start_i, selection_end_i) != String::npos;
case ID_FORMAT_REMINDER: case ID_FORMAT_REMINDER:
return false; // TODO return false; // TODO
default: default:
...@@ -355,15 +358,15 @@ bool TextValueEditor::hasFormat(int type) const { ...@@ -355,15 +358,15 @@ bool TextValueEditor::hasFormat(int type) const {
void TextValueEditor::doFormat(int type) { void TextValueEditor::doFormat(int type) {
switch (type) { switch (type) {
case ID_FORMAT_BOLD: { case ID_FORMAT_BOLD: {
getSet().actions.add(toggle_format_action(valueP(), _("b"), selection_start, selection_end, _("Bold"))); getSet().actions.add(toggle_format_action(valueP(), _("b"), selection_start_i, selection_end_i, _("Bold")));
break; break;
} }
case ID_FORMAT_ITALIC: { case ID_FORMAT_ITALIC: {
getSet().actions.add(toggle_format_action(valueP(), _("i"), selection_start, selection_end, _("Italic"))); getSet().actions.add(toggle_format_action(valueP(), _("i"), selection_start_i, selection_end_i, _("Italic")));
break; break;
} }
case ID_FORMAT_SYMBOL: { case ID_FORMAT_SYMBOL: {
getSet().actions.add(toggle_format_action(valueP(), _("sym"), selection_start, selection_end, _("Symbols"))); getSet().actions.add(toggle_format_action(valueP(), _("sym"), selection_start_i, selection_end_i, _("Symbols")));
break; break;
} }
} }
...@@ -378,7 +381,7 @@ void TextValueEditor::showCaret() { ...@@ -378,7 +381,7 @@ void TextValueEditor::showCaret() {
// The caret // The caret
wxCaret* caret = editor().GetCaret(); wxCaret* caret = editor().GetCaret();
// cursor rectangle // cursor rectangle
RealRect cursor = v.charRect(selection_end); RealRect cursor = v.charRect(selection_end_i);
cursor.width = 0; cursor.width = 0;
// height may be 0 near a <line> // height may be 0 near a <line>
// it is not 0 for empty text, because TextRenderer handles that case // it is not 0 for empty text, because TextRenderer handles that case
...@@ -437,10 +440,10 @@ void TextValueEditor::replaceSelection(const String& replacement, const String& ...@@ -437,10 +440,10 @@ void TextValueEditor::replaceSelection(const String& replacement, const String&
fixSelection(); fixSelection();
// execute the action before adding it to the stack, // execute the action before adding it to the stack,
// because we want to run scripts before action listeners see the action // 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) { if (!action) {
// nothing changed, but move the selection anyway // nothing changes, but move the selection anyway
moveSelection(selection_start); moveSelection(TYPE_CURSOR, selection_start);
return; return;
} }
// perform the action // perform the action
...@@ -451,12 +454,12 @@ void TextValueEditor::replaceSelection(const String& replacement, const String& ...@@ -451,12 +454,12 @@ void TextValueEditor::replaceSelection(const String& replacement, const String&
String val = value().value(); String val = value().value();
Char typed = replacement.GetChar(0); Char typed = replacement.GetChar(0);
Char typedU = toUpper(typed); 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... // the cursor may have moved because of sorting...
// is 'replacement' just after the current cursor? // 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 // 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 { } else {
// find the last occurence of 'replacement' in the value // find the last occurence of 'replacement' in the value
size_t pos = val.find_last_of(typed); size_t pos = val.find_last_of(typed);
...@@ -465,22 +468,23 @@ void TextValueEditor::replaceSelection(const String& replacement, const String& ...@@ -465,22 +468,23 @@ void TextValueEditor::replaceSelection(const String& replacement, const String&
pos = val.find_last_of(typedU); pos = val.find_last_of(typedU);
} }
if (pos != String::npos) { if (pos != String::npos) {
selection_end = selection_start = pos + 1; selection_end_i = selection_start_i = pos + 1;
} else { } else {
selection_end = selection_start; selection_end_i = selection_start_i;
} }
} }
} else { } 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 // scroll with next update
// scrollWithCursor = true; // 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()) { if (!isCurrent()) {
// selection is only visible for curent editor, we can do a move the simple way // 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; return;
} }
// Hide caret // Hide caret
...@@ -494,9 +498,9 @@ void TextValueEditor::moveSelection(size_t new_end, bool also_move_start, Moveme ...@@ -494,9 +498,9 @@ void TextValueEditor::moveSelection(size_t new_end, bool also_move_start, Moveme
rdc.SetClippingRegion(style().getRect()); rdc.SetClippingRegion(style().getRect());
} }
// clear old selection by drawing it again // 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 // move
moveSelectionNoRedraw(new_end, also_move_start, dir); moveSelectionNoRedraw(t, new_end, also_move_start, dir);
// scroll? // scroll?
// scrollWithCursor = true; // scrollWithCursor = true;
// if (onMove()) { // if (onMove()) {
...@@ -518,23 +522,53 @@ void TextValueEditor::moveSelection(size_t new_end, bool also_move_start, Moveme ...@@ -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)); 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) { void TextValueEditor::moveSelectionNoRedraw(IndexType t, size_t new_end, bool also_move_start, Movement dir) {
selection_end = new_end; if (t == TYPE_INDEX) {
if (also_move_start) selection_start = selection_end; selection_end_i = new_end;
fixSelection(dir); 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(); const String& val = value().value();
// value may have become smaller because of undo/redo // Which type takes precedent?
// make sure the selection stays inside the text if (t == TYPE_INDEX) {
size_t size = val.size(); 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(_("<sep"));
while (seppos != String::npos) {
size_t sepend = skip_tag(val,match_close_tag(val, seppos));
if ((selection_start_i <= seppos && selection_end_i > 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(_("<sep"), seppos + 1);
}
// REMOVEME
/*size_t size = val.size();
selection_end = min(size, selection_end); selection_end = min(size, selection_end);
selection_start = min(size, selection_start); selection_start = min(size, selection_start);
// start and end must not be inside or between tags
selection_start = v.firstVisibleChar(selection_start, dir == MOVE_LEFT ? -1 : +1);
selection_end = v.firstVisibleChar(selection_end, dir == MOVE_LEFT ? -1 : +1);
// start and end must be on the same side of separators // start and end must be on the same side of separators
size_t seppos = val.find(_("<sep")); size_t seppos = val.find(_("<sep"));
while (seppos != String::npos) { while (seppos != String::npos) {
size_t sepend = match_close_tag(val, seppos); size_t sepend = skip_tag(val,match_close_tag(val, seppos));
if (selection_start <= seppos && selection_end > seppos) selection_end = seppos; // not on same side if (selection_start <= seppos && selection_end > 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 >= sepend && selection_end < sepend) selection_end = sepend; // not on same side
if (selection_start > seppos && selection_start < sepend) { if (selection_start > seppos && selection_start < sepend) {
...@@ -551,7 +585,7 @@ void TextValueEditor::fixSelection(Movement dir) { ...@@ -551,7 +585,7 @@ void TextValueEditor::fixSelection(Movement dir) {
// start or end in an <atom>? if so, move them out // start or end in an <atom>? if so, move them out
size_t atompos = val.find(_("<atom")); size_t atompos = val.find(_("<atom"));
while (atompos != String::npos) { while (atompos != String::npos) {
size_t atomend = match_close_tag(val, atompos); size_t atomend = skip_tag(val,match_close_tag(val, atompos));
if (selection_start > atompos && selection_start < atomend) { // start inside atom if (selection_start > atompos && selection_start < atomend) { // start inside atom
selection_start = move(selection_start, atompos, atomend, dir); selection_start = move(selection_start, atompos, atomend, dir);
} }
...@@ -561,10 +595,8 @@ void TextValueEditor::fixSelection(Movement dir) { ...@@ -561,10 +595,8 @@ void TextValueEditor::fixSelection(Movement dir) {
// find next atom // find next atom
atompos = val.find(_("<atom"), atompos + 1); atompos = val.find(_("<atom"), atompos + 1);
} }
// start and end must not be inside or between tags */
selection_start = v.firstVisibleChar(selection_start, dir == MOVE_LEFT ? -1 : +1); // TODO? : More checks?
selection_end = v.firstVisibleChar(selection_end, dir == MOVE_LEFT ? -1 : +1);
// TODO
} }
...@@ -572,19 +604,19 @@ size_t TextValueEditor::prevCharBoundry(size_t pos) const { ...@@ -572,19 +604,19 @@ size_t TextValueEditor::prevCharBoundry(size_t pos) const {
return max(0, (int)pos - 1); return max(0, (int)pos - 1);
} }
size_t TextValueEditor::nextCharBoundry(size_t pos) const { size_t TextValueEditor::nextCharBoundry(size_t pos) const {
return min(value().value().size(), pos + 1); return pos + 1;
} }
size_t TextValueEditor::prevWordBoundry(size_t pos) const { size_t TextValueEditor::prevWordBoundry(size_t pos_i) const {
const String& val = value().value(); const String& val = value().value();
size_t p = val.find_last_not_of(_(" ,.:;()\n"), max(0, (int)(pos - 1))); //note: pos-1 might be < 0 size_t p = val.find_last_not_of(_(" ,.:;()\n"), max(0, (int)pos_i - 1));
if (p == String::npos) return 0; if (p == String::npos) return 0;
p = val.find_last_of(_(" ,.:;()\n"), p); p = val.find_last_of(_(" ,.:;()\n"), p);
if (p == String::npos) return 0; if (p == String::npos) return 0;
return p + 1; return p + 1;
} }
size_t TextValueEditor::nextWordBoundry(size_t pos) const { size_t TextValueEditor::nextWordBoundry(size_t pos_i) const {
const String& val = value().value(); const String& val = value().value();
size_t p = val.find_first_of(_(" ,.:;()\n"), pos); size_t p = val.find_first_of(_(" ,.:;()\n"), pos_i);
if (p == String::npos) return val.size(); if (p == String::npos) return val.size();
p = val.find_first_not_of(_(" ,.:;()\n"), p); p = val.find_first_not_of(_(" ,.:;()\n"), p);
if (p == String::npos) return val.size(); if (p == String::npos) return val.size();
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
// ----------------------------------------------------------------------------- : Includes // ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp> #include <util/prec.hpp>
#include <util/tagged_string.hpp> // for Movement
#include <gui/value/editor.hpp> #include <gui/value/editor.hpp>
#include <render/value/text.hpp> #include <render/value/text.hpp>
...@@ -17,11 +18,9 @@ class TextValueEditorScrollBar; ...@@ -17,11 +18,9 @@ class TextValueEditorScrollBar;
// ----------------------------------------------------------------------------- : TextValueEditor // ----------------------------------------------------------------------------- : TextValueEditor
/// Directions of cursor movement enum IndexType
enum Movement { TYPE_CURSOR ///< Positions are cursor positions
{ MOVE_LEFT ///< Always move the cursor to the left , TYPE_INDEX ///< Positions are character indices
, MOVE_MID ///< Move in whichever direction the distance to move is shorter (TODO: define shorter)
, MOVE_RIGHT ///< Always move the cursor to the right
}; };
/// An editor 'control' for editing TextValues /// An editor 'control' for editing TextValues
...@@ -83,16 +82,19 @@ class TextValueEditor : public TextValueViewer, public ValueEditor { ...@@ -83,16 +82,19 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
// --------------------------------------------------- : Data // --------------------------------------------------- : Data
private: 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 TextValueEditorScrollBar* scrollbar; ///< Scrollbar for multiline fields in native look
bool select_words; ///< Select whole words when dragging the mouse? bool select_words; ///< Select whole words when dragging the mouse?
// --------------------------------------------------- : Selection / movement // --------------------------------------------------- : Selection / movement
/// Move the selection to a new location, clears the previously drawn selection /// 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); /** t specifies what kind of position new_end is */
/// Move the selection to a new location, but does not redraw void moveSelection(IndexType t, size_t new_end, bool also_move_start=true, Movement dir = MOVE_MID);
void moveSelectionNoRedraw(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 /// Replace the current selection with 'replacement', name the action
void replaceSelection(const String& replacement, const String& name); void replaceSelection(const String& replacement, const String& name);
...@@ -104,7 +106,7 @@ class TextValueEditor : public TextValueViewer, public ValueEditor { ...@@ -104,7 +106,7 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
* *
* When correcting the selection, move in the given direction * 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 /// 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); 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 { ...@@ -113,11 +115,13 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
void showCaret(); void showCaret();
/// Position of previous visible & selectable character /// Position of previous visible & selectable character
/** Uses cursor positions */
size_t prevCharBoundry(size_t pos) const; size_t prevCharBoundry(size_t pos) const;
size_t nextCharBoundry(size_t pos) const; size_t nextCharBoundry(size_t pos) const;
/// Front of previous word, used witch Ctrl+Left/right /// Front of previous word, used witch Ctrl+Left/right
size_t prevWordBoundry(size_t pos) const; /** Uses character indices */
size_t nextWordBoundry(size_t pos) const; size_t prevWordBoundry(size_t pos_i) const;
size_t nextWordBoundry(size_t pos_i) const;
// --------------------------------------------------- : Scrolling // --------------------------------------------------- : Scrolling
......
...@@ -64,7 +64,7 @@ void WelcomeWindow::draw(DC& dc) { ...@@ -64,7 +64,7 @@ void WelcomeWindow::draw(DC& dc) {
} }
void WelcomeWindow::onOpenSet(wxCommandEvent&) { 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) { if (dlg.ShowModal() == wxID_OK) {
close(import_set(dlg.GetPath())); close(import_set(dlg.GetPath()));
} }
......
...@@ -130,7 +130,7 @@ class FontTextElement : public SimpleTextElement { ...@@ -130,7 +130,7 @@ class FontTextElement : public SimpleTextElement {
: SimpleTextElement(text, start, end) : SimpleTextElement(text, start, end)
, font(font) , 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 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<CharInfo>& out) const; virtual void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const;
virtual double minScale() const; virtual double minScale() const;
...@@ -146,7 +146,7 @@ class SymbolTextElement : public SimpleTextElement { ...@@ -146,7 +146,7 @@ class SymbolTextElement : public SimpleTextElement {
: SimpleTextElement(text, start, end) : SimpleTextElement(text, start, end)
, font(font), ctx(*ctx) , 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 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<CharInfo>& out) const; virtual void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const;
virtual double minScale() const; virtual double minScale() const;
...@@ -162,9 +162,22 @@ class CompoundTextElement : public TextElement { ...@@ -162,9 +162,22 @@ class CompoundTextElement : public TextElement {
public: public:
CompoundTextElement(const String& text, size_t start ,size_t end) : TextElement(text, start, end) {} 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<CharInfo>& out) const;
virtual double minScale() const;
TextElements elements; ///< the elements 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 // ----------------------------------------------------------------------------- : Other text elements
/// A text element that displays a horizontal separator line /// A text element that displays a horizontal separator line
......
...@@ -81,7 +81,7 @@ class TextViewer { ...@@ -81,7 +81,7 @@ class TextViewer {
/// Is the character at the given index visible? /// Is the character at the given index visible?
bool isVisible(size_t index) const; 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 /// 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; size_t firstVisibleChar(size_t index, int delta) const;
......
...@@ -159,6 +159,80 @@ String anti_tag(const String& tag) { ...@@ -159,6 +159,80 @@ String anti_tag(const String& tag) {
else return _("</") + tag + _(">"); else return _("</") + tag + _(">");
} }
// ----------------------------------------------------------------------------- : 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, _("<atom")) || is_substr(str, i, _("<sep"))) {
// skip tag contents, tag counts as a single 'character'
i = skip_tag(str, match_close_tag(str, i));
cursor++;
start = end;
end = i;
if (end > 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, _("<atom")) || is_substr(str, i, _("<sep"))) {
// skip tag contents, tag counts as a single 'character'
i = skip_tag(str, match_close_tag(str, i));
cur++;
if (cur == cursor) start = i;
} else {
i = skip_tag(str, i);
}
} else {
cur++;
i++;
if (cur == cursor) start = i;
}
}
end = min(i, str.size());
if (cur < cursor) start = end = str.size();
}
size_t cursor_to_index(const String& str, size_t cursor) {
size_t start, end;
cursor_to_index_range(str, cursor, start, end);
// TODO: If at i there is <tag></tag> return a position inside the tags
// This allows formating to be enabled without a selection
return start;
}
// ----------------------------------------------------------------------------- : Global operations // ----------------------------------------------------------------------------- : Global operations
String remove_tag(const String& str, const String& tag) { String remove_tag(const String& str, const String& tag) {
......
...@@ -80,6 +80,29 @@ String close_tag(const String& tag); ...@@ -80,6 +80,29 @@ String close_tag(const String& tag);
/// The matching close tag for an open tag and vice versa /// The matching close tag for an open tag and vice versa
String anti_tag(const String& tag); 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 // ----------------------------------------------------------------------------- : Global operations
/// 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.
......
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