diff options
Diffstat (limited to 'e-util/e-paned.c')
-rw-r--r-- | e-util/e-paned.c | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/e-util/e-paned.c b/e-util/e-paned.c new file mode 100644 index 0000000000..3b8f16bbec --- /dev/null +++ b/e-util/e-paned.c @@ -0,0 +1,503 @@ +/* + * e-paned.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-paned.h" + +#include <glib/gi18n-lib.h> + +#define E_PANED_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_PANED, EPanedPrivate)) + +#define SYNC_REQUEST_NONE 0 +#define SYNC_REQUEST_POSITION 1 +#define SYNC_REQUEST_PROPORTION 2 + +struct _EPanedPrivate { + gint hposition; + gint vposition; + gdouble proportion; + + gulong wse_handler_id; + + guint fixed_resize : 1; + guint sync_request : 2; + guint toplevel_ready : 1; +}; + +enum { + PROP_0, + PROP_HPOSITION, + PROP_VPOSITION, + PROP_PROPORTION, + PROP_FIXED_RESIZE +}; + +G_DEFINE_TYPE ( + EPaned, + e_paned, + GTK_TYPE_PANED) + +static gboolean +paned_queue_resize_on_idle (GtkWidget *paned) +{ + gtk_widget_queue_resize_no_redraw (paned); + + return FALSE; +} + +static gboolean +paned_window_state_event_cb (EPaned *paned, + GdkEventWindowState *event, + GtkWidget *toplevel) +{ + /* Wait for WITHDRAWN to change from 1 to 0. */ + if (!(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN)) + return FALSE; + + /* The whole point of this hack is to trap a point where if + * the window were to be maximized initially, the maximized + * allocation would already be negotiated. We're there now. + * Set a flag so we know it's safe to set GtkPaned position. */ + paned->priv->toplevel_ready = TRUE; + + if (paned->priv->sync_request != SYNC_REQUEST_NONE) + gtk_widget_queue_resize (GTK_WIDGET (paned)); + + /* We don't need to listen for window state events anymore. */ + g_signal_handler_disconnect (toplevel, paned->priv->wse_handler_id); + paned->priv->wse_handler_id = 0; + + return FALSE; +} + +static void +paned_notify_orientation_cb (EPaned *paned) +{ + /* Ignore the next "notify::position" emission. */ + if (e_paned_get_fixed_resize (paned)) + paned->priv->sync_request = SYNC_REQUEST_POSITION; + else + paned->priv->sync_request = SYNC_REQUEST_PROPORTION; + gtk_widget_queue_resize (GTK_WIDGET (paned)); +} + +static void +paned_notify_position_cb (EPaned *paned) +{ + GtkAllocation allocation; + GtkOrientable *orientable; + GtkOrientation orientation; + gdouble proportion; + gint position; + + /* If a sync has already been requested, do nothing. */ + if (paned->priv->sync_request != SYNC_REQUEST_NONE) + return; + + orientable = GTK_ORIENTABLE (paned); + orientation = gtk_orientable_get_orientation (orientable); + + gtk_widget_get_allocation (GTK_WIDGET (paned), &allocation); + position = gtk_paned_get_position (GTK_PANED (paned)); + + g_object_freeze_notify (G_OBJECT (paned)); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + position = MAX (0, allocation.width - position); + proportion = (gdouble) position / allocation.width; + + paned->priv->hposition = position; + g_object_notify (G_OBJECT (paned), "hposition"); + } else { + position = MAX (0, allocation.height - position); + proportion = (gdouble) position / allocation.height; + + paned->priv->vposition = position; + g_object_notify (G_OBJECT (paned), "vposition"); + } + + paned->priv->proportion = proportion; + g_object_notify (G_OBJECT (paned), "proportion"); + + if (e_paned_get_fixed_resize (paned)) + paned->priv->sync_request = SYNC_REQUEST_POSITION; + else + paned->priv->sync_request = SYNC_REQUEST_PROPORTION; + + g_object_thaw_notify (G_OBJECT (paned)); +} + +static void +paned_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_HPOSITION: + e_paned_set_hposition ( + E_PANED (object), + g_value_get_int (value)); + return; + + case PROP_VPOSITION: + e_paned_set_vposition ( + E_PANED (object), + g_value_get_int (value)); + return; + + case PROP_PROPORTION: + e_paned_set_proportion ( + E_PANED (object), + g_value_get_double (value)); + return; + + case PROP_FIXED_RESIZE: + e_paned_set_fixed_resize ( + E_PANED (object), + g_value_get_boolean (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +paned_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_HPOSITION: + g_value_set_int ( + value, e_paned_get_hposition ( + E_PANED (object))); + return; + + case PROP_VPOSITION: + g_value_set_int ( + value, e_paned_get_vposition ( + E_PANED (object))); + return; + + case PROP_PROPORTION: + g_value_set_double ( + value, e_paned_get_proportion ( + E_PANED (object))); + return; + + case PROP_FIXED_RESIZE: + g_value_set_boolean ( + value, e_paned_get_fixed_resize ( + E_PANED (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +paned_realize (GtkWidget *widget) +{ + EPanedPrivate *priv; + GtkWidget *toplevel; + GdkWindowState state; + GdkWindow *window; + + priv = E_PANED_GET_PRIVATE (widget); + + /* Chain up to parent's realize() method. */ + GTK_WIDGET_CLASS (e_paned_parent_class)->realize (widget); + + /* XXX This would be easier if we could be notified of + * window state events directly, but I can't seem + * to make that happen. */ + + toplevel = gtk_widget_get_toplevel (widget); + window = gtk_widget_get_window (toplevel); + state = gdk_window_get_state (window); + + /* If the window is withdrawn, wait for it to be shown before + * setting the pane position. If the window is already shown, + * it's safe to set the pane position immediately. */ + if (state & GDK_WINDOW_STATE_WITHDRAWN) + priv->wse_handler_id = g_signal_connect_swapped ( + toplevel, "window-state-event", + G_CALLBACK (paned_window_state_event_cb), widget); + else + priv->toplevel_ready = TRUE; +} + +static void +paned_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + EPaned *paned = E_PANED (widget); + GtkOrientable *orientable; + GtkOrientation orientation; + gdouble proportion; + gint allocated; + gint position; + + /* Chain up to parent's size_allocate() method. */ + GTK_WIDGET_CLASS (e_paned_parent_class)-> + size_allocate (widget, allocation); + + if (!paned->priv->toplevel_ready) + return; + + if (paned->priv->sync_request == SYNC_REQUEST_NONE) + return; + + orientable = GTK_ORIENTABLE (paned); + orientation = gtk_orientable_get_orientation (orientable); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + allocated = allocation->width; + position = e_paned_get_hposition (paned); + } else { + allocated = allocation->height; + position = e_paned_get_vposition (paned); + } + + proportion = e_paned_get_proportion (paned); + + if (paned->priv->sync_request == SYNC_REQUEST_POSITION) + position = MAX (0, allocated - position); + else + position = (1.0 - proportion) * allocated; + + gtk_paned_set_position (GTK_PANED (paned), position); + + paned->priv->sync_request = SYNC_REQUEST_NONE; + + /* gtk_paned_set_position() calls queue_resize, which cannot + * be called from size_allocate, so schedule it from an idle + * callback so the change takes effect. */ + g_idle_add_full ( + G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) paned_queue_resize_on_idle, + g_object_ref (paned), + (GDestroyNotify) g_object_unref); +} + +static void +e_paned_class_init (EPanedClass *class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EPanedPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = paned_set_property; + object_class->get_property = paned_get_property; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->realize = paned_realize; + widget_class->size_allocate = paned_size_allocate; + + g_object_class_install_property ( + object_class, + PROP_HPOSITION, + g_param_spec_int ( + "hposition", + "Horizontal Position", + "Pane position when oriented horizontally", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_VPOSITION, + g_param_spec_int ( + "vposition", + "Vertical Position", + "Pane position when oriented vertically", + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_PROPORTION, + g_param_spec_double ( + "proportion", + "Proportion", + "Proportion of the 2nd pane size", + 0.0, + 1.0, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_FIXED_RESIZE, + g_param_spec_boolean ( + "fixed-resize", + "Fixed Resize", + "Keep the 2nd pane fixed during resize", + TRUE, + G_PARAM_READWRITE)); +} + +static void +e_paned_init (EPaned *paned) +{ + paned->priv = E_PANED_GET_PRIVATE (paned); + + paned->priv->proportion = 0.5; + paned->priv->fixed_resize = TRUE; + + g_signal_connect ( + paned, "notify::orientation", + G_CALLBACK (paned_notify_orientation_cb), NULL); + + g_signal_connect ( + paned, "notify::position", + G_CALLBACK (paned_notify_position_cb), NULL); +} + +GtkWidget * +e_paned_new (GtkOrientation orientation) +{ + return g_object_new (E_TYPE_PANED, "orientation", orientation, NULL); +} + +gint +e_paned_get_hposition (EPaned *paned) +{ + g_return_val_if_fail (E_IS_PANED (paned), 0); + + return paned->priv->hposition; +} + +void +e_paned_set_hposition (EPaned *paned, + gint hposition) +{ + GtkOrientable *orientable; + GtkOrientation orientation; + + g_return_if_fail (E_IS_PANED (paned)); + + if (hposition == paned->priv->hposition) + return; + + paned->priv->hposition = hposition; + + g_object_notify (G_OBJECT (paned), "hposition"); + + orientable = GTK_ORIENTABLE (paned); + orientation = gtk_orientable_get_orientation (orientable); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) { + paned->priv->sync_request = SYNC_REQUEST_POSITION; + gtk_widget_queue_resize (GTK_WIDGET (paned)); + } +} + +gint +e_paned_get_vposition (EPaned *paned) +{ + g_return_val_if_fail (E_IS_PANED (paned), 0); + + return paned->priv->vposition; +} + +void +e_paned_set_vposition (EPaned *paned, + gint vposition) +{ + GtkOrientable *orientable; + GtkOrientation orientation; + + g_return_if_fail (E_IS_PANED (paned)); + + if (vposition == paned->priv->vposition) + return; + + paned->priv->vposition = vposition; + + g_object_notify (G_OBJECT (paned), "vposition"); + + orientable = GTK_ORIENTABLE (paned); + orientation = gtk_orientable_get_orientation (orientable); + + if (orientation == GTK_ORIENTATION_VERTICAL) { + paned->priv->sync_request = SYNC_REQUEST_POSITION; + gtk_widget_queue_resize (GTK_WIDGET (paned)); + } +} + +gdouble +e_paned_get_proportion (EPaned *paned) +{ + g_return_val_if_fail (E_IS_PANED (paned), 0.5); + + return paned->priv->proportion; +} + +void +e_paned_set_proportion (EPaned *paned, + gdouble proportion) +{ + g_return_if_fail (E_IS_PANED (paned)); + g_return_if_fail (CLAMP (proportion, 0.0, 1.0) == proportion); + + paned->priv->proportion = proportion; + + paned->priv->sync_request = SYNC_REQUEST_PROPORTION; + gtk_widget_queue_resize (GTK_WIDGET (paned)); + + g_object_notify (G_OBJECT (paned), "proportion"); +} + +gboolean +e_paned_get_fixed_resize (EPaned *paned) +{ + g_return_val_if_fail (E_IS_PANED (paned), FALSE); + + return paned->priv->fixed_resize; +} + +void +e_paned_set_fixed_resize (EPaned *paned, + gboolean fixed_resize) +{ + g_return_if_fail (E_IS_PANED (paned)); + + if (fixed_resize == paned->priv->fixed_resize) + return; + + paned->priv->fixed_resize = fixed_resize; + + g_object_notify (G_OBJECT (paned), "fixed-resize"); +} |