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 #include <gtk/gtk.h>
57
58 #include <clutter/clutter.h>
59
60 #include "st-entry.h"
61
62 #include "st-im-text.h"
63 #include "st-icon.h"
64 #include "st-widget.h"
65 #include "st-texture-cache.h"
66 #include "st-clipboard.h"
67 #include "st-private.h"
68
69 #include "st-widget-accessible.h"
70
71 #define HAS_FOCUS(actor) (clutter_actor_get_stage (actor) && clutter_stage_get_key_focus ((ClutterStage *) clutter_actor_get_stage (actor)) == actor)
72
73
74 /* properties */
75 enum
76 {
77 PROP_0,
78
79 PROP_CLUTTER_TEXT,
80 PROP_HINT_TEXT,
81 PROP_TEXT,
82 };
83
84 /* signals */
85 enum
86 {
87 PRIMARY_ICON_CLICKED,
88 SECONDARY_ICON_CLICKED,
89
90 LAST_SIGNAL
91 };
92
93 #define ST_ENTRY_PRIV(x) ((StEntry *) x)->priv
94
95 static void st_entry_check_cursor_blink (StEntry *entry);
96 static void st_entry_pend_cursor_blink (StEntry *entry);
97 static void st_entry_reset_blink_time (StEntry *entry);
98
99 struct _StEntryPrivate
100 {
101 ClutterActor *entry;
102 gchar *hint;
103
104 ClutterActor *primary_icon;
105 ClutterActor *secondary_icon;
106
107 gfloat spacing;
108
109 gboolean hint_visible;
110 gboolean capslock_warning_shown;
111 guint blink_time;
112 guint blink_timeout;
113 gboolean cursor_visible;
114 };
115
116 static guint entry_signals[LAST_SIGNAL] = { 0, };
117
118 G_DEFINE_TYPE_WITH_PRIVATE (StEntry, st_entry, ST_TYPE_WIDGET);
119
120 static GType st_entry_accessible_get_type (void) G_GNUC_CONST;
121
122 static void
st_entry_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)123 st_entry_set_property (GObject *gobject,
124 guint prop_id,
125 const GValue *value,
126 GParamSpec *pspec)
127 {
128 StEntry *entry = ST_ENTRY (gobject);
129
130 switch (prop_id)
131 {
132 case PROP_HINT_TEXT:
133 st_entry_set_hint_text (entry, g_value_get_string (value));
134 break;
135
136 case PROP_TEXT:
137 st_entry_set_text (entry, g_value_get_string (value));
138 break;
139
140 default:
141 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
142 break;
143 }
144 }
145
146 static void
st_entry_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)147 st_entry_get_property (GObject *gobject,
148 guint prop_id,
149 GValue *value,
150 GParamSpec *pspec)
151 {
152 StEntryPrivate *priv = ST_ENTRY_PRIV (gobject);
153
154 switch (prop_id)
155 {
156 case PROP_CLUTTER_TEXT:
157 g_value_set_object (value, priv->entry);
158 break;
159
160 case PROP_HINT_TEXT:
161 g_value_set_string (value, priv->hint);
162 break;
163
164 case PROP_TEXT:
165 g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->entry)));
166 break;
167
168 default:
169 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
170 break;
171 }
172 }
173
174 static void
show_capslock_feedback(StEntry * entry)175 show_capslock_feedback (StEntry *entry)
176 {
177 if (entry->priv->secondary_icon == NULL)
178 {
179 ClutterActor *icon = g_object_new (ST_TYPE_ICON,
180 "style-class", "capslock-warning",
181 "icon-type", ST_ICON_SYMBOLIC,
182 "icon-name", "dialog-warning",
183 NULL);
184
185 st_entry_set_secondary_icon (entry, icon);
186 entry->priv->capslock_warning_shown = TRUE;
187 }
188 }
189
190 static void
remove_capslock_feedback(StEntry * entry)191 remove_capslock_feedback (StEntry *entry)
192 {
193 if (entry->priv->capslock_warning_shown)
194 {
195 st_entry_set_secondary_icon (entry, NULL);
196 entry->priv->capslock_warning_shown = FALSE;
197 }
198 }
199
200 static void
keymap_state_changed(GdkKeymap * keymap,gpointer user_data)201 keymap_state_changed (GdkKeymap *keymap,
202 gpointer user_data)
203 {
204 StEntry *entry = ST_ENTRY (user_data);
205
206 if (clutter_text_get_password_char (CLUTTER_TEXT (entry->priv->entry)) != 0)
207 {
208 if (gdk_keymap_get_caps_lock_state (keymap))
209 show_capslock_feedback (entry);
210 else
211 remove_capslock_feedback (entry);
212 }
213 }
214
215 static void
st_entry_dispose(GObject * object)216 st_entry_dispose (GObject *object)
217 {
218 StEntry *entry = ST_ENTRY (object);
219 StEntryPrivate *priv = entry->priv;
220 GdkKeymap *keymap;
221
222 if (priv->blink_timeout)
223 {
224 g_source_remove (priv->blink_timeout);
225 priv->blink_timeout = 0;
226 }
227
228 keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
229 g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry);
230
231 G_OBJECT_CLASS (st_entry_parent_class)->dispose (object);
232 }
233
234 static void
st_entry_finalize(GObject * object)235 st_entry_finalize (GObject *object)
236 {
237 StEntryPrivate *priv = ST_ENTRY_PRIV (object);
238
239 g_free (priv->hint);
240 priv->hint = NULL;
241
242 G_OBJECT_CLASS (st_entry_parent_class)->finalize (object);
243 }
244
245 static void
st_entry_style_changed(StWidget * self)246 st_entry_style_changed (StWidget *self)
247 {
248 StEntryPrivate *priv = ST_ENTRY_PRIV (self);
249 StThemeNode *theme_node;
250 ClutterColor color;
251 gdouble size;
252
253 theme_node = st_widget_get_theme_node (self);
254
255 if (st_theme_node_lookup_length (theme_node, "caret-size", TRUE, &size))
256 clutter_text_set_cursor_size (CLUTTER_TEXT (priv->entry), (int)(.5 + size));
257
258 if (st_theme_node_lookup_color (theme_node, "caret-color", TRUE, &color))
259 clutter_text_set_cursor_color (CLUTTER_TEXT (priv->entry), &color);
260
261 if (st_theme_node_lookup_color (theme_node, "selection-background-color", TRUE, &color))
262 clutter_text_set_selection_color (CLUTTER_TEXT (priv->entry), &color);
263
264 if (st_theme_node_lookup_color (theme_node, "selected-color", TRUE, &color))
265 clutter_text_set_selected_text_color (CLUTTER_TEXT (priv->entry), &color);
266
267 _st_set_text_from_style ((ClutterText *)priv->entry, theme_node);
268
269 ST_WIDGET_CLASS (st_entry_parent_class)->style_changed (self);
270 }
271
272 static gboolean
st_entry_navigate_focus(StWidget * widget,ClutterActor * from,GtkDirectionType direction)273 st_entry_navigate_focus (StWidget *widget,
274 ClutterActor *from,
275 GtkDirectionType direction)
276 {
277 StEntryPrivate *priv = ST_ENTRY_PRIV (widget);
278
279 /* This is basically the same as st_widget_real_navigate_focus(),
280 * except that widget is behaving as a proxy for priv->entry (which
281 * isn't an StWidget and so has no can-focus flag of its own).
282 */
283
284 if (from == priv->entry)
285 return FALSE;
286 else if (st_widget_get_can_focus (widget))
287 {
288 clutter_actor_grab_key_focus (priv->entry);
289 return TRUE;
290 }
291 else
292 return FALSE;
293 }
294
295 static void
st_entry_get_preferred_width(ClutterActor * actor,gfloat for_height,gfloat * min_width_p,gfloat * natural_width_p)296 st_entry_get_preferred_width (ClutterActor *actor,
297 gfloat for_height,
298 gfloat *min_width_p,
299 gfloat *natural_width_p)
300 {
301 StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
302 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
303 gfloat icon_w;
304
305 st_theme_node_adjust_for_height (theme_node, &for_height);
306
307 clutter_actor_get_preferred_width (priv->entry, for_height,
308 min_width_p,
309 natural_width_p);
310
311 if (priv->primary_icon)
312 {
313 clutter_actor_get_preferred_width (priv->primary_icon, -1, NULL, &icon_w);
314
315 if (min_width_p)
316 *min_width_p += icon_w + priv->spacing;
317
318 if (natural_width_p)
319 *natural_width_p += icon_w + priv->spacing;
320 }
321
322 if (priv->secondary_icon)
323 {
324 clutter_actor_get_preferred_width (priv->secondary_icon,
325 -1, NULL, &icon_w);
326
327 if (min_width_p)
328 *min_width_p += icon_w + priv->spacing;
329
330 if (natural_width_p)
331 *natural_width_p += icon_w + priv->spacing;
332 }
333
334 st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p);
335 }
336
337 static void
st_entry_get_preferred_height(ClutterActor * actor,gfloat for_width,gfloat * min_height_p,gfloat * natural_height_p)338 st_entry_get_preferred_height (ClutterActor *actor,
339 gfloat for_width,
340 gfloat *min_height_p,
341 gfloat *natural_height_p)
342 {
343 StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
344 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
345 gfloat icon_h;
346
347 st_theme_node_adjust_for_width (theme_node, &for_width);
348
349 clutter_actor_get_preferred_height (priv->entry, for_width,
350 min_height_p,
351 natural_height_p);
352
353 if (priv->primary_icon)
354 {
355 clutter_actor_get_preferred_height (priv->primary_icon,
356 -1, NULL, &icon_h);
357
358 if (min_height_p && icon_h > *min_height_p)
359 *min_height_p = icon_h;
360
361 if (natural_height_p && icon_h > *natural_height_p)
362 *natural_height_p = icon_h;
363 }
364
365 if (priv->secondary_icon)
366 {
367 clutter_actor_get_preferred_height (priv->secondary_icon,
368 -1, NULL, &icon_h);
369
370 if (min_height_p && icon_h > *min_height_p)
371 *min_height_p = icon_h;
372
373 if (natural_height_p && icon_h > *natural_height_p)
374 *natural_height_p = icon_h;
375 }
376
377 st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p);
378 }
379
380 static void
st_entry_allocate(ClutterActor * actor,const ClutterActorBox * box,ClutterAllocationFlags flags)381 st_entry_allocate (ClutterActor *actor,
382 const ClutterActorBox *box,
383 ClutterAllocationFlags flags)
384 {
385 StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
386 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
387 ClutterActorBox content_box, child_box, icon_box;
388 gfloat icon_w, icon_h;
389 gfloat entry_h, min_h, pref_h, avail_h;
390
391 clutter_actor_set_allocation (actor, box, flags);
392
393 st_theme_node_get_content_box (theme_node, box, &content_box);
394
395 avail_h = content_box.y2 - content_box.y1;
396
397 child_box.x1 = content_box.x1;
398 child_box.x2 = content_box.x2;
399
400 if (priv->primary_icon)
401 {
402 clutter_actor_get_preferred_width (priv->primary_icon,
403 -1, NULL, &icon_w);
404 clutter_actor_get_preferred_height (priv->primary_icon,
405 -1, NULL, &icon_h);
406
407 icon_box.x1 = content_box.x1;
408 icon_box.x2 = icon_box.x1 + icon_w;
409
410 icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
411 icon_box.y2 = icon_box.y1 + icon_h;
412
413 clutter_actor_allocate (priv->primary_icon,
414 &icon_box,
415 flags);
416
417 /* reduce the size for the entry */
418 child_box.x1 = MIN (child_box.x2, child_box.x1 + icon_w + priv->spacing);
419 }
420
421 if (priv->secondary_icon)
422 {
423 clutter_actor_get_preferred_width (priv->secondary_icon,
424 -1, NULL, &icon_w);
425 clutter_actor_get_preferred_height (priv->secondary_icon,
426 -1, NULL, &icon_h);
427
428 icon_box.x2 = content_box.x2;
429 icon_box.x1 = icon_box.x2 - icon_w;
430
431 icon_box.y1 = (int) (content_box.y1 + avail_h / 2 - icon_h / 2);
432 icon_box.y2 = icon_box.y1 + icon_h;
433
434 clutter_actor_allocate (priv->secondary_icon,
435 &icon_box,
436 flags);
437
438 /* reduce the size for the entry */
439 child_box.x2 = MAX (child_box.x1, child_box.x2 - icon_w - priv->spacing);
440 }
441
442 clutter_actor_get_preferred_height (priv->entry, child_box.x2 - child_box.x1,
443 &min_h, &pref_h);
444
445 entry_h = CLAMP (pref_h, min_h, avail_h);
446
447 child_box.y1 = (int) (content_box.y1 + avail_h / 2 - entry_h / 2);
448 child_box.y2 = child_box.y1 + entry_h;
449
450 clutter_actor_allocate (priv->entry, &child_box, flags);
451 }
452
453 #define CURSOR_ON_MULTIPLIER 2
454 #define CURSOR_OFF_MULTIPLIER 1
455 #define CURSOR_PEND_MULTIPLIER 3
456 #define CURSOR_DIVIDER 3
457
458 static gboolean
cursor_blinks(StEntry * entry)459 cursor_blinks (StEntry *entry)
460 {
461 StEntryPrivate *priv = entry->priv;
462
463 if (clutter_actor_has_key_focus (CLUTTER_ACTOR (priv->entry)) &&
464 clutter_text_get_editable (CLUTTER_TEXT (priv->entry)) &&
465 clutter_text_get_selection_bound (CLUTTER_TEXT (priv->entry)) == clutter_text_get_cursor_position (CLUTTER_TEXT (priv->entry)))
466 {
467 gboolean blink;
468 g_object_get (gtk_settings_get_default (), "gtk-cursor-blink", &blink, NULL);
469
470 return blink;
471 }
472 else
473 return FALSE;
474 }
475
476 static gint
get_cursor_time(StEntry * entry)477 get_cursor_time (StEntry *entry)
478 {
479 gint time;
480 g_object_get (gtk_settings_get_default (), "gtk-cursor-blink-time", &time, NULL);
481
482 return time;
483 }
484
485 static gint
get_cursor_blink_timeout(StEntry * entry)486 get_cursor_blink_timeout (StEntry *entry)
487 {
488 gint timeout;
489 g_object_get (gtk_settings_get_default (), "gtk-cursor-blink-timeout", &timeout, NULL);
490
491 return timeout;
492 }
493
494 static void
show_cursor(StEntry * entry)495 show_cursor (StEntry *entry)
496 {
497 StEntryPrivate *priv = entry->priv;
498
499 if (!priv->cursor_visible)
500 {
501 priv->cursor_visible = TRUE;
502 clutter_text_set_cursor_visible (CLUTTER_TEXT (priv->entry), TRUE);
503 }
504 }
505
506 static void
hide_cursor(StEntry * entry)507 hide_cursor (StEntry *entry)
508 {
509 StEntryPrivate *priv = entry->priv;
510
511 if (priv->cursor_visible)
512 {
513 priv->cursor_visible = FALSE;
514 clutter_text_set_cursor_visible (CLUTTER_TEXT (priv->entry), FALSE);
515 }
516 }
517
518 /*
519 * Blink!
520 */
521 static gint
blink_cb(gpointer data)522 blink_cb (gpointer data)
523 {
524 StEntry *entry;
525 StEntryPrivate *priv;
526 guint blink_timeout;
527
528 entry = ST_ENTRY (data);
529 priv = entry->priv;
530
531 if (!clutter_actor_has_key_focus (priv->entry))
532 {
533 g_warning ("StEntry - did not receive key-focus-out event. If you\n"
534 "connect a handler to this signal, it must return\n"
535 "FALSE so the StEntry gets the event as well");
536
537 st_entry_check_cursor_blink (entry);
538
539 return FALSE;
540 }
541
542 if (clutter_text_get_selection_bound (CLUTTER_TEXT (priv->entry)) != clutter_text_get_cursor_position (CLUTTER_TEXT (priv->entry)))
543 {
544 st_entry_check_cursor_blink (entry);
545 return FALSE;
546 }
547
548 blink_timeout = get_cursor_blink_timeout (entry);
549 if (priv->blink_time > 1000 * blink_timeout &&
550 blink_timeout < G_MAXINT/1000)
551 {
552 /* we've blinked enough without the user doing anything, stop blinking */
553 show_cursor (entry);
554 priv->blink_timeout = 0;
555 }
556 else if (priv->cursor_visible)
557 {
558 hide_cursor (entry);
559 priv->blink_timeout = clutter_threads_add_timeout (get_cursor_time (entry) * CURSOR_OFF_MULTIPLIER / CURSOR_DIVIDER,
560 blink_cb,
561 entry);
562 }
563 else
564 {
565 show_cursor (entry);
566 priv->blink_time += get_cursor_time (entry);
567 priv->blink_timeout = clutter_threads_add_timeout (get_cursor_time (entry) * CURSOR_ON_MULTIPLIER / CURSOR_DIVIDER,
568 blink_cb,
569 entry);
570 }
571
572 /* Remove ourselves */
573 return FALSE;
574 }
575
576 static void
st_entry_check_cursor_blink(StEntry * entry)577 st_entry_check_cursor_blink (StEntry *entry)
578 {
579 StEntryPrivate *priv = entry->priv;
580
581 if (cursor_blinks (entry))
582 {
583 if (!priv->blink_timeout)
584 {
585 show_cursor (entry);
586 priv->blink_timeout = clutter_threads_add_timeout (get_cursor_time (entry) * CURSOR_ON_MULTIPLIER / CURSOR_DIVIDER,
587 blink_cb,
588 entry);
589 }
590 }
591 else
592 {
593 if (priv->blink_timeout)
594 {
595 g_source_remove (priv->blink_timeout);
596 priv->blink_timeout = 0;
597 }
598
599 show_cursor (entry);
600 }
601 }
602
603 static void
st_entry_pend_cursor_blink(StEntry * entry)604 st_entry_pend_cursor_blink (StEntry *entry)
605 {
606 StEntryPrivate *priv = entry->priv;
607
608 if (cursor_blinks (entry))
609 {
610 if (priv->blink_timeout != 0)
611 g_source_remove (priv->blink_timeout);
612
613 priv->blink_timeout = clutter_threads_add_timeout (get_cursor_time (entry) * CURSOR_PEND_MULTIPLIER / CURSOR_DIVIDER,
614 blink_cb,
615 entry);
616 show_cursor (entry);
617 }
618 }
619
620 static void
st_entry_reset_blink_time(StEntry * entry)621 st_entry_reset_blink_time (StEntry *entry)
622 {
623 StEntryPrivate *priv = entry->priv;
624
625 priv->blink_time = 0;
626 }
627
628 static void
clutter_text_focus_in_cb(ClutterText * text,ClutterActor * actor)629 clutter_text_focus_in_cb (ClutterText *text,
630 ClutterActor *actor)
631 {
632 StEntry *entry = ST_ENTRY (actor);
633 StEntryPrivate *priv = entry->priv;
634 GdkKeymap *keymap;
635
636 /* remove the hint if visible */
637 if (priv->hint && priv->hint_visible)
638 {
639 priv->hint_visible = FALSE;
640
641 clutter_text_set_text (text, "");
642 }
643
644 keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
645 keymap_state_changed (keymap, entry);
646 g_signal_connect (keymap, "state-changed",
647 G_CALLBACK (keymap_state_changed), entry);
648
649 st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "indeterminate");
650 st_widget_add_style_pseudo_class (ST_WIDGET (actor), "focus");
651
652 st_entry_reset_blink_time (entry);
653 st_entry_check_cursor_blink (entry);
654 }
655
656 static void
clutter_text_focus_out_cb(ClutterText * text,ClutterActor * actor)657 clutter_text_focus_out_cb (ClutterText *text,
658 ClutterActor *actor)
659 {
660 StEntry *entry = ST_ENTRY (actor);
661 StEntryPrivate *priv = entry->priv;
662 GdkKeymap *keymap;
663
664 st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "focus");
665
666 /* add a hint if the entry is empty */
667 if (priv->hint && !strcmp (clutter_text_get_text (text), ""))
668 {
669 priv->hint_visible = TRUE;
670
671 clutter_text_set_text (text, priv->hint);
672 st_widget_add_style_pseudo_class (ST_WIDGET (actor), "indeterminate");
673 }
674 st_entry_check_cursor_blink (entry);
675 remove_capslock_feedback (entry);
676
677 keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
678 g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry);
679 }
680
681 static void
clutter_text_password_char_cb(GObject * object,GParamSpec * pspec,gpointer user_data)682 clutter_text_password_char_cb (GObject *object,
683 GParamSpec *pspec,
684 gpointer user_data)
685 {
686 StEntry *entry = ST_ENTRY (user_data);
687
688 if (clutter_text_get_password_char (CLUTTER_TEXT (entry->priv->entry)) == 0)
689 remove_capslock_feedback (entry);
690 }
691
692 static void
clutter_text_selection_bound_cb(GObject * object,GParamSpec * pspec,gpointer user_data)693 clutter_text_selection_bound_cb (GObject *object,
694 GParamSpec *pspec,
695 gpointer user_data)
696 {
697 StEntry *entry = ST_ENTRY (user_data);
698
699 st_entry_reset_blink_time (entry);
700 st_entry_pend_cursor_blink (entry);
701 }
702
703 static void
clutter_text_cursor_changed(ClutterText * text,ClutterActor * actor)704 clutter_text_cursor_changed (ClutterText *text, ClutterActor *actor)
705 {
706 st_entry_reset_blink_time (ST_ENTRY (actor));
707 st_entry_pend_cursor_blink (ST_ENTRY (actor));
708 }
709
710 static void
st_entry_clipboard_callback(StClipboard * clipboard,const gchar * text,gpointer data)711 st_entry_clipboard_callback (StClipboard *clipboard,
712 const gchar *text,
713 gpointer data)
714 {
715 ClutterText *ctext;
716 gint cursor_pos;
717
718 if (!text)
719 return;
720
721 ctext = (ClutterText*)((StEntry *) data)->priv->entry;
722
723 /* delete the current selection before pasting */
724 clutter_text_delete_selection (ctext);
725
726 /* "paste" the clipboard text into the entry */
727 cursor_pos = clutter_text_get_cursor_position (ctext);
728 clutter_text_insert_text (ctext, text, cursor_pos);
729 }
730
731 static gboolean
clutter_text_button_press_event(ClutterActor * actor,ClutterButtonEvent * event,gpointer user_data)732 clutter_text_button_press_event (ClutterActor *actor,
733 ClutterButtonEvent *event,
734 gpointer user_data)
735 {
736 StEntryPrivate *priv = ST_ENTRY_PRIV (user_data);
737 GtkSettings *settings = gtk_settings_get_default ();
738 gboolean primary_paste_enabled;
739
740 g_object_get (settings,
741 "gtk-enable-primary-paste", &primary_paste_enabled,
742 NULL);
743
744 if (primary_paste_enabled && event->button == 2
745 && clutter_text_get_editable (CLUTTER_TEXT (priv->entry)))
746 {
747 StClipboard *clipboard;
748
749 clipboard = st_clipboard_get_default ();
750
751 /* By the time the clipboard callback is called,
752 * the rest of the signal handlers will have
753 * run, making the text cursor to be in the correct
754 * place.
755 */
756 st_clipboard_get_text (clipboard,
757 ST_CLIPBOARD_TYPE_PRIMARY,
758 st_entry_clipboard_callback,
759 user_data);
760 }
761
762 return FALSE;
763 }
764
765
766 static gboolean
st_entry_key_press_event(ClutterActor * actor,ClutterKeyEvent * event)767 st_entry_key_press_event (ClutterActor *actor,
768 ClutterKeyEvent *event)
769 {
770 StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
771
772 st_entry_reset_blink_time (ST_ENTRY (actor));
773 st_entry_pend_cursor_blink (ST_ENTRY (actor));
774
775 /* This is expected to handle events that were emitted for the inner
776 ClutterText. They only reach this function if the ClutterText
777 didn't handle them */
778
779 /* paste */
780 if ((event->modifier_state & CLUTTER_CONTROL_MASK)
781 && (event->keyval == CLUTTER_KEY_v || event->keyval == CLUTTER_KEY_V))
782 {
783 StClipboard *clipboard;
784
785 clipboard = st_clipboard_get_default ();
786
787 st_clipboard_get_text (clipboard,
788 ST_CLIPBOARD_TYPE_CLIPBOARD,
789 st_entry_clipboard_callback,
790 actor);
791
792 return TRUE;
793 }
794
795 /* copy */
796 if ((event->modifier_state & CLUTTER_CONTROL_MASK)
797 && (event->keyval == CLUTTER_KEY_c || event->keyval == CLUTTER_KEY_C))
798 {
799 StClipboard *clipboard;
800 gchar *text;
801
802 clipboard = st_clipboard_get_default ();
803
804 text = clutter_text_get_selection ((ClutterText*) priv->entry);
805
806 if (text && strlen (text))
807 st_clipboard_set_text (clipboard,
808 ST_CLIPBOARD_TYPE_CLIPBOARD,
809 text);
810
811 return TRUE;
812 }
813
814
815 /* cut */
816 if ((event->modifier_state & CLUTTER_CONTROL_MASK)
817 && (event->keyval == CLUTTER_KEY_x || event->keyval == CLUTTER_KEY_X))
818 {
819 StClipboard *clipboard;
820 gchar *text;
821
822 clipboard = st_clipboard_get_default ();
823
824 text = clutter_text_get_selection ((ClutterText*) priv->entry);
825
826 if (text && strlen (text))
827 {
828 st_clipboard_set_text (clipboard,ST_CLIPBOARD_TYPE_CLIPBOARD, text);
829
830 /* now delete the text */
831 clutter_text_delete_selection ((ClutterText *) priv->entry);
832 }
833
834 return TRUE;
835 }
836
837 return CLUTTER_ACTOR_CLASS (st_entry_parent_class)->key_press_event (actor, event);
838 }
839
840 static void
st_entry_key_focus_in(ClutterActor * actor)841 st_entry_key_focus_in (ClutterActor *actor)
842 {
843 StEntryPrivate *priv = ST_ENTRY_PRIV (actor);
844
845 /* We never want key focus. The ClutterText should be given first
846 pass for all key events */
847 clutter_actor_grab_key_focus (priv->entry);
848 }
849
850 static void
st_entry_class_init(StEntryClass * klass)851 st_entry_class_init (StEntryClass *klass)
852 {
853 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
854 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
855 StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
856 GParamSpec *pspec;
857
858 gobject_class->set_property = st_entry_set_property;
859 gobject_class->get_property = st_entry_get_property;
860 gobject_class->finalize = st_entry_finalize;
861 gobject_class->dispose = st_entry_dispose;
862
863 actor_class->get_preferred_width = st_entry_get_preferred_width;
864 actor_class->get_preferred_height = st_entry_get_preferred_height;
865 actor_class->allocate = st_entry_allocate;
866
867 actor_class->key_press_event = st_entry_key_press_event;
868 actor_class->key_focus_in = st_entry_key_focus_in;
869
870 widget_class->style_changed = st_entry_style_changed;
871 widget_class->navigate_focus = st_entry_navigate_focus;
872 widget_class->get_accessible_type = st_entry_accessible_get_type;
873
874 pspec = g_param_spec_object ("clutter-text",
875 "Clutter Text",
876 "Internal ClutterText actor",
877 CLUTTER_TYPE_TEXT,
878 G_PARAM_READABLE);
879 g_object_class_install_property (gobject_class, PROP_CLUTTER_TEXT, pspec);
880
881 pspec = g_param_spec_string ("hint-text",
882 "Hint Text",
883 "Text to display when the entry is not focused "
884 "and the text property is empty",
885 NULL, G_PARAM_READWRITE);
886 g_object_class_install_property (gobject_class, PROP_HINT_TEXT, pspec);
887
888 pspec = g_param_spec_string ("text",
889 "Text",
890 "Text of the entry",
891 NULL, G_PARAM_READWRITE);
892 g_object_class_install_property (gobject_class, PROP_TEXT, pspec);
893
894 /* signals */
895 /**
896 * StEntry::primary-icon-clicked:
897 *
898 * Emitted when the primary icon is clicked
899 */
900 entry_signals[PRIMARY_ICON_CLICKED] =
901 g_signal_new ("primary-icon-clicked",
902 G_TYPE_FROM_CLASS (klass),
903 G_SIGNAL_RUN_LAST,
904 G_STRUCT_OFFSET (StEntryClass, primary_icon_clicked),
905 NULL, NULL, NULL,
906 G_TYPE_NONE, 0);
907 /**
908 * StEntry::secondary-icon-clicked:
909 *
910 * Emitted when the secondary icon is clicked
911 */
912 entry_signals[SECONDARY_ICON_CLICKED] =
913 g_signal_new ("secondary-icon-clicked",
914 G_TYPE_FROM_CLASS (klass),
915 G_SIGNAL_RUN_LAST,
916 G_STRUCT_OFFSET (StEntryClass, secondary_icon_clicked),
917 NULL, NULL, NULL,
918 G_TYPE_NONE, 0);
919 }
920
921 static void
st_entry_init(StEntry * entry)922 st_entry_init (StEntry *entry)
923 {
924 StEntryPrivate *priv;
925
926 priv = entry->priv = st_entry_get_instance_private (entry);
927
928 priv->entry = g_object_new (ST_TYPE_IM_TEXT,
929 "line-alignment", PANGO_ALIGN_LEFT,
930 "editable", TRUE,
931 "reactive", TRUE,
932 "single-line-mode", TRUE,
933 NULL);
934
935 g_signal_connect (priv->entry, "key-focus-in",
936 G_CALLBACK (clutter_text_focus_in_cb), entry);
937
938 g_signal_connect (priv->entry, "key-focus-out",
939 G_CALLBACK (clutter_text_focus_out_cb), entry);
940
941 g_signal_connect (priv->entry, "notify::password-char",
942 G_CALLBACK (clutter_text_password_char_cb), entry);
943
944 g_signal_connect (priv->entry, "button-press-event",
945 G_CALLBACK (clutter_text_button_press_event), entry);
946
947 g_signal_connect (priv->entry, "notify::selection-bound",
948 G_CALLBACK (clutter_text_selection_bound_cb), entry);
949
950 g_signal_connect (priv->entry, "cursor-changed",
951 G_CALLBACK (clutter_text_cursor_changed), entry);
952
953 priv->spacing = 6.0f;
954
955 clutter_actor_add_child (CLUTTER_ACTOR (entry), priv->entry);
956 clutter_actor_set_reactive ((ClutterActor *) entry, TRUE);
957
958 /* set cursor hidden until we receive focus */
959 priv->blink_timeout = 0;
960 priv->blink_time = 0;
961 priv->cursor_visible = FALSE;
962 clutter_text_set_cursor_visible ((ClutterText *) priv->entry, FALSE);
963 }
964
965 /**
966 * st_entry_new:
967 * @text: text to set the entry to
968 *
969 * Create a new #StEntry with the specified entry
970 *
971 * Returns: a new #StEntry
972 */
973 StWidget *
st_entry_new(const gchar * text)974 st_entry_new (const gchar *text)
975 {
976 StWidget *entry;
977
978 /* add the entry to the stage, but don't allow it to be visible */
979 entry = g_object_new (ST_TYPE_ENTRY,
980 "text", text,
981 NULL);
982
983 return entry;
984 }
985
986 /**
987 * st_entry_get_text:
988 * @entry: a #StEntry
989 *
990 * Get the text displayed on the entry
991 *
992 * Returns: the text for the entry. This must not be freed by the application
993 */
994 const gchar *
st_entry_get_text(StEntry * entry)995 st_entry_get_text (StEntry *entry)
996 {
997 g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
998
999 if (entry->priv->hint_visible)
1000 return "";
1001 else
1002 return clutter_text_get_text (CLUTTER_TEXT (entry->priv->entry));
1003 }
1004
1005 /**
1006 * st_entry_set_text:
1007 * @entry: a #StEntry
1008 * @text: (allow-none): text to set the entry to
1009 *
1010 * Sets the text displayed on the entry
1011 */
1012 void
st_entry_set_text(StEntry * entry,const gchar * text)1013 st_entry_set_text (StEntry *entry,
1014 const gchar *text)
1015 {
1016 StEntryPrivate *priv;
1017
1018 g_return_if_fail (ST_IS_ENTRY (entry));
1019
1020 priv = entry->priv;
1021
1022 /* set a hint if we are blanking the entry */
1023 if (priv->hint
1024 && text && !strcmp ("", text)
1025 && !HAS_FOCUS (priv->entry))
1026 {
1027 text = priv->hint;
1028 priv->hint_visible = TRUE;
1029 st_widget_add_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
1030 }
1031 else
1032 {
1033 st_widget_remove_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
1034
1035 priv->hint_visible = FALSE;
1036 }
1037
1038 clutter_text_set_text (CLUTTER_TEXT (priv->entry), text);
1039
1040 g_object_notify (G_OBJECT (entry), "text");
1041 }
1042
1043 /**
1044 * st_entry_get_clutter_text:
1045 * @entry: a #StEntry
1046 *
1047 * Retrieve the internal #ClutterText so that extra parameters can be set
1048 *
1049 * Returns: (transfer none): the #ClutterText used by #StEntry. The entry is
1050 * owned by the #StEntry and should not be unref'ed by the application.
1051 */
1052 ClutterActor*
st_entry_get_clutter_text(StEntry * entry)1053 st_entry_get_clutter_text (StEntry *entry)
1054 {
1055 g_return_val_if_fail (ST_ENTRY (entry), NULL);
1056
1057 return entry->priv->entry;
1058 }
1059
1060 /**
1061 * st_entry_set_hint_text:
1062 * @entry: a #StEntry
1063 * @text: (allow-none): text to set as the entry hint
1064 *
1065 * Sets the text to display when the entry is empty and unfocused. When the
1066 * entry is displaying the hint, it has a pseudo class of "indeterminate".
1067 * A value of NULL unsets the hint.
1068 */
1069 void
st_entry_set_hint_text(StEntry * entry,const gchar * text)1070 st_entry_set_hint_text (StEntry *entry,
1071 const gchar *text)
1072 {
1073 StEntryPrivate *priv;
1074
1075 g_return_if_fail (ST_IS_ENTRY (entry));
1076
1077 priv = entry->priv;
1078
1079 g_free (priv->hint);
1080
1081 priv->hint = g_strdup (text);
1082
1083 if (!strcmp (clutter_text_get_text (CLUTTER_TEXT (priv->entry)), "")
1084 && !HAS_FOCUS (priv->entry))
1085 {
1086 priv->hint_visible = TRUE;
1087
1088 clutter_text_set_text (CLUTTER_TEXT (priv->entry), priv->hint);
1089 st_widget_add_style_pseudo_class (ST_WIDGET (entry), "indeterminate");
1090 }
1091 }
1092
1093 /**
1094 * st_entry_get_hint_text:
1095 * @entry: a #StEntry
1096 *
1097 * Gets the text that is displayed when the entry is empty and unfocused
1098 *
1099 * Returns: the current value of the hint property. This string is owned by the
1100 * #StEntry and should not be freed or modified.
1101 */
1102 const gchar *
st_entry_get_hint_text(StEntry * entry)1103 st_entry_get_hint_text (StEntry *entry)
1104 {
1105 g_return_val_if_fail (ST_IS_ENTRY (entry), NULL);
1106
1107 return entry->priv->hint;
1108 }
1109
1110 static gboolean
_st_entry_icon_press_cb(ClutterActor * actor,ClutterButtonEvent * event,StEntry * entry)1111 _st_entry_icon_press_cb (ClutterActor *actor,
1112 ClutterButtonEvent *event,
1113 StEntry *entry)
1114 {
1115 StEntryPrivate *priv = entry->priv;
1116
1117 if (actor == priv->primary_icon)
1118 g_signal_emit (entry, entry_signals[PRIMARY_ICON_CLICKED], 0);
1119 else
1120 g_signal_emit (entry, entry_signals[SECONDARY_ICON_CLICKED], 0);
1121
1122 return FALSE;
1123 }
1124
1125 static void
_st_entry_set_icon(StEntry * entry,ClutterActor ** icon,ClutterActor * new_icon)1126 _st_entry_set_icon (StEntry *entry,
1127 ClutterActor **icon,
1128 ClutterActor *new_icon)
1129 {
1130 if (*icon)
1131 {
1132 g_signal_handlers_disconnect_by_func (*icon,
1133 _st_entry_icon_press_cb,
1134 entry);
1135 clutter_actor_remove_child (CLUTTER_ACTOR (entry), *icon);
1136 *icon = NULL;
1137 }
1138
1139 if (new_icon)
1140 {
1141 *icon = g_object_ref (new_icon);
1142
1143 clutter_actor_set_reactive (*icon, TRUE);
1144 clutter_actor_add_child (CLUTTER_ACTOR (entry), *icon);
1145 g_signal_connect (*icon, "button-release-event",
1146 G_CALLBACK (_st_entry_icon_press_cb), entry);
1147 }
1148
1149 clutter_actor_queue_relayout (CLUTTER_ACTOR (entry));
1150 }
1151
1152 static void
_st_entry_set_icon_from_file(StEntry * entry,ClutterActor ** icon,const gchar * filename)1153 _st_entry_set_icon_from_file (StEntry *entry,
1154 ClutterActor **icon,
1155 const gchar *filename)
1156 {
1157 ClutterActor *new_icon = NULL;
1158
1159 if (filename)
1160 {
1161 StTextureCache *cache;
1162 GFile *file;
1163 gchar *uri;
1164
1165 cache = st_texture_cache_get_default ();
1166 file = g_file_new_for_path (filename);
1167 uri = g_file_get_uri (file);
1168
1169 g_object_unref (file);
1170
1171 new_icon = (ClutterActor*) st_texture_cache_load_uri_async (cache, uri, -1, -1);
1172 g_free (uri);
1173 }
1174
1175 _st_entry_set_icon (entry, icon, new_icon);
1176 }
1177
1178 /**
1179 * st_entry_set_primary_icon:
1180 * @entry: a #StEntry
1181 * @icon: (allow-none): a #ClutterActor
1182 *
1183 * Set the primary icon of the entry to @icon
1184 */
1185 void
st_entry_set_primary_icon(StEntry * entry,ClutterActor * icon)1186 st_entry_set_primary_icon (StEntry *entry,
1187 ClutterActor *icon)
1188 {
1189 StEntryPrivate *priv;
1190
1191 g_return_if_fail (ST_IS_ENTRY (entry));
1192
1193 priv = entry->priv;
1194
1195 _st_entry_set_icon (entry, &priv->primary_icon, icon);
1196 }
1197
1198 /**
1199 * st_entry_set_secondary_icon:
1200 * @entry: a #StEntry
1201 * @icon: (allow-none): an #ClutterActor
1202 *
1203 * Set the secondary icon of the entry to @icon
1204 */
1205 void
st_entry_set_secondary_icon(StEntry * entry,ClutterActor * icon)1206 st_entry_set_secondary_icon (StEntry *entry,
1207 ClutterActor *icon)
1208 {
1209 StEntryPrivate *priv;
1210
1211 g_return_if_fail (ST_IS_ENTRY (entry));
1212
1213 priv = entry->priv;
1214
1215 _st_entry_set_icon (entry, &priv->secondary_icon, icon);
1216 }
1217
1218 /**
1219 * st_entry_set_primary_icon_from_file:
1220 * @entry: a #StEntry
1221 * @filename: (allow-none): filename of an icon
1222 *
1223 * Set the primary icon of the entry to the given filename
1224 */
1225 void
st_entry_set_primary_icon_from_file(StEntry * entry,const gchar * filename)1226 st_entry_set_primary_icon_from_file (StEntry *entry,
1227 const gchar *filename)
1228 {
1229 StEntryPrivate *priv;
1230
1231 g_return_if_fail (ST_IS_ENTRY (entry));
1232
1233 priv = entry->priv;
1234
1235 _st_entry_set_icon_from_file (entry, &priv->primary_icon, filename);
1236
1237 }
1238
1239 /**
1240 * st_entry_set_secondary_icon_from_file:
1241 * @entry: a #StEntry
1242 * @filename: (allow-none): filename of an icon
1243 *
1244 * Set the primary icon of the entry to the given filename
1245 */
1246 void
st_entry_set_secondary_icon_from_file(StEntry * entry,const gchar * filename)1247 st_entry_set_secondary_icon_from_file (StEntry *entry,
1248 const gchar *filename)
1249 {
1250 StEntryPrivate *priv;
1251
1252 g_return_if_fail (ST_IS_ENTRY (entry));
1253
1254 priv = entry->priv;
1255
1256 _st_entry_set_icon_from_file (entry, &priv->secondary_icon, filename);
1257
1258 }
1259
1260 /******************************************************************************/
1261 /*************************** ACCESSIBILITY SUPPORT ****************************/
1262 /******************************************************************************/
1263
1264 #define ST_TYPE_ENTRY_ACCESSIBLE (st_entry_accessible_get_type ())
1265 #define ST_ENTRY_ACCESSIBLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessible))
1266 #define ST_IS_ENTRY_ACCESSIBLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ST_TYPE_ENTRY_ACCESSIBLE))
1267 #define ST_ENTRY_ACCESSIBLE_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
1268 #define ST_IS_ENTRY_ACCESSIBLE_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), ST_TYPE_ENTRY_ACCESSIBLE))
1269 #define ST_ENTRY_ACCESSIBLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ST_TYPE_ENTRY_ACCESSIBLE, StEntryAccessibleClass))
1270
1271 typedef struct _StEntryAccessible StEntryAccessible;
1272 typedef struct _StEntryAccessibleClass StEntryAccessibleClass;
1273
1274 struct _StEntryAccessible
1275 {
1276 StWidgetAccessible parent;
1277 };
1278
1279 struct _StEntryAccessibleClass
1280 {
1281 StWidgetAccessibleClass parent_class;
1282 };
1283
G_DEFINE_TYPE(StEntryAccessible,st_entry_accessible,ST_TYPE_WIDGET_ACCESSIBLE)1284 G_DEFINE_TYPE (StEntryAccessible, st_entry_accessible, ST_TYPE_WIDGET_ACCESSIBLE)
1285
1286 static void
1287 st_entry_accessible_init (StEntryAccessible *self)
1288 {
1289 /* initialization done on AtkObject->initialize */
1290 }
1291
1292 static void
st_entry_accessible_initialize(AtkObject * obj,gpointer data)1293 st_entry_accessible_initialize (AtkObject *obj,
1294 gpointer data)
1295 {
1296 ATK_OBJECT_CLASS (st_entry_accessible_parent_class)->initialize (obj, data);
1297
1298 /* StEntry is behaving as a StImText container */
1299 atk_object_set_role (obj, ATK_ROLE_PANEL);
1300 }
1301
1302 static gint
st_entry_accessible_get_n_children(AtkObject * obj)1303 st_entry_accessible_get_n_children (AtkObject *obj)
1304 {
1305 StEntry *entry = NULL;
1306
1307 g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), 0);
1308
1309 entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
1310
1311 if (entry == NULL)
1312 return 0;
1313
1314 if (entry->priv->entry == NULL)
1315 return 0;
1316 else
1317 return 1;
1318 }
1319
1320 static AtkObject*
st_entry_accessible_ref_child(AtkObject * obj,gint i)1321 st_entry_accessible_ref_child (AtkObject *obj,
1322 gint i)
1323 {
1324 StEntry *entry = NULL;
1325 AtkObject *result = NULL;
1326
1327 g_return_val_if_fail (ST_IS_ENTRY_ACCESSIBLE (obj), NULL);
1328 g_return_val_if_fail (i == 0, NULL);
1329
1330 entry = ST_ENTRY (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj)));
1331
1332 if (entry == NULL)
1333 return NULL;
1334
1335 if (entry->priv->entry == NULL)
1336 return NULL;
1337
1338 result = clutter_actor_get_accessible (entry->priv->entry);
1339 g_object_ref (result);
1340
1341 return result;
1342 }
1343
1344
1345 static void
st_entry_accessible_class_init(StEntryAccessibleClass * klass)1346 st_entry_accessible_class_init (StEntryAccessibleClass *klass)
1347 {
1348 AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
1349
1350 atk_class->initialize = st_entry_accessible_initialize;
1351 atk_class->get_n_children = st_entry_accessible_get_n_children;
1352 atk_class->ref_child= st_entry_accessible_ref_child;
1353 }
1354