1 /*
2  * Photos - access, organize and share your photos on GNOME
3  * Copyright © 2017 – 2019 Red Hat, Inc.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 
20 #include "config.h"
21 
22 #include <string.h>
23 
24 #include <gio/gio.h>
25 
26 #include "photos-utils.h"
27 #include "photos-zoom-controls.h"
28 
29 
30 struct _PhotosZoomControls
31 {
32   GtkBin parent_instance;
33   GAction *zoom_best_fit_action;
34   GAction *zoom_out_action;
35   GtkWidget *revealer;
36   GtkWidget *zoom_in_button;
37   GtkWidget *zoom_out_button;
38   GtkWidget *zoom_toggle_button;
39   gboolean hover;
40   guint notify_enabled_id;
41 };
42 
43 enum
44 {
45   PROP_0,
46   PROP_HOVER
47 };
48 
49 
50 G_DEFINE_TYPE (PhotosZoomControls, photos_zoom_controls, GTK_TYPE_BIN);
51 
52 
53 static void
photos_zoom_controls_update_buttons(PhotosZoomControls * self)54 photos_zoom_controls_update_buttons (PhotosZoomControls *self)
55 {
56   GtkWidget *image;
57   gboolean zoom_best_fit_enabled;
58   gboolean zoom_out_enabled;
59   const gchar *icon_name;
60 
61   zoom_best_fit_enabled = g_action_get_enabled (self->zoom_best_fit_action);
62   zoom_out_enabled = g_action_get_enabled (self->zoom_out_action);
63   g_return_if_fail (zoom_best_fit_enabled == zoom_out_enabled);
64 
65   gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), zoom_out_enabled);
66 
67   gtk_actionable_set_action_name (GTK_ACTIONABLE (self->zoom_toggle_button), NULL);
68   gtk_actionable_set_action_target_value (GTK_ACTIONABLE (self->zoom_toggle_button), NULL);
69 
70   if (zoom_out_enabled)
71     {
72       gtk_actionable_set_action_name (GTK_ACTIONABLE (self->zoom_toggle_button), "app.zoom-best-fit");
73     }
74   else
75     {
76       GVariant *target_value = NULL;
77 
78       target_value = photos_utils_create_zoom_target_value (1.0, PHOTOS_ZOOM_EVENT_MOUSE_CLICK);
79       gtk_actionable_set_action_target_value (GTK_ACTIONABLE (self->zoom_toggle_button), target_value);
80       gtk_actionable_set_action_name (GTK_ACTIONABLE (self->zoom_toggle_button), "app.zoom-in");
81     }
82 
83   icon_name = zoom_out_enabled ? "zoom-fit-best-symbolic" : "zoom-in-symbolic";
84   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_INVALID);
85   gtk_image_set_pixel_size (GTK_IMAGE (image), 16);
86   gtk_button_set_image (GTK_BUTTON (self->zoom_toggle_button), image);
87 }
88 
89 
90 static gboolean
photos_zoom_controls_notify_idle(gpointer user_data)91 photos_zoom_controls_notify_idle (gpointer user_data)
92 {
93   PhotosZoomControls *self = PHOTOS_ZOOM_CONTROLS (user_data);
94 
95   self->notify_enabled_id = 0;
96   photos_zoom_controls_update_buttons (self);
97   return G_SOURCE_REMOVE;
98 }
99 
100 
101 static void
photos_zoom_controls_notify_enabled(PhotosZoomControls * self)102 photos_zoom_controls_notify_enabled (PhotosZoomControls *self)
103 {
104   if (self->notify_enabled_id != 0)
105     return;
106 
107   self->notify_enabled_id = g_idle_add (photos_zoom_controls_notify_idle, self);
108 }
109 
110 
111 static void
photos_zoom_controls_set_hover(PhotosZoomControls * self,gboolean hover)112 photos_zoom_controls_set_hover (PhotosZoomControls *self, gboolean hover)
113 {
114   if (self->hover == hover)
115     return;
116 
117   self->hover = hover;
118   g_object_notify (G_OBJECT (self), "hover");
119 }
120 
121 
122 static gboolean
photos_zoom_controls_enter_notify(GtkWidget * widget,GdkEventCrossing * event)123 photos_zoom_controls_enter_notify (GtkWidget *widget, GdkEventCrossing *event)
124 {
125   PhotosZoomControls *self = PHOTOS_ZOOM_CONTROLS (widget);
126 
127   if (event->detail != GDK_NOTIFY_INFERIOR)
128     photos_zoom_controls_set_hover (self, TRUE);
129 
130   return GDK_EVENT_PROPAGATE;
131 }
132 
133 
134 static gboolean
photos_zoom_controls_leave_notify(GtkWidget * widget,GdkEventCrossing * event)135 photos_zoom_controls_leave_notify (GtkWidget *widget, GdkEventCrossing *event)
136 {
137   PhotosZoomControls *self = PHOTOS_ZOOM_CONTROLS (widget);
138 
139   if (event->detail != GDK_NOTIFY_INFERIOR)
140     photos_zoom_controls_set_hover (self, FALSE);
141 
142   return GDK_EVENT_PROPAGATE;
143 }
144 
145 
146 static void
photos_zoom_controls_realize(GtkWidget * widget)147 photos_zoom_controls_realize (GtkWidget *widget)
148 {
149   GdkVisual *visual;
150   g_autoptr (GdkWindow) window = NULL;
151   GdkWindow *parent_window;
152   GdkWindowAttr attributes;
153   GtkAllocation allocation;
154   gint attributes_mask;
155 
156   gtk_widget_set_realized (widget, TRUE);
157 
158   attributes.event_mask = gtk_widget_get_events (widget);
159   attributes.event_mask |= (GDK_BUTTON_PRESS_MASK
160                             | GDK_BUTTON_RELEASE_MASK
161                             | GDK_ENTER_NOTIFY_MASK
162                             | GDK_LEAVE_NOTIFY_MASK);
163 
164   gtk_widget_get_allocation (widget, &allocation);
165   attributes.x = allocation.x;
166   attributes.y = allocation.y;
167   attributes.width = allocation.width;
168   attributes.height = allocation.height;
169 
170   attributes.wclass = GDK_INPUT_OUTPUT;
171 
172   visual = gtk_widget_get_visual (widget);
173   attributes.visual = visual;
174 
175   attributes.window_type = GDK_WINDOW_CHILD;
176 
177   attributes_mask = GDK_WA_VISUAL | GDK_WA_X | GDK_WA_Y;
178 
179   parent_window = gtk_widget_get_parent_window (widget);
180   window = gdk_window_new (parent_window, &attributes, attributes_mask);
181   gtk_widget_set_window (widget, g_object_ref (window));
182   gtk_widget_register_window (widget, window);
183 }
184 
185 
186 static void
photos_zoom_controls_size_allocate(GtkWidget * widget,GtkAllocation * allocation)187 photos_zoom_controls_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
188 {
189   GdkWindow *window;
190 
191   GTK_WIDGET_CLASS (photos_zoom_controls_parent_class)->size_allocate (widget, allocation);
192 
193   if (!gtk_widget_get_realized (widget))
194     return;
195 
196   window = gtk_widget_get_window (widget);
197   gdk_window_move_resize (window, allocation->x, allocation->y, allocation->width, allocation->height);
198 }
199 
200 
201 static void
photos_zoom_controls_dispose(GObject * object)202 photos_zoom_controls_dispose (GObject *object)
203 {
204   PhotosZoomControls *self = PHOTOS_ZOOM_CONTROLS (object);
205 
206   if (self->notify_enabled_id != 0)
207     {
208       g_source_remove (self->notify_enabled_id);
209       self->notify_enabled_id = 0;
210     }
211 
212   G_OBJECT_CLASS (photos_zoom_controls_parent_class)->dispose (object);
213 }
214 
215 
216 static void
photos_zoom_controls_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)217 photos_zoom_controls_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
218 {
219   PhotosZoomControls *self = PHOTOS_ZOOM_CONTROLS (object);
220 
221   switch (prop_id)
222     {
223     case PROP_HOVER:
224       g_value_set_boolean (value, self->hover);
225       break;
226 
227     default:
228       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
229       break;
230     }
231 }
232 
233 
234 static void
photos_zoom_controls_init(PhotosZoomControls * self)235 photos_zoom_controls_init (PhotosZoomControls *self)
236 {
237   GApplication *app;
238 
239   app = g_application_get_default ();
240 
241   gtk_widget_init_template (GTK_WIDGET (self));
242   gtk_widget_set_has_window (GTK_WIDGET (self), TRUE);
243 
244   self->zoom_best_fit_action = g_action_map_lookup_action (G_ACTION_MAP (app), "zoom-best-fit");
245   g_signal_connect_swapped (self->zoom_best_fit_action,
246                             "notify::enabled",
247                             G_CALLBACK (photos_zoom_controls_notify_enabled),
248                             self);
249 
250   self->zoom_out_action = g_action_map_lookup_action (G_ACTION_MAP (app), "zoom-out");
251   g_signal_connect_swapped (self->zoom_out_action,
252                             "notify::enabled",
253                             G_CALLBACK (photos_zoom_controls_notify_enabled),
254                             self);
255 
256   photos_zoom_controls_update_buttons (self);
257 }
258 
259 
260 static void
photos_zoom_controls_class_init(PhotosZoomControlsClass * class)261 photos_zoom_controls_class_init (PhotosZoomControlsClass *class)
262 {
263   GObjectClass *object_class = G_OBJECT_CLASS (class);
264   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
265 
266   object_class->dispose = photos_zoom_controls_dispose;
267   object_class->get_property = photos_zoom_controls_get_property;
268 
269   widget_class->enter_notify_event = photos_zoom_controls_enter_notify;
270   widget_class->leave_notify_event = photos_zoom_controls_leave_notify;
271   widget_class->realize = photos_zoom_controls_realize;
272   widget_class->size_allocate = photos_zoom_controls_size_allocate;
273 
274   g_object_class_install_property (object_class,
275                                    PROP_HOVER,
276                                    g_param_spec_boolean ("hover",
277                                                          "Hover",
278                                                          "Whether the widget is hovered",
279                                                          FALSE,
280                                                          G_PARAM_EXPLICIT_NOTIFY |
281                                                          G_PARAM_READABLE |
282                                                          G_PARAM_STATIC_STRINGS));
283 
284   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Photos/zoom-controls.ui");
285   gtk_widget_class_bind_template_child (widget_class, PhotosZoomControls, revealer);
286   gtk_widget_class_bind_template_child (widget_class, PhotosZoomControls, zoom_in_button);
287   gtk_widget_class_bind_template_child (widget_class, PhotosZoomControls, zoom_out_button);
288   gtk_widget_class_bind_template_child (widget_class, PhotosZoomControls, zoom_toggle_button);
289 }
290 
291 
292 GtkWidget *
photos_zoom_controls_new(void)293 photos_zoom_controls_new (void)
294 {
295   return g_object_new (PHOTOS_TYPE_ZOOM_CONTROLS, NULL);
296 }
297 
298 
299 gboolean
photos_zoom_controls_get_hover(PhotosZoomControls * self)300 photos_zoom_controls_get_hover (PhotosZoomControls *self)
301 {
302   g_return_val_if_fail (PHOTOS_IS_ZOOM_CONTROLS (self), FALSE);
303   return self->hover;
304 }
305