/* Glimmer - gdstearaway.c * * This 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. * * 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 Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include "gdstearaway.h" #define GUTTER_SIZE 14 #define GUTTER_WIDTH 10 #define FRAME_THICK 4 /* function declarations */ static void gds_tear_away_class_init (GdsTearAwayClass * klass); static void gds_tear_away_init (GdsTearAway * tear_away); GtkWidget *gds_tear_away_new (GtkPositionType pos); void gds_tear_away_destroy (GtkObject * object); static gint gds_tear_away_button_press (GtkWidget * widget, GdkEventButton * event); static gint gds_tear_away_button_release (GtkWidget * widget, GdkEventButton * event); static void gds_tear_away_realize (GtkWidget * widget); static void gds_tear_away_map (GtkWidget * widget); static void gds_tear_away_unmap (GtkWidget * widget); static void gds_tear_away_size_allocate (GtkWidget * widget, GtkAllocation * allocation); static void gds_tear_away_size_request (GtkWidget * widget, GtkRequisition * requisition); static void gds_tear_away_paint (GtkWidget * widget, GdkRectangle * area); static void gds_tear_away_draw (GtkWidget * widget, GdkRectangle * area); static gint gds_tear_away_expose (GtkWidget * widget, GdkEventExpose * event); static gint gds_tear_away_close_event (GtkWidget * widget, GdkEventAny * event); static void gds_tear_away_window_focused (GtkWidget * widget, gpointer data); /* end function declarations */ typedef enum { WINDOW_FOCUSED, DETACHING, DETACHED, ATTACHING, ATTACHED, LAST_SIGNAL } GdsTearAwayEvent; static GtkBinClass *parent_class = NULL; static guint tear_away_signals[LAST_SIGNAL] = { 0 }; GtkType gds_tear_away_get_type (void) { static GtkType tear_away_type = 0; if (!tear_away_type) { static const GtkTypeInfo tear_away_info = { "GdsTearAway", sizeof (GdsTearAway), sizeof (GdsTearAwayClass), (GtkClassInitFunc) gds_tear_away_class_init, (GtkObjectInitFunc) gds_tear_away_init, NULL, NULL, (GtkClassInitFunc) NULL, }; tear_away_type = gtk_type_unique (GTK_TYPE_BIN, &tear_away_info); } return (tear_away_type); } static void gds_tear_away_class_init (GdsTearAwayClass * klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; GtkContainerClass *container_class; object_class = (GtkObjectClass *) klass; widget_class = (GtkWidgetClass *) klass; container_class = (GtkContainerClass *) klass; parent_class = gtk_type_class (GTK_TYPE_BIN); object_class->destroy = gds_tear_away_destroy; tear_away_signals[WINDOW_FOCUSED] = gtk_signal_new ("window_focused", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GdsTearAwayClass, window_focused), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); tear_away_signals[DETACHING] = gtk_signal_new ("detaching", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GdsTearAwayClass, detaching), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); tear_away_signals[DETACHED] = gtk_signal_new ("detached", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GdsTearAwayClass, detached), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); tear_away_signals[ATTACHING] = gtk_signal_new ("attaching", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GdsTearAwayClass, attaching), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); tear_away_signals[ATTACHED] = gtk_signal_new ("attached", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (GdsTearAwayClass, attached), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, tear_away_signals, LAST_SIGNAL); widget_class->activate_signal = tear_away_signals[WINDOW_FOCUSED]; widget_class->realize = gds_tear_away_realize; widget_class->size_allocate = gds_tear_away_size_allocate; widget_class->size_request = gds_tear_away_size_request; widget_class->map = gds_tear_away_map; widget_class->unmap = gds_tear_away_unmap; widget_class->draw = gds_tear_away_draw; widget_class->expose_event = gds_tear_away_expose; widget_class->button_press_event = gds_tear_away_button_press; widget_class->button_release_event = gds_tear_away_button_release; } static void gds_tear_away_init (GdsTearAway * tear_away) { GTK_WIDGET_UNSET_FLAGS (tear_away, GTK_NO_WINDOW); GTK_WIDGET_SET_FLAGS (tear_away, GTK_CAN_FOCUS); tear_away->new_parent = NULL; tear_away->title = NULL; tear_away->label_string = NULL; tear_away->torn = FALSE; tear_away->torn = GTK_POS_LEFT; } GtkWidget * gds_tear_away_new (GtkPositionType pos) { GdsTearAway *tear_away; tear_away = gtk_type_new (GDS_TYPE_TEAR_AWAY); tear_away->pos = pos; return GTK_WIDGET (tear_away); } static void gds_tear_away_realize (GtkWidget * widget) { GdsTearAway *tear_away; GdkWindowAttr attributes; gint attributes_mask; g_return_if_fail (widget != NULL); g_return_if_fail (GDS_IS_TEAR_AWAY (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); tear_away = GDS_TEAR_AWAY (widget); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.event_mask = gtk_widget_get_events (widget); attributes.event_mask |= (GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK); attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; /* main window */ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gdk_window_set_user_data (widget->window, tear_away); widget->style = gtk_style_attach (widget->style, widget->window); gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); } static void gds_tear_away_map (GtkWidget * widget) { if (!GTK_WIDGET_MAPPED (widget)) { GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED); gdk_window_show (widget->window); if (GTK_BIN (widget)->child) { if (!GTK_WIDGET_MAPPED (GTK_BIN (widget)->child) && GTK_WIDGET_VISIBLE (GTK_BIN (widget)->child)) { gtk_widget_map (GTK_BIN (widget)->child); } } } } static void gds_tear_away_unmap (GtkWidget * widget) { if (GTK_WIDGET_MAPPED (widget)) { GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED); gdk_window_hide (widget->window); if (GTK_BIN (widget)->child) { if (GTK_WIDGET_MAPPED (GTK_BIN (widget)->child) && GTK_WIDGET_VISIBLE (GTK_BIN (widget)->child)) { gtk_widget_unmap (GTK_BIN (widget)->child); } } } } void gds_tear_away_destroy (GtkObject * object) { GtkWidget *tear_away; gpointer data = 0; g_return_if_fail (object != NULL); g_return_if_fail (GDS_IS_TEAR_AWAY (object)); tear_away = GTK_WIDGET (object); g_free (GDS_TEAR_AWAY (tear_away)->title); g_free (GDS_TEAR_AWAY (tear_away)->label_string); data = GDS_TEAR_AWAY (tear_away)->new_parent; gtk_widget_destroy (tear_away); if (data) gtk_widget_destroy (GTK_WIDGET (data)); } static gint gds_tear_away_button_press (GtkWidget * widget, GdkEventButton * event) { GdsTearAway *tear_away; GtkWidget *child; gboolean in_handle; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GDS_IS_TEAR_AWAY (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); if (event->window != widget->window) return (FALSE); tear_away = GDS_TEAR_AWAY (widget); child = GTK_BIN (tear_away)->child; switch (tear_away->pos) { case GTK_POS_LEFT: in_handle = event->x < GUTTER_SIZE; break; case GTK_POS_TOP: in_handle = event->y < GUTTER_SIZE; break; case GTK_POS_RIGHT: in_handle = event->x > widget->allocation.width - GUTTER_SIZE; break; case GTK_POS_BOTTOM: in_handle = event->y > widget->allocation.height - GUTTER_SIZE; break; default: in_handle = event->x < GUTTER_SIZE; break; } if (event->button == 1 && event->type == GDK_2BUTTON_PRESS && in_handle) { if (tear_away->new_parent) { gtk_signal_emit (GTK_OBJECT (tear_away), tear_away_signals[ATTACHING]); gds_tear_away_bust_a_switch (tear_away); gtk_signal_emit (GTK_OBJECT (tear_away), tear_away_signals[ATTACHED]); } else { gtk_signal_emit (GTK_OBJECT (tear_away), tear_away_signals[DETACHING]); gds_tear_away_bust_a_switch (tear_away); gtk_signal_emit (GTK_OBJECT (tear_away), tear_away_signals[DETACHED]); } return (TRUE); } return (FALSE); } static gint gds_tear_away_button_release (GtkWidget * widget, GdkEventButton * event) { return FALSE; } static void gds_tear_away_paint (GtkWidget * widget, GdkRectangle * area) { gint x = 0, y = 0; guint width = 0, height = 0; GdkRectangle ha; if (GTK_WIDGET_DRAWABLE (widget)) { gdk_window_clear_area (widget->window, area->x, area->y, area->width, area->height); switch (GDS_TEAR_AWAY (widget)->pos) { case GTK_POS_LEFT: x = FRAME_THICK / 2 + GUTTER_WIDTH; y = FRAME_THICK / 2; width = widget->allocation.width - FRAME_THICK - GUTTER_WIDTH; //500 - 12 height = widget->allocation.height - FRAME_THICK; //500 - 4 ha.x = 2 * FRAME_THICK - FRAME_THICK / 2; ha.y = 2 * y; ha.height = height - FRAME_THICK; break; case GTK_POS_RIGHT: x = FRAME_THICK / 2; y = FRAME_THICK / 2; width = widget->allocation.width - FRAME_THICK - GUTTER_WIDTH; height = widget->allocation.height - FRAME_THICK; ha.x = width + FRAME_THICK + FRAME_THICK / 2; ha.y = 2 * y; ha.height = height - FRAME_THICK; break; case GTK_POS_TOP: x = FRAME_THICK / 2; y = FRAME_THICK / 2 + GUTTER_WIDTH; width = widget->allocation.width - FRAME_THICK; height = widget->allocation.height - FRAME_THICK - GUTTER_WIDTH; ha.x = 2 * x; ha.y = 2 * FRAME_THICK - FRAME_THICK / 2; ha.width = width - FRAME_THICK; break; case GTK_POS_BOTTOM: x = FRAME_THICK / 2; y = FRAME_THICK / 2; width = widget->allocation.width - FRAME_THICK; height = widget->allocation.height - FRAME_THICK - GUTTER_WIDTH; ha.x = 2 * x; ha.y = height + FRAME_THICK + FRAME_THICK / 2; ha.width = width - FRAME_THICK; break; } } if (ha.width <= 0) ha.width = 1; if (ha.height <= 0) ha.height = 1; gtk_paint_shadow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, area, widget, "frame", 0, 0, widget->allocation.width, widget->allocation.height); gtk_paint_shadow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, area, widget, "frame", x, y, width, height); if (GDS_TEAR_AWAY (widget)->pos == GTK_POS_TOP || GDS_TEAR_AWAY (widget)->pos == GTK_POS_BOTTOM) gtk_paint_hline (widget->style, widget->window, GTK_STATE_PRELIGHT, area, widget, "vscale", ha.x, ha.x + ha.width, ha.y); else gtk_paint_vline (widget->style, widget->window, GTK_STATE_PRELIGHT, area, widget, "vscale", ha.y, ha.y + ha.height, ha.x); } static void gds_tear_away_draw (GtkWidget * widget, GdkRectangle * area) { GtkBin *bin; GdkRectangle child_area; g_return_if_fail (widget != NULL); g_return_if_fail (GDS_IS_TEAR_AWAY (widget)); g_return_if_fail (area != NULL); bin = GTK_BIN (widget); if (GTK_WIDGET_DRAWABLE (widget)) { gds_tear_away_paint (widget, area); if (bin->child && gtk_widget_intersect (bin->child, area, &child_area)) gtk_widget_draw (bin->child, &child_area); } } static gint gds_tear_away_expose (GtkWidget * widget, GdkEventExpose * event) { GtkBin *bin; GdkEventExpose child_event; GdkRectangle *area; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GDS_IS_TEAR_AWAY (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); area = &event->area; bin = GTK_BIN (widget); if (GTK_WIDGET_DRAWABLE (widget)) { gds_tear_away_paint (widget, area); child_event = *event; if (bin->child && GTK_WIDGET_NO_WINDOW (bin->child) && gtk_widget_intersect (bin->child, &event->area, &child_event.area)) gtk_widget_event (bin->child, (GdkEvent *) & child_event); } return FALSE; } static void gds_tear_away_size_request (GtkWidget * widget, GtkRequisition * requisition) { GtkBin *bin; GdsTearAway *tear_away; GtkRequisition child_requisition; g_return_if_fail (widget != NULL); g_return_if_fail (GDS_IS_TEAR_AWAY (widget)); g_return_if_fail (requisition != NULL); tear_away = GDS_TEAR_AWAY (widget); bin = GTK_BIN (widget); requisition->width = 2 * FRAME_THICK; requisition->height = 2 * FRAME_THICK; if (bin->child) { gtk_widget_size_request (bin->child, &child_requisition); requisition->width += GTK_WIDGET_VISIBLE (bin->child) && child_requisition.width ? child_requisition.width : 20; requisition->height += GTK_WIDGET_VISIBLE (bin->child) && child_requisition.height ? child_requisition.height : 20; } if (tear_away->pos == GTK_POS_TOP || tear_away->pos == GTK_POS_BOTTOM) requisition->height += GUTTER_WIDTH; else requisition->width += GUTTER_WIDTH; } static void gds_tear_away_size_allocate (GtkWidget * widget, GtkAllocation * allocation) { GdsTearAway *tear_away; GtkBin *bin; GtkAllocation child_allocation; g_return_if_fail (widget != NULL); g_return_if_fail (GDS_IS_TEAR_AWAY (widget)); g_return_if_fail (allocation != NULL); tear_away = GDS_TEAR_AWAY (widget); bin = GTK_BIN (widget); widget->allocation = *allocation; if (GTK_WIDGET_REALIZED (widget)) { gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); } if (bin->child) { child_allocation.x = FRAME_THICK; child_allocation.y = FRAME_THICK; child_allocation.width = allocation->width - 2 * FRAME_THICK > 1 ? allocation->width - 2 * FRAME_THICK : 1; child_allocation.height = allocation->height - 2 * FRAME_THICK > 1 ? allocation->height - 2 * FRAME_THICK : 1; switch (tear_away->pos) { case GTK_POS_LEFT: child_allocation.x += GUTTER_WIDTH; case GTK_POS_RIGHT: child_allocation.width -= ((child_allocation.width - GUTTER_WIDTH) > 1) ? GUTTER_WIDTH : 0; break; case GTK_POS_TOP: child_allocation.y += GUTTER_WIDTH; case GTK_POS_BOTTOM: child_allocation.height -= ((child_allocation.height - GUTTER_WIDTH) > 1) ? GUTTER_WIDTH : 0; break; } gtk_widget_size_allocate (bin->child, &child_allocation); } } void gds_tear_away_child_reverse_visible (GdsTearAway * tear_away) { GtkBin *bin; g_return_if_fail (tear_away != NULL); g_return_if_fail (GDS_IS_TEAR_AWAY (tear_away)); bin = GTK_BIN (tear_away); if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) { gtk_widget_hide (bin->child); } else { gtk_widget_show (bin->child); } } void gds_tear_away_bust_a_switch (GdsTearAway * tear_away) { GtkWidget *label; GtkWidget *temp; gchar *string; g_return_if_fail (tear_away != NULL); g_return_if_fail (GDS_IS_TEAR_AWAY (tear_away)); if (tear_away->new_parent != NULL) { temp = gtk_object_get_data (GTK_OBJECT (tear_away->new_parent), "return_data"); if (GTK_IS_WIDGET (temp)) { gtk_widget_ref (GTK_WIDGET (tear_away)); gtk_container_remove (GTK_CONTAINER (tear_away->new_parent), GTK_WIDGET (tear_away)); if (tear_away->parent_type == GTK_TYPE_CONT) gtk_container_add (GTK_CONTAINER (temp), GTK_WIDGET (tear_away)); else { label = gtk_label_new (tear_away->label_string); gtk_widget_show (label); gtk_notebook_insert_page (GTK_NOTEBOOK (temp), GTK_WIDGET (tear_away), label, tear_away->parent_position); } gtk_widget_unref (GTK_WIDGET (tear_away)); gtk_widget_destroy (tear_away->new_parent); tear_away->new_parent = NULL; tear_away->torn = FALSE; tear_away->parent_position = -1; } } else { gint height = 0, width = 0; gdk_window_get_size (GTK_WIDGET (tear_away)->window, &width, &height); gtk_widget_ref (GTK_WIDGET (tear_away)); tear_away->new_parent = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_container_set_border_width (GTK_CONTAINER (tear_away->new_parent), 1); temp = GTK_WIDGET (tear_away)->parent; gtk_object_set_data (GTK_OBJECT (tear_away->new_parent), "return_data", (gpointer) temp); if (GTK_IS_NOTEBOOK (temp)) { tear_away->parent_type = GTK_TYPE_BOOK; tear_away->parent_position = gtk_notebook_page_num (GTK_NOTEBOOK (temp), GTK_WIDGET (tear_away)); label = gtk_notebook_get_tab_label (GTK_NOTEBOOK (temp), GTK_WIDGET (tear_away)); if (GTK_IS_LABEL (label)) { g_free (tear_away->label_string); gtk_label_get (GTK_LABEL (label), &string); tear_away->label_string = g_strdup (string); } } else { tear_away->parent_type = GTK_TYPE_CONT; tear_away->parent_position = -1; } gtk_container_remove (GTK_CONTAINER (GTK_WIDGET (tear_away)->parent), GTK_WIDGET (tear_away)); gtk_container_add (GTK_CONTAINER (tear_away->new_parent), GTK_WIDGET (tear_away)); gtk_widget_unref (GTK_WIDGET (tear_away)); gtk_window_set_policy (GTK_WINDOW (tear_away->new_parent), FALSE, TRUE, TRUE); if (tear_away->title) gtk_window_set_title (GTK_WINDOW (tear_away->new_parent), tear_away->title); gtk_widget_show (tear_away->new_parent); gtk_signal_connect (GTK_OBJECT (tear_away->new_parent), "focus_in_event", GTK_SIGNAL_FUNC (gds_tear_away_window_focused), NULL); gtk_signal_connect (GTK_OBJECT (tear_away->new_parent), "delete_event", GTK_SIGNAL_FUNC (gds_tear_away_close_event), NULL); tear_away->torn = TRUE; if (tear_away->label_string) gds_tear_away_set_title (tear_away, tear_away->label_string); gtk_widget_set_usize (tear_away->new_parent, width + 2, height + 2); } } static gint gds_tear_away_close_event (GtkWidget * widget, GdkEventAny * event) { GdsTearAway *tear_away; tear_away = (GdsTearAway *) GTK_BIN (widget)->child; gds_tear_away_bust_a_switch (tear_away); return (TRUE); } static void gds_tear_away_window_focused (GtkWidget * widget, gpointer data) { if (!GTK_WIDGET_HAS_FOCUS (GTK_BIN (widget)->child)) gtk_signal_emit (GTK_OBJECT (GTK_BIN (widget)->child), tear_away_signals[WINDOW_FOCUSED]); } void gds_tear_away_set_child (GdsTearAway * tear_away, GtkWidget * child) { g_return_if_fail (tear_away != NULL); g_return_if_fail (GDS_IS_TEAR_AWAY (tear_away)); g_return_if_fail (child != NULL); if (GTK_BIN (tear_away)->child != NULL) { gtk_container_remove (GTK_CONTAINER (tear_away), GTK_BIN (tear_away)->child); gtk_widget_queue_resize (GTK_WIDGET (tear_away)); } gtk_container_add (GTK_CONTAINER (tear_away), child); } void gds_tear_away_set_title (GdsTearAway * tear_away, const gchar * title) { g_return_if_fail (tear_away != NULL); g_return_if_fail (GDS_IS_TEAR_AWAY (tear_away)); g_free (tear_away->title); tear_away->title = g_new (char, strlen (title) + 1); strcpy (tear_away->title, title); if (tear_away->torn) gtk_window_set_title (GTK_WINDOW (GTK_WIDGET (tear_away)->parent), title); }