/* bonobo-dock.c
Copyright (C) 1998 Free Software Foundation
The Gnome Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The Gnome 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the Gnome Library; see the file COPYING.LIB. If not,
write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Author: Ettore Perazzoli <ettore@comm2000.it>
*/
/*
@NOTATION@
*/
#include "config.h"
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "bonobo-dock.h"
#include "bonobo-dock-band.h"
#include "bonobo-dock-item.h"
#include <libgnome/gnome-macros.h>
GNOME_CLASS_BOILERPLATE (BonoboDock, bonobo_dock,
GtkContainer, GTK_TYPE_CONTAINER)
#define noBONOBO_DOCK_DEBUG
/* FIXME: To be removed. */
#if defined BONOBO_DOCK_DEBUG && defined __GNUC__
#define DEBUG(x) \
do \
{ \
printf ("%s.%d: ", __FUNCTION__, __LINE__); \
printf x; \
putchar ('\n'); \
} \
while (0)
#else
#define DEBUG(x)
#endif
struct _BonoboDockPrivate
{
GdkDragContext *current_drag;
};
enum {
LAYOUT_CHANGED,
LAST_SIGNAL
};
static void bonobo_dock_size_request (GtkWidget *widget,
GtkRequisition *requisition);
static void bonobo_dock_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static void bonobo_dock_map (GtkWidget *widget);
static void bonobo_dock_unmap (GtkWidget *widget);
static void bonobo_dock_add (GtkContainer *container,
GtkWidget *child);
static void bonobo_dock_remove (GtkContainer *container,
GtkWidget *widget);
static void bonobo_dock_forall (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_data);
static void bonobo_dock_finalize (GObject *object);
static void size_request_v (GList *list,
GtkRequisition *requisition);
static void size_request_h (GList *list,
GtkRequisition *requisition);
static gint size_allocate_v (GList *list,
gint start_x, gint start_y,
guint width, gint direction);
static gint size_allocate_h (GList *list,
gint start_x, gint start_y,
guint width, gint direction);
static void map_widget (GtkWidget *w);
static void map_widget_foreach (gpointer data,
gpointer user_data);
static void map_band_list (GList *list);
static void unmap_widget (GtkWidget *w);
static void unmap_widget_foreach (gpointer data,
gpointer user_data);
static void unmap_band_list (GList *list);
static gboolean remove_from_band_list (GList **list,
BonoboDockBand *child);
static void forall_helper (GList *list,
GtkCallback callback,
gpointer callback_data);
static void drag_begin (GtkWidget *widget,
gpointer data);
static void drag_end_bands (GList **list,
BonoboDockItem *item);
static void drag_end (GtkWidget *widget,
gpointer data);
static gboolean drag_new (BonoboDock *dock,
BonoboDockItem *item,
GList **area,
GList *where,
gint x, gint y,
gboolean is_vertical);
static gboolean drag_to (BonoboDock *dock,
BonoboDockItem *item,
GList *where,
gint x, gint y,
gboolean is_vertical);
static gboolean drag_floating (BonoboDock *dock,
BonoboDockItem *item,
gint x, gint y,
gint rel_x, gint rel_y);
static gboolean drag_check (BonoboDock *dock,
BonoboDockItem *item,
GList **area,
gint x, gint y,
gboolean is_vertical);
static void drag_snap (BonoboDock *dock,
GtkWidget *widget,
gint x, gint y);
static void drag_motion (GtkWidget *widget,
gint x, gint y,
gpointer data);
static BonoboDockItem *get_docked_item_by_name (BonoboDock *dock,
const gchar *name,
BonoboDockPlacement *placement_return,
guint *num_band_return,
guint *band_position_return,
guint *offset_return);
static BonoboDockItem *get_floating_item_by_name (BonoboDock *dock,
const gchar *name);
static void connect_drag_signals (BonoboDock *dock,
GtkWidget *item);
static guint dock_signals[LAST_SIGNAL] = { 0 };
static void
bonobo_dock_class_init (BonoboDockClass *class)
{
GtkObjectClass *object_class;
GObjectClass *gobject_class;
GtkWidgetClass *widget_class;
GtkContainerClass *container_class;
object_class = (GtkObjectClass *) class;
gobject_class = (GObjectClass *) class;
widget_class = (GtkWidgetClass *) class;
container_class = (GtkContainerClass *) class;
gobject_class->finalize = bonobo_dock_finalize;
widget_class->size_request = bonobo_dock_size_request;
widget_class->size_allocate = bonobo_dock_size_allocate;
widget_class->map = bonobo_dock_map;
widget_class->unmap = bonobo_dock_unmap;
container_class->add = bonobo_dock_add;
container_class->remove = bonobo_dock_remove;
container_class->forall = bonobo_dock_forall;
dock_signals[LAYOUT_CHANGED] =
g_signal_new ("layout_changed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (BonoboDockClass,
layout_changed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
}
static void
bonobo_dock_instance_init (BonoboDock *dock)
{
GTK_WIDGET_SET_FLAGS (GTK_WIDGET (dock), GTK_NO_WINDOW);
dock->_priv = NULL;
/* XXX: when there is some private stuff enable this
dock->_priv = g_new0(BonoboDockPrivate, 1);
*/
dock->client_area = NULL;
dock->top_bands = NULL;
dock->bottom_bands = NULL;
dock->right_bands = NULL;
dock->left_bands = NULL;
dock->floating_children = NULL;
dock->floating_items_allowed = TRUE;
}
static void
size_request_v (GList *list, GtkRequisition *requisition)
{
for (; list != NULL; list = list->next)
{
GtkWidget *w;
GtkRequisition req;
w = GTK_WIDGET (list->data);
gtk_widget_size_request (w, &req);
requisition->width += req.width;
requisition->height = MAX (requisition->height, req.height);
}
}
static void
size_request_h (GList *list, GtkRequisition *requisition)
{
for (list = list; list != NULL; list = list->next)
{
GtkWidget *w;
GtkRequisition req;
w = GTK_WIDGET (list->data);
gtk_widget_size_request (w, &req);
requisition->height += req.height;
requisition->width = MAX (requisition->width, req.width);
}
}
static void
bonobo_dock_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
BonoboDock *dock;
GList *lp;
dock = BONOBO_DOCK (widget);
if (dock->client_area != NULL && GTK_WIDGET_VISIBLE (dock->client_area))
gtk_widget_size_request (dock->client_area, requisition);
else
{
requisition->width = 0;
requisition->height = 0;
}
size_request_v (dock->left_bands, requisition);
size_request_v (dock->right_bands, requisition);
size_request_h (dock->top_bands, requisition);
size_request_h (dock->bottom_bands, requisition);
lp = dock->floating_children;
while (lp != NULL)
{
GtkWidget *w;
GtkRequisition float_item_requisition;
w = lp->data;
lp = lp->next;
gtk_widget_size_request (w, &float_item_requisition);
}
}
static gint
size_allocate_h (GList *list, gint start_x, gint start_y, guint width,
gint direction)
{
GtkAllocation allocation;
allocation.x = start_x;
allocation.y = start_y;
allocation.width = width;
if (direction < 0)
list = g_list_last (list);
while (list != NULL)
{
GtkWidget *w;
w = GTK_WIDGET (list->data);
allocation.height = w->requisition.height;
if (direction > 0)
{
gtk_widget_size_allocate (w, &allocation);
allocation.y += allocation.height;
list = list->next;
}
else
{
allocation.y -= allocation.height;
gtk_widget_size_allocate (w, &allocation);
list = list->prev;
}
}
return allocation.y;
}
static gint
size_allocate_v (GList *list, gint start_x, gint start_y, guint height,
gint direction)
{
GtkAllocation allocation;
allocation.x = start_x;
allocation.y = start_y;
allocation.height = height;
if (direction < 0)
list = g_list_last (list);
while (list != NULL)
{
GtkWidget *w;
w = GTK_WIDGET (list->data);
allocation.width = w->requisition.width;
if (direction > 0)
{
gtk_widget_size_allocate (w, &allocation);
allocation.x += allocation.width;
list = list->next;
}
else
{
allocation.x -= allocation.width;
gtk_widget_size_allocate (w, &allocation);
list = list->prev;
}
}
return allocation.x;
}
static void
bonobo_dock_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
BonoboDock *dock;
gint top_bands_y, bottom_bands_y;
gint left_bands_x, right_bands_x;
GtkAllocation child_allocation;
GList *lp;
dock = BONOBO_DOCK (widget);
widget->allocation = *allocation;
top_bands_y = size_allocate_h (dock->top_bands,
allocation->x,
allocation->y,
allocation->width,
+1);
bottom_bands_y = size_allocate_h (dock->bottom_bands,
allocation->x,
allocation->y + allocation->height,
allocation->width,
-1);
child_allocation.height = MAX (bottom_bands_y - top_bands_y, 1);
left_bands_x = size_allocate_v (dock->left_bands,
allocation->x,
top_bands_y,
child_allocation.height,
+1);
right_bands_x = size_allocate_v (dock->right_bands,
allocation->x + allocation->width,
top_bands_y,
child_allocation.height,
-1);
child_allocation.width = MAX (right_bands_x - left_bands_x, 1);
child_allocation.x = left_bands_x;
child_allocation.y = top_bands_y;
dock->client_rect = child_allocation;
if (dock->client_area != NULL && GTK_WIDGET_VISIBLE (dock->client_area))
gtk_widget_size_allocate (dock->client_area, &child_allocation);
lp = dock->floating_children;
while (lp != NULL)
{
GtkWidget *w;
GtkAllocation float_item_allocation;
w = lp->data;
lp = lp->next;
float_item_allocation.x = 0;
float_item_allocation.y = 0;
float_item_allocation.width = w->requisition.width;
float_item_allocation.height = w->requisition.height;
gtk_widget_size_allocate (w, &float_item_allocation);
}
}
static void
map_widget (GtkWidget *w)
{
if (w != NULL && GTK_WIDGET_VISIBLE (w) && ! GTK_WIDGET_MAPPED (w))
gtk_widget_map (w);
}
static void
unmap_widget (GtkWidget *w)
{
if (w != NULL && GTK_WIDGET_VISIBLE (w) && GTK_WIDGET_MAPPED (w))
gtk_widget_unmap (w);
}
static void
map_widget_foreach (gpointer data,
gpointer user_data)
{
map_widget (GTK_WIDGET (data));
}
static void
unmap_widget_foreach (gpointer data,
gpointer user_data)
{
unmap_widget (GTK_WIDGET (data));
}
static void
map_band_list (GList *list)
{
while (list != NULL)
{
GtkWidget *w;
w = GTK_WIDGET (list->data);
map_widget (w);
list = list->next;
}
}
static void
unmap_band_list (GList *list)
{
while (list != NULL)
{
GtkWidget *w;
w = GTK_WIDGET (list->data);
unmap_widget (w);
list = list->next;
}
}
static void
bonobo_dock_map (GtkWidget *widget)
{
BonoboDock *dock;
g_return_if_fail (widget != NULL);
g_return_if_fail (BONOBO_IS_DOCK(widget));
GNOME_CALL_PARENT (GTK_WIDGET_CLASS, map, (widget));
dock = BONOBO_DOCK (widget);
map_widget (dock->client_area);
map_band_list (dock->top_bands);
map_band_list (dock->bottom_bands);
map_band_list (dock->left_bands);
map_band_list (dock->right_bands);
g_list_foreach (dock->floating_children, map_widget_foreach, NULL);
}
static void
bonobo_dock_unmap (GtkWidget *widget)
{
BonoboDock *dock;
g_return_if_fail (widget != NULL);
g_return_if_fail (BONOBO_IS_DOCK(widget));
dock = BONOBO_DOCK (widget);
unmap_widget (dock->client_area);
unmap_band_list (dock->top_bands);
unmap_band_list (dock->bottom_bands);
unmap_band_list (dock->left_bands);
unmap_band_list (dock->right_bands);
g_list_foreach (dock->floating_children, unmap_widget_foreach, NULL);
GNOME_CALL_PARENT (GTK_WIDGET_CLASS, unmap, (widget));
}
/* GtkContainer methods. */
static void
bonobo_dock_add (GtkContainer *container, GtkWidget *child)
{
BonoboDock *dock;
dock = BONOBO_DOCK (container);
bonobo_dock_add_item (dock, BONOBO_DOCK_ITEM(child), BONOBO_DOCK_TOP, 0, 0, 0, TRUE);
}
static gboolean
remove_from_band_list (GList **list, BonoboDockBand *child)
{
GList *lp;
for (lp = *list; lp != NULL; lp = lp->next)
{
if (lp->data == child)
{
gtk_widget_unparent (GTK_WIDGET (child));
*list = g_list_remove_link (*list, lp);
g_list_free (lp);
return TRUE;
}
}
return FALSE;
}
static void
bonobo_dock_remove (GtkContainer *container, GtkWidget *widget)
{
BonoboDock *dock;
dock = BONOBO_DOCK (container);
if (dock->client_area == widget)
{
gtk_widget_unparent (widget);
dock->client_area = NULL;
gtk_widget_queue_resize (GTK_WIDGET (dock));
}
else
{
/* Check if it's a floating child. */
{
GList *lp;
lp = dock->floating_children;
while (lp != NULL)
{
GtkWidget *w;
w = lp->data;
if (w == widget)
{
gtk_widget_unparent (w);
dock->floating_children
= g_list_remove_link (dock->floating_children, lp);
g_list_free (lp);
return;
}
lp = lp->next;
}
}
/* Then it must be one of the bands. */
{
BonoboDockBand *band;
g_return_if_fail (BONOBO_IS_DOCK_BAND (widget));
band = BONOBO_DOCK_BAND (widget);
if (remove_from_band_list (&dock->top_bands, band)
|| remove_from_band_list (&dock->bottom_bands, band)
|| remove_from_band_list (&dock->left_bands, band)
|| remove_from_band_list (&dock->right_bands, band))
{
gtk_widget_queue_resize (GTK_WIDGET (dock));
return;
}
}
}
}
static void
forall_helper (GList *list,
GtkCallback callback,
gpointer callback_data)
{
while (list != NULL)
{
GtkWidget *w;
w = GTK_WIDGET(list->data);
list = list->next;
(* callback) (w, callback_data);
}
}
static void
bonobo_dock_forall (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_data)
{
BonoboDock *dock;
GList *lp;
g_return_if_fail (container != NULL);
g_return_if_fail (BONOBO_IS_DOCK (container));
g_return_if_fail (callback != NULL);
dock = BONOBO_DOCK (container);
forall_helper (dock->top_bands, callback, callback_data);
forall_helper (dock->bottom_bands, callback, callback_data);
forall_helper (dock->left_bands, callback, callback_data);
forall_helper (dock->right_bands, callback, callback_data);
lp = dock->floating_children;
while (lp != NULL)
{
GtkWidget *w;
w = lp->data;
lp = lp->next;
(* callback) (w, callback_data);
}
if (dock->client_area != NULL)
(* callback) (dock->client_area, callback_data);
}
static void
bonobo_dock_finalize (GObject *object)
{
BonoboDock *self = BONOBO_DOCK (object);
g_free (self->_priv);
self->_priv = NULL;
if (G_OBJECT_CLASS (parent_class)->finalize)
(* G_OBJECT_CLASS (parent_class)->finalize) (object);
}
static void
new_band_setup (BonoboDock *dock,
GtkWidget *new_band,
GtkOrientation orientation)
{
bonobo_dock_band_set_orientation (
BONOBO_DOCK_BAND (new_band), orientation);
gtk_widget_set_parent (GTK_WIDGET (new_band), GTK_WIDGET (dock));
gtk_widget_queue_resize (GTK_WIDGET (new_band));
gtk_widget_show (GTK_WIDGET (new_band));
}
/* When an item is being dragged, there can be 3 situations:
(I) A new band is created and the item is docked to it.
(II) The item is docked to an existing band.
(III) The item must be floating, so it has to be detached if
currently not floating, and moved around in its own window. */
/* Case (I): Dock `item' into a new band next to `where' in the
docking area `area'. If `where' is NULL, the band becomes the
first one in `area'. */
static gboolean
drag_new (BonoboDock *dock,
BonoboDockItem *item,
GList **area,
GList *where,
gint x, gint y,
gboolean is_vertical)
{
BonoboDockBand *new_band;
GList *next;
DEBUG (("entering function"));
new_band = NULL;
/* We need a new band next to `where', but we try to re-use the band
next to it if either it contains no children, or it only contains
`item'. */
next = NULL;
if (where == NULL && area != NULL)
next = *area;
else
next = where->next;
if (next != NULL)
{
BonoboDockBand *band;
guint num_children;
band = BONOBO_DOCK_BAND (next->data);
num_children = bonobo_dock_band_get_num_children (band);
if (num_children == 0
|| (num_children == 1
&& GTK_WIDGET (band) == GTK_WIDGET (item)->parent))
new_band = BONOBO_DOCK_BAND (next->data);
}
/* Create the new band and make it our child if we cannot re-use an
existing one. */
if (new_band == NULL)
{
new_band = BONOBO_DOCK_BAND (bonobo_dock_band_new ());
/* This is mostly to remember that `drag_allocation' for this
child is bogus, as it was not previously allocated. */
new_band->new_for_drag = TRUE;
if (where == NULL)
*area = where = g_list_prepend (*area, new_band);
else if (where->next == NULL)
where = g_list_append (where, new_band);
else
where = g_list_prepend (where->next, new_band);
new_band_setup (dock, GTK_WIDGET (new_band),
is_vertical ? GTK_ORIENTATION_VERTICAL
: GTK_ORIENTATION_HORIZONTAL);
}
/* Move the item to the new band. (This is a no-op if we are using
`where->next' and it already contains `item'.) */
bonobo_dock_item_attach (item, GTK_WIDGET (new_band), x, y);
/* Prepare the band for dragging of `item'. */
bonobo_dock_band_drag_begin (new_band, item);
/* Set the offset of `item' in the band. */
if (is_vertical)
bonobo_dock_band_set_child_offset (new_band, GTK_WIDGET (item),
MAX (y - dock->client_rect.y, 0));
else
bonobo_dock_band_set_child_offset (new_band, GTK_WIDGET (item),
MAX (x - GTK_WIDGET (dock)->allocation.x, 0));
return TRUE;
}
/* Case (II): Drag into an existing band. */
static gboolean
drag_to (BonoboDock *dock,
BonoboDockItem *item,
GList *where,
gint x, gint y,
gboolean is_vertical)
{
DEBUG (("x %d y %d", x, y));
return bonobo_dock_band_drag_to (BONOBO_DOCK_BAND (where->data), item, x, y);
}
/* Case (III): Move a floating (i.e. floating) item. */
static gboolean
drag_floating (BonoboDock *dock,
BonoboDockItem *item,
gint x,
gint y,
gint rel_x,
gint rel_y)
{
GtkWidget *item_widget, *dock_widget;
item_widget = GTK_WIDGET (item);
dock_widget = GTK_WIDGET (dock);
if (!item->is_floating && item_widget->parent != dock_widget)
{
GtkAllocation *dock_allocation, *client_allocation;
/* The item is currently not floating (so it is not our child).
Make it so if we are outside the docking areas. */
dock_allocation = &dock_widget->allocation;
if (dock->client_area)
client_allocation = &dock->client_area->allocation;
else
client_allocation = NULL;
if (rel_x < 0
|| rel_x >= dock_allocation->width
|| rel_y < 0
|| rel_y >= dock_allocation->height
|| (client_allocation != NULL
&& rel_x >= client_allocation->x
&& rel_x < client_allocation->x + client_allocation->width
&& rel_y >= client_allocation->y
&& rel_y < client_allocation->y + client_allocation->height))
{
gtk_widget_ref (item_widget);
gtk_container_remove (GTK_CONTAINER (item_widget->parent),
item_widget);
gtk_widget_set_parent (item_widget, dock_widget);
dock->floating_children = g_list_prepend (dock->floating_children,
item);
gtk_widget_realize (item_widget);
gtk_widget_map (item_widget);
gtk_widget_queue_resize (item_widget);
bonobo_dock_item_detach (item, x, y);
if (item->in_drag)
bonobo_dock_item_grab_pointer (item);
gtk_widget_unref (item_widget);
}
}
else
{
/* The item is already floating; all we have to do is move it to
the current dragging position. */
bonobo_dock_item_drag_floating (item, x, y);
}
return TRUE;
}
/* Check if `item' can be docked to any of the DockBands of the dock
area `area'. If so, dock it and return TRUE; otherwise, return
FALSE. */
static gboolean
drag_check (BonoboDock *dock,
BonoboDockItem *item,
GList **area,
gint x, gint y,
gboolean is_vertical)
{
GList *lp;
GtkAllocation *alloc;
for (lp = *area; lp != NULL; lp = lp->next)
{
BonoboDockBand *band;
band = BONOBO_DOCK_BAND (lp->data);
if (! band->new_for_drag)
{
alloc = &band->drag_allocation;
if (x >= alloc->x - 10 && x < alloc->x + alloc->width
&& y >= alloc->y && y < alloc->y + alloc->height)
{
if (is_vertical)
{
if (x < alloc->x + alloc->width / 2
&& drag_to (dock, item, lp, x, y, TRUE))
return TRUE;
else
return drag_new (dock, item, area, lp, x, y, TRUE);
}
else
{
if (y < alloc->y + alloc->height / 2
&& drag_to (dock, item, lp, x, y, FALSE))
return TRUE;
else
return drag_new (dock, item, area, lp, x, y, FALSE);
}
}
}
}
return FALSE;
}
/* Snap the BonoboDockItem `widget' to `dock' at the specified
position. */
static void
drag_snap (BonoboDock *dock,
GtkWidget *widget,
gint x, gint y)
{
#define SNAP 50
BonoboDockItem *item;
BonoboDockItemBehavior item_behavior;
gint win_x, win_y;
gint rel_x, rel_y;
gboolean item_allows_horizontal, item_allows_vertical;
item = BONOBO_DOCK_ITEM (widget);
item_behavior = bonobo_dock_item_get_behavior (item);
item_allows_horizontal = ! (item_behavior
& BONOBO_DOCK_ITEM_BEH_NEVER_HORIZONTAL);
item_allows_vertical = ! (item_behavior
& BONOBO_DOCK_ITEM_BEH_NEVER_VERTICAL);
gdk_window_get_origin (GTK_WIDGET (dock)->window, &win_x, &win_y);
rel_x = x - win_x;
rel_y = y - win_y;
DEBUG (("(%d,%d)", x, y));
DEBUG (("relative (%d,%d)", rel_x, rel_y));
if (item_allows_horizontal
&& rel_x >= 0 && rel_x < GTK_WIDGET (dock)->allocation.width)
{
/* Check prepending to top/bottom bands. */
if (rel_y < 0 && rel_y >= -SNAP
&& drag_new (dock, item, &dock->top_bands, NULL,
rel_x, rel_y, FALSE))
return;
else if (rel_y >= dock->client_rect.y + dock->client_rect.height - SNAP
&& rel_y < dock->client_rect.y + dock->client_rect.height
&& drag_new (dock, item, &dock->bottom_bands, NULL,
rel_x, rel_y, FALSE))
return;
}
if (item_allows_vertical
&& rel_y >= dock->client_rect.y
&& rel_y < dock->client_rect.y + dock->client_rect.height)
{
/* Check prepending to left/right bands. */
if (rel_x < 0 && rel_x >= -SNAP
&& drag_new (dock, item, &dock->left_bands, NULL,
rel_x, rel_y, TRUE))
return;
else if (rel_x >= dock->client_rect.x + dock->client_rect.width - SNAP
&& rel_x < dock->client_rect.x + dock->client_rect.width
&& drag_new (dock, item, &dock->right_bands, NULL,
rel_x, rel_y, TRUE))
return;
}
/* Check dragging into bands. */
if (item_allows_horizontal
&& drag_check (dock, item, &dock->top_bands, rel_x, rel_y, FALSE))
return;
else if (item_allows_horizontal
&& drag_check (dock, item, &dock->bottom_bands, rel_x, rel_y, FALSE))
return;
else if (item_allows_vertical
&& drag_check (dock, item, &dock->left_bands, rel_x, rel_y, TRUE))
return;
else if (item_allows_vertical
&& drag_check (dock, item, &dock->right_bands, rel_x, rel_y, TRUE))
return;
/* We are not in any "interesting" area: the item must be floating
if allowed to. */
if (dock->floating_items_allowed
&& ! (item_behavior & BONOBO_DOCK_ITEM_BEH_NEVER_DETACH))
drag_floating (dock, item, x, y, rel_x, rel_y);
/* If still not floating, fall back to moving the item in its own
band. */
if (! item->is_floating)
bonobo_dock_band_drag_to (BONOBO_DOCK_BAND (GTK_WIDGET (item)->parent),
item, rel_x, rel_y);
}
/* "drag_begin" signal handling. */
static void
drag_begin (GtkWidget *widget, gpointer data)
{
BonoboDock *dock;
BonoboDockItem *item;
DEBUG (("entering function"));
dock = BONOBO_DOCK (data);
item = BONOBO_DOCK_ITEM (widget);
/* Communicate all the bands that `widget' is currently being
dragged. */
g_list_foreach (dock->top_bands, (GFunc) bonobo_dock_band_drag_begin, item);
g_list_foreach (dock->bottom_bands, (GFunc) bonobo_dock_band_drag_begin, item);
g_list_foreach (dock->right_bands, (GFunc) bonobo_dock_band_drag_begin, item);
g_list_foreach (dock->left_bands, (GFunc) bonobo_dock_band_drag_begin, item);
}
/* "drag_end" signal handling. */
static void
drag_end_bands (GList **list, BonoboDockItem *item)
{
GList *lp;
BonoboDockBand *band;
lp = *list;
while (lp != NULL)
{
band = BONOBO_DOCK_BAND(lp->data);
bonobo_dock_band_drag_end (band, item);
if (bonobo_dock_band_get_num_children (band) == 0)
{
GList *next;
next = lp->next;
/* This will remove this link, too. */
gtk_widget_destroy (GTK_WIDGET (band));
lp = next;
}
else
lp = lp->next;
}
}
static void
drag_end (GtkWidget *widget, gpointer data)
{
BonoboDockItem *item;
BonoboDock *dock;
DEBUG (("entering function"));
item = BONOBO_DOCK_ITEM (widget);
dock = BONOBO_DOCK (data);
/* Communicate to all the bands that `item' is no longer being
dragged. */
drag_end_bands (&dock->top_bands, item);
drag_end_bands (&dock->bottom_bands, item);
drag_end_bands (&dock->left_bands, item);
drag_end_bands (&dock->right_bands, item);
g_signal_emit (data, dock_signals[LAYOUT_CHANGED], 0);
}
/* "drag_motion" signal handling. */
/* Handle a drag motion on the BonoboDockItem `widget'. This is
connected to the "drag_motion" of all the children being added to
the BonoboDock, and tries to dock the dragged item at the current
(`x', `y') position of the pointer. */
static void
drag_motion (GtkWidget *widget,
gint x, gint y,
gpointer data)
{
drag_snap (BONOBO_DOCK (data), widget, x, y);
}
static BonoboDockItem *
get_docked_item_by_name (BonoboDock *dock,
const gchar *name,
BonoboDockPlacement *placement_return,
guint *num_band_return,
guint *band_position_return,
guint *offset_return)
{
{
struct
{
GList *band_list;
BonoboDockPlacement placement;
}
areas[] =
{
{ NULL, BONOBO_DOCK_TOP },
{ NULL, BONOBO_DOCK_BOTTOM },
{ NULL, BONOBO_DOCK_LEFT },
{ NULL, BONOBO_DOCK_RIGHT },
{ NULL, BONOBO_DOCK_FLOATING },
};
GList *lp;
guint i;
areas[0].band_list = dock->top_bands;
areas[1].band_list = dock->bottom_bands;
areas[2].band_list = dock->left_bands;
areas[3].band_list = dock->right_bands;
for (i = 0; i < 4; i++)
{
guint num_band;
for (lp = areas[i].band_list, num_band = 0;
lp != NULL;
lp = lp->next, num_band++)
{
BonoboDockBand *band;
BonoboDockItem *item;
band = BONOBO_DOCK_BAND(lp->data);
item = bonobo_dock_band_get_item_by_name (band,
name,
band_position_return,
offset_return);
if (item != NULL)
{
if (num_band_return != NULL)
*num_band_return = num_band;
if (placement_return != NULL)
*placement_return = areas[i].placement;
return item;
}
}
}
}
return NULL;
}
static BonoboDockItem *
get_floating_item_by_name (BonoboDock *dock,
const gchar *name)
{
GList *lp;
BonoboDockItem *item;
for (lp = dock->floating_children; lp != NULL; lp = lp->next)
{
item = lp->data;
if (strcmp (item->name, name) == 0)
return item;
}
return NULL;
}
static void
connect_drag_signals (BonoboDock *dock,
GtkWidget *item)
{
if (BONOBO_IS_DOCK_ITEM (item))
{
DEBUG (("here"));
g_signal_connect (item, "dock_drag_begin",
G_CALLBACK (drag_begin), dock);
g_signal_connect (item, "dock_drag_motion",
G_CALLBACK (drag_motion), dock);
g_signal_connect (item, "dock_drag_end",
G_CALLBACK (drag_end), dock);
}
}
/**
* bonobo_dock_new:
*
* Description: Creates a new #BonoboDock widget.
*
* Return value: The new widget.
**/
GtkWidget *
bonobo_dock_new (void)
{
BonoboDock *dock;
GtkWidget *widget;
dock = g_object_new (bonobo_dock_get_type (), NULL);
widget = GTK_WIDGET (dock);
#if 0 /* FIXME: should I? */
if (GTK_WIDGET_VISIBLE (widget))
gtk_widget_queue_resize (widget);
#endif
return widget;
}
/**
* bonobo_dock_allow_floating_items:
* @dock: A pointer to a #BonoboDock widget
* @enable: Specifies whether floating items are allowed in this dock
*
* Description: Enable or disable floating items on @dock, according
* to @enable.
**/
void
bonobo_dock_allow_floating_items (BonoboDock *dock,
gboolean enable)
{
dock->floating_items_allowed = enable;
}
static GList **
get_band_list (BonoboDock *dock, BonoboDockPlacement placement)
{
GList **band_ptr = NULL;
switch (placement)
{
case BONOBO_DOCK_TOP:
band_ptr = &dock->top_bands;
break;
case BONOBO_DOCK_BOTTOM:
band_ptr = &dock->bottom_bands;
break;
case BONOBO_DOCK_LEFT:
band_ptr = &dock->left_bands;
break;
case BONOBO_DOCK_RIGHT:
band_ptr = &dock->right_bands;
break;
default:
break;
}
return band_ptr;
}
/**
* bonobo_dock_add_item:
* @dock: A pointer to a #BonoboDock widget
* @item: The item to add
* @placement: Placement for the new item
* @band_num: Number of the band the new item must be added to
* @position: Position of the item in the specified band
* @offset: Offset (in pixels) from the previous item in the same band
* @in_new_band: Specifies whether a new band must be created for this item
*
* Description: Add @item to @dock. @placement can be either
* %BONOBO_DOCK_TOP, %BONOBO_DOCK_RIGHT, %BONOBO_DOCK_BOTTOM or
* %BONOBO_DOCK_LEFT, and specifies what area of the dock should
* contain the item. If @in_new_band is %TRUE, a new dock band is
* created at the position specified by @band_num; otherwise, the item
* is added to the @band_num'th band.
**/
void
bonobo_dock_add_item (BonoboDock *dock,
BonoboDockItem *item,
BonoboDockPlacement placement,
guint band_num,
gint position,
guint offset,
gboolean in_new_band)
{
BonoboDockBand *band;
GList **band_ptr;
GList *p;
DEBUG (("band_num %d offset %d position %d in_new_band %d",
band_num, offset, position, in_new_band));
if (placement == BONOBO_DOCK_FLOATING)
{
g_warning ("Floating dock items not supported by `bonobo_dock_add_item'.");
return;
}
band_ptr = get_band_list (dock, placement);
g_return_if_fail (band_ptr != NULL);
p = g_list_nth (*band_ptr, band_num);
if (in_new_band || p == NULL)
{
GtkWidget *new_band;
new_band = bonobo_dock_band_new ();
/* FIXME: slow. */
if (in_new_band)
{
*band_ptr = g_list_insert (*band_ptr, new_band, band_num);
p = g_list_nth (*band_ptr, band_num);
if (p == NULL)
p = g_list_last (*band_ptr);
}
else
{
*band_ptr = g_list_append (*band_ptr, new_band);
p = g_list_last (*band_ptr);
}
if (placement == BONOBO_DOCK_TOP || placement == BONOBO_DOCK_BOTTOM)
bonobo_dock_band_set_orientation (BONOBO_DOCK_BAND (new_band),
GTK_ORIENTATION_HORIZONTAL);
else
bonobo_dock_band_set_orientation (BONOBO_DOCK_BAND (new_band),
GTK_ORIENTATION_VERTICAL);
gtk_widget_set_parent (new_band, GTK_WIDGET (dock));
gtk_widget_show (new_band);
gtk_widget_queue_resize (GTK_WIDGET (dock));
}
band = BONOBO_DOCK_BAND (p->data);
bonobo_dock_band_insert (band, GTK_WIDGET(item), offset, position);
connect_drag_signals (dock, GTK_WIDGET(item));
g_signal_emit (dock, dock_signals[LAYOUT_CHANGED], 0);
}
/**
* bonobo_dock_add_floating_item:
* @dock: A #BonoboDock widget
* @item: The item to be added
* @x: X-coordinate for the floating item
* @y: Y-coordinate for the floating item
* @orientation: Orientation for the new item.
*
* Description: Add @item to @dock and make it floating at the
* specified (@x, @y) coordinates (relative to the root window of the
* screen).
**/
void
bonobo_dock_add_floating_item (BonoboDock *dock,
BonoboDockItem *item,
gint x, gint y,
GtkOrientation orientation)
{
GtkWidget *widget;
g_return_if_fail (BONOBO_IS_DOCK_ITEM (item));
bonobo_dock_item_set_orientation (item, orientation);
widget = GTK_WIDGET(item);
gtk_widget_ref (widget);
#if 0
if (widget->parent != NULL)
gtk_container_remove (GTK_CONTAINER (widget->parent), widget);
#endif
gtk_widget_set_parent (widget, GTK_WIDGET (dock));
if (GTK_WIDGET_REALIZED (widget->parent))
gtk_widget_realize (widget);
if (GTK_WIDGET_VISIBLE (widget->parent) && GTK_WIDGET_VISIBLE (widget))
{
if (GTK_WIDGET_MAPPED (widget->parent))
gtk_widget_map (widget);
gtk_widget_queue_resize (widget);
}
bonobo_dock_item_detach (item, x, y);
dock->floating_children = g_list_prepend (dock->floating_children, widget);
connect_drag_signals (dock, widget);
gtk_widget_unref (widget);
g_signal_emit (dock, dock_signals[LAYOUT_CHANGED], 0);
}
/**
* bonobo_dock_set_client_area:
* @dock: A #BonoboDock widget
* @widget: The widget to be used for the client area.
*
* Description: Specify a widget for the dock's client area.
**/
void
bonobo_dock_set_client_area (BonoboDock *dock, GtkWidget *widget)
{
g_return_if_fail (dock != NULL);
if (widget != NULL)
gtk_widget_ref (widget);
if (dock->client_area != NULL)
gtk_widget_unparent (dock->client_area);
if (widget != NULL)
{
gtk_widget_set_parent (widget, GTK_WIDGET (dock));
dock->client_area = widget;
if (GTK_WIDGET_REALIZED (widget->parent))
gtk_widget_realize (widget);
if (GTK_WIDGET_VISIBLE (widget->parent) && GTK_WIDGET_VISIBLE (widget))
{
if (GTK_WIDGET_MAPPED (widget->parent))
gtk_widget_map (widget);
gtk_widget_queue_resize (widget);
}
}
else
{
if (dock->client_area != NULL && GTK_WIDGET_VISIBLE (dock))
gtk_widget_queue_resize (GTK_WIDGET (dock));
dock->client_area = NULL;
}
if (widget != NULL)
gtk_widget_unref (widget);
}
/**
* bonobo_dock_get_client_area:
* @dock: A #BonoboDock widget.
*
* Description: Retrieve the widget being used as the client area in
* @dock.
*
* Returns: The client area widget.
**/
GtkWidget *
bonobo_dock_get_client_area (BonoboDock *dock)
{
return dock->client_area;
}
/**
* bonobo_dock_get_item_by_name:
* @dock: A #BonoboDock widget.
* @name: The name of the dock item to retrieve
* @placement_return: A pointer to a variable holding the item's placement
* @num_band_return: A pointer to a variable holding the band number
* @band_position_return: A pointer to a variable holding the position
* of the item within the band
* @offset_return: A pointer to a variable holding the offset of the item
* from the previous item in the same band
*
* Description: Retrieve the dock item named @name; information about
* its position in the dock is returned via @placement_return,
* @num_band_return, @band_position_return and @offset_return. If
* the placement is %BONOBO_DOCK_FLOATING *@num_band_return,
* *@band_position_return and *@offset_return are not set.
*
* Returns: The named #BonoboDockItem widget, or %NULL if no item with
* such name exists.
**/
BonoboDockItem *
bonobo_dock_get_item_by_name (BonoboDock *dock,
const gchar *name,
BonoboDockPlacement *placement_return,
guint *num_band_return,
guint *band_position_return,
guint *offset_return)
{
BonoboDockItem *item;
item = get_docked_item_by_name (dock,
name,
placement_return,
num_band_return,
band_position_return,
offset_return);
if (item != NULL)
return item;
item = get_floating_item_by_name (dock, name);
if (item != NULL)
{
if (placement_return != NULL)
*placement_return = BONOBO_DOCK_FLOATING;
return item;
}
return NULL;
}
/* Layout functions. */
static void
layout_add_floating (BonoboDock *dock,
BonoboDockLayout *layout)
{
GList *lp;
for (lp = dock->floating_children; lp != NULL; lp = lp->next)
{
GtkOrientation orientation;
gint x, y;
BonoboDockItem *item;
item = BONOBO_DOCK_ITEM (lp->data);
orientation = bonobo_dock_item_get_orientation (item);
bonobo_dock_item_get_floating_position (item, &x, &y);
bonobo_dock_layout_add_floating_item (layout, item,
x, y,
orientation);
}
}
static void
layout_add_bands (BonoboDock *dock,
BonoboDockLayout *layout,
BonoboDockPlacement placement,
GList *band_list)
{
guint band_num;
GList *lp;
for (lp = band_list, band_num = 0;
lp != NULL;
lp = lp->next, band_num++)
{
BonoboDockBand *band;
band = BONOBO_DOCK_BAND(lp->data);
bonobo_dock_band_layout_add (band, layout, placement, band_num);
}
}
/**
* bonobo_dock_get_layout:
* @dock: A #BonoboDock widget
*
* Description: Retrieve the layout of @dock.
*
* Returns: @dock's layout as a #BonoboDockLayout object.
**/
BonoboDockLayout *
bonobo_dock_get_layout (BonoboDock *dock)
{
BonoboDockLayout *layout;
layout = bonobo_dock_layout_new ();
layout_add_bands (dock, layout, BONOBO_DOCK_TOP, dock->top_bands);
layout_add_bands (dock, layout, BONOBO_DOCK_BOTTOM, dock->bottom_bands);
layout_add_bands (dock, layout, BONOBO_DOCK_LEFT, dock->left_bands);
layout_add_bands (dock, layout, BONOBO_DOCK_RIGHT, dock->right_bands);
layout_add_floating (dock, layout);
return layout;
}
/**
* bonobo_dock_add_from_layout:
* @dock: The #BonoboDock widget
* @layout: A #BonoboDockLayout widget
*
* Description: Add all the items in @layout to the specified @dock.
*
* Returns: %TRUE if the operation succeeds, %FALSE if it fails.
**/
gboolean
bonobo_dock_add_from_layout (BonoboDock *dock,
BonoboDockLayout *layout)
{
return bonobo_dock_layout_add_to_dock (layout, dock);
}
static GList **
find_band_list (BonoboDock *dock,
BonoboDockBand *band,
BonoboDockPlacement *placement)
{
GList **band_list = NULL;
if (g_list_find (dock->top_bands, band))
{
*placement = BONOBO_DOCK_TOP;
band_list = &dock->top_bands;
}
if (g_list_find (dock->bottom_bands, band))
{
*placement = BONOBO_DOCK_BOTTOM;
band_list = &dock->bottom_bands;
}
if (g_list_find (dock->left_bands, band))
{
*placement = BONOBO_DOCK_LEFT;
band_list = &dock->left_bands;
}
if (g_list_find (dock->right_bands, band))
{
*placement = BONOBO_DOCK_RIGHT;
band_list = &dock->right_bands;
}
return band_list;
}
static gboolean
insert_into_band_list (BonoboDock *dock,
GList **band_list,
GtkOrientation orientation,
BonoboDockItem *item,
gboolean prepend)
{
GtkWidget *new_band;
new_band = bonobo_dock_band_new ();
if (item->behavior & BONOBO_DOCK_ITEM_BEH_NEVER_VERTICAL)
orientation = GTK_ORIENTATION_HORIZONTAL;
if (item->behavior & BONOBO_DOCK_ITEM_BEH_NEVER_HORIZONTAL)
orientation = GTK_ORIENTATION_VERTICAL;
if (!bonobo_dock_band_append (
BONOBO_DOCK_BAND (new_band), GTK_WIDGET (item), 0))
return FALSE;
if (prepend)
*band_list = g_list_prepend (*band_list, new_band);
else
*band_list = g_list_append (*band_list, new_band);
new_band_setup (dock, new_band, orientation);
return TRUE;
}
gint
_bonobo_dock_handle_key_nav (BonoboDock *dock,
BonoboDockBand *band,
BonoboDockItem *item,
GdkEventKey *event)
{
GList *entry;
GList **band_list;
int cross_band_dir = 0;
int switch_side_dir = 0;
gboolean end_stop = FALSE;
gboolean was_inserted = FALSE;
GtkOrientation orientation;
BonoboDockPlacement placement;
if (!(event->state & GDK_CONTROL_MASK))
return FALSE;
switch (event->keyval)
{
case GDK_Up:
cross_band_dir = -1;
break;
case GDK_Down:
cross_band_dir = +1;
break;
case GDK_Left:
switch_side_dir = -1;
break;
case GDK_Right:
switch_side_dir = +1;
break;
default:
return FALSE;
}
band_list = find_band_list (dock, band, &placement);
g_return_val_if_fail (band_list != NULL, FALSE);
if (placement == BONOBO_DOCK_LEFT ||
placement == BONOBO_DOCK_RIGHT)
{
int tmp = switch_side_dir;
switch_side_dir = cross_band_dir;
cross_band_dir = tmp;
orientation = GTK_ORIENTATION_VERTICAL;
}
else
{
orientation = GTK_ORIENTATION_HORIZONTAL;
}
g_object_ref (G_OBJECT (item));
gtk_container_remove (GTK_CONTAINER (band), GTK_WIDGET (item));
/*
* Find somewhere new for it ...
*/
entry = g_list_find (*band_list, band);
g_return_val_if_fail (entry != NULL, FALSE);
if (cross_band_dir == -1)
{
for (entry = entry->prev; !was_inserted && entry;
entry = entry->prev)
was_inserted = bonobo_dock_band_append (
entry->data, GTK_WIDGET (item), 0);
if (!was_inserted &&
((*band_list)->data != band ||
bonobo_dock_band_get_num_children (band) > 0))
{
was_inserted = insert_into_band_list (
dock, band_list, orientation, item, TRUE);
}
if (!was_inserted)
{
if (placement == BONOBO_DOCK_BOTTOM)
{
was_inserted = insert_into_band_list (
dock, &dock->top_bands, orientation, item, FALSE);
}
else if (placement == BONOBO_DOCK_RIGHT)
{
was_inserted = insert_into_band_list (
dock, &dock->left_bands, orientation, item, FALSE);
}
else
end_stop = TRUE;
}
}
if (cross_band_dir == +1)
{
for (entry = entry->next; !was_inserted && entry;
entry = entry->next)
was_inserted = bonobo_dock_band_append (
entry->data, GTK_WIDGET (item), 0);
if (!was_inserted &&
(g_list_last (*band_list)->data != band ||
bonobo_dock_band_get_num_children (band) > 0))
{
was_inserted = insert_into_band_list (
dock, band_list, orientation, item, FALSE);
}
if (!was_inserted)
{
if (placement == BONOBO_DOCK_TOP)
{
was_inserted = insert_into_band_list (
dock, &dock->bottom_bands, orientation, item, TRUE);
}
else if (placement == BONOBO_DOCK_LEFT)
{
was_inserted = insert_into_band_list (
dock, &dock->right_bands, orientation, item, TRUE);
}
else
end_stop = TRUE;
}
}
if (!end_stop && !was_inserted)
{
orientation = (orientation == GTK_ORIENTATION_HORIZONTAL) ?
GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL;
if (placement == BONOBO_DOCK_TOP ||
placement == BONOBO_DOCK_BOTTOM)
{
if (switch_side_dir == -1)
was_inserted = insert_into_band_list (
dock, &dock->left_bands, orientation, item, FALSE);
else
was_inserted = insert_into_band_list (
dock, &dock->right_bands, orientation, item, TRUE);
}
else
{
if (switch_side_dir == -1)
was_inserted = insert_into_band_list (
dock, &dock->top_bands, orientation, item, FALSE);
else
was_inserted = insert_into_band_list (
dock, &dock->bottom_bands, orientation, item, TRUE);
}
}
if (!was_inserted)
{ /* geometry issue */
if (!bonobo_dock_band_append (band, GTK_WIDGET (item), 0))
g_error ("no space in fallback original band");
}
if (bonobo_dock_band_get_num_children (band) == 0)
gtk_widget_destroy (GTK_WIDGET (band));
g_object_unref (item);
return TRUE;
}