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 }