1 /* Copyright (C) 2004 Red Hat, Inc.
3 This library is free software; you can redistribute it and/or
4 modify it under the terms of the GNU Library General Public License as
5 published by the Free Software Foundation; either version 2 of the
6 License, or (at your option) any later version.
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
13 You should have received a copy of the GNU Library General Public
14 License along with the Gnome Library; see the file COPYING.LIB. If not,
15 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16 Boston, MA 02111-1307, USA.
21 #include "eggfindbar.h"
23 #include <glib/gi18n.h>
24 #include <gtk/gtkhbox.h>
25 #include <gtk/gtkentry.h>
26 #include <gtk/gtkcheckbutton.h>
27 #include <gtk/gtkvseparator.h>
28 #include <gtk/gtkstock.h>
29 #include <gtk/gtklabel.h>
30 #include <gdk/gdkkeysyms.h>
31 #include <gtk/gtkbindings.h>
35 struct _EggFindBarPrivate
39 GtkWidget *find_entry;
40 GtkWidget *next_button;
41 GtkWidget *previous_button;
42 GtkWidget *case_button;
43 GtkWidget *status_separator;
44 GtkWidget *status_label;
45 gulong set_focus_handler;
46 guint case_sensitive : 1;
49 #define EGG_FIND_BAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EGG_TYPE_FIND_BAR, EggFindBarPrivate))
59 static void egg_find_bar_finalize (GObject *object);
60 static void egg_find_bar_get_property (GObject *object,
64 static void egg_find_bar_set_property (GObject *object,
68 static void egg_find_bar_size_request (GtkWidget *widget,
69 GtkRequisition *requisition);
70 static void egg_find_bar_size_allocate (GtkWidget *widget,
71 GtkAllocation *allocation);
72 static void egg_find_bar_show (GtkWidget *widget);
73 static void egg_find_bar_hide (GtkWidget *widget);
74 static void egg_find_bar_grab_focus (GtkWidget *widget);
76 G_DEFINE_TYPE (EggFindBar, egg_find_bar, GTK_TYPE_BIN);
87 static guint find_bar_signals[LAST_SIGNAL] = { 0 };
90 egg_find_bar_class_init (EggFindBarClass *klass)
92 GObjectClass *object_class;
93 GtkWidgetClass *widget_class;
94 GtkBinClass *bin_class;
95 GtkBindingSet *binding_set;
97 egg_find_bar_parent_class = g_type_class_peek_parent (klass);
99 object_class = (GObjectClass *)klass;
100 widget_class = (GtkWidgetClass *)klass;
101 bin_class = (GtkBinClass *)klass;
103 object_class->set_property = egg_find_bar_set_property;
104 object_class->get_property = egg_find_bar_get_property;
106 object_class->finalize = egg_find_bar_finalize;
108 widget_class->size_request = egg_find_bar_size_request;
109 widget_class->size_allocate = egg_find_bar_size_allocate;
110 widget_class->show = egg_find_bar_show;
111 widget_class->hide = egg_find_bar_hide;
112 widget_class->grab_focus = egg_find_bar_grab_focus;
114 find_bar_signals[NEXT] =
115 g_signal_new ("next",
116 G_OBJECT_CLASS_TYPE (object_class),
118 G_STRUCT_OFFSET (EggFindBarClass, next),
120 g_cclosure_marshal_VOID__VOID,
122 find_bar_signals[PREVIOUS] =
123 g_signal_new ("previous",
124 G_OBJECT_CLASS_TYPE (object_class),
126 G_STRUCT_OFFSET (EggFindBarClass, previous),
128 g_cclosure_marshal_VOID__VOID,
130 find_bar_signals[CLOSE] =
131 g_signal_new ("close",
132 G_OBJECT_CLASS_TYPE (object_class),
133 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
134 G_STRUCT_OFFSET (EggFindBarClass, close),
136 g_cclosure_marshal_VOID__VOID,
138 find_bar_signals[SCROLL] =
139 g_signal_new ("scroll",
140 G_OBJECT_CLASS_TYPE (object_class),
141 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
142 G_STRUCT_OFFSET (EggFindBarClass, scroll),
144 g_cclosure_marshal_VOID__ENUM,
146 GTK_TYPE_SCROLL_TYPE);
149 * EggFindBar:search_string:
151 * The current string to search for. NULL or empty string
152 * both mean no current string.
155 g_object_class_install_property (object_class,
157 g_param_spec_string ("search_string",
159 _("The name of the string to be found"),
164 * EggFindBar:case_sensitive:
166 * TRUE for a case sensitive search.
169 g_object_class_install_property (object_class,
171 g_param_spec_boolean ("case_sensitive",
173 _("TRUE for a case sensitive search"),
177 /* Style properties */
178 gtk_widget_class_install_style_property (widget_class,
179 g_param_spec_boxed ("all_matches_color",
180 _("Highlight color"),
181 _("Color of highlight for all matches"),
185 gtk_widget_class_install_style_property (widget_class,
186 g_param_spec_boxed ("current_match_color",
188 _("Color of highlight for the current match"),
192 g_type_class_add_private (object_class, sizeof (EggFindBarPrivate));
194 binding_set = gtk_binding_set_by_class (klass);
196 gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
199 gtk_binding_entry_add_signal (binding_set, GDK_Up, 0,
201 GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_BACKWARD);
203 gtk_binding_entry_add_signal (binding_set, GDK_Down, 0,
205 GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_FORWARD);
209 egg_find_bar_emit_next (EggFindBar *find_bar)
211 g_signal_emit (find_bar, find_bar_signals[NEXT], 0);
215 egg_find_bar_emit_previous (EggFindBar *find_bar)
217 g_signal_emit (find_bar, find_bar_signals[PREVIOUS], 0);
221 next_clicked_callback (GtkButton *button,
224 EggFindBar *find_bar = EGG_FIND_BAR (data);
226 egg_find_bar_emit_next (find_bar);
230 previous_clicked_callback (GtkButton *button,
233 EggFindBar *find_bar = EGG_FIND_BAR (data);
235 egg_find_bar_emit_previous (find_bar);
239 case_sensitive_toggled_callback (GtkCheckButton *button,
242 EggFindBar *find_bar = EGG_FIND_BAR (data);
244 egg_find_bar_set_case_sensitive (find_bar,
245 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
249 entry_activate_callback (GtkEntry *entry,
252 EggFindBar *find_bar = EGG_FIND_BAR (data);
253 EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->priv;
255 /* We activate the "next" button here so we'll get a nice
257 if (find_bar->priv->search_string != NULL)
258 gtk_widget_activate (priv->next_button);
262 entry_changed_callback (GtkEntry *entry,
265 EggFindBar *find_bar = EGG_FIND_BAR (data);
268 /* paranoid strdup because set_search_string() sets
271 text = g_strdup (gtk_entry_get_text (entry));
273 egg_find_bar_set_search_string (find_bar, text);
279 set_focus_cb (GtkWidget *window,
283 GtkWidget *wbar = GTK_WIDGET (bar);
285 while (widget != NULL && widget != wbar)
287 widget = widget->parent;
290 /* if widget == bar, the new focus widget is in the bar, so we
295 g_signal_emit (bar, find_bar_signals[CLOSE], 0);
300 egg_find_bar_init (EggFindBar *find_bar)
302 EggFindBarPrivate *priv;
304 GtkWidget *separator;
305 GtkWidget *image_back;
306 GtkWidget *image_forward;
309 priv = EGG_FIND_BAR_GET_PRIVATE (find_bar);
310 find_bar->priv = priv;
312 priv->search_string = NULL;
315 gtk_widget_push_composite_child ();
316 priv->hbox = gtk_hbox_new (FALSE, 6);
317 gtk_container_set_border_width (GTK_CONTAINER (priv->hbox), 3);
319 label = gtk_label_new_with_mnemonic (_("F_ind:"));
320 separator = gtk_vseparator_new ();
322 priv->find_entry = gtk_entry_new ();
323 gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->find_entry);
325 priv->previous_button = gtk_button_new_with_mnemonic (_("_Previous"));
326 gtk_button_set_focus_on_click (GTK_BUTTON (priv->previous_button), FALSE);
327 gtk_widget_set_sensitive (GTK_WIDGET (priv->previous_button), FALSE);
329 priv->next_button = gtk_button_new_with_mnemonic (_("_Next"));
330 gtk_button_set_focus_on_click (GTK_BUTTON (priv->next_button), FALSE);
331 gtk_widget_set_sensitive (GTK_WIDGET (priv->next_button), FALSE);
333 image_back = gtk_image_new_from_stock (GTK_STOCK_GO_BACK,
334 GTK_ICON_SIZE_BUTTON);
335 image_forward = gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD,
336 GTK_ICON_SIZE_BUTTON);
338 gtk_button_set_image (GTK_BUTTON (priv->previous_button),
340 gtk_button_set_image (GTK_BUTTON (priv->next_button),
343 priv->case_button = gtk_check_button_new_with_mnemonic (_("C_ase Sensitive"));
345 priv->status_separator = gtk_vseparator_new ();
347 priv->status_label = gtk_label_new (NULL);
348 gtk_label_set_ellipsize (GTK_LABEL (priv->status_label),
349 PANGO_ELLIPSIZE_END);
350 gtk_misc_set_alignment (GTK_MISC (priv->status_label), 0.0, 0.5);
354 GtkWidget *button_label;
355 /* This hack doesn't work because GtkCheckButton doesn't pass the
356 * larger size allocation to the label, it always gives the label
357 * its exact request. If you un-ifdef this, set the box back
358 * on case_button to TRUE, TRUE below
360 button_label = gtk_bin_get_child (GTK_BIN (priv->case_button));
361 gtk_label_set_ellipsize (GTK_LABEL (button_label),
362 PANGO_ELLIPSIZE_END);
366 gtk_box_pack_start (GTK_BOX (priv->hbox),
367 label, FALSE, FALSE, 0);
368 gtk_box_pack_start (GTK_BOX (priv->hbox),
369 priv->find_entry, FALSE, FALSE, 0);
370 gtk_box_pack_start (GTK_BOX (priv->hbox),
371 priv->previous_button, FALSE, FALSE, 0);
372 gtk_box_pack_start (GTK_BOX (priv->hbox),
373 priv->next_button, FALSE, FALSE, 0);
374 gtk_box_pack_start (GTK_BOX (priv->hbox),
375 separator, FALSE, FALSE, 0);
376 gtk_box_pack_start (GTK_BOX (priv->hbox),
377 priv->case_button, FALSE, FALSE, 0);
378 gtk_box_pack_start (GTK_BOX (priv->hbox),
379 priv->status_separator, FALSE, FALSE, 0);
380 gtk_box_pack_start (GTK_BOX (priv->hbox),
381 priv->status_label, TRUE, TRUE, 0);
383 gtk_container_add (GTK_CONTAINER (find_bar), priv->hbox);
385 gtk_widget_show (priv->hbox);
386 gtk_widget_show (priv->find_entry);
387 gtk_widget_show (priv->previous_button);
388 gtk_widget_show (priv->next_button);
389 gtk_widget_show (separator);
390 gtk_widget_show (label);
391 gtk_widget_show (image_back);
392 gtk_widget_show (image_forward);
393 /* don't show status separator/label until they are set */
395 gtk_widget_pop_composite_child ();
397 g_signal_connect (priv->find_entry, "changed",
398 G_CALLBACK (entry_changed_callback),
400 g_signal_connect (priv->find_entry, "activate",
401 G_CALLBACK (entry_activate_callback),
403 g_signal_connect (priv->next_button, "clicked",
404 G_CALLBACK (next_clicked_callback),
406 g_signal_connect (priv->previous_button, "clicked",
407 G_CALLBACK (previous_clicked_callback),
409 g_signal_connect (priv->case_button, "toggled",
410 G_CALLBACK (case_sensitive_toggled_callback),
415 egg_find_bar_finalize (GObject *object)
417 EggFindBar *find_bar = EGG_FIND_BAR (object);
418 EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->priv;
420 g_free (priv->search_string);
422 G_OBJECT_CLASS (egg_find_bar_parent_class)->finalize (object);
426 egg_find_bar_set_property (GObject *object,
431 EggFindBar *find_bar = EGG_FIND_BAR (object);
435 case PROP_SEARCH_STRING:
436 egg_find_bar_set_search_string (find_bar, g_value_get_string (value));
438 case PROP_CASE_SENSITIVE:
439 egg_find_bar_set_case_sensitive (find_bar, g_value_get_boolean (value));
442 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
448 egg_find_bar_get_property (GObject *object,
453 EggFindBar *find_bar = EGG_FIND_BAR (object);
454 EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->priv;
458 case PROP_SEARCH_STRING:
459 g_value_set_string (value, priv->search_string);
461 case PROP_CASE_SENSITIVE:
462 g_value_set_boolean (value, priv->case_sensitive);
465 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
471 egg_find_bar_size_request (GtkWidget *widget,
472 GtkRequisition *requisition)
474 GtkBin *bin = GTK_BIN (widget);
475 GtkRequisition child_requisition;
476 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
478 gtk_widget_size_request (bin->child, &child_requisition);
480 *requisition = child_requisition;
484 requisition->width = 0;
485 requisition->height = 0;
490 egg_find_bar_size_allocate (GtkWidget *widget,
491 GtkAllocation *allocation)
493 GtkBin *bin = GTK_BIN (widget);
495 widget->allocation = *allocation;
497 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
498 gtk_widget_size_allocate (bin->child, allocation);
502 egg_find_bar_show (GtkWidget *widget)
504 EggFindBar *bar = EGG_FIND_BAR (widget);
505 EggFindBarPrivate *priv = bar->priv;
507 GTK_WIDGET_CLASS (egg_find_bar_parent_class)->show (widget);
509 if (priv->set_focus_handler == 0)
513 toplevel = gtk_widget_get_toplevel (widget);
515 priv->set_focus_handler =
516 g_signal_connect (toplevel, "set-focus",
517 G_CALLBACK (set_focus_cb), bar);
522 egg_find_bar_hide (GtkWidget *widget)
524 EggFindBar *bar = EGG_FIND_BAR (widget);
525 EggFindBarPrivate *priv = bar->priv;
527 if (priv->set_focus_handler != 0)
531 toplevel = gtk_widget_get_toplevel (widget);
533 g_signal_handlers_disconnect_by_func
534 (toplevel, (void (*)) G_CALLBACK (set_focus_cb), bar);
535 priv->set_focus_handler = 0;
538 GTK_WIDGET_CLASS (egg_find_bar_parent_class)->hide (widget);
542 egg_find_bar_grab_focus (GtkWidget *widget)
544 EggFindBar *find_bar = EGG_FIND_BAR (widget);
545 EggFindBarPrivate *priv = find_bar->priv;
547 gtk_widget_grab_focus (priv->find_entry);
553 * Creates a new #EggFindBar.
555 * Returns: a newly created #EggFindBar
560 egg_find_bar_new (void)
562 EggFindBar *find_bar;
564 find_bar = g_object_new (EGG_TYPE_FIND_BAR, NULL);
566 return GTK_WIDGET (find_bar);
570 * egg_find_bar_set_search_string:
572 * Sets the string that should be found/highlighted in the document.
573 * Empty string is converted to NULL.
578 egg_find_bar_set_search_string (EggFindBar *find_bar,
579 const char *search_string)
581 EggFindBarPrivate *priv;
583 g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
585 priv = (EggFindBarPrivate *)find_bar->priv;
587 g_object_freeze_notify (G_OBJECT (find_bar));
589 if (priv->search_string != search_string)
593 old = priv->search_string;
595 if (search_string && *search_string == '\0')
596 search_string = NULL;
598 /* Only update if the string has changed; setting the entry
599 * will emit changed on the entry which will re-enter
600 * this function, but we'll handle that fine with this
603 if ((old && search_string == NULL) ||
604 (old == NULL && search_string) ||
605 (old && search_string &&
606 strcmp (old, search_string) != 0))
610 priv->search_string = g_strdup (search_string);
613 gtk_entry_set_text (GTK_ENTRY (priv->find_entry),
614 priv->search_string ?
615 priv->search_string :
618 not_empty = (search_string == NULL) ? FALSE : TRUE;
620 gtk_widget_set_sensitive (GTK_WIDGET (find_bar->priv->next_button), not_empty);
621 gtk_widget_set_sensitive (GTK_WIDGET (find_bar->priv->previous_button), not_empty);
623 g_object_notify (G_OBJECT (find_bar),
628 g_object_thaw_notify (G_OBJECT (find_bar));
633 * egg_find_bar_get_search_string:
635 * Gets the string that should be found/highlighted in the document.
637 * Returns: the string
642 egg_find_bar_get_search_string (EggFindBar *find_bar)
644 EggFindBarPrivate *priv;
646 g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), NULL);
648 priv = find_bar->priv;
650 return priv->search_string ? priv->search_string : "";
654 * egg_find_bar_set_case_sensitive:
656 * Sets whether the search is case sensitive
661 egg_find_bar_set_case_sensitive (EggFindBar *find_bar,
662 gboolean case_sensitive)
664 EggFindBarPrivate *priv;
666 g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
668 priv = (EggFindBarPrivate *)find_bar->priv;
670 g_object_freeze_notify (G_OBJECT (find_bar));
672 case_sensitive = case_sensitive != FALSE;
674 if (priv->case_sensitive != case_sensitive)
676 priv->case_sensitive = case_sensitive;
678 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->case_button),
679 priv->case_sensitive);
681 g_object_notify (G_OBJECT (find_bar),
685 g_object_thaw_notify (G_OBJECT (find_bar));
689 * egg_find_bar_get_case_sensitive:
691 * Gets whether the search is case sensitive
693 * Returns: TRUE if it's case sensitive
698 egg_find_bar_get_case_sensitive (EggFindBar *find_bar)
700 EggFindBarPrivate *priv;
702 g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), FALSE);
704 priv = (EggFindBarPrivate *)find_bar->priv;
706 return priv->case_sensitive;
710 get_style_color (EggFindBar *find_bar,
711 const char *style_prop_name,
714 GdkColor *style_color;
716 gtk_widget_ensure_style (GTK_WIDGET (find_bar));
717 gtk_widget_style_get (GTK_WIDGET (find_bar),
718 "color", &style_color, NULL);
721 *color = *style_color;
722 gdk_color_free (style_color);
727 * egg_find_bar_get_all_matches_color:
729 * Gets the color to use to highlight all the
735 egg_find_bar_get_all_matches_color (EggFindBar *find_bar,
738 GdkColor found_color = { 0, 0, 0, 0x0f0f };
740 get_style_color (find_bar, "all_matches_color", &found_color);
742 *color = found_color;
746 * egg_find_bar_get_current_match_color:
748 * Gets the color to use to highlight the match
749 * we're currently on.
754 egg_find_bar_get_current_match_color (EggFindBar *find_bar,
757 GdkColor found_color = { 0, 0, 0, 0xffff };
759 get_style_color (find_bar, "current_match_color", &found_color);
761 *color = found_color;
765 * egg_find_bar_set_status_text:
767 * Sets some text to display if there's space; typical text would
768 * be something like "5 results on this page" or "No results"
770 * @text: the text to display
775 egg_find_bar_set_status_text (EggFindBar *find_bar,
778 EggFindBarPrivate *priv;
780 g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
782 priv = (EggFindBarPrivate *)find_bar->priv;
784 if (text == NULL || *text == '\0')
786 gtk_widget_hide (priv->status_label);
787 gtk_widget_hide (priv->status_separator);
788 gtk_label_set_text (GTK_LABEL (priv->status_label), NULL);
792 gtk_label_set_text (GTK_LABEL (priv->status_label), text);
793 gtk_widget_show (priv->status_label);
794 gtk_widget_show (priv->status_separator);