]> www.fi.muni.cz Git - evince.git/blob - shell/eggfindbar.c
change buttons to previous/next instead of back/forward
[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   GtkWidget *image_back;
270   GtkWidget *image_forward;
271
272   /* Data */
273   priv = EGG_FIND_BAR_GET_PRIVATE (find_bar);
274   find_bar->private_data = priv;
275
276   priv->search_string = NULL;
277
278   /* Widgets */
279   gtk_widget_push_composite_child ();
280   priv->hbox = gtk_hbox_new (FALSE, 6);
281   gtk_container_set_border_width (GTK_CONTAINER (priv->hbox), 3);
282
283   label = gtk_label_new_with_mnemonic (_("F_ind:"));
284   separator = gtk_vseparator_new ();
285
286   priv->close_button = gtk_button_new ();
287   gtk_button_set_relief (GTK_BUTTON (priv->close_button),
288                          GTK_RELIEF_NONE);
289   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
290                                     GTK_ICON_SIZE_SMALL_TOOLBAR);
291   gtk_container_add (GTK_CONTAINER (priv->close_button), image);
292
293   priv->find_entry = gtk_entry_new ();
294   gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->find_entry);
295   
296   priv->previous_button = gtk_button_new_with_mnemonic (_("_Previous"));
297   priv->next_button = gtk_button_new_with_mnemonic (_("_Next"));
298
299   image_back = gtk_image_new_from_stock (GTK_STOCK_GO_BACK,
300                                          GTK_ICON_SIZE_BUTTON);
301   image_forward = gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD,
302                                             GTK_ICON_SIZE_BUTTON);
303
304   gtk_button_set_image (GTK_BUTTON (priv->previous_button),
305                         image_back);
306   gtk_button_set_image (GTK_BUTTON (priv->next_button),
307                         image_forward);
308   
309   priv->case_button = gtk_check_button_new_with_mnemonic (_("C_ase Sensitive"));
310
311 #if 0
312  {
313    GtkWidget *button_label;
314    /* This hack doesn't work because GtkCheckButton doesn't pass the
315     * larger size allocation to the label, it always gives the label
316     * its exact request. If you un-ifdef this, set the box back
317     * on case_button to TRUE, TRUE below
318     */
319    button_label = gtk_bin_get_child (GTK_BIN (priv->case_button));
320    gtk_label_set_ellipsize (GTK_LABEL (button_label),
321                             PANGO_ELLIPSIZE_END);
322  }
323 #endif
324   
325   gtk_box_pack_start (GTK_BOX (priv->hbox),
326                       priv->close_button, FALSE, FALSE, 0);
327   gtk_box_pack_start (GTK_BOX (priv->hbox),
328                       label, FALSE, FALSE, 0);
329   gtk_box_pack_start (GTK_BOX (priv->hbox),
330                       priv->find_entry, FALSE, FALSE, 0);
331   gtk_box_pack_start (GTK_BOX (priv->hbox),
332                       priv->previous_button, FALSE, FALSE, 0);
333   gtk_box_pack_start (GTK_BOX (priv->hbox),
334                       priv->next_button, FALSE, FALSE, 0);
335   gtk_box_pack_start (GTK_BOX (priv->hbox),
336                       separator, FALSE, FALSE, 0);
337   gtk_box_pack_start (GTK_BOX (priv->hbox),
338                       priv->case_button, FALSE, FALSE, 0);
339
340   gtk_container_add (GTK_CONTAINER (find_bar), priv->hbox);
341
342   gtk_widget_show (priv->hbox);
343   gtk_widget_show (priv->close_button);
344   gtk_widget_show (priv->find_entry);
345   gtk_widget_show (priv->previous_button);
346   gtk_widget_show (priv->next_button);
347   gtk_widget_show (separator);
348   gtk_widget_show (label);
349   gtk_widget_show (image);
350   gtk_widget_show (image_back);
351   gtk_widget_show (image_forward);
352
353   gtk_widget_pop_composite_child ();
354
355   gtk_widget_show_all (priv->hbox);
356
357   g_signal_connect (priv->close_button, "clicked",
358                     G_CALLBACK (close_clicked_callback),
359                     find_bar);
360   g_signal_connect (priv->find_entry, "changed",
361                     G_CALLBACK (entry_changed_callback),
362                     find_bar);
363   g_signal_connect (priv->find_entry, "activate",
364                     G_CALLBACK (entry_activate_callback),
365                     find_bar);
366   g_signal_connect (priv->next_button, "clicked",
367                     G_CALLBACK (next_clicked_callback),
368                     find_bar);
369   g_signal_connect (priv->previous_button, "clicked",
370                     G_CALLBACK (previous_clicked_callback),
371                     find_bar);
372   g_signal_connect (priv->case_button, "toggled",
373                     G_CALLBACK (case_sensitive_toggled_callback),
374                     find_bar);
375 }
376
377 static void
378 egg_find_bar_finalize (GObject *object)
379 {
380   EggFindBar *find_bar = EGG_FIND_BAR (object);
381   EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->private_data;
382
383   g_free (priv->search_string);
384
385   G_OBJECT_CLASS (egg_find_bar_parent_class)->finalize (object);
386 }
387
388 static void
389 egg_find_bar_set_property (GObject      *object,
390                            guint         prop_id,
391                            const GValue *value,
392                            GParamSpec   *pspec)
393 {
394   EggFindBar *find_bar = EGG_FIND_BAR (object);
395
396   switch (prop_id)
397     {
398     case PROP_SEARCH_STRING:
399       egg_find_bar_set_search_string (find_bar, g_value_get_string (value));
400       break;
401     case PROP_CASE_SENSITIVE:
402       egg_find_bar_set_case_sensitive (find_bar, g_value_get_boolean (value));
403       break;
404     default:
405       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
406       break;
407     }
408 }
409
410 static void
411 egg_find_bar_get_property (GObject    *object,
412                            guint       prop_id,
413                            GValue     *value,
414                            GParamSpec *pspec)
415 {
416   EggFindBar *find_bar = EGG_FIND_BAR (object);
417   EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->private_data;
418
419   switch (prop_id)
420     {
421     case PROP_SEARCH_STRING:
422       g_value_set_string (value, priv->search_string);
423       break;
424     case PROP_CASE_SENSITIVE:
425       g_value_set_boolean (value, priv->case_sensitive);
426       break;
427     default:
428       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
429       break;
430     }
431 }
432
433 static void
434 egg_find_bar_size_request (GtkWidget      *widget,
435                            GtkRequisition *requisition)
436 {
437   GtkBin *bin = GTK_BIN (widget);
438   GtkRequisition child_requisition;
439   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
440     {
441       gtk_widget_size_request (bin->child, &child_requisition);
442
443       *requisition = child_requisition;
444     }
445   else
446     {
447       requisition->width = 0;
448       requisition->height = 0;
449     }
450 }
451
452 static void
453 egg_find_bar_size_allocate (GtkWidget     *widget,
454                             GtkAllocation *allocation)
455 {
456   GtkBin *bin = GTK_BIN (widget);
457
458   widget->allocation = *allocation;
459
460   if (bin->child && GTK_WIDGET_VISIBLE (bin->child))
461     gtk_widget_size_allocate (bin->child, allocation);
462 }
463
464 /**
465  * egg_find_bar_new:
466  *
467  * Creates a new #EggFindBar.
468  *
469  * Returns: a newly created #EggFindBar
470  *
471  * Since: 2.6
472  */
473 GtkWidget *
474 egg_find_bar_new (void)
475 {
476   EggFindBar *find_bar;
477
478   find_bar = g_object_new (EGG_TYPE_FIND_BAR, NULL);
479
480   return GTK_WIDGET (find_bar);
481 }
482
483 /**
484  * egg_find_bar_set_search_string:
485  *
486  * Sets the string that should be found/highlighted in the document.
487  * Empty string is converted to NULL.
488  *
489  * Since: 2.6
490  */
491 void
492 egg_find_bar_set_search_string  (EggFindBar *find_bar,
493                                  const char *search_string)
494 {
495   EggFindBarPrivate *priv;
496
497   g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
498
499   priv = (EggFindBarPrivate *)find_bar->private_data;
500
501   g_object_freeze_notify (G_OBJECT (find_bar));
502   
503   if (priv->search_string != search_string)
504     {
505       char *old;
506       
507       old = priv->search_string;
508
509       if (search_string && *search_string == '\0')
510         search_string = NULL;
511
512       /* Only update if the string has changed; setting the entry
513        * will emit changed on the entry which will re-enter
514        * this function, but we'll handle that fine with this
515        * short-circuit.
516        */
517       if ((old && search_string == NULL) ||
518           (old == NULL && search_string) ||
519           (old && search_string &&
520            strcmp (old, search_string) != 0))
521         {
522           priv->search_string = g_strdup (search_string);
523           g_free (old);
524           
525           gtk_entry_set_text (GTK_ENTRY (priv->find_entry),
526                               priv->search_string ?
527                               priv->search_string :
528                               "");
529           
530           g_object_notify (G_OBJECT (find_bar),
531                            "search_string");
532         }
533     }
534
535   g_object_thaw_notify (G_OBJECT (find_bar));
536 }
537
538
539 /**
540  * egg_find_bar_get_search_string:
541  *
542  * Gets the string that should be found/highlighted in the document.
543  *
544  * Returns: the string
545  *
546  * Since: 2.6
547  */
548 const char*
549 egg_find_bar_get_search_string  (EggFindBar *find_bar)
550 {
551   EggFindBarPrivate *priv;
552
553   g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), NULL);
554
555   priv = (EggFindBarPrivate *)find_bar->private_data;
556
557   return priv->search_string;
558 }
559
560 /**
561  * egg_find_bar_set_case_sensitive:
562  *
563  * Sets whether the search is case sensitive
564  *
565  * Since: 2.6
566  */
567 void
568 egg_find_bar_set_case_sensitive (EggFindBar *find_bar,
569                                  gboolean    case_sensitive)
570 {
571   EggFindBarPrivate *priv;
572
573   g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
574
575   priv = (EggFindBarPrivate *)find_bar->private_data;
576
577   g_object_freeze_notify (G_OBJECT (find_bar));
578
579   case_sensitive = case_sensitive != FALSE;
580
581   if (priv->case_sensitive != case_sensitive)
582     {
583       priv->case_sensitive = case_sensitive;
584
585       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->case_button),
586                                     priv->case_sensitive);
587
588       g_object_notify (G_OBJECT (find_bar),
589                        "case_sensitive");
590     }
591
592   g_object_thaw_notify (G_OBJECT (find_bar));
593 }
594
595 /**
596  * egg_find_bar_get_case_sensitive:
597  *
598  * Gets whether the search is case sensitive
599  *
600  * Returns: TRUE if it's case sensitive
601  *
602  * Since: 2.6
603  */
604 gboolean
605 egg_find_bar_get_case_sensitive (EggFindBar *find_bar)
606 {
607   EggFindBarPrivate *priv;
608
609   g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), FALSE);
610
611   priv = (EggFindBarPrivate *)find_bar->private_data;
612
613   return priv->case_sensitive;
614 }
615
616 static void
617 get_style_color (EggFindBar *find_bar,
618                  const char *style_prop_name,
619                  GdkColor   *color)
620 {
621   GdkColor *style_color;
622
623   gtk_widget_ensure_style (GTK_WIDGET (find_bar));
624   gtk_widget_style_get (GTK_WIDGET (find_bar),
625                         "color", &style_color, NULL);
626   if (style_color)
627     {
628       *color = *style_color;
629       gdk_color_free (style_color);
630     }
631 }
632
633 /**
634  * egg_find_bar_get_all_matches_color:
635  *
636  * Gets the color to use to highlight all the
637  * known matches.
638  *
639  * Since: 2.6
640  */
641 void
642 egg_find_bar_get_all_matches_color (EggFindBar *find_bar,
643                                     GdkColor   *color)
644 {
645   GdkColor found_color = { 0, 0, 0, 0x0f0f };
646
647   get_style_color (find_bar, "all_matches_color", &found_color);
648
649   *color = found_color;
650 }
651
652 /**
653  * egg_find_bar_get_current_match_color:
654  *
655  * Gets the color to use to highlight the match
656  * we're currently on.
657  *
658  * Since: 2.6
659  */
660 void
661 egg_find_bar_get_current_match_color (EggFindBar *find_bar,
662                                       GdkColor   *color)
663 {
664   GdkColor found_color = { 0, 0, 0, 0xffff };
665
666   get_style_color (find_bar, "current_match_color", &found_color);
667
668   *color = found_color;
669 }
670
671 /**
672  * egg_find_bar_grab_focus:
673  *
674  * Focuses the text entry in the find bar; currently GTK+ doesn't have
675  * a way to make this work on gtk_widget_grab_focus(find_bar).
676  *
677  * Since: 2.6
678  */
679 void
680 egg_find_bar_grab_focus (EggFindBar *find_bar)
681 {
682   EggFindBarPrivate *priv;
683
684   g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
685
686   priv = (EggFindBarPrivate *)find_bar->private_data;
687  
688   gtk_widget_grab_focus (priv->find_entry);
689 }