Evince
Evince is a document viewer capable of displaying multiple and single page document formats like PDF and Postscript.
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ev-search-box.c
Go to the documentation of this file.
1 /* ev-search-box.c
2  * this file is part of evince, a gnome document viewer
3  *
4  * Copyright (C) 2015 Igalia S.L.
5  *
6  * Evince is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Evince is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #include "config.h"
22 #include "ev-search-box.h"
23 
24 #include <glib/gi18n.h>
25 
26 enum {
31 
34 
36 };
37 
38 enum
39 {
41 
44 };
45 
51 
52  GtkWidget *entry;
53  GtkWidget *next_button;
54  GtkWidget *prev_button;
55 
57 };
58 
59 G_DEFINE_TYPE (EvSearchBox, ev_search_box, GTK_TYPE_BOX)
60 
61 static guint signals[LAST_SIGNAL] = { 0 };
62 
63 #define FIND_PAGE_RATE_REFRESH 100
64 
65 static void
67 {
68  EvSearchBoxPrivate *priv = box->priv;
69  gdouble fraction;
70 
71  fraction = priv->job ? MIN ((gdouble)priv->pages_searched / EV_JOB_FIND (priv->job)->n_pages, 1.) : 0.;
72  gtk_entry_set_progress_fraction (GTK_ENTRY (priv->entry), fraction);
73 }
74 
75 static void
77 {
78  EvSearchBoxPrivate *priv = box->priv;
79 
80  if (!priv->job)
81  return;
82 
83  if (!ev_job_is_finished (priv->job))
84  ev_job_cancel (priv->job);
85 
86  g_signal_handlers_disconnect_matched (priv->job, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, box);
87  g_object_unref (priv->job);
88  priv->job = NULL;
89 }
90 
91 static void
93  EvSearchBox *box)
94 {
95  g_signal_emit (box, signals[FINISHED], 0);
98 
99  if (!ev_job_find_has_results (job)) {
100  EvSearchBoxPrivate *priv = box->priv;
101 
102  gtk_style_context_add_class (gtk_widget_get_style_context (priv->entry), GTK_STYLE_CLASS_ERROR);
103 
104  gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->entry),
105  GTK_ENTRY_ICON_PRIMARY,
106  "face-uncertain-symbolic");
107  if (priv->supported_options != EV_FIND_DEFAULT) {
108  gtk_entry_set_icon_tooltip_text (GTK_ENTRY (priv->entry),
109  GTK_ENTRY_ICON_PRIMARY,
110  _("Not found, click to change search options"));
111  }
112  }
113 }
114 
128 static inline gboolean
130  gint page_rate)
131 {
132  return ((job->current_page % (gint)((job->n_pages / page_rate) + 1)) == 0);
133 }
134 
135 static void
137  gint page,
138  EvSearchBox *box)
139 {
140  EvSearchBoxPrivate *priv = box->priv;
141 
142  priv->pages_searched++;
143 
144  /* Adjust the status update when searching for a term according
145  * to the document size in pages. For documents smaller (or equal)
146  * than 100 pages, it will be updated in every page. A value of
147  * 100 is enough to update the find bar every 1%.
148  */
150  gboolean has_results = ev_job_find_has_results (job);
151 
153  gtk_widget_set_sensitive (priv->next_button, has_results);
154  gtk_widget_set_sensitive (priv->prev_button, has_results);
155  g_signal_emit (box, signals[UPDATED], 0);
156  }
157 }
158 
159 static void
160 search_changed_cb (GtkSearchEntry *entry,
161  EvSearchBox *box)
162 {
163  const char *search_string;
164  EvSearchBoxPrivate *priv = box->priv;
165 
167  priv->pages_searched = 0;
169 
170  gtk_widget_set_sensitive (priv->next_button, FALSE);
171  gtk_widget_set_sensitive (priv->prev_button, FALSE);
172 
173  gtk_style_context_remove_class (gtk_widget_get_style_context (priv->entry), GTK_STYLE_CLASS_ERROR);
174  gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->entry),
175  GTK_ENTRY_ICON_PRIMARY,
176  "edit-find-symbolic");
177  if (priv->supported_options != EV_FIND_DEFAULT) {
178  gtk_entry_set_icon_tooltip_text (GTK_ENTRY (priv->entry),
179  GTK_ENTRY_ICON_PRIMARY,
180  _("Search options"));
181  }
182 
183  search_string = gtk_entry_get_text (GTK_ENTRY (entry));
184  if (search_string && search_string[0]) {
186 
187  priv->job = ev_job_find_new (doc,
190  search_string,
191  FALSE);
193  g_signal_connect (priv->job, "finished",
194  G_CALLBACK (find_job_finished_cb),
195  box);
196  g_signal_connect (priv->job, "updated",
197  G_CALLBACK (find_job_updated_cb),
198  box);
199 
200  g_signal_emit (box, signals[STARTED], 0, priv->job);
202  } else {
203  g_signal_emit (box, signals[CLEARED], 0);
204  }
205 }
206 
207 static void
208 previous_clicked_cb (GtkButton *button,
209  EvSearchBox *box)
210 {
211  g_signal_emit (box, signals[PREVIOUS], 0);
212 }
213 
214 static void
215 next_clicked_cb (GtkButton *button,
216  EvSearchBox *box)
217 {
218  g_signal_emit (box, signals[NEXT], 0);
219 }
220 
221 static void
223  EvFindOptions options)
224 {
225  EvSearchBoxPrivate *priv = box->priv;
226  gboolean enable_search_options;
227 
228  if (priv->supported_options == options)
229  return;
230 
231  priv->supported_options = options;
232  enable_search_options = options != EV_FIND_DEFAULT;
233  g_object_set (priv->entry,
234  "primary-icon-activatable", enable_search_options,
235  "primary-icon-sensitive", enable_search_options,
236  "primary-icon-tooltip-text", enable_search_options ? _("Search options") : NULL,
237  NULL);
238 }
239 
240 static void
242  EvDocument *document)
243 {
244  if (!document || !EV_IS_DOCUMENT_FIND (document)) {
246  gtk_widget_set_sensitive (GTK_WIDGET (box), FALSE);
247  return;
248  }
249 
251  gtk_widget_set_sensitive (GTK_WIDGET (box), ev_document_get_n_pages (document) > 0);
252 }
253 
254 static void
256  GParamSpec *pspec,
257  EvSearchBox *box)
258 {
260 }
261 
262 static void
264  EvFindOptions options)
265 {
266  EvSearchBoxPrivate *priv = box->priv;
267 
268  if (priv->options == options)
269  return;
270 
271  priv->options = options;
272  search_changed_cb (GTK_SEARCH_ENTRY (priv->entry), box);
273 }
274 
275 static void
276 whole_words_only_toggled_cb (GtkCheckMenuItem *menu_item,
277  EvSearchBox *box)
278 {
279  EvFindOptions options = box->priv->options;
280 
281  if (gtk_check_menu_item_get_active (menu_item))
282  options |= EV_FIND_WHOLE_WORDS_ONLY;
283  else
284  options &= ~EV_FIND_WHOLE_WORDS_ONLY;
285  ev_search_box_set_options (box, options);
286 }
287 
288 static void
289 case_sensitive_toggled_cb (GtkCheckMenuItem *menu_item,
290  EvSearchBox *box)
291 {
292  EvFindOptions options = box->priv->options;
293 
294  if (gtk_check_menu_item_get_active (menu_item))
295  options |= EV_FIND_CASE_SENSITIVE;
296  else
297  options &= ~EV_FIND_CASE_SENSITIVE;
298  ev_search_box_set_options (box, options);
299 }
300 
301 static void
303  GtkWidget *menu)
304 {
305  EvSearchBoxPrivate *priv = box->priv;
306 
308  GtkWidget *menu_item;
309 
310  menu_item = gtk_check_menu_item_new_with_mnemonic (_("_Whole Words Only"));
311  g_signal_connect (menu_item, "toggled",
312  G_CALLBACK (whole_words_only_toggled_cb),
313  box);
314  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item),
316  gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
317  gtk_widget_show (menu_item);
318  }
319 
321  GtkWidget *menu_item;
322 
323  menu_item = gtk_check_menu_item_new_with_mnemonic (_("C_ase Sensitive"));
324  g_signal_connect (menu_item, "toggled",
325  G_CALLBACK (case_sensitive_toggled_cb),
326  box);
327  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item),
329  gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
330  gtk_widget_show (menu_item);
331  }
332 }
333 
334 static void
335 entry_icon_release_cb (GtkEntry *entry,
336  GtkEntryIconPosition icon_pos,
337  GdkEventButton *event,
338  EvSearchBox *box)
339 {
340  GtkWidget *menu;
341 
342  if (event->button != GDK_BUTTON_PRIMARY)
343  return;
344 
345  if (icon_pos == GTK_ENTRY_ICON_SECONDARY)
346  return;
347 
348  menu = gtk_menu_new ();
350  gtk_widget_show (menu);
351 
352  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
353  event->button, event->time);
354 }
355 
356 static void
357 entry_populate_popup_cb (GtkEntry *entry,
358  GtkMenu *menu,
359  EvSearchBox *box)
360 {
361  EvSearchBoxPrivate *priv = box->priv;
362  GtkWidget *separator;
363 
364  if (priv->supported_options == EV_FIND_DEFAULT)
365  return;
366 
367  separator = gtk_separator_menu_item_new ();
368  gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), separator);
369  gtk_widget_show (separator);
370  ev_search_box_entry_populate_popup (box, GTK_WIDGET (menu));
371 }
372 
373 static void
374 entry_activate_cb (GtkEntry *entry,
375  EvSearchBox *box)
376 {
377  g_signal_emit (box, signals[NEXT], 0);
378 }
379 
380 static void
381 entry_next_match_cb (GtkSearchEntry *entry,
382  EvSearchBox *box)
383 {
384  g_signal_emit (box, signals[NEXT], 0);
385 }
386 
387 static void
388 entry_previous_match_cb (GtkSearchEntry *entry,
389  EvSearchBox *box)
390 {
391  g_signal_emit (box, signals[PREVIOUS], 0);
392 }
393 
394 static void
395 ev_search_box_finalize (GObject *object)
396 {
397  EvSearchBox *box = EV_SEARCH_BOX (object);
398 
399  if (box->priv->model) {
400  g_object_remove_weak_pointer (G_OBJECT (box->priv->model),
401  (gpointer)&box->priv->model);
402  }
403 
404  G_OBJECT_CLASS (ev_search_box_parent_class)->finalize (object);
405 }
406 
407 static void
408 ev_search_box_dispose (GObject *object)
409 {
410  EvSearchBox *box = EV_SEARCH_BOX (object);
411 
413 
414  G_OBJECT_CLASS (ev_search_box_parent_class)->dispose (object);
415 }
416 
417 static void
418 ev_search_box_set_property (GObject *object,
419  guint prop_id,
420  const GValue *value,
421  GParamSpec *pspec)
422 {
423  EvSearchBox *box = EV_SEARCH_BOX (object);
424 
425  switch (prop_id) {
426  case PROP_DOCUMENT_MODEL:
427  box->priv->model = g_value_get_object (value);
428  break;
429  default:
430  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
431  }
432 }
433 
434 static void
435 ev_search_box_get_property (GObject *object,
436  guint prop_id,
437  GValue *value,
438  GParamSpec *pspec)
439 {
440  EvSearchBox *box = EV_SEARCH_BOX (object);
441 
442  switch (prop_id) {
443  case PROP_OPTIONS:
444  g_value_set_flags (value, box->priv->options);
445  break;
446  default:
447  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
448  }
449 }
450 
451 static void
452 ev_search_box_constructed (GObject *object)
453 {
454  EvSearchBox *box = EV_SEARCH_BOX (object);
455 
456  G_OBJECT_CLASS (ev_search_box_parent_class)->constructed (object);
457 
458  g_object_add_weak_pointer (G_OBJECT (box->priv->model),
459  (gpointer)&box->priv->model);
460 
462  g_signal_connect_object (box->priv->model, "notify::document",
463  G_CALLBACK (document_changed_cb),
464  box, 0);
465 }
466 
467 static void
468 ev_search_box_grab_focus (GtkWidget *widget)
469 {
470  EvSearchBox *box = EV_SEARCH_BOX (widget);
471 
472  gtk_widget_grab_focus (box->priv->entry);
473 }
474 
475 static void
477 {
478  GObjectClass *object_class = G_OBJECT_CLASS (klass);
479  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
480  GtkBindingSet *binding_set;
481 
482  object_class->finalize = ev_search_box_finalize;
483  object_class->dispose = ev_search_box_dispose;
484  object_class->constructed = ev_search_box_constructed;
485  object_class->set_property = ev_search_box_set_property;
486  object_class->get_property = ev_search_box_get_property;
487 
488  widget_class->grab_focus = ev_search_box_grab_focus;
489 
490  g_object_class_install_property (object_class,
492  g_param_spec_object ("document-model",
493  "DocumentModel",
494  "The document model",
496  G_PARAM_WRITABLE |
497  G_PARAM_CONSTRUCT_ONLY |
498  G_PARAM_STATIC_STRINGS));
499  g_object_class_install_property (object_class,
500  PROP_OPTIONS,
501  g_param_spec_flags ("options",
502  "Search options",
503  "The search options",
504  EV_TYPE_FIND_OPTIONS,
506  G_PARAM_READABLE |
507  G_PARAM_STATIC_STRINGS));
508 
509  signals[STARTED] =
510  g_signal_new ("started",
511  G_OBJECT_CLASS_TYPE (object_class),
512  G_SIGNAL_RUN_LAST,
513  0, NULL, NULL,
514  g_cclosure_marshal_VOID__OBJECT,
515  G_TYPE_NONE, 1,
517 
518  signals[UPDATED] =
519  g_signal_new ("updated",
520  G_OBJECT_CLASS_TYPE (object_class),
521  G_SIGNAL_RUN_LAST,
522  0, NULL, NULL,
523  g_cclosure_marshal_VOID__INT,
524  G_TYPE_NONE, 1,
525  G_TYPE_INT);
526  signals[FINISHED] =
527  g_signal_new ("finished",
528  G_OBJECT_CLASS_TYPE (object_class),
529  G_SIGNAL_RUN_LAST,
530  0, NULL, NULL,
531  g_cclosure_marshal_VOID__VOID,
532  G_TYPE_NONE, 0);
533  signals[CLEARED] =
534  g_signal_new ("cleared",
535  G_OBJECT_CLASS_TYPE (object_class),
536  G_SIGNAL_RUN_LAST,
537  0, NULL, NULL,
538  g_cclosure_marshal_VOID__VOID,
539  G_TYPE_NONE, 0);
540  signals[NEXT] =
541  g_signal_new ("next",
542  G_OBJECT_CLASS_TYPE (object_class),
543  G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
544  0, NULL, NULL,
545  g_cclosure_marshal_VOID__VOID,
546  G_TYPE_NONE, 0);
547  signals[PREVIOUS] =
548  g_signal_new ("previous",
549  G_OBJECT_CLASS_TYPE (object_class),
550  G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
551  0, NULL, NULL,
552  g_cclosure_marshal_VOID__VOID,
553  G_TYPE_NONE, 0);
554 
555  g_type_class_add_private (object_class, sizeof (EvSearchBoxPrivate));
556 
557  binding_set = gtk_binding_set_by_class (klass);
558  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, GDK_SHIFT_MASK,
559  "previous", 0);
560  gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, GDK_SHIFT_MASK,
561  "previous", 0);
562  gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, GDK_SHIFT_MASK,
563  "previous", 0);
564  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
565  "previous", 0);
566  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
567  "next", 0);
568 }
569 
570 static void
572 {
573  EvSearchBoxPrivate *priv;
574  GtkStyleContext *style_context;
575 
576  box->priv = G_TYPE_INSTANCE_GET_PRIVATE (box, EV_TYPE_SEARCH_BOX, EvSearchBoxPrivate);
577  priv = box->priv;
578 
579  gtk_orientable_set_orientation (GTK_ORIENTABLE (box), GTK_ORIENTATION_HORIZONTAL);
580  style_context = gtk_widget_get_style_context (GTK_WIDGET (box));
581  gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_LINKED);
582  gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_RAISED);
583 
584  priv->entry = gtk_search_entry_new ();
585 
586  gtk_box_pack_start (GTK_BOX (box), priv->entry, TRUE, TRUE, 0);
587  gtk_widget_show (priv->entry);
588 
589  priv->prev_button = gtk_button_new_from_icon_name ("go-up-symbolic", GTK_ICON_SIZE_MENU);
590  gtk_widget_set_tooltip_text (priv->prev_button, _("Find previous occurrence of the search string"));
591  gtk_widget_set_can_focus (priv->prev_button, FALSE);
592  gtk_widget_set_sensitive (priv->prev_button, FALSE);
593  gtk_container_add (GTK_CONTAINER (box), priv->prev_button);
594  gtk_widget_show (priv->prev_button);
595 
596  priv->next_button = gtk_button_new_from_icon_name ("go-down-symbolic", GTK_ICON_SIZE_MENU);
597  gtk_widget_set_tooltip_text (priv->next_button, _("Find next occurrence of the search string"));
598  gtk_widget_set_can_focus (priv->next_button, FALSE);
599  gtk_widget_set_sensitive (priv->next_button, FALSE);
600  gtk_container_add (GTK_CONTAINER (box), priv->next_button);
601  gtk_widget_show (priv->next_button);
602 
603  g_signal_connect (priv->entry, "search-changed",
604  G_CALLBACK (search_changed_cb),
605  box);
606  g_signal_connect (priv->entry, "icon-release",
607  G_CALLBACK (entry_icon_release_cb),
608  box);
609  g_signal_connect (priv->entry, "populate-popup",
610  G_CALLBACK (entry_populate_popup_cb),
611  box);
612  g_signal_connect (priv->entry, "activate",
613  G_CALLBACK (entry_activate_cb),
614  box);
615  g_signal_connect (priv->entry, "next-match",
616  G_CALLBACK (entry_next_match_cb),
617  box);
618  g_signal_connect (priv->entry, "previous-match",
619  G_CALLBACK (entry_previous_match_cb),
620  box);
621  g_signal_connect (priv->prev_button, "clicked",
622  G_CALLBACK (previous_clicked_cb),
623  box);
624  g_signal_connect (priv->next_button, "clicked",
625  G_CALLBACK (next_clicked_cb),
626  box);
627 }
628 
629 GtkWidget *
631 {
632  g_return_val_if_fail (EV_IS_DOCUMENT_MODEL (model), NULL);
633 
634  return GTK_WIDGET (g_object_new (EV_TYPE_SEARCH_BOX,
635  "document-model", model,
636  NULL));
637 }
638 
639 GtkSearchEntry *
641 {
642  g_return_val_if_fail (EV_IS_SEARCH_BOX (box), NULL);
643 
644  return GTK_SEARCH_ENTRY (box->priv->entry);
645 }
646 
647 gboolean
649 {
650  g_return_val_if_fail (EV_IS_SEARCH_BOX (box), FALSE);
651 
652  return gtk_widget_get_sensitive (box->priv->next_button);
653 }
654 
655 void
657 {
658  g_return_if_fail (EV_IS_SEARCH_BOX (box));
659 
660  search_changed_cb (GTK_SEARCH_ENTRY (box->priv->entry), box);
661 }