1 /* an input matrix
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 /*
31 #define DEBUG
32  */
33 
34 #include "ip.h"
35 
36 static ClassmodelClass *parent_class = NULL;
37 
38 static void
matrix_finalize(GObject * gobject)39 matrix_finalize( GObject *gobject )
40 {
41 	Matrix *matrix;
42 
43 	g_return_if_fail( gobject != NULL );
44 	g_return_if_fail( IS_MATRIX( gobject ) );
45 
46 	matrix = MATRIX( gobject );
47 
48 #ifdef DEBUG
49 	printf( "matrix_finalize\n" );
50 #endif /*DEBUG*/
51 
52 	/* My instance finalize stuff.
53 	 */
54 	IM_FREE( matrix->value.coeff );
55 
56 	G_OBJECT_CLASS( parent_class )->finalize( gobject );
57 }
58 
59 /* Rearrange our model for a new width/height.
60  */
61 gboolean
matrix_value_resize(MatrixValue * value,int width,int height)62 matrix_value_resize( MatrixValue *value, int width, int height )
63 {
64 	double *coeff;
65 	int x, y, i;
66 
67 	if( width == value->width && height == value->height )
68 		return( TRUE );
69 
70 	if( !(coeff = IARRAY( NULL, width * height, double )) )
71 		return( FALSE );
72 
73 	/* Set what we can with values from the old matrix.
74 	 */
75 	for( i = 0, y = 0; y < height; y++ )
76 		for( x = 0; x < width; x++, i++ )
77 			if( y < value->height && x < value->width )
78 				coeff[i] = value->coeff[x +
79 					y * value->width];
80 			else
81 				coeff[i] = 0.0;
82 
83 	/* Install new values.
84 	 */
85 	IM_FREE( value->coeff );
86 	value->coeff = coeff;
87 	value->width = width;
88 	value->height = height;
89 
90 	return( TRUE );
91 }
92 
93 /* Widgets for matrix edit.
94  */
95 typedef struct _MatrixEdit {
96 	iDialog *idlg;
97 
98 	Matrix *matrix;
99 
100 	GtkWidget *width;
101 	GtkWidget *height;
102 	GtkWidget *display;
103 } MatrixEdit;
104 
105 /* Done button hit.
106  */
107 /*ARGSUSED*/
108 static void
matrix_done_cb(iWindow * iwnd,void * client,iWindowNotifyFn nfn,void * sys)109 matrix_done_cb( iWindow *iwnd, void *client,
110 	iWindowNotifyFn nfn, void *sys )
111 {
112 	MatrixEdit *eds = (MatrixEdit *) client;
113 
114 	int width, height;
115 
116 	/* Parse values. We have to scan before we resize in case we are
117 	 * sizing smaller and we have unscanned changes at the edges.
118 	 */
119 	view_scan_all();
120 	eds->matrix->display = (MatrixDisplayType)
121 		gtk_combo_box_get_active( GTK_COMBO_BOX( eds->display ) );
122 	if( !get_geditable_pint( eds->width, &width ) ||
123 		!get_geditable_pint( eds->height, &height ) ||
124 		!matrix_value_resize( &eds->matrix->value, width, height ) ) {
125 		nfn( sys, IWINDOW_ERROR );
126 		return;
127 	}
128 
129 	/* Rebuild object.
130 	 */
131 	classmodel_update( CLASSMODEL( eds->matrix ) );
132 	symbol_recalculate_all();
133 
134 	nfn( sys, IWINDOW_YES );
135 }
136 
137 /* Build the insides of matrix edit.
138  */
139 static void
matrix_buildedit(iDialog * idlg,GtkWidget * work,MatrixEdit * eds)140 matrix_buildedit( iDialog *idlg, GtkWidget *work, MatrixEdit *eds )
141 {
142 	Matrix *matrix = eds->matrix;
143 
144 	GtkSizeGroup *group;
145 
146         /* Index with MatrixType.
147          */
148         static const char *display_names[] = {
149                 N_( "Text" ),
150                 N_( "Sliders" ),
151                 N_( "Toggle buttons" ),
152                 N_( "Text, plus scale and offset" )
153         };
154 
155 	group = gtk_size_group_new( GTK_SIZE_GROUP_HORIZONTAL );
156 
157         eds->width = build_glabeltext4( work, group, "Width" );
158 	idialog_init_entry( idlg, eds->width,
159 		"Width of matrix", "%d", matrix->value.width );
160         eds->height = build_glabeltext4( work, group, "Height" );
161 	idialog_init_entry( idlg, eds->height,
162 		"Height of matrix", "%d", matrix->value.height );
163         eds->display = build_goption( work, group, _( "Display as" ),
164                 display_names, IM_NUMBER( display_names ), NULL, NULL );
165 	gtk_combo_box_set_active( GTK_COMBO_BOX( eds->display ),
166 		matrix->display );
167 
168 	UNREF( group );
169 
170         gtk_widget_show_all( work );
171 }
172 
173 static View *
matrix_view_new(Model * model,View * parent)174 matrix_view_new( Model *model, View *parent )
175 {
176 	return( matrixview_new() );
177 }
178 
179 /* Pop up a matrix edit box.
180  */
181 static void
matrix_edit(GtkWidget * parent,Model * model)182 matrix_edit( GtkWidget *parent, Model *model )
183 {
184 	Matrix *matrix = MATRIX( model );
185 	MatrixEdit *eds = INEW( NULL, MatrixEdit );
186 	GtkWidget *idlg;
187 
188 	eds->matrix = matrix;
189 
190 	idlg = idialog_new();
191 	iwindow_set_title( IWINDOW( idlg ), _( "Edit %s %s" ),
192 		IOBJECT_GET_CLASS_NAME( model ),
193 		IOBJECT( HEAPMODEL( model )->row )->name );
194 	idialog_set_build( IDIALOG( idlg ),
195 		(iWindowBuildFn) matrix_buildedit, eds, NULL, NULL );
196 	idialog_set_callbacks( IDIALOG( idlg ),
197 		iwindow_true_cb, NULL, idialog_free_client, eds );
198 	idialog_add_ok( IDIALOG( idlg ),
199 		matrix_done_cb, _( "Set %s" ),
200 		IOBJECT_GET_CLASS_NAME( model ) );
201 	iwindow_set_parent( IWINDOW( idlg ), parent );
202 	idialog_set_iobject( IDIALOG( idlg ), IOBJECT( model ) );
203 	iwindow_build( IWINDOW( idlg ) );
204 
205 	gtk_widget_show( GTK_WIDGET( idlg ) );
206 }
207 
208 static gboolean
matrix_graphic_save(Classmodel * classmodel,GtkWidget * parent,const char * filename)209 matrix_graphic_save( Classmodel *classmodel,
210 	GtkWidget *parent, const char *filename )
211 {
212 	Matrix *matrix = MATRIX( classmodel );
213 	DOUBLEMASK *dmask;
214 	char buf[FILENAME_MAX];
215 
216 	if( !(dmask = matrix_model_to_dmask( matrix )) )
217 		return( FALSE );
218 
219 	/* We don't want $VAR etc. in the filename we pass down to the file
220 	 * ops.
221 	 */
222 	im_strncpy( buf, filename, FILENAME_MAX );
223 	path_expand( buf );
224 
225 	if( im_write_dmask_name( dmask, buf ) ) {
226 		error_vips_all();
227 		IM_FREEF( im_free_dmask, dmask );
228 		return( FALSE );
229 	}
230 	IM_FREEF( im_free_dmask, dmask );
231 
232 	mainw_recent_add( &mainw_recent_matrix, filename );
233 
234 	return( TRUE );
235 }
236 
237 static gboolean
matrix_graphic_replace(Classmodel * classmodel,GtkWidget * parent,const char * filename)238 matrix_graphic_replace( Classmodel *classmodel,
239 	GtkWidget *parent, const char *filename )
240 {
241 	Matrix *matrix = MATRIX( classmodel );
242 	Row *row = HEAPMODEL( matrix )->row;
243 	iText *itext = ITEXT( HEAPMODEL( matrix )->rhs->itext );
244 	DOUBLEMASK *dmask;
245 	char txt[MAX_STRSIZE];
246 	VipsBuf buf = VIPS_BUF_STATIC( txt );
247 
248 	/* We don't want $VAR etc. in the filename we pass down to the file
249 	 * ops.
250 	 */
251 	im_strncpy( txt, filename, FILENAME_MAX );
252 	path_expand( txt );
253 
254 	if( !(dmask = im_read_dmask( txt )) ) {
255 		error_vips_all();
256 		return( FALSE );
257 	}
258 
259 	matrix_dmask_to_ip( dmask, &buf );
260 	im_free_dmask( dmask );
261 
262 	if( itext_set_formula( itext, vips_buf_all( &buf ) ) ) {
263 		itext_set_edited( itext, TRUE );
264 		(void) expr_dirty( row->expr, link_serial_new() );
265 	}
266 
267 	mainw_recent_add( &mainw_recent_matrix, filename );
268 
269 	return( TRUE );
270 }
271 
272 /* Members of matrix we automate.
273  */
274 static ClassmodelMember matrix_members[] = {
275 	{ CLASSMODEL_MEMBER_MATRIX, NULL, 0,
276 		MEMBER_VALUE, NULL, N_( "Value" ),
277 		G_STRUCT_OFFSET( Matrix, value ) },
278 	{ CLASSMODEL_MEMBER_DOUBLE, NULL, 0,
279 		MEMBER_SCALE, "scale", N_( "Scale" ),
280 		G_STRUCT_OFFSET( Matrix, scale ) },
281 	{ CLASSMODEL_MEMBER_DOUBLE, NULL, 0,
282 		MEMBER_OFFSET, "offset", N_( "Offset" ),
283 		G_STRUCT_OFFSET( Matrix, offset ) },
284 	{ CLASSMODEL_MEMBER_STRING, NULL, 0,
285 		MEMBER_FILENAME, "filename", N_( "Filename" ),
286 		G_STRUCT_OFFSET( Classmodel, filename ) },
287 	{ CLASSMODEL_MEMBER_ENUM, NULL, MATRIX_DISPLAY_LAST - 1,
288 		MEMBER_DISPLAY, "display", N_( "Display" ),
289 		G_STRUCT_OFFSET( Matrix, display ) }
290 };
291 
292 static void
matrix_class_init(MatrixClass * class)293 matrix_class_init( MatrixClass *class )
294 {
295 	GObjectClass *gobject_class = (GObjectClass *) class;
296 	iObjectClass *iobject_class = (iObjectClass *) class;
297 	ModelClass *model_class = (ModelClass *) class;
298 	ClassmodelClass *classmodel_class = (ClassmodelClass *) class;
299 
300 	parent_class = g_type_class_peek_parent( class );
301 
302 	/* Create signals.
303 	 */
304 
305 	/* Init methods.
306 	 */
307 	gobject_class->finalize = matrix_finalize;
308 
309 	iobject_class->user_name = _( "Matrix" );
310 
311 	model_class->view_new = matrix_view_new;
312 	model_class->edit = matrix_edit;
313 
314 	classmodel_class->graphic_save = matrix_graphic_save;
315 	classmodel_class->graphic_replace = matrix_graphic_replace;
316 
317 	classmodel_class->filetype = filesel_type_matrix;
318 	classmodel_class->filetype_pref = "MATRIX_FILE_TYPE";
319 
320 	/* Static init.
321 	 */
322 	model_register_loadable( MODEL_CLASS( class ) );
323 
324 	classmodel_class->members = matrix_members;
325 	classmodel_class->n_members = IM_NUMBER( matrix_members );
326 }
327 
328 static void
matrix_init(Matrix * matrix)329 matrix_init( Matrix *matrix )
330 {
331 #ifdef DEBUG
332 	printf( "matrix_init\n" );
333 #endif /*DEBUG*/
334 
335 	matrix->value.coeff = NULL;
336         matrix->value.width = 0;
337 	matrix->value.height = 0;
338 	matrix->display = MATRIX_DISPLAY_TEXT;
339 	matrix->scale = 1.0;
340 	matrix->offset = 0.0;
341 	matrix->selected = FALSE;
342 
343 	iobject_set( IOBJECT( matrix ), CLASS_MATRIX, NULL );
344 }
345 
346 GtkType
matrix_get_type(void)347 matrix_get_type( void )
348 {
349 	static GType type = 0;
350 
351 	if( !type ) {
352 		static const GTypeInfo info = {
353 			sizeof( MatrixClass ),
354 			NULL,           /* base_init */
355 			NULL,           /* base_finalize */
356 			(GClassInitFunc) matrix_class_init,
357 			NULL,           /* class_finalize */
358 			NULL,           /* class_data */
359 			sizeof( Matrix ),
360 			32,             /* n_preallocs */
361 			(GInstanceInitFunc) matrix_init,
362 		};
363 
364 		type = g_type_register_static( TYPE_CLASSMODEL,
365 			"Matrix", &info, 0 );
366 	}
367 
368 	return( type );
369 }
370 
371 void
matrix_select(Matrix * matrix,int left,int top,int width,int height)372 matrix_select( Matrix *matrix, int left, int top, int width, int height )
373 {
374 	if( !matrix->selected ||
375 		matrix->range.left != left ||
376 		matrix->range.top != top ||
377 		matrix->range.width != width ||
378 		matrix->range.height != height ) {
379 		Row *row = HEAPMODEL( matrix )->row;
380 
381 #ifdef DEBUG
382 		printf( "matrix_select: "
383 			"left=%d, top = %d, width = %d, height = %d\n",
384 			left, top, width, height );
385 #endif /*DEBUG*/
386 
387 		matrix->selected = TRUE;
388 		matrix->range.left = left;
389 		matrix->range.top = top;
390 		matrix->range.width = width;
391 		matrix->range.height = height;
392 		iobject_changed( IOBJECT( matrix ) );
393 
394 		/* Also make sure this row is selected.
395 		 */
396 		row_select_ensure( row );
397 
398 		/* The range of cells selected has changed, so the workspace
399 		 * must update the status line too. row_select_ensure() only
400 		 * spots row on/off selects. Yuk!
401 		 */
402 		iobject_changed( IOBJECT( row->ws ) );
403 	}
404 }
405 
406 void
matrix_deselect(Matrix * matrix)407 matrix_deselect( Matrix *matrix )
408 {
409 	if( matrix->selected ) {
410 		Row *row = HEAPMODEL( matrix )->row;
411 
412 #ifdef DEBUG
413 		printf( "matrix_deselect\n" );
414 #endif /*DEBUG*/
415 
416 		matrix->selected = FALSE;
417 		iobject_changed( IOBJECT( matrix ) );
418 
419 		/* Also make sure this row is not selected.
420 		 */
421 		row_deselect( row );
422 	}
423 }
424 
425 /* Guess a display type from a filename.
426  */
427 static int
matrix_guess_display(const char * fname)428 matrix_guess_display( const char *fname )
429 {
430 	/* Choose display type based on filename suffix ... rec
431 	 * displays as 1, mor displays as 2, .con displays as 3, all others
432 	 * display as 0. Keep in sync with MatrixDisplayType.
433 	 */
434 	static const FileselFileType *types[] = {
435 		&filesel_xfile_type,	// matrix
436 		&filesel_rfile_type,	// recombination
437 		&filesel_mfile_type,	// morphology
438 		&filesel_cfile_type	// convolution
439 	};
440 
441 	int i;
442 
443 	if( !fname )
444 		return( 0 );
445 
446 	for( i = 0; i < IM_NUMBER( types ); i++ )
447 		if( is_file_type( types[i], fname ) )
448 			return( i );
449 
450 	return( 0 );
451 }
452 
453 /* Make an ip definition out of a DOUBLEMASK.
454  */
455 void
matrix_dmask_to_ip(DOUBLEMASK * dmask,VipsBuf * buf)456 matrix_dmask_to_ip( DOUBLEMASK *dmask, VipsBuf *buf )
457 {
458 	int x, y;
459 
460 	/* Build matrix expression.
461 	 */
462 	vips_buf_appends( buf, CLASS_MATRIX " " );
463 
464 	vips_buf_appends( buf, "[" );
465 	for( y = 0; y < dmask->ysize; y++ ) {
466 		vips_buf_appends( buf, "[" );
467 		for( x = 0; x < dmask->xsize; x++ ) {
468 			vips_buf_appendf( buf, "%g",
469 				dmask->coeff[x + y*dmask->xsize] );
470 			if( x != dmask->xsize - 1 )
471 				vips_buf_appends( buf, "," );
472 		}
473 		vips_buf_appends( buf, "]" );
474 		if( y != dmask->ysize - 1 )
475 			vips_buf_appends( buf, "," );
476 	}
477 	vips_buf_appends( buf, "]" );
478 
479 	vips_buf_appendf( buf, "(%g) (%g) \"%s\" %d",
480 		dmask->scale, dmask->offset, dmask->filename,
481 		matrix_guess_display( dmask->filename ) );
482 }
483 
484 /* Make a heap object out of a DOUBLEMASK.
485  */
486 gboolean
matrix_dmask_to_heap(Heap * heap,DOUBLEMASK * dmask,PElement * out)487 matrix_dmask_to_heap( Heap *heap, DOUBLEMASK *dmask, PElement *out )
488 {
489 	Symbol *sym = compile_lookup( symbol_root->expr->compile,
490 		CLASS_MATRIX );
491 
492 	PElement rhs;
493 
494 	if( !sym || !sym->expr || !sym->expr->compile ||
495 		!heap_copy( heap, sym->expr->compile, out ) )
496 		return( FALSE );
497 
498 	if( !heap_appl_add( heap, out, &rhs ) ||
499 		!heap_matrix_new( heap,
500 			dmask->xsize, dmask->ysize, dmask->coeff, &rhs ) ||
501 		!heap_appl_add( heap, out, &rhs ) ||
502 		!heap_real_new( heap, dmask->scale, &rhs ) ||
503 		!heap_appl_add( heap, out, &rhs ) ||
504 		!heap_real_new( heap, dmask->offset, &rhs ) ||
505 		!heap_appl_add( heap, out, &rhs ) ||
506 		!heap_managedstring_new( heap, dmask->filename, &rhs ) ||
507 		!heap_appl_add( heap, out, &rhs ) ||
508 		!heap_real_new( heap,
509 			matrix_guess_display( dmask->filename ), &rhs ) )
510 		return( FALSE );
511 
512 	return( TRUE );
513 }
514 
515 /* Cast an IMASK to a DMASK.
516  */
517 DOUBLEMASK *
matrix_imask_to_dmask(INTMASK * imask)518 matrix_imask_to_dmask( INTMASK *imask )
519 {
520 	DOUBLEMASK *dmask;
521 	int i;
522 
523 	if( !(dmask = im_create_dmask( imask->filename,
524 		imask->xsize, imask->ysize )) ) {
525 		error_vips_all();
526 		return( NULL );
527 	}
528 
529 	dmask->scale = imask->scale;
530 	dmask->offset = imask->offset;
531 	for( i = 0; i < imask->xsize * imask->ysize; i++ )
532 		dmask->coeff[i] = imask->coeff[i];
533 
534 	return( dmask );
535 }
536 
537 /* Cast a DMASK to an IMASK.
538  */
539 INTMASK *
matrix_dmask_to_imask(DOUBLEMASK * dmask)540 matrix_dmask_to_imask( DOUBLEMASK *dmask )
541 {
542 	INTMASK *imask;
543 	int i;
544 
545 	if( !(imask = im_create_imask( dmask->filename,
546 		dmask->xsize, dmask->ysize )) ) {
547 		error_vips_all();
548 		return( NULL );
549 	}
550 
551 	imask->scale = dmask->scale;
552 	imask->offset = dmask->offset;
553 	for( i = 0; i < dmask->xsize * dmask->ysize; i++ )
554 		imask->coeff[i] = dmask->coeff[i];
555 
556 	return( imask );
557 }
558 
559 /* Make a heap object out of an INTMASK.
560  */
561 gboolean
matrix_imask_to_heap(Heap * heap,INTMASK * imask,PElement * out)562 matrix_imask_to_heap( Heap *heap, INTMASK *imask, PElement *out )
563 {
564 	DOUBLEMASK *dmask;
565 
566 	if( !(dmask = matrix_imask_to_dmask( imask )) )
567 		return( FALSE );
568 	if( !matrix_dmask_to_heap( heap, dmask, out ) ) {
569 		im_free_dmask( dmask );
570 		return( FALSE );
571 	}
572 	im_free_dmask( dmask );
573 
574 	return( TRUE );
575 }
576 
577 /* Make a DOUBLEMASK out of an ip value.
578  */
579 DOUBLEMASK *
matrix_ip_to_dmask(PElement * root)580 matrix_ip_to_dmask( PElement *root )
581 {
582 	char buf[MAX_STRSIZE];
583 	char name[FILENAME_MAX];
584 	DOUBLEMASK *dmask;
585 	double scale, offset;
586 	char *filename;
587 	int width, height;
588 
589 	if( !class_get_member_matrix_size( root,
590 		MEMBER_VALUE, &width, &height ) )
591 		return( NULL );
592 
593 	if( class_get_member_string( root, MEMBER_FILENAME, buf, MAX_STRSIZE ) )
594 		filename = buf;
595 	else {
596 		if( !temp_name( name, "mat" ) )
597 			return( NULL );
598 
599 		filename = name;
600 	}
601 
602 	if( !(dmask = im_create_dmask( filename, width, height )) ) {
603 		error_vips_all();
604 		return( NULL );
605 	}
606 
607 	if( !class_get_member_matrix( root, MEMBER_VALUE,
608 		dmask->coeff, width * height, &width, &height ) ) {
609 		IM_FREEF( im_free_dmask, dmask );
610 		return( FALSE );
611 	}
612 
613 	if( !class_get_member_real( root, MEMBER_SCALE, &scale ) )
614 		scale = 1.0;
615 	if( !class_get_member_real( root, MEMBER_OFFSET, &offset ) )
616 		offset = 0.0;
617 	dmask->scale = scale;
618 	dmask->offset = offset;
619 
620 	return( dmask );
621 }
622 
623 /* Make an INTMASK out of an ip value.
624  */
625 INTMASK *
matrix_ip_to_imask(PElement * root)626 matrix_ip_to_imask( PElement *root )
627 {
628 	DOUBLEMASK *dmask;
629 	INTMASK *imask;
630 
631 	if( !(dmask = matrix_ip_to_dmask( root )) )
632 		return( NULL );
633 
634 	if( !(imask = matrix_dmask_to_imask( dmask )) ) {
635 		IM_FREEF( im_free_dmask, dmask );
636 		return( NULL );
637 	}
638 
639 	return( imask );
640 }
641 
642 DOUBLEMASK *
matrix_model_to_dmask(Matrix * matrix)643 matrix_model_to_dmask( Matrix *matrix )
644 {
645 	DOUBLEMASK *dmask;
646 	int i;
647 
648 	if( !(dmask = im_create_dmask( CLASSMODEL( matrix )->filename,
649 		matrix->value.width, matrix->value.height )) ) {
650 		error_vips_all();
651 		return( NULL );
652 	}
653 
654 	dmask->scale = matrix->scale;
655 	dmask->offset = matrix->offset;
656 	for( i = 0; i < matrix->value.width * matrix->value.height; i++ )
657 		dmask->coeff[i] = matrix->value.coeff[i];
658 
659 	return( dmask );
660 }
661 
662 gboolean
matrix_dmask_to_model(Matrix * matrix,DOUBLEMASK * dmask)663 matrix_dmask_to_model( Matrix *matrix, DOUBLEMASK *dmask )
664 {
665 	int i;
666 
667 	if( !matrix_value_resize( &matrix->value,
668 		dmask->xsize, dmask->ysize ) )
669 		return( FALSE );
670 
671 	matrix->scale = dmask->scale;
672 	matrix->offset = dmask->offset;
673 	for( i = 0; i < matrix->value.width * matrix->value.height; i++ )
674 		matrix->value.coeff[i] = dmask->coeff[i];
675 	matrix->display =
676 		(MatrixDisplayType) matrix_guess_display( dmask->filename );
677 	IM_SETSTR( CLASSMODEL( matrix )->filename, dmask->filename );
678 
679 	return( TRUE );
680 }
681 
682