1 /* a side panel that can slide in and out of view
2 */
3
4 /*
5
6 Copyright (C) 1991-2003 The National Gallery
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
22 */
23
24 /*
25
26 These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
27
28 */
29
30 #include "ip.h"
31
32 /*
33 #define DEBUG
34 */
35
36 /* Our signals.
37 */
38 enum {
39 SIG_CHANGED, /* Change to position or openness */
40 SIG_LAST
41 };
42
43 static GtkHPanedClass *parent_class = NULL;
44
45 static guint pane_signals[SIG_LAST] = { 0 };
46
47 #ifdef DEBUG
48 static char *
pane_handedness2char(PaneHandedness handedness)49 pane_handedness2char( PaneHandedness handedness )
50 {
51 switch( handedness ) {
52 case PANE_HIDE_LEFT: return( "PANE_HIDE_LEFT" );
53 case PANE_HIDE_RIGHT: return( "PANE_HIDE_RIGHT" );
54 default: g_assert( 0 );
55 }
56 }
57 #endif /*DEBUG*/
58
59 static void
pane_changed(Pane * pane)60 pane_changed( Pane *pane )
61 {
62 g_assert( IS_PANE( pane ) );
63
64 #ifdef DEBUG
65 printf( "pane_changed: %p %s\n",
66 pane, pane_handedness2char( pane->handedness ) );
67 #endif /*DEBUG*/
68
69 g_signal_emit( G_OBJECT( pane ), pane_signals[SIG_CHANGED], 0 );
70 }
71
72 static int
pane_closed_position(Pane * pane)73 pane_closed_position( Pane *pane )
74 {
75 /* Can't use max/min since we need to be able to work before our
76 * window has been built.
77 */
78 return( pane->handedness == PANE_HIDE_RIGHT ? 10000 : 0 );
79 }
80
81 /* An open position ... used in case we are asked to open, but the position is
82 * already closed.
83 */
84 static int
pane_open_position(Pane * pane)85 pane_open_position( Pane *pane )
86 {
87 int max_position;
88 int min_position;
89
90 g_object_get( pane,
91 "max_position", &max_position,
92 "min_position", &min_position,
93 NULL );
94
95 return( pane->handedness == PANE_HIDE_RIGHT ?
96 max_position - 200: min_position + 200 );
97 }
98
99 static void
pane_destroy(GtkObject * object)100 pane_destroy( GtkObject *object )
101 {
102 Pane *pane;
103
104 g_return_if_fail( object != NULL );
105 g_return_if_fail( IS_PANE( object ) );
106
107 pane = PANE( object );
108
109 #ifdef DEBUG
110 printf( "pane_destroy: %p %s\n",
111 pane, pane_handedness2char( pane->handedness ) );
112 #endif /*DEBUG*/
113
114 /* My instance destroy stuff.
115 */
116 IM_FREEF( g_source_remove, pane->animate_timeout );
117
118 GTK_OBJECT_CLASS( parent_class )->destroy( object );
119 }
120
121 static void
pane_class_init(PaneClass * class)122 pane_class_init( PaneClass *class )
123 {
124 GtkObjectClass *object_class = (GtkObjectClass *) class;
125
126 parent_class = g_type_class_peek_parent( class );
127
128 object_class->destroy = pane_destroy;
129
130 class->changed = NULL;
131
132 pane_signals[SIG_CHANGED] = g_signal_new( "changed",
133 G_OBJECT_CLASS_TYPE( object_class ),
134 G_SIGNAL_RUN_FIRST,
135 G_STRUCT_OFFSET( PaneClass, changed ),
136 NULL, NULL,
137 g_cclosure_marshal_VOID__VOID,
138 G_TYPE_NONE, 0 );
139 }
140
141 /* Position property has changed. We block the notify signal before we set
142 * position, so this change must have come from a user drag or parent window
143 * resize.
144 */
145 static void
pane_notify_position_cb(Pane * pane)146 pane_notify_position_cb( Pane *pane )
147 {
148 int max_position;
149 int min_position;
150 int position;
151
152 /* Can get here even though we block notify during position set in
153 * animate, because of delays in window setup.
154 */
155 if( pane->animate_timeout )
156 return;
157
158 g_object_get( pane,
159 "max_position", &max_position,
160 "min_position", &min_position,
161 NULL );
162
163 /* We can have 10,000 as position (meaning way to the
164 * right), take account of any clipping there may be.
165 */
166 pane->position = IM_CLIP( min_position, pane->position, max_position );
167
168 /* And the new value.
169 */
170 position = gtk_paned_get_position( GTK_PANED( pane ) );
171
172 #ifdef DEBUG
173 printf( "pane_notify_position_cb: %p %s %d\n",
174 pane, pane_handedness2char( pane->handedness ), position );
175 #endif /*DEBUG*/
176
177 pane_set_position( pane, position );
178 pane_set_user_position( pane, position );
179
180 /* Look for dragged close.
181 */
182 if( pane->open &&
183 pane->handedness == PANE_HIDE_LEFT &&
184 position == min_position )
185 pane_set_open( pane, FALSE );
186 if( pane->open &&
187 pane->handedness == PANE_HIDE_RIGHT &&
188 position == max_position )
189 pane_set_open( pane, FALSE );
190 }
191
192 static void
pane_init(Pane * pane)193 pane_init( Pane *pane )
194 {
195 pane->handedness = PANE_HIDE_LEFT;
196 pane->panechild = NULL;
197 pane->open = FALSE;
198 pane->position = 0;
199 pane->user_position = 0; /* overwritten on _link() */
200 pane->target_position = 0;
201 pane->close_on_end = FALSE;
202 pane->last_set_position = 0;
203 pane->animate_timeout = 0;
204
205 g_signal_connect( pane, "notify::position",
206 G_CALLBACK( pane_notify_position_cb ), NULL );
207 }
208
209 GType
pane_get_type(void)210 pane_get_type( void )
211 {
212 static GType type = 0;
213
214 if( !type ) {
215 static const GTypeInfo info = {
216 sizeof( PaneClass ),
217 NULL, /* base_init */
218 NULL, /* base_finalize */
219 (GClassInitFunc) pane_class_init,
220 NULL, /* class_finalize */
221 NULL, /* class_data */
222 sizeof( Pane ),
223 32, /* n_preallocs */
224 (GInstanceInitFunc) pane_init,
225 };
226
227 type = g_type_register_static( GTK_TYPE_HPANED,
228 "Pane", &info, 0 );
229 }
230
231 return( type );
232 }
233
234 /* Operations on the model.
235 */
236
237 void
pane_set_position(Pane * pane,int position)238 pane_set_position( Pane *pane, int position )
239 {
240 if( pane->position != position ) {
241 #ifdef DEBUG
242 printf( "pane_set_position: %p %s %d\n",
243 pane, pane_handedness2char( pane->handedness ),
244 position );
245 #endif /*DEBUG*/
246
247 g_signal_handlers_block_by_func( pane,
248 pane_notify_position_cb, NULL );
249 gtk_paned_set_position( GTK_PANED( pane ), position );
250 g_signal_handlers_unblock_by_func( pane,
251 pane_notify_position_cb, NULL );
252
253 pane->position = position;
254 pane_changed( pane );
255 }
256 }
257
258 void
pane_set_user_position(Pane * pane,int user_position)259 pane_set_user_position( Pane *pane, int user_position )
260 {
261 if( pane->user_position != user_position ) {
262 #ifdef DEBUG
263 printf( "pane_set_user_position: %p %s %d\n",
264 pane, pane_handedness2char( pane->handedness ),
265 user_position );
266 #endif /*DEBUG*/
267
268 pane->user_position = user_position;
269 pane_changed( pane );
270 }
271 }
272
273 void
pane_set_open(Pane * pane,gboolean open)274 pane_set_open( Pane *pane, gboolean open )
275 {
276 if( pane->open != open ) {
277 #ifdef DEBUG
278 printf( "pane_set_open: %p %s %d\n",
279 pane, pane_handedness2char( pane->handedness ),
280 open );
281 #endif /*DEBUG*/
282
283 widget_visible( GTK_WIDGET( pane->panechild ), open );
284 pane->open = open;
285 pane_changed( pane );
286 }
287 }
288
289 /* Set everything all at once on startup.
290 */
291 void
pane_set_state(Pane * pane,gboolean open,int user_position)292 pane_set_state( Pane *pane, gboolean open, int user_position )
293 {
294 if( pane->open != open ||
295 pane->user_position != user_position ) {
296 g_signal_handlers_block_by_func( pane,
297 pane_notify_position_cb, NULL );
298 gtk_paned_set_position( GTK_PANED( pane ), user_position );
299 g_signal_handlers_unblock_by_func( pane,
300 pane_notify_position_cb, NULL );
301
302 widget_visible( GTK_WIDGET( pane->panechild ), open );
303
304 pane->open = open;
305 pane->user_position = user_position;
306 pane->position = user_position;
307
308 pane_changed( pane );
309 }
310 }
311
312 void
pane_set_child(Pane * pane,Panechild * panechild)313 pane_set_child( Pane *pane, Panechild *panechild )
314 {
315 g_assert( !pane->panechild );
316
317 pane->panechild = panechild;
318
319 if( pane->handedness == PANE_HIDE_LEFT )
320 gtk_paned_pack1( GTK_PANED( pane ),
321 GTK_WIDGET( panechild ), TRUE, TRUE );
322 else
323 gtk_paned_pack2( GTK_PANED( pane ),
324 GTK_WIDGET( panechild ), TRUE, TRUE );
325 }
326
327 /* Control.
328 */
329
330 static gboolean
pane_animate_timeout_cb(Pane * pane)331 pane_animate_timeout_cb( Pane *pane )
332 {
333 int position = pane->position;
334 int target = pane->target_position;
335
336 int new;
337 gboolean more;
338
339 #ifdef DEBUG
340 printf( "pane_animate_timeout_cb: %p %s\n",
341 pane, pane_handedness2char( pane->handedness ) );
342 #endif /*DEBUG*/
343
344 more = TRUE;
345 new = position + (target - position) / 2;
346 if( ABS( position - target ) < 5 ||
347 new == pane->last_set_position ) {
348 /* At our target!
349 */
350 new = target;
351 more = FALSE;
352 pane->animate_timeout = 0;
353 }
354
355 pane_set_position( pane, new );
356 pane->last_set_position = new;
357
358 if( !more &&
359 pane->close_on_end )
360 pane_set_open( pane, FALSE );
361
362 return( more );
363 }
364
365 /* Close the pane with an animation.
366 */
367 void
pane_animate_closed(Pane * pane)368 pane_animate_closed( Pane *pane )
369 {
370 if( !pane->animate_timeout &&
371 pane->open ) {
372 int max_position;
373 int min_position;
374 int target_position;
375
376 target_position = pane_closed_position( pane );
377 g_object_get( pane,
378 "max_position", &max_position,
379 "min_position", &min_position,
380 NULL );
381
382 /* Can be zero if we're here very early.
383 */
384 if( max_position > 0 )
385 target_position =
386 IM_CLIP( min_position,
387 target_position, max_position );
388 pane->target_position = target_position;
389 pane->close_on_end = TRUE;
390 pane->last_set_position = -1;
391
392 pane->animate_timeout = g_timeout_add( 50,
393 (GSourceFunc) pane_animate_timeout_cb, pane );
394 }
395 }
396
397 /* Open the pane with an animation.
398 */
399 void
pane_animate_open(Pane * pane)400 pane_animate_open( Pane *pane )
401 {
402 if( !pane->animate_timeout &&
403 !pane->open ) {
404 int max_position;
405 int min_position;
406 int target_position;
407
408 target_position = pane->user_position;
409 g_object_get( pane,
410 "max_position", &max_position,
411 "min_position", &min_position,
412 NULL );
413
414 /* Can be zero if we're here very early.
415 */
416 if( max_position > 0 )
417 target_position =
418 IM_CLIP( min_position,
419 target_position, max_position );
420
421 /* user_position can be max or min if the pane was dragged
422 * closed.
423 */
424 if( target_position == max_position ||
425 target_position == min_position )
426 target_position = pane_open_position( pane );
427
428 #ifdef DEBUG
429 printf( "pane_animate_open: %p %s %d\n",
430 pane, pane_handedness2char( pane->handedness ),
431 target_position );
432 #endif /*DEBUG*/
433
434 pane->target_position = target_position;
435 pane->close_on_end = FALSE;
436 pane->last_set_position = -1;
437 pane_set_open( pane, TRUE );
438
439 pane->animate_timeout = g_timeout_add( 50,
440 (GSourceFunc) pane_animate_timeout_cb, pane );
441 }
442 }
443
444 static void
pane_link(Pane * pane,PaneHandedness handedness)445 pane_link( Pane *pane, PaneHandedness handedness )
446 {
447 #ifdef DEBUG
448 printf( "pane_link: %p %s\n",
449 pane, pane_handedness2char( handedness ) );
450 #endif /*DEBUG*/
451
452 pane->handedness = handedness;
453 pane_set_open( pane, FALSE );
454 }
455
456 Pane *
pane_new(PaneHandedness handedness)457 pane_new( PaneHandedness handedness )
458 {
459 Pane *pane;
460
461 pane = PANE( g_object_new( TYPE_PANE, NULL ) );
462 pane_link( pane, handedness );
463
464 return( pane );
465 }
466