1 /*
2 * eog-zoom-entry.c
3 * This file is part of eog
4 *
5 * Author: Felix Riemann <friemann@gnome.org>
6 *
7 * Copyright (C) 2017 GNOME Foundation
8 *
9 * Based on code (ev-zoom-action.c) by:
10 * - Carlos Garcia Campos <carlosgc@gnome.org>
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License along
23 * with this program; if not, write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include "eog-zoom-entry.h"
31 #include <glib/gi18n.h>
32 #include <gtk/gtk.h>
33 #include <math.h>
34
35 enum {
36 PROP_0,
37 PROP_SCROLL_VIEW,
38 PROP_MENU
39 };
40
41 typedef struct _EogZoomEntryPrivate {
42 GtkWidget *btn_zoom_in;
43 GtkWidget *btn_zoom_out;
44 GtkWidget *value_entry;
45
46 EogScrollView *view;
47
48 GMenu *menu;
49 GMenuModel *zoom_free_section;
50 GtkWidget *popup;
51 gboolean popup_shown;
52 } EogZoomEntryPrivate;
53
54 struct _EogZoomEntry {
55 GtkBox box;
56
57 EogZoomEntryPrivate *priv;
58 };
59
60 static const gdouble zoom_levels[] = {
61 (1.0/3.0), (1.0/2.0),
62 1.0, /* 100% */
63 (1.0/0.75), 2.0, 5.0, 10.0, 15.0, 20.0
64 };
65
66 G_DEFINE_TYPE_WITH_PRIVATE (EogZoomEntry, eog_zoom_entry, GTK_TYPE_BOX);
67
68 static void eog_zoom_entry_reset_zoom_level (EogZoomEntry *entry);
69 static void eog_zoom_entry_set_zoom_level (EogZoomEntry *entry, gdouble zoom);
70 static void eog_zoom_entry_update_sensitivity (EogZoomEntry *entry);
71
72 static gchar*
eog_zoom_entry_format_zoom_value(gdouble value)73 eog_zoom_entry_format_zoom_value (gdouble value)
74 {
75 gchar *name;
76 /* Mimic the zoom calculation from EogWindow to get matching displays */
77 const gint zoom_percent = (gint) floor (value * 100. + 0.5);
78
79 /* L10N: This is a percentage value used for the image zoom.
80 * This should be translated similar to the statusbar zoom value. */
81 name = g_strdup_printf(_("%d%%"), zoom_percent);
82
83 return name;
84 }
85
86 static void
eog_zoom_entry_populate_free_zoom_section(EogZoomEntry * zoom_entry)87 eog_zoom_entry_populate_free_zoom_section (EogZoomEntry *zoom_entry)
88 {
89 guint i;
90
91 for (i = 0; i < G_N_ELEMENTS (zoom_levels); i++) {
92 GMenuItem *item;
93 gchar *name;
94
95
96 if (zoom_levels[i] > EOG_SCROLL_VIEW_MAX_ZOOM_FACTOR)
97 break;
98
99 name = eog_zoom_entry_format_zoom_value (zoom_levels[i]);
100
101 item = g_menu_item_new (name, NULL);
102 g_menu_item_set_action_and_target (item, "win.zoom-set",
103 "d", zoom_levels[i]);
104 g_menu_append_item (G_MENU (zoom_entry->priv->zoom_free_section), item);
105 g_object_unref (item);
106 g_free (name);
107 }
108 }
109
110 static void
eog_zoom_entry_activate_cb(GtkEntry * gtk_entry,EogZoomEntry * entry)111 eog_zoom_entry_activate_cb (GtkEntry *gtk_entry, EogZoomEntry *entry)
112 {
113 const gchar *text = gtk_entry_get_text (gtk_entry);
114 gchar *end_ptr = NULL;
115 double zoom_perc;
116
117 if (!text || text[0] == '\0') {
118 eog_zoom_entry_reset_zoom_level (entry);
119 return;
120 }
121 zoom_perc = g_strtod (text, &end_ptr);
122
123 if (end_ptr) {
124 /* Skip whitespace after the digits */
125 while (end_ptr[0] != '\0' && g_ascii_isspace (end_ptr[0]))
126 end_ptr++;
127 if (end_ptr[0] != '\0' && end_ptr[0] != '%') {
128 eog_zoom_entry_reset_zoom_level (entry);
129 return;
130 }
131 }
132
133 eog_scroll_view_set_zoom (entry->priv->view, zoom_perc / 100.0);
134 }
135
136 static gboolean
focus_out_cb(EogZoomEntry * zoom_entry)137 focus_out_cb (EogZoomEntry *zoom_entry)
138 {
139 eog_zoom_entry_reset_zoom_level (zoom_entry);
140
141 return FALSE;
142 }
143
144 static void
popup_menu_closed(GtkWidget * popup,EogZoomEntry * zoom_entry)145 popup_menu_closed (GtkWidget *popup,
146 EogZoomEntry *zoom_entry)
147 {
148 if (zoom_entry->priv->popup != popup)
149 return;
150
151 zoom_entry->priv->popup_shown = FALSE;
152 zoom_entry->priv->popup = NULL;
153 }
154
155 static GtkWidget*
get_popup(EogZoomEntry * zoom_entry)156 get_popup (EogZoomEntry *zoom_entry)
157 {
158 GdkRectangle rect;
159
160 if (zoom_entry->priv->popup)
161 return zoom_entry->priv->popup;
162
163 zoom_entry->priv->popup = gtk_popover_new_from_model (GTK_WIDGET (zoom_entry),
164 G_MENU_MODEL (zoom_entry->priv->menu));
165 g_signal_connect (zoom_entry->priv->popup, "closed",
166 G_CALLBACK (popup_menu_closed),
167 zoom_entry);
168 gtk_entry_get_icon_area (GTK_ENTRY (zoom_entry->priv->value_entry),
169 GTK_ENTRY_ICON_SECONDARY, &rect);
170 gtk_popover_set_relative_to (GTK_POPOVER (zoom_entry->priv->popup),
171 zoom_entry->priv->value_entry);
172 gtk_popover_set_pointing_to (GTK_POPOVER (zoom_entry->priv->popup), &rect);
173 gtk_popover_set_position (GTK_POPOVER (zoom_entry->priv->popup), GTK_POS_BOTTOM);
174 gtk_widget_set_size_request (zoom_entry->priv->popup, 150, -1);
175
176 return zoom_entry->priv->popup;
177 }
178
179 static void
eog_zoom_entry_icon_press_cb(GtkEntry * entry,GtkEntryIconPosition icon_pos,GdkEvent * event,gpointer data)180 eog_zoom_entry_icon_press_cb (GtkEntry *entry, GtkEntryIconPosition icon_pos,
181 GdkEvent *event, gpointer data)
182 {
183 EogZoomEntry *zoom_entry;
184 guint button;
185
186 g_return_if_fail (EOG_IS_ZOOM_ENTRY (data));
187 g_return_if_fail (icon_pos == GTK_ENTRY_ICON_SECONDARY);
188
189 if (!gdk_event_get_button (event, &button) || button != GDK_BUTTON_PRIMARY)
190 return;
191
192 zoom_entry = EOG_ZOOM_ENTRY (data);
193
194 gtk_widget_show (get_popup (zoom_entry));
195 zoom_entry->priv->popup_shown = TRUE;
196 }
197
198 static void
eog_zoom_entry_view_zoom_changed_cb(EogScrollView * view,gdouble zoom,gpointer data)199 eog_zoom_entry_view_zoom_changed_cb (EogScrollView *view, gdouble zoom,
200 gpointer data)
201 {
202 EogZoomEntry *zoom_entry = EOG_ZOOM_ENTRY (data);
203
204 eog_zoom_entry_set_zoom_level (zoom_entry, zoom);
205 }
206
207 static void
button_sensitivity_changed_cb(GObject * gobject,GParamSpec * pspec,gpointer user_data)208 button_sensitivity_changed_cb (GObject *gobject,
209 GParamSpec *pspec,
210 gpointer user_data)
211 {
212 g_return_if_fail (EOG_IS_ZOOM_ENTRY (user_data));
213
214 eog_zoom_entry_update_sensitivity (EOG_ZOOM_ENTRY (user_data));
215 }
216
217 static void
eog_zoom_entry_constructed(GObject * object)218 eog_zoom_entry_constructed (GObject *object)
219 {
220 EogZoomEntry *zoom_entry = EOG_ZOOM_ENTRY (object);
221
222 G_OBJECT_CLASS (eog_zoom_entry_parent_class)->constructed (object);
223
224 g_signal_connect (zoom_entry->priv->view,
225 "zoom-changed",
226 G_CALLBACK (eog_zoom_entry_view_zoom_changed_cb),
227 zoom_entry);
228 eog_zoom_entry_reset_zoom_level (zoom_entry);
229
230 zoom_entry->priv->zoom_free_section =
231 g_menu_model_get_item_link (G_MENU_MODEL (zoom_entry->priv->menu),
232 1, G_MENU_LINK_SECTION);
233 eog_zoom_entry_populate_free_zoom_section (zoom_entry);
234
235 g_signal_connect (zoom_entry->priv->btn_zoom_in, "notify::sensitive",
236 G_CALLBACK (button_sensitivity_changed_cb),
237 zoom_entry);
238 g_signal_connect (zoom_entry->priv->btn_zoom_out, "notify::sensitive",
239 G_CALLBACK (button_sensitivity_changed_cb),
240 zoom_entry);
241 eog_zoom_entry_update_sensitivity (zoom_entry);
242 }
243
244 static void
eog_zoom_entry_finalize(GObject * object)245 eog_zoom_entry_finalize (GObject *object)
246 {
247 EogZoomEntry *zoom_entry = EOG_ZOOM_ENTRY (object);
248
249 g_clear_object (&zoom_entry->priv->menu);
250 g_clear_object (&zoom_entry->priv->zoom_free_section);
251 g_clear_object (&zoom_entry->priv->view);
252
253 G_OBJECT_CLASS (eog_zoom_entry_parent_class)->finalize (object);
254 }
255
256 static void
eog_zoom_entry_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)257 eog_zoom_entry_set_property (GObject *object,
258 guint prop_id,
259 const GValue *value,
260 GParamSpec *pspec)
261 {
262 EogZoomEntry *zoom_entry = EOG_ZOOM_ENTRY (object);
263
264 switch (prop_id) {
265 case PROP_SCROLL_VIEW:
266 zoom_entry->priv->view = g_value_dup_object (value);
267 break;
268 case PROP_MENU:
269 zoom_entry->priv->menu = g_value_dup_object (value);
270 break;
271 default:
272 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
273 }
274 }
275
276 static void
eog_zoom_entry_set_zoom_level(EogZoomEntry * entry,gdouble zoom)277 eog_zoom_entry_set_zoom_level (EogZoomEntry *entry, gdouble zoom)
278 {
279 gchar *zoom_str;
280
281 zoom = CLAMP (zoom, EOG_SCROLL_VIEW_MIN_ZOOM_FACTOR,
282 EOG_SCROLL_VIEW_MAX_ZOOM_FACTOR);
283 zoom_str = eog_zoom_entry_format_zoom_value (zoom);
284 gtk_entry_set_text (GTK_ENTRY (entry->priv->value_entry), zoom_str);
285 g_free (zoom_str);
286 }
287
288 static void
eog_zoom_entry_reset_zoom_level(EogZoomEntry * entry)289 eog_zoom_entry_reset_zoom_level (EogZoomEntry *entry)
290 {
291 const gdouble zoom = eog_scroll_view_get_zoom (entry->priv->view);
292 eog_zoom_entry_set_zoom_level (entry, zoom);
293 }
294
295 static void
eog_zoom_entry_update_sensitivity(EogZoomEntry * entry)296 eog_zoom_entry_update_sensitivity (EogZoomEntry *entry)
297 {
298 const gboolean current_state =
299 gtk_widget_is_sensitive (entry->priv->value_entry);
300 const gboolean new_state =
301 gtk_widget_is_sensitive (entry->priv->btn_zoom_in)
302 | gtk_widget_is_sensitive (entry->priv->btn_zoom_out);
303
304 if (current_state != new_state) {
305 gtk_widget_set_sensitive (entry->priv->value_entry, new_state);
306 }
307
308 }
309
310 static void
eog_zoom_entry_class_init(EogZoomEntryClass * klass)311 eog_zoom_entry_class_init (EogZoomEntryClass *klass)
312 {
313 GObjectClass *object_class = G_OBJECT_CLASS (klass);
314 GtkWidgetClass *wklass = GTK_WIDGET_CLASS (klass);
315
316 object_class->constructed = eog_zoom_entry_constructed;
317 object_class->set_property = eog_zoom_entry_set_property;
318 object_class->finalize = eog_zoom_entry_finalize;
319
320 gtk_widget_class_set_template_from_resource (wklass,
321 "/org/gnome/eog/ui/eog-zoom-entry.ui");
322 gtk_widget_class_bind_template_child_private (wklass,
323 EogZoomEntry,
324 btn_zoom_in);
325 gtk_widget_class_bind_template_child_private (wklass,
326 EogZoomEntry,
327 btn_zoom_out);
328 gtk_widget_class_bind_template_child_private (wklass,
329 EogZoomEntry,
330 value_entry);
331
332 gtk_widget_class_bind_template_callback (wklass,
333 eog_zoom_entry_activate_cb);
334 gtk_widget_class_bind_template_callback (wklass,
335 eog_zoom_entry_icon_press_cb);
336
337 g_object_class_install_property (object_class, PROP_SCROLL_VIEW,
338 g_param_spec_object ("scroll-view",
339 "EogScrollView",
340 "The EogScrollView to work with",
341 EOG_TYPE_SCROLL_VIEW,
342 G_PARAM_WRITABLE |
343 G_PARAM_CONSTRUCT_ONLY |
344 G_PARAM_STATIC_STRINGS));
345
346 g_object_class_install_property (object_class, PROP_MENU,
347 g_param_spec_object ("menu",
348 "Menu",
349 "The zoom popup menu",
350 G_TYPE_MENU,
351 G_PARAM_WRITABLE |
352 G_PARAM_CONSTRUCT_ONLY |
353 G_PARAM_STATIC_STRINGS));
354 }
355
356 static void
eog_zoom_entry_init(EogZoomEntry * entry)357 eog_zoom_entry_init (EogZoomEntry *entry)
358 {
359 entry->priv = eog_zoom_entry_get_instance_private (entry);
360 gtk_widget_init_template (GTK_WIDGET (entry));
361
362 g_signal_connect_swapped (entry->priv->value_entry, "focus-out-event",
363 G_CALLBACK (focus_out_cb),
364 entry);
365 }
366
367 GtkWidget*
eog_zoom_entry_new(EogScrollView * view,GMenu * menu)368 eog_zoom_entry_new(EogScrollView *view, GMenu *menu)
369 {
370 g_return_val_if_fail (EOG_IS_SCROLL_VIEW (view), NULL);
371 g_return_val_if_fail (G_IS_MENU (menu), NULL);
372
373 return g_object_new (EOG_TYPE_ZOOM_ENTRY,
374 "scroll-view", view,
375 "menu", menu,
376 NULL);
377 }
378