Merge pull request #1003 from wesnoth/composed_hotkeys

Composed hotkeys
This commit is contained in:
Andreas 2017-04-29 14:52:44 +12:00 committed by GitHub
commit af1326d19b
15 changed files with 308 additions and 111 deletions

View file

@ -83,7 +83,7 @@
[/hotkey]
[hotkey]
command=command
key=;
key=:
[/hotkey]
[hotkey]
command=continue

View file

@ -55,6 +55,11 @@ void controller_base::handle_event(const SDL_Event& event)
static const hotkey::hotkey_command& quit_hotkey = hotkey::hotkey_command::get_command_by_command(hotkey::HOTKEY_QUIT_GAME);
switch(event.type) {
case SDL_TEXTINPUT:
if(have_keyboard_focus()) {
hotkey::key_event(event, get_hotkey_command_executor());
}
break;
case SDL_KEYDOWN:
// Detect key press events, unless there something that has keyboard focus
// in which case the key press events should go only to it.

View file

@ -96,7 +96,10 @@ bool dispatcher::has_event(const ui_event event, const event_queue_type event_ty
*this))
|| find<set_event_message>(event,
dispatcher_implementation::has_handler(
event_type, *this));
event_type, *this))
|| find<set_event_raw_event>(event,
dispatcher_implementation::has_handler(
event_type, *this));
}
/**
@ -250,6 +253,39 @@ bool dispatcher::fire(const ui_event event,
trigger_keyboard(key, modifier, unicode));
}
/** Helper struct to wrap the functor call. */
class trigger_raw_event
{
public:
trigger_raw_event(const SDL_Event& sdlevent) : sdl_event_(sdlevent)
{
}
void operator()(signal_raw_event_function functor,
dispatcher& dispatcher,
const ui_event event,
bool& handled,
bool& halt)
{
functor(dispatcher, event, handled, halt, sdl_event_);
}
private:
const SDL_Event& sdl_event_;
};
bool dispatcher::fire(const ui_event event,
widget& target,
const SDL_Event& sdlevent)
{
assert(find<set_event_raw_event>(event, event_in_set()));
return fire_event<signal_raw_event_function>(
event,
dynamic_cast<widget*>(this),
&target,
trigger_raw_event(sdlevent));
}
/** Helper struct to wrap the functor call. */
class trigger_touch
{

View file

@ -49,9 +49,9 @@ struct message;
*
* This function is used for the callbacks in set_event.
*/
typedef std::function<void(
dispatcher& dispatcher, const ui_event event, bool& handled, bool& halt)>
signal_function;
typedef std::function<void(dispatcher& dispatcher,
const ui_event event,
bool& handled, bool& halt)> signal_function;
/**
* Callback function signature.
@ -75,8 +75,7 @@ typedef std::function<void(dispatcher& dispatcher,
bool& halt,
const SDL_Keycode key,
const SDL_Keymod modifier,
const utf8::string& unicode)>
signal_keyboard_function;
const utf8::string& unicode)> signal_keyboard_function;
/**
* Callback function signature.
@ -88,8 +87,7 @@ typedef std::function<void(dispatcher& dispatcher,
bool& handled,
bool& halt,
const point& pos,
const point& distance)>
signal_touch_function;
const point& distance)> signal_touch_function;
/**
* Callback function signature.
@ -115,6 +113,17 @@ typedef std::function<void(dispatcher& dispatcher,
bool& halt,
message& message)> signal_message_function;
/**
* Callback function signature.
*
* This function is used for the callbacks in set_event_message.
*/
typedef std::function<void(dispatcher& dispatcher,
const ui_event event,
bool& handled,
bool& halt,
const SDL_Event& sdlevent)> signal_raw_event_function;
/** Hotkey function handler signature. */
typedef std::function<bool(dispatcher& dispatcher,
hotkey::HOTKEY_COMMAND id)> thotkey_function;
@ -185,7 +194,9 @@ public:
* @param target The widget that should receive the event.
* @param coordinate The mouse position for the event.
*/
bool fire(const ui_event event, widget& target, const point& coordinate);
bool fire(const ui_event event,
widget& target,
const point& coordinate);
/**
* Fires an event which takes keyboard parameters.
@ -223,7 +234,9 @@ public:
* @param event The event to fire.
* @param target The widget that should receive the event.
*/
bool fire(const ui_event event, widget& target, void*);
bool fire(const ui_event event,
widget& target,
void*);
/**
* Fires an event which takes message parameters.
@ -236,7 +249,21 @@ public:
* (or another widget in the chain) to handle
* the message.
*/
bool fire(const ui_event event, widget& target, message& msg);
bool fire(const ui_event event,
widget& target,
message& msg);
/**
* Fires an event that's a raw SDL event
* @param event The event to fire.
* @param target The widget that should receive the event.
* Normally this is the window holding the
* widget.
* @param sdlevent The raw SDL event
*/
bool fire(const ui_event event,
widget& target,
const SDL_Event& sdlevent);
/**
* The position where to add a new callback in the signal handler.
@ -507,6 +534,39 @@ public:
signal_message_queue_.disconnect_signal(E, position, signal);
}
/**
* Connect a signal for callback in set_raw_event.
*
* @tparam E The event the callback needs to react to.
* @param signal The callback function.
* @param position The position to place the callback.
*/
template <ui_event E>
typename std::enable_if<has_key<set_event_raw_event, E>::value>::type
connect_signal(const signal_raw_event_function& signal,
const queue_position position = back_child)
{
signal_raw_event_queue_.connect_signal(E, position, signal);
}
/**
* Disconnect a signal for callback in set_raw_event.
*
* @tparam E The event the callback was used for.
* @param signal The callback function.
* @param position The place where the function was added.
* Needed remove the event from the right
* place. (The function doesn't care whether
* was added in front or back.)
*/
template <ui_event E>
typename std::enable_if<has_key<set_event_raw_event, E>::value>::type
disconnect_signal(const signal_raw_event_function& signal,
const queue_position position = back_child)
{
signal_raw_event_queue_.disconnect_signal(E, position, signal);
}
/**
* The behavior of the mouse events.
*
@ -733,6 +793,9 @@ private:
/** Signal queue for callbacks in set_event_message. */
signal_queue<signal_message_function> signal_message_queue_;
/** Signal queue for callbacks in set_raw_event. */
signal_queue<signal_raw_event_function> signal_raw_event_queue_;
/** Are we connected to the event handler. */
bool connected_;

View file

@ -110,6 +110,7 @@ struct dispatcher_implementation
IMPLEMENT_EVENT_SIGNAL_WRAPPER(touch)
IMPLEMENT_EVENT_SIGNAL_WRAPPER(notification)
IMPLEMENT_EVENT_SIGNAL_WRAPPER(message)
IMPLEMENT_EVENT_SIGNAL_WRAPPER(raw_event)
#undef IMPLEMENT_EVENT_SIGNAL_WRAPPER
#undef IMPLEMENT_EVENT_SIGNAL

View file

@ -164,6 +164,9 @@ private:
/***** Handlers *****/
/** Fires a raw SDL event. */
void raw_event(const SDL_Event &event);
/** Fires a draw event. */
using events::sdl_handler::draw;
void draw(const bool force);
@ -445,6 +448,8 @@ void sdl_event_handler::handle_event(const SDL_Event& event)
#endif
break;
}
raw_event(event);
}
void sdl_event_handler::handle_window_event(const SDL_Event& event)
@ -559,6 +564,15 @@ void sdl_event_handler::video_resize(const point& new_size)
}
}
void sdl_event_handler::raw_event(const SDL_Event& event) {
DBG_GUI_E << "Firing raw event\n";
for(auto dispatcher : dispatchers_)
{
dispatcher->fire(SDL_RAW_EVENT, dynamic_cast<widget&>(*dispatcher), event);
}
}
void sdl_event_handler::mouse(const ui_event event, const point& position)
{
DBG_GUI_E << "Firing: " << event << ".\n";
@ -961,6 +975,9 @@ std::ostream& operator<<(std::ostream& stream, const ui_event event)
case SDL_TOUCH_DOWN:
stream << "SDL touch down";
break;
case SDL_RAW_EVENT:
stream << "SDL raw event";
break;
}
return stream;

View file

@ -113,7 +113,9 @@ enum ui_event {
SDL_TOUCH_MOTION,
SDL_TOUCH_UP,
SDL_TOUCH_DOWN
SDL_TOUCH_DOWN,
SDL_RAW_EVENT /**< Raw SDL event. */
};
/**
@ -213,6 +215,13 @@ typedef boost::mpl::set<boost::mpl::int_<MESSAGE_SHOW_TOOLTIP>,
boost::mpl::int_<REQUEST_PLACEMENT> >
set_event_message;
/**
* Helper for catching use error of dispatcher::connect_signal.
*
* This version is for callbacks of raw events.
*/
typedef boost::mpl::set<boost::mpl::int_<SDL_RAW_EVENT> > set_event_raw_event;
/**
* Connects a dispatcher to the event handler.
*

View file

@ -36,37 +36,20 @@ hotkey_bind::hotkey_bind(const std::string& hotkey_id)
void hotkey_bind::pre_show(window& window)
{
connect_signal_pre_key_press(window, std::bind(&hotkey_bind::key_press_callback, this, std::ref(window), _5));
window.connect_signal<event::SDL_RAW_EVENT>(
std::bind(&hotkey_bind::sdl_event_callback, this, std::ref(window), _5),
event::dispatcher::front_child);
window.connect_signal<event::SDL_LEFT_BUTTON_DOWN>(
std::bind(&hotkey_bind::mouse_button_callback, this, std::ref(window), SDL_BUTTON_LEFT), event::dispatcher::front_child);
window.connect_signal<event::SDL_MIDDLE_BUTTON_DOWN>(
std::bind(&hotkey_bind::mouse_button_callback, this, std::ref(window), SDL_BUTTON_MIDDLE), event::dispatcher::front_child);
window.connect_signal<event::SDL_RIGHT_BUTTON_DOWN>(
std::bind(&hotkey_bind::mouse_button_callback, this, std::ref(window), SDL_BUTTON_RIGHT), event::dispatcher::front_child);
}
void hotkey_bind::key_press_callback(window& window, const SDL_Keycode key)
void hotkey_bind::sdl_event_callback(window& win, const SDL_Event &event)
{
/* HACK: SDL_KEYDOWN and SDL_TEXTINPUT events forward to the same GUI2 event (SDL_KEY_DOWN), meaning
* this even gets fired twice, causing problems since 'key' will be 0 in the latter case. SDLK_UNKNOWN
* is the key value used by SDL_TEXTINPUT handling, so exit here if that's detected.
*/
if(key == SDLK_UNKNOWN) {
return;
if (hotkey::is_hotkeyable_event(event)) {
new_binding_ = hotkey::create_hotkey(hotkey_id_, event);
win.set_retval(window::OK);
}
new_binding_ = hotkey::create_hotkey(hotkey_id_, SDL_GetScancodeFromKey(key));
window.set_retval(window::OK);
}
void hotkey_bind::mouse_button_callback(window& window, Uint8 button)
{
new_binding_ = hotkey::create_hotkey(hotkey_id_, button);
window.set_retval(window::OK);
}
} // namespace dialogs
} // namespace gui2

View file

@ -41,9 +41,7 @@ private:
hotkey::hotkey_ptr new_binding_;
void key_press_callback(window& window, const SDL_Keycode key);
void mouse_button_callback(window& window, Uint8 button);
void sdl_event_callback(window& win, const SDL_Event &event);
/** Inherited from modal_dialog, implemented by REGISTER_DIALOG. */
virtual const std::string& window_id() const override;

View file

@ -546,7 +546,10 @@ static void event_execute( const SDL_Event& event, command_executor* executor)
return;
}
bool press = event.type == SDL_KEYDOWN || event.type == SDL_JOYBUTTONDOWN || event.type == SDL_MOUSEBUTTONDOWN;
bool press = event.type == SDL_KEYDOWN ||
event.type == SDL_JOYBUTTONDOWN ||
event.type == SDL_MOUSEBUTTONDOWN ||
event.type == SDL_TEXTINPUT;
execute_command(hotkey::get_hotkey_command(hk->get_command()), executor, -1, press);
executor->set_button_state();

View file

@ -20,18 +20,12 @@
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gettext.hpp"
#include "serialization/unicode.hpp"
#include "sdl/surface.hpp"
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include "utils/functional.hpp"
#include "key.hpp"
#include <SDL.h>
#include <key.hpp>
static lg::log_domain log_config("config");
@ -128,17 +122,11 @@ bool hotkey_base::bindings_equal(hotkey_ptr other)
bool hotkey_base::matches(const SDL_Event &event) const
{
unsigned int mods = sdl_get_mods();
if (!hotkey::is_scope_active(hotkey::get_hotkey_command(get_command()).scope) ||
!active() || is_disabled()) {
return false;
}
if ((mods != mod_)) {
return false;
}
return matches_helper(event);
}
@ -155,30 +143,41 @@ void hotkey_base::save(config& item) const
save_helper(item);
}
hotkey_ptr create_hotkey(const std::string& id, SDL_Scancode new_val)
hotkey_ptr create_hotkey(const std::string &id, const SDL_Event &event)
{
hotkey_ptr base = hotkey_ptr(new hotkey_void);
unsigned mods = sdl_get_mods();
hotkey_keyboard_ptr keyboard(new hotkey_keyboard());
base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
keyboard->set_scancode(new_val);
base->set_mods(sdl_get_mods());
base->set_command(id);
base->unset_default();
return base;
}
hotkey_ptr create_hotkey(const std::string& id, Uint8 new_val)
{
hotkey_ptr base = hotkey_ptr(new hotkey_void);
hotkey_mouse_ptr mouse(new hotkey_mouse());
base = std::dynamic_pointer_cast<hotkey_base>(mouse);
mouse->set_button(new_val);
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP: {
if (mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI || CKey::is_uncomposable(event.key)) {
hotkey_keyboard_ptr keyboard(new hotkey_keyboard());
base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
SDL_Keycode code;
code = event.key.keysym.sym;
keyboard->set_keycode(code);
keyboard->set_text(SDL_GetKeyName(event.key.keysym.sym));
}
}
break;
case SDL_TEXTINPUT: {
hotkey_keyboard_ptr keyboard(new hotkey_keyboard());
base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
keyboard->set_text(std::string(event.text.text));
}
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP: {
hotkey_mouse_ptr mouse(new hotkey_mouse());
base = std::dynamic_pointer_cast<hotkey_base>(mouse);
mouse->set_button(event.button.button);
break;
}
default:
ERR_G<< "Trying to bind an unknown event type:" << event.type << "\n";
break;
}
base->set_mods(sdl_get_mods());
base->set_command(id);
@ -220,11 +219,12 @@ hotkey_ptr load_from_config(const config& cfg)
hotkey_keyboard_ptr keyboard(new hotkey_keyboard());
base = std::dynamic_pointer_cast<hotkey_base>(keyboard);
SDL_Scancode scancode = SDL_GetScancodeFromName(key_cfg.c_str());
if (scancode == SDL_SCANCODE_UNKNOWN) {
SDL_Keycode keycode = SDL_GetKeyFromName(key_cfg.c_str());
if (keycode == SDLK_UNKNOWN) {
ERR_G<< "Unknown key: " << key_cfg << "\n";
}
keyboard->set_scancode(scancode);
keyboard->set_text(key_cfg);
keyboard->set_keycode(keycode);
}
if (base == hotkey_ptr()) {
@ -256,6 +256,11 @@ bool hotkey_mouse::matches_helper(const SDL_Event &event) const
return false;
}
unsigned int mods = sdl_get_mods();
if ((mods != mod_)) {
return false;
}
if (event.button.button != button_) {
return false;
}
@ -278,29 +283,29 @@ void hotkey_mouse::save_helper(config &item) const
const std::string hotkey_keyboard::get_name_helper() const
{
std::string ret = std::string(SDL_GetKeyName(SDL_GetKeyFromScancode(scancode_)));
if (ret.size() == 1) {
boost::algorithm::to_lower(ret);
}
return ret;
return text_;
}
bool hotkey_keyboard::matches_helper(const SDL_Event &event) const
{
if (event.type != SDL_KEYDOWN && event.type != SDL_KEYUP) {
return false;
unsigned int mods = sdl_get_mods();
if ((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) &&
(mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI ||
CKey::is_uncomposable(event.key))) {
return event.key.keysym.sym == keycode_ && mods == mod_;
}
SDL_Scancode code;
code = event.key.keysym.scancode;
if (code != scancode_) {
return false;
if (event.type == SDL_TEXTINPUT) {
std::string text = std::string(event.text.text);
boost::algorithm::to_lower(text);
if (text == ":") {
mods = mods & ~KMOD_SHIFT;
}
return text_ == text && mods == mod_;
}
return true;
return false;
}
bool hotkey_mouse::bindings_equal_helper(hotkey_ptr other) const
@ -316,8 +321,8 @@ bool hotkey_mouse::bindings_equal_helper(hotkey_ptr other) const
void hotkey_keyboard::save_helper(config &item) const
{
if (scancode_ != SDL_SCANCODE_UNKNOWN) {
item["key"] = SDL_GetScancodeName(scancode_);
if (keycode_ != SDLK_UNKNOWN) {
item["key"] = SDL_GetKeyName(keycode_);
}
}
@ -340,7 +345,7 @@ bool hotkey_keyboard::bindings_equal_helper(hotkey_ptr other) const
return false;
}
return scancode_ == other_k->scancode_;
return keycode_ == other_k->keycode_;
}
void del_hotkey(hotkey_ptr item)
@ -470,4 +475,22 @@ std::string get_names(const std::string& id)
return boost::algorithm::join(names, ", ");
}
bool is_hotkeyable_event(const SDL_Event &event) {
if (event.type == SDL_JOYBUTTONUP ||
event.type == SDL_JOYHATMOTION ||
event.type == SDL_MOUSEBUTTONUP) {
return true;
}
unsigned mods = sdl_get_mods();
if (mods & KMOD_CTRL || mods & KMOD_ALT || mods & KMOD_GUI) {
return event.type == SDL_KEYUP;
} else {
return event.type == SDL_TEXTINPUT ||
(event.type == SDL_KEYUP && CKey::is_uncomposable(event.key));
}
}
}

View file

@ -21,6 +21,7 @@
#include <memory>
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
class config;
class CVideo;
@ -241,16 +242,22 @@ public:
/**
* Initialise new instance of this class that has no key associated with is.
*/
hotkey_keyboard() : hotkey_base(), scancode_(SDL_SCANCODE_UNKNOWN)
hotkey_keyboard() : hotkey_base(), keycode_(SDLK_UNKNOWN), text_("")
{}
/**
* Set the scancode associated with this class.
* @param scancode The SDL_Scancode that this hotkey should be associated with
* Set the keycode associated with this class.
* @param keycode_ The SDL_Keycode that this hotkey should be associated with
*/
void set_scancode(SDL_Scancode scancode)
void set_keycode(SDL_Keycode keycode)
{
scancode_ = scancode;
keycode_ = keycode;
}
void set_text(std::string text)
{
text_ = text;
boost::algorithm::to_lower(text_);
}
/**
@ -259,11 +266,12 @@ public:
*/
virtual bool valid() const
{
return scancode_ != SDL_SCANCODE_UNKNOWN;
return keycode_ != SDLK_UNKNOWN && text_ != "";
}
protected:
SDL_Scancode scancode_;
SDL_Keycode keycode_;
std::string text_;
virtual void save_helper(config& cfg) const;
virtual const std::string get_name_helper() const;
@ -371,11 +379,12 @@ void add_hotkey(const hotkey_ptr item);
*/
void del_hotkey(const hotkey_ptr item);
/** Create a new hotkey item bound to a keyboard key. */
hotkey_ptr create_hotkey(const std::string& id, SDL_Scancode new_val);
/** Create a new hotkey item bound to a mouse button. */
hotkey_ptr create_hotkey(const std::string& id, Uint8 new_val);
/**
* Create a new hotkey item for a command from an SDL_Event.
* @param id The command to bind to.
* @param event The SDL_Event to base the creation on.
*/
hotkey_ptr create_hotkey(const std::string &id, const SDL_Event &event);
/**
* Iterate through the list of hotkeys and return a hotkey that matches
@ -429,7 +438,7 @@ std::string get_names(const std::string& id);
*/
void save_hotkeys(config& cfg);
hotkey_ptr show_binding_dialog(CVideo& video, const std::string& id);
bool is_hotkeyable_event(const SDL_Event &event);
}

View file

@ -14,8 +14,6 @@
#include "key.hpp"
#include <SDL_keyboard.h>
CKey::CKey() :
key_list(SDL_GetKeyboardState(nullptr))
{
@ -25,3 +23,51 @@ bool CKey::operator[](int k) const
{
return key_list[SDL_GetScancodeFromKey(k)] > 0;
}
bool CKey::is_uncomposable(const SDL_KeyboardEvent &event) {
switch (event.keysym.sym) {
case SDLK_RETURN:
case SDLK_ESCAPE:
case SDLK_BACKSPACE:
case SDLK_TAB:
case SDLK_F1:
case SDLK_F2:
case SDLK_F3:
case SDLK_F4:
case SDLK_F5:
case SDLK_F6:
case SDLK_F7:
case SDLK_F8:
case SDLK_F9:
case SDLK_F10:
case SDLK_F11:
case SDLK_F12:
case SDLK_F13:
case SDLK_F14:
case SDLK_F15:
case SDLK_F16:
case SDLK_F17:
case SDLK_F18:
case SDLK_F19:
case SDLK_F20:
case SDLK_F21:
case SDLK_F22:
case SDLK_F23:
case SDLK_F24:
case SDLK_INSERT:
case SDLK_HOME:
case SDLK_PAGEUP:
case SDLK_PAGEDOWN:
case SDLK_DELETE:
case SDLK_END:
case SDLK_UP:
case SDLK_DOWN:
case SDLK_LEFT:
case SDLK_RIGHT:
return true;
default:
return false;
}
}

View file

@ -16,6 +16,7 @@
#define KEY_HPP_INCLUDED
#include <cstdint>
#include <SDL.h>
/**
* Class that keeps track of all the keys on the keyboard.
@ -31,6 +32,7 @@ class CKey
public:
CKey();
bool operator[](int k) const;
static bool is_uncomposable(const SDL_KeyboardEvent &event);
};
#endif

View file

@ -1015,6 +1015,8 @@ int main(int argc, char** argv)
//declare this here so that it will always be at the front of the event queue.
events::event_context global_context;
SDL_StartTextInput();
try {
std::cerr << "Battle for Wesnoth v" << game_config::revision << '\n';
const time_t t = time(nullptr);