/* * moobigpaned.c * * Copyright (C) 2004-2010 by Yevgen Muntyan * * This file is part of medit. medit is free software; you can * redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public * License along with medit. If not, see . */ /** * class:MooBigPaned: (parent GtkFrame) (constructable) (moo.private 1) **/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "moobigpaned.h" #include "marshals.h" #include #include #include #ifdef MOO_COMPILATION #include "mooutils-misc.h" #include "moocompat.h" #endif typedef struct { GSList *order; /* ids */ int size; int sticky; char *active; } MooPanedConfig; typedef struct { MooPanedConfig paned[4]; GHashTable *panes; } MooBigPanedConfig; typedef struct { int bbox_size; GdkRegion *bbox_region; GdkRegion *def_region; } DZ; struct _MooBigPanedPrivate { MooPanePosition order[4]; /* inner is paned[order[3]] */ GtkWidget *inner; GtkWidget *outer; MooBigPanedConfig *config; GHashTable *panes; int drop_pos; int drop_button_index; GdkRectangle drop_rect; GdkRectangle drop_button_rect; GdkWindow *drop_outline; DZ *dz; GdkRegion *drop_region; guint drop_region_is_buttons : 1; }; static void moo_big_paned_finalize (GObject *object); static void moo_big_paned_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void moo_big_paned_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static gboolean moo_big_paned_expose (GtkWidget *widget, GdkEventExpose *event, MooBigPaned *paned); static void child_set_pane_size (GtkWidget *child, int size, MooBigPaned *paned); static void sticky_pane_notify (GtkWidget *child, GParamSpec *pspec, MooBigPaned *paned); static void active_pane_notify (GtkWidget *child, GParamSpec *pspec, MooBigPaned *paned); static gboolean check_children_order (MooBigPaned *paned); static void handle_drag_start (MooPaned *child, GtkWidget *pane_widget, MooBigPaned *paned); static void handle_drag_motion (MooPaned *child, GtkWidget *pane_widget, MooBigPaned *paned); static void handle_drag_end (MooPaned *child, GtkWidget *pane_widget, gboolean drop, MooBigPaned *paned); static void config_changed (MooBigPaned *paned); static MooBigPanedConfig *config_new (void); static void config_free (MooBigPanedConfig *config); static MooBigPanedConfig *config_parse (const char *string); static char *config_serialize (MooBigPanedConfig *config); /* MOO_TYPE_BIG_PANED */ G_DEFINE_TYPE (MooBigPaned, moo_big_paned, GTK_TYPE_FRAME) enum { PROP_0, PROP_PANE_ORDER, PROP_ENABLE_HANDLE_DRAG, PROP_ENABLE_DETACHING, PROP_HANDLE_CURSOR_TYPE }; enum { CONFIG_CHANGED, NUM_SIGNALS }; static guint signals[NUM_SIGNALS]; static void moo_big_paned_class_init (MooBigPanedClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (MooBigPanedPrivate)); gobject_class->finalize = moo_big_paned_finalize; gobject_class->set_property = moo_big_paned_set_property; gobject_class->get_property = moo_big_paned_get_property; g_object_class_install_property (gobject_class, PROP_PANE_ORDER, g_param_spec_pointer ("pane-order", "pane-order", "pane-order", (GParamFlags) G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ENABLE_HANDLE_DRAG, g_param_spec_boolean ("enable-handle-drag", "enable-handle-drag", "enable-handle-drag", TRUE, (GParamFlags) (G_PARAM_CONSTRUCT | G_PARAM_WRITABLE))); g_object_class_install_property (gobject_class, PROP_ENABLE_DETACHING, g_param_spec_boolean ("enable-detaching", "enable-detaching", "enable-detaching", FALSE, (GParamFlags) (G_PARAM_CONSTRUCT | G_PARAM_WRITABLE))); g_object_class_install_property (gobject_class, PROP_HANDLE_CURSOR_TYPE, g_param_spec_enum ("handle-cursor-type", "handle-cursor-type", "handle-cursor-type", GDK_TYPE_CURSOR_TYPE, GDK_HAND2, (GParamFlags) (G_PARAM_CONSTRUCT | G_PARAM_READWRITE))); signals[CONFIG_CHANGED] = g_signal_new ("config-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, _moo_marshal_VOID__VOID, G_TYPE_NONE, 0); } #define NTH_CHILD(paned,n) paned->paned[paned->priv->order[n]] static void moo_big_paned_init (MooBigPaned *paned) { int i; paned->priv = G_TYPE_INSTANCE_GET_PRIVATE (paned, MOO_TYPE_BIG_PANED, MooBigPanedPrivate); paned->priv->panes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); paned->priv->config = config_new (); paned->priv->drop_pos = -1; /* XXX destroy */ for (i = 0; i < 4; ++i) { GtkWidget *child; paned->paned[i] = child = GTK_WIDGET (g_object_new (MOO_TYPE_PANED, "pane-position", (MooPanePosition) i, (const char*) NULL)); g_object_ref_sink (child); gtk_widget_show (child); g_signal_connect_after (child, "set-pane-size", G_CALLBACK (child_set_pane_size), paned); g_signal_connect (child, "notify::sticky-pane", G_CALLBACK (sticky_pane_notify), paned); g_signal_connect (child, "notify::active-pane", G_CALLBACK (active_pane_notify), paned); g_signal_connect (child, "handle-drag-start", G_CALLBACK (handle_drag_start), paned); g_signal_connect (child, "handle-drag-motion", G_CALLBACK (handle_drag_motion), paned); g_signal_connect (child, "handle-drag-end", G_CALLBACK (handle_drag_end), paned); } paned->priv->order[0] = MOO_PANE_POS_LEFT; paned->priv->order[1] = MOO_PANE_POS_RIGHT; paned->priv->order[2] = MOO_PANE_POS_TOP; paned->priv->order[3] = MOO_PANE_POS_BOTTOM; paned->priv->inner = NTH_CHILD (paned, 3); paned->priv->outer = NTH_CHILD (paned, 0); gtk_container_add (GTK_CONTAINER (paned), NTH_CHILD (paned, 0)); for (i = 0; i < 3; ++i) gtk_container_add (GTK_CONTAINER (NTH_CHILD (paned, i)), NTH_CHILD (paned, i+1)); g_assert (check_children_order (paned)); } static gboolean check_children_order (MooBigPaned *paned) { int i; if (GTK_BIN (paned)->child != NTH_CHILD (paned, 0)) return FALSE; for (i = 0; i < 3; ++i) if (GTK_BIN (NTH_CHILD (paned, i))->child != NTH_CHILD (paned, i+1)) return FALSE; return TRUE; } void moo_big_paned_set_pane_order (MooBigPaned *paned, int *order) { MooPanePosition new_order[4]; int i; GtkWidget *child; g_return_if_fail (MOO_IS_BIG_PANED (paned)); g_return_if_fail (order != NULL); for (i = 0; i < 4; ++i) { g_return_if_fail (0 <= order[i] && order[i] < 4); new_order[i] = (MooPanePosition) order[i]; } g_return_if_fail (check_children_order (paned)); for (i = 0; i < 4; ++i) { if (new_order[i] != paned->priv->order[i]) break; } if (i == 4) return; child = moo_big_paned_get_child (paned); if (child) g_object_ref (child); gtk_container_remove (GTK_CONTAINER (paned), NTH_CHILD (paned, 0)); for (i = 0; i < 3; ++i) gtk_container_remove (GTK_CONTAINER (NTH_CHILD (paned, i)), NTH_CHILD (paned, i+1)); if (child) gtk_container_remove (GTK_CONTAINER (NTH_CHILD (paned, 3)), child); for (i = 0; i < 4; ++i) paned->priv->order[i] = new_order[i]; gtk_container_add (GTK_CONTAINER (paned), NTH_CHILD (paned, 0)); for (i = 0; i < 3; ++i) gtk_container_add (GTK_CONTAINER (NTH_CHILD (paned, i)), NTH_CHILD (paned, i+1)); paned->priv->inner = NTH_CHILD (paned, 3); paned->priv->outer = NTH_CHILD (paned, 0); if (child) { gtk_container_add (GTK_CONTAINER (paned->priv->inner), child); g_object_unref (child); } g_assert (check_children_order (paned)); g_object_notify (G_OBJECT (paned), "pane-order"); } static void moo_big_paned_finalize (GObject *object) { MooBigPaned *paned = MOO_BIG_PANED (object); int i; g_hash_table_destroy (paned->priv->panes); config_free (paned->priv->config); for (i = 0; i < 4; ++i) g_object_unref (paned->paned[i]); if (paned->priv->drop_outline) { g_critical ("oops"); gdk_window_set_user_data (paned->priv->drop_outline, NULL); gdk_window_destroy (paned->priv->drop_outline); } G_OBJECT_CLASS (moo_big_paned_parent_class)->finalize (object); } GtkWidget * moo_big_paned_new (void) { return GTK_WIDGET (g_object_new (MOO_TYPE_BIG_PANED, (const char*) NULL)); } static void config_changed (MooBigPaned *paned) { g_signal_emit (paned, signals[CONFIG_CHANGED], 0); } static void child_set_pane_size (GtkWidget *child, int size, MooBigPaned *paned) { MooPanePosition pos; g_object_get (child, "pane-position", &pos, NULL); g_return_if_fail (paned->paned[pos] == child); paned->priv->config->paned[pos].size = size; config_changed (paned); } static void sticky_pane_notify (GtkWidget *child, G_GNUC_UNUSED GParamSpec *pspec, MooBigPaned *paned) { MooPanePosition pos; gboolean sticky; g_object_get (child, "pane-position", &pos, "sticky-pane", &sticky, NULL); g_return_if_fail (paned->paned[pos] == child); if (paned->priv->config->paned[pos].sticky != sticky) { paned->priv->config->paned[pos].sticky = sticky; config_changed (paned); } } static void active_pane_notify (GtkWidget *child, G_GNUC_UNUSED GParamSpec *pspec, MooBigPaned *paned) { MooPanePosition pos; MooPane *pane = NULL; const char *id = NULL; MooPanedConfig *pc; g_object_get (child, "pane-position", &pos, "active-pane", &pane, NULL); g_return_if_fail (paned->paned[pos] == child); if (pane) id = moo_pane_get_id (pane); pc = &paned->priv->config->paned[pos]; if ((id && !pc->active) || (!id && pc->active) || (id && pc->active && strcmp (id, pc->active) != 0)) { g_free (pc->active); pc->active = g_strdup (id); config_changed (paned); } if (pane) g_object_unref (pane); } static void pane_params_changed (MooPane *pane, G_GNUC_UNUSED GParamSpec *pspec, MooBigPaned *paned) { const char *id; MooPaneParams *params; id = moo_pane_get_id (pane); g_return_if_fail (id != NULL); params = moo_pane_get_params (pane); g_hash_table_insert (paned->priv->config->panes, g_strdup (id), params); config_changed (paned); } static MooPaneParams * get_pane_config (MooBigPaned *paned, const char *id, MooPanePosition *position, int *index) { MooPaneParams *params; int pos; gboolean found = FALSE; g_return_val_if_fail (id != NULL, NULL); if (!(params = (MooPaneParams*) g_hash_table_lookup (paned->priv->config->panes, id))) return NULL; for (pos = 0; pos < 4 && !found; ++pos) { GSList *link; int index_here = 0; for (link = paned->priv->config->paned[pos].order; link != NULL && !found; link = link->next) { if (strcmp ((const char*) link->data, id) == 0) { *index = index_here; *position = (MooPanePosition) pos; found = TRUE; } else if (g_hash_table_lookup (paned->priv->panes, link->data)) { index_here += 1; } } } if (!found) { g_critical ("%s: oops", G_STRFUNC); params = NULL; } return params; } static void add_pane_id_to_list (MooBigPaned *paned, MooPanedConfig *pc, const char *id, int index) { GSList *link; int real_index; g_return_if_fail (index >= 0); for (link = pc->order, real_index = 0; link != NULL; link = link->next, ++real_index) { if (index == 0) break; if (g_hash_table_lookup (paned->priv->panes, link->data)) index -= 1; } pc->order = g_slist_insert (pc->order, g_strdup (id), real_index); } static void add_pane_config (MooBigPaned *paned, const char *id, MooPane *pane, MooPanePosition pos, int index) { MooPaneParams *params; g_return_if_fail (index >= 0); g_return_if_fail (!g_hash_table_lookup (paned->priv->config->panes, id)); params = moo_pane_get_params (pane); g_hash_table_insert (paned->priv->config->panes, g_strdup (id), params); add_pane_id_to_list (paned, &paned->priv->config->paned[pos], id, index); config_changed (paned); } static void move_pane_config (MooBigPaned *paned, const char *id, MooPanePosition old_pos, MooPanePosition new_pos, int new_index) { GSList *old_link; MooBigPanedConfig *config = paned->priv->config; MooPanedConfig *old_pc; g_return_if_fail (new_index >= 0); g_return_if_fail (g_hash_table_lookup (config->panes, id) != NULL); old_pc = &config->paned[old_pos]; old_link = g_slist_find_custom (old_pc->order, id, (GCompareFunc) strcmp); g_return_if_fail (old_link != NULL); g_free (old_link->data); old_pc->order = g_slist_delete_link (old_pc->order, old_link); add_pane_id_to_list (paned, &paned->priv->config->paned[new_pos], id, new_index); config_changed (paned); } void moo_big_paned_set_config (MooBigPaned *paned, const char *string) { MooBigPanedConfig *config; int pos; g_return_if_fail (MOO_IS_BIG_PANED (paned)); if (!string || !string[0]) return; for (pos = 0; pos < 4; ++pos) { if (paned->priv->config->paned[pos].order != NULL) { g_critical ("%s: this function may be called only once " "and before any panes are added", G_STRFUNC); return; } } if (g_hash_table_size (paned->priv->config->panes) != 0) { g_critical ("%s: this function may be called only once " "and before any panes are added", G_STRFUNC); return; } if (!(config = config_parse (string))) return; config_free (paned->priv->config); paned->priv->config = config; for (pos = 0; pos < 4; ++pos) { if (config->paned[pos].size > 0) moo_paned_set_pane_size (MOO_PANED (paned->paned[pos]), config->paned[pos].size); if (config->paned[pos].sticky >= 0) moo_paned_set_sticky_pane (MOO_PANED (paned->paned[pos]), config->paned[pos].sticky); } } char * moo_big_paned_get_config (MooBigPaned *paned) { g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); return config_serialize (paned->priv->config); } MooPane * moo_big_paned_insert_pane (MooBigPaned *paned, GtkWidget *pane_widget, const char *id, MooPaneLabel *pane_label, MooPanePosition position, int index) { MooPane *pane; MooPaneParams *params = NULL; g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); g_return_val_if_fail (GTK_IS_WIDGET (pane_widget), NULL); g_return_val_if_fail (position < 4, NULL); if (id && moo_big_paned_lookup_pane (paned, id) != NULL) { g_critical ("%s: pane with id '%s' already exists", G_STRFUNC, id); return NULL; } if (id) params = get_pane_config (paned, id, &position, &index); if (index < 0) index = moo_paned_n_panes (MOO_PANED (paned->paned[position])); pane = moo_paned_insert_pane (MOO_PANED (paned->paned[position]), pane_widget, pane_label, index); if (pane && id) { _moo_pane_set_id (pane, id); if (params) moo_pane_set_params (pane, params); g_hash_table_insert (paned->priv->panes, g_strdup (id), g_object_ref (pane)); g_signal_connect (pane, "notify::params", G_CALLBACK (pane_params_changed), paned); if (!params) add_pane_config (paned, id, pane, position, index); if (paned->priv->config->paned[position].active && !strcmp (paned->priv->config->paned[position].active, id)) moo_pane_open (pane); } return pane; } void moo_big_paned_reorder_pane (MooBigPaned *paned, GtkWidget *pane_widget, MooPanePosition new_position, int new_index) { MooPane *pane; MooPaned *child; MooPanePosition old_position; int old_index; const char *id; g_return_if_fail (MOO_IS_BIG_PANED (paned)); g_return_if_fail (GTK_IS_WIDGET (pane_widget)); g_return_if_fail (new_position < 4); pane = moo_big_paned_find_pane (paned, pane_widget, &child); g_return_if_fail (pane != NULL); g_object_get (child, "pane-position", &old_position, NULL); old_index = moo_paned_get_pane_num (child, pane_widget); g_return_if_fail (old_index >= 0); if (new_index < 0) { if (old_position == new_position) new_index = (int) moo_paned_n_panes (child) - 1; else new_index = moo_paned_n_panes (MOO_PANED (paned->paned[new_position])); } if (old_position == new_position && old_index == new_index) return; pane = moo_paned_get_pane (child, pane_widget); if (old_position == new_position) { _moo_paned_reorder_child (child, pane, new_index); } else { g_object_ref (pane); moo_paned_remove_pane (child, pane_widget); _moo_paned_insert_pane (MOO_PANED (paned->paned[new_position]), pane, new_index); moo_pane_open (pane); g_object_unref (pane); } if ((id = moo_pane_get_id (pane))) move_pane_config (paned, id, old_position, new_position, new_index); } void moo_big_paned_add_child (MooBigPaned *paned, GtkWidget *child) { g_return_if_fail (MOO_IS_BIG_PANED (paned)); gtk_container_add (GTK_CONTAINER (paned->priv->inner), child); } void moo_big_paned_remove_child (MooBigPaned *paned) { g_return_if_fail (MOO_IS_BIG_PANED (paned)); gtk_container_remove (GTK_CONTAINER (paned->priv->inner), GTK_BIN (paned->priv->inner)->child); } GtkWidget * moo_big_paned_get_child (MooBigPaned *paned) { g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); return GTK_BIN(paned->priv->inner)->child; } gboolean moo_big_paned_remove_pane (MooBigPaned *paned, GtkWidget *widget) { MooPaned *child; MooPane *pane; const char *id; g_return_val_if_fail (MOO_IS_BIG_PANED (paned), FALSE); g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); pane = moo_big_paned_find_pane (paned, widget, &child); if (!pane) return FALSE; if ((id = moo_pane_get_id (pane))) g_hash_table_remove (paned->priv->panes, id); return moo_paned_remove_pane (child, widget); } MooPane * moo_big_paned_lookup_pane (MooBigPaned *paned, const char *pane_id) { g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); g_return_val_if_fail (pane_id != NULL, NULL); return MOO_PANE (g_hash_table_lookup (paned->priv->panes, pane_id)); } #define PROXY_FUNC(name) \ void \ moo_big_paned_##name (MooBigPaned *paned, \ GtkWidget *widget) \ { \ MooPane *pane; \ MooPaned *child = NULL; \ \ g_return_if_fail (MOO_IS_BIG_PANED (paned)); \ g_return_if_fail (GTK_IS_WIDGET (widget)); \ \ pane = moo_big_paned_find_pane (paned, widget, &child); \ g_return_if_fail (pane != NULL); \ \ moo_paned_##name (child, pane); \ } PROXY_FUNC (open_pane) PROXY_FUNC (present_pane) PROXY_FUNC (attach_pane) PROXY_FUNC (detach_pane) #undef PROXY_FUNC void moo_big_paned_hide_pane (MooBigPaned *paned, GtkWidget *widget) { MooPaned *child = NULL; g_return_if_fail (MOO_IS_BIG_PANED (paned)); g_return_if_fail (GTK_IS_WIDGET (widget)); moo_big_paned_find_pane (paned, widget, &child); g_return_if_fail (child != NULL); moo_paned_hide_pane (child); } MooPaned * moo_big_paned_get_paned (MooBigPaned *paned, MooPanePosition position) { g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); g_return_val_if_fail (position < 4, NULL); return MOO_PANED (paned->paned[position]); } MooPane * moo_big_paned_find_pane (MooBigPaned *paned, GtkWidget *widget, MooPaned **child_paned) { int i; MooPane *pane; g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); if (child_paned) *child_paned = NULL; for (i = 0; i < 4; ++i) { pane = moo_paned_get_pane (MOO_PANED (paned->paned[i]), widget); if (pane) { if (child_paned) *child_paned = MOO_PANED (paned->paned[i]); return pane; } } return NULL; } static void moo_big_paned_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MooBigPaned *paned = MOO_BIG_PANED (object); int i; switch (prop_id) { case PROP_PANE_ORDER: moo_big_paned_set_pane_order (paned, (int*) g_value_get_pointer (value)); break; case PROP_ENABLE_HANDLE_DRAG: for (i = 0; i < 4; ++i) g_object_set (paned->paned[i], "enable-handle-drag", g_value_get_boolean (value), NULL); break; case PROP_ENABLE_DETACHING: for (i = 0; i < 4; ++i) g_object_set (paned->paned[i], "enable-detaching", g_value_get_boolean (value), NULL); break; case PROP_HANDLE_CURSOR_TYPE: for (i = 0; i < 4; ++i) g_object_set (paned->paned[i], "handle-cursor-type", (GdkCursorType) g_value_get_enum (value), NULL); g_object_notify (object, "handle-cursor-type"); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void moo_big_paned_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MooBigPaned *paned = MOO_BIG_PANED (object); GdkCursorType cursor_type; switch (prop_id) { case PROP_PANE_ORDER: g_value_set_pointer (value, paned->priv->order); break; case PROP_HANDLE_CURSOR_TYPE: g_object_get (paned->paned[0], "handle-cursor-type", &cursor_type, NULL); g_value_set_enum (value, cursor_type); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } GtkWidget * moo_big_paned_get_pane (MooBigPaned *paned, MooPanePosition position, int index_) { g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); g_return_val_if_fail (position < 4, NULL); return moo_pane_get_child (moo_paned_get_nth_pane (MOO_PANED (paned->paned[position]), index_)); } /*****************************************************************************/ /* rearranging panes */ static void create_drop_outline (MooBigPaned *paned); static void get_drop_area (MooBigPaned *paned, MooPaned *active_child, MooPanePosition position, int index, GdkRectangle *rect, GdkRectangle *button_rect); // static void invalidate_drop_outline (MooBigPaned *paned); static GdkRegion * region_6 (int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int x5, int y5) { GdkPoint points[6]; points[0].x = x0; points[0].y = y0; points[1].x = x1; points[1].y = y1; points[2].x = x2; points[2].y = y2; points[3].x = x3; points[3].y = y3; points[4].x = x4; points[4].y = y4; points[5].x = x5; points[5].y = y5; return gdk_region_polygon (points, 6, GDK_WINDING_RULE); } static GdkRegion * region_4 (int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3) { GdkPoint points[4]; points[0].x = x0; points[0].y = y0; points[1].x = x1; points[1].y = y1; points[2].x = x2; points[2].y = y2; points[3].x = x3; points[3].y = y3; return gdk_region_polygon (points, 4, GDK_WINDING_RULE); } #define LEFT(rect) ((rect).x) #define RIGHT(rect) ((rect).x + (rect).width - 1) #define TOP(rect) ((rect).y) #define BOTTOM(rect) ((rect).y + (rect).height - 1) static void get_drop_zones (MooBigPaned *paned) { int pos; GdkRectangle parent; GdkRectangle child_rect; GdkRectangle button_rect; GdkRectangle drop_rect; int bbox_size[4]; g_return_if_fail (paned->priv->dz == NULL); paned->priv->dz = g_new0 (DZ, 4); for (pos = 0; pos < 4; ++pos) { bbox_size[pos] = moo_paned_get_button_box_size (MOO_PANED (paned->paned[pos])); if (bbox_size[pos] <= 0) bbox_size[pos] = 30; paned->priv->dz[pos].bbox_size = bbox_size[pos]; } parent = paned->priv->outer->allocation; child_rect = parent; child_rect.x += bbox_size[MOO_PANE_POS_LEFT]; child_rect.width -= bbox_size[MOO_PANE_POS_LEFT] + bbox_size[MOO_PANE_POS_RIGHT]; child_rect.y += bbox_size[MOO_PANE_POS_TOP]; child_rect.height -= bbox_size[MOO_PANE_POS_TOP] + bbox_size[MOO_PANE_POS_BOTTOM]; button_rect = parent; button_rect.x += 2*bbox_size[MOO_PANE_POS_LEFT]; button_rect.width -= 2*bbox_size[MOO_PANE_POS_LEFT] + 2*bbox_size[MOO_PANE_POS_RIGHT]; button_rect.y += 2*bbox_size[MOO_PANE_POS_TOP]; button_rect.height -= 2*bbox_size[MOO_PANE_POS_TOP] + 2*bbox_size[MOO_PANE_POS_BOTTOM]; drop_rect = button_rect; drop_rect.x += button_rect.width / 3; drop_rect.y += button_rect.height / 3; drop_rect.width -= 2 * button_rect.width / 3; drop_rect.height -= 2 * button_rect.height / 3; paned->priv->dz[MOO_PANE_POS_TOP].bbox_region = region_6 (LEFT (child_rect), TOP (parent), RIGHT (child_rect), TOP (parent), RIGHT (child_rect), TOP (child_rect), RIGHT (button_rect), TOP (button_rect), LEFT (button_rect), TOP (button_rect), LEFT (child_rect), TOP (child_rect)); paned->priv->dz[MOO_PANE_POS_TOP].def_region = region_4 (LEFT (button_rect), TOP (button_rect), RIGHT (button_rect), TOP (button_rect), RIGHT (drop_rect), TOP (drop_rect), LEFT (drop_rect), TOP (drop_rect)); paned->priv->dz[MOO_PANE_POS_LEFT].bbox_region = region_6 (LEFT (parent), TOP (child_rect), LEFT (child_rect), TOP (child_rect), LEFT (button_rect), TOP (button_rect), LEFT (button_rect), BOTTOM (button_rect), LEFT (child_rect), BOTTOM (child_rect), LEFT (parent), BOTTOM (child_rect)); paned->priv->dz[MOO_PANE_POS_LEFT].def_region = region_4 (LEFT (button_rect), TOP (button_rect), LEFT (drop_rect), TOP (drop_rect), LEFT (drop_rect), BOTTOM (drop_rect), LEFT (button_rect), BOTTOM (button_rect)); paned->priv->dz[MOO_PANE_POS_BOTTOM].bbox_region = region_6 (LEFT (child_rect), BOTTOM (parent), LEFT (child_rect), BOTTOM (child_rect), LEFT (button_rect), BOTTOM (button_rect), RIGHT (button_rect), BOTTOM (button_rect), RIGHT (child_rect), BOTTOM (child_rect), RIGHT (child_rect), BOTTOM (parent)); paned->priv->dz[MOO_PANE_POS_BOTTOM].def_region = region_4 (LEFT (button_rect), BOTTOM (button_rect), LEFT (drop_rect), BOTTOM (drop_rect), RIGHT (drop_rect), BOTTOM (drop_rect), RIGHT (button_rect), BOTTOM (button_rect)); paned->priv->dz[MOO_PANE_POS_RIGHT].bbox_region = region_6 (RIGHT (parent), TOP (child_rect), RIGHT (child_rect), TOP (child_rect), RIGHT (button_rect), TOP (button_rect), RIGHT (button_rect), BOTTOM (button_rect), RIGHT (child_rect), BOTTOM (child_rect), RIGHT (parent), BOTTOM (child_rect)); paned->priv->dz[MOO_PANE_POS_RIGHT].def_region = region_4 (RIGHT (button_rect), TOP (button_rect), RIGHT (drop_rect), TOP (drop_rect), RIGHT (drop_rect), BOTTOM (drop_rect), RIGHT (button_rect), BOTTOM (button_rect)); paned->priv->drop_region = NULL; paned->priv->drop_region_is_buttons = FALSE; } #undef LEFT #undef RIGHT #undef TOP #undef BOTTOM static void handle_drag_start (G_GNUC_UNUSED MooPaned *child, G_GNUC_UNUSED GtkWidget *pane_widget, MooBigPaned *paned) { g_return_if_fail (GTK_WIDGET_REALIZED (paned->priv->outer)); g_signal_connect (paned->priv->outer, "expose-event", G_CALLBACK (moo_big_paned_expose), paned); paned->priv->drop_pos = -1; get_drop_zones (paned); } static gboolean get_new_button_index (MooBigPaned *paned, MooPaned *active_child, int x, int y) { int new_button; MooPaned *child; g_assert (paned->priv->drop_pos >= 0); child = MOO_PANED (paned->paned[paned->priv->drop_pos]); new_button = _moo_paned_get_button (child, x, y, paned->priv->outer->window); if (child == active_child) { int n_buttons = moo_paned_n_panes (child); if (new_button >= n_buttons || new_button < 0) new_button = n_buttons - 1; } if (new_button != paned->priv->drop_button_index) { paned->priv->drop_button_index = new_button; return TRUE; } else { return FALSE; } } static void get_default_button_index (MooBigPaned *paned, MooPaned *active_child) { MooPaned *child; g_assert (paned->priv->drop_pos >= 0); child = MOO_PANED (paned->paned[paned->priv->drop_pos]); if (child == active_child) paned->priv->drop_button_index = _moo_paned_get_open_pane_index (child); else paned->priv->drop_button_index = -1; } static gboolean get_new_drop_position (MooBigPaned *paned, MooPaned *active_child, int x, int y) { int pos; gboolean changed = FALSE; if (paned->priv->drop_region) { g_assert (paned->priv->drop_pos >= 0); if (gdk_region_point_in (paned->priv->drop_region, x, y)) { if (!paned->priv->drop_region_is_buttons) return FALSE; else return get_new_button_index (paned, active_child, x, y); } paned->priv->drop_pos = -1; paned->priv->drop_region = NULL; changed = TRUE; } for (pos = 0; pos < 4; ++pos) { if (gdk_region_point_in (paned->priv->dz[pos].def_region, x, y)) { paned->priv->drop_pos = pos; paned->priv->drop_region = paned->priv->dz[pos].def_region; paned->priv->drop_region_is_buttons = FALSE; get_default_button_index (paned, active_child); return TRUE; } if (gdk_region_point_in (paned->priv->dz[pos].bbox_region, x, y)) { paned->priv->drop_pos = pos; paned->priv->drop_region = paned->priv->dz[pos].bbox_region; paned->priv->drop_region_is_buttons = TRUE; get_new_button_index (paned, active_child, x, y); return TRUE; } } return changed; } static void handle_drag_motion (MooPaned *child, G_GNUC_UNUSED GtkWidget *pane_widget, MooBigPaned *paned) { int x, y; g_return_if_fail (GTK_WIDGET_REALIZED (paned->priv->outer)); gdk_window_get_pointer (paned->priv->outer->window, &x, &y, NULL); if (!get_new_drop_position (paned, child, x, y)) return; if (paned->priv->drop_outline) { gdk_window_set_user_data (paned->priv->drop_outline, NULL); gdk_window_destroy (paned->priv->drop_outline); paned->priv->drop_outline = NULL; } if (paned->priv->drop_pos >= 0) { get_drop_area (paned, child, (MooPanePosition) paned->priv->drop_pos, paned->priv->drop_button_index, &paned->priv->drop_rect, &paned->priv->drop_button_rect); create_drop_outline (paned); } } static void cleanup_drag (MooBigPaned *paned) { int pos; if (paned->priv->drop_outline) { gdk_window_set_user_data (paned->priv->drop_outline, NULL); gdk_window_destroy (paned->priv->drop_outline); paned->priv->drop_outline = NULL; } paned->priv->drop_pos = -1; paned->priv->drop_region = NULL; g_signal_handlers_disconnect_by_func (paned->priv->outer, (gpointer) moo_big_paned_expose, paned); for (pos = 0; pos < 4; ++pos) { gdk_region_destroy (paned->priv->dz[pos].bbox_region); gdk_region_destroy (paned->priv->dz[pos].def_region); } g_free (paned->priv->dz); paned->priv->dz = NULL; } static void handle_drag_end (MooPaned *child, GtkWidget *pane_widget, gboolean drop, MooBigPaned *paned) { int x, y; MooPanePosition new_pos; int new_index; g_return_if_fail (GTK_WIDGET_REALIZED (paned->priv->outer)); if (!drop) { cleanup_drag (paned); return; } gdk_window_get_pointer (paned->priv->outer->window, &x, &y, NULL); get_new_drop_position (paned, child, x, y); if (paned->priv->drop_pos < 0) { cleanup_drag (paned); return; } new_pos = (MooPanePosition) paned->priv->drop_pos; new_index = paned->priv->drop_button_index; cleanup_drag (paned); moo_big_paned_reorder_pane (paned, pane_widget, new_pos, new_index); } static void get_drop_area (MooBigPaned *paned, MooPaned *active_child, MooPanePosition position, int index, GdkRectangle *rect, GdkRectangle *button_rect) { int width, height, size = 0; MooPanePosition active_position; width = paned->priv->outer->allocation.width; height = paned->priv->outer->allocation.height; g_object_get (active_child, "pane-position", &active_position, NULL); g_return_if_fail (active_position < 4); if (active_position == position) { size = moo_paned_get_pane_size (active_child) + moo_paned_get_button_box_size (active_child); } else { switch (position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: size = width / 3; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: size = height / 3; break; } } switch (position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: rect->y = paned->priv->outer->allocation.y; rect->width = size; rect->height = height; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: rect->x = paned->priv->outer->allocation.x; rect->width = width; rect->height = size; break; } switch (position) { case MOO_PANE_POS_LEFT: rect->x = paned->priv->outer->allocation.x; break; case MOO_PANE_POS_RIGHT: rect->x = paned->priv->outer->allocation.x + width - size; break; case MOO_PANE_POS_TOP: rect->y = paned->priv->outer->allocation.y; break; case MOO_PANE_POS_BOTTOM: rect->y = paned->priv->outer->allocation.y + height - size; break; } _moo_paned_get_button_position (MOO_PANED (paned->paned[position]), index, button_rect, paned->priv->outer->window); } #define RECT_POINT_IN(rect,x,y) (x < (rect)->x + (rect)->width && \ y < (rect)->height + (rect)->y && \ x >= (rect)->x && y >= (rect)->y) // static int // get_drop_position (MooBigPaned *paned, // MooPaned *child, // int x, // int y, // int *button_index) // { // int width, height, i; // MooPanePosition position; // GdkRectangle rect, button_rect; // // *button_index = -1; // // width = paned->priv->outer->allocation.width; // height = paned->priv->outer->allocation.height; // // if (x < paned->priv->outer->allocation.x || // x >= paned->priv->outer->allocation.x + width || // y < paned->priv->outer->allocation.y || // y >= paned->priv->outer->allocation.y + height) // return -1; // // g_object_get (child, "pane-position", &position, NULL); // g_return_val_if_fail (position < 4, -1); // // get_drop_area (paned, child, position, -1, &rect, &button_rect); // // if (RECT_POINT_IN (&rect, x, y)) // return position; // // for (i = 0; i < 4; ++i) // { // if (paned->priv->order[i] == position) // continue; // // get_drop_area (paned, child, paned->priv->order[i], -1, // &rect, &button_rect); // // if (RECT_POINT_IN (&rect, x, y)) // return paned->priv->order[i]; // } // // return -1; // } // static void // invalidate_drop_outline (MooBigPaned *paned) // { // GdkRectangle line; // GdkRegion *outline; // // outline = gdk_region_new (); // // line.x = paned->priv->drop_rect.x; // line.y = paned->priv->drop_rect.y; // line.width = 2; // line.height = paned->priv->drop_rect.height; // gdk_region_union_with_rect (outline, &line); // // line.x = paned->priv->drop_rect.x; // line.y = paned->priv->drop_rect.y + paned->priv->drop_rect.height; // line.width = paned->priv->drop_rect.width; // line.height = 2; // gdk_region_union_with_rect (outline, &line); // // line.x = paned->priv->drop_rect.x + paned->priv->drop_rect.width; // line.y = paned->priv->drop_rect.y; // line.width = 2; // line.height = paned->priv->drop_rect.height; // gdk_region_union_with_rect (outline, &line); // // line.x = paned->priv->drop_rect.x; // line.y = paned->priv->drop_rect.y; // line.width = paned->priv->drop_rect.width; // line.height = 2; // gdk_region_union_with_rect (outline, &line); // // gdk_window_invalidate_region (paned->priv->outer->window, outline, TRUE); // // gdk_region_destroy (outline); // } static gboolean moo_big_paned_expose (GtkWidget *widget, GdkEventExpose *event, MooBigPaned *paned) { GTK_WIDGET_CLASS(G_OBJECT_GET_CLASS (widget))->expose_event (widget, event); if (paned->priv->drop_pos >= 0) { g_return_val_if_fail (paned->priv->drop_outline != NULL, FALSE); gdk_draw_rectangle (paned->priv->drop_outline, widget->style->fg_gc[GTK_STATE_NORMAL], FALSE, 0, 0, paned->priv->drop_rect.width - 1, paned->priv->drop_rect.height - 1); gdk_draw_rectangle (paned->priv->drop_outline, widget->style->fg_gc[GTK_STATE_NORMAL], FALSE, 1, 1, paned->priv->drop_rect.width - 3, paned->priv->drop_rect.height - 3); } return FALSE; } static GdkBitmap * create_rect_mask (int width, int height, GdkRectangle *rect) { GdkBitmap *bitmap; GdkGC *gc; GdkColor white = {0, 0, 0, 0}; GdkColor black = {1, 1, 1, 1}; bitmap = gdk_pixmap_new (NULL, width, height, 1); gc = gdk_gc_new (bitmap); gdk_gc_set_foreground (gc, &white); gdk_draw_rectangle (bitmap, gc, TRUE, 0, 0, width, height); gdk_gc_set_foreground (gc, &black); gdk_draw_rectangle (bitmap, gc, FALSE, 0, 0, width - 1, height - 1); gdk_draw_rectangle (bitmap, gc, FALSE, 1, 1, width - 3, height - 3); gdk_draw_rectangle (bitmap, gc, FALSE, rect->x, rect->y, rect->width, rect->height); gdk_draw_rectangle (bitmap, gc, FALSE, rect->x + 1, rect->y + 1, rect->width - 2, rect->height - 2); g_object_unref (gc); return bitmap; } static void create_drop_outline (MooBigPaned *paned) { static GdkWindowAttr attributes; int attributes_mask; GdkBitmap *mask; GdkRectangle button_rect; g_return_if_fail (paned->priv->drop_outline == NULL); attributes.x = paned->priv->drop_rect.x; attributes.y = paned->priv->drop_rect.y; attributes.width = paned->priv->drop_rect.width; attributes.height = paned->priv->drop_rect.height; attributes.window_type = GDK_WINDOW_CHILD; attributes.visual = gtk_widget_get_visual (paned->priv->outer); attributes.colormap = gtk_widget_get_colormap (paned->priv->outer); attributes.wclass = GDK_INPUT_OUTPUT; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; paned->priv->drop_outline = gdk_window_new (paned->priv->outer->window, &attributes, attributes_mask); gdk_window_set_user_data (paned->priv->drop_outline, paned); button_rect = paned->priv->drop_button_rect; button_rect.x -= paned->priv->drop_rect.x; button_rect.y -= paned->priv->drop_rect.y; mask = create_rect_mask (paned->priv->drop_rect.width, paned->priv->drop_rect.height, &button_rect); gdk_window_shape_combine_mask (paned->priv->drop_outline, mask, 0, 0); g_object_unref (mask); gdk_window_show (paned->priv->drop_outline); } /*************************************************************************/ /* Config */ #define CONFIG_STRING_MAGIC "mbpconfig-1.0 " #define CONFIG_STRING_MAGIC_LEN strlen (CONFIG_STRING_MAGIC) static MooBigPanedConfig * config_new (void) { MooBigPanedConfig *config; int pos; config = g_new0 (MooBigPanedConfig, 1); for (pos = 0; pos < 4; pos++) { config->paned[pos].order = NULL; config->paned[pos].size = -1; config->paned[pos].sticky = -1; config->paned[pos].active = NULL; } config->panes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) moo_pane_params_free); return config; } static void config_free (MooBigPanedConfig *config) { if (config) { int pos; for (pos = 0; pos < 4; pos++) { g_slist_foreach (config->paned[pos].order, (GFunc) g_free, NULL); g_slist_free (config->paned[pos].order); g_free (config->paned[pos].active); } g_hash_table_destroy (config->panes); g_free (config); } } static gboolean parse_int (const char *string, int *valuep) { long value; char *endptr; errno = 0; value = strtol (string, &endptr, 10); if (errno || endptr == string || *endptr != 0 || value < G_MININT || value > G_MAXINT) return FALSE; *valuep = value; return TRUE; } static gboolean parse_bool (const char *string, gboolean *value) { if (!string[0] || string[1] || (string[0] != '0' && string[0] != '1')) return FALSE; *value = string[0] == '1'; return TRUE; } static gboolean parse_paned_config (const char *string, MooPanedConfig *config) { char **tokens, **p; tokens = g_strsplit (string, ",", 0); if (g_strv_length (tokens) < 3) { g_critical ("%s: invalid paned config string '%s'", G_STRFUNC, string); goto error; } if (!parse_int (tokens[0], &config->size) || config->size < -1 || config->size > 100000) { g_critical ("%s: invalid size '%s' in paned config string '%s'", G_STRFUNC, tokens[0], string); goto error; } if (!parse_int (tokens[1], &config->sticky) || config->sticky < -1 || config->sticky > 1) { g_critical ("%s: invalid boolean '%s' in paned config string '%s'", G_STRFUNC, tokens[1], string); goto error; } if (tokens[2][0]) config->active = g_strdup (tokens[2]); else config->active = NULL; for (p = tokens + 3; *p; ++p) config->order = g_slist_prepend (config->order, g_strdup (*p)); config->order = g_slist_reverse (config->order); g_strfreev (tokens); return TRUE; error: g_strfreev (tokens); return FALSE; } static void paned_config_serialize (MooPanedConfig *config, GString *string) { GSList *l; g_string_append_printf (string, "%d,%d,%s", config->size, config->sticky, config->active ? config->active : ""); for (l = config->order; l != NULL; l = l->next) { char *id = (char*) l->data; g_string_append_printf (string, ",%s", id); } } static gboolean parse_pane_config (const char *string, GHashTable *hash) { char **tokens = NULL; const char *colon; MooPaneParams params = {{-1, -1, -1, -1}, 0, 0, 0}; gboolean value[3]; if (!(colon = strchr (string, ':')) || colon == string) { g_critical ("%s: invalid pane config string '%s'", G_STRFUNC, string); goto error; } tokens = g_strsplit (colon + 1, ",", 0); if (g_strv_length (tokens) != 7) { g_critical ("%s: invalid pane config string '%s'", G_STRFUNC, string); goto error; } if (!parse_int (tokens[0], ¶ms.window_position.x) || !parse_int (tokens[1], ¶ms.window_position.y) || !parse_int (tokens[2], ¶ms.window_position.width) || !parse_int (tokens[3], ¶ms.window_position.height) || params.window_position.width < -1 || params.window_position.width > 100000 || params.window_position.height < -1 || params.window_position.height > 100000) { g_critical ("%s: invalid window position in pane config string '%s'", G_STRFUNC, string); goto error; } if (!parse_bool (tokens[4], &value[0]) || !parse_bool (tokens[5], &value[1]) || !parse_bool (tokens[6], &value[2])) { g_critical ("%s: invalid parameters in pane config string '%s'", G_STRFUNC, string); goto error; } params.detached = value[0]; params.maximized = value[1]; params.keep_on_top = value[2]; g_hash_table_insert (hash, g_strndup (string, colon - string), moo_pane_params_copy (¶ms)); g_strfreev (tokens); return TRUE; error: g_strfreev (tokens); return FALSE; } static void pane_config_serialize (const char *id, MooPaneParams *params, GString *string) { g_string_append_printf (string, ";%s:%d,%d,%d,%d,%s,%s,%s", id, params->window_position.x, params->window_position.y, params->window_position.width, params->window_position.height, params->detached ? "1" : "0", params->maximized ? "1" : "0", params->keep_on_top ? "1" : "0"); } static MooBigPanedConfig * config_parse (const char *string) { MooBigPanedConfig *config = NULL; char **tokens = NULL; char **p; int pos; g_return_val_if_fail (string != NULL, NULL); if (strncmp (string, CONFIG_STRING_MAGIC, CONFIG_STRING_MAGIC_LEN) != 0) { g_critical ("%s: invalid magic in config string '%s'", G_STRFUNC, string); goto error; } string += CONFIG_STRING_MAGIC_LEN; tokens = g_strsplit (string, ";", 0); if (g_strv_length (tokens) < 4) { g_critical ("%s: invalid string '%s'", G_STRFUNC, string); goto error; } config = config_new (); for (pos = 0; pos < 4; ++pos) if (!parse_paned_config (tokens[pos], &config->paned[pos])) goto error; for (p = tokens + 4; *p; ++p) if (!parse_pane_config (*p, config->panes)) goto error; g_strfreev (tokens); return config; error: g_strfreev (tokens); config_free (config); return NULL; } static char * config_serialize (MooBigPanedConfig *config) { GString *string; int pos; g_return_val_if_fail (config != NULL, NULL); string = g_string_new (CONFIG_STRING_MAGIC); for (pos = 0; pos < 4; ++pos) { if (pos != 0) g_string_append (string, ";"); paned_config_serialize (&config->paned[pos], string); } g_hash_table_foreach (config->panes, (GHFunc) pane_config_serialize, string); return g_string_free (string, FALSE); }