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