gtk2/gtk/gtkhandlebox.c
Havoc Pennington b67c5af55b Add invariant that a child is unmapped if parent is unmapped
Requires fixes to GtkContainer and GtkWindow to unmap their
children, rather than just withdrawing or hiding the container
window.

Requires fix to GtkHandleBox to chain up to GtkContainer unmap.

Historically we avoided these unmaps for efficiency reasons,
but these days it's a bigger problem that there's no way
for child widgets to know that one of their ancestors has
become unmapped.
2010-12-20 12:58:04 -05:00

1509 lines
43 KiB
C

/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
* Copyright (C) 1998 Elliot Lee
*
* 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 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include <stdlib.h>
#include "gtkhandlebox.h"
#include "gtkinvisible.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
#include "gtkwindow.h"
#include "gtkprivate.h"
#include "gtkintl.h"
struct _GtkHandleBoxPrivate
{
/* Properties */
GtkPositionType handle_position;
GtkPositionType snap_edge;
GtkShadowType shadow_type;
gboolean child_detached;
/* Properties */
GtkAllocation attach_allocation;
GtkAllocation float_allocation;
GdkDevice *grab_device;
GdkWindow *bin_window; /* parent window for children */
GdkWindow *float_window;
/* Variables used during a drag
*/
gint orig_x;
gint orig_y;
guint float_window_mapped : 1;
guint in_drag : 1;
guint shrink_on_detach : 1;
};
enum {
PROP_0,
PROP_SHADOW_TYPE,
PROP_HANDLE_POSITION,
PROP_SNAP_EDGE,
PROP_SNAP_EDGE_SET,
PROP_CHILD_DETACHED
};
#define DRAG_HANDLE_SIZE 10
#define CHILDLESS_SIZE 25
#define GHOST_HEIGHT 3
#define TOLERANCE 5
enum {
SIGNAL_CHILD_ATTACHED,
SIGNAL_CHILD_DETACHED,
SIGNAL_LAST
};
/* The algorithm for docking and redocking implemented here
* has a couple of nice properties:
*
* 1) During a single drag, docking always occurs at the
* the same cursor position. This means that the users
* motions are reversible, and that you won't
* undock/dock oscillations.
*
* 2) Docking generally occurs at user-visible features.
* The user, once they figure out to redock, will
* have useful information about doing it again in
* the future.
*
* Please try to preserve these properties if you
* change the algorithm. (And the current algorithm
* is far from ideal). Briefly, the current algorithm
* for deciding whether the handlebox is docked or not:
*
* 1) The decision is done by comparing two rectangles - the
* allocation if the widget at the start of the drag,
* and the boundary of hb->bin_window at the start of
* of the drag offset by the distance that the cursor
* has moved.
*
* 2) These rectangles must have one edge, the "snap_edge"
* of the handlebox, aligned within TOLERANCE.
*
* 3) On the other dimension, the extents of one rectangle
* must be contained in the extents of the other,
* extended by tolerance. That is, either we can have:
*
* <-TOLERANCE-|--------bin_window--------------|-TOLERANCE->
* <--------float_window-------------------->
*
* or we can have:
*
* <-TOLERANCE-|------float_window--------------|-TOLERANCE->
* <--------bin_window-------------------->
*/
static void gtk_handle_box_set_property (GObject *object,
guint param_id,
const GValue *value,
GParamSpec *pspec);
static void gtk_handle_box_get_property (GObject *object,
guint param_id,
GValue *value,
GParamSpec *pspec);
static void gtk_handle_box_map (GtkWidget *widget);
static void gtk_handle_box_unmap (GtkWidget *widget);
static void gtk_handle_box_realize (GtkWidget *widget);
static void gtk_handle_box_unrealize (GtkWidget *widget);
static void gtk_handle_box_style_set (GtkWidget *widget,
GtkStyle *previous_style);
static void gtk_handle_box_size_request (GtkWidget *widget,
GtkRequisition *requisition);
static void gtk_handle_box_get_preferred_width (GtkWidget *widget,
gint *minimum,
gint *natural);
static void gtk_handle_box_get_preferred_height (GtkWidget *widget,
gint *minimum,
gint *natural);
static void gtk_handle_box_size_allocate (GtkWidget *widget,
GtkAllocation *real_allocation);
static void gtk_handle_box_add (GtkContainer *container,
GtkWidget *widget);
static void gtk_handle_box_remove (GtkContainer *container,
GtkWidget *widget);
static gboolean gtk_handle_box_draw (GtkWidget *widget,
cairo_t *cr);
static gboolean gtk_handle_box_button_press (GtkWidget *widget,
GdkEventButton *event);
static gboolean gtk_handle_box_motion (GtkWidget *widget,
GdkEventMotion *event);
static gboolean gtk_handle_box_delete_event (GtkWidget *widget,
GdkEventAny *event);
static void gtk_handle_box_reattach (GtkHandleBox *hb);
static void gtk_handle_box_end_drag (GtkHandleBox *hb,
guint32 time);
static guint handle_box_signals[SIGNAL_LAST] = { 0 };
G_DEFINE_TYPE (GtkHandleBox, gtk_handle_box, GTK_TYPE_BIN)
static void
gtk_handle_box_class_init (GtkHandleBoxClass *class)
{
GObjectClass *gobject_class;
GtkWidgetClass *widget_class;
GtkContainerClass *container_class;
gobject_class = (GObjectClass *) class;
widget_class = (GtkWidgetClass *) class;
container_class = (GtkContainerClass *) class;
gobject_class->set_property = gtk_handle_box_set_property;
gobject_class->get_property = gtk_handle_box_get_property;
g_object_class_install_property (gobject_class,
PROP_SHADOW_TYPE,
g_param_spec_enum ("shadow-type",
P_("Shadow type"),
P_("Appearance of the shadow that surrounds the container"),
GTK_TYPE_SHADOW_TYPE,
GTK_SHADOW_OUT,
GTK_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_HANDLE_POSITION,
g_param_spec_enum ("handle-position",
P_("Handle position"),
P_("Position of the handle relative to the child widget"),
GTK_TYPE_POSITION_TYPE,
GTK_POS_LEFT,
GTK_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_SNAP_EDGE,
g_param_spec_enum ("snap-edge",
P_("Snap edge"),
P_("Side of the handlebox that's lined up with the docking point to dock the handlebox"),
GTK_TYPE_POSITION_TYPE,
GTK_POS_TOP,
GTK_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_SNAP_EDGE_SET,
g_param_spec_boolean ("snap-edge-set",
P_("Snap edge set"),
P_("Whether to use the value from the snap_edge property or a value derived from handle_position"),
FALSE,
GTK_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_CHILD_DETACHED,
g_param_spec_boolean ("child-detached",
P_("Child Detached"),
P_("A boolean value indicating whether the handlebox's child is attached or detached."),
FALSE,
GTK_PARAM_READABLE));
widget_class->map = gtk_handle_box_map;
widget_class->unmap = gtk_handle_box_unmap;
widget_class->realize = gtk_handle_box_realize;
widget_class->unrealize = gtk_handle_box_unrealize;
widget_class->style_set = gtk_handle_box_style_set;
widget_class->get_preferred_width = gtk_handle_box_get_preferred_width;
widget_class->get_preferred_height = gtk_handle_box_get_preferred_height;
widget_class->size_allocate = gtk_handle_box_size_allocate;
widget_class->draw = gtk_handle_box_draw;
widget_class->button_press_event = gtk_handle_box_button_press;
widget_class->delete_event = gtk_handle_box_delete_event;
container_class->add = gtk_handle_box_add;
container_class->remove = gtk_handle_box_remove;
class->child_attached = NULL;
class->child_detached = NULL;
handle_box_signals[SIGNAL_CHILD_ATTACHED] =
g_signal_new (I_("child-attached"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkHandleBoxClass, child_attached),
NULL, NULL,
_gtk_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
GTK_TYPE_WIDGET);
handle_box_signals[SIGNAL_CHILD_DETACHED] =
g_signal_new (I_("child-detached"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkHandleBoxClass, child_detached),
NULL, NULL,
_gtk_marshal_VOID__OBJECT,
G_TYPE_NONE, 1,
GTK_TYPE_WIDGET);
g_type_class_add_private (gobject_class, sizeof (GtkHandleBoxPrivate));
}
static void
gtk_handle_box_init (GtkHandleBox *handle_box)
{
GtkHandleBoxPrivate *priv;
handle_box->priv = G_TYPE_INSTANCE_GET_PRIVATE (handle_box,
GTK_TYPE_HANDLE_BOX,
GtkHandleBoxPrivate);
priv = handle_box->priv;
gtk_widget_set_has_window (GTK_WIDGET (handle_box), TRUE);
priv->bin_window = NULL;
priv->float_window = NULL;
priv->shadow_type = GTK_SHADOW_OUT;
priv->handle_position = GTK_POS_LEFT;
priv->float_window_mapped = FALSE;
priv->child_detached = FALSE;
priv->in_drag = FALSE;
priv->shrink_on_detach = TRUE;
priv->snap_edge = -1;
}
static void
gtk_handle_box_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkHandleBox *handle_box = GTK_HANDLE_BOX (object);
switch (prop_id)
{
case PROP_SHADOW_TYPE:
gtk_handle_box_set_shadow_type (handle_box, g_value_get_enum (value));
break;
case PROP_HANDLE_POSITION:
gtk_handle_box_set_handle_position (handle_box, g_value_get_enum (value));
break;
case PROP_SNAP_EDGE:
gtk_handle_box_set_snap_edge (handle_box, g_value_get_enum (value));
break;
case PROP_SNAP_EDGE_SET:
if (!g_value_get_boolean (value))
gtk_handle_box_set_snap_edge (handle_box, (GtkPositionType)-1);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_handle_box_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkHandleBox *handle_box = GTK_HANDLE_BOX (object);
GtkHandleBoxPrivate *priv = handle_box->priv;
switch (prop_id)
{
case PROP_SHADOW_TYPE:
g_value_set_enum (value, priv->shadow_type);
break;
case PROP_HANDLE_POSITION:
g_value_set_enum (value, priv->handle_position);
break;
case PROP_SNAP_EDGE:
g_value_set_enum (value,
(priv->snap_edge == -1 ?
GTK_POS_TOP : priv->snap_edge));
break;
case PROP_SNAP_EDGE_SET:
g_value_set_boolean (value, priv->snap_edge != -1);
break;
case PROP_CHILD_DETACHED:
g_value_set_boolean (value, priv->child_detached);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
GtkWidget*
gtk_handle_box_new (void)
{
return g_object_new (GTK_TYPE_HANDLE_BOX, NULL);
}
static void
gtk_handle_box_map (GtkWidget *widget)
{
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
GtkBin *bin = GTK_BIN (widget);
GtkWidget *child;
gtk_widget_set_mapped (widget, TRUE);
child = gtk_bin_get_child (bin);
if (child != NULL &&
gtk_widget_get_visible (child) &&
!gtk_widget_get_mapped (child))
gtk_widget_map (child);
if (priv->child_detached && !priv->float_window_mapped)
{
gdk_window_show (priv->float_window);
priv->float_window_mapped = TRUE;
}
gdk_window_show (priv->bin_window);
gdk_window_show (gtk_widget_get_window (widget));
}
static void
gtk_handle_box_unmap (GtkWidget *widget)
{
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
gtk_widget_set_mapped (widget, FALSE);
gdk_window_hide (gtk_widget_get_window (widget));
if (priv->float_window_mapped)
{
gdk_window_hide (priv->float_window);
priv->float_window_mapped = FALSE;
}
GTK_WIDGET_CLASS (gtk_handle_box_parent_class)->unmap (widget);
}
static void
gtk_handle_box_realize (GtkWidget *widget)
{
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
GtkAllocation allocation;
GtkRequisition requisition;
GtkStateType state;
GtkStyle *style;
GtkWidget *child;
GdkWindow *window;
GdkWindowAttr attributes;
gint attributes_mask;
gtk_widget_set_realized (widget, TRUE);
gtk_widget_get_allocation (widget, &allocation);
attributes.x = allocation.x;
attributes.y = allocation.y;
attributes.width = allocation.width;
attributes.height = allocation.height;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual (widget);
attributes.event_mask = (gtk_widget_get_events (widget)
| GDK_EXPOSURE_MASK);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
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);
attributes.x = 0;
attributes.y = 0;
attributes.width = allocation.width;
attributes.height = allocation.height;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.event_mask = (gtk_widget_get_events (widget) |
GDK_EXPOSURE_MASK |
GDK_BUTTON1_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
priv->bin_window = gdk_window_new (window,
&attributes, attributes_mask);
gdk_window_set_user_data (priv->bin_window, widget);
child = gtk_bin_get_child (GTK_BIN (hb));
if (child)
gtk_widget_set_parent_window (child, priv->bin_window);
gtk_widget_get_preferred_size (widget, &requisition, NULL);
attributes.x = 0;
attributes.y = 0;
attributes.width = requisition.width;
attributes.height = requisition.height;
attributes.window_type = GDK_WINDOW_TOPLEVEL;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual (widget);
attributes.event_mask = (gtk_widget_get_events (widget) |
GDK_KEY_PRESS_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK |
GDK_FOCUS_CHANGE_MASK |
GDK_STRUCTURE_MASK);
attributes.type_hint = GDK_WINDOW_TYPE_HINT_TOOLBAR;
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_TYPE_HINT;
priv->float_window = gdk_window_new (gtk_widget_get_root_window (widget),
&attributes, attributes_mask);
gdk_window_set_user_data (priv->float_window, widget);
gdk_window_set_decorations (priv->float_window, 0);
gdk_window_set_type_hint (priv->float_window, GDK_WINDOW_TYPE_HINT_TOOLBAR);
gtk_widget_style_attach (widget);
style = gtk_widget_get_style (widget);
state = gtk_widget_get_state (widget);
gtk_style_set_background (style, window, state);
gtk_style_set_background (style, priv->bin_window, state);
gtk_style_set_background (style, priv->float_window, state);
}
static void
gtk_handle_box_unrealize (GtkWidget *widget)
{
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
gdk_window_set_user_data (priv->bin_window, NULL);
gdk_window_destroy (priv->bin_window);
priv->bin_window = NULL;
gdk_window_set_user_data (priv->float_window, NULL);
gdk_window_destroy (priv->float_window);
priv->float_window = NULL;
GTK_WIDGET_CLASS (gtk_handle_box_parent_class)->unrealize (widget);
}
static void
gtk_handle_box_style_set (GtkWidget *widget,
GtkStyle *previous_style)
{
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
if (gtk_widget_get_realized (widget) &&
gtk_widget_get_has_window (widget))
{
GtkStateType state;
GtkStyle *style;
style = gtk_widget_get_style (widget);
state = gtk_widget_get_state (widget);
gtk_style_set_background (style, gtk_widget_get_window (widget), state);
gtk_style_set_background (style, priv->bin_window, state);
gtk_style_set_background (style, priv->float_window, state);
}
}
static int
effective_handle_position (GtkHandleBox *hb)
{
GtkHandleBoxPrivate *priv = hb->priv;
int handle_position;
if (gtk_widget_get_direction (GTK_WIDGET (hb)) == GTK_TEXT_DIR_LTR)
handle_position = priv->handle_position;
else
{
switch (priv->handle_position)
{
case GTK_POS_LEFT:
handle_position = GTK_POS_RIGHT;
break;
case GTK_POS_RIGHT:
handle_position = GTK_POS_LEFT;
break;
default:
handle_position = priv->handle_position;
break;
}
}
return handle_position;
}
static void
gtk_handle_box_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
GtkBin *bin = GTK_BIN (widget);
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
GtkRequisition child_requisition;
GtkWidget *child;
gint handle_position;
handle_position = effective_handle_position (hb);
if (handle_position == GTK_POS_LEFT ||
handle_position == GTK_POS_RIGHT)
{
requisition->width = DRAG_HANDLE_SIZE;
requisition->height = 0;
}
else
{
requisition->width = 0;
requisition->height = DRAG_HANDLE_SIZE;
}
child = gtk_bin_get_child (bin);
/* if our child is not visible, we still request its size, since we
* won't have any useful hint for our size otherwise.
*/
if (child)
{
gtk_widget_get_preferred_size (child, &child_requisition, NULL);
}
else
{
child_requisition.width = 0;
child_requisition.height = 0;
}
if (priv->child_detached)
{
/* FIXME: This doesn't work currently */
if (!priv->shrink_on_detach)
{
if (handle_position == GTK_POS_LEFT ||
handle_position == GTK_POS_RIGHT)
requisition->height += child_requisition.height;
else
requisition->width += child_requisition.width;
}
else
{
if (handle_position == GTK_POS_LEFT ||
handle_position == GTK_POS_RIGHT)
requisition->height += gtk_widget_get_style (widget)->ythickness;
else
requisition->width += gtk_widget_get_style (widget)->xthickness;
}
}
else
{
guint border_width;
border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
requisition->width += border_width * 2;
requisition->height += border_width * 2;
if (child)
{
requisition->width += child_requisition.width;
requisition->height += child_requisition.height;
}
else
{
requisition->width += CHILDLESS_SIZE;
requisition->height += CHILDLESS_SIZE;
}
}
}
static void
gtk_handle_box_get_preferred_width (GtkWidget *widget,
gint *minimum,
gint *natural)
{
GtkRequisition requisition;
gtk_handle_box_size_request (widget, &requisition);
*minimum = *natural = requisition.width;
}
static void
gtk_handle_box_get_preferred_height (GtkWidget *widget,
gint *minimum,
gint *natural)
{
GtkRequisition requisition;
gtk_handle_box_size_request (widget, &requisition);
*minimum = *natural = requisition.height;
}
static void
gtk_handle_box_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkBin *bin = GTK_BIN (widget);
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
GtkRequisition child_requisition;
GtkWidget *child;
gint handle_position;
handle_position = effective_handle_position (hb);
child = gtk_bin_get_child (bin);
if (child)
{
gtk_widget_get_preferred_size (child, &child_requisition, NULL);
}
else
{
child_requisition.width = 0;
child_requisition.height = 0;
}
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);
if (child != NULL && gtk_widget_get_visible (child))
{
GtkAllocation child_allocation;
guint border_width;
border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
child_allocation.x = border_width;
child_allocation.y = border_width;
if (handle_position == GTK_POS_LEFT)
child_allocation.x += DRAG_HANDLE_SIZE;
else if (handle_position == GTK_POS_TOP)
child_allocation.y += DRAG_HANDLE_SIZE;
if (priv->child_detached)
{
guint float_width;
guint float_height;
child_allocation.width = child_requisition.width;
child_allocation.height = child_requisition.height;
float_width = child_allocation.width + 2 * border_width;
float_height = child_allocation.height + 2 * border_width;
if (handle_position == GTK_POS_LEFT ||
handle_position == GTK_POS_RIGHT)
float_width += DRAG_HANDLE_SIZE;
else
float_height += DRAG_HANDLE_SIZE;
if (gtk_widget_get_realized (widget))
{
gdk_window_resize (priv->float_window,
float_width,
float_height);
gdk_window_move_resize (priv->bin_window,
0,
0,
float_width,
float_height);
}
}
else
{
child_allocation.width = MAX (1, (gint) allocation->width - 2 * border_width);
child_allocation.height = MAX (1, (gint) allocation->height - 2 * border_width);
if (handle_position == GTK_POS_LEFT ||
handle_position == GTK_POS_RIGHT)
child_allocation.width -= DRAG_HANDLE_SIZE;
else
child_allocation.height -= DRAG_HANDLE_SIZE;
if (gtk_widget_get_realized (widget))
gdk_window_move_resize (priv->bin_window,
0,
0,
allocation->width,
allocation->height);
}
gtk_widget_size_allocate (child, &child_allocation);
}
}
static void
gtk_handle_box_draw_ghost (GtkHandleBox *hb,
cairo_t *cr)
{
GtkWidget *widget = GTK_WIDGET (hb);
GtkStateType state;
GtkStyle *style;
GdkWindow *window;
guint x;
guint y;
guint width;
guint height;
gint allocation_width;
gint allocation_height;
gint handle_position;
handle_position = effective_handle_position (hb);
allocation_width = gtk_widget_get_allocated_width (widget);
allocation_height = gtk_widget_get_allocated_height (widget);
if (handle_position == GTK_POS_LEFT ||
handle_position == GTK_POS_RIGHT)
{
x = handle_position == GTK_POS_LEFT ? 0 : allocation_width - DRAG_HANDLE_SIZE;
y = 0;
width = DRAG_HANDLE_SIZE;
height = allocation_height;
}
else
{
x = 0;
y = handle_position == GTK_POS_TOP ? 0 : allocation_height - DRAG_HANDLE_SIZE;
width = allocation_width;
height = DRAG_HANDLE_SIZE;
}
style = gtk_widget_get_style (widget);
window = gtk_widget_get_window (widget);
state = gtk_widget_get_state (widget);
gtk_paint_shadow (style,
cr,
state,
GTK_SHADOW_ETCHED_IN,
widget, "handle",
x,
y,
width,
height);
if (handle_position == GTK_POS_LEFT ||
handle_position == GTK_POS_RIGHT)
gtk_paint_hline (style,
cr,
state,
widget, "handlebox",
handle_position == GTK_POS_LEFT ? DRAG_HANDLE_SIZE : 0,
handle_position == GTK_POS_LEFT ? allocation_width : allocation_width - DRAG_HANDLE_SIZE,
allocation_height / 2);
else
gtk_paint_vline (style,
cr,
state,
widget, "handlebox",
handle_position == GTK_POS_TOP ? DRAG_HANDLE_SIZE : 0,
handle_position == GTK_POS_TOP ? allocation_height : allocation_height - DRAG_HANDLE_SIZE,
allocation_width / 2);
}
void
gtk_handle_box_set_shadow_type (GtkHandleBox *handle_box,
GtkShadowType type)
{
GtkHandleBoxPrivate *priv;
g_return_if_fail (GTK_IS_HANDLE_BOX (handle_box));
priv = handle_box->priv;
if ((GtkShadowType) priv->shadow_type != type)
{
priv->shadow_type = type;
g_object_notify (G_OBJECT (handle_box), "shadow-type");
gtk_widget_queue_resize (GTK_WIDGET (handle_box));
}
}
/**
* gtk_handle_box_get_shadow_type:
* @handle_box: a #GtkHandleBox
*
* Gets the type of shadow drawn around the handle box. See
* gtk_handle_box_set_shadow_type().
*
* Return value: the type of shadow currently drawn around the handle box.
**/
GtkShadowType
gtk_handle_box_get_shadow_type (GtkHandleBox *handle_box)
{
g_return_val_if_fail (GTK_IS_HANDLE_BOX (handle_box), GTK_SHADOW_ETCHED_OUT);
return handle_box->priv->shadow_type;
}
void
gtk_handle_box_set_handle_position (GtkHandleBox *handle_box,
GtkPositionType position)
{
GtkHandleBoxPrivate *priv;
g_return_if_fail (GTK_IS_HANDLE_BOX (handle_box));
priv = handle_box->priv;
if ((GtkPositionType) priv->handle_position != position)
{
priv->handle_position = position;
g_object_notify (G_OBJECT (handle_box), "handle-position");
gtk_widget_queue_resize (GTK_WIDGET (handle_box));
}
}
/**
* gtk_handle_box_get_handle_position:
* @handle_box: a #GtkHandleBox
*
* Gets the handle position of the handle box. See
* gtk_handle_box_set_handle_position().
*
* Return value: the current handle position.
**/
GtkPositionType
gtk_handle_box_get_handle_position (GtkHandleBox *handle_box)
{
g_return_val_if_fail (GTK_IS_HANDLE_BOX (handle_box), GTK_POS_LEFT);
return handle_box->priv->handle_position;
}
void
gtk_handle_box_set_snap_edge (GtkHandleBox *handle_box,
GtkPositionType edge)
{
GtkHandleBoxPrivate *priv;
g_return_if_fail (GTK_IS_HANDLE_BOX (handle_box));
priv = handle_box->priv;
if (priv->snap_edge != edge)
{
priv->snap_edge = edge;
g_object_freeze_notify (G_OBJECT (handle_box));
g_object_notify (G_OBJECT (handle_box), "snap-edge");
g_object_notify (G_OBJECT (handle_box), "snap-edge-set");
g_object_thaw_notify (G_OBJECT (handle_box));
}
}
/**
* gtk_handle_box_get_snap_edge:
* @handle_box: a #GtkHandleBox
*
* Gets the edge used for determining reattachment of the handle box. See
* gtk_handle_box_set_snap_edge().
*
* Return value: the edge used for determining reattachment, or (GtkPositionType)-1 if this
* is determined (as per default) from the handle position.
**/
GtkPositionType
gtk_handle_box_get_snap_edge (GtkHandleBox *handle_box)
{
g_return_val_if_fail (GTK_IS_HANDLE_BOX (handle_box), (GtkPositionType)-1);
return handle_box->priv->snap_edge;
}
/**
* gtk_handle_box_get_child_detached:
* @handle_box: a #GtkHandleBox
*
* Whether the handlebox's child is currently detached.
*
* Return value: %TRUE if the child is currently detached, otherwise %FALSE
*
* Since: 2.14
**/
gboolean
gtk_handle_box_get_child_detached (GtkHandleBox *handle_box)
{
g_return_val_if_fail (GTK_IS_HANDLE_BOX (handle_box), FALSE);
return handle_box->priv->child_detached;
}
static void
gtk_handle_box_paint (GtkWidget *widget,
cairo_t *cr)
{
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
GtkBin *bin = GTK_BIN (widget);
GtkWidget *child;
gint width, height;
GdkRectangle rect;
gint handle_position;
GtkOrientation handle_orientation;
handle_position = effective_handle_position (hb);
width = gdk_window_get_width (priv->bin_window);
height = gdk_window_get_height (priv->bin_window);
gtk_paint_box (gtk_widget_get_style (widget),
cr,
gtk_widget_get_state (widget),
priv->shadow_type,
widget, "handlebox_bin",
0, 0, width, height);
switch (handle_position)
{
case GTK_POS_LEFT:
rect.x = 0;
rect.y = 0;
rect.width = DRAG_HANDLE_SIZE;
rect.height = height;
handle_orientation = GTK_ORIENTATION_VERTICAL;
break;
case GTK_POS_RIGHT:
rect.x = width - DRAG_HANDLE_SIZE;
rect.y = 0;
rect.width = DRAG_HANDLE_SIZE;
rect.height = height;
handle_orientation = GTK_ORIENTATION_VERTICAL;
break;
case GTK_POS_TOP:
rect.x = 0;
rect.y = 0;
rect.width = width;
rect.height = DRAG_HANDLE_SIZE;
handle_orientation = GTK_ORIENTATION_HORIZONTAL;
break;
case GTK_POS_BOTTOM:
rect.x = 0;
rect.y = height - DRAG_HANDLE_SIZE;
rect.width = width;
rect.height = DRAG_HANDLE_SIZE;
handle_orientation = GTK_ORIENTATION_HORIZONTAL;
break;
default:
g_assert_not_reached ();
break;
}
gtk_paint_handle (gtk_widget_get_style (widget), cr,
GTK_STATE_NORMAL, GTK_SHADOW_OUT,
widget, "handlebox",
rect.x, rect.y, rect.width, rect.height,
handle_orientation);
child = gtk_bin_get_child (bin);
if (child != NULL && gtk_widget_get_visible (child))
GTK_WIDGET_CLASS (gtk_handle_box_parent_class)->draw (widget, cr);
}
static gboolean
gtk_handle_box_draw (GtkWidget *widget,
cairo_t *cr)
{
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
if (gtk_cairo_should_draw_window (cr, gtk_widget_get_window (widget)))
{
if (priv->child_detached)
gtk_handle_box_draw_ghost (hb, cr);
}
else if (gtk_cairo_should_draw_window (cr, priv->bin_window))
gtk_handle_box_paint (widget, cr);
return FALSE;
}
static GtkWidget *
gtk_handle_box_get_invisible (void)
{
static GtkWidget *handle_box_invisible = NULL;
if (!handle_box_invisible)
{
handle_box_invisible = gtk_invisible_new ();
gtk_widget_show (handle_box_invisible);
}
return handle_box_invisible;
}
static gboolean
gtk_handle_box_grab_event (GtkWidget *widget,
GdkEvent *event,
GtkHandleBox *hb)
{
GtkHandleBoxPrivate *priv = hb->priv;
switch (event->type)
{
case GDK_BUTTON_RELEASE:
if (priv->in_drag) /* sanity check */
{
gtk_handle_box_end_drag (hb, event->button.time);
return TRUE;
}
break;
case GDK_MOTION_NOTIFY:
return gtk_handle_box_motion (GTK_WIDGET (hb), (GdkEventMotion *)event);
break;
default:
break;
}
return FALSE;
}
static gboolean
gtk_handle_box_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
gboolean event_handled;
GdkCursor *fleur;
gint handle_position;
handle_position = effective_handle_position (hb);
event_handled = FALSE;
if ((event->button == 1) &&
(event->type == GDK_BUTTON_PRESS || event->type == GDK_2BUTTON_PRESS))
{
GtkWidget *child;
gboolean in_handle;
if (event->window != priv->bin_window)
return FALSE;
child = gtk_bin_get_child (GTK_BIN (hb));
if (child)
{
GtkAllocation child_allocation;
guint border_width;
gtk_widget_get_allocation (child, &child_allocation);
border_width = gtk_container_get_border_width (GTK_CONTAINER (hb));
switch (handle_position)
{
case GTK_POS_LEFT:
in_handle = event->x < DRAG_HANDLE_SIZE;
break;
case GTK_POS_TOP:
in_handle = event->y < DRAG_HANDLE_SIZE;
break;
case GTK_POS_RIGHT:
in_handle = event->x > 2 * border_width + child_allocation.width;
break;
case GTK_POS_BOTTOM:
in_handle = event->y > 2 * border_width + child_allocation.height;
break;
default:
in_handle = FALSE;
break;
}
}
else
{
in_handle = FALSE;
event_handled = TRUE;
}
if (in_handle)
{
if (event->type == GDK_BUTTON_PRESS) /* Start a drag */
{
GtkWidget *invisible = gtk_handle_box_get_invisible ();
GdkWindow *window;
gint root_x, root_y;
gtk_invisible_set_screen (GTK_INVISIBLE (invisible),
gtk_widget_get_screen (GTK_WIDGET (hb)));
gdk_window_get_origin (priv->bin_window, &root_x, &root_y);
priv->orig_x = event->x_root;
priv->orig_y = event->y_root;
priv->float_allocation.x = root_x - event->x_root;
priv->float_allocation.y = root_y - event->y_root;
priv->float_allocation.width = gdk_window_get_width (priv->bin_window);
priv->float_allocation.height = gdk_window_get_height (priv->bin_window);
window = gtk_widget_get_window (widget);
if (gdk_window_is_viewable (window))
{
gdk_window_get_origin (window, &root_x, &root_y);
priv->attach_allocation.x = root_x;
priv->attach_allocation.y = root_y;
priv->attach_allocation.width = gdk_window_get_width (window);
priv->attach_allocation.height = gdk_window_get_height (window);
}
else
{
priv->attach_allocation.x = -1;
priv->attach_allocation.y = -1;
priv->attach_allocation.width = 0;
priv->attach_allocation.height = 0;
}
priv->in_drag = TRUE;
priv->grab_device = event->device;
fleur = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
GDK_FLEUR);
if (gdk_device_grab (event->device,
gtk_widget_get_window (invisible),
GDK_OWNERSHIP_WINDOW,
FALSE,
(GDK_BUTTON1_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK |
GDK_BUTTON_RELEASE_MASK),
fleur,
event->time) != GDK_GRAB_SUCCESS)
{
priv->in_drag = FALSE;
priv->grab_device = NULL;
}
else
{
gtk_device_grab_add (invisible, priv->grab_device, TRUE);
g_signal_connect (invisible, "event",
G_CALLBACK (gtk_handle_box_grab_event), hb);
}
gdk_cursor_unref (fleur);
event_handled = TRUE;
}
else if (priv->child_detached) /* Double click */
{
gtk_handle_box_reattach (hb);
}
}
}
return event_handled;
}
static gboolean
gtk_handle_box_motion (GtkWidget *widget,
GdkEventMotion *event)
{
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
GtkWidget *child;
gint new_x, new_y;
gint snap_edge;
gboolean is_snapped = FALSE;
gint handle_position;
GdkGeometry geometry;
GdkScreen *screen, *pointer_screen;
if (!priv->in_drag)
return FALSE;
handle_position = effective_handle_position (hb);
/* Calculate the attachment point on the float, if the float
* were detached
*/
new_x = 0;
new_y = 0;
screen = gtk_widget_get_screen (widget);
gdk_display_get_device_state (gdk_screen_get_display (screen),
event->device,
&pointer_screen,
&new_x, &new_y, NULL);
if (pointer_screen != screen)
{
new_x = priv->orig_x;
new_y = priv->orig_y;
}
new_x += priv->float_allocation.x;
new_y += priv->float_allocation.y;
snap_edge = priv->snap_edge;
if (snap_edge == -1)
snap_edge = (handle_position == GTK_POS_LEFT ||
handle_position == GTK_POS_RIGHT) ?
GTK_POS_TOP : GTK_POS_LEFT;
if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
switch (snap_edge)
{
case GTK_POS_LEFT:
snap_edge = GTK_POS_RIGHT;
break;
case GTK_POS_RIGHT:
snap_edge = GTK_POS_LEFT;
break;
default:
break;
}
/* First, check if the snapped edge is aligned
*/
switch (snap_edge)
{
case GTK_POS_TOP:
is_snapped = abs (priv->attach_allocation.y - new_y) < TOLERANCE;
break;
case GTK_POS_BOTTOM:
is_snapped = abs (priv->attach_allocation.y + (gint)priv->attach_allocation.height -
new_y - (gint)priv->float_allocation.height) < TOLERANCE;
break;
case GTK_POS_LEFT:
is_snapped = abs (priv->attach_allocation.x - new_x) < TOLERANCE;
break;
case GTK_POS_RIGHT:
is_snapped = abs (priv->attach_allocation.x + (gint)priv->attach_allocation.width -
new_x - (gint)priv->float_allocation.width) < TOLERANCE;
break;
}
/* Next, check if coordinates in the other direction are sufficiently
* aligned
*/
if (is_snapped)
{
gint float_pos1 = 0; /* Initialize to suppress warnings */
gint float_pos2 = 0;
gint attach_pos1 = 0;
gint attach_pos2 = 0;
switch (snap_edge)
{
case GTK_POS_TOP:
case GTK_POS_BOTTOM:
attach_pos1 = priv->attach_allocation.x;
attach_pos2 = priv->attach_allocation.x + priv->attach_allocation.width;
float_pos1 = new_x;
float_pos2 = new_x + priv->float_allocation.width;
break;
case GTK_POS_LEFT:
case GTK_POS_RIGHT:
attach_pos1 = priv->attach_allocation.y;
attach_pos2 = priv->attach_allocation.y + priv->attach_allocation.height;
float_pos1 = new_y;
float_pos2 = new_y + priv->float_allocation.height;
break;
}
is_snapped = ((attach_pos1 - TOLERANCE < float_pos1) &&
(attach_pos2 + TOLERANCE > float_pos2)) ||
((float_pos1 - TOLERANCE < attach_pos1) &&
(float_pos2 + TOLERANCE > attach_pos2));
}
child = gtk_bin_get_child (GTK_BIN (hb));
if (is_snapped)
{
if (priv->child_detached)
{
priv->child_detached = FALSE;
gdk_window_hide (priv->float_window);
gdk_window_reparent (priv->bin_window, gtk_widget_get_window (widget), 0, 0);
priv->float_window_mapped = FALSE;
g_signal_emit (hb,
handle_box_signals[SIGNAL_CHILD_ATTACHED],
0,
child);
gtk_widget_queue_resize (widget);
}
}
else
{
gint width, height;
width = gdk_window_get_width (priv->float_window);
height = gdk_window_get_height (priv->float_window);
switch (handle_position)
{
case GTK_POS_LEFT:
new_y += ((gint)priv->float_allocation.height - height) / 2;
break;
case GTK_POS_RIGHT:
new_x += (gint)priv->float_allocation.width - width;
new_y += ((gint)priv->float_allocation.height - height) / 2;
break;
case GTK_POS_TOP:
new_x += ((gint)priv->float_allocation.width - width) / 2;
break;
case GTK_POS_BOTTOM:
new_x += ((gint)priv->float_allocation.width - width) / 2;
new_y += (gint)priv->float_allocation.height - height;
break;
}
if (priv->child_detached)
{
gdk_window_move (priv->float_window, new_x, new_y);
gdk_window_raise (priv->float_window);
}
else
{
guint border_width;
GtkRequisition child_requisition;
priv->child_detached = TRUE;
if (child)
{
gtk_widget_get_preferred_size (child, &child_requisition, NULL);
}
else
{
child_requisition.width = 0;
child_requisition.height = 0;
}
border_width = gtk_container_get_border_width (GTK_CONTAINER (hb));
width = child_requisition.width + 2 * border_width;
height = child_requisition.height + 2 * border_width;
if (handle_position == GTK_POS_LEFT || handle_position == GTK_POS_RIGHT)
width += DRAG_HANDLE_SIZE;
else
height += DRAG_HANDLE_SIZE;
gdk_window_move_resize (priv->float_window, new_x, new_y, width, height);
gdk_window_reparent (priv->bin_window, priv->float_window, 0, 0);
gdk_window_set_geometry_hints (priv->float_window, &geometry, GDK_HINT_POS);
gdk_window_show (priv->float_window);
priv->float_window_mapped = TRUE;
#if 0
/* this extra move is necessary if we use decorations, or our
* window manager insists on decorations.
*/
gdk_display_sync (gtk_widget_get_display (widget));
gdk_window_move (priv->float_window, new_x, new_y);
gdk_display_sync (gtk_widget_get_display (widget));
#endif /* 0 */
g_signal_emit (hb,
handle_box_signals[SIGNAL_CHILD_DETACHED],
0,
child);
gtk_widget_queue_resize (widget);
}
}
return TRUE;
}
static void
gtk_handle_box_add (GtkContainer *container,
GtkWidget *widget)
{
GtkHandleBoxPrivate *priv = GTK_HANDLE_BOX (container)->priv;
gtk_widget_set_parent_window (widget, priv->bin_window);
GTK_CONTAINER_CLASS (gtk_handle_box_parent_class)->add (container, widget);
}
static void
gtk_handle_box_remove (GtkContainer *container,
GtkWidget *widget)
{
GTK_CONTAINER_CLASS (gtk_handle_box_parent_class)->remove (container, widget);
gtk_handle_box_reattach (GTK_HANDLE_BOX (container));
}
static gint
gtk_handle_box_delete_event (GtkWidget *widget,
GdkEventAny *event)
{
GtkHandleBox *hb = GTK_HANDLE_BOX (widget);
GtkHandleBoxPrivate *priv = hb->priv;
if (event->window == priv->float_window)
{
gtk_handle_box_reattach (hb);
return TRUE;
}
return FALSE;
}
static void
gtk_handle_box_reattach (GtkHandleBox *hb)
{
GtkHandleBoxPrivate *priv = hb->priv;
GtkWidget *child;
GtkWidget *widget = GTK_WIDGET (hb);
if (priv->child_detached)
{
priv->child_detached = FALSE;
if (gtk_widget_get_realized (widget))
{
gdk_window_hide (priv->float_window);
gdk_window_reparent (priv->bin_window, gtk_widget_get_window (widget),
0, 0);
child = gtk_bin_get_child (GTK_BIN (hb));
if (child)
g_signal_emit (hb,
handle_box_signals[SIGNAL_CHILD_ATTACHED],
0,
child);
}
priv->float_window_mapped = FALSE;
}
if (priv->in_drag)
gtk_handle_box_end_drag (hb, GDK_CURRENT_TIME);
gtk_widget_queue_resize (GTK_WIDGET (hb));
}
static void
gtk_handle_box_end_drag (GtkHandleBox *hb,
guint32 time)
{
GtkHandleBoxPrivate *priv = hb->priv;
GtkWidget *invisible = gtk_handle_box_get_invisible ();
priv->in_drag = FALSE;
gtk_device_grab_remove (invisible, priv->grab_device);
gdk_device_ungrab (priv->grab_device, time);
g_signal_handlers_disconnect_by_func (invisible,
G_CALLBACK (gtk_handle_box_grab_event),
hb);
priv->grab_device = NULL;
}