mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-11 21:27:01 -04:00
Implemented auto replace, including GUI
git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@631 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
+10
-3
@@ -23,6 +23,7 @@
|
||||
#include <gui/print_window.hpp>
|
||||
#include <gui/images_export_window.hpp>
|
||||
#include <gui/html_export_window.hpp>
|
||||
#include <gui/auto_replace_window.hpp>
|
||||
#include <gui/icon_menu.hpp>
|
||||
#include <gui/util.hpp>
|
||||
#include <util/io/package_manager.hpp>
|
||||
@@ -86,9 +87,10 @@ SetWindow::SetWindow(Window* parent, const SetP& set)
|
||||
menuEdit->Append(ID_EDIT_COPY, _("copy"), _MENU_("copy"), _HELP_("copy"));
|
||||
menuEdit->Append(ID_EDIT_PASTE, _("paste"), _MENU_("paste"), _HELP_("paste"));
|
||||
menuEdit->AppendSeparator();
|
||||
menuEdit->Append(ID_EDIT_FIND, _("find"), _MENU_("find"), _(""));
|
||||
menuEdit->Append(ID_EDIT_FIND_NEXT, _MENU_("find next"), _(""));
|
||||
menuEdit->Append(ID_EDIT_REPLACE, _MENU_("replace"), _(""));
|
||||
menuEdit->Append(ID_EDIT_FIND, _("find"), _MENU_("find"), _HELP_("find"));
|
||||
menuEdit->Append(ID_EDIT_FIND_NEXT, _MENU_("find next"), _HELP_("find next"));
|
||||
menuEdit->Append(ID_EDIT_REPLACE, _MENU_("replace"), _HELP_("replace"));
|
||||
menuEdit->Append(ID_EDIT_AUTO_REPLACE, _MENU_("auto replace"), _HELP_("auto replace"));
|
||||
menuEdit->AppendSeparator();
|
||||
menuEdit->Append(ID_EDIT_PREFERENCES, _MENU_("preferences"), _HELP_("preferences"));
|
||||
menuBar->Append(menuEdit, _MENU_("edit"));
|
||||
@@ -619,6 +621,10 @@ void SetWindow::onReplaceAll(wxFindDialogEvent&) {
|
||||
current_panel->doReplaceAll(find_data);
|
||||
}
|
||||
|
||||
void SetWindow::onEditAutoReplace(wxCommandEvent&) {
|
||||
(new AutoReplaceWindow(this, *set->game))->Show();
|
||||
}
|
||||
|
||||
void SetWindow::onEditPreferences(wxCommandEvent&) {
|
||||
PreferencesWindow wnd(this);
|
||||
if (wnd.ShowModal() == wxID_OK) {
|
||||
@@ -697,6 +703,7 @@ BEGIN_EVENT_TABLE(SetWindow, wxFrame)
|
||||
EVT_MENU (ID_EDIT_FIND, SetWindow::onEditFind)
|
||||
EVT_MENU (ID_EDIT_FIND_NEXT, SetWindow::onEditFindNext)
|
||||
EVT_MENU (ID_EDIT_REPLACE, SetWindow::onEditReplace)
|
||||
EVT_MENU (ID_EDIT_AUTO_REPLACE, SetWindow::onEditAutoReplace)
|
||||
EVT_MENU (ID_EDIT_PREFERENCES, SetWindow::onEditPreferences)
|
||||
EVT_MENU (ID_WINDOW_NEW, SetWindow::onWindowNewWindow)
|
||||
EVT_TOOL_RANGE (ID_WINDOW_MIN, ID_WINDOW_MAX, SetWindow::onWindowSelect)
|
||||
|
||||
@@ -131,6 +131,7 @@ class SetWindow : public wxFrame, public SetView {
|
||||
void onEditFind (wxCommandEvent&);
|
||||
void onEditFindNext (wxCommandEvent&);
|
||||
void onEditReplace (wxCommandEvent&);
|
||||
void onEditAutoReplace (wxCommandEvent&);
|
||||
void onEditPreferences (wxCommandEvent&);
|
||||
|
||||
void onFind (wxFindDialogEvent&);
|
||||
|
||||
+85
-26
@@ -12,6 +12,7 @@
|
||||
#include <gui/drop_down_list.hpp>
|
||||
#include <data/word_list.hpp>
|
||||
#include <data/game.hpp>
|
||||
#include <data/settings.hpp>
|
||||
#include <data/action/value.hpp>
|
||||
#include <util/tagged_string.hpp>
|
||||
#include <util/find_replace.hpp>
|
||||
@@ -23,6 +24,7 @@ DECLARE_SHARED_POINTER_TYPE(DropDownList);
|
||||
DECLARE_TYPEOF_COLLECTION(WordListP);
|
||||
DECLARE_TYPEOF_COLLECTION(WordListWordP);
|
||||
DECLARE_TYPEOF_COLLECTION(WordListPosP);
|
||||
DECLARE_TYPEOF_COLLECTION(AutoReplaceP);
|
||||
|
||||
// ----------------------------------------------------------------------------- : TextValueEditorScrollBar
|
||||
|
||||
@@ -273,8 +275,8 @@ bool TextValueEditor::onMotion(const RealPoint& pos, wxMouseEvent& ev) {
|
||||
if (select_words) {
|
||||
// on the left, swap start and end
|
||||
bool left = selection_end_i < selection_start_i;
|
||||
size_t next = nextWordBoundry(index);
|
||||
size_t prev = prevWordBoundry(index);
|
||||
size_t next = nextWordBoundary(index);
|
||||
size_t prev = prevWordBoundary(index);
|
||||
if (( left && next > max(selection_start_i, selection_end_i)) ||
|
||||
(!left && prev < min(selection_start_i, selection_end_i))) {
|
||||
left = !left;
|
||||
@@ -294,8 +296,8 @@ bool TextValueEditor::onLeftDClick(const RealPoint& pos, wxMouseEvent& ev) {
|
||||
select_words = true;
|
||||
selecting = true;
|
||||
size_t index = v.indexAt(style().getRotation().trInv(pos));
|
||||
moveSelection(TYPE_INDEX, prevWordBoundry(index), true, MOVE_MID);
|
||||
moveSelection(TYPE_INDEX, nextWordBoundry(index), false, MOVE_MID);
|
||||
moveSelection(TYPE_INDEX, prevWordBoundary(index), true, MOVE_MID);
|
||||
moveSelection(TYPE_INDEX, nextWordBoundary(index), false, MOVE_MID);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -323,17 +325,17 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) {
|
||||
case WXK_LEFT:
|
||||
// move left (selection?)
|
||||
if (ev.ControlDown()) {
|
||||
moveSelection(TYPE_INDEX, prevWordBoundry(selection_end_i),!ev.ShiftDown(), MOVE_LEFT);
|
||||
moveSelection(TYPE_INDEX, prevWordBoundary(selection_end_i),!ev.ShiftDown(), MOVE_LEFT);
|
||||
} else {
|
||||
moveSelection(TYPE_CURSOR, prevCharBoundry(selection_end), !ev.ShiftDown(), MOVE_LEFT);
|
||||
moveSelection(TYPE_CURSOR, prevCharBoundary(selection_end), !ev.ShiftDown(), MOVE_LEFT);
|
||||
}
|
||||
break;
|
||||
case WXK_RIGHT:
|
||||
// move left (selection?)
|
||||
if (ev.ControlDown()) {
|
||||
moveSelection(TYPE_INDEX, nextWordBoundry(selection_end_i),!ev.ShiftDown(), MOVE_RIGHT);
|
||||
moveSelection(TYPE_INDEX, nextWordBoundary(selection_end_i),!ev.ShiftDown(), MOVE_RIGHT);
|
||||
} else {
|
||||
moveSelection(TYPE_CURSOR, nextCharBoundry(selection_end), !ev.ShiftDown(), MOVE_RIGHT);
|
||||
moveSelection(TYPE_CURSOR, nextCharBoundary(selection_end), !ev.ShiftDown(), MOVE_RIGHT);
|
||||
}
|
||||
break;
|
||||
case WXK_UP:
|
||||
@@ -369,10 +371,10 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) {
|
||||
case WXK_BACK:
|
||||
if (selection_start == selection_end) {
|
||||
// if no selection, select previous character
|
||||
moveSelectionNoRedraw(TYPE_CURSOR, prevCharBoundry(selection_end), false);
|
||||
moveSelectionNoRedraw(TYPE_CURSOR, prevCharBoundary(selection_end), false);
|
||||
if (selection_start == selection_end) {
|
||||
// Walk over a <sep> as if we are the LEFT key
|
||||
moveSelection(TYPE_CURSOR, prevCharBoundry(selection_end), true, MOVE_LEFT);
|
||||
moveSelection(TYPE_CURSOR, prevCharBoundary(selection_end), true, MOVE_LEFT);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -381,10 +383,10 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) {
|
||||
case WXK_DELETE:
|
||||
if (selection_start == selection_end) {
|
||||
// if no selection select next
|
||||
moveSelectionNoRedraw(TYPE_CURSOR, nextCharBoundry(selection_end), false);
|
||||
moveSelectionNoRedraw(TYPE_CURSOR, nextCharBoundary(selection_end), false);
|
||||
if (selection_start == selection_end) {
|
||||
// Walk over a <sep> as if we are the RIGHT key
|
||||
moveSelection(TYPE_CURSOR, nextCharBoundry(selection_end), true, MOVE_RIGHT);
|
||||
moveSelection(TYPE_CURSOR, nextCharBoundary(selection_end), true, MOVE_RIGHT);
|
||||
}
|
||||
}
|
||||
replaceSelection(wxEmptyString, _ACTION_("delete"));
|
||||
@@ -411,9 +413,9 @@ bool TextValueEditor::onChar(wxKeyEvent& ev) {
|
||||
// this might not work for internationalized input.
|
||||
// It might also not be portable!
|
||||
#ifdef UNICODE
|
||||
replaceSelection(escape(String(ev.GetUnicodeKey(), 1)), _ACTION_("typing"));
|
||||
replaceSelection(escape(String(ev.GetUnicodeKey(), 1)), _ACTION_("typing"), true);
|
||||
#else
|
||||
replaceSelection(escape(String((Char)ev.GetKeyCode(), 1)), _ACTION_("typing"));
|
||||
replaceSelection(escape(String((Char)ev.GetKeyCode(), 1)), _ACTION_("typing"), true);
|
||||
#endif
|
||||
} else {
|
||||
return false;
|
||||
@@ -775,7 +777,7 @@ size_t match_cursor_position(size_t pos1, const String& text1, size_t pos2, cons
|
||||
return 1000 * before + 2 * after - penalty; // matching 'before' is more important
|
||||
}
|
||||
|
||||
void TextValueEditor::replaceSelection(const String& replacement, const String& name) {
|
||||
void TextValueEditor::replaceSelection(const String& replacement, const String& name, bool allow_auto_replace, bool select_on_undo) {
|
||||
if (replacement.empty() && selection_start == selection_end) {
|
||||
// no text selected, nothing to delete
|
||||
return;
|
||||
@@ -785,10 +787,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
|
||||
TextValueAction* action = typing_action(valueP(), selection_start_i, selection_end_i, selection_start, selection_end, replacement, name);
|
||||
TextValueAction* action = typing_action(valueP(), selection_start_i, selection_end_i, select_on_undo ? selection_start : selection_end, selection_end, replacement, name);
|
||||
if (!action) {
|
||||
// nothing changes, but move the selection anyway
|
||||
moveSelection(TYPE_CURSOR, selection_start);
|
||||
moveSelection(TYPE_CURSOR, selection_end);
|
||||
return;
|
||||
}
|
||||
// what we would expect if no scripts take place
|
||||
@@ -832,10 +834,31 @@ void TextValueEditor::replaceSelection(const String& replacement, const String&
|
||||
selection_end = selection_start = best_cursor;
|
||||
fixSelection(TYPE_CURSOR, MOVE_RIGHT);
|
||||
}
|
||||
// auto replace after typing?
|
||||
if (allow_auto_replace) tryAutoReplace();
|
||||
// scroll with next update
|
||||
scroll_with_cursor = true;
|
||||
}
|
||||
|
||||
void TextValueEditor::tryAutoReplace() {
|
||||
size_t end = selection_start_i;
|
||||
GameSettings& gs = settings.gameSettingsFor(*getSet().game);
|
||||
FOR_EACH(ar, gs.auto_replaces) {
|
||||
if (ar->enabled && ar->match.size() <= end) {
|
||||
size_t start = end - ar->match.size();
|
||||
if (is_substr(value().value(), start, ar->match) &&
|
||||
(!ar->whole_word || (isWordBoundary(start) && isWordBoundary(end)))
|
||||
) {
|
||||
// replace
|
||||
selection_start_i = start;
|
||||
selection_end_i = end;
|
||||
fixSelection(TYPE_INDEX);
|
||||
replaceSelection(ar->replace, _ACTION_("auto replace"), false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextValueEditor::moveSelection(IndexType t, size_t new_end, bool also_move_start, Movement dir) {
|
||||
size_t old_start = selection_start_i;
|
||||
size_t old_end = selection_end_i;
|
||||
@@ -891,28 +914,64 @@ void TextValueEditor::fixSelection(IndexType t, Movement dir) {
|
||||
}
|
||||
|
||||
|
||||
size_t TextValueEditor::prevCharBoundry(size_t pos) const {
|
||||
size_t TextValueEditor::prevCharBoundary(size_t pos) const {
|
||||
return max(0, (int)pos - 1);
|
||||
}
|
||||
size_t TextValueEditor::nextCharBoundry(size_t pos) const {
|
||||
size_t TextValueEditor::nextCharBoundary(size_t pos) const {
|
||||
return min(index_to_cursor(value().value(), String::npos), pos + 1);
|
||||
}
|
||||
size_t TextValueEditor::prevWordBoundry(size_t pos_i) const {
|
||||
|
||||
static const Char word_bound_chars[] = _(" ,.:;()\n");
|
||||
bool isWordBoundaryChar(Char c) {
|
||||
return c == _(' ') || c == _(',') || c == _('.') || c == _(':') || c == _(';') ||
|
||||
c == _('(') || c == _(')') || c == _('\n') || isPunct(c);
|
||||
}
|
||||
|
||||
size_t TextValueEditor::prevWordBoundary(size_t pos_i) const {
|
||||
const String& val = value().value();
|
||||
size_t p = val.find_last_not_of(_(" ,.:;()\n"), max(0, (int)pos_i - 1));
|
||||
size_t p = val.find_last_not_of(word_bound_chars, max(0, (int)pos_i - 1));
|
||||
if (p == String::npos) return 0;
|
||||
p = val.find_last_of(_(" ,.:;()\n"), p);
|
||||
p = val.find_last_of(word_bound_chars, p);
|
||||
if (p == String::npos) return 0;
|
||||
return p + 1;
|
||||
}
|
||||
size_t TextValueEditor::nextWordBoundry(size_t pos_i) const {
|
||||
size_t TextValueEditor::nextWordBoundary(size_t pos_i) const {
|
||||
const String& val = value().value();
|
||||
size_t p = val.find_first_of(_(" ,.:;()\n"), pos_i);
|
||||
size_t p = val.find_first_of(word_bound_chars, pos_i);
|
||||
if (p == String::npos) return val.size();
|
||||
p = val.find_first_not_of(_(" ,.:;()\n"), p);
|
||||
p = val.find_first_not_of(word_bound_chars, p);
|
||||
if (p == String::npos) return val.size();
|
||||
return p;
|
||||
}
|
||||
bool TextValueEditor::isWordBoundary(size_t pos_i) const {
|
||||
const String& val = value().value();
|
||||
// boundary after?
|
||||
size_t pos = pos_i;
|
||||
while (true) {
|
||||
if (pos >= val.size()) return true;
|
||||
Char c = val.GetChar(pos);
|
||||
if (c == _('<')) pos = skip_tag(val,pos); // skip tags
|
||||
else if (isWordBoundaryChar(c)) return true;
|
||||
else break;
|
||||
}
|
||||
// boundary before?
|
||||
pos = pos_i;
|
||||
while (true) {
|
||||
if (pos == 0) return true;
|
||||
Char c = val.GetChar(pos - 1);
|
||||
if (c == _('>')) {
|
||||
// (try to) skip tags in reverse
|
||||
while (true) {
|
||||
if (pos == 0) return false; // not a tag
|
||||
--pos;
|
||||
c = val.GetChar(pos - 1);
|
||||
if (c == _('<')) { --pos; break; } // was a tag
|
||||
else if (c == _('>')) return false; // was not a tag
|
||||
}
|
||||
}
|
||||
else return isWordBoundaryChar(c);
|
||||
}
|
||||
}
|
||||
|
||||
void TextValueEditor::select(size_t start, size_t end) {
|
||||
selection_start = start;
|
||||
@@ -1129,7 +1188,7 @@ void TextValueEditor::drawWordListIndicators(RotatedDC& dc) {
|
||||
if (current && wl->active) {
|
||||
dc.SetPen (Color(0, 128,255));
|
||||
dc.SetBrush(Color(64, 160,255));
|
||||
} else if (current && selection_end_i >= wl->start && selection_end_i <= wl->end) {
|
||||
} else if (current && selection_end_i >= wl->start && selection_end_i <= wl->end && !dropDownShown()) {
|
||||
dc.SetPen (Color(64, 160,255));
|
||||
dc.SetBrush(Color(160,208,255));
|
||||
} else {
|
||||
|
||||
@@ -116,7 +116,9 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
|
||||
|
||||
/// Replace the current selection with 'replacement', name the action
|
||||
/** replacement should be a tagged string (i.e. already escaped) */
|
||||
void replaceSelection(const String& replacement, const String& name);
|
||||
void replaceSelection(const String& replacement, const String& name, bool allow_auto_replace = false, bool select_on_undo = true);
|
||||
/// Try to autoreplace at the position before the cursor
|
||||
void tryAutoReplace();
|
||||
|
||||
/// Make sure the selection satisfies its constraints
|
||||
/** - selection_start and selection_end are inside the text
|
||||
@@ -135,12 +137,13 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
|
||||
|
||||
/// Position of previous visible & selectable character
|
||||
/** Uses cursor positions */
|
||||
size_t prevCharBoundry(size_t pos) const;
|
||||
size_t nextCharBoundry(size_t pos) const;
|
||||
size_t prevCharBoundary(size_t pos) const;
|
||||
size_t nextCharBoundary(size_t pos) const;
|
||||
/// Front of previous word, used witch Ctrl+Left/right
|
||||
/** Uses character indices */
|
||||
size_t prevWordBoundry(size_t pos_i) const;
|
||||
size_t nextWordBoundry(size_t pos_i) const;
|
||||
size_t prevWordBoundary(size_t pos_i) const;
|
||||
size_t nextWordBoundary(size_t pos_i) const;
|
||||
bool isWordBoundary(size_t pos_i) const;
|
||||
|
||||
// --------------------------------------------------- : Scrolling
|
||||
|
||||
|
||||
Reference in New Issue
Block a user