Add hotkeys plugin

develop
Anuradha Dissanayake 2014-06-02 15:48:34 +12:00
parent e4a6f13347
commit f764081199
4 changed files with 392 additions and 0 deletions

@ -2621,6 +2621,19 @@ materials, it returns you back to this screen. If you use this along with severa
enabled materials, you should be able to place complex constructions more conveniently. enabled materials, you should be able to place complex constructions more conveniently.
Hotkeys
=======
Opens an in-game screen showing DFHack keybindings that are valid in the current mode.
.. image:: images/hotkeys.png
Type ``hotkeys`` into the DFHack console to open the screen, or bind the command to a
globally active hotkey in dfhack.init, e.g.:
``keybinding add Ctrl-F1 hotkeys``
gui/liquids gui/liquids
=========== ===========

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

@ -159,6 +159,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(mousequery mousequery.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp)
DFHACK_PLUGIN(autotrade autotrade.cpp) DFHACK_PLUGIN(autotrade autotrade.cpp)
DFHACK_PLUGIN(stocks stocks.cpp) DFHACK_PLUGIN(stocks stocks.cpp)
DFHACK_PLUGIN(hotkeys hotkeys.cpp)
DFHACK_PLUGIN(treefarm treefarm.cpp) DFHACK_PLUGIN(treefarm treefarm.cpp)
DFHACK_PLUGIN(cleanconst cleanconst.cpp) DFHACK_PLUGIN(cleanconst cleanconst.cpp)
DFHACK_PLUGIN(3dveins 3dveins.cpp) DFHACK_PLUGIN(3dveins 3dveins.cpp)

@ -0,0 +1,378 @@
#include "uicommon.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/ui.h"
#include "modules/Maps.h"
#include "modules/World.h"
#include "modules/Gui.h"
#include "PluginManager.h"
DFHACK_PLUGIN("hotkeys");
#define PLUGIN_VERSION 0.1
static map<string, string> current_bindings;
static vector<string> sorted_keys;
static bool show_usage = false;
static void send_key(const df::interface_key &key)
{
set< df::interface_key > keys;
keys.insert(key);
Gui::getCurViewscreen(true)->feed(&keys);
}
static bool can_invoke(string cmdline, df::viewscreen *screen)
{
vector<string> cmd_parts;
split_string(&cmd_parts, cmdline, " ");
if (toLower(cmd_parts[0]) == "hotkeys")
return false;
return Core::getInstance().getPluginManager()->CanInvokeHotkey(cmd_parts[0], screen);
}
static void add_binding_if_valid(string sym, string cmdline, df::viewscreen *screen)
{
if (!can_invoke(cmdline, screen))
return;
current_bindings[sym] = cmdline;
sorted_keys.push_back(sym);
string keyspec = sym + "@dfhack/viewscreen_hotkeys";
Core::getInstance().AddKeyBinding(keyspec, "hotkeys invoke " + int_to_string(sorted_keys.size() - 1));
}
static void find_active_keybindings(df::viewscreen *screen)
{
current_bindings.clear();
sorted_keys.clear();
vector<string> valid_keys;
for (char c = 'A'; c <= 'Z'; c++)
{
valid_keys.push_back(string(&c, 1));
}
for (int i = 1; i < 10; i++)
{
valid_keys.push_back("F" + int_to_string(i));
}
auto current_focus = Gui::getFocusString(screen);
for (int shifted = 0; shifted < 2; shifted++)
{
for (int ctrl = 0; ctrl < 2; ctrl++)
{
for (int alt = 0; alt < 2; alt++)
{
for (auto it = valid_keys.begin(); it != valid_keys.end(); it++)
{
string sym;
if (shifted) sym += "Shift-";
if (ctrl) sym += "Ctrl-";
if (alt) sym += "Alt-";
sym += *it;
auto list = Core::getInstance().ListKeyBindings(sym);
for (auto invoke_cmd = list.begin(); invoke_cmd != list.end(); invoke_cmd++)
{
bool add_temp_binding = false;
if (invoke_cmd->find(":") == string::npos)
{
add_binding_if_valid(sym, *invoke_cmd, screen);
}
else
{
vector<string> tokens;
split_string(&tokens, *invoke_cmd, ":");
string focus = tokens[0].substr(1);
if (prefix_matches(focus, current_focus))
{
auto cmdline = trim(tokens[1]);
add_binding_if_valid(sym, cmdline, screen);
}
}
}
}
}
}
}
}
static bool close_hotkeys_screen()
{
auto screen = Core::getTopViewscreen();
if (Gui::getFocusString(screen) != "dfhack/viewscreen_hotkeys")
return false;
Screen::dismiss(Core::getTopViewscreen());
for_each_(sorted_keys, [] (const string &sym)
{ Core::getInstance().ClearKeyBindings(sym + "@dfhack/viewscreen_hotkeys"); });
sorted_keys.clear();
return true;
}
static void invoke_command(const int index)
{
if (sorted_keys.size() <= index)
return;
auto cmd = current_bindings[sorted_keys[index]];
if (close_hotkeys_screen())
{
Core::getInstance().setHotkeyCmd(cmd);
}
}
class ViewscreenHotkeys : public dfhack_viewscreen
{
public:
ViewscreenHotkeys(df::viewscreen *top_screen) : top_screen(top_screen)
{
hotkeys_column.multiselect = false;
hotkeys_column.auto_select = true;
hotkeys_column.setTitle("Key Binding");
hotkeys_column.bottom_margin = 4;
hotkeys_column.allow_search = false;
focus = Gui::getFocusString(top_screen);
populateColumns();
}
void populateColumns()
{
hotkeys_column.clear();
int max_key_length = 0;
for_each_(sorted_keys, [&] (const string &sym)
{ if (sym.length() > max_key_length) { max_key_length = sym.length(); } });
int padding = max_key_length + 2;
for (int i = 0; i < sorted_keys.size(); i++)
{
string text = pad_string(sorted_keys[i], padding, false);
text += current_bindings[sorted_keys[i]];
hotkeys_column.add(text, i+1);
}
help_start = hotkeys_column.fixWidth() + 2;
hotkeys_column.filterDisplay();
}
void feed(set<df::interface_key> *input)
{
if (hotkeys_column.feed(input))
return;
if (input->count(interface_key::LEAVESCREEN))
{
close_hotkeys_screen();
}
else if (input->count(interface_key::SELECT))
{
invoke_command(hotkeys_column.highlighted_index);
}
else if (input->count(interface_key::CUSTOM_U))
{
show_usage = !show_usage;
}
}
void render()
{
if (Screen::isDismissed(this))
return;
dfhack_viewscreen::render();
Screen::clear();
Screen::drawBorder(" Hotkeys ");
hotkeys_column.display(true);
int32_t y = gps->dimy - 3;
int32_t x = 2;
OutputHotkeyString(x, y, "Leave", "Esc");
x += 3;
OutputHotkeyString(x, y, "Invoke", "Enter or Hotkey");
x += 3;
OutputToggleString(x, y, "Show Usage", "u", show_usage);
y = gps->dimy - 4;
x = 2;
OutputHotkeyString(x, y, focus.c_str(), "Context", false, help_start, COLOR_WHITE, COLOR_BROWN);
if (sorted_keys.size() == 0)
return;
y = 2;
x = help_start;
auto width = gps->dimx - help_start - 2;
vector <string> parts;
Core::cheap_tokenise(current_bindings[sorted_keys[hotkeys_column.highlighted_index]], parts);
if(parts.size() == 0)
return;
string first = parts[0];
parts.erase(parts.begin());
if (first[0] == '#')
return;
Plugin *plugin = Core::getInstance().getPluginManager()->getPluginByCommand(first);
if (plugin)
{
for (auto i = 0; i < plugin->size(); i++)
{
auto pc = plugin->operator[](i);
if (pc.name == first)
{
OutputString(COLOR_BROWN, x, y, "Help", true, help_start);
vector <string> lines;
string help_text = pc.description;
if (show_usage)
help_text += "\n\n" + pc.usage;
split_string(&lines, help_text, "\n");
for (auto it = lines.begin(); it != lines.end() && y < gps->dimy - 4; it++)
{
auto wrapped_lines = wrapString(*it, width);
for (auto wit = wrapped_lines.begin(); wit != wrapped_lines.end() && y < gps->dimy - 4; wit++)
{
OutputString(COLOR_WHITE, x, y, *wit, true, help_start);
}
}
break;
}
}
}
}
virtual std::string getFocusString()
{
return "viewscreen_hotkeys";
}
private:
ListColumn<int> hotkeys_column;
df::viewscreen *top_screen;
string focus;
int32_t help_start;
void resize(int32_t x, int32_t y)
{
dfhack_viewscreen::resize(x, y);
hotkeys_column.resize();
}
static vector<string> wrapString(string str, int width)
{
vector<string> result;
string excess;
if (str.length() > width)
{
auto cut_space = str.rfind(' ', width-1);
int excess_start;
if (cut_space == string::npos)
{
cut_space = width-1;
excess_start = cut_space;
}
else
{
excess_start = cut_space + 1;
}
string line = str.substr(0, cut_space);
excess = str.substr(excess_start);
result.push_back(line);
auto excess_lines = wrapString(excess, width);
result.insert(result.end(), excess_lines.begin(), excess_lines.end());
}
else
{
result.push_back(str);
}
return result;
}
};
static command_result hotkeys_cmd(color_ostream &out, vector <string> & parameters)
{
bool show_help = false;
if (parameters.empty())
{
if (Maps::IsValid())
{
auto top_screen = Core::getTopViewscreen();
if (Gui::getFocusString(top_screen) != "dfhack/viewscreen_hotkeys")
{
find_active_keybindings(top_screen);
Screen::show(new ViewscreenHotkeys(top_screen));
}
}
}
else
{
auto cmd = parameters[0][0];
if (cmd == 'v')
{
out << "Hotkeys" << endl << "Version: " << PLUGIN_VERSION << endl;
}
else if (cmd == 'i')
{
int index;
stringstream index_raw(parameters[1]);
index_raw >> index;
invoke_command(index);
}
else
{
return CR_WRONG_USAGE;
}
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps)
out.printerr("Could not insert hotkeys hooks!\n");
commands.push_back(
PluginCommand(
"hotkeys", "Shows ingame viewscreen with all dfhack keybindings active in current mode.",
hotkeys_cmd, false, ""));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
sorted_keys.clear();
break;
default:
break;
}
return CR_OK;
}