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