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