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-zoom-action.c
Go to the documentation of this file.
1 /* ev-zoom-action.c
2  * this file is part of evince, a gnome document viewer
3  *
4  * Copyright (C) 2012 Carlos Garcia Campos <carlosgc@gnome.org>
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-zoom-action.h"
23 
24 #include <glib/gi18n.h>
25 
26 enum {
29 };
30 
31 enum
32 {
34 
37 };
38 
39 enum {
42 };
43 
44 static const struct {
45  const gchar *name;
46  float level;
47 } zoom_levels[] = {
48  { N_("50%"), 0.5 },
49  { N_("70%"), 0.7071067811 },
50  { N_("85%"), 0.8408964152 },
51  { N_("100%"), 1.0 },
52  { N_("125%"), 1.1892071149 },
53  { N_("150%"), 1.4142135623 },
54  { N_("175%"), 1.6817928304 },
55  { N_("200%"), 2.0 },
56  { N_("300%"), 2.8284271247 },
57  { N_("400%"), 4.0 },
58  { N_("800%"), 8.0 },
59  { N_("1600%"), 16.0 },
60  { N_("3200%"), 32.0 },
61  { N_("6400%"), 64.0 }
62 };
63 
65  GtkWidget *entry;
66 
68  GMenu *menu;
69 
70  GMenuModel *zoom_free_section;
71  GtkWidget *popup;
72  gboolean popup_shown;
73 };
74 
75 G_DEFINE_TYPE (EvZoomAction, ev_zoom_action, GTK_TYPE_BOX)
76 
77 static guint signals[LAST_SIGNAL] = { 0 };
78 
79 #define EPSILON 0.000001
80 
81 static void
83  float zoom)
84 {
85  gchar *zoom_str;
86  float zoom_perc;
87  guint i;
88 
89  for (i = 0; i < G_N_ELEMENTS (zoom_levels); i++) {
90  if (ABS (zoom - zoom_levels[i].level) < EPSILON) {
91  gtk_entry_set_text (GTK_ENTRY (zoom_action->priv->entry),
92  zoom_levels[i].name);
93  return;
94  }
95  }
96 
97  zoom_perc = zoom * 100.;
98  if (ABS ((gint)zoom_perc - zoom_perc) < 0.001)
99  zoom_str = g_strdup_printf ("%d%%", (gint)zoom_perc);
100  else
101  zoom_str = g_strdup_printf ("%.2f%%", zoom_perc);
102  gtk_entry_set_text (GTK_ENTRY (zoom_action->priv->entry), zoom_str);
103  g_free (zoom_str);
104 }
105 
106 static void
108 {
109  float zoom = ev_document_model_get_scale (zoom_action->priv->model);
110  GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (zoom_action));
111 
112  zoom *= 72.0 / ev_document_misc_get_screen_dpi (screen);
113  ev_zoom_action_set_zoom_level (zoom_action, zoom);
114 }
115 
116 static void
118  GParamSpec *pspec,
119  EvZoomAction *zoom_action)
120 {
121  ev_zoom_action_update_zoom_level (zoom_action);
122 }
123 
124 static void
126  GParamSpec *pspec,
127  EvZoomAction *zoom_action)
128 {
129  EvDocument *document = ev_document_model_get_document (model);
130 
131  if (!document) {
132  gtk_widget_set_sensitive (GTK_WIDGET (zoom_action), FALSE);
133  return;
134  }
135  gtk_widget_set_sensitive (GTK_WIDGET (zoom_action), ev_document_get_n_pages (document) > 0);
136 
137  ev_zoom_action_update_zoom_level (zoom_action);
138 }
139 
140 static void
142  gint width)
143 {
144  /* width + 3 (two decimals and the comma) + 3 (for the icon) */
145  gtk_entry_set_width_chars (GTK_ENTRY (zoom_action->priv->entry), width + 3 + 3);
146 }
147 
148 static void
150 {
151  gdouble max_scale;
152  guint i;
153  gint width = 0;
154 
155  max_scale = ev_document_model_get_max_scale (zoom_action->priv->model);
156 
157  for (i = 0; i < G_N_ELEMENTS (zoom_levels); i++) {
158  GMenuItem *item;
159  gint length;
160 
161  if (zoom_levels[i].level > max_scale)
162  break;
163 
164  length = g_utf8_strlen (zoom_levels[i].name, -1);
165  if (length > width)
166  width = length;
167 
168  item = g_menu_item_new (zoom_levels[i].name, NULL);
169  g_menu_item_set_action_and_target (item, "win.zoom",
170  "d", zoom_levels[i].level);
171  g_menu_append_item (G_MENU (zoom_action->priv->zoom_free_section), item);
172  g_object_unref (item);
173  }
174 
175  ev_zoom_action_set_width_chars (zoom_action, width);
176 }
177 
178 static void
180  GParamSpec *pspec,
181  EvZoomAction *zoom_action)
182 {
183  g_menu_remove_all (G_MENU (zoom_action->priv->zoom_free_section));
184  g_clear_pointer (&zoom_action->priv->popup, (GDestroyNotify)gtk_widget_destroy);
186 }
187 
188 static void
189 entry_activated_cb (GtkEntry *entry,
190  EvZoomAction *zoom_action)
191 {
192  GdkScreen *screen;
193  double zoom_perc;
194  float zoom;
195  const gchar *text = gtk_entry_get_text (entry);
196  gchar *end_ptr = NULL;
197 
198  if (!text || text[0] == '\0') {
199  ev_zoom_action_update_zoom_level (zoom_action);
200  g_signal_emit (zoom_action, signals[ACTIVATED], 0, NULL);
201  return;
202  }
203 
204  zoom_perc = g_strtod (text, &end_ptr);
205  if (end_ptr && end_ptr[0] != '\0' && end_ptr[0] != '%') {
206  ev_zoom_action_update_zoom_level (zoom_action);
207  g_signal_emit (zoom_action, signals[ACTIVATED], 0, NULL);
208  return;
209  }
210 
211  screen = gtk_widget_get_screen (GTK_WIDGET (zoom_action));
212  zoom = zoom_perc / 100.;
214  ev_document_model_set_scale (zoom_action->priv->model,
215  zoom * ev_document_misc_get_screen_dpi (screen) / 72.0);
216  g_signal_emit (zoom_action, signals[ACTIVATED], 0, NULL);
217 }
218 
219 static gboolean
221 {
222  ev_zoom_action_update_zoom_level (zoom_action);
223 
224  return FALSE;
225 }
226 
227 static void
228 popup_menu_closed (GtkWidget *popup,
229  EvZoomAction *zoom_action)
230 {
231  if (zoom_action->priv->popup != popup)
232  return;
233 
234  zoom_action->priv->popup_shown = FALSE;
235  zoom_action->priv->popup = NULL;
236 }
237 
238 static GtkWidget *
239 get_popup (EvZoomAction *zoom_action)
240 {
241  GdkRectangle rect;
242 
243  if (zoom_action->priv->popup)
244  return zoom_action->priv->popup;
245 
246  zoom_action->priv->popup = gtk_popover_new_from_model (GTK_WIDGET (zoom_action),
247  G_MENU_MODEL (zoom_action->priv->menu));
248  g_signal_connect (zoom_action->priv->popup, "closed",
249  G_CALLBACK (popup_menu_closed),
250  zoom_action);
251  gtk_entry_get_icon_area (GTK_ENTRY (zoom_action->priv->entry),
252  GTK_ENTRY_ICON_SECONDARY, &rect);
253  gtk_popover_set_pointing_to (GTK_POPOVER (zoom_action->priv->popup), &rect);
254  gtk_popover_set_position (GTK_POPOVER (zoom_action->priv->popup), GTK_POS_BOTTOM);
255 
256  return zoom_action->priv->popup;
257 }
258 
259 static void
260 entry_icon_press_callback (GtkEntry *entry,
261  GtkEntryIconPosition icon_pos,
262  GdkEventButton *event,
263  EvZoomAction *zoom_action)
264 {
265  if (event->button != GDK_BUTTON_PRIMARY)
266  return;
267 
268  gtk_widget_show (get_popup (zoom_action));
269  zoom_action->priv->popup_shown = TRUE;
270 }
271 
272 static void
273 ev_zoom_action_finalize (GObject *object)
274 {
275  EvZoomAction *zoom_action = EV_ZOOM_ACTION (object);
276 
277  if (zoom_action->priv->model) {
278  g_object_remove_weak_pointer (G_OBJECT (zoom_action->priv->model),
279  (gpointer)&zoom_action->priv->model);
280  }
281 
282  g_clear_object (&zoom_action->priv->menu);
283  g_clear_object (&zoom_action->priv->zoom_free_section);
284 
285  G_OBJECT_CLASS (ev_zoom_action_parent_class)->finalize (object);
286 }
287 
288 static void
290  guint prop_id,
291  const GValue *value,
292  GParamSpec *pspec)
293 {
294  EvZoomAction *zoom_action = EV_ZOOM_ACTION (object);
295 
296  switch (prop_id) {
297  case PROP_DOCUMENT_MODEL:
298  zoom_action->priv->model = g_value_get_object (value);
299  break;
300  case PROP_MENU:
301  zoom_action->priv->menu = g_value_dup_object (value);
302  break;
303  default:
304  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
305  }
306 }
307 
308 static void
310 {
311  gint width;
312 
313  width = g_utf8_strlen (zoom_levels[G_N_ELEMENTS (zoom_levels) - 1].name, -1);
314  ev_zoom_action_set_width_chars (zoom_action, width);
315 }
316 
317 static void
319  gint *minimum_width,
320  gint *natural_width)
321 {
322  *minimum_width = *natural_width = 0;
323 
324  GTK_WIDGET_CLASS (ev_zoom_action_parent_class)->get_preferred_width (widget, minimum_width, natural_width);
325  *natural_width = *minimum_width;
326 }
327 
328 static void
329 ev_zoom_action_constructed (GObject *object)
330 {
331  EvZoomAction *zoom_action = EV_ZOOM_ACTION (object);
332 
333  G_OBJECT_CLASS (ev_zoom_action_parent_class)->constructed (object);
334 
335  zoom_action->priv->zoom_free_section =
336  g_menu_model_get_item_link (G_MENU_MODEL (zoom_action->priv->menu),
337  ZOOM_FREE_SECTION, G_MENU_LINK_SECTION);
339 
340  g_object_add_weak_pointer (G_OBJECT (zoom_action->priv->model),
341  (gpointer)&zoom_action->priv->model);
342  if (ev_document_model_get_document (zoom_action->priv->model)) {
343  ev_zoom_action_update_zoom_level (zoom_action);
344  } else {
345  ev_zoom_action_set_zoom_level (zoom_action, 1.);
346  gtk_widget_set_sensitive (GTK_WIDGET (zoom_action), FALSE);
347  }
348 
349  g_signal_connect_object (zoom_action->priv->model, "notify::document",
350  G_CALLBACK (document_changed_cb),
351  zoom_action, 0);
352  g_signal_connect_object (zoom_action->priv->model, "notify::scale",
353  G_CALLBACK (zoom_changed_cb),
354  zoom_action, 0);
355  g_signal_connect_object (zoom_action->priv->model, "notify::max-scale",
356  G_CALLBACK (max_zoom_changed_cb),
357  zoom_action, 0);
358 
359  setup_initial_entry_size (zoom_action);
360 }
361 
362 static void
364 {
365  GObjectClass *object_class = G_OBJECT_CLASS (klass);
366  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
367 
368  object_class->finalize = ev_zoom_action_finalize;
369  object_class->constructed = ev_zoom_action_constructed;
370  object_class->set_property = ev_zoom_action_set_property;
371 
372  widget_class->get_preferred_width = ev_zoom_action_get_preferred_width;
373 
374  g_object_class_install_property (object_class,
376  g_param_spec_object ("document-model",
377  "DocumentModel",
378  "The document model",
380  G_PARAM_WRITABLE |
381  G_PARAM_CONSTRUCT_ONLY |
382  G_PARAM_STATIC_STRINGS));
383 
384  g_object_class_install_property (object_class,
385  PROP_MENU,
386  g_param_spec_object ("menu",
387  "Menu",
388  "The zoom popup menu",
389  G_TYPE_MENU,
390  G_PARAM_WRITABLE |
391  G_PARAM_CONSTRUCT_ONLY |
392  G_PARAM_STATIC_STRINGS));
393 
394  signals[ACTIVATED] =
395  g_signal_new ("activated",
396  G_OBJECT_CLASS_TYPE (object_class),
397  G_SIGNAL_RUN_LAST,
398  0, NULL, NULL,
399  g_cclosure_marshal_VOID__VOID,
400  G_TYPE_NONE, 0);
401 
402  g_type_class_add_private (object_class, sizeof (EvZoomActionPrivate));
403 }
404 
405 static void
407 {
408  EvZoomActionPrivate *priv;
409 
410  zoom_action->priv = G_TYPE_INSTANCE_GET_PRIVATE (zoom_action, EV_TYPE_ZOOM_ACTION, EvZoomActionPrivate);
411  priv = zoom_action->priv;
412 
413  gtk_orientable_set_orientation (GTK_ORIENTABLE (zoom_action), GTK_ORIENTATION_VERTICAL);
414 
415  priv->entry = gtk_entry_new ();
416  gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->entry),
417  GTK_ENTRY_ICON_SECONDARY,
418  "go-down-symbolic");
419  gtk_box_pack_start (GTK_BOX (zoom_action), priv->entry, TRUE, FALSE, 0);
420  gtk_widget_show (priv->entry);
421 
422  g_signal_connect (priv->entry, "icon-press",
423  G_CALLBACK (entry_icon_press_callback),
424  zoom_action);
425  g_signal_connect (priv->entry, "activate",
426  G_CALLBACK (entry_activated_cb),
427  zoom_action);
428  g_signal_connect_swapped (priv->entry, "focus-out-event",
429  G_CALLBACK (focus_out_cb),
430  zoom_action);
431 }
432 
433 GtkWidget *
435  GMenu *menu)
436 {
437  g_return_val_if_fail (EV_IS_DOCUMENT_MODEL (model), NULL);
438  g_return_val_if_fail (G_IS_MENU (menu), NULL);
439 
440  return GTK_WIDGET (g_object_new (EV_TYPE_ZOOM_ACTION,
441  "document-model", model,
442  "menu", menu,
443  NULL));
444 }
445 
446 gboolean
448 {
449  g_return_val_if_fail (EV_IS_ZOOM_ACTION (action), FALSE);
450 
451  return action->priv->popup_shown;
452 }