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

No issues found

   1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
   2 /*
   3  * st-entry.c: Plain entry actor
   4  *
   5  * Copyright 2008, 2009 Intel Corporation
   6  * Copyright 2009, 2010 Red Hat, Inc.
   7  * Copyright 2010 Florian Mç«Żllner
   8  *
   9  * This program is free software; you can redistribute it and/or modify it
  10  * under the terms and conditions of the GNU Lesser General Public License,
  11  * version 2.1, as published by the Free Software Foundation.
  12  *
  13  * This program is distributed in the hope it will be useful, but WITHOUT ANY
  14  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
  16  * more details.
  17  *
  18  * You should have received a copy of the GNU Lesser General Public License
  19  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20  */
  21 
  22 /**
  23  * SECTION:st-entry
  24  * @short_description: Widget for displaying text
  25  *
  26  * #StEntry is a simple widget for displaying text. It derives from
  27  * #StWidget to add extra style and placement functionality over
  28  * #ClutterText. The internal #ClutterText is publicly accessibly to allow
  29  * applications to set further properties.
  30  *
  31  * #StEntry supports the following pseudo style states:
  32  * <itemizedlist>
  33  *  <listitem>
  34  *   <para>focus: the widget has focus</para>
  35  *  </listitem>
  36  *  <listitem>
  37  *   <para>indeterminate: the widget is showing the hint text</para>
  38  *  </listitem>
  39  *  <listitem>
  40  *   <para>hover: the widget is showing the hint text and is underneath the
  41  *                pointer</para>
  42  *  </listitem>
  43  * </itemizedlist>
  44  */
  45 
  46 #ifdef HAVE_CONFIG_H
  47 #include "config.h"
  48 #endif
  49 
  50 #include <math.h>
  51 
  52 #include <stdlib.h>
  53 #include <string.h>
  54 
  55 #include <glib.h>
  56 
  57 #include <clutter/clutter.h>
  58 
  59 #include "st-entry.h"
  60 
  61 #include "st-im-text.h"
  62 #include "st-icon.h"
  63 #include "st-widget.h"
  64 #include "st-texture-cache.h"
  65 #include "st-clipboard.h"
  66 #include "st-private.h"
  67 
  68 #include "st-widget-accessible.h"
  69 
  70 #define HAS_FOCUS(actor) (clutter_actor_get_stage (actor) && clutter_stage_get_key_focus ((ClutterStage *) clutter_actor_get_stage (actor)) == actor)
  71 
  72 
  73 /* properties */
  74 enum
  75 {
  76   PROP_0,
  77 
  78   PROP_CLUTTER_TEXT,
  79   PROP_HINT_TEXT,
  80   PROP_TEXT,
  81 };
  82 
  83 /* signals */
  84 enum
  85 {
  86   PRIMARY_ICON_CLICKED,
  87   SECONDARY_ICON_CLICKED,
  88 
  89   LAST_SIGNAL
  90 };
  91 
  92 #define ST_ENTRY_GET_PRIVATE(obj)     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_ENTRY, StEntryPrivate))
  93 #define ST_ENTRY_PRIV(x) ((StEntry *) x)->priv
  94 
  95 
  96 struct _StEntryPrivate
  97 {
  98   ClutterActor *entry;
  99   gchar        *hint;
 100 
 101   ClutterActor *primary_icon;
 102   ClutterActor *secondary_icon;
 103 
 104   gfloat        spacing;
 105 
 106   gboolean      hint_visible;
 107   gboolean      capslock_warning_shown;
 108 };
 109 
 110 static guint entry_signals[LAST_SIGNAL] = { 0, };
 111 
 112 G_DEFINE_TYPE (StEntry, st_entry, ST_TYPE_WIDGET);
 113 
 114 static GType st_entry_accessible_get_type (void) G_GNUC_CONST;
 115 
 116 static void
 117 st_entry_set_property (GObject      *gobject,
 118                        guint         prop_id,
 119                        const GValue *value,
 120                        GParamSpec   *pspec)
 121 {
 122   StEntry *entry = ST_ENTRY (gobject);
 123 
 124   switch (prop_id)
 125     {
 126     case PROP_HINT_TEXT:
 127       st_entry_set_hint_text (entry, g_value_get_string (value));
 128       break;
 129 
 130     case PROP_TEXT:
 131       st_entry_set_text (entry, g_value_get_string (value));
 132       break;
 133 
 134     default:
 135       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
 136       break;
 137     }
 138 }
 139 
 140 static void
 141 st_entry_get_property (GObject    *gobject,
 142                        guint       prop_id,
 143                        GValue     *value,
 144                        GParamSpec *pspec)
 145 {
 146   StEntryPrivate *priv = ST_ENTRY_PRIV (gobject);
 147 
 148   switch (prop_id)
 149     {
 150     case PROP_CLUTTER_TEXT:
 151       g_value_set_object (value, priv->entry);
 152       break;
 153 
 154     case PROP_HINT_TEXT:
 155       g_value_set_string (value, priv->hint);
 156       break;
 157 
 158     case PROP_TEXT:
 159       g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->entry)));
 160       break;
 161 
 162     default:
 163       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
 164       break;
 165     }
 166 }
 167 
 168 static void
 169 show_capslock_feedback (StEntry *entry)
 170 {
 171   if (entry->priv->secondary_icon == NULL)
 172     {
 173       ClutterActor *icon = g_object_new (ST_TYPE_ICON,
 174                                          "style-class", "capslock-warning",
 175                                          "icon-name", "dialog-warning-symbolic",
 176                                          NULL);
 177 
 178       st_entry_set_secondary_icon (entry, icon);
 179       entry->priv->capslock_warning_shown = TRUE;
 180     }
 181 }
 182 
 183 static void
 184 remove_capslock_feedback (StEntry *entry)
 185 {
 186   if (entry->priv->capslock_warning_shown)
 187     {
 188       st_entry_set_secondary_icon (entry, NULL);
 189       entry->priv->capslock_warning_shown = FALSE;
 190     }
 191 }
 192 
 193 static void
 194 keymap_state_changed (GdkKeymap *keymap,
 195                       gpointer   user_data)
 196 {
 197   StEntry *entry = ST_ENTRY (user_data);
 198 
 199   if (clutter_text_get_password_char (CLUTTER_TEXT (entry->priv->entry)) != 0)
 200     {
 201       if (gdk_keymap_get_caps_lock_state (keymap))
 202         show_capslock_feedback (entry);
 203       else
 204         remove_capslock_feedback (entry);
 205     }
 206 }
 207 
 208 static void
 209 st_entry_dispose (GObject *object)
 210 {
 211   StEntry *entry = ST_ENTRY (object);
 212   StEntryPrivate *priv = entry->priv;
 213   GdkKeymap *keymap;
 214 
 215   if (priv->entry)
 216     {
 217       clutter_actor_destroy (priv->entry);
 218       priv->entry = NULL;
 219     }
 220 
 221   keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
 222   g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry);
 223 
 224   G_OBJECT_CLASS (st_entry_parent_class)->dispose (object);
 225 }
 226 
 227 static void
 228 st_entry_finalize (GObject *object)
 229 {
 230   StEntryPrivate *priv = ST_ENTRY_PRIV (object);
 231 
 232   g_free (priv->hint);
 233   priv->hint = NULL;
 234 
 235   G_OBJECT_CLASS (st_entry_parent_class)->finalize (object);
 236 }
 237 
 238 static void
 239 st_entry_style_changed (StWidget *self)
 240 {
 241   StEntryPrivate *priv = ST_ENTRY_PRIV (self);
 242   StThemeNode *theme_node;
 243   ClutterColor color;
 244   const PangoFontDescription *font;
 245   gchar *font_string, *font_name;
 246   gdouble size;
 247 
 248   theme_node = st_widget_get_theme_node (self);
 249  
 250   st_theme_node_get_foreground_color (theme_node, &color);
 251   clutter_text_set_color (CLUTTER_TEXT (priv->entry), &color);
 252 
 253   if (st_theme_node_lookup_length (theme_node, "caret-size", TRUE, &size))
 254     clutter_text_set_cursor_size (CLUTTER_TEXT (priv->entry), (int)(.5 + size));
 255 
 256   if (st_theme_node_lookup_color (theme_node, "caret-color", TRUE, &color))
 257     clutter_text_set_cursor_color (CLUTTER_TEXT (priv->entry), &color);
 258 
 259   if (st_theme_node_lookup_color (theme_node, "selection-background-color", TRUE, &color))
 260     clutter_text_set_selection_color (CLUTTER_TEXT (priv->entry), &color);
 261 
 262   if (st_theme_node_lookup_color (theme_node, "selected-color", TRUE, &color))
 263     clutter_text_set_selected_text_color (CLUTTER_TEXT (priv->entry), &color);
 264 
 265   font = st_theme_node_get_font (theme_node);
 266   font_string = pango_font_description_to_string (font);
 267   font_name = g_strdup (clutter_text_get_font_name (CLUTTER_TEXT (priv->entry)));
 268   clutter_text_set_font_name (CLUTTER_TEXT (priv->entry), font_string);
 269   if (strcmp (clutter_text_get_font_name (CLUTTER_TEXT (priv->entry)), font_name) != 0)
 270     clutter_actor_queue_relayout (priv->entry);
 271   g_free (font_string);
 272   g_free (font_name);
 273 
 274   ST_WIDGET_CLASS (st_entry_parent_class)->style_changed (self);
 275 }
 276 
 277 static gboolean
 278 st_entry_navigate_focus (StWidget         *widget,
 279                          ClutterActor     *from,
 280                          GtkDirectionType  direction)
 281 {
 282   StEntryPrivate *priv = ST_ENTRY_PRIV (widget);
 283 
 284   /* This is basically the same as st_widget_real_navigate_focus(),
 285    * except that widget is behaving as a proxy for priv->entry (which
 286    * isn't an StWidget and so has no can-focus flag of its own).
 287    */
 288 
 289   if (from == priv->entry)
 290     return FALSE;
 291   else if (st_widget_get_can_focus (widget) &&
 292            CLUTTER_ACTOR_IS_MAPPED (priv->entry))
 293     {
 294       clutter_actor_grab_key_focus (priv->entry);
 295       return TRUE;
 296     }
 297   else
 298     return FALSE;
 299 }
 300 
 301 static void
 302 st_entry_get_preferred_width (ClutterActor *actor,
 303                               gfloat        for_height,
 304                               gfloat       *min_width_p,
 305                               gfloat       *natural_width_p)
 306 {
 307   StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
 308   StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
 309   gfloat icon_w;
 310 
 311   st_theme_node_adjust_for_height (theme_node, &for_height);
 312 
 313   clutter_actor_get_preferred_width (priv->entry, for_height,
 314                                      min_width_p,
 315                                      natural_width_p);
 316 
 317   if (priv->primary_icon)
 318     {
 319       clutter_actor_get_preferred_width (priv->primary_icon, -1, NULL, &icon_w);
 320 
 321       if (min_width_p)
 322         *min_width_p += icon_w + priv->spacing;
 323 
 324       if (natural_width_p)
 325         *natural_width_p += icon_w + priv->spacing;
 326     }
 327 
 328   if (priv->secondary_icon)
 329     {
 330       clutter_actor_get_preferred_width (priv->secondary_icon,
 331                                          -1, NULL, &icon_w);
 332 
 333       if (min_width_p)
 334         *min_width_p += icon_w + priv->spacing;
 335 
 336       if (natural_width_p)
 337         *natural_width_p += icon_w + priv->spacing;
 338     }
 339 
 340   st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
 341 }
 342 
 343 static void
 344 st_entry_get_preferred_height (ClutterActor *actor,
 345                                gfloat        for_width,
 346                                gfloat       *min_height_p,
 347                                gfloat       *natural_height_p)
 348 {
 349   StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
 350   StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
 351   gfloat icon_h;
 352 
 353   st_theme_node_adjust_for_width (theme_node, &for_width);
 354 
 355   clutter_actor_get_preferred_height (priv->entry, for_width,
 356                                       min_height_p,
 357                                       natural_height_p);
 358 
 359   if (priv->primary_icon)
 360     {
 361       clutter_actor_get_preferred_height (priv->primary_icon,
 362                                           -1, NULL, &icon_h);
 363 
 364       if (min_height_p && icon_h > *min_height_p)
 365         *min_height_p = icon_h;
 366 
 367       if (natural_height_p && icon_h > *natural_height_p)
 368         *natural_height_p = icon_h;
 369     }
 370 
 371   if (priv->secondary_icon)
 372     {
 373       clutter_actor_get_preferred_height (priv->secondary_icon,
 374                                           -1, NULL, &icon_h);
 375 
 376       if (min_height_p && icon_h > *min_height_p)
 377         *min_height_p = icon_h;
 378 
 379       if (natural_height_p && icon_h > *natural_height_p)
 380         *natural_height_p = icon_h;
 381     }
 382 
 383   st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
 384 }
 385 
 386 static void
 387 st_entry_allocate (ClutterActor          *actor,
 388                    const ClutterActorBox *box,
 389                    ClutterAllocationFlags flags)
 390 {
 391   StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
 392   StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
 393   ClutterActorBox content_box, child_box, icon_box;
 394   gfloat icon_w, icon_h;
 395   gfloat entry_h, min_h, pref_h, avail_h;
 396 
 397   clutter_actor_set_allocation (actor, box, flags);
 398 
 399   st_theme_node_get_content_box (theme_node, box, &content_box);
 400 
 401   avail_h = content_box.y2 - content_box.y1;
 402 
 403   child_box.x1 = content_box.x1;
 404   child_box.x2 = content_box.x2;
 405 
 406   if (priv->primary_icon)
 407     {
 408       clutter_actor_get_preferred_width (priv->primary_icon,
 409                                          -1, NULL, &icon_w);
 410       clutter_actor_get_preferred_height (priv->primary_icon,
 411                                           -1, NULL, &icon_h);
 412 
 413       icon_box.x1 = content_box.x1;
 414       icon_box.x2 = icon_box.x1 + icon_w;
 415 
 416       icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
 417       icon_box.y2 = icon_box.y1 + icon_h;
 418 
 419       clutter_actor_allocate (priv->primary_icon,
 420                               &icon_box,
 421                               flags);
 422 
 423       /* reduce the size for the entry */
 424       child_box.x1 += icon_w + priv->spacing;
 425     }
 426 
 427   if (priv->secondary_icon)
 428     {
 429       clutter_actor_get_preferred_width (priv->secondary_icon,
 430                                          -1, NULL, &icon_w);
 431       clutter_actor_get_preferred_height (priv->secondary_icon,
 432                                           -1, NULL, &icon_h);
 433 
 434       icon_box.x2 = content_box.x2;
 435       icon_box.x1 = icon_box.x2 - icon_w;
 436 
 437       icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
 438       icon_box.y2 = icon_box.y1 + icon_h;
 439 
 440       clutter_actor_allocate (priv->secondary_icon,
 441                               &icon_box,
 442                               flags);
 443 
 444       /* reduce the size for the entry */
 445       child_box.x2 -= icon_w - priv->spacing;
 446     }
 447 
 448   clutter_actor_get_preferred_height (priv->entry, child_box.x2 - child_box.x1,
 449                                       &min_h, &pref_h);
 450 
 451   entry_h = CLAMP (pref_h, min_h, avail_h);
 452 
 453   child_box.y1 = (int) (content_box.y1 + avail_h / 2 - entry_h / 2);
 454   child_box.y2 = child_box.y1 + entry_h;
 455 
 456   clutter_actor_allocate (priv->entry, &child_box, flags);
 457 }
 458 
 459 static void
 460 clutter_text_focus_in_cb (ClutterText  *text,
 461                           ClutterActor *actor)
 462 {
 463   StEntry *entry = ST_ENTRY (actor);
 464   StEntryPrivate *priv = entry->priv;
 465   GdkKeymap *keymap;
 466 
 467   /* remove the hint if visible */
 468   if (priv->hint && priv->hint_visible)
 469     {
 470       priv->hint_visible = FALSE;
 471 
 472       clutter_text_set_text (text, "");
 473     }
 474 
 475   keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
 476   keymap_state_changed (keymap, entry);
 477   g_signal_connect (keymap, "state-changed",
 478                     G_CALLBACK (keymap_state_changed), entry);
 479 
 480   st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "indeterminate");
 481   st_widget_add_style_pseudo_class (ST_WIDGET (actor), "focus");
 482   clutter_text_set_cursor_visible (text, TRUE);
 483 }
 484 
 485 static void
 486 clutter_text_focus_out_cb (ClutterText  *text,
 487                            ClutterActor *actor)
 488 {
 489   StEntry *entry = ST_ENTRY (actor);
 490   StEntryPrivate *priv = entry->priv;
 491   GdkKeymap *keymap;
 492 
 493   st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "focus");
 494 
 495   /* add a hint if the entry is empty */
 496   if (priv->hint && !strcmp (clutter_text_get_text (text), ""))
 497     {
 498       priv->hint_visible = TRUE;
 499 
 500       clutter_text_set_text (text, priv->hint);
 501       st_widget_add_style_pseudo_class (ST_WIDGET (actor), "indeterminate");
 502     }
 503   clutter_text_set_cursor_visible (text, FALSE);
 504   remove_capslock_feedback (entry);
 505 
 506   keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
 507   g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry);
 508 }
 509 
 510 static void
 511 clutter_text_password_char_cb (GObject    *object,
 512                                GParamSpec *pspec,
 513                                gpointer    user_data)
 514 {
 515   StEntry *entry = ST_ENTRY (user_data);
 516 
 517   if (clutter_text_get_password_char (CLUTTER_TEXT (entry->priv->entry)) == 0)
 518     remove_capslock_feedback (entry);
 519 }
 520 
 521 static void
 522 st_entry_clipboard_callback (StClipboard *clipboard,
 523                              const gchar *text,
 524                              gpointer     data)
 525 {
 526   ClutterText *ctext = (ClutterText*)((StEntry *) data)->priv->entry;
 527   gint cursor_pos;
 528 
 529   if (!text)
 530     return;
 531 
 532   /* delete the current selection before pasting */
 533   clutter_text_delete_selection (ctext);
 534 
 535   /* "paste" the clipboard text into the entry */
 536   cursor_pos = clutter_text_get_cursor_position (ctext);
 537   clutter_text_insert_text (ctext, text, cursor_pos);
 538 }
 539 
 540 static gboolean
 541 st_entry_key_press_event (ClutterActor    *actor,
 542                           ClutterKeyEvent *event)
 543 {
 544   StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
 545 
 546   /* This is expected to handle events that were emitted for the inner
 547      ClutterText. They only reach this function if the ClutterText
 548      didn't handle them */
 549 
 550   /* paste */
 551   if ((event->modifier_state & CLUTTER_CONTROL_MASK)
 552       && event->keyval == CLUTTER_v)
 553     {
 554       StClipboard *clipboard;
 555 
 556       clipboard = st_clipboard_get_default ();
 557 
 558       st_clipboard_get_text (clipboard, st_entry_clipboard_callback, actor);
 559 
 560       return TRUE;
 561     }
 562 
 563   /* copy */
 564   if ((event->modifier_state & CLUTTER_CONTROL_MASK)
 565       && event->keyval == CLUTTER_c)
 566     {
 567       StClipboard *clipboard;
 568       gchar *text;
 569 
 570       clipboard = st_clipboard_get_default ();
 571 
 572       text = clutter_text_get_selection ((ClutterText*) priv->entry);
 573 
 574       if (text && strlen (text))
 575         st_clipboard_set_text (clipboard, text);
 576 
 577       return TRUE;
 578     }
 579 
 580 
 581   /* cut */
 582   if ((event->modifier_state & CLUTTER_CONTROL_MASK)
 583       && event->keyval == CLUTTER_x)
 584     {
 585       StClipboard *clipboard;
 586       gchar *text;
 587 
 588       clipboard = st_clipboard_get_default ();
 589 
 590       text = clutter_text_get_selection ((ClutterText*) priv->entry);
 591 
 592       if (text && strlen (text))
 593         {
 594           st_clipboard_set_text (clipboard, text);
 595 
 596           /* now delete the text */
 597           clutter_text_delete_selection ((ClutterText *) priv->entry);
 598         }
 599 
 600       return TRUE;
 601     }
 602 
 603   return CLUTTER_ACTOR_CLASS (st_entry_parent_class)->key_press_event (actor, event);
 604 }
 605 
 606 static void
 607 st_entry_key_focus_in (ClutterActor *actor)
 608 {
 609   StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
 610 
 611   /* We never want key focus. The ClutterText should be given first
 612      pass for all key events */
 613   clutter_actor_grab_key_focus (priv->entry);
 614 }
 615 
 616 static void
 617 st_entry_class_init (StEntryClass *klass)
 618 {
 619   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 620   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
 621   StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
 622   GParamSpec *pspec;
 623 
 624   g_type_class_add_private (klass, sizeof (StEntryPrivate));
 625 
 626   gobject_class->set_property = st_entry_set_property;
 627   gobject_class->get_property = st_entry_get_property;
 628   gobject_class->finalize = st_entry_finalize;
 629   gobject_class->dispose = st_entry_dispose;
 630 
 631   actor_class->get_preferred_width = st_entry_get_preferred_width;
 632   actor_class->get_preferred_height = st_entry_get_preferred_height;
 633   actor_class->allocate = st_entry_allocate;
 634 
 635   actor_class->key_press_event = st_entry_key_press_event;
 636   actor_class->key_focus_in = st_entry_key_focus_in;
 637 
 638   widget_class->style_changed = st_entry_style_changed;
 639   widget_class->navigate_focus = st_entry_navigate_focus;
 640   widget_class->get_accessible_type = st_entry_accessible_get_type;
 641 
 642   pspec = g_param_spec_object ("clutter-text",
 643 			       "Clutter Text",
 644 			       "Internal ClutterText actor",
 645 			       CLUTTER_TYPE_TEXT,
 646 			       G_PARAM_READABLE);
 647   g_object_class_install_property (gobject_class, PROP_CLUTTER_TEXT, pspec);
 648 
 649   pspec = g_param_spec_string ("hint-text",
 650                                "Hint Text",
 651                                "Text to display when the entry is not focused "
 652                                "and the text property is empty",
 653                                NULL, G_PARAM_READWRITE);
 654   g_object_class_install_property (gobject_class, PROP_HINT_TEXT, pspec);
 655 
 656   pspec = g_param_spec_string ("text",
 657                                "Text",
 658                                "Text of the entry",
 659                                NULL, G_PARAM_READWRITE);
 660   g_object_class_install_property (gobject_class, PROP_TEXT, pspec);
 661 
 662   /* signals */
 663   /**
 664    * StEntry::primary-icon-clicked:
 665    *
 666    * Emitted when the primary icon is clicked
 667    */
 668   entry_signals[PRIMARY_ICON_CLICKED] =
 669     g_signal_new ("primary-icon-clicked",
 670                   G_TYPE_FROM_CLASS (klass),
 671                   G_SIGNAL_RUN_LAST,
 672                   G_STRUCT_OFFSET (StEntryClass, primary_icon_clicked),
 673                   NULL, NULL, NULL,
 674                   G_TYPE_NONE, 0);
 675   /**
 676    * StEntry::secondary-icon-clicked:
 677    *
 678    * Emitted when the secondary icon is clicked
 679    */
 680   entry_signals[SECONDARY_ICON_CLICKED] =
 681     g_signal_new ("secondary-icon-clicked",
 682                   G_TYPE_FROM_CLASS (klass),
 683                   G_SIGNAL_RUN_LAST,
 684                   G_STRUCT_OFFSET (StEntryClass, secondary_icon_clicked),
 685                   NULL, NULL, NULL,
 686                   G_TYPE_NONE, 0);
 687 }
 688 
 689 static void
 690 st_entry_init (StEntry *entry)
 691 {
 692   StEntryPrivate *priv;
 693 
 694   priv = entry->priv = ST_ENTRY_GET_PRIVATE (entry);
 695 
 696   priv->entry = g_object_new (ST_TYPE_IM_TEXT,
 697                               "line-alignment", PANGO_ALIGN_LEFT,
 698                               "editable", TRUE,
 699                               "reactive", TRUE,
 700                               "single-line-mode", TRUE,
 701                               NULL);
 702 
 703   g_signal_connect (priv->entry, "key-focus-in",
 704                     G_CALLBACK (clutter_text_focus_in_cb), entry);
 705 
 706   g_signal_connect (priv->entry, "key-focus-out",
 707                     G_CALLBACK (clutter_text_focus_out_cb), entry);
 708 
 709   g_signal_connect (priv->entry, "notify::password-char",
 710                     G_CALLBACK (clutter_text_password_char_cb), entry);
 711 
 712   priv->spacing = 6.0f;
 713 
 714   clutter_actor_add_child (CLUTTER_ACTOR (entry), priv->entry);
 715   clutter_actor_set_reactive ((ClutterActor *) entry, TRUE);
 716 
 717   /* set cursor hidden until we receive focus */
 718   clutter_text_set_cursor_visible ((ClutterText *) priv->entry, FALSE);
 719 }
 720 
 721 /**
 722  * st_entry_new:
 723  * @text: text to set the entry to
 724  *
 725  * Create a new #StEntry with the specified entry
 726  *
 727  * Returns: a new #StEntry
 728  */
 729 StWidget *
 730 st_entry_new (const gchar *text)
 731 {
 732   StWidget *entry;
 733 
 734   /* add the entry to the stage, but don't allow it to be visible */
 735   entry = g_object_new (ST_TYPE_ENTRY,
 736                         "text", text,
 737                         NULL);
 738 
 739   return entry;
 740 }
 741 
 742 /**
 743  * st_entry_get_text:
 744  * @entry: a #StEntry
 745  *
 746  * Get the text displayed on the entry
 747  *
 748  * Returns: the text for the entry. This must not be freed by the application
 749  */
 750 const gchar *
 751 st_entry_get_text (StEntry *entry)
 752 {
 753   g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
 754 
 755   if (entry->priv->hint_visible)
 756     return "";
 757   else
 758     return clutter_text_get_text (CLUTTER_TEXT (entry->priv->entry));
 759 }
 760 
 761 /**
 762  * st_entry_set_text:
 763  * @entry: a #StEntry
 764  * @text: (allow-none): text to set the entry to
 765  *
 766  * Sets the text displayed on the entry
 767  */
 768 void
 769 st_entry_set_text (StEntry     *entry,
 770                    const gchar *text)
 771 {
 772   StEntryPrivate *priv;
 773 
 774   g_return_if_fail (ST_IS_ENTRY (entry));
 775 
 776   priv = entry->priv;
 777 
 778   /* set a hint if we are blanking the entry */
 779   if (priv->hint
 780       && text && !strcmp ("", text)
 781       && !HAS_FOCUS (priv->entry))
 782     {
 783       text = priv->hint;
 784       priv->hint_visible = TRUE;
 785       st_widget_add_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
 786     }
 787   else
 788     {
 789       st_widget_remove_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
 790 
 791       priv->hint_visible = FALSE;
 792     }
 793 
 794   clutter_text_set_text (CLUTTER_TEXT (priv->entry), text);
 795 
 796   g_object_notify (G_OBJECT (entry), "text");
 797 }
 798 
 799 /**
 800  * st_entry_get_clutter_text:
 801  * @entry: a #StEntry
 802  *
 803  * Retrieve the internal #ClutterText so that extra parameters can be set
 804  *
 805  * Returns: (transfer none): the #ClutterText used by #StEntry. The entry is
 806  * owned by the #StEntry and should not be unref'ed by the application.
 807  */
 808 ClutterActor*
 809 st_entry_get_clutter_text (StEntry *entry)
 810 {
 811   g_return_val_if_fail (ST_ENTRY (entry), NULL);
 812 
 813   return entry->priv->entry;
 814 }
 815 
 816 /**
 817  * st_entry_set_hint_text:
 818  * @entry: a #StEntry
 819  * @text: (allow-none): text to set as the entry hint
 820  *
 821  * Sets the text to display when the entry is empty and unfocused. When the
 822  * entry is displaying the hint, it has a pseudo class of "indeterminate".
 823  * A value of NULL unsets the hint.
 824  */
 825 void
 826 st_entry_set_hint_text (StEntry     *entry,
 827                         const gchar *text)
 828 {
 829   StEntryPrivate *priv;
 830 
 831   g_return_if_fail (ST_IS_ENTRY (entry));
 832 
 833   priv = entry->priv;
 834 
 835   g_free (priv->hint);
 836 
 837   priv->hint = g_strdup (text);
 838 
 839   if (!strcmp (clutter_text_get_text (CLUTTER_TEXT (priv->entry)), "")
 840       && !HAS_FOCUS (priv->entry))
 841     {
 842       priv->hint_visible = TRUE;
 843 
 844       clutter_text_set_text (CLUTTER_TEXT (priv->entry), priv->hint);
 845       st_widget_add_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
 846     }
 847 }
 848 
 849 /**
 850  * st_entry_get_hint_text:
 851  * @entry: a #StEntry
 852  *
 853  * Gets the text that is displayed when the entry is empty and unfocused
 854  *
 855  * Returns: the current value of the hint property. This string is owned by the
 856  * #StEntry and should not be freed or modified.
 857  */
 858 const gchar *
 859 st_entry_get_hint_text (StEntry *entry)
 860 {
 861   g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
 862 
 863   return entry->priv->hint;
 864 }
 865 
 866 static gboolean
 867 _st_entry_icon_press_cb (ClutterActor       *actor,
 868                          ClutterButtonEvent *event,
 869                          StEntry            *entry)
 870 {
 871   StEntryPrivate *priv = entry->priv;
 872 
 873   if (actor == priv->primary_icon)
 874     g_signal_emit (entry, entry_signals[PRIMARY_ICON_CLICKED], 0);
 875   else
 876     g_signal_emit (entry, entry_signals[SECONDARY_ICON_CLICKED], 0);
 877 
 878   return FALSE;
 879 }
 880 
 881 static void
 882 _st_entry_set_icon (StEntry       *entry,
 883                     ClutterActor **icon,
 884                     ClutterActor  *new_icon)
 885 {
 886   if (*icon)
 887     {
 888       g_signal_handlers_disconnect_by_func (*icon,
 889                                             _st_entry_icon_press_cb,
 890                                             entry);
 891       clutter_actor_remove_child (CLUTTER_ACTOR (entry), *icon);
 892       *icon = NULL;
 893     }
 894 
 895   if (new_icon)
 896     {
 897       *icon = g_object_ref (new_icon);
 898 
 899       clutter_actor_set_reactive (*icon, TRUE);
 900       clutter_actor_add_child (CLUTTER_ACTOR (entry), *icon);
 901       g_signal_connect (*icon, "button-release-event",
 902                         G_CALLBACK (_st_entry_icon_press_cb), entry);
 903     }
 904 
 905   clutter_actor_queue_relayout (CLUTTER_ACTOR (entry));
 906 }
 907 
 908 /**
 909  * st_entry_set_primary_icon:
 910  * @entry: a #StEntry
 911  * @icon: (allow-none): a #ClutterActor
 912  *
 913  * Set the primary icon of the entry to @icon
 914  */
 915 void
 916 st_entry_set_primary_icon (StEntry      *entry,
 917                            ClutterActor *icon)
 918 {
 919   StEntryPrivate *priv;
 920 
 921   g_return_if_fail (ST_IS_ENTRY (entry));
 922 
 923   priv = entry->priv;
 924 
 925   _st_entry_set_icon (entry, &priv->primary_icon, icon);
 926 }
 927 
 928 /**
 929  * st_entry_set_secondary_icon:
 930  * @entry: a #StEntry
 931  * @icon: (allow-none): an #ClutterActor
 932  *
 933  * Set the secondary icon of the entry to @icon
 934  */
 935 void
 936 st_entry_set_secondary_icon (StEntry      *entry,
 937                              ClutterActor *icon)
 938 {
 939   StEntryPrivate *priv;
 940 
 941   g_return_if_fail (ST_IS_ENTRY (entry));
 942 
 943   priv = entry->priv;
 944 
 945   _st_entry_set_icon (entry, &priv->secondary_icon, icon);
 946 }
 947 
 948 /******************************************************************************/
 949 /*************************** ACCESSIBILITY SUPPORT ****************************/
 950 /******************************************************************************/
 951 
 952 #define ST_TYPE_ENTRY_ACCESSIBLE         (st_entry_accessible_get_type ())
 953 #define ST_ENTRY_ACCESSIBLE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessible))
 954 #define ST_IS_ENTRY_ACCESSIBLE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), ST_TYPE_ENTRY_ACCESSIBLE))
 955 #define ST_ENTRY_ACCESSIBLE_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c),    ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
 956 #define ST_IS_ENTRY_ACCESSIBLE_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c),    ST_TYPE_ENTRY_ACCESSIBLE))
 957 #define ST_ENTRY_ACCESSIBLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o),  ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
 958 
 959 typedef struct _StEntryAccessible  StEntryAccessible;
 960 typedef struct _StEntryAccessibleClass  StEntryAccessibleClass;
 961 
 962 struct _StEntryAccessible
 963 {
 964   StWidgetAccessible parent;
 965 };
 966 
 967 struct _StEntryAccessibleClass
 968 {
 969   StWidgetAccessibleClass parent_class;
 970 };
 971 
 972 G_DEFINE_TYPE (StEntryAccessible, st_entry_accessible, ST_TYPE_WIDGET_ACCESSIBLE)
 973 
 974 static void
 975 st_entry_accessible_init (StEntryAccessible *self)
 976 {
 977   /* initialization done on AtkObject->initialize */
 978 }
 979 
 980 static void
 981 st_entry_accessible_initialize (AtkObject *obj,
 982                                 gpointer   data)
 983 {
 984   ATK_OBJECT_CLASS (st_entry_accessible_parent_class)->initialize (obj, data);
 985 
 986   /* StEntry is behaving as a StImText container */
 987   atk_object_set_role (obj, ATK_ROLE_PANEL);
 988 }
 989 
 990 static gint
 991 st_entry_accessible_get_n_children (AtkObject *obj)
 992 {
 993   StEntry *entry = NULL;
 994 
 995   g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), 0);
 996 
 997   entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
 998 
 999   if (entry == NULL)
1000     return 0;
1001 
1002   if (entry->priv->entry == NULL)
1003     return 0;
1004   else
1005     return 1;
1006 }
1007 
1008 static AtkObject*
1009 st_entry_accessible_ref_child (AtkObject *obj,
1010                                gint       i)
1011 {
1012   StEntry *entry = NULL;
1013   AtkObject *result = NULL;
1014 
1015   g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), NULL);
1016   g_return_val_if_fail (i == 0, NULL);
1017 
1018   entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
1019 
1020   if (entry == NULL)
1021     return NULL;
1022 
1023   if (entry->priv->entry == NULL)
1024     return NULL;
1025 
1026   result = clutter_actor_get_accessible (entry->priv->entry);
1027   g_object_ref (result);
1028 
1029   return result;
1030 }
1031 
1032 
1033 static void
1034 st_entry_accessible_class_init (StEntryAccessibleClass *klass)
1035 {
1036   AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
1037 
1038   atk_class->initialize = st_entry_accessible_initialize;
1039   atk_class->get_n_children = st_entry_accessible_get_n_children;
1040   atk_class->ref_child= st_entry_accessible_ref_child;
1041 }