diff --git a/AUTHORS b/AUTHORS index 9cea5ec6..0934b10e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,6 +13,7 @@ Contributors: Alexander Hesse Brian Vuyk Nick Schermer + Matthias Kruk Graphics: extension: Nancy Runge diff --git a/extensions/mouse-gestures/main.c b/extensions/mouse-gestures/main.c new file mode 100644 index 00000000..2777ca4d --- /dev/null +++ b/extensions/mouse-gestures/main.c @@ -0,0 +1,229 @@ +/* + Copyright (C) 2009 Matthias Kruk + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See the file COPYING for the full license text. +*/ + +#include +#include "mouse-gestures.h" + +#define MOUSE_GESTURES_VERSION "0.1" +#define DEVIANCE 20 // the deviance to determine if a line is straight or not +#define MINLENGTH 50 // the minimal length of a line to be treated as a gesture + +// #define __MOUSE_GESTURES_DEBUG__ // uncomment for debugging purposes + +MouseGesture *gesture; + +void mouse_gesture_clear(MouseGesture *g) +{ + g->start.x = 0; + g->start.y = 0; + g->middle.x = 0; + g->middle.y = 0; + g->end.x = 0; + g->end.y = 0; + g->last = MOUSE_BUTTON_UNSET; + return; +} + +MouseGesture* mouse_gesture_new(void) +{ + MouseGesture *g = NULL; + if((g = g_new(MouseGesture, 1)) == NULL) + return(NULL); + mouse_gesture_clear(g); + return(g); +} + +static gboolean mouse_gestures_handle_events(GtkWidget *widget, GdkEvent *event, MidoriBrowser *browser) +{ + if(event->type == GDK_BUTTON_PRESS) // a button was pressed + { + if(gesture->last == MOUSE_BUTTON_UNSET) // if the gesture was previously cleaned -> new gesture -> new start coordinates + { + gesture->start.x = event->button.x; + gesture->start.y = event->button.y; + gesture->last = event->button.button; + } + return(TRUE); + } + else if(event->type == GDK_MOTION_NOTIFY) // the mouse was moved + { + if(gesture->last != MOUSE_BUTTON_UNSET) + { + guint x, y; + x = event->motion.x; + y = event->motion.y; + if((gesture->start.x - x < DEVIANCE && gesture->start.x - x > -DEVIANCE) || + (gesture->start.y - y < DEVIANCE && gesture->start.y - y > -DEVIANCE)) + { + gesture->middle.x = x; + gesture->middle.y = y; + } + else if((gesture->middle.x - x < DEVIANCE && gesture->middle.x - x > -DEVIANCE) || + (gesture->middle.y - y < DEVIANCE && gesture->middle.y - y > -DEVIANCE)) + { + gesture->end.x = x; + gesture->end.y = y; + } + } + return(TRUE); + } + else if(event->type == GDK_BUTTON_RELEASE) + { + if(gesture->last == MOUSE_BUTTON_MIDDLE) // all mouse gestures will use the middle mouse button + { + // middle mouse button has been released + if(gesture->middle.x - gesture->start.x < DEVIANCE && gesture->middle.x - gesture->start.x > -DEVIANCE) + { + // StartNode to MiddleNode is a straight vertical line + if(gesture->middle.y > gesture->start.y + MINLENGTH) + { + // StartNode to MiddleNode is drawn downwards + if(gesture->middle.y - gesture->end.y < DEVIANCE && gesture->middle.y - gesture->end.y > -DEVIANCE && gesture->end.x > gesture->middle.x + MINLENGTH) + { + // MiddleNode to EndNode is a straight horizontal line (to the right) -> close tab + midori_browser_activate_action(browser, "TabClose"); + #ifdef __MOUSE_GESTURES_DEBUG__ + g_print("TabClose gesture\n"); + #endif + } + else if(gesture->middle.y - gesture->end.y < DEVIANCE && gesture->middle.y - gesture->end.y > -DEVIANCE && gesture->end.x < gesture->middle.x - MINLENGTH) + { + // MiddleNode to EndNode is a straight horizontal line (to the left) -> reload + midori_browser_activate_action(browser, "Reload"); + #ifdef __MOUSE_GESTURES_DEBUG__ + g_print("Reload gesture\n"); + #endif + } + else if(gesture->end.y == 0 && gesture->end.x == 0) + { + // no EndNode, just a vertical line -> new tab + midori_browser_activate_action(browser, "TabNew"); + #ifdef __MOUSE_GESTURES_DEBUG__ + g_print("TabNew gesture\n"); + #endif + } + } + if(gesture->middle.y < gesture->start.y - MINLENGTH) + { + // StartNode to MiddleNode is drawn upwards + if(gesture->end.y == 0 && gesture->end.x == 0) + { + // no EndNode, just a vertical line -> stop + midori_browser_activate_action(browser, "Stop"); + #ifdef __MOUSE_GESTURES_DEBUG__ + g_print("Stop gesture\n"); + #endif + } + } + } + else if(gesture->middle.y - gesture->start.y < DEVIANCE && gesture->middle.y - gesture->start.y > -DEVIANCE) + { + // Start Node to MiddleNode is a straight horizontal line + if(gesture->middle.x > gesture->start.x + MINLENGTH) + { + // the line was drawn from left to right + if(gesture->end.x == 0 && gesture->end.y == 0) + { + // we only have one horizontal line from left to right -> forward gesture + midori_browser_activate_action(browser, "Forward"); + #ifdef __MOUSE_GESTURES_DEBUG__ + g_print("Forward gesture\n"); + #endif + } + } + else if(gesture->middle.x < gesture->start.x - MINLENGTH) + { + // the line was drawn from right to left + if(gesture->end.x == 0 && gesture->end.y == 0) + { + // we only have one horizontal line from right to left -> backwards gesture + midori_browser_activate_action(browser, "Back"); + #ifdef __MOUSE_GESTURES_DEBUG__ + g_print("Back gesture\n"); + #endif + } + } + } + } + mouse_gesture_clear(gesture); // gesture finished, clear it + return(TRUE); + } + else + return(FALSE); // this event is supposed to be handled again (by someone else's code) since it was irrelevant to us +} + +static void mouse_gestures_tab_cb(MidoriBrowser* browser, GtkWidget *view) // this is performed when a new tab is created +{ + g_signal_connect(view, "event", G_CALLBACK(mouse_gestures_handle_events), browser); // perform the above callback when an event from the view is received + return; +} + +static void mouse_gestures_browser_cb(MidoriApp *app, MidoriBrowser *browser) // this is performed when ever a new window is created +{ + g_signal_connect(browser, "add-tab", G_CALLBACK(mouse_gestures_tab_cb), NULL); // connect the above callback to the "add-tab" signal of browser + return; +} + +/* this is performed when the extension is deactivated. + disconnect all signal handlers, so that mouse gestures are no longer handled */ +static void mouse_gestures_deactivate(MidoriExtension *extension, MidoriApp *app) +{ + gulong signal_id = g_signal_handler_find(app, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, mouse_gestures_browser_cb, NULL); // get the signal handler id + if(signal_id != 0) // if that id is valid + g_signal_handler_disconnect(app, signal_id); // disconnect the signal + KatzeArray *browsers = katze_object_get_object(app, "browsers"); // get the array that contains all browsers + guint i; // our counter variable :) + for(i = 0; i < katze_array_get_length(browsers); i++) // from the first to the last browser + { + MidoriBrowser *browser = katze_array_get_nth_item(browsers, i); // get a pointer on the current browser + signal_id = g_signal_handler_find(browser, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, mouse_gestures_tab_cb, NULL); // search forthe signal handler id + if(signal_id != 0) // and if its not invalid.. + g_signal_handler_disconnect(browser, signal_id); // disconnect it + + GtkWidget *notebook = katze_object_get_object(browser, "notebook"); // get a pointer on the notebook + gint j; // another counter + for(j = 0; j < gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook)); j++) // from the first to the last tab + { + GtkWidget *page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), j); // get a pointer on the tab's view + signal_id = g_signal_handler_find(page, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, mouse_gestures_handle_events, NULL); // find the signal id of the event handler + if(signal_id != 0) // if the id is valid + g_signal_handler_disconnect(page, signal_id); // disconnect the handler + } + } + g_free(gesture); // free the structure that contains the gesture information + return; +} + +static void mouse_gestures_activate(MidoriExtension *extension, MidoriApp *app) // this is performed on extension-activation +{ + g_signal_connect(app, "add-browser", G_CALLBACK(mouse_gestures_browser_cb), NULL); // connect the above callback to the "add-browser" signal of app + g_signal_connect(extension, "deactivate", G_CALLBACK(mouse_gestures_deactivate), app); // connect the deactivate callback to the extension + return; +} + +MidoriExtension* extension_init(void) +{ + MidoriExtension* extension = NULL; + extension = g_object_new(MIDORI_TYPE_EXTENSION, + "name", "Mouse gestures", + "description", "Control Midori by moving the mouse", + "version", MOUSE_GESTURES_VERSION, + "authors", "Matthias Kruk ", NULL); + g_signal_connect(extension, "activate", G_CALLBACK(mouse_gestures_activate), NULL); // connect the callback that's executed on activation + gesture = NULL; // initialise gesture pointer; initialising pointers is always a good choice + if((gesture = mouse_gesture_new()) == NULL) // allocate space for the gesture + { + // no space could be allocated + g_free(extension); // free the space used by this extension + return(NULL); + } + return(extension); +} diff --git a/extensions/mouse-gestures/mouse-gestures.h b/extensions/mouse-gestures/mouse-gestures.h new file mode 100644 index 00000000..945c05ab --- /dev/null +++ b/extensions/mouse-gestures/mouse-gestures.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2009 Matthias Kruk + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See the file COPYING for the full license text. +*/ + +#ifndef __MOUSE_GESTURES_H__ +#define __MOUSE_GESTURES_H__ + +typedef struct _MouseGesture MouseGesture; +typedef enum _MouseButton MouseButton; + +enum _MouseButton { + MOUSE_BUTTON_LEFT = 1, + MOUSE_BUTTON_RIGHT = 3, + MOUSE_BUTTON_MIDDLE = 2, + MOUSE_BUTTON_UNSET = 0 +}; + +struct MouseGestureNode { + double x; + double y; +} MouseGestureNode_t; + +struct _MouseGesture { + struct MouseGestureNode start; + struct MouseGestureNode middle; + struct MouseGestureNode end; + MouseButton last; +}; + +#endif