/* * glade-design-layout.c * * Copyright (C) 2006-2007 Vincent Geddes * 2011-2013 Juan Pablo Ugarte * * Authors: * Vincent Geddes * Juan Pablo Ugarte * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "config.h" #include "glade.h" #include "glade-design-layout.h" #include "glade-design-private.h" #include "glade-accumulators.h" #include "glade-marshallers.h" #include #include #define GLADE_DESIGN_LAYOUT_PRIVATE(object) (((GladeDesignLayout*)object)->priv) #define OUTLINE_WIDTH 4 #define PADDING 12 #define MARGIN_STEP 6 typedef enum { ACTIVITY_NONE, ACTIVITY_RESIZE_WIDTH, ACTIVITY_RESIZE_HEIGHT, ACTIVITY_RESIZE_WIDTH_AND_HEIGHT, ACTIVITY_ALIGNMENTS, ACTIVITY_MARGINS, ACTIVITY_MARGINS_VERTICAL, /* These activities are only used to set the cursor */ ACTIVITY_MARGINS_HORIZONTAL, ACTIVITY_MARGINS_TOP_LEFT, ACTIVITY_MARGINS_TOP_RIGHT, ACTIVITY_MARGINS_BOTTOM_LEFT, ACTIVITY_MARGINS_BOTTOM_RIGHT, N_ACTIVITY } Activity; typedef enum { MARGIN_TOP = 1 << 0, MARGIN_BOTTOM = 1 << 1, MARGIN_LEFT = 1 << 2, MARGIN_RIGHT = 1 << 3 } Margins; struct _GladeDesignLayoutPrivate { GladeWidget *gchild; GdkWindow *window, *offscreen_window; gint child_offset; GdkRectangle east, south, south_east; GdkCursor *cursors[N_ACTIVITY]; GdkRectangle child_rect; PangoLayout *widget_name; gint layout_width; GtkStyleContext *default_context; /* Colors */ GdkRGBA fg_color; GdkRGBA frame_color[2]; GdkRGBA frame_color_active[2]; /* Margin edit mode */ GtkWidget *selection; gint top, bottom, left, right; gint m_dy, m_dx; gint max_width, max_height; Margins margin; GtkAlign valign, halign; Margins node_over; /* state machine */ Activity activity; /* the current activity */ gint dx; /* child.width - event.pointer.x */ gint dy; /* child.height - event.pointer.y */ gint new_width; /* user's new requested width */ gint new_height; /* user's new requested height */ /* Drag & Drop */ GtkWidget *drag_source; gint drag_x, drag_y; GladeWidget *drag_dest; /* Properties */ GladeDesignView *view; GladeProject *project; }; enum { PROP_0, PROP_DESIGN_VIEW }; G_DEFINE_TYPE_WITH_PRIVATE (GladeDesignLayout, glade_design_layout, GTK_TYPE_BIN) #define RECTANGLE_POINT_IN(rect,x,y) (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y && y <= (rect.y + rect.height)) static inline gint get_margin_left (GtkWidget *widget) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS return gtk_widget_get_margin_left (widget); G_GNUC_END_IGNORE_DEPRECATIONS } static inline gint get_margin_right (GtkWidget *widget) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS return gtk_widget_get_margin_right (widget); G_GNUC_END_IGNORE_DEPRECATIONS } static inline gint get_margin_top (GtkWidget *widget) { return gtk_widget_get_margin_top (widget); } static inline gint get_margin_bottom (GtkWidget *widget) { return gtk_widget_get_margin_bottom (widget); } static inline void get_margins (GtkWidget *widget, gint *l, gint *r, gint *t, gint *b) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS *l = gtk_widget_get_margin_left (widget); *r = gtk_widget_get_margin_right (widget); G_GNUC_END_IGNORE_DEPRECATIONS *t = gtk_widget_get_margin_top (widget); *b = gtk_widget_get_margin_bottom (widget); } static Margins gdl_get_margins_from_pointer (GladeDesignLayout *layout, GtkWidget *widget, gint x, gint y) { GladeDesignLayoutPrivate *priv = GLADE_DESIGN_LAYOUT_PRIVATE (layout); gint width, height, xx, yy, top, bottom, left, right; GdkRectangle rec, child_rec; Margins margin = 0; width = gtk_widget_get_allocated_width (widget); height = gtk_widget_get_allocated_height (widget); gtk_widget_translate_coordinates (widget, GTK_WIDGET (layout), 0, 0, &xx, &yy); get_margins (widget, &left, &right, &top, &bottom); rec.x = xx - left - OUTLINE_WIDTH; rec.y = yy - top - OUTLINE_WIDTH; rec.width = width + left + right + (OUTLINE_WIDTH * 2); rec.height = height + top + bottom + (OUTLINE_WIDTH * 2); gtk_widget_get_allocation (gtk_bin_get_child (GTK_BIN (layout)), &child_rec); child_rec.x = (child_rec.x + priv->child_offset) - OUTLINE_WIDTH; child_rec.y = (child_rec.y + priv->child_offset) - OUTLINE_WIDTH; child_rec.width += OUTLINE_WIDTH * 2; child_rec.height += OUTLINE_WIDTH * 2; gdk_rectangle_intersect (&rec, &child_rec, &rec); if (RECTANGLE_POINT_IN (rec, x, y)) { if (y <= yy + OUTLINE_WIDTH) margin |= MARGIN_TOP; else if (y >= yy + height - OUTLINE_WIDTH) margin |= MARGIN_BOTTOM; if (x <= xx + OUTLINE_WIDTH) margin |= MARGIN_LEFT; else if (x >= xx + width - OUTLINE_WIDTH) margin |= MARGIN_RIGHT; } return margin; } static Activity gdl_get_activity_from_pointer (GladeDesignLayout *layout, gint x, gint y) { GladeDesignLayoutPrivate *priv = GLADE_DESIGN_LAYOUT_PRIVATE (layout); if (priv->selection) { priv->margin = gdl_get_margins_from_pointer (layout, priv->selection, x, y); if (priv->margin) { GladePointerMode mode = glade_project_get_pointer_mode (priv->project); return (mode == GLADE_POINTER_ALIGN_EDIT) ? ACTIVITY_ALIGNMENTS : ACTIVITY_MARGINS; } } if (RECTANGLE_POINT_IN (priv->south_east, x, y)) return ACTIVITY_RESIZE_WIDTH_AND_HEIGHT; if (RECTANGLE_POINT_IN (priv->east, x, y)) return ACTIVITY_RESIZE_WIDTH; if (RECTANGLE_POINT_IN (priv->south, x, y)) return ACTIVITY_RESIZE_HEIGHT; return ACTIVITY_NONE; } static inline void gdl_set_cursor (GladeDesignLayoutPrivate *priv, GdkCursor *cursor) { if (cursor != gdk_window_get_cursor (priv->window)) gdk_window_set_cursor (priv->window, cursor); } static Activity gdl_margin_get_activity (Margins margin) { if (margin & MARGIN_TOP) { if (margin & MARGIN_LEFT) return ACTIVITY_MARGINS_TOP_LEFT; else if (margin & MARGIN_RIGHT) return ACTIVITY_MARGINS_TOP_RIGHT; else return ACTIVITY_MARGINS_VERTICAL; } else if (margin & MARGIN_BOTTOM) { if (margin & MARGIN_LEFT) return ACTIVITY_MARGINS_BOTTOM_LEFT; else if (margin & MARGIN_RIGHT) return ACTIVITY_MARGINS_BOTTOM_RIGHT; else return ACTIVITY_MARGINS_VERTICAL; } else if (margin & MARGIN_LEFT || margin & MARGIN_RIGHT) return ACTIVITY_MARGINS_HORIZONTAL; return ACTIVITY_NONE; } static gboolean glade_design_layout_enter_leave_notify_event (GtkWidget *widget, GdkEventCrossing *ev) { GtkWidget *child; GladeDesignLayoutPrivate *priv; if ((child = gtk_bin_get_child (GTK_BIN (widget))) == NULL || ev->window != gtk_widget_get_window (widget)) return FALSE; priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); if (ev->type == GDK_ENTER_NOTIFY) { Activity activity = priv->activity; if (priv->activity == ACTIVITY_MARGINS) activity = gdl_margin_get_activity (priv->margin); gdl_set_cursor (priv, priv->cursors[activity]); } else if (priv->activity == ACTIVITY_NONE) gdl_set_cursor (priv, NULL); return FALSE; } static void gdl_update_max_margins (GladeDesignLayout *layout, GtkWidget *child, gint width, gint height) { GladeDesignLayoutPrivate *priv = layout->priv; gint parent_w, parent_h, layout_w, layout_h; gint top, bottom, left, right; GtkRequisition req; gtk_widget_get_preferred_size (child, &req, NULL); get_margins (priv->selection, &left, &right, &top, &bottom); priv->max_width = width - (req.width - left - right); parent_w = gtk_widget_get_allocated_width (GTK_WIDGET (priv->view)); layout_w = gtk_widget_get_allocated_width (GTK_WIDGET (layout)); if (parent_w > layout_w) priv->max_width += parent_w - layout_w - (PADDING - OUTLINE_WIDTH); priv->max_height = height - (req.height - top - bottom) ; parent_h = gtk_widget_get_allocated_height (GTK_WIDGET (priv->view)); layout_h = gtk_widget_get_allocated_height (GTK_WIDGET (layout)); if (parent_h > layout_h) priv->max_height += parent_h - layout_h - (PADDING - OUTLINE_WIDTH); } static void glade_design_layout_update_child (GladeDesignLayout *layout, GtkWidget *child, GtkAllocation *allocation) { GladeWidget *gchild; /* Update GladeWidget metadata */ gchild = glade_widget_get_from_gobject (child); g_object_set (gchild, "toplevel-width", allocation->width, "toplevel-height", allocation->height, NULL); if (layout->priv->selection) gdl_update_max_margins (layout, child, allocation->width, allocation->height); gtk_widget_queue_resize (GTK_WIDGET (layout)); } static inline void gdl_alignments_invalidate (GdkWindow *window, GtkWidget *parent, GtkWidget *selection, Margins nodes) { cairo_region_t *region = cairo_region_create (); cairo_rectangle_int_t rect = {0, 0, 16, 16}; gint x1, x2, x3, y1, y2, y3; GtkAllocation alloc; gint x, y, w, h; gtk_widget_get_allocation (selection, &alloc); w = alloc.width; h = alloc.height; gtk_widget_translate_coordinates (selection, parent, 0, 0, &x, &y); x1 = x - get_margin_left (selection); x2 = x + w/2; x3 = x + w + get_margin_right (selection); y1 = y - get_margin_top (selection); y2 = y + h/2; y3 = y + h + get_margin_bottom (selection); /* Only invalidate node area */ if (nodes & MARGIN_TOP) { rect.x = x2 - 5; rect.y = y1 - 10; cairo_region_union_rectangle (region, &rect); } if (nodes & MARGIN_BOTTOM) { rect.x = x2 - 8; rect.y = y3 - 13; cairo_region_union_rectangle (region, &rect); } rect.y = y2 - 10; if (nodes & MARGIN_LEFT) { rect.x = x1 - 8; cairo_region_union_rectangle (region, &rect); } if (nodes & MARGIN_RIGHT) { rect.x = x3 - 5; cairo_region_union_rectangle (region, &rect); } gdk_window_invalidate_region (window, region, FALSE); cairo_region_destroy (region); } static void gdl_update_cursor_for_position (GtkWidget *widget, gint x, gint y) { GladeDesignLayout *layout = GLADE_DESIGN_LAYOUT (widget); Activity activity = gdl_get_activity_from_pointer (layout, x, y); GladeDesignLayoutPrivate *priv = layout->priv; if (priv->node_over != priv->margin && (activity == ACTIVITY_ALIGNMENTS || glade_project_get_pointer_mode (priv->project) == GLADE_POINTER_ALIGN_EDIT)) { if (priv->selection) gdl_alignments_invalidate (priv->window, widget, priv->selection, priv->node_over | priv->margin); else gdk_window_invalidate_rect (priv->window, NULL, FALSE); priv->node_over = priv->margin; } if (activity == ACTIVITY_MARGINS) activity = gdl_margin_get_activity (priv->margin); /* Only set the cursor if changed */ gdl_set_cursor (priv, priv->cursors[activity]); } static gboolean glade_design_layout_motion_notify_event (GtkWidget *widget, GdkEventMotion *ev) { GladeDesignLayout *layout = GLADE_DESIGN_LAYOUT (widget); GladeDesignLayoutPrivate *priv = layout->priv; GtkAllocation allocation; GtkWidget *child; gint x, y; if ((child = gtk_bin_get_child (GTK_BIN (widget))) == NULL) return FALSE; x = ev->x; y = ev->y; if (ev->state & GDK_BUTTON1_MASK && priv->drag_source && gtk_drag_check_threshold (priv->drag_source, priv->drag_x, priv->drag_y, x, y)) { static GtkTargetList *target = NULL; if (target == NULL) target = gtk_target_list_new (_glade_dnd_get_target (), 1); gtk_drag_begin_with_coordinates (widget, target, 0, 1, (GdkEvent*)ev, x, y); return TRUE; } gtk_widget_get_allocation (child, &allocation); allocation.x += priv->child_offset; allocation.y += priv->child_offset; switch (priv->activity) { case ACTIVITY_RESIZE_WIDTH: allocation.width = MAX (0, x - priv->dx - PADDING - OUTLINE_WIDTH); glade_design_layout_update_child (layout, child, &allocation); break; case ACTIVITY_RESIZE_HEIGHT: allocation.height = MAX (0, y - priv->dy - PADDING - OUTLINE_WIDTH); glade_design_layout_update_child (layout, child, &allocation); break; case ACTIVITY_RESIZE_WIDTH_AND_HEIGHT: allocation.height = MAX (0, y - priv->dy - PADDING - OUTLINE_WIDTH); allocation.width = MAX (0, x - priv->dx - PADDING - OUTLINE_WIDTH); glade_design_layout_update_child (layout, child, &allocation); break; case ACTIVITY_MARGINS: { gboolean shift = ev->state & GDK_SHIFT_MASK; gboolean snap = ev->state & GDK_CONTROL_MASK; GtkWidget *selection = priv->selection; Margins margin = priv->margin; if (margin & MARGIN_TOP) { gint max_height = (shift) ? priv->max_height/2 : priv->max_height - get_margin_bottom (selection); gint val = MAX (0, MIN (priv->m_dy - y, max_height)); if (snap) val = (val/MARGIN_STEP)*MARGIN_STEP; gtk_widget_set_margin_top (selection, val); if (shift) gtk_widget_set_margin_bottom (selection, val); } else if (margin & MARGIN_BOTTOM) { gint max_height = (shift) ? priv->max_height/2 : priv->max_height - get_margin_top (selection); gint val = MAX (0, MIN (y - priv->m_dy, max_height)); if (snap) val = (val/MARGIN_STEP)*MARGIN_STEP; gtk_widget_set_margin_bottom (selection, val); if (shift) gtk_widget_set_margin_top (selection, val); } if (margin & MARGIN_LEFT) { gint max_width = (shift) ? priv->max_width/2 : priv->max_width - get_margin_right (selection); gint val = MAX (0, MIN (priv->m_dx - x, max_width)); if (snap) val = (val/MARGIN_STEP)*MARGIN_STEP; gtk_widget_set_margin_start (selection, val); if (shift) gtk_widget_set_margin_end (selection, val); } else if (margin & MARGIN_RIGHT) { gint max_width = (shift) ? priv->max_width/2 : priv->max_width - get_margin_left (selection); gint val = MAX (0, MIN (x - priv->m_dx, max_width)); if (snap) val = (val/MARGIN_STEP)*MARGIN_STEP; gtk_widget_set_margin_end (selection, val); if (shift) gtk_widget_set_margin_start (selection, val); } } break; default: gdl_update_cursor_for_position (widget, x, y); break; } return (priv->activity != ACTIVITY_NONE); } static gboolean glade_project_is_toplevel_active (GladeProject *project, GtkWidget *toplevel) { GList *l; for (l = glade_project_selection_get (project); l; l = g_list_next (l)) { if (GTK_IS_WIDGET (l->data) && gtk_widget_is_ancestor (l->data, toplevel)) return TRUE; } return FALSE; } static void gdl_edit_mode_set_selection (GladeDesignLayout *layout, GladePointerMode mode, GtkWidget *selection) { GladeDesignLayoutPrivate *priv = layout->priv; if ((selection && GTK_IS_WIDGET (selection) == FALSE) || gtk_bin_get_child (GTK_BIN (layout)) == selection) selection = NULL; if (priv->selection == selection) return; priv->selection = selection; if (selection) { if (mode == GLADE_POINTER_MARGIN_EDIT) { GtkWidget *child = gtk_bin_get_child (GTK_BIN (layout)); /* Save initital margins to know which one where edited */ get_margins (selection, &priv->left, &priv->right, &priv->top, &priv->bottom); gdl_update_max_margins (layout, child, gtk_widget_get_allocated_width (child), gtk_widget_get_allocated_height (child)); } else if (mode == GLADE_POINTER_ALIGN_EDIT) { priv->valign = gtk_widget_get_valign (selection); priv->halign = gtk_widget_get_halign (selection); } gdk_window_invalidate_rect (priv->window, NULL, FALSE); } else { gdl_set_cursor (priv, NULL); } glade_project_set_pointer_mode (priv->project, mode); } static gboolean glade_design_layout_button_press_event (GtkWidget *widget, GdkEventButton *ev) { GladeDesignLayoutPrivate *priv; GtkAllocation child_allocation; Activity activity; GtkWidget *child; gint x, y; if (ev->button != 1 || (ev->type != GDK_BUTTON_PRESS && ev->type != GDK_2BUTTON_PRESS) || (child = gtk_bin_get_child (GTK_BIN (widget))) == NULL) return FALSE; priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); x = ev->x; y = ev->y; priv->activity = activity = gdl_get_activity_from_pointer (GLADE_DESIGN_LAYOUT (widget), x, y); /* Check if we are in margin edit mode */ if (priv->selection) { GtkWidget *selection = priv->selection; switch (activity) { case ACTIVITY_NONE: gdl_edit_mode_set_selection (GLADE_DESIGN_LAYOUT (widget), GLADE_POINTER_SELECT, NULL); return FALSE; break; case ACTIVITY_ALIGNMENTS: { gboolean top, bottom, left, right; Margins node = priv->margin; GtkAlign valign, halign; GladeWidget *gwidget; valign = gtk_widget_get_valign (selection); halign = gtk_widget_get_halign (selection); if (valign == GTK_ALIGN_FILL) top = bottom = TRUE; else { top = (valign == GTK_ALIGN_START); bottom = (valign == GTK_ALIGN_END); } if (halign == GTK_ALIGN_FILL) left = right = TRUE; else { left = (halign == GTK_ALIGN_START); right = (halign == GTK_ALIGN_END); } if (node & MARGIN_TOP) valign = (top) ? ((bottom) ? GTK_ALIGN_END : GTK_ALIGN_CENTER) : ((bottom) ? GTK_ALIGN_FILL : GTK_ALIGN_START); else if (node & MARGIN_BOTTOM) valign = (bottom) ? ((top) ? GTK_ALIGN_START : GTK_ALIGN_CENTER) : ((top) ? GTK_ALIGN_FILL : GTK_ALIGN_END); if (node & MARGIN_LEFT) halign = (left) ? ((right) ? GTK_ALIGN_END : GTK_ALIGN_CENTER) : ((right) ? GTK_ALIGN_FILL : GTK_ALIGN_START); else if (node & MARGIN_RIGHT) halign = (right) ? ((left) ? GTK_ALIGN_START : GTK_ALIGN_CENTER) : ((left) ? GTK_ALIGN_FILL : GTK_ALIGN_END); if ((gwidget = glade_widget_get_from_gobject (selection))) { GladeProperty *property; glade_command_push_group (_("Editing alignments of %s"), glade_widget_get_name (gwidget)); if (gtk_widget_get_valign (selection) != valign) { if ((property = glade_widget_get_property (gwidget, "valign"))) glade_command_set_property (property, valign); } if (gtk_widget_get_halign (selection) != halign) { if ((property = glade_widget_get_property (gwidget, "halign"))) glade_command_set_property (property, halign); } glade_command_pop_group (); } } break; case ACTIVITY_MARGINS: priv->m_dx = x + ((priv->margin & MARGIN_LEFT) ? get_margin_left (selection) : get_margin_right (selection) * -1); priv->m_dy = y + ((priv->margin & MARGIN_TOP) ? get_margin_top (selection) : get_margin_bottom (selection) * -1); gdl_set_cursor (priv, priv->cursors[gdl_margin_get_activity (priv->margin)]); return TRUE; break; default: gdl_set_cursor (priv, priv->cursors[priv->activity]); break; } } gtk_widget_get_allocation (child, &child_allocation); priv->dx = x - (child_allocation.x + child_allocation.width + priv->child_offset); priv->dy = y - (child_allocation.y + child_allocation.height + priv->child_offset); if (activity != ACTIVITY_NONE && (!glade_project_is_toplevel_active (priv->project, child) || ev->type == GDK_2BUTTON_PRESS)) { glade_project_selection_set (priv->project, G_OBJECT (child), TRUE); } return (activity != ACTIVITY_NONE); } static gboolean glade_design_layout_button_release_event (GtkWidget *widget, GdkEventButton *ev) { GladeDesignLayoutPrivate *priv; GtkWidget *child; if ((child = gtk_bin_get_child (GTK_BIN (widget))) == NULL) return FALSE; priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); /* Check if margins where edited and execute corresponding glade command */ if (priv->selection && priv->activity == ACTIVITY_MARGINS) { GladeWidget *gwidget = glade_widget_get_from_gobject (priv->selection); gint top, bottom, left, right; GladeProperty *property; get_margins (priv->selection, &left, &right, &top, &bottom); glade_command_push_group (_("Editing margins of %s"), glade_widget_get_name (gwidget)); if (priv->top != top) { if ((property = glade_widget_get_property (gwidget, "margin-top"))) glade_command_set_property (property, top); } if (priv->bottom != bottom) { if ((property = glade_widget_get_property (gwidget, "margin-bottom"))) glade_command_set_property (property, bottom); } if (priv->left != left) { if ((property = glade_widget_get_property (gwidget, "margin-left"))) glade_command_set_property (property, left); } if (priv->right != right) { if ((property = glade_widget_get_property (gwidget, "margin-right"))) glade_command_set_property (property, right); } glade_command_pop_group (); } else if (priv->activity == ACTIVITY_ALIGNMENTS) { priv->node_over = 0; gdk_window_invalidate_rect (priv->window, NULL, FALSE); } priv->activity = ACTIVITY_NONE; gdl_update_cursor_for_position (widget, ev->x, ev->y); return TRUE; } static void glade_design_layout_get_preferred_height (GtkWidget *widget, gint *minimum, gint *natural) { GladeDesignLayoutPrivate *priv; GtkWidget *child; GladeWidget *gchild; gint child_height = 0; guint border_width = 0; priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); *minimum = 0; child = gtk_bin_get_child (GTK_BIN (widget)); if (child && gtk_widget_get_visible (child)) { GtkRequisition req; gint height; gchild = glade_widget_get_from_gobject (child); g_assert (gchild); gtk_widget_get_preferred_size (child, &req, NULL); g_object_get (gchild, "toplevel-height", &child_height, NULL); child_height = MAX (child_height, req.height); if (priv->widget_name) pango_layout_get_pixel_size (priv->widget_name, NULL, &height); else height = PADDING; *minimum = MAX (*minimum, PADDING + 2.5*OUTLINE_WIDTH + height + child_height); } border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); *minimum += border_width * 2; *natural = *minimum; } static void glade_design_layout_get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural) { GtkWidget *child; GladeWidget *gchild; gint child_width = 0; guint border_width = 0; *minimum = 0; child = gtk_bin_get_child (GTK_BIN (widget)); if (child && gtk_widget_get_visible (child)) { GtkRequisition req; gchild = glade_widget_get_from_gobject (child); g_assert (gchild); gtk_widget_get_preferred_size (child, &req, NULL); g_object_get (gchild, "toplevel-width", &child_width, NULL); child_width = MAX (child_width, req.width); *minimum = MAX (*minimum, 2*PADDING + 2*OUTLINE_WIDTH + child_width); } border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); *minimum += border_width * 2; *natural = *minimum; } static void glade_design_layout_get_preferred_width_for_height (GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width) { glade_design_layout_get_preferred_width (widget, minimum_width, natural_width); } static void glade_design_layout_get_preferred_height_for_width (GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height) { glade_design_layout_get_preferred_height (widget, minimum_height, natural_height); } static void update_rectangles (GladeDesignLayoutPrivate *priv, GtkAllocation *alloc) { GdkRectangle *rect = &priv->south_east; gint width, height; /* Update rectangles used to resize the children */ priv->east.x = alloc->width + priv->child_offset; priv->east.y = priv->child_offset; priv->east.height = alloc->height; priv->south.x = priv->child_offset; priv->south.y = alloc->height + priv->child_offset; priv->south.width = alloc->width; /* Update south east rectangle width */ if (priv->widget_name) pango_layout_get_pixel_size (priv->widget_name, &width, &height); else width = height = 0; priv->layout_width = width + (OUTLINE_WIDTH*2); width = MIN (alloc->width, width); rect->x = alloc->x + priv->child_offset + alloc->width - width - OUTLINE_WIDTH; rect->y = alloc->y + priv->child_offset + alloc->height; rect->width = width + (OUTLINE_WIDTH*2); rect->height = height + (OUTLINE_WIDTH*1.5); /* Update south rectangle width */ priv->south.width = rect->x - priv->south.x; } static void glade_design_layout_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkWidget *child; gtk_widget_set_allocation (widget, allocation); if (gtk_widget_get_realized (widget)) { gdk_window_move_resize (gtk_widget_get_window (widget), allocation->x, allocation->y, allocation->width, allocation->height); } child = gtk_bin_get_child (GTK_BIN (widget)); if (child && gtk_widget_get_visible (child)) { GladeDesignLayoutPrivate *priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); GtkAllocation alloc; gint height, offset; offset = gtk_container_get_border_width (GTK_CONTAINER (widget)) + PADDING + OUTLINE_WIDTH; priv->child_rect.x = priv->child_rect.y = priv->child_offset = offset; if (priv->widget_name) pango_layout_get_pixel_size (priv->widget_name, NULL, &height); else height = PADDING; alloc.x = alloc.y = 0; priv->child_rect.width = alloc.width = allocation->width - (offset * 2); priv->child_rect.height = alloc.height = allocation->height - (offset + OUTLINE_WIDTH * 1.5 + height); if (gtk_widget_get_realized (widget)) gdk_window_move_resize (priv->offscreen_window, 0, 0, alloc.width, alloc.height); gtk_widget_size_allocate (child, &alloc); update_rectangles (priv, &alloc); } } static inline void update_widget_name (GladeDesignLayout *layout, GladeWidget *gwidget) { GladeDesignLayoutPrivate *priv = layout->priv; if (priv->widget_name && gwidget) { if (glade_widget_has_name (gwidget)) { pango_layout_set_text (priv->widget_name, glade_widget_get_display_name (gwidget), -1); } else { GladeWidgetAdaptor *adaptor = glade_widget_get_adaptor (gwidget); pango_layout_set_text (priv->widget_name, glade_widget_adaptor_get_name (adaptor), -1); } gtk_widget_queue_resize (GTK_WIDGET (layout)); } } static void on_glade_widget_name_notify (GObject *gobject, GParamSpec *pspec, GladeDesignLayout *layout) { update_widget_name (layout, GLADE_WIDGET (gobject)); } static void glade_design_layout_add (GtkContainer *container, GtkWidget *widget) { GladeDesignLayout *layout = GLADE_DESIGN_LAYOUT (container); GladeDesignLayoutPrivate *priv = layout->priv; GtkStyleContext *context = gtk_widget_get_style_context (widget); priv->child_rect.width = 0; priv->child_rect.height = 0; gtk_style_context_add_class (context, "background"); gtk_widget_set_parent_window (widget, priv->offscreen_window); GTK_CONTAINER_CLASS (glade_design_layout_parent_class)->add (container, widget); if (!priv->gchild && (priv->gchild = glade_widget_get_from_gobject (G_OBJECT (widget)))) { update_widget_name (layout, priv->gchild); g_signal_connect (priv->gchild, "notify::name", G_CALLBACK (on_glade_widget_name_notify), layout); } gtk_widget_queue_draw (GTK_WIDGET (container)); } static void glade_design_layout_remove (GtkContainer *container, GtkWidget *widget) { GladeWidget *gchild; if ((gchild = glade_widget_get_from_gobject (G_OBJECT (widget)))) { GladeDesignLayoutPrivate *priv = GLADE_DESIGN_LAYOUT_PRIVATE (container); g_signal_handlers_disconnect_by_func (gchild, on_glade_widget_name_notify, GLADE_DESIGN_LAYOUT (container)); if (gchild == priv->gchild) priv->gchild = NULL; } GTK_CONTAINER_CLASS (glade_design_layout_parent_class)->remove (container, widget); gtk_widget_queue_draw (GTK_WIDGET (container)); } static gboolean glade_design_layout_damage (GtkWidget *widget, GdkEventExpose *event) { gdk_window_invalidate_rect (gtk_widget_get_window (widget), NULL, TRUE); return TRUE; } static inline void draw_frame (GtkWidget *widget, cairo_t *cr, gboolean selected, int x, int y, int w, int h) { GladeDesignLayoutPrivate *priv = ((GladeDesignLayout *)widget)->priv; GtkStyleContext *context = gtk_widget_get_style_context (widget); gtk_render_background (context, cr, x, y, w, h); gtk_render_frame (context, cr, x, y, w, h); if (priv->widget_name) { GdkRectangle *rect = &priv->south_east; gtk_style_context_save (context); gtk_style_context_add_class (context, "handle"); gtk_render_background (context, cr, rect->x, rect->y, rect->width, rect->height); gtk_render_frame (context, cr, rect->x, rect->y, rect->width, rect->height); gtk_render_layout (context, cr, rect->x + OUTLINE_WIDTH, rect->y + OUTLINE_WIDTH, priv->widget_name); gtk_style_context_restore (context); } } static void draw_margin_selection (cairo_t *cr, gint x1, gint x2, gint x3, gint x4, gint y1, gint y2, gint y3, gint y4, gdouble r, gdouble g, gdouble b, gint x5, gint y5) { cairo_pattern_t *gradient = cairo_pattern_create_linear (x1, y1, x5, y5); cairo_pattern_add_color_stop_rgba (gradient, 0, r+.24, g+.24, b+.24, .08); cairo_pattern_add_color_stop_rgba (gradient, 1, r, g, b, .16); cairo_set_source (cr, gradient); cairo_move_to (cr, x1, y1); cairo_line_to (cr, x2, y2); cairo_line_to (cr, x3, y3); cairo_line_to (cr, x4, y4); cairo_close_path (cr); cairo_fill (cr); cairo_pattern_destroy (gradient); } static inline void draw_selection (cairo_t *cr, GtkWidget *parent, GtkWidget *widget, GdkRGBA *color) { gint x, y, w, h, xw, yh, y_top, yh_bottom, x_left, xw_right; gint top, bottom, left, right; gdouble r, g, b; GtkAllocation alloc; GtkStyleContext *context; gtk_widget_get_allocation (widget, &alloc); if (alloc.x < 0 || alloc.y < 0) return; context = gtk_widget_get_style_context (parent); gtk_style_context_save (context); gtk_style_context_add_class (context, "selection"); r = color->red; g = color->green; b = color->blue; gtk_widget_translate_coordinates (widget, parent, 0, 0, &x, &y); w = alloc.width; h = alloc.height; xw = x + w; yh = y + h; get_margins (widget, &left, &right, &top, &bottom); y_top = y - top; yh_bottom = yh + bottom; x_left = x - left; xw_right = xw + right; /* Draw widget area overlay */ gtk_render_background (context, cr, x, y, w, h); /* Draw margins overlays */ if (top) draw_margin_selection (cr, x, xw, xw_right, x_left, y, y, y_top, y_top, r, g, b, x, y_top); if (bottom) draw_margin_selection (cr, x, xw, xw_right, x_left, yh, yh, yh_bottom, yh_bottom, r, g, b, x, yh_bottom); if (left) draw_margin_selection (cr, x, x, x_left, x_left, y, yh, yh_bottom, y_top, r, g, b, x_left, y); if (right) draw_margin_selection (cr, xw, xw, xw_right, xw_right, y, yh, yh_bottom, y_top, r, g, b, xw_right, y); /* Draw Selection box */ gtk_render_frame (context, cr, x - left, y - top, w + left + right, h + top + bottom); gtk_style_context_restore (context); } #define DIMENSION_OFFSET 9 #define DIMENSION_LINE_OFFSET 4 static void draw_hmark (cairo_t *cr, gdouble x, gdouble y) { cairo_move_to (cr, x + 2, y - 2); cairo_line_to (cr, x - 2, y + 2); } static void draw_vmark (cairo_t *cr, gdouble x, gdouble y) { cairo_move_to (cr, x - 2, y - 2); cairo_line_to (cr, x + 2, y + 2); } static void draw_vguide (cairo_t *cr, gdouble x, gdouble y, gint len) { cairo_move_to (cr, x, y - DIMENSION_LINE_OFFSET); cairo_line_to (cr, x, y + len); } static void draw_hguide (cairo_t *cr, gdouble x, gdouble y, gint len) { cairo_move_to (cr, x + DIMENSION_LINE_OFFSET, y); cairo_line_to (cr, x - len, y); } static void draw_pixel_value (cairo_t *cr, GdkRGBA *bg, GdkRGBA *fg, gdouble x, gdouble y, gboolean rotate, gboolean draw_border, gint val) { cairo_text_extents_t extents; gchar pixel_str[8]; g_snprintf (pixel_str, 8, "%d", val); cairo_text_extents (cr, pixel_str, &extents); cairo_save (cr); if (rotate) { cairo_translate (cr, x - 1.5, y + .5 + extents.width/2); cairo_rotate (cr, G_PI/-2); } else cairo_translate (cr, x - (extents.width+extents.x_bearing)/2, y - 2); cairo_move_to (cr, 0, 0); if (draw_border || extents.width + 4 >= val) { cairo_set_source_rgba (cr, bg->red, bg->green, bg->blue, .9); cairo_text_path (cr, pixel_str); cairo_set_line_width (cr, 3); cairo_stroke (cr); cairo_set_line_width (cr, 1); gdk_cairo_set_source_rgba (cr, fg); } cairo_show_text (cr, pixel_str); cairo_restore (cr); } static void draw_stroke_lines (cairo_t *cr, GdkRGBA *bg, GdkRGBA *fg, gboolean remark) { if (remark) { cairo_set_source_rgba (cr, bg->red, bg->green, bg->blue, .9); cairo_set_line_width (cr, 3); cairo_stroke_preserve (cr); cairo_set_line_width (cr, 1); } gdk_cairo_set_source_rgba (cr, fg); cairo_stroke (cr); } static void draw_dimensions (cairo_t *cr, GdkRGBA *bg, GdkRGBA *fg, gdouble x, gdouble y, gint w, gint h, gint top, gint bottom, gint left, gint right) { gboolean h_clutter, v_clutter; gdouble xx, yy; GdkRGBA color; w--; h--; xx = x + w + DIMENSION_OFFSET; yy = y - DIMENSION_OFFSET; h_clutter = top < DIMENSION_OFFSET*2; v_clutter = right < (DIMENSION_OFFSET + OUTLINE_WIDTH); /* Color half way betwen fg and bg */ color.red = ABS (bg->red - fg->red)/2; color.green = ABS (bg->green - fg->green)/2; color.blue = ABS (bg->blue - fg->blue)/2; color.alpha = fg->alpha; cairo_set_font_size (cr, 8.0); /* Draw dimension lines and guides */ if (left || right) { /* Draw horizontal lines */ cairo_move_to (cr, x - left - DIMENSION_LINE_OFFSET, yy); cairo_line_to (cr, x + w + right + DIMENSION_LINE_OFFSET, yy); if (top < DIMENSION_OFFSET) { draw_vguide (cr, x - left, yy, DIMENSION_OFFSET - top); draw_vguide (cr, x + w + right, yy, DIMENSION_OFFSET - top); } draw_vguide (cr, x, yy, DIMENSION_OFFSET); draw_vguide (cr, x + w, yy, DIMENSION_OFFSET); draw_stroke_lines (cr, bg, &color, top < DIMENSION_OFFSET+OUTLINE_WIDTH); /* Draw dimension line marks */ if (left) draw_hmark (cr, x - left, yy); draw_hmark (cr, x, yy); draw_hmark (cr, x + w, yy); if (right) draw_hmark (cr, x + w + right, yy); draw_stroke_lines (cr, bg, fg, top < DIMENSION_OFFSET+OUTLINE_WIDTH); } if (top || bottom) { /* Draw vertical lines */ cairo_move_to (cr, xx, y - top - DIMENSION_LINE_OFFSET); cairo_line_to (cr, xx, y + h + bottom + DIMENSION_LINE_OFFSET); if (right < DIMENSION_OFFSET) { draw_hguide (cr, xx, y - top, DIMENSION_OFFSET - right); draw_hguide (cr, xx, y + h + bottom, DIMENSION_OFFSET - right); } draw_hguide (cr, xx, y, DIMENSION_OFFSET); draw_hguide (cr, xx, y + h, DIMENSION_OFFSET); draw_stroke_lines (cr, bg, &color, v_clutter); /* Draw marks */ if (top) draw_vmark (cr, xx, y - top); draw_vmark (cr, xx, y); draw_vmark (cr, xx, y + h); if (bottom) draw_vmark (cr, xx, y + h + bottom); draw_stroke_lines (cr, bg, fg, v_clutter); } if (left || right) { /* Draw pixel values */ draw_pixel_value (cr, bg, fg, x + w/2, yy, FALSE, h_clutter, w+1); if (left) draw_pixel_value (cr,bg, fg, x - left/2, yy, FALSE, h_clutter, left); if (right) draw_pixel_value (cr,bg, fg, x + w + right/2, yy, FALSE, h_clutter, right); } if (top || bottom) { /* Draw pixel values */ draw_pixel_value (cr,bg, fg, xx, y + h/2, TRUE, v_clutter, h+1); if (top) draw_pixel_value (cr,bg, fg, xx, y - top/2, TRUE, v_clutter, top); if (bottom) draw_pixel_value (cr,bg, fg, xx, y + h + bottom/2, TRUE, v_clutter, bottom); } } static void draw_pushpin (cairo_t *cr, gdouble x, gdouble y, gint angle, GdkRGBA *outline, GdkRGBA *fill, GdkRGBA *outline2, GdkRGBA *fg, gboolean over, gboolean active) { cairo_save (cr); if (active) { outline = outline2; cairo_translate (cr, x + .5, y); cairo_rotate (cr, angle*(G_PI/180)); } else cairo_translate (cr, x + 1.5, y); /* Swap colors if mouse is over */ if (over) { GdkRGBA *tmp = outline; outline = fill; fill = tmp; } cairo_move_to (cr, 0, 0); _glade_design_layout_draw_pushpin (cr, (active) ? 2.5 : 4, outline, fill, (over) ? outline : fill, fg); cairo_restore (cr); } static inline void draw_selection_nodes (cairo_t *cr, GladeDesignLayoutPrivate *priv, GtkWidget *parent) { GladePointerMode mode = glade_project_get_pointer_mode (priv->project); Margins node = priv->node_over; GtkWidget *widget = priv->selection; gint top, bottom, left, right; gint x1, x2, x3, y1, y2, y3; GtkAllocation alloc; GdkRGBA *c1, *c2, *c3, *fg; gint x, y, w, h; gint offset = priv->child_offset; GdkRectangle clip = {0, }; gtk_widget_translate_coordinates (widget, parent, 0, 0, &x, &y); c1 = &priv->frame_color_active[0]; c2 = &priv->frame_color_active[1]; c3 = &priv->frame_color[0]; fg = &priv->fg_color; gtk_widget_get_allocation (widget, &alloc); w = alloc.width; h = alloc.height; get_margins (widget, &left, &right, &top, &bottom); /* Draw nodes */ x1 = x - left; x2 = x + w/2; x3 = x + w + right; y1 = y - top; y2 = y + h/2; y3 = y + h + bottom; /* Calculate clipping region to avoid drawing nodes outside of the toplevel * allocation. This could happen for example with GtkOverlay overlay children. */ if (offset > x1) { clip.x = offset; clip.width = 0; } else { clip.x = 0; clip.width = offset; } if (offset > y1) { clip.y = offset; clip.height = 0; } else { clip.y = 0; clip.height = offset; } clip.width += (priv->child_rect.width + offset < x3) ? priv->child_rect.width : gdk_window_get_width (priv->window); clip.height += (priv->child_rect.height + offset < y3) ? priv->child_rect.height : gdk_window_get_height (priv->window); cairo_save (cr); gdk_cairo_rectangle (cr, &clip); cairo_clip (cr); /* Draw nodes */ cairo_set_line_width (cr, OUTLINE_WIDTH); if (mode == GLADE_POINTER_MARGIN_EDIT) { _glade_design_layout_draw_node (cr, x2, y1, c1, c2); _glade_design_layout_draw_node (cr, x2, y3, c1, c2); _glade_design_layout_draw_node (cr, x1, y2, c1, c2); _glade_design_layout_draw_node (cr, x3, y2, c1, c2); /* Draw dimensions */ if (top || bottom || left || right) { cairo_set_line_width (cr, 1); draw_dimensions (cr, c2, fg, x+.5, y+.5, w, h, top, bottom, left, right); } } else if (mode == GLADE_POINTER_ALIGN_EDIT) { GtkAlign valign, halign; valign = gtk_widget_get_valign (widget); halign = gtk_widget_get_halign (widget); if (valign == GTK_ALIGN_FILL) { draw_pushpin (cr, x2, y1, 45, c3, c2, c1, fg, node & MARGIN_TOP, TRUE); draw_pushpin (cr, x2, y3-4, -45, c3, c2, c1, fg, node & MARGIN_BOTTOM, TRUE); } else { draw_pushpin (cr, x2, y1, 45, c3, c2, c1, fg, node & MARGIN_TOP, valign == GTK_ALIGN_START); draw_pushpin (cr, x2, y3-4, -45, c3, c2, c1, fg, node & MARGIN_BOTTOM, valign == GTK_ALIGN_END); } if (halign == GTK_ALIGN_FILL) { draw_pushpin (cr, x1, y2, -45, c3, c2, c1, fg, node & MARGIN_LEFT, TRUE); draw_pushpin (cr, x3, y2, 45, c3, c2, c1, fg, node & MARGIN_RIGHT, TRUE); } else { draw_pushpin (cr, x1, y2, -45, c3, c2, c1, fg, node & MARGIN_LEFT, halign == GTK_ALIGN_START); draw_pushpin (cr, x3, y2, 45, c3, c2, c1, fg, node & MARGIN_RIGHT, halign == GTK_ALIGN_END); } } cairo_restore (cr); } static inline void draw_drag_dest (GladeDesignLayout *layout, cairo_t *cr, GladeWidget *drag) { GladeDesignLayoutPrivate *priv = layout->priv; GObject *obj = glade_widget_get_object (drag); const double dashes = 3; GtkWidget *widget; gint x, y; if (!GTK_IS_WIDGET (obj)) return; widget = GTK_WIDGET (obj); gtk_widget_translate_coordinates (widget, GTK_WIDGET (layout), 0, 0, &x, &y); cairo_set_line_width (cr, 2); cairo_set_dash (cr, &dashes, 1, 0); gdk_cairo_set_source_rgba (cr, &priv->frame_color_active[0]); cairo_rectangle (cr, x+1, y+1, gtk_widget_get_allocated_width (widget)-2, gtk_widget_get_allocated_height(widget)-2); cairo_stroke (cr); } static gboolean glade_design_layout_draw (GtkWidget *widget, cairo_t *cr) { GladeDesignLayoutPrivate *priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); GdkWindow *window = gtk_widget_get_window (widget); if (gtk_cairo_should_draw_window (cr, window)) { GtkWidget *child; if ((child = gtk_bin_get_child (GTK_BIN (widget))) && gtk_widget_get_visible (child)) { gint border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); gboolean selected = FALSE; GList *l; /* draw frame */ draw_frame (widget, cr, selected, border_width + PADDING, border_width + PADDING, priv->child_rect.width + 2 * OUTLINE_WIDTH, priv->child_rect.height + 2 * OUTLINE_WIDTH); /* draw offscreen widgets */ gdk_cairo_set_source_window (cr, priv->offscreen_window, priv->child_offset, priv->child_offset); gdk_cairo_rectangle (cr, &priv->child_rect); cairo_fill (cr); /* Draw selection */ cairo_set_line_width (cr, OUTLINE_WIDTH/2); cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); for (l = glade_project_selection_get (priv->project); l; l = g_list_next (l)) { GtkWidget *selection = l->data; /* Dont draw selection on toplevels */ if (child != selection) { if (GTK_IS_WIDGET (selection) && gtk_widget_is_ancestor (selection, child)) { GdkRectangle *rect = &priv->child_rect; cairo_save (cr); cairo_rectangle (cr, rect->x - OUTLINE_WIDTH/2, rect->y - OUTLINE_WIDTH/2, rect->width + OUTLINE_WIDTH, rect->height + OUTLINE_WIDTH); cairo_clip (cr); draw_selection (cr, widget, selection, &priv->frame_color_active[0]); cairo_restore (cr); selected = TRUE; } } else selected = TRUE; } /* Draw selection nodes if we are in margins edit mode */ if (priv->selection && gtk_widget_is_ancestor (priv->selection, child)) draw_selection_nodes (cr, priv, widget); if (priv->drag_dest) draw_drag_dest (GLADE_DESIGN_LAYOUT (widget), cr, priv->drag_dest); } } else if (gtk_cairo_should_draw_window (cr, priv->offscreen_window)) { GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); gtk_render_background (priv->default_context, cr, 0, 0, gdk_window_get_width (priv->offscreen_window), gdk_window_get_height (priv->offscreen_window)); if (child) gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr); } return FALSE; } static inline void to_child (GladeDesignLayout *bin, double widget_x, double widget_y, double *x_out, double *y_out) { GladeDesignLayoutPrivate *priv = bin->priv; *x_out = widget_x - priv->child_offset; *y_out = widget_y - priv->child_offset; } static inline void to_parent (GladeDesignLayout *bin, double offscreen_x, double offscreen_y, double *x_out, double *y_out) { GladeDesignLayoutPrivate *priv = bin->priv; *x_out = offscreen_x + priv->child_offset; *y_out = offscreen_y + priv->child_offset; } static GdkWindow * pick_offscreen_child (GdkWindow *offscreen_window, double widget_x, double widget_y, GladeDesignLayout *bin) { GladeDesignLayoutPrivate *priv = bin->priv; GtkWidget *child = gtk_bin_get_child (GTK_BIN (bin)); if (child && gtk_widget_get_visible (child)) { GtkAllocation child_area; double x, y; to_child (bin, widget_x, widget_y, &x, &y); gtk_widget_get_allocation (child, &child_area); if (x >= 0 && x < child_area.width && y >= 0 && y < child_area.height) return (priv->selection) ? NULL : priv->offscreen_window; } return NULL; } static void offscreen_window_to_parent (GdkWindow *offscreen_window, double offscreen_x, double offscreen_y, double *parent_x, double *parent_y, GladeDesignLayout *bin) { to_parent (bin, offscreen_x, offscreen_y, parent_x, parent_y); } static void offscreen_window_from_parent (GdkWindow *window, double parent_x, double parent_y, double *offscreen_x, double *offscreen_y, GladeDesignLayout *bin) { to_child (bin, parent_x, parent_y, offscreen_x, offscreen_y); } static void glade_design_layout_realize (GtkWidget *widget) { GladeDesignLayoutPrivate *priv; GdkWindowAttr attributes; GtkWidget *child; gint attributes_mask, border_width; GtkAllocation allocation; GdkDisplay *display; priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); gtk_widget_set_realized (widget, TRUE); gtk_widget_get_allocation (widget, &allocation); border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); attributes.x = allocation.x + border_width; attributes.y = allocation.y + border_width; attributes.width = allocation.width - 2 * border_width; attributes.height = allocation.height - 2 * border_width; attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK; attributes.visual = gtk_widget_get_visual (widget); attributes.wclass = GDK_INPUT_OUTPUT; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL; priv->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gtk_widget_set_window (widget, priv->window); gdk_window_set_user_data (priv->window, widget); g_signal_connect (priv->window, "pick-embedded-child", G_CALLBACK (pick_offscreen_child), widget); /* Offscreen window */ child = gtk_bin_get_child (GTK_BIN (widget)); attributes.window_type = GDK_WINDOW_OFFSCREEN; attributes.x = attributes.y = 0; if (child && gtk_widget_get_visible (child)) { GtkAllocation alloc; gtk_widget_get_allocation (child, &alloc); attributes.width = alloc.width; attributes.height = alloc.height; } else attributes.width = attributes.height = 0; priv->offscreen_window = gdk_window_new (NULL, &attributes, attributes_mask); gdk_window_set_user_data (priv->offscreen_window, widget); if (child) gtk_widget_set_parent_window (child, priv->offscreen_window); gdk_offscreen_window_set_embedder (priv->offscreen_window, priv->window); g_signal_connect (priv->offscreen_window, "to-embedder", G_CALLBACK (offscreen_window_to_parent), widget); g_signal_connect (priv->offscreen_window, "from-embedder", G_CALLBACK (offscreen_window_from_parent), widget); gdk_window_show (priv->offscreen_window); gdk_window_set_cursor (priv->window, NULL); gdk_window_set_cursor (priv->offscreen_window, NULL); /* Allocate cursors */ display = gtk_widget_get_display (widget); priv->cursors[ACTIVITY_RESIZE_HEIGHT] = gdk_cursor_new_for_display (display, GDK_BOTTOM_SIDE); priv->cursors[ACTIVITY_RESIZE_WIDTH] = gdk_cursor_new_for_display (display, GDK_RIGHT_SIDE); priv->cursors[ACTIVITY_RESIZE_WIDTH_AND_HEIGHT] = gdk_cursor_new_for_display (display, GDK_BOTTOM_RIGHT_CORNER); priv->cursors[ACTIVITY_MARGINS_VERTICAL] = gdk_cursor_new_for_display (display, GDK_SB_V_DOUBLE_ARROW); priv->cursors[ACTIVITY_MARGINS_HORIZONTAL] = gdk_cursor_new_for_display (display, GDK_SB_H_DOUBLE_ARROW); priv->cursors[ACTIVITY_MARGINS_TOP_LEFT] = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_CORNER); priv->cursors[ACTIVITY_MARGINS_TOP_RIGHT] = gdk_cursor_new_for_display (display, GDK_TOP_RIGHT_CORNER); priv->cursors[ACTIVITY_MARGINS_BOTTOM_LEFT] = gdk_cursor_new_for_display (display, GDK_BOTTOM_LEFT_CORNER); priv->cursors[ACTIVITY_MARGINS_BOTTOM_RIGHT] = g_object_ref (priv->cursors[ACTIVITY_RESIZE_WIDTH_AND_HEIGHT]); priv->widget_name = pango_layout_new (gtk_widget_get_pango_context (widget)); if (child) update_widget_name (GLADE_DESIGN_LAYOUT (widget), glade_widget_get_from_gobject (child)); } static void glade_design_layout_unrealize (GtkWidget * widget) { GladeDesignLayoutPrivate *priv; gint i; priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); if (priv->offscreen_window) { gdk_window_set_user_data (priv->offscreen_window, NULL); gdk_window_destroy (priv->offscreen_window); priv->offscreen_window = NULL; } /* Free cursors */ for (i = 0; i < N_ACTIVITY; i++) { if (priv->cursors[i]) { g_object_unref (priv->cursors[i]); priv->cursors[i] = NULL; } } if (priv->widget_name) { g_object_unref (priv->widget_name); priv->widget_name = NULL; } GTK_WIDGET_CLASS (glade_design_layout_parent_class)->unrealize (widget); } static void glade_design_layout_style_updated (GtkWidget *widget) { GladeDesignLayoutPrivate *priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); _glade_design_layout_get_colors (&priv->frame_color[0], &priv->frame_color[1], &priv->frame_color_active[0], &priv->frame_color_active[1]); priv->fg_color = priv->frame_color[1]; } static void glade_design_layout_init (GladeDesignLayout *layout) { GtkWidgetPath *path = gtk_widget_path_new (); GladeDesignLayoutPrivate *priv; gint i; layout->priv = priv = glade_design_layout_get_instance_private (layout); priv->activity = ACTIVITY_NONE; for (i = 0; i < N_ACTIVITY; i++) priv->cursors[i] = NULL; priv->new_width = -1; priv->new_height = -1; priv->node_over = 0; priv->default_context = gtk_style_context_new (); gtk_widget_path_append_type (path, GTK_TYPE_WINDOW); gtk_style_context_set_path (priv->default_context, path); gtk_style_context_add_class (priv->default_context, GTK_STYLE_CLASS_BACKGROUND); /* setup static member of rectangles */ priv->east.width = PADDING + OUTLINE_WIDTH; priv->south.height = PADDING + OUTLINE_WIDTH; gtk_widget_set_has_window (GTK_WIDGET (layout), TRUE); gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (layout)), GTK_STYLE_CLASS_VIEW); gtk_widget_path_unref (path); } static void on_pointer_mode_notify (GladeProject *project, GParamSpec *pspec, GladeDesignLayout *layout) { GladeDesignLayoutPrivate *priv = layout->priv; GladePointerMode mode; GtkWidget *selection; g_return_if_fail (priv->window); mode = glade_project_get_pointer_mode (priv->project); if (mode == GLADE_POINTER_MARGIN_EDIT || mode == GLADE_POINTER_ALIGN_EDIT) { GList *l = glade_project_selection_get (project); selection = (l && g_list_next (l) == NULL && GTK_IS_WIDGET (l->data)) ? l->data : NULL; gdl_edit_mode_set_selection (layout, mode, NULL); } else selection = NULL; gdl_edit_mode_set_selection (layout, mode, selection); gdk_window_invalidate_rect (priv->window, NULL, FALSE); } static void glade_design_layout_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_DESIGN_VIEW: { GladeDesignLayoutPrivate *priv = GLADE_DESIGN_LAYOUT_PRIVATE (object); priv->view = GLADE_DESIGN_VIEW (g_value_get_object (value)); priv->project = glade_design_view_get_project (priv->view); g_signal_connect (priv->project, "notify::pointer-mode", G_CALLBACK (on_pointer_mode_notify), GLADE_DESIGN_LAYOUT (object)); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void glade_design_layout_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_DESIGN_VIEW: g_value_set_object (value, GLADE_DESIGN_LAYOUT_PRIVATE (object)->view); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void on_project_selection_changed (GladeProject *project, GladeDesignLayout *layout) { GladeDesignLayoutPrivate *priv = layout->priv; GladePointerMode mode = glade_project_get_pointer_mode (project); if (priv->selection) gdl_edit_mode_set_selection (layout, GLADE_POINTER_SELECT, NULL); else if (mode == GLADE_POINTER_ALIGN_EDIT || mode == GLADE_POINTER_MARGIN_EDIT) { GList *l = glade_project_selection_get (project); gdl_edit_mode_set_selection (layout, mode, (l) ? l->data : NULL); } } static GObject * glade_design_layout_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params) { GladeDesignLayoutPrivate *priv; GObject *object; object = G_OBJECT_CLASS (glade_design_layout_parent_class)->constructor (type, n_construct_params, construct_params); priv = GLADE_DESIGN_LAYOUT_PRIVATE (object); g_signal_connect (priv->project, "selection-changed", G_CALLBACK (on_project_selection_changed), GLADE_DESIGN_LAYOUT (object)); glade_design_layout_style_updated (GTK_WIDGET (object)); return object; } static void glade_design_layout_dispose (GObject *object) { GtkWidget *child; /* NOTE: Remove child from hierarchy! * This way we ensure gtk_widget_destroy() will not be called on it. * Glade API like glade_widget_get_children() depends on children hierarchy. */ if ((child = gtk_bin_get_child (GTK_BIN (object)))) gtk_container_remove (GTK_CONTAINER (object), child); G_OBJECT_CLASS (glade_design_layout_parent_class)->dispose (object); } static void glade_design_layout_finalize (GObject *object) { GladeDesignLayout *layout = GLADE_DESIGN_LAYOUT (object); GladeDesignLayoutPrivate *priv = layout->priv; g_clear_object (&priv->default_context); g_clear_object (&priv->drag_dest); g_signal_handlers_disconnect_by_func (priv->project, on_project_selection_changed, layout); g_signal_handlers_disconnect_by_func (priv->project, on_pointer_mode_notify, layout); G_OBJECT_CLASS (glade_design_layout_parent_class)->finalize (object); } static void glade_design_layout_drag_begin (GtkWidget *widget, GdkDragContext *context) { GladeDesignLayoutPrivate *priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); GladeWidgetAdaptor *adaptor; GladeWidget *gwidget; gchar *description; gwidget = glade_widget_get_from_gobject (priv->drag_source); adaptor = glade_widget_get_adaptor (gwidget); description = g_strdup_printf ("%s [%s]", glade_widget_adaptor_get_name (adaptor), glade_widget_get_name (gwidget)); _glade_dnd_set_icon_widget (context, glade_widget_adaptor_get_icon_name (adaptor), description); g_free (description); } static void glade_design_layout_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, guint info, guint time) { GladeDesignLayoutPrivate *priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); _glade_dnd_set_data (data, G_OBJECT (priv->drag_source)); } static void glade_design_layout_drag_end (GtkWidget *widget, GdkDragContext *context) { GladeDesignLayoutPrivate *priv = GLADE_DESIGN_LAYOUT_PRIVATE (widget); priv->drag_source = NULL; } static void glade_design_layout_class_init (GladeDesignLayoutClass * klass) { GObjectClass *object_class; GtkWidgetClass *widget_class; GtkContainerClass *container_class; GtkCssProvider *css_provider; object_class = G_OBJECT_CLASS (klass); widget_class = GTK_WIDGET_CLASS (klass); container_class = GTK_CONTAINER_CLASS (klass); object_class->constructor = glade_design_layout_constructor; object_class->dispose = glade_design_layout_dispose; object_class->finalize = glade_design_layout_finalize; object_class->set_property = glade_design_layout_set_property; object_class->get_property = glade_design_layout_get_property; container_class->add = glade_design_layout_add; container_class->remove = glade_design_layout_remove; widget_class->realize = glade_design_layout_realize; widget_class->unrealize = glade_design_layout_unrealize; widget_class->motion_notify_event = glade_design_layout_motion_notify_event; widget_class->enter_notify_event = glade_design_layout_enter_leave_notify_event; widget_class->leave_notify_event = glade_design_layout_enter_leave_notify_event; widget_class->button_press_event = glade_design_layout_button_press_event; widget_class->button_release_event = glade_design_layout_button_release_event; widget_class->draw = glade_design_layout_draw; widget_class->get_preferred_height = glade_design_layout_get_preferred_height; widget_class->get_preferred_width = glade_design_layout_get_preferred_width; widget_class->get_preferred_width_for_height = glade_design_layout_get_preferred_width_for_height; widget_class->get_preferred_height_for_width = glade_design_layout_get_preferred_height_for_width; widget_class->size_allocate = glade_design_layout_size_allocate; widget_class->style_updated = glade_design_layout_style_updated; widget_class->drag_begin = glade_design_layout_drag_begin; widget_class->drag_end = glade_design_layout_drag_end; widget_class->drag_data_get = glade_design_layout_drag_data_get; g_object_class_install_property (object_class, PROP_DESIGN_VIEW, g_param_spec_object ("design-view", _("Design View"), _("The GladeDesignView that contains this layout"), GLADE_TYPE_DESIGN_VIEW, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_signal_override_class_closure (g_signal_lookup ("damage-event", GTK_TYPE_WIDGET), GLADE_TYPE_DESIGN_LAYOUT, g_cclosure_new (G_CALLBACK (glade_design_layout_damage), NULL, NULL)); /* Setup Custom CSS */ gtk_widget_class_set_css_name (widget_class, "glade-design-layout"); css_provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (css_provider, "/org/gnome/gladeui/glade-design-layout.css"); gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), GTK_STYLE_PROVIDER (css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref (css_provider); } /* Internal API */ GtkWidget * _glade_design_layout_new (GladeDesignView *view) { return g_object_new (GLADE_TYPE_DESIGN_LAYOUT, "design-view", view, NULL); } void _glade_design_layout_draw_node (cairo_t *cr, gdouble x, gdouble y, GdkRGBA *fg, GdkRGBA *bg) { cairo_new_sub_path (cr); cairo_arc (cr, x, y, OUTLINE_WIDTH, 0, 2*G_PI); gdk_cairo_set_source_rgba (cr, bg); cairo_stroke_preserve (cr); gdk_cairo_set_source_rgba (cr, fg); cairo_fill (cr); } void _glade_design_layout_draw_pushpin (cairo_t *cr, gdouble needle_length, GdkRGBA *outline, GdkRGBA *fill, GdkRGBA *bg, GdkRGBA *fg) { cairo_save (cr); /* Draw needle */ cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); cairo_set_line_width (cr, 1); cairo_move_to (cr, 1, 2); cairo_line_to (cr, 1, 2+needle_length); cairo_set_source_rgba (cr, bg->red, bg->green, bg->blue, .9); cairo_stroke(cr); cairo_move_to (cr, 0, 2); cairo_line_to (cr, 0, 2+needle_length); gdk_cairo_set_source_rgba (cr, fg); cairo_stroke (cr); /* Draw top and bottom fat lines */ cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); cairo_move_to (cr, -4, 0); cairo_line_to (cr, 4, 0); cairo_move_to (cr, -2.5, -7); cairo_line_to (cr, 2.5, -7); gdk_cairo_set_source_rgba (cr, outline); cairo_set_line_width (cr, 4); cairo_stroke_preserve (cr); gdk_cairo_set_source_rgba (cr, fill); cairo_set_line_width (cr, 2); cairo_stroke (cr); /* Draw middle section */ cairo_move_to (cr, -2, -5); cairo_line_to (cr, 2, -5); cairo_line_to (cr, 3, -2); cairo_line_to (cr, -3, -2); cairo_close_path (cr); gdk_cairo_set_source_rgba (cr, outline); cairo_set_line_width (cr, 2); cairo_stroke_preserve (cr); gdk_cairo_set_source_rgba (cr, fill); cairo_fill (cr); /* Draw middle section shadow */ cairo_set_source_rgb (cr, fill->red-.16, fill->green-.16, fill->blue-.16); cairo_set_line_width (cr, 1); cairo_move_to (cr, 1, -5); cairo_line_to (cr, 1.5, -2); cairo_stroke (cr); cairo_restore (cr); } static inline void _glade_design_layout_coords_from_event (GdkWindow *parent, GdkEvent *event, gint *x, gint *y) { GdkWindow *child = event->any.window; gdouble xx, yy; if (!gdk_event_get_coords (event, &xx, &yy)) { *x = *y = 0; g_warning ("wrong event type %d", event->type); return; } while (child && parent != child) { gdk_window_coords_to_parent (child, xx, yy, &xx, &yy); child = gdk_window_get_parent (child); } *x = xx; *y = yy; } void _glade_design_layout_get_colors (GdkRGBA *c1, GdkRGBA *c2, GdkRGBA *c3, GdkRGBA *c4) { GtkStyleContext *context = gtk_style_context_new (); GtkWidgetPath *path = gtk_widget_path_new (); gfloat off; /* Fake style context */ gtk_widget_path_append_type (path, GTK_TYPE_WIDGET); gtk_style_context_set_path (context, path); gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW); /* Get colors */ gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL); gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), c1); gtk_style_context_get_color (context, gtk_style_context_get_state (context), c2); gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL | GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED); gtk_style_context_set_state (context, gtk_style_context_get_state (context)); gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), c3); gtk_style_context_get_color (context, gtk_style_context_get_state (context), c4); off = ((c1->red + c1->green + c1->blue)/3 < .5) ? .16 : -.16; c1->red += off; c1->green += off; c1->blue += off; /* Free resources */ gtk_widget_path_free (path); g_object_unref (context); } void _glade_design_layout_get_hot_point (GladeDesignLayout *layout, gint *x, gint *y) { GladeDesignLayoutPrivate *priv = layout->priv; if (x) *x = priv->drag_x; if (y) *y = priv->drag_y; } void _glade_design_layout_set_highlight (GladeDesignLayout *layout, GladeWidget *drag) { GladeDesignLayoutPrivate *priv = layout->priv; g_clear_object (&priv->drag_dest); if (drag) priv->drag_dest = g_object_ref (drag); gtk_widget_queue_draw (GTK_WIDGET (layout)); } typedef struct { GtkWidget *toplevel; gint x, y; GtkWidget *child; gint level; } FindInContainerData; static void find_first_child_inside_container (GtkWidget *widget, FindInContainerData *data) { gint x, y, w, h; if (data->child || !gtk_widget_get_mapped (widget)) return; gtk_widget_translate_coordinates (data->toplevel, widget, data->x, data->y, &x, &y); /* Margins are not part of the widget allocation */ w = gtk_widget_get_allocated_width (widget) + get_margin_right (widget); h = gtk_widget_get_allocated_height (widget) + get_margin_bottom (widget); if (x >= (0 - get_margin_left (widget)) && x < w && y >= (0 - get_margin_top (widget)) && y < h) { if (GLADE_IS_PLACEHOLDER (widget)) data->child = widget; else { GladeWidget *gwidget = glade_widget_get_from_gobject (widget); if (GTK_IS_CONTAINER (widget)) { if (gwidget) data->child = _glade_design_layout_get_child_at_position (widget, x, y); else gtk_container_forall (GTK_CONTAINER (widget), (GtkCallback) find_first_child_inside_container, data); } if (!data->child && gwidget) data->child = widget; } } } static void find_last_child_inside_container (GtkWidget *widget, FindInContainerData *data) { gint x, y, w, h; if ((data->child && data->level) || !gtk_widget_get_mapped (widget)) return; gtk_widget_translate_coordinates (data->toplevel, widget, data->x, data->y, &x, &y); /* Margins are not part of the widget allocation */ w = gtk_widget_get_allocated_width (widget) + get_margin_right (widget); h = gtk_widget_get_allocated_height (widget) + get_margin_bottom (widget); if (x >= (0 - get_margin_left (widget)) && x < w && y >= (0 - get_margin_top (widget)) && y < h) { GladeWidget *gwidget = glade_widget_get_from_gobject (widget); if (GTK_IS_CONTAINER (widget)) { if (!data->level) data->child = NULL; data->level++; if (gwidget) data->child = _glade_design_layout_get_child_at_position (widget, x, y); else gtk_container_forall (GTK_CONTAINER (widget), (GtkCallback) find_last_child_inside_container, data); data->level--; } if (data->level) { if (!data->child && (GLADE_IS_PLACEHOLDER (widget) || gwidget)) data->child = widget; } else if ((!data->child || data->toplevel == gtk_widget_get_parent (data->child)) && (GLADE_IS_PLACEHOLDER (widget) || gwidget)) data->child = widget; } } GtkWidget * _glade_design_layout_get_child_at_position (GtkWidget *widget, gint x, gint y) { gboolean find_last; if (!gtk_widget_get_mapped (widget)) return NULL; find_last = (GTK_IS_FIXED (widget) || GTK_IS_LAYOUT (widget) || GTK_IS_OVERLAY (widget)); if (x >= 0 && x <= gtk_widget_get_allocated_width (widget) && y >= 0 && y <= gtk_widget_get_allocated_height (widget)) { if (GTK_IS_CONTAINER (widget)) { FindInContainerData data = {widget, x, y, NULL, 0}; if (find_last) gtk_container_forall (GTK_CONTAINER (widget), (GtkCallback)find_last_child_inside_container, &data); else gtk_container_forall (GTK_CONTAINER (widget), (GtkCallback)find_first_child_inside_container, &data); return (data.child) ? data.child : widget; } else return widget; } return NULL; } static inline gboolean gdl_get_child_from_event (GladeDesignLayout *layout, GdkEvent *event, GladeWidget **gwidget, GtkWidget **placeholder, gint *x, gint *y) { GladeDesignLayoutPrivate *priv = layout->priv; GtkWidget *child; if (!priv->gchild) return TRUE; _glade_design_layout_coords_from_event (priv->window, event, x, y); child = GTK_WIDGET (glade_widget_get_object (priv->gchild)); if ((child = _glade_design_layout_get_child_at_position (child, *x - priv->child_offset, *y - priv->child_offset))) { if (GLADE_IS_PLACEHOLDER (child)) { *gwidget = glade_placeholder_get_parent (GLADE_PLACEHOLDER (child)); *placeholder = child; } else { *gwidget = glade_widget_get_from_gobject (child); *placeholder = NULL; } return FALSE; } return TRUE; } static inline void gdl_drag_source_check (GladeDesignLayout *layout, GladePointerMode mode, GdkEvent *event, GladeWidget *gwidget, gint x, gint y) { GladeDesignLayoutPrivate *priv = layout->priv; if (mode == GLADE_POINTER_SELECT && event->type == GDK_BUTTON_PRESS && event->button.button == 1) { GObject *source; if (gwidget && (source = glade_widget_get_object (gwidget)) && !(event->button.state & GDK_SHIFT_MASK) && _glade_drag_can_drag (GLADE_DRAG (gwidget))) { priv->drag_source = GTK_WIDGET (source); gtk_widget_translate_coordinates (GTK_WIDGET (layout), priv->drag_source, x, y, &priv->drag_x, &priv->drag_y); } else { priv->drag_source = NULL; } } else if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) { priv->drag_source = NULL; } } GladeWidget * _glade_design_layout_get_child (GladeDesignLayout *layout) { return layout->priv->gchild; } /* * _glade_design_layout_do_event: * @layout: A #GladeDesignLayout * @event: an event to process * * Process events to make widget selection work. This function should be called * before the child widget get the event. See gdk_event_handler_set() * * Returns: true if the event was handled. */ gboolean _glade_design_layout_do_event (GladeDesignLayout *layout, GdkEvent *event) { GladeDesignLayoutPrivate *priv = layout->priv; GtkWidget *widget = GTK_WIDGET (layout); GtkWidget *placeholder; GladeWidget *gwidget; GladePointerMode mode; gboolean retval; gint x, y; GList *l; if (gdl_get_child_from_event (layout, event, &gwidget, &placeholder, &x, &y)) return FALSE; mode = glade_project_get_pointer_mode (priv->project); /* Check if we want to enter in margin edit mode */ if ((event->type == GDK_BUTTON_PRESS || event->type == GDK_2BUTTON_PRESS) && !(event->button.state & GDK_SHIFT_MASK) && /* SHIFT is reserved for Drag&Resize */ mode != GLADE_POINTER_DRAG_RESIZE && /* This mode is for adaptors implementations */ (l = glade_project_selection_get (priv->project)) && g_list_next (l) == NULL && GTK_IS_WIDGET (l->data) && gtk_widget_is_ancestor (l->data, widget)) { if (gdl_get_margins_from_pointer (layout, l->data, x, y)) { if (event->button.button == 2) { glade_project_set_pointer_mode (priv->project, (mode == GLADE_POINTER_MARGIN_EDIT) ? GLADE_POINTER_ALIGN_EDIT : GLADE_POINTER_MARGIN_EDIT); return TRUE; } else if (event->button.button == 1 && priv->selection == NULL) { gdl_edit_mode_set_selection (layout, GLADE_POINTER_MARGIN_EDIT, l->data); return TRUE; } return FALSE; } } /* Check if this event could start a drag event and save the initial * coordinates for later. */ gdl_drag_source_check (layout, mode, event, gwidget, x, y); /* Try the placeholder first */ if (placeholder && gtk_widget_event (placeholder, event)) retval = TRUE; else if (gwidget) /* Then we try a GladeWidget */ retval = glade_widget_event (gwidget, event); else retval = FALSE; return retval; }