Skip to content

Commit

Permalink
Implement IME support for GUI2 textboxes
Browse files Browse the repository at this point in the history
  • Loading branch information
CelticMinstrel committed Jun 5, 2017
1 parent e6fce88 commit a2c65bf
Show file tree
Hide file tree
Showing 14 changed files with 326 additions and 67 deletions.
9 changes: 9 additions & 0 deletions data/gui/widget/text_box_default.cfg
Expand Up @@ -68,6 +68,15 @@
color = "([255, 255, 255, cursor_alpha])"
thickness = 1
[/line]

[line]
x1 = "(composition_offset + {X_OFFSET})"
y1 = "(text_y_offset + text_font_height - 2)"
x2 = "(composition_offset + {X_OFFSET} + composition_width)"
y2 = "(text_y_offset + text_font_height - 2)"
color = "([140, 140, 0, if(composition_width > 0, 255, 0)])"
thickness = 2
[/line]
#enddef

#define _GUI_RESOLUTION RESOLUTION MIN_WIDTH DEFAULT_WIDTH HEIGHT X_OFFSET EXTRA_WIDTH FONT_SIZE BACKGROUND_ENABLED BACKGROUND_DISABLED
Expand Down
8 changes: 8 additions & 0 deletions src/gui/core/event/dispatcher.cpp
Expand Up @@ -81,6 +81,8 @@ bool dispatcher::has_event(const ui_event event, const event_queue_type event_ty
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_keyboard>(
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_text_input>(
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_touch>(
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_notification>(
Expand Down Expand Up @@ -158,6 +160,12 @@ bool dispatcher::fire(const ui_event event, widget& target, const SDL_Event& sdl
return fire_event<signal_raw_event_function>(event, this, &target, sdlevent);
}

bool dispatcher::fire(const ui_event event, widget& target, const std::string& text, int32_t start, int32_t len)
{
assert(find<set_event_text_input>(event, event_in_set()));
return fire_event<signal_text_input_function>(event, this, &target, text, start, len);
}

bool dispatcher::fire(const ui_event event, widget& target, const point& pos, const point& distance)
{
assert(find<set_event_touch>(event, event_in_set()));
Expand Down
69 changes: 67 additions & 2 deletions src/gui/core/event/dispatcher.hpp
Expand Up @@ -115,17 +115,30 @@ 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.
* This function is used for the callbacks in set_event_raw_event.
*/
typedef std::function<void(dispatcher& dispatcher,
const ui_event event,
bool& handled,
bool& halt,
const SDL_Event& sdlevent)> signal_raw_event_function;

/**
* Callback function signature.
*
* This function is used for the callbacks in set_event_text_input.
*/
typedef std::function<void(dispatcher& dispatcher,
const ui_event event,
bool& handled,
bool& halt,
const std::string& text,
int32_t current_pos,
int32_t select_len)> signal_text_input_function;

/** Hotkey function handler signature. */
typedef std::function<bool(dispatcher& dispatcher,
hotkey::HOTKEY_COMMAND id)> hotkey_function;
Expand Down Expand Up @@ -267,6 +280,22 @@ class dispatcher
widget& target,
const SDL_Event& sdlevent);

/**
* Fires an event which takes text input parameters
* @param event The event to fire.
* @param target The widget that should receive the event.
* Normally this is the window holding the
* widget.
* @param text The text involved in the event
* @param start The start point for IME editing
* @param len The selection length for IME editing
*/
bool fire(const ui_event event,
widget& target,
const std::string& text,
int32_t start,
int32_t len);

/**
* The position where to add a new callback in the signal handler.
*
Expand Down Expand Up @@ -569,6 +598,39 @@ class dispatcher
signal_raw_event_queue_.disconnect_signal(E, position, signal);
}

/**
* Connect a signal for callback in set_text_input.
*
* @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>
utils::enable_if_t<has_key<set_event_text_input, E>::value>
connect_signal(const signal_text_input_function& signal,
const queue_position position = back_child)
{
signal_text_input_queue_.connect_signal(E, position, signal);
}

/**
* Disconnect a signal for callback in set_text_input.
*
* @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>
utils::enable_if_t<has_key<set_event_text_input, E>::value>
disconnect_signal(const signal_text_input_function& signal,
const queue_position position = back_child)
{
signal_text_input_queue_.disconnect_signal(E, position, signal);
}

/**
* The behavior of the mouse events.
*
Expand Down Expand Up @@ -776,6 +838,9 @@ class dispatcher
/** Signal queue for callbacks in set_raw_event. */
signal_queue<signal_raw_event_function> signal_raw_event_queue_;

/** Signal queue for callbacks in set_event_text_input. */
signal_queue<signal_text_input_function> signal_text_input_queue_;

/** Are we connected to the event handler. */
bool connected_;

Expand Down
1 change: 1 addition & 0 deletions src/gui/core/event/dispatcher_private.hpp
Expand Up @@ -110,6 +110,7 @@ struct dispatcher_implementation
IMPLEMENT_EVENT_SIGNAL_WRAPPER(notification)
IMPLEMENT_EVENT_SIGNAL_WRAPPER(message)
IMPLEMENT_EVENT_SIGNAL_WRAPPER(raw_event)
IMPLEMENT_EVENT_SIGNAL_WRAPPER(text_input)

#undef IMPLEMENT_EVENT_SIGNAL_WRAPPER
#undef IMPLEMENT_EVENT_SIGNAL
Expand Down
46 changes: 34 additions & 12 deletions src/gui/core/event/distributor.cpp
Expand Up @@ -563,6 +563,12 @@ distributor::distributor(widget& owner,
owner_.connect_signal<event::SDL_KEY_DOWN>(std::bind(
&distributor::signal_handler_sdl_key_down, this, _5, _6, _7));

owner_.connect_signal<event::SDL_TEXT_INPUT>(std::bind(
&distributor::signal_handler_sdl_text_input, this, _5, _6, _7));

owner_.connect_signal<event::SDL_TEXT_EDITING>(std::bind(
&distributor::signal_handler_sdl_text_editing, this, _5, _6, _7));

owner_.connect_signal<event::NOTIFY_REMOVAL>(std::bind(
&distributor::signal_handler_notify_removal, this, _1, _2));

Expand All @@ -574,6 +580,12 @@ distributor::~distributor()
owner_.disconnect_signal<event::SDL_KEY_DOWN>(std::bind(
&distributor::signal_handler_sdl_key_down, this, _5, _6, _7));

owner_.disconnect_signal<event::SDL_TEXT_INPUT>(std::bind(
&distributor::signal_handler_sdl_text_input, this, _5, _6, _7));

owner_.disconnect_signal<event::SDL_TEXT_EDITING>(std::bind(
&distributor::signal_handler_sdl_text_editing, this, _5, _6, _7));

owner_.disconnect_signal<event::NOTIFY_REMOVAL>(std::bind(
&distributor::signal_handler_notify_removal, this, _1, _2));
}
Expand Down Expand Up @@ -634,27 +646,22 @@ void distributor::keyboard_remove_from_chain(widget* w)
}
}

void distributor::signal_handler_sdl_key_down(const SDL_Keycode key,
const SDL_Keymod modifier,
const utf8::string& unicode)
template<typename Fcn, typename P1, typename P2, typename P3>
void distributor::signal_handler_keyboard_internal(event::ui_event evt, P1&& p1, P2&& p2, P3&& p3)
{
/** @todo Test whether recursion protection is needed. */

DBG_GUI_E << LOG_HEADER << event::SDL_KEY_DOWN << ".\n";
DBG_GUI_E << LOG_HEADER << evt << ".\n";

if(keyboard_focus_) {
// Attempt to cast to styled_widget, to avoid sending events if the
// widget is disabled. If the cast fails, we assume the widget
// is enabled and ready to receive events.
styled_widget* control = dynamic_cast<styled_widget*>(keyboard_focus_);
if(!control || control->get_active()) {
DBG_GUI_E << LOG_HEADER << "Firing: " << event::SDL_KEY_DOWN
DBG_GUI_E << LOG_HEADER << "Firing: " << evt
<< ".\n";
if(owner_.fire(event::SDL_KEY_DOWN,
*keyboard_focus_,
key,
modifier,
unicode)) {
if(owner_.fire(evt, *keyboard_focus_, p1, p2, p3)) {
return;
}
}
Expand Down Expand Up @@ -691,14 +698,29 @@ void distributor::signal_handler_sdl_key_down(const SDL_Keycode key,
continue;
}

DBG_GUI_E << LOG_HEADER << "Firing: " << event::SDL_KEY_DOWN << ".\n";
if(owner_.fire(event::SDL_KEY_DOWN, **ritor, key, modifier, unicode)) {
DBG_GUI_E << LOG_HEADER << "Firing: " << evt << ".\n";
if(owner_.fire(evt, **ritor, p1, p2, p3)) {

return;
}
}
}

void distributor::signal_handler_sdl_key_down(const SDL_Keycode key, const SDL_Keymod modifier, const utf8::string& unicode)
{
signal_handler_keyboard_internal<signal_keyboard_function>(event::SDL_KEY_DOWN, key, modifier, unicode);
}

void distributor::signal_handler_sdl_text_input(const utf8::string& unicode, int32_t start, int32_t end)
{
signal_handler_keyboard_internal<signal_text_input_function>(event::SDL_TEXT_INPUT, unicode, start, end);
}

void distributor::signal_handler_sdl_text_editing(const utf8::string& unicode, int32_t start, int32_t end)
{
signal_handler_keyboard_internal<signal_text_input_function>(event::SDL_TEXT_EDITING, unicode, start, end);
}

void distributor::signal_handler_notify_removal(dispatcher& w,
const ui_event event)
{
Expand Down
6 changes: 6 additions & 0 deletions src/gui/core/event/distributor.hpp
Expand Up @@ -356,6 +356,12 @@ class distributor :
const SDL_Keymod modifier,
const utf8::string& unicode);

void signal_handler_sdl_text_input(const utf8::string& unicode, int32_t start, int32_t len);
void signal_handler_sdl_text_editing(const utf8::string& unicode, int32_t start, int32_t len);

template<typename Fcn, typename P1, typename P2, typename P3>
void signal_handler_keyboard_internal(event::ui_event evt, P1&& p1, P2&& p2, P3&& p3);

void signal_handler_notify_removal(dispatcher& widget, const ui_event event);
};

Expand Down
28 changes: 28 additions & 0 deletions src/gui/core/event/handler.cpp
Expand Up @@ -279,6 +279,15 @@ class sdl_event_handler : public events::sdl_handler
*/
void text_input(const std::string& unicode);

/**
* Fires a text editing event.
*
* @param unicode The unicode value for the text being edited.
* @param start The start position for the text being edited.
* @param len The selection length for the text being edited.
*/
void text_editing(const std::string& unicode, int32_t start, int32_t len);

/**
* Fires a keyboard event which has no parameters.
*
Expand Down Expand Up @@ -424,6 +433,10 @@ void sdl_event_handler::handle_event(const SDL_Event& event)
text_input(event.text.text);
break;

case SDL_TEXTEDITING:
text_editing(event.edit.text, event.edit.start, event.edit.length);
break;

case SDL_FINGERMOTION:
touch_motion(point(event.tfinger.x, event.tfinger.y), point(event.tfinger.dx, event.tfinger.dy));
break;
Expand Down Expand Up @@ -719,6 +732,21 @@ void sdl_event_handler::key_down(const SDL_Event& event)
void sdl_event_handler::text_input(const std::string& unicode)
{
key_down(SDLK_UNKNOWN, static_cast<SDL_Keymod>(0), unicode);

if(dispatcher* dispatcher = keyboard_dispatcher()) {
dispatcher->fire(SDL_TEXT_INPUT,
dynamic_cast<widget&>(*dispatcher),
unicode, -1, -1);
}
}

void sdl_event_handler::text_editing(const std::string& unicode, int32_t start, int32_t end)
{
if(dispatcher* dispatcher = keyboard_dispatcher()) {
dispatcher->fire(SDL_TEXT_EDITING,
dynamic_cast<widget&>(*dispatcher),
unicode, start, end);
}
}

bool sdl_event_handler::hotkey_pressed(const hotkey::hotkey_ptr key)
Expand Down
11 changes: 11 additions & 0 deletions src/gui/core/event/handler.hpp
Expand Up @@ -89,6 +89,8 @@ enum ui_event {
SDL_WHEEL_UP, /**< An SDL wheel up event. */
SDL_WHEEL_DOWN, /**< An SDL wheel down event. */
SDL_KEY_DOWN, /**< An SDL key down event. */
SDL_TEXT_INPUT, /**< An SDL text input (commit) event. */
SDL_TEXT_EDITING, /**< An SDL text editing (IME) event. */

NOTIFY_REMOVAL, /**< Sent by a widget to notify others it's being destroyed. */
NOTIFY_MODIFIED, /**<
Expand Down Expand Up @@ -222,6 +224,15 @@ set_event_message;
*/
typedef boost::mpl::set<boost::mpl::int_<SDL_RAW_EVENT> > set_event_raw_event;

/**
* Helper for catching use error of dispatcher::connect_signal.
*
* This version is for callbacks of text input events.
*/
typedef boost::mpl::set<boost::mpl::int_<SDL_TEXT_INPUT>,
boost::mpl::int_<SDL_TEXT_EDITING>>
set_event_text_input;

/**
* Connects a dispatcher to the event handler.
*
Expand Down
32 changes: 22 additions & 10 deletions src/gui/widgets/text_box.cpp
Expand Up @@ -137,6 +137,10 @@ void text_box::update_canvas()
const unsigned start = get_selection_start();
const int length = get_selection_length();

// Set the cursor info.
const unsigned edit_start = get_composition_start();
const int edit_length = get_composition_length();

set_maximum_length(max_input_length_);

PangoEllipsizeMode ellipse_mode = PANGO_ELLIPSIZE_NONE;
Expand All @@ -162,6 +166,19 @@ void text_box::update_canvas()
end_offset = get_cursor_position(start).x;
}

// Set the composition info
unsigned comp_start_offset = 0;
unsigned comp_end_offset = 0;
if(edit_length == 0) {
// No nothing.
} else if(edit_length > 0) {
comp_start_offset = get_cursor_position(edit_start).x;
comp_end_offset = get_cursor_position(edit_start + edit_length).x;
} else {
comp_start_offset = get_cursor_position(edit_start + edit_length).x;
comp_end_offset = get_cursor_position(edit_start).x;
}

/***** Set in all canvases *****/

const int max_width = get_text_maximum_width();
Expand All @@ -182,6 +199,9 @@ void text_box::update_canvas()
tmp.set_variable("selection_offset", wfl::variant(start_offset));
tmp.set_variable("selection_width", wfl::variant(end_offset - start_offset));
tmp.set_variable("text_wrap_mode", wfl::variant(ellipse_mode));

tmp.set_variable("composition_offset", wfl::variant(comp_start_offset));
tmp.set_variable("composition_width", wfl::variant(comp_end_offset - comp_start_offset));
}
}

Expand Down Expand Up @@ -298,23 +318,15 @@ bool text_box::history_down()
return true;
}

void text_box::handle_key_default(bool& handled,
SDL_Keycode key,
SDL_Keymod modifier,
const utf8::string& unicode)
void text_box::handle_key_tab(SDL_Keymod modifier, bool& handled)
{
if(key == SDLK_TAB && (modifier & KMOD_CTRL)) {
if(modifier & KMOD_CTRL) {
if(!(modifier & KMOD_SHIFT)) {
handled = history_up();
} else {
handled = history_down();
}
}

if(!handled) {
// Inherited.
text_box_base::handle_key_default(handled, key, modifier, unicode);
}
}

void text_box::handle_key_clear_line(SDL_Keymod /*modifier*/, bool& handled)
Expand Down

0 comments on commit a2c65bf

Please sign in to comment.