1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  *  Copyright © 2018 Jan-Michael Brummer
4  *
5  *  This file is part of Epiphany.
6  *
7  *  Epiphany is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  Epiphany is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include "ephy-embed.h"
24 #include "ephy-mouse-gesture-controller.h"
25 #include "ephy-prefs.h"
26 #include "ephy-settings.h"
27 #include "ephy-window.h"
28 
29 #include <math.h>
30 #include <gtk/gtk.h>
31 
32 #define NUM_SEQUENCES 2
33 
34 typedef enum {
35   MOUSE_DIRECTION_UNKNOWN = 0,
36   MOUSE_DIRECTION_RIGHT,
37   MOUSE_DIRECTION_LEFT,
38   MOUSE_DIRECTION_DOWN,
39   MOUSE_DIRECTION_UP,
40 } MouseDirection;
41 
42 struct _EphyMouseGestureController {
43   GObject parent_instance;
44 
45   GtkEventController *controller;
46   EphyWindow *window;
47   WebKitWebView *web_view;
48 
49   MouseDirection sequence[NUM_SEQUENCES];
50   MouseDirection direction;
51   gint sequence_pos;
52   gdouble last_x;
53   gdouble last_y;
54   gboolean gesture_active;
55 };
56 
57 enum {
58   PROP_0,
59   PROP_WINDOW,
60   LAST_PROP
61 };
62 
63 static GParamSpec *obj_properties[LAST_PROP];
64 
G_DEFINE_TYPE(EphyMouseGestureController,ephy_mouse_gesture_controller,G_TYPE_OBJECT)65 G_DEFINE_TYPE (EphyMouseGestureController, ephy_mouse_gesture_controller, G_TYPE_OBJECT)
66 
67 static void
68 ephy_mouse_gesture_controller_motion_cb (GtkEventControllerMotion *controller,
69                                          gdouble                   x,
70                                          gdouble                   y,
71                                          gpointer                  user_data)
72 {
73   EphyMouseGestureController *self = EPHY_MOUSE_GESTURE_CONTROLLER (user_data);
74   MouseDirection direction;
75   gdouble offset_x, offset_y;
76 
77   if (!self->gesture_active || self->sequence_pos == NUM_SEQUENCES)
78     return;
79 
80   if (isnan (self->last_x) || isnan (self->last_y)) {
81     self->last_x = x;
82     self->last_y = y;
83     return;
84   }
85 
86   offset_x = x - self->last_x;
87   offset_y = y - self->last_y;
88 
89   /* Try to guess direction */
90   if (fabs (offset_x) > fabs (offset_y) * 2) {
91     if (offset_x > 0)
92       direction = MOUSE_DIRECTION_RIGHT;
93     else
94       direction = MOUSE_DIRECTION_LEFT;
95   } else if (fabs (offset_y) > fabs (offset_x) * 2) {
96     if (offset_y > 0)
97       direction = MOUSE_DIRECTION_DOWN;
98     else
99       direction = MOUSE_DIRECTION_UP;
100   } else {
101     return;
102   }
103 
104   self->last_x = x;
105   self->last_y = y;
106 
107   if (self->direction == direction)
108     return;
109 
110   self->sequence[self->sequence_pos++] = direction;
111 
112   self->direction = direction;
113 }
114 
115 static void
ephy_mouse_gesture_controller_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)116 ephy_mouse_gesture_controller_set_property (GObject      *object,
117                                             guint         prop_id,
118                                             const GValue *value,
119                                             GParamSpec   *pspec)
120 {
121   EphyMouseGestureController *self = EPHY_MOUSE_GESTURE_CONTROLLER (object);
122 
123   switch (prop_id) {
124     case PROP_WINDOW:
125       self->window = g_value_get_object (value);
126       break;
127     default:
128       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
129       break;
130   }
131 }
132 
133 static void
ephy_mouse_gesture_controller_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)134 ephy_mouse_gesture_controller_get_property (GObject    *object,
135                                             guint       prop_id,
136                                             GValue     *value,
137                                             GParamSpec *pspec)
138 {
139   EphyMouseGestureController *self = EPHY_MOUSE_GESTURE_CONTROLLER (object);
140 
141   switch (prop_id) {
142     case PROP_WINDOW:
143       g_value_set_object (value, self->window);
144       break;
145     default:
146       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
147       break;
148   }
149 }
150 
151 static void
ephy_mouse_gesture_controller_reset(EphyMouseGestureController * self)152 ephy_mouse_gesture_controller_reset (EphyMouseGestureController *self)
153 {
154   self->direction = MOUSE_DIRECTION_UNKNOWN;
155   self->sequence_pos = 0;
156   self->last_x = NAN;
157   self->last_y = NAN;
158   self->gesture_active = FALSE;
159 }
160 
161 static gboolean
ephy_mouse_gesture_controller_button_press_cb(GtkWidget * widget,GdkEvent * event,gpointer user_data)162 ephy_mouse_gesture_controller_button_press_cb (GtkWidget *widget,
163                                                GdkEvent  *event,
164                                                gpointer   user_data)
165 {
166   EphyMouseGestureController *self = EPHY_MOUSE_GESTURE_CONTROLLER (user_data);
167   GdkEventButton *button_event = (GdkEventButton *)event;
168 
169   if (button_event->button == GDK_BUTTON_MIDDLE)
170     self->gesture_active = TRUE;
171   else
172     self->gesture_active = FALSE;
173 
174   return FALSE;
175 }
176 
177 static void
handle_gesture(gpointer user_data)178 handle_gesture (gpointer user_data)
179 {
180   EphyMouseGestureController *self = EPHY_MOUSE_GESTURE_CONTROLLER (user_data);
181   GActionGroup *action_group_toolbar = gtk_widget_get_action_group (GTK_WIDGET (self->window), "toolbar");
182   GActionGroup *action_group_win = gtk_widget_get_action_group (GTK_WIDGET (self->window), "win");
183   GActionGroup *action_group_tab = gtk_widget_get_action_group (GTK_WIDGET (self->window), "tab");
184   GAction *action;
185 
186   switch (self->sequence_pos) {
187     case 1:
188       if (self->sequence[0] == MOUSE_DIRECTION_LEFT) {
189         /* Nav back */
190         action = g_action_map_lookup_action (G_ACTION_MAP (action_group_toolbar), "navigation-back");
191         g_action_activate (action, NULL);
192       } else if (self->sequence[0] == MOUSE_DIRECTION_RIGHT) {
193         /* Nav forward */
194         action = g_action_map_lookup_action (G_ACTION_MAP (action_group_toolbar), "navigation-forward");
195         g_action_activate (action, NULL);
196       } else if (self->sequence[0] == MOUSE_DIRECTION_DOWN) {
197         /* New tab */
198         action = g_action_map_lookup_action (G_ACTION_MAP (action_group_win), "new-tab");
199         g_action_activate (action, NULL);
200       }
201       break;
202     case 2:
203       if (self->sequence[0] == MOUSE_DIRECTION_DOWN && self->sequence[1] == MOUSE_DIRECTION_RIGHT) {
204         /* Close tab */
205         action = g_action_map_lookup_action (G_ACTION_MAP (action_group_tab), "close");
206         g_action_activate (action, NULL);
207       } else if (self->sequence[0] == MOUSE_DIRECTION_UP && self->sequence[1] == MOUSE_DIRECTION_DOWN) {
208         /* Reload tab */
209         action = g_action_map_lookup_action (G_ACTION_MAP (action_group_toolbar), "reload");
210         g_action_activate (action, NULL);
211       }
212       break;
213     default:
214       break;
215   }
216 
217   ephy_mouse_gesture_controller_reset (self);
218 }
219 
220 static gboolean
ephy_mouse_gesture_controller_button_release_cb(GtkWidget * widget,GdkEvent * event,gpointer user_data)221 ephy_mouse_gesture_controller_button_release_cb (GtkWidget *widget,
222                                                  GdkEvent  *event,
223                                                  gpointer   user_data)
224 {
225   EphyMouseGestureController *self = EPHY_MOUSE_GESTURE_CONTROLLER (user_data);
226   GdkEventButton *button_event = (GdkEventButton *)event;
227 
228   if (button_event->button == GDK_BUTTON_MIDDLE) {
229     if (self->gesture_active && g_settings_get_boolean (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_ENABLE_MOUSE_GESTURES))
230       handle_gesture (user_data);
231 
232     self->gesture_active = FALSE;
233   }
234 
235   return FALSE;
236 }
237 
238 static void
ephy_mouse_gesture_controller_init(EphyMouseGestureController * self)239 ephy_mouse_gesture_controller_init (EphyMouseGestureController *self)
240 {
241 }
242 
243 void
ephy_mouse_gesture_controller_unset_web_view(EphyMouseGestureController * self)244 ephy_mouse_gesture_controller_unset_web_view (EphyMouseGestureController *self)
245 {
246   if (self->web_view) {
247     g_signal_handlers_disconnect_by_func (self->web_view,
248                                           G_CALLBACK (ephy_mouse_gesture_controller_button_press_cb),
249                                           self);
250     g_signal_handlers_disconnect_by_func (self->web_view,
251                                           G_CALLBACK (ephy_mouse_gesture_controller_button_release_cb),
252                                           self);
253     g_clear_object (&self->web_view);
254   }
255 }
256 
257 static void
ephy_mouse_gesture_controller_dispose(GObject * object)258 ephy_mouse_gesture_controller_dispose (GObject *object)
259 {
260   EphyMouseGestureController *self = EPHY_MOUSE_GESTURE_CONTROLLER (object);
261 
262   g_clear_object (&self->controller);
263   ephy_mouse_gesture_controller_unset_web_view (self);
264 
265   G_OBJECT_CLASS (ephy_mouse_gesture_controller_parent_class)->dispose (object);
266 }
267 
268 static void
ephy_mouse_gesture_controller_constructed(GObject * object)269 ephy_mouse_gesture_controller_constructed (GObject *object)
270 {
271   EphyMouseGestureController *self = EPHY_MOUSE_GESTURE_CONTROLLER (object);
272 
273   ephy_mouse_gesture_controller_reset (self);
274 
275   self->controller = gtk_event_controller_motion_new (GTK_WIDGET (self->window));
276   g_signal_connect (self->controller, "motion", G_CALLBACK (ephy_mouse_gesture_controller_motion_cb), self);
277 }
278 
279 static void
ephy_mouse_gesture_controller_class_init(EphyMouseGestureControllerClass * klass)280 ephy_mouse_gesture_controller_class_init (EphyMouseGestureControllerClass *klass)
281 {
282   GObjectClass *object_class = G_OBJECT_CLASS (klass);
283 
284   object_class->dispose = ephy_mouse_gesture_controller_dispose;
285   object_class->constructed = ephy_mouse_gesture_controller_constructed;
286 
287   /* class creation */
288   object_class->set_property = ephy_mouse_gesture_controller_set_property;
289   object_class->get_property = ephy_mouse_gesture_controller_get_property;
290 
291   obj_properties[PROP_WINDOW] =
292     g_param_spec_object ("window",
293                          "window",
294                          "window",
295                          EPHY_TYPE_WINDOW,
296                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
297 
298   g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
299 }
300 
301 EphyMouseGestureController *
ephy_mouse_gesture_controller_new(EphyWindow * window)302 ephy_mouse_gesture_controller_new (EphyWindow *window)
303 {
304   return g_object_new (EPHY_TYPE_MOUSE_GESTURE_CONTROLLER,
305                        "window", window,
306                        NULL);
307 }
308 
309 void
ephy_mouse_gesture_controller_set_web_view(EphyMouseGestureController * self,WebKitWebView * web_view)310 ephy_mouse_gesture_controller_set_web_view (EphyMouseGestureController *self,
311                                             WebKitWebView              *web_view)
312 {
313   ephy_mouse_gesture_controller_unset_web_view (self);
314 
315   g_signal_connect_object (web_view, "button-press-event", G_CALLBACK (ephy_mouse_gesture_controller_button_press_cb), self, 0);
316   g_signal_connect_object (web_view, "button-release-event", G_CALLBACK (ephy_mouse_gesture_controller_button_release_cb), self, 0);
317 
318   self->web_view = g_object_ref (web_view);
319 }
320