clutter: Implement ClutterInputFocus subclass for ClutterText

This only applies when the actor is editable. This object will allow
editable ClutterText instances to interact with the input method.
This commit is contained in:
Carlos Garnacho 2017-12-06 12:49:34 +01:00
parent aa6561a3b1
commit 3a914a915e

View File

@ -63,6 +63,7 @@
#include "clutter-units.h" #include "clutter-units.h"
#include "clutter-paint-volume-private.h" #include "clutter-paint-volume-private.h"
#include "clutter-scriptable.h" #include "clutter-scriptable.h"
#include "clutter-input-focus.h"
/* cursor width in pixels */ /* cursor width in pixels */
#define DEFAULT_CURSOR_SIZE 2 #define DEFAULT_CURSOR_SIZE 2
@ -98,6 +99,12 @@ struct _LayoutCache
guint age; guint age;
}; };
struct _ClutterTextInputFocus
{
ClutterInputFocus parent_instance;
ClutterText *text;
};
struct _ClutterTextPrivate struct _ClutterTextPrivate
{ {
PangoFontDescription *font_desc; PangoFontDescription *font_desc;
@ -176,6 +183,8 @@ struct _ClutterTextPrivate
/* Signal handler for when the :text-direction changes */ /* Signal handler for when the :text-direction changes */
guint direction_changed_id; guint direction_changed_id;
ClutterInputFocus *input_focus;
/* bitfields */ /* bitfields */
guint alignment : 2; guint alignment : 2;
guint wrap : 1; guint wrap : 1;
@ -269,6 +278,111 @@ static const ClutterColor default_selected_text_color = { 0, 0, 0, 255 };
static ClutterAnimatableIface *parent_animatable_iface = NULL; static ClutterAnimatableIface *parent_animatable_iface = NULL;
static ClutterScriptableIface *parent_scriptable_iface = NULL; static ClutterScriptableIface *parent_scriptable_iface = NULL;
/* ClutterTextInputFocus */
#define CLUTTER_TYPE_TEXT_INPUT_FOCUS (clutter_text_input_focus_get_type ())
G_DECLARE_FINAL_TYPE (ClutterTextInputFocus, clutter_text_input_focus,
CLUTTER, TEXT_INPUT_FOCUS, ClutterInputFocus)
G_DEFINE_TYPE (ClutterTextInputFocus, clutter_text_input_focus,
CLUTTER_TYPE_INPUT_FOCUS)
static void
clutter_text_input_focus_request_surrounding (ClutterInputFocus *focus)
{
ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text;
ClutterTextBuffer *buffer;
const gchar *text;
gint anchor_pos, cursor_pos;
buffer = clutter_text_get_buffer (clutter_text);
text = clutter_text_buffer_get_text (buffer);
cursor_pos = clutter_text_get_cursor_position (clutter_text);
if (cursor_pos < 0)
cursor_pos = clutter_text_buffer_get_length (buffer);
anchor_pos = clutter_text_get_selection_bound (clutter_text);
if (anchor_pos < 0)
anchor_pos = cursor_pos;
clutter_input_focus_set_surrounding (focus, text,
g_utf8_offset_to_pointer (text, cursor_pos) - text,
g_utf8_offset_to_pointer (text, anchor_pos) - text);
}
static void
clutter_text_input_focus_delete_surrounding (ClutterInputFocus *focus,
guint offset,
guint len)
{
ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text;
if (clutter_text_get_editable (clutter_text))
clutter_text_delete_text (clutter_text, offset, len);
}
static void
clutter_text_input_focus_commit_text (ClutterInputFocus *focus,
const gchar *text)
{
ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text;
if (clutter_text_get_editable (clutter_text))
{
clutter_text_delete_selection (clutter_text);
clutter_text_insert_text (clutter_text, text,
clutter_text_get_cursor_position (clutter_text));
}
}
static void
clutter_text_input_focus_set_preedit_text (ClutterInputFocus *focus,
const gchar *preedit_text,
guint cursor_pos)
{
ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text;
if (clutter_text_get_editable (clutter_text))
{
PangoAttrList *list;
list = pango_attr_list_new ();
pango_attr_list_insert (list, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE));
clutter_text_set_preedit_string (clutter_text,
preedit_text, list,
cursor_pos);
pango_attr_list_unref (list);
}
}
static void
clutter_text_input_focus_class_init (ClutterTextInputFocusClass *klass)
{
ClutterInputFocusClass *focus_class = CLUTTER_INPUT_FOCUS_CLASS (klass);
focus_class->request_surrounding = clutter_text_input_focus_request_surrounding;
focus_class->delete_surrounding = clutter_text_input_focus_delete_surrounding;
focus_class->commit_text = clutter_text_input_focus_commit_text;
focus_class->set_preedit_text = clutter_text_input_focus_set_preedit_text;
}
static void
clutter_text_input_focus_init (ClutterTextInputFocus *focus)
{
}
static ClutterInputFocus *
clutter_text_input_focus_new (ClutterText *text)
{
ClutterTextInputFocus *focus;
focus = g_object_new (CLUTTER_TYPE_TEXT_INPUT_FOCUS, NULL);
focus->text = text;
return CLUTTER_INPUT_FOCUS (focus);
}
/* ClutterText */
static void clutter_scriptable_iface_init (ClutterScriptableIface *iface); static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);
static void clutter_animatable_iface_init (ClutterAnimatableIface *iface); static void clutter_animatable_iface_init (ClutterAnimatableIface *iface);
@ -1009,6 +1123,22 @@ clutter_text_position_to_coords (ClutterText *self,
return TRUE; return TRUE;
} }
static inline void
update_cursor_location (ClutterText *self)
{
ClutterTextPrivate *priv = self->priv;
ClutterRect rect;
float x, y;
if (!priv->editable)
return;
rect = priv->cursor_rect;
clutter_actor_get_transformed_position (CLUTTER_ACTOR (self), &x, &y);
clutter_rect_offset (&rect, x, y);
clutter_input_focus_set_cursor_location (priv->input_focus, &rect);
}
static inline void static inline void
clutter_text_ensure_cursor_position (ClutterText *self) clutter_text_ensure_cursor_position (ClutterText *self)
{ {
@ -1057,6 +1187,8 @@ clutter_text_ensure_cursor_position (ClutterText *self)
g_signal_emit (self, text_signals[CURSOR_EVENT], 0, &cursor_pos); g_signal_emit (self, text_signals[CURSOR_EVENT], 0, &cursor_pos);
g_signal_emit (self, text_signals[CURSOR_CHANGED], 0); g_signal_emit (self, text_signals[CURSOR_CHANGED], 0);
update_cursor_location (self);
} }
} }
@ -1503,6 +1635,8 @@ clutter_text_finalize (GObject *gobject)
clutter_text_set_buffer (self, NULL); clutter_text_set_buffer (self, NULL);
g_free (priv->font_name); g_free (priv->font_name);
g_clear_object (&priv->input_focus);
G_OBJECT_CLASS (clutter_text_parent_class)->finalize (gobject); G_OBJECT_CLASS (clutter_text_parent_class)->finalize (gobject);
} }
@ -2085,9 +2219,11 @@ clutter_text_key_press (ClutterActor *actor,
g_assert (pool != NULL); g_assert (pool != NULL);
/* we allow passing synthetic events that only contain /* we allow passing synthetic events that only contain
* the Unicode value and not the key symbol * the Unicode value and not the key symbol, unless they
* contain the input method flag.
*/ */
if (event->keyval == 0 && (event->flags & CLUTTER_EVENT_FLAG_SYNTHETIC)) if (event->keyval == 0 && (event->flags & CLUTTER_EVENT_FLAG_SYNTHETIC) &&
!(event->flags & CLUTTER_EVENT_FLAG_INPUT_METHOD))
res = FALSE; res = FALSE;
else else
res = clutter_binding_pool_activate (pool, event->keyval, res = clutter_binding_pool_activate (pool, event->keyval,
@ -2105,6 +2241,9 @@ clutter_text_key_press (ClutterActor *actor,
{ {
gunichar key_unichar; gunichar key_unichar;
if (clutter_input_focus_filter_key_event (priv->input_focus, event))
return CLUTTER_EVENT_STOP;
/* Skip keys when control is pressed */ /* Skip keys when control is pressed */
key_unichar = clutter_event_get_key_unicode ((ClutterEvent *) event); key_unichar = clutter_event_get_key_unicode ((ClutterEvent *) event);
@ -2141,6 +2280,19 @@ clutter_text_key_press (ClutterActor *actor,
return CLUTTER_EVENT_PROPAGATE; return CLUTTER_EVENT_PROPAGATE;
} }
static gboolean
clutter_text_key_release (ClutterActor *actor,
ClutterKeyEvent *event)
{
ClutterText *self = CLUTTER_TEXT (actor);
ClutterTextPrivate *priv = self->priv;
if (clutter_input_focus_filter_key_event (priv->input_focus, event))
return CLUTTER_EVENT_STOP;
return CLUTTER_EVENT_PROPAGATE;
}
static void static void
clutter_text_compute_layout_offsets (ClutterText *self, clutter_text_compute_layout_offsets (ClutterText *self,
PangoLayout *layout, PangoLayout *layout,
@ -2663,6 +2815,11 @@ static void
clutter_text_key_focus_in (ClutterActor *actor) clutter_text_key_focus_in (ClutterActor *actor)
{ {
ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv; ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv;
ClutterBackend *backend = clutter_get_default_backend ();
ClutterInputMethod *method = clutter_backend_get_input_method (backend);
if (method && priv->editable)
clutter_input_method_focus_in (method, priv->input_focus);
priv->has_focus = TRUE; priv->has_focus = TRUE;
@ -2673,9 +2830,14 @@ static void
clutter_text_key_focus_out (ClutterActor *actor) clutter_text_key_focus_out (ClutterActor *actor)
{ {
ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv; ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv;
ClutterBackend *backend = clutter_get_default_backend ();
ClutterInputMethod *method = clutter_backend_get_input_method (backend);
priv->has_focus = FALSE; priv->has_focus = FALSE;
if (priv->editable && clutter_input_focus_is_focused (priv->input_focus))
clutter_input_method_focus_out (method);
clutter_text_queue_redraw (actor); clutter_text_queue_redraw (actor);
} }
@ -3369,6 +3531,7 @@ clutter_text_class_init (ClutterTextClass *klass)
actor_class->get_preferred_height = clutter_text_get_preferred_height; actor_class->get_preferred_height = clutter_text_get_preferred_height;
actor_class->allocate = clutter_text_allocate; actor_class->allocate = clutter_text_allocate;
actor_class->key_press_event = clutter_text_key_press; actor_class->key_press_event = clutter_text_key_press;
actor_class->key_release_event = clutter_text_key_release;
actor_class->button_press_event = clutter_text_button_press; actor_class->button_press_event = clutter_text_button_press;
actor_class->button_release_event = clutter_text_button_release; actor_class->button_release_event = clutter_text_button_release;
actor_class->motion_event = clutter_text_motion; actor_class->motion_event = clutter_text_motion;
@ -4169,6 +4332,8 @@ clutter_text_init (ClutterText *self)
g_signal_connect (self, "notify::text-direction", g_signal_connect (self, "notify::text-direction",
G_CALLBACK (clutter_text_direction_changed_cb), G_CALLBACK (clutter_text_direction_changed_cb),
NULL); NULL);
priv->input_focus = clutter_text_input_focus_new (self);
} }
/** /**
@ -4459,6 +4624,8 @@ void
clutter_text_set_editable (ClutterText *self, clutter_text_set_editable (ClutterText *self,
gboolean editable) gboolean editable)
{ {
ClutterBackend *backend = clutter_get_default_backend ();
ClutterInputMethod *method = clutter_backend_get_input_method (backend);
ClutterTextPrivate *priv; ClutterTextPrivate *priv;
g_return_if_fail (CLUTTER_IS_TEXT (self)); g_return_if_fail (CLUTTER_IS_TEXT (self));
@ -4469,6 +4636,14 @@ clutter_text_set_editable (ClutterText *self,
{ {
priv->editable = editable; priv->editable = editable;
if (method)
{
if (!priv->editable && clutter_input_focus_is_focused (priv->input_focus))
clutter_input_method_focus_out (method);
else if (priv->has_focus)
clutter_input_method_focus_in (method, priv->input_focus);
}
clutter_text_queue_redraw (CLUTTER_ACTOR (self)); clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_EDITABLE]); g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_EDITABLE]);