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