diff --git a/configure.ac b/configure.ac index 88ca8715b..736051f9c 100644 --- a/configure.ac +++ b/configure.ac @@ -57,7 +57,7 @@ PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-unix-2.0 gtk+-2.0 dbus-glib-1 mutter-plugin gnome-desktop-2.0 >= 2.26 libstartup-notification-1.0 gobject-introspection-1.0 >= 0.6.5) PKG_CHECK_MODULES(TIDY, clutter-1.0) -PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 clutter-imcontext-0.1 libcroco-0.6) +PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 libcroco-0.6) PKG_CHECK_MODULES(BIG, clutter-1.0 gtk+-2.0 librsvg-2.0) PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0) PKG_CHECK_MODULES(TRAY, gtk+-2.0) diff --git a/src/Makefile-st.am b/src/Makefile-st.am index 6ac755a70..9cb69f552 100644 --- a/src/Makefile-st.am +++ b/src/Makefile-st.am @@ -74,6 +74,7 @@ st_source_h = \ st/st-button.h \ st/st-clipboard.h \ st/st-entry.h \ + st/st-im-text.h \ st/st-label.h \ st/st-private.h \ st/st-scrollable.h \ @@ -109,6 +110,7 @@ st_source_c = \ st/st-button.c \ st/st-clipboard.c \ st/st-entry.c \ + st/st-im-text.c \ st/st-label.c \ st/st-private.c \ st/st-scrollable.c \ diff --git a/src/st/st-entry.c b/src/st/st-entry.c index c3f57d6a2..450f4cc01 100644 --- a/src/st/st-entry.c +++ b/src/st/st-entry.c @@ -53,10 +53,10 @@ #include #include -#include #include "st-entry.h" +#include "st-im-text.h" #include "st-widget.h" #include "st-texture-cache.h" #include "st-marshal.h" @@ -645,7 +645,7 @@ st_entry_init (StEntry *entry) priv = entry->priv = ST_ENTRY_GET_PRIVATE (entry); - priv->entry = g_object_new (CLUTTER_TYPE_IMTEXT, + priv->entry = g_object_new (ST_TYPE_IM_TEXT, "line-alignment", PANGO_ALIGN_LEFT, "editable", TRUE, "reactive", TRUE, diff --git a/src/st/st-im-text.c b/src/st/st-im-text.c new file mode 100644 index 000000000..aa94b4058 --- /dev/null +++ b/src/st/st-im-text.c @@ -0,0 +1,477 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * st-im-text.c + * + * This started as a copy of ClutterIMText converted to use + * GtkIMContext rather than ClutterIMContext. Original code: + * + * Author: raymond liu + * + * Copyright (C) 2009, Intel Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/** + * SECTION:StIMText + * @short_description: Text widget with input method support + * @stability: Unstable + * @see_also: #ClutterText + * @include: st-imtext/st-imtext.h + * + * #StIMText derives from ClutterText and hooks up better text input + * via #GtkIMContext. It is meant to be a drop-in replacement for + * ClutterIMText but using GtkIMContext rather than ClutterIMContext. + */ + +/* Places where this actor doesn't support all of GtkIMContext: + * + * A) It doesn't support preedit. This makes it fairly useless for + * most complicated input methods. Fixing this requires support + * directly in ClutterText, since there is no way to wedge a + * preedit string in externally. + * B) It doesn't support surrounding context via the + * :retrieve-surrounding and :delete-surrounding signals. This could + * be added here, but only affects a small number of input methods + * and really doesn't make a lot of sense without A) + * + * Another problem that will show up with usage in GNOME Shell's overview + * is that the user may have trouble seeing and interacting with ancilliary + * windows shown by the IM. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "st-im-text.h" + +#define ST_IM_TEXT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_IM_TEXT, StIMTextPrivate)) + +struct _StIMTextPrivate +{ + GtkIMContext *im_context; + GdkWindow *window; + + guint need_im_reset : 1; +}; + +static void st_im_text_commit_cb (GtkIMContext *context, + const gchar *str, + StIMText *imtext); + +G_DEFINE_TYPE (StIMText, st_im_text, CLUTTER_TYPE_TEXT) + +static void +st_im_text_dispose (GObject *object) +{ + StIMTextPrivate *priv = ST_IM_TEXT (object)->priv; + + g_signal_handlers_disconnect_by_func (priv->im_context, + (void *) st_im_text_commit_cb, + object); + + g_object_unref (priv->im_context); + priv->im_context = NULL; +} + +static void +update_im_cursor_location (StIMText *self) +{ + StIMTextPrivate *priv = self->priv; + ClutterText *clutter_text = CLUTTER_TEXT (self); + ClutterActor *parent; + gint position; + gfloat cursor_x, cursor_y, cursor_height; + gfloat actor_x, actor_y; + GdkRectangle area; + + position = clutter_text_get_cursor_position (clutter_text); + clutter_text_position_to_coords (clutter_text, position, + &cursor_x, &cursor_y, &cursor_height); + + /* This is a workaround for a bug in Clutter where + * clutter_actor_get_transformed_position doesn't work during + * clutter_actor_paint() because the actor has already set up + * a model-view matrix. + * + * http://bugzilla.openedhand.com/show_bug.cgi?id=1115 + */ + actor_x = actor_y = 0.; + parent = CLUTTER_ACTOR (self); + while (parent) + { + gfloat x, y; + + clutter_actor_get_position (parent, &x, &y); + actor_x += x; + actor_y += y; + + parent = clutter_actor_get_parent (parent); + } + + area.x = (int)(0.5 + cursor_x + actor_x); + area.y = (int)(0.5 + cursor_y + actor_y); + area.width = 0; + area.height = (int)(0.5 + cursor_height); + + gtk_im_context_set_cursor_location (priv->im_context, &area); +} + +static void +st_im_text_commit_cb (GtkIMContext *context, + const gchar *str, + StIMText *imtext) +{ + ClutterText *clutter_text = CLUTTER_TEXT (imtext); + + if (clutter_text_get_editable (clutter_text)) + { + clutter_text_delete_selection (clutter_text); + clutter_text_insert_text (clutter_text, str, + clutter_text_get_cursor_position (clutter_text)); + } +} + +static void +reset_im_context (StIMText *self) +{ + StIMTextPrivate *priv = self->priv; + + if (priv->need_im_reset) + { + gtk_im_context_reset (priv->im_context); + priv->need_im_reset = FALSE; + } +} + +static void +st_im_text_paint (ClutterActor *actor) +{ + StIMText *self = ST_IM_TEXT (actor); + ClutterText *clutter_text = CLUTTER_TEXT (actor); + + /* This updates the cursor position as a side-effect */ + if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->paint) + CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->paint (actor); + + if (clutter_text_get_editable (clutter_text)) + update_im_cursor_location (self); +} + +/* Returns a new reference to window */ +static GdkWindow * +window_for_actor (ClutterActor *actor) +{ + GdkDisplay *display = gdk_display_get_default (); + ClutterActor *stage; + Window xwindow; + GdkWindow *window; + + stage = clutter_actor_get_stage (actor); + xwindow = clutter_x11_get_stage_window ((ClutterStage *)stage); + + window = gdk_window_lookup_for_display (display, xwindow); + if (window) + g_object_ref (window); + else + window = gdk_window_foreign_new_for_display (display, xwindow); + + return window; +} + +static void +st_im_text_realize (ClutterActor *actor) +{ + StIMTextPrivate *priv = ST_IM_TEXT (actor)->priv; + + priv->window = window_for_actor (actor); + gtk_im_context_set_client_window (priv->im_context, priv->window); +} + +static void +st_im_text_unrealize (ClutterActor *actor) +{ + StIMText *self = ST_IM_TEXT (actor); + StIMTextPrivate *priv = self->priv; + + reset_im_context (self); + gtk_im_context_set_client_window (priv->im_context, NULL); + g_object_unref (priv->window); + priv->window = NULL; +} + +static gboolean +key_is_modifier (guint16 keyval) +{ + /* See gdkkeys-x11.c:_gdk_keymap_key_is_modifier() for how this + * really should be implemented */ + + switch (keyval) + { + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Control_L: + case GDK_Control_R: + case GDK_Caps_Lock: + case GDK_Shift_Lock: + case GDK_Meta_L: + case GDK_Meta_R: + case GDK_Alt_L: + case GDK_Alt_R: + case GDK_Super_L: + case GDK_Super_R: + case GDK_Hyper_L: + case GDK_Hyper_R: + case GDK_ISO_Lock: + case GDK_ISO_Level2_Latch: + case GDK_ISO_Level3_Shift: + case GDK_ISO_Level3_Latch: + case GDK_ISO_Level3_Lock: + case GDK_ISO_Level5_Shift: + case GDK_ISO_Level5_Latch: + case GDK_ISO_Level5_Lock: + case GDK_ISO_Group_Shift: + case GDK_ISO_Group_Latch: + case GDK_ISO_Group_Lock: + return TRUE; + default: + return FALSE; + } +} + +static GdkEventKey * +key_event_to_gdk (ClutterKeyEvent *event_clutter) +{ + GdkDisplay *display = gdk_display_get_default (); + GdkKeymap *keymap = gdk_keymap_get_for_display (display); + GdkEventKey *event_gdk; + event_gdk = (GdkEventKey *)gdk_event_new ((event_clutter->type == CLUTTER_KEY_PRESS) ? + GDK_KEY_PRESS : GDK_KEY_RELEASE); + + event_gdk->window = window_for_actor ((ClutterActor *)event_clutter->stage); + event_gdk->send_event = FALSE; + event_gdk->time = event_clutter->time; + /* This depends on ClutterModifierType and GdkModifierType being + * identical, which they are currently. (They both match the X + * modifier state in the low 16-bits and have the same extensions.) */ + event_gdk->state = event_clutter->modifier_state; + event_gdk->keyval = event_clutter->keyval; + event_gdk->hardware_keycode = event_clutter->hardware_keycode; + /* For non-proper non-XKB support, we'd need a huge cut-and-paste + * from gdkkeys-x11.c; this is a macro that just shifts a few bits + * out of state, so won't make the situation worse if the server + * doesn't support XKB; we'll just end up with group == 0 */ + event_gdk->group = XkbGroupForCoreState (event_gdk->state); + + gdk_keymap_translate_keyboard_state (keymap, event_gdk->hardware_keycode, + event_gdk->state, event_gdk->group, + &event_gdk->keyval, NULL, NULL, NULL); + + if (event_clutter->unicode_value) + { + /* This is not particularly close to what GDK does - event_gdk->string + * is supposed to be in the locale encoding, and have control keys + * as control characters, etc. See gdkevents-x11.c:translate_key_event(). + * Hopefully no input method is using event.string. + */ + char buf[6]; + + event_gdk->length = g_unichar_to_utf8 (event_clutter->unicode_value, buf); + event_gdk->string = g_strndup (buf, event_gdk->length); + } + + event_gdk->is_modifier = key_is_modifier (event_gdk->keyval); + + return event_gdk; +} + +static gboolean +st_im_text_button_press_event (ClutterActor *actor, + ClutterButtonEvent *event) +{ + /* The button press indicates the user moving the cursor, or selecting + * etc, so we should abort any current preedit operation. ClutterText + * treats all buttons identically, so so do we. + */ + reset_im_context (ST_IM_TEXT (actor)); + + if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->button_press_event) + return CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->button_press_event (actor, event); + else + return FALSE; +} + +static gboolean +st_im_text_key_press_event (ClutterActor *actor, + ClutterKeyEvent *event) +{ + StIMText *self = ST_IM_TEXT (actor); + StIMTextPrivate *priv = self->priv; + ClutterText *clutter_text = CLUTTER_TEXT (actor); + gboolean result = FALSE; + int old_position; + + if (clutter_text_get_editable (clutter_text)) + { + GdkEventKey *event_gdk = key_event_to_gdk (event); + + if (gtk_im_context_filter_keypress (priv->im_context, event_gdk)) + { + priv->need_im_reset = TRUE; + result = TRUE; + } + + gdk_event_free ((GdkEvent *)event_gdk); + } + + /* ClutterText:position isn't properly notified, so we have to + * check before/after to catch a keypress (like an arrow key) + * moving the cursor position, which should reset the IM context. + * (Resetting on notify::position would require a sentinel when + * committing text) + * + * http://bugzilla.openedhand.com/show_bug.cgi?id=1830 + */ + old_position = clutter_text_get_cursor_position (clutter_text); + + if (!result && + CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_press_event) + result = CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_press_event (actor, event); + + if (clutter_text_get_cursor_position (clutter_text) != old_position) + reset_im_context (self); + + return result; +} + +static gboolean +st_im_text_key_release_event (ClutterActor *actor, + ClutterKeyEvent *event) +{ + StIMText *self = ST_IM_TEXT (actor); + StIMTextPrivate *priv = self->priv; + ClutterText *clutter_text = CLUTTER_TEXT (actor); + GdkEventKey *event_gdk; + gboolean result = FALSE; + + if (clutter_text_get_editable (clutter_text)) + { + event_gdk = key_event_to_gdk (event); + + if (gtk_im_context_filter_keypress (priv->im_context, event_gdk)) + { + priv->need_im_reset = TRUE; + result = TRUE; + } + + gdk_event_free ((GdkEvent *)event_gdk); + } + + if (!result && + CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_release_event) + result = CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_release_event (actor, event); + + return result; +} + +static void +st_im_text_key_focus_in (ClutterActor *actor) +{ + StIMTextPrivate *priv = ST_IM_TEXT (actor)->priv; + ClutterText *clutter_text = CLUTTER_TEXT (actor); + + if (clutter_text_get_editable (clutter_text)) + { + priv->need_im_reset = TRUE; + gtk_im_context_focus_in (priv->im_context); + } + + if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_in) + CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_in (actor); +} + +static void +st_im_text_key_focus_out (ClutterActor *actor) +{ + StIMTextPrivate *priv = ST_IM_TEXT (actor)->priv; + ClutterText *clutter_text = CLUTTER_TEXT (actor); + + if (clutter_text_get_editable (clutter_text)) + { + priv->need_im_reset = TRUE; + gtk_im_context_focus_out (priv->im_context); + } + + if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_out) + CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_out (actor); +} + +static void +st_im_text_class_init (StIMTextClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + g_type_class_add_private (klass, sizeof (StIMTextPrivate)); + + object_class->dispose = st_im_text_dispose; + + actor_class->paint = st_im_text_paint; + actor_class->realize = st_im_text_realize; + actor_class->unrealize = st_im_text_unrealize; + + actor_class->button_press_event = st_im_text_button_press_event; + actor_class->key_press_event = st_im_text_key_press_event; + actor_class->key_release_event = st_im_text_key_release_event; + actor_class->key_focus_in = st_im_text_key_focus_in; + actor_class->key_focus_out = st_im_text_key_focus_out; +} + +static void +st_im_text_init (StIMText *self) +{ + StIMTextPrivate *priv; + + self->priv = priv = ST_IM_TEXT_GET_PRIVATE (self); + + priv->im_context = gtk_im_multicontext_new (); + g_signal_connect (priv->im_context, "commit", + G_CALLBACK (st_im_text_commit_cb), self); +} + +/** + * st_im_text_new: + * @text: text to set to + * + * Create a new #StIMText with the specified text + * + * Returns: a new #ClutterActor + */ +ClutterActor * +st_im_text_new (const gchar *text) +{ + return g_object_new (ST_TYPE_IM_TEXT, + "text", text, + NULL); +} diff --git a/src/st/st-im-text.h b/src/st/st-im-text.h new file mode 100644 index 000000000..e897b1041 --- /dev/null +++ b/src/st/st-im-text.h @@ -0,0 +1,69 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * st-imtext.h + * + * This is a copy of ClutterIMText converted to use GtkIMContext rather + * than ClutterIMContext. Original code: + * + * Author: raymond liu + * + * Copyright (C) 2009, Intel Corporation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION) +#error "Only can be included directly.h" +#endif + +#ifndef __ST_IM_TEXT_H__ +#define __ST_IM_TEXT_H__ + +G_BEGIN_DECLS + +#include + +#define ST_TYPE_IM_TEXT (st_im_text_get_type ()) +#define ST_IM_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_IM_TEXT, StIMText)) +#define ST_IS_IM_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_IM_TEXT)) +#define ST_IM_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_IM_TEXT, StIMTextClass)) +#define ST_IS_IM_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_IM_TEXT)) +#define ST_IM_TEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_IM_TEXT, StIMTextClass)) + +typedef struct _StIMText StIMText; +typedef struct _StIMTextPrivate StIMTextPrivate; +typedef struct _StIMTextClass StIMTextClass; + +struct _StIMText +{ + ClutterText parent_instance; + + StIMTextPrivate *priv; +}; + +struct _StIMTextClass +{ + ClutterTextClass parent_class; +}; + +GType st_im_text_get_type (void) G_GNUC_CONST; + +ClutterActor *st_im_text_new (const gchar *text); +void st_im_text_set_autoshow_im (StIMText *self, + gboolean autoshow); + +G_END_DECLS + +#endif /* __ST_IM_TEXT_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index ebe65fcfe..c55355fb5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -6,6 +6,7 @@ TEST_JS = \ interactive/box-layout.js \ interactive/calendar.js \ interactive/css-fonts.js \ + interactive/entry.js \ interactive/inline-style.js \ interactive/scrolling.js \ interactive/table.js \ diff --git a/tests/interactive/entry.js b/tests/interactive/entry.js new file mode 100644 index 000000000..470770bdd --- /dev/null +++ b/tests/interactive/entry.js @@ -0,0 +1,32 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const Clutter = imports.gi.Clutter; +const Gtk = imports.gi.Gtk; +const Lang = imports.lang; +const St = imports.gi.St; + +const Calendar = imports.ui.calendar; +const UI = imports.testcommon.ui; + +Gtk.init(null, null); + +UI.init(); +let stage = Clutter.Stage.get_default(); +stage.width = stage.height = 400; +stage.show(); + +let vbox = new St.BoxLayout({ vertical: true, + width: stage.width, + height: stage.height, + style: 'padding: 10px; spacing: 10px; font: 15px sans-serif;' }); +stage.add_actor(vbox); + +let entry = new St.Entry({ style: 'border: 1px solid black;' }); +vbox.add(entry, + { expand: true, + y_fill: false, y_align: St.Align.MIDDLE }); +entry.grab_key_focus(); + +stage.show(); +Clutter.main(); +stage.destroy(); diff --git a/tools/build/gnome-shell.modules b/tools/build/gnome-shell.modules index 8d31d9000..649dfd07f 100644 --- a/tools/build/gnome-shell.modules +++ b/tools/build/gnome-shell.modules @@ -39,13 +39,6 @@ - - - - - - - @@ -62,7 +55,6 @@ -