evolution-3.6.4/widgets/misc/e-paned.c

No issues found

  1 /*
  2  * e-paned.c
  3  *
  4  * This program is free software; you can redistribute it and/or
  5  * modify it under the terms of the GNU Lesser General Public
  6  * License as published by the Free Software Foundation; either
  7  * version 2 of the License, or (at your option) version 3.
  8  *
  9  * This program is distributed in the hope that it will be useful,
 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 12  * Lesser General Public License for more details.
 13  *
 14  * You should have received a copy of the GNU Lesser General Public
 15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 16  *
 17  *
 18  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 19  *
 20  */
 21 
 22 #ifdef HAVE_CONFIG_H
 23 #include <config.h>
 24 #endif
 25 
 26 #include "e-paned.h"
 27 
 28 #include <glib/gi18n-lib.h>
 29 
 30 #define E_PANED_GET_PRIVATE(obj) \
 31 	(G_TYPE_INSTANCE_GET_PRIVATE \
 32 	((obj), E_TYPE_PANED, EPanedPrivate))
 33 
 34 #define SYNC_REQUEST_NONE		0
 35 #define SYNC_REQUEST_POSITION		1
 36 #define SYNC_REQUEST_PROPORTION		2
 37 
 38 struct _EPanedPrivate {
 39 	gint hposition;
 40 	gint vposition;
 41 	gdouble proportion;
 42 
 43 	gulong wse_handler_id;
 44 
 45 	guint fixed_resize	: 1;
 46 	guint sync_request	: 2;
 47 	guint toplevel_ready	: 1;
 48 };
 49 
 50 enum {
 51 	PROP_0,
 52 	PROP_HPOSITION,
 53 	PROP_VPOSITION,
 54 	PROP_PROPORTION,
 55 	PROP_FIXED_RESIZE
 56 };
 57 
 58 G_DEFINE_TYPE (
 59 	EPaned,
 60 	e_paned,
 61 	GTK_TYPE_PANED)
 62 
 63 static gboolean
 64 paned_queue_resize_on_idle (GtkWidget *paned)
 65 {
 66 	gtk_widget_queue_resize_no_redraw (paned);
 67 
 68 	return FALSE;
 69 }
 70 
 71 static gboolean
 72 paned_window_state_event_cb (EPaned *paned,
 73                              GdkEventWindowState *event,
 74                              GtkWidget *toplevel)
 75 {
 76 	/* Wait for WITHDRAWN to change from 1 to 0. */
 77 	if (!(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN))
 78 		return FALSE;
 79 
 80 	/* The whole point of this hack is to trap a point where if
 81 	 * the window were to be maximized initially, the maximized
 82 	 * allocation would already be negotiated.  We're there now.
 83 	 * Set a flag so we know it's safe to set GtkPaned position. */
 84 	paned->priv->toplevel_ready = TRUE;
 85 
 86 	if (paned->priv->sync_request != SYNC_REQUEST_NONE)
 87 		gtk_widget_queue_resize (GTK_WIDGET (paned));
 88 
 89 	/* We don't need to listen for window state events anymore. */
 90 	g_signal_handler_disconnect (toplevel, paned->priv->wse_handler_id);
 91 	paned->priv->wse_handler_id = 0;
 92 
 93 	return FALSE;
 94 }
 95 
 96 static void
 97 paned_notify_orientation_cb (EPaned *paned)
 98 {
 99 	/* Ignore the next "notify::position" emission. */
100 	if (e_paned_get_fixed_resize (paned))
101 		paned->priv->sync_request = SYNC_REQUEST_POSITION;
102 	else
103 		paned->priv->sync_request = SYNC_REQUEST_PROPORTION;
104 	gtk_widget_queue_resize (GTK_WIDGET (paned));
105 }
106 
107 static void
108 paned_notify_position_cb (EPaned *paned)
109 {
110 	GtkAllocation allocation;
111 	GtkOrientable *orientable;
112 	GtkOrientation orientation;
113 	gdouble proportion;
114 	gint position;
115 
116 	/* If a sync has already been requested, do nothing. */
117 	if (paned->priv->sync_request != SYNC_REQUEST_NONE)
118 		return;
119 
120 	orientable = GTK_ORIENTABLE (paned);
121 	orientation = gtk_orientable_get_orientation (orientable);
122 
123 	gtk_widget_get_allocation (GTK_WIDGET (paned), &allocation);
124 	position = gtk_paned_get_position (GTK_PANED (paned));
125 
126 	g_object_freeze_notify (G_OBJECT (paned));
127 
128 	if (orientation == GTK_ORIENTATION_HORIZONTAL) {
129 		position = MAX (0, allocation.width - position);
130 		proportion = (gdouble) position / allocation.width;
131 
132 		paned->priv->hposition = position;
133 		g_object_notify (G_OBJECT (paned), "hposition");
134 	} else {
135 		position = MAX (0, allocation.height - position);
136 		proportion = (gdouble) position / allocation.height;
137 
138 		paned->priv->vposition = position;
139 		g_object_notify (G_OBJECT (paned), "vposition");
140 	}
141 
142 	paned->priv->proportion = proportion;
143 	g_object_notify (G_OBJECT (paned), "proportion");
144 
145 	if (e_paned_get_fixed_resize (paned))
146 		paned->priv->sync_request = SYNC_REQUEST_POSITION;
147 	else
148 		paned->priv->sync_request = SYNC_REQUEST_PROPORTION;
149 
150 	g_object_thaw_notify (G_OBJECT (paned));
151 }
152 
153 static void
154 paned_set_property (GObject *object,
155                     guint property_id,
156                     const GValue *value,
157                     GParamSpec *pspec)
158 {
159 	switch (property_id) {
160 		case PROP_HPOSITION:
161 			e_paned_set_hposition (
162 				E_PANED (object),
163 				g_value_get_int (value));
164 			return;
165 
166 		case PROP_VPOSITION:
167 			e_paned_set_vposition (
168 				E_PANED (object),
169 				g_value_get_int (value));
170 			return;
171 
172 		case PROP_PROPORTION:
173 			e_paned_set_proportion (
174 				E_PANED (object),
175 				g_value_get_double (value));
176 			return;
177 
178 		case PROP_FIXED_RESIZE:
179 			e_paned_set_fixed_resize (
180 				E_PANED (object),
181 				g_value_get_boolean (value));
182 			return;
183 	}
184 
185 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
186 }
187 
188 static void
189 paned_get_property (GObject *object,
190                     guint property_id,
191                     GValue *value,
192                     GParamSpec *pspec)
193 {
194 	switch (property_id) {
195 		case PROP_HPOSITION:
196 			g_value_set_int (
197 				value, e_paned_get_hposition (
198 				E_PANED (object)));
199 			return;
200 
201 		case PROP_VPOSITION:
202 			g_value_set_int (
203 				value, e_paned_get_vposition (
204 				E_PANED (object)));
205 			return;
206 
207 		case PROP_PROPORTION:
208 			g_value_set_double (
209 				value, e_paned_get_proportion (
210 				E_PANED (object)));
211 			return;
212 
213 		case PROP_FIXED_RESIZE:
214 			g_value_set_boolean (
215 				value, e_paned_get_fixed_resize (
216 				E_PANED (object)));
217 			return;
218 	}
219 
220 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
221 }
222 
223 static void
224 paned_realize (GtkWidget *widget)
225 {
226 	EPanedPrivate *priv;
227 	GtkWidget *toplevel;
228 	GdkWindowState state;
229 	GdkWindow *window;
230 
231 	priv = E_PANED_GET_PRIVATE (widget);
232 
233 	/* Chain up to parent's realize() method. */
234 	GTK_WIDGET_CLASS (e_paned_parent_class)->realize (widget);
235 
236 	/* XXX This would be easier if we could be notified of
237 	 *     window state events directly, but I can't seem
238 	 *     to make that happen. */
239 
240 	toplevel = gtk_widget_get_toplevel (widget);
241 	window = gtk_widget_get_window (toplevel);
242 	state = gdk_window_get_state (window);
243 
244 	/* If the window is withdrawn, wait for it to be shown before
245 	 * setting the pane position.  If the window is already shown,
246 	 * it's safe to set the pane position immediately. */
247 	if (state & GDK_WINDOW_STATE_WITHDRAWN)
248 		priv->wse_handler_id = g_signal_connect_swapped (
249 			toplevel, "window-state-event",
250 			G_CALLBACK (paned_window_state_event_cb), widget);
251 	else
252 		priv->toplevel_ready = TRUE;
253 }
254 
255 static void
256 paned_size_allocate (GtkWidget *widget,
257                      GtkAllocation *allocation)
258 {
259 	EPaned *paned = E_PANED (widget);
260 	GtkOrientable *orientable;
261 	GtkOrientation orientation;
262 	gdouble proportion;
263 	gint allocated;
264 	gint position;
265 
266 	/* Chain up to parent's size_allocate() method. */
267 	GTK_WIDGET_CLASS (e_paned_parent_class)->
268 		size_allocate (widget, allocation);
269 
270 	if (!paned->priv->toplevel_ready)
271 		return;
272 
273 	if (paned->priv->sync_request == SYNC_REQUEST_NONE)
274 		return;
275 
276 	orientable = GTK_ORIENTABLE (paned);
277 	orientation = gtk_orientable_get_orientation (orientable);
278 
279 	if (orientation == GTK_ORIENTATION_HORIZONTAL) {
280 		allocated = allocation->width;
281 		position = e_paned_get_hposition (paned);
282 	} else {
283 		allocated = allocation->height;
284 		position = e_paned_get_vposition (paned);
285 	}
286 
287 	proportion = e_paned_get_proportion (paned);
288 
289 	if (paned->priv->sync_request == SYNC_REQUEST_POSITION)
290 		position = MAX (0, allocated - position);
291 	else
292 		position = (1.0 - proportion) * allocated;
293 
294 	gtk_paned_set_position (GTK_PANED (paned), position);
295 
296 	paned->priv->sync_request = SYNC_REQUEST_NONE;
297 
298 	/* gtk_paned_set_position() calls queue_resize, which cannot
299 	 * be called from size_allocate, so schedule it from an idle
300 	 * callback so the change takes effect. */
301 	g_idle_add_full (
302 		G_PRIORITY_DEFAULT_IDLE,
303 		(GSourceFunc) paned_queue_resize_on_idle,
304 		g_object_ref (paned),
305 		(GDestroyNotify) g_object_unref);
306 }
307 
308 static void
309 e_paned_class_init (EPanedClass *class)
310 {
311 	GObjectClass *object_class;
312 	GtkWidgetClass *widget_class;
313 
314 	g_type_class_add_private (class, sizeof (EPanedPrivate));
315 
316 	object_class = G_OBJECT_CLASS (class);
317 	object_class->set_property = paned_set_property;
318 	object_class->get_property = paned_get_property;
319 
320 	widget_class = GTK_WIDGET_CLASS (class);
321 	widget_class->realize = paned_realize;
322 	widget_class->size_allocate = paned_size_allocate;
323 
324 	g_object_class_install_property (
325 		object_class,
326 		PROP_HPOSITION,
327 		g_param_spec_int (
328 			"hposition",
329 			"Horizontal Position",
330 			"Pane position when oriented horizontally",
331 			G_MININT,
332 			G_MAXINT,
333 			0,
334 			G_PARAM_READWRITE));
335 
336 	g_object_class_install_property (
337 		object_class,
338 		PROP_VPOSITION,
339 		g_param_spec_int (
340 			"vposition",
341 			"Vertical Position",
342 			"Pane position when oriented vertically",
343 			G_MININT,
344 			G_MAXINT,
345 			0,
346 			G_PARAM_READWRITE));
347 
348 	g_object_class_install_property (
349 		object_class,
350 		PROP_PROPORTION,
351 		g_param_spec_double (
352 			"proportion",
353 			"Proportion",
354 			"Proportion of the 2nd pane size",
355 			0.0,
356 			1.0,
357 			0.0,
358 			G_PARAM_READWRITE));
359 
360 	g_object_class_install_property (
361 		object_class,
362 		PROP_FIXED_RESIZE,
363 		g_param_spec_boolean (
364 			"fixed-resize",
365 			"Fixed Resize",
366 			"Keep the 2nd pane fixed during resize",
367 			TRUE,
368 			G_PARAM_READWRITE));
369 }
370 
371 static void
372 e_paned_init (EPaned *paned)
373 {
374 	paned->priv = E_PANED_GET_PRIVATE (paned);
375 
376 	paned->priv->proportion = 0.5;
377 	paned->priv->fixed_resize = TRUE;
378 
379 	g_signal_connect (
380 		paned, "notify::orientation",
381 		G_CALLBACK (paned_notify_orientation_cb), NULL);
382 
383 	g_signal_connect (
384 		paned, "notify::position",
385 		G_CALLBACK (paned_notify_position_cb), NULL);
386 }
387 
388 GtkWidget *
389 e_paned_new (GtkOrientation orientation)
390 {
391 	return g_object_new (E_TYPE_PANED, "orientation", orientation, NULL);
392 }
393 
394 gint
395 e_paned_get_hposition (EPaned *paned)
396 {
397 	g_return_val_if_fail (E_IS_PANED (paned), 0);
398 
399 	return paned->priv->hposition;
400 }
401 
402 void
403 e_paned_set_hposition (EPaned *paned,
404                        gint hposition)
405 {
406 	GtkOrientable *orientable;
407 	GtkOrientation orientation;
408 
409 	g_return_if_fail (E_IS_PANED (paned));
410 
411 	if (hposition == paned->priv->hposition)
412 		return;
413 
414 	paned->priv->hposition = hposition;
415 
416 	g_object_notify (G_OBJECT (paned), "hposition");
417 
418 	orientable = GTK_ORIENTABLE (paned);
419 	orientation = gtk_orientable_get_orientation (orientable);
420 
421 	if (orientation == GTK_ORIENTATION_HORIZONTAL) {
422 		paned->priv->sync_request = SYNC_REQUEST_POSITION;
423 		gtk_widget_queue_resize (GTK_WIDGET (paned));
424 	}
425 }
426 
427 gint
428 e_paned_get_vposition (EPaned *paned)
429 {
430 	g_return_val_if_fail (E_IS_PANED (paned), 0);
431 
432 	return paned->priv->vposition;
433 }
434 
435 void
436 e_paned_set_vposition (EPaned *paned,
437                        gint vposition)
438 {
439 	GtkOrientable *orientable;
440 	GtkOrientation orientation;
441 
442 	g_return_if_fail (E_IS_PANED (paned));
443 
444 	if (vposition == paned->priv->vposition)
445 		return;
446 
447 	paned->priv->vposition = vposition;
448 
449 	g_object_notify (G_OBJECT (paned), "vposition");
450 
451 	orientable = GTK_ORIENTABLE (paned);
452 	orientation = gtk_orientable_get_orientation (orientable);
453 
454 	if (orientation == GTK_ORIENTATION_VERTICAL) {
455 		paned->priv->sync_request = SYNC_REQUEST_POSITION;
456 		gtk_widget_queue_resize (GTK_WIDGET (paned));
457 	}
458 }
459 
460 gdouble
461 e_paned_get_proportion (EPaned *paned)
462 {
463 	g_return_val_if_fail (E_IS_PANED (paned), 0.5);
464 
465 	return paned->priv->proportion;
466 }
467 
468 void
469 e_paned_set_proportion (EPaned *paned,
470                         gdouble proportion)
471 {
472 	g_return_if_fail (E_IS_PANED (paned));
473 	g_return_if_fail (CLAMP (proportion, 0.0, 1.0) == proportion);
474 
475 	paned->priv->proportion = proportion;
476 
477 	paned->priv->sync_request = SYNC_REQUEST_PROPORTION;
478 	gtk_widget_queue_resize (GTK_WIDGET (paned));
479 
480 	g_object_notify (G_OBJECT (paned), "proportion");
481 }
482 
483 gboolean
484 e_paned_get_fixed_resize (EPaned *paned)
485 {
486 	g_return_val_if_fail (E_IS_PANED (paned), FALSE);
487 
488 	return paned->priv->fixed_resize;
489 }
490 
491 void
492 e_paned_set_fixed_resize (EPaned *paned,
493                           gboolean fixed_resize)
494 {
495 	g_return_if_fail (E_IS_PANED (paned));
496 
497 	if (fixed_resize == paned->priv->fixed_resize)
498 		return;
499 
500 	paned->priv->fixed_resize = fixed_resize;
501 
502 	g_object_notify (G_OBJECT (paned), "fixed-resize");
503 }