Added new files resulting from directory restructuring.
[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.h"
27 #include "gsheetmodel.h"
28 #include "gsheet-column-iface.h"
29
30 #include "psppire-variable.h"
31 #include "psppire-data-store.h"
32
33 #include "dictionary.h"
34 #include "missing-values.h"
35 #include "value-labels.h"
36 #include "data-in.h"
37
38 #include "file-handle-def.h"
39 #include "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   g_return_if_fail (data);
173
174   PsppireDataStore *store  = PSPPIRE_DATA_STORE(data);
175
176   g_assert(first >= 0);
177
178   g_sheet_model_rows_deleted (G_SHEET_MODEL(store), first, n_cases);
179 }
180
181
182 static void
183 insert_case_callback(GtkWidget *w, gint casenum, gpointer data)
184 {
185   g_return_if_fail (data);
186
187   PsppireDataStore *store  = PSPPIRE_DATA_STORE(data);
188   
189   g_sheet_model_range_changed (G_SHEET_MODEL(store),
190                                  casenum, -1,
191                                  psppire_case_array_get_n_cases(store->cases),
192                                  -1);
193 }
194
195
196 static void
197 changed_case_callback(GtkWidget *w, gint casenum, gpointer data)
198 {
199   g_return_if_fail (data);
200
201   PsppireDataStore *store  = PSPPIRE_DATA_STORE(data);
202   
203
204   g_sheet_model_range_changed (G_SHEET_MODEL(store),
205                                  casenum, -1,
206                                  casenum, -1);
207
208 }
209
210
211 static void
212 delete_variables_callback(GtkWidget *w, gint var_num, gint n_vars, gpointer data)
213 {
214   g_return_if_fail (data);
215
216   PsppireDataStore *store  = PSPPIRE_DATA_STORE(data);
217
218   g_sheet_column_columns_deleted(G_SHEET_COLUMN(store),
219                                    var_num, n_vars);
220 }
221
222
223 static void
224 insert_variable_callback(GtkWidget *w, gint var_num, gpointer data)
225 {
226   g_return_if_fail (data);
227
228   PsppireDataStore *store  = PSPPIRE_DATA_STORE(data);
229   
230   /* 
231   g_sheet_model_range_changed (G_SHEET_MODEL(store),
232                                  casenum, -1,
233                                  psppire_case_array_get_n_cases(store->cases),
234                                  -1);
235   */
236
237   psppire_case_array_resize(store->cases, 
238                          dict_get_next_value_idx (store->dict->dict));
239
240 }
241
242
243
244
245 /**
246  * psppire_data_store_new:
247  * @dict: The dictionary for this data_store.
248  *
249  *
250  * Return value: a new #PsppireDataStore
251  **/
252 PsppireDataStore *
253 psppire_data_store_new (PsppireDict *dict, PsppireCaseArray *cases)
254 {
255   PsppireDataStore *retval;
256
257   retval = g_object_new (GTK_TYPE_DATA_STORE, NULL);
258
259   retval->cases = cases;
260   g_signal_connect(cases, "cases-deleted", G_CALLBACK(delete_cases_callback), 
261                    retval);
262
263   g_signal_connect(cases, "case-inserted", G_CALLBACK(insert_case_callback), 
264                    retval);
265
266
267   g_signal_connect(cases, "case-changed", G_CALLBACK(changed_case_callback), 
268                    retval);
269
270
271   psppire_data_store_set_dictionary(retval, dict);
272
273
274   return retval;
275 }
276
277
278
279 /**
280  * psppire_data_store_replace_set_dictionary:
281  * @data_store: The variable store
282  * @dict: The dictionary to set
283  *
284  * If a dictionary is already associated with the data-store, then it will be
285  * destroyed.
286  **/
287 void
288 psppire_data_store_set_dictionary(PsppireDataStore *data_store, PsppireDict *dict)
289 {
290 #if 0
291   if ( data_store->dict ) g_object_unref(data_store->dict);
292 #endif
293
294   data_store->dict = dict;
295
296   psppire_case_array_resize(data_store->cases, 
297                          dict_get_next_value_idx (data_store->dict->dict));
298
299
300   g_signal_connect(dict, "variable-inserted", 
301                    G_CALLBACK(insert_variable_callback), 
302                    data_store);
303
304   g_signal_connect(dict, "variables-deleted", 
305                    G_CALLBACK(delete_variables_callback), 
306                    data_store);
307
308
309   /* The entire model has changed */
310   g_sheet_model_range_changed (G_SHEET_MODEL(data_store), -1, -1, -1, -1);
311 }
312
313 static void
314 psppire_data_store_finalize (GObject *object)
315 {
316
317   /* must chain up */
318   (* parent_class->finalize) (object);
319 }
320
321
322 static const gchar *const 
323 psppire_data_store_get_string(GSheetModel *model, gint row, gint column)
324 {
325   static gchar s[255];
326   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
327
328   g_return_val_if_fail(store->dict, NULL);
329   g_return_val_if_fail(store->cases, NULL);
330
331   if (column >= psppire_dict_get_var_cnt(store->dict))
332     return NULL;
333
334   if ( row >= psppire_case_array_get_n_cases(store->cases))
335     return NULL;
336
337   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, column);
338
339
340   const union value *v = 
341     psppire_case_array_get_value(store->cases, row, 
342                               psppire_variable_get_index(pv));
343
344
345   if ( store->show_labels) 
346     {
347       const struct val_labs * vl = psppire_variable_get_value_labels(pv);
348
349       const gchar *label;
350       if ( (label = val_labs_find(vl, *v)) )
351         {
352           return label;
353         }
354     }
355
356   const struct fmt_spec *fp = psppire_variable_get_write_spec(pv);
357
358   if ( psppire_variable_get_type(pv) == NUMERIC ) 
359     {
360       /* Converts binary value V into printable form in the exactly
361          FP->W character in buffer S according to format specification
362          FP.  No null terminator is appended to the buffer.  */
363       data_out (s, fp, v);
364       s[fp->w] = '\0';
365     }
366   else
367     {
368       const gint len = psppire_variable_get_width(pv);
369       memcpy(s, v->s, len);
370       s[len] = '\0';
371     }
372
373   static gchar buf[255];
374   GError *err = NULL;
375   gchar *text = g_locale_to_utf8(s, fp->w, 0, 0, &err);
376   if ( !err ) 
377     { 
378       g_snprintf(buf, 255, text);
379       g_free(text);
380     }
381   else
382     {
383       g_warning("Cannot convert string \"%s\" to utf-8: %s\n", s, err->message);
384       g_error_free(err);
385       return NULL;
386     }
387
388   return buf ;
389 }
390
391
392 static gboolean
393 set_null_string_value(union value *val, gpointer data)
394 {
395   strcpy(val->s, "");
396   return TRUE;
397 }
398
399 static gboolean
400 set_sysmis_value(union value *val, gpointer data)
401 {
402   val->f = SYSMIS;
403   return TRUE;
404 }
405
406
407 static gboolean 
408 psppire_data_store_clear_datum(GSheetModel *model, 
409                                           gint row, gint col)
410
411 {
412   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
413
414   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
415
416   const gint index = psppire_variable_get_index(pv) ;
417
418   if ( psppire_variable_get_type(pv) == NUMERIC) 
419     psppire_case_array_set_value(store->cases, row, index, set_sysmis_value,0);
420   else
421     psppire_case_array_set_value(store->cases, row, index, set_null_string_value,0);
422   return TRUE;
423 }
424
425
426 static gboolean
427 fillit(union value *val, gpointer data)
428 {
429   struct data_in *d_in = data;
430
431   d_in->v = val;
432
433   if ( ! data_in(d_in) ) 
434     {
435       g_warning("Cant encode string\n");
436       return FALSE;
437     }
438
439   return TRUE;
440 }
441
442
443 /* Attempts to update that part of the variable store which corresponds 
444    to ROW, COL with  the value TEXT.
445    Returns true if anything was updated, false otherwise.
446 */
447 static gboolean 
448 psppire_data_store_set_string(GSheetModel *model, 
449                           const gchar *text, gint row, gint col)
450 {
451   gint r;
452   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
453
454   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
455
456   for(r = psppire_case_array_get_n_cases(store->cases) ; r <= row ; ++r ) 
457     {
458       gint c;
459       psppire_case_array_insert_case(store->cases, r);
460
461       for (c = 0 ; c < psppire_dict_get_var_cnt(store->dict); ++c ) 
462         psppire_data_store_clear_datum(model, r, c);
463     }
464
465   const gint index = psppire_variable_get_index(pv);
466
467   struct data_in d_in;
468   d_in.s = text;
469   d_in.e = text + strlen(text);
470   d_in.v = 0;
471   d_in.f1 = d_in.f2 = 0;
472   d_in.format = * psppire_variable_get_write_spec(pv);
473   d_in.flags = 0;
474
475   psppire_case_array_set_value(store->cases, row, index, fillit, &d_in);
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   g_assert(handle);
525
526   const struct sfm_write_options wo = {
527     true, /* writeable */
528     false, /* dont compress */
529     3 /* version */
530   }; 
531
532   struct sfm_writer *writer = sfm_open_writer(handle, store->dict->dict, wo);
533
534   if ( ! writer) 
535     return;
536
537   psppire_case_array_iterate_case(store->cases, write_case, writer);
538
539   sfm_close_writer(writer);
540 }
541
542
543
544 /* Column related funcs */
545
546 static gint
547 geometry_get_column_count(const GSheetColumn *geom)
548 {
549   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
550
551   return max(MIN_COLUMNS, psppire_dict_get_var_cnt(ds->dict));
552 }
553
554 /* Return the width that an  'M' character would occupy when typeset at
555    row, col */
556 static guint 
557 M_width(GtkSheet *sheet, gint row, gint col)
558 {
559   GtkSheetCellAttr attributes;
560
561   gtk_sheet_get_attributes(sheet, row, col, &attributes);
562
563   PangoRectangle rect;
564   /* FIXME: make this a member of the data store */
565   static PangoLayout *layout = 0;
566
567   if (! layout ) 
568     layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), "M");
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   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
596
597   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
598     return 75;
599
600   /* FIXME: We can optimise this by caching the widths until they're resized */
601   const struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
602
603   return columnWidthToPixels(sheet, unit, psppire_variable_get_columns(pv));
604 }
605
606
607
608
609 static void
610 geometry_set_width(GSheetColumn *geom, gint unit, gint width, GtkSheet *sheet)
611 {
612   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
613
614   struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
615
616   psppire_variable_set_columns(pv, width / M_width(sheet, 1, unit));
617 }
618
619
620
621 static GtkJustification
622 geometry_get_justification(const GSheetColumn *geom, gint unit)
623 {
624   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
625
626
627   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
628     return GTK_JUSTIFY_LEFT;
629
630   const struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
631
632   /* Kludge: Happily GtkJustification is defined similarly
633      to enum alignment from pspp/variable.h */
634   return psppire_variable_get_alignment(pv);
635 }
636
637
638 static const gchar null_var_name[]=_("var");
639  
640 static const gchar *
641 geometry_get_button_label(const GSheetColumn *geom, gint unit)
642 {
643   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
644
645   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
646     return null_var_name;
647
648   struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
649
650   return psppire_variable_get_name(pv);
651 }
652
653
654 static gboolean
655 geometry_get_sensitivity(const GSheetColumn *geom, gint unit)
656 {
657   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
658
659
660   return (unit < psppire_dict_get_var_cnt(ds->dict));
661 }
662
663
664 static void
665 psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
666 {
667   iface->get_column_count = geometry_get_column_count;
668   iface->get_width = geometry_get_width;
669   iface->set_width = geometry_set_width;
670   iface->get_visibility = always_true;
671   iface->get_sensitivity = geometry_get_sensitivity;
672   iface->get_justification = geometry_get_justification;
673
674   iface->get_button_label = geometry_get_button_label;
675 }