0cb719046eab964b9a78dc584c628735019979da
[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 *const 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 *const 
330 psppire_data_store_get_string(GSheetModel *model, gint row, gint column)
331 {
332
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 label;
362         }
363     }
364
365   fp = psppire_variable_get_write_spec(pv);
366
367   s = g_string_sized_new (fp->w);
368     
369   /* Converts binary value V into printable form in the exactly
370      FP->W character in buffer S according to format specification
371      FP.  No null terminator is appended to the buffer.  */
372   data_out (s->str, fp, v);
373
374   return g_string_free(s, FALSE);
375 #if 0
376   {
377     static gchar buf[255];
378     GError *err = NULL;
379     gchar *text = g_locale_to_utf8(s, fp->w, 0, 0, &err);
380     if ( !err ) 
381       { 
382         g_snprintf(buf, 255, text);
383         g_free(text);
384       }
385     else
386       {
387         g_warning("Cannot convert string \"%s\" to utf-8: %s\n", s, err->message);
388         g_error_free(err);
389         return NULL;
390       }
391
392   return buf ;
393   }
394 #endif
395 }
396
397
398 static gboolean
399 set_null_string_value(union value *val, gpointer data)
400 {
401   strcpy(val->s, "");
402   return TRUE;
403 }
404
405 static gboolean
406 set_sysmis_value(union value *val, gpointer data)
407 {
408   val->f = SYSMIS;
409   return TRUE;
410 }
411
412
413 static gboolean 
414 psppire_data_store_clear_datum(GSheetModel *model, 
415                                           gint row, gint col)
416
417 {
418   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
419
420   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
421
422   const gint index = psppire_variable_get_index(pv) ;
423
424   if ( psppire_variable_get_type(pv) == NUMERIC) 
425     psppire_case_array_set_value(store->cases, row, index, set_sysmis_value,0);
426   else
427     psppire_case_array_set_value(store->cases, row, index, set_null_string_value,0);
428   return TRUE;
429 }
430
431
432 static gboolean
433 fillit(union value *val, gpointer data)
434 {
435   struct data_in *d_in = data;
436
437   d_in->v = val;
438
439   if ( ! data_in(d_in) ) 
440     {
441       g_warning("Cant encode string\n");
442       return FALSE;
443     }
444
445   return TRUE;
446 }
447
448
449 /* Attempts to update that part of the variable store which corresponds 
450    to ROW, COL with  the value TEXT.
451    Returns true if anything was updated, false otherwise.
452 */
453 static gboolean 
454 psppire_data_store_set_string(GSheetModel *model, 
455                           const gchar *text, gint row, gint col)
456 {
457   gint r;
458   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
459
460   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
461
462   for(r = psppire_case_array_get_n_cases(store->cases) ; r <= row ; ++r ) 
463     {
464       gint c;
465       psppire_case_array_insert_case(store->cases, r);
466
467       for (c = 0 ; c < psppire_dict_get_var_cnt(store->dict); ++c ) 
468         psppire_data_store_clear_datum(model, r, c);
469     }
470
471   {
472     const gint index = psppire_variable_get_index(pv);
473
474     struct data_in d_in;
475     d_in.s = text;
476     d_in.e = text + strlen(text);
477     d_in.v = 0;
478     d_in.f1 = d_in.f2 = 0;
479     d_in.format = * psppire_variable_get_write_spec(pv);
480     d_in.flags = 0;
481
482     psppire_case_array_set_value(store->cases, row, index, fillit, &d_in);
483   }
484
485   return TRUE;
486 }
487
488
489 void
490 psppire_data_store_set_font(PsppireDataStore *store, PangoFontDescription *fd)
491 {
492   g_return_if_fail (store);
493   g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
494
495   store->font_desc = fd;
496   g_sheet_model_range_changed (G_SHEET_MODEL(store),
497                                  -1, -1, -1, -1);
498 }
499
500
501 void
502 psppire_data_store_show_labels(PsppireDataStore *store, gboolean show_labels)
503 {
504   g_return_if_fail (store);
505   g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
506
507   store->show_labels = show_labels;
508
509   g_sheet_model_range_changed (G_SHEET_MODEL(store),
510                                  -1, -1, -1, -1);
511 }
512
513
514
515 static gboolean 
516 write_case(const struct ccase *cc, 
517            gpointer aux)
518 {
519   struct sfm_writer *writer = aux;
520
521   if ( ! sfm_write_case(writer, cc) )
522     return FALSE;
523
524
525   return TRUE;
526 }
527
528 void
529 psppire_data_store_create_system_file(PsppireDataStore *store,
530                               struct file_handle *handle)
531 {
532   const struct sfm_write_options wo = {
533     true, /* writeable */
534     false, /* dont compress */
535     3 /* version */
536   }; 
537
538   struct sfm_writer *writer ;
539
540   g_assert(handle);
541
542   writer = sfm_open_writer(handle, store->dict->dict, wo);
543
544   if ( ! writer) 
545     return;
546
547   psppire_case_array_iterate_case(store->cases, write_case, writer);
548
549   sfm_close_writer(writer);
550 }
551
552
553
554 /* Column related funcs */
555
556 static gint
557 geometry_get_column_count(const GSheetColumn *geom)
558 {
559   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
560
561   return max(MIN_COLUMNS, psppire_dict_get_var_cnt(ds->dict));
562 }
563
564 /* Return the width that an  'M' character would occupy when typeset at
565    row, col */
566 static guint 
567 M_width(GtkSheet *sheet, gint row, gint col)
568 {
569   GtkSheetCellAttr attributes;
570   PangoRectangle rect;
571   /* FIXME: make this a member of the data store */
572   static PangoLayout *layout = 0;
573
574   gtk_sheet_get_attributes(sheet, row, col, &attributes);
575
576   if (! layout ) 
577     layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), "M");
578
579   g_assert(layout);
580   
581   pango_layout_set_font_description (layout, 
582                                      attributes.font_desc);
583
584   pango_layout_get_extents (layout, NULL, &rect);
585
586 #if 0
587   g_object_unref(G_OBJECT(layout));
588 #endif
589
590   return PANGO_PIXELS(rect.width);
591 }
592
593
594 /* Return the number of pixels corresponding to a column of 
595    WIDTH characters */
596 static inline guint 
597 columnWidthToPixels(GtkSheet *sheet, gint column, guint width)
598 {
599   return (M_width(sheet, 0, column) * width);
600 }
601
602
603 static gint
604 geometry_get_width(const GSheetColumn *geom, gint unit, GtkSheet *sheet)
605 {
606   const struct PsppireVariable *pv ;
607   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
608
609   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
610     return 75;
611
612   /* FIXME: We can optimise this by caching the widths until they're resized */
613   pv = psppire_dict_get_variable(ds->dict, unit);
614
615   return columnWidthToPixels(sheet, unit, psppire_variable_get_columns(pv));
616 }
617
618
619
620
621 static void
622 geometry_set_width(GSheetColumn *geom, gint unit, gint width, GtkSheet *sheet)
623 {
624   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
625
626   struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
627
628   psppire_variable_set_columns(pv, width / M_width(sheet, 1, unit));
629 }
630
631
632
633 static GtkJustification
634 geometry_get_justification(const GSheetColumn *geom, gint unit)
635 {
636   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
637   const struct PsppireVariable *pv ;
638
639
640   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
641     return GTK_JUSTIFY_LEFT;
642
643   pv = psppire_dict_get_variable(ds->dict, unit);
644
645   /* Kludge: Happily GtkJustification is defined similarly
646      to enum alignment from pspp/variable.h */
647   return psppire_variable_get_alignment(pv);
648 }
649
650
651 static const gchar null_var_name[]=_("var");
652  
653 static const gchar *
654 geometry_get_button_label(const GSheetColumn *geom, gint unit)
655 {
656   struct PsppireVariable *pv ;
657   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
658
659   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
660     return null_var_name;
661
662   pv = psppire_dict_get_variable(ds->dict, unit);
663
664   return psppire_variable_get_name(pv);
665 }
666
667
668 static gboolean
669 geometry_get_sensitivity(const GSheetColumn *geom, gint unit)
670 {
671   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
672
673
674   return (unit < psppire_dict_get_var_cnt(ds->dict));
675 }
676
677
678 static void
679 psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
680 {
681   iface->get_column_count = geometry_get_column_count;
682   iface->get_width = geometry_get_width;
683   iface->set_width = geometry_set_width;
684   iface->get_visibility = always_true;
685   iface->get_sensitivity = geometry_get_sensitivity;
686   iface->get_justification = geometry_get_justification;
687
688   iface->get_button_label = geometry_get_button_label;
689 }