gnome-shell/src/st/st-entry.c

1268 lines
35 KiB
C
Raw Normal View History

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* st-entry.c: Plain entry actor
*
* Copyright 2008, 2009 Intel Corporation
* Copyright 2009, 2010 Red Hat, Inc.
* Copyright 2010 Florian Müllner
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:st-entry
* @short_description: Widget for displaying text
*
* #StEntry is a simple widget for displaying text. It derives from
* #StWidget to add extra style and placement functionality over
* #ClutterText. The internal #ClutterText is publicly accessibly to allow
* applications to set further properties.
*
* #StEntry supports the following pseudo style states:
* <itemizedlist>
* <listitem>
* <para>focus: the widget has focus</para>
* </listitem>
* <listitem>
* <para>indeterminate: the widget is showing the hint text</para>
* </listitem>
* <listitem>
* <para>hover: the widget is showing the hint text and is underneath the
* pointer</para>
* </listitem>
* </itemizedlist>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <clutter/clutter.h>
#include "st-entry.h"
#include "st-im-text.h"
#include "st-icon.h"
#include "st-widget.h"
#include "st-texture-cache.h"
#include "st-clipboard.h"
#include "st-private.h"
#include <clutter/x11/clutter-x11.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include "st-widget-accessible.h"
#define HAS_FOCUS(actor) (clutter_actor_get_stage (actor) && clutter_stage_get_key_focus ((ClutterStage *) clutter_actor_get_stage (actor)) == actor)
/* properties */
enum
{
PROP_0,
PROP_CLUTTER_TEXT,
PROP_HINT_TEXT,
PROP_TEXT,
PROP_INPUT_PURPOSE,
PROP_INPUT_HINTS,
};
/* signals */
enum
{
PRIMARY_ICON_CLICKED,
SECONDARY_ICON_CLICKED,
LAST_SIGNAL
};
#define ST_ENTRY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_ENTRY, StEntryPrivate))
#define ST_ENTRY_PRIV(x) ((StEntry *) x)->priv
struct _StEntryPrivate
{
ClutterActor *entry;
gchar *hint;
ClutterActor *primary_icon;
ClutterActor *secondary_icon;
gfloat spacing;
gboolean hint_visible;
gboolean capslock_warning_shown;
gboolean has_ibeam;
};
static guint entry_signals[LAST_SIGNAL] = { 0, };
G_DEFINE_TYPE (StEntry, st_entry, ST_TYPE_WIDGET);
static GType st_entry_accessible_get_type (void) G_GNUC_CONST;
static void
st_entry_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
StEntry *entry = ST_ENTRY (gobject);
switch (prop_id)
{
case PROP_HINT_TEXT:
st_entry_set_hint_text (entry, g_value_get_string (value));
break;
case PROP_TEXT:
st_entry_set_text (entry, g_value_get_string (value));
break;
case PROP_INPUT_PURPOSE:
st_entry_set_input_purpose (entry, g_value_get_enum (value));
break;
case PROP_INPUT_HINTS:
st_entry_set_input_hints (entry, g_value_get_flags (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
st_entry_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
StEntryPrivate *priv = ST_ENTRY_PRIV (gobject);
switch (prop_id)
{
case PROP_CLUTTER_TEXT:
g_value_set_object (value, priv->entry);
break;
case PROP_HINT_TEXT:
g_value_set_string (value, priv->hint);
break;
case PROP_TEXT:
g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->entry)));
break;
case PROP_INPUT_PURPOSE:
g_value_set_enum (value, st_im_text_get_input_purpose (ST_IM_TEXT (priv->entry)));
break;
case PROP_INPUT_HINTS:
g_value_set_flags (value, st_im_text_get_input_hints (ST_IM_TEXT (priv->entry)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
show_capslock_feedback (StEntry *entry)
{
if (entry->priv->secondary_icon == NULL)
{
ClutterActor *icon = g_object_new (ST_TYPE_ICON,
"style-class", "capslock-warning",
"icon-name", "dialog-warning-symbolic",
NULL);
st_entry_set_secondary_icon (entry, icon);
entry->priv->capslock_warning_shown = TRUE;
}
}
static void
remove_capslock_feedback (StEntry *entry)
{
if (entry->priv->capslock_warning_shown)
{
st_entry_set_secondary_icon (entry, NULL);
entry->priv->capslock_warning_shown = FALSE;
}
}
static void
keymap_state_changed (GdkKeymap *keymap,
gpointer user_data)
{
StEntry *entry = ST_ENTRY (user_data);
if (clutter_text_get_password_char (CLUTTER_TEXT (entry->priv->entry)) != 0)
{
if (gdk_keymap_get_caps_lock_state (keymap))
show_capslock_feedback (entry);
else
remove_capslock_feedback (entry);
}
}
static void
st_entry_dispose (GObject *object)
{
StEntry *entry = ST_ENTRY (object);
StEntryPrivate *priv = entry->priv;
GdkKeymap *keymap;
if (priv->entry)
{
clutter_actor_destroy (priv->entry);
priv->entry = NULL;
}
keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry);
G_OBJECT_CLASS (st_entry_parent_class)->dispose (object);
}
static void
st_entry_finalize (GObject *object)
{
StEntryPrivate *priv = ST_ENTRY_PRIV (object);
g_free (priv->hint);
priv->hint = NULL;
G_OBJECT_CLASS (st_entry_parent_class)->finalize (object);
}
static void
st_entry_style_changed (StWidget *self)
{
StEntryPrivate *priv = ST_ENTRY_PRIV (self);
StThemeNode *theme_node;
ClutterColor color;
const PangoFontDescription *font;
gchar *font_string, *font_name;
gdouble size;
theme_node = st_widget_get_theme_node (self);
st_theme_node_get_foreground_color (theme_node, &color);
clutter_text_set_color (CLUTTER_TEXT (priv->entry), &color);
if (st_theme_node_lookup_length (theme_node, "caret-size", TRUE, &size))
clutter_text_set_cursor_size (CLUTTER_TEXT (priv->entry), (int)(.5 + size));
if (st_theme_node_lookup_color (theme_node, "caret-color", TRUE, &color))
clutter_text_set_cursor_color (CLUTTER_TEXT (priv->entry), &color);
if (st_theme_node_lookup_color (theme_node, "selection-background-color", TRUE, &color))
clutter_text_set_selection_color (CLUTTER_TEXT (priv->entry), &color);
if (st_theme_node_lookup_color (theme_node, "selected-color", TRUE, &color))
clutter_text_set_selected_text_color (CLUTTER_TEXT (priv->entry), &color);
font = st_theme_node_get_font (theme_node);
font_string = pango_font_description_to_string (font);
font_name = g_strdup (clutter_text_get_font_name (CLUTTER_TEXT (priv->entry)));
clutter_text_set_font_name (CLUTTER_TEXT (priv->entry), font_string);
if (strcmp (clutter_text_get_font_name (CLUTTER_TEXT (priv->entry)), font_name) != 0)
clutter_actor_queue_relayout (priv->entry);
g_free (font_string);
g_free (font_name);
ST_WIDGET_CLASS (st_entry_parent_class)->style_changed (self);
}
static gboolean
st_entry_navigate_focus (StWidget *widget,
ClutterActor *from,
GtkDirectionType direction)
{
StEntryPrivate *priv = ST_ENTRY_PRIV (widget);
/* This is basically the same as st_widget_real_navigate_focus(),
* except that widget is behaving as a proxy for priv->entry (which
* isn't an StWidget and so has no can-focus flag of its own).
*/
if (from == priv->entry)
return FALSE;
else if (st_widget_get_can_focus (widget) &&
CLUTTER_ACTOR_IS_MAPPED (priv->entry))
{
clutter_actor_grab_key_focus (priv->entry);
return TRUE;
}
else
return FALSE;
}
static void
st_entry_get_preferred_width (ClutterActor *actor,
gfloat for_height,
gfloat *min_width_p,
gfloat *natural_width_p)
{
StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
gfloat icon_w;
st_theme_node_adjust_for_height (theme_node, &for_height);
clutter_actor_get_preferred_width (priv->entry, for_height,
min_width_p,
natural_width_p);
if (priv->primary_icon)
{
clutter_actor_get_preferred_width (priv->primary_icon, -1, NULL, &icon_w);
if (min_width_p)
*min_width_p += icon_w + priv->spacing;
if (natural_width_p)
*natural_width_p += icon_w + priv->spacing;
}
if (priv->secondary_icon)
{
clutter_actor_get_preferred_width (priv->secondary_icon,
-1, NULL, &icon_w);
if (min_width_p)
*min_width_p += icon_w + priv->spacing;
if (natural_width_p)
*natural_width_p += icon_w + priv->spacing;
}
st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
}
static void
st_entry_get_preferred_height (ClutterActor *actor,
gfloat for_width,
gfloat *min_height_p,
gfloat *natural_height_p)
{
StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
gfloat icon_h;
st_theme_node_adjust_for_width (theme_node, &for_width);
clutter_actor_get_preferred_height (priv->entry, for_width,
min_height_p,
natural_height_p);
if (priv->primary_icon)
{
clutter_actor_get_preferred_height (priv->primary_icon,
-1, NULL, &icon_h);
if (min_height_p && icon_h > *min_height_p)
*min_height_p = icon_h;
if (natural_height_p && icon_h > *natural_height_p)
*natural_height_p = icon_h;
}
if (priv->secondary_icon)
{
clutter_actor_get_preferred_height (priv->secondary_icon,
-1, NULL, &icon_h);
if (min_height_p && icon_h > *min_height_p)
*min_height_p = icon_h;
if (natural_height_p && icon_h > *natural_height_p)
*natural_height_p = icon_h;
}
st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
}
static void
st_entry_allocate (ClutterActor *actor,
const ClutterActorBox *box,
ClutterAllocationFlags flags)
{
StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
ClutterActorBox content_box, child_box, icon_box;
gfloat icon_w, icon_h;
gfloat entry_h, min_h, pref_h, avail_h;
ClutterActor *left_icon, *right_icon;
if (clutter_actor_get_text_direction (actor) == CLUTTER_TEXT_DIRECTION_RTL)
{
right_icon = priv->primary_icon;
left_icon = priv->secondary_icon;
}
else
{
left_icon = priv->primary_icon;
right_icon = priv->secondary_icon;
}
clutter_actor_set_allocation (actor, box, flags);
st_theme_node_get_content_box (theme_node, box, &content_box);
avail_h = content_box.y2 - content_box.y1;
child_box.x1 = content_box.x1;
child_box.x2 = content_box.x2;
if (left_icon)
{
clutter_actor_get_preferred_width (left_icon, -1, NULL, &icon_w);
clutter_actor_get_preferred_height (left_icon, -1, NULL, &icon_h);
icon_box.x1 = content_box.x1;
icon_box.x2 = icon_box.x1 + icon_w;
icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
icon_box.y2 = icon_box.y1 + icon_h;
clutter_actor_allocate (left_icon, &icon_box, flags);
/* reduce the size for the entry */
child_box.x1 += icon_w + priv->spacing;
}
if (right_icon)
{
clutter_actor_get_preferred_width (right_icon, -1, NULL, &icon_w);
clutter_actor_get_preferred_height (right_icon, -1, NULL, &icon_h);
icon_box.x2 = content_box.x2;
icon_box.x1 = icon_box.x2 - icon_w;
icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
icon_box.y2 = icon_box.y1 + icon_h;
clutter_actor_allocate (right_icon, &icon_box, flags);
/* reduce the size for the entry */
child_box.x2 -= icon_w + priv->spacing;
}
clutter_actor_get_preferred_height (priv->entry, child_box.x2 - child_box.x1,
&min_h, &pref_h);
entry_h = CLAMP (pref_h, min_h, avail_h);
child_box.y1 = (int) (content_box.y1 + avail_h / 2 - entry_h / 2);
child_box.y2 = child_box.y1 + entry_h;
clutter_actor_allocate (priv->entry, &child_box, flags);
}
static void
clutter_text_focus_in_cb (ClutterText *text,
ClutterActor *actor)
{
StEntry *entry = ST_ENTRY (actor);
StEntryPrivate *priv = entry->priv;
GdkKeymap *keymap;
/* remove the hint if visible */
if (priv->hint && priv->hint_visible)
{
priv->hint_visible = FALSE;
clutter_text_set_text (text, "");
}
keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
keymap_state_changed (keymap, entry);
g_signal_connect (keymap, "state-changed",
G_CALLBACK (keymap_state_changed), entry);
st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "indeterminate");
st_widget_add_style_pseudo_class (ST_WIDGET (actor), "focus");
clutter_text_set_cursor_visible (text, TRUE);
}
static void
clutter_text_focus_out_cb (ClutterText *text,
ClutterActor *actor)
{
StEntry *entry = ST_ENTRY (actor);
StEntryPrivate *priv = entry->priv;
GdkKeymap *keymap;
st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "focus");
/* add a hint if the entry is empty */
if (priv->hint && !strcmp (clutter_text_get_text (text), ""))
{
priv->hint_visible = TRUE;
clutter_text_set_text (text, priv->hint);
st_widget_add_style_pseudo_class (ST_WIDGET (actor), "indeterminate");
}
clutter_text_set_cursor_visible (text, FALSE);
remove_capslock_feedback (entry);
keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry);
}
static void
clutter_text_password_char_cb (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
StEntry *entry = ST_ENTRY (user_data);
if (clutter_text_get_password_char (CLUTTER_TEXT (entry->priv->entry)) == 0)
remove_capslock_feedback (entry);
}
static void
st_entry_clipboard_callback (StClipboard *clipboard,
const gchar *text,
gpointer data)
{
ClutterText *ctext = (ClutterText*)((StEntry *) data)->priv->entry;
gint cursor_pos;
if (!text)
return;
/* delete the current selection before pasting */
clutter_text_delete_selection (ctext);
/* "paste" the clipboard text into the entry */
cursor_pos = clutter_text_get_cursor_position (ctext);
clutter_text_insert_text (ctext, text, cursor_pos);
}
static gboolean
clutter_text_button_press_event (ClutterActor *actor,
ClutterButtonEvent *event,
gpointer user_data)
{
GtkSettings *settings = gtk_settings_get_default ();
gboolean primary_paste_enabled;
g_object_get (settings,
"gtk-enable-primary-paste", &primary_paste_enabled,
NULL);
if (primary_paste_enabled && event->button == 2)
{
StClipboard *clipboard;
clipboard = st_clipboard_get_default ();
/* By the time the clipboard callback is called,
* the rest of the signal handlers will have
* run, making the text cursor to be in the correct
* place.
*/
st_clipboard_get_text (clipboard,
ST_CLIPBOARD_TYPE_PRIMARY,
st_entry_clipboard_callback,
user_data);
}
return FALSE;
}
static gboolean
st_entry_key_press_event (ClutterActor *actor,
ClutterKeyEvent *event)
{
StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
/* This is expected to handle events that were emitted for the inner
ClutterText. They only reach this function if the ClutterText
didn't handle them */
/* paste */
if ((event->modifier_state & CLUTTER_CONTROL_MASK)
&& event->keyval == CLUTTER_v)
{
StClipboard *clipboard;
clipboard = st_clipboard_get_default ();
st_clipboard_get_text (clipboard,
ST_CLIPBOARD_TYPE_CLIPBOARD,
st_entry_clipboard_callback,
actor);
return TRUE;
}
/* copy */
if ((event->modifier_state & CLUTTER_CONTROL_MASK)
&& event->keyval == CLUTTER_c &&
clutter_text_get_password_char ((ClutterText*) priv->entry) == 0)
{
StClipboard *clipboard;
gchar *text;
clipboard = st_clipboard_get_default ();
text = clutter_text_get_selection ((ClutterText*) priv->entry);
if (text && strlen (text))
st_clipboard_set_text (clipboard,
ST_CLIPBOARD_TYPE_CLIPBOARD,
text);
return TRUE;
}
/* cut */
if ((event->modifier_state & CLUTTER_CONTROL_MASK)
&& event->keyval == CLUTTER_x &&
clutter_text_get_password_char ((ClutterText*) priv->entry) == 0)
{
StClipboard *clipboard;
gchar *text;
clipboard = st_clipboard_get_default ();
text = clutter_text_get_selection ((ClutterText*) priv->entry);
if (text && strlen (text))
{
st_clipboard_set_text (clipboard,
ST_CLIPBOARD_TYPE_CLIPBOARD,
text);
/* now delete the text */
clutter_text_delete_selection ((ClutterText *) priv->entry);
}
return TRUE;
}
return CLUTTER_ACTOR_CLASS (st_entry_parent_class)->key_press_event (actor, event);
}
static void
st_entry_key_focus_in (ClutterActor *actor)
{
StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
/* We never want key focus. The ClutterText should be given first
pass for all key events */
clutter_actor_grab_key_focus (priv->entry);
}
static void
st_entry_set_cursor (StEntry *entry,
gboolean use_ibeam)
{
Display *dpy;
ClutterActor *stage, *actor = CLUTTER_ACTOR (entry);
Window wid;
static Cursor ibeam = None;
dpy = clutter_x11_get_default_display ();
stage = clutter_actor_get_stage (actor);
if (stage == NULL)
{
g_warn_if_fail (!entry->priv->has_ibeam);
return;
}
wid = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));
if (ibeam == None)
ibeam = XCreateFontCursor (dpy, XC_xterm);
if (use_ibeam)
XDefineCursor (dpy, wid, ibeam);
else
XUndefineCursor (dpy, wid);
entry->priv->has_ibeam = use_ibeam;
}
static gboolean
st_entry_crossing_event (ClutterActor *actor,
ClutterCrossingEvent *event)
{
if (event->source == ST_ENTRY (actor)->priv->entry && event->related != NULL)
st_entry_set_cursor (ST_ENTRY (actor), (event->type == CLUTTER_ENTER));
return FALSE;
}
static void
st_entry_unmap (ClutterActor *actor)
{
if (ST_ENTRY (actor)->priv->has_ibeam)
st_entry_set_cursor (ST_ENTRY (actor), FALSE);
CLUTTER_ACTOR_CLASS (st_entry_parent_class)->unmap (actor);
}
static void
st_entry_class_init (StEntryClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
GParamSpec *pspec;
g_type_class_add_private (klass, sizeof (StEntryPrivate));
gobject_class->set_property = st_entry_set_property;
gobject_class->get_property = st_entry_get_property;
gobject_class->finalize = st_entry_finalize;
gobject_class->dispose = st_entry_dispose;
actor_class->get_preferred_width = st_entry_get_preferred_width;
actor_class->get_preferred_height = st_entry_get_preferred_height;
actor_class->allocate = st_entry_allocate;
actor_class->unmap = st_entry_unmap;
actor_class->key_press_event = st_entry_key_press_event;
actor_class->key_focus_in = st_entry_key_focus_in;
actor_class->enter_event = st_entry_crossing_event;
actor_class->leave_event = st_entry_crossing_event;
widget_class->style_changed = st_entry_style_changed;
widget_class->navigate_focus = st_entry_navigate_focus;
widget_class->get_accessible_type = st_entry_accessible_get_type;
pspec = g_param_spec_object ("clutter-text",
"Clutter Text",
"Internal ClutterText actor",
CLUTTER_TYPE_TEXT,
G_PARAM_READABLE);
g_object_class_install_property (gobject_class, PROP_CLUTTER_TEXT, pspec);
pspec = g_param_spec_string ("hint-text",
"Hint Text",
"Text to display when the entry is not focused "
"and the text property is empty",
NULL, G_PARAM_READWRITE);
g_object_class_install_property (gobject_class, PROP_HINT_TEXT, pspec);
pspec = g_param_spec_string ("text",
"Text",
"Text of the entry",
NULL, G_PARAM_READWRITE);
g_object_class_install_property (gobject_class, PROP_TEXT, pspec);
pspec = g_param_spec_enum ("input-purpose",
"Purpose",
"Purpose of the text field",
GTK_TYPE_INPUT_PURPOSE,
GTK_INPUT_PURPOSE_FREE_FORM,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (gobject_class,
PROP_INPUT_PURPOSE,
pspec);
pspec = g_param_spec_flags ("input-hints",
"hints",
"Hints for the text field behaviour",
GTK_TYPE_INPUT_HINTS,
GTK_INPUT_HINT_NONE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (gobject_class,
PROP_INPUT_HINTS,
pspec);
/* signals */
/**
* StEntry::primary-icon-clicked:
*
* Emitted when the primary icon is clicked
*/
entry_signals[PRIMARY_ICON_CLICKED] =
g_signal_new ("primary-icon-clicked",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (StEntryClass, primary_icon_clicked),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
/**
* StEntry::secondary-icon-clicked:
*
* Emitted when the secondary icon is clicked
*/
entry_signals[SECONDARY_ICON_CLICKED] =
g_signal_new ("secondary-icon-clicked",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (StEntryClass, secondary_icon_clicked),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
st_entry_init (StEntry *entry)
{
StEntryPrivate *priv;
priv = entry->priv = ST_ENTRY_GET_PRIVATE (entry);
priv->entry = g_object_new (ST_TYPE_IM_TEXT,
"line-alignment", PANGO_ALIGN_LEFT,
"editable", TRUE,
"reactive", TRUE,
"single-line-mode", TRUE,
NULL);
g_signal_connect (priv->entry, "key-focus-in",
G_CALLBACK (clutter_text_focus_in_cb), entry);
g_signal_connect (priv->entry, "key-focus-out",
G_CALLBACK (clutter_text_focus_out_cb), entry);
g_signal_connect (priv->entry, "notify::password-char",
G_CALLBACK (clutter_text_password_char_cb), entry);
g_signal_connect (priv->entry, "button-press-event",
G_CALLBACK (clutter_text_button_press_event), entry);
priv->spacing = 6.0f;
clutter_actor_add_child (CLUTTER_ACTOR (entry), priv->entry);
clutter_actor_set_reactive ((ClutterActor *) entry, TRUE);
/* set cursor hidden until we receive focus */
clutter_text_set_cursor_visible ((ClutterText *) priv->entry, FALSE);
}
/**
* st_entry_new:
* @text: text to set the entry to
*
* Create a new #StEntry with the specified entry
*
* Returns: a new #StEntry
*/
StWidget *
st_entry_new (const gchar *text)
{
StWidget *entry;
/* add the entry to the stage, but don't allow it to be visible */
entry = g_object_new (ST_TYPE_ENTRY,
"text", text,
NULL);
return entry;
}
/**
* st_entry_get_text:
* @entry: a #StEntry
*
* Get the text displayed on the entry
*
* Returns: the text for the entry. This must not be freed by the application
*/
const gchar *
st_entry_get_text (StEntry *entry)
{
g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
if (entry->priv->hint_visible)
return "";
else
return clutter_text_get_text (CLUTTER_TEXT (entry->priv->entry));
}
/**
* st_entry_set_text:
* @entry: a #StEntry
* @text: (allow-none): text to set the entry to
*
* Sets the text displayed on the entry
*/
void
st_entry_set_text (StEntry *entry,
const gchar *text)
{
StEntryPrivate *priv;
g_return_if_fail (ST_IS_ENTRY (entry));
priv = entry->priv;
/* set a hint if we are blanking the entry */
if (priv->hint
&& text && !strcmp ("", text)
&& !HAS_FOCUS (priv->entry))
{
text = priv->hint;
priv->hint_visible = TRUE;
st_widget_add_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
}
else
{
st_widget_remove_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
priv->hint_visible = FALSE;
}
clutter_text_set_text (CLUTTER_TEXT (priv->entry), text);
g_object_notify (G_OBJECT (entry), "text");
}
/**
* st_entry_get_clutter_text:
* @entry: a #StEntry
*
* Retrieve the internal #ClutterText so that extra parameters can be set
*
* Returns: (transfer none): the #ClutterText used by #StEntry. The entry is
* owned by the #StEntry and should not be unref'ed by the application.
*/
ClutterActor*
st_entry_get_clutter_text (StEntry *entry)
{
g_return_val_if_fail (ST_ENTRY (entry), NULL);
return entry->priv->entry;
}
/**
* st_entry_set_hint_text:
* @entry: a #StEntry
* @text: (allow-none): text to set as the entry hint
*
* Sets the text to display when the entry is empty and unfocused. When the
* entry is displaying the hint, it has a pseudo class of "indeterminate".
* A value of NULL unsets the hint.
*/
void
st_entry_set_hint_text (StEntry *entry,
const gchar *text)
{
StEntryPrivate *priv;
g_return_if_fail (ST_IS_ENTRY (entry));
priv = entry->priv;
g_free (priv->hint);
priv->hint = g_strdup (text);
if (!strcmp (clutter_text_get_text (CLUTTER_TEXT (priv->entry)), "")
&& !HAS_FOCUS (priv->entry))
{
priv->hint_visible = TRUE;
clutter_text_set_text (CLUTTER_TEXT (priv->entry), priv->hint);
st_widget_add_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
}
}
/**
* st_entry_get_hint_text:
* @entry: a #StEntry
*
* Gets the text that is displayed when the entry is empty and unfocused
*
* Returns: the current value of the hint property. This string is owned by the
* #StEntry and should not be freed or modified.
*/
const gchar *
st_entry_get_hint_text (StEntry *entry)
{
g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
return entry->priv->hint;
}
/**
* st_entry_set_input_purpose:
* @entry: a #StEntry
* @purpose: the purpose
*
* Sets the #StEntry:input-purpose property which
* can be used by on-screen keyboards and other input
* methods to adjust their behaviour.
*/
void
st_entry_set_input_purpose (StEntry *entry,
GtkInputPurpose purpose)
{
StIMText *imtext;
g_return_if_fail (ST_IS_ENTRY (entry));
imtext = ST_IM_TEXT (entry->priv->entry);
if (st_im_text_get_input_purpose (imtext) != purpose)
{
st_im_text_set_input_purpose (imtext, purpose);
g_object_notify (G_OBJECT (entry), "input-purpose");
}
}
/**
* st_entry_get_input_purpose:
* @entry: a #StEntry
*
* Gets the value of the #StEntry:input-purpose property.
*/
GtkInputPurpose
st_entry_get_input_purpose (StEntry *entry)
{
g_return_val_if_fail (ST_IS_ENTRY (entry), GTK_INPUT_PURPOSE_FREE_FORM);
return st_im_text_get_input_purpose (ST_IM_TEXT (entry->priv->entry));
}
/**
* st_entry_set_input_hints:
* @entry: a #StEntry
* @hints: the hints
*
* Sets the #StEntry:input-hints property, which
* allows input methods to fine-tune their behaviour.
*/
void
st_entry_set_input_hints (StEntry *entry,
GtkInputHints hints)
{
StIMText *imtext;
g_return_if_fail (ST_IS_ENTRY (entry));
imtext = ST_IM_TEXT (entry->priv->entry);
if (st_im_text_get_input_hints (imtext) != hints)
{
st_im_text_set_input_hints (imtext, hints);
g_object_notify (G_OBJECT (entry), "input-hints");
}
}
/**
* st_entry_get_input_hints:
* @entry: a #StEntry
*
* Gets the value of the #StEntry:input-hints property.
*/
GtkInputHints
st_entry_get_input_hints (StEntry *entry)
{
g_return_val_if_fail (ST_IS_ENTRY (entry), GTK_INPUT_HINT_NONE);
return st_im_text_get_input_hints (ST_IM_TEXT (entry->priv->entry));
}
static gboolean
_st_entry_icon_press_cb (ClutterActor *actor,
ClutterButtonEvent *event,
StEntry *entry)
{
StEntryPrivate *priv = entry->priv;
if (actor == priv->primary_icon)
g_signal_emit (entry, entry_signals[PRIMARY_ICON_CLICKED], 0);
else
g_signal_emit (entry, entry_signals[SECONDARY_ICON_CLICKED], 0);
return FALSE;
}
static void
_st_entry_set_icon (StEntry *entry,
ClutterActor **icon,
ClutterActor *new_icon)
{
if (*icon)
{
g_signal_handlers_disconnect_by_func (*icon,
_st_entry_icon_press_cb,
entry);
clutter_actor_remove_child (CLUTTER_ACTOR (entry), *icon);
*icon = NULL;
}
if (new_icon)
{
*icon = g_object_ref (new_icon);
clutter_actor_set_reactive (*icon, TRUE);
clutter_actor_add_child (CLUTTER_ACTOR (entry), *icon);
g_signal_connect (*icon, "button-release-event",
G_CALLBACK (_st_entry_icon_press_cb), entry);
}
clutter_actor_queue_relayout (CLUTTER_ACTOR (entry));
}
/**
* st_entry_set_primary_icon:
* @entry: a #StEntry
* @icon: (allow-none): a #ClutterActor
*
* Set the primary icon of the entry to @icon
*/
void
st_entry_set_primary_icon (StEntry *entry,
ClutterActor *icon)
{
StEntryPrivate *priv;
g_return_if_fail (ST_IS_ENTRY (entry));
priv = entry->priv;
_st_entry_set_icon (entry, &priv->primary_icon, icon);
}
/**
* st_entry_set_secondary_icon:
* @entry: a #StEntry
* @icon: (allow-none): an #ClutterActor
*
* Set the secondary icon of the entry to @icon
*/
void
st_entry_set_secondary_icon (StEntry *entry,
ClutterActor *icon)
{
StEntryPrivate *priv;
g_return_if_fail (ST_IS_ENTRY (entry));
priv = entry->priv;
_st_entry_set_icon (entry, &priv->secondary_icon, icon);
}
/******************************************************************************/
/*************************** ACCESSIBILITY SUPPORT ****************************/
/******************************************************************************/
#define ST_TYPE_ENTRY_ACCESSIBLE (st_entry_accessible_get_type ())
#define ST_ENTRY_ACCESSIBLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessible))
#define ST_IS_ENTRY_ACCESSIBLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ST_TYPE_ENTRY_ACCESSIBLE))
#define ST_ENTRY_ACCESSIBLE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
#define ST_IS_ENTRY_ACCESSIBLE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), ST_TYPE_ENTRY_ACCESSIBLE))
#define ST_ENTRY_ACCESSIBLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
typedef struct _StEntryAccessible StEntryAccessible;
typedef struct _StEntryAccessibleClass StEntryAccessibleClass;
struct _StEntryAccessible
{
StWidgetAccessible parent;
};
struct _StEntryAccessibleClass
{
StWidgetAccessibleClass parent_class;
};
G_DEFINE_TYPE (StEntryAccessible, st_entry_accessible, ST_TYPE_WIDGET_ACCESSIBLE)
static void
st_entry_accessible_init (StEntryAccessible *self)
{
/* initialization done on AtkObject->initialize */
}
static void
st_entry_accessible_initialize (AtkObject *obj,
gpointer data)
{
ATK_OBJECT_CLASS (st_entry_accessible_parent_class)->initialize (obj, data);
/* StEntry is behaving as a StImText container */
atk_object_set_role (obj, ATK_ROLE_PANEL);
}
static gint
st_entry_accessible_get_n_children (AtkObject *obj)
{
StEntry *entry = NULL;
g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), 0);
entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
if (entry == NULL)
return 0;
if (entry->priv->entry == NULL)
return 0;
else
return 1;
}
static AtkObject*
st_entry_accessible_ref_child (AtkObject *obj,
gint i)
{
StEntry *entry = NULL;
AtkObject *result = NULL;
g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), NULL);
g_return_val_if_fail (i == 0, NULL);
entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
if (entry == NULL)
return NULL;
if (entry->priv->entry == NULL)
return NULL;
result = clutter_actor_get_accessible (entry->priv->entry);
g_object_ref (result);
return result;
}
static void
st_entry_accessible_class_init (StEntryAccessibleClass *klass)
{
AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
atk_class->initialize = st_entry_accessible_initialize;
atk_class->get_n_children = st_entry_accessible_get_n_children;
atk_class->ref_child= st_entry_accessible_ref_child;
}