[WIP] Implement IME support for GUI2 textboxes (#1758)
Implement IME support for GUI2 textboxes
This commit is contained in:
parent
8b0c9af52d
commit
5d43078ba4
14 changed files with 349 additions and 67 deletions
|
@ -68,6 +68,15 @@
|
|||
color = "([255, 255, 255, cursor_alpha])"
|
||||
thickness = 1
|
||||
[/line]
|
||||
|
||||
[rectangle]
|
||||
x = "(composition_offset + {X_OFFSET})"
|
||||
y = "(text_y_offset + text_font_height - 2)"
|
||||
w = "(composition_width)"
|
||||
h = "2"
|
||||
fill_color = "([140, 140, 0, if(composition_width > 0, 255, 0)])"
|
||||
border_thickness = 0
|
||||
[/rectangle]
|
||||
#enddef
|
||||
|
||||
#define _GUI_RESOLUTION RESOLUTION MIN_WIDTH DEFAULT_WIDTH HEIGHT X_OFFSET EXTRA_WIDTH FONT_SIZE BACKGROUND_ENABLED BACKGROUND_DISABLED
|
||||
|
|
|
@ -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>(
|
||||
|
@ -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()));
|
||||
|
|
|
@ -115,10 +115,10 @@ 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,
|
||||
|
@ -126,6 +126,19 @@ typedef std::function<void(dispatcher& dispatcher,
|
|||
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;
|
||||
|
@ -267,6 +280,22 @@ public:
|
|||
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.
|
||||
*
|
||||
|
@ -569,6 +598,39 @@ public:
|
|||
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.
|
||||
*
|
||||
|
@ -776,6 +838,9 @@ private:
|
|||
/** 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_;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "gui/widgets/settings.hpp"
|
||||
#include "gui/widgets/widget.hpp"
|
||||
#include "gui/widgets/window.hpp"
|
||||
#include "gui/widgets/text_box_base.hpp"
|
||||
|
||||
#include "utils/functional.hpp"
|
||||
|
||||
|
@ -563,6 +564,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));
|
||||
|
||||
|
@ -574,6 +581,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));
|
||||
}
|
||||
|
@ -634,13 +647,12 @@ 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
|
||||
|
@ -648,16 +660,17 @@ void distributor::signal_handler_sdl_key_down(const SDL_Keycode key,
|
|||
// 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;
|
||||
}
|
||||
}
|
||||
if(text_box_base* tb = dynamic_cast<text_box_base*>(keyboard_focus_)) {
|
||||
if(tb->is_composing()) {
|
||||
return; // Skip the keyboard chain if composition is in progress.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(std::vector<widget*>::reverse_iterator ritor
|
||||
|
@ -691,14 +704,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)
|
||||
{
|
||||
|
|
|
@ -356,6 +356,12 @@ private:
|
|||
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);
|
||||
};
|
||||
|
||||
|
|
|
@ -279,6 +279,15 @@ private:
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -929,6 +957,12 @@ std::ostream& operator<<(std::ostream& stream, const ui_event event)
|
|||
case SDL_KEY_DOWN:
|
||||
stream << "SDL key down";
|
||||
break;
|
||||
case SDL_TEXT_INPUT:
|
||||
stream << "SDL text input";
|
||||
break;
|
||||
case SDL_TEXT_EDITING:
|
||||
stream << "SDL text editing";
|
||||
break;
|
||||
|
||||
case NOTIFY_REMOVAL:
|
||||
stream << "notify removal";
|
||||
|
|
|
@ -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, /**<
|
||||
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -247,10 +247,7 @@ private:
|
|||
bool history_down();
|
||||
|
||||
/** Inherited from text_box_base. */
|
||||
void handle_key_default(bool& handled,
|
||||
SDL_Keycode key,
|
||||
SDL_Keymod modifier,
|
||||
const utf8::string& unicode) override;
|
||||
void handle_key_tab(SDL_Keymod modifier, bool& handled) override;
|
||||
|
||||
/** Inherited from text_box_base. */
|
||||
void handle_key_clear_line(SDL_Keymod modifier, bool& handled) override;
|
||||
|
|
|
@ -38,6 +38,7 @@ text_box_base::text_box_base()
|
|||
, text_()
|
||||
, selection_start_(0)
|
||||
, selection_length_(0)
|
||||
, ime_in_progress_(false)
|
||||
, cursor_timer_(0)
|
||||
, cursor_alpha_(0)
|
||||
, cursor_blink_rate_ms_(750)
|
||||
|
@ -51,8 +52,10 @@ text_box_base::text_box_base()
|
|||
#endif
|
||||
|
||||
connect_signal<event::SDL_KEY_DOWN>(std::bind(
|
||||
&text_box_base::signal_handler_sdl_key_down, this, _2, _3, _5, _6, _7));
|
||||
|
||||
&text_box_base::signal_handler_sdl_key_down, this, _2, _3, _5, _6));
|
||||
connect_signal<event::SDL_TEXT_INPUT>(std::bind(&text_box_base::handle_commit, this, _3, _5));
|
||||
connect_signal<event::SDL_TEXT_EDITING>(std::bind(&text_box_base::handle_editing, this, _3, _5, _6, _7));
|
||||
|
||||
connect_signal<event::RECEIVE_KEYBOARD_FOCUS>(std::bind(
|
||||
&text_box_base::signal_handler_receive_keyboard_focus, this, _2));
|
||||
connect_signal<event::LOSE_KEYBOARD_FOCUS>(
|
||||
|
@ -160,6 +163,15 @@ void text_box_base::insert_char(const utf8::string& unicode)
|
|||
}
|
||||
}
|
||||
|
||||
void text_box_base::interrupt_composition()
|
||||
{
|
||||
ime_in_progress_ = false;
|
||||
ime_length_ = 0;
|
||||
// We need to inform the IME that text input is no longer in progress.
|
||||
SDL_StopTextInput();
|
||||
SDL_StartTextInput();
|
||||
}
|
||||
|
||||
void text_box_base::copy_selection(const bool mouse)
|
||||
{
|
||||
if(selection_length_ == 0) {
|
||||
|
@ -386,17 +398,60 @@ void text_box_base::handle_key_delete(SDL_Keymod /*modifier*/, bool& handled)
|
|||
fire(event::NOTIFY_MODIFIED, *this, nullptr);
|
||||
}
|
||||
|
||||
void text_box_base::handle_key_default(bool& handled,
|
||||
SDL_Keycode /*key*/,
|
||||
SDL_Keymod /*modifier*/,
|
||||
const utf8::string& unicode)
|
||||
void text_box_base::handle_commit(bool& handled, const utf8::string& unicode)
|
||||
{
|
||||
DBG_GUI_E << LOG_SCOPE_HEADER << '\n';
|
||||
|
||||
if(unicode.size() > 1 || unicode[0] != 0) {
|
||||
handled = true;
|
||||
if(ime_in_progress_) {
|
||||
selection_start_ = ime_start_point_;
|
||||
selection_length_ = ime_length_;
|
||||
ime_in_progress_ = false;
|
||||
ime_length_ = 0;
|
||||
}
|
||||
insert_char(unicode);
|
||||
fire(event::NOTIFY_MODIFIED, *this, nullptr);
|
||||
|
||||
if(text_changed_callback_) {
|
||||
text_changed_callback_(this, this->text());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void text_box_base::handle_editing(bool& handled, const utf8::string& unicode, int32_t start, int32_t len)
|
||||
{
|
||||
if(unicode.size() > 1 || unicode[0] != 0) {
|
||||
handled = true;
|
||||
size_t new_len = utf8::size(unicode);
|
||||
if(!ime_in_progress_) {
|
||||
ime_in_progress_ = true;
|
||||
delete_selection();
|
||||
ime_start_point_ = selection_start_;
|
||||
text_cached_ = text_.text();
|
||||
SDL_Rect rect = get_rectangle();
|
||||
if(new_len > 0) {
|
||||
rect.x += get_cursor_position(ime_start_point_).x;
|
||||
rect.w = get_cursor_position(ime_start_point_ + new_len).x - rect.x;
|
||||
} else {
|
||||
rect.x += get_cursor_position(ime_start_point_ + new_len).x;
|
||||
rect.w = get_cursor_position(ime_start_point_).x - rect.x;
|
||||
}
|
||||
SDL_SetTextInputRect(&rect);
|
||||
}
|
||||
ime_cursor_ = start;
|
||||
ime_length_ = new_len;
|
||||
std::string new_text(text_cached_);
|
||||
new_text.insert(ime_start_point_, unicode);
|
||||
text_.set_text(new_text, false);
|
||||
|
||||
// Update status
|
||||
set_cursor(ime_start_point_ + ime_cursor_, false);
|
||||
if(len > 0) {
|
||||
set_cursor(ime_start_point_ + ime_cursor_ + len, true);
|
||||
}
|
||||
update_canvas();
|
||||
set_is_dirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -413,8 +468,7 @@ void text_box_base::signal_handler_middle_button_click(const event::ui_event eve
|
|||
void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
|
||||
bool& handled,
|
||||
const SDL_Keycode key,
|
||||
SDL_Keymod modifier,
|
||||
const utf8::string& unicode)
|
||||
SDL_Keymod modifier)
|
||||
{
|
||||
|
||||
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
|
||||
|
@ -456,8 +510,7 @@ void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
|
|||
|
||||
case SDLK_a:
|
||||
if(!(modifier & KMOD_CTRL)) {
|
||||
handle_key_default(handled, key, modifier, unicode);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
// If ctrl-a is used for home drop the styled_widget modifier
|
||||
|
@ -470,8 +523,7 @@ void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
|
|||
|
||||
case SDLK_e:
|
||||
if(!(modifier & KMOD_CTRL)) {
|
||||
handle_key_default(handled, key, modifier, unicode);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
// If ctrl-e is used for end drop the styled_widget modifier
|
||||
|
@ -487,11 +539,10 @@ void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
|
|||
break;
|
||||
|
||||
case SDLK_u:
|
||||
if(modifier & KMOD_CTRL) {
|
||||
handle_key_clear_line(modifier, handled);
|
||||
} else {
|
||||
handle_key_default(handled, key, modifier, unicode);
|
||||
if(!(modifier & KMOD_CTRL)) {
|
||||
return;
|
||||
}
|
||||
handle_key_clear_line(modifier, handled);
|
||||
break;
|
||||
|
||||
case SDLK_DELETE:
|
||||
|
@ -500,8 +551,7 @@ void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
|
|||
|
||||
case SDLK_c:
|
||||
if(!(modifier & copypaste_modifier)) {
|
||||
handle_key_default(handled, key, modifier, unicode);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
// atm we don't care whether there is something to copy or paste
|
||||
|
@ -512,8 +562,7 @@ void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
|
|||
|
||||
case SDLK_x:
|
||||
if(!(modifier & copypaste_modifier)) {
|
||||
handle_key_default(handled, key, modifier, unicode);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
copy_selection(false);
|
||||
|
@ -523,16 +572,33 @@ void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
|
|||
|
||||
case SDLK_v:
|
||||
if(!(modifier & copypaste_modifier)) {
|
||||
handle_key_default(handled, key, modifier, unicode);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
paste_selection(false);
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
case SDLK_RETURN:
|
||||
case SDLK_KP_ENTER:
|
||||
if(!ime_in_progress_ || (modifier & (KMOD_CTRL | KMOD_ALT | KMOD_GUI | KMOD_SHIFT))) {
|
||||
return;
|
||||
}
|
||||
// The IME will handle it, we just need to make sure nothing else handles it too.
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
case SDLK_ESCAPE:
|
||||
if(!ime_in_progress_ || (modifier & (KMOD_CTRL | KMOD_ALT | KMOD_GUI | KMOD_SHIFT))) {
|
||||
return;
|
||||
}
|
||||
interrupt_composition();
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
handle_key_default(handled, key, modifier, unicode);
|
||||
// Don't call the text changed callback if nothing happened.
|
||||
return;
|
||||
}
|
||||
|
||||
if(text_changed_callback_) {
|
||||
|
|
|
@ -250,6 +250,23 @@ protected:
|
|||
}
|
||||
void set_selection_length(const int selection_length);
|
||||
|
||||
size_t get_composition_start() const
|
||||
{
|
||||
return ime_start_point_;
|
||||
}
|
||||
|
||||
size_t get_composition_length() const
|
||||
{
|
||||
return ime_length_;
|
||||
}
|
||||
|
||||
public:
|
||||
bool is_composing() const
|
||||
{
|
||||
return ime_in_progress_;
|
||||
}
|
||||
|
||||
void interrupt_composition();
|
||||
|
||||
private:
|
||||
/** Note the order of the states must be the same as defined in
|
||||
|
@ -280,6 +297,9 @@ private:
|
|||
/** The text entered in the widget. */
|
||||
font::pango_text text_;
|
||||
|
||||
/** Cached version of the text without any pending IME modifications. */
|
||||
std::string text_cached_;
|
||||
|
||||
/** Start of the selected text. */
|
||||
size_t selection_start_;
|
||||
|
||||
|
@ -292,6 +312,12 @@ private:
|
|||
*/
|
||||
int selection_length_;
|
||||
|
||||
// Values to support input method editors
|
||||
bool ime_in_progress_;
|
||||
int ime_start_point_;
|
||||
int ime_cursor_;
|
||||
int ime_length_;
|
||||
|
||||
size_t cursor_timer_;
|
||||
|
||||
unsigned short cursor_alpha_;
|
||||
|
@ -441,23 +467,24 @@ private:
|
|||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Default key handler if none of the above functions is called.
|
||||
* Tab key.
|
||||
*
|
||||
* Unmodified If invalid unicode it's ignored.
|
||||
* Else if text selected the selected text is
|
||||
* replaced with the unicode character send.
|
||||
* Else the unicode character is inserted after
|
||||
* the cursor.
|
||||
* Control Ignored.
|
||||
* Shift Ignored (already in the unicode value).
|
||||
* Alt Ignored.
|
||||
* Unmodified Implementation defined.
|
||||
* Control Implementation defined.
|
||||
* Shift Implementation defined.
|
||||
* Alt Implementation defined.
|
||||
*/
|
||||
virtual void handle_key_default(bool& handled,
|
||||
SDL_Keycode key,
|
||||
SDL_Keymod modifier,
|
||||
virtual void handle_key_tab(SDL_Keymod /*modifier*/, bool& /*handled*/)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void handle_commit(bool& handled,
|
||||
const utf8::string& unicode);
|
||||
virtual void handle_editing(bool& handled,
|
||||
const utf8::string& unicode,
|
||||
int32_t start, int32_t len);
|
||||
|
||||
private:
|
||||
/**
|
||||
|
@ -479,8 +506,13 @@ private:
|
|||
void signal_handler_sdl_key_down(const event::ui_event event,
|
||||
bool& handled,
|
||||
const SDL_Keycode key,
|
||||
SDL_Keymod modifier,
|
||||
const utf8::string& unicode);
|
||||
SDL_Keymod modifier);
|
||||
|
||||
void signal_handler_sdl_text_input(const event::ui_event event,
|
||||
bool& handled,
|
||||
const utf8::string& unicode,
|
||||
int32_t start,
|
||||
int32_t len);
|
||||
|
||||
void signal_handler_receive_keyboard_focus(const event::ui_event event);
|
||||
void signal_handler_lose_keyboard_focus(const event::ui_event event);
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "gui/dialogs/tooltip.hpp"
|
||||
#include "gui/widgets/button.hpp"
|
||||
#include "gui/widgets/container_base.hpp"
|
||||
#include "gui/widgets/text_box_base.hpp"
|
||||
#include "gui/core/register_widget.hpp"
|
||||
#include "gui/widgets/grid.hpp"
|
||||
#include "gui/widgets/helper.hpp"
|
||||
|
@ -687,6 +688,10 @@ int window::show(const bool restore, const unsigned auto_close_timeout)
|
|||
font::undraw_floating_labels(video_.getSurface());
|
||||
}
|
||||
|
||||
if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
|
||||
tb->interrupt_composition();
|
||||
}
|
||||
|
||||
return retval_;
|
||||
}
|
||||
|
||||
|
@ -1405,6 +1410,15 @@ void window::signal_handler_sdl_key_down(const event::ui_event event,
|
|||
{
|
||||
DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
|
||||
|
||||
if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
|
||||
if(tb->is_composing()) {
|
||||
if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
|
||||
tb->interrupt_composition();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!enter_disabled_ && (key == SDLK_KP_ENTER || key == SDLK_RETURN)) {
|
||||
set_retval(OK);
|
||||
handled = true;
|
||||
|
|
|
@ -30,7 +30,6 @@ bool CKey::is_uncomposable(const SDL_KeyboardEvent &event) {
|
|||
case SDLK_RETURN:
|
||||
case SDLK_ESCAPE:
|
||||
case SDLK_BACKSPACE:
|
||||
case SDLK_BACKQUOTE:
|
||||
case SDLK_TAB:
|
||||
case SDLK_F1:
|
||||
case SDLK_F2:
|
||||
|
|
Loading…
Add table
Reference in a new issue