Merge remote-tracking branch 'eswald/hotkeys'
commit
a225984bc5
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
@ -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;
|
||||
}
|
Loading…
Reference in New Issue