Initial import of the Text actor from Tidy
authorEmmanuele Bassi <ebassi@linux.intel.com>
Thu, 11 Dec 2008 11:12:48 +0000 (11:12 +0000)
committerEmmanuele Bassi <ebassi@gnome.org>
Thu, 11 Dec 2008 11:12:48 +0000 (11:12 +0000)
The TidyText actor is meant as a replacement for both ClutterLabel
and ClutterText.

Any text-displaying and editing actor should derive from ClutterText
and implement the various visual cues to differentiate the editable
from the non-editable state. Those visual cues usually belong to
a high-level toolkit, especially if themeing is involved.

clutter/Makefile.am
clutter/clutter-text.c [new file with mode: 0644]
clutter/clutter-text.h [new file with mode: 0644]
clutter/clutter.h
tests/interactive/Makefile.am
tests/interactive/test-text.c [new file with mode: 0644]

index 97c9907..f515232 100644 (file)
@@ -86,6 +86,7 @@ source_h =                                    \
        $(srcdir)/clutter-stage.h               \
        $(srcdir)/clutter-stage-manager.h       \
        $(srcdir)/clutter-texture.h             \
+        $(srcdir)/clutter-text.h                \
        $(srcdir)/clutter-timeline.h            \
        $(srcdir)/clutter-timeout-pool.h        \
        $(srcdir)/clutter-types.h               \
@@ -180,6 +181,7 @@ source_c = \
        clutter-stage-manager.c         \
        clutter-stage-window.c          \
        clutter-texture.c               \
+        clutter-text.c                  \
        clutter-timeline.c              \
        clutter-timeout-pool.c          \
        clutter-units.c                 \
diff --git a/clutter/clutter-text.c b/clutter/clutter-text.c
new file mode 100644 (file)
index 0000000..6733fe1
--- /dev/null
@@ -0,0 +1,1561 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2006-2008 OpenedHand
+ *
+ * Authored By Øyvind Kolås <pippin@o-hand.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* TODO: undo/redo hooks?
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include "clutter-text.h"
+#include <clutter/clutter.h>
+
+static gboolean clutter_text_key_press          (ClutterActor    *actor,
+                                              ClutterKeyEvent *kev);
+static gboolean clutter_text_position_to_coords (ClutterText        *ttext,
+                                              gint             position,
+                                              gint            *x,
+                                              gint            *y,
+                                              gint            *cursor_height);
+static gint     clutter_text_coords_to_position (ClutterText        *text,
+                                              gint             x,
+                                              gint             y);
+static void     clutter_text_set_property       (GObject         *gobject,
+                                              guint            prop_id,
+                                              const GValue    *value,
+                                              GParamSpec      *pspec);
+static void     clutter_text_get_property       (GObject         *gobject,
+                                              guint            prop_id,
+                                              GValue          *value,
+                                              GParamSpec      *pspec);
+static void     clutter_text_finalize           (GObject         *gobject);
+
+static void init_commands (ClutterText *ttext);
+static void init_mappings (ClutterText *ttext);
+
+void
+clutter_text_delete_text (ClutterText *ttext,
+                       gssize    start_pos,
+                       gssize    end_pos);
+
+static gboolean
+clutter_text_truncate_selection (ClutterText     *ttext,
+                              const gchar  *commandline,
+                              ClutterEvent *event);
+
+G_DEFINE_TYPE (ClutterText, clutter_text, CLUTTER_TYPE_LABEL);
+
+struct _ClutterTextPrivate
+{
+  gboolean        editable;
+  gboolean        cursor_visible;
+  gboolean        activatable;
+  gboolean        selectable;
+
+  gint            position;   /* current cursor position */
+  gint            selection_bound; 
+                              /* current 'other end of selection' position */
+  gboolean        in_select_drag;
+  gint            x_pos;      /* the x position in the pangolayout, used to
+                               * avoid drifting when repeatedly moving up|down
+                               */
+  gboolean        cursor_color_set;
+  ClutterColor    cursor_color;
+  ClutterGeometry cursor_pos; /* Where to draw the cursor */
+
+  GList          *mappings;
+  GList          *commands; /* each instance has it's own set of commands
+                               so that actor specific actions can be added
+                               to single actor classes
+                              */
+};
+
+#define CLUTTER_TEXT_GET_PRIVATE(obj)                 \
+              (G_TYPE_INSTANCE_GET_PRIVATE ((obj),  \
+               CLUTTER_TYPE_TEXT,                     \
+               ClutterTextPrivate))
+
+enum
+{
+  PROP_0,
+  PROP_POSITION,
+  PROP_SELECTION_BOUND,
+  PROP_CURSOR_VISIBLE,
+  PROP_CURSOR_COLOR,
+  PROP_CURSOR_COLOR_SET,
+  PROP_EDITABLE,
+  PROP_SELECTABLE,
+  PROP_ACTIVATABLE
+};
+
+enum
+{
+  TEXT_CHANGED,
+  CURSOR_EVENT,
+  ACTIVATE,
+  LAST_SIGNAL
+};
+
+static guint label_signals[LAST_SIGNAL] = { 0, };
+
+#define offset_real(text, pos)                                \
+   (pos==-1?g_utf8_strlen(text, -1):pos)                          \
+
+#define offset_to_bytes(text,pos)\
+   (pos==-1?strlen(text):((gint)(g_utf8_offset_to_pointer (text, pos) - text)))
+
+#define bytes_to_offset(text, pos)                            \
+    (g_utf8_pointer_to_offset (text, text + pos))
+
+
+typedef struct TextCommand {
+  const gchar *name;
+  gboolean (*func) (ClutterText     *ttext,
+                    const gchar  *commandline,
+                    ClutterEvent *event);
+} TextCommand;
+
+typedef struct ClutterTextMapping {
+  ClutterModifierType    state;
+  guint                  keyval;
+  const gchar           *action;
+} ClutterTextMapping;
+
+
+void
+clutter_text_mappings_clear (ClutterText *ttext)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+  GList *iter;
+  for (iter = priv->mappings; iter; iter=iter->next)
+    {
+      g_free (iter->data);
+    }
+  g_list_free (priv->mappings);
+  priv->mappings = NULL;
+}
+
+void clutter_text_add_mapping (ClutterText           *ttext,
+                            guint               keyval,
+                            ClutterModifierType state,
+                            const gchar        *commandline)
+{
+  ClutterTextMapping *tmapping = g_new (ClutterTextMapping, 1);
+  ClutterTextPrivate *priv = ttext->priv;
+  tmapping->keyval = keyval;
+  tmapping->state = state;
+  tmapping->action = commandline;
+  priv->mappings = g_list_append (priv->mappings, tmapping);
+}
+
+void clutter_text_add_action (ClutterText    *ttext,
+                           const gchar *name,
+                           gboolean (*func) (ClutterText            *ttext,
+                                             const gchar         *commandline,
+                                             ClutterEvent        *event))
+{
+  TextCommand *tcommand = g_new (TextCommand, 1);
+  ClutterTextPrivate *priv = ttext->priv;
+  tcommand->name = name;
+  tcommand->func = func;
+  priv->commands = g_list_append (priv->commands, tcommand);
+}
+
+static void init_mappings (ClutterText *ttext)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+  if (priv->mappings)
+    return;
+  clutter_text_add_mapping (ttext, CLUTTER_Left, 0,    "move-left");
+  clutter_text_add_mapping (ttext, CLUTTER_KP_Left, 0, "move-left");
+  clutter_text_add_mapping (ttext, CLUTTER_Right, 0,   "move-right");
+  clutter_text_add_mapping (ttext, CLUTTER_KP_Right, 0,"move-right");
+  clutter_text_add_mapping (ttext, CLUTTER_Up, 0,      "move-up");
+  clutter_text_add_mapping (ttext, CLUTTER_KP_Up, 0,   "move-up");
+  clutter_text_add_mapping (ttext, CLUTTER_Down, 0,    "move-down");
+  clutter_text_add_mapping (ttext, CLUTTER_KP_Down, 0, "move-down");
+  clutter_text_add_mapping (ttext, CLUTTER_Begin, 0,   "move-start-line");
+  clutter_text_add_mapping (ttext, CLUTTER_Home, 0,    "move-start-line");
+  clutter_text_add_mapping (ttext, CLUTTER_KP_Home, 0, "move-start-line");
+  clutter_text_add_mapping (ttext, CLUTTER_End, 0,     "move-end-line");
+  clutter_text_add_mapping (ttext, CLUTTER_KP_End, 0,  "move-end-line");
+  clutter_text_add_mapping (ttext, CLUTTER_BackSpace,0,"delete-previous");
+  clutter_text_add_mapping (ttext, CLUTTER_Delete, 0,  "delete-next");
+  clutter_text_add_mapping (ttext, CLUTTER_KP_Delete,0,"delete-next");
+  clutter_text_add_mapping (ttext, CLUTTER_Return, 0,  "activate");
+  clutter_text_add_mapping (ttext, CLUTTER_KP_Enter, 0,"activate");
+  clutter_text_add_mapping (ttext, CLUTTER_ISO_Enter,0,"activate");
+}
+
+
+static gint
+clutter_text_coords_to_position (ClutterText *text,
+                              gint      x,
+                              gint      y)
+{
+  gint index_;
+  gint px, py;
+  gint trailing;
+
+  px = x * PANGO_SCALE;
+  py = y * PANGO_SCALE;
+
+  pango_layout_xy_to_index (clutter_label_get_layout (CLUTTER_LABEL (text)),
+                            px, py, &index_, &trailing);
+
+  return index_ + trailing;
+}
+
+static gboolean
+clutter_text_position_to_coords (ClutterText *ttext,
+                              gint      position,
+                              gint     *x,
+                              gint     *y,
+                              gint     *cursor_height)
+{
+  ClutterTextPrivate  *priv;
+  gint              index_;
+  PangoRectangle    rect;
+  const gchar      *text;
+
+  text = clutter_label_get_text (CLUTTER_LABEL (ttext));
+
+  priv = ttext->priv;
+
+  if (position == -1)
+    {
+      index_ = strlen (text);
+    }
+  else
+    {
+      index_ = offset_to_bytes (text, position);
+    }
+
+  if (index_ > strlen (text))
+   index_ = strlen (text);
+
+  pango_layout_get_cursor_pos (
+        clutter_label_get_layout (CLUTTER_LABEL (ttext)),
+        index_, &rect, NULL);
+
+  if (x)
+    *x = rect.x / PANGO_SCALE;
+  if (y)
+    *y = (rect.y + rect.height) / PANGO_SCALE;
+  if (cursor_height)
+    *cursor_height = rect.height / PANGO_SCALE;
+
+  return TRUE; /* FIXME: should return false if coords were outside text */
+}
+
+static void
+clutter_text_ensure_cursor_position (ClutterText *ttext)
+{
+  gint x,y,cursor_height;
+  
+  ClutterTextPrivate  *priv;
+  priv = ttext->priv;
+
+  clutter_text_position_to_coords (ttext, priv->position, &x, &y, &cursor_height);
+
+  priv->cursor_pos.x = x;
+  priv->cursor_pos.y = y - cursor_height;
+  priv->cursor_pos.width = 2; 
+  priv->cursor_pos.height = cursor_height;
+
+  g_signal_emit (ttext, label_signals[CURSOR_EVENT], 0, &priv->cursor_pos);
+}
+
+gint
+clutter_text_get_cursor_position (ClutterText *ttext)
+{
+  g_return_val_if_fail (CLUTTER_IS_TEXT (ttext), -1);
+  return ttext->priv->position;
+}
+
+void
+clutter_text_set_cursor_position (ClutterText *ttext,
+                               gint       position)
+{
+  ClutterLabel        *label = CLUTTER_LABEL (ttext);
+  const gchar         *text;
+  ClutterTextPrivate *priv;
+  gint len;
+
+  g_return_if_fail (CLUTTER_IS_TEXT (ttext));
+
+  priv = ttext->priv;
+
+  text = clutter_label_get_text (label);
+  if (text == NULL)
+    return;
+
+  len = g_utf8_strlen (text, -1);
+
+  if (position < 0 || position >= len)
+    priv->position = -1;
+  else
+    priv->position = position;
+
+  if (CLUTTER_ACTOR_IS_VISIBLE (ttext))
+    clutter_actor_queue_redraw (CLUTTER_ACTOR (ttext));
+}
+
+static gboolean
+clutter_text_truncate_selection (ClutterText     *ttext,
+                              const gchar  *commandline,
+                              ClutterEvent *event)
+{
+  const gchar *utf8 = clutter_label_get_text (CLUTTER_LABEL (ttext));
+  ClutterTextPrivate *priv;
+  gint             start_index;
+  gint             end_index;
+
+  priv = ttext->priv;
+
+  g_object_ref (ttext);
+
+  start_index = offset_real (utf8, priv->position);
+  end_index = offset_real (utf8, priv->selection_bound);
+
+  if (end_index == start_index)
+    return FALSE;
+
+  if (end_index < start_index)
+    {
+      gint temp = start_index;
+      start_index = end_index;
+      end_index = temp;
+    }
+
+  clutter_text_delete_text (ttext, start_index, end_index);
+  priv->position = start_index;
+  priv->selection_bound = start_index;
+  return TRUE;
+}
+
+
+void
+clutter_text_insert_unichar (ClutterText *ttext,
+                           gunichar   wc)
+{
+  ClutterTextPrivate *priv;
+  GString *new = NULL;
+  const gchar *old_text;
+  glong pos;
+
+  g_return_if_fail (CLUTTER_IS_TEXT (ttext));
+  g_return_if_fail (g_unichar_validate (wc));
+
+  if (wc == 0)
+    return;
+
+  clutter_text_truncate_selection (ttext, NULL, 0);
+
+  priv = ttext->priv;
+
+  g_object_ref (ttext);
+
+  old_text = clutter_label_get_text (CLUTTER_LABEL (ttext));
+
+
+  new = g_string_new (old_text);
+  pos = offset_to_bytes (old_text, priv->position);
+  new = g_string_insert_unichar (new, pos, wc);
+
+  clutter_label_set_text (CLUTTER_LABEL (ttext), new->str);
+
+  if (priv->position >= 0)
+    {
+      clutter_text_set_cursor_position (ttext, priv->position + 1);
+      clutter_text_set_selection_bound (ttext, priv->position);
+    }
+
+  g_string_free (new, TRUE);
+
+  g_object_unref (ttext);
+
+  g_signal_emit (G_OBJECT (ttext), label_signals[TEXT_CHANGED], 0);
+}
+
+void
+clutter_text_delete_text (ClutterText *ttext,
+                       gssize    start_pos,
+                       gssize    end_pos)
+{
+  ClutterTextPrivate *priv;
+  GString *new = NULL;
+  gint start_bytes;
+  gint end_bytes;
+  const gchar *text;
+
+  g_return_if_fail (CLUTTER_IS_TEXT (ttext));
+
+  priv = ttext->priv;
+  text = clutter_label_get_text (CLUTTER_LABEL (ttext));
+
+  if (end_pos == -1)
+    {
+      start_bytes = offset_to_bytes (text, g_utf8_strlen (text, -1) - 1);
+      end_bytes = offset_to_bytes (text, g_utf8_strlen (text, -1));
+    }
+  else
+    {
+      start_bytes = offset_to_bytes (text, start_pos);
+      end_bytes = offset_to_bytes (text, end_pos);
+    }
+
+  new = g_string_new (text);
+
+  new = g_string_erase (new, start_bytes, end_bytes - start_bytes);
+
+  clutter_label_set_text (CLUTTER_LABEL (ttext), new->str);
+
+  g_string_free (new, TRUE);
+  g_signal_emit (G_OBJECT (ttext), label_signals[TEXT_CHANGED], 0);
+}
+
+static void
+clutter_text_finalize (GObject *gobject)
+{
+  ClutterTextPrivate *priv;
+  ClutterText        *ttext;
+  GList           *iter;
+
+  ttext = CLUTTER_TEXT (gobject);
+  priv = ttext->priv;
+
+  clutter_text_mappings_clear (ttext);
+
+  for (iter = priv->commands; iter; iter=iter->next)
+      g_free (iter->data);
+  g_list_free (priv->commands);
+  priv->commands = NULL;
+}
+
+static void
+clutter_text_set_property (GObject      *gobject,
+                        guint         prop_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  ClutterTextPrivate *priv;
+  ClutterText        *ttext;
+
+  ttext = CLUTTER_TEXT (gobject);
+  priv = ttext->priv;
+
+  switch (prop_id)
+    {
+    case PROP_POSITION:
+      clutter_text_set_cursor_position (ttext, g_value_get_int (value));
+      break;
+    case PROP_SELECTION_BOUND:
+      clutter_text_set_selection_bound (ttext, g_value_get_int (value));
+      break;
+    case PROP_CURSOR_VISIBLE:
+      clutter_text_set_cursor_visible (ttext, g_value_get_boolean (value));
+      break;
+    case PROP_CURSOR_COLOR:
+      clutter_text_set_cursor_color (ttext, g_value_get_boxed (value));
+      break;
+    case PROP_EDITABLE:
+      clutter_text_set_editable (ttext, g_value_get_boolean (value));
+      break;
+    case PROP_ACTIVATABLE:
+      clutter_text_set_activatable (ttext, g_value_get_boolean (value));
+      break;
+    case PROP_SELECTABLE:
+      clutter_text_set_selectable (ttext, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+clutter_text_get_property (GObject    *gobject,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  ClutterTextPrivate *priv;
+
+  priv = CLUTTER_TEXT (gobject)->priv;
+
+  switch (prop_id)
+    {
+    case PROP_CURSOR_VISIBLE:
+      g_value_set_boolean (value, priv->cursor_visible);
+      break;
+    case PROP_CURSOR_COLOR:
+      {
+        ClutterColor color;
+        clutter_text_get_cursor_color (CLUTTER_TEXT (gobject), &color);
+        g_value_set_boxed (value, &color);
+      }
+      break;
+    case PROP_POSITION:
+      g_value_set_int (value, CLUTTER_FIXED_TO_FLOAT (priv->position));
+      break;
+    case PROP_SELECTION_BOUND:
+      g_value_set_int (value, CLUTTER_FIXED_TO_FLOAT (priv->selection_bound));
+      break;
+    case PROP_EDITABLE:
+      g_value_set_boolean (value, priv->editable);
+      break;
+    case PROP_SELECTABLE:
+      g_value_set_boolean (value, priv->selectable);
+      break;
+    case PROP_ACTIVATABLE:
+      g_value_set_boolean (value, priv->activatable);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+cursor_paint (ClutterActor *actor,
+              gpointer      user_data)
+{
+  ClutterText        *ttext = CLUTTER_TEXT (actor);
+  ClutterTextPrivate *priv   = ttext->priv;
+
+  if (priv->editable &&
+      priv->cursor_visible)
+    {
+      if (priv->cursor_color_set)
+        {
+          cogl_set_source_color4ub (priv->cursor_color.red,
+                                    priv->cursor_color.green,
+                                    priv->cursor_color.blue,
+                                    priv->cursor_color.alpha);
+        }
+      else
+        {
+          ClutterColor color;
+          clutter_label_get_color (CLUTTER_LABEL (actor), &color);
+          cogl_set_source_color4ub (color.red,
+                                    color.green,
+                                    color.blue,
+                                    color.alpha);
+        }
+
+      clutter_text_ensure_cursor_position (ttext);
+
+      if (priv->position == 0)
+        priv->cursor_pos.x -= 2;
+
+      if (priv->position == priv->selection_bound)
+        {
+          cogl_rectangle (priv->cursor_pos.x,
+                          priv->cursor_pos.y,
+                          priv->cursor_pos.width,
+                          priv->cursor_pos.height);
+        }
+      else
+        {
+          gint lines;
+          gint start_index;
+          gint end_index;
+          const gchar *utf8 = clutter_label_get_text (CLUTTER_LABEL (ttext));
+
+          start_index = offset_to_bytes (utf8, priv->position);
+          end_index = offset_to_bytes (utf8, priv->selection_bound);
+
+          if (start_index > end_index)
+            {
+              gint temp = start_index;
+              start_index = end_index;
+              end_index = temp;
+            }
+          
+          PangoLayout *layout = clutter_label_get_layout (CLUTTER_LABEL (ttext));
+          lines = pango_layout_get_line_count (layout);
+          gint line_no;
+          for (line_no = 0; line_no < lines; line_no++)
+            {
+              PangoLayoutLine *line;
+              gint  n_ranges;
+              gint *ranges;
+              gint  i;
+              gint y;
+              gint height;
+              gint index;
+              gint maxindex;
+
+              line = pango_layout_get_line_readonly (layout, line_no);
+              pango_layout_line_x_to_index (line, G_MAXINT, &maxindex, NULL);
+              if (maxindex < start_index)
+                continue;
+
+              pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges);
+              pango_layout_line_x_to_index (line, 0, &index, NULL);
+
+              clutter_text_position_to_coords (ttext, bytes_to_offset (utf8, index), NULL, &y, &height);
+
+              for (i=0;i<n_ranges;i++)
+                {
+                  cogl_rectangle (ranges[i*2+0]/PANGO_SCALE,
+                                  y-height,
+                                  (ranges[i*2+1]-ranges[i*2+0])/PANGO_SCALE,
+                                  height);
+                }
+
+              g_free (ranges);
+
+            }
+      }
+    }
+}
+
+
+
+static gboolean
+clutter_text_press (ClutterActor       *actor,
+                 ClutterButtonEvent *bev)
+{
+  ClutterText *ttext = CLUTTER_TEXT (actor);
+  ClutterTextPrivate *priv = ttext->priv;
+  ClutterUnit           x, y;
+  gint                  index_;
+  const gchar          *text;
+
+  text = clutter_label_get_text (CLUTTER_LABEL (actor));
+
+  x = CLUTTER_UNITS_FROM_INT (bev->x);
+  y = CLUTTER_UNITS_FROM_INT (bev->y);
+
+  clutter_actor_transform_stage_point (actor, x, y, &x, &y);
+
+  index_ = clutter_text_coords_to_position (ttext, CLUTTER_UNITS_TO_INT (x),
+                                                CLUTTER_UNITS_TO_INT (y));
+
+  clutter_text_set_cursor_position (ttext, bytes_to_offset (text, index_));
+  clutter_text_set_selection_bound (ttext, bytes_to_offset (text, index_)
+    );
+
+  /* we'll steal keyfocus if we do not have it */
+  {
+    ClutterActor *stage;
+    for (stage = actor;
+         clutter_actor_get_parent (stage);
+         stage = clutter_actor_get_parent (stage));
+    if (stage && CLUTTER_IS_STAGE (stage))
+      clutter_stage_set_key_focus (CLUTTER_STAGE (stage), actor);
+  }
+
+  priv->in_select_drag = TRUE;
+  clutter_grab_pointer (actor);
+
+  return TRUE;
+}
+
+
+static gboolean
+clutter_text_motion (ClutterActor       *actor,
+                  ClutterMotionEvent *mev)
+{
+  ClutterText *ttext = CLUTTER_TEXT (actor);
+  ClutterTextPrivate *priv = ttext->priv;
+  ClutterUnit           x, y;
+  gint                  index_;
+  const gchar          *text;
+
+  if (!priv->in_select_drag)
+    {
+      return FALSE;
+    }
+
+  text = clutter_label_get_text (CLUTTER_LABEL (actor));
+
+  x = CLUTTER_UNITS_FROM_INT (mev->x);
+  y = CLUTTER_UNITS_FROM_INT (mev->y);
+
+  clutter_actor_transform_stage_point (actor, x, y, &x, &y);
+
+  index_ = clutter_text_coords_to_position (ttext, CLUTTER_UNITS_TO_INT (x),
+                                                CLUTTER_UNITS_TO_INT (y));
+
+  if (priv->selectable)
+    {
+      clutter_text_set_cursor_position (ttext, bytes_to_offset (text, index_));
+    }
+  else
+    {
+      clutter_text_set_cursor_position (ttext, bytes_to_offset (text, index_));
+      clutter_text_set_selection_bound (ttext, bytes_to_offset (text, index_));
+    }
+
+  return TRUE;
+}
+
+static gboolean
+clutter_text_release (ClutterActor       *actor,
+                   ClutterButtonEvent *bev)
+{
+  ClutterText *ttext = CLUTTER_TEXT (actor);
+  ClutterTextPrivate *priv = ttext->priv;
+  if (priv->in_select_drag)
+    {
+      clutter_ungrab_pointer ();
+      priv->in_select_drag = FALSE;
+      return TRUE;
+    }
+  return FALSE;
+}
+
+static void
+clutter_text_constructed (GObject *object)
+{
+  g_signal_connect (CLUTTER_ACTOR (object),
+                    "paint", G_CALLBACK (cursor_paint),
+                    NULL);
+
+  if (G_OBJECT_CLASS (clutter_text_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (clutter_text_parent_class)->constructed (object);
+}
+
+static void
+clutter_text_class_init (ClutterTextClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+
+  gobject_class->set_property = clutter_text_set_property;
+  gobject_class->get_property = clutter_text_get_property;
+  gobject_class->constructed = clutter_text_constructed;
+  gobject_class->finalize = clutter_text_finalize;
+
+  actor_class->key_press_event = clutter_text_key_press;
+  actor_class->button_press_event = clutter_text_press;
+  actor_class->button_release_event = clutter_text_release;
+  actor_class->motion_event = clutter_text_motion;
+
+  /**
+   * ClutterText:editable:
+   *
+   * Whether key events delivered to the actor causes editing.
+   */
+  g_object_class_install_property
+    (gobject_class, PROP_EDITABLE,
+     g_param_spec_boolean ("editable",
+                       "Editable",
+                       "Whether the text is editable",
+                       TRUE,
+                       G_PARAM_READWRITE));
+
+
+  /**
+   * ClutterText:selectable:
+   *
+   * Whether it is possible to select text.
+   */
+  g_object_class_install_property
+    (gobject_class, PROP_SELECTABLE,
+     g_param_spec_boolean ("selectable",
+                       "Editable",
+                       "Whether the text is selectable",
+                       TRUE,
+                       G_PARAM_READWRITE));
+
+  /**
+   * ClutterText:activatable:
+   *
+   * Toggles whether return invokes the activate signal or not.
+   */
+  g_object_class_install_property
+    (gobject_class, PROP_ACTIVATABLE,
+     g_param_spec_boolean ("activatable",
+                       "Editable",
+                       "Whether return causes the activate signal to be fired",
+                       TRUE,
+                       G_PARAM_READWRITE));
+
+  /**
+   * ClutterText:cursor-visible:
+   *
+   * Whether the input cursor is visible or not, it will only be visible
+   * if both cursor-visible is set and editable is set at the same time,
+   * the value defaults to TRUE.
+   */
+  g_object_class_install_property
+    (gobject_class, PROP_CURSOR_VISIBLE,
+     g_param_spec_boolean ("cursor-visible",
+                       "Cursor Visible",
+                       "Whether the input cursor is visible",
+                       TRUE,
+                       G_PARAM_READWRITE));
+
+
+  g_object_class_install_property
+    (gobject_class, PROP_CURSOR_COLOR,
+     g_param_spec_boxed ("cursor-color",
+                        "Cursor Colour",
+                        "Cursor  Colour",
+                        CLUTTER_TYPE_COLOR,
+                        G_PARAM_READWRITE));
+
+  /**
+   * ClutterText:position:
+   *
+   * The current input cursor position. -1 is taken to be the end of the text
+   */
+  g_object_class_install_property
+    (gobject_class, PROP_POSITION,
+     g_param_spec_int ("position",
+                       "Position",
+                       "The cursor position",
+                       -1, G_MAXINT,
+                       -1,
+                       G_PARAM_READWRITE));
+
+
+  /**
+   * ClutterText:selection-bound:
+   *
+   * The current input cursor position. -1 is taken to be the end of the text
+   */
+  g_object_class_install_property
+    (gobject_class, PROP_SELECTION_BOUND,
+     g_param_spec_int ("selection-bound",
+                       "Selection-bound",
+                       "The cursor position of the other end of the selection.",
+                       -1, G_MAXINT,
+                       -1,
+                       G_PARAM_READWRITE));
+
+ /**
+   * ClutterText::text-changed:
+   * @actor: the actor which received the event
+   *
+   * The ::text-changed signal is emitted after @entry's text changes
+   */
+  label_signals[TEXT_CHANGED] =
+    g_signal_new ("text-changed",
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (ClutterEntryClass, text_changed),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+
+  label_signals[CURSOR_EVENT] =
+    g_signal_new ("cursor-event",
+                 G_TYPE_FROM_CLASS (gobject_class),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (ClutterEntryClass, cursor_event),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__BOXED,
+                 G_TYPE_NONE, 1,
+                 CLUTTER_TYPE_GEOMETRY | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+
+ /**
+   * ClutterText::activate
+   * @actor: the actor which received the event
+   *
+   * The ::activate signal is emitted each time the entry is 'activated'
+   * by the user, normally by pressing the 'Enter' key.
+   *
+   * Since: 0.4
+   */
+  label_signals[ACTIVATE] =
+    g_signal_new ("activate",
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (ClutterEntryClass, activate),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+
+  g_type_class_add_private (klass, sizeof (ClutterTextPrivate));
+}
+
+static void
+clutter_text_init (ClutterText *self)
+{
+  ClutterTextPrivate *priv;
+
+  self->priv = priv = CLUTTER_TEXT_GET_PRIVATE (self);
+  priv->x_pos = -1;
+  priv->cursor_visible = TRUE;
+  priv->editable = FALSE;
+
+  priv->cursor_color_set = FALSE;
+  init_commands (self); /* FIXME: free */
+  init_mappings (self); /* FIXME: free */
+}
+
+ClutterActor *
+clutter_text_new_full (const gchar        *font_name,
+                     const gchar        *text,
+                     const ClutterColor *color)
+{
+  return g_object_new (CLUTTER_TYPE_TEXT,
+                       "font-name", font_name,
+                       "text", text,
+                       "color", color,
+                       NULL);
+}
+
+ClutterActor *
+clutter_text_new_with_text (const gchar *font_name,
+                         const gchar *text)
+{
+  return g_object_new (CLUTTER_TYPE_TEXT,
+                       "font-name", font_name,
+                       "text", text,
+                       NULL);
+}
+
+
+void
+clutter_text_set_editable (ClutterText *label,
+                        gboolean  editable)
+{
+  ClutterTextPrivate *priv;
+
+  priv = label->priv;
+  priv->editable = editable;
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (label));
+}
+
+gboolean
+clutter_text_get_editable (ClutterText *label)
+{
+  ClutterTextPrivate *priv;
+  priv = label->priv;
+  return priv->editable;
+}
+
+
+void
+clutter_text_set_selectable (ClutterText *label,
+                          gboolean  selectable)
+{
+  ClutterTextPrivate *priv;
+
+  priv = label->priv;
+  priv->selectable = selectable;
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (label));
+}
+
+gboolean
+clutter_text_get_selectable (ClutterText *label)
+{
+  ClutterTextPrivate *priv;
+  priv = label->priv;
+  return priv->selectable;
+}
+
+
+void
+clutter_text_set_activatable (ClutterText *label,
+                           gboolean  activatable)
+{
+  ClutterTextPrivate *priv;
+
+  priv = label->priv;
+  priv->activatable = activatable;
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (label));
+}
+
+gboolean
+clutter_text_get_activatable (ClutterText *label)
+{
+  ClutterTextPrivate *priv;
+  priv = label->priv;
+  return priv->activatable;
+}
+
+
+void
+clutter_text_set_cursor_visible (ClutterText *label,
+                              gboolean  cursor_visible)
+{
+  ClutterTextPrivate *priv;
+
+  priv = label->priv;
+  priv->cursor_visible = cursor_visible;
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (label));
+}
+
+gboolean
+clutter_text_get_cursor_visible (ClutterText *label)
+{
+  ClutterTextPrivate *priv;
+  priv = label->priv;
+  return priv->cursor_visible;
+}
+
+void
+clutter_text_set_cursor_color (ClutterText           *text,
+                           const ClutterColor *color)
+{
+  ClutterTextPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_LABEL (text));
+  g_return_if_fail (color != NULL);
+
+  priv = text->priv;
+
+  g_object_ref (text);
+
+  if (color)
+    {
+      priv->cursor_color = *color;
+      priv->cursor_color_set = TRUE;
+    }
+  else
+    {
+      priv->cursor_color_set = FALSE;
+    }
+}
+
+
+void
+clutter_text_get_cursor_color (ClutterText     *text,
+                            ClutterColor *color)
+{
+  ClutterTextPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_LABEL (text));
+  g_return_if_fail (color != NULL);
+
+  priv = text->priv;
+
+  *color = priv->cursor_color;
+}
+
+
+gint
+clutter_text_get_selection_bound (ClutterText *text)
+{
+  ClutterTextPrivate *priv;
+  priv = text->priv;
+
+  return priv->selection_bound;
+}
+
+gchar *
+clutter_text_get_selection (ClutterText *text)
+{
+  ClutterTextPrivate *priv;
+  const gchar *utf8 = clutter_label_get_text (CLUTTER_LABEL (text));
+  gchar       *str;
+  gint         len;
+  gint         start_index;
+  gint         end_index;
+  gint         start_offset;
+  gint         end_offset;
+
+  priv = text->priv;
+
+  start_index = priv->position;
+  end_index = priv->selection_bound;
+
+  if (end_index == start_index)
+    return g_strdup ("");
+  if (end_index < start_index)
+    {
+      gint temp = start_index;
+      start_index = end_index;
+      end_index = temp;
+    }
+
+  start_offset = offset_to_bytes (utf8, start_index);
+  end_offset = offset_to_bytes (utf8, end_index);
+  len = end_offset - start_offset;
+
+  str = g_malloc (len + 1);
+  g_utf8_strncpy (str, utf8 + start_offset, end_index-start_index);
+  return str;
+}
+
+
+
+void
+clutter_text_set_selection_bound (ClutterText *text,
+                               gint      selection_bound)
+{
+  ClutterTextPrivate *priv;
+
+  priv = text->priv;
+  priv->selection_bound = selection_bound;
+
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (text));
+}
+
+
+/****************************************************************/
+/* The following are the commands available for keybinding when */
+/* using the entry, these can also be invoked programmatically  */
+/* through clutter_text_action()                                   */
+/****************************************************************/
+
+static gboolean
+clutter_text_action_activate (ClutterText            *ttext,
+                           const gchar         *commandline,
+                           ClutterEvent *event)
+{
+  g_signal_emit (G_OBJECT (ttext), label_signals[ACTIVATE], 0);
+  return TRUE;
+}
+
+static void
+clutter_text_clear_selection (ClutterText *ttext)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+  priv->selection_bound = priv->position;
+}
+
+static gboolean
+clutter_text_action_move_left (ClutterText     *ttext,
+                            const gchar  *commandline,
+                            ClutterEvent *event)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+  gint pos = priv->position;
+  gint len;
+  len = g_utf8_strlen (clutter_label_get_text (CLUTTER_LABEL (ttext)), -1);
+
+  if (pos != 0 && len !=0)
+    {
+      if (pos == -1)
+        {
+          clutter_text_set_cursor_position (ttext, len - 1);
+        }
+      else
+        {
+          clutter_text_set_cursor_position (ttext, pos - 1);
+        }
+    }
+
+  if (!(priv->selectable && event &&
+      (event->key.modifier_state & CLUTTER_SHIFT_MASK)))
+    clutter_text_clear_selection (ttext);
+
+  return TRUE;
+}
+
+
+static gboolean
+clutter_text_action_move_right (ClutterText            *ttext,
+                             const gchar         *commandline,
+                             ClutterEvent *event)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+  gint pos;
+  gint len;
+  len = g_utf8_strlen (clutter_label_get_text (CLUTTER_LABEL (ttext)), -1);
+
+  pos = priv->position;
+  if (pos != -1 && len !=0)
+    {
+      if (pos != len)
+        {
+          clutter_text_set_cursor_position (ttext, pos + 1);
+        }
+    }
+
+  if (!(priv->selectable &&
+        event &&
+       (event->key.modifier_state & CLUTTER_SHIFT_MASK)))
+    {
+      clutter_text_clear_selection (ttext);
+    }
+
+  return TRUE;
+}
+
+static gboolean
+clutter_text_action_move_up (ClutterText            *ttext,
+                          const gchar         *commandline,
+                          ClutterEvent *event)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+  gint                          line_no;
+  gint                          index_;
+  gint                          x;
+  const gchar                  *text;
+  PangoLayoutLine              *layout_line;
+
+  text = clutter_label_get_text (CLUTTER_LABEL (ttext));
+
+  pango_layout_index_to_line_x (
+        clutter_label_get_layout (CLUTTER_LABEL (ttext)),
+        offset_to_bytes (text, priv->position),
+        0,
+        &line_no,
+        &x);
+  
+  if (priv->x_pos != -1)
+    x = priv->x_pos;
+  else
+    priv->x_pos = x;
+
+  line_no -= 1;
+  if (line_no < 0)
+    return FALSE;
+
+  layout_line = pango_layout_get_line_readonly (
+                    clutter_label_get_layout (CLUTTER_LABEL (ttext)),
+                    line_no);
+
+  if (!layout_line)
+    return TRUE;
+
+  pango_layout_line_x_to_index (layout_line, x, &index_, NULL);
+
+  {
+    gint pos = bytes_to_offset (text, index_);
+    clutter_text_set_cursor_position (ttext, pos);
+  }
+
+  if (!(priv->selectable && event &&
+      (event->key.modifier_state & CLUTTER_SHIFT_MASK)))
+    clutter_text_clear_selection (ttext);
+
+  return TRUE;
+}
+
+static gboolean
+clutter_text_action_move_down (ClutterText            *ttext,
+                            const gchar         *commandline,
+                            ClutterEvent *event)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+  gint                          line_no;
+  gint                          index_;
+  gint                          x;
+  const gchar                  *text;
+  PangoLayoutLine              *layout_line;
+
+  text = clutter_label_get_text (CLUTTER_LABEL (ttext));
+
+  pango_layout_index_to_line_x (
+        clutter_label_get_layout (CLUTTER_LABEL (ttext)),
+        offset_to_bytes (text, priv->position),
+        0,
+        &line_no,
+        &x);
+  
+  if (priv->x_pos != -1)
+    x = priv->x_pos;
+  else
+    priv->x_pos = x;
+
+  layout_line = pango_layout_get_line_readonly (
+                    clutter_label_get_layout (CLUTTER_LABEL (ttext)),
+                    line_no + 1);
+
+  if (!layout_line)
+    {
+      return FALSE;
+    }
+
+  pango_layout_line_x_to_index (layout_line, x, &index_, NULL);
+
+    {
+      gint pos = bytes_to_offset (text, index_);
+      clutter_text_set_cursor_position (ttext, pos);
+    }
+  if (!(priv->selectable && event &&
+      (event->key.modifier_state & CLUTTER_SHIFT_MASK)))
+    clutter_text_clear_selection (ttext);
+  return TRUE;
+}
+
+static gboolean
+clutter_text_action_move_start (ClutterText            *ttext,
+                             const gchar         *commandline,
+                             ClutterEvent *event)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+
+  clutter_text_set_cursor_position (ttext, 0);
+  if (!(priv->selectable && event &&
+      (event->key.modifier_state & CLUTTER_SHIFT_MASK)))
+    clutter_text_clear_selection (ttext);
+  return TRUE;
+}
+
+static gboolean
+clutter_text_action_move_end (ClutterText            *ttext,
+                           const gchar         *commandline,
+                           ClutterEvent *event)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+
+  clutter_text_set_cursor_position (ttext, -1);
+  if (!(priv->selectable && event &&
+      (event->key.modifier_state & CLUTTER_SHIFT_MASK)))
+    clutter_text_clear_selection (ttext);
+
+  return TRUE;
+}
+
+static gboolean
+clutter_text_action_move_start_line (ClutterText            *ttext,
+                                  const gchar         *commandline,
+                                  ClutterEvent *event)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+  gint                          line_no;
+  gint                          index_;
+  const gchar                  *text;
+  PangoLayoutLine              *layout_line;
+  gint                          position;
+
+  text = clutter_label_get_text (CLUTTER_LABEL (ttext));
+
+
+  pango_layout_index_to_line_x (
+        clutter_label_get_layout (CLUTTER_LABEL (ttext)),
+        offset_to_bytes (text, priv->position),
+        0,
+        &line_no,
+        NULL);
+
+  layout_line = pango_layout_get_line_readonly (
+                    clutter_label_get_layout (CLUTTER_LABEL (ttext)),
+                    line_no);
+
+  pango_layout_line_x_to_index (layout_line, 0, &index_, NULL);
+
+  position = bytes_to_offset (text, index_);
+  clutter_text_set_cursor_position (ttext, position);
+
+  if (!(priv->selectable && event &&
+      (event->key.modifier_state & CLUTTER_SHIFT_MASK)))
+    clutter_text_clear_selection (ttext);
+
+  return TRUE;
+}
+
+static gboolean
+clutter_text_action_move_end_line (ClutterText            *ttext,
+                                const gchar         *commandline,
+                                ClutterEvent *event)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+  gint                          line_no;
+  gint                          index_;
+  gint                          trailing;
+  const gchar                  *text;
+  PangoLayoutLine              *layout_line;
+  gint                          position;
+
+  text = clutter_label_get_text (CLUTTER_LABEL (ttext));
+
+  index_ = offset_to_bytes (text, priv->position);
+
+  pango_layout_index_to_line_x (
+        clutter_label_get_layout (CLUTTER_LABEL (ttext)),
+        index_,
+        0,
+        &line_no,
+        NULL);
+
+  layout_line = pango_layout_get_line_readonly (
+                    clutter_label_get_layout (CLUTTER_LABEL (ttext)),
+                    line_no);
+
+  pango_layout_line_x_to_index (layout_line, G_MAXINT, &index_, &trailing);
+  index_ += trailing;
+
+  position = bytes_to_offset (text, index_);
+
+  clutter_text_set_cursor_position (ttext, position);
+
+  if (!(priv->selectable && event &&
+      (event->key.modifier_state & CLUTTER_SHIFT_MASK)))
+    clutter_text_clear_selection (ttext);
+
+  return TRUE;
+}
+
+static gboolean
+clutter_text_action_delete_next (ClutterText *ttext,
+                              const gchar         *commandline,
+                              ClutterEvent *event)
+{
+  ClutterTextPrivate *priv;
+  gint pos;
+  gint len;
+  if (clutter_text_truncate_selection (ttext, NULL, 0))
+    return TRUE;
+  priv = ttext->priv;
+  pos = priv->position;
+  len = g_utf8_strlen (clutter_label_get_text (CLUTTER_LABEL (ttext)), -1);
+
+  if (len && pos != -1 && pos < len)
+    clutter_text_delete_text (ttext, pos, pos+1);;
+  return TRUE;
+}
+
+static gboolean
+clutter_text_action_delete_previous (ClutterText            *ttext,
+                                  const gchar         *commandline,
+                                  ClutterEvent *event)
+{
+  ClutterTextPrivate *priv;
+  gint pos;
+  gint len;
+  if (clutter_text_truncate_selection (ttext, NULL, 0))
+    return TRUE;
+  priv = ttext->priv;
+  pos = priv->position;
+  len = g_utf8_strlen (clutter_label_get_text (CLUTTER_LABEL (ttext)), -1);
+
+  if (pos != 0 && len !=0)
+    {
+      if (pos == -1)
+        {
+          clutter_text_set_cursor_position (ttext, len - 1);
+          clutter_text_set_selection_bound (ttext, len - 1);
+        }
+      else
+        {
+          clutter_text_set_cursor_position (ttext, pos - 1);
+          clutter_text_set_selection_bound (ttext, pos - 1);
+        }
+      clutter_text_delete_text (ttext, pos-1, pos);;
+    }
+  return TRUE;
+}
+
+
+static void init_commands (ClutterText *ttext)
+{
+  ClutterTextPrivate *priv = ttext->priv;
+  if (priv->commands)
+    return;
+  clutter_text_add_action (ttext, "move-left",       clutter_text_action_move_left);
+  clutter_text_add_action (ttext, "move-right",      clutter_text_action_move_right);
+  clutter_text_add_action (ttext, "move-up",         clutter_text_action_move_up);
+  clutter_text_add_action (ttext, "move-down",       clutter_text_action_move_down);
+  clutter_text_add_action (ttext, "move-start",      clutter_text_action_move_start);
+  clutter_text_add_action (ttext, "move-end",        clutter_text_action_move_end);
+  clutter_text_add_action (ttext, "move-start-line", clutter_text_action_move_start_line);
+  clutter_text_add_action (ttext, "move-end-line",   clutter_text_action_move_end_line);
+  clutter_text_add_action (ttext, "delete-previous", clutter_text_action_delete_previous);
+  clutter_text_add_action (ttext, "delete-next",     clutter_text_action_delete_next);
+  clutter_text_add_action (ttext, "activate",        clutter_text_action_activate);
+  clutter_text_add_action (ttext, "truncate-selection", clutter_text_truncate_selection);
+}
+
+gboolean
+clutter_text_action (ClutterText     *ttext,
+                  const gchar  *command,
+                  ClutterEvent *event)
+{
+  gchar command2[64];
+  gint i;
+  
+  GList *iter;
+  ClutterTextPrivate *priv = ttext->priv;
+
+  for (i=0; command[i] &&
+            command[i]!=' '&&
+            i<62; i++)
+    {
+      command2[i]=command[i];
+    }
+  command2[i]='\0';
+
+  if (!g_str_equal (command2, "move-up") &&
+      !g_str_equal (command2, "move-down"))
+    priv->x_pos = -1;
+
+  for (iter=priv->commands;iter;iter=iter->next)
+    {
+      TextCommand *tcommand = iter->data;
+      if (g_str_equal (command2, tcommand->name))
+        return tcommand->func (ttext, command, event);
+    }
+
+  g_warning ("unhandled text command %s", command);
+  return FALSE;
+}
+
+static gboolean
+clutter_text_key_press (ClutterActor    *actor,
+                     ClutterKeyEvent *kev)
+{
+  ClutterTextPrivate *priv   = CLUTTER_TEXT (actor)->priv;
+  gint             keyval = clutter_key_event_symbol (kev);
+  GList           *iter;
+
+  if (!priv->editable)
+    return FALSE;
+
+  for (iter=priv->mappings;iter;iter=iter->next)
+    {
+      ClutterTextMapping *mapping = iter->data;
+      
+      if (
+          (mapping->keyval == keyval) &&
+            (
+             (mapping->state == 0) || 
+             (mapping->state && (kev->modifier_state & mapping->state)) 
+            ) 
+         )
+        {
+          if (!g_str_equal (mapping->action, "activate") ||
+              priv->activatable)
+            return clutter_text_action (CLUTTER_TEXT (actor), mapping->action, (ClutterEvent*)kev);
+        }
+    }
+
+    {
+      gunichar key_unichar = clutter_key_event_unicode (kev);
+
+      if (key_unichar == '\r')  /* return is reported as CR we want LF */
+        key_unichar = '\n';
+      if (g_unichar_validate (key_unichar))
+        {
+          clutter_text_insert_unichar (CLUTTER_TEXT (actor), key_unichar);
+          return TRUE;
+        }
+    }
+  return FALSE;
+}
+
diff --git a/clutter/clutter-text.h b/clutter/clutter-text.h
new file mode 100644 (file)
index 0000000..908275c
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2006-2008 OpenedHand
+ *
+ * Authored By Øyvind Kolås <pippin@o-hand.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __CLUTTER_TEXT_H__
+#define __CLUTTER_TEXT_H__
+
+#include <clutter/clutter-label.h>
+#include <clutter/clutter-types.h>
+
+G_BEGIN_DECLS
+
+#define CLUTTER_TYPE_TEXT (clutter_text_get_type ())
+
+#define CLUTTER_TEXT(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+  CLUTTER_TYPE_TEXT, ClutterText))
+
+#define CLUTTER_TEXT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+  CLUTTER_TYPE_TEXT, ClutterTextClass))
+
+#define CLUTTER_IS_TEXT(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+  CLUTTER_TYPE_TEXT))
+
+#define CLUTTER_IS_TEXT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+  CLUTTER_TYPE_TEXT))
+
+#define CLUTTER_TEXT_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+  CLUTTER_TYPE_TEXT, ClutterTextClass))
+
+typedef struct _ClutterText        ClutterText;
+typedef struct _ClutterTextPrivate ClutterTextPrivate;
+typedef struct _ClutterTextClass   ClutterTextClass;
+
+struct _ClutterText
+{
+  ClutterLabel     parent_instance;
+
+  /*< private >*/
+  ClutterTextPrivate *priv;
+};
+
+struct _ClutterTextClass
+{
+  ClutterLabelClass parent_class;
+
+  void (* text_changed) (ClutterText        *text);
+  void (* activate)     (ClutterText        *text);
+  void (* cursor_event) (ClutterText        *text,
+                         ClutterGeometry *geometry);
+};
+
+GType clutter_text_get_type (void) G_GNUC_CONST;
+
+ClutterActor *clutter_text_new_full            (const gchar        *font_name,
+                                             const gchar        *text,
+                                             const ClutterColor *color);
+ClutterActor *clutter_text_new_with_text       (const gchar        *font_name,
+                                             const gchar        *text);
+
+void          clutter_text_set_editable        (ClutterText           *label,
+                                             gboolean            editable);
+gboolean      clutter_text_get_editable        (ClutterText           *label);
+void          clutter_text_set_activatable     (ClutterText           *label,
+                                             gboolean            activatable);
+gboolean      clutter_text_get_activatable      (ClutterText          *label);
+
+gint          clutter_text_get_cursor_position (ClutterText           *label);
+void          clutter_text_set_cursor_position (ClutterText           *label,
+                                             gint                position);
+void          clutter_text_set_cursor_visible  (ClutterText           *label,
+                                             gboolean            cursor_visible);
+gboolean      clutter_text_get_cursor_visible  (ClutterText           *label);
+void          clutter_text_set_cursor_color    (ClutterText           *text,
+                                             const ClutterColor *color);
+void          clutter_text_get_cursor_color    (ClutterText           *text,
+                                             ClutterColor       *color);
+void          clutter_text_set_selectable      (ClutterText           *label,
+                                             gboolean            selectable);
+gboolean      clutter_text_get_selectable      (ClutterText           *label);
+void          clutter_text_set_selection_bound (ClutterText           *text,
+                                             gint                selection_bound);
+gint          clutter_text_get_selection_bound (ClutterText           *text);
+gchar *       clutter_text_get_selection       (ClutterText           *text);
+void          clutter_text_insert_unichar      (ClutterText           *ttext,
+                                             gunichar            wc);
+
+
+/* add a custom action that can be used in keybindings */
+void clutter_text_add_action (ClutterText    *ttext,
+                           const gchar *name,
+                           gboolean (*func) (ClutterText            *ttext,
+                                             const gchar         *commandline,
+                                             ClutterEvent        *event));
+
+/* invoke an action registered by you or one of the tidy text default actions */
+gboolean clutter_text_action      (ClutterText            *ttext,
+                                const gchar         *commandline,
+                                ClutterEvent        *event);
+
+void     clutter_text_mappings_clear (ClutterText *ttext);
+
+/* Add a keybinding to handle for the default keypress vfunc handler */
+void     clutter_text_add_mapping (ClutterText           *ttext,
+                                guint               keyval,
+                                ClutterModifierType state,
+                                const gchar        *commandline);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_TEXT_H__ */
index 56c1e17..664c889 100644 (file)
@@ -68,6 +68,7 @@
 #include "clutter-stage.h"
 #include "clutter-stage-manager.h"
 #include "clutter-texture.h"
+#include "clutter-text.h"
 #include "clutter-timeline.h"
 #include "clutter-timeout-pool.h"
 #include "clutter-types.h"
index dcc4e67..3a53616 100644 (file)
@@ -38,7 +38,8 @@ UNIT_TESTS = \
        test-layout.c \
         test-animation.c \
         test-easing.c \
-        test-binding-pool.c
+        test-binding-pool.c \
+        test-text.c
 
 if X11_TESTS
 UNIT_TESTS += test-pixmap.c
diff --git a/tests/interactive/test-text.c b/tests/interactive/test-text.c
new file mode 100644 (file)
index 0000000..6a0393f
--- /dev/null
@@ -0,0 +1,176 @@
+/* Try this text editor, it has issues but does work,
+ * try ctrl+A, ctrl+C, ctrl+V, ctrl+X as well as selecting text with
+ * mouse and keyboard, /Øyvind K
+ */
+
+#include <gmodule.h>
+#include <clutter/clutter.h>
+
+#define FONT "Mono Bold 22px"
+
+static gchar *clipboard = NULL;
+
+static gchar *runes =
+"ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠᛁᚱᚪ᛫ᚷᛖᚻᚹᛦᛚᚳᚢᛗ\n"
+"ᛋᚳᛖᚪᛚ᛫ᚦᛖᚪᚻ᛫ᛗᚪᚾᚾᚪ᛫ᚷᛖᚻᚹᛦᛚᚳ᛫ᛗᛁᚳᛚᚢᚾ᛫ᚻᛦᛏ᛫ᛞᚫᛚᚪᚾ\n"
+"ᚷᛁᚠ᛫ᚻᛖ᛫ᚹᛁᛚᛖ᛫ᚠᚩᚱ᛫ᛞᚱᛁᚻᛏᚾᛖ᛫ᛞᚩᛗᛖᛋ᛫ᚻᛚᛇᛏᚪᚾ᛬\n";
+
+
+static gboolean
+select_all (ClutterText     *ttext,
+            const gchar  *commandline,
+            ClutterEvent *event)
+{
+  gint len;
+  len = g_utf8_strlen (clutter_label_get_text (CLUTTER_LABEL (ttext)), -1);
+
+  clutter_text_set_cursor_position (ttext, 0);
+  clutter_text_set_selection_bound (ttext, len);
+
+  return TRUE;
+}
+
+static gboolean
+copy (ClutterText     *ttext,
+      const gchar  *commandline,
+      ClutterEvent *event)
+{
+  if (clipboard)
+    g_free (clipboard);
+  clipboard = clutter_text_get_selection (ttext);
+  return TRUE;
+}
+
+static gboolean
+paste (ClutterText     *ttext,
+       const gchar  *commandline,
+       ClutterEvent *event)
+{
+  const gchar *p;
+  if (!clipboard)
+    return TRUE;
+
+  for (p=clipboard; *p!='\0'; p=g_utf8_next_char (p))
+    {
+      clutter_text_insert_unichar (ttext, g_utf8_get_char_validated (p, 3));
+    }
+  return TRUE;
+}
+
+static gboolean
+cut (ClutterText     *ttext,
+     const gchar  *commandline,
+     ClutterEvent *event)
+{
+  clutter_text_action (ttext, "copy", NULL);
+  clutter_text_action (ttext, "truncate-selection", NULL);
+  return TRUE;
+}
+
+static gboolean
+pageup (ClutterText     *ttext,
+        const gchar  *commandline,
+        ClutterEvent *event)
+{
+  gint i;
+  for (i=0;i<10;i++)
+    clutter_text_action (ttext, "move-up", event);
+  return TRUE;
+}
+
+static gboolean
+pagedown (ClutterText     *ttext,
+          const gchar  *commandline,
+          ClutterEvent *event)
+{
+  gint i;
+  for (i=0;i<10;i++)
+    clutter_text_action (ttext, "move-down", event);
+  return TRUE;
+}
+
+static void cursor_event (ClutterText        *text,
+                          ClutterGeometry *geometry)
+{
+  gint y;
+  y = clutter_actor_get_y (CLUTTER_ACTOR (text));
+
+  if (y + geometry->y < 50)
+    {
+      clutter_actor_set_y (CLUTTER_ACTOR (text), y + 100);
+    }
+  else if (y + geometry->y > 720)
+    {
+      clutter_actor_set_y (CLUTTER_ACTOR (text), y - 100);
+    }
+
+}
+
+G_MODULE_EXPORT gint
+test_text_main (gint    argc,
+                gchar **argv)
+{
+  ClutterActor         *stage;
+  ClutterActor         *text;
+  ClutterColor          text_color       = {0x33, 0xff, 0x33, 0xff};
+  ClutterColor          cursor_color     = {0xff, 0x33, 0x33, 0xff};
+  ClutterColor          background_color = {0x00, 0x00, 0x00, 0xff};
+  clutter_init (&argc, &argv);
+
+  stage = clutter_stage_get_default ();
+  clutter_stage_set_color (CLUTTER_STAGE (stage), &background_color);
+
+  text = clutter_text_new_full (FONT, "·", &text_color);
+
+  clutter_container_add (CLUTTER_CONTAINER (stage), text, NULL);
+  clutter_actor_set_position (text, 40, 30);
+  clutter_actor_set_width (text, 1024);
+  clutter_label_set_line_wrap (CLUTTER_LABEL (text), TRUE);
+
+  clutter_actor_set_reactive (text, TRUE);
+  clutter_stage_set_key_focus (CLUTTER_STAGE (stage), text);
+
+  clutter_text_set_editable (CLUTTER_TEXT (text), TRUE);
+  clutter_text_set_selectable (CLUTTER_TEXT (text), TRUE);
+  clutter_text_set_cursor_color (CLUTTER_TEXT (text), &cursor_color);
+
+  clutter_text_add_action (CLUTTER_TEXT (text), "select-all", select_all);
+  clutter_text_add_action (CLUTTER_TEXT (text), "copy",       copy);
+  clutter_text_add_action (CLUTTER_TEXT (text), "paste",      paste);
+  clutter_text_add_action (CLUTTER_TEXT (text), "cut",        cut);
+  clutter_text_add_action (CLUTTER_TEXT (text), "pageup",     pageup);
+  clutter_text_add_action (CLUTTER_TEXT (text), "pagedown",   pagedown);
+
+  clutter_text_add_mapping (CLUTTER_TEXT (text),
+        CLUTTER_a, CLUTTER_CONTROL_MASK, "select-all");
+  clutter_text_add_mapping (CLUTTER_TEXT (text),
+        CLUTTER_c, CLUTTER_CONTROL_MASK, "copy");
+  clutter_text_add_mapping (CLUTTER_TEXT (text),
+        CLUTTER_v, CLUTTER_CONTROL_MASK, "paste");
+  clutter_text_add_mapping (CLUTTER_TEXT (text),
+        CLUTTER_x, CLUTTER_CONTROL_MASK, "cut");
+  clutter_text_add_mapping (CLUTTER_TEXT (text),
+        CLUTTER_Page_Up, 0, "pageup");
+  clutter_text_add_mapping (CLUTTER_TEXT (text),
+        CLUTTER_Page_Down, 0, "pagedown");
+
+  if (argv[1])
+    {
+      gchar                *utf8;
+      g_file_get_contents (argv[1], &utf8, NULL, NULL);
+      clutter_label_set_text (CLUTTER_LABEL (text), utf8);
+    }
+  else
+    {
+      clutter_label_set_text (CLUTTER_LABEL (text), runes);
+    }
+
+  g_signal_connect (text, "cursor-event", G_CALLBACK (cursor_event), NULL);
+
+  clutter_actor_set_size (stage, 1024, 768);
+  clutter_actor_show (stage);
+
+  clutter_main ();
+  return 0;
+}