/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * Copyright © 2011 Igalia S.L. * * 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. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "ephy-overlay-escaping-child.h" #define EPHY_OVERLAY_ESCAPING_CHILD_DEFAULT_DISTANCE 20 G_DEFINE_TYPE (EphyOverlayEscapingChild, ephy_overlay_escaping_child, GEDIT_TYPE_OVERLAY_CHILD); /* properties */ enum { PROP_0, PROP_ESCAPING_DISTANCE }; struct _EphyOverlayEscapingChildPrivate { guint escaping_distance; GtkAllocation initial_allocation; GdkRectangle escaping_area; }; /* If the pointer leaves the window, restore the widget position */ static gboolean parent_leave_notify_event (GtkWidget *widget, GdkEventMotion *event, GtkWidget *parent) { EphyOverlayEscapingChildPrivate *priv = EPHY_OVERLAY_ESCAPING_CHILD (widget)->priv; GtkAllocation alloc; gtk_widget_get_allocation (widget, &alloc); alloc.y = priv->initial_allocation.y; gtk_widget_size_allocate (widget, &alloc); return FALSE; } /* this should be in Gdk...really */ static gboolean is_point_in_rectangle (int point_x, int point_y, GdkRectangle rectangle) { int rectangle_x_higher_bound = rectangle.x + rectangle.width; int rectangle_y_higher_bound = rectangle.y + rectangle.height; return point_x >= rectangle.x && point_x < rectangle_x_higher_bound && point_y >= rectangle.y && point_y < rectangle_y_higher_bound; } /* Keep the widget-pointer distance at at least * EphyOverlayEscapingChildPrivate::escaping_distance by sliding the widget * away if needed. */ static gboolean parent_motion_notify_event (GtkWidget *widget, GdkEventMotion *event, GtkWidget *parent) { EphyOverlayEscapingChildPrivate *priv = EPHY_OVERLAY_ESCAPING_CHILD (widget)->priv; int distance_x, distance_y; GtkAllocation alloc; gtk_widget_get_allocation (widget, &alloc); if (is_point_in_rectangle (event->x, event->y, priv->escaping_area)) { gdk_window_get_device_position (gtk_widget_get_window (widget), gdk_event_get_device ((GdkEvent *) event), &distance_x, &distance_y, NULL); alloc.y += priv->escaping_distance + distance_y; } else { /* Put the widget at its original position if we are out of the escaping * zone. Do nothing if it is already there. */ if (alloc.y == priv->initial_allocation.y) return FALSE; alloc.y = priv->initial_allocation.y; } gtk_widget_size_allocate (widget, &alloc); return FALSE; } /* When the parent overlay is resized, the child relative position is modified. * So we update our initial_allocation to this new value and redefine our * escaping area. */ static void parent_size_allocate (GtkWidget *widget, GdkRectangle *allocation, GtkWidget *parent) { EphyOverlayEscapingChildPrivate *priv = EPHY_OVERLAY_ESCAPING_CHILD (widget)->priv; GtkAllocation initial_allocation; gtk_widget_get_allocation (widget, &initial_allocation); priv->escaping_area = priv->initial_allocation = initial_allocation; /* Define an escaping area around the widget. * Current implementation only handle horizontal lowerside widgets */ priv->escaping_area.height += priv->escaping_distance; /* escape on both right and left */ priv->escaping_area.width += 2 * priv->escaping_distance; priv->escaping_area.x -= priv->escaping_distance; priv->escaping_area.y -= priv->escaping_distance; } /* Install listeners on our overlay parents to locate the pointer * and our relative position. */ static void ephy_overlay_escaping_child_parent_set (GtkWidget *widget, GtkWidget *previous_parent) { GtkWidget *parent; if (previous_parent != NULL) { g_signal_handlers_disconnect_by_func (previous_parent, G_CALLBACK (parent_motion_notify_event), widget); g_signal_handlers_disconnect_by_func (previous_parent, G_CALLBACK (parent_leave_notify_event), widget); g_signal_handlers_disconnect_by_func (previous_parent, G_CALLBACK (parent_size_allocate), widget); } parent = gtk_widget_get_parent (widget); if (parent == NULL) return; g_signal_connect_swapped (parent, "motion-notify-event", G_CALLBACK (parent_motion_notify_event), widget); g_signal_connect_swapped (parent, "leave-notify-event", G_CALLBACK (parent_leave_notify_event), widget); g_signal_connect_swapped (parent, "size-allocate", G_CALLBACK (parent_size_allocate), widget); } /* When the mouse is over us, translate the event coords and slide the widget * accordingly */ static gboolean ephy_overlay_escaping_child_motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { EphyOverlayEscapingChildPrivate *priv = EPHY_OVERLAY_ESCAPING_CHILD (widget)->priv; event->x += priv->initial_allocation.x; event->y += priv->initial_allocation.y; return parent_motion_notify_event (widget, event, gtk_widget_get_parent (widget)); } /* Make our event window propagate mouse motion events, so we can slide the widget, * when hovered. */ static void ephy_overlay_escaping_child_realize (GtkWidget *widget) { GdkWindow *window; GdkEventMask events; GTK_WIDGET_CLASS (ephy_overlay_escaping_child_parent_class)->realize (widget); window = gtk_widget_get_window (widget); events = gdk_window_get_events (window); events |= GDK_POINTER_MOTION_MASK; gdk_window_set_events (window, events); } static void ephy_overlay_escaping_child_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { EphyOverlayEscapingChild *self = EPHY_OVERLAY_ESCAPING_CHILD (object); EphyOverlayEscapingChildPrivate *priv = self->priv; switch (property_id) { case PROP_ESCAPING_DISTANCE: g_value_set_uint (value, priv->escaping_distance); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void ephy_overlay_escaping_child_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { EphyOverlayEscapingChild *self = EPHY_OVERLAY_ESCAPING_CHILD (object); EphyOverlayEscapingChildPrivate *priv = self->priv; switch (property_id) { case PROP_ESCAPING_DISTANCE: priv->escaping_distance = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void ephy_overlay_escaping_child_class_init (EphyOverlayEscapingChildClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); g_type_class_add_private (klass, sizeof(EphyOverlayEscapingChildPrivate)); gobject_class->get_property = ephy_overlay_escaping_child_get_property; gobject_class->set_property = ephy_overlay_escaping_child_set_property; widget_class->parent_set = ephy_overlay_escaping_child_parent_set; widget_class->motion_notify_event = ephy_overlay_escaping_child_motion_notify_event; widget_class->realize = ephy_overlay_escaping_child_realize; g_object_class_install_property (gobject_class, PROP_ESCAPING_DISTANCE, g_param_spec_uint ("escaping-distance", "Escaping distance", "Maximum distance between the mouse pointer and the widget", 0, G_MAXUINT, EPHY_OVERLAY_ESCAPING_CHILD_DEFAULT_DISTANCE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static void ephy_overlay_escaping_child_init (EphyOverlayEscapingChild *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EPHY_TYPE_OVERLAY_ESCAPING_CHILD, EphyOverlayEscapingChildPrivate); } /** * ephy_overlay_escaping_child_new: * @widget: the wrapped #GtkWidget * @escaping_distance: the distance from which the widget escapes the mouse * pointer * * Creates a new #EphyOverlayEscapingChild object wrapping the provided * widget. The widget will stay at a minimal distance of @escaping_distance pixel * from the mouse pointer. * * Returns: a new #EphyOverlayEscapingChild object */ EphyOverlayEscapingChild * ephy_overlay_escaping_child_new_with_distance (GtkWidget *widget, guint escaping_distance) { return g_object_new (EPHY_TYPE_OVERLAY_ESCAPING_CHILD, "widget", widget, "escaping-distance", escaping_distance, NULL); } /** * ephy_overlay_escaping_child_new: * @widget: the wrapped #GtkWidget * * Creates a new #EphyOverlayEscapingChild object wrapping the provided * widget. The widget will stay at a minimal distance of 20 pixels from * the mouse pointer. * * Returns: a new #EphyOverlayEscapingChild object */ EphyOverlayEscapingChild * ephy_overlay_escaping_child_new (GtkWidget *widget) { return g_object_new (EPHY_TYPE_OVERLAY_ESCAPING_CHILD, "widget", widget, NULL); }