Merge remote-tracking branch 'lethosor/confirm-lua' into develop

develop
lethosor 2015-11-25 20:41:07 -05:00
commit ff677d12ba
4 changed files with 563 additions and 243 deletions

@ -108,7 +108,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(cleanowned cleanowned.cpp) DFHACK_PLUGIN(cleanowned cleanowned.cpp)
DFHACK_PLUGIN(colonies colonies.cpp) DFHACK_PLUGIN(colonies colonies.cpp)
DFHACK_PLUGIN(command-prompt command-prompt.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(createitem createitem.cpp)
DFHACK_PLUGIN(cursecheck cursecheck.cpp) DFHACK_PLUGIN(cursecheck cursecheck.cpp)
DFHACK_PLUGIN(deramp deramp.cpp) DFHACK_PLUGIN(deramp deramp.cpp)

@ -1,13 +1,18 @@
#include <set>
#include <map> #include <map>
#include <set>
#include <queue>
#include "Console.h" #include "Console.h"
#include "Core.h" #include "Core.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "Error.h"
#include "Export.h" #include "Export.h"
#include "LuaTools.h"
#include "LuaWrapper.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "VTableInterpose.h" #include "VTableInterpose.h"
#include "uicommon.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "uicommon.h"
#include "df/building_tradedepotst.h" #include "df/building_tradedepotst.h"
#include "df/general_ref.h" #include "df/general_ref.h"
@ -30,7 +35,9 @@ typedef std::set<df::interface_key> ikey_set;
command_result df_confirm (color_ostream &out, vector <string> & parameters); command_result df_confirm (color_ostream &out, vector <string> & parameters);
struct conf_wrapper; struct conf_wrapper;
static std::map<std::string, conf_wrapper*> confirmations; static std::map<string, conf_wrapper*> confirmations;
string active_id;
std::queue<string> cmds;
template <typename VT, typename FT> template <typename VT, typename FT>
inline bool in_vector (std::vector<VT> &vec, FT item) inline bool in_vector (std::vector<VT> &vec, FT item)
@ -38,6 +45,45 @@ inline bool in_vector (std::vector<VT> &vec, FT item)
return std::find(vec.begin(), vec.end(), item) != vec.end(); 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;
}
bool set_conf_state (string name, bool state);
struct conf_wrapper {
private:
bool enabled;
std::set<VMethodInterposeLinkBase*> 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 { namespace trade {
static bool goods_selected (const std::vector<char> &selected) static bool goods_selected (const std::vector<char> &selected)
{ {
@ -48,10 +94,12 @@ namespace trade {
} }
inline bool trader_goods_selected (df::viewscreen_tradegoodsst *screen) inline bool trader_goods_selected (df::viewscreen_tradegoodsst *screen)
{ {
CHECK_NULL_POINTER(screen);
return goods_selected(screen->trader_selected); return goods_selected(screen->trader_selected);
} }
inline bool broker_goods_selected (df::viewscreen_tradegoodsst *screen) inline bool broker_goods_selected (df::viewscreen_tradegoodsst *screen)
{ {
CHECK_NULL_POINTER(screen);
return goods_selected(screen->broker_selected); return goods_selected(screen->broker_selected);
} }
@ -81,20 +129,129 @@ namespace trade {
} }
inline bool trader_goods_all_selected(df::viewscreen_tradegoodsst *screen) inline bool trader_goods_all_selected(df::viewscreen_tradegoodsst *screen)
{ {
CHECK_NULL_POINTER(screen);
return goods_all_selected(screen->trader_selected, screen->trader_items); return goods_all_selected(screen->trader_selected, screen->trader_items);
} }
inline bool broker_goods_all_selected(df::viewscreen_tradegoodsst *screen) inline bool broker_goods_all_selected(df::viewscreen_tradegoodsst *screen)
{ {
CHECK_NULL_POINTER(screen);
return goods_all_selected(screen->broker_selected, screen->broker_items); return goods_all_selected(screen->broker_selected, screen->broker_items);
} }
} }
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);
}
bool simple_call (const char *func)
{
Lua::StackUnwinder top(l_state);
return call(func, 0, 0);
}
template <typename T>
void push (T val)
{
Lua::Push(l_state, val);
}
template <typename KeyType, typename ValueType>
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;
}
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),
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),
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 T> template <class T>
class confirmation { class confirmation {
public: public:
enum cstate { INACTIVE, ACTIVE, SELECTED }; enum cstate { INACTIVE, ACTIVE, SELECTED };
typedef T screen_type; typedef T screen_type;
screen_type *screen; 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) { bool feed (ikey_set *input) {
if (state == INACTIVE) if (state == INACTIVE)
{ {
@ -103,7 +260,7 @@ public:
if (intercept_key(*it)) if (intercept_key(*it))
{ {
last_key = *it; last_key = *it;
state = ACTIVE; set_state(ACTIVE);
return true; return true;
} }
} }
@ -112,9 +269,11 @@ public:
else if (state == ACTIVE) else if (state == ACTIVE)
{ {
if (input->count(df::interface_key::LEAVESCREEN)) if (input->count(df::interface_key::LEAVESCREEN))
state = INACTIVE; set_state(INACTIVE);
else if (input->count(df::interface_key::SELECT)) 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 true;
} }
return false; return false;
@ -136,7 +295,7 @@ public:
if (state == ACTIVE) if (state == ACTIVE)
{ {
split_string(&lines, get_message(), "\n"); 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) for (auto it = lines.begin(); it != lines.end(); ++it)
max_length = std::max(max_length, it->size()); max_length = std::max(max_length, it->size());
int width = max_length + 4; int width = max_length + 4;
@ -165,11 +324,15 @@ public:
Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY),
(gps->dimx / 2) - (title.size() / 2), y1, title); (gps->dimx / 2) - (title.size() / 2), y1, title);
int x = x1 + 2; int x = x1 + 2;
OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); int y = y2;
OutputString(COLOR_WHITE, x, y2, ": Cancel"); 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(); x = x2 - 2 - 3 - Screen::getKeyDisplay(df::interface_key::SELECT).size();
OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::SELECT)); OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT));
OutputString(COLOR_WHITE, x, y2, ": Ok"); OutputString(COLOR_WHITE, x, y, ": Ok");
Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), x1 + 1, y1 + 1, x2 - 1, y2 - 1); 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++) for (size_t i = 0; i < lines.size(); i++)
{ {
@ -181,50 +344,54 @@ public:
ikey_set tmp; ikey_set tmp;
tmp.insert(last_key); tmp.insert(last_key);
screen->feed(&tmp); screen->feed(&tmp);
state = INACTIVE; set_state(INACTIVE);
} }
} }
virtual bool intercept_key (df::interface_key key) = 0;
virtual string get_id() = 0; virtual string get_id() = 0;
virtual string get_title() { return "Confirm"; } #define CONF_LUA_START using namespace conf_lua; Lua::StackUnwinder unwind(l_state); push(screen); push(get_id());
virtual string get_message() = 0; bool intercept_key (df::interface_key key)
virtual UIColor get_color() { return COLOR_YELLOW; } {
protected: CONF_LUA_START;
cstate state; push(key);
df::interface_key last_key; if (call("intercept_key", 3, 1))
return lua_toboolean(l_state, -1);
else
return false;
}; };
string get_title()
struct conf_wrapper {
private:
bool enabled;
std::set<VMethodInterposeLinkBase*> hooks;
public:
conf_wrapper()
:enabled(false)
{}
void add_hook(VMethodInterposeLinkBase *hook)
{ {
if (!hooks.count(hook)) CONF_LUA_START;
hooks.insert(hook); if (call("get_title", 2, 1) && lua_isstring(l_state, -1))
return lua_tostring(l_state, -1);
else
return "Confirm";
} }
bool apply (bool state) { string get_message()
if (state == enabled)
return true;
for (auto h = hooks.begin(); h != hooks.end(); ++h)
{ {
if (!(**h).apply(state)) CONF_LUA_START;
return false; if (call("get_message", 2, 1) && lua_isstring(l_state, -1))
} return lua_tostring(l_state, -1);
enabled = state; else
return true; return "<Message generation failed>";
};
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;
} }
inline bool is_enabled() { return enabled; } #undef CONF_LUA_START
protected:
cstate state;
df::interface_key last_key;
}; };
template<typename T> template<typename T>
int conf_register(confirmation<T> *c, ...) int conf_register(confirmation<T> *c, ...)
{ {
conf_wrapper *w = new conf_wrapper; conf_wrapper *w = new conf_wrapper();
confirmations[c->get_id()] = w; confirmations[c->get_id()] = w;
va_list args; va_list args;
va_start(args, c); va_start(args, c);
@ -234,8 +401,7 @@ int conf_register(confirmation<T> *c, ...)
return 0; return 0;
} }
#define IMPLEMENT_CONFIRMATION_HOOKS(cls) IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, 0) #define IMPLEMENT_CONFIRMATION_HOOKS(cls, prio) \
#define IMPLEMENT_CONFIRMATION_HOOKS_PRIO(cls, prio) \
static cls cls##_instance; \ static cls cls##_instance; \
struct cls##_hooks : cls::screen_type { \ struct cls##_hooks : cls::screen_type { \
typedef cls::screen_type interpose_base; \ typedef cls::screen_type interpose_base; \
@ -265,198 +431,33 @@ static int conf_register_##cls = conf_register(&cls##_instance, \
&INTERPOSE_HOOK(cls##_hooks, key_conflict), \ &INTERPOSE_HOOK(cls##_hooks, key_conflict), \
NULL); NULL);
class trade_confirmation : public confirmation<df::viewscreen_tradegoodsst> { #define DEFINE_CONFIRMATION(cls, screen, prio) \
public: class confirmation_##cls : public confirmation<df::screen> { \
virtual bool intercept_key (df::interface_key key) virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \
{ }; \
return key == df::interface_key::TRADE_TRADE; IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, prio);
}
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<df::viewscreen_tradegoodsst> {
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<df::viewscreen_tradegoodsst> {
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<df::viewscreen_tradegoodsst> {
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<df::viewscreen_tradegoodsst> {
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);
class hauling_route_delete_confirmation : public confirmation<df::viewscreen_dwarfmodest> {
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<df::viewscreen_dwarfmodest> {
public:
virtual bool intercept_key (df::interface_key key)
{
df::building_tradedepotst *depot = virtual_cast<df::building_tradedepotst>(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<df::viewscreen_layer_militaryst> {
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<df::viewscreen_layer_militaryst> {
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<df::viewscreen_dwarfmodest> {
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<df::viewscreen_dwarfmodest> { /* This section defines stubs for all confirmation dialogs, with methods
public: implemented in plugins/lua/confirm.lua.
virtual bool intercept_key (df::interface_key key) 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
return ui->main.mode == ui_sidebar_mode::NotesRoutes && key == df::interface_key::D_NOTE_ROUTE_DELETE; */
} DEFINE_CONFIRMATION(trade, viewscreen_tradegoodsst, 0);
virtual string get_id() { return "route-delete"; } DEFINE_CONFIRMATION(trade_cancel, viewscreen_tradegoodsst, -1);
virtual string get_title() { return "Delete route"; } DEFINE_CONFIRMATION(trade_seize, viewscreen_tradegoodsst, 0);
virtual string get_message() { return "Are you sure you want to delete this route?"; } DEFINE_CONFIRMATION(trade_offer, viewscreen_tradegoodsst, 0);
}; DEFINE_CONFIRMATION(trade_select_all, viewscreen_tradegoodsst, 0);
IMPLEMENT_CONFIRMATION_HOOKS(route_delete_confirmation); 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 <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, vector <PluginCommand> &commands)
{ {
if (!conf_lua::init(out))
return CR_FAILURE;
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"confirm", "confirm",
"Confirmation dialogs", "Confirmation dialogs",
@ -480,6 +481,10 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
} }
is_enabled = enable; is_enabled = enable;
} }
if (is_enabled)
{
conf_lua::simple_call("check");
}
return CR_OK; return CR_OK;
} }
@ -487,10 +492,21 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out)
{ {
if (plugin_enable(out, false) != CR_OK) if (plugin_enable(out, false) != CR_OK)
return CR_FAILURE; return CR_FAILURE;
conf_lua::cleanup();
return CR_OK; 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; bool found = false;
for (auto it = confirmations.begin(); it != confirmations.end(); ++it) for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
@ -501,7 +517,12 @@ void enable_conf (color_ostream &out, string name, bool state)
it->second->apply(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()); out.printerr("Unrecognized option: %s\n", name.c_str());
} }
@ -509,13 +530,11 @@ command_result df_confirm (color_ostream &out, vector <string> & parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;
bool state = true; 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"; out << "Available options: \n";
for (auto it = confirmations.begin(); it != confirmations.end(); ++it) for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
{ out.print(" %20s: %s\n", it->first.c_str(), it->second->is_enabled() ? "enabled" : "disabled");
out << " " << it->first << ": " << (it->second->is_enabled() ? "enabled" : "disabled") << std::endl;
}
return CR_OK; return CR_OK;
} }
for (auto it = parameters.begin(); it != parameters.end(); ++it) for (auto it = parameters.begin(); it != parameters.end(); ++it)
@ -527,10 +546,8 @@ command_result df_confirm (color_ostream &out, vector <string> & parameters)
else if (*it == "all") else if (*it == "all")
{ {
for (auto it = confirmations.begin(); it != confirmations.end(); ++it) for (auto it = confirmations.begin(); it != confirmations.end(); ++it)
{
it->second->apply(state); it->second->apply(state);
} }
}
else else
enable_conf(out, *it, state); enable_conf(out, *it, state);
} }

@ -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] 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, '<No title>') end
cls.get_message = function() return if_nil(cls.message, '<No 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 seize 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

@ -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()