Change signal name and signature from "backend-changed" to "items-changed"
[pspp] / src / ui / gui / psppire-data-store.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2006, 2008, 2009, 2010, 2011, 2012, 2013  Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
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
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18 #include <string.h>
19 #include <stdlib.h>
20 #include <gettext.h>
21 #define _(msgid) gettext (msgid)
22 #define N_(msgid) msgid
23
24 #include <data/datasheet.h>
25 #include <data/data-out.h>
26 #include <data/variable.h>
27
28 #include <ui/gui/psppire-marshal.h>
29
30 #include <pango/pango-context.h>
31
32 #include "psppire-data-store.h"
33 #include <libpspp/i18n.h>
34 #include "helper.h"
35
36 #include <data/dictionary.h>
37 #include <data/missing-values.h>
38 #include <data/value-labels.h>
39 #include <data/data-in.h>
40 #include <data/format.h>
41
42 #include <math/sort.h>
43
44 #include "xalloc.h"
45 #include "xmalloca.h"
46
47
48
49 static void psppire_data_store_init            (PsppireDataStore      *data_store);
50 static void psppire_data_store_class_init      (PsppireDataStoreClass *class);
51
52 static void psppire_data_store_finalize        (GObject           *object);
53 static void psppire_data_store_dispose        (GObject           *object);
54
55 static gboolean psppire_data_store_insert_case (PsppireDataStore *ds,
56                                                 struct ccase *cc,
57                                                 casenumber posn);
58
59
60 static gboolean psppire_data_store_data_in (PsppireDataStore *ds,
61                                             casenumber casenum, gint idx,
62                                             struct substring input,
63                                             const struct fmt_spec *fmt);
64
65 static GObjectClass *parent_class = NULL;
66
67
68 enum
69   {
70     ITEMS_CHANGED,
71     CASES_DELETED,
72     CASE_INSERTED,
73     CASE_CHANGED,
74     n_SIGNALS
75   };
76
77 static guint signals [n_SIGNALS];
78
79 static gint
80 __tree_model_iter_n_children (GtkTreeModel *tree_model,
81                              GtkTreeIter *iter)
82 {
83   PsppireDataStore *store  = PSPPIRE_DATA_STORE (tree_model);
84
85   gint n =  datasheet_get_n_rows (store->datasheet);
86
87   return n;
88 }
89
90
91 static gint
92 __tree_model_get_n_columns (GtkTreeModel *tree_model)
93 {
94   PsppireDataStore *store  = PSPPIRE_DATA_STORE (tree_model);
95
96   return psppire_dict_get_value_cnt (store->dict);
97 }
98
99
100 static gboolean
101 __iter_nth_child (GtkTreeModel *tree_model,
102                   GtkTreeIter *iter,
103                   GtkTreeIter *parent,
104                   gint n)
105 {
106   PsppireDataStore *store  = PSPPIRE_DATA_STORE (tree_model);
107   
108   g_assert (parent == NULL);
109
110   g_return_val_if_fail (store, FALSE);
111   g_return_val_if_fail (store->datasheet, FALSE);
112
113   if (n >= datasheet_get_n_rows (store->datasheet))
114     {
115       iter->stamp = -1;
116       iter->user_data = NULL;
117       return FALSE;
118     }
119   
120   iter->user_data = n;
121   return TRUE;
122 }
123
124
125
126 static void
127 __get_value (GtkTreeModel *tree_model,
128              GtkTreeIter *iter,
129              gint column,
130              GValue *value)
131 {
132   PsppireDataStore *store  = PSPPIRE_DATA_STORE (tree_model);
133
134   g_value_init (value, G_TYPE_DOUBLE);
135
136   gint row = GPOINTER_TO_INT (iter->user_data);
137
138   struct ccase *cc = datasheet_get_row (store->datasheet, row);
139   
140   g_value_set_double (value, case_data_idx (cc, column)->f);
141   case_unref (cc);
142 }
143
144
145 static void
146 __tree_model_init (GtkTreeModelIface *iface)
147 {
148   iface->get_flags       = NULL; 
149   iface->get_n_columns   = __tree_model_get_n_columns ;
150   iface->get_column_type = NULL; 
151   iface->get_iter        = NULL; 
152   iface->iter_next       = NULL; 
153   iface->get_path        = NULL; 
154   iface->get_value       = __get_value;
155
156   iface->iter_children   = NULL; 
157   iface->iter_has_child  = NULL; 
158   iface->iter_n_children = __tree_model_iter_n_children;
159   iface->iter_nth_child  = __iter_nth_child;
160   iface->iter_parent     = NULL; 
161 }
162
163
164 GType
165 psppire_data_store_get_type (void)
166 {
167   static GType data_store_type = 0;
168
169   if (!data_store_type)
170     {
171       static const GTypeInfo data_store_info =
172       {
173         sizeof (PsppireDataStoreClass),
174         NULL,           /* base_init */
175         NULL,           /* base_finalize */
176         (GClassInitFunc) psppire_data_store_class_init,
177         NULL,           /* class_finalize */
178         NULL,           /* class_data */
179         sizeof (PsppireDataStore),
180         0,
181         (GInstanceInitFunc) psppire_data_store_init,
182       };
183
184       static const GInterfaceInfo tree_model_info = {
185         (GInterfaceInitFunc) __tree_model_init,
186         NULL,
187         NULL
188       };
189
190       data_store_type = g_type_register_static (G_TYPE_OBJECT,
191                                                 "PsppireDataStore",
192                                                 &data_store_info, 0);
193
194       g_type_add_interface_static (data_store_type, GTK_TYPE_TREE_MODEL,
195                                    &tree_model_info);
196     }
197
198   return data_store_type;
199 }
200
201
202 static void
203 psppire_data_store_class_init (PsppireDataStoreClass *class)
204 {
205   GObjectClass *object_class;
206
207   parent_class = g_type_class_peek_parent (class);
208   object_class = (GObjectClass*) class;
209
210   object_class->finalize = psppire_data_store_finalize;
211   object_class->dispose = psppire_data_store_dispose;
212
213     signals [ITEMS_CHANGED] =
214     g_signal_new ("changed",
215                   G_TYPE_FROM_CLASS (class),
216                   G_SIGNAL_RUN_FIRST,
217                   0,
218                   NULL, NULL,
219                   psppire_marshal_VOID__UINT_UINT_UINT,
220                   G_TYPE_NONE,
221                   3,
222                   G_TYPE_UINT,
223                   G_TYPE_UINT,
224                   G_TYPE_UINT);
225
226   signals [CASE_INSERTED] =
227     g_signal_new ("case-inserted",
228                   G_TYPE_FROM_CLASS (class),
229                   G_SIGNAL_RUN_FIRST,
230                   0,
231                   NULL, NULL,
232                   g_cclosure_marshal_VOID__INT,
233                   G_TYPE_NONE,
234                   1,
235                   G_TYPE_INT);
236
237
238   signals [CASE_CHANGED] =
239     g_signal_new ("case-changed",
240                   G_TYPE_FROM_CLASS (class),
241                   G_SIGNAL_RUN_FIRST,
242                   0,
243                   NULL, NULL,
244                   g_cclosure_marshal_VOID__INT,
245                   G_TYPE_NONE,
246                   1,
247                   G_TYPE_INT);
248
249   signals [CASES_DELETED] =
250     g_signal_new ("cases-deleted",
251                   G_TYPE_FROM_CLASS (class),
252                   G_SIGNAL_RUN_FIRST,
253                   0,
254                   NULL, NULL,
255                   psppire_marshal_VOID__INT_INT,
256                   G_TYPE_NONE,
257                   2,
258                   G_TYPE_INT,
259                   G_TYPE_INT);
260 }
261
262
263
264 static gboolean
265 psppire_data_store_insert_value (PsppireDataStore *ds,
266                                   gint width, gint where);
267
268 casenumber
269 psppire_data_store_get_case_count (const PsppireDataStore *store)
270 {
271   return datasheet_get_n_rows (store->datasheet);
272 }
273
274 size_t
275 psppire_data_store_get_value_count (const PsppireDataStore *store)
276 {
277   return psppire_dict_get_value_cnt (store->dict);
278 }
279
280 const struct caseproto *
281 psppire_data_store_get_proto (const PsppireDataStore *store)
282 {
283   return psppire_dict_get_proto (store->dict);
284 }
285
286 static void
287 psppire_data_store_init (PsppireDataStore *data_store)
288 {
289   data_store->dict = NULL;
290   data_store->datasheet = NULL;
291   data_store->dispose_has_run = FALSE;
292 }
293
294 /*
295    A callback which occurs after a variable has been deleted.
296  */
297 static void
298 delete_variable_callback (GObject *obj, const struct variable *var UNUSED,
299                           gint dict_index, gint case_index,
300                           gpointer data)
301 {
302   PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
303
304   g_return_if_fail (store->datasheet);
305
306   datasheet_delete_columns (store->datasheet, case_index, 1);
307   datasheet_insert_column (store->datasheet, NULL, -1, case_index);
308 }
309
310 struct resize_datum_aux
311   {
312     const struct dictionary *dict;
313     const struct variable *new_variable;
314     const struct variable *old_variable;
315   };
316
317 static void
318 resize_datum (const union value *old, union value *new, const void *aux_)
319 {
320   const struct resize_datum_aux *aux = aux_;
321   int new_width = var_get_width (aux->new_variable);
322   const char *enc = dict_get_encoding (aux->dict);
323   const struct fmt_spec *newfmt = var_get_print_format (aux->new_variable);
324   char *s = data_out (old, enc, var_get_print_format (aux->old_variable));
325   enum fmt_type type = (fmt_usable_for_input (newfmt->type)
326                         ? newfmt->type
327                         : FMT_DOLLAR);
328   free (data_in (ss_cstr (s), enc, type, new, new_width, enc));
329   free (s);
330 }
331
332 static void
333 variable_changed_callback (GObject *obj, gint var_num, guint what, const struct variable *oldvar,
334                            gpointer data)
335 {
336   PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
337   struct variable *variable = psppire_dict_get_variable (store->dict, var_num);
338
339   if (what & VAR_TRAIT_WIDTH)
340     {
341       int posn = var_get_case_index (variable);
342       struct resize_datum_aux aux;
343       aux.old_variable = oldvar;
344       aux.new_variable = variable;
345       aux.dict = store->dict->dict;
346       datasheet_resize_column (store->datasheet, posn, var_get_width (variable),
347                                resize_datum, &aux);
348     }
349 }
350
351 static void
352 insert_variable_callback (GObject *obj, gint var_num, gpointer data)
353 {
354   struct variable *variable;
355   PsppireDataStore *store;
356   gint posn;
357
358   g_return_if_fail (data);
359
360   store  = PSPPIRE_DATA_STORE (data);
361
362   variable = psppire_dict_get_variable (store->dict, var_num);
363   posn = var_get_case_index (variable);
364   psppire_data_store_insert_value (store, var_get_width (variable), posn);
365 }
366
367 /**
368  * psppire_data_store_new:
369  * @dict: The dictionary for this data_store.
370  *
371  *
372  * Return value: a new #PsppireDataStore
373  **/
374 PsppireDataStore *
375 psppire_data_store_new (PsppireDict *dict)
376 {
377   PsppireDataStore *retval;
378
379   retval = g_object_new (PSPPIRE_TYPE_DATA_STORE, NULL);
380
381   psppire_data_store_set_dictionary (retval, dict);
382
383   return retval;
384 }
385
386 void
387 psppire_data_store_set_reader (PsppireDataStore *ds,
388                                struct casereader *reader)
389 {
390   gint i;
391   gint old_n = 0;
392   if ( ds->datasheet)
393     {
394       old_n = datasheet_get_n_rows (ds->datasheet);
395       datasheet_destroy (ds->datasheet);
396     }
397
398   ds->datasheet = datasheet_create (reader);
399
400   gint new_n = datasheet_get_n_rows (ds->datasheet);
401   
402   if ( ds->dict )
403     for (i = 0 ; i < n_dict_signals; ++i )
404       {
405         if ( ds->dict_handler_id [i] > 0)
406           {
407             g_signal_handler_unblock (ds->dict,
408                                       ds->dict_handler_id[i]);
409           }
410       }
411
412   g_signal_emit (ds, signals[ITEMS_CHANGED], 0, 0, old_n, new_n);
413 }
414
415
416 /**
417  * psppire_data_store_replace_set_dictionary:
418  * @data_store: The variable store
419  * @dict: The dictionary to set
420  *
421  * If a dictionary is already associated with the data-store, then it will be
422  * destroyed.
423  **/
424 void
425 psppire_data_store_set_dictionary (PsppireDataStore *data_store, PsppireDict *dict)
426 {
427   int i;
428
429   /* Disconnect any existing handlers */
430   if ( data_store->dict )
431     for (i = 0 ; i < n_dict_signals; ++i )
432       {
433         g_signal_handler_disconnect (data_store->dict,
434                                      data_store->dict_handler_id[i]);
435       }
436
437   data_store->dict = dict;
438
439   if ( dict != NULL)
440     {
441
442       data_store->dict_handler_id [VARIABLE_INSERTED] =
443         g_signal_connect (dict, "variable-inserted",
444                           G_CALLBACK (insert_variable_callback),
445                           data_store);
446
447       data_store->dict_handler_id [VARIABLE_DELETED] =
448         g_signal_connect (dict, "variable-deleted",
449                           G_CALLBACK (delete_variable_callback),
450                           data_store);
451
452       data_store->dict_handler_id [VARIABLE_CHANGED] =
453         g_signal_connect (dict, "variable-changed",
454                           G_CALLBACK (variable_changed_callback),
455                           data_store);
456     }
457
458
459
460   /* The entire model has changed */
461
462   if ( data_store->dict )
463     for (i = 0 ; i < n_dict_signals; ++i )
464       {
465         if ( data_store->dict_handler_id [i] > 0)
466           {
467             g_signal_handler_block (data_store->dict,
468                                     data_store->dict_handler_id[i]);
469           }
470       }
471 }
472
473 static void
474 psppire_data_store_finalize (GObject *object)
475 {
476   PsppireDataStore *ds = PSPPIRE_DATA_STORE (object);
477
478   if (ds->datasheet)
479     {
480       datasheet_destroy (ds->datasheet);
481       ds->datasheet = NULL;
482     }
483
484   /* must chain up */
485   (* parent_class->finalize) (object);
486 }
487
488
489 static void
490 psppire_data_store_dispose (GObject *object)
491 {
492   PsppireDataStore *ds = PSPPIRE_DATA_STORE (object);
493
494   if (ds->dispose_has_run)
495     return;
496
497   psppire_data_store_set_dictionary (ds, NULL);
498
499   /* must chain up */
500   (* parent_class->dispose) (object);
501
502   ds->dispose_has_run = TRUE;
503 }
504
505
506
507 /* Insert a blank case before POSN */
508 gboolean
509 psppire_data_store_insert_new_case (PsppireDataStore *ds, casenumber posn)
510 {
511   gboolean result;
512   const struct caseproto *proto;
513   struct ccase *cc;
514   g_return_val_if_fail (ds, FALSE);
515
516   proto = datasheet_get_proto (ds->datasheet);
517   g_return_val_if_fail (caseproto_get_n_widths (proto) > 0, FALSE);
518   g_return_val_if_fail (posn <= psppire_data_store_get_case_count (ds), FALSE);
519
520   cc = case_create (proto);
521   case_set_missing (cc);
522
523   result = psppire_data_store_insert_case (ds, cc, posn);
524
525   case_unref (cc);
526
527   return result;
528 }
529
530 gchar *
531 psppire_data_store_get_string (PsppireDataStore *store,
532                                glong row, const struct variable *var,
533                                bool use_value_label)
534 {
535   gchar *string;
536   union value v;
537   int width;
538
539   g_return_val_if_fail (store != NULL, NULL);
540   g_return_val_if_fail (store->datasheet != NULL, NULL);
541   g_return_val_if_fail (var != NULL, NULL);
542
543   if (row < 0 || row >= datasheet_get_n_rows (store->datasheet))
544     return NULL;
545
546   width = var_get_width (var);
547   value_init (&v, width);
548   datasheet_get_value (store->datasheet, row, var_get_case_index (var), &v);
549
550   string = NULL;
551   if (use_value_label)
552     {
553       const char *label = var_lookup_value_label (var, &v);
554       if (label != NULL)
555         string = g_strdup (label);
556     }
557   if (string == NULL)
558     string = value_to_text (v, var);
559
560   value_destroy (&v, width);
561
562   return string;
563 }
564
565
566 /* Attempts to update that part of the variable store which corresponds to VAR
567    within ROW with the value TEXT.
568
569    If USE_VALUE_LABEL is true, and TEXT is a value label for the column's
570    variable, then stores the value from that value label instead of the literal
571    TEXT.
572
573    Returns true if anything was updated, false otherwise.  */
574 gboolean
575 psppire_data_store_set_string (PsppireDataStore *store,
576                                const gchar *text,
577                                glong row, const struct variable *var,
578                                gboolean use_value_label)
579 {
580   gint case_index;
581   glong n_cases;
582   gboolean ok;
583
584   n_cases = psppire_data_store_get_case_count (store);
585   if (row > n_cases)
586     return FALSE;
587   if (row == n_cases)
588     psppire_data_store_insert_new_case (store, row);
589
590   case_index = var_get_case_index (var);
591   if (use_value_label)
592     {
593       const struct val_labs *vls = var_get_value_labels (var);
594       const union value *value = vls ? val_labs_find_value (vls, text) : NULL;
595       if (value)
596         ok = datasheet_put_value (store->datasheet, row, case_index, value);
597       else
598         ok = FALSE;
599     }
600   else
601     ok = psppire_data_store_data_in (store, row, case_index, ss_cstr (text),
602                                      var_get_print_format (var));
603
604   if (ok)
605     g_signal_emit (store, signals [CASE_CHANGED], 0, row);
606   return ok;
607 }
608
609
610
611 void
612 psppire_data_store_clear (PsppireDataStore *ds)
613 {
614   datasheet_destroy (ds->datasheet);
615   ds->datasheet = NULL;
616
617   psppire_dict_clear (ds->dict);
618
619   g_signal_emit (ds, signals [CASES_DELETED], 0, 0, -1);
620 }
621
622
623
624 /* Return a casereader made from this datastore */
625 struct casereader *
626 psppire_data_store_get_reader (PsppireDataStore *ds)
627 {
628   int i;
629   struct casereader *reader ;
630
631   if ( ds->dict )
632     for (i = 0 ; i < n_dict_signals; ++i )
633       {
634         g_signal_handler_block (ds->dict,
635                                 ds->dict_handler_id[i]);
636       }
637
638   reader = datasheet_make_reader (ds->datasheet);
639
640   /* We must not reference this again */
641   ds->datasheet = NULL;
642
643   return reader;
644 }
645
646
647
648 /* Column related funcs */
649
650
651 static const gchar null_var_name[]=N_("var");
652
653
654 \f
655
656
657 /* Returns the CASENUMth case, or a null pointer on failure.
658  */
659 struct ccase *
660 psppire_data_store_get_case (const PsppireDataStore *ds,
661                              casenumber casenum)
662 {
663   g_return_val_if_fail (ds, FALSE);
664   g_return_val_if_fail (ds->datasheet, FALSE);
665
666   return datasheet_get_row (ds->datasheet, casenum);
667 }
668
669
670 gboolean
671 psppire_data_store_delete_cases (PsppireDataStore *ds, casenumber first,
672                                  casenumber n_cases)
673 {
674   g_return_val_if_fail (ds, FALSE);
675   g_return_val_if_fail (ds->datasheet, FALSE);
676
677   g_return_val_if_fail (first + n_cases <=
678                         psppire_data_store_get_case_count (ds), FALSE);
679
680
681   datasheet_delete_rows (ds->datasheet, first, n_cases);
682
683   g_signal_emit (ds, signals [CASES_DELETED], 0, first, n_cases);
684
685   return TRUE;
686 }
687
688
689
690 /* Insert case CC into the case file before POSN */
691 static gboolean
692 psppire_data_store_insert_case (PsppireDataStore *ds,
693                                 struct ccase *cc,
694                                 casenumber posn)
695 {
696   bool result ;
697
698   g_return_val_if_fail (ds, FALSE);
699   g_return_val_if_fail (ds->datasheet, FALSE);
700
701   cc = case_ref (cc);
702   result = datasheet_insert_rows (ds->datasheet, posn, &cc, 1);
703
704   if ( result )
705     g_signal_emit (ds, signals [CASE_INSERTED], 0, posn);
706   else
707     g_warning ("Cannot insert case at position %ld\n", posn);
708
709   return result;
710 }
711
712
713 /* Set the value of VAR in case CASENUM to V.
714    V must be the correct width for IDX.
715    Returns true if successful, false on I/O error. */
716 gboolean
717 psppire_data_store_set_value (PsppireDataStore *ds, casenumber casenum,
718                               const struct variable *var, const union value *v)
719 {
720   glong n_cases;
721   bool ok;
722
723   g_return_val_if_fail (ds, FALSE);
724   g_return_val_if_fail (ds->datasheet, FALSE);
725
726   n_cases = psppire_data_store_get_case_count (ds);
727   if ( casenum > n_cases)
728     return FALSE;
729
730   if (casenum == n_cases)
731     psppire_data_store_insert_new_case (ds, casenum);
732
733   ok = datasheet_put_value (ds->datasheet, casenum, var_get_case_index (var),
734                             v);
735   if (ok)
736     g_signal_emit (ds, signals [CASE_CHANGED], 0, casenum);
737
738   return ok;
739 }
740
741
742
743
744 /* Set the IDXth value of case C using D_IN */
745 static gboolean
746 psppire_data_store_data_in (PsppireDataStore *ds, casenumber casenum, gint idx,
747                             struct substring input, const struct fmt_spec *fmt)
748 {
749   union value value;
750   int width;
751   bool ok;
752
753   PsppireDict *dict;
754
755   g_return_val_if_fail (ds, FALSE);
756   g_return_val_if_fail (ds->datasheet, FALSE);
757
758   g_return_val_if_fail (idx < datasheet_get_n_columns (ds->datasheet), FALSE);
759
760   dict = ds->dict;
761
762   width = fmt_var_width (fmt);
763   g_return_val_if_fail (caseproto_get_width (
764                           datasheet_get_proto (ds->datasheet), idx) == width,
765                         FALSE);
766   value_init (&value, width);
767   ok = (datasheet_get_value (ds->datasheet, casenum, idx, &value)
768         && data_in_msg (input, UTF8, fmt->type, &value, width,
769                         dict_get_encoding (dict->dict))
770         && datasheet_put_value (ds->datasheet, casenum, idx, &value));
771   value_destroy (&value, width);
772
773   return ok;
774 }
775
776 /* Resize the cases in the casefile, by inserting a value of the
777    given WIDTH into every one of them at the position immediately
778    preceding WHERE.
779 */
780 static gboolean
781 psppire_data_store_insert_value (PsppireDataStore *ds,
782                                  gint width, gint where)
783 {
784   union value value;
785
786   g_return_val_if_fail (ds, FALSE);
787
788   g_assert (width >= 0);
789
790   if ( ! ds->datasheet )
791     ds->datasheet = datasheet_create (NULL);
792
793   value_init (&value, width);
794   value_set_missing (&value, width);
795
796   datasheet_insert_column (ds->datasheet, &value, width, where);
797   value_destroy (&value, width);
798
799   return TRUE;
800 }
801
802 gboolean
803 psppire_data_store_filtered (PsppireDataStore *ds,
804                              glong row)
805 {
806   union value val;
807
808   const struct dictionary *dict;
809   const struct variable *filter;
810
811   if ( row < 0 || row >= datasheet_get_n_rows (ds->datasheet))
812     return FALSE;
813
814   dict = ds->dict->dict;
815   g_return_val_if_fail (dict, FALSE);
816   filter = dict_get_filter (dict);
817   if ( ! filter)
818     return FALSE;
819
820   g_return_val_if_fail (var_is_numeric (filter), FALSE);
821   value_init (&val, 0);
822   if ( ! datasheet_get_value (ds->datasheet, row,
823                               var_get_case_index (filter),
824                               &val) )
825     return FALSE;
826
827   return (val.f == 0.0);
828 }