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 typedef struct _EggFindBarPrivate EggFindBarPrivate;
36 struct _EggFindBarPrivate
40 GtkWidget *close_button;
41 GtkWidget *find_entry;
42 GtkWidget *next_button;
43 GtkWidget *previous_button;
44 GtkWidget *case_button;
45 GtkWidget *status_separator;
46 GtkWidget *status_label;
47 guint case_sensitive : 1;
50 #define EGG_FIND_BAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EGG_TYPE_FIND_BAR, EggFindBarPrivate))
60 static void egg_find_bar_finalize (GObject *object);
61 static void egg_find_bar_get_property (GObject *object,
65 static void egg_find_bar_set_property (GObject *object,
69 static void egg_find_bar_size_request (GtkWidget *widget,
70 GtkRequisition *requisition);
71 static void egg_find_bar_size_allocate (GtkWidget *widget,
72 GtkAllocation *allocation);
74 G_DEFINE_TYPE (EggFindBar, egg_find_bar, GTK_TYPE_BIN);
84 static guint find_bar_signals[LAST_SIGNAL] = { 0 };
87 egg_find_bar_class_init (EggFindBarClass *klass)
89 GObjectClass *object_class;
90 GtkWidgetClass *widget_class;
91 GtkBinClass *bin_class;
92 GtkBindingSet *binding_set;
94 object_class = (GObjectClass *)klass;
95 widget_class = (GtkWidgetClass *)klass;
96 bin_class = (GtkBinClass *)klass;
98 object_class->set_property = egg_find_bar_set_property;
99 object_class->get_property = egg_find_bar_get_property;
101 object_class->finalize = egg_find_bar_finalize;
103 widget_class->size_request = egg_find_bar_size_request;
104 widget_class->size_allocate = egg_find_bar_size_allocate;
106 find_bar_signals[NEXT] =
107 g_signal_new ("next",
108 G_OBJECT_CLASS_TYPE (object_class),
112 g_cclosure_marshal_VOID__VOID,
114 find_bar_signals[PREVIOUS] =
115 g_signal_new ("previous",
116 G_OBJECT_CLASS_TYPE (object_class),
120 g_cclosure_marshal_VOID__VOID,
122 find_bar_signals[CLOSE] =
123 g_signal_new ("close",
124 G_OBJECT_CLASS_TYPE (object_class),
125 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
128 g_cclosure_marshal_VOID__VOID,
132 * EggFindBar:search_string:
134 * The current string to search for. NULL or empty string
135 * both mean no current string.
138 g_object_class_install_property (object_class,
140 g_param_spec_string ("search_string",
142 _("The name of the string to be found"),
147 * EggFindBar:case_sensitive:
149 * TRUE for a case sensitive search.
152 g_object_class_install_property (object_class,
154 g_param_spec_boolean ("case_sensitive",
156 _("TRUE for a case sensitive search"),
160 /* Style properties */
161 gtk_widget_class_install_style_property (widget_class,
162 g_param_spec_boxed ("all_matches_color",
163 _("Highlight color"),
164 _("Color of highlight for all matches"),
168 gtk_widget_class_install_style_property (widget_class,
169 g_param_spec_boxed ("current_match_color",
171 _("Color of highlight for the current match"),
175 g_type_class_add_private (object_class, sizeof (EggFindBarPrivate));
177 binding_set = gtk_binding_set_by_class (klass);
179 gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
184 egg_find_bar_emit_next (EggFindBar *find_bar)
186 g_signal_emit (find_bar, find_bar_signals[NEXT], 0);
190 egg_find_bar_emit_previous (EggFindBar *find_bar)
192 g_signal_emit (find_bar, find_bar_signals[PREVIOUS], 0);
196 egg_find_bar_emit_close (EggFindBar *find_bar)
198 g_signal_emit (find_bar, find_bar_signals[CLOSE], 0);
202 close_clicked_callback (GtkButton *button,
205 EggFindBar *find_bar = EGG_FIND_BAR (data);
207 egg_find_bar_emit_close (find_bar);
211 next_clicked_callback (GtkButton *button,
214 EggFindBar *find_bar = EGG_FIND_BAR (data);
216 egg_find_bar_emit_next (find_bar);
220 previous_clicked_callback (GtkButton *button,
223 EggFindBar *find_bar = EGG_FIND_BAR (data);
225 egg_find_bar_emit_previous (find_bar);
229 case_sensitive_toggled_callback (GtkCheckButton *button,
232 EggFindBar *find_bar = EGG_FIND_BAR (data);
234 egg_find_bar_set_case_sensitive (find_bar,
235 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
239 entry_activate_callback (GtkEntry *entry,
242 EggFindBar *find_bar = EGG_FIND_BAR (data);
243 EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->private_data;
245 /* We activate the "next" button here so we'll get a nice
247 gtk_widget_activate (priv->next_button);
251 entry_changed_callback (GtkEntry *entry,
254 EggFindBar *find_bar = EGG_FIND_BAR (data);
257 /* paranoid strdup because set_search_string() sets
260 text = g_strdup (gtk_entry_get_text (entry));
262 egg_find_bar_set_search_string (find_bar, text);
268 egg_find_bar_init (EggFindBar *find_bar)
270 EggFindBarPrivate *priv;
272 GtkWidget *separator;
274 GtkWidget *image_back;
275 GtkWidget *image_forward;
278 priv = EGG_FIND_BAR_GET_PRIVATE (find_bar);
279 find_bar->private_data = priv;
281 priv->search_string = NULL;
284 gtk_widget_push_composite_child ();
285 priv->hbox = gtk_hbox_new (FALSE, 6);
286 gtk_container_set_border_width (GTK_CONTAINER (priv->hbox), 3);
288 label = gtk_label_new_with_mnemonic (_("F_ind:"));
289 separator = gtk_vseparator_new ();
291 priv->close_button = gtk_button_new ();
292 gtk_button_set_relief (GTK_BUTTON (priv->close_button),
294 image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
295 GTK_ICON_SIZE_SMALL_TOOLBAR);
296 gtk_container_add (GTK_CONTAINER (priv->close_button), image);
298 priv->find_entry = gtk_entry_new ();
299 gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->find_entry);
301 priv->previous_button = gtk_button_new_with_mnemonic (_("_Previous"));
302 priv->next_button = gtk_button_new_with_mnemonic (_("_Next"));
304 image_back = gtk_image_new_from_stock (GTK_STOCK_GO_BACK,
305 GTK_ICON_SIZE_BUTTON);
306 image_forward = gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD,
307 GTK_ICON_SIZE_BUTTON);
309 gtk_button_set_image (GTK_BUTTON (priv->previous_button),
311 gtk_button_set_image (GTK_BUTTON (priv->next_button),
314 priv->case_button = gtk_check_button_new_with_mnemonic (_("C_ase Sensitive"));
316 priv->status_separator = gtk_vseparator_new ();
318 priv->status_label = gtk_label_new (NULL);
319 gtk_label_set_ellipsize (GTK_LABEL (priv->status_label),
320 PANGO_ELLIPSIZE_END);
321 gtk_misc_set_alignment (GTK_MISC (priv->status_label), 0.0, 0.5);
325 GtkWidget *button_label;
326 /* This hack doesn't work because GtkCheckButton doesn't pass the
327 * larger size allocation to the label, it always gives the label
328 * its exact request. If you un-ifdef this, set the box back
329 * on case_button to TRUE, TRUE below
331 button_label = gtk_bin_get_child (GTK_BIN (priv->case_button));
332 gtk_label_set_ellipsize (GTK_LABEL (button_label),
333 PANGO_ELLIPSIZE_END);
337 gtk_box_pack_start (GTK_BOX (priv->hbox),
338 priv->close_button, FALSE, FALSE, 0);
339 gtk_box_pack_start (GTK_BOX (priv->hbox),
340 label, FALSE, FALSE, 0);
341 gtk_box_pack_start (GTK_BOX (priv->hbox),
342 priv->find_entry, FALSE, FALSE, 0);
343 gtk_box_pack_start (GTK_BOX (priv->hbox),
344 priv->previous_button, FALSE, FALSE, 0);
345 gtk_box_pack_start (GTK_BOX (priv->hbox),
346 priv->next_button, FALSE, FALSE, 0);
347 gtk_box_pack_start (GTK_BOX (priv->hbox),
348 separator, FALSE, FALSE, 0);
349 gtk_box_pack_start (GTK_BOX (priv->hbox),
350 priv->case_button, FALSE, FALSE, 0);
351 gtk_box_pack_start (GTK_BOX (priv->hbox),
352 priv->status_separator, FALSE, FALSE, 0);
353 gtk_box_pack_start (GTK_BOX (priv->hbox),
354 priv->status_label, TRUE, TRUE, 0);
356 gtk_container_add (GTK_CONTAINER (find_bar), priv->hbox);
358 gtk_widget_show (priv->hbox);
359 gtk_widget_show (priv->close_button);
360 gtk_widget_show (priv->find_entry);
361 gtk_widget_show (priv->previous_button);
362 gtk_widget_show (priv->next_button);
363 gtk_widget_show (separator);
364 gtk_widget_show (label);
365 gtk_widget_show (image);
366 gtk_widget_show (image_back);
367 gtk_widget_show (image_forward);
368 /* don't show status separator/label until they are set */
370 gtk_widget_pop_composite_child ();
372 gtk_widget_show_all (priv->hbox);
374 g_signal_connect (priv->close_button, "clicked",
375 G_CALLBACK (close_clicked_callback),
377 g_signal_connect (priv->find_entry, "changed",
378 G_CALLBACK (entry_changed_callback),
380 g_signal_connect (priv->find_entry, "activate",
381 G_CALLBACK (entry_activate_callback),
383 g_signal_connect (priv->next_button, "clicked",
384 G_CALLBACK (next_clicked_callback),
386 g_signal_connect (priv->previous_button, "clicked",
387 G_CALLBACK (previous_clicked_callback),
389 g_signal_connect (priv->case_button, "toggled",
390 G_CALLBACK (case_sensitive_toggled_callback),
395 egg_find_bar_finalize (GObject *object)
397 EggFindBar *find_bar = EGG_FIND_BAR (object);
398 EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->private_data;
400 g_free (priv->search_string);
402 G_OBJECT_CLASS (egg_find_bar_parent_class)->finalize (object);
406 egg_find_bar_set_property (GObject *object,
411 EggFindBar *find_bar = EGG_FIND_BAR (object);
415 case PROP_SEARCH_STRING:
416 egg_find_bar_set_search_string (find_bar, g_value_get_string (value));
418 case PROP_CASE_SENSITIVE:
419 egg_find_bar_set_case_sensitive (find_bar, g_value_get_boolean (value));
422 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
428 egg_find_bar_get_property (GObject *object,
433 EggFindBar *find_bar = EGG_FIND_BAR (object);
434 EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->private_data;
438 case PROP_SEARCH_STRING:
439 g_value_set_string (value, priv->search_string);
441 case PROP_CASE_SENSITIVE:
442 g_value_set_boolean (value, priv->case_sensitive);
445 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
451 egg_find_bar_size_request (GtkWidget *widget,
452 GtkRequisition *requisition)
454 GtkBin *bin = GTK_BIN (widget);
455 GtkRequisition child_requisition;
456 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
458 gtk_widget_size_request (bin->child, &child_requisition);
460 *requisition = child_requisition;
464 requisition->width = 0;
465 requisition->height = 0;
470 egg_find_bar_size_allocate (GtkWidget *widget,
471 GtkAllocation *allocation)
473 GtkBin *bin = GTK_BIN (widget);
475 widget->allocation = *allocation;
477 if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
478 gtk_widget_size_allocate (bin->child, allocation);
484 * Creates a new #EggFindBar.
486 * Returns: a newly created #EggFindBar
491 egg_find_bar_new (void)
493 EggFindBar *find_bar;
495 find_bar = g_object_new (EGG_TYPE_FIND_BAR, NULL);
497 return GTK_WIDGET (find_bar);
501 * egg_find_bar_set_search_string:
503 * Sets the string that should be found/highlighted in the document.
504 * Empty string is converted to NULL.
509 egg_find_bar_set_search_string (EggFindBar *find_bar,
510 const char *search_string)
512 EggFindBarPrivate *priv;
514 g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
516 priv = (EggFindBarPrivate *)find_bar->private_data;
518 g_object_freeze_notify (G_OBJECT (find_bar));
520 if (priv->search_string != search_string)
524 old = priv->search_string;
526 if (search_string && *search_string == '\0')
527 search_string = NULL;
529 /* Only update if the string has changed; setting the entry
530 * will emit changed on the entry which will re-enter
531 * this function, but we'll handle that fine with this
534 if ((old && search_string == NULL) ||
535 (old == NULL && search_string) ||
536 (old && search_string &&
537 strcmp (old, search_string) != 0))
539 priv->search_string = g_strdup (search_string);
542 gtk_entry_set_text (GTK_ENTRY (priv->find_entry),
543 priv->search_string ?
544 priv->search_string :
547 g_object_notify (G_OBJECT (find_bar),
552 g_object_thaw_notify (G_OBJECT (find_bar));
557 * egg_find_bar_get_search_string:
559 * Gets the string that should be found/highlighted in the document.
561 * Returns: the string
566 egg_find_bar_get_search_string (EggFindBar *find_bar)
568 EggFindBarPrivate *priv;
570 g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), NULL);
572 priv = (EggFindBarPrivate *)find_bar->private_data;
574 return priv->search_string;
578 * egg_find_bar_set_case_sensitive:
580 * Sets whether the search is case sensitive
585 egg_find_bar_set_case_sensitive (EggFindBar *find_bar,
586 gboolean case_sensitive)
588 EggFindBarPrivate *priv;
590 g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
592 priv = (EggFindBarPrivate *)find_bar->private_data;
594 g_object_freeze_notify (G_OBJECT (find_bar));
596 case_sensitive = case_sensitive != FALSE;
598 if (priv->case_sensitive != case_sensitive)
600 priv->case_sensitive = case_sensitive;
602 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->case_button),
603 priv->case_sensitive);
605 g_object_notify (G_OBJECT (find_bar),
609 g_object_thaw_notify (G_OBJECT (find_bar));
613 * egg_find_bar_get_case_sensitive:
615 * Gets whether the search is case sensitive
617 * Returns: TRUE if it's case sensitive
622 egg_find_bar_get_case_sensitive (EggFindBar *find_bar)
624 EggFindBarPrivate *priv;
626 g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), FALSE);
628 priv = (EggFindBarPrivate *)find_bar->private_data;
630 return priv->case_sensitive;
634 get_style_color (EggFindBar *find_bar,
635 const char *style_prop_name,
638 GdkColor *style_color;
640 gtk_widget_ensure_style (GTK_WIDGET (find_bar));
641 gtk_widget_style_get (GTK_WIDGET (find_bar),
642 "color", &style_color, NULL);
645 *color = *style_color;
646 gdk_color_free (style_color);
651 * egg_find_bar_get_all_matches_color:
653 * Gets the color to use to highlight all the
659 egg_find_bar_get_all_matches_color (EggFindBar *find_bar,
662 GdkColor found_color = { 0, 0, 0, 0x0f0f };
664 get_style_color (find_bar, "all_matches_color", &found_color);
666 *color = found_color;
670 * egg_find_bar_get_current_match_color:
672 * Gets the color to use to highlight the match
673 * we're currently on.
678 egg_find_bar_get_current_match_color (EggFindBar *find_bar,
681 GdkColor found_color = { 0, 0, 0, 0xffff };
683 get_style_color (find_bar, "current_match_color", &found_color);
685 *color = found_color;
689 * egg_find_bar_grab_focus:
691 * Focuses the text entry in the find bar; currently GTK+ doesn't have
692 * a way to make this work on gtk_widget_grab_focus(find_bar).
697 egg_find_bar_grab_focus (EggFindBar *find_bar)
699 EggFindBarPrivate *priv;
701 g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
703 priv = (EggFindBarPrivate *)find_bar->private_data;
705 gtk_widget_grab_focus (priv->find_entry);
709 * egg_find_bar_set_status_text:
711 * Sets some text to display if there's space; typical text would
712 * be something like "5 results on this page" or "No results"
714 * @text: the text to display
719 egg_find_bar_set_status_text (EggFindBar *find_bar,
722 EggFindBarPrivate *priv;
724 g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
726 priv = (EggFindBarPrivate *)find_bar->private_data;
728 if (text == NULL || *text == '\0')
730 gtk_widget_hide (priv->status_label);
731 gtk_widget_hide (priv->status_separator);
732 gtk_label_set_text (GTK_LABEL (priv->status_label), NULL);
736 gtk_label_set_text (GTK_LABEL (priv->status_label), text);
737 gtk_widget_show (priv->status_label);
738 gtk_widget_show (priv->status_separator);