From a98720ae19f4892676c043bb6e4f2258eb201827 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Thu, 11 Dec 2008 11:12:48 +0000 Subject: [PATCH] Initial import of the Text actor from Tidy 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 | 2 + clutter/clutter-text.c | 1561 +++++++++++++++++++++++++++++++++ clutter/clutter-text.h | 136 +++ clutter/clutter.h | 1 + tests/interactive/Makefile.am | 3 +- tests/interactive/test-text.c | 176 ++++ 6 files changed, 1878 insertions(+), 1 deletion(-) create mode 100644 clutter/clutter-text.c create mode 100644 clutter/clutter-text.h create mode 100644 tests/interactive/test-text.c diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 97c9907ad..f51523200 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -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 index 000000000..6733fe1bd --- /dev/null +++ b/clutter/clutter-text.c @@ -0,0 +1,1561 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2006-2008 OpenedHand + * + * Authored By Øyvind Kolås + * + * 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 + +#include "clutter-text.h" +#include + +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;ipriv; + 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 index 000000000..908275cc2 --- /dev/null +++ b/clutter/clutter-text.h @@ -0,0 +1,136 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Copyright (C) 2006-2008 OpenedHand + * + * Authored By Øyvind Kolås + * + * 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 +#include + +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__ */ diff --git a/clutter/clutter.h b/clutter/clutter.h index 56c1e1789..664c88970 100644 --- a/clutter/clutter.h +++ b/clutter/clutter.h @@ -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" diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am index dcc4e673a..3a536162d 100644 --- a/tests/interactive/Makefile.am +++ b/tests/interactive/Makefile.am @@ -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 index 000000000..6a0393f44 --- /dev/null +++ b/tests/interactive/test-text.c @@ -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 +#include + +#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; +}