Open the build folder and double click the batch script there. This will eventually open
diff --git a/Memory.xml b/Memory.xml
index b4f7ce1f3..62f20f9ab 100644
--- a/Memory.xml
+++ b/Memory.xml
@@ -1078,12 +1078,23 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -2316,12 +2327,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
cmake
@@ -3188,12 +3214,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/.gitignore b/build/.gitignore
index 474fe987c..990de1678 100644
--- a/build/.gitignore
+++ b/build/.gitignore
@@ -1 +1,2 @@
-build-real
\ No newline at end of file
+VC2010
+DF_PATH.txt
diff --git a/build/build-debug.bat b/build/build-debug.bat
new file mode 100644
index 000000000..a9492de13
--- /dev/null
+++ b/build/build-debug.bat
@@ -0,0 +1,4 @@
+call "%VS100COMNTOOLS%vsvars32.bat"
+cd VC2010
+msbuild /m /p:Platform=Win32 /p:Configuration=RelWithDebInfo ALL_BUILD.vcxproj
+cd ..
\ No newline at end of file
diff --git a/build/build-release.bat b/build/build-release.bat
new file mode 100644
index 000000000..e1ad315e5
--- /dev/null
+++ b/build/build-release.bat
@@ -0,0 +1,5 @@
+call "%VS100COMNTOOLS%vsvars32.bat"
+cd VC2010
+msbuild /m /p:Platform=Win32 /p:Configuration=Release ALL_BUILD.vcxproj
+cd ..
+pause
\ No newline at end of file
diff --git a/build/generate-MSVC-2010.bat b/build/generate-MSVC-2010.bat
deleted file mode 100644
index 00c990083..000000000
--- a/build/generate-MSVC-2010.bat
+++ /dev/null
@@ -1,5 +0,0 @@
-mkdir VC2010
-cd VC2010
-echo Pre-generating a build folder
-cmake ..\.. -G"Visual Studio 10"
-cmake-gui .
\ No newline at end of file
diff --git a/build/generate-MSVC-all.bat b/build/generate-MSVC-all.bat
new file mode 100644
index 000000000..686ec3bcf
--- /dev/null
+++ b/build/generate-MSVC-all.bat
@@ -0,0 +1,6 @@
+IF EXIST DF_PATH.txt SET /P _DF_PATH= 0 Then
+ Set spoFile = fso.CreateTextFile("DF_PATH.txt", True)
+ spoFile.WriteLine(objF.Self.Path)
+ End If
+End If
+
+Function IsValue(obj)
+ ' Check whether the value has been returned.
+ Dim tmp
+ On Error Resume Next
+ tmp = " " & obj
+ If Err <> 0 Then
+ IsValue = False
+ Else
+ IsValue = True
+ End If
+ On Error GoTo 0
+End Function
\ No newline at end of file
diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt
index 9a87db9af..808c13a18 100644
--- a/library/CMakeLists.txt
+++ b/library/CMakeLists.txt
@@ -13,60 +13,59 @@ include_directories (include)
include_directories (depends/md5)
include_directories (depends/tinyxml)
include_directories (depends/tthread)
-include_directories (private)
-execute_process(COMMAND perl xml/list.pl xml include/dfhack/df ";"
+execute_process(COMMAND perl xml/list.pl xml include/df ";"
WORKING_DIRECTORY ${dfapi_SOURCE_DIR}
OUTPUT_VARIABLE GENERATED_HDRS)
SET(PROJECT_HDRS_INTERNAL
- private/ContextShared.h
- private/Internal.h
- private/wdirent.h
)
SET(PROJECT_HDRS
+include/Internal.h
include/DFHack.h
-include/dfhack/Console.h
-include/dfhack/Core.h
-include/dfhack/DataDefs.h
-include/dfhack/Error.h
-include/dfhack/Export.h
-include/dfhack/FakeSDL.h
-include/dfhack/MiscUtils.h
-include/dfhack/Module.h
-include/dfhack/Pragma.h
-include/dfhack/Process.h
-include/dfhack/TileTypes.h
-include/dfhack/Types.h
-include/dfhack/Vector.h
-include/dfhack/VersionInfo.h
-include/dfhack/VersionInfoFactory.h
-include/dfhack/Virtual.h
-include/dfhack/extra/MapExtras.h
-include/dfhack/extra/stopwatch.h
-include/dfhack/extra/termutil.h
-include/dfhack/modules/Buildings.h
-include/dfhack/modules/Constructions.h
-include/dfhack/modules/Units.h
-include/dfhack/modules/Engravings.h
-include/dfhack/modules/Gui.h
-include/dfhack/modules/Items.h
-include/dfhack/modules/kitchen.h
-include/dfhack/modules/Maps.h
-include/dfhack/modules/Materials.h
-include/dfhack/modules/Notes.h
-include/dfhack/modules/Translation.h
-include/dfhack/modules/Vegetation.h
-include/dfhack/modules/Vermin.h
-include/dfhack/modules/World.h
-include/dfhack/modules/Graphic.h
+include/Console.h
+include/Core.h
+include/DataDefs.h
+include/Error.h
+include/Export.h
+include/Hooks.h
+include/MiscUtils.h
+include/Module.h
+include/Pragma.h
+include/MemAccess.h
+include/SDL_events.h
+include/SDL_keyboard.h
+include/SDL_keysym.h
+include/TileTypes.h
+include/Types.h
+include/VersionInfo.h
+include/VersionInfoFactory.h
+include/Virtual.h
+include/modules/Buildings.h
+include/modules/Constructions.h
+include/modules/Units.h
+include/modules/Engravings.h
+include/modules/Gui.h
+include/modules/Items.h
+include/modules/kitchen.h
+include/modules/Maps.h
+include/modules/MapCache.h
+include/modules/Materials.h
+include/modules/Notes.h
+include/modules/Translation.h
+include/modules/Vegetation.h
+include/modules/Vermin.h
+include/modules/World.h
+include/modules/Graphic.h
)
SET(PROJECT_SRCS
Core.cpp
DataDefs.cpp
DataStatics.cpp
+DataStaticsCtor.cpp
+MiscUtils.cpp
PluginManager.cpp
TileTypes.cpp
VersionInfo.cpp
@@ -104,17 +103,18 @@ SET(PROJECT_HDRS_LINUX
)
SET(PROJECT_HDRS_WINDOWS
+include/wdirent.h
)
SET(PROJECT_SRCS_LINUX
Console-linux.cpp
-FakeSDL-linux.cpp
+Hooks-linux.cpp
Process-linux.cpp
)
SET(PROJECT_SRCS_WINDOWS
Console-windows.cpp
-FakeSDL-windows.cpp
+Hooks-windows.cpp
Process-windows.cpp
)
@@ -134,17 +134,18 @@ LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS})
SET_SOURCE_FILES_PROPERTIES(${GENERATED_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE GENERATED TRUE)
+FILE(GLOB GENERATE_INPUT_SCRIPTS ${dfapi_SOURCE_DIR}/xml/*.pm ${dfapi_SOURCE_DIR}/xml/*.xslt)
FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/*.xml)
ADD_CUSTOM_COMMAND(
- OUTPUT ${dfapi_SOURCE_DIR}/include/dfhack/df/static.inc
- COMMAND perl xml/codegen.pl xml include/dfhack/df
+ OUTPUT ${dfapi_SOURCE_DIR}/include/df/static.inc
+ COMMAND perl xml/codegen.pl xml include/df
WORKING_DIRECTORY ${dfapi_SOURCE_DIR}
MAIN_DEPENDENCY ${dfapi_SOURCE_DIR}/xml/codegen.pl
- DEPENDS ${GENERATE_INPUT_XMLS}
+ DEPENDS ${GENERATE_INPUT_XMLS} ${GENERATE_INPUT_SCRIPTS}
)
-ADD_CUSTOM_TARGET(generate_headers DEPENDS ${dfapi_SOURCE_DIR}/include/dfhack/df/static.inc)
+ADD_CUSTOM_TARGET(generate_headers DEPENDS ${dfapi_SOURCE_DIR}/include/df/static.inc)
# Compilation
diff --git a/library/Console-linux.cpp b/library/Console-linux.cpp
index e15a6e9cb..c1bea16d6 100644
--- a/library/Console-linux.cpp
+++ b/library/Console-linux.cpp
@@ -61,8 +61,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include
#include
-#include "dfhack/Console.h"
-#include "dfhack/FakeSDL.h"
+#include "Console.h"
+#include "Hooks.h"
using namespace DFHack;
#include "tinythread.h"
diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp
index 4daab5c2f..de2c2aca8 100644
--- a/library/Console-windows.cpp
+++ b/library/Console-windows.cpp
@@ -50,8 +50,8 @@ POSSIBILITY OF SUCH DAMAGE.
#include
#include
-#include "dfhack/Console.h"
-#include "dfhack/FakeSDL.h"
+#include "Console.h"
+#include "Hooks.h"
#include
#include
#include
diff --git a/library/Core.cpp b/library/Core.cpp
index 5296a057e..4a3dacd25 100644
--- a/library/Core.cpp
+++ b/library/Core.cpp
@@ -34,30 +34,37 @@ distribution.
#include
using namespace std;
-#include "dfhack/Error.h"
-#include "dfhack/Process.h"
-#include "dfhack/Core.h"
-#include "dfhack/DataDefs.h"
-#include "dfhack/Console.h"
-#include "dfhack/Module.h"
-#include "dfhack/VersionInfoFactory.h"
-#include "dfhack/VersionInfo.h"
-#include "dfhack/PluginManager.h"
+#include "Error.h"
+#include "MemAccess.h"
+#include "Core.h"
+#include "DataDefs.h"
+#include "Console.h"
+#include "Module.h"
+#include "VersionInfoFactory.h"
+#include "VersionInfo.h"
+#include "PluginManager.h"
#include "ModuleFactory.h"
-#include "dfhack/modules/Gui.h"
-#include "dfhack/modules/World.h"
-#include "dfhack/modules/Graphic.h"
+#include "modules/Gui.h"
+#include "modules/World.h"
+#include "modules/Graphic.h"
using namespace DFHack;
-#include "dfhack/SDL_fakes/events.h"
+#include "SDL_events.h"
+
+#include "df/ui.h"
+#include "df/world.h"
+#include "df/world_data.h"
+#include "df/interface.h"
+#include "df/viewscreen_dwarfmodest.h"
#include
#include
#include
#include
#include "tinythread.h"
-using namespace tthread;
+using namespace tthread;
+using namespace df::enums;
struct Core::Cond
{
@@ -91,9 +98,35 @@ struct Core::Cond
void cheap_tokenise(string const& input, vector &output)
{
- istringstream str(input);
- istream_iterator cur(str), end;
- output.assign(cur, end);
+ string *cur = NULL;
+
+ for (unsigned i = 0; i < input.size(); i++) {
+ char c = input[i];
+ if (isspace(c)) {
+ cur = NULL;
+ } else {
+ if (!cur) {
+ output.push_back("");
+ cur = &output.back();
+ }
+
+ if (c == '"') {
+ for (i++; i < input.size(); i++) {
+ c = input[i];
+ if (c == '"')
+ break;
+ else if (c == '\\') {
+ if (++i < input.size())
+ cur->push_back(input[i]);
+ }
+ else
+ cur->push_back(c);
+ }
+ } else {
+ cur->push_back(c);
+ }
+ }
+ }
}
struct IODATA
@@ -119,8 +152,17 @@ void fHKthread(void * iodata)
std::string stuff = core->getHotkeyCmd(); // waits on mutex!
if(!stuff.empty())
{
- vector crap;
- command_result cr = plug_mgr->InvokeCommand(stuff, crap, false);
+ vector args;
+ cheap_tokenise(stuff, args);
+ if (args.empty()) {
+ core->con.printerr("Empty hotkey command.\n");
+ continue;
+ }
+
+ string first = args[0];
+ args.erase(args.begin());
+ command_result cr = plug_mgr->InvokeCommand(first, args, false);
+
if(cr == CR_WOULD_BREAK)
{
core->con.printerr("It isn't possible to run an interactive command outside the console.\n");
@@ -129,53 +171,25 @@ void fHKthread(void * iodata)
}
}
-// A thread function... for the interactive console.
-void fIOthread(void * iodata)
+static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clueless_counter, const string &command)
{
- IODATA * iod = ((IODATA*) iodata);
- Core * core = iod->core;
- PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr;
- CommandHistory main_history;
- main_history.load("dfhack.history");
Console & con = core->con;
- if(plug_mgr == 0 || core == 0)
- {
- con.printerr("Something horrible happened in Core's constructor...\n");
- return;
- }
- con.print("DFHack is ready. Have a nice day!\n"
- "Type in '?' or 'help' for general help, 'ls' to see all commands.\n");
- int clueless_counter = 0;
- while (true)
+
+ if (!command.empty())
{
- string command = "";
- int ret = con.lineedit("[DFHack]# ",command, main_history);
- if(ret == -2)
- {
- cerr << "Console is shutting down properly." << endl;
- return;
- }
- else if(ret == -1)
- {
- cerr << "Console caught an unspecified error." << endl;
- continue;
- }
- else if(ret)
- {
- // a proper, non-empty command was entered
- main_history.add(command);
- main_history.save("dfhack.history");
- }
// cut the input into parts
vector parts;
cheap_tokenise(command,parts);
if(parts.size() == 0)
{
clueless_counter ++;
- continue;
+ return;
}
string first = parts[0];
parts.erase(parts.begin());
+
+ if (first[0] == '#') return;
+
cerr << "Invoking: " << command << endl;
// let's see what we actually got
@@ -194,6 +208,7 @@ void fIOthread(void * iodata)
" cls - Clear the console.\n"
" fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n"
+ " keybinding - Modify bindings of commands to keys\n"
"Plugin management (useful for developers):\n"
//" belongs COMMAND - Tell which plugin a command belongs to.\n"
" plug [PLUGIN|v] - List plugin state and description.\n"
@@ -202,6 +217,27 @@ void fIOthread(void * iodata)
" reload PLUGIN|all - Reload a plugin or all loaded plugins.\n"
);
}
+ else if (parts.size() == 1)
+ {
+ Plugin *plug = plug_mgr->getPluginByCommand(parts[0]);
+ if (plug) {
+ for (int j = 0; j < plug->size();j++)
+ {
+ const PluginCommand & pcmd = (plug->operator[](j));
+ if (pcmd.name != parts[0])
+ continue;
+
+ if (pcmd.isHotkeyCommand())
+ con.color(Console::COLOR_CYAN);
+ con.print("%s: %s\n",pcmd.name.c_str(), pcmd.description.c_str());
+ con.reset_color();
+ if (!pcmd.usage.empty())
+ con << "Usage:\n" << pcmd.usage << flush;
+ return;
+ }
+ }
+ con.printerr("Unknown command: %s\n", parts[0].c_str());
+ }
else
{
con.printerr("not implemented yet\n");
@@ -301,7 +337,10 @@ void fIOthread(void * iodata)
else for (int j = 0; j < plug->size();j++)
{
const PluginCommand & pcmd = (plug->operator[](j));
+ if (pcmd.isHotkeyCommand())
+ con.color(Console::COLOR_CYAN);
con.print(" %-22s - %s\n",pcmd.name.c_str(), pcmd.description.c_str());
+ con.reset_color();
}
}
else
@@ -329,7 +368,10 @@ void fIOthread(void * iodata)
for (int j = 0; j < plug->size();j++)
{
const PluginCommand & pcmd = (plug->operator[](j));
+ if (pcmd.isHotkeyCommand())
+ con.color(Console::COLOR_CYAN);
con.print(" %-22s- %s\n",pcmd.name.c_str(), pcmd.description.c_str());
+ con.reset_color();
}
}
}
@@ -344,6 +386,49 @@ void fIOthread(void * iodata)
con.print("%s\n", plug->getName().c_str());
}
}
+ else if(first == "keybinding")
+ {
+ if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add"))
+ {
+ std::string keystr = parts[1];
+ if (parts[0] == "set")
+ core->ClearKeyBindings(keystr);
+ for (int i = parts.size()-1; i >= 2; i--)
+ {
+ if (!core->AddKeyBinding(keystr, parts[i])) {
+ con.printerr("Invalid key spec: %s\n", keystr.c_str());
+ break;
+ }
+ }
+ }
+ else if (parts.size() >= 2 && parts[0] == "clear")
+ {
+ for (unsigned i = 1; i < parts.size(); i++)
+ {
+ if (!core->ClearKeyBindings(parts[i])) {
+ con.printerr("Invalid key spec: %s\n", parts[i].c_str());
+ break;
+ }
+ }
+ }
+ else if (parts.size() == 2 && parts[0] == "list")
+ {
+ std::vector list = core->ListKeyBindings(parts[1]);
+ if (list.empty())
+ con << "No bindings." << endl;
+ for (unsigned i = 0; i < list.size(); i++)
+ con << " " << list[i] << endl;
+ }
+ else
+ {
+ con << "Usage:" << endl
+ << " keybinding list " << endl
+ << " keybinding clear ..." << endl
+ << " keybinding set \"cmdline\" \"cmdline\"..." << endl
+ << " keybinding add \"cmdline\" \"cmdline\"..." << endl
+ << "Later adds, and earlier items within one command have priority." << endl;
+ }
+ }
else if(first == "fpause")
{
World * w = core->getWorld();
@@ -384,6 +469,70 @@ void fIOthread(void * iodata)
*/
}
}
+ }
+}
+
+static void loadInitFile(Core *core, PluginManager *plug_mgr, string fname)
+{
+ ifstream init(fname);
+ if (init.bad())
+ return;
+
+ int tmp = 0;
+ string command;
+ while (getline(init, command))
+ {
+ if (!command.empty())
+ runInteractiveCommand(core, plug_mgr, tmp, command);
+ }
+}
+
+// A thread function... for the interactive console.
+void fIOthread(void * iodata)
+{
+ IODATA * iod = ((IODATA*) iodata);
+ Core * core = iod->core;
+ PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr;
+
+ CommandHistory main_history;
+ main_history.load("dfhack.history");
+
+ Console & con = core->con;
+ if(plug_mgr == 0 || core == 0)
+ {
+ con.printerr("Something horrible happened in Core's constructor...\n");
+ return;
+ }
+
+ loadInitFile(core, plug_mgr, "dfhack.init");
+
+ con.print("DFHack is ready. Have a nice day!\n"
+ "Type in '?' or 'help' for general help, 'ls' to see all commands.\n");
+
+ int clueless_counter = 0;
+ while (true)
+ {
+ string command = "";
+ int ret = con.lineedit("[DFHack]# ",command, main_history);
+ if(ret == -2)
+ {
+ cerr << "Console is shutting down properly." << endl;
+ return;
+ }
+ else if(ret == -1)
+ {
+ cerr << "Console caught an unspecified error." << endl;
+ continue;
+ }
+ else if(ret)
+ {
+ // a proper, non-empty command was entered
+ main_history.add(command);
+ main_history.save("dfhack.history");
+ }
+
+ runInteractiveCommand(core, plug_mgr, clueless_counter, command);
+
if(clueless_counter == 3)
{
con.print("Do 'help' or '?' for the list of available commands.\n");
@@ -408,11 +557,12 @@ Core::Core()
StackMutex = 0;
core_cond = 0;
// set up hotkey capture
- memset(hotkey_states,0,sizeof(hotkey_states));
hotkey_set = false;
HotkeyMutex = 0;
HotkeyCond = 0;
misc_data_mutex=0;
+ last_world_data_ptr = NULL;
+ top_viewscreen = NULL;
};
void Core::fatal (std::string output, bool deactivate)
@@ -497,7 +647,7 @@ bool Core::Init()
}
// initialize data defs
- virtual_identity::Init();
+ virtual_identity::Init(this);
InitDataDefGlobals(this);
// create mutex for syncing with interactive tasks
@@ -605,8 +755,36 @@ int Core::Update()
if(errorstate)
return -1;
+ // detect if the game was loaded or unloaded in the meantime
+ void *new_wdata = NULL;
+ if (df::global::world) {
+ df::world_data *wdata = df::global::world->world_data;
+ // when the game is unloaded, world_data isn't deleted, but its contents are
+ if (wdata && !wdata->sites.empty())
+ new_wdata = wdata;
+ }
+
+ if (new_wdata != last_world_data_ptr) {
+ last_world_data_ptr = new_wdata;
+ plug_mgr->OnStateChange(new_wdata ? SC_GAME_LOADED : SC_GAME_UNLOADED);
+ }
+
+ // detect if the viewscreen changed
+ if (df::global::gview)
+ {
+ df::viewscreen *screen = &df::global::gview->view;
+ while (screen->child)
+ screen = screen->child;
+ if (screen != top_viewscreen)
+ {
+ top_viewscreen = screen;
+ plug_mgr->OnStateChange(SC_VIEWSCREEN_CHANGED);
+ }
+ }
+
// notify all the plugins that a game tick is finished
plug_mgr->OnUpdate();
+
// wake waiting tools
// do not allow more tools to join in while we process stuff here
StackMutex->lock();
@@ -695,39 +873,168 @@ int Core::SDL_Event(SDL::Event* ev, int orig_return)
if(ev && ev->type == SDL::ET_KEYDOWN || ev->type == SDL::ET_KEYUP)
{
SDL::KeyboardEvent * ke = (SDL::KeyboardEvent *)ev;
- bool shift = ke->ksym.mod & SDL::KMOD_SHIFT;
- // consuming F1 .. F8
- int idx = ke->ksym.sym - SDL::K_F1;
- if(idx < 0 || idx > 7)
- return orig_return;
- idx += 8*shift;
- // now we have the real index...
- if(ke->state == SDL::BTN_PRESSED && !hotkey_states[idx])
+
+ if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym])
{
- hotkey_states[idx] = 1;
- Gui * g = getGui();
- if(g->hotkeys && g->df_interface && g->df_menu_state)
- {
- t_viewscreen * ws = g->GetCurrentScreen();
- // FIXME: put hardcoded values into memory.xml
- if(ws->getClassName() == "viewscreen_dwarfmodest" && *g->df_menu_state == 0x23)
- return orig_return;
- else
- {
- t_hotkey & hotkey = (*g->hotkeys)[idx];
- setHotkeyCmd(hotkey.name);
- }
- }
+ hotkey_states[ke->ksym.sym] = true;
+
+ int mod = 0;
+ if (ke->ksym.mod & SDL::KMOD_SHIFT) mod |= 1;
+ if (ke->ksym.mod & SDL::KMOD_CTRL) mod |= 2;
+ if (ke->ksym.mod & SDL::KMOD_ALT) mod |= 4;
+
+ SelectHotkey(ke->ksym.sym, mod);
}
else if(ke->state == SDL::BTN_RELEASED)
{
- hotkey_states[idx] = 0;
+ hotkey_states[ke->ksym.sym] = false;
}
}
return orig_return;
// do stuff with the events...
}
+bool Core::SelectHotkey(int sym, int modifiers)
+{
+ // Find the topmost viewscreen
+ if (!df::global::gview || !df::global::ui)
+ return false;
+
+ df::viewscreen *screen = &df::global::gview->view;
+ while (screen->child)
+ screen = screen->child;
+
+ std::string cmd;
+
+ {
+ tthread::lock_guard lock(*HotkeyMutex);
+
+ // Check the internal keybindings
+ std::vector &bindings = key_bindings[sym];
+ for (int i = bindings.size()-1; i >= 0; --i) {
+ if (bindings[i].modifiers != modifiers)
+ continue;
+ if (!plug_mgr->CanInvokeHotkey(bindings[i].command[0], screen))
+ continue;
+ cmd = bindings[i].cmdline;
+ break;
+ }
+
+ if (cmd.empty()) {
+ // Check the hotkey keybindings
+ int idx = sym - SDL::K_F1;
+ if(idx >= 0 && idx < 8)
+ {
+ if (modifiers & 1)
+ idx += 8;
+
+ if (strict_virtual_cast(screen) &&
+ df::global::ui->main.mode != ui_sidebar_mode::Hotkeys)
+ {
+ cmd = df::global::ui->main.hotkeys[idx].name;
+ }
+ }
+ }
+ }
+
+ if (!cmd.empty()) {
+ setHotkeyCmd(cmd);
+ return true;
+ }
+ else
+ return false;
+}
+
+static bool parseKeySpec(std::string keyspec, int *psym, int *pmod)
+{
+ *pmod = 0;
+
+ // ugh, ugly
+ for (;;) {
+ if (keyspec.size() > 6 && keyspec.substr(0, 6) == "Shift-") {
+ *pmod |= 1;
+ keyspec = keyspec.substr(6);
+ } else if (keyspec.size() > 5 && keyspec.substr(0, 5) == "Ctrl-") {
+ *pmod |= 2;
+ keyspec = keyspec.substr(5);
+ } else if (keyspec.size() > 4 && keyspec.substr(0, 4) == "Alt-") {
+ *pmod |= 4;
+ keyspec = keyspec.substr(4);
+ } else
+ break;
+ }
+
+ if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') {
+ *psym = SDL::K_a + (keyspec[0]-'A');
+ return true;
+ } else if (keyspec.size() == 2 && keyspec[0] == 'F' && keyspec[1] >= '1' && keyspec[1] <= '9') {
+ *psym = SDL::K_F1 + (keyspec[1]-'1');
+ return true;
+ } else
+ return false;
+}
+
+bool Core::ClearKeyBindings(std::string keyspec)
+{
+ int sym, mod;
+ if (!parseKeySpec(keyspec, &sym, &mod))
+ return false;
+
+ tthread::lock_guard lock(*HotkeyMutex);
+
+ std::vector &bindings = key_bindings[sym];
+ for (int i = bindings.size()-1; i >= 0; --i) {
+ if (bindings[i].modifiers == mod)
+ bindings.erase(bindings.begin()+i);
+ }
+
+ return true;
+}
+
+bool Core::AddKeyBinding(std::string keyspec, std::string cmdline)
+{
+ int sym;
+ KeyBinding binding;
+ if (!parseKeySpec(keyspec, &sym, &binding.modifiers))
+ return false;
+
+ cheap_tokenise(cmdline, binding.command);
+ if (binding.command.empty())
+ return false;
+
+ tthread::lock_guard lock(*HotkeyMutex);
+
+ // Don't add duplicates
+ std::vector &bindings = key_bindings[sym];
+ for (int i = bindings.size()-1; i >= 0; --i) {
+ if (bindings[i].modifiers == binding.modifiers &&
+ bindings[i].cmdline == cmdline)
+ return true;
+ }
+
+ binding.cmdline = cmdline;
+ bindings.push_back(binding);
+ return true;
+}
+
+std::vector Core::ListKeyBindings(std::string keyspec)
+{
+ int sym, mod;
+ std::vector rv;
+ if (!parseKeySpec(keyspec, &sym, &mod))
+ return rv;
+
+ tthread::lock_guard lock(*HotkeyMutex);
+
+ std::vector &bindings = key_bindings[sym];
+ for (int i = bindings.size()-1; i >= 0; --i) {
+ if (bindings[i].modifiers == mod)
+ rv.push_back(bindings[i].cmdline);
+ }
+
+ return rv;
+}
+
////////////////
// ClassNamCheck
////////////////
diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp
index 2f1f9c238..6c40ec819 100644
--- a/library/DataDefs.cpp
+++ b/library/DataDefs.cpp
@@ -28,12 +28,14 @@ distribution.
#include
#include