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);
86 static guint find_bar_signals[LAST_SIGNAL] = { 0 };
89 egg_find_bar_class_init (EggFindBarClass *klass)
91 GObjectClass *object_class;
92 GtkWidgetClass *widget_class;
93 GtkBinClass *bin_class;
94 GtkBindingSet *binding_set;
96 egg_find_bar_parent_class = g_type_class_peek_parent (klass);
98 object_class = (GObjectClass *)klass;
99 widget_class = (GtkWidgetClass *)klass;
100 bin_class = (GtkBinClass *)klass;
102 object_class->set_property = egg_find_bar_set_property;
103 object_class->get_property = egg_find_bar_get_property;
105 object_class->finalize = egg_find_bar_finalize;
107 widget_class->size_request = egg_find_bar_size_request;
108 widget_class->size_allocate = egg_find_bar_size_allocate;
109 widget_class->show = egg_find_bar_show;
110 widget_class->hide = egg_find_bar_hide;
111 widget_class->grab_focus = egg_find_bar_grab_focus;
113 find_bar_signals[NEXT] =
114 g_signal_new ("next",
115 G_OBJECT_CLASS_TYPE (object_class),
117 G_STRUCT_OFFSET (EggFindBarClass, next),
119 g_cclosure_marshal_VOID__VOID,
121 find_bar_signals[PREVIOUS] =
122 g_signal_new ("previous",
123 G_OBJECT_CLASS_TYPE (object_class),
125 G_STRUCT_OFFSET (EggFindBarClass, previous),
127 g_cclosure_marshal_VOID__VOID,
129 find_bar_signals[CLOSE] =
130 g_signal_new ("close",
131 G_OBJECT_CLASS_TYPE (object_class),
132 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
133 G_STRUCT_OFFSET (EggFindBarClass, close),
135 g_cclosure_marshal_VOID__VOID,
139 * EggFindBar:search_string:
141 * The current string to search for. NULL or empty string
142 * both mean no current string.
145 g_object_class_install_property (object_class,
147 g_param_spec_string ("search_string",
149 _("The name of the string to be found"),
154 * EggFindBar:case_sensitive:
156 * TRUE for a case sensitive search.
159 g_object_class_install_property (object_class,
161 g_param_spec_boolean ("case_sensitive",
163 _("TRUE for a case sensitive search"),
167 /* Style properties */
168 gtk_widget_class_install_style_property (widget_class,
169 g_param_spec_boxed ("all_matches_color",
170 _("Highlight color"),
171 _("Color of highlight for all matches"),
175 gtk_widget_class_install_style_property (widget_class,
176 g_param_spec_boxed ("current_match_color",
178 _("Color of highlight for the current match"),
182 g_type_class_add_private (object_class, sizeof (EggFindBarPrivate));
184 binding_set = gtk_binding_set_by_class (klass);
186 gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
191 egg_find_bar_emit_next (EggFindBar *find_bar)
193 g_signal_emit (find_bar, find_bar_signals[NEXT], 0);
197 egg_find_bar_emit_previous (EggFindBar *find_bar)
199 g_signal_emit (find_bar, find_bar_signals[PREVIOUS], 0);
203 next_clicked_callback (GtkButton *button,
206 EggFindBar *find_bar = EGG_FIND_BAR (data);
208 egg_find_bar_emit_next (find_bar);
212 previous_clicked_callback (GtkButton *button,
215 EggFindBar *find_bar = EGG_FIND_BAR (data);
217 egg_find_bar_emit_previous (find_bar);
221 case_sensitive_toggled_callback (GtkCheckButton *button,
224 EggFindBar *find_bar = EGG_FIND_BAR (data);
226 egg_find_bar_set_case_sensitive (find_bar,
227 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
231 entry_activate_callback (GtkEntry *entry,
234 EggFindBar *find_bar = EGG_FIND_BAR (data);
235 EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->priv;
237 /* We activate the "next" button here so we'll get a nice
239 if (find_bar->priv->search_string != NULL)
240 gtk_widget_activate (priv->next_button);
244 entry_changed_callback (GtkEntry *entry,
247 EggFindBar *find_bar = EGG_FIND_BAR (data);
250 /* paranoid strdup because set_search_string() sets
253 text = g_strdup (gtk_entry_get_text (entry));
255 egg_find_bar_set_search_string (find_bar, text);
261 set_focus_cb (GtkWidget *window,
265 GtkWidget *wbar = GTK_WIDGET (bar);
267 while (widget != NULL && widget != wbar)
269 widget = widget->parent;
272 /* if widget == bar, the new focus widget is in the bar, so we
277 g_signal_emit (bar, find_bar_signals[CLOSE], 0);
282 egg_find_bar_init (EggFindBar *find_bar)
284 EggFindBarPrivate *priv;
286 GtkWidget *separator;
287 GtkWidget *image_back;
288 GtkWidget *image_forward;
291 priv = EGG_FIND_BAR_GET_PRIVATE (find_bar);
292 find_bar->priv = priv;
294 priv->search_string = NULL;
297 gtk_widget_push_composite_child ();
298 priv->hbox = gtk_hbox_new (FALSE, 6);
299 gtk_container_set_border_width (GTK_CONTAINER (priv->hbox), 3);
301 label = gtk_label_new_with_mnemonic (_("F_ind:"));
302 separator = gtk_vseparator_new ();
304 priv->find_entry = gtk_entry_new ();
305 gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->find_entry);
307 priv->previous_button = gtk_button_new_with_mnemonic (_("_Previous"));
308 gtk_button_set_focus_on_click (GTK_BUTTON (priv->previous_button), FALSE);
309 gtk_widget_set_sensitive (GTK_WIDGET (priv->previous_button), FALSE);
311 priv->next_button = gtk_button_new_with_mnemonic (_("_Next"));
312 gtk_button_set_focus_on_click (GTK_BUTTON (priv->next_button), FALSE);
313 gtk_widget_set_sensitive (GTK_WIDGET (priv->next_button), FALSE);
315 image_back = gtk_image_new_from_stock (GTK_STOCK_GO_BACK,
316 GTK_ICON_SIZE_BUTTON);
317 image_forward = gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD,
318 GTK_ICON_SIZE_BUTTON);
320 gtk_button_set_image (GTK_BUTTON (priv->previous_button),
322 gtk_button_set_image (GTK_BUTTON (priv->next_button),
325 priv->case_button = gtk_check_button_new_with_mnemonic (_("C_ase Sensitive"));
327 priv->status_separator = gtk_vseparator_new ();
329 priv->status_label = gtk_label_new (NULL);
330 gtk_label_set_ellipsize (GTK_LABEL (priv->status_label),
331 PANGO_ELLIPSIZE_END);
332 gtk_misc_set_alignment (GTK_MISC (priv->status_label), 0.0, 0.5);
336 GtkWidget *button_label;
337 /* This hack doesn't work because GtkCheckButton doesn't pass the
338 * larger size allocation to the label, it always gives the label
339 * its exact request. If you un-ifdef this, set the box back
340 * on case_button to TRUE, TRUE below
342 button_label = gtk_bin_get_child (GTK_BIN (priv->case_button));
343 gtk_label_set_ellipsize (GTK_LABEL (button_label),
344 PANGO_ELLIPSIZE_END);
348 gtk_box_pack_start (GTK_BOX (priv->hbox),
349 label, FALSE, FALSE, 0);
350 gtk_box_pack_start (GTK_BOX (priv->hbox),
351 priv->find_entry, FALSE, FALSE, 0);
352 gtk_box_pack_start (GTK_BOX (priv->hbox),
353 priv->previous_button, FALSE, FALSE, 0);
354 gtk_box_pack_start (GTK_BOX (priv->hbox),
355 priv->next_button, FALSE, FALSE, 0);
356 gtk_box_pack_start (GTK_BOX (priv->hbox),
357 separator, FALSE, FALSE, 0);
358 gtk_box_pack_start (GTK_BOX (priv->hbox),
359 priv->case_button, FALSE, FALSE, 0);
360 gtk_box_pack_start (GTK_BOX (priv->hbox),
361 priv->status_separator, FALSE, FALSE, 0);
362 gtk_box_pack_start (GTK_BOX (priv->hbox),
363 priv->status_label, TRUE, TRUE, 0);
365 gtk_container_add (GTK_CONTAINER (find_bar), priv->hbox);
367 gtk_widget_show (priv->hbox);
368 gtk_widget_show (priv->find_entry);
369 gtk_widget_show (priv->previous_button);
370 gtk_widget_show (priv->next_button);
371 gtk_widget_show (separator);
372 gtk_widget_show (label);
373 gtk_widget_show (image_back);
374 gtk_widget_show (image_forward);
375 /* don't show status separator/label until they are set */
377 gtk_widget_pop_composite_child ();
379 g_signal_connect (priv->find_entry, "changed",
380 G_CALLBACK (entry_changed_callback),
382 g_signal_connect (priv->find_entry, "activate",
383 G_CALLBACK (entry_activate_callback),
385 g_signal_connect (priv->next_button, "clicked",
386 G_CALLBACK (next_clicked_callback),
388 g_signal_connect (priv->previous_button, "clicked",
389 G_CALLBACK (previous_clicked_callback),
391 g_signal_connect (priv->case_button, "toggled",
392 G_CALLBACK (case_sensitive_toggled_callback),
397 egg_find_bar_finalize (GObject *object)
399 EggFindBar *find_bar = EGG_FIND_BAR (object);
400 EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->priv;
402 g_free (priv->search_string);
404 G_OBJECT_CLASS (egg_find_bar_parent_class)->finalize (object);
408 egg_find_bar_set_property (GObject *object,
413 EggFindBar *find_bar = EGG_FIND_BAR (object);
417 case PROP_SEARCH_STRING:
418 egg_find_bar_set_search_string (find_bar, g_value_get_string (value));
420 case PROP_CASE_SENSITIVE:
421 egg_find_bar_set_case_sensitive (find_bar, g_value_get_boolean (value));
424 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
430 egg_find_bar_get_property (GObject *object,
435 EggFindBar *find_bar = EGG_FIND_BAR (object);
436 EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->priv;
440 case PROP_SEARCH_STRING:
441 g_value_set_string (value, priv->search_string);
443 case PROP_CASE_SENSITIVE:
444 g_value_set_boolean (value, priv->case_sensitive);
447 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
453 egg_find_bar_size_request (GtkWidget *widget,
454 GtkRequisition *requisition)
456 GtkBin *bin = GTK_BIN (widget);
457 GtkRequisition child_requisition;
458 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
460 gtk_widget_size_request (bin->child, &child_requisition);
462 *requisition = child_requisition;
466 requisition->width = 0;
467 requisition->height = 0;
472 egg_find_bar_size_allocate (GtkWidget *widget,
473 GtkAllocation *allocation)
475 GtkBin *bin = GTK_BIN (widget);
477 widget->allocation = *allocation;
479 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
480 gtk_widget_size_allocate (bin->child, allocation);
484 egg_find_bar_show (GtkWidget *widget)
486 EggFindBar *bar = EGG_FIND_BAR (widget);
487 EggFindBarPrivate *priv = bar->priv;
489 GTK_WIDGET_CLASS (egg_find_bar_parent_class)->show (widget);
491 if (priv->set_focus_handler == 0)
495 toplevel = gtk_widget_get_toplevel (widget);
497 priv->set_focus_handler =
498 g_signal_connect (toplevel, "set-focus",
499 G_CALLBACK (set_focus_cb), bar);
504 egg_find_bar_hide (GtkWidget *widget)
506 EggFindBar *bar = EGG_FIND_BAR (widget);
507 EggFindBarPrivate *priv = bar->priv;
509 if (priv->set_focus_handler != 0)
513 toplevel = gtk_widget_get_toplevel (widget);
515 g_signal_handlers_disconnect_by_func
516 (toplevel, (void (*)) G_CALLBACK (set_focus_cb), bar);
517 priv->set_focus_handler = 0;
520 GTK_WIDGET_CLASS (egg_find_bar_parent_class)->hide (widget);
524 egg_find_bar_grab_focus (GtkWidget *widget)
526 EggFindBar *find_bar = EGG_FIND_BAR (widget);
527 EggFindBarPrivate *priv = find_bar->priv;
529 gtk_widget_grab_focus (priv->find_entry);
535 * Creates a new #EggFindBar.
537 * Returns: a newly created #EggFindBar
542 egg_find_bar_new (void)
544 EggFindBar *find_bar;
546 find_bar = g_object_new (EGG_TYPE_FIND_BAR, NULL);
548 return GTK_WIDGET (find_bar);
552 * egg_find_bar_set_search_string:
554 * Sets the string that should be found/highlighted in the document.
555 * Empty string is converted to NULL.
560 egg_find_bar_set_search_string (EggFindBar *find_bar,
561 const char *search_string)
563 EggFindBarPrivate *priv;
565 g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
567 priv = (EggFindBarPrivate *)find_bar->priv;
569 g_object_freeze_notify (G_OBJECT (find_bar));
571 if (priv->search_string != search_string)
575 old = priv->search_string;
577 if (search_string && *search_string == '\0')
578 search_string = NULL;
580 /* Only update if the string has changed; setting the entry
581 * will emit changed on the entry which will re-enter
582 * this function, but we'll handle that fine with this
585 if ((old && search_string == NULL) ||
586 (old == NULL && search_string) ||
587 (old && search_string &&
588 strcmp (old, search_string) != 0))
592 priv->search_string = g_strdup (search_string);
595 gtk_entry_set_text (GTK_ENTRY (priv->find_entry),
596 priv->search_string ?
597 priv->search_string :
600 not_empty = (search_string == NULL) ? FALSE : TRUE;
602 gtk_widget_set_sensitive (GTK_WIDGET (find_bar->priv->next_button), not_empty);
603 gtk_widget_set_sensitive (GTK_WIDGET (find_bar->priv->previous_button), not_empty);
605 g_object_notify (G_OBJECT (find_bar),
610 g_object_thaw_notify (G_OBJECT (find_bar));
615 * egg_find_bar_get_search_string:
617 * Gets the string that should be found/highlighted in the document.
619 * Returns: the string
624 egg_find_bar_get_search_string (EggFindBar *find_bar)
626 EggFindBarPrivate *priv;
628 g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), NULL);
630 priv = find_bar->priv;
632 return priv->search_string ? priv->search_string : "";
636 * egg_find_bar_set_case_sensitive:
638 * Sets whether the search is case sensitive
643 egg_find_bar_set_case_sensitive (EggFindBar *find_bar,
644 gboolean case_sensitive)
646 EggFindBarPrivate *priv;
648 g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
650 priv = (EggFindBarPrivate *)find_bar->priv;
652 g_object_freeze_notify (G_OBJECT (find_bar));
654 case_sensitive = case_sensitive != FALSE;
656 if (priv->case_sensitive != case_sensitive)
658 priv->case_sensitive = case_sensitive;
660 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->case_button),
661 priv->case_sensitive);
663 g_object_notify (G_OBJECT (find_bar),
667 g_object_thaw_notify (G_OBJECT (find_bar));
671 * egg_find_bar_get_case_sensitive:
673 * Gets whether the search is case sensitive
675 * Returns: TRUE if it's case sensitive
680 egg_find_bar_get_case_sensitive (EggFindBar *find_bar)
682 EggFindBarPrivate *priv;
684 g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), FALSE);
686 priv = (EggFindBarPrivate *)find_bar->priv;
688 return priv->case_sensitive;
692 get_style_color (EggFindBar *find_bar,
693 const char *style_prop_name,
696 GdkColor *style_color;
698 gtk_widget_ensure_style (GTK_WIDGET (find_bar));
699 gtk_widget_style_get (GTK_WIDGET (find_bar),
700 "color", &style_color, NULL);
703 *color = *style_color;
704 gdk_color_free (style_color);
709 * egg_find_bar_get_all_matches_color:
711 * Gets the color to use to highlight all the
717 egg_find_bar_get_all_matches_color (EggFindBar *find_bar,
720 GdkColor found_color = { 0, 0, 0, 0x0f0f };
722 get_style_color (find_bar, "all_matches_color", &found_color);
724 *color = found_color;
728 * egg_find_bar_get_current_match_color:
730 * Gets the color to use to highlight the match
731 * we're currently on.
736 egg_find_bar_get_current_match_color (EggFindBar *find_bar,
739 GdkColor found_color = { 0, 0, 0, 0xffff };
741 get_style_color (find_bar, "current_match_color", &found_color);
743 *color = found_color;
747 * egg_find_bar_set_status_text:
749 * Sets some text to display if there's space; typical text would
750 * be something like "5 results on this page" or "No results"
752 * @text: the text to display
757 egg_find_bar_set_status_text (EggFindBar *find_bar,
760 EggFindBarPrivate *priv;
762 g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
764 priv = (EggFindBarPrivate *)find_bar->priv;
766 if (text == NULL || *text == '\0')
768 gtk_widget_hide (priv->status_label);
769 gtk_widget_hide (priv->status_separator);
770 gtk_label_set_text (GTK_LABEL (priv->status_label), NULL);
774 gtk_label_set_text (GTK_LABEL (priv->status_label), text);
775 gtk_widget_show (priv->status_label);
776 gtk_widget_show (priv->status_separator);