Initial framework for scenario editor undo/redo

In addition to the framework, this enables undo/redo of one action - adding a new town
This commit is contained in:
2017-02-12 18:27:01 -05:00
parent e38053d39f
commit 683ab805ec
14 changed files with 130 additions and 32 deletions

View File

@@ -703,6 +703,7 @@ void aTextInsert::undo() {
auto result = contents.erase(del_start, del_end);
in.setText(contents);
in.selectionPoint = in.insertionPoint = result - contents.begin();
done = false;
}
void aTextInsert::redo() {
@@ -710,6 +711,7 @@ void aTextInsert::redo() {
contents.insert(at, text);
in.setText(contents);
in.selectionPoint = in.insertionPoint = at + text.length();
done = true;
}
void aTextInsert::append(char c) {
@@ -725,6 +727,7 @@ void aTextDelete::undo() {
contents.insert(start, text);
in.setText(contents);
in.selectionPoint = in.insertionPoint = start + ip;
done = false;
}
void aTextDelete::redo() {
@@ -734,6 +737,7 @@ void aTextDelete::redo() {
auto result = contents.erase(del_start, del_end);
in.setText(contents);
in.selectionPoint = in.insertionPoint = result - contents.begin();
done = true;
}
void aTextDelete::append_front(char c) {

View File

@@ -38,6 +38,7 @@ short current_terrain_type = 0;
short safety = 0;
location spot_hit,last_spot_hit(-1,-1),mouse_spot(-1,-1);
short copied_spec = -1;
cUndoList undo_list;
cTown::cItem store_place_item;
@@ -228,8 +229,7 @@ static bool handle_lb_action(location the_point) {
case LB_LOAD_SCEN:
file_to_load = nav_get_scenario();
if(!file_to_load.empty() && load_scenario(file_to_load, scenario)) {
cur_town = scenario.last_town_edited;
town = scenario.towns[cur_town];
set_current_town(scenario.last_town_edited);
cur_out = scenario.last_out_edited;
current_terrain = scenario.outdoors[cur_out.x][cur_out.y];
overall_mode = MODE_MAIN_SCREEN;
@@ -252,7 +252,7 @@ static bool handle_lb_action(location the_point) {
showError("You have reached the limit of 200 towns you can have in one scenario.");
return true;
}
if(new_town(scenario.towns.size()))
if(new_town())
set_up_main_screen();
break;
case LB_EDIT_TEXT:
@@ -302,6 +302,7 @@ static bool handle_lb_action(location the_point) {
set_up_main_screen();
}
mouse_button_held = false;
update_edit_menu();
return true;
}
return false;

View File

@@ -1,5 +1,6 @@
#include "scen.global.hpp"
#include "undo.hpp"
void init_screen_locs();
void handle_action(location the_point,sf::Event event);
@@ -45,3 +46,12 @@ void place_edit_special(location loc);
void set_special(location spot_hit);
bool save_check(std::string which_dlog);
/// Represents the action of adding a new town to the end of the list
class aNewTown : public cAction {
class cTown* theTown;
public:
aNewTown(class cTown* t);
void undo() override;
void redo() override;
~aNewTown();
};

View File

@@ -13,12 +13,11 @@
#include <iostream>
#include "fileio.hpp"
#include "scen.actions.hpp"
#include "scen.townout.hpp"
//extern bool ae_loading, startup_loaded, All_Done, party_in_memory, finished_init;
extern cScenario scenario;
extern cTown* town;
extern cOutdoors* current_terrain;
extern short cur_town;
extern location cur_out;
extern bool change_made, ae_loading;
@@ -52,8 +51,7 @@ void set_up_apple_events(int, char*[]) {
std::copy(msg.get(), msg.get() + len, std::inserter(fileName, fileName.begin()));
if(load_scenario(fileName, scenario)) {
cur_town = scenario.last_town_edited;
town = scenario.towns[cur_town];
set_current_town(scenario.last_town_edited);
cur_out = scenario.last_out_edited;
current_terrain = scenario.outdoors[cur_out.x][cur_out.y];
change_made = false;

View File

@@ -20,6 +20,7 @@
#include "map_parse.hpp"
#include "winutil.hpp"
#include "choicedlog.hpp"
#include "undo.hpp"
extern cScenario scenario;
@@ -995,6 +996,9 @@ void save_scenario(bool rename) {
if(toFile.empty()) return;
}
extern cUndoList undo_list;
undo_list.save();
if(scenario.is_legacy && cChoiceDlog("save-legacy-scen", {"update", "cancel"}).show() == "update")
scenario.is_legacy = false;

View File

@@ -208,6 +208,7 @@ void Handle_Update() {
extern fs::path progDir;
void handle_menu_choice(eMenu item_hit) {
extern cUndoList undo_list;
bool isEdit = false, isHelp = false;
std::string helpDlog;
fs::path file_to_load;
@@ -232,6 +233,8 @@ void handle_menu_choice(eMenu item_hit) {
set_up_main_screen();
} else if(!file_to_load.empty())
set_up_start_screen(); // Failed to load file, dump to start
undo_list.clear();
update_edit_menu();
break;
case eMenu::FILE_SAVE:
save_scenario();
@@ -244,6 +247,8 @@ void handle_menu_choice(eMenu item_hit) {
overall_mode = MODE_MAIN_SCREEN;
set_up_main_screen();
}
undo_list.clear();
update_edit_menu();
break;
case eMenu::FILE_CLOSE:
if(!save_check("save-before-close"))
@@ -253,6 +258,8 @@ void handle_menu_choice(eMenu item_hit) {
pal_sbar->hide();
shut_down_menus(0);
set_up_start_screen();
undo_list.clear();
update_edit_menu();
break;
case eMenu::QUIT: // quit
if(!save_check("save-before-quit"))
@@ -292,9 +299,8 @@ void handle_menu_choice(eMenu item_hit) {
showError("You have reached the limit of 200 towns you can have in one scenario.");
return;
}
if(new_town(scenario.towns.size()))
if(new_town())
set_up_main_screen();
change_made = true;
break;
case eMenu::OUT_RESIZE:
if(resize_outdoors())
@@ -523,7 +529,15 @@ void handle_menu_choice(eMenu item_hit) {
if(isEdit) {
if(!cDialog::sendInput(editKey)) {
// Handle non-dialog edit operations here.
// switch(editKey.k) {}
switch(editKey.k) {
case key_undo: undo_list.undo(); break;
case key_redo: undo_list.redo(); break;
default: break;
}
update_edit_menu();
if(overall_mode == MODE_MAIN_SCREEN)
set_up_main_screen();
redraw_screen();
}
}
if(isHelp)

View File

@@ -11,6 +11,7 @@
void init_menubar();
void shut_down_menus(short mode);
void update_edit_menu();
enum class eMenu {
NONE, ABOUT, QUIT, FRILL, UNFRILL,

View File

@@ -10,12 +10,14 @@
#include "scenario.hpp" // Include before Cocoa because the Cocoa header defines things that cause compilation errors in here
#include <Cocoa/Cocoa.h>
#include "winutil.hpp"
#include "undo.hpp"
using MenuHandle = NSMenu*;
MenuHandle menu_bar_handle;
MenuHandle file_menu, edit_menu, app_menu, scen_menu, town_menu, out_menu, help_menu;
extern cScenario scenario;
extern cUndoList undo_list;
@interface MenuHandler : NSObject
-(void) menuChoice:(id) sender;
@@ -141,6 +143,27 @@ void shut_down_menus(short mode) {
}
}
void update_edit_menu() {
NSMenuItem* mi_undo = [edit_menu itemAtIndex: 0];
if(undo_list.noUndo()) {
[mi_undo setTitle: @"Can't Undo"];
[mi_undo setEnabled: NO];
} else {
std::string undo_name = "Undo " + undo_list.undoName();
[mi_undo setTitle: [NSString stringWithCString: undo_name.c_str() encoding: NSASCIIStringEncoding]];
[mi_undo setEnabled: YES];
}
NSMenuItem* mi_redo = [edit_menu itemAtIndex: 1];
if(undo_list.noRedo()) {
[mi_redo setTitle: @"Can't Redo"];
[mi_redo setEnabled: NO];
} else {
std::string redo_name = "Redo " + undo_list.redoName();
[mi_redo setTitle: [NSString stringWithCString: redo_name.c_str() encoding: NSASCIIStringEncoding]];
[mi_redo setEnabled: YES];
}
}
@implementation MenuHandler
-(void) menuChoice:(id) sender {
handle_menu_choice(eMenu([[sender representedObject] intValue]));

View File

@@ -190,6 +190,10 @@ void shut_down_menus(short mode) {
DrawMenuBar(mainPtr.getSystemHandle());
}
void update_edit_menu() {
// TODO: Set the undo/redo menuitem title according to the current action to be undone/redone
}
#include "cursors.hpp"
LRESULT CALLBACK menuProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) {
@@ -212,17 +216,15 @@ LRESULT CALLBACK menuProc(HWND handle, UINT message, WPARAM wParam, LPARAM lPara
#include "fileio.hpp"
#include "scen.actions.hpp"
#include "scen.townout.hpp"
extern short cur_town;
extern location cur_out;
extern cTown* town;
extern cOutdoors* current_terrain;
extern bool change_made, ae_loading;
void set_up_apple_events(int argc, char* argv[]) {
if(argc > 1) {
if(load_scenario(argv[1], scenario)) {
cur_town = scenario.last_town_edited;
town = scenario.towns[cur_town];
set_current_town(scenario.last_town_edited);
cur_out = scenario.last_out_edited;
current_terrain = scenario.outdoors[cur_out.x][cur_out.y];
change_made = false;

View File

@@ -7,6 +7,7 @@
#include "scenario.hpp"
#include "town.hpp"
#include "graphtool.hpp"
#include "scen.actions.hpp"
#include "scen.graphics.hpp"
#include "scen.townout.hpp"
#include "scen.keydlgs.hpp"
@@ -31,6 +32,7 @@ extern ter_num_t template_terrain[64][64];
extern cScenario scenario;
extern cOutdoors* current_terrain;
extern location cur_out;
extern cUndoList undo_list;
const char *day_str_1[] = {"Unused","Day creature appears","Day creature disappears",
"Unused","Unused","Unused","Unused","Unused","Unused"};
@@ -1283,9 +1285,37 @@ location pick_out(location default_loc,cScenario& scenario) {
return default_loc;
}
bool new_town(short which_town) {
void set_current_town(int to) {
if(to < 0 || to >= scenario.towns.size()) return;
cur_town = to;
town = scenario.towns[cur_town];
scenario.last_town_edited = cur_town;
}
aNewTown::aNewTown(cTown* t)
: cAction("add town")
, theTown(t)
{}
void aNewTown::undo() {
scenario.towns.resize(scenario.towns.size() - 1);
done = false;
set_current_town(scenario.towns.size() - 1);
}
void aNewTown::redo() {
scenario.towns.push_back(theTown);
done = true;
set_current_town(scenario.towns.size() - 1);
}
aNewTown::~aNewTown() {
if(!done) delete theTown;
}
bool new_town() {
cChoiceDlog new_dlg("new-town", {"okay", "cancel"});
new_dlg->getControl("num").setTextToNum(which_town);
new_dlg->getControl("num").setTextToNum(scenario.towns.size());
if(new_dlg.show() == "cancel") return false;
std::string size = dynamic_cast<cLedGroup&>(new_dlg->getControl("size")).getSelected();
@@ -1295,9 +1325,7 @@ bool new_town(short which_town) {
else if(size == "med") scenario.towns.push_back(new cTown(scenario, AREA_MEDIUM));
else if(size == "sm") scenario.towns.push_back(new cTown(scenario, AREA_SMALL));
cur_town = which_town;
town = scenario.towns[cur_town];
scenario.last_town_edited = cur_town;
set_current_town(scenario.towns.size() - 1);
town->name = new_dlg->getControl("name").getText().substr(0,30);
for(short i = 0; i < town->max_dim; i++)
@@ -1314,6 +1342,7 @@ bool new_town(short which_town) {
}
}
undo_list.add(action_ptr(new aNewTown(scenario.towns.back())));
change_made = true;
return true;

View File

@@ -16,9 +16,10 @@ short edit_talk_node(short which_node);
location pick_out(location default_loc,cScenario& scenario);
cTown* pick_import_town();
cOutdoors* pick_import_out();
bool new_town(short which_town);
bool new_town();
bool resize_outdoors();
void edit_placed_item(short which_i);
void delete_last_town();
void edit_town_wand();
void set_current_town(int to);

View File

@@ -28,11 +28,22 @@ void cUndoList::redo(){
(*cur)->redo();
}
bool cUndoList::noUndo() {
std::string cUndoList::undoName() const {
if(noUndo()) return "";
return (*cur)->getActionName();
}
std::string cUndoList::redoName() const {
if(noRedo()) return "";
auto next = std::next(cur);
return (*next)->getActionName();
}
bool cUndoList::noUndo() const {
return cur == theList.end();
}
bool cUndoList::noRedo() {
bool cUndoList::noRedo() const {
return cur == theList.begin();
}

View File

@@ -9,6 +9,9 @@
*
*/
#ifndef BOE_UNDO_HPP
#define BOE_UNDO_HPP
#include <list>
#include <memory>
#include <string>
@@ -16,7 +19,7 @@
class cAction {
std::string actname;
protected:
bool done = false;
bool done = true;
public:
cAction(std::string name) : actname(name) {}
virtual void undo() = 0; ///< Undoes this action if it has not already been undone
@@ -39,18 +42,14 @@ public:
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 clear(); ///< Clears the list
bool noUndo(); ///< Check whether there's an action to undo
bool noRedo(); ///< Check whether there's an action to redo
bool noUndo() const; ///< Check whether there's an action to undo
bool noRedo() const; ///< Check whether there's an action to redo
std::string undoName() const; ///< Get the action name of the next action to undo
std::string redoName() const; ///< Get the action name of the next action to redo
void add(action_ptr what);
static size_t maxUndoSize;
};
// As a special convention, I will prefix action classes with 'a' instead of 'c'
/*
class aEditMonster : public cAction {
cMonster oldMonst, newMonst;
};
class aEditItem : public cAction {
cItem oldItem, newItem;
};*/
#endif