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:
@@ -58,6 +58,8 @@
|
|||||||
2BF04B2C0BF51924006C0831 /* boe.startup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2BF04B050BF51924006C0831 /* boe.startup.cpp */; };
|
2BF04B2C0BF51924006C0831 /* boe.startup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2BF04B050BF51924006C0831 /* boe.startup.cpp */; };
|
||||||
2BF04B2D0BF51924006C0831 /* boe.text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2BF04B070BF51924006C0831 /* boe.text.cpp */; };
|
2BF04B2D0BF51924006C0831 /* boe.text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2BF04B070BF51924006C0831 /* boe.text.cpp */; };
|
||||||
2BF04B2E0BF51924006C0831 /* boe.town.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2BF04B090BF51924006C0831 /* boe.town.cpp */; };
|
2BF04B2E0BF51924006C0831 /* boe.town.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2BF04B090BF51924006C0831 /* boe.town.cpp */; };
|
||||||
|
91034D1D1B21DAC5008F01C1 /* undo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 912283C80FD0E16C00B21642 /* undo.cpp */; };
|
||||||
|
91034D1F1B21DAC6008F01C1 /* undo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 912283C80FD0E16C00B21642 /* undo.cpp */; };
|
||||||
9107074C18F1D18400F7BD7F /* scrollbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9191460018E63D8E005CF3A4 /* scrollbar.cpp */; };
|
9107074C18F1D18400F7BD7F /* scrollbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9191460018E63D8E005CF3A4 /* scrollbar.cpp */; };
|
||||||
9107074D18F1D18400F7BD7F /* scrollbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9191460018E63D8E005CF3A4 /* scrollbar.cpp */; };
|
9107074D18F1D18400F7BD7F /* scrollbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9191460018E63D8E005CF3A4 /* scrollbar.cpp */; };
|
||||||
9107074E18F1D18500F7BD7F /* scrollbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9191460018E63D8E005CF3A4 /* scrollbar.cpp */; };
|
9107074E18F1D18500F7BD7F /* scrollbar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9191460018E63D8E005CF3A4 /* scrollbar.cpp */; };
|
||||||
@@ -1530,6 +1532,7 @@
|
|||||||
914699011A747C6600F20F5E /* creature.cpp in Sources */,
|
914699011A747C6600F20F5E /* creature.cpp in Sources */,
|
||||||
91E30F2B1A74819C0057C54A /* fileio_party.cpp in Sources */,
|
91E30F2B1A74819C0057C54A /* fileio_party.cpp in Sources */,
|
||||||
91E30F2E1A7481C40057C54A /* fileio.cpp in Sources */,
|
91E30F2E1A7481C40057C54A /* fileio.cpp in Sources */,
|
||||||
|
91034D1F1B21DAC6008F01C1 /* undo.cpp in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -1592,6 +1595,7 @@
|
|||||||
91E30F311A748ABA0057C54A /* fileio_scen.cpp in Sources */,
|
91E30F311A748ABA0057C54A /* fileio_scen.cpp in Sources */,
|
||||||
9117A4111A7EC06700CD6EB4 /* living.cpp in Sources */,
|
9117A4111A7EC06700CD6EB4 /* living.cpp in Sources */,
|
||||||
91DBE9A81A873D3900ED006C /* specials_parse.cpp in Sources */,
|
91DBE9A81A873D3900ED006C /* specials_parse.cpp in Sources */,
|
||||||
|
91034D1D1B21DAC5008F01C1 /* undo.cpp in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -373,6 +373,9 @@ void cTextField::handleInput(cKey key) {
|
|||||||
style.pointSize = 12;
|
style.pointSize = 12;
|
||||||
size_t new_ip;
|
size_t new_ip;
|
||||||
std::string contents = getText();
|
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(!key.spec) {
|
||||||
if(haveSelection) {
|
if(haveSelection) {
|
||||||
cKey deleteKey = key;
|
cKey deleteKey = key;
|
||||||
@@ -381,12 +384,21 @@ void cTextField::handleInput(cKey key) {
|
|||||||
handleInput(deleteKey);
|
handleInput(deleteKey);
|
||||||
contents = getText();
|
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));
|
contents.insert(contents.begin() + insertionPoint, char(key.c));
|
||||||
selectionPoint = ++insertionPoint;
|
selectionPoint = ++insertionPoint;
|
||||||
} else switch(key.k) {
|
} else switch(key.k) {
|
||||||
case key_enter: break; // Shouldn't be receiving this anyway
|
case key_enter: break; // Shouldn't be receiving this anyway
|
||||||
// TODO: Implement all the other special keys
|
// TODO: Implement all the other special keys
|
||||||
case key_left: case key_word_left:
|
case key_left: case key_word_left:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
if(haveSelection && !select) {
|
if(haveSelection && !select) {
|
||||||
selectionPoint = insertionPoint = std::min(selectionPoint,insertionPoint);
|
selectionPoint = insertionPoint = std::min(selectionPoint,insertionPoint);
|
||||||
break;
|
break;
|
||||||
@@ -402,6 +414,7 @@ void cTextField::handleInput(cKey key) {
|
|||||||
if(!select) selectionPoint = insertionPoint;
|
if(!select) selectionPoint = insertionPoint;
|
||||||
break;
|
break;
|
||||||
case key_right: case key_word_right:
|
case key_right: case key_word_right:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
if(haveSelection && !select) {
|
if(haveSelection && !select) {
|
||||||
selectionPoint = insertionPoint = std::max(selectionPoint,insertionPoint);
|
selectionPoint = insertionPoint = std::max(selectionPoint,insertionPoint);
|
||||||
break;
|
break;
|
||||||
@@ -417,6 +430,7 @@ void cTextField::handleInput(cKey key) {
|
|||||||
if(!select) selectionPoint = insertionPoint;
|
if(!select) selectionPoint = insertionPoint;
|
||||||
break;
|
break;
|
||||||
case key_up:
|
case key_up:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
if(haveSelection && !select)
|
if(haveSelection && !select)
|
||||||
selectionPoint = insertionPoint = std::min(selectionPoint,insertionPoint);
|
selectionPoint = insertionPoint = std::min(selectionPoint,insertionPoint);
|
||||||
if(snippets[ip_row].at.y == snippets[0].at.y) {
|
if(snippets[ip_row].at.y == snippets[0].at.y) {
|
||||||
@@ -430,6 +444,7 @@ void cTextField::handleInput(cKey key) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case key_down:
|
case key_down:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
if(haveSelection && !select)
|
if(haveSelection && !select)
|
||||||
selectionPoint = insertionPoint = std::max(selectionPoint,insertionPoint);
|
selectionPoint = insertionPoint = std::max(selectionPoint,insertionPoint);
|
||||||
if(snippets[ip_row].at.y == snippets.back().at.y) {
|
if(snippets[ip_row].at.y == snippets.back().at.y) {
|
||||||
@@ -443,13 +458,20 @@ void cTextField::handleInput(cKey key) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case key_bsp: case key_word_bsp:
|
case key_bsp: case key_word_bsp:
|
||||||
|
case key_del: case key_word_del:
|
||||||
if(haveSelection) {
|
if(haveSelection) {
|
||||||
if(key.k == key_word_bsp)
|
if(key.k == key_word_bsp)
|
||||||
handleInput({true, key_word_left, mod_shift});
|
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 begin = contents.begin() + std::min(selectionPoint, insertionPoint);
|
||||||
auto end = contents.begin() + std::max(selectionPoint, insertionPoint);
|
auto end = contents.begin() + std::max(selectionPoint, insertionPoint);
|
||||||
|
std::string removed(begin, end);
|
||||||
auto result = contents.erase(begin, end);
|
auto result = contents.erase(begin, end);
|
||||||
|
bool dir = insertionPoint < selectionPoint;
|
||||||
selectionPoint = insertionPoint = result - contents.begin();
|
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) {
|
} else if(key.k == key_word_bsp) {
|
||||||
cKey selectKey = key;
|
cKey selectKey = key;
|
||||||
selectKey.k = key_word_left;
|
selectKey.k = key_word_left;
|
||||||
@@ -459,20 +481,19 @@ void cTextField::handleInput(cKey key) {
|
|||||||
if(selectionPoint != insertionPoint)
|
if(selectionPoint != insertionPoint)
|
||||||
handleInput(key);
|
handleInput(key);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else if(key.k == key_bsp) {
|
||||||
if(insertionPoint == 0) break;
|
if(insertionPoint == 0) break;
|
||||||
|
char c = contents[insertionPoint - 1];
|
||||||
contents.erase(insertionPoint - 1,1);
|
contents.erase(insertionPoint - 1,1);
|
||||||
selectionPoint = --insertionPoint;
|
selectionPoint = --insertionPoint;
|
||||||
}
|
if(aTextDelete* del = dynamic_cast<aTextDelete*>(current_action.get()))
|
||||||
break;
|
del->append_front(c);
|
||||||
case key_del: case key_word_del:
|
else {
|
||||||
if(haveSelection) {
|
if(current_action) history.add(current_action);
|
||||||
if(key.k == key_word_del)
|
aTextDelete* new_del = new aTextDelete(*this, insertionPoint + 1, insertionPoint + 1);
|
||||||
handleInput({true, key_word_right, mod_shift});
|
new_del->append_front(c);
|
||||||
auto begin = contents.begin() + std::min(selectionPoint, insertionPoint);
|
current_action.reset(new_del);
|
||||||
auto end = contents.begin() + std::max(selectionPoint, insertionPoint);
|
}
|
||||||
auto result = contents.erase(begin, end);
|
|
||||||
selectionPoint = insertionPoint = result - contents.begin();
|
|
||||||
} else if(key.k == key_word_del) {
|
} else if(key.k == key_word_del) {
|
||||||
cKey selectKey = key;
|
cKey selectKey = key;
|
||||||
selectKey.k = key_word_right;
|
selectKey.k = key_word_right;
|
||||||
@@ -482,9 +503,18 @@ void cTextField::handleInput(cKey key) {
|
|||||||
if(selectionPoint != insertionPoint)
|
if(selectionPoint != insertionPoint)
|
||||||
handleInput(key);
|
handleInput(key);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else if(key.k == key_del) {
|
||||||
if(insertionPoint == contents.length()) break;
|
if(insertionPoint == contents.length()) break;
|
||||||
|
char c = contents[insertionPoint];
|
||||||
contents.erase(insertionPoint,1);
|
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;
|
break;
|
||||||
case key_top:
|
case key_top:
|
||||||
@@ -498,15 +528,18 @@ void cTextField::handleInput(cKey key) {
|
|||||||
selectionPoint = contents.length();
|
selectionPoint = contents.length();
|
||||||
break;
|
break;
|
||||||
case key_end:
|
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);
|
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);
|
set_ip(loc(new_ip, snippets[ip_row].at.y), select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
|
||||||
if(!select) selectionPoint = insertionPoint;
|
if(!select) selectionPoint = insertionPoint;
|
||||||
break;
|
break;
|
||||||
case key_home:
|
case key_home:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
set_ip(snippets[ip_row].at, select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
|
set_ip(snippets[ip_row].at, select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
|
||||||
if(!select) selectionPoint = insertionPoint;
|
if(!select) selectionPoint = insertionPoint;
|
||||||
break;
|
break;
|
||||||
case key_pgup:
|
case key_pgup:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
if(snippets[ip_row].at.y != snippets[0].at.y) {
|
if(snippets[ip_row].at.y != snippets[0].at.y) {
|
||||||
int x = snippets[ip_row].at.x + ip_col, y = frame.top + 2;
|
int x = snippets[ip_row].at.x + ip_col, y = frame.top + 2;
|
||||||
set_ip(loc(x,y), select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
|
set_ip(loc(x,y), select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
|
||||||
@@ -514,6 +547,7 @@ void cTextField::handleInput(cKey key) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case key_pgdn:
|
case key_pgdn:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
if(snippets[ip_row].at.y != snippets.back().at.y) {
|
if(snippets[ip_row].at.y != snippets.back().at.y) {
|
||||||
int x = snippets[ip_row].at.x + ip_col, y = frame.bottom - 2;
|
int x = snippets[ip_row].at.x + ip_col, y = frame.bottom - 2;
|
||||||
set_ip(loc(x,y), select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
|
set_ip(loc(x,y), select ? &cTextField::selectionPoint : &cTextField::insertionPoint);
|
||||||
@@ -522,6 +556,7 @@ void cTextField::handleInput(cKey key) {
|
|||||||
break;
|
break;
|
||||||
case key_copy:
|
case key_copy:
|
||||||
case key_cut:
|
case key_cut:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
set_clipboard(contents.substr(std::min(insertionPoint,selectionPoint), abs(insertionPoint - selectionPoint)));
|
set_clipboard(contents.substr(std::min(insertionPoint,selectionPoint), abs(insertionPoint - selectionPoint)));
|
||||||
if(key.k == key_cut) {
|
if(key.k == key_cut) {
|
||||||
cKey deleteKey = key;
|
cKey deleteKey = key;
|
||||||
@@ -531,20 +566,30 @@ void cTextField::handleInput(cKey key) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case key_paste:
|
case key_paste:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
if(!get_clipboard().empty()) {
|
if(!get_clipboard().empty()) {
|
||||||
cKey deleteKey = {true, key_bsp, mod_none};
|
if(haveSelection) {
|
||||||
handleInput(deleteKey);
|
cKey deleteKey = {true, key_bsp, mod_none};
|
||||||
|
handleInput(deleteKey);
|
||||||
|
}
|
||||||
contents = getText();
|
contents = getText();
|
||||||
std::string toInsert = get_clipboard();
|
std::string toInsert = get_clipboard();
|
||||||
contents.insert(insertionPoint, toInsert);
|
contents.insert(insertionPoint, toInsert);
|
||||||
|
history.add(action_ptr(new aTextInsert(*this, insertionPoint, toInsert)));
|
||||||
insertionPoint += toInsert.length();
|
insertionPoint += toInsert.length();
|
||||||
selectionPoint = insertionPoint;
|
selectionPoint = insertionPoint;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case key_undo:
|
case key_undo:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
|
history.undo();
|
||||||
|
return;
|
||||||
case key_redo:
|
case key_redo:
|
||||||
break;
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
|
history.redo();
|
||||||
|
return;
|
||||||
case key_selectall:
|
case key_selectall:
|
||||||
|
if(current_action) history.add(current_action), current_action.reset();
|
||||||
selectionPoint = 0;
|
selectionPoint = 0;
|
||||||
insertionPoint = contents.length();
|
insertionPoint = contents.length();
|
||||||
break;
|
break;
|
||||||
@@ -562,6 +607,59 @@ void cTextField::handleInput(cKey key) {
|
|||||||
selectionPoint = sp;
|
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() {
|
cControl::storage_t cTextField::store() {
|
||||||
storage_t storage = cControl::store();
|
storage_t storage = cControl::store();
|
||||||
storage["fld-ip"] = insertionPoint;
|
storage["fld-ip"] = insertionPoint;
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ public:
|
|||||||
long tabOrder = 0;
|
long tabOrder = 0;
|
||||||
private:
|
private:
|
||||||
void set_ip(location clickLoc, int cTextField::* insertionPoint);
|
void set_ip(location clickLoc, int cTextField::* insertionPoint);
|
||||||
|
cUndoList history;
|
||||||
|
action_ptr current_action;
|
||||||
eFldType field_type;
|
eFldType field_type;
|
||||||
focus_callback_t onFocus;
|
focus_callback_t onFocus;
|
||||||
bool haveFocus;
|
bool haveFocus;
|
||||||
@@ -77,10 +79,37 @@ private:
|
|||||||
int selectionPoint;
|
int selectionPoint;
|
||||||
sf::Color color;
|
sf::Color color;
|
||||||
bool ip_visible;
|
bool ip_visible;
|
||||||
sf::Clock ip_timer;
|
sf::Clock ip_timer, hist_timer;
|
||||||
bool changeMade = true;
|
bool changeMade = true;
|
||||||
rectangle text_rect;
|
rectangle text_rect;
|
||||||
std::vector<snippet_t> snippets;
|
std::vector<snippet_t> snippets;
|
||||||
int ip_row, ip_col;
|
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
|
#endif
|
||||||
|
|||||||
@@ -8,23 +8,34 @@
|
|||||||
|
|
||||||
#include "undo.hpp"
|
#include "undo.hpp"
|
||||||
|
|
||||||
|
cAction::~cAction() {}
|
||||||
|
|
||||||
cUndoList::cUndoList(){
|
cUndoList::cUndoList(){
|
||||||
lastSave = cur = theList.begin();
|
lastSave = cur = theList.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t cUndoList::maxUndoSize = 0;
|
size_t cUndoList::maxUndoSize = 50;
|
||||||
|
|
||||||
// TODO: These functions should have error checking to ensure they do not access an out of bounds action
|
|
||||||
void cUndoList::undo(){
|
void cUndoList::undo(){
|
||||||
|
if(noUndo()) return;
|
||||||
(*cur)->undo();
|
(*cur)->undo();
|
||||||
cur--;
|
cur++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cUndoList::redo(){
|
void cUndoList::redo(){
|
||||||
cur++;
|
if(noRedo()) return;
|
||||||
|
cur--;
|
||||||
(*cur)->redo();
|
(*cur)->redo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cUndoList::noUndo() {
|
||||||
|
return cur == theList.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cUndoList::noRedo() {
|
||||||
|
return cur == theList.begin();
|
||||||
|
}
|
||||||
|
|
||||||
void cUndoList::save(){
|
void cUndoList::save(){
|
||||||
lastSave = cur;
|
lastSave = cur;
|
||||||
}
|
}
|
||||||
@@ -33,8 +44,18 @@ void cUndoList::revert(){
|
|||||||
while(cur != lastSave) undo();
|
while(cur != lastSave) undo();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cUndoList::add(cAction* what){
|
void cUndoList::clear() {
|
||||||
theList.push_back(what);
|
theList.clear();
|
||||||
num_actions++;
|
}
|
||||||
while(num_actions > maxUndoSize) theList.pop_front(), num_actions--;
|
|
||||||
|
void cUndoList::add(action_ptr what){
|
||||||
|
if(!what) return;
|
||||||
|
theList.erase(theList.begin(), cur);
|
||||||
|
theList.push_front(what);
|
||||||
|
num_actions++;
|
||||||
|
while(num_actions > maxUndoSize) {
|
||||||
|
theList.pop_back();
|
||||||
|
num_actions--;
|
||||||
|
}
|
||||||
|
cur = theList.begin();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,27 +10,38 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
class cAction {
|
class cAction {
|
||||||
|
std::string actname;
|
||||||
|
protected:
|
||||||
|
bool done = false;
|
||||||
public:
|
public:
|
||||||
virtual void undo() = 0; // undoes this action if it has not already been undone
|
cAction(std::string name) : actname(name) {}
|
||||||
virtual void redo() = 0; // redoes this action if it has been undone
|
virtual void undo() = 0; ///< Undoes this action if it has not already been undone
|
||||||
virtual bool isDone() = 0; // checks to see whether the action has been undone; returns false if it has
|
virtual void redo() = 0; ///< Redoes this action if it has been undone
|
||||||
virtual std::string getActionName() = 0; // returns the name of this action for display in the Edit menu
|
bool isDone() {return done;}; ///< checks to see whether the action has been undone; returns false if it has
|
||||||
|
std::string getActionName() {return actname;} ///< returns the name of this action for display in the Edit menu
|
||||||
virtual ~cAction();
|
virtual ~cAction();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using action_ptr = std::shared_ptr<cAction>;
|
||||||
|
|
||||||
class cUndoList {
|
class cUndoList {
|
||||||
std::list<cAction*> theList;
|
std::list<action_ptr> theList;
|
||||||
std::list<cAction*>::iterator cur, lastSave;
|
std::list<action_ptr>::iterator cur, lastSave;
|
||||||
size_t num_actions;
|
size_t num_actions = 0;
|
||||||
public:
|
public:
|
||||||
cUndoList();
|
cUndoList();
|
||||||
void undo(); // undoes the current action and decrements the cur pointer
|
void undo(); ///< Undoes the current action and decrements the cur pointer
|
||||||
void redo(); // increments the cur pointer and redoes the current action
|
void redo(); ///< Increments the cur pointer and redoes the current action
|
||||||
void save(); // sets the last saved action to the current action
|
void save(); ///< Sets the last saved action to the current action
|
||||||
void revert(); // undoes all actions back to (but excluding) the last saved action
|
void revert(); ///< Undoes all actions back to (but excluding) the last saved action
|
||||||
void add(cAction* what);
|
void clear(); ///< Clears the list
|
||||||
|
bool noUndo(); ///< Check whether there's an action to undo
|
||||||
|
bool noRedo(); ///< Check whether there's an action to redo
|
||||||
|
void add(action_ptr what);
|
||||||
static size_t maxUndoSize;
|
static size_t maxUndoSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user