mirror of
https://github.com/brl/mutter.git
synced 2024-11-30 03:50:47 -05:00
494629d4b9
This new function can be used to retrieve the coordinates where the ClutterText will draw the PangoLayout representing the text. http://bugzilla.clutter-project.org/show_bug.cgi?id=2553
5517 lines
151 KiB
C
5517 lines
151 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 <math.h>
|
|
|
|
#include "clutter-text.h"
|
|
|
|
#include "clutter-actor-private.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-marshal.h"
|
|
#include "clutter-private.h" /* includes <cogl-pango/cogl-pango.h> */
|
|
#include "clutter-profile.h"
|
|
#include "clutter-units.h"
|
|
#include "clutter-paint-volume-private.h"
|
|
|
|
/* 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
|
|
*
|
|
* since we might get multiple queries from layout managers doing a
|
|
* double-pass allocations, like tabular ones, we should use 6 slots
|
|
*/
|
|
#define N_CACHED_LAYOUTS 6
|
|
|
|
#define CLUTTER_TEXT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_TEXT, ClutterTextPrivate))
|
|
|
|
typedef struct _LayoutCache LayoutCache;
|
|
|
|
static const ClutterColor default_cursor_color = { 0, 0, 0, 255 };
|
|
static const ClutterColor default_selection_color = { 0, 0, 0, 255 };
|
|
static const ClutterColor default_text_color = { 0, 0, 0, 255 };
|
|
static const ClutterColor default_selected_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;
|
|
|
|
/* 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;
|
|
|
|
/* the displayed text */
|
|
gchar *text;
|
|
|
|
gchar *font_name;
|
|
|
|
gchar *preedit_str;
|
|
|
|
ClutterColor text_color;
|
|
|
|
LayoutCache cached_layouts[N_CACHED_LAYOUTS];
|
|
guint cache_age;
|
|
|
|
/* These are the attributes set by the attributes property */
|
|
PangoAttrList *attrs;
|
|
/* These are the attributes derived from the text when the
|
|
use-markup property is set */
|
|
PangoAttrList *markup_attrs;
|
|
/* This is the combination of the above two lists. It is set to NULL
|
|
whenever either of them changes and then regenerated by merging
|
|
the two lists whenever a layout is needed */
|
|
PangoAttrList *effective_attrs;
|
|
/* These are the attributes for the preedit string. These are merged
|
|
with the effective attributes into a temporary list before
|
|
creating a layout */
|
|
PangoAttrList *preedit_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 selection_color_set : 1;
|
|
guint in_select_drag : 1;
|
|
guint cursor_color_set : 1;
|
|
guint preedit_set : 1;
|
|
guint is_default_font : 1;
|
|
guint has_focus : 1;
|
|
guint selected_text_color_set : 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 x position of the PangoLayout when in
|
|
* single line mode, to scroll the contents of the
|
|
* text actor
|
|
*/
|
|
gint text_x;
|
|
|
|
/* the y position of the PangoLayout, fixed to 0 by
|
|
* default for now */
|
|
gint text_y;
|
|
|
|
/* 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;
|
|
guint cursor_size;
|
|
|
|
/* Box representing the paint volume. The box is lazily calculated
|
|
and cached */
|
|
ClutterPaintVolume paint_volume;
|
|
gboolean paint_volume_valid;
|
|
|
|
guint preedit_cursor_pos;
|
|
gint preedit_n_chars;
|
|
|
|
ClutterColor selection_color;
|
|
|
|
ClutterColor selected_text_color;
|
|
|
|
gint max_length;
|
|
|
|
gunichar password_char;
|
|
|
|
/* Signal handler for when the backend changes its font settings */
|
|
guint font_changed_id;
|
|
|
|
/* Signal handler for when the :text-direction changes */
|
|
guint direction_changed_id;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_FONT_NAME,
|
|
PROP_FONT_DESCRIPTION,
|
|
PROP_TEXT,
|
|
PROP_COLOR,
|
|
PROP_USE_MARKUP,
|
|
PROP_ATTRIBUTES,
|
|
PROP_LINE_ALIGNMENT,
|
|
PROP_LINE_WRAP,
|
|
PROP_LINE_WRAP_MODE,
|
|
PROP_JUSTIFY,
|
|
PROP_ELLIPSIZE,
|
|
PROP_POSITION,
|
|
PROP_SELECTION_BOUND,
|
|
PROP_SELECTION_COLOR,
|
|
PROP_SELECTION_COLOR_SET,
|
|
PROP_CURSOR_VISIBLE,
|
|
PROP_CURSOR_COLOR,
|
|
PROP_CURSOR_COLOR_SET,
|
|
PROP_CURSOR_SIZE,
|
|
PROP_EDITABLE,
|
|
PROP_SELECTABLE,
|
|
PROP_ACTIVATABLE,
|
|
PROP_PASSWORD_CHAR,
|
|
PROP_MAX_LENGTH,
|
|
PROP_SINGLE_LINE_MODE,
|
|
PROP_SELECTED_TEXT_COLOR,
|
|
PROP_SELECTED_TEXT_COLOR_SET,
|
|
|
|
PROP_LAST
|
|
};
|
|
|
|
static GParamSpec *obj_props[PROP_LAST];
|
|
|
|
enum
|
|
{
|
|
TEXT_CHANGED,
|
|
CURSOR_EVENT,
|
|
ACTIVATE,
|
|
INSERT_TEXT,
|
|
DELETE_TEXT,
|
|
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint text_signals[LAST_SIGNAL] = { 0, };
|
|
|
|
static void clutter_text_font_changed_cb (ClutterText *text);
|
|
|
|
static void
|
|
clutter_text_dirty_paint_volume (ClutterText *text)
|
|
{
|
|
ClutterTextPrivate *priv = text->priv;
|
|
|
|
if (priv->paint_volume_valid)
|
|
{
|
|
clutter_paint_volume_free (&priv->paint_volume);
|
|
priv->paint_volume_valid = FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_text_queue_redraw (ClutterActor *self)
|
|
{
|
|
/* This is a wrapper for clutter_actor_queue_redraw that also
|
|
dirties the cached paint volume. It would be nice if we could
|
|
just override the default implementation of the queue redraw
|
|
signal to do this instead but that doesn't work because the
|
|
signal isn't immediately emitted when queue_redraw is called.
|
|
Clutter will however immediately call get_paint_volume when
|
|
queue_redraw is called so we do need to dirty it immediately. */
|
|
|
|
clutter_text_dirty_paint_volume (CLUTTER_TEXT (self));
|
|
|
|
clutter_actor_queue_redraw (self);
|
|
}
|
|
|
|
#define clutter_actor_queue_redraw \
|
|
Please_use_clutter_text_queue_redraw_instead
|
|
|
|
#define offset_real(t,p) ((p) == -1 ? g_utf8_strlen ((t), -1) : (p))
|
|
|
|
static gint
|
|
offset_to_bytes (const gchar *text,
|
|
gint pos)
|
|
{
|
|
const gchar *ptr;
|
|
|
|
if (pos < 0)
|
|
return strlen (text);
|
|
|
|
/* Loop over each character in the string until we either reach the
|
|
end or the requested position */
|
|
for (ptr = text; *ptr && pos-- > 0; ptr = g_utf8_next_char (ptr));
|
|
|
|
return ptr - text;
|
|
}
|
|
|
|
#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;
|
|
|
|
if (priv->selection_bound != priv->position)
|
|
{
|
|
priv->selection_bound = priv->position;
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_BOUND]);
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
}
|
|
}
|
|
|
|
static gchar *
|
|
clutter_text_get_display_text (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
|
|
if (priv->text == NULL)
|
|
return g_strdup ("");
|
|
|
|
if (G_LIKELY (priv->password_char == 0))
|
|
return g_strndup (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;
|
|
|
|
invisible_char = priv->password_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);
|
|
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_text_ensure_effective_attributes (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
|
|
/* If we already have the effective attributes then we don't need to
|
|
do anything */
|
|
if (priv->effective_attrs == NULL)
|
|
{
|
|
if (priv->attrs != NULL)
|
|
{
|
|
/* If there are no markup attributes then we can just use
|
|
these attributes directly */
|
|
if (priv->markup_attrs == NULL)
|
|
priv->effective_attrs = pango_attr_list_ref (priv->attrs);
|
|
else
|
|
{
|
|
/* Otherwise we need to merge the two lists */
|
|
PangoAttrIterator *iter;
|
|
GSList *attributes, *l;
|
|
|
|
priv->effective_attrs = pango_attr_list_copy (priv->markup_attrs);
|
|
|
|
iter = pango_attr_list_get_iterator (priv->attrs);
|
|
do
|
|
{
|
|
attributes = pango_attr_iterator_get_attrs (iter);
|
|
|
|
for (l = attributes; l != NULL; l = l->next)
|
|
{
|
|
PangoAttribute *attr = l->data;
|
|
|
|
pango_attr_list_insert (priv->effective_attrs, attr);
|
|
}
|
|
|
|
g_slist_free (attributes);
|
|
}
|
|
while (pango_attr_iterator_next (iter));
|
|
}
|
|
}
|
|
else if (priv->markup_attrs != NULL)
|
|
{
|
|
/* We can just use the markup attributes directly */
|
|
priv->effective_attrs = pango_attr_list_ref (priv->markup_attrs);
|
|
}
|
|
}
|
|
}
|
|
|
|
static PangoLayout *
|
|
clutter_text_create_layout_no_cache (ClutterText *text,
|
|
gint width,
|
|
gint height,
|
|
PangoEllipsizeMode ellipsize)
|
|
{
|
|
ClutterTextPrivate *priv = text->priv;
|
|
PangoLayout *layout;
|
|
gchar *contents;
|
|
gsize contents_len;
|
|
|
|
CLUTTER_STATIC_TIMER (text_layout_timer,
|
|
"Mainloop",
|
|
"Text Layout",
|
|
"Layout creation",
|
|
0);
|
|
|
|
CLUTTER_TIMER_START (_clutter_uprof_context, text_layout_timer);
|
|
|
|
layout = clutter_actor_create_pango_layout (CLUTTER_ACTOR (text), NULL);
|
|
pango_layout_set_font_description (layout, priv->font_desc);
|
|
|
|
contents = clutter_text_get_display_text (text);
|
|
contents_len = strlen (contents);
|
|
|
|
if (priv->editable && priv->preedit_set)
|
|
{
|
|
GString *tmp = g_string_new (contents);
|
|
PangoAttrList *tmp_attrs = pango_attr_list_new ();
|
|
gint cursor_index;
|
|
|
|
if (priv->position == 0)
|
|
cursor_index = 0;
|
|
else
|
|
cursor_index = offset_to_bytes (contents, priv->position);
|
|
|
|
g_string_insert (tmp, cursor_index, priv->preedit_str);
|
|
|
|
pango_layout_set_text (layout, tmp->str, tmp->len);
|
|
|
|
if (priv->preedit_attrs != NULL)
|
|
{
|
|
pango_attr_list_splice (tmp_attrs, priv->preedit_attrs,
|
|
cursor_index,
|
|
strlen (priv->preedit_str));
|
|
|
|
pango_layout_set_attributes (layout, tmp_attrs);
|
|
}
|
|
|
|
g_string_free (tmp, TRUE);
|
|
pango_attr_list_unref (tmp_attrs);
|
|
}
|
|
else
|
|
pango_layout_set_text (layout, contents, contents_len);
|
|
|
|
if (!priv->editable)
|
|
{
|
|
/* This will merge the markup attributes and the attributes
|
|
property if needed */
|
|
clutter_text_ensure_effective_attributes (text);
|
|
|
|
if (priv->effective_attrs != NULL)
|
|
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);
|
|
pango_layout_set_wrap (layout, priv->wrap_mode);
|
|
|
|
pango_layout_set_ellipsize (layout, ellipsize);
|
|
pango_layout_set_width (layout, width);
|
|
pango_layout_set_height (layout, height);
|
|
|
|
g_free (contents);
|
|
|
|
CLUTTER_TIMER_STOP (_clutter_uprof_context, text_layout_timer);
|
|
|
|
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_dirty_paint_volume (text);
|
|
}
|
|
|
|
/*
|
|
* clutter_text_set_font_description_internal:
|
|
* @self: a #ClutterText
|
|
* @desc: a #PangoFontDescription
|
|
*
|
|
* Sets @desc as the font description to be used by the #ClutterText
|
|
* actor. The font description ownership is transferred to @self so
|
|
* the #PangoFontDescription must not be freed after this function
|
|
*
|
|
* This function will also set the :font-name field as a side-effect
|
|
*
|
|
* This function will evict the layout cache, and queue a relayout if
|
|
* the #ClutterText actor has contents.
|
|
*/
|
|
static inline void
|
|
clutter_text_set_font_description_internal (ClutterText *self,
|
|
PangoFontDescription *desc)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
|
|
if (priv->font_desc == desc)
|
|
return;
|
|
|
|
if (priv->font_desc != NULL)
|
|
pango_font_description_free (priv->font_desc);
|
|
|
|
priv->font_desc = desc;
|
|
|
|
/* update the font name string we use */
|
|
g_free (priv->font_name);
|
|
priv->font_name = pango_font_description_to_string (priv->font_desc);
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
if (priv->text && priv->text[0] != '\0')
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_FONT_DESCRIPTION]);
|
|
}
|
|
|
|
static void
|
|
clutter_text_font_changed_cb (ClutterText *text)
|
|
{
|
|
if (text->priv->is_default_font)
|
|
{
|
|
PangoFontDescription *font_desc;
|
|
ClutterSettings *settings;
|
|
gchar *font_name = NULL;
|
|
|
|
settings = clutter_settings_get_default ();
|
|
g_object_get (settings, "font-name", &font_name, NULL);
|
|
|
|
CLUTTER_NOTE (ACTOR, "Text[%p]: default font changed to '%s'",
|
|
text,
|
|
font_name);
|
|
|
|
font_desc = pango_font_description_from_string (font_name);
|
|
clutter_text_set_font_description_internal (text, font_desc);
|
|
|
|
g_free (font_name);
|
|
}
|
|
|
|
clutter_text_dirty_cache (text);
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (text));
|
|
}
|
|
|
|
static void
|
|
clutter_text_direction_changed_cb (GObject *gobject,
|
|
GParamSpec *pspec)
|
|
{
|
|
clutter_text_dirty_cache (CLUTTER_TEXT (gobject));
|
|
|
|
/* no need to queue a relayout: set_text_direction() will do that for us */
|
|
}
|
|
|
|
/*
|
|
* clutter_text_create_layout:
|
|
* @text: a #ClutterText
|
|
* @allocation_width: the allocation width
|
|
* @allocation_height: the allocation height
|
|
*
|
|
* 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,
|
|
gfloat allocation_width,
|
|
gfloat allocation_height)
|
|
{
|
|
ClutterTextPrivate *priv = text->priv;
|
|
LayoutCache *oldest_cache = priv->cached_layouts;
|
|
gboolean found_free_cache = FALSE;
|
|
gint width = -1;
|
|
gint height = -1;
|
|
PangoEllipsizeMode ellipsize = PANGO_ELLIPSIZE_NONE;
|
|
int i;
|
|
|
|
CLUTTER_STATIC_COUNTER (text_cache_hit_counter,
|
|
"Text layout cache hit counter",
|
|
"Increments for each layout cache hit",
|
|
0);
|
|
CLUTTER_STATIC_COUNTER (text_cache_miss_counter,
|
|
"Text layout cache miss counter",
|
|
"Increments for each layout cache miss",
|
|
0);
|
|
|
|
/* First determine the width, height, and ellipsize mode that
|
|
* we need for the layout. The ellipsize mode depends on
|
|
* allocation_width/allocation_size as follows:
|
|
*
|
|
* Cases, assuming ellipsize != NONE on actor:
|
|
*
|
|
* Width request: ellipsization can be set or not on layout,
|
|
* doesn't matter.
|
|
*
|
|
* Height request: ellipsization must never be set on layout
|
|
* if wrap=true, because we need to measure the wrapped
|
|
* height. It must always be set if wrap=false.
|
|
*
|
|
* Allocate: ellipsization must always be set.
|
|
*
|
|
* See http://bugzilla.gnome.org/show_bug.cgi?id=560931
|
|
*/
|
|
|
|
if (priv->ellipsize != PANGO_ELLIPSIZE_NONE)
|
|
{
|
|
if (allocation_height < 0 && priv->wrap)
|
|
; /* must not set ellipsization on wrap=true height request */
|
|
else
|
|
{
|
|
if (!priv->editable)
|
|
ellipsize = priv->ellipsize;
|
|
}
|
|
}
|
|
|
|
/* When painting, we always need to set the width, since
|
|
* we might need to align to the right. When getting the
|
|
* height, however, there are some cases where we know that
|
|
* the width won't affect the width.
|
|
*
|
|
* - editable, single-line text actors, since those can
|
|
* scroll the layout.
|
|
* - non-wrapping, non-ellipsizing actors.
|
|
*/
|
|
if (allocation_width >= 0 &&
|
|
(allocation_height >= 0 ||
|
|
!((priv->editable && priv->single_line_mode) ||
|
|
(priv->ellipsize == PANGO_ELLIPSIZE_NONE && !priv->wrap))))
|
|
{
|
|
width = allocation_width * 1024 + 0.5f;
|
|
}
|
|
|
|
/* Pango only uses height if ellipsization is enabled, so don't set
|
|
* height if ellipsize isn't set. Pango implicitly enables wrapping
|
|
* if height is set, so don't set height if wrapping is disabled.
|
|
* In other words, only set height if we want to both wrap then
|
|
* ellipsize and we're not in single line mode.
|
|
*
|
|
* See http://bugzilla.gnome.org/show_bug.cgi?id=560931 if this
|
|
* seems odd.
|
|
*/
|
|
if (allocation_height >= 0 &&
|
|
priv->wrap &&
|
|
priv->ellipsize != PANGO_ELLIPSIZE_NONE &&
|
|
!priv->single_line_mode)
|
|
{
|
|
height = allocation_height * 1024 + 0.5f;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
else
|
|
{
|
|
PangoLayout *cached = priv->cached_layouts[i].layout;
|
|
gint cached_width = pango_layout_get_width (cached);
|
|
gint cached_height = pango_layout_get_height (cached);
|
|
gint cached_ellipsize = pango_layout_get_ellipsize (cached);
|
|
|
|
if (cached_width == width &&
|
|
cached_height == height &&
|
|
cached_ellipsize == ellipsize)
|
|
{
|
|
/* If this cached layout is using the same size then we can
|
|
* just return that directly
|
|
*/
|
|
CLUTTER_NOTE (ACTOR,
|
|
"ClutterText: %p: cache hit for size %.2fx%.2f",
|
|
text,
|
|
allocation_width,
|
|
allocation_height);
|
|
|
|
CLUTTER_COUNTER_INC (_clutter_uprof_context,
|
|
text_cache_hit_counter);
|
|
|
|
return priv->cached_layouts[i].layout;
|
|
}
|
|
|
|
/* When getting the preferred height for a specific width,
|
|
* we might be able to reuse the layout from getting the
|
|
* preferred width. If the width that the layout gives
|
|
* unconstrained is less than the width that we are using
|
|
* than the height will be unaffected by that width.
|
|
*/
|
|
if (allocation_height < 0 &&
|
|
cached_width == -1 &&
|
|
cached_ellipsize == ellipsize)
|
|
{
|
|
PangoRectangle logical_rect;
|
|
|
|
pango_layout_get_extents (priv->cached_layouts[i].layout,
|
|
NULL,
|
|
&logical_rect);
|
|
|
|
if (logical_rect.width <= width)
|
|
{
|
|
/* We've been asked for our height for the width we gave as a result
|
|
* of a _get_preferred_width call
|
|
*/
|
|
CLUTTER_NOTE (ACTOR,
|
|
"ClutterText: %p: cache hit for size %.2fx%.2f "
|
|
"(unwrapped width narrower than given width)",
|
|
text,
|
|
allocation_width,
|
|
allocation_height);
|
|
|
|
CLUTTER_COUNTER_INC (_clutter_uprof_context,
|
|
text_cache_hit_counter);
|
|
|
|
return priv->cached_layouts[i].layout;
|
|
}
|
|
}
|
|
|
|
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 size %.2fx%.2f",
|
|
text,
|
|
allocation_width,
|
|
allocation_height);
|
|
|
|
CLUTTER_COUNTER_INC (_clutter_uprof_context, text_cache_miss_counter);
|
|
|
|
/* 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, width, height, ellipsize);
|
|
|
|
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++;
|
|
return oldest_cache->layout;
|
|
}
|
|
|
|
static gint
|
|
clutter_text_coords_to_position (ClutterText *text,
|
|
gfloat x,
|
|
gfloat y)
|
|
{
|
|
ClutterTextPrivate *priv = text->priv;
|
|
gint index_;
|
|
gint px, py;
|
|
gint trailing;
|
|
|
|
/* Take any offset due to scrolling into account */
|
|
if (priv->single_line_mode)
|
|
x += priv->text_x * -1;
|
|
|
|
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: (out): return location for the X coordinate, or %NULL
|
|
* @y: (out): return location for the Y coordinate, or %NULL
|
|
* @line_height: (out): return location for the line height, or %NULL
|
|
*
|
|
* Retrieves the coordinates of the given @position.
|
|
*
|
|
* Return value: %TRUE if the conversion was successful
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_position_to_coords (ClutterText *self,
|
|
gint position,
|
|
gfloat *x,
|
|
gfloat *y,
|
|
gfloat *line_height)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
PangoRectangle rect;
|
|
gint n_chars;
|
|
gint password_char_bytes = 1;
|
|
gint index_;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->preedit_set)
|
|
n_chars = priv->n_chars + priv->preedit_n_chars;
|
|
else
|
|
n_chars = priv->n_chars;
|
|
|
|
if (position < -1 || position > n_chars)
|
|
return FALSE;
|
|
|
|
if (priv->password_char != 0)
|
|
password_char_bytes = g_unichar_to_utf8 (priv->password_char, NULL);
|
|
|
|
if (position == -1)
|
|
{
|
|
if (priv->password_char == 0)
|
|
{
|
|
if (priv->editable && priv->preedit_set)
|
|
index_ = priv->n_bytes + strlen (priv->preedit_str);
|
|
else
|
|
index_ = priv->n_bytes;
|
|
}
|
|
else
|
|
index_ = n_chars * password_char_bytes;
|
|
}
|
|
else if (position == 0)
|
|
{
|
|
index_ = 0;
|
|
}
|
|
else
|
|
{
|
|
gchar *text = clutter_text_get_display_text (self);
|
|
GString *tmp = g_string_new (text);
|
|
gint cursor_index;
|
|
|
|
cursor_index = offset_to_bytes (text, priv->position);
|
|
|
|
if (priv->preedit_str != NULL)
|
|
g_string_insert (tmp, cursor_index, priv->preedit_str);
|
|
|
|
if (priv->password_char == 0)
|
|
index_ = offset_to_bytes (tmp->str, position);
|
|
else
|
|
index_ = position * password_char_bytes;
|
|
|
|
g_free (text);
|
|
g_string_free (tmp, TRUE);
|
|
}
|
|
|
|
pango_layout_get_cursor_pos (clutter_text_get_layout (self),
|
|
index_,
|
|
&rect, NULL);
|
|
|
|
if (x)
|
|
{
|
|
*x = (gfloat) rect.x / 1024.0f;
|
|
|
|
/* Take any offset due to scrolling into account */
|
|
if (priv->single_line_mode)
|
|
*x += priv->text_x;
|
|
}
|
|
|
|
if (y)
|
|
*y = (gfloat) rect.y / 1024.0f;
|
|
|
|
if (line_height)
|
|
*line_height = (gfloat) rect.height / 1024.0f;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static inline void
|
|
clutter_text_ensure_cursor_position (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gfloat x, y, cursor_height;
|
|
ClutterGeometry cursor_pos = { 0, };
|
|
gboolean x_changed, y_changed;
|
|
gboolean width_changed, height_changed;
|
|
gint position;
|
|
|
|
position = priv->position;
|
|
|
|
if (priv->editable && priv->preedit_set)
|
|
{
|
|
if (position == -1)
|
|
position = priv->n_chars;
|
|
position += priv->preedit_cursor_pos;
|
|
}
|
|
|
|
CLUTTER_NOTE (MISC, "Cursor at %d (preedit %s at pos: %d)",
|
|
position,
|
|
priv->preedit_set ? "set" : "unset",
|
|
priv->preedit_set ? priv->preedit_cursor_pos : 0);
|
|
|
|
x = y = cursor_height = 0;
|
|
clutter_text_position_to_coords (self, position,
|
|
&x, &y,
|
|
&cursor_height);
|
|
|
|
cursor_pos.x = x;
|
|
cursor_pos.y = y;
|
|
cursor_pos.width = priv->cursor_size;
|
|
cursor_pos.height = cursor_height - 2;
|
|
|
|
x_changed = priv->cursor_pos.x != cursor_pos.x;
|
|
y_changed = priv->cursor_pos.y != cursor_pos.y;
|
|
width_changed = priv->cursor_pos.width != cursor_pos.width;
|
|
height_changed = priv->cursor_pos.height != cursor_pos.height;
|
|
|
|
if (x_changed || y_changed || width_changed || height_changed)
|
|
{
|
|
priv->cursor_pos = cursor_pos;
|
|
|
|
g_signal_emit (self, text_signals[CURSOR_EVENT], 0, &priv->cursor_pos);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_delete_selection:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Deletes the currently selected text
|
|
*
|
|
* This function is only useful in subclasses of #ClutterText
|
|
*
|
|
* Return value: %TRUE if text was deleted or if the text actor
|
|
* is empty, and %FALSE otherwise
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_delete_selection (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
gint start_index;
|
|
gint end_index;
|
|
gint old_position, old_selection;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
|
|
|
|
priv = self->priv;
|
|
|
|
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;
|
|
}
|
|
|
|
old_position = priv->position;
|
|
old_selection = priv->selection_bound;
|
|
|
|
clutter_text_delete_text (self, start_index, end_index);
|
|
|
|
priv->position = start_index;
|
|
priv->selection_bound = start_index;
|
|
|
|
/* Not required to be guarded by g_object_freeze/thaw_notify */
|
|
if (priv->position != old_position)
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_POSITION]);
|
|
|
|
if (priv->selection_bound != old_selection)
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_BOUND]);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Utility function to update both cursor position and selection bound
|
|
* at once
|
|
*/
|
|
static inline void
|
|
clutter_text_set_positions (ClutterText *self,
|
|
gint new_pos,
|
|
gint new_bound)
|
|
{
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
clutter_text_set_cursor_position (self, new_pos);
|
|
clutter_text_set_selection_bound (self, new_bound);
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
}
|
|
|
|
static inline void
|
|
clutter_text_set_text_internal (ClutterText *self,
|
|
const gchar *text)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
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 *p = g_utf8_offset_to_pointer (text, priv->max_length);
|
|
gchar *n = g_malloc0 ((p - text) + 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);
|
|
}
|
|
|
|
if (priv->n_bytes == 0)
|
|
clutter_text_set_positions (self, -1, -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_by_pspec (G_OBJECT (self), obj_props[PROP_TEXT]);
|
|
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
}
|
|
|
|
static inline void
|
|
clutter_text_set_markup_internal (ClutterText *self,
|
|
const gchar *str)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
GError *error;
|
|
gchar *text = NULL;
|
|
PangoAttrList *attrs = NULL;
|
|
gboolean res;
|
|
|
|
error = NULL;
|
|
res = pango_parse_markup (str, -1, 0,
|
|
&attrs,
|
|
&text,
|
|
NULL,
|
|
&error);
|
|
if (!res)
|
|
{
|
|
if (G_LIKELY (error))
|
|
{
|
|
g_warning ("Failed to set the markup of the actor of class '%s': %s",
|
|
G_OBJECT_TYPE_NAME (self),
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
else
|
|
g_warning ("Failed to set the markup of the actor of class '%s'",
|
|
G_OBJECT_TYPE_NAME (self));
|
|
|
|
return;
|
|
}
|
|
|
|
if (text)
|
|
{
|
|
gint tmp_pos = 0;
|
|
|
|
g_signal_emit (self, text_signals[DELETE_TEXT], 0, 0, -1);
|
|
g_signal_emit (self, text_signals[INSERT_TEXT], 0, text,
|
|
strlen (text), &tmp_pos);
|
|
|
|
clutter_text_set_text_internal (self, text);
|
|
|
|
g_free (text);
|
|
}
|
|
|
|
/* Store the new markup attributes */
|
|
if (priv->markup_attrs != NULL)
|
|
pango_attr_list_unref (priv->markup_attrs);
|
|
|
|
priv->markup_attrs = attrs;
|
|
|
|
/* Clear the effective attributes so they will be regenerated when a
|
|
layout is created */
|
|
if (priv->effective_attrs != NULL)
|
|
{
|
|
pango_attr_list_unref (priv->effective_attrs);
|
|
priv->effective_attrs = NULL;
|
|
}
|
|
}
|
|
|
|
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_internal (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_FONT_DESCRIPTION:
|
|
clutter_text_set_font_description (self, g_value_get_boxed (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_LINE_ALIGNMENT:
|
|
clutter_text_set_line_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_SELECTION_COLOR:
|
|
clutter_text_set_selection_color (self, g_value_get_boxed (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_CURSOR_SIZE:
|
|
clutter_text_set_cursor_size (self, g_value_get_int (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_PASSWORD_CHAR:
|
|
clutter_text_set_password_char (self, g_value_get_uint (value));
|
|
break;
|
|
|
|
case PROP_MAX_LENGTH:
|
|
clutter_text_set_max_length (self, g_value_get_int (value));
|
|
break;
|
|
|
|
case PROP_SINGLE_LINE_MODE:
|
|
clutter_text_set_single_line_mode (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_SELECTED_TEXT_COLOR:
|
|
clutter_text_set_selected_text_color (self, clutter_value_get_color (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_FONT_DESCRIPTION:
|
|
g_value_set_boxed (value, priv->font_desc);
|
|
break;
|
|
|
|
case PROP_USE_MARKUP:
|
|
g_value_set_boolean (value, priv->use_markup);
|
|
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_CURSOR_SIZE:
|
|
g_value_set_int (value, priv->cursor_size);
|
|
break;
|
|
|
|
case PROP_POSITION:
|
|
g_value_set_int (value, priv->position);
|
|
break;
|
|
|
|
case PROP_SELECTION_BOUND:
|
|
g_value_set_int (value, 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_SELECTION_COLOR:
|
|
clutter_value_set_color (value, &priv->selection_color);
|
|
break;
|
|
|
|
case PROP_SELECTION_COLOR_SET:
|
|
g_value_set_boolean (value, priv->selection_color_set);
|
|
break;
|
|
|
|
case PROP_ACTIVATABLE:
|
|
g_value_set_boolean (value, priv->activatable);
|
|
break;
|
|
|
|
case PROP_PASSWORD_CHAR:
|
|
g_value_set_uint (value, priv->password_char);
|
|
break;
|
|
|
|
case PROP_MAX_LENGTH:
|
|
g_value_set_int (value, priv->max_length);
|
|
break;
|
|
|
|
case PROP_SINGLE_LINE_MODE:
|
|
g_value_set_boolean (value, priv->single_line_mode);
|
|
break;
|
|
|
|
case PROP_ELLIPSIZE:
|
|
g_value_set_enum (value, priv->ellipsize);
|
|
break;
|
|
|
|
case PROP_LINE_WRAP:
|
|
g_value_set_boolean (value, priv->wrap);
|
|
break;
|
|
|
|
case PROP_LINE_WRAP_MODE:
|
|
g_value_set_enum (value, priv->wrap_mode);
|
|
break;
|
|
|
|
case PROP_LINE_ALIGNMENT:
|
|
g_value_set_enum (value, priv->alignment);
|
|
break;
|
|
|
|
case PROP_JUSTIFY:
|
|
g_value_set_boolean (value, priv->justify);
|
|
break;
|
|
|
|
case PROP_ATTRIBUTES:
|
|
g_value_set_boxed (value, priv->attrs);
|
|
break;
|
|
|
|
case PROP_SELECTED_TEXT_COLOR:
|
|
clutter_value_set_color (value, &priv->selected_text_color);
|
|
break;
|
|
|
|
case PROP_SELECTED_TEXT_COLOR_SET:
|
|
g_value_set_boolean (value, priv->selected_text_color_set);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_text_dispose (GObject *gobject)
|
|
{
|
|
ClutterText *self = CLUTTER_TEXT (gobject);
|
|
ClutterTextPrivate *priv = self->priv;
|
|
|
|
/* get rid of the entire cache */
|
|
clutter_text_dirty_cache (self);
|
|
|
|
if (priv->direction_changed_id)
|
|
{
|
|
g_signal_handler_disconnect (self, priv->direction_changed_id);
|
|
priv->direction_changed_id = 0;
|
|
}
|
|
|
|
if (priv->font_changed_id)
|
|
{
|
|
g_signal_handler_disconnect (clutter_get_default_backend (),
|
|
priv->font_changed_id);
|
|
priv->font_changed_id = 0;
|
|
}
|
|
|
|
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);
|
|
|
|
if (priv->attrs)
|
|
pango_attr_list_unref (priv->attrs);
|
|
if (priv->markup_attrs)
|
|
pango_attr_list_unref (priv->markup_attrs);
|
|
if (priv->effective_attrs)
|
|
pango_attr_list_unref (priv->effective_attrs);
|
|
if (priv->preedit_attrs)
|
|
pango_attr_list_unref (priv->preedit_attrs);
|
|
|
|
clutter_text_dirty_paint_volume (self);
|
|
|
|
g_free (priv->text);
|
|
g_free (priv->font_name);
|
|
|
|
G_OBJECT_CLASS (clutter_text_parent_class)->finalize (gobject);
|
|
}
|
|
|
|
typedef void (* ClutterTextSelectionFunc) (ClutterText *text,
|
|
const ClutterActorBox *box,
|
|
gpointer user_data);
|
|
|
|
static void
|
|
clutter_text_foreach_selection_rectangle (ClutterText *self,
|
|
ClutterTextSelectionFunc func,
|
|
gpointer user_data)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
PangoLayout *layout = clutter_text_get_layout (self);
|
|
gchar *utf8 = clutter_text_get_display_text (self);
|
|
gint lines;
|
|
gint start_index;
|
|
gint end_index;
|
|
gint line_no;
|
|
|
|
if (priv->position == 0)
|
|
start_index = 0;
|
|
else
|
|
start_index = offset_to_bytes (utf8, priv->position);
|
|
|
|
if (priv->selection_bound == 0)
|
|
end_index = 0;
|
|
else
|
|
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;
|
|
ClutterActorBox box;
|
|
gfloat 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);
|
|
|
|
box.y1 = y;
|
|
box.y2 = y + height;
|
|
|
|
for (i = 0; i < n_ranges; i++)
|
|
{
|
|
gint range_x;
|
|
gint range_width;
|
|
|
|
range_x = ranges[i * 2] / PANGO_SCALE;
|
|
|
|
/* Account for any scrolling in single line mode */
|
|
if (priv->single_line_mode)
|
|
range_x += priv->text_x;
|
|
|
|
|
|
range_width = (ranges[i * 2 + 1] - ranges[i * 2])
|
|
/ PANGO_SCALE;
|
|
|
|
box.x1 = range_x;
|
|
box.x2 = range_x + range_width;
|
|
|
|
func (self, &box, user_data);
|
|
}
|
|
|
|
g_free (ranges);
|
|
}
|
|
|
|
g_free (utf8);
|
|
}
|
|
|
|
static void
|
|
clutter_text_add_selection_rectangle_to_path (ClutterText *text,
|
|
const ClutterActorBox *box,
|
|
gpointer user_data)
|
|
{
|
|
cogl_path_rectangle (user_data, box->x1, box->y1, box->x2, box->y2);
|
|
}
|
|
|
|
/* Draws the selected text, its background, and the cursor */
|
|
static void
|
|
selection_paint (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
ClutterActor *actor = CLUTTER_ACTOR (self);
|
|
guint8 paint_opacity = clutter_actor_get_paint_opacity (actor);
|
|
|
|
if (!priv->has_focus)
|
|
return;
|
|
|
|
if (priv->editable && priv->cursor_visible)
|
|
{
|
|
const ClutterColor *color;
|
|
gint position;
|
|
|
|
position = priv->position;
|
|
|
|
if (position == priv->selection_bound)
|
|
{
|
|
/* No selection, just draw the cursor */
|
|
if (priv->cursor_color_set)
|
|
color = &priv->cursor_color;
|
|
else
|
|
color = &priv->text_color;
|
|
|
|
cogl_set_source_color4ub (color->red,
|
|
color->green,
|
|
color->blue,
|
|
paint_opacity
|
|
* color->alpha
|
|
/ 255);
|
|
|
|
cogl_rectangle (priv->cursor_pos.x,
|
|
priv->cursor_pos.y,
|
|
priv->cursor_pos.x + priv->cursor_pos.width,
|
|
priv->cursor_pos.y + priv->cursor_pos.height);
|
|
}
|
|
else
|
|
{
|
|
/* Paint selection background first */
|
|
PangoLayout *layout = clutter_text_get_layout (self);
|
|
CoglPath *selection_path = cogl_path_new ();
|
|
CoglColor cogl_color = { 0, };
|
|
|
|
/* Paint selection background */
|
|
if (priv->selection_color_set)
|
|
color = &priv->selection_color;
|
|
else if (priv->cursor_color_set)
|
|
color = &priv->cursor_color;
|
|
else
|
|
color = &priv->text_color;
|
|
|
|
cogl_set_source_color4ub (color->red,
|
|
color->green,
|
|
color->blue,
|
|
paint_opacity * color->alpha / 255);
|
|
|
|
clutter_text_foreach_selection_rectangle
|
|
(self,
|
|
clutter_text_add_selection_rectangle_to_path,
|
|
selection_path);
|
|
|
|
cogl_path_fill (selection_path);
|
|
|
|
/* Paint selected text */
|
|
cogl_clip_push_from_path (selection_path);
|
|
cogl_object_unref (selection_path);
|
|
|
|
if (priv->selected_text_color_set)
|
|
color = &priv->selected_text_color;
|
|
else if (priv->selection_color_set)
|
|
color = &priv->selection_color;
|
|
else if (priv->cursor_color_set)
|
|
color = &priv->cursor_color;
|
|
else
|
|
color = &priv->text_color;
|
|
|
|
cogl_color_init_from_4ub (&cogl_color,
|
|
color->red,
|
|
color->green,
|
|
color->blue,
|
|
paint_opacity * color->alpha / 255);
|
|
|
|
cogl_pango_render_layout (layout, priv->text_x, 0, &cogl_color, 0);
|
|
|
|
cogl_clip_pop ();
|
|
}
|
|
}
|
|
}
|
|
|
|
static gint
|
|
clutter_text_move_word_backward (ClutterText *self,
|
|
gint start)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint retval = start;
|
|
|
|
if (priv->text && start > 0)
|
|
{
|
|
PangoLayout *layout = clutter_text_get_layout (self);
|
|
PangoLogAttr *log_attrs = NULL;
|
|
gint n_attrs = 0;
|
|
|
|
pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
|
|
|
|
retval = start - 1;
|
|
while (retval > 0 && !log_attrs[retval].is_word_start)
|
|
retval -= 1;
|
|
|
|
g_free (log_attrs);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static gint
|
|
clutter_text_move_word_forward (ClutterText *self,
|
|
gint start)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint retval = start;
|
|
|
|
if (priv->text && start < priv->n_chars)
|
|
{
|
|
PangoLayout *layout = clutter_text_get_layout (self);
|
|
PangoLogAttr *log_attrs = NULL;
|
|
gint n_attrs = 0;
|
|
|
|
pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
|
|
|
|
retval = start + 1;
|
|
while (retval < priv->n_chars && !log_attrs[retval].is_word_end)
|
|
retval += 1;
|
|
|
|
g_free (log_attrs);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static gint
|
|
clutter_text_move_line_start (ClutterText *self,
|
|
gint start)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
PangoLayoutLine *layout_line;
|
|
PangoLayout *layout;
|
|
gint line_no;
|
|
gint index_;
|
|
gint position;
|
|
|
|
layout = clutter_text_get_layout (self);
|
|
|
|
if (start == 0)
|
|
index_ = 0;
|
|
else
|
|
index_ = offset_to_bytes (priv->text, start);
|
|
|
|
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_);
|
|
|
|
return position;
|
|
}
|
|
|
|
static gint
|
|
clutter_text_move_line_end (ClutterText *self,
|
|
gint start)
|
|
{
|
|
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 (start == 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_);
|
|
|
|
return position;
|
|
}
|
|
|
|
static void
|
|
clutter_text_select_word (ClutterText *self)
|
|
{
|
|
gint cursor_pos = self->priv->position;
|
|
gint start_pos, end_pos;
|
|
|
|
start_pos = clutter_text_move_word_backward (self, cursor_pos);
|
|
end_pos = clutter_text_move_word_forward (self, cursor_pos);
|
|
|
|
clutter_text_set_selection (self, start_pos, end_pos);
|
|
}
|
|
|
|
static void
|
|
clutter_text_select_line (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint cursor_pos = priv->position;
|
|
gint start_pos, end_pos;
|
|
|
|
if (priv->single_line_mode)
|
|
{
|
|
start_pos = 0;
|
|
end_pos = -1;
|
|
}
|
|
else
|
|
{
|
|
start_pos = clutter_text_move_line_start (self, cursor_pos);
|
|
end_pos = clutter_text_move_line_end (self, cursor_pos);
|
|
}
|
|
|
|
clutter_text_set_selection (self, start_pos, end_pos);
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_button_press (ClutterActor *actor,
|
|
ClutterButtonEvent *event)
|
|
{
|
|
ClutterText *self = CLUTTER_TEXT (actor);
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gboolean res = FALSE;
|
|
gfloat x, y;
|
|
gint index_;
|
|
|
|
/* we'll steal keyfocus if we need it */
|
|
if (priv->editable || priv->selectable)
|
|
clutter_actor_grab_key_focus (actor);
|
|
|
|
/* if the actor is empty we just reset everything and not
|
|
* set up the dragging of the selection since there's nothing
|
|
* to select
|
|
*/
|
|
if (priv->text == NULL || priv->text[0] == '\0')
|
|
{
|
|
clutter_text_set_positions (self, -1, -1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
res = clutter_actor_transform_stage_point (actor,
|
|
event->x,
|
|
event->y,
|
|
&x, &y);
|
|
if (res)
|
|
{
|
|
gint offset;
|
|
|
|
index_ = clutter_text_coords_to_position (self, x, y);
|
|
offset = bytes_to_offset (priv->text, index_);
|
|
|
|
/* what we select depends on the number of button clicks we
|
|
* receive:
|
|
*
|
|
* 1: just position the cursor and the selection
|
|
* 2: select the current word
|
|
* 3: select the contents of the whole actor
|
|
*/
|
|
if (event->click_count == 1)
|
|
{
|
|
clutter_text_set_positions (self, offset, offset);
|
|
}
|
|
else if (event->click_count == 2)
|
|
{
|
|
clutter_text_select_word (self);
|
|
}
|
|
else if (event->click_count == 3)
|
|
{
|
|
clutter_text_select_line (self);
|
|
}
|
|
}
|
|
|
|
/* grab the pointer */
|
|
priv->in_select_drag = TRUE;
|
|
clutter_grab_pointer (actor);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_motion (ClutterActor *actor,
|
|
ClutterMotionEvent *mev)
|
|
{
|
|
ClutterText *self = CLUTTER_TEXT (actor);
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gfloat x, y;
|
|
gint index_, offset;
|
|
gboolean res;
|
|
|
|
if (!priv->in_select_drag)
|
|
return FALSE;
|
|
|
|
res = clutter_actor_transform_stage_point (actor,
|
|
mev->x, mev->y,
|
|
&x, &y);
|
|
if (!res)
|
|
return FALSE;
|
|
|
|
index_ = clutter_text_coords_to_position (self, x, y);
|
|
offset = bytes_to_offset (priv->text, index_);
|
|
|
|
if (priv->selectable)
|
|
clutter_text_set_cursor_position (self, offset);
|
|
else
|
|
clutter_text_set_positions (self, offset, offset);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_button_release (ClutterActor *actor,
|
|
ClutterButtonEvent *bev)
|
|
{
|
|
ClutterText *self = CLUTTER_TEXT (actor);
|
|
ClutterTextPrivate *priv = self->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;
|
|
|
|
if (!priv->editable)
|
|
return FALSE;
|
|
|
|
/* we need to use the ClutterText type name to find our own
|
|
* key bindings; subclasses will override or chain up this
|
|
* event handler, so they can do whatever they want there
|
|
*/
|
|
pool = clutter_binding_pool_find (g_type_name (CLUTTER_TYPE_TEXT));
|
|
g_assert (pool != NULL);
|
|
|
|
/* we allow passing synthetic events that only contain
|
|
* the Unicode value and not the key symbol
|
|
*/
|
|
if (event->keyval == 0 && (event->flags & CLUTTER_EVENT_FLAG_SYNTHETIC))
|
|
res = FALSE;
|
|
else
|
|
res = clutter_binding_pool_activate (pool, event->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;
|
|
/* Skip keys when control is pressed */
|
|
else if ((event->modifier_state & CLUTTER_CONTROL_MASK) == 0)
|
|
{
|
|
gunichar key_unichar;
|
|
|
|
key_unichar = clutter_event_get_key_unicode ((ClutterEvent *) event);
|
|
|
|
/* return is reported as CR, but we want LF */
|
|
if (key_unichar == '\r')
|
|
key_unichar = '\n';
|
|
|
|
if (key_unichar == '\n' ||
|
|
(g_unichar_validate (key_unichar) &&
|
|
!g_unichar_iscntrl (key_unichar)))
|
|
{
|
|
/* truncate the eventual selection so that the
|
|
* Unicode character can replace it
|
|
*/
|
|
clutter_text_delete_selection (self);
|
|
clutter_text_insert_unichar (self, key_unichar);
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#define TEXT_PADDING 2
|
|
|
|
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;
|
|
gint text_x = priv->text_x;
|
|
gboolean clip_set = FALSE;
|
|
|
|
/* Note that if anything in this paint method changes it needs to be
|
|
reflected in the get_paint_volume implementation which is tightly
|
|
tied to the workings of this function */
|
|
|
|
if (G_UNLIKELY (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;
|
|
}
|
|
|
|
clutter_actor_get_allocation_box (self, &alloc);
|
|
|
|
if (priv->editable && priv->single_line_mode)
|
|
layout = clutter_text_create_layout (text, -1, -1);
|
|
else
|
|
layout = clutter_text_create_layout (text,
|
|
alloc.x2 - alloc.x1,
|
|
alloc.y2 - alloc.y1);
|
|
|
|
if (priv->editable && priv->cursor_visible)
|
|
clutter_text_ensure_cursor_position (text);
|
|
|
|
if (priv->editable && priv->single_line_mode)
|
|
{
|
|
PangoRectangle logical_rect = { 0, };
|
|
gint actor_width, text_width;
|
|
|
|
pango_layout_get_extents (layout, NULL, &logical_rect);
|
|
|
|
cogl_clip_push_rectangle (0, 0,
|
|
(alloc.x2 - alloc.x1),
|
|
(alloc.y2 - alloc.y1));
|
|
clip_set = TRUE;
|
|
|
|
actor_width = (alloc.x2 - alloc.x1)
|
|
- 2 * TEXT_PADDING;
|
|
text_width = logical_rect.width / PANGO_SCALE;
|
|
|
|
if (actor_width < text_width)
|
|
{
|
|
gint cursor_x = priv->cursor_pos.x;
|
|
|
|
if (priv->position == -1)
|
|
{
|
|
text_x = actor_width - text_width;
|
|
}
|
|
else if (priv->position == 0)
|
|
{
|
|
text_x = TEXT_PADDING;
|
|
}
|
|
else
|
|
{
|
|
if (cursor_x < 0)
|
|
{
|
|
text_x = text_x - cursor_x - TEXT_PADDING;
|
|
}
|
|
else if (cursor_x > actor_width)
|
|
{
|
|
text_x = text_x + (actor_width - cursor_x) - TEXT_PADDING;
|
|
}
|
|
}
|
|
/* Update the absolute cursor position as it may have moved due to
|
|
* scrolling */
|
|
priv->text_x = text_x;
|
|
clutter_text_ensure_cursor_position (text);
|
|
}
|
|
else
|
|
{
|
|
text_x = TEXT_PADDING;
|
|
}
|
|
}
|
|
else
|
|
text_x = 0;
|
|
|
|
priv->text_x = text_x;
|
|
|
|
real_opacity = clutter_actor_get_paint_opacity (self)
|
|
* priv->text_color.alpha
|
|
/ 255;
|
|
|
|
CLUTTER_NOTE (PAINT, "painting text (text: '%s')", priv->text);
|
|
|
|
cogl_color_init_from_4ub (&color,
|
|
priv->text_color.red,
|
|
priv->text_color.green,
|
|
priv->text_color.blue,
|
|
real_opacity);
|
|
cogl_pango_render_layout (layout, text_x, priv->text_y, &color, 0);
|
|
|
|
selection_paint (text);
|
|
|
|
if (clip_set)
|
|
cogl_clip_pop ();
|
|
|
|
}
|
|
|
|
static void
|
|
clutter_text_add_selection_to_paint_volume_cb (ClutterText *text,
|
|
const ClutterActorBox *box,
|
|
gpointer user_data)
|
|
{
|
|
ClutterPaintVolume *total_volume = user_data;
|
|
ClutterPaintVolume rect_volume;
|
|
ClutterVertex vertex;
|
|
|
|
_clutter_paint_volume_init_static (&rect_volume, CLUTTER_ACTOR (text));
|
|
|
|
vertex.x = box->x1;
|
|
vertex.y = box->y1;
|
|
vertex.z = 0.0f;
|
|
clutter_paint_volume_set_origin (&rect_volume, &vertex);
|
|
clutter_paint_volume_set_width (&rect_volume, box->x2 - box->x1);
|
|
clutter_paint_volume_set_height (&rect_volume, box->y2 - box->y1);
|
|
|
|
clutter_paint_volume_union (total_volume, &rect_volume);
|
|
|
|
clutter_paint_volume_free (&rect_volume);
|
|
}
|
|
|
|
static void
|
|
clutter_text_get_paint_volume_for_cursor (ClutterText *text,
|
|
ClutterPaintVolume *volume)
|
|
{
|
|
ClutterTextPrivate *priv = text->priv;
|
|
ClutterVertex origin;
|
|
|
|
clutter_text_ensure_cursor_position (text);
|
|
|
|
if (priv->position == priv->selection_bound)
|
|
{
|
|
origin.x = priv->cursor_pos.x;
|
|
origin.y = priv->cursor_pos.y;
|
|
origin.z = 0;
|
|
clutter_paint_volume_set_origin (volume, &origin);
|
|
clutter_paint_volume_set_width (volume, priv->cursor_pos.width);
|
|
clutter_paint_volume_set_height (volume, priv->cursor_pos.height);
|
|
}
|
|
else
|
|
{
|
|
clutter_text_foreach_selection_rectangle
|
|
(text, clutter_text_add_selection_to_paint_volume_cb, volume);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_get_paint_volume (ClutterActor *self,
|
|
ClutterPaintVolume *volume)
|
|
{
|
|
ClutterText *text = CLUTTER_TEXT (self);
|
|
ClutterTextPrivate *priv = text->priv;
|
|
|
|
/* ClutterText uses the logical layout as the natural size of the
|
|
actor. This means that it can sometimes paint outside of its
|
|
allocation for example with italic fonts with serifs. Therefore
|
|
we should use the ink rectangle of the layout instead */
|
|
|
|
if (!priv->paint_volume_valid)
|
|
{
|
|
PangoLayout *layout;
|
|
PangoRectangle ink_rect;
|
|
ClutterVertex origin;
|
|
|
|
/* If the text is single line editable then it gets clipped to
|
|
the allocation anyway so we can just use that */
|
|
if (priv->editable && priv->single_line_mode)
|
|
return _clutter_actor_set_default_paint_volume (self,
|
|
CLUTTER_TYPE_TEXT,
|
|
volume);
|
|
|
|
if (G_OBJECT_TYPE (self) != CLUTTER_TYPE_TEXT)
|
|
return FALSE;
|
|
|
|
if (!clutter_actor_has_allocation (self))
|
|
return FALSE;
|
|
|
|
_clutter_paint_volume_init_static (&priv->paint_volume, self);
|
|
|
|
layout = clutter_text_get_layout (text);
|
|
pango_layout_get_extents (layout, &ink_rect, NULL);
|
|
|
|
origin.x = ink_rect.x / (float) PANGO_SCALE;
|
|
origin.y = ink_rect.y / (float) PANGO_SCALE;
|
|
origin.z = 0;
|
|
clutter_paint_volume_set_origin (&priv->paint_volume, &origin);
|
|
clutter_paint_volume_set_width (&priv->paint_volume,
|
|
ink_rect.width / (float) PANGO_SCALE);
|
|
clutter_paint_volume_set_height (&priv->paint_volume,
|
|
ink_rect.height / (float) PANGO_SCALE);
|
|
|
|
/* If the cursor is visible then that will likely be drawn
|
|
outside of the ink rectangle so we should merge that in */
|
|
if (priv->editable && priv->cursor_visible && priv->has_focus)
|
|
{
|
|
ClutterPaintVolume cursor_paint_volume;
|
|
|
|
_clutter_paint_volume_init_static (&cursor_paint_volume,
|
|
self);
|
|
|
|
clutter_text_get_paint_volume_for_cursor (text, &cursor_paint_volume);
|
|
|
|
clutter_paint_volume_union (&priv->paint_volume,
|
|
&cursor_paint_volume);
|
|
|
|
clutter_paint_volume_free (&cursor_paint_volume);
|
|
}
|
|
|
|
priv->paint_volume_valid = TRUE;
|
|
}
|
|
|
|
_clutter_paint_volume_copy_static (&priv->paint_volume, volume);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clutter_text_get_preferred_width (ClutterActor *self,
|
|
gfloat for_height,
|
|
gfloat *min_width_p,
|
|
gfloat *natural_width_p)
|
|
{
|
|
ClutterText *text = CLUTTER_TEXT (self);
|
|
ClutterTextPrivate *priv = text->priv;
|
|
PangoRectangle logical_rect = { 0, };
|
|
PangoLayout *layout;
|
|
gint logical_width;
|
|
gfloat layout_width;
|
|
|
|
layout = clutter_text_create_layout (text, -1, -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
|
|
? ceilf (logical_width / 1024.0f)
|
|
: 1;
|
|
|
|
if (min_width_p)
|
|
{
|
|
if (priv->wrap || priv->ellipsize || priv->editable)
|
|
*min_width_p = 1;
|
|
else
|
|
*min_width_p = layout_width;
|
|
}
|
|
|
|
if (natural_width_p)
|
|
{
|
|
if (priv->editable && priv->single_line_mode)
|
|
*natural_width_p = layout_width + TEXT_PADDING * 2;
|
|
else
|
|
*natural_width_p = layout_width;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_text_get_preferred_height (ClutterActor *self,
|
|
gfloat for_width,
|
|
gfloat *min_height_p,
|
|
gfloat *natural_height_p)
|
|
{
|
|
ClutterTextPrivate *priv = CLUTTER_TEXT (self)->priv;
|
|
|
|
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;
|
|
gfloat layout_height;
|
|
|
|
if (priv->single_line_mode)
|
|
for_width = -1;
|
|
|
|
layout = clutter_text_create_layout (CLUTTER_TEXT (self),
|
|
for_width, -1);
|
|
|
|
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 = ceilf (logical_height / 1024.0f);
|
|
|
|
if (min_height_p)
|
|
{
|
|
/* if we wrap and ellipsize then the minimum height is
|
|
* going to be at least the size of the first line
|
|
*/
|
|
if ((priv->ellipsize && priv->wrap) && !priv->single_line_mode)
|
|
{
|
|
PangoLayoutLine *line;
|
|
gfloat line_height;
|
|
|
|
line = pango_layout_get_line_readonly (layout, 0);
|
|
pango_layout_line_get_extents (line, NULL, &logical_rect);
|
|
|
|
logical_height = logical_rect.y + logical_rect.height;
|
|
line_height = ceilf (logical_height / 1024.0f);
|
|
|
|
*min_height_p = line_height;
|
|
}
|
|
else
|
|
*min_height_p = layout_height;
|
|
}
|
|
|
|
if (natural_height_p)
|
|
*natural_height_p = layout_height;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_text_allocate (ClutterActor *self,
|
|
const ClutterActorBox *box,
|
|
ClutterAllocationFlags flags)
|
|
{
|
|
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
|
|
*
|
|
* if the Text is editable and in single line mode we don't want
|
|
* to have any limit on the layout size, since the paint will clip
|
|
* it to the allocation of the actor
|
|
*/
|
|
if (text->priv->editable && text->priv->single_line_mode)
|
|
clutter_text_create_layout (text, -1, -1);
|
|
else
|
|
clutter_text_create_layout (text,
|
|
box->x2 - box->x1,
|
|
box->y2 - box->y1);
|
|
|
|
parent_class = CLUTTER_ACTOR_CLASS (clutter_text_parent_class);
|
|
parent_class->allocate (self, box, flags);
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_has_overlaps (ClutterActor *self)
|
|
{
|
|
ClutterTextPrivate *priv = CLUTTER_TEXT (self)->priv;
|
|
|
|
return priv->editable ||
|
|
priv->selectable ||
|
|
priv->cursor_visible;
|
|
}
|
|
|
|
static void
|
|
clutter_text_key_focus_in (ClutterActor *actor)
|
|
{
|
|
ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv;
|
|
|
|
priv->has_focus = TRUE;
|
|
|
|
clutter_text_queue_redraw (actor);
|
|
}
|
|
|
|
static void
|
|
clutter_text_key_focus_out (ClutterActor *actor)
|
|
{
|
|
ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv;
|
|
|
|
priv->has_focus = FALSE;
|
|
|
|
clutter_text_queue_redraw (actor);
|
|
}
|
|
|
|
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 new_pos = 0;
|
|
gint len;
|
|
|
|
len = priv->n_chars;
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
if (pos != 0 && len != 0)
|
|
{
|
|
if (modifiers & CLUTTER_CONTROL_MASK)
|
|
{
|
|
if (pos == -1)
|
|
new_pos = clutter_text_move_word_backward (self, len);
|
|
else
|
|
new_pos = clutter_text_move_word_backward (self, pos);
|
|
}
|
|
else
|
|
{
|
|
if (pos == -1)
|
|
new_pos = len - 1;
|
|
else
|
|
new_pos = pos - 1;
|
|
}
|
|
|
|
clutter_text_set_cursor_position (self, new_pos);
|
|
}
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
g_object_thaw_notify (G_OBJECT (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 = priv->n_chars;
|
|
gint new_pos = 0;
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
if (pos != -1 && len !=0)
|
|
{
|
|
if (modifiers & CLUTTER_CONTROL_MASK)
|
|
{
|
|
if (pos != len)
|
|
new_pos = clutter_text_move_word_forward (self, pos);
|
|
}
|
|
else
|
|
{
|
|
if (pos != len)
|
|
new_pos = pos + 1;
|
|
}
|
|
|
|
clutter_text_set_cursor_position (self, new_pos);
|
|
}
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
g_object_thaw_notify (G_OBJECT (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_, trailing;
|
|
gint pos;
|
|
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;
|
|
|
|
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_, &trailing);
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
pos = bytes_to_offset (priv->text, index_);
|
|
clutter_text_set_cursor_position (self, pos + trailing);
|
|
|
|
/* Store the target x position to avoid drifting left and right when
|
|
moving the cursor up and down */
|
|
priv->x_pos = x;
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
g_object_thaw_notify (G_OBJECT (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_, trailing;
|
|
gint x;
|
|
gint pos;
|
|
|
|
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;
|
|
|
|
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_, &trailing);
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
pos = bytes_to_offset (priv->text, index_);
|
|
clutter_text_set_cursor_position (self, pos + trailing);
|
|
|
|
/* Store the target x position to avoid drifting left and right when
|
|
moving the cursor up and down */
|
|
priv->x_pos = x;
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_line_start (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint position;
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
position = clutter_text_move_line_start (self, priv->position);
|
|
clutter_text_set_cursor_position (self, position);
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_line_end (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint position;
|
|
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
position = clutter_text_move_line_end (self, priv->position);
|
|
clutter_text_set_cursor_position (self, position);
|
|
|
|
if (!(priv->selectable && (modifiers & CLUTTER_SHIFT_MASK)))
|
|
clutter_text_clear_selection (self);
|
|
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_select_all (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
clutter_text_set_positions (self, 0, self->priv->n_chars);
|
|
|
|
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_delete_selection (self))
|
|
return TRUE;
|
|
|
|
pos = priv->position;
|
|
len = priv->n_chars;
|
|
|
|
if (len && pos != -1 && pos < len)
|
|
clutter_text_delete_text (self, pos, pos + 1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_del_word_next (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint pos;
|
|
gint len;
|
|
|
|
pos = priv->position;
|
|
len = priv->n_chars;
|
|
|
|
if (len && pos != -1 && pos < len)
|
|
{
|
|
gint end;
|
|
|
|
end = clutter_text_move_word_forward (self, pos);
|
|
clutter_text_delete_text (self, pos, end);
|
|
|
|
if (priv->selection_bound >= end)
|
|
{
|
|
gint new_bound;
|
|
|
|
new_bound = priv->selection_bound - (end - pos);
|
|
clutter_text_set_selection_bound (self, new_bound);
|
|
}
|
|
else if (priv->selection_bound > pos)
|
|
{
|
|
clutter_text_set_selection_bound (self, pos);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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_delete_selection (self))
|
|
return TRUE;
|
|
|
|
pos = priv->position;
|
|
len = priv->n_chars;
|
|
|
|
if (pos != 0 && len != 0)
|
|
{
|
|
if (pos == -1)
|
|
{
|
|
clutter_text_delete_text (self, len - 1, len);
|
|
|
|
clutter_text_set_positions (self, -1, -1);
|
|
}
|
|
else
|
|
{
|
|
clutter_text_delete_text (self, pos - 1, pos);
|
|
|
|
clutter_text_set_positions (self, pos - 1, pos - 1);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_del_word_prev (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
gint pos;
|
|
gint len;
|
|
|
|
pos = priv->position;
|
|
len = priv->n_chars;
|
|
|
|
if (pos != 0 && len != 0)
|
|
{
|
|
gint new_pos;
|
|
|
|
if (pos == -1)
|
|
{
|
|
new_pos = clutter_text_move_word_backward (self, len);
|
|
clutter_text_delete_text (self, new_pos, len);
|
|
|
|
clutter_text_set_positions (self, -1, -1);
|
|
}
|
|
else
|
|
{
|
|
new_pos = clutter_text_move_word_backward (self, pos);
|
|
clutter_text_delete_text (self, new_pos, pos);
|
|
|
|
clutter_text_set_cursor_position (self, new_pos);
|
|
if (priv->selection_bound >= pos)
|
|
{
|
|
gint new_bound;
|
|
|
|
new_bound = priv->selection_bound - (pos - new_pos);
|
|
clutter_text_set_selection_bound (self, new_bound);
|
|
}
|
|
else if (priv->selection_bound >= new_pos)
|
|
{
|
|
clutter_text_set_selection_bound (self, new_pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_text_real_activate (ClutterText *self,
|
|
const gchar *action,
|
|
guint keyval,
|
|
ClutterModifierType modifiers)
|
|
{
|
|
return clutter_text_activate (self);
|
|
}
|
|
|
|
static inline void
|
|
clutter_text_add_move_binding (ClutterBindingPool *pool,
|
|
const gchar *action,
|
|
guint key_val,
|
|
ClutterModifierType additional_modifiers,
|
|
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);
|
|
|
|
if (additional_modifiers != 0)
|
|
{
|
|
clutter_binding_pool_install_action (pool, action,
|
|
key_val,
|
|
additional_modifiers,
|
|
callback,
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (pool, action,
|
|
key_val,
|
|
CLUTTER_SHIFT_MASK |
|
|
additional_modifiers,
|
|
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_paint_volume = clutter_text_get_paint_volume;
|
|
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;
|
|
actor_class->key_focus_in = clutter_text_key_focus_in;
|
|
actor_class->key_focus_out = clutter_text_key_focus_out;
|
|
actor_class->has_overlaps = clutter_text_has_overlaps;
|
|
|
|
/**
|
|
* ClutterText:font-name:
|
|
*
|
|
* The font to be used by the #ClutterText, as a string
|
|
* that can be parsed by pango_font_description_from_string().
|
|
*
|
|
* If set to %NULL, the default system font will be used instead.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_string ("font-name",
|
|
P_("Font Name"),
|
|
P_("The font to be used by the text"),
|
|
NULL,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_FONT_NAME] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_FONT_NAME, pspec);
|
|
|
|
/**
|
|
* ClutterText:font-description:
|
|
*
|
|
* The #PangoFontDescription that should be used by the #ClutterText
|
|
*
|
|
* If you have a string describing the font then you should look at
|
|
* #ClutterText:font-name instead
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
pspec = g_param_spec_boxed ("font-description",
|
|
P_("Font Description"),
|
|
P_("The font description to be used"),
|
|
PANGO_TYPE_FONT_DESCRIPTION,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_FONT_DESCRIPTION] = pspec;
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_FONT_DESCRIPTION,
|
|
pspec);
|
|
|
|
/**
|
|
* ClutterText:text:
|
|
*
|
|
* The text to render inside the actor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_string ("text",
|
|
P_("Text"),
|
|
P_("The text to render"),
|
|
"",
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_TEXT] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_TEXT, pspec);
|
|
|
|
/**
|
|
* ClutterText:color:
|
|
*
|
|
* The color used to render the text.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = clutter_param_spec_color ("color",
|
|
P_("Font Color"),
|
|
P_("Color of the font used by the text"),
|
|
&default_text_color,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_COLOR] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_COLOR, pspec);
|
|
|
|
/**
|
|
* ClutterText:editable:
|
|
*
|
|
* Whether key events delivered to the actor causes editing.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("editable",
|
|
P_("Editable"),
|
|
P_("Whether the text is editable"),
|
|
TRUE,
|
|
G_PARAM_READWRITE);
|
|
obj_props[PROP_EDITABLE] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_EDITABLE, pspec);
|
|
|
|
/**
|
|
* ClutterText:selectable:
|
|
*
|
|
* Whether it is possible to select text, either using the pointer
|
|
* or the keyboard.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("selectable",
|
|
P_("Selectable"),
|
|
P_("Whether the text is selectable"),
|
|
TRUE,
|
|
G_PARAM_READWRITE);
|
|
obj_props[PROP_SELECTABLE] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_SELECTABLE, pspec);
|
|
|
|
/**
|
|
* ClutterText:activatable:
|
|
*
|
|
* Toggles whether return invokes the activate signal or not.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("activatable",
|
|
P_("Activatable"),
|
|
P_("Whether pressing return causes the activate signal to be emitted"),
|
|
TRUE,
|
|
G_PARAM_READWRITE);
|
|
obj_props[PROP_ACTIVATABLE] = pspec;
|
|
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 #ClutterText:cursor-visible and #ClutterText:editable are
|
|
* set to %TRUE.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("cursor-visible",
|
|
P_("Cursor Visible"),
|
|
P_("Whether the input cursor is visible"),
|
|
TRUE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_CURSOR_VISIBLE] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_CURSOR_VISIBLE, pspec);
|
|
|
|
/**
|
|
* ClutterText:cursor-color:
|
|
*
|
|
* The color of the cursor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = clutter_param_spec_color ("cursor-color",
|
|
P_("Cursor Color"),
|
|
P_("Cursor Color"),
|
|
&default_cursor_color,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_CURSOR_COLOR] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_CURSOR_COLOR, pspec);
|
|
|
|
/**
|
|
* ClutterText:cursor-color-set:
|
|
*
|
|
* Will be set to %TRUE if #ClutterText:cursor-color has been set.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("cursor-color-set",
|
|
P_("Cursor Color Set"),
|
|
P_("Whether the cursor color has been set"),
|
|
FALSE,
|
|
CLUTTER_PARAM_READABLE);
|
|
obj_props[PROP_CURSOR_COLOR_SET] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_CURSOR_COLOR_SET, pspec);
|
|
|
|
/**
|
|
* ClutterText:cursor-size:
|
|
*
|
|
* The size of the cursor, in pixels. If set to -1 the size used will
|
|
* be the default cursor size of 2 pixels.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_int ("cursor-size",
|
|
P_("Cursor Size"),
|
|
P_("The width of the cursor, in pixels"),
|
|
-1, G_MAXINT, DEFAULT_CURSOR_SIZE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_CURSOR_SIZE] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_CURSOR_SIZE, pspec);
|
|
|
|
/**
|
|
* ClutterText:position:
|
|
*
|
|
* The current input cursor position. -1 is taken to be the end of the text
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_int ("position",
|
|
P_("Cursor Position"),
|
|
P_("The cursor position"),
|
|
-1, G_MAXINT,
|
|
-1,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_POSITION] = pspec;
|
|
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
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_int ("selection-bound",
|
|
P_("Selection-bound"),
|
|
P_("The cursor position of the other end of the selection"),
|
|
-1, G_MAXINT,
|
|
-1,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_SELECTION_BOUND] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_SELECTION_BOUND, pspec);
|
|
|
|
/**
|
|
* ClutterText:selection-color:
|
|
*
|
|
* The color of the selection.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = clutter_param_spec_color ("selection-color",
|
|
P_("Selection Color"),
|
|
P_("Selection Color"),
|
|
&default_selection_color,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_SELECTION_COLOR] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_SELECTION_COLOR, pspec);
|
|
|
|
/**
|
|
* ClutterText:selection-color-set:
|
|
*
|
|
* Will be set to %TRUE if #ClutterText:selection-color has been set.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("selection-color-set",
|
|
P_("Selection Color Set"),
|
|
P_("Whether the selection color has been set"),
|
|
FALSE,
|
|
CLUTTER_PARAM_READABLE);
|
|
obj_props[PROP_SELECTION_COLOR_SET] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_SELECTION_COLOR_SET, pspec);
|
|
|
|
/**
|
|
* ClutterText:attributes:
|
|
*
|
|
* A list of #PangoStyleAttribute<!-- -->s to be applied to the
|
|
* contents of the #ClutterText actor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boxed ("attributes",
|
|
P_("Attributes"),
|
|
P_("A list of style attributes to apply to the contents of the actor"),
|
|
PANGO_TYPE_ATTR_LIST,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_ATTRIBUTES] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_ATTRIBUTES, pspec);
|
|
|
|
/**
|
|
* ClutterText:use-markup:
|
|
*
|
|
* Whether the text includes Pango markup.
|
|
*
|
|
* For more informations about the Pango markup format, see
|
|
* pango_layout_set_markup() in the Pango documentation.
|
|
*
|
|
* <note>It is not possible to round-trip this property between
|
|
* %TRUE and %FALSE. Once a string with markup has been set on
|
|
* a #ClutterText actor with :use-markup set to %TRUE, the markup
|
|
* is stripped from the string.</note>
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("use-markup",
|
|
P_("Use markup"),
|
|
P_("Whether or not the text includes Pango markup"),
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_USE_MARKUP] = pspec;
|
|
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",
|
|
P_("Line wrap"),
|
|
P_("If set, wrap the lines if the text becomes too wide"),
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_LINE_WRAP] = pspec;
|
|
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",
|
|
P_("Line wrap mode"),
|
|
P_("Control how line-wrapping is done"),
|
|
PANGO_TYPE_WRAP_MODE,
|
|
PANGO_WRAP_WORD,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_LINE_WRAP_MODE] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_LINE_WRAP_MODE, pspec);
|
|
|
|
/**
|
|
* ClutterText:ellipsize:
|
|
*
|
|
* The preferred place to ellipsize the contents of the #ClutterText actor
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_enum ("ellipsize",
|
|
P_("Ellipsize"),
|
|
P_("The preferred place to ellipsize the string"),
|
|
PANGO_TYPE_ELLIPSIZE_MODE,
|
|
PANGO_ELLIPSIZE_NONE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_ELLIPSIZE] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_ELLIPSIZE, pspec);
|
|
|
|
/**
|
|
* ClutterText:line-alignment:
|
|
*
|
|
* The preferred alignment for the text. This property controls
|
|
* the alignment of multi-line paragraphs.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_enum ("line-alignment",
|
|
P_("Line Alignment"),
|
|
P_("The preferred alignment for the string, for multi-line text"),
|
|
PANGO_TYPE_ALIGNMENT,
|
|
PANGO_ALIGN_LEFT,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_LINE_ALIGNMENT] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_LINE_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",
|
|
P_("Justify"),
|
|
P_("Whether the text should be justified"),
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_JUSTIFY] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_JUSTIFY, pspec);
|
|
|
|
/**
|
|
* ClutterText:password-char:
|
|
*
|
|
* If non-zero, the character that should be used in place of
|
|
* the actual text in a password text actor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_unichar ("password-char",
|
|
P_("Password Character"),
|
|
P_("If non-zero, use this character to display the actor's contents"),
|
|
0,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_PASSWORD_CHAR] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_PASSWORD_CHAR, pspec);
|
|
|
|
/**
|
|
* ClutterText:max-length:
|
|
*
|
|
* The maximum length of the contents of the #ClutterText actor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_int ("max-length",
|
|
P_("Max Length"),
|
|
P_("Maximum length of the text inside the actor"),
|
|
-1, G_MAXINT, 0,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_MAX_LENGTH] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_MAX_LENGTH, pspec);
|
|
|
|
/**
|
|
* ClutterText:single-line-mode:
|
|
*
|
|
* Whether the #ClutterText actor should be in single line mode
|
|
* or not. A single line #ClutterText actor will only contain a
|
|
* single line of text, scrolling it in case its length is bigger
|
|
* than the allocated size.
|
|
*
|
|
* Setting this property will also set the #ClutterText:activatable
|
|
* property as a side-effect.
|
|
*
|
|
* The #ClutterText:single-line-mode property is used only if the
|
|
* #ClutterText:editable property is set to %TRUE.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
pspec = g_param_spec_boolean ("single-line-mode",
|
|
P_("Single Line Mode"),
|
|
P_("Whether the text should be a single line"),
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_SINGLE_LINE_MODE] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_SINGLE_LINE_MODE, pspec);
|
|
|
|
/**
|
|
* ClutterText:selected-text-color:
|
|
*
|
|
* The color of selected text.
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
pspec = clutter_param_spec_color ("selected-text-color",
|
|
P_("Selected Text Color"),
|
|
P_("Selected Text Color"),
|
|
&default_selected_text_color,
|
|
CLUTTER_PARAM_READWRITE);
|
|
obj_props[PROP_SELECTED_TEXT_COLOR] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_SELECTED_TEXT_COLOR, pspec);
|
|
|
|
/**
|
|
* ClutterText:selected-text-color-set:
|
|
*
|
|
* Will be set to %TRUE if #ClutterText:selected-text-color has been set.
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
pspec = g_param_spec_boolean ("selected-text-color-set",
|
|
P_("Selected Text Color Set"),
|
|
P_("Whether the selected text color has been set"),
|
|
FALSE,
|
|
CLUTTER_PARAM_READABLE);
|
|
obj_props[PROP_SELECTED_TEXT_COLOR_SET] = pspec;
|
|
g_object_class_install_property (gobject_class, PROP_SELECTED_TEXT_COLOR_SET, 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 (I_("text-changed"),
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterTextClass, text_changed),
|
|
NULL, NULL,
|
|
_clutter_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
/**
|
|
* ClutterText::insert-text:
|
|
* @self: the #ClutterText that emitted the signal
|
|
* @new_text: the new text to insert
|
|
* @new_text_length: the length of the new text, in bytes, or -1 if
|
|
* new_text is nul-terminated
|
|
* @position: the position, in characters, at which to insert the
|
|
* new text. this is an in-out parameter. After the signal
|
|
* emission is finished, it should point after the newly
|
|
* inserted text.
|
|
*
|
|
* This signal is emitted when text is inserted into the actor by
|
|
* the user. It is emitted before @self text changes.
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
text_signals[INSERT_TEXT] =
|
|
g_signal_new (I_("insert-text"),
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
0,
|
|
NULL, NULL,
|
|
_clutter_marshal_VOID__STRING_INT_POINTER,
|
|
G_TYPE_NONE, 3,
|
|
G_TYPE_STRING,
|
|
G_TYPE_INT,
|
|
G_TYPE_POINTER);
|
|
|
|
/**
|
|
* ClutterText::delete-text:
|
|
* @self: the #ClutterText that emitted the signal
|
|
* @start_pos: the starting position
|
|
* @end_pos: the end position
|
|
*
|
|
* This signal is emitted when text is deleted from the actor by
|
|
* the user. It is emitted before @self text changes.
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
text_signals[DELETE_TEXT] =
|
|
g_signal_new (I_("delete-text"),
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
0,
|
|
NULL, NULL,
|
|
_clutter_marshal_VOID__INT_INT,
|
|
G_TYPE_NONE, 2,
|
|
G_TYPE_INT,
|
|
G_TYPE_INT);
|
|
|
|
/**
|
|
* ClutterText::cursor-event:
|
|
* @self: the #ClutterText that emitted the signal
|
|
* @geometry: the coordinates of the cursor
|
|
*
|
|
* The ::cursor-event signal is emitted whenever the cursor position
|
|
* changes inside a #ClutterText actor. Inside @geometry it is stored
|
|
* the current position and size of the cursor, relative to the actor
|
|
* itself.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
text_signals[CURSOR_EVENT] =
|
|
g_signal_new (I_("cursor-event"),
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterTextClass, cursor_event),
|
|
NULL, NULL,
|
|
_clutter_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. The signal is
|
|
* emitted only if #ClutterText:activatable is set to %TRUE.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
text_signals[ACTIVATE] =
|
|
g_signal_new (I_("activate"),
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterTextClass, activate),
|
|
NULL, NULL,
|
|
_clutter_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_KEY_Left, CLUTTER_CONTROL_MASK,
|
|
G_CALLBACK (clutter_text_real_move_left));
|
|
clutter_text_add_move_binding (binding_pool, "move-left",
|
|
CLUTTER_KEY_KP_Left, CLUTTER_CONTROL_MASK,
|
|
G_CALLBACK (clutter_text_real_move_left));
|
|
clutter_text_add_move_binding (binding_pool, "move-right",
|
|
CLUTTER_KEY_Right, CLUTTER_CONTROL_MASK,
|
|
G_CALLBACK (clutter_text_real_move_right));
|
|
clutter_text_add_move_binding (binding_pool, "move-right",
|
|
CLUTTER_KEY_KP_Right, CLUTTER_CONTROL_MASK,
|
|
G_CALLBACK (clutter_text_real_move_right));
|
|
clutter_text_add_move_binding (binding_pool, "move-up",
|
|
CLUTTER_KEY_Up, 0,
|
|
G_CALLBACK (clutter_text_real_move_up));
|
|
clutter_text_add_move_binding (binding_pool, "move-up",
|
|
CLUTTER_KEY_KP_Up, 0,
|
|
G_CALLBACK (clutter_text_real_move_up));
|
|
clutter_text_add_move_binding (binding_pool, "move-down",
|
|
CLUTTER_KEY_Down, 0,
|
|
G_CALLBACK (clutter_text_real_move_down));
|
|
clutter_text_add_move_binding (binding_pool, "move-down",
|
|
CLUTTER_KEY_KP_Down, 0,
|
|
G_CALLBACK (clutter_text_real_move_down));
|
|
|
|
clutter_text_add_move_binding (binding_pool, "line-start",
|
|
CLUTTER_KEY_Home, 0,
|
|
G_CALLBACK (clutter_text_real_line_start));
|
|
clutter_text_add_move_binding (binding_pool, "line-start",
|
|
CLUTTER_KEY_KP_Home, 0,
|
|
G_CALLBACK (clutter_text_real_line_start));
|
|
clutter_text_add_move_binding (binding_pool, "line-start",
|
|
CLUTTER_KEY_Begin, 0,
|
|
G_CALLBACK (clutter_text_real_line_start));
|
|
clutter_text_add_move_binding (binding_pool, "line-end",
|
|
CLUTTER_KEY_End, 0,
|
|
G_CALLBACK (clutter_text_real_line_end));
|
|
clutter_text_add_move_binding (binding_pool, "line-end",
|
|
CLUTTER_KEY_KP_End, 0,
|
|
G_CALLBACK (clutter_text_real_line_end));
|
|
|
|
clutter_binding_pool_install_action (binding_pool, "select-all",
|
|
CLUTTER_KEY_a, CLUTTER_CONTROL_MASK,
|
|
G_CALLBACK (clutter_text_real_select_all),
|
|
NULL, NULL);
|
|
|
|
clutter_binding_pool_install_action (binding_pool, "delete-next",
|
|
CLUTTER_KEY_Delete, 0,
|
|
G_CALLBACK (clutter_text_real_del_next),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "delete-next",
|
|
CLUTTER_KEY_Delete, CLUTTER_CONTROL_MASK,
|
|
G_CALLBACK (clutter_text_real_del_word_next),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "delete-next",
|
|
CLUTTER_KEY_KP_Delete, 0,
|
|
G_CALLBACK (clutter_text_real_del_next),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "delete-next",
|
|
CLUTTER_KEY_KP_Delete, CLUTTER_CONTROL_MASK,
|
|
G_CALLBACK (clutter_text_real_del_word_next),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "delete-prev",
|
|
CLUTTER_KEY_BackSpace, 0,
|
|
G_CALLBACK (clutter_text_real_del_prev),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "delete-prev",
|
|
CLUTTER_KEY_BackSpace, CLUTTER_SHIFT_MASK,
|
|
G_CALLBACK (clutter_text_real_del_prev),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "delete-prev",
|
|
CLUTTER_KEY_BackSpace, CLUTTER_CONTROL_MASK,
|
|
G_CALLBACK (clutter_text_real_del_word_prev),
|
|
NULL, NULL);
|
|
|
|
clutter_binding_pool_install_action (binding_pool, "activate",
|
|
CLUTTER_KEY_Return, 0,
|
|
G_CALLBACK (clutter_text_real_activate),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "activate",
|
|
CLUTTER_KEY_KP_Enter, 0,
|
|
G_CALLBACK (clutter_text_real_activate),
|
|
NULL, NULL);
|
|
clutter_binding_pool_install_action (binding_pool, "activate",
|
|
CLUTTER_KEY_ISO_Enter, 0,
|
|
G_CALLBACK (clutter_text_real_activate),
|
|
NULL, NULL);
|
|
}
|
|
|
|
static void
|
|
clutter_text_init (ClutterText *self)
|
|
{
|
|
ClutterSettings *settings;
|
|
ClutterTextPrivate *priv;
|
|
gchar *font_name;
|
|
int i;
|
|
|
|
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;
|
|
|
|
/* default to "" so that clutter_text_get_text() will
|
|
* return a valid string and we can safely call strlen()
|
|
* or strcmp() on it
|
|
*/
|
|
priv->text = g_strdup ("");
|
|
|
|
priv->text_color = default_text_color;
|
|
priv->cursor_color = default_cursor_color;
|
|
priv->selection_color = default_selection_color;
|
|
priv->selected_text_color = default_selected_text_color;
|
|
|
|
/* get the default font name from the context; we don't use
|
|
* set_font_description() here because we are initializing
|
|
* the Text and we don't need notifications and sanity checks
|
|
*/
|
|
settings = clutter_settings_get_default ();
|
|
g_object_get (settings, "font-name", &font_name, NULL);
|
|
|
|
priv->font_name = font_name; /* font_name is allocated */
|
|
priv->font_desc = pango_font_description_from_string (font_name);
|
|
priv->is_default_font = TRUE;
|
|
|
|
priv->position = -1;
|
|
priv->selection_bound = -1;
|
|
|
|
priv->x_pos = -1;
|
|
priv->cursor_visible = TRUE;
|
|
priv->editable = FALSE;
|
|
priv->selectable = TRUE;
|
|
|
|
priv->selection_color_set = FALSE;
|
|
priv->cursor_color_set = FALSE;
|
|
priv->selected_text_color_set = FALSE;
|
|
priv->preedit_set = FALSE;
|
|
|
|
priv->password_char = 0;
|
|
|
|
priv->max_length = 0;
|
|
|
|
priv->text_y = 0;
|
|
|
|
priv->cursor_size = DEFAULT_CURSOR_SIZE;
|
|
memset (&priv->cursor_pos, 0, sizeof (ClutterGeometry));
|
|
|
|
priv->font_changed_id =
|
|
g_signal_connect_swapped (clutter_get_default_backend (),
|
|
"settings-changed",
|
|
G_CALLBACK (clutter_text_font_changed_cb),
|
|
self);
|
|
|
|
priv->direction_changed_id =
|
|
g_signal_connect (self, "notify::text-direction",
|
|
G_CALLBACK (clutter_text_direction_changed_cb),
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_new:
|
|
*
|
|
* Creates a new #ClutterText actor. This actor can be used to
|
|
* display and edit text.
|
|
*
|
|
* Return value: the newly created #ClutterText actor
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
ClutterActor *
|
|
clutter_text_new (void)
|
|
{
|
|
return g_object_new (CLUTTER_TYPE_TEXT, NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_new_full:
|
|
* @font_name: a string with a font description
|
|
* @text: the contents of the actor
|
|
* @color: the color to be used to render @text
|
|
*
|
|
* Creates a new #ClutterText actor, using @font_name as the font
|
|
* description; @text will be used to set the contents of the actor;
|
|
* and @color will be used as the color to render @text.
|
|
*
|
|
* This function is equivalent to calling clutter_text_new(),
|
|
* clutter_text_set_font_name(), clutter_text_set_text() and
|
|
* clutter_text_set_color().
|
|
*
|
|
* Return value: the newly created #ClutterText actor
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_new_with_text:
|
|
* @font_name: a string with a font description
|
|
* @text: the contents of the actor
|
|
*
|
|
* Creates a new #ClutterText actor, using @font_name as the font
|
|
* description; @text will be used to set the contents of the actor.
|
|
*
|
|
* This function is equivalent to calling clutter_text_new(),
|
|
* clutter_text_set_font_name(), and clutter_text_set_text().
|
|
*
|
|
* Return value: the newly created #ClutterText actor
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_editable:
|
|
* @self: a #ClutterText
|
|
* @editable: whether the #ClutterText should be editable
|
|
*
|
|
* Sets whether the #ClutterText actor should be editable.
|
|
*
|
|
* An editable #ClutterText with key focus set using
|
|
* clutter_actor_grab_key_focus() or clutter_stage_take_key_focus()
|
|
* will receive key events and will update its contents accordingly.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
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;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_EDITABLE]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_editable:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves whether a #ClutterText is editable or not.
|
|
*
|
|
* Return value: %TRUE if the actor is editable
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_get_editable (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
|
|
|
|
return self->priv->editable;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_selectable:
|
|
* @self: a #ClutterText
|
|
* @selectable: whether the #ClutterText actor should be selectable
|
|
*
|
|
* Sets whether a #ClutterText actor should be selectable.
|
|
*
|
|
* A selectable #ClutterText will allow selecting its contents using
|
|
* the pointer or the keyboard.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
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;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTABLE]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_selectable:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves whether a #ClutterText is selectable or not.
|
|
*
|
|
* Return value: %TRUE if the actor is selectable
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_get_selectable (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), TRUE);
|
|
|
|
return self->priv->selectable;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_activatable:
|
|
* @self: a #ClutterText
|
|
* @activatable: whether the #ClutterText actor should be activatable
|
|
*
|
|
* Sets whether a #ClutterText actor should be activatable.
|
|
*
|
|
* An activatable #ClutterText actor will emit the #ClutterText::activate
|
|
* signal whenever the 'Enter' (or 'Return') key is pressed; if it is not
|
|
* activatable, a new line will be appended to the current content.
|
|
*
|
|
* An activatable #ClutterText must also be set as editable using
|
|
* clutter_text_set_editable().
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
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;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ACTIVATABLE]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_activatable:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves whether a #ClutterText is activatable or not.
|
|
*
|
|
* Return value: %TRUE if the actor is activatable
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_get_activatable (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), TRUE);
|
|
|
|
return self->priv->activatable;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_activate:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Emits the #ClutterText::activate signal, if @self has been set
|
|
* as activatable using clutter_text_set_activatable().
|
|
*
|
|
* This function can be used to emit the ::activate signal inside
|
|
* a #ClutterActor::captured-event or #ClutterActor::key-press-event
|
|
* signal handlers before the default signal handler for the
|
|
* #ClutterText is invoked.
|
|
*
|
|
* Return value: %TRUE if the ::activate signal has been emitted,
|
|
* and %FALSE otherwise
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_activate (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->activatable)
|
|
{
|
|
g_signal_emit (self, text_signals[ACTIVATE], 0);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_cursor_visible:
|
|
* @self: a #ClutterText
|
|
* @cursor_visible: whether the cursor should be visible
|
|
*
|
|
* Sets whether the cursor of a #ClutterText actor should be
|
|
* visible or not.
|
|
*
|
|
* The color of the cursor will be the same as the text color
|
|
* unless clutter_text_set_cursor_color() has been called.
|
|
*
|
|
* The size of the cursor can be set using clutter_text_set_cursor_size().
|
|
*
|
|
* The position of the cursor can be changed programmatically using
|
|
* clutter_text_set_cursor_position().
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
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;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_VISIBLE]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_cursor_visible:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves whether the cursor of a #ClutterText actor is visible.
|
|
*
|
|
* Return value: %TRUE if the cursor is visible
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_get_cursor_visible (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), TRUE);
|
|
|
|
return self->priv->cursor_visible;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_cursor_color:
|
|
* @self: a #ClutterText
|
|
* @color: the color of the cursor, or %NULL to unset it
|
|
*
|
|
* Sets the color of the cursor of a #ClutterText actor.
|
|
*
|
|
* If @color is %NULL, the cursor color will be the same as the
|
|
* text color.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
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;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_COLOR]);
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_COLOR_SET]);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_cursor_color:
|
|
* @self: a #ClutterText
|
|
* @color: return location for a #ClutterColor
|
|
*
|
|
* Retrieves the color of the cursor of a #ClutterText actor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_selection:
|
|
* @self: a #ClutterText
|
|
* @start_pos: start of the selection, in characters
|
|
* @end_pos: end of the selection, in characters
|
|
*
|
|
* Selects the region of text between @start_pos and @end_pos.
|
|
*
|
|
* This function changes the position of the cursor to match
|
|
* @start_pos and the selection bound to match @end_pos.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_selection (ClutterText *self,
|
|
gssize start_pos,
|
|
gssize end_pos)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
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);
|
|
|
|
clutter_text_set_positions (self, start_pos, end_pos);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_selection:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the currently selected text.
|
|
*
|
|
* Return value: a newly allocated string containing the currently
|
|
* selected text, or %NULL. Use g_free() to free the returned
|
|
* string.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gchar *
|
|
clutter_text_get_selection (ClutterText *self)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
gchar *str;
|
|
gint len;
|
|
gint start_index, end_index;
|
|
gint start_offset, end_offset;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
|
|
|
|
priv = self->priv;
|
|
|
|
start_index = priv->position;
|
|
end_index = priv->selection_bound;
|
|
|
|
if (end_index == start_index)
|
|
return g_strdup ("");
|
|
|
|
if ((end_index != -1 && end_index < start_index) ||
|
|
start_index == -1)
|
|
{
|
|
gint temp = start_index;
|
|
start_index = end_index;
|
|
end_index = temp;
|
|
}
|
|
|
|
start_offset = offset_to_bytes (priv->text, start_index);
|
|
end_offset = offset_to_bytes (priv->text, end_index);
|
|
len = end_offset - start_offset;
|
|
|
|
str = g_malloc (len + 1);
|
|
g_utf8_strncpy (str, priv->text + start_offset, end_index - start_index);
|
|
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_selection_bound:
|
|
* @self: a #ClutterText
|
|
* @selection_bound: the position of the end of the selection, in characters
|
|
*
|
|
* Sets the other end of the selection, starting from the current
|
|
* cursor position.
|
|
*
|
|
* If @selection_bound is -1, the selection unset.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
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)
|
|
{
|
|
gint len = priv->n_chars;
|
|
|
|
if (selection_bound < 0 || selection_bound >= len)
|
|
priv->selection_bound = -1;
|
|
else
|
|
priv->selection_bound = selection_bound;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_BOUND]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_selection_bound:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the other end of the selection of a #ClutterText actor,
|
|
* in characters from the current cursor position.
|
|
*
|
|
* Return value: the position of the other end of the selection
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
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_set_selection_color:
|
|
* @self: a #ClutterText
|
|
* @color: the color of the selection, or %NULL to unset it
|
|
*
|
|
* Sets the color of the selection of a #ClutterText actor.
|
|
*
|
|
* If @color is %NULL, the selection color will be the same as the
|
|
* cursor color, or if no cursor color is set either then it will be
|
|
* the same as the text color.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_selection_color (ClutterText *self,
|
|
const ClutterColor *color)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (color)
|
|
{
|
|
priv->selection_color = *color;
|
|
priv->selection_color_set = TRUE;
|
|
}
|
|
else
|
|
priv->selection_color_set = FALSE;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_COLOR]);
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_COLOR_SET]);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_selection_color:
|
|
* @self: a #ClutterText
|
|
* @color: (out caller-allocates): return location for a #ClutterColor
|
|
*
|
|
* Retrieves the color of the selection of a #ClutterText actor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_get_selection_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->selection_color;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_selected_text_color:
|
|
* @self: a #ClutterText
|
|
* @color: the selected text color, or %NULL to unset it
|
|
*
|
|
* Sets the selected text color of a #ClutterText actor.
|
|
*
|
|
* If @color is %NULL, the selected text color will be the same as the
|
|
* selection color, which then falls back to cursor, and then text color.
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
void
|
|
clutter_text_set_selected_text_color (ClutterText *self,
|
|
const ClutterColor *color)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (color)
|
|
{
|
|
priv->selected_text_color = *color;
|
|
priv->selected_text_color_set = TRUE;
|
|
}
|
|
else
|
|
priv->selected_text_color_set = FALSE;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTED_TEXT_COLOR]);
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTED_TEXT_COLOR_SET]);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_selected_text_color:
|
|
* @self: a #ClutterText
|
|
* @color: (out caller-allocates): return location for a #ClutterColor
|
|
*
|
|
* Retrieves the color of selected text of a #ClutterText actor.
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
void
|
|
clutter_text_get_selected_text_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->selected_text_color;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_font_description:
|
|
* @self: a #ClutterText
|
|
* @font_desc: a #PangoFontDescription
|
|
*
|
|
* Sets @font_desc as the font description for a #ClutterText
|
|
*
|
|
* The #PangoFontDescription is copied by the #ClutterText actor
|
|
* so you can safely call pango_font_description_free() on it after
|
|
* calling this function.
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
void
|
|
clutter_text_set_font_description (ClutterText *self,
|
|
PangoFontDescription *font_desc)
|
|
{
|
|
PangoFontDescription *copy;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
copy = pango_font_description_copy (font_desc);
|
|
clutter_text_set_font_description_internal (self, copy);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_font_description:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the #PangoFontDescription used by @self
|
|
*
|
|
* Return value: a #PangoFontDescription. The returned value is owned
|
|
* by the #ClutterText actor and it should not be modified or freed
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
PangoFontDescription *
|
|
clutter_text_get_font_description (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
|
|
|
|
return self->priv->font_desc;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
const 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: (allow-none): a font name, or %NULL to set the default font name
|
|
*
|
|
* Sets the font used by a #ClutterText. The @font_name string
|
|
* must either be %NULL, which means that the font name from the
|
|
* default #ClutterBackend will be used; or 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;
|
|
gboolean is_default_font;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
/* get the default font name from the backend */
|
|
if (font_name == NULL || font_name[0] == '\0')
|
|
{
|
|
ClutterSettings *settings = clutter_settings_get_default ();
|
|
gchar *default_font_name = NULL;
|
|
|
|
g_object_get (settings, "font-name", &default_font_name, NULL);
|
|
|
|
if (default_font_name != NULL)
|
|
font_name = default_font_name;
|
|
else
|
|
{
|
|
/* last fallback */
|
|
default_font_name = g_strdup ("Sans 12");
|
|
}
|
|
|
|
is_default_font = TRUE;
|
|
}
|
|
else
|
|
is_default_font = FALSE;
|
|
|
|
priv = self->priv;
|
|
|
|
if (g_strcmp0 (priv->font_name, font_name) == 0)
|
|
goto out;
|
|
|
|
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);
|
|
goto out;
|
|
}
|
|
|
|
/* this will set the font_name field as well */
|
|
clutter_text_set_font_description_internal (self, desc);
|
|
priv->is_default_font = is_default_font;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_FONT_NAME]);
|
|
|
|
out:
|
|
if (is_default_font)
|
|
g_free ((gchar *) 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
|
|
*/
|
|
const gchar *
|
|
clutter_text_get_text (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
|
|
|
|
return self->priv->text;
|
|
}
|
|
|
|
static inline void
|
|
clutter_text_set_use_markup_internal (ClutterText *self,
|
|
gboolean use_markup)
|
|
{
|
|
ClutterTextPrivate *priv = self->priv;
|
|
|
|
if (priv->use_markup != use_markup)
|
|
{
|
|
priv->use_markup = use_markup;
|
|
|
|
/* reset the attributes lists so that they can be
|
|
* re-generated
|
|
*/
|
|
if (priv->effective_attrs != NULL)
|
|
{
|
|
pango_attr_list_unref (priv->effective_attrs);
|
|
priv->effective_attrs = NULL;
|
|
}
|
|
|
|
if (priv->markup_attrs)
|
|
{
|
|
pango_attr_list_unref (priv->markup_attrs);
|
|
priv->markup_attrs = NULL;
|
|
}
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_USE_MARKUP]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_text:
|
|
* @self: a #ClutterText
|
|
* @text: the text to set. Passing %NULL is the same as passing "" (the
|
|
* empty string)
|
|
*
|
|
* Sets the contents of a #ClutterText actor.
|
|
*
|
|
* If the #ClutterText:use-markup property was set to %TRUE it
|
|
* will be reset to %FALSE as a side effect. If you want to
|
|
* maintain the #ClutterText:use-markup you should use the
|
|
* clutter_text_set_markup() function instead
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_text (ClutterText *self,
|
|
const gchar *text)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
g_signal_emit (self, text_signals[DELETE_TEXT], 0, 0, -1);
|
|
if (text)
|
|
{
|
|
gint tmp_pos = 0;
|
|
g_signal_emit (self, text_signals[INSERT_TEXT], 0, text,
|
|
strlen (text), &tmp_pos);
|
|
}
|
|
|
|
clutter_text_set_use_markup_internal (self, FALSE);
|
|
clutter_text_set_text_internal (self, text ? text : "");
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_markup:
|
|
* @self: a #ClutterText
|
|
* @markup: a string containing Pango markup. Passing %NULL is the same
|
|
* as passing "" (the empty string)
|
|
*
|
|
* Sets @markup as the contents of a #ClutterText.
|
|
*
|
|
* This is a convenience function for setting a string containing
|
|
* Pango markup, and it is logically equivalent to:
|
|
*
|
|
* |[
|
|
* clutter_text_set_text (CLUTTER_TEXT (actor), markup);
|
|
* clutter_text_set_use_markup (CLUTTER_TEXT (actor), TRUE);
|
|
* ]|
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_markup (ClutterText *self,
|
|
const gchar *markup)
|
|
{
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
clutter_text_set_use_markup_internal (self, TRUE);
|
|
|
|
if (markup != NULL && *markup != '\0')
|
|
clutter_text_set_markup_internal (self, markup);
|
|
else
|
|
clutter_text_set_text_internal (self, "");
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_layout:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the current #PangoLayout used by a #ClutterText actor.
|
|
*
|
|
* Return value: (transfer none): 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)
|
|
{
|
|
gfloat width, height;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL);
|
|
|
|
if (self->priv->editable && self->priv->single_line_mode)
|
|
return clutter_text_create_layout (self, -1, -1);
|
|
|
|
clutter_actor_get_size (CLUTTER_ACTOR (self), &width, &height);
|
|
|
|
return clutter_text_create_layout (self, width, height);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_COLOR]);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_color:
|
|
* @self: a #ClutterText
|
|
* @color: (out caller-allocates): return location for a #ClutterColor
|
|
*
|
|
* Retrieves the text color as set by clutter_text_set_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_by_pspec (G_OBJECT (self), obj_props[PROP_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_by_pspec (G_OBJECT (self), obj_props[PROP_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_by_pspec (G_OBJECT (self), obj_props[PROP_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 #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);
|
|
|
|
priv->attrs = attrs;
|
|
|
|
/* Clear the effective attributes so they will be regenerated when a
|
|
layout is created */
|
|
if (priv->effective_attrs)
|
|
{
|
|
pango_attr_list_unref (priv->effective_attrs);
|
|
priv->effective_attrs = NULL;
|
|
}
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_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: (transfer none): 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_line_alignment:
|
|
* @self: a #ClutterText
|
|
* @alignment: A #PangoAlignment
|
|
*
|
|
* Sets the way that the lines of a wrapped label are aligned with
|
|
* respect to each other. This does not affect the overall alignment
|
|
* of the label within its allocated or specified width.
|
|
*
|
|
* To align a #ClutterText actor you should add it to a container
|
|
* that supports alignment, or use the anchor point.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_line_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_by_pspec (G_OBJECT (self), obj_props[PROP_LINE_ALIGNMENT]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_line_alignment:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the alignment of a #ClutterText, as set by
|
|
* clutter_text_set_line_alignment().
|
|
*
|
|
* Return value: a #PangoAlignment
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
PangoAlignment
|
|
clutter_text_get_line_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>.
|
|
*
|
|
* Setting #ClutterText:use-markup on an editable #ClutterText will
|
|
* not have any effect except hiding the markup.
|
|
*
|
|
* See also #ClutterText:use-markup.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_use_markup (ClutterText *self,
|
|
gboolean setting)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
gchar *str;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
str = g_strdup (priv->text);
|
|
|
|
clutter_text_set_use_markup_internal (self, setting);
|
|
|
|
if (setting)
|
|
clutter_text_set_markup_internal (self, str);
|
|
else
|
|
clutter_text_set_text_internal (self, str);
|
|
|
|
g_free (str);
|
|
|
|
clutter_text_dirty_cache (self);
|
|
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
}
|
|
|
|
/**
|
|
* 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_by_pspec (G_OBJECT (self), obj_props[PROP_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;
|
|
|
|
if (priv->position == position)
|
|
return;
|
|
|
|
len = priv->n_chars;
|
|
|
|
if (position < 0 || position >= len)
|
|
priv->position = -1;
|
|
else
|
|
priv->position = position;
|
|
|
|
/* Forget the target x position so that it will be recalculated next
|
|
time the cursor is moved up or down */
|
|
priv->x_pos = -1;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_POSITION]);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_cursor_size:
|
|
* @self: a #ClutterText
|
|
* @size: the size of the cursor, in pixels, or -1 to use the
|
|
* default value
|
|
*
|
|
* Sets the size of the cursor of a #ClutterText. The cursor
|
|
* will only be visible if the #ClutterText:cursor-visible property
|
|
* is set to %TRUE.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_cursor_size (ClutterText *self,
|
|
gint size)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->cursor_size != size)
|
|
{
|
|
if (size < 0)
|
|
size = DEFAULT_CURSOR_SIZE;
|
|
|
|
priv->cursor_size = size;
|
|
|
|
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_SIZE]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_cursor_size:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the size of the cursor of a #ClutterText actor.
|
|
*
|
|
* Return value: the size of the cursor, in pixels
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
guint
|
|
clutter_text_get_cursor_size (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), DEFAULT_CURSOR_SIZE);
|
|
|
|
return self->priv->cursor_size;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_password_char:
|
|
* @self: a #ClutterText
|
|
* @wc: a Unicode character, or 0 to unset the password character
|
|
*
|
|
* Sets the character to use in place of the actual text in a
|
|
* password text actor.
|
|
*
|
|
* If @wc is 0 the text will be displayed as it is entered in the
|
|
* #ClutterText actor.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_password_char (ClutterText *self,
|
|
gunichar wc)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->password_char != wc)
|
|
{
|
|
priv->password_char = wc;
|
|
|
|
clutter_text_dirty_cache (self);
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_PASSWORD_CHAR]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_password_char:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves the character to use in place of the actual text
|
|
* as set by clutter_text_set_password_char().
|
|
*
|
|
* Return value: a Unicode character or 0 if the password
|
|
* character is not set
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gunichar
|
|
clutter_text_get_password_char (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0);
|
|
|
|
return self->priv->password_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 = priv->n_chars;
|
|
|
|
priv->max_length = max;
|
|
|
|
new = g_strdup (priv->text);
|
|
clutter_text_set_text (self, new);
|
|
g_free (new);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_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);
|
|
|
|
g_signal_emit (self, text_signals[INSERT_TEXT], 0, &wc, 1, &pos);
|
|
|
|
clutter_text_set_text_internal (self, new->str);
|
|
|
|
if (priv->position >= 0)
|
|
clutter_text_set_positions (self,
|
|
priv->position + 1,
|
|
priv->position + 1);
|
|
|
|
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;
|
|
gint pos_bytes;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
g_return_if_fail (text != NULL);
|
|
|
|
priv = self->priv;
|
|
|
|
pos_bytes = offset_to_bytes (priv->text, position);
|
|
|
|
new = g_string_new (priv->text);
|
|
new = g_string_insert (new, pos_bytes, text);
|
|
|
|
g_signal_emit (self, text_signals[INSERT_TEXT], 0,
|
|
text,
|
|
g_utf8_strlen (text, -1),
|
|
&position);
|
|
|
|
clutter_text_set_text_internal (self, new->str);
|
|
|
|
if (position >= 0 && priv->position >= position)
|
|
{
|
|
gint new_pos = priv->position + g_utf8_strlen (text, -1);
|
|
|
|
clutter_text_set_positions (self, new_pos, new_pos);
|
|
}
|
|
|
|
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 (start_pos == 0)
|
|
start_bytes = 0;
|
|
else
|
|
start_bytes = offset_to_bytes (priv->text, start_pos);
|
|
|
|
if (end_pos == -1)
|
|
end_bytes = offset_to_bytes (priv->text, priv->n_chars);
|
|
else
|
|
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);
|
|
|
|
g_signal_emit (self, text_signals[DELETE_TEXT], 0, start_pos, end_pos);
|
|
|
|
clutter_text_set_text_internal (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 pos;
|
|
gint num_pos;
|
|
gint start_pos;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (!priv->text)
|
|
return;
|
|
|
|
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);
|
|
}
|
|
|
|
start_pos = clutter_text_get_cursor_position (self);
|
|
g_signal_emit (self, text_signals[DELETE_TEXT], 0,
|
|
start_pos, start_pos + n_chars);
|
|
|
|
clutter_text_set_text_internal (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_by_pspec (G_OBJECT (self), obj_props[PROP_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, but not including @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);
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_single_line_mode:
|
|
* @self: a #ClutterText
|
|
* @single_line: whether to enable single line mode
|
|
*
|
|
* Sets whether a #ClutterText actor should be in single line mode
|
|
* or not. Only editable #ClutterText<!-- -->s can be in single line
|
|
* mode.
|
|
*
|
|
* A text actor in single line mode will not wrap text and will clip
|
|
* the visible area to the predefined size. The contents of the
|
|
* text actor will scroll to display the end of the text if its length
|
|
* is bigger than the allocated width.
|
|
*
|
|
* When setting the single line mode the #ClutterText:activatable
|
|
* property is also set as a side effect. Instead of entering a new
|
|
* line character, the text actor will emit the #ClutterText::activate
|
|
* signal.
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
void
|
|
clutter_text_set_single_line_mode (ClutterText *self,
|
|
gboolean single_line)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (priv->single_line_mode != single_line)
|
|
{
|
|
g_object_freeze_notify (G_OBJECT (self));
|
|
|
|
priv->single_line_mode = single_line;
|
|
|
|
if (priv->single_line_mode)
|
|
{
|
|
priv->activatable = TRUE;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ACTIVATABLE]);
|
|
}
|
|
|
|
clutter_text_dirty_cache (self);
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SINGLE_LINE_MODE]);
|
|
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_text_get_single_line_mode:
|
|
* @self: a #ClutterText
|
|
*
|
|
* Retrieves whether the #ClutterText actor is in single line mode.
|
|
*
|
|
* Return value: %TRUE if the #ClutterText actor is in single line mode
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
gboolean
|
|
clutter_text_get_single_line_mode (ClutterText *self)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE);
|
|
|
|
return self->priv->single_line_mode;
|
|
}
|
|
|
|
/**
|
|
* clutter_text_set_preedit_string:
|
|
* @self: a #ClutterText
|
|
* @preedit_str: (allow-none): the pre-edit string, or %NULL to unset it
|
|
* @preedit_attrs: (allow-none): the pre-edit string attributes
|
|
* @cursor_pos: the cursor position for the pre-edit string
|
|
*
|
|
* Sets, or unsets, the pre-edit string. This function is useful
|
|
* for input methods to display a string (with eventual specific
|
|
* Pango attributes) before it is entered inside the #ClutterText
|
|
* buffer.
|
|
*
|
|
* The preedit string and attributes are ignored if the #ClutterText
|
|
* actor is not editable.
|
|
*
|
|
* This function should not be used by applications
|
|
*
|
|
* Since: 1.2
|
|
*/
|
|
void
|
|
clutter_text_set_preedit_string (ClutterText *self,
|
|
const gchar *preedit_str,
|
|
PangoAttrList *preedit_attrs,
|
|
guint cursor_pos)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
g_free (priv->preedit_str);
|
|
priv->preedit_str = NULL;
|
|
|
|
if (priv->preedit_attrs != NULL)
|
|
{
|
|
pango_attr_list_unref (priv->preedit_attrs);
|
|
priv->preedit_attrs = NULL;
|
|
}
|
|
|
|
priv->preedit_n_chars = 0;
|
|
priv->preedit_cursor_pos = 0;
|
|
|
|
if (preedit_str == NULL || *preedit_str == '\0')
|
|
priv->preedit_set = FALSE;
|
|
else
|
|
{
|
|
priv->preedit_str = g_strdup (preedit_str);
|
|
|
|
if (priv->preedit_str != NULL)
|
|
priv->preedit_n_chars = g_utf8_strlen (priv->preedit_str, -1);
|
|
else
|
|
priv->preedit_n_chars = 0;
|
|
|
|
if (preedit_attrs != NULL)
|
|
priv->preedit_attrs = pango_attr_list_ref (preedit_attrs);
|
|
|
|
priv->preedit_cursor_pos =
|
|
CLAMP (cursor_pos, 0, priv->preedit_n_chars);
|
|
|
|
priv->preedit_set = TRUE;
|
|
}
|
|
|
|
clutter_text_dirty_cache (self);
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
|
|
}
|
|
|
|
|
|
/**
|
|
* clutter_text_get_layout_offsets:
|
|
* @self: a #ClutterText
|
|
* @x: (out): location to store X offset of layout, or %NULL
|
|
* @y: (out): location to store Y offset of layout, or %NULL
|
|
*
|
|
* Obtains the coordinates where the #ClutterText will draw the #PangoLayout
|
|
* representing the text.
|
|
*
|
|
* Since: 1.7
|
|
**/
|
|
void
|
|
clutter_text_get_layout_offsets (ClutterText *self,
|
|
gint *x,
|
|
gint *y)
|
|
{
|
|
ClutterTextPrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXT (self));
|
|
|
|
priv = self->priv;
|
|
|
|
if (x != NULL)
|
|
*x = priv->text_x;
|
|
|
|
if (y != NULL)
|
|
*y = priv->text_y;
|
|
}
|