1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2011 Benjamin Moody
5  *
6  * This program is free software: you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23 
24 #define GDK_PIXBUF_ENABLE_BACKEND
25 
26 #include <stdio.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <gtk/gtk.h>
30 #include <glib/gstdio.h>
31 #include <ticalcs.h>
32 #include <tilem.h>
33 
34 #include "gui.h"
35 
36 #define GAMMA 2.2
37 
38 struct _TilemAnimFrame {
39 	struct _TilemAnimFrame *next;
40 	unsigned duration : 24;
41 	unsigned contrast : 8;
42 	byte data[1];
43 };
44 
45 struct _TilemAnimation {
46 	GdkPixbufAnimation parent;
47 
48 	int num_frames;
49 	TilemAnimFrame *start;
50 	TilemAnimFrame *end;
51 	dword last_stamp;
52 
53 	TilemLCDBuffer *temp_buffer;
54 
55 	GdkPixbuf *static_pixbuf;
56 
57 	int base_contrast;
58 	int display_width;
59 	int display_height;
60 	int frame_rowstride;
61 	int frame_size;
62 	int image_width;
63 	int image_height;
64 	dword *palette;
65 	gdouble speed;
66 	gdouble time_stretch;
67 
68 	gboolean out_of_memory;
69 };
70 
71 struct _TilemAnimationClass {
72 	GdkPixbufAnimationClass parent_class;
73 };
74 
75 #define TILEM_TYPE_ANIM_ITER           (tilem_anim_iter_get_type())
76 #define TILEM_ANIM_ITER(obj)           (G_TYPE_CHECK_INSTANCE_CAST((obj), TILEM_TYPE_ANIM_ITER, TilemAnimIter))
77 #define TILEM_ANIM_ITER_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST((cls), TILEM_TYPE_ANIM_ITER, TilemAnimIterClass))
78 #define TILEM_IS_ANIM_ITER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE((obj), TILEM_TYPE_ANIM_ITER))
79 #define TILEM_IS_ANIM_ITER_CLASS(cls)  (G_TYPE_CHECK_CLASS_TYPE((cls), TILEM_TYPE_ANIM_ITER))
80 #define TILEM_ANIM_ITER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TILEM_TYPE_ANIM_ITER, TilemAnimIterClass))
81 
82 typedef struct _TilemAnimIter {
83 	GdkPixbufAnimationIter parent;
84 	GTimeVal current_time;
85 	int time_elapsed;
86 	TilemAnimation *anim;
87 	TilemAnimFrame *frame;
88 	GdkPixbuf *pixbuf;
89 } TilemAnimIter;
90 
91 typedef struct _TilemAnimIterClass {
92 	GdkPixbufAnimationIterClass parent_class;
93 } TilemAnimIterClass;
94 
95 G_DEFINE_TYPE(TilemAnimation, tilem_animation,
96               GDK_TYPE_PIXBUF_ANIMATION);
97 
98 G_DEFINE_TYPE(TilemAnimIter, tilem_anim_iter,
99               GDK_TYPE_PIXBUF_ANIMATION_ITER);
100 
alloc_frame(int bufsize)101 static TilemAnimFrame * alloc_frame(int bufsize)
102 {
103 	TilemAnimFrame *frm;
104 
105 	frm = g_try_malloc(sizeof(TilemAnimFrame) + bufsize - 1);
106 	if (!frm)
107 		return NULL;
108 	frm->next = NULL;
109 	return frm;
110 }
111 
free_frame(TilemAnimFrame * frm)112 static void free_frame(TilemAnimFrame *frm)
113 {
114 	g_free(frm);
115 }
116 
adjust_contrast(TilemAnimation * anim,int contrast)117 static int adjust_contrast(TilemAnimation *anim, int contrast)
118 {
119 	TilemAnimFrame *frm;
120 
121 	if (!contrast)
122 		return 0;
123 
124 	if (!anim->base_contrast) {
125 		for (frm = anim->start; frm; frm = frm->next) {
126 			if (frm->contrast != 0) {
127 				anim->base_contrast = frm->contrast;
128 				break;
129 			}
130 		}
131 	}
132 
133 	contrast = (contrast - anim->base_contrast + 32);
134 	return CLAMP(contrast, 0, 63);
135 }
136 
set_lcdbuf_from_frame(TilemAnimation * anim,TilemLCDBuffer * buf,const TilemAnimFrame * frm)137 static void set_lcdbuf_from_frame(TilemAnimation *anim,
138                                   TilemLCDBuffer *buf,
139                                   const TilemAnimFrame *frm)
140 {
141 	buf->width = anim->display_width;
142 	buf->height = anim->display_height;
143 	buf->rowstride = anim->frame_rowstride;
144 	buf->contrast = adjust_contrast(anim, frm->contrast);
145 	buf->data = (byte *) frm->data;
146 }
147 
frame_to_pixbuf(TilemAnimation * anim,const TilemAnimFrame * frm)148 static GdkPixbuf * frame_to_pixbuf(TilemAnimation *anim,
149                                    const TilemAnimFrame *frm)
150 {
151 	GdkPixbuf *pb;
152 
153 	pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
154 	                    anim->image_width, anim->image_height);
155 
156 	set_lcdbuf_from_frame(anim, anim->temp_buffer, frm);
157 	tilem_draw_lcd_image_rgb(anim->temp_buffer,
158 	                         gdk_pixbuf_get_pixels(pb),
159 	                         anim->image_width,
160 	                         anim->image_height,
161 	                         gdk_pixbuf_get_rowstride(pb),
162 	                         3, anim->palette,
163 	                         TILEM_SCALE_SMOOTH);
164 	anim->temp_buffer->data = NULL;
165 
166 	return pb;
167 }
168 
tilem_animation_is_static_image(GdkPixbufAnimation * ganim)169 static gboolean tilem_animation_is_static_image(GdkPixbufAnimation *ganim)
170 {
171 	TilemAnimation *anim = TILEM_ANIMATION(ganim);
172 
173 	g_return_val_if_fail(TILEM_IS_ANIMATION(ganim), FALSE);
174 
175 	if (anim->start == anim->end)
176 		return TRUE;
177 	else
178 		return FALSE;
179 }
180 
tilem_animation_get_static_image(GdkPixbufAnimation * ganim)181 static GdkPixbuf * tilem_animation_get_static_image(GdkPixbufAnimation *ganim)
182 {
183 	TilemAnimation *anim = TILEM_ANIMATION(ganim);
184 
185 	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL);
186 	g_return_val_if_fail(anim->start != NULL, NULL);
187 
188 	if (!anim->static_pixbuf)
189 		anim->static_pixbuf = frame_to_pixbuf(anim, anim->start);
190 
191 	return anim->static_pixbuf;
192 }
193 
tilem_animation_get_size(GdkPixbufAnimation * ganim,int * width,int * height)194 static void tilem_animation_get_size(GdkPixbufAnimation *ganim,
195                                      int *width, int *height)
196 {
197 	TilemAnimation *anim = TILEM_ANIMATION(ganim);
198 
199 	g_return_if_fail(TILEM_IS_ANIMATION(anim));
200 
201 	if (width) *width = anim->image_width;
202 	if (height) *height = anim->image_height;
203 }
204 
205 static GdkPixbufAnimationIter *
tilem_animation_get_iter(GdkPixbufAnimation * ganim,const GTimeVal * start_time)206 tilem_animation_get_iter(GdkPixbufAnimation *ganim,
207                          const GTimeVal *start_time)
208 {
209 	TilemAnimation *anim = TILEM_ANIMATION(ganim);
210 	TilemAnimIter *iter;
211 
212 	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL);
213 
214 	iter = g_object_new(TILEM_TYPE_ANIM_ITER, NULL);
215 	iter->anim = anim;
216 	iter->frame = anim->start;
217 	iter->current_time = *start_time;
218 
219 	g_object_ref(anim);
220 
221 	return GDK_PIXBUF_ANIMATION_ITER(iter);
222 }
223 
tilem_animation_init(G_GNUC_UNUSED TilemAnimation * anim)224 static void tilem_animation_init(G_GNUC_UNUSED TilemAnimation *anim)
225 {
226 }
227 
tilem_animation_finalize(GObject * obj)228 static void tilem_animation_finalize(GObject *obj)
229 {
230 	TilemAnimation *anim = TILEM_ANIMATION(obj);
231 	TilemAnimFrame *frm;
232 
233 	g_return_if_fail(TILEM_IS_ANIMATION(anim));
234 
235 	while (anim->start) {
236 		frm = anim->start;
237 		anim->start = frm->next;
238 		free_frame(frm);
239 	}
240 
241 	anim->start = anim->end = NULL;
242 
243 	if (anim->temp_buffer)
244 		tilem_lcd_buffer_free(anim->temp_buffer);
245 	anim->temp_buffer = NULL;
246 
247 	if (anim->palette)
248 		tilem_free(anim->palette);
249 	anim->palette = NULL;
250 
251 	if (anim->static_pixbuf)
252 		g_object_unref(anim->static_pixbuf);
253 	anim->static_pixbuf = NULL;
254 
255 	if (G_OBJECT_CLASS(tilem_animation_parent_class)->finalize)
256 		(*G_OBJECT_CLASS(tilem_animation_parent_class)->finalize)(obj);
257 }
258 
tilem_animation_class_init(TilemAnimationClass * klass)259 static void tilem_animation_class_init(TilemAnimationClass *klass)
260 {
261 	GdkPixbufAnimationClass *aclass = GDK_PIXBUF_ANIMATION_CLASS(klass);
262 	GObjectClass *oclass = G_OBJECT_CLASS(klass);
263 
264 	aclass->is_static_image  = tilem_animation_is_static_image;
265 	aclass->get_static_image = tilem_animation_get_static_image;
266 	aclass->get_size         = tilem_animation_get_size;
267 	aclass->get_iter         = tilem_animation_get_iter;
268 
269 	oclass->finalize = tilem_animation_finalize;
270 }
271 
tilem_anim_iter_get_delay_time(GdkPixbufAnimationIter * giter)272 static int tilem_anim_iter_get_delay_time(GdkPixbufAnimationIter *giter)
273 {
274 	TilemAnimIter *iter = TILEM_ANIM_ITER(giter);
275 
276 	g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), 0);
277 	g_return_val_if_fail(iter->anim != NULL, 0);
278 	g_return_val_if_fail(iter->frame != NULL, 0);
279 
280 	if (iter->anim->start == iter->anim->end)
281 		return -1;
282 	else
283 		return ((iter->frame->duration - iter->time_elapsed)
284 		        * iter->anim->time_stretch);
285 }
286 
tilem_anim_iter_get_pixbuf(GdkPixbufAnimationIter * giter)287 static GdkPixbuf * tilem_anim_iter_get_pixbuf(GdkPixbufAnimationIter *giter)
288 {
289 	TilemAnimIter *iter = TILEM_ANIM_ITER(giter);
290 
291 	g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), NULL);
292 	g_return_val_if_fail(iter->anim != NULL, NULL);
293 	g_return_val_if_fail(iter->frame != NULL, NULL);
294 
295 	if (!iter->pixbuf)
296 		iter->pixbuf = frame_to_pixbuf(iter->anim, iter->frame);
297 
298 	return iter->pixbuf;
299 }
300 
tilem_anim_iter_on_currently_loading_frame(G_GNUC_UNUSED GdkPixbufAnimationIter * giter)301 static gboolean tilem_anim_iter_on_currently_loading_frame(G_GNUC_UNUSED GdkPixbufAnimationIter *giter)
302 {
303 	return FALSE;
304 }
305 
306 static gboolean
tilem_anim_iter_advance(GdkPixbufAnimationIter * giter,const GTimeVal * current_time)307 tilem_anim_iter_advance(GdkPixbufAnimationIter *giter,
308                         const GTimeVal *current_time)
309 {
310 	TilemAnimIter *iter = TILEM_ANIM_ITER(giter);
311 	int ms;
312 
313 	g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), FALSE);
314 	g_return_val_if_fail(iter->anim != NULL, FALSE);
315 	g_return_val_if_fail(iter->frame != NULL, FALSE);
316 
317 	ms = ((current_time->tv_usec - iter->current_time.tv_usec) / 1000
318 	      + (current_time->tv_sec - iter->current_time.tv_sec) * 1000);
319 
320 	g_time_val_add(&iter->current_time, ms * 1000);
321 
322 	ms *= iter->anim->speed;
323 
324 	ms += iter->time_elapsed;
325 	if (ms < iter->frame->duration) {
326 		iter->time_elapsed = ms;
327 		return FALSE;
328 	}
329 
330 	if (iter->pixbuf)
331 		g_object_unref(iter->pixbuf);
332 	iter->pixbuf = NULL;
333 
334 	while (ms >= iter->frame->duration) {
335 		ms -= iter->frame->duration;
336 		if (iter->frame->next)
337 			iter->frame = iter->frame->next;
338 		else
339 			iter->frame = iter->anim->start;
340 	}
341 
342 	iter->time_elapsed = ms;
343 	return TRUE;
344 }
345 
tilem_anim_iter_init(G_GNUC_UNUSED TilemAnimIter * iter)346 static void tilem_anim_iter_init(G_GNUC_UNUSED TilemAnimIter *iter)
347 {
348 }
349 
tilem_anim_iter_finalize(GObject * obj)350 static void tilem_anim_iter_finalize(GObject *obj)
351 {
352 	TilemAnimIter *iter = TILEM_ANIM_ITER(obj);
353 
354 	g_return_if_fail(TILEM_IS_ANIM_ITER(obj));
355 
356 	if (iter->anim)
357 		g_object_unref(iter->anim);
358 	iter->anim = NULL;
359 
360 	if (iter->pixbuf)
361 		g_object_unref(iter->pixbuf);
362 	iter->pixbuf = NULL;
363 
364 	if (G_OBJECT_CLASS(tilem_anim_iter_parent_class)->finalize)
365 		(*G_OBJECT_CLASS(tilem_anim_iter_parent_class)->finalize)(obj);
366 }
367 
tilem_anim_iter_class_init(TilemAnimIterClass * klass)368 static void tilem_anim_iter_class_init(TilemAnimIterClass *klass)
369 {
370 	GdkPixbufAnimationIterClass *iclass = GDK_PIXBUF_ANIMATION_ITER_CLASS(klass);
371 	GObjectClass *oclass = G_OBJECT_CLASS(klass);
372 
373 	iclass->get_delay_time             = tilem_anim_iter_get_delay_time;
374 	iclass->get_pixbuf                 = tilem_anim_iter_get_pixbuf;
375 	iclass->on_currently_loading_frame = tilem_anim_iter_on_currently_loading_frame;
376 	iclass->advance                    = tilem_anim_iter_advance;
377 
378 	oclass->finalize = tilem_anim_iter_finalize;
379 }
380 
tilem_animation_new(int display_width,int display_height)381 TilemAnimation * tilem_animation_new(int display_width, int display_height)
382 {
383 	TilemAnimation *anim;
384 	TilemAnimFrame *dummy_frame;
385 
386 	g_return_val_if_fail(display_width > 0, NULL);
387 	g_return_val_if_fail(display_height > 0, NULL);
388 
389 	anim = g_object_new(TILEM_TYPE_ANIMATION, NULL);
390 	anim->display_width = display_width;
391 	anim->display_height = display_height;
392 	anim->frame_rowstride = (display_width + 7) & ~7;
393 	anim->frame_size = anim->frame_rowstride * display_height;
394 
395 	anim->image_width = display_width;
396 	anim->image_height = display_height;
397 	anim->speed = 1.0;
398 	anim->time_stretch = 1.0;
399 
400 	anim->temp_buffer = tilem_lcd_buffer_new();
401 	anim->palette = tilem_color_palette_new(255, 255, 255, 0, 0, 0, GAMMA);
402 
403 	dummy_frame = alloc_frame(anim->frame_size);
404 	dummy_frame->duration = 0;
405 	dummy_frame->contrast = 0;
406 	anim->start = anim->end = dummy_frame;
407 
408 	return anim;
409 }
410 
tilem_animation_append_frame(TilemAnimation * anim,const TilemLCDBuffer * buf,int duration)411 gboolean tilem_animation_append_frame(TilemAnimation *anim,
412                                       const TilemLCDBuffer *buf,
413                                       int duration)
414 {
415 	TilemAnimFrame *frm;
416 
417 	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), FALSE);
418 	g_return_val_if_fail(anim->end != NULL, FALSE);
419 	g_return_val_if_fail(buf != NULL, FALSE);
420 	g_return_val_if_fail(buf->data != NULL, FALSE);
421 	g_return_val_if_fail(buf->height == anim->display_height, FALSE);
422 	g_return_val_if_fail(buf->rowstride == anim->frame_rowstride, FALSE);
423 
424 	if (anim->out_of_memory)
425 		return FALSE;
426 
427 	if (anim->end->contrast == buf->contrast
428 	    && (anim->last_stamp == buf->stamp
429 	        || !memcmp(anim->end->data, buf->data, anim->frame_size))) {
430 		anim->end->duration += duration;
431 	}
432 	else {
433 		if (anim->end->duration == 0) {
434 			frm = anim->end;
435 		}
436 		else {
437 			frm = alloc_frame(anim->frame_size);
438 			if (!frm) {
439 				anim->out_of_memory = TRUE;
440 				return FALSE;
441 			}
442 			anim->end->next = frm;
443 			anim->end = frm;
444 		}
445 
446 		frm->contrast = buf->contrast;
447 		frm->duration = duration;
448 		memcpy(frm->data, buf->data, anim->frame_size);
449 	}
450 
451 	anim->last_stamp = buf->stamp;
452 	return TRUE;
453 }
454 
tilem_animation_set_size(TilemAnimation * anim,int width,int height)455 void tilem_animation_set_size(TilemAnimation *anim, int width, int height)
456 {
457 	g_return_if_fail(TILEM_IS_ANIMATION(anim));
458 	anim->image_width = width;
459 	anim->image_height = height;
460 }
461 
tilem_animation_set_colors(TilemAnimation * anim,const GdkColor * foreground,const GdkColor * background)462 void tilem_animation_set_colors(TilemAnimation *anim,
463                                 const GdkColor *foreground,
464                                 const GdkColor *background)
465 {
466 	g_return_if_fail(TILEM_IS_ANIMATION(anim));
467 	g_return_if_fail(foreground != NULL);
468 	g_return_if_fail(background != NULL);
469 
470 	if (anim->palette)
471 		tilem_free(anim->palette);
472 
473 	anim->palette = tilem_color_palette_new(background->red >> 8,
474 	                                        background->green >> 8,
475 	                                        background->blue >> 8,
476 	                                        foreground->red >> 8,
477 	                                        foreground->green >> 8,
478 	                                        foreground->blue >> 8,
479 	                                        GAMMA);
480 }
481 
tilem_animation_set_speed(TilemAnimation * anim,gdouble factor)482 void tilem_animation_set_speed(TilemAnimation *anim, gdouble factor)
483 {
484 	g_return_if_fail(TILEM_IS_ANIMATION(anim));
485 	g_return_if_fail(factor > 0.0);
486 	anim->speed = factor;
487 	anim->time_stretch = 1.0 / factor;
488 }
489 
tilem_animation_get_speed(TilemAnimation * anim)490 gdouble tilem_animation_get_speed(TilemAnimation *anim)
491 {
492 	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), 1.0);
493 	return anim->speed;
494 }
495 
tilem_animation_next_frame(TilemAnimation * anim,TilemAnimFrame * frm)496 TilemAnimFrame *tilem_animation_next_frame(TilemAnimation *anim,
497                                            TilemAnimFrame *frm)
498 {
499 	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL);
500 	if (frm)
501 		return frm->next;
502 	else
503 		return anim->start;
504 }
505 
tilem_anim_frame_get_duration(TilemAnimFrame * frm)506 int tilem_anim_frame_get_duration(TilemAnimFrame *frm)
507 {
508 	g_return_val_if_fail(frm != NULL, 0);
509 	return frm->duration;
510 }
511 
tilem_animation_get_indexed_image(TilemAnimation * anim,TilemAnimFrame * frm,byte ** buffer,int * width,int * height)512 void tilem_animation_get_indexed_image(TilemAnimation *anim,
513                                        TilemAnimFrame *frm,
514                                        byte **buffer,
515                                        int *width, int *height)
516 {
517 	g_return_if_fail(TILEM_IS_ANIMATION(anim));
518 	g_return_if_fail(frm != NULL);
519 	g_return_if_fail(buffer != NULL);
520 	g_return_if_fail(width != NULL);
521 	g_return_if_fail(height != NULL);
522 
523 	*width = anim->image_width;
524 	*height = anim->image_height;
525 	*buffer = g_new(byte, anim->image_width * anim->image_height);
526 
527 	set_lcdbuf_from_frame(anim, anim->temp_buffer, frm);
528 	tilem_draw_lcd_image_indexed(anim->temp_buffer, *buffer,
529 	                             anim->image_width, anim->image_height,
530 	                             anim->image_width,
531 	                             TILEM_SCALE_SMOOTH);
532 	anim->temp_buffer->data = NULL;
533 }
534 
tilem_animation_save(TilemAnimation * anim,const char * fname,const char * type,char ** option_keys,char ** option_values,GError ** err)535 gboolean tilem_animation_save(TilemAnimation *anim,
536                               const char *fname, const char *type,
537                               char **option_keys, char **option_values,
538                               GError **err)
539 {
540 	FILE *fp;
541 	char *dname;
542 	int errnum;
543 	GdkPixbuf *pb;
544 	gboolean status;
545 	byte palette[768];
546 	int i;
547 
548 	g_return_val_if_fail(TILEM_IS_ANIMATION(anim), FALSE);
549 	g_return_val_if_fail(fname != NULL, FALSE);
550 	g_return_val_if_fail(type != NULL, FALSE);
551 	g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
552 
553 	if (strcmp(type, "gif") != 0) {
554 		pb = gdk_pixbuf_animation_get_static_image
555 			(GDK_PIXBUF_ANIMATION(anim));
556 		status = gdk_pixbuf_savev(pb, fname, type,
557 		                          option_keys, option_values,
558 		                          err);
559 		return status;
560 	}
561 
562 	fp = g_fopen(fname, "wb");
563 	if (!fp) {
564 		errnum = errno;
565 		dname = g_filename_display_name(fname);
566 		g_set_error(err, G_FILE_ERROR,
567 		            g_file_error_from_errno(errnum),
568 		            "Failed to open '%s' for writing: %s",
569 		            dname, g_strerror(errnum));
570 		g_free(dname);
571 		return FALSE;
572 	}
573 
574 	for (i = 0; i < 256; i++) {
575 		palette[3 * i] = anim->palette[i] >> 16;
576 		palette[3 * i + 1] = anim->palette[i] >> 8;
577 		palette[3 * i + 2] = anim->palette[i];
578 	}
579 
580 	tilem_animation_write_gif(anim, palette, 256, fp);
581 
582 	if (fclose(fp)) {
583 		errnum = errno;
584 		dname = g_filename_display_name(fname);
585 		g_set_error(err, G_FILE_ERROR,
586 		            g_file_error_from_errno(errnum),
587 		            "Error while closing '%s': %s",
588 		            dname, g_strerror(errnum));
589 		g_free(dname);
590 		return FALSE;
591 	}
592 
593 	return TRUE;
594 }
595