mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-10 20:40:40 +00:00
917 lines
26 KiB
C++
917 lines
26 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2003 - 2007 by David White <dave@whitevine.net>
|
|
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License version 2
|
|
or at your option any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
#include "global.hpp"
|
|
|
|
#define GETTEXT_DOMAIN "wesnoth-lib"
|
|
|
|
#include "config.hpp"
|
|
#include "construct_dialog.hpp"
|
|
#include "display.hpp"
|
|
#include "events.hpp"
|
|
#include "hotkeys.hpp"
|
|
#include "game_config.hpp"
|
|
#include "game_errors.hpp"
|
|
#include "gettext.hpp"
|
|
#include "log.hpp"
|
|
#include "menu_events.hpp"
|
|
#include "preferences_display.hpp"
|
|
#include "show_dialog.hpp"
|
|
#include "util.hpp"
|
|
#include "video.hpp"
|
|
#include "wesconfig.h"
|
|
#include "wml_separators.hpp"
|
|
#include "SDL.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <map>
|
|
|
|
#define ERR_G LOG_STREAM(err, general)
|
|
#define LOG_G LOG_STREAM(info, general)
|
|
#define DBG_G LOG_STREAM(debug, general)
|
|
#define ERR_CONFIG LOG_STREAM(err, config)
|
|
|
|
namespace {
|
|
|
|
const struct {
|
|
hotkey::HOTKEY_COMMAND id;
|
|
const char* command;
|
|
const char* description;
|
|
bool hidden;
|
|
} hotkey_list_[] = {
|
|
{ hotkey::HOTKEY_CYCLE_UNITS, "cycle", N_("Next Unit"), false },
|
|
{ hotkey::HOTKEY_CYCLE_BACK_UNITS, "cycleback", N_("Previous Unit"), false },
|
|
{ hotkey::HOTKEY_UNIT_HOLD_POSITION, "holdposition", N_("Hold Position"), false},
|
|
{ hotkey::HOTKEY_END_UNIT_TURN, "endunitturn", N_("End Unit Turn"), false },
|
|
{ hotkey::HOTKEY_LEADER, "leader", N_("Leader"), false },
|
|
{ hotkey::HOTKEY_UNDO, "undo", N_("Undo"), false },
|
|
{ hotkey::HOTKEY_REDO, "redo", N_("Redo"), false },
|
|
{ hotkey::HOTKEY_ZOOM_IN, "zoomin", N_("Zoom In"), false },
|
|
{ hotkey::HOTKEY_ZOOM_OUT, "zoomout", N_("Zoom Out"), false },
|
|
{ hotkey::HOTKEY_ZOOM_DEFAULT, "zoomdefault", N_("Default Zoom"), false },
|
|
{ hotkey::HOTKEY_FULLSCREEN, "fullscreen", N_("Toggle Full Screen"), false },
|
|
{ hotkey::HOTKEY_SCREENSHOT, "screenshot", N_("Screenshot"), false },
|
|
{ hotkey::HOTKEY_ACCELERATED, "accelerated", N_("Accelerated"), false },
|
|
{ hotkey::HOTKEY_UNIT_DESCRIPTION, "describeunit", N_("Unit Description"), false },
|
|
{ hotkey::HOTKEY_RENAME_UNIT, "renameunit", N_("Rename Unit"), false },
|
|
{ hotkey::HOTKEY_SAVE_GAME, "save", N_("Save Game"), false },
|
|
{ hotkey::HOTKEY_SAVE_REPLAY, "savereplay", N_("Save Replay"), false },
|
|
{ hotkey::HOTKEY_SAVE_MAP, "savemap", N_("Save The Map"), false },
|
|
{ hotkey::HOTKEY_LOAD_GAME, "load", N_("Load Game"), false },
|
|
{ hotkey::HOTKEY_RECRUIT, "recruit", N_("Recruit"), false },
|
|
{ hotkey::HOTKEY_REPEAT_RECRUIT, "repeatrecruit", N_("Repeat Recruit"), false },
|
|
{ hotkey::HOTKEY_RECALL, "recall", N_("Recall"), false },
|
|
{ hotkey::HOTKEY_ENDTURN, "endturn", N_("End Turn"), false },
|
|
{ hotkey::HOTKEY_TOGGLE_GRID, "togglegrid", N_("Toggle Grid"), false },
|
|
{ hotkey::HOTKEY_MOUSE_SCROLL, "mousescroll", N_("Mouse Scrolling"), false },
|
|
{ hotkey::HOTKEY_STATUS_TABLE, "statustable", N_("Status Table"), false },
|
|
{ hotkey::HOTKEY_MUTE, "mute", N_("Mute"), false },
|
|
{ hotkey::HOTKEY_SPEAK, "speak", N_("Speak"), false },
|
|
{ hotkey::HOTKEY_CREATE_UNIT, "createunit", N_("Create Unit (Debug!)"), false },
|
|
{ hotkey::HOTKEY_CHANGE_UNIT_SIDE, "changeside", N_("Change Unit Side (Debug!)"), false },
|
|
{ hotkey::HOTKEY_PREFERENCES, "preferences", N_("Preferences"), false },
|
|
{ hotkey::HOTKEY_OBJECTIVES, "objectives", N_("Scenario Objectives"), false },
|
|
{ hotkey::HOTKEY_UNIT_LIST, "unitlist", N_("Unit List"), false },
|
|
{ hotkey::HOTKEY_STATISTICS, "statistics", N_("Statistics"), false },
|
|
{ hotkey::HOTKEY_QUIT_GAME, "quit", N_("Quit Game"), false },
|
|
{ hotkey::HOTKEY_LABEL_TEAM_TERRAIN, "labelteamterrain", N_("Set Team Label"), false },
|
|
{ hotkey::HOTKEY_LABEL_TERRAIN, "labelterrain", N_("Set Label"), false },
|
|
{ hotkey::HOTKEY_CLEAR_LABELS, "clearlabels", N_("Clear Labels"), false },
|
|
{ hotkey::HOTKEY_SHOW_ENEMY_MOVES, "showenemymoves", N_("Show Enemy Moves"), false },
|
|
{ hotkey::HOTKEY_BEST_ENEMY_MOVES, "bestenemymoves", N_("Best Possible Enemy Moves"), false },
|
|
{ hotkey::HOTKEY_PLAY_REPLAY, "playreplay", N_("Play"), false },
|
|
{ hotkey::HOTKEY_RESET_REPLAY, "resetreplay", N_("Reset"), false },
|
|
{ hotkey::HOTKEY_STOP_REPLAY, "stopreplay", N_("Stop"), false },
|
|
{ hotkey::HOTKEY_REPLAY_NEXT_TURN, "replaynextturn", N_("Next Turn"), false },
|
|
{ hotkey::HOTKEY_REPLAY_NEXT_SIDE, "replaynextside", N_("Next Side"), false },
|
|
{ hotkey::HOTKEY_REPLAY_SHOW_EVERYTHING, "replayshoweverything",
|
|
N_("Full map"), false },
|
|
{ hotkey::HOTKEY_REPLAY_SHOW_EACH, "replayshoweach",
|
|
N_("Each team"), false },
|
|
{ hotkey::HOTKEY_REPLAY_SHOW_TEAM1, "replayshowteam1",
|
|
N_("Team 1"), false },
|
|
{ hotkey::HOTKEY_REPLAY_SKIP_ANIMATION, "replayskipanimation", N_("Skip animation"), false },
|
|
|
|
{ hotkey::HOTKEY_EDIT_SET_TERRAIN, "editsetterrain", N_("Set Terrain"),true },
|
|
{ hotkey::HOTKEY_EDIT_QUIT, "editquit", N_("Quit Editor"),true },
|
|
{ hotkey::HOTKEY_EDIT_NEW_MAP, "editnewmap", N_("New Map"),true },
|
|
{ hotkey::HOTKEY_EDIT_LOAD_MAP, "editloadmap", N_("Load Map"),true },
|
|
{ hotkey::HOTKEY_EDIT_SAVE_MAP, "editsavemap", N_("Save Map"),true },
|
|
{ hotkey::HOTKEY_EDIT_SAVE_AS, "editsaveas", N_("Save As"),true },
|
|
{ hotkey::HOTKEY_EDIT_SET_START_POS, "editsetstartpos", N_("Set Player's keep"),true },
|
|
{ hotkey::HOTKEY_EDIT_FLOOD_FILL, "editfloodfill", N_("Flood Fill"),true },
|
|
{ hotkey::HOTKEY_EDIT_FILL_SELECTION, "editfillselection", N_("Fill Selection"),true },
|
|
{ hotkey::HOTKEY_EDIT_ROTATE_SELECTION, "editrotateselection", N_("Rotate Selection"),true },
|
|
{ hotkey::HOTKEY_EDIT_CUT, "editcut", N_("Cut"),true },
|
|
{ hotkey::HOTKEY_EDIT_COPY, "editcopy", N_("Copy"),true },
|
|
{ hotkey::HOTKEY_EDIT_PASTE, "editpaste", N_("Paste"),true },
|
|
{ hotkey::HOTKEY_EDIT_REVERT, "editrevert", N_("Revert from Disk"),true },
|
|
{ hotkey::HOTKEY_EDIT_RESIZE, "editresize", N_("Resize Map"),true },
|
|
{ hotkey::HOTKEY_EDIT_FLIP, "editflip", N_("Flip Map"),true },
|
|
{ hotkey::HOTKEY_EDIT_SELECT_ALL, "editselectall", N_("Select All"),true },
|
|
{ hotkey::HOTKEY_EDIT_DRAW, "editdraw", N_("Draw Terrain"),true },
|
|
{ hotkey::HOTKEY_EDIT_REFRESH, "editrefresh", N_("Refresh Image Cache"), true },
|
|
{ hotkey::HOTKEY_EDIT_AUTO_UPDATE, "editautoupdate", N_("Delay transition updates"), true },
|
|
{ hotkey::HOTKEY_EDIT_UPDATE, "editupdate", N_("Update transitions"), true },
|
|
|
|
{ hotkey::HOTKEY_DELAY_SHROUD, "delayshroud", N_("Delay Shroud Updates"), false },
|
|
{ hotkey::HOTKEY_UPDATE_SHROUD, "updateshroud", N_("Update Shroud Now"), false },
|
|
{ hotkey::HOTKEY_CONTINUE_MOVE, "continue", N_("Continue Move"), false },
|
|
{ hotkey::HOTKEY_SEARCH, "search", N_("Find Label or Unit"), false },
|
|
{ hotkey::HOTKEY_SPEAK_ALLY, "speaktoally", N_("Speak to Ally"), false },
|
|
{ hotkey::HOTKEY_SPEAK_ALL, "speaktoall", N_("Speak to All"), false },
|
|
{ hotkey::HOTKEY_HELP, "help", N_("Help"), false },
|
|
{ hotkey::HOTKEY_CHAT_LOG, "chatlog", N_("View Chat Log"), false },
|
|
{ hotkey::HOTKEY_LANGUAGE, "changelanguage", N_("Change the language"), true },
|
|
|
|
{ hotkey::HOTKEY_USER_CMD, "command", N_("Enter user command"), false },
|
|
{ hotkey::HOTKEY_CLEAR_MSG, "clearmessages", N_("Clear messages"), false },
|
|
#ifdef USRCMD2
|
|
{ hotkey::HOTKEY_USER_CMD_2, "usercommand#2", N_("User-Command#2"), false },
|
|
{ hotkey::HOTKEY_USER_CMD_3, "usercommand#3", N_("User-Command#3"), false },
|
|
#endif
|
|
{ hotkey::HOTKEY_NULL, NULL, NULL, true }
|
|
};
|
|
|
|
std::vector<hotkey::hotkey_item> hotkeys_;
|
|
hotkey::hotkey_item null_hotkey_;
|
|
|
|
}
|
|
|
|
namespace hotkey {
|
|
|
|
static void key_event_execute(display& disp, const SDL_KeyboardEvent& event, command_executor* executor);
|
|
|
|
|
|
hotkey_item::hotkey_item(HOTKEY_COMMAND id,
|
|
const std::string& command, const std::string& description, bool hidden) :
|
|
id_(id),
|
|
command_(command),
|
|
description_(description),
|
|
type_(UNBOUND),
|
|
character_(0),
|
|
ctrl_(false),
|
|
alt_(false),
|
|
cmd_(false),
|
|
keycode_(0),
|
|
shift_(false),
|
|
hidden_(hidden)
|
|
{
|
|
}
|
|
|
|
// There are two kinds of "key" values. One refers to actual keys, like
|
|
// F1 or SPACE. The other refers to characters produced, eg 'M' or ':'.
|
|
// For the latter, specifying shift+; doesn't make sense, because ; is
|
|
// already shifted on French keyboards, for example. You really want to
|
|
// say ':', however that is typed. However, when you say shift+SPACE,
|
|
// you're really referring to the space bar, as shift+SPACE usually just
|
|
// produces a SPACE character.
|
|
void hotkey_item::load_from_config(const config& cfg)
|
|
{
|
|
const std::string& key = cfg["key"];
|
|
|
|
alt_ = (cfg["alt"] == "yes");
|
|
cmd_ = (cfg["cmd"] == "yes");
|
|
ctrl_ = (cfg["ctrl"] == "yes");
|
|
shift_ = (cfg["shift"] == "yes");
|
|
|
|
if (!key.empty()) {
|
|
// They may really want a specific key on the keyboard: we assume
|
|
// that any single character keyname is a character.
|
|
if (key.size() > 1) {
|
|
type_ = BY_KEYCODE;
|
|
|
|
keycode_ = sdl_keysym_from_name(key);
|
|
if (keycode_ == SDLK_UNKNOWN) {
|
|
if (tolower(key[0]) != 'f') {
|
|
ERR_CONFIG << "hotkey key '" << key << "' invalid\n";
|
|
} else {
|
|
int num = lexical_cast_default<int>
|
|
(std::string(key.begin()+1, key.end()), 1);
|
|
keycode_ = num + SDLK_F1 - 1;
|
|
}
|
|
}
|
|
} else if (key == " " || shift_
|
|
#ifdef __APPLE__
|
|
|| alt_
|
|
#endif
|
|
) {
|
|
// Space must be treated as a key because shift-space
|
|
// isn't a different character from space, and control key
|
|
// makes it go weird. shift=yes should never be specified
|
|
// on single characters (eg. key=m, shift=yes would be
|
|
// key=M), but we don't want to break old preferences
|
|
// files.
|
|
type_ = BY_KEYCODE;
|
|
keycode_ = key[0];
|
|
} else {
|
|
type_ = BY_CHARACTER;
|
|
character_ = key[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string hotkey_item::get_name() const
|
|
{
|
|
std::stringstream str;
|
|
if (type_ == BY_CHARACTER) {
|
|
if (alt_)
|
|
str << "alt+";
|
|
if (cmd_)
|
|
str << "cmd+";
|
|
if (ctrl_)
|
|
str << "ctrl+";
|
|
str << static_cast<char>(character_);
|
|
} else if (type_ == BY_KEYCODE) {
|
|
if (alt_)
|
|
str << "alt+";
|
|
if (ctrl_)
|
|
str << "ctrl+";
|
|
if (shift_)
|
|
str << "shift+";
|
|
if (cmd_)
|
|
str << "cmd+";
|
|
str << SDL_GetKeyName(SDLKey(keycode_));
|
|
}
|
|
return str.str();
|
|
}
|
|
|
|
void hotkey_item::set_description(const std::string& description)
|
|
{
|
|
description_ = description;
|
|
}
|
|
void hotkey_item::clear_hotkey()
|
|
{
|
|
type_ = UNBOUND;
|
|
}
|
|
|
|
void hotkey_item::set_key(int character, int keycode, bool shift, bool ctrl, bool alt, bool cmd)
|
|
{
|
|
const std::string keyname = SDL_GetKeyName(SDLKey(keycode));
|
|
|
|
LOG_G << "setting hotkey: char=" << lexical_cast<std::string>(character)
|
|
<< " keycode=" << lexical_cast<std::string>(keycode) << " "
|
|
<< (shift ? "shift," : "")
|
|
<< (ctrl ? "ctrl," : "")
|
|
<< (alt ? "alt," : "")
|
|
<< (cmd ? "cmd," : "")
|
|
<< "\n";
|
|
|
|
// Sometimes control modifies by -64, ie ^A == 1.
|
|
if (character < 64 && ctrl) {
|
|
if (shift)
|
|
character += 64;
|
|
else
|
|
character += 96;
|
|
LOG_G << "Mapped to character " << lexical_cast<std::string>(character) << "\n";
|
|
}
|
|
|
|
// For some reason on Mac OS, if cmd and shift are down, the character doesn't get upper-cased
|
|
if (cmd && character > 96 && character < 123 && shift)
|
|
character -= 32;
|
|
|
|
// We handle simple cases by character, others by the actual key.
|
|
if (isprint(character) && !isspace(character)) {
|
|
type_ = BY_CHARACTER;
|
|
character_ = character;
|
|
ctrl_ = ctrl;
|
|
alt_ = alt;
|
|
cmd_ = cmd;
|
|
LOG_G << "type = BY_CHARACTER\n";
|
|
} else {
|
|
type_ = BY_KEYCODE;
|
|
keycode_ = keycode;
|
|
shift_ = shift;
|
|
ctrl_ = ctrl;
|
|
alt_ = alt;
|
|
cmd_ = cmd;
|
|
LOG_G << "type = BY_KEYCODE\n";
|
|
}
|
|
}
|
|
|
|
manager::manager()
|
|
{
|
|
for (int i = 0; hotkey_list_[i].command; ++i) {
|
|
hotkeys_.push_back(hotkey_item(hotkey_list_[i].id, hotkey_list_[i].command,
|
|
"", hotkey_list_[i].hidden));
|
|
}
|
|
}
|
|
|
|
|
|
manager::~manager()
|
|
{
|
|
hotkeys_.clear();
|
|
}
|
|
|
|
void load_descriptions()
|
|
{
|
|
for (size_t i = 0; hotkey_list_[i].command; ++i) {
|
|
if (i >= hotkeys_.size()) {
|
|
ERR_G << "Hotkey list too short: " << hotkeys_.size() << "\n";
|
|
}
|
|
hotkeys_[i].set_description(dsgettext(PACKAGE "-lib", hotkey_list_[i].description));
|
|
}
|
|
}
|
|
|
|
void load_hotkeys(const config& cfg)
|
|
{
|
|
const config::child_list& children = cfg.get_children("hotkey");
|
|
for(config::child_list::const_iterator i = children.begin(); i != children.end(); ++i) {
|
|
hotkey_item& h = get_hotkey((**i)["command"]);
|
|
|
|
if(h.get_id() != HOTKEY_NULL) {
|
|
h.load_from_config(**i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void save_hotkeys(config& cfg)
|
|
{
|
|
cfg.clear_children("hotkey");
|
|
|
|
for(std::vector<hotkey_item>::iterator i = hotkeys_.begin(); i != hotkeys_.end(); ++i) {
|
|
if (i->hidden() || i->get_type() == hotkey_item::UNBOUND)
|
|
continue;
|
|
|
|
config& item = cfg.add_child("hotkey");
|
|
item["command"] = i->get_command();
|
|
|
|
if (i->get_type() == hotkey_item::BY_KEYCODE) {
|
|
item["key"] = SDL_GetKeyName(SDLKey(i->get_keycode()));
|
|
item["shift"] = i->get_shift() ? "yes" : "no";
|
|
} else if (i->get_type() == hotkey_item::BY_CHARACTER) {
|
|
item["key"] = std::string(1, static_cast<char>(i->get_character()));
|
|
}
|
|
item["alt"] = i->get_alt() ? "yes" : "no";
|
|
item["ctrl"] = i->get_ctrl() ? "yes" : "no";
|
|
item["cmd"] = i->get_cmd() ? "yes" : "no";
|
|
}
|
|
}
|
|
|
|
hotkey_item& get_hotkey(HOTKEY_COMMAND id)
|
|
{
|
|
std::vector<hotkey_item>::iterator itor;
|
|
|
|
for (itor = hotkeys_.begin(); itor != hotkeys_.end(); ++itor) {
|
|
if (itor->get_id() == id)
|
|
break;
|
|
}
|
|
|
|
if (itor == hotkeys_.end())
|
|
return null_hotkey_;
|
|
|
|
return *itor;
|
|
}
|
|
|
|
hotkey_item& get_hotkey(const std::string& command)
|
|
{
|
|
std::vector<hotkey_item>::iterator itor;
|
|
|
|
for (itor = hotkeys_.begin(); itor != hotkeys_.end(); ++itor) {
|
|
if (itor->get_command() == command)
|
|
break;
|
|
}
|
|
|
|
if (itor == hotkeys_.end())
|
|
return null_hotkey_;
|
|
|
|
return *itor;
|
|
}
|
|
|
|
hotkey_item& get_hotkey(int character, int keycode, bool shift, bool ctrl, bool alt, bool cmd)
|
|
{
|
|
std::vector<hotkey_item>::iterator itor;
|
|
|
|
DBG_G << "getting hotkey: char=" << lexical_cast<std::string>(character)
|
|
<< " keycode=" << lexical_cast<std::string>(keycode) << " "
|
|
<< (shift ? "shift," : "")
|
|
<< (ctrl ? "ctrl," : "")
|
|
<< (alt ? "alt," : "")
|
|
<< (cmd ? "cmd," : "")
|
|
<< "\n";
|
|
|
|
// Sometimes control modifies by -64, ie ^A == 1.
|
|
if (character < 64 && ctrl) {
|
|
if (shift)
|
|
character += 64;
|
|
else
|
|
character += 96;
|
|
DBG_G << "Mapped to character " << lexical_cast<std::string>(character) << "\n";
|
|
}
|
|
|
|
// For some reason on Mac OS, if cmd and shift are down, the character doesn't get upper-cased
|
|
if (cmd && character > 96 && character < 123 && shift)
|
|
character -= 32;
|
|
|
|
for (itor = hotkeys_.begin(); itor != hotkeys_.end(); ++itor) {
|
|
if (itor->get_type() == hotkey_item::BY_CHARACTER) {
|
|
if (character == itor->get_character()) {
|
|
if (ctrl == itor->get_ctrl()
|
|
&& alt == itor->get_alt()
|
|
&& cmd == itor->get_cmd()) {
|
|
DBG_G << "Could match by character..." << "yes\n";
|
|
break;
|
|
}
|
|
DBG_G << "Could match by character..." << "but modifiers different\n";
|
|
}
|
|
} else if (itor->get_type() == hotkey_item::BY_KEYCODE) {
|
|
if (keycode == itor->get_keycode()) {
|
|
if (shift == itor->get_shift()
|
|
&& ctrl == itor->get_ctrl()
|
|
&& alt == itor->get_alt()
|
|
&& cmd == itor->get_cmd()) {
|
|
DBG_G << "Could match by keycode..." << "yes\n";
|
|
break;
|
|
}
|
|
DBG_G << "Could match by keycode..." << "but modifiers different\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (itor == hotkeys_.end())
|
|
return null_hotkey_;
|
|
|
|
return *itor;
|
|
}
|
|
|
|
hotkey_item& get_hotkey(const SDL_KeyboardEvent& event)
|
|
{
|
|
return get_hotkey(event.keysym.unicode, event.keysym.sym,
|
|
(event.keysym.mod & KMOD_SHIFT) != 0,
|
|
(event.keysym.mod & KMOD_CTRL) != 0,
|
|
(event.keysym.mod & KMOD_ALT) != 0,
|
|
(event.keysym.mod & KMOD_LMETA) != 0
|
|
#ifdef __APPLE__
|
|
|| (event.keysym.mod & KMOD_RMETA) != 0
|
|
#endif
|
|
);
|
|
}
|
|
void _get_visible_hotkey_itor(int index, std::vector<hotkey_item>::iterator& itor)
|
|
{
|
|
int counter = 0;
|
|
for (itor = hotkeys_.begin(); itor != hotkeys_.end(); ++itor) {
|
|
if (itor->hidden())
|
|
continue;
|
|
|
|
if (index == counter)
|
|
break;
|
|
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
hotkey_item& get_visible_hotkey(int index)
|
|
{
|
|
|
|
std::vector<hotkey_item>::iterator itor;
|
|
_get_visible_hotkey_itor(index, itor);
|
|
if (itor == hotkeys_.end())
|
|
return null_hotkey_;
|
|
|
|
return *itor;
|
|
}
|
|
|
|
std::vector<hotkey_item>& get_hotkeys()
|
|
{
|
|
return hotkeys_;
|
|
}
|
|
|
|
basic_handler::basic_handler(display* disp, command_executor* exec) : disp_(disp), exec_(exec) {}
|
|
|
|
void basic_handler::handle_event(const SDL_Event& event)
|
|
{
|
|
if(event.type == SDL_KEYDOWN && disp_ != NULL) {
|
|
|
|
//if we're in a dialog we only want to handle things that are explicitly handled
|
|
//by the executor. If we're not in a dialog we can call the regular key event handler
|
|
if(!gui::in_dialog()) {
|
|
key_event(*disp_,event.key,exec_);
|
|
} else if(exec_ != NULL) {
|
|
key_event_execute(*disp_,event.key,exec_);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void key_event(display& disp, const SDL_KeyboardEvent& event, command_executor* executor)
|
|
{
|
|
if(event.keysym.sym == SDLK_ESCAPE && disp.in_game()) {
|
|
ERR_G << "escape pressed..showing quit\n";
|
|
const int res = gui::dialog(disp,_("Quit"),_("Do you really want to quit?"),gui::YES_NO).show();
|
|
if(res == 0) {
|
|
throw end_level_exception(QUIT);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
key_event_execute(disp,event,executor);
|
|
}
|
|
|
|
void key_event_execute(display& disp, const SDL_KeyboardEvent& event, command_executor* executor)
|
|
{
|
|
const hotkey_item* hk = &get_hotkey(event);
|
|
|
|
#if 0
|
|
// This is not generally possible without knowing keyboard layout.
|
|
if(hk->null()) {
|
|
//no matching hotkey was found, but try an in-exact match.
|
|
hk = &get_hotkey(event, true);
|
|
}
|
|
#endif
|
|
|
|
if(hk->null())
|
|
return;
|
|
|
|
execute_command(disp,hk->get_id(),executor);
|
|
}
|
|
|
|
bool command_executor::execute_command(HOTKEY_COMMAND command, int /*index*/)
|
|
{
|
|
switch(command) {
|
|
case HOTKEY_CYCLE_UNITS:
|
|
cycle_units();
|
|
break;
|
|
case HOTKEY_CYCLE_BACK_UNITS:
|
|
cycle_back_units();
|
|
break;
|
|
case HOTKEY_ENDTURN:
|
|
end_turn();
|
|
break;
|
|
case HOTKEY_UNIT_HOLD_POSITION:
|
|
unit_hold_position();
|
|
break;
|
|
case HOTKEY_END_UNIT_TURN:
|
|
end_unit_turn();
|
|
break;
|
|
case HOTKEY_LEADER:
|
|
goto_leader();
|
|
break;
|
|
case HOTKEY_UNDO:
|
|
undo();
|
|
break;
|
|
case HOTKEY_REDO:
|
|
redo();
|
|
break;
|
|
case HOTKEY_UNIT_DESCRIPTION:
|
|
unit_description();
|
|
break;
|
|
case HOTKEY_RENAME_UNIT:
|
|
rename_unit();
|
|
break;
|
|
case HOTKEY_SAVE_GAME:
|
|
save_game();
|
|
break;
|
|
case HOTKEY_SAVE_REPLAY:
|
|
save_replay();
|
|
break;
|
|
case HOTKEY_SAVE_MAP:
|
|
save_map();
|
|
break;
|
|
case HOTKEY_LOAD_GAME:
|
|
load_game();
|
|
break;
|
|
case HOTKEY_TOGGLE_GRID:
|
|
toggle_grid();
|
|
break;
|
|
case HOTKEY_STATUS_TABLE:
|
|
status_table();
|
|
break;
|
|
case HOTKEY_RECALL:
|
|
recall();
|
|
break;
|
|
case HOTKEY_RECRUIT:
|
|
recruit();
|
|
break;
|
|
case hotkey::HOTKEY_REPEAT_RECRUIT:
|
|
repeat_recruit();
|
|
break;
|
|
case HOTKEY_SPEAK:
|
|
speak();
|
|
break;
|
|
case HOTKEY_SPEAK_ALLY:
|
|
whisper();
|
|
break;
|
|
case HOTKEY_SPEAK_ALL:
|
|
shout();
|
|
break;
|
|
case HOTKEY_CREATE_UNIT:
|
|
create_unit();
|
|
break;
|
|
case HOTKEY_CHANGE_UNIT_SIDE:
|
|
change_unit_side();
|
|
break;
|
|
case HOTKEY_PREFERENCES:
|
|
preferences();
|
|
break;
|
|
case HOTKEY_OBJECTIVES:
|
|
objectives();
|
|
break;
|
|
case HOTKEY_UNIT_LIST:
|
|
unit_list();
|
|
break;
|
|
case HOTKEY_STATISTICS:
|
|
show_statistics();
|
|
break;
|
|
case HOTKEY_LABEL_TEAM_TERRAIN:
|
|
label_terrain(true);
|
|
break;
|
|
case HOTKEY_LABEL_TERRAIN:
|
|
label_terrain(false);
|
|
break;
|
|
case HOTKEY_CLEAR_LABELS:
|
|
clear_labels();
|
|
break;
|
|
case HOTKEY_SHOW_ENEMY_MOVES:
|
|
show_enemy_moves(false);
|
|
break;
|
|
case HOTKEY_BEST_ENEMY_MOVES:
|
|
show_enemy_moves(true);
|
|
break;
|
|
case HOTKEY_DELAY_SHROUD:
|
|
toggle_shroud_updates();
|
|
break;
|
|
case HOTKEY_UPDATE_SHROUD:
|
|
update_shroud_now();
|
|
break;
|
|
case HOTKEY_CONTINUE_MOVE:
|
|
continue_move();
|
|
break;
|
|
case HOTKEY_SEARCH:
|
|
search();
|
|
break;
|
|
case HOTKEY_HELP:
|
|
show_help();
|
|
break;
|
|
case HOTKEY_CHAT_LOG:
|
|
show_chat_log();
|
|
break;
|
|
case HOTKEY_USER_CMD:
|
|
user_command();
|
|
break;
|
|
//%%
|
|
case HOTKEY_CLEAR_MSG:
|
|
clear_messages();
|
|
break;
|
|
#ifdef USRCMD2
|
|
case HOTKEY_USER_CMD_2:
|
|
//user_command();
|
|
user_command_2();
|
|
break;
|
|
case HOTKEY_USER_CMD_3:
|
|
user_command_3();
|
|
break;
|
|
#endif
|
|
|
|
case HOTKEY_EDIT_SET_TERRAIN:
|
|
edit_set_terrain();
|
|
break;
|
|
case HOTKEY_EDIT_QUIT:
|
|
edit_quit();
|
|
break;
|
|
case HOTKEY_EDIT_NEW_MAP:
|
|
edit_new_map();
|
|
break;
|
|
case HOTKEY_EDIT_LOAD_MAP:
|
|
edit_load_map();
|
|
break;
|
|
case HOTKEY_EDIT_SAVE_MAP:
|
|
edit_save_map();
|
|
break;
|
|
case HOTKEY_EDIT_SAVE_AS:
|
|
edit_save_as();
|
|
break;
|
|
case HOTKEY_EDIT_SET_START_POS:
|
|
edit_set_start_pos();
|
|
break;
|
|
case HOTKEY_EDIT_FLOOD_FILL:
|
|
edit_flood_fill();
|
|
break;
|
|
case HOTKEY_EDIT_FILL_SELECTION:
|
|
edit_fill_selection();
|
|
break;
|
|
case HOTKEY_EDIT_ROTATE_SELECTION:
|
|
edit_rotate_selection();
|
|
break;
|
|
case HOTKEY_EDIT_CUT:
|
|
edit_cut();
|
|
break;
|
|
case HOTKEY_EDIT_PASTE:
|
|
edit_paste();
|
|
break;
|
|
case HOTKEY_EDIT_COPY:
|
|
edit_copy();
|
|
break;
|
|
case HOTKEY_EDIT_REVERT:
|
|
edit_revert();
|
|
break;
|
|
case HOTKEY_EDIT_RESIZE:
|
|
edit_resize();
|
|
break;
|
|
case HOTKEY_EDIT_FLIP:
|
|
edit_flip();
|
|
break;
|
|
case HOTKEY_EDIT_SELECT_ALL:
|
|
edit_select_all();
|
|
break;
|
|
case HOTKEY_EDIT_DRAW:
|
|
edit_draw();
|
|
break;
|
|
case HOTKEY_EDIT_REFRESH:
|
|
edit_refresh();
|
|
break;
|
|
case HOTKEY_EDIT_UPDATE:
|
|
edit_update();
|
|
break;
|
|
case HOTKEY_EDIT_AUTO_UPDATE:
|
|
edit_auto_update();
|
|
break;
|
|
|
|
case HOTKEY_LANGUAGE:
|
|
change_language();
|
|
break;
|
|
case HOTKEY_PLAY_REPLAY:
|
|
play_replay();
|
|
break;
|
|
case HOTKEY_RESET_REPLAY:
|
|
reset_replay();
|
|
break;
|
|
case HOTKEY_STOP_REPLAY:
|
|
stop_replay();
|
|
break;
|
|
case HOTKEY_REPLAY_NEXT_TURN:
|
|
replay_next_turn();
|
|
break;
|
|
case HOTKEY_REPLAY_NEXT_SIDE:
|
|
replay_next_side();
|
|
break;
|
|
case HOTKEY_REPLAY_SHOW_EVERYTHING:
|
|
replay_show_everything();
|
|
break;
|
|
case HOTKEY_REPLAY_SHOW_EACH:
|
|
replay_show_each();
|
|
break;
|
|
case HOTKEY_REPLAY_SHOW_TEAM1:
|
|
replay_show_team1();
|
|
break;
|
|
case HOTKEY_REPLAY_SKIP_ANIMATION:
|
|
replay_skip_animation();
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void execute_command(display& disp, HOTKEY_COMMAND command, command_executor* executor, int index)
|
|
{
|
|
const int zoom_amount = 4;
|
|
|
|
if(executor != NULL) {
|
|
if(!executor->can_execute_command(command, index) || executor->execute_command(command, index))
|
|
return;
|
|
}
|
|
switch(command) {
|
|
case HOTKEY_ZOOM_IN:
|
|
disp.set_zoom(zoom_amount);
|
|
break;
|
|
case HOTKEY_ZOOM_OUT:
|
|
disp.set_zoom(-zoom_amount);
|
|
break;
|
|
case HOTKEY_ZOOM_DEFAULT:
|
|
disp.set_default_zoom();
|
|
break;
|
|
case HOTKEY_FULLSCREEN:
|
|
preferences::set_fullscreen(!preferences::fullscreen());
|
|
break;
|
|
case HOTKEY_MOUSE_SCROLL:
|
|
preferences::enable_mouse_scroll(!preferences::mouse_scroll_enabled());
|
|
break;
|
|
case HOTKEY_SCREENSHOT:
|
|
disp.screenshot();
|
|
break;
|
|
case HOTKEY_ACCELERATED:
|
|
preferences::set_turbo(!preferences::turbo());
|
|
break;
|
|
case HOTKEY_MUTE:
|
|
{
|
|
// look if both is not playing
|
|
static struct before_muted_s
|
|
{
|
|
bool playing_sound,playing_music;
|
|
before_muted_s() : playing_sound(false),playing_music(false){}
|
|
} before_muted;
|
|
if (preferences::music_on() || preferences::sound_on())
|
|
{
|
|
//then remember settings and mute both
|
|
before_muted.playing_sound = preferences::sound_on();
|
|
before_muted.playing_music = preferences::music_on();
|
|
preferences::set_sound(false);
|
|
preferences::set_music(false);
|
|
}
|
|
else
|
|
{
|
|
//then set settings before mute
|
|
preferences::set_sound(before_muted.playing_sound);
|
|
preferences::set_music(before_muted.playing_music);
|
|
}
|
|
}
|
|
break;
|
|
case HOTKEY_QUIT_GAME: {
|
|
if(disp.in_game()) {
|
|
ERR_G << "is in game -- showing quit message\n";
|
|
const int res = gui::dialog(disp,_("Quit"),_("Do you really want to quit?"),gui::YES_NO).show();
|
|
if(res == 0) {
|
|
throw end_level_exception(QUIT);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
DBG_G << "command_executor: unknown command number " << command << ", ignoring.\n";
|
|
break;
|
|
}
|
|
}
|
|
|
|
void command_executor::show_menu(const std::vector<std::string>& items_arg, int xloc, int yloc, bool context_menu, display& gui)
|
|
{
|
|
std::vector<std::string> items = items_arg;
|
|
if (can_execute_command(hotkey::get_hotkey(items.front()).get_id(), 0)){
|
|
//if just one item is passed in, that means we should execute that item
|
|
if(!context_menu && items.size() == 1 && items_arg.size() == 1) {
|
|
hotkey::execute_command(gui,hotkey::get_hotkey(items.front()).get_id(),this);
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> menu = get_menu_images(items);
|
|
|
|
int res = 0;
|
|
{
|
|
gui::dialog mmenu = gui::dialog(gui,"","",
|
|
gui::MESSAGE, gui::dialog::hotkeys_style);
|
|
mmenu.set_menu(menu);
|
|
res = mmenu.show(xloc, yloc);
|
|
} // this will kill the dialog
|
|
if (res < 0 || size_t(res) >= items.size())
|
|
return;
|
|
|
|
const hotkey::HOTKEY_COMMAND cmd = hotkey::get_hotkey(items[res]).get_id();
|
|
hotkey::execute_command(gui,cmd,this,res);
|
|
}
|
|
}
|
|
|
|
std::string command_executor::get_menu_image(hotkey::HOTKEY_COMMAND command, int index) const {
|
|
switch(get_action_state(command)) {
|
|
case ACTION_ON: return game_config::checked_menu_image;
|
|
case ACTION_OFF: return game_config::unchecked_menu_image;
|
|
default: return get_action_image(command, index);
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> command_executor::get_menu_images(const std::vector<std::string>& items){
|
|
std::vector<std::string> result;
|
|
bool has_image = false;
|
|
|
|
for(size_t i = 0; i < items.size(); ++i) {
|
|
std::string const& item = items[i];
|
|
const hotkey::hotkey_item hk = hotkey::get_hotkey(item);
|
|
|
|
std::stringstream str;
|
|
//see if this menu item has an associated image
|
|
std::string img(get_menu_image(hk.get_id(), i));
|
|
if(img.empty() == false) {
|
|
has_image = true;
|
|
str << IMAGE_PREFIX << img << COLUMN_SEPARATOR;
|
|
}
|
|
|
|
if (hk.get_id() == hotkey::HOTKEY_NULL) {
|
|
str << item.substr(0, item.find_last_not_of(' ') + 1) << COLUMN_SEPARATOR;
|
|
} else {
|
|
str << hk.get_description() << COLUMN_SEPARATOR << hk.get_name();
|
|
}
|
|
|
|
result.push_back(str.str());
|
|
}
|
|
//If any of the menu items have an image, create an image column
|
|
if(has_image)
|
|
for(std::vector<std::string>::iterator i = result.begin(); i != result.end(); ++i)
|
|
if(*(i->begin()) != IMAGE_PREFIX)
|
|
i->insert(i->begin(), COLUMN_SEPARATOR);
|
|
return result;
|
|
}
|
|
|
|
}
|