]> www.fi.muni.cz Git - evince.git/blob - shell/eggfindbar.c
[dualscreen] fix crash on ctrl+w and fix control window closing
[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., 51 Franklin Street, Fifth Floor, 
16  * Boston, MA 02110-1301, USA.
17  */
18
19 #include "config.h"
20
21 #include <string.h>
22
23 #include <glib/gi18n.h>
24 #include <gtk/gtk.h>
25 #include <gdk/gdkkeysyms.h>
26
27 #include "eggfindbar.h"
28
29 struct _EggFindBarPrivate
30 {
31   gchar *search_string;
32
33   GtkToolItem *next_button;
34   GtkToolItem *previous_button;
35   GtkToolItem *status_separator;
36   GtkToolItem *status_item;
37   GtkToolItem *case_button;
38
39   GtkWidget *find_entry;
40   GtkWidget *status_label;
41
42   gulong set_focus_handler;
43   guint case_sensitive : 1;
44 };
45
46 #define EGG_FIND_BAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EGG_TYPE_FIND_BAR, EggFindBarPrivate))
47
48 enum {
49     PROP_0,
50     PROP_SEARCH_STRING,
51     PROP_CASE_SENSITIVE
52 };
53
54 static void egg_find_bar_finalize      (GObject        *object);
55 static void egg_find_bar_get_property  (GObject        *object,
56                                         guint           prop_id,
57                                         GValue         *value,
58                                         GParamSpec     *pspec);
59 static void egg_find_bar_set_property  (GObject        *object,
60                                         guint           prop_id,
61                                         const GValue   *value,
62                                         GParamSpec     *pspec);
63 static void egg_find_bar_show          (GtkWidget *widget);
64 static void egg_find_bar_hide          (GtkWidget *widget);
65 static void egg_find_bar_grab_focus    (GtkWidget *widget);
66
67 G_DEFINE_TYPE (EggFindBar, egg_find_bar, GTK_TYPE_TOOLBAR);
68
69 enum
70   {
71     NEXT,
72     PREVIOUS,
73     CLOSE,
74     SCROLL,
75     LAST_SIGNAL
76   };
77
78 static guint find_bar_signals[LAST_SIGNAL] = { 0 };
79
80 static void
81 egg_find_bar_class_init (EggFindBarClass *klass)
82 {
83   GObjectClass *object_class;
84   GtkWidgetClass *widget_class;
85   GtkBindingSet *binding_set;
86         
87   egg_find_bar_parent_class = g_type_class_peek_parent (klass);
88
89   object_class = (GObjectClass *)klass;
90   widget_class = (GtkWidgetClass *)klass;
91
92   object_class->set_property = egg_find_bar_set_property;
93   object_class->get_property = egg_find_bar_get_property;
94
95   object_class->finalize = egg_find_bar_finalize;
96
97   widget_class->show = egg_find_bar_show;
98   widget_class->hide = egg_find_bar_hide;
99   
100   widget_class->grab_focus = egg_find_bar_grab_focus;
101
102   find_bar_signals[NEXT] =
103     g_signal_new ("next",
104                   G_OBJECT_CLASS_TYPE (object_class),
105                   G_SIGNAL_RUN_FIRST,
106                   G_STRUCT_OFFSET (EggFindBarClass, next),
107                   NULL, NULL,
108                   g_cclosure_marshal_VOID__VOID,
109                   G_TYPE_NONE, 0);
110   find_bar_signals[PREVIOUS] =
111     g_signal_new ("previous",
112                   G_OBJECT_CLASS_TYPE (object_class),
113                   G_SIGNAL_RUN_FIRST,
114                   G_STRUCT_OFFSET (EggFindBarClass, previous),
115                   NULL, NULL,
116                   g_cclosure_marshal_VOID__VOID,
117                   G_TYPE_NONE, 0);
118   find_bar_signals[CLOSE] =
119     g_signal_new ("close",
120                   G_OBJECT_CLASS_TYPE (object_class),
121                   G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
122                   G_STRUCT_OFFSET (EggFindBarClass, close),
123                   NULL, NULL,
124                   g_cclosure_marshal_VOID__VOID,
125                   G_TYPE_NONE, 0);
126   find_bar_signals[SCROLL] =
127     g_signal_new ("scroll",
128                   G_OBJECT_CLASS_TYPE (object_class),
129                   G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
130                   G_STRUCT_OFFSET (EggFindBarClass, scroll),
131                   NULL, NULL,
132                   g_cclosure_marshal_VOID__ENUM,
133                   G_TYPE_NONE, 1,
134                   GTK_TYPE_SCROLL_TYPE);
135
136   /**
137    * EggFindBar:search_string:
138    *
139    * The current string to search for. NULL or empty string
140    * both mean no current string.
141    *
142    */
143   g_object_class_install_property (object_class,
144                                    PROP_SEARCH_STRING,
145                                    g_param_spec_string ("search_string",
146                                                         "Search string",
147                                                         "The name of the string to be found",
148                                                         NULL,
149                                                         G_PARAM_READWRITE));
150
151   /**
152    * EggFindBar:case_sensitive:
153    *
154    * TRUE for a case sensitive search.
155    *
156    */
157   g_object_class_install_property (object_class,
158                                    PROP_CASE_SENSITIVE,
159                                    g_param_spec_boolean ("case_sensitive",
160                                                          "Case sensitive",
161                                                          "TRUE for a case sensitive search",
162                                                          FALSE,
163                                                          G_PARAM_READWRITE));
164
165   g_type_class_add_private (object_class, sizeof (EggFindBarPrivate));
166
167   binding_set = gtk_binding_set_by_class (klass);
168
169   gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0,
170                                 "close", 0);
171
172   gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, 0,
173                                 "scroll", 1,
174                                 GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_BACKWARD);
175   gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, 0,
176                                 "scroll", 1,
177                                 GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_FORWARD);
178   gtk_binding_entry_add_signal (binding_set, GDK_KEY_Page_Up, 0,
179                                 "scroll", 1,
180                                 GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD);
181   gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Page_Up, 0,
182                                 "scroll", 1,
183                                 GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD);
184   gtk_binding_entry_add_signal (binding_set, GDK_KEY_Page_Down, 0,
185                                 "scroll", 1,
186                                 GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD);
187   gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Page_Down, 0,
188                                 "scroll", 1,
189                                 GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD);
190 }
191
192 static void
193 egg_find_bar_emit_next (EggFindBar *find_bar)
194 {
195   g_signal_emit (find_bar, find_bar_signals[NEXT], 0);
196 }
197
198 static void
199 egg_find_bar_emit_previous (EggFindBar *find_bar)
200 {
201   g_signal_emit (find_bar, find_bar_signals[PREVIOUS], 0);
202 }
203
204 static void
205 next_clicked_callback (GtkButton *button,
206                        void      *data)
207 {
208   EggFindBar *find_bar = EGG_FIND_BAR (data);
209
210   egg_find_bar_emit_next (find_bar);
211 }
212
213 static void
214 previous_clicked_callback (GtkButton *button,
215                            void      *data)
216 {
217   EggFindBar *find_bar = EGG_FIND_BAR (data);
218
219   egg_find_bar_emit_previous (find_bar);
220 }
221
222 static void
223 case_sensitive_toggled_callback (GtkCheckButton *button,
224                                  void           *data)
225 {
226   EggFindBar *find_bar = EGG_FIND_BAR (data);
227
228   egg_find_bar_set_case_sensitive (find_bar,
229                                    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
230 }
231
232 static void
233 entry_activate_callback (GtkEntry *entry,
234                           void     *data)
235 {
236   EggFindBar *find_bar = EGG_FIND_BAR (data);
237
238   if (find_bar->priv->search_string != NULL)
239     egg_find_bar_emit_next (find_bar);
240 }
241
242 static void
243 entry_changed_callback (GtkEntry *entry,
244                         void     *data)
245 {
246   EggFindBar *find_bar = EGG_FIND_BAR (data);
247   char *text;
248
249   /* paranoid strdup because set_search_string() sets
250    * the entry text
251    */
252   text = g_strdup (gtk_entry_get_text (entry));
253
254   egg_find_bar_set_search_string (find_bar, text);
255   
256   g_free (text);
257 }
258
259 static void
260 set_focus_cb (GtkWidget *window,
261               GtkWidget *widget,
262               EggFindBar *bar)
263 {
264   GtkWidget *wbar = GTK_WIDGET (bar);
265
266   while (widget != NULL && widget != wbar)
267     {
268       widget = gtk_widget_get_parent (widget);
269     }
270
271   /* if widget == bar, the new focus widget is in the bar, so we
272    * don't deactivate.
273    */
274   if (widget != wbar)
275     {
276       g_signal_emit (bar, find_bar_signals[CLOSE], 0);
277     }
278 }
279
280 static void
281 egg_find_bar_init (EggFindBar *find_bar)
282 {
283   EggFindBarPrivate *priv;
284   GtkWidget *label;
285   GtkWidget *alignment;
286   GtkWidget *box;
287   GtkToolItem *item;
288   GtkWidget *arrow;
289
290   /* Data */
291   priv = EGG_FIND_BAR_GET_PRIVATE (find_bar);
292   
293   find_bar->priv = priv;  
294   priv->search_string = NULL;
295
296   gtk_toolbar_set_style (GTK_TOOLBAR (find_bar), GTK_TOOLBAR_BOTH_HORIZ);
297
298   /* Find: |_____| */
299   item = gtk_tool_item_new ();
300   box = gtk_hbox_new (FALSE, 12);
301   
302   alignment = gtk_alignment_new (0.0, 0.5, 1.0, 0.0);
303   gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 2, 2);
304
305   label = gtk_label_new_with_mnemonic (_("Find:"));
306
307   priv->find_entry = gtk_entry_new ();
308   gtk_entry_set_width_chars (GTK_ENTRY (priv->find_entry), 32);
309   gtk_entry_set_max_length (GTK_ENTRY (priv->find_entry), 512);
310   gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->find_entry);
311
312   /* Prev */
313   arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
314   priv->previous_button = gtk_tool_button_new (arrow, Q_("Find Pre_vious"));
315   gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (priv->previous_button), TRUE);
316   gtk_tool_item_set_is_important (priv->previous_button, TRUE);
317   gtk_widget_set_tooltip_text (GTK_WIDGET (priv->previous_button),
318                                _("Find previous occurrence of the search string"));
319
320   /* Next */
321   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
322   priv->next_button = gtk_tool_button_new (arrow, Q_("Find Ne_xt"));
323   gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (priv->next_button), TRUE);
324   gtk_tool_item_set_is_important (priv->next_button, TRUE);
325   gtk_widget_set_tooltip_text (GTK_WIDGET (priv->next_button),
326                                _("Find next occurrence of the search string"));
327
328   /* Separator*/
329   priv->status_separator = gtk_separator_tool_item_new();
330
331   /* Case button */
332   priv->case_button = gtk_toggle_tool_button_new ();
333   g_object_set (G_OBJECT (priv->case_button), "label", _("C_ase Sensitive"), NULL);
334   gtk_tool_item_set_is_important (priv->case_button, TRUE);
335   gtk_widget_set_tooltip_text (GTK_WIDGET (priv->case_button),
336                                _("Toggle case sensitive search"));
337
338   /* Status */
339   priv->status_item = gtk_tool_item_new();
340   gtk_tool_item_set_expand (priv->status_item, TRUE);
341   priv->status_label = gtk_label_new (NULL);
342   gtk_label_set_ellipsize (GTK_LABEL (priv->status_label),
343                            PANGO_ELLIPSIZE_END);
344   gtk_misc_set_alignment (GTK_MISC (priv->status_label), 0.0, 0.5);
345
346
347   g_signal_connect (priv->find_entry, "changed",
348                     G_CALLBACK (entry_changed_callback),
349                     find_bar);
350   g_signal_connect (priv->find_entry, "activate",
351                     G_CALLBACK (entry_activate_callback),
352                     find_bar);
353   g_signal_connect (priv->next_button, "clicked",
354                     G_CALLBACK (next_clicked_callback),
355                     find_bar);
356   g_signal_connect (priv->previous_button, "clicked",
357                     G_CALLBACK (previous_clicked_callback),
358                     find_bar);
359   g_signal_connect (priv->case_button, "toggled",
360                     G_CALLBACK (case_sensitive_toggled_callback),
361                     find_bar);
362
363   gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
364   gtk_box_pack_start (GTK_BOX (box), priv->find_entry, TRUE, TRUE, 0);
365   gtk_container_add (GTK_CONTAINER (alignment), box);
366   gtk_container_add (GTK_CONTAINER (item), alignment);
367   gtk_toolbar_insert (GTK_TOOLBAR (find_bar), item, -1);
368   gtk_toolbar_insert (GTK_TOOLBAR (find_bar), priv->previous_button, -1);
369   gtk_toolbar_insert (GTK_TOOLBAR (find_bar), priv->next_button, -1);
370   gtk_toolbar_insert (GTK_TOOLBAR (find_bar), priv->case_button, -1);
371   gtk_toolbar_insert (GTK_TOOLBAR (find_bar), priv->status_separator, -1);
372   gtk_container_add  (GTK_CONTAINER (priv->status_item), priv->status_label);
373   gtk_toolbar_insert (GTK_TOOLBAR (find_bar), priv->status_item, -1);
374
375   /* don't show status separator/label until they are set */
376
377   gtk_widget_show_all (GTK_WIDGET (item));
378   gtk_widget_show_all (GTK_WIDGET (priv->next_button));
379   gtk_widget_show_all (GTK_WIDGET (priv->previous_button));
380   gtk_widget_show (priv->status_label);
381 }
382
383 static void
384 egg_find_bar_finalize (GObject *object)
385 {
386   EggFindBar *find_bar = EGG_FIND_BAR (object);
387   EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->priv;
388
389   g_free (priv->search_string);
390
391   G_OBJECT_CLASS (egg_find_bar_parent_class)->finalize (object);
392 }
393
394 static void
395 egg_find_bar_set_property (GObject      *object,
396                            guint         prop_id,
397                            const GValue *value,
398                            GParamSpec   *pspec)
399 {
400   EggFindBar *find_bar = EGG_FIND_BAR (object);
401
402   switch (prop_id)
403     {
404     case PROP_SEARCH_STRING:
405       egg_find_bar_set_search_string (find_bar, g_value_get_string (value));
406       break;
407     case PROP_CASE_SENSITIVE:
408       egg_find_bar_set_case_sensitive (find_bar, g_value_get_boolean (value));
409       break;
410     default:
411       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
412       break;
413     }
414 }
415
416 static void
417 egg_find_bar_get_property (GObject    *object,
418                            guint       prop_id,
419                            GValue     *value,
420                            GParamSpec *pspec)
421 {
422   EggFindBar *find_bar = EGG_FIND_BAR (object);
423   EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->priv;
424
425   switch (prop_id)
426     {
427     case PROP_SEARCH_STRING:
428       g_value_set_string (value, priv->search_string);
429       break;
430     case PROP_CASE_SENSITIVE:
431       g_value_set_boolean (value, priv->case_sensitive);
432       break;
433     default:
434       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
435       break;
436     }
437 }
438
439 static void
440 egg_find_bar_show (GtkWidget *widget)
441 {
442   EggFindBar *bar = EGG_FIND_BAR (widget);
443   EggFindBarPrivate *priv = bar->priv;
444
445   GTK_WIDGET_CLASS (egg_find_bar_parent_class)->show (widget);
446
447   if (priv->set_focus_handler == 0)
448     {
449       GtkWidget *toplevel;
450
451       toplevel = gtk_widget_get_toplevel (widget);
452
453       priv->set_focus_handler =
454         g_signal_connect (toplevel, "set-focus",
455                           G_CALLBACK (set_focus_cb), bar);
456     }
457 }
458
459 static void
460 egg_find_bar_hide (GtkWidget *widget)
461 {
462   EggFindBar *bar = EGG_FIND_BAR (widget);
463   EggFindBarPrivate *priv = bar->priv;
464
465   if (priv->set_focus_handler != 0)
466     {
467       GtkWidget *toplevel;
468
469       toplevel = gtk_widget_get_toplevel (widget);
470
471       g_signal_handlers_disconnect_by_func
472         (toplevel, (void (*)) G_CALLBACK (set_focus_cb), bar);
473       priv->set_focus_handler = 0;
474     }
475
476   GTK_WIDGET_CLASS (egg_find_bar_parent_class)->hide (widget);
477 }
478
479 static void
480 egg_find_bar_grab_focus (GtkWidget *widget)
481 {
482   EggFindBar *find_bar = EGG_FIND_BAR (widget);
483   EggFindBarPrivate *priv = find_bar->priv;
484
485   gtk_widget_grab_focus (priv->find_entry);
486 }
487
488 /**
489  * egg_find_bar_new:
490  *
491  * Creates a new #EggFindBar.
492  *
493  * Returns: a newly created #EggFindBar
494  *
495  * Since: 2.6
496  */
497 GtkWidget *
498 egg_find_bar_new (void)
499 {
500   EggFindBar *find_bar;
501
502   find_bar = g_object_new (EGG_TYPE_FIND_BAR, NULL);
503
504   return GTK_WIDGET (find_bar);
505 }
506
507 /**
508  * egg_find_bar_set_search_string:
509  *
510  * Sets the string that should be found/highlighted in the document.
511  * Empty string is converted to NULL.
512  *
513  * Since: 2.6
514  */
515 void
516 egg_find_bar_set_search_string  (EggFindBar *find_bar,
517                                  const char *search_string)
518 {
519   EggFindBarPrivate *priv;
520
521   g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
522
523   priv = (EggFindBarPrivate *)find_bar->priv;
524
525   g_object_freeze_notify (G_OBJECT (find_bar));
526   
527   if (priv->search_string != search_string)
528     {
529       char *old;
530       
531       old = priv->search_string;
532
533       if (search_string && *search_string == '\0')
534         search_string = NULL;
535
536       /* Only update if the string has changed; setting the entry
537        * will emit changed on the entry which will re-enter
538        * this function, but we'll handle that fine with this
539        * short-circuit.
540        */
541       if ((old && search_string == NULL) ||
542           (old == NULL && search_string) ||
543           (old && search_string &&
544            strcmp (old, search_string) != 0))
545         {
546           gboolean not_empty;
547           
548           priv->search_string = g_strdup (search_string);
549           g_free (old);
550           
551           gtk_entry_set_text (GTK_ENTRY (priv->find_entry),
552                               priv->search_string ?
553                               priv->search_string :
554                               "");
555            
556           not_empty = (search_string == NULL) ? FALSE : TRUE;            
557
558           gtk_widget_set_sensitive (GTK_WIDGET (find_bar->priv->next_button), not_empty);
559           gtk_widget_set_sensitive (GTK_WIDGET (find_bar->priv->previous_button), not_empty);
560
561           g_object_notify (G_OBJECT (find_bar),
562                            "search_string");
563         }
564     }
565
566   g_object_thaw_notify (G_OBJECT (find_bar));
567 }
568
569
570 /**
571  * egg_find_bar_get_search_string:
572  *
573  * Gets the string that should be found/highlighted in the document.
574  *
575  * Returns: the string
576  *
577  * Since: 2.6
578  */
579 const char*
580 egg_find_bar_get_search_string  (EggFindBar *find_bar)
581 {
582   EggFindBarPrivate *priv;
583
584   g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), NULL);
585
586   priv = find_bar->priv;
587
588   return priv->search_string ? priv->search_string : "";
589 }
590
591 /**
592  * egg_find_bar_set_case_sensitive:
593  *
594  * Sets whether the search is case sensitive
595  *
596  * Since: 2.6
597  */
598 void
599 egg_find_bar_set_case_sensitive (EggFindBar *find_bar,
600                                  gboolean    case_sensitive)
601 {
602   EggFindBarPrivate *priv;
603
604   g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
605
606   priv = (EggFindBarPrivate *)find_bar->priv;
607
608   g_object_freeze_notify (G_OBJECT (find_bar));
609
610   case_sensitive = case_sensitive != FALSE;
611
612   if (priv->case_sensitive != case_sensitive)
613     {
614       priv->case_sensitive = case_sensitive;
615
616       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->case_button),
617                                     priv->case_sensitive);
618
619       g_object_notify (G_OBJECT (find_bar),
620                        "case_sensitive");
621     }
622
623   g_object_thaw_notify (G_OBJECT (find_bar));
624 }
625
626 /**
627  * egg_find_bar_get_case_sensitive:
628  *
629  * Gets whether the search is case sensitive
630  *
631  * Returns: TRUE if it's case sensitive
632  *
633  * Since: 2.6
634  */
635 gboolean
636 egg_find_bar_get_case_sensitive (EggFindBar *find_bar)
637 {
638   EggFindBarPrivate *priv;
639
640   g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), FALSE);
641
642   priv = (EggFindBarPrivate *)find_bar->priv;
643
644   return priv->case_sensitive;
645 }
646
647 /**
648  * egg_find_bar_set_status_text:
649  *
650  * Sets some text to display if there's space; typical text would
651  * be something like "5 results on this page" or "No results"
652  *
653  * @text: the text to display
654  *
655  * Since: 2.6
656  */
657 void
658 egg_find_bar_set_status_text (EggFindBar *find_bar,
659                               const char *text)
660 {
661   EggFindBarPrivate *priv;
662
663   g_return_if_fail (EGG_IS_FIND_BAR (find_bar));
664
665   priv = (EggFindBarPrivate *)find_bar->priv;
666   
667   gtk_label_set_text (GTK_LABEL (priv->status_label), text);
668   g_object_set (priv->status_separator, "visible", text != NULL && *text != '\0', NULL);
669   g_object_set (priv->status_item, "visible", text != NULL && *text !='\0', NULL);
670 }