mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
improved cursor handling in text editor
git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@121 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
+108
-76
@@ -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 <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;
|
||||
}
|
||||
}
|
||||
@@ -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 <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"));
|
||||
@@ -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(), _("<kw-"), selection_start, selection_start);
|
||||
size_t kwpos = in_tag(value().value(), _("<kw-"), selection_start_i, selection_start_i);
|
||||
if (kwpos != String::npos) {
|
||||
Char c = String(value().value()).GetChar(kwpos + 4);
|
||||
m.AppendSeparator();
|
||||
@@ -211,7 +213,7 @@ bool TextValueEditor::onContextMenu(wxMenu& m, wxContextMenuEvent& ev) {
|
||||
void TextValueEditor::onMenu(wxCommandEvent& ev) {
|
||||
if (ev.GetId() == ID_FORMAT_REMINDER) {
|
||||
// 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) {
|
||||
// getSet().actions.add(new TextToggleReminderAction(value, kwpos));
|
||||
}
|
||||
@@ -225,7 +227,7 @@ void TextValueEditor::onMenu(wxCommandEvent& ev) {
|
||||
void TextValueEditor::draw(RotatedDC& dc) {
|
||||
TextValueViewer::draw(dc);
|
||||
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
|
||||
// but it has to be done after the viewer has updated the TextViewer
|
||||
@@ -268,15 +270,16 @@ wxCursor TextValueEditor::cursor() const {
|
||||
|
||||
void TextValueEditor::onValueChange() {
|
||||
TextValueViewer::onValueChange();
|
||||
selection_start = 0;
|
||||
selection_end = 0;
|
||||
selection_start = selection_end = 0;
|
||||
selection_start_i = selection_end_i = 0;
|
||||
}
|
||||
|
||||
void TextValueEditor::onAction(const ValueAction& action, bool undone) {
|
||||
TextValueViewer::onAction(action, undone);
|
||||
TYPE_CASE(action, TextValueAction) {
|
||||
selection_start = action.selection_start;
|
||||
selection_end = action.selection_end;
|
||||
selection_start_i = action.selection_start;
|
||||
selection_end_i = action.selection_end;
|
||||
fixSelection(TYPE_INDEX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,10 +307,10 @@ bool TextValueEditor::doPaste() {
|
||||
|
||||
bool TextValueEditor::doCopy() {
|
||||
// determine string to store
|
||||
if (selection_start > 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(), _("<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:
|
||||
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:
|
||||
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:
|
||||
return false; // TODO
|
||||
default:
|
||||
@@ -355,15 +358,15 @@ bool TextValueEditor::hasFormat(int type) const {
|
||||
void TextValueEditor::doFormat(int type) {
|
||||
switch (type) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -378,7 +381,7 @@ void TextValueEditor::showCaret() {
|
||||
// The caret
|
||||
wxCaret* caret = editor().GetCaret();
|
||||
// cursor rectangle
|
||||
RealRect cursor = v.charRect(selection_end);
|
||||
RealRect cursor = v.charRect(selection_end_i);
|
||||
cursor.width = 0;
|
||||
// height may be 0 near a <line>
|
||||
// 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(_("<sep"));
|
||||
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_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_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
|
||||
size_t seppos = val.find(_("<sep"));
|
||||
while (seppos != String::npos) {
|
||||
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 >= 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 <atom>? if so, move them out
|
||||
size_t atompos = val.find(_("<atom"));
|
||||
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
|
||||
selection_start = move(selection_start, atompos, atomend, dir);
|
||||
}
|
||||
@@ -561,10 +595,8 @@ void TextValueEditor::fixSelection(Movement dir) {
|
||||
// find next atom
|
||||
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);
|
||||
selection_end = v.firstVisibleChar(selection_end, dir == MOVE_LEFT ? -1 : +1);
|
||||
// TODO
|
||||
*/
|
||||
// TODO? : More checks?
|
||||
}
|
||||
|
||||
|
||||
@@ -572,19 +604,19 @@ size_t TextValueEditor::prevCharBoundry(size_t pos) const {
|
||||
return max(0, (int)pos - 1);
|
||||
}
|
||||
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();
|
||||
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;
|
||||
p = val.find_last_of(_(" ,.:;()\n"), p);
|
||||
if (p == String::npos) return 0;
|
||||
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();
|
||||
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();
|
||||
p = val.find_first_not_of(_(" ,.:;()\n"), p);
|
||||
if (p == String::npos) return val.size();
|
||||
|
||||
+17
-13
@@ -10,6 +10,7 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <util/tagged_string.hpp> // for Movement
|
||||
#include <gui/value/editor.hpp>
|
||||
#include <render/value/text.hpp>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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<CharInfo>& 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<CharInfo>& 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<CharInfo>& 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -159,6 +159,80 @@ String anti_tag(const String& 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
|
||||
|
||||
String remove_tag(const String& str, const String& tag) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user