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