undo/redo for edit/clear shop

This commit is contained in:
2025-06-12 17:33:25 -05:00
parent 71629e45bd
commit 7e9fbe75a2
7 changed files with 119 additions and 18 deletions

View File

@@ -0,0 +1,12 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!-- NOTE: This file should be updated to use relative positioning the next time it changes. -->
<?xml-stylesheet href="dialog.xsl" type="text/xsl"?>
<dialog defbtn='keep' escbtn='cancel'>
<pict type='dlog' num='7' top='6' left='6'/>
<text name='keep-msg' top='6' left='49' width='256' height='32'>
Keep changes to {{shop}} before editing another shop?
</text>
<button name='cancel' type='regular' top='43' left='109'>Cancel</button>
<button name='revert' type='regular' top='43' left='175'>Discard</button>
<button name='keep' type='regular' top='43' left='240'>Keep</button>
</dialog>

View File

@@ -328,3 +328,24 @@ int cShopItem::getCost(int adj) const {
cost /= 10;
return cost;
}
bool cShopItem::operator==(const cShopItem& other) {
CHECK_EQ(other, type);
CHECK_EQ(other, quantity);
CHECK_EQ(other, index);
CHECK_EQ(other, item);
return true;
}
bool cShop::operator==(const cShop& other) {
CHECK_EQ(other, items.size());
for(size_t i = 0; i < items.size(); ++i){
if(items[i] != other.items[i]) return false;
}
CHECK_EQ(other, cost_adj);
CHECK_EQ(other, name);
CHECK_EQ(other, type);
CHECK_EQ(other, prompt);
CHECK_EQ(other, face);
return true;
}

View File

@@ -49,6 +49,10 @@ struct cShopItem {
size_t quantity, index;
cItem item{ITEM_SHOP};
int getCost(int adj) const;
// For detecting actual changes to shops in the scenario editor
bool operator==(const cShopItem& other);
bool operator!=(const cShopItem& other) { return !(*this == other); }
};
enum eShopPreset {SHOP_HEALING, SHOP_JUNK};
@@ -92,6 +96,10 @@ public:
void takeOne(size_t i);
void clearItem(size_t i);
void clear();
// For detecting actual changes to shops in the scenario editor
bool operator==(const cShop& other);
bool operator!=(const cShop& other) { return !(*this == other); }
};
std::istream& operator>>(std::istream& in, eShopType& type);

View File

@@ -666,19 +666,17 @@ static bool handle_rb_action(location the_point, bool option_hit) {
scenario.shops.pop_back();
}
// Clear shop (it can't be fully deleted)
else scenario.shops[j] = cShop("Unused Shop");
else{
cShop cleared("Unused Shop");
undo_list.add(action_ptr(new aEditClearShop("Clear Shop", j, scenario.shops[j], cleared)));
update_edit_menu();
scenario.shops[j] = cleared;
}
} else {
bool is_new = (j == scenario.shops.size());
if(edit_shop(j)){
// Create new confirmed
if(is_new){
undo_list.add(action_ptr(new aCreateDeleteShop(true, scenario.shops.back())));
update_edit_menu();
}
// Shop edited
else{
// TODO undo action
}
// Shop create/edit undo action is added in save_shop_from_dlog() because shop editor
// has left/right buttons and can be launched with create/edit buttons elsewhere.
}
// Create new canceled
else if(is_new){

View File

@@ -2472,7 +2472,7 @@ static void put_shop_in_dlog(cDialog& me, const cShop& shop, size_t which_shop)
}
}
static bool save_shop_from_dlog(cDialog& me, cShop& shop, size_t which_shop, bool close) {
static bool save_shop_from_dlog(cDialog& me, cShop& shop, size_t which_shop, bool& is_new, bool need_confirm, bool close) {
if(!me.toast(true)) return false;
shop.setName(me["name"].getText());
@@ -2481,13 +2481,38 @@ static bool save_shop_from_dlog(cDialog& me, cShop& shop, size_t which_shop, boo
shop.setFace(dynamic_cast<cPict&>(me["face"]).getPicNum());
// Items are filled in as they're added by the dialog, so that's all we need to do here
scenario.shops[which_shop] = shop;
if(shop != scenario.shops[which_shop] || is_new){
if(need_confirm){
// Confirm keeping changes
cChoiceDlog dlog("confirm-edit-shop", {"keep","revert","cancel"}, &me);
dlog->getControl("keep-msg").replaceText("{{shop}}", shop.getName());
std::string choice = dlog.show();
if(choice == "revert"){
put_shop_in_dlog(me, scenario.shops[which_shop], which_shop);
return false;
}else if(choice == "cancel"){
return false;
}
}
// Create new confirmed
if(is_new){
undo_list.add(action_ptr(new aCreateDeleteShop(true, shop)));
update_edit_menu();
}
// Shop edited
else{
undo_list.add(action_ptr(new aEditClearShop("Edit Shop", which_shop, scenario.shops[which_shop], shop)));
update_edit_menu();
}
scenario.shops[which_shop] = shop;
}
if(!close) me.untoast();
return true;
}
static bool change_shop_dlog_page(cDialog& me, std::string dir, cShop& shop, size_t& which_shop) {
if(!save_shop_from_dlog(me, shop, which_shop, false))
static bool change_shop_dlog_page(cDialog& me, std::string dir, cShop& shop, size_t& which_shop, bool& is_new) {
if(!save_shop_from_dlog(me, shop, which_shop, is_new, true, false))
return true;
if(dir == "left") {
@@ -2728,7 +2753,12 @@ static bool add_shop_entry(cDialog& me, std::string type, cShop& shop, size_t wh
bool edit_shop(size_t which_shop, cDialog* parent) {
using namespace std::placeholders;
if(which_shop == scenario.shops.size()) scenario.shops.emplace_back("New Shop");
bool is_new = false;
if(which_shop == scenario.shops.size()){
is_new = true;
scenario.shops.emplace_back("New Shop");
}
bool was_new = is_new;
cShop shop = scenario.shops[which_shop];
if(shop.size() == 0 && (shop.getName() == "New Shop" || shop.getName() == "Unused Shop")) {
cChoiceDlog new_shop_dlg("new-shop", {"magic", "heal", "custom", "cancel"});
@@ -2744,7 +2774,7 @@ bool edit_shop(size_t which_shop, cDialog* parent) {
cDialog shop_dlg(*ResMgr::dialogs.get("edit-shop"), parent);
shop_dlg["cancel"].attachClickHandler(std::bind(&cDialog::toast, _1, false));
shop_dlg["okay"].attachClickHandler(std::bind(save_shop_from_dlog, _1, std::ref(shop), std::ref(which_shop), true));
shop_dlg["okay"].attachClickHandler(std::bind(save_shop_from_dlog, _1, std::ref(shop), std::ref(which_shop), std::ref(is_new), false, true));
shop_dlg["pickface"].attachClickHandler(std::bind(pick_picture, PIC_TALK, _1, "", "face"));
shop_dlg.attachClickHandlers(std::bind(change_shop_dlog_items_page, _1, _2, std::ref(shop)), {"up", "down"});
shop_dlg.attachClickHandlers(std::bind(delete_shop_entry, _1, _2, std::ref(shop), std::ref(which_shop)), {"del1", "del2", "del3", "del4", "del5"});
@@ -2755,12 +2785,12 @@ bool edit_shop(size_t which_shop, cDialog* parent) {
shop_dlg["left"].hide();
shop_dlg["right"].hide();
} else {
shop_dlg.attachClickHandlers(std::bind(change_shop_dlog_page, _1, _2, std::ref(shop), std::ref(which_shop)), {"left", "right"});
shop_dlg.attachClickHandlers(std::bind(change_shop_dlog_page, _1, _2, std::ref(shop), std::ref(which_shop), std::ref(is_new)), {"left", "right"});
}
put_shop_in_dlog(shop_dlg, shop, which_shop);
shop_dlg.run();
return shop_dlg.accepted();
return shop_dlg.accepted() || was_new != is_new;
}
static void put_vehicle_area(cDialog& me, const cVehicle& what) {

View File

@@ -479,4 +479,24 @@ bool aEditClearQuest::redo_me() {
}
scenario.quests[which] = after;
return true;
}
bool aEditClearShop::undo_me() {
// If not editing shops, show it
if(overall_mode != MODE_EDIT_SHOPS){
start_shops_editing();
// TODO scroll to show the shop
}
scenario.shops[which] = before;
return true;
}
bool aEditClearShop::redo_me() {
// If not editing shops, show it
if(overall_mode != MODE_EDIT_SHOPS){
start_shops_editing();
// TODO scroll to show the shop
}
scenario.shops[which] = after;
return true;
}

View File

@@ -328,4 +328,16 @@ public:
cAction(name), which(which), before(before), after(after) {}
};
/// Action which edits or clears a shop
class aEditClearShop : public cAction {
size_t which;
cShop before;
cShop after;
bool undo_me() override;
bool redo_me() override;
public:
aEditClearShop(std::string name, size_t which, cShop before, cShop after) :
cAction(name), which(which), before(before), after(after) {}
};
#endif