]> www.fi.muni.cz Git - evince.git/blob - shell/eggfindbar.c
hook up the find bar widget; now we just need a document to find things in
[evince.git] / shell / eggfindbar.c
1 /* Copyright (C) 2004 Red Hat, Inc.
2
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.
7
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.
12
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.
17 */
18
19 #include <config.h>
20
21 #include "eggfindbar.h"
22
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>
32
33 #include <string.h>
34
35 typedef struct _EggFindBarPrivate EggFindBarPrivate;
36 struct _EggFindBarPrivate
37 {
38   gchar *search_string;
39   GtkWidget *hbox;
40   GtkWidget *close_button;
41   GtkWidget *find_entry;
42   GtkWidget *next_button;
43   GtkWidget *previous_button;
44   GtkWidget *case_button;
45   guint case_sensitive : 1;
46 };
47
48 #define EGG_FIND_BAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EGG_TYPE_FIND_BAR, EggFindBarPrivate))
49
50
51 enum
52   {
53     PROP_0,
54     PROP_SEARCH_STRING,
55     PROP_CASE_SENSITIVE
56   };
57
58 static void egg_find_bar_finalize      (GObject        *object);
59 static void egg_find_bar_get_property  (GObject        *object,
60                                         guint           prop_id,
61                                         GValue         *value,
62                                         GParamSpec     *pspec);
63 static void egg_find_bar_set_property  (GObject        *object,
64                                         guint           prop_id,
65                                         const GValue   *value,
66                                         GParamSpec     *pspec);
67 static void egg_find_bar_size_request  (GtkWidget      *widget,
68                                         GtkRequisition *requisition);
69 static void egg_find_bar_size_allocate (GtkWidget      *widget,
70                                         GtkAllocation  *allocation);
71
72 G_DEFINE_TYPE (EggFindBar, egg_find_bar, GTK_TYPE_BIN);
73
74 enum
75   {
76     NEXT,
77     PREVIOUS,
78     CLOSE,
79     LAST_SIGNAL
80   };
81
82 static guint find_bar_signals[LAST_SIGNAL] = { 0 };
83
84 static void
85 egg_find_bar_class_init (EggFindBarClass *klass)
86 {
87   GObjectClass *object_class;
88   GtkWidgetClass *widget_class;
89   GtkBinClass *bin_class;
90   GtkBindingSet *binding_set;
91
92   object_class = (GObjectClass *)klass;
93   widget_class = (GtkWidgetClass *)klass;
94   bin_class = (GtkBinClass *)klass;
95
96   object_class->set_property = egg_find_bar_set_property;
97   object_class->get_property = egg_find_bar_get_property;
98
99   object_class->finalize = egg_find_bar_finalize;
100
101   widget_class->size_request = egg_find_bar_size_request;
102   widget_class->size_allocate = egg_find_bar_size_allocate;
103
104   find_bar_signals[NEXT] =
105     g_signal_new ("next",
106                   G_OBJECT_CLASS_TYPE (object_class),
107                   G_SIGNAL_RUN_FIRST,
108                   0,
109                   NULL, NULL,
110                   g_cclosure_marshal_VOID__VOID,
111                   G_TYPE_NONE, 0);
112   find_bar_signals[PREVIOUS] =
113     g_signal_new ("previous",
114                   G_OBJECT_CLASS_TYPE (object_class),
115                   G_SIGNAL_RUN_FIRST,
116                   0,
117                   NULL, NULL,
118                   g_cclosure_marshal_VOID__VOID,
119                   G_TYPE_NONE, 0);
120   find_bar_signals[CLOSE] =
121     g_signal_new ("close",
122                   G_OBJECT_CLASS_TYPE (object_class),
123                   G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
124                   0,
125                   NULL, NULL,
126                   g_cclosure_marshal_VOID__VOID,
127                   G_TYPE_NONE, 0);
128
129   /**
130    * EggFindBar:search_string:
131    *
132    * The current string to search for. NULL or empty string
133    * both mean no current string.
134    *
135    */
136   g_object_class_install_property (object_class,
137                                    PROP_SEARCH_STRING,
138                                    g_param_spec_string ("search_string",
139                                                         _("Search string"),
140                                                         _("The name of the string to be found"),
141                                                         NULL,
142                                                         G_PARAM_READWRITE));
143
144   /**
145    * EggFindBar:case_sensitive:
146    *
147    * TRUE for a case sensitive search.
148    *
149    */
150   g_object_class_install_property (object_class,
151                                    PROP_CASE_SENSITIVE,
152                                    g_param_spec_boolean ("case_sensitive",
153                                                          _("Case sensitive"),
154                                                          _("TRUE for a case sensitive search"),
155                                                          FALSE,
156                                                          G_PARAM_READWRITE));
157
158   /* Style properties */
159   gtk_widget_class_install_style_property (widget_class,
160                                            g_param_spec_boxed ("all_matches_color",
161                                                                _("Highlight color"),
162                                                                _("Color of highlight for all matches"),
163                                                                GDK_TYPE_COLOR,
164                                                                G_PARAM_READABLE));
165
166   gtk_widget_class_install_style_property (widget_class,
167                                            g_param_spec_boxed ("current_match_color",
168                                                                _("Current color"),
169                                                                _("Color of highlight for the current match"),
170                                                                GDK_TYPE_COLOR,
171                                                                G_PARAM_READABLE));
172
173   g_type_class_add_private (object_class, sizeof (EggFindBarPrivate));
174
175   binding_set = gtk_binding_set_by_class (klass);
176
177   gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
178                                 "close", 0);
179 }
180
181 static void
182 egg_find_bar_emit_next (EggFindBar *find_bar)
183 {
184   g_signal_emit (find_bar, find_bar_signals[NEXT], 0);
185 }
186
187 static void
188 egg_find_bar_emit_previous (EggFindBar *find_bar)
189 {
190   g_signal_emit (find_bar, find_bar_signals[PREVIOUS], 0);
191 }
192
193 static void
194 egg_find_bar_emit_close (EggFindBar *find_bar)
195 {
196   g_signal_emit (find_bar, find_bar_signals[CLOSE], 0);
197 }
198
199 static void
200 close_clicked_callback (GtkButton *button,
201                         void      *data)
202 {
203   EggFindBar *find_bar = EGG_FIND_BAR (data);
204
205   egg_find_bar_emit_close (find_bar);
206 }
207
208 static void
209 next_clicked_callback (GtkButton *button,
210                        void      *data)
211 {
212   EggFindBar *find_bar = EGG_FIND_BAR (data);
213
214   egg_find_bar_emit_next (find_bar);
215 }
216
217 static void
218 previous_clicked_callback (GtkButton *button,
219                            void      *data)
220 {
221   EggFindBar *find_bar = EGG_FIND_BAR (data);
222
223   egg_find_bar_emit_previous (find_bar);
224 }
225
226 static void
227 case_sensitive_toggled_callback (GtkCheckButton *button,
228                                  void           *data)
229 {
230   EggFindBar *find_bar = EGG_FIND_BAR (data);
231
232   egg_find_bar_set_case_sensitive (find_bar,
233                                    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
234 }
235
236 static void
237 entry_activate_callback (GtkEntry *entry,
238                           void     *data)
239 {
240   EggFindBar *find_bar = EGG_FIND_BAR (data);
241
242   egg_find_bar_emit_next (find_bar);
243 }
244
245 static void
246 entry_changed_callback (GtkEntry *entry,
247                         void     *data)
248 {
249   EggFindBar *find_bar = EGG_FIND_BAR (data);
250   char *text;
251
252   /* paranoid strdup because set_search_string() sets
253    * the entry text
254    */
255   text = g_strdup (gtk_entry_get_text (entry));
256
257   egg_find_bar_set_search_string (find_bar, text);
258
259   g_free (text);
260 }
261
262 static void
263 egg_find_bar_init (EggFindBar *find_bar)
264 {
265   EggFindBarPrivate *priv;
266   GtkWidget *label;
267   GtkWidget *separator;
268   GtkWidget *image;
269
270   /* Data */
271   priv = EGG_FIND_BAR_GET_PRIVATE (find_bar);
272   find_bar->private_data = priv;
273
274   priv->search_string = NULL;
275
276   /* Widgets */
277   gtk_widget_push_composite_child ();
278   priv->hbox = gtk_hbox_new (FALSE, 6);
279   gtk_container_set_border_width (GTK_CONTAINER (priv->hbox), 3);
280
281   label = gtk_label_new_with_mnemonic (_("F_ind:"));
282   separator = gtk_vseparator_new ();
283
284   priv->close_button = gtk_button_new ();
285   gtk_button_set_relief (GTK_BUTTON (priv->close_button),
286                          GTK_RELIEF_NONE);
287   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
288                                     GTK_ICON_SIZE_SMALL_TOOLBAR);
289   gtk_container_add (GTK_CONTAINER (priv->close_button), image);
290
291   priv->find_entry = gtk_entry_new ();
292   gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->find_entry);
293   
294   priv->previous_button = gtk_button_new_from_stock (GTK_STOCK_GO_BACK);
295   priv->next_button = gtk_button_new_from_stock (GTK_STOCK_GO_FORWARD);
296
297   priv->case_button = gtk_check_button_new_with_mnemonic (_("C_ase Sensitive"));
298
299   gtk_box_pack_start (GTK_BOX (priv->hbox),
300                       priv->close_button, FALSE, FALSE, 0);
301   gtk_box_pack_start (GTK_BOX (priv->hbox),
302                       label, FALSE, FALSE, 0);
303   gtk_box_pack_start (GTK_BOX (priv->hbox),
304                       priv->find_entry, FALSE, FALSE, 0);
305   gtk_box_pack_start (GTK_BOX (priv->hbox),
306                       priv->previous_button, FALSE, FALSE, 0);
307   gtk_box_pack_start (GTK_BOX (priv->hbox),
308                       priv->next_button, FALSE, FALSE, 0);
309   gtk_box_pack_start (GTK_BOX (priv->hbox),
310                       separator, FALSE, FALSE, 0);
311   gtk_box_pack_start (GTK_BOX (priv->hbox),
312                       priv->case_button, FALSE, FALSE, 0);
313
314   gtk_container_add (GTK_CONTAINER (find_bar), priv->hbox);
315
316   gtk_widget_show (priv->hbox);
317   gtk_widget_show (priv->close_button);
318   gtk_widget_show (priv->find_entry);
319   gtk_widget_show (priv->previous_button);
320   gtk_widget_show (priv->next_button);
321   gtk_widget_show (separator);
322   gtk_widget_show (label);
323   gtk_widget_show (image);
324
325   gtk_widget_pop_composite_child ();
326
327   gtk_widget_show_all (priv->hbox);
328
329   g_signal_connect (priv->close_button, "clicked",
330                     G_CALLBACK (close_clicked_callback),
331                     find_bar);
332   g_signal_connect (priv->find_entry, "changed",
333                     G_CALLBACK (entry_changed_callback),
334                     find_bar);
335   g_signal_connect (priv->find_entry, "activate",
336                     G_CALLBACK (entry_activate_callback),
337                     find_bar);
338   g_signal_connect (priv->next_button, "clicked",
339                     G_CALLBACK (next_clicked_callback),
340                     find_bar);
341   g_signal_connect (priv->previous_button, "clicked",
342                     G_CALLBACK (previous_clicked_callback),
343                     find_bar);
344   g_signal_connect (priv->case_button, "toggled",
345                     G_CALLBACK (case_sensitive_toggled_callback),
346                     find_bar);
347 }
348
349 static void
350 egg_find_bar_finalize (GObject *object)
351 {
352   EggFindBar *find_bar = EGG_FIND_BAR (object);
353   EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->private_data;
354
355   g_free (priv->search_string);
356
357   G_OBJECT_CLASS (egg_find_bar_parent_class)->finalize (object);
358 }
359
360 static void
361 egg_find_bar_set_property (GObject      *object,
362                            guint         prop_id,
363                            const GValue *value,
364                            GParamSpec   *pspec)
365 {
366   EggFindBar *find_bar = EGG_FIND_BAR (object);
367
368   switch (prop_id)
369     {
370     case PROP_SEARCH_STRING:
371       egg_find_bar_set_search_string (find_bar, g_value_get_string (value));
372       break;
373     case PROP_CASE_SENSITIVE:
374       egg_find_bar_set_case_sensitive (find_bar, g_value_get_boolean (value));
375       break;
376     default:
377       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
378       break;
379     }
380 }
381
382 static void
383 egg_find_bar_get_property (GObject    *object,
384                            guint       prop_id,
385                            GValue     *value,
386                            GParamSpec *pspec)
387 {
388   EggFindBar *find_bar = EGG_FIND_BAR (object);
389   EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->private_data;
390
391   switch (prop_id)
392     {
393     case PROP_SEARCH_STRING:
394       g_value_set_string (value, priv->search_string);
395       break;
396     case PROP_CASE_SENSITIVE:
397       g_value_set_boolean (value, priv->case_sensitive);
398       break;
399     default:
400       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
401       break;
402     }
403 }
404
405 static void
406 egg_find_bar_size_request (GtkWidget      *widget,
407                            GtkRequisition *requisition)
408 {
409   GtkBin *bin = GTK_BIN (widget);
410   GtkRequisition child_requisition;
411   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
412     {
413       gtk_widget_size_request (bin->child, &child_requisition);
414
415       *requisition = child_requisition;
416     }
417   else
418     {
419       requisition->width = 0;
420       requisition->height = 0;
421     }
422 }
423
424 static void
425 egg_find_bar_size_allocate (GtkWidget     *widget,
426                             GtkAllocation *allocation)
427 {
428   GtkBin *bin = GTK_BIN (widget);
429
430   widget->allocation = *allocation;
431
432   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
433     gtk_widget_size_allocate (bin->child, allocation);
434 }
435
436 /**
437  * egg_find_bar_new:
438  *
439  * Creates a new #EggFindBar.
440  *
441  * Returns: a newly created #EggFindBar
442  *
443  * Since: 2.6
444  */
445 GtkWidget *
446 egg_find_bar_new (void)
447 {
448   EggFindBar *find_bar;
449
450   find_bar = g_object_new (EGG_TYPE_FIND_BAR, NULL);
451
452   return GTK_WIDGET (find_bar);
453 }
454
455 /**
456  * egg_find_bar_set_search_string:
457  *
458  * Sets the string that should be found/highlighted in the document.
459  * Empty string is converted to NULL.
460  *
461  * Since: 2.6
462  */
463 void
464 egg_find_bar_set_search_string  (EggFindBar *find_bar,
465                                  const char *search_string)
466 {
467   EggFindBarPrivate *priv;
468
469   g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
470
471   priv = (EggFindBarPrivate *)find_bar->private_data;
472
473   g_object_freeze_notify (G_OBJECT (find_bar));
474   
475   if (priv->search_string != search_string)
476     {
477       char *old;
478       
479       old = priv->search_string;
480
481       if (search_string && *search_string == '\0')
482         search_string = NULL;
483
484       /* Only update if the string has changed; setting the entry
485        * will emit changed on the entry which will re-enter
486        * this function, but we'll handle that fine with this
487        * short-circuit.
488        */
489       if ((old && search_string == NULL) ||
490           (old == NULL && search_string) ||
491           (old && search_string &&
492            strcmp (old, search_string) != 0))
493         {
494           priv->search_string = g_strdup (search_string);
495           g_free (old);
496           
497           gtk_entry_set_text (GTK_ENTRY (priv->find_entry),
498                               priv->search_string ?
499                               priv->search_string :
500                               "");
501           
502           g_object_notify (G_OBJECT (find_bar),
503                            "search_string");
504         }
505     }
506
507   g_object_thaw_notify (G_OBJECT (find_bar));
508 }
509
510
511 /**
512  * egg_find_bar_get_search_string:
513  *
514  * Gets the string that should be found/highlighted in the document.
515  *
516  * Returns: the string
517  *
518  * Since: 2.6
519  */
520 const char*
521 egg_find_bar_get_search_string  (EggFindBar *find_bar)
522 {
523   EggFindBarPrivate *priv;
524
525   g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), NULL);
526
527   priv = (EggFindBarPrivate *)find_bar->private_data;
528
529   return priv->search_string;
530 }
531
532 /**
533  * egg_find_bar_set_case_sensitive:
534  *
535  * Sets whether the search is case sensitive
536  *
537  * Since: 2.6
538  */
539 void
540 egg_find_bar_set_case_sensitive (EggFindBar *find_bar,
541                                  gboolean    case_sensitive)
542 {
543   EggFindBarPrivate *priv;
544
545   g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
546
547   priv = (EggFindBarPrivate *)find_bar->private_data;
548
549   g_object_freeze_notify (G_OBJECT (find_bar));
550
551   case_sensitive = case_sensitive != FALSE;
552
553   if (priv->case_sensitive != case_sensitive)
554     {
555       priv->case_sensitive = case_sensitive;
556
557       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->case_button),
558                                     priv->case_sensitive);
559
560       g_object_notify (G_OBJECT (find_bar),
561                        "case_sensitive");
562     }
563
564   g_object_thaw_notify (G_OBJECT (find_bar));
565 }
566
567 /**
568  * egg_find_bar_get_case_sensitive:
569  *
570  * Gets whether the search is case sensitive
571  *
572  * Returns: TRUE if it's case sensitive
573  *
574  * Since: 2.6
575  */
576 gboolean
577 egg_find_bar_get_case_sensitive (EggFindBar *find_bar)
578 {
579   EggFindBarPrivate *priv;
580
581   g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), FALSE);
582
583   priv = (EggFindBarPrivate *)find_bar->private_data;
584
585   return priv->case_sensitive;
586 }
587
588 static void
589 get_style_color (EggFindBar *find_bar,
590                  const char *style_prop_name,
591                  GdkColor   *color)
592 {
593   GdkColor *style_color;
594
595   gtk_widget_ensure_style (GTK_WIDGET (find_bar));
596   gtk_widget_style_get (GTK_WIDGET (find_bar),
597                         "color", &style_color, NULL);
598   if (style_color)
599     {
600       *color = *style_color;
601       gdk_color_free (style_color);
602     }
603 }
604
605 /**
606  * egg_find_bar_get_all_matches_color:
607  *
608  * Gets the color to use to highlight all the
609  * known matches.
610  *
611  * Since: 2.6
612  */
613 void
614 egg_find_bar_get_all_matches_color (EggFindBar *find_bar,
615                                     GdkColor   *color)
616 {
617   GdkColor found_color = { 0, 0, 0, 0x0f0f };
618
619   get_style_color (find_bar, "all_matches_color", &found_color);
620
621   *color = found_color;
622 }
623
624 /**
625  * egg_find_bar_get_current_match_color:
626  *
627  * Gets the color to use to highlight the match
628  * we're currently on.
629  *
630  * Since: 2.6
631  */
632 void
633 egg_find_bar_get_current_match_color (EggFindBar *find_bar,
634                                       GdkColor   *color)
635 {
636   GdkColor found_color = { 0, 0, 0, 0xffff };
637
638   get_style_color (find_bar, "current_match_color", &found_color);
639
640   *color = found_color;
641 }
642
643 /**
644  * egg_find_bar_grab_focus:
645  *
646  * Focuses the text entry in the find bar; currently GTK+ doesn't have
647  * a way to make this work on gtk_widget_grab_focus(find_bar).
648  *
649  * Since: 2.6
650  */
651 void
652 egg_find_bar_grab_focus (EggFindBar *find_bar)
653 {
654   EggFindBarPrivate *priv;
655
656   g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
657
658   priv = (EggFindBarPrivate *)find_bar->private_data;
659  
660   gtk_widget_grab_focus (priv->find_entry);
661 }