Merge pull request #2721 from robob27/enable-confirm

Enable Confirm
develop
Myk 2023-02-05 16:19:31 -08:00 committed by GitHub
commit a7b5ce418e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 679 additions and 762 deletions

@ -97,8 +97,8 @@ enable automelt
#enable automaterial #enable automaterial
# Other interface improvement tools # Other interface improvement tools
#enable \ enable \
# confirm \ confirm
# dwarfmonitor \ # dwarfmonitor \
# mousequery \ # mousequery \
# autogems \ # autogems \

@ -950,10 +950,19 @@ Screens
Returns the topmost viewscreen. If ``skip_dismissed`` is *true*, Returns the topmost viewscreen. If ``skip_dismissed`` is *true*,
ignores screens already marked to be removed. ignores screens already marked to be removed.
* ``dfhack.gui.getFocusString(viewscreen)`` * ``dfhack.gui.getFocusStrings(viewscreen)``
Returns a string representation of the current focus position Returns a table of string representations of the current UI focuses.
in the ui. The string has a "screen/foo/bar/baz..." format. The strings have a "screen/foo/bar/baz..." format e.g..::
[1] = "dwarfmode/Info/CREATURES/CITIZEN"
[2] = "dwardmode/Squads"
* ``dfhack.gui.matchFocusString(focus_string)``
Returns ``true`` if the given ``focus_string`` is found in the current
focus strings, or as a prefix to any of the focus strings, or ``false``
if no match is found. Matching is case insensitive.
* ``dfhack.gui.getCurFocus([skip_dismissed])`` * ``dfhack.gui.getCurFocus([skip_dismissed])``

@ -5,7 +5,7 @@ confirm
:summary: Adds confirmation dialogs for destructive actions. :summary: Adds confirmation dialogs for destructive actions.
:tags: untested fort interface :tags: untested fort interface
Now you can get the chance to avoid seizing goods from traders or deleting a Now you can get the chance to avoid accidentally disbanding a squad or deleting a
hauling route in case you hit the key accidentally. hauling route in case you hit the key accidentally.
Usage Usage

@ -947,8 +947,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
<< "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, `, or Enter)." << endl << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, `, or Enter)." << endl
<< "Context may be used to limit the scope of the binding, by" << endl << "Context may be used to limit the scope of the binding, by" << endl
<< "requiring the current context to have a certain prefix." << endl << "requiring the current context to have a certain prefix." << endl
<< "Current UI context is: " << "Current UI context is: " << endl
<< Gui::getFocusString(Core::getTopViewscreen()) << endl; << join_strings("\n", Gui::getCurFocus(true)) << endl;
} }
} }
else if (first == "alias") else if (first == "alias")
@ -2419,11 +2419,13 @@ bool Core::SelectHotkey(int sym, int modifiers)
binding.modifiers, modifiers); binding.modifiers, modifiers);
continue; continue;
} }
string focusString = Gui::getFocusString(screen); if (!binding.focus.empty()) {
if (!binding.focus.empty() && !prefix_matches(binding.focus, focusString)) { if (!Gui::matchFocusString(binding.focus)) {
DEBUG(keybinding).print("skipping keybinding due to focus string mismatch: '%s' !~ '%s'\n", std::vector<std::string> focusStrings = Gui::getCurFocus(true);
focusString.c_str(), binding.focus.c_str()); DEBUG(keybinding).print("skipping keybinding due to focus string mismatch: '%s' !~ '%s'\n",
continue; join_strings(", ", focusStrings).c_str(), binding.focus.c_str());
continue;
}
} }
if (!plug_mgr->CanInvokeHotkey(binding.command[0], screen)) { if (!plug_mgr->CanInvokeHotkey(binding.command[0], screen)) {
DEBUG(keybinding).print("skipping keybinding due to hotkey guard rejection (command: '%s')\n", DEBUG(keybinding).print("skipping keybinding due to hotkey guard rejection (command: '%s')\n",

@ -1471,13 +1471,12 @@ static int gui_getMousePos(lua_State *L)
static const LuaWrapper::FunctionReg dfhack_gui_module[] = { static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getCurViewscreen), WRAPM(Gui, getCurViewscreen),
WRAPM(Gui, getDFViewscreen), WRAPM(Gui, getDFViewscreen),
WRAPM(Gui, getFocusString),
WRAPM(Gui, getCurFocus),
WRAPM(Gui, getSelectedWorkshopJob), WRAPM(Gui, getSelectedWorkshopJob),
WRAPM(Gui, getSelectedJob), WRAPM(Gui, getSelectedJob),
WRAPM(Gui, getSelectedUnit), WRAPM(Gui, getSelectedUnit),
WRAPM(Gui, getSelectedItem), WRAPM(Gui, getSelectedItem),
WRAPM(Gui, getSelectedBuilding), WRAPM(Gui, getSelectedBuilding),
WRAPM(Gui, getSelectedStockpile),
WRAPM(Gui, getSelectedPlant), WRAPM(Gui, getSelectedPlant),
WRAPM(Gui, getAnyUnit), WRAPM(Gui, getAnyUnit),
WRAPM(Gui, getAnyItem), WRAPM(Gui, getAnyItem),
@ -1495,9 +1494,24 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, refreshSidebar), WRAPM(Gui, refreshSidebar),
WRAPM(Gui, inRenameBuilding), WRAPM(Gui, inRenameBuilding),
WRAPM(Gui, getDepthAt), WRAPM(Gui, getDepthAt),
WRAPM(Gui, matchFocusString),
{ NULL, NULL } { NULL, NULL }
}; };
static int gui_getFocusStrings(lua_State *state) {
df::viewscreen *r = Lua::GetDFObject<df::viewscreen>(state, 1);
std::vector<std::string> focusStrings = Gui::getFocusStrings(r);
Lua::PushVector(state, focusStrings);
return 1;
}
static int gui_getCurFocus(lua_State *state) {
bool skip_dismissed = lua_toboolean(state, 1);
std::vector<std::string> cur_focus = Gui::getCurFocus(skip_dismissed);
Lua::PushVector(state, cur_focus);
return 1;
}
static int gui_autoDFAnnouncement(lua_State *state) static int gui_autoDFAnnouncement(lua_State *state)
{ {
bool rv; bool rv;
@ -1623,6 +1637,8 @@ static const luaL_Reg dfhack_gui_funcs[] = {
{ "pauseRecenter", gui_pauseRecenter }, { "pauseRecenter", gui_pauseRecenter },
{ "revealInDwarfmodeMap", gui_revealInDwarfmodeMap }, { "revealInDwarfmodeMap", gui_revealInDwarfmodeMap },
{ "getMousePos", gui_getMousePos }, { "getMousePos", gui_getMousePos },
{ "getFocusStrings", gui_getFocusStrings },
{ "getCurFocus", gui_getCurFocus },
{ NULL, NULL } { NULL, NULL }
}; };

@ -36,6 +36,7 @@ distribution.
#include "df/plotinfost.h" #include "df/plotinfost.h"
#include "df/announcement_type.h" #include "df/announcement_type.h"
#include "df/announcement_flags.h" #include "df/announcement_flags.h"
#include "df/building_stockpilest.h"
#include "df/report_init.h" #include "df/report_init.h"
#include "df/report_zoom_type.h" #include "df/report_zoom_type.h"
#include "df/unit_report_type.h" #include "df/unit_report_type.h"
@ -65,7 +66,9 @@ namespace DFHack
*/ */
namespace Gui namespace Gui
{ {
DFHACK_EXPORT std::string getFocusString(df::viewscreen *top); DFHACK_EXPORT std::vector<std::string> getFocusStrings(df::viewscreen *top);
DFHACK_EXPORT bool matchFocusString(std::string focusString, bool prefixMatch = true);
// Full-screen item details view // Full-screen item details view
DFHACK_EXPORT bool item_details_hotkey(df::viewscreen *top); DFHACK_EXPORT bool item_details_hotkey(df::viewscreen *top);
@ -107,6 +110,10 @@ namespace DFHack
DFHACK_EXPORT df::building *getAnyBuilding(df::viewscreen *top); DFHACK_EXPORT df::building *getAnyBuilding(df::viewscreen *top);
DFHACK_EXPORT df::building *getSelectedBuilding(color_ostream &out, bool quiet = false); DFHACK_EXPORT df::building *getSelectedBuilding(color_ostream &out, bool quiet = false);
DFHACK_EXPORT bool any_stockpile_hotkey(df::viewscreen* top);
DFHACK_EXPORT df::building_stockpilest *getAnyStockpile(df::viewscreen* top);
DFHACK_EXPORT df::building_stockpilest *getSelectedStockpile(color_ostream& out, bool quiet = false);
// A plant is selected, e.g. via 'k' // A plant is selected, e.g. via 'k'
DFHACK_EXPORT bool any_plant_hotkey(df::viewscreen *top); DFHACK_EXPORT bool any_plant_hotkey(df::viewscreen *top);
DFHACK_EXPORT df::plant *getAnyPlant(df::viewscreen *top); DFHACK_EXPORT df::plant *getAnyPlant(df::viewscreen *top);
@ -191,8 +198,8 @@ namespace DFHack
return strict_virtual_cast<T>(getViewscreenByIdentity(T::_identity, n)); return strict_virtual_cast<T>(getViewscreenByIdentity(T::_identity, n));
} }
inline std::string getCurFocus(bool skip_dismissed = false) { inline std::vector<std::string> getCurFocus(bool skip_dismissed = false) {
return getFocusString(getCurViewscreen(skip_dismissed)); return getFocusStrings(getCurViewscreen(skip_dismissed));
} }
/// get the size of the window buffer /// get the size of the window buffer

@ -29,46 +29,6 @@ SIDEBAR_MODE_KEYS = {
[df.ui_sidebar_mode.ViewUnits]='D_VIEWUNIT', [df.ui_sidebar_mode.ViewUnits]='D_VIEWUNIT',
} }
-- Sends ESC keycodes until we get to dwarfmode/Default and then enters the
-- specified sidebar mode with the corresponding keycode. If we don't get to
-- Default after max_esc presses of ESC (default value is 10), we throw an
-- error. The target sidebar mode must be a member of SIDEBAR_MODE_KEYS
function enterSidebarMode(sidebar_mode, max_esc)
local navkey = SIDEBAR_MODE_KEYS[sidebar_mode]
if not navkey then
error(('Invalid or unsupported sidebar mode: %s (%s)')
:format(sidebar_mode, df.ui_sidebar_mode[sidebar_mode]))
end
local max_esc_num = tonumber(max_esc)
if max_esc and (not max_esc_num or max_esc_num <= 0) then
error(('max_esc must be a positive number: got %s')
:format(tostring(max_esc)))
end
local remaining_esc = max_esc_num or 10
local focus_string = ''
while remaining_esc > 0 do
local screen = dfhack.gui.getCurViewscreen(true)
focus_string = dfhack.gui.getFocusString(screen)
if df.global.plotinfo.main.mode == df.ui_sidebar_mode.Default and
focus_string == 'dwarfmode/Default' then
if #navkey > 0 then gui.simulateInput(screen, navkey) end
if navkey == 'D_DESIGNATE' then
-- if the z-level happens to be on the surface, the mode will be
-- set to DesignateChopTrees. we need an extra step to get to
-- DesignateMine
gui.simulateInput(dfhack.gui.getCurViewscreen(true),
'DESIGNATE_DIG')
end
return
end
gui.simulateInput(screen, 'LEAVESCREEN')
remaining_esc = remaining_esc - 1
end
error(('Unable to get into target sidebar mode (%s) from' ..
' current UI viewscreen (%s).'):format(
df.ui_sidebar_mode[sidebar_mode], focus_string))
end
function getPanelLayout() function getPanelLayout()
local dims = dfhack.gui.getDwarfmodeViewDims() local dims = dfhack.gui.getDwarfmodeViewDims()
return { return {

@ -130,236 +130,337 @@ static std::string getNameChunk(virtual_identity *id, int start, int end)
* Classifying focus context by means of a string path. * Classifying focus context by means of a string path.
*/ */
typedef void (*getFocusStringHandler)(std::string &str, df::viewscreen *screen); typedef void (*getFocusStringsHandler)(std::string &str, std::vector<std::string> &strList, df::viewscreen *screen);
static std::map<virtual_identity*, getFocusStringHandler> getFocusStringHandlers; static std::map<virtual_identity*, getFocusStringsHandler> getFocusStringsHandlers;
#define VIEWSCREEN(name) df::viewscreen_##name##st #define VIEWSCREEN(name) df::viewscreen_##name##st
#define DEFINE_GET_FOCUS_STRING_HANDLER(screen_type) \ #define DEFINE_GET_FOCUS_STRING_HANDLER(screen_type) \
static void getFocusString_##screen_type(std::string &focus, VIEWSCREEN(screen_type) *screen);\ static void getFocusStrings_##screen_type(std::string &baseFocus, std::vector<std::string> &focusStrings, VIEWSCREEN(screen_type) *screen);\
DFHACK_STATIC_ADD_TO_MAP(\ DFHACK_STATIC_ADD_TO_MAP(\
&getFocusStringHandlers, &VIEWSCREEN(screen_type)::_identity, \ &getFocusStringsHandlers, &VIEWSCREEN(screen_type)::_identity, \
(getFocusStringHandler)getFocusString_##screen_type \ (getFocusStringsHandler)getFocusStrings_##screen_type \
); \ ); \
static void getFocusString_##screen_type(std::string &focus, VIEWSCREEN(screen_type) *screen) static void getFocusStrings_##screen_type(std::string &baseFocus, std::vector<std::string> &focusStrings, VIEWSCREEN(screen_type) *screen)
DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
{ {
/* TODO: understand how this changes for v50 std::string newFocusString;
using namespace df::enums::ui_sidebar_mode;
using df::global::ui_workshop_in_add;
using df::global::ui_build_selector;
using df::global::ui_selected_unit;
using df::global::ui_look_list;
using df::global::ui_look_cursor;
using df::global::ui_building_item_cursor;
using df::global::ui_building_assign_type;
using df::global::ui_building_assign_is_marked;
using df::global::ui_building_assign_units;
using df::global::ui_building_assign_items;
using df::global::ui_building_in_assign;
focus += "/" + enum_item_key(plotinfo->main.mode);
switch (plotinfo->main.mode)
{
case QueryBuilding:
if (df::building *selected = world->selected_building)
{
if (!selected->jobs.empty() &&
selected->jobs[0]->job_type == job_type::DestroyBuilding)
{
focus += "/Destroying";
break;
}
focus += "/Some";
virtual_identity *id = virtual_identity::get(selected);
bool jobs = false;
if (id == &df::building_workshopst::_identity ||
id == &df::building_furnacest::_identity)
{
focus += "/Workshop";
jobs = true;
}
else if (id == &df::building_trapst::_identity)
{
auto trap = (df::building_trapst*)selected;
focus += "/" + enum_item_key(trap->trap_type);
if (trap->trap_type == trap_type::Lever)
jobs = true;
}
else if (ui_building_in_assign && *ui_building_in_assign &&
ui_building_assign_type && ui_building_assign_units &&
ui_building_assign_type->size() == ui_building_assign_units->size())
{
focus += "/Assign";
if (ui_building_item_cursor)
{
auto unit = vector_get(*ui_building_assign_units, *ui_building_item_cursor);
focus += unit ? "/Unit" : "/None";
}
}
else
focus += "/" + enum_item_key(selected->getType());
if (jobs)
{
if (ui_workshop_in_add && *ui_workshop_in_add)
focus += "/AddJob";
else if (!selected->jobs.empty())
focus += "/Job";
else
focus += "/Empty";
}
}
else
focus += "/None";
break;
case Build: if(game->main_interface.main_designation_selected != -1) {
if (ui_build_selector) newFocusString = baseFocus;
{ newFocusString += "/Designate/" + enum_item_key(game->main_interface.main_designation_selected);
// Not selecting, or no choices? focusStrings.push_back(newFocusString);
if (ui_build_selector->building_type < 0) }
focus += "/Type"; if (game->main_interface.info.open) {
else if (ui_build_selector->stage != 2) newFocusString = baseFocus;
{ newFocusString += "/Info";
if (ui_build_selector->stage != 1) newFocusString += "/" + enum_item_key(game->main_interface.info.current_mode);
focus += "/NoMaterials";
else
focus += "/Position";
focus += "/" + enum_item_key(ui_build_selector->building_type); switch(game->main_interface.info.current_mode) {
} case df::enums::info_interface_mode_type::CREATURES:
else newFocusString += "/" + enum_item_key(game->main_interface.info.creatures.current_mode);
{ break;
focus += "/Material"; case df::enums::info_interface_mode_type::BUILDINGS:
if (ui_build_selector->is_grouped) newFocusString += "/" + enum_item_key(game->main_interface.info.buildings.mode);
focus += "/Groups"; break;
else case df::enums::info_interface_mode_type::LABOR:
focus += "/Items"; newFocusString += "/" + enum_item_key(game->main_interface.info.labor.mode);
} break;
case df::enums::info_interface_mode_type::ARTIFACTS:
newFocusString += "/" + enum_item_key(game->main_interface.info.artifacts.mode);
break;
case df::enums::info_interface_mode_type::JUSTICE:
newFocusString += "/" + enum_item_key(game->main_interface.info.justice.current_mode);
break;
default:
break;
} }
break;
case ViewUnits: focusStrings.push_back(newFocusString);
if (ui_selected_unit) }
{ if (game->main_interface.view_sheets.open) {
if (vector_get(world->units.active, *ui_selected_unit)) newFocusString = baseFocus;
{ newFocusString += "/ViewSheets";
focus += "/Some"; newFocusString += "/" + enum_item_key(game->main_interface.view_sheets.active_sheet);
focusStrings.push_back(newFocusString);
}
using df::global::ui_unit_view_mode; if(game->main_interface.bottom_mode_selected != -1) {
newFocusString = baseFocus;
if (ui_unit_view_mode) switch(game->main_interface.bottom_mode_selected) {
focus += "/" + enum_item_key(ui_unit_view_mode->value); case df::enums::main_bottom_mode_type::STOCKPILE:
if (game->main_interface.stockpile.cur_bld) {
newFocusString += "/Some";
} }
else newFocusString += "/Stockpile";
focus += "/None"; break;
} case df::enums::main_bottom_mode_type::STOCKPILE_PAINT:
break; newFocusString += "/Stockpile/Paint";
break;
case LookAround: case df::enums::main_bottom_mode_type::HAULING:
if (ui_look_list && ui_look_cursor) newFocusString += "/Hauling";
{ break;
auto item = vector_get(ui_look_list->items, *ui_look_cursor); case df::enums::main_bottom_mode_type::ZONE:
if (item) newFocusString += "/Zone";
focus += "/" + enum_item_key(item->type); if (game->main_interface.civzone.cur_bld) {
else newFocusString += "/Some";
focus += "/None"; newFocusString += "/" + enum_item_key(game->main_interface.civzone.cur_bld->type);
}
break;
case BuildingItems:
if (VIRTUAL_CAST_VAR(selected, df::building_actual, world->selected_building))
{
if (selected->contained_items.empty())
focus += "/Some/Empty";
else
focus += "/Some/Item";
}
else
focus += "/None";
break;
case ZonesPenInfo:
if (ui_building_assign_type && ui_building_assign_units &&
ui_building_assign_is_marked && ui_building_assign_items &&
ui_building_assign_type->size() == ui_building_assign_units->size())
{
focus += "/Assign";
if (ui_building_item_cursor)
{
if (vector_get(*ui_building_assign_units, *ui_building_item_cursor))
focus += "/Unit";
else if (vector_get(*ui_building_assign_items, *ui_building_item_cursor))
focus += "/Vermin";
else
focus += "/None";
} }
} break;
break; case df::enums::main_bottom_mode_type::ZONE_PAINT:
newFocusString += "/Zone/Paint";
case Burrows:
if (plotinfo->burrows.in_confirm_delete)
focus += "/ConfirmDelete";
else if (plotinfo->burrows.in_add_units_mode)
focus += "/AddUnits";
else if (plotinfo->burrows.in_edit_name_mode)
focus += "/EditName";
else if (plotinfo->burrows.in_define_mode)
focus += "/Define";
else
focus += "/List";
break;
case Hauling:
if (plotinfo->hauling.in_assign_vehicle)
{
auto vehicle = vector_get(plotinfo->hauling.vehicles, plotinfo->hauling.cursor_vehicle);
focus += "/AssignVehicle/" + std::string(vehicle ? "Some" : "None");
}
else
{
int idx = plotinfo->hauling.cursor_top;
auto route = vector_get(plotinfo->hauling.view_routes, idx);
auto stop = vector_get(plotinfo->hauling.view_stops, idx);
std::string tag = stop ? "Stop" : (route ? "Route" : "None");
if (plotinfo->hauling.in_name)
focus += "/Rename/" + tag;
else if (plotinfo->hauling.in_stop)
{
int sidx = plotinfo->hauling.cursor_stop;
auto cond = vector_get(plotinfo->hauling.stop_conditions, sidx);
auto link = vector_get(plotinfo->hauling.stop_links, sidx);
focus += "/DefineStop";
if (cond) // TODO: figure out why enum_item_key doesn't work on this?
focus += "/Cond/" + enum_item_key(cond->mode); switch(game->main_interface.civzone.adding_new_type) {
else if (link) case df::enums::civzone_type::MeetingHall:
{ newFocusString += "/MeetingHall";
focus += "/Link/"; break;
if (link->mode.bits.give) focus += "Give"; case df::enums::civzone_type::Bedroom:
if (link->mode.bits.take) focus += "Take"; newFocusString += "/Bedroom";
} break;
else case df::enums::civzone_type::DiningHall:
focus += "/None"; newFocusString += "/DiningHall";
break;
case df::enums::civzone_type::Pen:
newFocusString += "/Pen";
break;
case df::enums::civzone_type::Pond:
newFocusString += "/Pond";
break;
case df::enums::civzone_type::WaterSource:
newFocusString += "/WaterSource";
break;
case df::enums::civzone_type::Dungeon:
newFocusString += "/Dungeon";
break;
case df::enums::civzone_type::FishingArea:
newFocusString += "/FishingArea";
break;
case df::enums::civzone_type::SandCollection:
newFocusString += "/SandCollection";
break;
case df::enums::civzone_type::Office:
newFocusString += "/Office";
break;
case df::enums::civzone_type::Dormitory:
newFocusString += "/Dormitory";
break;
case df::enums::civzone_type::Barracks:
newFocusString += "/Barracks";
break;
case df::enums::civzone_type::ArcheryRange:
newFocusString += "/ArcheryRange";
break;
case df::enums::civzone_type::Dump:
newFocusString += "/Dump";
break;
case df::enums::civzone_type::AnimalTraining:
newFocusString += "/AnimalTraining";
break;
case df::enums::civzone_type::Tomb:
newFocusString += "/Tomb";
break;
case df::enums::civzone_type::PlantGathering:
newFocusString += "/PlantGathering";
break;
case df::enums::civzone_type::ClayCollection:
newFocusString += "/ClayCollection";
break;
} }
else break;
focus += "/Select/" + tag; case df::enums::main_bottom_mode_type::BURROW:
newFocusString += "/Burrow";
break;
case df::enums::main_bottom_mode_type::BURROW_PAINT:
newFocusString += "/Burrow/Paint";
break;
case df::enums::main_bottom_mode_type::BUILDING:
newFocusString += "/Building";
break;
case df::enums::main_bottom_mode_type::BUILDING_PLACEMENT:
newFocusString += "/Building/Placement";
break;
case df::enums::main_bottom_mode_type::BUILDING_PICK_MATERIALS:
newFocusString += "/Building/PickMaterials";
break;
default:
break;
} }
break;
default: focusStrings.push_back(newFocusString);
break; }
if (game->main_interface.trade.open) {
newFocusString = baseFocus;
newFocusString += "/Trade";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.job_details.open) {
newFocusString = baseFocus;
newFocusString += "/JobDetails";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.assign_trade.open) {
newFocusString = baseFocus;
newFocusString += "/AssignTrade";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.diplomacy.open) {
newFocusString = baseFocus;
newFocusString += "/Diplomacy";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.petitions.open) {
newFocusString = baseFocus;
newFocusString += "/Petitions";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.stocks.open) {
newFocusString = baseFocus;
newFocusString += "/Stocks";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.assign_display_item.open) {
newFocusString = baseFocus;
newFocusString += "/AssignDisplayItem";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.name_creator.open) {
newFocusString = baseFocus;
newFocusString += "/NameCreator";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.image_creator.open) {
newFocusString = baseFocus;
newFocusString += "/ImageCreator";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.unit_selector.open) {
newFocusString = baseFocus;
newFocusString += "/UnitSelector";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.announcement_alert.open) {
newFocusString = baseFocus;
newFocusString += "/AnnouncementAlert";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.custom_symbol.open) {
newFocusString = baseFocus;
newFocusString += "/CustomSymbol";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.patrol_routes.open) {
newFocusString = baseFocus;
newFocusString += "/PatrolRoutes";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.squad_schedule.open) {
newFocusString = baseFocus;
newFocusString += "/SquadSchedule";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.squad_selector.open) {
newFocusString = baseFocus;
newFocusString += "/SquadSelector";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.burrow_selector.open) {
newFocusString = baseFocus;
newFocusString += "/BurrowSelector";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.location_selector.open) {
newFocusString = baseFocus;
newFocusString += "/LocationSelector";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.location_details.open) {
newFocusString = baseFocus;
newFocusString += "/LocationDetails";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.hauling_stop_conditions.open) {
newFocusString = baseFocus;
newFocusString += "/HaulingStopConditions";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.assign_vehicle.open) {
newFocusString = baseFocus;
newFocusString += "/AssignVehicle";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.stockpile_link.open) {
newFocusString = baseFocus;
newFocusString += "/StockpileLink";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.stockpile_tools.open) {
newFocusString = baseFocus;
newFocusString += "/StockpileTools";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.custom_stockpile.open) {
newFocusString = baseFocus;
newFocusString += "/CustomStockpile";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.create_squad.open) {
newFocusString = baseFocus;
newFocusString += "/CreateSquad";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.squad_supplies.open) {
newFocusString = baseFocus;
newFocusString += "/SquadSupplies";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.assign_uniform.open) {
newFocusString = baseFocus;
newFocusString += "/AssignUniform";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.create_work_order.open) {
newFocusString = baseFocus;
newFocusString += "/CreateWorkOrder";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.hotkey.open) {
newFocusString = baseFocus;
newFocusString += "/Hotkey";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.options.open) {
newFocusString = baseFocus;
newFocusString += "/Options";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.help.open) {
newFocusString = baseFocus;
newFocusString += "/Help";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.settings.open) {
newFocusString = baseFocus;
newFocusString += "/Settings";
focusStrings.push_back(newFocusString);
}
if (game->main_interface.squad_equipment.open) {
newFocusString = baseFocus;
newFocusString += "/SquadEquipment";
focusStrings.push_back(newFocusString);
}
// squads should be last because it's the only one not exclusive with the others? or something?
if (game->main_interface.squads.open) {
newFocusString = baseFocus;
newFocusString += "/Squads";
focusStrings.push_back(newFocusString);
}
if (!newFocusString.size()) {
focusStrings.push_back(baseFocus);
} }
*/
} }
/* TODO: understand how this changes for v50 /* TODO: understand how this changes for v50
@ -372,237 +473,44 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dungeonmode)
focus += "/" + enum_item_key(adventure->menu); focus += "/" + enum_item_key(adventure->menu);
} }
*/
DEFINE_GET_FOCUS_STRING_HANDLER(unitlist) bool Gui::matchFocusString(std::string focusString, bool prefixMatch) {
{ focusString = toLower(focusString);
focus += "/" + enum_item_key(screen->page); std::vector<std::string> currentFocusStrings = getFocusStrings(getCurViewscreen(true));
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_military)
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
auto list3 = getLayerList(screen, 2);
if (!list1 || !list2 || !list3) return;
focus += "/" + enum_item_key(screen->page);
int cur_list;
if (list1->active) cur_list = 0;
else if (list2->active) cur_list = 1;
else if (list3->active) cur_list = 2;
else return;
switch (screen->page)
{
case df::viewscreen_layer_militaryst::Positions:
{
static const char *lists[] = { "/Squads", "/Positions", "/Candidates" };
focus += lists[cur_list];
break;
}
case df::viewscreen_layer_militaryst::Equip:
{
focus += "/" + enum_item_key(screen->equip.mode);
switch (screen->equip.mode)
{
case df::viewscreen_layer_militaryst::T_equip::Customize:
{
if (screen->equip.edit_mode < 0)
focus += "/View";
else
focus += "/" + enum_item_key(screen->equip.edit_mode);
break;
}
case df::viewscreen_layer_militaryst::T_equip::Uniform:
break;
case df::viewscreen_layer_militaryst::T_equip::Priority:
{
if (screen->equip.prio_in_move >= 0)
focus += "/Move";
else
focus += "/View";
break;
}
}
static const char *lists[] = { "/Squads", "/Positions", "/Choices" };
focus += lists[cur_list];
break;
}
default:
break;
}
}
DEFINE_GET_FOCUS_STRING_HANDLER(workshop_profile)
{
typedef df::viewscreen_workshop_profilest::T_tab T_tab;
switch(screen->tab)
{
case T_tab::Workers:
focus += "/Unit";
break;
case T_tab::Orders:
focus += "/Orders";
break;
case T_tab::Restrictions:
focus += "/Restrictions";
break;
}
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_noblelist)
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
if (!list1 || !list2) return;
focus += "/" + enum_item_key(screen->mode);
}
DEFINE_GET_FOCUS_STRING_HANDLER(pet)
{
focus += "/" + enum_item_key(screen->mode);
switch (screen->mode)
{
case df::viewscreen_petst::List:
focus += vector_get(screen->is_vermin, screen->cursor) ? "/Vermin" : "/Unit";
break;
case df::viewscreen_petst::SelectTrainer:
if (vector_get(screen->trainer_unit, screen->trainer_cursor))
focus += "/Unit";
break;
default:
break;
}
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_overall_health)
{
auto list1 = getLayerList(screen, 0);
if (!list1) return;
focus += "/Units";
}
DEFINE_GET_FOCUS_STRING_HANDLER(tradegoods)
{
if (!screen->has_traders || screen->is_unloading)
focus += "/NoTraders";
else if (screen->in_edit_count)
focus += "/EditCount";
else
focus += (screen->in_right_pane ? "/Items/Broker" : "/Items/Trader");
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_assigntrade)
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
if (!list1 || !list2) return;
int list_idx = vector_get(screen->visible_lists, list1->cursor, (int16_t)-1);
unsigned num_lists = sizeof(screen->lists)/sizeof(screen->lists[0]);
if (unsigned(list_idx) >= num_lists)
return;
if (list1->active)
focus += "/Groups";
else
focus += "/Items";
}
DEFINE_GET_FOCUS_STRING_HANDLER(stores)
{
if (!screen->in_right_list)
focus += "/Categories";
else if (screen->in_group_mode)
focus += "/Groups";
else
focus += "/Items";
}
DEFINE_GET_FOCUS_STRING_HANDLER(layer_stockpile)
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
auto list3 = getLayerList(screen, 2);
if (!list1 || !list2 || !list3 || !screen->settings) return;
auto group = screen->cur_group;
if (group != vector_get(screen->group_ids, list1->cursor))
return;
focus += "/" + enum_item_key(group);
auto bits = vector_get(screen->group_bits, list1->cursor);
if (bits.whole && !(bits.whole & screen->settings->flags.whole))
{
focus += "/Off";
return;
}
focus += "/On";
if (list2->active || list3->active || screen->list_ids.empty()) {
focus += "/" + enum_item_key(screen->cur_list);
if (list3->active)
focus += (screen->item_names.empty() ? "/None" : "/Item");
}
}
DEFINE_GET_FOCUS_STRING_HANDLER(locations)
{
focus += "/" + enum_item_key(screen->menu);
}
DEFINE_GET_FOCUS_STRING_HANDLER(jobmanagement) return std::find_if(currentFocusStrings.begin(), currentFocusStrings.end(), [&focusString, &prefixMatch](std::string item) {
{ return prefixMatch ? prefix_matches(focusString, toLower(item)) : focusString == toLower(item);
focus += (screen->in_max_workshops ? "/MaxWorkshops" : "/Main"); }) != currentFocusStrings.end();
} }
DEFINE_GET_FOCUS_STRING_HANDLER(workquota_condition) std::vector<std::string> Gui::getFocusStrings(df::viewscreen* top)
{ {
focus += "/" + enum_item_key(screen->mode); std::vector<std::string> focusStrings;
if (screen->item_count_edit)
focus += "/EditCount";
}
*/
std::string Gui::getFocusString(df::viewscreen *top)
{
if (!top) if (!top)
return ""; return focusStrings;
if (dfhack_viewscreen::is_instance(top)) if (dfhack_viewscreen::is_instance(top))
{ {
auto name = static_cast<dfhack_viewscreen*>(top)->getFocusString(); auto name = static_cast<dfhack_viewscreen*>(top)->getFocusString();
return name.empty() ? "dfhack" : "dfhack/"+name; focusStrings.push_back(name.empty() ? "dfhack" : "dfhack/" + name);
} }
else if (virtual_identity *id = virtual_identity::get(top)) else if (virtual_identity *id = virtual_identity::get(top))
{ {
std::string name = getNameChunk(id, 11, 2); std::string name = getNameChunk(id, 11, 2);
auto handler = map_find(getFocusStringHandlers, id); auto handler = map_find(getFocusStringsHandlers, id);
if (handler) if (handler)
handler(name, top); handler(name, focusStrings, top);
return name;
} }
else else
{ {
Core &core = Core::getInstance(); Core &core = Core::getInstance();
std::string name = core.p->readClassName(*(void**)top); std::string name = core.p->readClassName(*(void**)top);
return name.substr(11, name.size()-11-2); focusStrings.push_back(name.substr(11, name.size()-11-2));
} }
return focusStrings;
} }
// Predefined common guard functions // Predefined common guard functions
@ -1269,6 +1177,28 @@ df::item *Gui::getSelectedItem(color_ostream &out, bool quiet)
return item; return item;
} }
bool Gui::any_stockpile_hotkey(df::viewscreen* top)
{
return getAnyStockpile(top) != NULL;
}
df::building_stockpilest* Gui::getAnyStockpile(df::viewscreen* top) {
if (matchFocusString("dwarfmode/Some/Stockpile")) {
return game->main_interface.stockpile.cur_bld;
}
return NULL;
}
df::building_stockpilest* Gui::getSelectedStockpile(color_ostream& out, bool quiet) {
df::building_stockpilest* stockpile = getAnyStockpile(Core::getTopViewscreen());
if (!stockpile && !quiet)
out.printerr("No stockpile is selected in the UI.\n");
return stockpile;
}
df::building *Gui::getAnyBuilding(df::viewscreen *top) df::building *Gui::getAnyBuilding(df::viewscreen *top)
{ {
using df::global::game; using df::global::game;

@ -97,7 +97,7 @@ add_subdirectory(channel-safely)
dfhack_plugin(cleanconst cleanconst.cpp) dfhack_plugin(cleanconst cleanconst.cpp)
dfhack_plugin(cleaners cleaners.cpp) dfhack_plugin(cleaners cleaners.cpp)
dfhack_plugin(cleanowned cleanowned.cpp) dfhack_plugin(cleanowned cleanowned.cpp)
#dfhack_plugin(confirm confirm.cpp LINK_LIBRARIES lua) 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(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua)

@ -15,16 +15,11 @@
#include "modules/Gui.h" #include "modules/Gui.h"
#include "uicommon.h" #include "uicommon.h"
#include "df/building_tradedepotst.h" #include "df/gamest.h"
#include "df/general_ref.h" #include "df/general_ref.h"
#include "df/general_ref_contained_in_itemst.h" #include "df/general_ref_contained_in_itemst.h"
#include "df/interfacest.h" #include "df/interfacest.h"
#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_jobmanagementst.h"
#include "df/viewscreen_justicest.h"
#include "df/viewscreen_layer_militaryst.h"
#include "df/viewscreen_locationsst.h"
#include "df/viewscreen_tradegoodsst.h"
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
@ -35,8 +30,8 @@ using std::vector;
DFHACK_PLUGIN("confirm"); DFHACK_PLUGIN("confirm");
DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(game);
REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(plotinfo);
typedef std::set<df::interface_key> ikey_set; 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);
@ -46,11 +41,6 @@ static map<string, conf_wrapper*> confirmations;
string active_id; string active_id;
queue<string> cmds; queue<string> cmds;
// true when confirm is paused
bool paused = false;
// if set, confirm will unpause when this screen is no longer on the stack
df::viewscreen *paused_screen = NULL;
namespace DFHack { namespace DFHack {
DBG_DECLARE(confirm,status); DBG_DECLARE(confirm,status);
} }
@ -72,11 +62,14 @@ string char_replace (string s, char a, char b)
} }
bool set_conf_state (string name, bool state); bool set_conf_state (string name, bool state);
bool set_conf_paused (string name, bool pause);
class confirmation_base { class confirmation_base {
public: public:
enum cstate { INACTIVE, ACTIVE, SELECTED }; enum cstate { INACTIVE, ACTIVE, SELECTED };
virtual string get_id() = 0; virtual string get_id() = 0;
virtual string get_focus_string() = 0;
virtual bool match_prefix() = 0;
virtual bool set_state(cstate) = 0; virtual bool set_state(cstate) = 0;
static bool set_state(string id, cstate state) static bool set_state(string id, cstate state)
@ -96,10 +89,12 @@ confirmation_base *confirmation_base::active = nullptr;
struct conf_wrapper { struct conf_wrapper {
private: private:
bool enabled; bool enabled;
bool paused;
std::set<VMethodInterposeLinkBase*> hooks; std::set<VMethodInterposeLinkBase*> hooks;
public: public:
conf_wrapper() conf_wrapper()
:enabled(false) :enabled(false),
paused(false)
{} {}
void add_hook(VMethodInterposeLinkBase *hook) void add_hook(VMethodInterposeLinkBase *hook)
{ {
@ -117,29 +112,35 @@ public:
enabled = state; enabled = state;
return true; return true;
} }
bool set_paused (bool pause) {
paused = pause;
return true;
}
inline bool is_enabled() { return enabled; } inline bool is_enabled() { return enabled; }
inline bool is_paused() { return paused; }
}; };
namespace trade { namespace trade {
static bool goods_selected (const std::vector<char> &selected) static bool goods_selected (std::vector<uint8_t> &selected)
{ {
for (char c : selected) if(!game->main_interface.trade.open)
if (c) return false;
for (uint8_t sel : selected)
if (sel == 1)
return true; return true;
return false; return false;
} }
inline bool trader_goods_selected (df::viewscreen_tradegoodsst *screen) inline bool trader_goods_selected ()
{ {
CHECK_NULL_POINTER(screen); return goods_selected(game->main_interface.trade.goodflag[0]);
return goods_selected(screen->trader_selected);
} }
inline bool broker_goods_selected (df::viewscreen_tradegoodsst *screen) inline bool broker_goods_selected ()
{ {
CHECK_NULL_POINTER(screen); return goods_selected(game->main_interface.trade.goodflag[1]);
return goods_selected(screen->broker_selected);
} }
static bool goods_all_selected(const std::vector<char> &selected, const std::vector<df::item*> &items) \ /*static bool goods_all_selected(const std::vector<char>& selected, const std::vector<df::item*>& items) \
{ {
for (size_t i = 0; i < selected.size(); ++i) for (size_t i = 0; i < selected.size(); ++i)
{ {
@ -162,16 +163,14 @@ namespace trade {
} }
return true; return true;
} }
inline bool trader_goods_all_selected(df::viewscreen_tradegoodsst *screen) inline bool trader_goods_all_selected()
{ {
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()
{ {
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 { namespace conf_lua {
@ -228,6 +227,7 @@ namespace conf_lua {
lua_newtable(L); lua_newtable(L);
Lua::TableInsert(L, "id", item.first); Lua::TableInsert(L, "id", item.first);
Lua::TableInsert(L, "enabled", item.second->is_enabled()); Lua::TableInsert(L, "enabled", item.second->is_enabled());
Lua::TableInsert(L, "paused", item.second->is_paused());
lua_settable(L, -3); lua_settable(L, -3);
} }
return 1; return 1;
@ -240,28 +240,17 @@ namespace conf_lua {
lua_pushnil(L); lua_pushnil(L);
return 1; return 1;
} }
int unpause(lua_State *)
{
DEBUG(status).print("unpausing\n");
paused = false;
paused_screen = NULL;
return 0;
}
int get_paused (lua_State *L)
{
Lua::Push(L, paused);
return 1;
}
} }
} }
#define CONF_LUA_FUNC(ns, name) {#name, df::wrap_function(ns::name, true)} #define CONF_LUA_FUNC(ns, name) {#name, df::wrap_function(ns::name, true)}
DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_FUNCTIONS {
CONF_LUA_FUNC( , set_conf_state), CONF_LUA_FUNC( , set_conf_state),
CONF_LUA_FUNC( , set_conf_paused),
CONF_LUA_FUNC(trade, broker_goods_selected), CONF_LUA_FUNC(trade, broker_goods_selected),
CONF_LUA_FUNC(trade, broker_goods_all_selected), //CONF_LUA_FUNC(trade, broker_goods_all_selected),
CONF_LUA_FUNC(trade, trader_goods_selected), CONF_LUA_FUNC(trade, trader_goods_selected),
CONF_LUA_FUNC(trade, trader_goods_all_selected), //CONF_LUA_FUNC(trade, trader_goods_all_selected),
DFHACK_LUA_END DFHACK_LUA_END
}; };
@ -270,14 +259,12 @@ DFHACK_PLUGIN_LUA_COMMANDS {
CONF_LUA_CMD(get_ids), CONF_LUA_CMD(get_ids),
CONF_LUA_CMD(get_conf_data), CONF_LUA_CMD(get_conf_data),
CONF_LUA_CMD(get_active_id), CONF_LUA_CMD(get_active_id),
CONF_LUA_CMD(unpause),
CONF_LUA_CMD(get_paused),
DFHACK_LUA_END DFHACK_LUA_END
}; };
void show_options() void show_options()
{ {
cmds.push("gui/confirm-opts"); cmds.push("gui/confirm");
} }
template <class T> template <class T>
@ -306,45 +293,70 @@ public:
return true; return true;
} }
bool feed (ikey_set *input) { bool feed (ikey_set *input) {
if (paused) bool mouseExit = false;
{ if(df::global::enabler->mouse_rbut) {
// we can only detect that we've left the screen by intercepting the mouseExit = true;
// ESC key
if (!paused_screen && input->count(df::interface_key::LEAVESCREEN))
conf_lua::api::unpause(NULL);
return false;
} }
else if (state == INACTIVE) bool mouseSelect = false;
if(df::global::enabler->mouse_lbut) {
mouseSelect = true;
}
conf_wrapper *wrapper = confirmations[this->get_id()];
if(wrapper->is_paused()) {
std::string concernedFocus = this->get_focus_string();
if(!Gui::matchFocusString(this->get_focus_string(), this->match_prefix()))
wrapper->set_paused(false);
return false;
} else if (state == INACTIVE)
{ {
if(mouseExit) {
if(intercept_key("MOUSE_RIGHT") && set_state(ACTIVE)) {
df::global::enabler->mouse_rbut = 0;
df::global::enabler->mouse_rbut_down = 0;
mouse_pos = df::coord2d(df::global::gps->mouse_x, df::global::gps->mouse_y);
last_key_is_right_click = true;
return true;
}
} else
last_key_is_right_click = false;
if(mouseSelect) {
if(intercept_key("MOUSE_LEFT") && set_state(ACTIVE)) {
df::global::enabler->mouse_lbut = 0;
df::global::enabler->mouse_lbut_down = 0;
mouse_pos = df::coord2d(df::global::gps->mouse_x, df::global::gps->mouse_y);
last_key_is_left_click = true;
return true;
}
} else
last_key_is_left_click = false;
for (df::interface_key key : *input) for (df::interface_key key : *input)
{ {
if (intercept_key(key)) if (intercept_key(key) && set_state(ACTIVE))
{ {
if (set_state(ACTIVE)) last_key = key;
{ return true;
last_key = key;
return true;
}
} }
} }
return false; return false;
} }
else if (state == ACTIVE) else if (state == ACTIVE)
{ {
if (input->count(df::interface_key::LEAVESCREEN)) if (input->count(df::interface_key::LEAVESCREEN) || mouseExit) {
if(mouseExit) {
df::global::enabler->mouse_rbut = 0;
df::global::enabler->mouse_rbut_down = 0;
}
set_state(INACTIVE); set_state(INACTIVE);
else if (input->count(df::interface_key::SELECT)) } else if (input->count(df::interface_key::SELECT))
set_state(SELECTED); set_state(SELECTED);
else if (input->count(df::interface_key::CUSTOM_P)) else if (input->count(df::interface_key::CUSTOM_P))
{ {
DEBUG(status).print("pausing\n"); DEBUG(status).print("pausing\n");
paused = true;
// only record the screen when we're not at the top viewscreen wrapper->set_paused(true);
// since this screen will *always* be on the stack. for
// dwarfmode screens, use ESC detection to discover when to
// unpause
if (!df::viewscreen_dwarfmodest::_identity.is_instance(screen))
paused_screen = screen;
set_state(INACTIVE); set_state(INACTIVE);
} }
else if (input->count(df::interface_key::CUSTOM_S)) else if (input->count(df::interface_key::CUSTOM_S))
@ -429,12 +441,35 @@ public:
else if (state == SELECTED) else if (state == SELECTED)
{ {
ikey_set tmp; ikey_set tmp;
tmp.insert(last_key); if(last_key_is_left_click) {
screen->feed(&tmp); long prevx = df::global::gps->mouse_x;
long prevy = df::global::gps->mouse_y;
df::global::gps->mouse_x = mouse_pos.x;
df::global::gps->mouse_y = mouse_pos.y;
df::global::enabler->mouse_lbut = 1;
df::global::enabler->mouse_lbut_down = 1;
screen->feed(&tmp);
df::global::enabler->mouse_lbut = 0;
df::global::enabler->mouse_lbut_down = 0;
df::global::gps->mouse_x = prevx;
df::global::gps->mouse_y = prevy;
}
else if(last_key_is_right_click) {
tmp.insert(df::interface_key::LEAVESCREEN);
screen->feed(&tmp);
}
else {
tmp.insert(last_key);
screen->feed(&tmp);
}
set_state(INACTIVE); set_state(INACTIVE);
} }
// clean up any artifacts
df::global::gps->force_full_display_count = 1;
} }
virtual string get_id() override = 0; string get_id() override = 0;
string get_focus_string() override = 0;
bool match_prefix() override = 0;
#define CONF_LUA_START using namespace conf_lua; Lua::StackUnwinder unwind(l_state); push(screen); push(get_id()); #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) bool intercept_key (df::interface_key key)
{ {
@ -445,6 +480,15 @@ public:
else else
return false; return false;
}; };
bool intercept_key (std::string mouse_button = "MOUSE_LEFT")
{
CONF_LUA_START;
push(mouse_button);
if (call("intercept_key", 3, 1))
return lua_toboolean(l_state, -1);
else
return false;
};
string get_title() string get_title()
{ {
CONF_LUA_START; CONF_LUA_START;
@ -473,6 +517,9 @@ public:
protected: protected:
cstate state; cstate state;
df::interface_key last_key; df::interface_key last_key;
bool last_key_is_left_click;
bool last_key_is_right_click;
df::coord2d mouse_pos;
}; };
template<typename T> template<typename T>
@ -501,23 +548,19 @@ struct cls##_hooks : cls::screen_type { \
INTERPOSE_NEXT(render)(); \ INTERPOSE_NEXT(render)(); \
cls##_instance.render(); \ cls##_instance.render(); \
} \ } \
DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \
{ \
return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \
} \
}; \ }; \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \ IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \ IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, key_conflict, prio); \
static int conf_register_##cls = conf_register(&cls##_instance, {\ static int conf_register_##cls = conf_register(&cls##_instance, {\
&INTERPOSE_HOOK(cls##_hooks, feed), \ &INTERPOSE_HOOK(cls##_hooks, feed), \
&INTERPOSE_HOOK(cls##_hooks, render), \ &INTERPOSE_HOOK(cls##_hooks, render), \
&INTERPOSE_HOOK(cls##_hooks, key_conflict), \
}); });
#define DEFINE_CONFIRMATION(cls, screen) \ #define DEFINE_CONFIRMATION(cls, screen, focusString) \
class confirmation_##cls : public confirmation<df::screen> { \ class confirmation_##cls : public confirmation<df::screen> { \
virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \ virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \
virtual string get_focus_string() { return focusString; } \
virtual bool match_prefix() { return focusString[strlen(focusString) - 1] == '*'; } \
}; \ }; \
IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, 0); IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, 0);
@ -525,21 +568,39 @@ static int conf_register_##cls = conf_register(&cls##_instance, {\
implemented in plugins/lua/confirm.lua. implemented in plugins/lua/confirm.lua.
IDs (used in the "confirm enable/disable" command, by Lua, and in the docs) 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 are obtained by replacing '_' with '-' in the first argument to DEFINE_CONFIRMATION
The second argument to DEFINE_CONFIRMATION determines the viewscreen that any
intercepted input will be fed to.
The third argument to DEFINE_CONFIRMATION determines the focus string that will
be used to determine if the confirmation should be unpaused. If a confirmation is paused
and the focus string is no longer found in the current focus, the confirmation will be
unpaused. Focus strings ending in "*" will use prefix matching e.g. "dwarfmode/Info*" would
match "dwarfmode/Info/Foo", "dwarfmode/Info/Bar" and so on. All matching is case insensitive.
*/ */
DEFINE_CONFIRMATION(trade, viewscreen_tradegoodsst);
DEFINE_CONFIRMATION(trade_cancel, viewscreen_tradegoodsst); DEFINE_CONFIRMATION(trade_cancel, viewscreen_dwarfmodest, "dwarfmode/Trade");
DEFINE_CONFIRMATION(trade_seize, viewscreen_tradegoodsst); DEFINE_CONFIRMATION(haul_delete_route, viewscreen_dwarfmodest, "dwarfmode/Hauling");
DEFINE_CONFIRMATION(trade_offer, viewscreen_tradegoodsst); DEFINE_CONFIRMATION(haul_delete_stop, viewscreen_dwarfmodest, "dwarfmode/Hauling");
DEFINE_CONFIRMATION(trade_select_all, viewscreen_tradegoodsst); DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest, "dwarfmode/ViewSheets/BUILDING");
DEFINE_CONFIRMATION(haul_delete, viewscreen_dwarfmodest); DEFINE_CONFIRMATION(squad_disband, viewscreen_dwarfmodest, "dwarfmode/Squads");
DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest); DEFINE_CONFIRMATION(order_remove, viewscreen_dwarfmodest, "dwarfmode/Info/WORK_ORDERS");
DEFINE_CONFIRMATION(squad_disband, viewscreen_layer_militaryst); DEFINE_CONFIRMATION(zone_remove, viewscreen_dwarfmodest, "dwarfmode/Zone*");
DEFINE_CONFIRMATION(uniform_delete, viewscreen_layer_militaryst); DEFINE_CONFIRMATION(burrow_remove, viewscreen_dwarfmodest, "dwarfmode/Burrow");
DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest); DEFINE_CONFIRMATION(stockpile_remove, viewscreen_dwarfmodest, "dwarfmode/Some/Stockpile");
DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest);
DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst); // these are more complex to implement
DEFINE_CONFIRMATION(convict, viewscreen_justicest); //DEFINE_CONFIRMATION(convict, viewscreen_dwarfmodest);
DEFINE_CONFIRMATION(order_remove, viewscreen_jobmanagementst); //DEFINE_CONFIRMATION(trade, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(trade_seize, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(trade_offer, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(trade_select_all, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(uniform_delete, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest);
// locations can't be retired currently
//DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst);
DFhackCExport command_result plugin_init (color_ostream &out, vector <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, vector <PluginCommand> &commands)
{ {
@ -585,22 +646,6 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out)
return CR_OK; return CR_OK;
} }
static bool screen_found(df::viewscreen *target_screen)
{
if (!df::global::gview)
return false;
df::viewscreen *screen = &df::global::gview->view;
while (screen)
{
if (screen == target_screen)
return true;
screen = screen->child;
}
return false;
}
DFhackCExport command_result plugin_onupdate (color_ostream &out) DFhackCExport command_result plugin_onupdate (color_ostream &out)
{ {
while (!cmds.empty()) while (!cmds.empty())
@ -609,10 +654,6 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out)
cmds.pop(); cmds.pop();
} }
// if the screen that we paused on is no longer on the stack, unpause
if (paused_screen && !screen_found(paused_screen))
conf_lua::api::unpause(NULL);
return CR_OK; return CR_OK;
} }
@ -637,6 +678,27 @@ bool set_conf_state (string name, bool state)
return found; return found;
} }
bool set_conf_paused (string name, bool pause)
{
bool found = false;
for (auto it : confirmations)
{
if (it.first == name)
{
found = true;
it.second->set_paused(pause);
}
}
if (pause == true)
{
// dismiss the confirmation too
confirmation_base::set_state(name, confirmation_base::INACTIVE);
}
return found;
}
void enable_conf (color_ostream &out, string name, bool state) void enable_conf (color_ostream &out, string name, bool state)
{ {
if (!set_conf_state(name, state)) if (!set_conf_state(name, state))

@ -26,7 +26,6 @@ static const string INVOKE_HOTKEYS_COMMAND = "hotkeys";
static const std::string MENU_SCREEN_FOCUS_STRING = "dfhack/lua/hotkeys/menu"; static const std::string MENU_SCREEN_FOCUS_STRING = "dfhack/lua/hotkeys/menu";
static bool valid = false; // whether the following two vars contain valid data static bool valid = false; // whether the following two vars contain valid data
static string current_focus;
static map<string, string> current_bindings; static map<string, string> current_bindings;
static vector<string> sorted_keys; static vector<string> sorted_keys;
@ -38,14 +37,13 @@ static bool can_invoke(const string &cmdline, df::viewscreen *screen) {
} }
static int cleanupHotkeys(lua_State *) { static int cleanupHotkeys(lua_State *) {
DEBUG(log).print("cleaning up old stub keybindings for %s\n", current_focus.c_str()); DEBUG(log).print("cleaning up old stub keybindings for: %s\n", join_strings(", ", Gui::getCurFocus(true)).c_str());
std::for_each(sorted_keys.begin(), sorted_keys.end(), [](const string &sym) { std::for_each(sorted_keys.begin(), sorted_keys.end(), [](const string &sym) {
string keyspec = sym + "@" + MENU_SCREEN_FOCUS_STRING; string keyspec = sym + "@" + MENU_SCREEN_FOCUS_STRING;
DEBUG(log).print("clearing keybinding: %s\n", keyspec.c_str()); DEBUG(log).print("clearing keybinding: %s\n", keyspec.c_str());
Core::getInstance().ClearKeyBindings(keyspec); Core::getInstance().ClearKeyBindings(keyspec);
}); });
valid = false; valid = false;
current_focus = "";
sorted_keys.clear(); sorted_keys.clear();
current_bindings.clear(); current_bindings.clear();
return 0; return 0;
@ -90,7 +88,6 @@ static void find_active_keybindings(df::viewscreen *screen, bool filtermenu) {
valid_keys.push_back("`"); valid_keys.push_back("`");
current_focus = Gui::getFocusString(screen);
for (int shifted = 0; shifted < 2; shifted++) { for (int shifted = 0; shifted < 2; shifted++) {
for (int alt = 0; alt < 2; alt++) { for (int alt = 0; alt < 2; alt++) {
for (int ctrl = 0; ctrl < 2; ctrl++) { for (int ctrl = 0; ctrl < 2; ctrl++) {
@ -112,7 +109,7 @@ static void find_active_keybindings(df::viewscreen *screen, bool filtermenu) {
vector<string> tokens; vector<string> tokens;
split_string(&tokens, *invoke_cmd, ":"); split_string(&tokens, *invoke_cmd, ":");
string focus = tokens[0].substr(1); string focus = tokens[0].substr(1);
if (prefix_matches(focus, current_focus)) { if(Gui::matchFocusString(focus)) {
auto cmdline = trim(tokens[1]); auto cmdline = trim(tokens[1]);
add_binding_if_valid(sym, cmdline, screen, filtermenu); add_binding_if_valid(sym, cmdline, screen, filtermenu);
} }
@ -145,8 +142,8 @@ static void list(color_ostream &out) {
if (!valid) if (!valid)
find_active_keybindings(Gui::getCurViewscreen(true), false); find_active_keybindings(Gui::getCurViewscreen(true), false);
out.print("Valid keybindings for the current screen (%s)\n", out.print("Valid keybindings for the current focus:\n %s\n",
current_focus.c_str()); join_strings("\n", Gui::getCurFocus(true)).c_str());
std::for_each(sorted_keys.begin(), sorted_keys.end(), [&](const string &sym) { std::for_each(sorted_keys.begin(), sorted_keys.end(), [&](const string &sym) {
out.print("%s: %s\n", sym.c_str(), current_bindings[sym].c_str()); out.print("%s: %s\n", sym.c_str(), current_bindings[sym].c_str());
}); });
@ -158,7 +155,7 @@ static void list(color_ostream &out) {
static bool invoke_command(color_ostream &out, const size_t index) { static bool invoke_command(color_ostream &out, const size_t index) {
auto screen = Core::getTopViewscreen(); auto screen = Core::getTopViewscreen();
if (sorted_keys.size() <= index || if (sorted_keys.size() <= index ||
Gui::getFocusString(screen) != MENU_SCREEN_FOCUS_STRING) !Gui::matchFocusString(MENU_SCREEN_FOCUS_STRING))
return false; return false;
auto cmd = current_bindings[sorted_keys[index]]; auto cmd = current_bindings[sorted_keys[index]];

@ -1,7 +1,5 @@
local _ENV = mkmodule('plugins.confirm') local _ENV = mkmodule('plugins.confirm')
local ui = df.global.plotinfo
local confs = {} local confs = {}
-- Wraps df.interface_key[foo] functionality but fails with invalid keys -- Wraps df.interface_key[foo] functionality but fails with invalid keys
keys = {} keys = {}
@ -11,6 +9,9 @@ setmetatable(keys, {
end, end,
__newindex = function() error('Table is read-only') end __newindex = function() error('Table is read-only') end
}) })
-- Mouse keys will be sent as a string instead of interface_key
local MOUSE_LEFT = "MOUSE_LEFT"
local MOUSE_RIGHT = "MOUSE_RIGHT"
--[[ The screen where a confirmation has been triggered --[[ The screen where a confirmation has been triggered
Note that this is *not* necessarily the topmost viewscreen, so do not use Note that this is *not* necessarily the topmost viewscreen, so do not use
gui.getCurViewscreen() or related functions. ]] gui.getCurViewscreen() or related functions. ]]
@ -55,20 +56,95 @@ is equivalent to:
]] ]]
trade_cancel = defconf('trade-cancel')
function trade_cancel.intercept_key(key)
return dfhack.gui.matchFocusString("dwarfmode/Trade") and
(key == keys.LEAVESCREEN or key == MOUSE_RIGHT) and
(trader_goods_selected() or broker_goods_selected())
end
trade_cancel.title = "Cancel trade"
trade_cancel.message = "Are you sure you want leave this screen?\nSelected items will not be saved."
haul_delete_route = defconf('haul-delete-route')
function haul_delete_route.intercept_key(key)
return df.global.game.main_interface.current_hover == 180 and key == MOUSE_LEFT
end
haul_delete_route.title = "Confirm deletion"
haul_delete_route.message = "Are you sure you want to delete this route?"
haul_delete_stop = defconf('haul-delete-stop')
function haul_delete_stop.intercept_key(key)
return df.global.game.main_interface.current_hover == 185 and key == MOUSE_LEFT
end
haul_delete_stop.title = "Confirm deletion"
haul_delete_stop.message = "Are you sure you want to delete this stop?"
depot_remove = defconf('depot-remove')
function depot_remove.intercept_key(key)
if df.global.game.main_interface.current_hover == 299 and
key == MOUSE_LEFT and
df.building_tradedepotst:is_instance(dfhack.gui.getSelectedBuilding(true)) then
for _, caravan in pairs(df.global.plotinfo.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 == MOUSE_LEFT and df.global.game.main_interface.current_hover == 341
end
squad_disband.title = "Disband squad"
squad_disband.message = "Are you sure you want to disband this squad?"
order_remove = defconf('order-remove')
function order_remove.intercept_key(key)
return key == MOUSE_LEFT and df.global.game.main_interface.current_hover == 222
end
order_remove.title = "Remove manager order"
order_remove.message = "Are you sure you want to remove this order?"
zone_remove = defconf('zone-remove')
function zone_remove.intercept_key(key)
return key == MOUSE_LEFT and df.global.game.main_interface.current_hover == 130
end
zone_remove.title = "Remove zone"
zone_remove.message = "Are you sure you want to remove this zone?"
burrow_remove = defconf('burrow-remove')
function burrow_remove.intercept_key(key)
return key == MOUSE_LEFT and df.global.game.main_interface.current_hover == 171
end
burrow_remove.title = "Remove burrow"
burrow_remove.message = "Are you sure you want to remove this burrow?"
stockpile_remove = defconf('stockpile-remove')
function stockpile_remove.intercept_key(key)
return key == MOUSE_LEFT and df.global.game.main_interface.current_hover == 118
end
stockpile_remove.title = "Remove stockpile"
stockpile_remove.message = "Are you sure you want to remove this stockpile?"
-- these confirmations have more complex button detection requirements
--[[
trade = defconf('trade') trade = defconf('trade')
function trade.intercept_key(key) function trade.intercept_key(key)
return screen.in_edit_count == 0 and dfhack.gui.matchFocusString("dwarfmode/Trade") and key == MOUSE_LEFT and hovering over trade button?
key == keys.TRADE_TRADE
end end
trade.title = "Confirm trade" trade.title = "Confirm trade"
function trade.get_message() function trade.get_message()
if trader_goods_selected(screen) and broker_goods_selected(screen) then if trader_goods_selected() and broker_goods_selected() then
return "Are you sure you want to trade the selected goods?" return "Are you sure you want to trade the selected goods?"
elseif trader_goods_selected(screen) then elseif trader_goods_selected() then
return "You are not giving any items. This is likely\n" .. return "You are not giving any items. This is likely\n" ..
"to irritate the merchants.\n" .. "to irritate the merchants.\n" ..
"Attempt to trade anyway?" "Attempt to trade anyway?"
elseif broker_goods_selected(screen) then elseif broker_goods_selected() then
return "You are not receiving any items. You may want to\n" .. return "You are not receiving any items. You may want to\n" ..
"offer these items instead or choose items to receive.\n" .. "offer these items instead or choose items to receive.\n" ..
"Attempt to trade anyway?" "Attempt to trade anyway?"
@ -79,19 +155,10 @@ function trade.get_message()
end end
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') trade_seize = defconf('trade-seize')
function trade_seize.intercept_key(key) function trade_seize.intercept_key(key)
return screen.in_edit_count == 0 and return screen.in_edit_count == 0 and
trader_goods_selected(screen) and trader_goods_selected() and
key == keys.TRADE_SEIZE key == keys.TRADE_SEIZE
end end
trade_seize.title = "Confirm seize" trade_seize.title = "Confirm seize"
@ -100,7 +167,7 @@ trade_seize.message = "Are you sure you want to seize these goods?"
trade_offer = defconf('trade-offer') trade_offer = defconf('trade-offer')
function trade_offer.intercept_key(key) function trade_offer.intercept_key(key)
return screen.in_edit_count == 0 and return screen.in_edit_count == 0 and
broker_goods_selected(screen) and broker_goods_selected() and
key == keys.TRADE_OFFER key == keys.TRADE_OFFER
end end
trade_offer.title = "Confirm offer" trade_offer.title = "Confirm offer"
@ -109,9 +176,9 @@ trade_offer.message = "Are you sure you want to offer these goods?\nYou will rec
trade_select_all = defconf('trade-select-all') trade_select_all = defconf('trade-select-all')
function trade_select_all.intercept_key(key) function trade_select_all.intercept_key(key)
if screen.in_edit_count == 0 and key == keys.SEC_SELECT then 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 if screen.in_right_pane and broker_goods_selected() and not broker_goods_all_selected() then
return true return true
elseif not screen.in_right_pane and trader_goods_selected(screen) and not trader_goods_all_selected(screen) then elseif not screen.in_right_pane and trader_goods_selected() and not trader_goods_all_selected() then
return true return true
end end
end end
@ -121,49 +188,6 @@ trade_select_all.title = "Confirm selection"
trade_select_all.message = "Selecting all goods will overwrite your current selection\n" .. trade_select_all.message = "Selecting all goods will overwrite your current selection\n" ..
"and cannot be undone. Continue?" "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') uniform_delete = defconf('uniform-delete')
function uniform_delete.intercept_key(key) function uniform_delete.intercept_key(key)
return key == keys.D_MILITARY_DELETE_UNIFORM and return key == keys.D_MILITARY_DELETE_UNIFORM and
@ -193,17 +217,6 @@ end
route_delete.title = "Delete route" route_delete.title = "Delete route"
route_delete.message = "Are you sure you want to delete this route?" route_delete.message = "Are you sure you want to delete this route?"
location_retire = defconf('location-retire')
function location_retire.intercept_key(key)
return key == keys.LOCATION_RETIRE and
(screen.menu == df.viewscreen_locationsst.T_menu.Locations or
screen.menu == df.viewscreen_locationsst.T_menu.Occupations) and
screen.in_edit == df.viewscreen_locationsst.T_in_edit.None and
screen.locations[screen.location_idx]
end
location_retire.title = "Retire location"
location_retire.message = "Are you sure you want to retire this location?"
convict = defconf('convict') convict = defconf('convict')
convict.title = "Confirm conviction" convict.title = "Confirm conviction"
function convict.intercept_key(key) function convict.intercept_key(key)
@ -218,14 +231,21 @@ function convict.get_message()
return "Are you sure you want to convict " .. name .. "?\n" .. return "Are you sure you want to convict " .. name .. "?\n" ..
"This action is irreversible." "This action is irreversible."
end end
]]--
order_remove = defconf('order-remove') -- locations cannot be retired currently
function order_remove.intercept_key(key) --[[
return key == keys.MANAGER_REMOVE and location_retire = defconf('location-retire')
not screen.in_max_workshops function location_retire.intercept_key(key)
return key == keys.LOCATION_RETIRE and
(screen.menu == df.viewscreen_locationsst.T_menu.Locations or
screen.menu == df.viewscreen_locationsst.T_menu.Occupations) and
screen.in_edit == df.viewscreen_locationsst.T_in_edit.None and
screen.locations[screen.location_idx]
end end
order_remove.title = "Remove manager order" location_retire.title = "Retire location"
order_remove.message = "Are you sure you want to remove this order?" location_retire.message = "Are you sure you want to retire this location?"
]]--
-- End of confirmation definitions -- End of confirmation definitions

@ -196,17 +196,6 @@ static inline char get_string_input(const std::set<df::interface_key> *input)
* Utility Functions * Utility Functions
*/ */
static inline df::building_stockpilest *get_selected_stockpile()
{
if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) ||
df::global::plotinfo->main.mode != ui_sidebar_mode::QueryBuilding)
{
return nullptr;
}
return virtual_cast<df::building_stockpilest>(df::global::world->selected_building);
}
static inline bool can_trade() static inline bool can_trade()
{ {
if (df::global::plotinfo->caravans.size() == 0) if (df::global::plotinfo->caravans.size() == 0)

@ -1,75 +0,0 @@
config = {
mode = 'fortress',
}
local gui = require('gui')
local guidm = require('gui.dwarfmode')
function test.enterSidebarMode()
expect.error_match('Invalid or unsupported sidebar mode',
function() guidm.enterSidebarMode('badmode') end)
expect.error_match('Invalid or unsupported sidebar mode',
function() guidm.enterSidebarMode(
df.ui_sidebar_mode.OrdersRefuse) end)
expect.error_match('must be a positive number',
function() guidm.enterSidebarMode(0, 'gg') end)
expect.error_match('must be a positive number',
function() guidm.enterSidebarMode(0, 0) end)
expect.error_match('must be a positive number',
function() guidm.enterSidebarMode(0, '0') end)
expect.error_match('must be a positive number',
function() guidm.enterSidebarMode(0, -1) end)
expect.error_match('must be a positive number',
function() guidm.enterSidebarMode(0, '-1') end)
-- Simulate not being able to get to default from a screen via mocks. This
-- failure can actually happen in-game in some situations, such as when
-- naming a building with ctrl-N (no way to cancel changes).
mock.patch({{dfhack.gui, 'getFocusString', mock.func()},
{gui, 'simulateInput', mock.func()}},
function()
expect.error_match('Unable to get into target sidebar mode',
function()
guidm.enterSidebarMode(df.ui_sidebar_mode.Default)
end)
end)
-- verify expected starting state
expect.eq(df.ui_sidebar_mode.Default, df.global.plotinfo.main.mode)
expect.eq('dwarfmode/Default', dfhack.gui.getCurFocus(true))
-- get into the orders screen
gui.simulateInput(dfhack.gui.getCurViewscreen(true), 'D_JOBLIST')
gui.simulateInput(dfhack.gui.getCurViewscreen(true), 'UNITJOB_MANAGER')
expect.eq(df.ui_sidebar_mode.Default, df.global.plotinfo.main.mode)
expect.eq('jobmanagement/Main', dfhack.gui.getCurFocus(true))
-- get back into default from some deep screen
guidm.enterSidebarMode(df.ui_sidebar_mode.Default)
expect.eq(df.ui_sidebar_mode.Default, df.global.plotinfo.main.mode)
expect.eq('dwarfmode/Default', dfhack.gui.getCurFocus(true))
-- move from default to some other mode
guidm.enterSidebarMode(df.ui_sidebar_mode.QueryBuilding)
expect.eq(df.ui_sidebar_mode.QueryBuilding, df.global.plotinfo.main.mode)
expect.str_find('^dwarfmode/QueryBuilding', dfhack.gui.getCurFocus(true))
-- move between non-default modes
guidm.enterSidebarMode(df.ui_sidebar_mode.LookAround)
expect.eq(df.ui_sidebar_mode.LookAround, df.global.plotinfo.main.mode)
expect.str_find('^dwarfmode/LookAround', dfhack.gui.getCurFocus(true))
-- get back into default from a supported mode
guidm.enterSidebarMode(df.ui_sidebar_mode.Default)
expect.eq(df.ui_sidebar_mode.Default, df.global.plotinfo.main.mode)
expect.eq('dwarfmode/Default', dfhack.gui.getCurFocus(true))
-- verify that all supported modes lead where we say they'll go
for k,v in pairs(guidm.SIDEBAR_MODE_KEYS) do
guidm.enterSidebarMode(k)
expect.eq(k, df.global.plotinfo.main.mode, df.ui_sidebar_mode[k])
end
-- end test back in default so the test harness doesn't have to autocorrect
guidm.enterSidebarMode(df.ui_sidebar_mode.Default)
end