mutter/clutter/clutter-text.c
Emmanuele Bassi a98720ae19 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.
2008-12-11 11:12:48 +00:00

1562 lines
43 KiB
C

/*
* 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;
}