From b7dd93b6e8847c45a0e905453f7c50c46ec20d86 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 21 Nov 2015 18:59:30 -0500 Subject: [PATCH 1/2] Implement most of the confirm plugin in Lua This should make resolving future issues easier, although implementing new confirmations in lua isn't possible yet (each one requires a line in confirm.cpp). This also resolves an issue with note-delete and route-delete, with dfhack/df-structures@1bc4f61 --- library/xml | 2 +- plugins/CMakeLists.txt | 2 +- plugins/confirm.cpp | 344 +++++++++++++++++----------------------- plugins/lua/confirm.lua | 229 ++++++++++++++++++++++++++ 4 files changed, 375 insertions(+), 202 deletions(-) create mode 100644 plugins/lua/confirm.lua diff --git a/library/xml b/library/xml index 378a580f7..1bc4f6133 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 378a580f7e333607a64a301d598e3885954a5d9d +Subproject commit 1bc4f6133c8a5bbd397001f97647c15724317e07 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index aee9d1421..f30f00f08 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -108,7 +108,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(cleanowned cleanowned.cpp) DFHACK_PLUGIN(colonies colonies.cpp) DFHACK_PLUGIN(command-prompt command-prompt.cpp) - DFHACK_PLUGIN(confirm confirm.cpp) + DFHACK_PLUGIN(confirm confirm.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(createitem createitem.cpp) DFHACK_PLUGIN(cursecheck cursecheck.cpp) DFHACK_PLUGIN(deramp deramp.cpp) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 3b3771956..dd951a85c 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -6,6 +6,8 @@ #include "Export.h" #include "PluginManager.h" #include "VTableInterpose.h" +#include "LuaTools.h" +#include "LuaWrapper.h" #include "uicommon.h" #include "modules/Gui.h" @@ -38,6 +40,16 @@ inline bool in_vector (std::vector &vec, FT item) return std::find(vec.begin(), vec.end(), item) != vec.end(); } +string char_replace (string s, char a, char b) +{ + string res = s; + size_t i = res.size(); + while (i--) + if (res[i] == a) + res[i] = b; + return res; +} + namespace trade { static bool goods_selected (const std::vector &selected) { @@ -89,6 +101,70 @@ namespace trade { } } +namespace conf_lua { + static color_ostream_proxy *out; + static lua_State *l_state; + bool init (color_ostream &dfout) + { + out = new color_ostream_proxy(Core::getInstance().getConsole()); + l_state = Lua::Open(*out); + return l_state; + } + void cleanup() + { + if (out) + { + delete out; + out = NULL; + } + lua_close(l_state); + } + bool call (const char *func, int nargs = 0, int nres = 0) + { + if (!Lua::PushModulePublic(*out, l_state, "plugins.confirm", func)) + return false; + if (nargs > 0) + lua_insert(l_state, lua_gettop(l_state) - nargs); + return Lua::SafeCall(*out, l_state, nargs, nres); + } + template + void push (T val) + { + Lua::Push(l_state, val); + } + template + void table_set (lua_State *L, KeyType k, ValueType v) + { + Lua::Push(L, k); + Lua::Push(L, v); + lua_settable(L, -3); + } + namespace api { + int get_ids (lua_State *L) + { + lua_newtable(L); + for (auto it = confirmations.begin(); it != confirmations.end(); ++it) + table_set(L, it->first, true); + return 1; + } + } +} + +#define CONF_LUA_FUNC(ns, name) {#name, df::wrap_function(ns::name, true)} +DFHACK_PLUGIN_LUA_FUNCTIONS { + CONF_LUA_FUNC(trade, broker_goods_selected), + CONF_LUA_FUNC(trade, broker_goods_all_selected), + CONF_LUA_FUNC(trade, trader_goods_selected), + CONF_LUA_FUNC(trade, trader_goods_all_selected), + DFHACK_LUA_END +}; + +#define CONF_LUA_CMD(name) {#name, conf_lua::api::name} +DFHACK_PLUGIN_LUA_COMMANDS { + CONF_LUA_CMD(get_ids), + DFHACK_LUA_END +}; + template class confirmation { public: @@ -184,11 +260,42 @@ public: state = INACTIVE; } } - virtual bool intercept_key (df::interface_key key) = 0; virtual string get_id() = 0; - virtual string get_title() { return "Confirm"; } - virtual string get_message() = 0; - virtual UIColor get_color() { return COLOR_YELLOW; } + #define CONF_LUA_START using namespace conf_lua; Lua::StackUnwinder unwind(l_state); push(screen); push(get_id()); + bool intercept_key (df::interface_key key) + { + CONF_LUA_START; + push(key); + if (call("intercept_key", 3, 1)) + return lua_toboolean(l_state, -1); + else + return false; + }; + string get_title() + { + CONF_LUA_START; + if (call("get_title", 2, 1) && lua_isstring(l_state, -1)) + return lua_tostring(l_state, -1); + else + return "Confirm"; + } + string get_message() + { + CONF_LUA_START; + if (call("get_message", 2, 1) && lua_isstring(l_state, -1)) + return lua_tostring(l_state, -1); + else + return ""; + }; + UIColor get_color() + { + CONF_LUA_START; + if (call("get_color", 2, 1) && lua_isnumber(l_state, -1)) + return lua_tointeger(l_state, -1) % 16; + else + return COLOR_YELLOW; + } + #undef CONF_LUA_START protected: cstate state; df::interface_key last_key; @@ -234,8 +341,7 @@ int conf_register(confirmation *c, ...) return 0; } -#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0) -#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \ +#define IMPLEMENT_CONFIRMATION_HOOKS(cls, prio) \ static cls cls##_instance; \ struct cls##_hooks : cls::screen_type { \ typedef cls::screen_type interpose_base; \ @@ -265,198 +371,33 @@ static int conf_register_##cls = conf_register(&cls##_instance, \ &INTERPOSE_HOOK(cls##_hooks, key_conflict), \ NULL); -class trade_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return key == df::interface_key::TRADE_TRADE; - } - virtual string get_id() { return "trade"; } - virtual string get_title() { return "Confirm trade"; } - virtual string get_message() - { - if (trade::trader_goods_selected(screen) && trade::broker_goods_selected(screen)) - return "Are you sure you want to trade the selected goods?"; - else if (trade::trader_goods_selected(screen)) - return "You are not giving any items. This is likely\n" - "to irritate the merchants.\n" - "Attempt to trade anyway?"; - else if (trade::broker_goods_selected(screen)) - return "You are not receiving any items. You may want to\n" - "offer these items instead or choose items to receive.\n" - "Attempt to trade anyway?"; - else - return "No items are selected. This is likely\n" - "to irritate the merchants.\n" - "Attempt to trade anyway?"; - } -}; -IMPLEMENT_CONFIRMATION_HOOKS(trade_confirmation); - -class trade_cancel_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return key == df::interface_key::LEAVESCREEN && - (trade::trader_goods_selected(screen) || trade::broker_goods_selected(screen)); - } - virtual string get_id() { return "trade-cancel"; } - virtual string get_title() { return "Cancel trade"; } - virtual string get_message() { return "Are you sure you want leave this screen?\nSelected items will not be saved."; } -}; -IMPLEMENT_CONFIRMATION_HOOKS_PRIO(trade_cancel_confirmation, -1); - -class trade_seize_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return trade::trader_goods_selected(screen) && key == df::interface_key::TRADE_SEIZE; - } - virtual string get_id() { return "trade-seize"; } - virtual string get_title() { return "Confirm seize"; } - virtual string get_message() { return "Are you sure you want to sieze these goods?"; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation); - -class trade_offer_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return trade::broker_goods_selected(screen) && key == df::interface_key::TRADE_OFFER; - } - virtual string get_id() { return "trade-offer"; } - virtual string get_title() { return "Confirm offer"; } - virtual string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation); - -class trade_select_all_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - if (key == df::interface_key::SEC_SELECT) - { - if (screen->in_right_pane && trade::broker_goods_selected(screen) && !trade::broker_goods_all_selected(screen)) - return true; - else if (!screen->in_right_pane && trade::trader_goods_selected(screen) && !trade::trader_goods_all_selected(screen)) - return true; - } - return false; - } - virtual string get_id() { return "trade-select-all"; } - virtual string get_title() { return "Confirm selection"; } - virtual string get_message() - { - return "Selecting all goods will overwrite your current selection\n" - "and cannot be undone. Continue?"; - } -}; -IMPLEMENT_CONFIRMATION_HOOKS(trade_select_all_confirmation); +#define DEFINE_CONFIRMATION(cls, screen, prio) \ + class confirmation_##cls : public confirmation { \ + virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \ + }; \ + IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, prio); -class hauling_route_delete_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - if (ui->main.mode == ui_sidebar_mode::Hauling && - ui->hauling.view_routes.size() && - !ui->hauling.in_name && - !ui->hauling.in_stop && - !ui->hauling.in_assign_vehicle - ) - return key == df::interface_key::D_HAULING_REMOVE; - return false; - } - virtual string get_id() { return "haul-delete"; } - virtual string get_title() { return "Confirm deletion"; } - virtual string get_message() - { - std::string type = (ui->hauling.view_stops[ui->hauling.cursor_top]) ? "stop" : "route"; - return std::string("Are you sure you want to delete this ") + type + "?"; - } -}; -IMPLEMENT_CONFIRMATION_HOOKS(hauling_route_delete_confirmation); - -class depot_remove_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - df::building_tradedepotst *depot = virtual_cast(Gui::getAnyBuilding(screen)); - if (depot && key == df::interface_key::DESTROYBUILDING) - { - for (auto it = ui->caravans.begin(); it != ui->caravans.end(); ++it) - { - if ((**it).time_remaining) - return true; - } - } - return false; - } - virtual string get_id() { return "depot-remove"; } - virtual string get_title() { return "Confirm depot removal"; } - virtual string get_message() - { - return "Are you sure you want to remove this depot?\n" - "Merchants are present and will lose profits."; - } -}; -IMPLEMENT_CONFIRMATION_HOOKS(depot_remove_confirmation); - -class squad_disband_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return screen->page == df::viewscreen_layer_militaryst::T_page::Positions && - screen->num_squads && - !screen->in_rename_alert && - key == df::interface_key::D_MILITARY_DISBAND_SQUAD; - } - virtual string get_id() { return "squad-disband"; } - virtual string get_title() { return "Disband squad"; } - virtual string get_message() { return "Are you sure you want to disband this squad?"; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(squad_disband_confirmation); - -class uniform_delete_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return screen->page == df::viewscreen_layer_militaryst::T_page::Uniforms && - !screen->equip.uniforms.empty() && - !screen->equip.in_name_uniform && - key == df::interface_key::D_MILITARY_DELETE_UNIFORM; - } - virtual string get_id() { return "uniform-delete"; } - virtual string get_title() { return "Delete uniform"; } - virtual string get_message() { return "Are you sure you want to delete this uniform?"; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(uniform_delete_confirmation); - -class note_delete_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return ui->main.mode == ui_sidebar_mode::NotesPoints && key == df::interface_key::D_NOTE_DELETE; - } - virtual string get_id() { return "note-delete"; } - virtual string get_title() { return "Delete note"; } - virtual string get_message() { return "Are you sure you want to delete this note?"; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(note_delete_confirmation); - -class route_delete_confirmation : public confirmation { -public: - virtual bool intercept_key (df::interface_key key) - { - return ui->main.mode == ui_sidebar_mode::NotesRoutes && key == df::interface_key::D_NOTE_ROUTE_DELETE; - } - virtual string get_id() { return "route-delete"; } - virtual string get_title() { return "Delete route"; } - virtual string get_message() { return "Are you sure you want to delete this route?"; } -}; -IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation); +/* This section defines stubs for all confirmation dialogs, with methods + implemented in plugins/lua/confirm.lua. + IDs (used in the "confirm enable/disable" command, by Lua, and in the docs) + are obtained by replacing '_' with '-' in the first argument to DEFINE_CONFIRMATION +*/ +DEFINE_CONFIRMATION(trade, viewscreen_tradegoodsst, 0); +DEFINE_CONFIRMATION(trade_cancel, viewscreen_tradegoodsst, -1); +DEFINE_CONFIRMATION(trade_seize, viewscreen_tradegoodsst, 0); +DEFINE_CONFIRMATION(trade_offer, viewscreen_tradegoodsst, 0); +DEFINE_CONFIRMATION(trade_select_all, viewscreen_tradegoodsst, 0); +DEFINE_CONFIRMATION(haul_delete, viewscreen_dwarfmodest, 0); +DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest, 0); +DEFINE_CONFIRMATION(squad_disband, viewscreen_layer_militaryst, 0); +DEFINE_CONFIRMATION(uniform_delete, viewscreen_layer_militaryst, 0); +DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest, 0); +DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest, 0); DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { + if (!conf_lua::init(out)) + return CR_FAILURE; commands.push_back(PluginCommand( "confirm", "Confirmation dialogs", @@ -480,6 +421,12 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) } is_enabled = enable; } + if (is_enabled) + { + using namespace conf_lua; + Lua::StackUnwinder unwind(l_state); + call("check"); + } return CR_OK; } @@ -487,6 +434,7 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) { if (plugin_enable(out, false) != CR_OK) return CR_FAILURE; + conf_lua::cleanup(); return CR_OK; } @@ -509,13 +457,11 @@ command_result df_confirm (color_ostream &out, vector & parameters) { CoreSuspender suspend; bool state = true; - if (in_vector(parameters, "help") || in_vector(parameters, "status")) + if (parameters.empty() || in_vector(parameters, "help") || in_vector(parameters, "status")) { out << "Available options: \n"; for (auto it = confirmations.begin(); it != confirmations.end(); ++it) - { - out << " " << it->first << ": " << (it->second->is_enabled() ? "enabled" : "disabled") << std::endl; - } + out.print(" %20s: %s\n", it->first.c_str(), it->second->is_enabled() ? "enabled" : "disabled"); return CR_OK; } for (auto it = parameters.begin(); it != parameters.end(); ++it) @@ -527,9 +473,7 @@ command_result df_confirm (color_ostream &out, vector & parameters) else if (*it == "all") { for (auto it = confirmations.begin(); it != confirmations.end(); ++it) - { it->second->apply(state); - } } else enable_conf(out, *it, state); diff --git a/plugins/lua/confirm.lua b/plugins/lua/confirm.lua new file mode 100644 index 000000000..d60d52387 --- /dev/null +++ b/plugins/lua/confirm.lua @@ -0,0 +1,229 @@ +local _ENV = mkmodule('plugins.confirm') + +local ui = df.global.ui + +local confs = {} +-- Wraps df.interface_key[foo] functionality but fails with invalid keys +keys = {} +setmetatable(keys, { + __index = function(self, k) + return df.interface_key[k] and df.interface_key[k] or error('Invalid key: ' .. tostring(k)) + end, + __newindex = function() error('Table is read-only') end +}) +--[[ The screen where a confirmation has been triggered +Note that this is *not* necessarily the topmost viewscreen, so do not use +gui.getCurViewscreen() or related functions. ]] +screen = nil + +function if_nil(obj, default) + if obj == nil then + return default + else + return obj + end +end + +function defconf(id) + if not get_ids()[id] then + error('Bad confirmation ID (not defined in plugin): ' .. id) + end + local cls = {} + cls.intercept_key = function(key) return false end + cls.get_title = function() return if_nil(cls.title, '') end + cls.get_message = function() return if_nil(cls.message, '') end + cls.get_color = function() return if_nil(cls.color, COLOR_YELLOW) end + confs[id] = cls + return cls +end + +--[[ Beginning of confirmation definitions +All confirmations declared in confirm.cpp must have a corresponding call to +defconf() here, and should implement intercept_key(), get_title(), and +get_message(). get_color() can also be implemented here, but the default should +be sufficient. + +In cases where getter functions always return the same value (e.g. get_title()), +they can be replaced with a field named after the method without the "get_" +prefix: + + trade.title = "Confirm trade" + +is equivalent to: + + function trade.get_title() return "Confirm trade" end + +]] + +trade = defconf('trade') +function trade.intercept_key(key) + return screen.in_edit_count == 0 and + key == keys.TRADE_TRADE +end +trade.title = "Confirm trade" +function trade.get_message() + if trader_goods_selected(screen) and broker_goods_selected(screen) then + return "Are you sure you want to trade the selected goods?" + elseif trader_goods_selected(screen) then + return "You are not giving any items. This is likely\n" .. + "to irritate the merchants.\n" .. + "Attempt to trade anyway?" + elseif broker_goods_selected(screen) then + return "You are not receiving any items. You may want to\n" .. + "offer these items instead or choose items to receive.\n" .. + "Attempt to trade anyway?" + else + return "No items are selected. This is likely\n" .. + "to irritate the merchants.\n" .. + "Attempt to trade anyway?" + end +end + +trade_cancel = defconf('trade-cancel') +function trade_cancel.intercept_key(key) + return screen.in_edit_count == 0 and + key == keys.LEAVESCREEN and + (trader_goods_selected(screen) or broker_goods_selected(screen)) +end +trade_cancel.title = "Cancel trade" +trade_cancel.message = "Are you sure you want leave this screen?\nSelected items will not be saved." + +trade_seize = defconf('trade-seize') +function trade_seize.intercept_key(key) + return screen.in_edit_count == 0 and + trader_goods_selected(screen) and + key == keys.TRADE_SEIZE +end +trade_seize.title = "Confirm seize" +trade_seize.message = "Are you sure you want to sieze these goods?" + +trade_offer = defconf('trade-offer') +function trade_offer.intercept_key(key) + return screen.in_edit_count == 0 and + broker_goods_selected(screen) and + key == keys.TRADE_OFFER +end +trade_offer.title = "Confirm offer" +trade_offer.message = "Are you sure you want to offer these goods?\nYou will receive no payment." + +trade_select_all = defconf('trade-select-all') +function trade_select_all.intercept_key(key) + if screen.in_edit_count == 0 and key == keys.SEC_SELECT then + if screen.in_right_pane and broker_goods_selected(screen) and not broker_goods_all_selected(screen) then + return true + elseif not screen.in_right_pane and trader_goods_selected(screen) and not trader_goods_all_selected(screen) then + return true + end + end + return false +end +trade_select_all.title = "Confirm selection" +trade_select_all.message = "Selecting all goods will overwrite your current selection\n" .. + "and cannot be undone. Continue?" + +haul_delete = defconf('haul-delete') +function haul_delete.intercept_key(key) + if ui.main.mode == df.ui_sidebar_mode.Hauling and + #ui.hauling.view_routes > 0 and + not ui.hauling.in_name and + not ui.hauling.in_stop and + not ui.hauling.in_assign_vehicle then + return key == keys.D_HAULING_REMOVE + end + return false +end +haul_delete.title = "Confirm deletion" +function haul_delete.get_message() + local t = ui.hauling.view_stops[ui.hauling.cursor_top] and "stop" or "route" + return "Are you sure you want to delete this " .. + (ui.hauling.view_stops[ui.hauling.cursor_top] and "stop" or "route") .. "?" +end + +depot_remove = defconf('depot-remove') +function depot_remove.intercept_key(key) + if df.building_tradedepotst:is_instance(dfhack.gui.getSelectedBuilding(true)) and + key == keys.DESTROYBUILDING then + for _, caravan in pairs(ui.caravans) do + if caravan.time_remaining > 0 then + return true + end + end + end +end +depot_remove.title = "Confirm depot removal" +depot_remove.message = "Are you sure you want to remove this depot?\n" .. + "Merchants are present and will lose profits." + +squad_disband = defconf('squad-disband') +function squad_disband.intercept_key(key) + return key == keys.D_MILITARY_DISBAND_SQUAD and + screen.page == screen._type.T_page.Positions and + screen.num_squads > 0 and + not screen.in_rename_alert +end +squad_disband.title = "Disband squad" +squad_disband.message = "Are you sure you want to disband this squad?" + +uniform_delete = defconf('uniform-delete') +function uniform_delete.intercept_key(key) + return key == keys.D_MILITARY_DELETE_UNIFORM and + screen.page == screen._type.T_page.Uniforms and + #screen.equip.uniforms > 0 and + not screen.equip.in_name_uniform +end +uniform_delete.title = "Delete uniform" +uniform_delete.message = "Are you sure you want to delete this uniform?" + +note_delete = defconf('note-delete') +function note_delete.intercept_key(key) + return key == keys.D_NOTE_DELETE and + ui.main.mode == df.ui_sidebar_mode.NotesPoints and + not ui.waypoints.in_edit_text_mode +end +note_delete.title = "Delete note" +note_delete.message = "Are you sure you want to delete this note?" + +route_delete = defconf('route-delete') +function route_delete.intercept_key(key) + return key == keys.D_NOTE_ROUTE_DELETE and + ui.main.mode == df.ui_sidebar_mode.NotesRoutes and + not ui.waypoints.in_edit_name_mode +end +route_delete.title = "Delete route" +route_delete.message = "Are you sure you want to delete this route?" + +-- End of confirmation definitions + +function check() + local undefined = {} + for id in pairs(get_ids()) do + if not confs[id] then + table.insert(undefined, id) + end + end + if #undefined > 0 then + error('Confirmation definitions missing: ' .. table.concat(undefined, ', ')) + end +end + +--[[ +The C++ plugin invokes methods of individual confirmations through four +functions (corresponding to method names) which receive the relevant screen, +the confirmation ID, and extra arguments in some cases, but these don't have to +do aything unique. +]] + +function define_wrapper(name) + _ENV[name] = function(scr, id, ...) + _ENV.screen = scr + if not confs[id] then + error('Bad confirmation ID: ' .. id) + end + return confs[id][name](...) + end +end +define_wrapper('intercept_key') +define_wrapper('get_title') +define_wrapper('get_message') +define_wrapper('get_color') +return _ENV From af92b3ae1f8534da4d8a7455c63697493e66ece1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 21 Nov 2015 20:58:49 -0500 Subject: [PATCH 2/2] Fix some confirm plugin issues and add a simple configuration UI - Detect null pointers in trade-related functions - Fix typo/issues pointed out by @dscorbett - Reorder includes --- plugins/confirm.cpp | 167 +++++++++++++++++++++++++---------- plugins/lua/confirm.lua | 4 +- scripts/gui/confirm-opts.lua | 74 ++++++++++++++++ 3 files changed, 196 insertions(+), 49 deletions(-) create mode 100644 scripts/gui/confirm-opts.lua diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index dd951a85c..ec8ee2a73 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -1,15 +1,18 @@ -#include #include +#include +#include + #include "Console.h" #include "Core.h" #include "DataDefs.h" +#include "Error.h" #include "Export.h" -#include "PluginManager.h" -#include "VTableInterpose.h" #include "LuaTools.h" #include "LuaWrapper.h" -#include "uicommon.h" +#include "PluginManager.h" +#include "VTableInterpose.h" #include "modules/Gui.h" +#include "uicommon.h" #include "df/building_tradedepotst.h" #include "df/general_ref.h" @@ -32,7 +35,9 @@ typedef std::set ikey_set; command_result df_confirm (color_ostream &out, vector & parameters); struct conf_wrapper; -static std::map confirmations; +static std::map confirmations; +string active_id; +std::queue cmds; template inline bool in_vector (std::vector &vec, FT item) @@ -50,6 +55,35 @@ string char_replace (string s, char a, char b) return res; } +bool set_conf_state (string name, bool state); + +struct conf_wrapper { +private: + bool enabled; + std::set hooks; +public: + conf_wrapper() + :enabled(false) + {} + void add_hook(VMethodInterposeLinkBase *hook) + { + if (!hooks.count(hook)) + hooks.insert(hook); + } + bool apply (bool state) { + if (state == enabled) + return true; + for (auto h = hooks.begin(); h != hooks.end(); ++h) + { + if (!(**h).apply(state)) + return false; + } + enabled = state; + return true; + } + inline bool is_enabled() { return enabled; } +}; + namespace trade { static bool goods_selected (const std::vector &selected) { @@ -60,10 +94,12 @@ namespace trade { } inline bool trader_goods_selected (df::viewscreen_tradegoodsst *screen) { + CHECK_NULL_POINTER(screen); return goods_selected(screen->trader_selected); } inline bool broker_goods_selected (df::viewscreen_tradegoodsst *screen) { + CHECK_NULL_POINTER(screen); return goods_selected(screen->broker_selected); } @@ -93,10 +129,12 @@ namespace trade { } inline bool trader_goods_all_selected(df::viewscreen_tradegoodsst *screen) { + CHECK_NULL_POINTER(screen); return goods_all_selected(screen->trader_selected, screen->trader_items); } inline bool broker_goods_all_selected(df::viewscreen_tradegoodsst *screen) { + CHECK_NULL_POINTER(screen); return goods_all_selected(screen->broker_selected, screen->broker_items); } } @@ -127,6 +165,11 @@ namespace conf_lua { lua_insert(l_state, lua_gettop(l_state) - nargs); return Lua::SafeCall(*out, l_state, nargs, nres); } + bool simple_call (const char *func) + { + Lua::StackUnwinder top(l_state); + return call(func, 0, 0); + } template void push (T val) { @@ -147,11 +190,34 @@ namespace conf_lua { table_set(L, it->first, true); return 1; } + int get_conf_data (lua_State *L) + { + lua_newtable(L); + int i = 1; + for (auto it = confirmations.begin(); it != confirmations.end(); ++it) + { + Lua::Push(L, i++); + lua_newtable(L); + table_set(L, "id", it->first); + table_set(L, "enabled", it->second->is_enabled()); + lua_settable(L, -3); + } + return 1; + } + int get_active_id (lua_State *L) + { + if (active_id.size()) + Lua::Push(L, active_id); + else + lua_pushnil(L); + return 1; + } } } #define CONF_LUA_FUNC(ns, name) {#name, df::wrap_function(ns::name, true)} DFHACK_PLUGIN_LUA_FUNCTIONS { + CONF_LUA_FUNC( , set_conf_state), CONF_LUA_FUNC(trade, broker_goods_selected), CONF_LUA_FUNC(trade, broker_goods_all_selected), CONF_LUA_FUNC(trade, trader_goods_selected), @@ -162,15 +228,30 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { #define CONF_LUA_CMD(name) {#name, conf_lua::api::name} DFHACK_PLUGIN_LUA_COMMANDS { CONF_LUA_CMD(get_ids), + CONF_LUA_CMD(get_conf_data), + CONF_LUA_CMD(get_active_id), DFHACK_LUA_END }; +void show_options() +{ + cmds.push("gui/confirm-opts"); +} + template class confirmation { public: enum cstate { INACTIVE, ACTIVE, SELECTED }; typedef T screen_type; screen_type *screen; + void set_state (cstate s) + { + state = s; + if (s == INACTIVE) + active_id = ""; + else + active_id = get_id(); + } bool feed (ikey_set *input) { if (state == INACTIVE) { @@ -179,7 +260,7 @@ public: if (intercept_key(*it)) { last_key = *it; - state = ACTIVE; + set_state(ACTIVE); return true; } } @@ -188,9 +269,11 @@ public: else if (state == ACTIVE) { if (input->count(df::interface_key::LEAVESCREEN)) - state = INACTIVE; + set_state(INACTIVE); else if (input->count(df::interface_key::SELECT)) - state = SELECTED; + set_state(SELECTED); + else if (input->count(df::interface_key::CUSTOM_S)) + show_options(); return true; } return false; @@ -212,7 +295,7 @@ public: if (state == ACTIVE) { split_string(&lines, get_message(), "\n"); - size_t max_length = 30; + size_t max_length = 40; for (auto it = lines.begin(); it != lines.end(); ++it) max_length = std::max(max_length, it->size()); int width = max_length + 4; @@ -241,11 +324,15 @@ public: Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), (gps->dimx / 2) - (title.size() / 2), y1, title); int x = x1 + 2; - OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); - OutputString(COLOR_WHITE, x, y2, ": Cancel"); + int y = y2; + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); + OutputString(COLOR_WHITE, x, y, ": Cancel"); + x = (gps->dimx - (Screen::getKeyDisplay(df::interface_key::CUSTOM_S) + ": Settings").size()) / 2 + 1; + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_S)); + OutputString(COLOR_WHITE, x, y, ": Settings"); x = x2 - 2 - 3 - Screen::getKeyDisplay(df::interface_key::SELECT).size(); - OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::SELECT)); - OutputString(COLOR_WHITE, x, y2, ": Ok"); + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT)); + OutputString(COLOR_WHITE, x, y, ": Ok"); Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), x1 + 1, y1 + 1, x2 - 1, y2 - 1); for (size_t i = 0; i < lines.size(); i++) { @@ -257,7 +344,7 @@ public: ikey_set tmp; tmp.insert(last_key); screen->feed(&tmp); - state = INACTIVE; + set_state(INACTIVE); } } virtual string get_id() = 0; @@ -301,37 +388,10 @@ protected: df::interface_key last_key; }; -struct conf_wrapper { -private: - bool enabled; - std::set hooks; -public: - conf_wrapper() - :enabled(false) - {} - void add_hook(VMethodInterposeLinkBase *hook) - { - if (!hooks.count(hook)) - hooks.insert(hook); - } - bool apply (bool state) { - if (state == enabled) - return true; - for (auto h = hooks.begin(); h != hooks.end(); ++h) - { - if (!(**h).apply(state)) - return false; - } - enabled = state; - return true; - } - inline bool is_enabled() { return enabled; } -}; - template int conf_register(confirmation *c, ...) { - conf_wrapper *w = new conf_wrapper; + conf_wrapper *w = new conf_wrapper(); confirmations[c->get_id()] = w; va_list args; va_start(args, c); @@ -423,9 +483,7 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) } if (is_enabled) { - using namespace conf_lua; - Lua::StackUnwinder unwind(l_state); - call("check"); + conf_lua::simple_call("check"); } return CR_OK; } @@ -438,7 +496,17 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) return CR_OK; } -void enable_conf (color_ostream &out, string name, bool state) +DFhackCExport command_result plugin_onupdate (color_ostream &out) +{ + while (!cmds.empty()) + { + Core::getInstance().runCommand(out, cmds.front()); + cmds.pop(); + } + return CR_OK; +} + +bool set_conf_state (string name, bool state) { bool found = false; for (auto it = confirmations.begin(); it != confirmations.end(); ++it) @@ -449,7 +517,12 @@ void enable_conf (color_ostream &out, string name, bool state) it->second->apply(state); } } - if (!found) + return found; +} + +void enable_conf (color_ostream &out, string name, bool state) +{ + if (!set_conf_state(name, state)) out.printerr("Unrecognized option: %s\n", name.c_str()); } diff --git a/plugins/lua/confirm.lua b/plugins/lua/confirm.lua index d60d52387..751b1b4da 100644 --- a/plugins/lua/confirm.lua +++ b/plugins/lua/confirm.lua @@ -7,7 +7,7 @@ local confs = {} keys = {} setmetatable(keys, { __index = function(self, k) - return df.interface_key[k] and df.interface_key[k] or error('Invalid key: ' .. tostring(k)) + return df.interface_key[k] or error('Invalid key: ' .. tostring(k)) end, __newindex = function() error('Table is read-only') end }) @@ -95,7 +95,7 @@ function trade_seize.intercept_key(key) key == keys.TRADE_SEIZE end trade_seize.title = "Confirm seize" -trade_seize.message = "Are you sure you want to sieze these goods?" +trade_seize.message = "Are you sure you want to seize these goods?" trade_offer = defconf('trade-offer') function trade_offer.intercept_key(key) diff --git a/scripts/gui/confirm-opts.lua b/scripts/gui/confirm-opts.lua new file mode 100644 index 000000000..d2aba2a94 --- /dev/null +++ b/scripts/gui/confirm-opts.lua @@ -0,0 +1,74 @@ +-- confirm plugin options +--[[=begin + +gui/confirm-opts +================ +A basic configuration interface for the `confirm` plugin. + +=end]] + + +confirm = require 'plugins.confirm' +gui = require 'gui' + +Opts = defclass(Opts, gui.FramedScreen) +Opts.ATTRS = { + frame_style = gui.GREY_LINE_FRAME, + frame_title = 'Confirmation dialogs', + frame_width = 32, + frame_height = 20, + frame_inset = 1, + focus_path = 'confirm/opts', +} + +function Opts:init() + self:refresh() + self.cursor = 1 + local active_id = confirm.get_active_id() + for i, c in pairs(self.data) do + if c.id == active_id then + self.cursor = i + break + end + end +end + +function Opts:refresh() + self.data = confirm.get_conf_data() + self.frame_height = #self.data +end + +function Opts:onRenderBody(p) + for i, c in pairs(self.data) do + local highlight = (i == self.cursor and 8 or 0) + p:pen(COLOR_GREY + highlight) + p:string(c.id .. ': ') + p:pen((c.enabled and COLOR_GREEN or COLOR_RED) + highlight) + p:string(c.enabled and 'Enabled' or 'Disabled') + p:newline() + end +end + +function Opts:onInput(keys) + local conf = self.data[self.cursor] + if keys.LEAVESCREEN then + self:dismiss() + elseif keys.SELECT then + confirm.set_conf_state(conf.id, not conf.enabled) + self:refresh() + elseif keys.SEC_SELECT then + for _, c in pairs(self.data) do + confirm.set_conf_state(c.id, not conf.enabled) + end + self:refresh() + elseif keys.STANDARDSCROLL_UP or keys.STANDARDSCROLL_DOWN then + self.cursor = self.cursor + (keys.STANDARDSCROLL_UP and -1 or 1) + if self.cursor < 1 then + self.cursor = #self.data + elseif self.cursor > #self.data then + self.cursor = 1 + end + end +end + +Opts():show()