mirror of
https://github.com/brl/mutter.git
synced 2024-11-13 01:36:10 -05:00
ffb500d3a6
Moving the text by a "page" depends on being able to define a "page size" in terms of lines of text. Since we don't define something similar to an Adjustment that allows us to handle this behaviour, we should defer the paging implementation to a higher level class based on ClutterText.
3245 lines
85 KiB
C
3245 lines
85 KiB
C
/*
|
|
* Clutter.
|
|
*
|
|
* An OpenGL based 'interactive canvas' library.
|
|
*
|
|
* Copyright (C) 2008 Intel Corporation.
|
|
*
|
|
* Authored By: Øyvind Kolås <pippin@o-hand.com>
|
|
* Emmanuele Bassi <ebassi@linux.intel.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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:clutter-text
|
|
* @short_description: An actor for displaying and editing text
|
|
*
|
|
* #ClutterText is an actor that displays custom text using Pango
|
|
* as the text rendering engine.
|
|
*
|
|
* #ClutterText also allows inline editing of the text if the
|
|
* actor is set editable using clutter_text_set_editable().
|
|
*
|
|
* Selection using keyboard or pointers can be enabled using
|
|
* clutter_text_set_selectable().
|
|
*
|
|
* #ClutterText is available since Clutter 1.0
|
|
*/
|
|
|
|
/* TODO: undo/redo hooks?
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include "clutter-text.h"
|
|
|
|
#include "clutter-binding-pool.h"
|
|
#include "clutter-debug.h"
|
|
#include "clutter-enum-types.h"
|
|
#include "clutter-keysyms.h"
|
|
#include "clutter-main.h"
|
|
#include "clutter-private.h"
|
|
#include "clutter-units.h"
|
|
|
|
#include "cogl-pango.h"
|
|
|
|
#define DEFAULT_FONT_NAME "Sans 10"
|
|
|
|
/* cursor width in pixels */
|
|
#define DEFAULT_CURSOR_SIZE 2
|
|
|
|
/* We need at least three cached layouts to run the allocation without
|
|
* regenerating a new layout. First the layout will be generated at
|
|
* full width to get the preferred width, then it will be generated at
|
|
* the preferred width to get the preferred height and then it might
|
|
* be regenerated at a different width to get the height for the
|
|
* actual allocated width
|
|
*/
|
|
#define N_CACHED_LAYOUTS 3
|
|
|
|
#define CLUTTER_TEXT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_TEXT, ClutterTextPrivate))
|
|
|
|
typedef struct _LayoutCache LayoutCache;
|
|
|
|
/* Probably move into main */
|
|
static PangoContext *_context = NULL;
|
|
|
|
static const ClutterColor default_cursor_color = { 0, 0, 0, 255 };
|
|
static const ClutterColor default_text_color = { 0, 0, 0, 255 };
|
|
|
|
G_DEFINE_TYPE (ClutterText, clutter_text, CLUTTER_TYPE_ACTOR);
|
|
|
|
struct _LayoutCache
|
|
{
|
|
/* Cached layout. Pango internally caches the computed extents
|
|
* when they are requested so there is no need to cache that as
|
|
* well
|
|
*/
|
|
PangoLayout *layout;
|
|
|
|
/* The width that used to generate this layout */
|
|
ClutterUnit width;
|
|
|
|
/* A number representing the age of this cache (so that when a
|
|
* new layout is needed the last used cache is replaced)
|
|
*/
|
|
guint age;
|
|
};
|
|
|
|
struct _ClutterTextPrivate
|
|
{
|
|
PangoFontDescription *font_desc;
|
|
|
|
gchar *text;
|
|
gchar *font_name;
|
|
|
|
ClutterColor text_color;
|
|
|
|
LayoutCache cached_layouts[N_CACHED_LAYOUTS];
|
|
guint cache_age;
|
|
|
|
PangoAttrList *attrs;
|
|
PangoAttrList *effective_attrs;
|
|
|
|
guint alignment : 2;
|
|
guint wrap : 1;
|
|
guint use_underline : 1;
|
|
guint use_markup : 1;
|
|
guint ellipsize : 3;
|
|
guint single_line_mode : 1;
|
|
guint wrap_mode : 3;
|
|
guint justify : 1;
|
|
guint editable : 1;
|
|
guint cursor_visible : 1;
|
|
guint activatable : 1;
|
|
guint selectable : 1;
|
|
guint in_select_drag : 1;
|
|
guint cursor_color_set : 1;
|
|
guint text_visible : 1;
|
|
|
|
/* current cursor position */
|
|
gint position;
|
|
|
|
/* current 'other end of selection' position */
|
|
gint selection_bound;
|
|
|
|
/* the x position in the pangolayout, used to
|
|
* avoid drifting when repeatedly moving up|down
|
|
*/
|
|
gint x_pos;
|
|
|
|
/* the length of the text, in bytes */
|
|
gint n_bytes;
|
|
|
|
/* the length of the text, in characters */
|
|
gint n_chars;
|
|
|
|
/* Where to draw the cursor */
|
|
ClutterGeometry cursor_pos;
|
|
ClutterColor cursor_color;
|
|
gint cursor_size;
|
|
|
|
gint max_length;
|
|
|
|
gunichar priv_char;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_FONT_NAME,
|
|
PROP_TEXT,
|
|
PROP_COLOR,
|
|
PROP_USE_MARKUP,
|
|
PROP_ATTRIBUTES,
|
|
PROP_ALIGNMENT,
|
|
PROP_LINE_WRAP,
|
|
PROP_LINE_WRAP_MODE,
|
|
PROP_JUSTIFY,
|
|
PROP_ELLIPSIZE,
|
|
PROP_POSITION,
|
|
PROP_SELECTION_BOUND,
|
|
PROP_CURSOR_VISIBLE,
|
|
PROP_CURSOR_COLOR,
|
|
PROP_CURSOR_COLOR_SET,
|
|
PROP_EDITABLE,
|
|
PROP_SELECTABLE,
|
|
PROP_ACTIVATABLE,
|
|
PROP_TEXT_VISIBLE,
|
|
PROP_INVISIBLE_CHAR,
|
|
PROP_MAX_LENGTH
|
|
};
|
|
|
|
enum
|
|
{
|
|
TEXT_CHANGED,
|
|
CURSOR_EVENT,
|
|
ACTIVATE,
|
|
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint text_signals[LAST_SIGNAL] = { 0, };
|
|
|
|
#define offset_real(t,p) \
|
|
((p) == -1 ? g_utf8_strlen ((t), -1) : (p))
|
|
|
|
static gint
|
|
offset_to_bytes (const gchar *text,
|
|
gint pos)
|
|
{
|
|
gchar *c = NULL;
|
|
gint i, j, len;
|
|
|
|
if (pos < 0)
|
|
return strlen (text);
|
|
|
|
c = g_utf8_next_char (text);
|
|
j = 1;
|
|
len = strlen (text);
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
if (&text[i] == c)
|
|
{
|
|
if (j == pos)
|
|
break;
|
|
else
|
|
{
|
|
c = g_utf8_next_char (c);
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
#define bytes_to_offset(t,p) \
|
|
(g_utf8_pointer_to_offset ((t), (t) + (p)))
|
|
|
|
|
|
static inline void
|
|
clutter_text_clear_selection (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
|
|
priv->selection_bound = priv->position;
|
|
}
|
|
|
|
static PangoLayout *
|
|
clutter_text_create_layout_no_cache (ClutterText *text,
|
|
ClutterUnit allocation_width)
|
|
{
|
|
ClutterTextPrivate *priv = text->priv;
|
|
PangoLayout *layout;
|
|
|
|
layout = pango_layout_new (_context);
|
|
|
|
pango_layout_set_font_description (layout, priv->font_desc);
|
|
|
|
if (priv->effective_attrs)
|
|
pango_layout_set_attributes (layout, priv->effective_attrs);
|
|
|
|
pango_layout_set_alignment (layout, priv->alignment);
|
|
pango_layout_set_single_paragraph_mode (layout, priv->single_line_mode);
|
|
pango_layout_set_justify (layout, priv->justify);
|
|
|
|
if (priv->text)
|
|
{
|
|
if (!priv->use_markup)
|
|
{
|
|
if (priv->text_visible)
|
|
pango_layout_set_text (layout, priv->text, priv->n_bytes);
|
|
else
|
|
{
|
|
GString *str = g_string_sized_new (priv->n_bytes);
|
|
gunichar invisible_char;
|
|
gchar buf[7];
|
|
gint char_len, i;
|
|
|
|
if (priv->priv_char != 0)
|
|
invisible_char = priv->priv_char;
|
|
else
|
|
invisible_char = ' ';
|
|
|
|
/* we need to convert the string built of invisible
|
|
* characters into UTF-8 for it to be fed to the Pango
|
|
* layout
|
|
*/
|
|
memset (buf, 0, sizeof (buf));
|
|
char_len = g_unichar_to_utf8 (invisible_char, buf);
|
|
|
|
for (i = 0; i < priv->n_chars; i++)
|
|
g_string_append_len (str, buf, char_len);
|
|
|
|
pango_layout_set_text (layout, str->str, str->len);
|
|
|
|
g_string_free (str, TRUE);
|
|
}
|
|
}
|
|
else
|
|
pango_layout_set_markup (layout, priv->text, -1);
|
|
}
|
|
|
|
if (allocation_width > 0 &&
|
|
(priv->ellipsize != PANGO_ELLIPSIZE_NONE || priv->wrap))
|
|
{
|
|
int layout_width, layout_height;
|
|
|
|
pango_layout_get_size (layout, &layout_width, &layout_height);
|
|
|
|
/* No need to set ellipsize or wrap if we already have enough
|
|
* space, since we don't want to make the layout wider than it
|
|
* would be otherwise.
|
|
*/
|
|
|
|
if (CLUTTER_UNITS_FROM_PANGO_UNIT (layout_width) > allocation_width)
|
|
{
|
|
if (priv->ellipsize != PANGO_ELLIPSIZE_NONE)
|
|
{
|
|
gint width;
|
|
|
|
width = allocation_width > 0
|
|
? CLUTTER_UNITS_TO_PANGO_UNIT (allocation_width)
|
|
: -1;
|
|
|
|
pango_layout_set_ellipsize (layout, priv->ellipsize);
|
|
pango_layout_set_width (layout, width);
|
|
}
|
|
else if (priv->wrap)
|
|
{
|
|
gint width;
|
|
|
|
width = allocation_width > 0
|
|
? CLUTTER_UNITS_TO_PANGO_UNIT (allocation_width)
|
|
: -1;
|
|
|
|
pango_layout_set_wrap (layout, priv->wrap_mode);
|
|
pango_layout_set_width (layout, width);
|
|
}
|
|
}
|
|
}
|
|
|
|
return layout;
|
|
}
|
|
|
|
static void
|
|
clutter_text_dirty_cache (ClutterText *text)
|
|
{
|
|
ClutterTextPrivate *priv = text->priv;
|
|
int i;
|
|
|
|
/* Delete the cached layouts so they will be recreated the next time
|
|
they are needed */
|
|
for (i = 0; i < N_CACHED_LAYOUTS; i++)
|
|
if (priv->cached_layouts[i].layout)
|
|
{
|
|
g_object_unref (priv->cached_layouts[i].layout);
|
|
priv->cached_layouts[i].layout = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* clutter_text_create_layout:
|
|
* @text: a #ClutterText
|
|
* @allocation_width: the allocation width
|
|
*
|
|
* Like clutter_text_create_layout_no_cache(), but will also ensure
|
|
* the glyphs cache. If a previously cached layout generated using the
|
|
* same width is available then that will be used instead of
|
|
* generating a new one.
|
|
*/
|
|
static PangoLayout *
|
|
clutter_text_create_layout (ClutterText *text,
|
|
ClutterUnit allocation_width)
|
|
{
|
|
ClutterTextPrivate *priv = text->priv;
|
|
LayoutCache *oldest_cache = priv->cached_layouts;
|
|
gboolean found_free_cache = FALSE;
|
|
int i;
|
|
|
|
/* Search for a cached layout with the same width and keep track of
|
|
the oldest one */
|
|
for (i = 0; i < N_CACHED_LAYOUTS; i++)
|
|
{
|
|
if (priv->cached_layouts[i].layout == NULL)
|
|
{
|
|
/* Always prefer free cache spaces */
|
|
found_free_cache = TRUE;
|
|
oldest_cache = priv->cached_layouts + i;
|
|
}
|
|
/* If this cached layout is using the same width then we can
|
|
just return that directly */
|
|
else if (priv->cached_layouts[i].width == allocation_width)
|
|
{
|
|
CLUTTER_NOTE (ACTOR, "ClutterText: %p: cache hit for width %i",
|
|
text,
|
|
CLUTTER_UNITS_TO_DEVICE (allocation_width));
|
|
|
|
return priv->cached_layouts[i].layout;
|
|
}
|
|
else if (!found_free_cache &&
|
|
(priv->cached_layouts[i].age < oldest_cache->age))
|
|
{
|
|
oldest_cache = priv->cached_layouts + i;
|
|
}
|
|
}
|
|
|
|
CLUTTER_NOTE (ACTOR, "ClutterText: %p: cache miss for width %i",
|
|
text,
|
|
CLUTTER_UNITS_TO_DEVICE (allocation_width));
|
|
|
|
/* If we make it here then we didn't have a cached version so we
|
|
need to recreate the layout */
|
|
if (oldest_cache->layout)
|
|
g_object_unref (oldest_cache->layout);
|
|
|
|
oldest_cache->layout =
|
|
clutter_text_create_layout_no_cache (text, allocation_width);
|
|
|
|
cogl_pango_ensure_glyph_cache_for_layout (oldest_cache->layout);
|
|
|
|
/* Mark the 'time' this cache was created and advance the time */
|
|
oldest_cache->age = priv->cache_age++;
|
|
oldest_cache->width = allocation_width;
|
|
|
|
return oldest_cache->layout;
|
|
}
|
|
|
|
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_text_get_layout (text),
|
|
px, py,
|
|
&index_, &trailing);
|
|
|
|
return index_ + trailing;
|
|
}
|
|
|
|
/*
|
|
* clutter_text_position_to_coords:
|
|
* @self: a #ClutterText
|
|
* @position: position in characters
|
|
* @x: return location for the X coordinate, or %NULL
|
|
* @y: return location for the Y coordinate, or %NULL
|
|
* @line_height: return location for the line height, or %NULL
|
|
*
|
|
* Retrieves the coordinates of the given @position.
|
|
*
|
|
* Return value: %TRUE if the conversion was successful
|
|
*/
|
|
static gboolean
|
|
clutter_text_position_to_coords (ClutterText *self,
|
|
gint position,
|
|
ClutterUnit *x,
|
|
ClutterUnit *y,
|
|
ClutterUnit *line_height)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
PangoRectangle rect;
|
|
gint priv_char_bytes;
|
|
gint index_;
|
|
|
|
if (!priv->text_visible && priv->priv_char)
|
|
priv_char_bytes = g_unichar_to_utf8 (priv->priv_char, NULL);
|
|
else
|
|
priv_char_bytes = 1;
|
|
|
|
if (position == -1)
|
|
{
|
|
if (priv->text_visible)
|
|
index_ = priv->n_bytes;
|
|
else
|
|
index_ = priv->n_chars * priv_char_bytes;
|
|
}
|
|
else if (position == 0)
|
|
{
|
|
index_ = 0;
|
|
}
|
|
else
|
|
{
|
|
if (priv->text_visible)
|
|
index_ = offset_to_bytes (priv->text, position);
|
|
else
|
|
index_ = priv->position * priv_char_bytes;
|
|
}
|
|
|
|
pango_layout_get_cursor_pos (clutter_text_get_layout (self), index_,
|
|
&rect, NULL);
|
|
|
|
if (x)
|
|
*x = CLUTTER_UNITS_FROM_PANGO_UNIT (rect.x);
|
|
|
|
if (y)
|
|
*y = CLUTTER_UNITS_FROM_PANGO_UNIT (rect.y);
|
|
|
|
if (line_height)
|
|
*line_height = CLUTTER_UNITS_FROM_PANGO_UNIT (rect.height);
|
|
|
|
/* FIXME: should return false if coords were outside text */
|
|
return TRUE;
|
|
}
|
|
|
|
static inline void
|
|
clutter_text_ensure_cursor_position (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
ClutterUnit x, y, cursor_height;
|
|
|
|
x = y = cursor_height = 0;
|
|
clutter_text_position_to_coords (self, priv->position,
|
|
&x, &y,
|
|
&cursor_height);
|
|
|
|
priv->cursor_pos.x = CLUTTER_UNITS_TO_DEVICE (x);
|
|
priv->cursor_pos.y = CLUTTER_UNITS_TO_DEVICE (y);
|
|
priv->cursor_pos.width = priv->cursor_size;
|
|
priv->cursor_pos.height = CLUTTER_UNITS_TO_DEVICE (cursor_height);
|
|
|
|
g_signal_emit (self, text_signals[CURSOR_EVENT], 0, &priv->cursor_pos);
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_truncate_selection (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint start_index;
|
|
gint end_index;
|
|
|
|
if (!priv->text)
|
|
return TRUE;
|
|
|
|
start_index = offset_real (priv->text, priv->position);
|
|
end_index = offset_real (priv->text, 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 (self, start_index, end_index);
|
|
|
|
priv->position = start_index;
|
|
priv->selection_bound = start_index;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clutter_text_set_property (GObject *gobject,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterText *self = CLUTTER_TEXT (gobject);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_TEXT:
|
|
clutter_text_set_text (self, g_value_get_string (value));
|
|
break;
|
|
|
|
case PROP_COLOR:
|
|
clutter_text_set_color (self, clutter_value_get_color (value));
|
|
break;
|
|
|
|
case PROP_FONT_NAME:
|
|
clutter_text_set_font_name (self, g_value_get_string (value));
|
|
break;
|
|
|
|
case PROP_USE_MARKUP:
|
|
clutter_text_set_use_markup (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_ATTRIBUTES:
|
|
clutter_text_set_attributes (self, g_value_get_boxed (value));
|
|
break;
|
|
|
|
case PROP_ALIGNMENT:
|
|
clutter_text_set_alignment (self, g_value_get_enum (value));
|
|
break;
|
|
|
|
case PROP_LINE_WRAP:
|
|
clutter_text_set_line_wrap (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_LINE_WRAP_MODE:
|
|
clutter_text_set_line_wrap_mode (self, g_value_get_enum (value));
|
|
break;
|
|
|
|
case PROP_JUSTIFY:
|
|
clutter_text_set_justify (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_ELLIPSIZE:
|
|
clutter_text_set_ellipsize (self, g_value_get_enum (value));
|
|
break;
|
|
|
|
case PROP_POSITION:
|
|
clutter_text_set_cursor_position (self, g_value_get_int (value));
|
|
break;
|
|
|
|
case PROP_SELECTION_BOUND:
|
|
clutter_text_set_selection_bound (self, g_value_get_int (value));
|
|
break;
|
|
|
|
case PROP_CURSOR_VISIBLE:
|
|
clutter_text_set_cursor_visible (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_CURSOR_COLOR:
|
|
clutter_text_set_cursor_color (self, g_value_get_boxed (value));
|
|
break;
|
|
|
|
case PROP_EDITABLE:
|
|
clutter_text_set_editable (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_ACTIVATABLE:
|
|
clutter_text_set_activatable (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_SELECTABLE:
|
|
clutter_text_set_selectable (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_TEXT_VISIBLE:
|
|
clutter_text_set_text_visible (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_INVISIBLE_CHAR:
|
|
clutter_text_set_invisible_char (self, g_value_get_uint (value));
|
|
break;
|
|
|
|
case PROP_MAX_LENGTH:
|
|
clutter_text_set_max_length (self, g_value_get_int (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_text_get_property (GObject *gobject,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterTextPrivate *priv = CLUTTER_TEXT (gobject)->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_TEXT:
|
|
g_value_set_string (value, priv->text);
|
|
break;
|
|
|
|
case PROP_FONT_NAME:
|
|
g_value_set_string (value, priv->font_name);
|
|
break;
|
|
|
|
case PROP_COLOR:
|
|
clutter_value_set_color (value, &priv->text_color);
|
|
break;
|
|
|
|
case PROP_CURSOR_VISIBLE:
|
|
g_value_set_boolean (value, priv->cursor_visible);
|
|
break;
|
|
|
|
case PROP_CURSOR_COLOR:
|
|
clutter_value_set_color (value, &priv->cursor_color);
|
|
break;
|
|
|
|
case PROP_CURSOR_COLOR_SET:
|
|
g_value_set_boolean (value, priv->cursor_color_set);
|
|
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;
|
|
|
|
case PROP_TEXT_VISIBLE:
|
|
g_value_set_boolean (value, priv->text_visible);
|
|
break;
|
|
|
|
case PROP_INVISIBLE_CHAR:
|
|
g_value_set_uint (value, priv->priv_char);
|
|
break;
|
|
|
|
case PROP_MAX_LENGTH:
|
|
g_value_set_int (value, priv->max_length);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_text_dispose (GObject *gobject)
|
|
{
|
|
ClutterText *self = CLUTTER_TEXT (gobject);
|
|
|
|
/* get rid of the entire cache */
|
|
clutter_text_dirty_cache (self);
|
|
|
|
G_OBJECT_CLASS (clutter_text_parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static void
|
|
clutter_text_finalize (GObject *gobject)
|
|
{
|
|
ClutterText *self = CLUTTER_TEXT (gobject);
|
|
ClutterTextPrivate *priv = self->priv;
|
|
|
|
if (priv->font_desc)
|
|
pango_font_description_free (priv->font_desc);
|
|
|
|
g_free (priv->text);
|
|
g_free (priv->font_name);
|
|
|
|
G_OBJECT_CLASS (clutter_text_parent_class)->finalize (gobject);
|
|
}
|
|
|
|
static void
|
|
cursor_paint (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
ClutterActor *actor = CLUTTER_ACTOR (self);
|
|
guint8 real_opacity;
|
|
|
|
if (priv->editable && priv->cursor_visible)
|
|
{
|
|
if (priv->cursor_color_set)
|
|
{
|
|
real_opacity = clutter_actor_get_paint_opacity (actor)
|
|
* priv->cursor_color.alpha
|
|
/ 255;
|
|
|
|
cogl_set_source_color4ub (priv->cursor_color.red,
|
|
priv->cursor_color.green,
|
|
priv->cursor_color.blue,
|
|
real_opacity);
|
|
}
|
|
else
|
|
{
|
|
real_opacity = clutter_actor_get_paint_opacity (actor)
|
|
* priv->text_color.alpha
|
|
/ 255;
|
|
|
|
cogl_set_source_color4ub (priv->text_color.red,
|
|
priv->text_color.green,
|
|
priv->text_color.blue,
|
|
real_opacity);
|
|
}
|
|
|
|
clutter_text_ensure_cursor_position (self);
|
|
|
|
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
|
|
{
|
|
PangoLayout *layout = clutter_text_get_layout (self);
|
|
const gchar *utf8 = priv->text;
|
|
gint lines;
|
|
gint start_index;
|
|
gint end_index;
|
|
gint line_no;
|
|
|
|
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;
|
|
}
|
|
|
|
lines = pango_layout_get_line_count (layout);
|
|
|
|
for (line_no = 0; line_no < lines; line_no++)
|
|
{
|
|
PangoLayoutLine *line;
|
|
gint n_ranges;
|
|
gint *ranges;
|
|
gint i;
|
|
gint index;
|
|
gint maxindex;
|
|
ClutterUnit y, height;
|
|
|
|
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 (self,
|
|
bytes_to_offset (utf8, index),
|
|
NULL, &y, &height);
|
|
|
|
for (i = 0; i < n_ranges; i++)
|
|
{
|
|
gint range_x;
|
|
gint range_width;
|
|
|
|
range_x = ranges[i * 2]
|
|
/ PANGO_SCALE;
|
|
range_width = (ranges[i * 2 + 1] - ranges[i * 2])
|
|
/ PANGO_SCALE;
|
|
|
|
cogl_rectangle (range_x,
|
|
CLUTTER_UNITS_TO_DEVICE (y),
|
|
range_width,
|
|
CLUTTER_UNITS_TO_DEVICE (height));
|
|
}
|
|
|
|
g_free (ranges);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_button_press (ClutterActor *actor,
|
|
ClutterButtonEvent *event)
|
|
{
|
|
ClutterText *self = CLUTTER_TEXT (actor);
|
|
ClutterTextPrivate *priv = self->priv;
|
|
ClutterUnit x, y;
|
|
gint index_;
|
|
|
|
x = CLUTTER_UNITS_FROM_INT (event->x);
|
|
y = CLUTTER_UNITS_FROM_INT (event->y);
|
|
|
|
clutter_actor_transform_stage_point (actor, x, y, &x, &y);
|
|
|
|
index_ = clutter_text_coords_to_position (self,
|
|
CLUTTER_UNITS_TO_INT (x),
|
|
CLUTTER_UNITS_TO_INT (y));
|
|
|
|
clutter_text_set_cursor_position (self, bytes_to_offset (priv->text, index_));
|
|
clutter_text_set_selection_bound (self, bytes_to_offset (priv->text, index_));
|
|
|
|
/* grab the pointer */
|
|
priv->in_select_drag = TRUE;
|
|
clutter_grab_pointer (actor);
|
|
|
|
/* we'll steal keyfocus if we do not have it */
|
|
clutter_actor_grab_key_focus (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_text_get_text (ttext);
|
|
|
|
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_button_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 gboolean
|
|
clutter_text_key_press (ClutterActor *actor,
|
|
ClutterKeyEvent *event)
|
|
{
|
|
ClutterText *self = CLUTTER_TEXT (actor);
|
|
ClutterTextPrivate *priv = self->priv;
|
|
ClutterBindingPool *pool;
|
|
gboolean res;
|
|
gint keyval;
|
|
|
|
if (!priv->editable)
|
|
return FALSE;
|
|
|
|
keyval = clutter_key_event_symbol (event);
|
|
|
|
pool = clutter_binding_pool_find (G_OBJECT_TYPE_NAME (actor));
|
|
g_assert (pool != NULL);
|
|
|
|
/* we allow passing synthetic events that only contain
|
|
* the Unicode value and not the key symbol
|
|
*/
|
|
if (keyval == 0 && (event->flags & CLUTTER_EVENT_FLAG_SYNTHETIC))
|
|
res = FALSE;
|
|
else
|
|
res = clutter_binding_pool_activate (pool, keyval,
|
|
event->modifier_state,
|
|
G_OBJECT (actor));
|
|
|
|
/* if the key binding has handled the event we bail out
|
|
* as fast as we can; otherwise, we try to insert the
|
|
* Unicode character inside the key event into the text
|
|
* actor
|
|
*/
|
|
if (res)
|
|
return TRUE;
|
|
else
|
|
{
|
|
gunichar key_unichar = clutter_key_event_unicode (event);
|
|
|
|
/* return is reported as CR, but we want LF */
|
|
if (key_unichar == '\r')
|
|
key_unichar = '\n';
|
|
|
|
if (g_unichar_validate (key_unichar))
|
|
{
|
|
/* truncate the eventual selection so that the
|
|
* Unicode character can replace it
|
|
*/
|
|
clutter_text_truncate_selection (self);
|
|
clutter_text_insert_unichar (self, key_unichar);
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
clutter_text_paint (ClutterActor *self)
|
|
{
|
|
ClutterText *text = CLUTTER_TEXT (self);
|
|
ClutterTextPrivate *priv = text->priv;
|
|
PangoLayout *layout;
|
|
ClutterActorBox alloc = { 0, };
|
|
CoglColor color = { 0, };
|
|
guint8 real_opacity;
|
|
|
|
if (priv->font_desc == NULL || priv->text == NULL)
|
|
{
|
|
CLUTTER_NOTE (ACTOR, "desc: %p, text %p",
|
|
priv->font_desc ? priv->font_desc : 0x0,
|
|
priv->text ? priv->text : 0x0);
|
|
return;
|
|
}
|
|
|
|
cursor_paint (text);
|
|
|
|
CLUTTER_NOTE (PAINT, "painting text (text:`%s')", priv->text);
|
|
|
|
clutter_actor_get_allocation_box (self, &alloc);
|
|
layout = clutter_text_create_layout (text, alloc.x2 - alloc.x1);
|
|
|
|
real_opacity = clutter_actor_get_paint_opacity (self)
|
|
* priv->text_color.alpha
|
|
/ 255;
|
|
|
|
cogl_color_set_from_4ub (&color,
|
|
priv->text_color.red,
|
|
priv->text_color.green,
|
|
priv->text_color.blue,
|
|
real_opacity);
|
|
cogl_pango_render_layout (layout, 0, 0, &color, 0);
|
|
}
|
|
|
|
static void
|
|
clutter_text_get_preferred_width (ClutterActor *self,
|
|
ClutterUnit for_height,
|
|
ClutterUnit *min_width_p,
|
|
ClutterUnit *natural_width_p)
|
|
{
|
|
ClutterText *text = CLUTTER_TEXT (self);
|
|
ClutterTextPrivate *priv = text->priv;
|
|
PangoRectangle logical_rect = { 0, };
|
|
PangoLayout *layout;
|
|
gint logical_width;
|
|
ClutterUnit layout_width;
|
|
|
|
layout = clutter_text_create_layout (text, -1);
|
|
|
|
pango_layout_get_extents (layout, NULL, &logical_rect);
|
|
|
|
/* the X coordinate of the logical rectangle might be non-zero
|
|
* according to the Pango documentation; hence, we need to offset
|
|
* the width accordingly
|
|
*/
|
|
logical_width = logical_rect.x + logical_rect.width;
|
|
|
|
layout_width = logical_width > 0
|
|
? CLUTTER_UNITS_FROM_PANGO_UNIT (logical_width)
|
|
: 1;
|
|
|
|
if (min_width_p)
|
|
{
|
|
if (priv->wrap || priv->ellipsize)
|
|
*min_width_p = 1;
|
|
else
|
|
*min_width_p = layout_width;
|
|
}
|
|
|
|
if (natural_width_p)
|
|
*natural_width_p = layout_width;
|
|
}
|
|
|
|
static void
|
|
clutter_text_get_preferred_height (ClutterActor *self,
|
|
ClutterUnit for_width,
|
|
ClutterUnit *min_height_p,
|
|
ClutterUnit *natural_height_p)
|
|
{
|
|
ClutterText *text = CLUTTER_TEXT (self);
|
|
|
|
if (for_width == 0)
|
|
{
|
|
if (min_height_p)
|
|
*min_height_p = 0;
|
|
|
|
if (natural_height_p)
|
|
*natural_height_p = 0;
|
|
}
|
|
else
|
|
{
|
|
PangoLayout *layout;
|
|
PangoRectangle logical_rect = { 0, };
|
|
gint logical_height;
|
|
ClutterUnit layout_height;
|
|
|
|
layout = clutter_text_create_layout (text, for_width);
|
|
|
|
pango_layout_get_extents (layout, NULL, &logical_rect);
|
|
|
|
/* the Y coordinate of the logical rectangle might be non-zero
|
|
* according to the Pango documentation; hence, we need to offset
|
|
* the height accordingly
|
|
*/
|
|
logical_height = logical_rect.y + logical_rect.height;
|
|
|
|
layout_height = CLUTTER_UNITS_FROM_PANGO_UNIT (logical_height);
|
|
|
|
if (min_height_p)
|
|
*min_height_p = layout_height;
|
|
|
|
if (natural_height_p)
|
|
*natural_height_p = layout_height;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_text_allocate (ClutterActor *self,
|
|
const ClutterActorBox *box,
|
|
gboolean origin_changed)
|
|
{
|
|
ClutterText *text = CLUTTER_TEXT (self);
|
|
ClutterActorClass *parent_class;
|
|
|
|
/* Ensure that there is a cached layout with the right width so
|
|
* that we don't need to create the text during the paint run
|
|
*/
|
|
clutter_text_create_layout (text, box->x2 - box->x1);
|
|
|
|
parent_class = CLUTTER_ACTOR_CLASS (clutter_text_parent_class);
|
|
parent_class->allocate (self, box, origin_changed);
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_move_left (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint pos = priv->position;
|
|
gint len;
|
|
|
|
len = g_utf8_strlen (priv->text, -1);
|
|
|
|
if (pos != 0 && len !=0)
|
|
{
|
|
if (pos == -1)
|
|
clutter_text_set_cursor_position (self, len - 1);
|
|
else
|
|
clutter_text_set_cursor_position (self, pos - 1);
|
|
}
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_move_right (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint pos = priv->position;
|
|
gint len;
|
|
|
|
len = g_utf8_strlen (priv->text, -1);
|
|
|
|
if (pos != -1 && len !=0)
|
|
{
|
|
if (pos != len)
|
|
clutter_text_set_cursor_position (self, pos + 1);
|
|
}
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_move_up (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
PangoLayoutLine *layout_line;
|
|
PangoLayout *layout;
|
|
gint line_no;
|
|
gint index_;
|
|
gint x;
|
|
|
|
layout = clutter_text_get_layout (self);
|
|
|
|
if (priv->position == 0)
|
|
index_ = 0;
|
|
else
|
|
index_ = offset_to_bytes (priv->text, priv->position);
|
|
|
|
pango_layout_index_to_line_x (layout, index_,
|
|
0,
|
|
&line_no, &x);
|
|
|
|
line_no -= 1;
|
|
if (line_no < 0)
|
|
return FALSE;
|
|
|
|
if (priv->x_pos != -1)
|
|
x = priv->x_pos;
|
|
else
|
|
priv->x_pos = x;
|
|
|
|
layout_line = pango_layout_get_line_readonly (layout, line_no);
|
|
if (!layout_line)
|
|
return FALSE;
|
|
|
|
pango_layout_line_x_to_index (layout_line, x, &index_, NULL);
|
|
|
|
{
|
|
gint pos = bytes_to_offset (priv->text, index_);
|
|
|
|
clutter_text_set_cursor_position (self, pos);
|
|
}
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_move_down (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
PangoLayoutLine *layout_line;
|
|
PangoLayout *layout;
|
|
gint line_no;
|
|
gint index_;
|
|
gint x;
|
|
|
|
layout = clutter_text_get_layout (self);
|
|
|
|
if (priv->position == 0)
|
|
index_ = 0;
|
|
else
|
|
index_ = offset_to_bytes (priv->text, priv->position);
|
|
|
|
pango_layout_index_to_line_x (layout, index_,
|
|
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 (layout, line_no + 1);
|
|
if (!layout_line)
|
|
return FALSE;
|
|
|
|
pango_layout_line_x_to_index (layout_line, x, &index_, NULL);
|
|
|
|
{
|
|
gint pos = bytes_to_offset (priv->text, index_);
|
|
|
|
clutter_text_set_cursor_position (self, pos);
|
|
}
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_line_start (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
PangoLayoutLine *layout_line;
|
|
PangoLayout *layout;
|
|
gint line_no;
|
|
gint index_;
|
|
gint position;
|
|
|
|
layout = clutter_text_get_layout (self);
|
|
|
|
if (priv->position == 0)
|
|
index_ = 0;
|
|
else
|
|
index_ = offset_to_bytes (priv->text, priv->position);
|
|
|
|
pango_layout_index_to_line_x (layout, index_,
|
|
0,
|
|
&line_no, NULL);
|
|
|
|
layout_line = pango_layout_get_line_readonly (layout, line_no);
|
|
if (!layout_line)
|
|
return FALSE;
|
|
|
|
pango_layout_line_x_to_index (layout_line, 0, &index_, NULL);
|
|
|
|
position = bytes_to_offset (priv->text, index_);
|
|
clutter_text_set_cursor_position (self, position);
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_line_end (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
PangoLayoutLine *layout_line;
|
|
PangoLayout *layout;
|
|
gint line_no;
|
|
gint index_;
|
|
gint trailing;
|
|
gint position;
|
|
|
|
layout = clutter_text_get_layout (self);
|
|
|
|
if (priv->position == 0)
|
|
index_ = 0;
|
|
else
|
|
index_ = offset_to_bytes (priv->text, priv->position);
|
|
|
|
pango_layout_index_to_line_x (layout, index_,
|
|
0,
|
|
&line_no, NULL);
|
|
|
|
layout_line = pango_layout_get_line_readonly (layout, line_no);
|
|
if (!layout_line)
|
|
return FALSE;
|
|
|
|
pango_layout_line_x_to_index (layout_line, G_MAXINT, &index_, &trailing);
|
|
index_ += trailing;
|
|
|
|
position = bytes_to_offset (priv->text, index_);
|
|
|
|
clutter_text_set_cursor_position (self, position);
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_del_next (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint pos;
|
|
gint len;
|
|
|
|
if (clutter_text_truncate_selection (self))
|
|
return TRUE;
|
|
|
|
pos = priv->position;
|
|
len = g_utf8_strlen (priv->text, -1);
|
|
|
|
if (len && pos != -1 && pos < len)
|
|
{
|
|
clutter_text_delete_text (self, pos, pos + 1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_del_prev (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint pos;
|
|
gint len;
|
|
|
|
if (clutter_text_truncate_selection (self))
|
|
return TRUE;
|
|
|
|
pos = priv->position;
|
|
len = g_utf8_strlen (priv->text, -1);
|
|
|
|
if (pos != 0 && len != 0)
|
|
{
|
|
if (pos == -1)
|
|
{
|
|
clutter_text_set_cursor_position (self, len - 1);
|
|
clutter_text_set_selection_bound (self, len - 1);
|
|
}
|
|
else
|
|
{
|
|
clutter_text_set_cursor_position (self, pos - 1);
|
|
clutter_text_set_selection_bound (self, pos - 1);
|
|
}
|
|
|
|
clutter_text_delete_text (self, pos - 1, pos);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_activate (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
|
|
if (priv->activatable)
|
|
{
|
|
g_signal_emit (self, text_signals[ACTIVATE], 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
clutter_text_add_move_binding (ClutterBindingPool *pool,
|
|
const gchar *action,
|
|
guint key_val,
|
|
GCallback callback)
|
|
{
|
|
clutter_binding_pool_install_action (pool, action,
|
|
key_val, 0,
|
|
callback,
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (pool, action,
|
|
key_val, CLUTTER_SHIFT_MASK,
|
|
callback,
|
|
NULL, NULL);
|
|
}
|
|
|
|
static void
|
|
clutter_text_class_init (ClutterTextClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
|
|
ClutterBindingPool *binding_pool;
|
|
GParamSpec *pspec;
|
|
|
|
g_type_class_add_private (klass, sizeof (ClutterTextPrivate));
|
|
|
|
gobject_class->set_property = clutter_text_set_property;
|
|
gobject_class->get_property = clutter_text_get_property;
|
|
gobject_class->dispose = clutter_text_dispose;
|
|
gobject_class->finalize = clutter_text_finalize;
|
|
|
|
actor_class->paint = clutter_text_paint;
|
|
actor_class->get_preferred_width = clutter_text_get_preferred_width;
|
|
actor_class->get_preferred_height = clutter_text_get_preferred_height;
|
|
actor_class->allocate = clutter_text_allocate;
|
|
actor_class->key_press_event = clutter_text_key_press;
|
|
actor_class->button_press_event = clutter_text_button_press;
|
|
actor_class->button_release_event = clutter_text_button_release;
|
|
actor_class->motion_event = clutter_text_motion;
|
|
|
|
/**
|
|
* ClutterText:font-name:
|
|
*
|
|
* The font to be used by the #ClutterText, as a string
|
|
* that can be parsed by pango_font_description_from_string().
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_string ("font-name",
|
|
"Font Name",
|
|
"The font to be used by the text",
|
|
NULL,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_FONT_NAME, pspec);
|
|
|
|
pspec = g_param_spec_string ("text",
|
|
"Text",
|
|
"The text to render",
|
|
NULL,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_TEXT, pspec);
|
|
|
|
pspec = clutter_param_spec_color ("color",
|
|
"Font Color",
|
|
"Color of the font used by the text",
|
|
&default_text_color,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_COLOR, pspec);
|
|
|
|
/**
|
|
* ClutterText:editable:
|
|
*
|
|
* Whether key events delivered to the actor causes editing.
|
|
*/
|
|
pspec = g_param_spec_boolean ("editable",
|
|
"Editable",
|
|
"Whether the text is editable",
|
|
TRUE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_EDITABLE, pspec);
|
|
|
|
/**
|
|
* ClutterText:selectable:
|
|
*
|
|
* Whether it is possible to select text.
|
|
*/
|
|
pspec = g_param_spec_boolean ("selectable",
|
|
"Selectable",
|
|
"Whether the text is selectable",
|
|
TRUE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_SELECTABLE, pspec);
|
|
|
|
/**
|
|
* ClutterText:activatable:
|
|
*
|
|
* Toggles whether return invokes the activate signal or not.
|
|
*/
|
|
pspec = g_param_spec_boolean ("activatable",
|
|
"Activatable",
|
|
"Whether pressing return causes the "
|
|
"activate signal to be emitted",
|
|
TRUE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_ACTIVATABLE, pspec);
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
pspec = g_param_spec_boolean ("cursor-visible",
|
|
"Cursor Visible",
|
|
"Whether the input cursor is visible",
|
|
TRUE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_CURSOR_VISIBLE, pspec);
|
|
|
|
pspec = clutter_param_spec_color ("cursor-color",
|
|
"Cursor Color",
|
|
"Cursor Color",
|
|
&default_cursor_color,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_CURSOR_COLOR, pspec);
|
|
|
|
pspec = g_param_spec_boolean ("cursor-color-set",
|
|
"Cursor Color Set",
|
|
"Whether the cursor color has been set",
|
|
FALSE,
|
|
CLUTTER_PARAM_READABLE);
|
|
g_object_class_install_property (gobject_class, PROP_CURSOR_COLOR_SET, pspec);
|
|
|
|
/**
|
|
* ClutterText:position:
|
|
*
|
|
* The current input cursor position. -1 is taken to be the end of the text
|
|
*/
|
|
pspec = g_param_spec_int ("position",
|
|
"Position",
|
|
"The cursor position",
|
|
-1, G_MAXINT,
|
|
-1,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_POSITION, pspec);
|
|
|
|
/**
|
|
* ClutterText:selection-bound:
|
|
*
|
|
* The current input cursor position. -1 is taken to be the end of the text
|
|
*/
|
|
pspec = g_param_spec_int ("selection-bound",
|
|
"Selection-bound",
|
|
"The cursor position of the other end "
|
|
"of the selection",
|
|
-1, G_MAXINT,
|
|
-1,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_SELECTION_BOUND, pspec);
|
|
|
|
pspec = g_param_spec_boxed ("attributes",
|
|
"Attributes",
|
|
"A list of style attributes to apply to "
|
|
"the contents of the actor",
|
|
PANGO_TYPE_ATTR_LIST,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_ATTRIBUTES, pspec);
|
|
|
|
/**
|
|
* ClutterText:use-markup:
|
|
*
|
|
* Whether the text includes Pango markup. See pango_layout_set_markup()
|
|
* in the Pango documentation.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("use-markup",
|
|
"Use markup",
|
|
"Whether or not the text "
|
|
"includes Pango markup",
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_USE_MARKUP, pspec);
|
|
|
|
/**
|
|
* ClutterText:line-wrap:
|
|
*
|
|
* Whether to wrap the lines of #ClutterText:text if the contents
|
|
* exceed the available allocation. The wrapping strategy is
|
|
* controlled by the #ClutterText:line-wrap-mode property.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("line-wrap",
|
|
"Line wrap",
|
|
"If set, wrap the lines if the text "
|
|
"becomes too wide",
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_LINE_WRAP, pspec);
|
|
|
|
/**
|
|
* ClutterText:line-wrap-mode:
|
|
*
|
|
* If #ClutterText:line-wrap is set to %TRUE, this property will
|
|
* control how the text is wrapped.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_enum ("line-wrap-mode",
|
|
"Line wrap mode",
|
|
"Control how line-wrapping is done",
|
|
PANGO_TYPE_WRAP_MODE,
|
|
PANGO_WRAP_WORD,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_LINE_WRAP_MODE, pspec);
|
|
|
|
pspec = g_param_spec_enum ("ellipsize",
|
|
"Ellipsize",
|
|
"The preferred place to ellipsize the string",
|
|
PANGO_TYPE_ELLIPSIZE_MODE,
|
|
PANGO_ELLIPSIZE_NONE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_ELLIPSIZE, pspec);
|
|
|
|
/**
|
|
* ClutterText:alignment:
|
|
*
|
|
* The preferred alignment for the text. This property controls
|
|
* the alignment of multi-line paragraphs.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_enum ("alignment",
|
|
"Alignment",
|
|
"The preferred alignment for the string, "
|
|
"for multi-line text",
|
|
PANGO_TYPE_ALIGNMENT,
|
|
PANGO_ALIGN_LEFT,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_ALIGNMENT, pspec);
|
|
|
|
/**
|
|
* ClutterText:justify:
|
|
*
|
|
* Whether the contents of the #ClutterText should be justified
|
|
* on both margins.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("justify",
|
|
"Justify",
|
|
"Whether the text should be justified",
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_JUSTIFY, pspec);
|
|
|
|
pspec = g_param_spec_boolean ("text-visible",
|
|
"Text Visible",
|
|
"Whether the text should be visible "
|
|
"or subsituted with an invisible "
|
|
"Unicode character",
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_TEXT_VISIBLE, pspec);
|
|
|
|
pspec = g_param_spec_unichar ("invisible-char",
|
|
"Invisible Character",
|
|
"The Unicode character used when the "
|
|
"text is set as not visible",
|
|
'*',
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_INVISIBLE_CHAR, pspec);
|
|
|
|
pspec = g_param_spec_int ("max-length",
|
|
"Max Length",
|
|
"Maximum length of the text inside the actor",
|
|
-1, G_MAXINT, 0,
|
|
CLUTTER_PARAM_READWRITE);
|
|
g_object_class_install_property (gobject_class, PROP_MAX_LENGTH, pspec);
|
|
|
|
/**
|
|
* ClutterText::text-changed:
|
|
* @self: the #ClutterText that emitted the signal
|
|
*
|
|
* The ::text-changed signal is emitted after @actor's text changes
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
text_signals[TEXT_CHANGED] =
|
|
g_signal_new ("text-changed",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterTextClass, text_changed),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
/**
|
|
* ClutterText::cursor-event:
|
|
* @self: the #ClutterText that emitted the signal
|
|
* @geometry: the coordinates of the cursor
|
|
*
|
|
* FIXME
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
text_signals[CURSOR_EVENT] =
|
|
g_signal_new ("cursor-event",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterTextClass, cursor_event),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__BOXED,
|
|
G_TYPE_NONE, 1,
|
|
CLUTTER_TYPE_GEOMETRY | G_SIGNAL_TYPE_STATIC_SCOPE);
|
|
|
|
/**
|
|
* ClutterText::activate
|
|
* @self: the #ClutterText that emitted the signal
|
|
*
|
|
* The ::activate signal is emitted each time the actor is 'activated'
|
|
* by the user, normally by pressing the 'Enter' key.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
text_signals[ACTIVATE] =
|
|
g_signal_new ("activate",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterTextClass, activate),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
binding_pool = clutter_binding_pool_get_for_class (klass);
|
|
|
|
clutter_text_add_move_binding (binding_pool, "move-left",
|
|
CLUTTER_Left,
|
|
G_CALLBACK (clutter_text_real_move_left));
|
|
clutter_text_add_move_binding (binding_pool, "move-left",
|
|
CLUTTER_KP_Left,
|
|
G_CALLBACK (clutter_text_real_move_left));
|
|
clutter_text_add_move_binding (binding_pool, "move-right",
|
|
CLUTTER_Right,
|
|
G_CALLBACK (clutter_text_real_move_right));
|
|
clutter_text_add_move_binding (binding_pool, "move-right",
|
|
CLUTTER_KP_Right,
|
|
G_CALLBACK (clutter_text_real_move_right));
|
|
clutter_text_add_move_binding (binding_pool, "move-up",
|
|
CLUTTER_Up,
|
|
G_CALLBACK (clutter_text_real_move_up));
|
|
clutter_text_add_move_binding (binding_pool, "move-up",
|
|
CLUTTER_KP_Up,
|
|
G_CALLBACK (clutter_text_real_move_up));
|
|
clutter_text_add_move_binding (binding_pool, "move-down",
|
|
CLUTTER_Down,
|
|
G_CALLBACK (clutter_text_real_move_down));
|
|
clutter_text_add_move_binding (binding_pool, "move-down",
|
|
CLUTTER_KP_Down,
|
|
G_CALLBACK (clutter_text_real_move_down));
|
|
|
|
clutter_binding_pool_install_action (binding_pool, "line-start",
|
|
CLUTTER_Home, 0,
|
|
G_CALLBACK (clutter_text_real_line_start),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "line-start",
|
|
CLUTTER_KP_Home, 0,
|
|
G_CALLBACK (clutter_text_real_line_start),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "line-start",
|
|
CLUTTER_Begin, 0,
|
|
G_CALLBACK (clutter_text_real_line_start),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "line-end",
|
|
CLUTTER_End, 0,
|
|
G_CALLBACK (clutter_text_real_line_end),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "line-end",
|
|
CLUTTER_KP_End, 0,
|
|
G_CALLBACK (clutter_text_real_line_end),
|
|
NULL, NULL);
|
|
|
|
clutter_binding_pool_install_action (binding_pool, "delete-next",
|
|
CLUTTER_Delete, 0,
|
|
G_CALLBACK (clutter_text_real_del_next),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "delete-next",
|
|
CLUTTER_KP_Delete, 0,
|
|
G_CALLBACK (clutter_text_real_del_next),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "delete-prev",
|
|
CLUTTER_BackSpace, 0,
|
|
G_CALLBACK (clutter_text_real_del_prev),
|
|
NULL, NULL);
|
|
|
|
clutter_binding_pool_install_action (binding_pool, "activate",
|
|
CLUTTER_Return, 0,
|
|
G_CALLBACK (clutter_text_real_activate),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "activate",
|
|
CLUTTER_KP_Enter, 0,
|
|
G_CALLBACK (clutter_text_real_activate),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "activate",
|
|
CLUTTER_ISO_Enter, 0,
|
|
G_CALLBACK (clutter_text_real_activate),
|
|
NULL, NULL);
|
|
}
|
|
|
|
static void
|
|
clutter_text_init (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
int i;
|
|
|
|
if (G_UNLIKELY (_context == NULL))
|
|
_context = _clutter_context_create_pango_context (CLUTTER_CONTEXT ());
|
|
|
|
self->priv = priv = CLUTTER_TEXT_GET_PRIVATE (self);
|
|
|
|
priv->alignment = PANGO_ALIGN_LEFT;
|
|
priv->wrap = FALSE;
|
|
priv->wrap_mode = PANGO_WRAP_WORD;
|
|
priv->ellipsize = PANGO_ELLIPSIZE_NONE;
|
|
priv->use_underline = FALSE;
|
|
priv->use_markup = FALSE;
|
|
priv->justify = FALSE;
|
|
|
|
for (i = 0; i < N_CACHED_LAYOUTS; i++)
|
|
priv->cached_layouts[i].layout = NULL;
|
|
|
|
priv->text = NULL;
|
|
|
|
priv->text_color = default_text_color;
|
|
priv->cursor_color = default_cursor_color;
|
|
|
|
priv->font_name = g_strdup (DEFAULT_FONT_NAME);
|
|
priv->font_desc = pango_font_description_from_string (priv->font_name);
|
|
|
|
priv->position = -1;
|
|
priv->selection_bound = -1;
|
|
|
|
priv->x_pos = -1;
|
|
priv->cursor_visible = TRUE;
|
|
priv->editable = FALSE;
|
|
priv->selectable = TRUE;
|
|
|
|
priv->cursor_color_set = FALSE;
|
|
|
|
priv->text_visible = TRUE;
|
|
priv->priv_char = '*';
|
|
|
|
priv->max_length = 0;
|
|
|
|
priv->cursor_size = DEFAULT_CURSOR_SIZE;
|
|
}
|
|
|
|
ClutterActor *
|
|
clutter_text_new (void)
|
|
{
|
|
return g_object_new (CLUTTER_TYPE_TEXT, NULL);
|
|
}
|
|
|
|
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 *self,
|
|
gboolean editable)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->editable != editable)
|
|
{
|
|
priv->editable = editable;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (self))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "editable");
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
clutter_text_get_editable (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
|
|
|
|
return self->priv->editable;
|
|
}
|
|
|
|
|
|
void
|
|
clutter_text_set_selectable (ClutterText *self,
|
|
gboolean selectable)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->selectable != selectable)
|
|
{
|
|
priv->selectable = selectable;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (self))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "selectable");
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
clutter_text_get_selectable (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), TRUE);
|
|
|
|
return self->priv->selectable;
|
|
}
|
|
|
|
|
|
void
|
|
clutter_text_set_activatable (ClutterText *self,
|
|
gboolean activatable)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->activatable != activatable)
|
|
{
|
|
priv->activatable = activatable;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (self))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "activatable");
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
clutter_text_get_activatable (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), TRUE);
|
|
|
|
return self->priv->activatable;
|
|
}
|
|
|
|
void
|
|
clutter_text_set_cursor_visible (ClutterText *self,
|
|
gboolean cursor_visible)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->cursor_visible != cursor_visible)
|
|
{
|
|
priv->cursor_visible = cursor_visible;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (self))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "cursor-visible");
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
clutter_text_get_cursor_visible (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), TRUE);
|
|
|
|
return self->priv->cursor_visible;
|
|
}
|
|
|
|
void
|
|
clutter_text_set_cursor_color (ClutterText *self,
|
|
const ClutterColor *color)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (color)
|
|
{
|
|
priv->cursor_color = *color;
|
|
priv->cursor_color_set = TRUE;
|
|
}
|
|
else
|
|
priv->cursor_color_set = FALSE;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (self))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "cursor-color");
|
|
g_object_notify (G_OBJECT (self), "cursor-color-set");
|
|
}
|
|
|
|
|
|
void
|
|
clutter_text_get_cursor_color (ClutterText *self,
|
|
ClutterColor *color)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
g_return_if_fail (color != NULL);
|
|
|
|
priv = self->priv;
|
|
|
|
*color = priv->cursor_color;
|
|
}
|
|
|
|
gchar *
|
|
clutter_text_get_selection (ClutterText *text)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
const gchar *utf8 = clutter_text_get_text (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 *self,
|
|
gint selection_bound)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->selection_bound != selection_bound)
|
|
{
|
|
priv->selection_bound = selection_bound;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (self))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "selection-bound");
|
|
}
|
|
}
|
|
|
|
gint
|
|
clutter_text_get_selection_bound (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), -1);
|
|
|
|
return self->priv->selection_bound;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_font_name:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the font name as set by clutter_text_set_font_name().
|
|
*
|
|
* Return value: a string containing the font name. The returned
|
|
* string is owned by the #ClutterText actor and should not be
|
|
* modified or freed
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
G_CONST_RETURN gchar *
|
|
clutter_text_get_font_name (ClutterText *text)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (text), NULL);
|
|
|
|
return text->priv->font_name;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_font_name:
|
|
* @self: a #ClutterText
|
|
* @font_name: a font name
|
|
*
|
|
* Sets the font used by a #ClutterText. The @font_name string
|
|
* must be something that can be parsed by the
|
|
* pango_font_description_from_string() function, like:
|
|
*
|
|
* |[
|
|
* clutter_text_set_font_name (text, "Sans 10pt");
|
|
* clutter_text_set_font_name (text, "Serif 16px");
|
|
* clutter_text_set_font_name (text, "Helvetica 10");
|
|
* ]|
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_font_name (ClutterText *self,
|
|
const gchar *font_name)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
PangoFontDescription *desc;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
if (!font_name || font_name[0] == '\0')
|
|
font_name = DEFAULT_FONT_NAME;
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->font_name && strcmp (priv->font_name, font_name) == 0)
|
|
return;
|
|
|
|
desc = pango_font_description_from_string (font_name);
|
|
if (!desc)
|
|
{
|
|
g_warning ("Attempting to create a PangoFontDescription for "
|
|
"font name `%s', but failed.",
|
|
font_name);
|
|
return;
|
|
}
|
|
|
|
g_free (priv->font_name);
|
|
priv->font_name = g_strdup (font_name);
|
|
|
|
if (priv->font_desc)
|
|
pango_font_description_free (priv->font_desc);
|
|
|
|
priv->font_desc = desc;
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
if (priv->text && priv->text[0] != '\0')
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "font-name");
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_text:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves a pointer to the current contents of a #ClutterText
|
|
* actor.
|
|
*
|
|
* If you need a copy of the contents for manipulating, either
|
|
* use g_strdup() on the returned string, or use:
|
|
*
|
|
* |[
|
|
* copy = clutter_text_get_chars (text, 0, -1);
|
|
* ]|
|
|
*
|
|
* Which will return a newly allocated string.
|
|
*
|
|
* Return value: the contents of the actor. The returned string
|
|
* is owned by the #ClutterText actor and should never be
|
|
* modified or freed
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
G_CONST_RETURN gchar *
|
|
clutter_text_get_text (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
|
|
|
|
return self->priv->text;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_text:
|
|
* @self: a #ClutterText
|
|
* @text: the text to set
|
|
*
|
|
* Sets the contents of a #ClutterText actor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_text (ClutterText *self,
|
|
const gchar *text)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
g_return_if_fail (text != NULL);
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->max_length > 0)
|
|
{
|
|
gint len = g_utf8_strlen (text, -1);
|
|
|
|
if (len < priv->max_length)
|
|
{
|
|
g_free (priv->text);
|
|
|
|
priv->text = g_strdup (text);
|
|
priv->n_bytes = strlen (text);
|
|
priv->n_chars = len;
|
|
}
|
|
else
|
|
{
|
|
gchar *n = g_malloc0 (priv->max_length + 1);
|
|
|
|
g_free (priv->text);
|
|
|
|
g_utf8_strncpy (n, text, priv->max_length);
|
|
|
|
priv->text = n;
|
|
priv->n_bytes = strlen (n);
|
|
priv->n_chars = priv->max_length;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_free (priv->text);
|
|
|
|
priv->text = g_strdup (text);
|
|
priv->n_bytes = strlen (text);
|
|
priv->n_chars = g_utf8_strlen (text, -1);
|
|
}
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_signal_emit (self, text_signals[TEXT_CHANGED], 0);
|
|
|
|
g_object_notify (G_OBJECT (self), "text");
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_layout:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the current #PangoLayout used by a #ClutterText actor.
|
|
*
|
|
* Return value: a #PangoLayout. The returned object is owned by
|
|
* the #ClutterText actor and should not be modified or freed
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
PangoLayout *
|
|
clutter_text_get_layout (ClutterText *self)
|
|
{
|
|
ClutterUnit width;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
|
|
|
|
width = clutter_actor_get_widthu (CLUTTER_ACTOR (self));
|
|
|
|
return clutter_text_create_layout (self, width);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_color:
|
|
* @self: a #ClutterText
|
|
* @color: a #ClutterColor
|
|
*
|
|
* Sets the color of the contents of a #ClutterText actor.
|
|
*
|
|
* The overall opacity of the #ClutterText actor will be the
|
|
* result of the alpha value of @color and the composited
|
|
* opacity of the actor itself on the scenegraph, as returned
|
|
* by clutter_actor_get_paint_opacity().
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_color (ClutterText *self,
|
|
const ClutterColor *color)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
g_return_if_fail (color != NULL);
|
|
|
|
priv = self->priv;
|
|
|
|
priv->text_color = *color;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (self))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "color");
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_color:
|
|
* @self: a #ClutterText
|
|
* @color: return location for a #ClutterColor
|
|
*
|
|
* Retrieves the text color as set by clutter_text_get_color().
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_get_color (ClutterText *self,
|
|
ClutterColor *color)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
g_return_if_fail (color != NULL);
|
|
|
|
priv = self->priv;
|
|
|
|
*color = priv->text_color;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_ellipsize:
|
|
* @self: a #ClutterText
|
|
* @mode: a #PangoEllipsizeMode
|
|
*
|
|
* Sets the mode used to ellipsize (add an ellipsis: "...") to the
|
|
* text if there is not enough space to render the entire contents
|
|
* of a #ClutterText actor
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_ellipsize (ClutterText *self,
|
|
PangoEllipsizeMode mode)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE &&
|
|
mode <= PANGO_ELLIPSIZE_END);
|
|
|
|
priv = self->priv;
|
|
|
|
if ((PangoEllipsizeMode) priv->ellipsize != mode)
|
|
{
|
|
priv->ellipsize = mode;
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "ellipsize");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_ellipsize:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Returns the ellipsizing position of a #ClutterText actor, as
|
|
* set by clutter_text_set_ellipsize().
|
|
*
|
|
* Return value: #PangoEllipsizeMode
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
PangoEllipsizeMode
|
|
clutter_text_get_ellipsize (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), PANGO_ELLIPSIZE_NONE);
|
|
|
|
return self->priv->ellipsize;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_line_wrap:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the value set using clutter_text_set_line_wrap().
|
|
*
|
|
* Return value: %TRUE if the #ClutterText actor should wrap
|
|
* its contents
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_get_line_wrap (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
|
|
|
|
return self->priv->wrap;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_line_wrap:
|
|
* @self: a #ClutterText
|
|
* @line_wrap: whether the contents should wrap
|
|
*
|
|
* Sets whether the contents of a #ClutterText actor should wrap,
|
|
* if they don't fit the size assigned to the actor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_line_wrap (ClutterText *self,
|
|
gboolean line_wrap)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->wrap != line_wrap)
|
|
{
|
|
priv->wrap = line_wrap;
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "line-wrap");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_line_wrap_mode:
|
|
* @self: a #ClutterText
|
|
* @wrap_mode: the line wrapping mode
|
|
*
|
|
* If line wrapping is enabled (see clutter_text_set_line_wrap()) this
|
|
* function controls how the line wrapping is performed. The default is
|
|
* %PANGO_WRAP_WORD which means wrap on word boundaries.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_line_wrap_mode (ClutterText *self,
|
|
PangoWrapMode wrap_mode)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->wrap_mode != wrap_mode)
|
|
{
|
|
priv->wrap_mode = wrap_mode;
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "line-wrap-mode");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_line_wrap_mode:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the line wrap mode used by the #ClutterText actor.
|
|
*
|
|
* See clutter_text_set_line_wrap_mode ().
|
|
*
|
|
* Return value: the wrap mode used by the #ClutterText
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
PangoWrapMode
|
|
clutter_text_get_line_wrap_mode (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), PANGO_WRAP_WORD);
|
|
|
|
return self->priv->wrap_mode;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_attributes:
|
|
* @self: a #ClutterText
|
|
* @attrs: a #PangoAttrList or %NULL to unset the attributes
|
|
*
|
|
* Sets the attributes list that are going to be applied to the
|
|
* #ClutterText contents. The attributes set with this function
|
|
* will be ignored if the #ClutterText:use_markup property is
|
|
* set to %TRUE.
|
|
*
|
|
* The #ClutterText actor will take a reference on the #PangoAttrList
|
|
* passed to this function.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_attributes (ClutterText *self,
|
|
PangoAttrList *attrs)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (attrs)
|
|
pango_attr_list_ref (attrs);
|
|
|
|
if (priv->attrs)
|
|
pango_attr_list_unref (priv->attrs);
|
|
|
|
if (!priv->use_markup)
|
|
{
|
|
if (attrs)
|
|
pango_attr_list_ref (attrs);
|
|
|
|
if (priv->effective_attrs)
|
|
pango_attr_list_unref (priv->effective_attrs);
|
|
|
|
priv->effective_attrs = attrs;
|
|
}
|
|
|
|
priv->attrs = attrs;
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
g_object_notify (G_OBJECT (self), "attributes");
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_attributes:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Gets the attribute list that was set on the #ClutterText actor
|
|
* clutter_text_set_attributes(), if any.
|
|
*
|
|
* Return value: the attribute list, or %NULL if none was set. The
|
|
* returned value is owned by the #ClutterText and should not be
|
|
* unreferenced.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
PangoAttrList *
|
|
clutter_text_get_attributes (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
|
|
|
|
return self->priv->attrs;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_alignment:
|
|
* @self: a #ClutterText
|
|
* @alignment: A #PangoAlignment
|
|
*
|
|
* Sets text alignment of the #ClutterText actor.
|
|
*
|
|
* The alignment will only be used when the contents of the
|
|
* #ClutterText actor are enough to wrap, and the #ClutterText:line-wrap
|
|
* property is set to %TRUE.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_alignment (ClutterText *self,
|
|
PangoAlignment alignment)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->alignment != alignment)
|
|
{
|
|
priv->alignment = alignment;
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "alignment");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_alignment:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the alignment of @self.
|
|
*
|
|
* Return value: a #PangoAlignment
|
|
*
|
|
* Since 1.0
|
|
*/
|
|
PangoAlignment
|
|
clutter_text_get_alignment (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), PANGO_ALIGN_LEFT);
|
|
|
|
return self->priv->alignment;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_use_markup:
|
|
* @self: a #ClutterText
|
|
* @setting: %TRUE if the text should be parsed for markup.
|
|
*
|
|
* Sets whether the contents of the #ClutterText actor contains markup
|
|
* in <link linkend="PangoMarkupFormat">Pango's text markup language</link>.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_use_markup (ClutterText *self,
|
|
gboolean setting)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->use_markup != setting)
|
|
{
|
|
priv->use_markup = setting;
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "use-markup");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_use_markup:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves whether the contents of the #ClutterText actor should be
|
|
* parsed for the Pango text markup.
|
|
*
|
|
* Return value: %TRUE if the contents will be parsed for markup
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_get_use_markup (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
|
|
|
|
return self->priv->use_markup;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_justify:
|
|
* @self: a #ClutterText
|
|
* @justify: whether the text should be justified
|
|
*
|
|
* Sets whether the text of the #ClutterText actor should be justified
|
|
* on both margins. This setting is ignored if Clutter is compiled
|
|
* against Pango < 1.18.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_justify (ClutterText *self,
|
|
gboolean justify)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->justify != justify)
|
|
{
|
|
priv->justify = justify;
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "justify");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_justify:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves whether the #ClutterText actor should justify its contents
|
|
* on both margins.
|
|
*
|
|
* Return value: %TRUE if the text should be justified
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
gboolean
|
|
clutter_text_get_justify (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
|
|
|
|
return self->priv->justify;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_cursor_position:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the cursor position.
|
|
*
|
|
* Return value: the cursor position, in characters
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gint
|
|
clutter_text_get_cursor_position (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), -1);
|
|
|
|
return self->priv->position;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_cursor_position:
|
|
* @self: a #ClutterText
|
|
* @position: the new cursor position, in characters
|
|
*
|
|
* Sets the cursor of a #ClutterText actor at @position.
|
|
*
|
|
* The position is expressed in characters, not in bytes.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_cursor_position (ClutterText *self,
|
|
gint position)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
gint len;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
len = g_utf8_strlen (priv->text, -1);
|
|
|
|
if (position < 0 || position >= len)
|
|
priv->position = -1;
|
|
else
|
|
priv->position = position;
|
|
|
|
if (CLUTTER_ACTOR_IS_VISIBLE (self))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_text_visible:
|
|
* @self: a #ClutterText
|
|
* @visible: %TRUE if the contents of the actor are displayed as plain text.
|
|
*
|
|
* Sets whether the contents of the text actor are visible or not. When
|
|
* visibility is set to %FALSE, characters are displayed as the invisible
|
|
* char, and will also appear that way when the text in the text actor is
|
|
* copied elsewhere.
|
|
*
|
|
* The default invisible char is the asterisk '*', but it can be changed with
|
|
* clutter_text_set_invisible_char().
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_text_visible (ClutterText *self,
|
|
gboolean visible)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->text_visible != visible)
|
|
{
|
|
priv->text_visible = visible;
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify (G_OBJECT (self), "text-visible");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_text_visible:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the actor's text visibility.
|
|
*
|
|
* Return value: %TRUE if the contents of the actor are displayed as plaintext
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_get_text_visible (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), TRUE);
|
|
|
|
return self->priv->text_visible;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_invisible_char:
|
|
* @self: a #ClutterText
|
|
* @wc: a Unicode character
|
|
*
|
|
* Sets the character to use in place of the actual text when
|
|
* clutter_text_set_text_visible() has been called to set text visibility
|
|
* to %FALSE. i.e. this is the character used in "password mode" to show the
|
|
* user how many characters have been typed. The default invisible char is an
|
|
* asterisk ('*'). If you set the invisible char to 0, then the user will get
|
|
* no feedback at all: there will be no text on the screen as they type.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_invisible_char (ClutterText *self,
|
|
gunichar wc)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
priv->priv_char = wc;
|
|
|
|
if (priv->text_visible)
|
|
{
|
|
clutter_text_dirty_cache (self);
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
}
|
|
|
|
g_object_notify (G_OBJECT (self), "invisible-char");
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_invisible_char:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Returns the character to use in place of the actual text when
|
|
* the #ClutterText:text-visibility property is set to %FALSE.
|
|
*
|
|
* Return value: a Unicode character
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gunichar
|
|
clutter_text_get_invisible_char (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), '*');
|
|
|
|
return self->priv->priv_char;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_max_length:
|
|
* @self: a #ClutterText
|
|
* @max: the maximum number of characters allowed in the text actor; 0
|
|
* to disable or -1 to set the length of the current string
|
|
*
|
|
* Sets the maximum allowed length of the contents of the actor. If the
|
|
* current contents are longer than the given length, then they will be
|
|
* truncated to fit.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_max_length (ClutterText *self,
|
|
gint max)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
gchar *new = NULL;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->max_length != max)
|
|
{
|
|
if (max < 0)
|
|
max = g_utf8_strlen (priv->text, -1);
|
|
|
|
priv->max_length = max;
|
|
|
|
new = g_strdup (priv->text);
|
|
clutter_text_set_text (self, new);
|
|
g_free (new);
|
|
|
|
g_object_notify (G_OBJECT (self), "max-length");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_max_length:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Gets the maximum length of text that can be set into a text actor.
|
|
*
|
|
* See clutter_text_set_max_length().
|
|
*
|
|
* Return value: the maximum number of characters.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gint
|
|
clutter_text_get_max_length (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0);
|
|
|
|
return self->priv->max_length;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_insert_unichar:
|
|
* @self: a #ClutterText
|
|
* @wc: a Unicode character
|
|
*
|
|
* Inserts @wc at the current cursor position of a
|
|
* #ClutterText actor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_insert_unichar (ClutterText *self,
|
|
gunichar wc)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
GString *new = NULL;
|
|
glong pos;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
g_return_if_fail (g_unichar_validate (wc));
|
|
|
|
if (wc == 0)
|
|
return;
|
|
|
|
priv = self->priv;
|
|
|
|
new = g_string_new (priv->text);
|
|
|
|
if (priv->text)
|
|
pos = offset_to_bytes (priv->text, priv->position);
|
|
else
|
|
pos = 0;
|
|
|
|
new = g_string_insert_unichar (new, pos, wc);
|
|
|
|
clutter_text_set_text (self, new->str);
|
|
|
|
if (priv->position >= 0)
|
|
{
|
|
clutter_text_set_cursor_position (self, priv->position + 1);
|
|
clutter_text_set_selection_bound (self, priv->position);
|
|
}
|
|
|
|
g_string_free (new, TRUE);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_insert_text:
|
|
* @self: a #ClutterText
|
|
* @text: the text to be inserted
|
|
* @position: the position of the insertion, or -1
|
|
*
|
|
* Inserts @text into a #ClutterActor at the given position.
|
|
*
|
|
* If @position is a negative number, the text will be appended
|
|
* at the end of the current contents of the #ClutterText.
|
|
*
|
|
* The position is expressed in characters, not in bytes.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_insert_text (ClutterText *self,
|
|
const gchar *text,
|
|
gssize position)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
GString *new = NULL;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
g_return_if_fail (text != NULL);
|
|
|
|
priv = self->priv;
|
|
|
|
new = g_string_new (priv->text);
|
|
new = g_string_insert (new, position, text);
|
|
|
|
clutter_text_set_text (self, new->str);
|
|
|
|
g_string_free (new, TRUE);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_delete_text:
|
|
* @self: a #ClutterText
|
|
* @start_pos: starting position
|
|
* @end_pos: ending position
|
|
*
|
|
* Deletes the text inside a #ClutterText actor between @start_pos
|
|
* and @end_pos.
|
|
*
|
|
* The starting and ending positions are expressed in characters,
|
|
* not in bytes.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_delete_text (ClutterText *self,
|
|
gssize start_pos,
|
|
gssize end_pos)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
GString *new = NULL;
|
|
gint start_bytes;
|
|
gint end_bytes;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (!priv->text)
|
|
return;
|
|
|
|
if (end_pos == -1)
|
|
{
|
|
start_bytes = offset_to_bytes (priv->text,
|
|
g_utf8_strlen (priv->text, -1) - 1);
|
|
end_bytes = offset_to_bytes (priv->text,
|
|
g_utf8_strlen (priv->text, -1));
|
|
}
|
|
else
|
|
{
|
|
start_bytes = offset_to_bytes (priv->text, start_pos);
|
|
end_bytes = offset_to_bytes (priv->text, end_pos);
|
|
}
|
|
|
|
new = g_string_new (priv->text);
|
|
new = g_string_erase (new, start_bytes, end_bytes - start_bytes);
|
|
|
|
clutter_text_set_text (self, new->str);
|
|
|
|
g_string_free (new, TRUE);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_delete_chars:
|
|
* @self: a #ClutterText
|
|
* @n_chars: the number of characters to delete
|
|
*
|
|
* Deletes @n_chars inside a #ClutterText actor, starting from the
|
|
* current cursor position.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_delete_chars (ClutterText *self,
|
|
guint n_chars)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
GString *new = NULL;
|
|
gint len;
|
|
gint pos;
|
|
gint num_pos;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (!priv->text)
|
|
return;
|
|
|
|
len = g_utf8_strlen (priv->text, -1);
|
|
new = g_string_new (priv->text);
|
|
|
|
if (priv->position == -1)
|
|
{
|
|
num_pos = offset_to_bytes (priv->text, priv->n_chars - n_chars);
|
|
new = g_string_erase (new, num_pos, -1);
|
|
}
|
|
else
|
|
{
|
|
pos = offset_to_bytes (priv->text, priv->position - n_chars);
|
|
num_pos = offset_to_bytes (priv->text, priv->position);
|
|
new = g_string_erase (new, pos, num_pos - pos);
|
|
}
|
|
|
|
clutter_text_set_text (self, new->str);
|
|
|
|
if (priv->position > 0)
|
|
clutter_text_set_cursor_position (self, priv->position - n_chars);
|
|
|
|
g_string_free (new, TRUE);
|
|
|
|
g_object_notify (G_OBJECT (self), "text");
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_chars:
|
|
* @self: a #ClutterText
|
|
* @start_pos: start of text, in characters
|
|
* @end_pos: end of text, in characters
|
|
*
|
|
* Retrieves the contents of the #ClutterText actor between
|
|
* @start_pos and @end_pos.
|
|
*
|
|
* The positions are specified in characters, not in bytes.
|
|
*
|
|
* Return value: a newly allocated string with the contents of
|
|
* the text actor between the specified positions. Use g_free()
|
|
* to free the resources when done
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gchar *
|
|
clutter_text_get_chars (ClutterText *self,
|
|
gssize start_pos,
|
|
gssize end_pos)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
gint start_index, end_index;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
|
|
|
|
priv = self->priv;
|
|
|
|
if (end_pos < 0)
|
|
end_pos = priv->n_chars;
|
|
|
|
start_pos = MIN (priv->n_chars, start_pos);
|
|
end_pos = MIN (priv->n_chars, end_pos);
|
|
|
|
start_index = g_utf8_offset_to_pointer (priv->text, start_pos)
|
|
- priv->text;
|
|
end_index = g_utf8_offset_to_pointer (priv->text, end_pos)
|
|
- priv->text;
|
|
|
|
return g_strndup (priv->text + start_index, end_index - start_index);
|
|
}
|