When inserting new cases into the data sheet, initialise them with blank/SYSMIS
[pspp-builds.git] / src / ui / gui / psppire-data-store.c
1 /* psppire-data-store.c
2  
3    PSPPIRE --- A Graphical User Interface for PSPP
4    Copyright (C) 2006  Free Software Foundation
5    Written by John Darrington
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20    02110-1301, USA. */
21
22 #include <config.h>
23 #include <string.h>
24 #include <stdlib.h>
25
26 #include <gtksheet/gtksheet.h>
27 #include <gtksheet/gsheetmodel.h>
28 #include <gtksheet/gsheet-column-iface.h>
29
30 #include "psppire-variable.h"
31 #include "psppire-data-store.h"
32 #include "helper.h"
33
34 #include <data/dictionary.h>
35 #include <data/missing-values.h>
36 #include <data/value-labels.h>
37 #include <data/data-in.h>
38
39 #include <data/file-handle-def.h>
40 #include <data/sys-file-writer.h>
41
42 #define _(A) A
43 #define N_(A) A
44
45
46 static void psppire_data_store_init            (PsppireDataStore      *data_store);
47 static void psppire_data_store_class_init      (PsppireDataStoreClass *class);
48 static void psppire_data_store_sheet_model_init (GSheetModelIface *iface);
49 static void psppire_data_store_sheet_column_init (GSheetColumnIface *iface);
50 static void psppire_data_store_finalize        (GObject           *object);
51
52 static const gchar *psppire_data_store_get_string(GSheetModel *sheet_model, gint row, gint column);
53
54 static gboolean psppire_data_store_set_string(GSheetModel *model, 
55                                           const gchar *text, gint row, gint column);
56
57 static gboolean psppire_data_store_clear_datum(GSheetModel *model, 
58                                           gint row, gint column);
59
60
61 #define MIN_COLUMNS 10
62
63 #define max(A,B) ((A>B)?A:B)
64
65 static GObjectClass *parent_class = NULL;
66
67 inline GType
68 psppire_data_store_get_type (void)
69 {
70   static GType data_store_type = 0;
71
72   if (!data_store_type)
73     {
74       static const GTypeInfo data_store_info =
75       {
76         sizeof (PsppireDataStoreClass),
77         NULL,           /* base_init */
78         NULL,           /* base_finalize */
79         (GClassInitFunc) psppire_data_store_class_init,
80         NULL,           /* class_finalize */
81         NULL,           /* class_data */
82         sizeof (PsppireDataStore),
83         0,
84         (GInstanceInitFunc) psppire_data_store_init,
85       };
86
87       static const GInterfaceInfo sheet_model_info =
88       {
89         (GInterfaceInitFunc) psppire_data_store_sheet_model_init,
90         NULL,
91         NULL
92       };
93
94       static const GInterfaceInfo sheet_column_info =
95       {
96         (GInterfaceInitFunc) psppire_data_store_sheet_column_init,
97         NULL,
98         NULL
99       };
100
101
102
103       data_store_type = g_type_register_static (G_TYPE_OBJECT, "PsppireDataStore",
104                                                 &data_store_info, 0);
105
106       g_type_add_interface_static (data_store_type,
107                                    G_TYPE_SHEET_MODEL,
108                                    &sheet_model_info);
109
110       g_type_add_interface_static (data_store_type,
111                                    G_TYPE_SHEET_COLUMN,
112                                    &sheet_column_info);
113
114     }
115
116   return data_store_type;
117 }
118
119 static void
120 psppire_data_store_class_init (PsppireDataStoreClass *class)
121 {
122   GObjectClass *object_class;
123
124   parent_class = g_type_class_peek_parent (class);
125   object_class = (GObjectClass*) class;
126
127   object_class->finalize = psppire_data_store_finalize;
128 }
129
130
131 static void
132 psppire_data_store_init (PsppireDataStore *data_store)
133 {
134   data_store->dict = 0;
135   data_store->cases = 0;
136 }
137
138 const PangoFontDescription *
139 psppire_data_store_get_font_desc(GSheetModel *model,
140                               gint row, gint column)
141 {
142   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
143   
144   return store->font_desc;
145 }
146
147
148 static void
149 psppire_data_store_sheet_model_init (GSheetModelIface *iface)
150 {
151   iface->free_strings = TRUE;
152   iface->get_string = psppire_data_store_get_string;
153   iface->set_string = psppire_data_store_set_string;
154   iface->clear_datum = psppire_data_store_clear_datum;
155   iface->is_editable = NULL;
156   iface->is_visible = NULL;
157   iface->get_foreground = NULL;
158   iface->get_background = NULL;
159   iface->get_font_desc = psppire_data_store_get_font_desc;
160   iface->get_cell_border = NULL;
161 }
162
163 static
164 gboolean always_true()
165 {
166   return TRUE;
167 }
168
169
170
171 static void
172 delete_cases_callback(GtkWidget *w, gint first, gint n_cases, gpointer data)
173 {
174   PsppireDataStore *store  ;
175
176   g_return_if_fail (data);
177
178   store  = PSPPIRE_DATA_STORE(data);
179
180   g_assert(first >= 0);
181
182   g_sheet_model_rows_deleted (G_SHEET_MODEL(store), first, n_cases);
183 }
184
185
186 static void
187 insert_case_callback(GtkWidget *w, gint casenum, gpointer data)
188 {
189   PsppireDataStore *store  ;
190
191   g_return_if_fail (data);
192
193   store  = PSPPIRE_DATA_STORE(data);
194   
195   g_sheet_model_range_changed (G_SHEET_MODEL(store),
196                                casenum, -1,
197                                psppire_case_array_get_n_cases(store->cases),
198                                -1);
199 }
200
201
202 static void
203 changed_case_callback(GtkWidget *w, gint casenum, gpointer data)
204 {
205   PsppireDataStore *store  ;
206   g_return_if_fail (data);
207
208   store  = PSPPIRE_DATA_STORE(data);
209   
210   g_sheet_model_range_changed (G_SHEET_MODEL(store),
211                                  casenum, -1,
212                                  casenum, -1);
213
214 }
215
216
217 static void
218 delete_variables_callback(GtkWidget *w, gint var_num, gint n_vars, gpointer data)
219 {
220   PsppireDataStore *store ;
221
222   g_return_if_fail (data);
223
224   store  = PSPPIRE_DATA_STORE(data);
225
226   g_sheet_column_columns_deleted(G_SHEET_COLUMN(store),
227                                    var_num, n_vars);
228 }
229
230
231 static void
232 insert_variable_callback(GtkWidget *w, gint var_num, gpointer data)
233 {
234   PsppireDataStore *store;
235
236   g_return_if_fail (data);
237
238   store  = PSPPIRE_DATA_STORE(data);
239   
240   /* 
241   g_sheet_model_range_changed (G_SHEET_MODEL(store),
242                                  casenum, -1,
243                                  psppire_case_array_get_n_cases(store->cases),
244                                  -1);
245   */
246
247   psppire_case_array_resize(store->cases, 
248                          dict_get_next_value_idx (store->dict->dict));
249
250 }
251
252
253
254
255 /**
256  * psppire_data_store_new:
257  * @dict: The dictionary for this data_store.
258  *
259  *
260  * Return value: a new #PsppireDataStore
261  **/
262 PsppireDataStore *
263 psppire_data_store_new (PsppireDict *dict, PsppireCaseArray *cases)
264 {
265   PsppireDataStore *retval;
266
267   retval = g_object_new (GTK_TYPE_DATA_STORE, NULL);
268
269   retval->cases = cases;
270   g_signal_connect(cases, "cases-deleted", G_CALLBACK(delete_cases_callback), 
271                    retval);
272
273   g_signal_connect(cases, "case-inserted", G_CALLBACK(insert_case_callback), 
274                    retval);
275
276
277   g_signal_connect(cases, "case-changed", G_CALLBACK(changed_case_callback), 
278                    retval);
279
280
281   psppire_data_store_set_dictionary(retval, dict);
282
283
284   return retval;
285 }
286
287
288
289 /**
290  * psppire_data_store_replace_set_dictionary:
291  * @data_store: The variable store
292  * @dict: The dictionary to set
293  *
294  * If a dictionary is already associated with the data-store, then it will be
295  * destroyed.
296  **/
297 void
298 psppire_data_store_set_dictionary(PsppireDataStore *data_store, PsppireDict *dict)
299 {
300 #if 0
301   if ( data_store->dict ) g_object_unref(data_store->dict);
302 #endif
303
304   data_store->dict = dict;
305
306   psppire_case_array_resize(data_store->cases, 
307                          dict_get_next_value_idx (data_store->dict->dict));
308
309
310   g_signal_connect(dict, "variable-inserted", 
311                    G_CALLBACK(insert_variable_callback), 
312                    data_store);
313
314   g_signal_connect(dict, "variables-deleted", 
315                    G_CALLBACK(delete_variables_callback), 
316                    data_store);
317
318
319   /* The entire model has changed */
320   g_sheet_model_range_changed (G_SHEET_MODEL(data_store), -1, -1, -1, -1);
321 }
322
323 static void
324 psppire_data_store_finalize (GObject *object)
325 {
326
327   /* must chain up */
328   (* parent_class->finalize) (object);
329 }
330
331
332 static const gchar *
333 psppire_data_store_get_string(GSheetModel *model, gint row, gint column)
334 {
335   const char *text;
336   const struct fmt_spec *fp ;
337   const struct PsppireVariable *pv ;
338   const union value *v ;
339   GString *s;
340   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
341
342   g_return_val_if_fail(store->dict, NULL);
343   g_return_val_if_fail(store->cases, NULL);
344
345   if (column >= psppire_dict_get_var_cnt(store->dict))
346     return NULL;
347
348   if ( row >= psppire_case_array_get_n_cases(store->cases))
349     return NULL;
350
351
352   pv = psppire_dict_get_variable(store->dict, column);
353
354   v =  psppire_case_array_get_value(store->cases, row, 
355                               psppire_variable_get_index(pv));
356
357   if ( store->show_labels) 
358     {
359       const struct val_labs * vl = psppire_variable_get_value_labels(pv);
360
361       const gchar *label;
362       if ( (label = val_labs_find(vl, *v)) )
363         {
364           return pspp_locale_to_utf8(label, -1, 0);
365         }
366     }
367
368   fp = psppire_variable_get_write_spec(pv);
369
370   s = g_string_sized_new (fp->w + 1);
371   g_string_set_size(s, fp->w);
372   
373   memset(s->str, 0, fp->w);
374
375   g_assert(fp->w == s->len);
376     
377   /* Converts binary value V into printable form in the exactly
378      FP->W character in buffer S according to format specification
379      FP.  No null terminator is appended to the buffer.  */
380   data_out (s->str, fp, v);
381
382   
383   text = pspp_locale_to_utf8(s->str, fp->w, 0);
384   g_string_free(s, TRUE);
385
386   return text;
387 }
388
389
390 static gboolean
391 set_null_string_value(union value *val, gpointer data)
392 {
393   strcpy(val->s, "");
394   return TRUE;
395 }
396
397 static gboolean
398 set_sysmis_value(union value *val, gpointer data)
399 {
400   val->f = SYSMIS;
401   return TRUE;
402 }
403
404
405 static gboolean 
406 psppire_data_store_clear_datum(GSheetModel *model, 
407                                           gint row, gint col)
408
409 {
410   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
411
412   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
413
414   const gint index = psppire_variable_get_index(pv) ;
415
416   if ( psppire_variable_get_type(pv) == NUMERIC) 
417     psppire_case_array_set_value(store->cases, row, index, set_sysmis_value,0);
418   else
419     psppire_case_array_set_value(store->cases, row, index, set_null_string_value,0);
420   return TRUE;
421 }
422
423
424 static gboolean
425 fillit(union value *val, gpointer data)
426 {
427   struct data_in *d_in = data;
428
429   d_in->v = val;
430
431   if ( ! data_in(d_in) ) 
432     {
433       g_warning("Cant encode string\n");
434       return FALSE;
435     }
436
437   return TRUE;
438 }
439
440
441 /* Attempts to update that part of the variable store which corresponds 
442    to ROW, COL with  the value TEXT.
443    Returns true if anything was updated, false otherwise.
444 */
445 static gboolean 
446 psppire_data_store_set_string(GSheetModel *model, 
447                           const gchar *text, gint row, gint col)
448 {
449   gint r;
450   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
451
452   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
453
454   for(r = psppire_case_array_get_n_cases(store->cases) ; r <= row ; ++r ) 
455     {
456       gint c;
457       psppire_case_array_insert_case(store->cases, r, 0, 0);
458
459       for (c = 0 ; c < psppire_dict_get_var_cnt(store->dict); ++c ) 
460         psppire_data_store_clear_datum(model, r, c);
461     }
462
463   {
464     const gint index = psppire_variable_get_index(pv);
465
466     struct data_in d_in;
467     d_in.s = text;
468     d_in.e = text + strlen(text);
469     d_in.v = 0;
470     d_in.f1 = d_in.f2 = 0;
471     d_in.format = * psppire_variable_get_write_spec(pv);
472     d_in.flags = 0;
473
474     psppire_case_array_set_value(store->cases, row, index, fillit, &d_in);
475   }
476
477   return TRUE;
478 }
479
480
481 void
482 psppire_data_store_set_font(PsppireDataStore *store, PangoFontDescription *fd)
483 {
484   g_return_if_fail (store);
485   g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
486
487   store->font_desc = fd;
488   g_sheet_model_range_changed (G_SHEET_MODEL(store),
489                                  -1, -1, -1, -1);
490 }
491
492
493 void
494 psppire_data_store_show_labels(PsppireDataStore *store, gboolean show_labels)
495 {
496   g_return_if_fail (store);
497   g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
498
499   store->show_labels = show_labels;
500
501   g_sheet_model_range_changed (G_SHEET_MODEL(store),
502                                  -1, -1, -1, -1);
503 }
504
505
506
507 static gboolean 
508 write_case(const struct ccase *cc, 
509            gpointer aux)
510 {
511   struct sfm_writer *writer = aux;
512
513   if ( ! sfm_write_case(writer, cc) )
514     return FALSE;
515
516
517   return TRUE;
518 }
519
520 void
521 psppire_data_store_create_system_file(PsppireDataStore *store,
522                               struct file_handle *handle)
523 {
524   const struct sfm_write_options wo = {
525     true, /* writeable */
526     false, /* dont compress */
527     3 /* version */
528   }; 
529
530   struct sfm_writer *writer ;
531
532   g_assert(handle);
533
534   writer = sfm_open_writer(handle, store->dict->dict, wo);
535
536   if ( ! writer) 
537     return;
538
539   psppire_case_array_iterate_case(store->cases, write_case, writer);
540
541   sfm_close_writer(writer);
542 }
543
544
545
546 /* Column related funcs */
547
548 static gint
549 geometry_get_column_count(const GSheetColumn *geom)
550 {
551   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
552
553   return max(MIN_COLUMNS, psppire_dict_get_var_cnt(ds->dict));
554 }
555
556 /* Return the width that an  'M' character would occupy when typeset at
557    row, col */
558 static guint 
559 M_width(GtkSheet *sheet, gint row, gint col)
560 {
561   GtkSheetCellAttr attributes;
562   PangoRectangle rect;
563   /* FIXME: make this a member of the data store */
564   static PangoLayout *layout = 0;
565
566   gtk_sheet_get_attributes(sheet, row, col, &attributes);
567
568   if (! layout ) 
569     layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), "M");
570
571   g_assert(layout);
572   
573   pango_layout_set_font_description (layout, 
574                                      attributes.font_desc);
575
576   pango_layout_get_extents (layout, NULL, &rect);
577
578 #if 0
579   g_object_unref(G_OBJECT(layout));
580 #endif
581
582   return PANGO_PIXELS(rect.width);
583 }
584
585
586 /* Return the number of pixels corresponding to a column of 
587    WIDTH characters */
588 static inline guint 
589 columnWidthToPixels(GtkSheet *sheet, gint column, guint width)
590 {
591   return (M_width(sheet, 0, column) * width);
592 }
593
594
595 static gint
596 geometry_get_width(const GSheetColumn *geom, gint unit, GtkSheet *sheet)
597 {
598   const struct PsppireVariable *pv ;
599   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
600
601   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
602     return 75;
603
604   /* FIXME: We can optimise this by caching the widths until they're resized */
605   pv = psppire_dict_get_variable(ds->dict, unit);
606
607   return columnWidthToPixels(sheet, unit, psppire_variable_get_columns(pv));
608 }
609
610
611
612
613 static void
614 geometry_set_width(GSheetColumn *geom, gint unit, gint width, GtkSheet *sheet)
615 {
616   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
617
618   struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
619
620   psppire_variable_set_columns(pv, width / M_width(sheet, 1, unit));
621 }
622
623
624
625 static GtkJustification
626 geometry_get_justification(const GSheetColumn *geom, gint unit)
627 {
628   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
629   const struct PsppireVariable *pv ;
630
631
632   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
633     return GTK_JUSTIFY_LEFT;
634
635   pv = psppire_dict_get_variable(ds->dict, unit);
636
637   /* Kludge: Happily GtkJustification is defined similarly
638      to enum alignment from pspp/variable.h */
639   return psppire_variable_get_alignment(pv);
640 }
641
642
643 static const gchar null_var_name[]=_("var");
644  
645 static const gchar *
646 geometry_get_button_label(const GSheetColumn *geom, gint unit)
647 {
648   const gchar *text;
649   struct PsppireVariable *pv ;
650   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
651
652   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
653     return pspp_locale_to_utf8(null_var_name, -1, 0);
654
655   pv = psppire_dict_get_variable(ds->dict, unit);
656
657   text =  pspp_locale_to_utf8(psppire_variable_get_name(pv), -1, 0);
658
659   return text;
660 }
661
662
663 static gboolean
664 geometry_get_sensitivity(const GSheetColumn *geom, gint unit)
665 {
666   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
667
668
669   return (unit < psppire_dict_get_var_cnt(ds->dict));
670 }
671
672
673 static void
674 psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
675 {
676   iface->get_column_count = geometry_get_column_count;
677   iface->get_width = geometry_get_width;
678   iface->set_width = geometry_set_width;
679   iface->get_visibility = always_true;
680   iface->get_sensitivity = geometry_get_sensitivity;
681   iface->get_justification = geometry_get_justification;
682
683   iface->get_button_label = geometry_get_button_label;
684 }