Implement undo/redo history for dialog text fields

- Also fixed a minor issue with pasting (the character before the insertion point was removed before pasting)
This commit is contained in:
2015-06-05 15:07:28 -04:00
parent 4fa22a34f1
commit d398bfaa80
5 changed files with 199 additions and 36 deletions

View File

@@ -373,6 +373,9 @@ void cTextField::handleInput(cKey key) {
style.pointSize = 12;
size_t new_ip;
std::string contents = getText();
if(current_action && hist_timer.getElapsedTime().asSeconds() > 5.0f)
history.add(current_action), current_action.reset();
hist_timer.restart();
if(!key.spec) {
if(haveSelection) {
cKey deleteKey = key;
@@ -381,12 +384,21 @@ void cTextField::handleInput(cKey key) {
handleInput(deleteKey);
contents = getText();
}
if(aTextInsert* ins = dynamic_cast<aTextInsert*>(current_action.get()))
ins->append(key.c);
else {
if(current_action) history.add(current_action);
aTextInsert* new_ins = new aTextInsert(*this, insertionPoint);
new_ins->append(key.c);
current_action.reset(new_ins);
}
contents.insert(contents.begin() + insertionPoint, char(key.c));
selectionPoint = ++insertionPoint;
} else switch(key.k) {
case key_enter: break; // Shouldn't be receiving this anyway
// TODO: Implement all the other special keys
case key_left: case key_word_left:
if(current_action) history.add(current_action), current_action.reset();
if(haveSelection && !select) {
selectionPoint = insertionPoint = std::min(selectionPoint,insertionPoint);
break;
@@ -402,6 +414,7 @@ void cTextField::handleInput(cKey key) {
if(!select) selectionPoint = insertionPoint;
break;
case key_right: case key_word_right:
if(current_action) history.add(current_action), current_action.reset();
if(haveSelection && !select) {
selectionPoint = insertionPoint = std::max(selectionPoint,insertionPoint);
break;
@@ -417,6 +430,7 @@ void cTextField::handleInput(cKey key) {
if(!select) selectionPoint = insertionPoint;
break;
case key_up:
if(current_action) history.add(current_action), current_action.reset();
if(haveSelection && !select)
selectionPoint = insertionPoint = std::min(selectionPoint,insertionPoint);
if(snippets[ip_row].at.y == snippets[0].at.y) {
@@ -430,6 +444,7 @@ void cTextField::handleInput(cKey key) {
}
break;
case key_down:
if(current_action) history.add(current_action), current_action.reset();
if(haveSelection && !select)
selectionPoint = insertionPoint = std::max(selectionPoint,insertionPoint);
if(snippets[ip_row].at.y == snippets.back().at.y) {
@@ -443,13 +458,20 @@ void cTextField::handleInput(cKey key) {
}
break;
case key_bsp: case key_word_bsp:
case key_del: case key_word_del:
if(haveSelection) {
if(key.k == key_word_bsp)
handleInput({true, key_word_left, mod_shift});
else if(key.k == key_word_del)
handleInput({true, key_word_right, mod_shift});
auto begin = contents.begin() + std::min(selectionPoint, insertionPoint);
auto end = contents.begin() + std::max(selectionPoint, insertionPoint);
std::string removed(begin, end);
auto result = contents.erase(begin, end);
bool dir = insertionPoint < selectionPoint;
selectionPoint = insertionPoint = result - contents.begin();
if(current_action) history.add(current_action), current_action.reset();
history.add(action_ptr(new aTextDelete(*this, std::min(selectionPoint, insertionPoint), removed, dir)));
} else if(key.k == key_word_bsp) {
cKey selectKey = key;
selectKey.k = key_word_left;
@@ -459,20 +481,19 @@ void cTextField::handleInput(cKey key) {
if(selectionPoint != insertionPoint)
handleInput(key);
return;
} else {
} else if(key.k == key_bsp) {
if(insertionPoint == 0) break;
char c = contents[insertionPoint - 1];
contents.erase(insertionPoint - 1,1);
selectionPoint = --insertionPoint;
}
break;
case key_del: case key_word_del:
if(haveSelection) {
if(key.k == key_word_del)
handleInput({true, key_word_right, mod_shift});
auto begin = contents.begin() + std::min(selectionPoint, insertionPoint);
auto end = contents.begin() + std::max(selectionPoint, insertionPoint);
auto result = contents.erase(begin, end);
selectionPoint = insertionPoint = result - contents.begin();
if(aTextDelete* del = dynamic_cast<aTextDelete*>(current_action.get()))
del->append_front(c);
else {
if(current_action) history.add(current_action);
aTextDelete* new_del = new aTextDelete(*this, insertionPoint + 1, insertionPoint + 1);
new_del->append_front(c);
current_action.reset(new_del);
}
} else if(key.k == key_word_del) {
cKey selectKey = key;
selectKey.k = key_word_right;
@@ -482,9 +503,18 @@ void cTextField::handleInput(cKey key) {
if(selectionPoint != insertionPoint)
handleInput(key);
return;
} else {
} else if(key.k == key_del) {
if(insertionPoint == contents.length()) break;
char c = contents[insertionPoint];
contents.erase(insertionPoint,1);
if(aTextDelete* del = dynamic_cast<aTextDelete*>(current_action.get()))
del->append_back(c);
else {
if(current_action) history.add(current_action);
aTextDelete* new_del = new aTextDelete(*this, insertionPoint, insertionPoint);
new_del->append_back(c);
current_action.reset(new_del);
}
}
break;
case key_top:
@@ -498,15 +528,18 @@ void cTextField::handleInput(cKey key) {
selectionPoint = contents.length();
break;
case key_end:
if(current_action) history.add(current_action), current_action.reset();
new_ip = snippets[ip_row].at.x + string_length(snippets[ip_row].text, style);
set_ip(loc(new_ip, snippets[ip_row].at.y), select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
if(!select) selectionPoint = insertionPoint;
break;
case key_home:
if(current_action) history.add(current_action), current_action.reset();
set_ip(snippets[ip_row].at, select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
if(!select) selectionPoint = insertionPoint;
break;
case key_pgup:
if(current_action) history.add(current_action), current_action.reset();
if(snippets[ip_row].at.y != snippets[0].at.y) {
int x = snippets[ip_row].at.x + ip_col, y = frame.top + 2;
set_ip(loc(x,y), select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
@@ -514,6 +547,7 @@ void cTextField::handleInput(cKey key) {
}
break;
case key_pgdn:
if(current_action) history.add(current_action), current_action.reset();
if(snippets[ip_row].at.y != snippets.back().at.y) {
int x = snippets[ip_row].at.x + ip_col, y = frame.bottom - 2;
set_ip(loc(x,y), select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
@@ -522,6 +556,7 @@ void cTextField::handleInput(cKey key) {
break;
case key_copy:
case key_cut:
if(current_action) history.add(current_action), current_action.reset();
set_clipboard(contents.substr(std::min(insertionPoint,selectionPoint), abs(insertionPoint - selectionPoint)));
if(key.k == key_cut) {
cKey deleteKey = key;
@@ -531,20 +566,30 @@ void cTextField::handleInput(cKey key) {
}
break;
case key_paste:
if(current_action) history.add(current_action), current_action.reset();
if(!get_clipboard().empty()) {
cKey deleteKey = {true, key_bsp, mod_none};
handleInput(deleteKey);
if(haveSelection) {
cKey deleteKey = {true, key_bsp, mod_none};
handleInput(deleteKey);
}
contents = getText();
std::string toInsert = get_clipboard();
contents.insert(insertionPoint, toInsert);
history.add(action_ptr(new aTextInsert(*this, insertionPoint, toInsert)));
insertionPoint += toInsert.length();
selectionPoint = insertionPoint;
}
break;
case key_undo:
if(current_action) history.add(current_action), current_action.reset();
history.undo();
return;
case key_redo:
break;
if(current_action) history.add(current_action), current_action.reset();
history.redo();
return;
case key_selectall:
if(current_action) history.add(current_action), current_action.reset();
selectionPoint = 0;
insertionPoint = contents.length();
break;
@@ -562,6 +607,59 @@ void cTextField::handleInput(cKey key) {
selectionPoint = sp;
}
aTextInsert::aTextInsert(cTextField& in, int at, std::string text) : cAction("insert text"), in(in), at(at), text(text) {}
void aTextInsert::undo() {
std::string contents = in.getText();
auto del_start = contents.begin() + at;
auto del_end = del_start + text.length();
auto result = contents.erase(del_start, del_end);
in.setText(contents);
in.selectionPoint = in.insertionPoint = result - contents.begin();
}
void aTextInsert::redo() {
std::string contents = in.getText();
contents.insert(at, text);
in.setText(contents);
in.selectionPoint = in.insertionPoint = at + text.length();
}
void aTextInsert::append(char c) {
text += c;
}
aTextDelete::aTextDelete(cTextField& in, int start, int end) : cAction("delete text"), in(in), start(start), end(end), ip(0) {}
aTextDelete::aTextDelete(cTextField& in, int start, std::string content, bool from_start) : cAction("delete text"), in(in), start(start), end(start + content.size()), text(content), ip(from_start ? 0 : content.size()) {}
void aTextDelete::undo() {
std::string contents = in.getText();
contents.insert(start, text);
in.setText(contents);
in.selectionPoint = in.insertionPoint = start + ip;
}
void aTextDelete::redo() {
std::string contents = in.getText();
auto del_start = contents.begin() + start;
auto del_end = contents.begin() + end;
auto result = contents.erase(del_start, del_end);
in.setText(contents);
in.selectionPoint = in.insertionPoint = result - contents.begin();
}
void aTextDelete::append_front(char c) {
text = c + text;
start--;
ip++;
}
void aTextDelete::append_back(char c) {
text += c;
end++;
}
cControl::storage_t cTextField::store() {
storage_t storage = cControl::store();
storage["fld-ip"] = insertionPoint;

View File

@@ -70,6 +70,8 @@ public:
long tabOrder = 0;
private:
void set_ip(location clickLoc, int cTextField::* insertionPoint);
cUndoList history;
action_ptr current_action;
eFldType field_type;
focus_callback_t onFocus;
bool haveFocus;
@@ -77,10 +79,37 @@ private:
int selectionPoint;
sf::Color color;
bool ip_visible;
sf::Clock ip_timer;
sf::Clock ip_timer, hist_timer;
bool changeMade = true;
rectangle text_rect;
std::vector<snippet_t> snippets;
int ip_row, ip_col;
friend class aTextInsert;
friend class aTextDelete;
};
class aTextInsert : public cAction {
cTextField& in;
int at;
std::string text;
public:
aTextInsert(cTextField& in, int at, std::string text = "");
void undo(), redo();
void append(char c);
~aTextInsert() {}
};
class aTextDelete : public cAction {
cTextField& in;
int start, end, ip;
std::string text;
public:
aTextDelete(cTextField& in, int start, int end);
aTextDelete(cTextField& in, int start, std::string content, bool from_start);
void undo(), redo();
void append_front(char c);
void append_back(char c);
~aTextDelete() {}
};
#endif