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