diff options
Diffstat (limited to 'clutter/clutter/clutter-text.c')
-rw-r--r-- | clutter/clutter/clutter-text.c | 6857 |
1 files changed, 0 insertions, 6857 deletions
diff --git a/clutter/clutter/clutter-text.c b/clutter/clutter/clutter-text.c deleted file mode 100644 index 45c7eac56..000000000 --- a/clutter/clutter/clutter-text.c +++ /dev/null @@ -1,6857 +0,0 @@ -/* - * 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 - */ - -#include "clutter-build-config.h" - -#include <string.h> -#include <math.h> - -#include "clutter-text.h" - -#include "clutter-actor-private.h" -#include "clutter-animatable.h" -#include "clutter-backend-private.h" -#include "clutter-binding-pool.h" -#include "clutter-color.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-property-transition.h" -#include "clutter-text-buffer.h" -#include "clutter-units.h" -#include "clutter-paint-volume-private.h" -#include "clutter-scriptable.h" -#include "clutter-input-focus.h" - -/* cursor width in pixels */ -#define DEFAULT_CURSOR_SIZE 2 - -/* vertical padding for the cursor */ -#define CURSOR_Y_PADDING 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 - -typedef struct _LayoutCache LayoutCache; - -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 _ClutterTextInputFocus -{ - ClutterInputFocus parent_instance; - ClutterText *text; -}; - -struct _ClutterTextPrivate -{ - PangoFontDescription *font_desc; - - /* the displayed text */ - ClutterTextBuffer *buffer; - - 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; - - /* 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 (in both physical and logical pixels) - * when in single line mode, to scroll the contents of the - * text actor - */ - gint text_x; - gint text_logical_x; - - /* the y position of the PangoLayout (in both physical and logical pixels), - * fixed to 0 by default for now */ - gint text_y; - gint text_logical_y; - - /* Where to draw the cursor */ - graphene_rect_t cursor_rect; - ClutterColor cursor_color; - guint cursor_size; - - /* Box representing the paint volume. The box is lazily calculated - and cached */ - ClutterPaintVolume paint_volume; - - guint preedit_cursor_pos; - gint preedit_n_chars; - - ClutterColor selection_color; - - ClutterColor selected_text_color; - - gunichar password_char; - - guint password_hint_id; - guint password_hint_timeout; - - /* Signal handler for when the backend changes its font settings */ - gulong settings_changed_id; - - /* Signal handler for when the :text-direction changes */ - gulong direction_changed_id; - - ClutterInputFocus *input_focus; - ClutterInputContentHintFlags input_hints; - ClutterInputContentPurpose input_purpose; - - /* bitfields */ - 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 in_select_touch : 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; - guint paint_volume_valid : 1; - guint show_password_hint : 1; - guint password_hint_visible : 1; - guint resolved_direction : 4; -}; - -enum -{ - PROP_0, - - PROP_BUFFER, - 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, /* XXX:2.0 - remove */ - PROP_SELECTION_BOUND, - PROP_SELECTION_COLOR, - PROP_SELECTION_COLOR_SET, - PROP_CURSOR_VISIBLE, - PROP_CURSOR_COLOR, - PROP_CURSOR_COLOR_SET, - PROP_CURSOR_SIZE, - PROP_CURSOR_POSITION, - 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_INPUT_HINTS, - PROP_INPUT_PURPOSE, - - PROP_LAST -}; - -static GParamSpec *obj_props[PROP_LAST]; - -enum -{ - TEXT_CHANGED, - CURSOR_EVENT, /* XXX:2.0 - remove */ - ACTIVATE, - INSERT_TEXT, - DELETE_TEXT, - CURSOR_CHANGED, - - LAST_SIGNAL -}; - -static guint text_signals[LAST_SIGNAL] = { 0, }; - -static void clutter_text_settings_changed_cb (ClutterText *text); -static void buffer_connect_signals (ClutterText *self); -static void buffer_disconnect_signals (ClutterText *self); -static ClutterTextBuffer *get_buffer (ClutterText *self); - -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 }; - -static CoglPipeline *default_color_pipeline = NULL; - -static ClutterAnimatableInterface *parent_animatable_iface = NULL; -static ClutterScriptableIface *parent_scriptable_iface = NULL; - -/* ClutterTextInputFocus */ -#define CLUTTER_TYPE_TEXT_INPUT_FOCUS (clutter_text_input_focus_get_type ()) - -G_DECLARE_FINAL_TYPE (ClutterTextInputFocus, clutter_text_input_focus, - CLUTTER, TEXT_INPUT_FOCUS, ClutterInputFocus) -G_DEFINE_TYPE (ClutterTextInputFocus, clutter_text_input_focus, - CLUTTER_TYPE_INPUT_FOCUS) - -/* Utilities pango to (logical) pixels functions */ -static float -pixels_to_pango (float px) -{ - return ceilf (px * (float) PANGO_SCALE); -} - -static float -logical_pixels_to_pango (float px, - float scale) -{ - return pixels_to_pango (px * scale); -} - -static float -pango_to_pixels (float size) -{ - return ceilf (size / (float) PANGO_SCALE); -} - -static float -pango_to_logical_pixels (float size, - float scale) -{ - return pango_to_pixels (size / scale); -} - -static void -clutter_text_input_focus_request_surrounding (ClutterInputFocus *focus) -{ - ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text; - ClutterTextBuffer *buffer; - const gchar *text; - gint anchor_pos, cursor_pos; - - buffer = clutter_text_get_buffer (clutter_text); - text = clutter_text_buffer_get_text (buffer); - - cursor_pos = clutter_text_get_cursor_position (clutter_text); - if (cursor_pos < 0) - cursor_pos = clutter_text_buffer_get_length (buffer); - - anchor_pos = clutter_text_get_selection_bound (clutter_text); - if (anchor_pos < 0) - anchor_pos = cursor_pos; - - clutter_input_focus_set_surrounding (focus, text, - g_utf8_offset_to_pointer (text, cursor_pos) - text, - g_utf8_offset_to_pointer (text, anchor_pos) - text); -} - -static void -clutter_text_input_focus_delete_surrounding (ClutterInputFocus *focus, - int offset, - guint len) -{ - ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text; - int cursor; - int start; - - cursor = clutter_text_get_cursor_position (clutter_text); - start = cursor + offset; - if (start < 0) - { - g_warning ("The offset '%d' of deleting surrounding is larger than the cursor pos '%d'", - offset, cursor); - return; - } - if (clutter_text_get_editable (clutter_text)) - clutter_text_delete_text (clutter_text, start, len); -} - -static void -clutter_text_input_focus_commit_text (ClutterInputFocus *focus, - const gchar *text) -{ - ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text; - - if (clutter_text_get_editable (clutter_text)) - { - clutter_text_delete_selection (clutter_text); - clutter_text_insert_text (clutter_text, text, - clutter_text_get_cursor_position (clutter_text)); - clutter_text_set_preedit_string (clutter_text, NULL, NULL, 0); - } -} - -static void -clutter_text_input_focus_set_preedit_text (ClutterInputFocus *focus, - const gchar *preedit_text, - guint cursor_pos) -{ - ClutterText *clutter_text = CLUTTER_TEXT_INPUT_FOCUS (focus)->text; - - if (clutter_text_get_editable (clutter_text)) - { - PangoAttrList *list; - - list = pango_attr_list_new (); - pango_attr_list_insert (list, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE)); - clutter_text_set_preedit_string (clutter_text, - preedit_text, list, - cursor_pos); - pango_attr_list_unref (list); - } -} - -static void -clutter_text_input_focus_class_init (ClutterTextInputFocusClass *klass) -{ - ClutterInputFocusClass *focus_class = CLUTTER_INPUT_FOCUS_CLASS (klass); - - focus_class->request_surrounding = clutter_text_input_focus_request_surrounding; - focus_class->delete_surrounding = clutter_text_input_focus_delete_surrounding; - focus_class->commit_text = clutter_text_input_focus_commit_text; - focus_class->set_preedit_text = clutter_text_input_focus_set_preedit_text; -} - -static void -clutter_text_input_focus_init (ClutterTextInputFocus *focus) -{ -} - -static ClutterInputFocus * -clutter_text_input_focus_new (ClutterText *text) -{ - ClutterTextInputFocus *focus; - - focus = g_object_new (CLUTTER_TYPE_TEXT_INPUT_FOCUS, NULL); - focus->text = text; - - return CLUTTER_INPUT_FOCUS (focus); -} - -/* ClutterText */ -static void clutter_scriptable_iface_init (ClutterScriptableIface *iface); -static void clutter_animatable_iface_init (ClutterAnimatableInterface *iface); - -G_DEFINE_TYPE_WITH_CODE (ClutterText, - clutter_text, - CLUTTER_TYPE_ACTOR, - G_ADD_PRIVATE (ClutterText) - G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE, - clutter_scriptable_iface_init) - G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_ANIMATABLE, - clutter_animatable_iface_init)); - -static inline 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 inline 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); -} - -static gboolean -clutter_text_should_draw_cursor (ClutterText *self) -{ - ClutterTextPrivate *priv = self->priv; - - return (priv->editable || priv->selectable) && - priv->cursor_visible && - priv->has_focus; -} - -#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 gboolean -clutter_text_is_empty (ClutterText *self) -{ - if (self->priv->buffer == NULL) - return TRUE; - - if (clutter_text_buffer_get_length (self->priv->buffer) == 0) - return TRUE; - - return FALSE; -} - -static gchar * -clutter_text_get_display_text (ClutterText *self) -{ - ClutterTextPrivate *priv = self->priv; - ClutterTextBuffer *buffer; - const gchar *text; - - /* short-circuit the case where the buffer is unset or it's empty, - * to avoid creating a pointless TextBuffer and emitting - * notifications with it - */ - if (clutter_text_is_empty (self)) - return g_strdup (""); - - buffer = get_buffer (self); - text = clutter_text_buffer_get_text (buffer); - - /* simple short-circuit to avoid going through GString - * with an empty text and a password char set - */ - if (text[0] == '\0') - return g_strdup (""); - - if (G_LIKELY (priv->password_char == 0)) - return g_strdup (text); - else - { - GString *str; - gunichar invisible_char; - gchar buf[7]; - gint char_len, i; - guint n_chars; - - n_chars = clutter_text_buffer_get_length (buffer); - str = g_string_sized_new (clutter_text_buffer_get_bytes (buffer)); - 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); - - if (priv->show_password_hint && priv->password_hint_visible) - { - char *last_char; - - for (i = 0; i < n_chars - 1; i++) - g_string_append_len (str, buf, char_len); - - last_char = g_utf8_offset_to_pointer (text, n_chars - 1); - g_string_append (str, last_char); - } - else - { - for (i = 0; i < n_chars; i++) - g_string_append_len (str, buf, char_len); - } - - return g_string_free (str, FALSE); - } -} - -static void -ensure_effective_pango_scale_attribute (ClutterText *self) -{ - float resource_scale; - ClutterTextPrivate *priv = self->priv; - - resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (self)); - - if (priv->effective_attrs != NULL) - { - PangoAttrIterator *iter; - PangoAttribute *scale_attrib; - PangoAttrList *old_attributes; - - old_attributes = priv->effective_attrs; - priv->effective_attrs = pango_attr_list_copy (priv->effective_attrs); - pango_attr_list_unref (old_attributes); - - iter = pango_attr_list_get_iterator (priv->effective_attrs); - scale_attrib = pango_attr_iterator_get (iter, PANGO_ATTR_SCALE); - - if (scale_attrib != NULL) - resource_scale *= ((PangoAttrFloat *) scale_attrib)->value; - - pango_attr_iterator_destroy (iter); - } - else - priv->effective_attrs = pango_attr_list_new (); - - pango_attr_list_change (priv->effective_attrs, - pango_attr_scale_new (resource_scale)); -} - -static void -set_effective_pango_attributes (ClutterText *self, - PangoAttrList *attributes) -{ - ClutterTextPrivate *priv = self->priv; - - if (attributes != NULL) - { - PangoAttrList *old_attributes = priv->effective_attrs; - priv->effective_attrs = pango_attr_list_ref (attributes); - - if (old_attributes != NULL) - pango_attr_list_unref (old_attributes); - } - else - { - g_clear_pointer (&priv->effective_attrs, pango_attr_list_unref); - } - - ensure_effective_pango_scale_attribute (self); -} - -static inline 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) - return; - - /* Same as if we don't have any attribute at all. - * We also ignore markup attributes for editable. */ - if (priv->attrs == NULL && (priv->editable || priv->markup_attrs == NULL)) - { - set_effective_pango_attributes (self, NULL); - return; - } - - if (priv->attrs != NULL) - { - /* If there are no markup attributes, or if this is editable (in which - * case we ignore markup), then we can just use these attrs directly */ - if (priv->editable || priv->markup_attrs == NULL) - set_effective_pango_attributes (self, priv->attrs); - else - { - /* Otherwise we need to merge the two lists */ - PangoAttrList *effective_attrs; - PangoAttrIterator *iter; - GSList *attributes, *l; - - 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 (effective_attrs, attr); - } - - g_slist_free (attributes); - } - while (pango_attr_iterator_next (iter)); - - pango_attr_iterator_destroy (iter); - - set_effective_pango_attributes (self, effective_attrs); - pango_attr_list_unref (effective_attrs); - } - } - else if (priv->markup_attrs != NULL) - { - set_effective_pango_attributes (self, 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; - - 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 - { - PangoDirection pango_dir; - - if (priv->password_char != 0) - pango_dir = PANGO_DIRECTION_NEUTRAL; - else - pango_dir = _clutter_pango_find_base_dir (contents, contents_len); - - if (pango_dir == PANGO_DIRECTION_NEUTRAL) - { - ClutterBackend *backend = clutter_get_default_backend (); - ClutterTextDirection text_dir; - - if (clutter_actor_has_key_focus (CLUTTER_ACTOR (text))) - { - ClutterSeat *seat; - ClutterKeymap *keymap; - - seat = clutter_backend_get_default_seat (backend); - keymap = clutter_seat_get_keymap (seat); - pango_dir = clutter_keymap_get_direction (keymap); - } - else - { - text_dir = clutter_actor_get_text_direction (CLUTTER_ACTOR (text)); - - if (text_dir == CLUTTER_TEXT_DIRECTION_RTL) - pango_dir = PANGO_DIRECTION_RTL; - else - pango_dir = PANGO_DIRECTION_LTR; - } - } - - pango_context_set_base_dir (clutter_actor_get_pango_context (CLUTTER_ACTOR (text)), pango_dir); - - priv->resolved_direction = pango_dir; - - pango_layout_set_text (layout, contents, contents_len); - } - - /* 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); - - 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 #PangoFontDescription is copied. - * - * 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, - gboolean is_default_font) -{ - ClutterTextPrivate *priv = self->priv; - - priv->is_default_font = is_default_font; - - if (priv->font_desc == desc || - pango_font_description_equal (priv->font_desc, desc)) - return; - - if (priv->font_desc != NULL) - pango_font_description_free (priv->font_desc); - - priv->font_desc = pango_font_description_copy (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 (clutter_text_buffer_get_length (get_buffer (self)) != 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_settings_changed_cb (ClutterText *text) -{ - ClutterTextPrivate *priv = text->priv; - guint password_hint_time = 0; - ClutterSettings *settings; - - settings = clutter_settings_get_default (); - - g_object_get (settings, "password-hint-time", &password_hint_time, NULL); - - priv->show_password_hint = password_hint_time > 0; - priv->password_hint_timeout = password_hint_time; - - if (priv->is_default_font) - { - PangoFontDescription *font_desc; - gchar *font_name = NULL; - - 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, TRUE); - - pango_font_description_free (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; - - /* 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 = pixels_to_pango (allocation_width); - } - - /* 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 = pixels_to_pango (allocation_height); - } - - /* 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); - - 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); - - 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); - - /* 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 PangoLayout * -create_text_layout_with_scale (ClutterText *text, - gfloat allocation_width, - gfloat allocation_height, - gfloat scale) -{ - if (allocation_width > 0) - allocation_width = roundf (allocation_width * scale); - - if (allocation_height > 0) - allocation_height = roundf (allocation_height * scale); - - return clutter_text_create_layout (text, allocation_width, allocation_height); -} - -static PangoLayout * -maybe_create_text_layout_with_resource_scale (ClutterText *text, - gfloat allocation_width, - gfloat allocation_height) -{ - float resource_scale; - - resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (text)); - - return create_text_layout_with_scale (text, - allocation_width, - allocation_height, - resource_scale); -} - -/** - * clutter_text_coords_to_position: - * @self: a #ClutterText - * @x: the X coordinate, relative to the actor - * @y: the Y coordinate, relative to the actor - * - * Retrieves the position of the character at the given coordinates. - * - * Return: the position of the character - * - * Since: 1.10 - */ -gint -clutter_text_coords_to_position (ClutterText *self, - gfloat x, - gfloat y) -{ - gint index_; - gint px, py; - gint trailing; - gfloat resource_scale; - - g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0); - - resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (self)); - - /* Take any offset due to scrolling into account, and normalize - * the coordinates to PangoScale units - */ - px = logical_pixels_to_pango (x - self->priv->text_logical_x, resource_scale); - py = logical_pixels_to_pango (y - self->priv->text_logical_y, resource_scale); - - pango_layout_xy_to_index (clutter_text_get_layout (self), - px, py, - &index_, &trailing); - - return index_ + trailing; -} - -static gboolean -clutter_text_position_to_coords_internal (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_; - gsize n_bytes; - - g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE); - - priv = self->priv; - - n_chars = clutter_text_buffer_get_length (get_buffer (self)); - if (priv->preedit_set) - n_chars += priv->preedit_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) - { - n_bytes = clutter_text_buffer_get_bytes (get_buffer (self)); - if (priv->editable && priv->preedit_set) - index_ = n_bytes + strlen (priv->preedit_str); - else - index_ = 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 = pango_to_pixels (rect.x); - - /* Take any offset due to scrolling into account */ - if (priv->single_line_mode) - *x += priv->text_x; - } - - if (y) - *y = pango_to_pixels (rect.y); - - if (line_height) - *line_height = pango_to_pixels (rect.height); - - return TRUE; -} - -/** - * 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) -{ - gfloat resource_scale; - gboolean ret; - - g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE); - - resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (self)); - - ret = clutter_text_position_to_coords_internal (self, position, - x, y, line_height); - - if (x) - *x /= resource_scale; - - if (y) - *y /= resource_scale; - - if (line_height) - *line_height /= resource_scale; - - return ret; -} - -static inline void -update_cursor_location (ClutterText *self) -{ - ClutterTextPrivate *priv = self->priv; - graphene_rect_t rect; - float x, y; - - if (!priv->editable) - return; - - clutter_text_get_cursor_rect (self, &rect); - clutter_actor_get_transformed_position (CLUTTER_ACTOR (self), &x, &y); - graphene_rect_offset (&rect, x, y); - clutter_input_focus_set_cursor_location (priv->input_focus, &rect); -} - -static inline void -clutter_text_ensure_cursor_position (ClutterText *self, - float scale) -{ - ClutterTextPrivate *priv = self->priv; - gfloat x, y, cursor_height; - graphene_rect_t cursor_rect = GRAPHENE_RECT_INIT_ZERO; - gint position; - - position = priv->position; - - if (priv->editable && priv->preedit_set) - { - if (position == -1) - position = clutter_text_buffer_get_length (get_buffer (self)); - - 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_internal (self, position, - &x, &y, - &cursor_height); - - graphene_rect_init (&cursor_rect, - x, - y + CURSOR_Y_PADDING * scale, - priv->cursor_size * scale, - cursor_height - 2 * CURSOR_Y_PADDING * scale); - - if (!graphene_rect_equal (&priv->cursor_rect, &cursor_rect)) - { - priv->cursor_rect = cursor_rect; - - g_signal_emit (self, text_signals[CURSOR_EVENT], 0, &cursor_rect); - g_signal_emit (self, text_signals[CURSOR_CHANGED], 0); - - update_cursor_location (self); - } -} - -/** - * 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; - guint n_chars; - - g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE); - - priv = self->priv; - - n_chars = clutter_text_buffer_get_length (get_buffer (self)); - if (n_chars == 0) - return TRUE; - - start_index = priv->position == -1 ? n_chars : priv->position; - end_index = priv->selection_bound == -1 ? n_chars : 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) - { - /* XXX:2.0 - remove */ - g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_POSITION]); - g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_POSITION]); - g_signal_emit (self, text_signals[CURSOR_CHANGED], 0); - } - - 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_markup_internal (ClutterText *self, - const gchar *str) -{ - ClutterTextPrivate *priv = self->priv; - GError *error; - gchar *text = NULL; - PangoAttrList *attrs = NULL; - gboolean res; - - g_assert (str != NULL); - - error = NULL; - res = pango_parse_markup (str, -1, 0, - &attrs, - &text, - NULL, - &error); - - if (!res) - { - if (G_LIKELY (error != NULL)) - { - g_warning ("Failed to set the markup of the actor '%s': %s", - _clutter_actor_get_debug_name (CLUTTER_ACTOR (self)), - error->message); - g_error_free (error); - } - else - g_warning ("Failed to set the markup of the actor '%s'", - _clutter_actor_get_debug_name (CLUTTER_ACTOR (self))); - - return; - } - - if (text) - { - clutter_text_buffer_set_text (get_buffer (self), text, -1); - 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_BUFFER: - clutter_text_set_buffer (self, g_value_get_object (value)); - break; - - case PROP_TEXT: - { - const char *str = g_value_get_string (value); - if (self->priv->use_markup) - clutter_text_set_markup_internal (self, str ? str : ""); - else - clutter_text_buffer_set_text (get_buffer (self), str ? str : "", -1); - } - 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: /* XXX:2.0: remove */ - case PROP_CURSOR_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) -{ - ClutterText *self = CLUTTER_TEXT (gobject); - ClutterTextPrivate *priv = self->priv; - - switch (prop_id) - { - case PROP_BUFFER: - g_value_set_object (value, clutter_text_get_buffer (self)); - break; - - case PROP_TEXT: - g_value_set_string (value, clutter_text_buffer_get_text (get_buffer (self))); - 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: /* XXX:2.0 - remove */ - case PROP_CURSOR_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, clutter_text_buffer_get_max_length (get_buffer (self))); - 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); - - g_clear_signal_handler (&priv->direction_changed_id, self); - g_clear_signal_handler (&priv->settings_changed_id, - clutter_get_default_backend ()); - - g_clear_handle_id (&priv->password_hint_id, g_source_remove); - - clutter_text_set_buffer (self, NULL); - - 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); - - clutter_text_set_buffer (self, NULL); - g_free (priv->font_name); - - g_clear_object (&priv->input_focus); - - 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, - float scale, - 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_internal (self, - bytes_to_offset (utf8, index_), - NULL, &y, &height); - - box.y1 = y; - box.y2 = y + height; - - for (i = 0; i < n_ranges; i++) - { - gfloat range_x; - gfloat range_width; - - range_x = pango_to_pixels (ranges[i * 2]); - - /* Account for any scrolling in single line mode */ - if (priv->single_line_mode) - range_x += priv->text_x; - - - range_width = pango_to_pixels (ranges[i * 2 + 1] - ranges[i * 2]); - box.x1 = range_x; - box.x2 = ceilf (range_x + range_width); - - clutter_actor_box_scale (&box, scale); - - func (self, &box, user_data); - } - - g_free (ranges); - } - - g_free (utf8); -} - -static void -clutter_text_foreach_selection_rectangle_prescaled (ClutterText *self, - ClutterTextSelectionFunc func, - gpointer user_data) -{ - clutter_text_foreach_selection_rectangle (self, 1.0f, func, user_data); -} - -static void -paint_selection_rectangle (ClutterText *self, - const ClutterActorBox *box, - gpointer user_data) -{ - CoglFramebuffer *fb = user_data; - ClutterTextPrivate *priv = self->priv; - ClutterActor *actor = CLUTTER_ACTOR (self); - guint8 paint_opacity = clutter_actor_get_paint_opacity (actor); - CoglPipeline *color_pipeline = cogl_pipeline_copy (default_color_pipeline); - PangoLayout *layout = clutter_text_get_layout (self); - CoglColor cogl_color = { 0, }; - const ClutterColor *color; - - /* 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_color_init_from_4ub (&cogl_color, - color->red, - color->green, - color->blue, - paint_opacity * color->alpha / 255); - cogl_color_premultiply (&cogl_color); - cogl_pipeline_set_color (color_pipeline, &cogl_color); - - cogl_framebuffer_push_rectangle_clip (fb, - box->x1, box->y1, - box->x2, box->y2); - cogl_framebuffer_draw_rectangle (fb, color_pipeline, - box->x1, box->y1, - box->x2, box->y2); - - if (priv->selected_text_color_set) - color = &priv->selected_text_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_show_layout (fb, layout, priv->text_x, 0, &cogl_color); - - cogl_framebuffer_pop_clip (fb); - cogl_object_unref (color_pipeline); -} - -/* Draws the selected text, its background, and the cursor */ -static void -selection_paint (ClutterText *self, - CoglFramebuffer *fb) -{ - ClutterTextPrivate *priv = self->priv; - ClutterActor *actor = CLUTTER_ACTOR (self); - guint8 paint_opacity = clutter_actor_get_paint_opacity (actor); - const ClutterColor *color; - - if (!clutter_text_should_draw_cursor (self)) - return; - - if (priv->position == priv->selection_bound) - { - CoglPipeline *color_pipeline = cogl_pipeline_copy (default_color_pipeline); - CoglColor cogl_color; - - /* No selection, just draw the cursor */ - 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_color_premultiply (&cogl_color); - cogl_pipeline_set_color (color_pipeline, &cogl_color); - - cogl_framebuffer_draw_rectangle (fb, - color_pipeline, - priv->cursor_rect.origin.x, - priv->cursor_rect.origin.y, - priv->cursor_rect.origin.x + priv->cursor_rect.size.width, - priv->cursor_rect.origin.y + priv->cursor_rect.size.height); - } - else - { - clutter_text_foreach_selection_rectangle_prescaled (self, - paint_selection_rectangle, - fb); - } -} - -static gint -clutter_text_move_word_backward (ClutterText *self, - gint start) -{ - gint retval = start; - - if (clutter_text_buffer_get_length (get_buffer (self)) > 0 && 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) -{ - gint retval = start; - guint n_chars; - - n_chars = clutter_text_buffer_get_length (get_buffer (self)); - if (n_chars > 0 && start < 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 < 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) -{ - PangoLayoutLine *layout_line; - PangoLayout *layout; - gint line_no; - gint index_; - gint position; - const gchar *text; - - layout = clutter_text_get_layout (self); - text = clutter_text_buffer_get_text (get_buffer (self)); - - if (start == 0) - index_ = 0; - else - index_ = offset_to_bytes (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 (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; - const gchar *text; - - layout = clutter_text_get_layout (self); - text = clutter_text_buffer_get_text (get_buffer (self)); - - if (start == 0) - index_ = 0; - else - index_ = offset_to_bytes (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 (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_press (ClutterActor *actor, - ClutterEvent *event) -{ - ClutterText *self = CLUTTER_TEXT (actor); - ClutterTextPrivate *priv = self->priv; - ClutterEventType type = clutter_event_type (event); - gboolean res = FALSE; - gfloat x, y; - gint index_; - - /* if a ClutterText is just used for display purposes, then we - * should ignore the events we receive - */ - if (!(priv->editable || priv->selectable)) - return CLUTTER_EVENT_PROPAGATE; - - clutter_actor_grab_key_focus (actor); - clutter_input_focus_set_input_panel_state (priv->input_focus, - CLUTTER_INPUT_PANEL_STATE_TOGGLE); - - /* 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 (clutter_text_buffer_get_length (get_buffer (self)) == 0) - { - clutter_text_set_positions (self, -1, -1); - - return CLUTTER_EVENT_STOP; - } - - clutter_event_get_coords (event, &x, &y); - - res = clutter_actor_transform_stage_point (actor, x, y, &x, &y); - if (res) - { - const char *text; - int offset; - - index_ = clutter_text_coords_to_position (self, x, y); - text = clutter_text_buffer_get_text (get_buffer (self)); - offset = bytes_to_offset (text, index_); - - /* what we select depends on the number of button clicks we - * receive, and whether we are selectable: - * - * 1: just position the cursor and the selection - * 2: select the current word - * 3: select the contents of the whole actor - */ - if (type == CLUTTER_BUTTON_PRESS) - { - gint click_count = clutter_event_get_click_count (event); - - if (click_count == 1) - { - clutter_text_set_positions (self, offset, offset); - } - else if (priv->selectable && click_count == 2) - { - clutter_text_select_word (self); - } - else if (priv->selectable && click_count == 3) - { - clutter_text_select_line (self); - } - } - else - { - /* touch events do not have click count */ - clutter_text_set_positions (self, offset, offset); - } - } - - /* we don't need to go any further if we're not selectable */ - if (!priv->selectable) - return CLUTTER_EVENT_STOP; - - /* grab the pointer */ - priv->in_select_drag = TRUE; - - if (type == CLUTTER_BUTTON_PRESS) - { - clutter_input_device_grab (clutter_event_get_device (event), - actor); - } - else - { - clutter_input_device_sequence_grab (clutter_event_get_device (event), - clutter_event_get_event_sequence (event), - actor); - priv->in_select_touch = TRUE; - } - - return CLUTTER_EVENT_STOP; -} - -static gboolean -clutter_text_move (ClutterActor *actor, - ClutterEvent *event) -{ - ClutterText *self = CLUTTER_TEXT (actor); - ClutterTextPrivate *priv = self->priv; - gfloat x, y; - gint index_, offset; - gboolean res; - const gchar *text; - - if (!priv->in_select_drag) - return CLUTTER_EVENT_PROPAGATE; - - clutter_event_get_coords (event, &x, &y); - - res = clutter_actor_transform_stage_point (actor, x, y, &x, &y); - if (!res) - return CLUTTER_EVENT_PROPAGATE; - - index_ = clutter_text_coords_to_position (self, x, y); - text = clutter_text_buffer_get_text (get_buffer (self)); - offset = bytes_to_offset (text, index_); - - if (priv->selectable) - clutter_text_set_cursor_position (self, offset); - else - clutter_text_set_positions (self, offset, offset); - - return CLUTTER_EVENT_STOP; -} - -static gboolean -clutter_text_release (ClutterActor *actor, - ClutterEvent *event) -{ - ClutterText *self = CLUTTER_TEXT (actor); - ClutterTextPrivate *priv = self->priv; - ClutterEventType type = clutter_event_type (event); - - if (priv->in_select_drag) - { - if (type == CLUTTER_BUTTON_RELEASE) - { - if (!priv->in_select_touch) - { - clutter_input_device_ungrab (clutter_event_get_device (event)); - priv->in_select_drag = FALSE; - - return CLUTTER_EVENT_STOP; - } - } - else - { - if (priv->in_select_touch) - { - ClutterInputDevice *device = clutter_event_get_device (event); - ClutterEventSequence *sequence = - clutter_event_get_event_sequence (event); - - clutter_input_device_sequence_ungrab (device, sequence); - priv->in_select_touch = FALSE; - priv->in_select_drag = FALSE; - - return CLUTTER_EVENT_STOP; - } - } - } - - return CLUTTER_EVENT_PROPAGATE; -} - -static gboolean -clutter_text_button_press (ClutterActor *actor, - ClutterButtonEvent *event) -{ - return clutter_text_press (actor, (ClutterEvent *) event); -} - -static gboolean -clutter_text_motion (ClutterActor *actor, - ClutterMotionEvent *event) -{ - return clutter_text_move (actor, (ClutterEvent *) event); -} - -static gboolean -clutter_text_button_release (ClutterActor *actor, - ClutterButtonEvent *event) -{ - return clutter_text_release (actor, (ClutterEvent *) event); -} - -static gboolean -clutter_text_touch_event (ClutterActor *actor, - ClutterTouchEvent *event) -{ - switch (event->type) - { - case CLUTTER_TOUCH_BEGIN: - return clutter_text_press (actor, (ClutterEvent *) event); - - case CLUTTER_TOUCH_END: - case CLUTTER_TOUCH_CANCEL: - /* TODO: the cancel case probably need a special handler */ - return clutter_text_release (actor, (ClutterEvent *) event); - - case CLUTTER_TOUCH_UPDATE: - return clutter_text_move (actor, (ClutterEvent *) event); - - default: - break; - } - - return CLUTTER_EVENT_PROPAGATE; -} - -static gboolean -clutter_text_remove_password_hint (gpointer data) -{ - ClutterText *self = data; - - self->priv->password_hint_visible = FALSE; - self->priv->password_hint_id = 0; - - clutter_text_dirty_cache (data); - clutter_text_queue_redraw (data); - - return G_SOURCE_REMOVE; -} - -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 CLUTTER_EVENT_PROPAGATE; - - /* 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); - - if (!(event->flags & CLUTTER_EVENT_FLAG_INPUT_METHOD) && - clutter_input_focus_is_focused (priv->input_focus) && - clutter_input_focus_filter_event (priv->input_focus, - (ClutterEvent *) event)) - return CLUTTER_EVENT_STOP; - - /* we allow passing synthetic events that only contain - * the Unicode value and not the key symbol, unless they - * contain the input method flag. - */ - if (event->keyval == 0 && (event->flags & CLUTTER_EVENT_FLAG_SYNTHETIC) && - !(event->flags & CLUTTER_EVENT_FLAG_INPUT_METHOD)) - 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 CLUTTER_EVENT_STOP; - else if ((event->modifier_state & CLUTTER_CONTROL_MASK) == 0) - { - gunichar key_unichar; - - /* Skip keys when control is pressed */ - 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' && !priv->single_line_mode) || - (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); - - if (priv->show_password_hint) - { - g_clear_handle_id (&priv->password_hint_id, g_source_remove); - - priv->password_hint_visible = TRUE; - priv->password_hint_id = - clutter_threads_add_timeout (priv->password_hint_timeout, - clutter_text_remove_password_hint, - self); - } - - return CLUTTER_EVENT_STOP; - } - } - - return CLUTTER_EVENT_PROPAGATE; -} - -static gboolean -clutter_text_key_release (ClutterActor *actor, - ClutterKeyEvent *event) -{ - ClutterText *self = CLUTTER_TEXT (actor); - ClutterTextPrivate *priv = self->priv; - - if (clutter_input_focus_is_focused (priv->input_focus) && - clutter_input_focus_filter_event (priv->input_focus, - (ClutterEvent *) event)) - return CLUTTER_EVENT_STOP; - - return CLUTTER_EVENT_PROPAGATE; -} - -static void -clutter_text_compute_layout_offsets (ClutterText *self, - PangoLayout *layout, - const ClutterActorBox *alloc, - int *text_x, - int *text_y) -{ - ClutterActor *actor = CLUTTER_ACTOR (self); - ClutterActorAlign x_align, y_align; - PangoRectangle logical_rect; - float alloc_width, alloc_height; - float x, y; - - clutter_actor_box_get_size (alloc, &alloc_width, &alloc_height); - pango_layout_get_pixel_extents (layout, NULL, &logical_rect); - - if (clutter_actor_needs_expand (actor, CLUTTER_ORIENTATION_HORIZONTAL)) - x_align = _clutter_actor_get_effective_x_align (actor); - else - x_align = CLUTTER_ACTOR_ALIGN_FILL; - - if (clutter_actor_needs_expand (actor, CLUTTER_ORIENTATION_VERTICAL)) - y_align = clutter_actor_get_y_align (actor); - else - y_align = CLUTTER_ACTOR_ALIGN_FILL; - - x = 0.f; - switch (x_align) - { - case CLUTTER_ACTOR_ALIGN_FILL: - case CLUTTER_ACTOR_ALIGN_START: - break; - - case CLUTTER_ACTOR_ALIGN_END: - if (alloc_width > logical_rect.width) - x = alloc_width - logical_rect.width; - break; - - case CLUTTER_ACTOR_ALIGN_CENTER: - if (alloc_width > logical_rect.width) - x = (alloc_width - logical_rect.width) / 2.f; - break; - } - - y = 0.f; - switch (y_align) - { - case CLUTTER_ACTOR_ALIGN_FILL: - case CLUTTER_ACTOR_ALIGN_START: - break; - - case CLUTTER_ACTOR_ALIGN_END: - if (alloc_height > logical_rect.height) - y = alloc_height - logical_rect.height; - break; - - case CLUTTER_ACTOR_ALIGN_CENTER: - if (alloc_height > logical_rect.height) - y = (alloc_height - logical_rect.height) / 2.f; - break; - } - - if (text_x != NULL) - *text_x = floorf (x); - - if (text_y != NULL) - *text_y = floorf (y); -} - -#define TEXT_PADDING 2 - -static void -clutter_text_paint (ClutterActor *self, - ClutterPaintContext *paint_context) -{ - ClutterText *text = CLUTTER_TEXT (self); - ClutterTextPrivate *priv = text->priv; - CoglFramebuffer *fb; - PangoLayout *layout; - ClutterActorBox alloc = { 0, }; - CoglColor color = { 0, }; - guint8 real_opacity; - gint text_x = priv->text_x; - gint text_y = priv->text_y; - gboolean clip_set = FALSE; - gboolean bg_color_set = FALSE; - guint n_chars; - float alloc_width; - float alloc_height; - float resource_scale; - - fb = clutter_paint_context_get_framebuffer (paint_context); - - /* 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 */ - n_chars = clutter_text_buffer_get_length (get_buffer (text)); - - clutter_actor_get_allocation_box (self, &alloc); - - if (G_UNLIKELY (default_color_pipeline == NULL)) - { - CoglContext *ctx = - clutter_backend_get_cogl_context (clutter_get_default_backend ()); - default_color_pipeline = cogl_pipeline_new (ctx); - } - - g_assert (default_color_pipeline != NULL); - - g_object_get (self, "background-color-set", &bg_color_set, NULL); - if (bg_color_set) - { - CoglPipeline *color_pipeline = cogl_pipeline_copy (default_color_pipeline); - ClutterColor bg_color; - - clutter_actor_get_background_color (self, &bg_color); - bg_color.alpha = clutter_actor_get_paint_opacity (self) - * bg_color.alpha - / 255; - - cogl_color_init_from_4ub (&color, - bg_color.red, - bg_color.green, - bg_color.blue, - bg_color.alpha); - cogl_color_premultiply (&color); - cogl_pipeline_set_color (color_pipeline, &color); - - cogl_framebuffer_draw_rectangle (fb, - color_pipeline, - 0, 0, - clutter_actor_box_get_width (&alloc), - clutter_actor_box_get_height (&alloc)); - - cogl_object_unref (color_pipeline); - } - - /* don't bother painting an empty text actor, unless it's - * editable, in which case we want to paint at least the - * cursor - */ - if (n_chars == 0 && - !clutter_text_should_draw_cursor (text)) - return; - - resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (self)); - - clutter_actor_box_scale (&alloc, resource_scale); - clutter_actor_box_get_size (&alloc, &alloc_width, &alloc_height); - - if (priv->editable && priv->single_line_mode) - layout = clutter_text_create_layout (text, -1, -1); - else - { - /* the only time when we create the PangoLayout using the full - * width and height of the allocation is when we can both wrap - * and ellipsize - */ - if (priv->wrap && priv->ellipsize) - { - layout = clutter_text_create_layout (text, alloc_width, alloc_height); - } - else - { - /* if we're not wrapping we cannot set the height of the - * layout, otherwise Pango will happily wrap the text to - * fit in the rectangle - thus making the :wrap property - * useless - * - * see bug: - * - * http://bugzilla.clutter-project.org/show_bug.cgi?id=2339 - * - * in order to fix this, we create a layout that would fit - * in the assigned width, then we clip the actor if the - * logical rectangle overflows the allocation. - */ - layout = clutter_text_create_layout (text, alloc_width, -1); - } - } - - if (resource_scale != 1.0f) - { - float paint_scale = 1.0f / resource_scale; - cogl_framebuffer_push_matrix (fb); - cogl_framebuffer_scale (fb, paint_scale, paint_scale, 1.0f); - } - - if (clutter_text_should_draw_cursor (text)) - clutter_text_ensure_cursor_position (text, resource_scale); - - if (priv->editable && priv->single_line_mode) - { - PangoRectangle logical_rect = { 0, }; - gint actor_width, text_width; - gboolean rtl; - - pango_layout_get_extents (layout, NULL, &logical_rect); - - cogl_framebuffer_push_rectangle_clip (fb, 0, 0, alloc_width, alloc_height); - clip_set = TRUE; - - actor_width = alloc_width - 2 * TEXT_PADDING; - text_width = pango_to_pixels (logical_rect.width); - - rtl = priv->resolved_direction == PANGO_DIRECTION_RTL; - - if (actor_width < text_width) - { - gint cursor_x = graphene_rect_get_x (&priv->cursor_rect); - - if (priv->position == -1) - { - text_x = rtl ? TEXT_PADDING : actor_width - text_width; - } - else if (priv->position == 0) - { - text_x = rtl ? actor_width - text_width : 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; - } - } - } - else - { - text_x = rtl ? actor_width - text_width : TEXT_PADDING; - } - } - else if (!priv->editable && !(priv->wrap && priv->ellipsize)) - { - PangoRectangle logical_rect = { 0, }; - - pango_layout_get_pixel_extents (layout, NULL, &logical_rect); - - /* don't clip if the layout managed to fit inside our allocation */ - if (logical_rect.width > alloc_width || - logical_rect.height > alloc_height) - { - cogl_framebuffer_push_rectangle_clip (fb, 0, 0, alloc_width, alloc_height); - clip_set = TRUE; - } - - clutter_text_compute_layout_offsets (text, layout, &alloc, &text_x, &text_y); - } - else - clutter_text_compute_layout_offsets (text, layout, &alloc, &text_x, &text_y); - - if (priv->text_x != text_x || - priv->text_y != text_y) - { - priv->text_x = text_x; - priv->text_y = text_y; - priv->text_logical_x = roundf ((float) text_x / resource_scale); - priv->text_logical_y = roundf ((float) text_y / resource_scale); - - clutter_text_ensure_cursor_position (text, resource_scale); - } - - real_opacity = clutter_actor_get_paint_opacity (self) - * priv->text_color.alpha - / 255; - - CLUTTER_NOTE (PAINT, "painting text (text: '%s')", - clutter_text_buffer_get_text (get_buffer (text))); - - cogl_color_init_from_4ub (&color, - priv->text_color.red, - priv->text_color.green, - priv->text_color.blue, - real_opacity); - cogl_pango_show_layout (fb, layout, priv->text_x, priv->text_y, &color); - - selection_paint (text, fb); - - if (resource_scale != 1.0f) - cogl_framebuffer_pop_matrix (fb); - - if (clip_set) - cogl_framebuffer_pop_clip (fb); -} - -static void -add_selection_to_paint_volume (ClutterText *text, - const ClutterActorBox *box, - gpointer user_data) -{ - ClutterPaintVolume *total_volume = user_data; - ClutterPaintVolume rect_volume; - graphene_point3d_t 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, - float resource_scale, - ClutterPaintVolume *volume) -{ - ClutterTextPrivate *priv = text->priv; - graphene_point3d_t origin; - - clutter_text_ensure_cursor_position (text, resource_scale); - - if (priv->position == priv->selection_bound) - { - float width, height; - - width = priv->cursor_rect.size.width / resource_scale; - height = priv->cursor_rect.size.height / resource_scale; - origin.x = priv->cursor_rect.origin.x / resource_scale; - origin.y = priv->cursor_rect.origin.y / resource_scale; - origin.z = 0; - - clutter_paint_volume_set_origin (volume, &origin); - clutter_paint_volume_set_width (volume, width); - clutter_paint_volume_set_height (volume, height); - } - else - { - clutter_text_foreach_selection_rectangle (text, - 1.0f / resource_scale, - add_selection_to_paint_volume, - 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; - graphene_point3d_t origin; - float resource_scale; - - /* 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; - - resource_scale = clutter_actor_get_resource_scale (self); - - _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 = pango_to_logical_pixels (ink_rect.x, resource_scale); - origin.y = pango_to_logical_pixels (ink_rect.y, resource_scale); - origin.z = 0; - clutter_paint_volume_set_origin (&priv->paint_volume, &origin); - clutter_paint_volume_set_width (&priv->paint_volume, - pango_to_logical_pixels (ink_rect.width, - resource_scale)); - clutter_paint_volume_set_height (&priv->paint_volume, - pango_to_logical_pixels (ink_rect.height, - resource_scale)); - - /* If the cursor is visible then that will likely be drawn - outside of the ink rectangle so we should merge that in */ - if (clutter_text_should_draw_cursor (text)) - { - ClutterPaintVolume cursor_paint_volume; - - _clutter_paint_volume_init_static (&cursor_paint_volume, self); - - clutter_text_get_paint_volume_for_cursor (text, resource_scale, - &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; - gfloat resource_scale; - - resource_scale = clutter_actor_get_resource_scale (self); - - 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 - ? pango_to_logical_pixels (logical_width, resource_scale) - : 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; - gfloat resource_scale; - - resource_scale = clutter_actor_get_resource_scale (self); - - if (priv->single_line_mode) - for_width = -1; - - layout = create_text_layout_with_scale (CLUTTER_TEXT (self), - for_width, -1, resource_scale); - - 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 = pango_to_logical_pixels (logical_height, resource_scale); - - 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 = pango_to_logical_pixels (logical_height, - resource_scale); - - *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) -{ - 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 - maybe_create_text_layout_with_resource_scale (text, - box->x2 - box->x1, - box->y2 - box->y1); - - parent_class = CLUTTER_ACTOR_CLASS (clutter_text_parent_class); - parent_class->allocate (self, box); -} - -static gboolean -clutter_text_has_overlaps (ClutterActor *self) -{ - return clutter_text_should_draw_cursor ((ClutterText *) self); -} - -static float -clutter_text_calculate_resource_scale (ClutterActor *actor, - int phase) -{ - ClutterActorClass *parent_class = CLUTTER_ACTOR_CLASS (clutter_text_parent_class); - float new_resource_scale; - - new_resource_scale = parent_class->calculate_resource_scale (actor, phase); - - if (phase == 1) - return MAX (new_resource_scale, clutter_actor_get_real_resource_scale (actor)); - - return new_resource_scale; -} - -static void -clutter_text_resource_scale_changed (ClutterActor *actor) -{ - ClutterText *text = CLUTTER_TEXT (actor); - ClutterTextPrivate *priv = text->priv; - - g_clear_pointer (&priv->effective_attrs, pango_attr_list_unref); - clutter_text_dirty_cache (text); - - clutter_actor_queue_immediate_relayout (actor); -} - -static gboolean -clutter_text_event (ClutterActor *self, - ClutterEvent *event) -{ - ClutterText *text = CLUTTER_TEXT (self); - ClutterTextPrivate *priv = text->priv; - - if (clutter_input_focus_is_focused (priv->input_focus) && - (event->type == CLUTTER_IM_COMMIT || - event->type == CLUTTER_IM_DELETE || - event->type == CLUTTER_IM_PREEDIT)) - { - return clutter_input_focus_filter_event (priv->input_focus, event); - } - - return CLUTTER_EVENT_PROPAGATE; -} - -static void -clutter_text_im_focus (ClutterText *text) -{ - ClutterTextPrivate *priv = text->priv; - ClutterBackend *backend = clutter_get_default_backend (); - ClutterInputMethod *method = clutter_backend_get_input_method (backend); - - if (!method) - return; - - clutter_input_method_focus_in (method, priv->input_focus); - clutter_input_focus_set_content_purpose (priv->input_focus, - priv->input_purpose); - clutter_input_focus_set_content_hints (priv->input_focus, - priv->input_hints); - clutter_input_focus_set_can_show_preedit (priv->input_focus, TRUE); - update_cursor_location (text); -} - -static void -clutter_text_key_focus_in (ClutterActor *actor) -{ - ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv; - - if (priv->editable) - clutter_text_im_focus (CLUTTER_TEXT (actor)); - - priv->has_focus = TRUE; - - clutter_text_queue_redraw (actor); -} - -static void -clutter_text_key_focus_out (ClutterActor *actor) -{ - ClutterTextPrivate *priv = CLUTTER_TEXT (actor)->priv; - ClutterBackend *backend = clutter_get_default_backend (); - ClutterInputMethod *method = clutter_backend_get_input_method (backend); - - priv->has_focus = FALSE; - - if (priv->editable && clutter_input_focus_is_focused (priv->input_focus)) - { - clutter_text_set_preedit_string (CLUTTER_TEXT (actor), NULL, NULL, 0); - clutter_input_method_focus_out (method); - } - - 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 = clutter_text_buffer_get_length (get_buffer (self)); - - 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 = clutter_text_buffer_get_length (get_buffer (self)); - 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; - const gchar *text; - - layout = clutter_text_get_layout (self); - text = clutter_text_buffer_get_text (get_buffer (self)); - - if (priv->position == 0) - index_ = 0; - else - index_ = offset_to_bytes (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 (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; - const gchar *text; - - layout = clutter_text_get_layout (self); - text = clutter_text_buffer_get_text (get_buffer (self)); - - if (priv->position == 0) - index_ = 0; - else - index_ = offset_to_bytes (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 (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) -{ - guint n_chars = clutter_text_buffer_get_length (get_buffer (self)); - clutter_text_set_positions (self, 0, 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 = clutter_text_buffer_get_length (get_buffer (self)); - - 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 = clutter_text_buffer_get_length (get_buffer (self)); - - 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 = clutter_text_buffer_get_length (get_buffer (self)); - - 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 = clutter_text_buffer_get_length (get_buffer (self)); - - 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 gboolean -clutter_text_parse_custom_node (ClutterScriptable *scriptable, - ClutterScript *script, - GValue *value, - const gchar *name, - JsonNode *node) -{ - if (strncmp (name, "font-description", 16) == 0) - { - g_value_init (value, G_TYPE_STRING); - g_value_set_string (value, json_node_get_string (node)); - - return TRUE; - } - - return parent_scriptable_iface->parse_custom_node (scriptable, script, - value, - name, - node); -} - -static void -clutter_text_set_color_internal (ClutterText *self, - GParamSpec *pspec, - const ClutterColor *color) -{ - ClutterTextPrivate *priv = CLUTTER_TEXT (self)->priv; - GParamSpec *other = NULL; - - switch (pspec->param_id) - { - case PROP_COLOR: - priv->text_color = *color; - break; - - case PROP_CURSOR_COLOR: - if (color) - { - priv->cursor_color = *color; - priv->cursor_color_set = TRUE; - } - else - priv->cursor_color_set = FALSE; - - other = obj_props[PROP_CURSOR_COLOR_SET]; - break; - - case PROP_SELECTION_COLOR: - if (color) - { - priv->selection_color = *color; - priv->selection_color_set = TRUE; - } - else - priv->selection_color_set = FALSE; - - other = obj_props[PROP_SELECTION_COLOR_SET]; - break; - - case PROP_SELECTED_TEXT_COLOR: - if (color) - { - priv->selected_text_color = *color; - priv->selected_text_color_set = TRUE; - } - else - priv->selected_text_color_set = FALSE; - - other = obj_props[PROP_SELECTED_TEXT_COLOR_SET]; - break; - - default: - g_assert_not_reached (); - break; - } - - clutter_text_queue_redraw (CLUTTER_ACTOR (self)); - g_object_notify_by_pspec (G_OBJECT (self), pspec); - if (other) - g_object_notify_by_pspec (G_OBJECT (self), other); -} - -static void -clutter_text_set_color_animated (ClutterText *self, - GParamSpec *pspec, - const ClutterColor *color) -{ - ClutterActor *actor = CLUTTER_ACTOR (self); - ClutterTextPrivate *priv = self->priv; - const ClutterAnimationInfo *info; - ClutterTransition *transition; - - info = _clutter_actor_get_animation_info (actor); - transition = clutter_actor_get_transition (actor, pspec->name); - - /* jump to the end if there is no easing state, or if the easing - * state has a duration of 0 msecs - */ - if (info->cur_state == NULL || - info->cur_state->easing_duration == 0) - { - /* ensure that we remove any currently running transition */ - if (transition != NULL) - { - clutter_actor_remove_transition (actor, pspec->name); - transition = NULL; - } - - clutter_text_set_color_internal (self, pspec, color); - - return; - } - - if (transition == NULL) - { - transition = clutter_property_transition_new (pspec->name); - clutter_transition_set_animatable (transition, - CLUTTER_ANIMATABLE (self)); - clutter_transition_set_remove_on_complete (transition, TRUE); - - /* delay only makes sense if the transition has just been created */ - clutter_timeline_set_delay (CLUTTER_TIMELINE (transition), - info->cur_state->easing_delay); - - clutter_actor_add_transition (actor, pspec->name, transition); - - /* the actor now owns the transition */ - g_object_unref (transition); - } - - /* if a transition already exist, update its bounds */ - switch (pspec->param_id) - { - case PROP_COLOR: - clutter_transition_set_from (transition, CLUTTER_TYPE_COLOR, - &priv->text_color); - break; - - case PROP_CURSOR_COLOR: - clutter_transition_set_from (transition, CLUTTER_TYPE_COLOR, - &priv->cursor_color); - break; - - case PROP_SELECTION_COLOR: - clutter_transition_set_from (transition, CLUTTER_TYPE_COLOR, - &priv->selection_color); - break; - - case PROP_SELECTED_TEXT_COLOR: - clutter_transition_set_from (transition, CLUTTER_TYPE_COLOR, - &priv->selected_text_color); - break; - - default: - g_assert_not_reached (); - } - - clutter_transition_set_to (transition, CLUTTER_TYPE_COLOR, color); - - /* always use the current easing state */ - clutter_timeline_set_duration (CLUTTER_TIMELINE (transition), - info->cur_state->easing_duration); - clutter_timeline_set_progress_mode (CLUTTER_TIMELINE (transition), - info->cur_state->easing_mode); - - /* ensure that we start from the beginning */ - clutter_timeline_rewind (CLUTTER_TIMELINE (transition)); - clutter_timeline_start (CLUTTER_TIMELINE (transition)); -} - -static void -clutter_text_set_custom_property (ClutterScriptable *scriptable, - ClutterScript *script, - const gchar *name, - const GValue *value) -{ - if (strncmp (name, "font-description", 16) == 0) - { - g_assert (G_VALUE_HOLDS (value, G_TYPE_STRING)); - if (g_value_get_string (value) != NULL) - clutter_text_set_font_name (CLUTTER_TEXT (scriptable), - g_value_get_string (value)); - } - else - parent_scriptable_iface->set_custom_property (scriptable, script, - name, - value); -} - -static void -clutter_scriptable_iface_init (ClutterScriptableIface *iface) -{ - parent_scriptable_iface = g_type_interface_peek_parent (iface); - - iface->parse_custom_node = clutter_text_parse_custom_node; - iface->set_custom_property = clutter_text_set_custom_property; -} - -static void -clutter_text_set_final_state (ClutterAnimatable *animatable, - const char *property_name, - const GValue *value) -{ - if (strcmp (property_name, "color") == 0) - { - const ClutterColor *color = clutter_value_get_color (value); - clutter_text_set_color_internal (CLUTTER_TEXT (animatable), - obj_props[PROP_COLOR], color); - } - else if (strcmp (property_name, "cursor-color") == 0) - { - const ClutterColor *color = clutter_value_get_color (value); - clutter_text_set_color_internal (CLUTTER_TEXT (animatable), - obj_props[PROP_CURSOR_COLOR], - color); - } - else if (strcmp (property_name, "selected-text-color") == 0) - { - const ClutterColor *color = clutter_value_get_color (value); - clutter_text_set_color_internal (CLUTTER_TEXT (animatable), - obj_props[PROP_SELECTED_TEXT_COLOR], - color); - } - else if (strcmp (property_name, "selection-color") == 0) - { - const ClutterColor *color = clutter_value_get_color (value); - clutter_text_set_color_internal (CLUTTER_TEXT (animatable), - obj_props[PROP_SELECTION_COLOR], - color); - } - else - parent_animatable_iface->set_final_state (animatable, property_name, value); -} - -static void -clutter_animatable_iface_init (ClutterAnimatableInterface *iface) -{ - parent_animatable_iface = g_type_interface_peek_parent (iface); - - iface->set_final_state = clutter_text_set_final_state; -} - -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; - - 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->key_release_event = clutter_text_key_release; - 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->touch_event = clutter_text_touch_event; - 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; - actor_class->calculate_resource_scale = clutter_text_calculate_resource_scale; - actor_class->resource_scale_changed = clutter_text_resource_scale_changed; - actor_class->event = clutter_text_event; - - /** - * ClutterText:buffer: - * - * The buffer which stores the text for this #ClutterText. - * - * If set to %NULL, a default buffer will be created. - * - * Since: 1.8 - */ - pspec = g_param_spec_object ("buffer", - P_("Buffer"), - P_("The buffer for the text"), - CLUTTER_TYPE_TEXT_BUFFER, - CLUTTER_PARAM_READWRITE); - obj_props[PROP_BUFFER] = pspec; - g_object_class_install_property (gobject_class, PROP_BUFFER, pspec); - - /** - * 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 | - CLUTTER_PARAM_ANIMATABLE); - 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"), - FALSE, - 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. - * - * This property depends on the #ClutterActor:reactive property being - * set to %TRUE. - * - * 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. - * - * The cursor will only be visible if this property and either - * the #ClutterText:editable or the #ClutterText:selectable properties - * 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 | - CLUTTER_PARAM_ANIMATABLE); - 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 - * - * Deprecated: 1.12: Use ClutterText:cursor-position instead. - */ - pspec = g_param_spec_int ("position", - P_("Cursor Position"), - P_("The cursor position"), - -1, G_MAXINT, - -1, - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS | - G_PARAM_DEPRECATED); - obj_props[PROP_POSITION] = pspec; - g_object_class_install_property (gobject_class, PROP_POSITION, pspec); - - /** - * ClutterText:cursor-position: - * - * The current input cursor position. -1 is taken to be the end of the text - * - * Since: 1.12 - */ - pspec = g_param_spec_int ("cursor-position", - P_("Cursor Position"), - P_("The cursor position"), - -1, G_MAXINT, - -1, - CLUTTER_PARAM_READWRITE); - obj_props[PROP_CURSOR_POSITION] = pspec; - g_object_class_install_property (gobject_class, PROP_CURSOR_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 | - CLUTTER_PARAM_ANIMATABLE); - 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 information about the Pango markup format, see - * pango_layout_set_markup() in the Pango documentation. - * - * 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. - * - * 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 | - CLUTTER_PARAM_ANIMATABLE); - 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); - - pspec = g_param_spec_flags ("input-hints", - P_("Input hints"), - P_("Input hints"), - CLUTTER_TYPE_INPUT_CONTENT_HINT_FLAGS, - 0, CLUTTER_PARAM_READWRITE); - obj_props[PROP_INPUT_HINTS] = pspec; - g_object_class_install_property (gobject_class, PROP_INPUT_HINTS, pspec); - - pspec = g_param_spec_enum ("input-purpose", - P_("Input purpose"), - P_("Input purpose"), - CLUTTER_TYPE_INPUT_CONTENT_PURPOSE, - 0, CLUTTER_PARAM_READWRITE); - obj_props[PROP_INPUT_PURPOSE] = pspec; - g_object_class_install_property (gobject_class, PROP_INPUT_PURPOSE, 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, NULL, - 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 - * @rect: the coordinates of the cursor - * - * The ::cursor-event signal is emitted whenever the cursor position - * changes inside a #ClutterText actor. Inside @rect it is stored - * the current position and size of the cursor, relative to the actor - * itself. - * - * Since: 1.0 - * - * Deprecated: 1.16: Use the #ClutterText::cursor-changed signal instead - */ - text_signals[CURSOR_EVENT] = - g_signal_new (I_("cursor-event"), - G_TYPE_FROM_CLASS (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_DEPRECATED, - G_STRUCT_OFFSET (ClutterTextClass, cursor_event), - NULL, NULL, NULL, - G_TYPE_NONE, 1, - GRAPHENE_TYPE_RECT | G_SIGNAL_TYPE_STATIC_SCOPE); - - /** - * ClutterText::cursor-changed: - * @self: the #ClutterText that emitted the signal - * - * The ::cursor-changed signal is emitted whenever the cursor - * position or size changes. - * - * Since: 1.16 - */ - text_signals[CURSOR_CHANGED] = - g_signal_new (I_("cursor-changed"), - G_TYPE_FROM_CLASS (gobject_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (ClutterTextClass, cursor_changed), - NULL, NULL, NULL, - G_TYPE_NONE, 0); - - /** - * 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, NULL, - 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, "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, password_hint_time; - - self->priv = priv = clutter_text_get_instance_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->buffer = NULL; - - 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, - "password-hint-time", &password_hint_time, - 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->show_password_hint = password_hint_time > 0; - priv->password_hint_timeout = password_hint_time; - - priv->text_y = 0; - - priv->cursor_size = DEFAULT_CURSOR_SIZE; - - priv->settings_changed_id = - g_signal_connect_swapped (clutter_get_default_backend (), - "settings-changed", - G_CALLBACK (clutter_text_settings_changed_cb), - self); - - priv->direction_changed_id = - g_signal_connect (self, "notify::text-direction", - G_CALLBACK (clutter_text_direction_changed_cb), - NULL); - - priv->input_focus = clutter_text_input_focus_new (self); -} - -/** - * 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: (allow-none): 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); -} - -static ClutterTextBuffer* -get_buffer (ClutterText *self) -{ - ClutterTextPrivate *priv = self->priv; - - if (priv->buffer == NULL) - { - ClutterTextBuffer *buffer; - buffer = clutter_text_buffer_new (); - clutter_text_set_buffer (self, buffer); - g_object_unref (buffer); - } - - return priv->buffer; -} - -/* GtkEntryBuffer signal handlers - */ -static void -buffer_inserted_text (ClutterTextBuffer *buffer, - guint position, - const gchar *chars, - guint n_chars, - ClutterText *self) -{ - ClutterTextPrivate *priv; - gint new_position; - gint new_selection_bound; - - priv = self->priv; - if (priv->position >= 0 || priv->selection_bound >= 0) - { - new_position = priv->position; - new_selection_bound = priv->selection_bound; - - if (position <= new_position) - new_position += n_chars; - if (position <= new_selection_bound) - new_selection_bound += n_chars; - - if (priv->position != new_position || priv->selection_bound != new_selection_bound) - clutter_text_set_positions (self, new_position, new_selection_bound); - } - - /* TODO: What are we supposed to with the out value of position? */ -} - -static void -buffer_deleted_text (ClutterTextBuffer *buffer, - guint position, - guint n_chars, - ClutterText *self) -{ - ClutterTextPrivate *priv; - gint new_position; - gint new_selection_bound; - - priv = self->priv; - if (priv->position >= 0 || priv->selection_bound >= 0) - { - new_position = priv->position; - new_selection_bound = priv->selection_bound; - - if (position < new_position) - new_position -= n_chars; - if (position < new_selection_bound) - new_selection_bound -= n_chars; - - if (priv->position != new_position || priv->selection_bound != new_selection_bound) - clutter_text_set_positions (self, new_position, new_selection_bound); - } -} - -static void -clutter_text_queue_redraw_or_relayout (ClutterText *self) -{ - ClutterActor *actor = CLUTTER_ACTOR (self); - gfloat preferred_width; - gfloat preferred_height; - - clutter_text_dirty_cache (self); - - /* we're using our private implementations here to avoid the caching done by ClutterActor */ - clutter_text_get_preferred_width (actor, -1, NULL, &preferred_width); - clutter_text_get_preferred_height (actor, preferred_width, NULL, &preferred_height); - - if (clutter_actor_has_allocation (actor) && - fabsf (preferred_width - clutter_actor_get_width (actor)) <= 0.001 && - fabsf (preferred_height - clutter_actor_get_height (actor)) <= 0.001) - clutter_text_queue_redraw (actor); - else - clutter_actor_queue_relayout (actor); -} - -static void -buffer_notify_text (ClutterTextBuffer *buffer, - GParamSpec *spec, - ClutterText *self) -{ - g_object_freeze_notify (G_OBJECT (self)); - - clutter_text_queue_redraw_or_relayout (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 void -buffer_notify_max_length (ClutterTextBuffer *buffer, - GParamSpec *spec, - ClutterText *self) -{ - g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_MAX_LENGTH]); -} - -static void -buffer_connect_signals (ClutterText *self) -{ - ClutterTextPrivate *priv = self->priv; - g_signal_connect (priv->buffer, "inserted-text", G_CALLBACK (buffer_inserted_text), self); - g_signal_connect (priv->buffer, "deleted-text", G_CALLBACK (buffer_deleted_text), self); - g_signal_connect (priv->buffer, "notify::text", G_CALLBACK (buffer_notify_text), self); - g_signal_connect (priv->buffer, "notify::max-length", G_CALLBACK (buffer_notify_max_length), self); -} - -static void -buffer_disconnect_signals (ClutterText *self) -{ - ClutterTextPrivate *priv = self->priv; - g_signal_handlers_disconnect_by_func (priv->buffer, buffer_inserted_text, self); - g_signal_handlers_disconnect_by_func (priv->buffer, buffer_deleted_text, self); - g_signal_handlers_disconnect_by_func (priv->buffer, buffer_notify_text, self); - g_signal_handlers_disconnect_by_func (priv->buffer, buffer_notify_max_length, self); -} - -/** - * clutter_text_new_with_buffer: - * @buffer: The buffer to use for the new #ClutterText. - * - * Creates a new entry with the specified text buffer. - * - * Return value: a new #ClutterText - * - * Since: 1.10 - */ -ClutterActor * -clutter_text_new_with_buffer (ClutterTextBuffer *buffer) -{ - g_return_val_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer), NULL); - return g_object_new (CLUTTER_TYPE_TEXT, "buffer", buffer, NULL); -} - -/** - * clutter_text_get_buffer: - * @self: a #ClutterText - * - * Get the #ClutterTextBuffer object which holds the text for - * this widget. - * - * Returns: (transfer none): A #GtkEntryBuffer object. - * - * Since: 1.10 - */ -ClutterTextBuffer* -clutter_text_get_buffer (ClutterText *self) -{ - g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL); - - return get_buffer (self); -} - -/** - * clutter_text_set_buffer: - * @self: a #ClutterText - * @buffer: a #ClutterTextBuffer - * - * Set the #ClutterTextBuffer object which holds the text for - * this widget. - * - * Since: 1.10 - */ -void -clutter_text_set_buffer (ClutterText *self, - ClutterTextBuffer *buffer) -{ - ClutterTextPrivate *priv; - GObject *obj; - - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - priv = self->priv; - - if (buffer) - { - g_return_if_fail (CLUTTER_IS_TEXT_BUFFER (buffer)); - g_object_ref (buffer); - } - - if (priv->buffer) - { - buffer_disconnect_signals (self); - g_object_unref (priv->buffer); - } - - priv->buffer = buffer; - - if (priv->buffer) - buffer_connect_signals (self); - - obj = G_OBJECT (self); - g_object_freeze_notify (obj); - g_object_notify_by_pspec (obj, obj_props[PROP_BUFFER]); - g_object_notify_by_pspec (obj, obj_props[PROP_TEXT]); - g_object_notify_by_pspec (obj, obj_props[PROP_MAX_LENGTH]); - g_object_thaw_notify (obj); -} - -/** - * 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_set_key_focus() - * will receive key events and will update its contents accordingly. - * - * Since: 1.0 - */ -void -clutter_text_set_editable (ClutterText *self, - gboolean editable) -{ - ClutterBackend *backend = clutter_get_default_backend (); - ClutterInputMethod *method = clutter_backend_get_input_method (backend); - ClutterTextPrivate *priv; - - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - priv = self->priv; - - if (priv->editable != editable) - { - priv->editable = editable; - - if (method) - { - if (!priv->editable && clutter_input_focus_is_focused (priv->input_focus)) - clutter_input_method_focus_out (method); - else if (priv->has_focus) - clutter_text_im_focus (self); - } - - 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; - - cursor_visible = !!cursor_visible; - - if (priv->cursor_visible != cursor_visible) - { - priv->cursor_visible = cursor_visible; - - clutter_text_queue_redraw_or_relayout (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: (allow-none): 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) -{ - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - clutter_text_set_color_animated (self, obj_props[PROP_CURSOR_COLOR], color); -} - -/** - * clutter_text_get_cursor_color: - * @self: a #ClutterText - * @color: (out): 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) -{ - guint n_chars; - - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - n_chars = clutter_text_buffer_get_length (get_buffer (self)); - if (end_pos < 0) - end_pos = n_chars; - - start_pos = MIN (n_chars, start_pos); - end_pos = MIN (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; - const gchar *text; - - 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; - } - - text = clutter_text_buffer_get_text (get_buffer (self)); - start_offset = offset_to_bytes (text, start_index); - end_offset = offset_to_bytes (text, end_index); - len = end_offset - start_offset; - - str = g_malloc (len + 1); - g_utf8_strncpy (str, 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 = clutter_text_buffer_get_length (get_buffer (self)); - - 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: (allow-none): 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) -{ - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - clutter_text_set_color_animated (self, obj_props[PROP_SELECTION_COLOR], - color); -} - -/** - * 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: (allow-none): 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) -{ - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - clutter_text_set_color_animated (self, obj_props[PROP_SELECTED_TEXT_COLOR], - color); -} - -/** - * 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) -{ - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - clutter_text_set_font_description_internal (self, font_desc, - font_desc == NULL); -} - -/** - * 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: - * - * |[ - * // Set the font to the system's Sans, 10 points - * clutter_text_set_font_name (text, "Sans 10"); - * - * // Set the font to the system's Serif, 16 pixels - * clutter_text_set_font_name (text, "Serif 16px"); - * - * // Set the font to Helvetica, 10 points - * 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 */ - 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 == NULL) - { - 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, is_default_font); - - g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_FONT_NAME]); - - pango_font_description_free (desc); - -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. - * - * If the #ClutterText actor is empty, this function will return - * an empty string, and not %NULL. - * - * Return value: (transfer none): 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 clutter_text_buffer_get_text (get_buffer (self)); -} - -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: (allow-none): 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)); - - /* if the text is editable (i.e. there is not markup flag to reset) then - * changing the contents will result in selection and cursor changes that - * we should avoid - */ - if (self->priv->editable) - { - if (g_strcmp0 (clutter_text_buffer_get_text (get_buffer (self)), text) == 0) - return; - } - - clutter_text_set_use_markup_internal (self, FALSE); - clutter_text_buffer_set_text (get_buffer (self), text ? text : "", -1); -} - -/** - * clutter_text_set_markup: - * @self: a #ClutterText - * @markup: (allow-none): 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: - * - * |[ - * /* the order is important */ - * 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_buffer_set_text (get_buffer (self), "", 0); -} - -/** - * 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) -{ - PangoLayout *layout; - 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); - layout = maybe_create_text_layout_with_resource_scale (self, width, height); - - if (!layout) - layout = clutter_text_create_layout (self, width, height); - - return layout; -} - -/** - * 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) -{ - g_return_if_fail (CLUTTER_IS_TEXT (self)); - g_return_if_fail (color != NULL); - - clutter_text_set_color_animated (self, obj_props[PROP_COLOR], 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: (allow-none): 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 (pango_attr_list_equal (priv->attrs, attrs)) - return; - - 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_queue_redraw_or_relayout (self); - - g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ATTRIBUTES]); -} - -/** - * 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_queue_redraw_or_relayout (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) -{ - const gchar *text; - - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - text = clutter_text_buffer_get_text (get_buffer (self)); - - clutter_text_set_use_markup_internal (self, setting); - - if (setting) - clutter_text_set_markup_internal (self, text); - - clutter_text_queue_redraw_or_relayout (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_queue_redraw_or_relayout (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 = clutter_text_buffer_get_length (get_buffer (self)); - - 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)); - - /* XXX:2.0 - remove */ - g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_POSITION]); - g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_POSITION]); - g_signal_emit (self, text_signals[CURSOR_CHANGED], 0); -} - -/** - * 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) -{ - g_return_if_fail (CLUTTER_IS_TEXT (self)); - clutter_text_buffer_set_max_length (get_buffer (self), max); -} - -/** - * 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 clutter_text_buffer_get_max_length (get_buffer (self)); -} - -static void -clutter_text_real_insert_text (ClutterText *self, - guint start_pos, - const gchar *chars, - guint n_chars) -{ - gsize n_bytes; - - n_bytes = g_utf8_offset_to_pointer (chars, n_chars) - chars; - - /* - * insert-text is emitted here instead of as part of a - * buffer_inserted_text() callback because that should be emitted - * before the buffer changes, while ClutterTextBuffer::deleted-text - * is emitter after. See BG#722220 for more info. - */ - g_signal_emit (self, text_signals[INSERT_TEXT], 0, chars, - n_bytes, &start_pos); - - /* - * The actual insertion from the buffer. This will end firing the - * following signal handlers: buffer_inserted_text(), - * buffer_notify_text(), buffer_notify_max_length() - */ - clutter_text_buffer_insert_text (get_buffer (self), start_pos, chars, n_chars); -} - -/** - * 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; - - priv = self->priv; - - new = g_string_new (""); - g_string_append_unichar (new, wc); - - clutter_text_real_insert_text (self, priv->position, new->str, 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) -{ - g_return_if_fail (CLUTTER_IS_TEXT (self)); - g_return_if_fail (text != NULL); - - clutter_text_real_insert_text (self, position, text, g_utf8_strlen (text, -1)); -} - -static -void clutter_text_real_delete_text (ClutterText *self, - gssize start_pos, - gssize end_pos) -{ - /* - * delete-text is emitted here instead of as part of a - * buffer_deleted_text() callback because that should be emitted - * before the buffer changes, while ClutterTextBuffer::deleted-text - * is emitter after. See BG#722220 for more info. - */ - g_signal_emit (self, text_signals[DELETE_TEXT], 0, start_pos, end_pos); - - /* - * The actual deletion from the buffer. This will end firing the - * following signal handlers: buffer_deleted_text(), - * buffer_notify_text(), buffer_notify_max_length() - */ - clutter_text_buffer_delete_text (get_buffer (self), start_pos, end_pos - start_pos); -} - - - -/** - * 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) -{ - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - clutter_text_real_delete_text (self, start_pos, end_pos); -} - -/** - * 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. - * - * Somewhat awkwardly, the cursor position is decremented by the same - * number of characters you've deleted. - * - * Since: 1.0 - */ -void -clutter_text_delete_chars (ClutterText *self, - guint n_chars) -{ - ClutterTextPrivate *priv; - - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - priv = self->priv; - - clutter_text_real_delete_text (self, priv->position, priv->position + n_chars); - - if (priv->position > 0) - clutter_text_set_cursor_position (self, priv->position - n_chars); -} - -/** - * 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) -{ - gint start_index, end_index; - guint n_chars; - const gchar *text; - - g_return_val_if_fail (CLUTTER_IS_TEXT (self), NULL); - - n_chars = clutter_text_buffer_get_length (get_buffer (self)); - text = clutter_text_buffer_get_text (get_buffer (self)); - - if (end_pos < 0) - end_pos = n_chars; - - start_pos = MIN (n_chars, start_pos); - end_pos = MIN (n_chars, end_pos); - - start_index = g_utf8_offset_to_pointer (text, start_pos) - text; - end_index = g_utf8_offset_to_pointer (text, end_pos) - text; - - return g_strndup (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_queue_redraw_or_relayout (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.8 - */ -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_logical_x; - - if (y != NULL) - *y = priv->text_logical_y; -} - -/** - * clutter_text_get_cursor_rect: - * @self: a #ClutterText - * @rect: (out caller-allocates): return location of a #ClutterRect - * - * Retrieves the rectangle that contains the cursor. - * - * The coordinates of the rectangle's origin are in actor-relative - * coordinates. - * - * Since: 1.16 - */ -void -clutter_text_get_cursor_rect (ClutterText *self, - graphene_rect_t *rect) -{ - float inverse_scale; - - g_return_if_fail (CLUTTER_IS_TEXT (self)); - g_return_if_fail (rect != NULL); - - inverse_scale = 1.f / clutter_actor_get_resource_scale (CLUTTER_ACTOR (self)); - - graphene_rect_scale (&self->priv->cursor_rect, - inverse_scale, - inverse_scale, - rect); -} - -void -clutter_text_set_input_hints (ClutterText *self, - ClutterInputContentHintFlags hints) -{ - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - self->priv->input_hints = hints; - - if (clutter_input_focus_is_focused (self->priv->input_focus)) - clutter_input_focus_set_content_hints (self->priv->input_focus, hints); - g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_INPUT_HINTS]); -} - -ClutterInputContentHintFlags -clutter_text_get_input_hints (ClutterText *self) -{ - g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0); - - return self->priv->input_hints; -} - -void -clutter_text_set_input_purpose (ClutterText *self, - ClutterInputContentPurpose purpose) -{ - g_return_if_fail (CLUTTER_IS_TEXT (self)); - - self->priv->input_purpose = purpose; - - if (clutter_input_focus_is_focused (self->priv->input_focus)) - clutter_input_focus_set_content_purpose (self->priv->input_focus, purpose); - g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_INPUT_PURPOSE]); -} - -ClutterInputContentPurpose -clutter_text_get_input_purpose (ClutterText *self) -{ - g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0); - - return self->priv->input_purpose; -} - -gboolean -clutter_text_has_preedit (ClutterText *self) -{ - g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE); - - return self->priv->preedit_set; -} |