/* * gedit-overlay.c * This file is part of gedit * * Copyright (C) 2011 - Ignacio Casal Quinteiro * * Based on Mike Krüger work. * * gedit 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. * * gedit 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 "gedit-overlay.h" #include "gedit-overlay-child.h" #define GEDIT_OVERLAY_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_OVERLAY, GeditOverlayPrivate)) typedef struct { GtkWidget *child; GtkWidget *original; } ChildContainer; struct _GeditOverlayPrivate { GtkWidget *main_widget; GtkWidget *relative_widget; GSList *children; }; enum { PROP_0, PROP_MAIN_WIDGET, PROP_RELATIVE_WIDGET }; G_DEFINE_TYPE (GeditOverlay, gedit_overlay, GTK_TYPE_CONTAINER) static ChildContainer * child_container_new (GtkWidget *child, GtkWidget *original) { ChildContainer *ret; ret = g_slice_new (ChildContainer); ret->child = child; ret->original = original; return ret; } static void child_container_free (ChildContainer *container) { g_slice_free (ChildContainer, container); } static GtkWidget * child_container_get_child (ChildContainer *container) { GtkWidget *child; if (container->child != NULL) { child = container->child; } else { child = container->original; } return child; } static void add_toplevel_widget (GeditOverlay *overlay, GtkWidget *child, GtkWidget *original) { ChildContainer *container; if (child != NULL) { gtk_widget_set_parent (child, GTK_WIDGET (overlay)); } else { gtk_widget_set_parent (original, GTK_WIDGET (overlay)); } container = child_container_new (child, original); overlay->priv->children = g_slist_append (overlay->priv->children, container); } static void gedit_overlay_dispose (GObject *object) { G_OBJECT_CLASS (gedit_overlay_parent_class)->dispose (object); } static void gedit_overlay_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GeditOverlay *overlay = GEDIT_OVERLAY (object); GeditOverlayPrivate *priv = overlay->priv; switch (prop_id) { case PROP_MAIN_WIDGET: g_value_set_object (value, priv->main_widget); break; case PROP_RELATIVE_WIDGET: g_value_set_object (value, priv->relative_widget); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GtkWidget * wrap_child_if_needed (GtkWidget *widget) { GtkWidget *child; if (GEDIT_IS_OVERLAY_CHILD (widget)) { return widget; } child = GTK_WIDGET (gedit_overlay_child_new (widget)); gtk_widget_show (child); g_signal_connect_swapped (widget, "destroy", G_CALLBACK (gtk_widget_destroy), child); return child; } static void gedit_overlay_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GeditOverlay *overlay = GEDIT_OVERLAY (object); GeditOverlayPrivate *priv = overlay->priv; switch (prop_id) { case PROP_MAIN_WIDGET: { priv->main_widget = g_value_get_object (value); add_toplevel_widget (overlay, NULL, priv->main_widget); break; } case PROP_RELATIVE_WIDGET: priv->relative_widget = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gedit_overlay_realize (GtkWidget *widget) { GtkAllocation allocation; GdkWindow *window; GdkWindowAttr attributes; gint attributes_mask; GtkStyleContext *context; gtk_widget_set_realized (widget, TRUE); gtk_widget_get_allocation (widget, &allocation); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = allocation.x; attributes.y = allocation.y; attributes.width = allocation.width; attributes.height = allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.event_mask = gtk_widget_get_events (widget); attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y; window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gtk_widget_set_window (widget, window); gdk_window_set_user_data (window, widget); context = gtk_widget_get_style_context (widget); gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL); gtk_style_context_set_background (context, window); } static void gedit_overlay_get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural) { GeditOverlayPrivate *priv = GEDIT_OVERLAY (widget)->priv; *minimum = 0; *natural = 0; if (priv->main_widget) { gtk_widget_get_preferred_width (priv->main_widget, minimum, natural); } } static void gedit_overlay_get_preferred_height (GtkWidget *widget, gint *minimum, gint *natural) { GeditOverlayPrivate *priv = GEDIT_OVERLAY (widget)->priv; *minimum = 0; *natural = 0; if (priv->main_widget) { gtk_widget_get_preferred_height (priv->main_widget, minimum, natural); } } static void gedit_overlay_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GeditOverlay *overlay = GEDIT_OVERLAY (widget); GeditOverlayPrivate *priv = overlay->priv; GtkAllocation main_alloc; GSList *l; GTK_WIDGET_CLASS (gedit_overlay_parent_class)->size_allocate (widget, allocation); /* main widget allocation */ main_alloc.x = 0; main_alloc.y = 0; main_alloc.width = allocation->width; main_alloc.height = allocation->height; gtk_widget_size_allocate (overlay->priv->main_widget, &main_alloc); /* if a relative widget exists place the floating widgets in relation to it */ if (priv->relative_widget) { gtk_widget_get_allocation (priv->relative_widget, &main_alloc); } for (l = priv->children; l != NULL; l = g_slist_next (l)) { ChildContainer *container = l->data; GtkWidget *child; GtkRequisition req; GtkAllocation alloc; guint offset; child = child_container_get_child (container); if (child == priv->main_widget) continue; gtk_widget_get_preferred_size (child, NULL, &req); offset = gedit_overlay_child_get_offset (GEDIT_OVERLAY_CHILD (child)); /* FIXME: Add all the positions here */ switch (gedit_overlay_child_get_position (GEDIT_OVERLAY_CHILD (child))) { /* The gravity is treated as position and not as a gravity */ case GEDIT_OVERLAY_CHILD_POSITION_NORTH_EAST: alloc.x = MAX (main_alloc.x, main_alloc.width - req.width - (gint) offset); alloc.y = 0; break; case GEDIT_OVERLAY_CHILD_POSITION_NORTH_WEST: alloc.x = offset; alloc.y = 0; break; case GEDIT_OVERLAY_CHILD_POSITION_SOUTH_WEST: alloc.x = offset; alloc.y = MAX (main_alloc.y, main_alloc.height - req.height); break; case GEDIT_OVERLAY_CHILD_POSITION_SOUTH_EAST: alloc.x = MAX (main_alloc.x, main_alloc.width - req.width - (gint) offset); alloc.y = MAX (main_alloc.y, main_alloc.height - req.height); break; default: alloc.x = 0; alloc.y = 0; } if (main_alloc.width < req.width || main_alloc.height < req.height) { GdkWindow *child_window = gtk_widget_get_window (child); if (child_window) gdk_window_move_resize (child_window, alloc.x, alloc.y, MIN (main_alloc.width, req.width), MIN (main_alloc.height, req.height)); } alloc.width = req.width; alloc.height = req.height; gtk_widget_size_allocate (child, &alloc); } } static GeditOverlayChild * get_overlay_child (GeditOverlay *overlay, GtkWidget *widget) { GSList *l; for (l = overlay->priv->children; l != NULL; l = g_slist_next (l)) { ChildContainer *container = l->data; if (container->original == widget && GEDIT_IS_OVERLAY_CHILD (container->child)) { return GEDIT_OVERLAY_CHILD (container->child); } } return NULL; } static void overlay_add (GtkContainer *overlay, GtkWidget *widget) { GeditOverlayChild *child; /* check that the widget is not added yet */ child = get_overlay_child (GEDIT_OVERLAY (overlay), widget); if (child == NULL) { add_toplevel_widget (GEDIT_OVERLAY (overlay), wrap_child_if_needed (widget), widget); } } static void gedit_overlay_remove (GtkContainer *overlay, GtkWidget *widget) { GeditOverlayPrivate *priv = GEDIT_OVERLAY (overlay)->priv; GSList *l; for (l = priv->children; l != NULL; l = g_slist_next (l)) { ChildContainer *container = l->data; GtkWidget *original = container->original; if (original == widget) { gtk_widget_unparent (widget); if (container->child != NULL && original != container->child) { g_signal_handlers_disconnect_by_func (original, gtk_widget_destroy, container->child); gtk_widget_destroy (container->child); } child_container_free (container); priv->children = g_slist_delete_link (priv->children, l); break; } } } static void gedit_overlay_forall (GtkContainer *overlay, gboolean include_internals, GtkCallback callback, gpointer callback_data) { GeditOverlayPrivate *priv = GEDIT_OVERLAY (overlay)->priv; GSList *children; children = priv->children; while (children) { GtkWidget *child; ChildContainer *container = children->data; children = children->next; child = child_container_get_child (container); (* callback) (child, callback_data); } } static GType gedit_overlay_child_type (GtkContainer *overlay) { return GTK_TYPE_WIDGET; } static void gedit_overlay_class_init (GeditOverlayClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->dispose = gedit_overlay_dispose; object_class->get_property = gedit_overlay_get_property; object_class->set_property = gedit_overlay_set_property; widget_class->realize = gedit_overlay_realize; widget_class->get_preferred_width = gedit_overlay_get_preferred_width; widget_class->get_preferred_height = gedit_overlay_get_preferred_height; widget_class->size_allocate = gedit_overlay_size_allocate; container_class->add = overlay_add; container_class->remove = gedit_overlay_remove; container_class->forall = gedit_overlay_forall; container_class->child_type = gedit_overlay_child_type; g_object_class_install_property (object_class, PROP_MAIN_WIDGET, g_param_spec_object ("main-widget", "Main Widget", "The Main Widget", GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (object_class, PROP_RELATIVE_WIDGET, g_param_spec_object ("relative-widget", "Relative Widget", "Widget on which the floating widgets are placed", GTK_TYPE_WIDGET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_type_class_add_private (object_class, sizeof (GeditOverlayPrivate)); } static void gedit_overlay_init (GeditOverlay *overlay) { overlay->priv = GEDIT_OVERLAY_GET_PRIVATE (overlay); gtk_widget_set_app_paintable (GTK_WIDGET (overlay), TRUE); } /** * gedit_overlay_new: * @main_widget: a #GtkWidget * @relative_widget: (allow-none): a #Gtkwidget * * Creates a new #GeditOverlay. If @relative_widget is not %NULL the floating * widgets will be placed in relation to it, if not @main_widget will be use * for this purpose. * * Returns: a new #GeditOverlay object. */ GtkWidget * gedit_overlay_new (GtkWidget *main_widget, GtkWidget *relative_widget) { g_return_val_if_fail (GTK_IS_WIDGET (main_widget), NULL); return GTK_WIDGET (g_object_new (GEDIT_TYPE_OVERLAY, "main-widget", main_widget, "relative-widget", relative_widget, NULL)); } /** * gedit_overlay_add: * @overlay: a #GeditOverlay * @widget: a #GtkWidget to be added to the container * @position: a #GeditOverlayChildPosition * @offset: offset for @widget * * Adds @widget to @overlay in a specific position. */ void gedit_overlay_add (GeditOverlay *overlay, GtkWidget *widget, GeditOverlayChildPosition position, guint offset) { GeditOverlayChild *child; g_return_if_fail (GEDIT_IS_OVERLAY (overlay)); g_return_if_fail (GTK_IS_WIDGET (widget)); gtk_container_add (GTK_CONTAINER (overlay), widget); /* NOTE: can we improve this without exposing overlay child? */ child = get_overlay_child (overlay, widget); g_assert (child != NULL); gedit_overlay_child_set_position (child, position); gedit_overlay_child_set_offset (child, offset); }