Fixed bug where gui would crash when columns were resized.
[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 <gettext.h>
26 #define _(msgid) gettext(msgid)
27 #define N_(msgid) msgid
28
29 #include <data/casefile.h>
30 #include <data/case.h>
31
32 #include <gtksheet/gtksheet.h>
33 #include <gtksheet/gsheetmodel.h>
34 #include <gtksheet/gsheet-column-iface.h>
35
36 #include <pango/pango-context.h>
37
38 #include "psppire-variable.h"
39 #include "psppire-data-store.h"
40 #include "helper.h"
41
42 #include <data/dictionary.h>
43 #include <data/missing-values.h>
44 #include <data/value-labels.h>
45 #include <data/data-in.h>
46
47 #include <data/file-handle-def.h>
48 #include <data/sys-file-writer.h>
49
50
51
52 static void psppire_data_store_init            (PsppireDataStore      *data_store);
53 static void psppire_data_store_class_init      (PsppireDataStoreClass *class);
54 static void psppire_data_store_sheet_model_init (GSheetModelIface *iface);
55 static void psppire_data_store_sheet_column_init (GSheetColumnIface *iface);
56 static void psppire_data_store_sheet_row_init (GSheetRowIface *iface);
57
58 static void psppire_data_store_finalize        (GObject           *object);
59
60 static gchar *psppire_data_store_get_string(const GSheetModel *sheet_model, gint row, gint column);
61
62 static gboolean psppire_data_store_set_string(GSheetModel *model, 
63                                           const gchar *text, gint row, gint column);
64
65 static gboolean psppire_data_store_clear_datum(GSheetModel *model, 
66                                           gint row, gint column);
67
68
69 #define MIN_COLUMNS 10
70
71 #define TRAILING_ROWS 10
72
73 static GObjectClass *parent_class = NULL;
74
75
76 enum  {FONT_CHANGED,
77        n_SIGNALS};
78
79 static guint signal[n_SIGNALS];
80
81
82 inline GType
83 psppire_data_store_get_type (void)
84 {
85   static GType data_store_type = 0;
86
87   if (!data_store_type)
88     {
89       static const GTypeInfo data_store_info =
90       {
91         sizeof (PsppireDataStoreClass),
92         NULL,           /* base_init */
93         NULL,           /* base_finalize */
94         (GClassInitFunc) psppire_data_store_class_init,
95         NULL,           /* class_finalize */
96         NULL,           /* class_data */
97         sizeof (PsppireDataStore),
98         0,
99         (GInstanceInitFunc) psppire_data_store_init,
100       };
101
102       static const GInterfaceInfo sheet_model_info =
103       {
104         (GInterfaceInitFunc) psppire_data_store_sheet_model_init,
105         NULL,
106         NULL
107       };
108
109       static const GInterfaceInfo sheet_column_info =
110       {
111         (GInterfaceInitFunc) psppire_data_store_sheet_column_init,
112         NULL,
113         NULL
114       };
115
116       static const GInterfaceInfo sheet_row_info =
117       {
118         (GInterfaceInitFunc) psppire_data_store_sheet_row_init,
119         NULL,
120         NULL
121       };
122
123
124       data_store_type = g_type_register_static (G_TYPE_OBJECT, "PsppireDataStore",
125                                                 &data_store_info, 0);
126
127       g_type_add_interface_static (data_store_type,
128                                    G_TYPE_SHEET_MODEL,
129                                    &sheet_model_info);
130
131       g_type_add_interface_static (data_store_type,
132                                    G_TYPE_SHEET_COLUMN,
133                                    &sheet_column_info);
134
135       g_type_add_interface_static (data_store_type,
136                                    G_TYPE_SHEET_ROW,
137                                    &sheet_row_info);
138     }
139
140   return data_store_type;
141 }
142
143
144 static void
145 psppire_data_store_class_init (PsppireDataStoreClass *class)
146 {
147   GObjectClass *object_class;
148
149   parent_class = g_type_class_peek_parent (class);
150   object_class = (GObjectClass*) class;
151
152   object_class->finalize = psppire_data_store_finalize;
153
154   signal[FONT_CHANGED] =
155     g_signal_new ("font_changed",
156                   G_TYPE_FROM_CLASS(class),
157                   G_SIGNAL_RUN_FIRST,
158                   0,
159                   NULL, NULL,
160                   g_cclosure_marshal_VOID__VOID,
161                   G_TYPE_NONE, 
162                   0);
163 }
164
165
166
167 static gint
168 psppire_data_store_get_var_count (const GSheetModel *model)
169 {
170   const PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
171   
172   return psppire_dict_get_var_cnt(store->dict);
173 }
174
175 static gint
176 psppire_data_store_get_case_count (const GSheetModel *model)
177 {
178   const PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
179
180   return psppire_case_file_get_case_count(store->case_file);
181 }
182
183
184 static void
185 psppire_data_store_init (PsppireDataStore *data_store)
186 {
187   data_store->dict = 0;
188   data_store->case_file = 0;
189   data_store->width_of_m = 10;
190 }
191
192 const PangoFontDescription *
193 psppire_data_store_get_font_desc(const GSheetModel *model,
194                               gint row, gint column)
195 {
196   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
197   
198   return store->font_desc;
199 }
200
201
202 static void
203 psppire_data_store_sheet_model_init (GSheetModelIface *iface)
204 {
205   iface->free_strings = TRUE;
206   iface->get_string = psppire_data_store_get_string;
207   iface->set_string = psppire_data_store_set_string;
208   iface->clear_datum = psppire_data_store_clear_datum;
209   iface->is_editable = NULL;
210   iface->is_visible = NULL;
211   iface->get_foreground = NULL;
212   iface->get_background = NULL;
213   iface->get_font_desc = psppire_data_store_get_font_desc;
214   iface->get_cell_border = NULL;
215   iface->get_column_count = psppire_data_store_get_var_count;
216   iface->get_row_count = psppire_data_store_get_case_count;
217 }
218
219 static
220 gboolean always_true()
221 {
222   return TRUE;
223 }
224
225
226 static void
227 delete_cases_callback(GtkWidget *w, gint first, gint n_cases, gpointer data)
228 {
229   PsppireDataStore *store  ;
230
231   g_return_if_fail (data);
232
233   store  = PSPPIRE_DATA_STORE(data);
234
235   g_assert(first >= 0);
236
237   g_sheet_model_rows_deleted (G_SHEET_MODEL(store), first, n_cases);
238 }
239
240
241 static void
242 insert_case_callback(GtkWidget *w, gint casenum, gpointer data)
243 {
244   PsppireDataStore *store  ;
245
246   g_return_if_fail (data);
247
248   store  = PSPPIRE_DATA_STORE(data);
249   
250   g_sheet_model_range_changed (G_SHEET_MODEL(store),
251                                casenum, -1,
252                                psppire_case_file_get_case_count(store->case_file),
253                                -1);
254
255   g_sheet_model_rows_inserted (G_SHEET_MODEL(store), casenum, 1);
256 }
257
258
259 static void
260 changed_case_callback(GtkWidget *w, gint casenum, gpointer data)
261 {
262   PsppireDataStore *store  ;
263   g_return_if_fail (data);
264
265   store  = PSPPIRE_DATA_STORE(data);
266   
267   g_sheet_model_range_changed (G_SHEET_MODEL(store),
268                                  casenum, -1,
269                                  casenum, -1);
270 }
271
272
273 static void
274 delete_variables_callback(GObject *obj, gint var_num, gint n_vars, gpointer data)
275 {
276   PsppireDataStore *store ;
277
278   g_return_if_fail (data);
279
280   store  = PSPPIRE_DATA_STORE(data);
281
282   g_sheet_model_columns_deleted (G_SHEET_MODEL(store), var_num, n_vars);
283
284   g_sheet_column_columns_changed(G_SHEET_COLUMN(store),
285                                    var_num, -1);
286 }
287
288 static void
289 insert_variable_callback(GObject *obj, gint var_num, gpointer data)
290 {
291   PsppireDataStore *store;
292   gint posn;
293
294   g_return_if_fail (data);
295
296   store  = PSPPIRE_DATA_STORE(data);
297   
298   if ( var_num > 0 ) 
299     {
300       struct PsppireVariable *variable;
301       variable = psppire_dict_get_variable(store->dict, var_num);
302
303       posn = psppire_variable_get_fv(variable);
304     }
305   else
306     {
307       posn = 0;
308     }
309
310   psppire_case_file_insert_values(store->case_file, 1, posn);
311
312   g_sheet_column_columns_changed(G_SHEET_COLUMN(store),
313                                   var_num, 1);
314
315   g_sheet_model_columns_inserted (G_SHEET_MODEL(store), var_num, 1);
316 }
317
318
319 static void
320 dict_size_change_callback(GObject *obj, 
321                           gint posn, gint adjustment, gpointer data)
322 {
323   PsppireDataStore *store ;
324
325   g_return_if_fail (data);
326
327   store  = PSPPIRE_DATA_STORE(data);
328
329   /* 
330   if ( adjustment > 0 )
331   */
332   psppire_case_file_insert_values (store->case_file, adjustment, posn);
333 }
334
335
336
337 /**
338  * psppire_data_store_new:
339  * @dict: The dictionary for this data_store.
340  *
341  *
342  * Return value: a new #PsppireDataStore
343  **/
344 PsppireDataStore *
345 psppire_data_store_new (PsppireDict *dict)
346 {
347   PsppireDataStore *retval;
348
349   retval = g_object_new (GTK_TYPE_DATA_STORE, NULL);
350
351   psppire_data_store_set_dictionary(retval, dict);
352
353
354   return retval;
355 }
356
357
358
359 /**
360  * psppire_data_store_replace_set_dictionary:
361  * @data_store: The variable store
362  * @dict: The dictionary to set
363  *
364  * If a dictionary is already associated with the data-store, then it will be
365  * destroyed.
366  **/
367 void
368 psppire_data_store_set_dictionary(PsppireDataStore *data_store, PsppireDict *dict)
369 {
370   gint var_cnt = psppire_dict_get_next_value_idx(dict);
371 #if 0
372   if ( data_store->dict ) g_object_unref(data_store->dict);
373 #endif
374
375   data_store->dict = dict;
376
377   if ( data_store->case_file)
378     {
379       g_object_unref(data_store->case_file);
380       data_store->case_file = 0;
381     }
382
383   data_store->case_file = psppire_case_file_new(var_cnt);
384
385   g_signal_connect(data_store->case_file, "cases-deleted", 
386                    G_CALLBACK(delete_cases_callback), 
387                    data_store);
388
389   g_signal_connect(data_store->case_file, "case-inserted", 
390                    G_CALLBACK(insert_case_callback), 
391                    data_store);
392
393
394   g_signal_connect(data_store->case_file, "case-changed", 
395                    G_CALLBACK(changed_case_callback), 
396                    data_store);
397
398   g_signal_connect(dict, "variable-inserted", 
399                    G_CALLBACK(insert_variable_callback), 
400                    data_store);
401
402   g_signal_connect(dict, "variables-deleted", 
403                    G_CALLBACK(delete_variables_callback), 
404                    data_store);
405
406   g_signal_connect (dict, "dict-size-changed", 
407                     G_CALLBACK(dict_size_change_callback),
408                     data_store);
409
410   /* The entire model has changed */
411   g_sheet_model_range_changed (G_SHEET_MODEL(data_store), -1, -1, -1, -1);
412   
413   g_sheet_column_columns_changed(G_SHEET_COLUMN(data_store), 0, -1);
414 }
415
416 static void
417 psppire_data_store_finalize (GObject *object)
418 {
419
420   /* must chain up */
421   (* parent_class->finalize) (object);
422 }
423
424
425 static gchar *
426 psppire_data_store_get_string(const GSheetModel *model, gint row, gint column)
427 {
428   gint idx;
429   char *text;
430   const struct fmt_spec *fp ;
431   const struct PsppireVariable *pv ;
432   const union value *v ;
433   GString *s;
434   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
435
436   g_return_val_if_fail(store->dict, NULL);
437   g_return_val_if_fail(store->case_file, NULL);
438
439   if (column >= psppire_dict_get_var_cnt(store->dict))
440     return NULL;
441
442   if ( row >= psppire_case_file_get_case_count(store->case_file))
443     return NULL;
444
445   pv = psppire_dict_get_variable(store->dict, column);
446
447   idx = psppire_variable_get_fv(pv);
448
449   v = psppire_case_file_get_value(store->case_file, row, idx);
450
451   g_return_val_if_fail(v, NULL);
452
453   if ( store->show_labels) 
454     {
455       const struct val_labs * vl = psppire_variable_get_value_labels(pv);
456
457       const gchar *label;
458       if ( (label = val_labs_find(vl, *v)) )
459         {
460           return pspp_locale_to_utf8(label, -1, 0);
461         }
462     }
463
464   fp = psppire_variable_get_write_spec(pv);
465
466   s = g_string_sized_new (fp->w + 1);
467   g_string_set_size(s, fp->w);
468   
469   memset(s->str, 0, fp->w);
470
471   g_assert(fp->w == s->len);
472     
473   /* Converts binary value V into printable form in the exactly
474      FP->W character in buffer S according to format specification
475      FP.  No null terminator is appended to the buffer.  */
476   data_out (s->str, fp, v);
477
478   text = pspp_locale_to_utf8(s->str, fp->w, 0);
479   g_string_free(s, TRUE);
480
481   return text;
482 }
483
484
485 static gboolean 
486 psppire_data_store_clear_datum(GSheetModel *model, 
487                                           gint row, gint col)
488
489 {
490   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
491
492   union value v;
493   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
494
495   const gint index = psppire_variable_get_fv(pv) ;
496
497   if ( psppire_variable_get_type(pv) == NUMERIC) 
498     v.f = SYSMIS;
499   else
500     memcpy(v.s, "", MAX_SHORT_STRING);
501
502   psppire_case_file_set_value(store->case_file, row, index, &v, 
503                               psppire_variable_get_width(pv));
504   return TRUE;
505 }
506
507
508 /* Attempts to update that part of the variable store which corresponds 
509    to ROW, COL with  the value TEXT.
510    Returns true if anything was updated, false otherwise.
511 */
512 static gboolean 
513 psppire_data_store_set_string(GSheetModel *model, 
514                           const gchar *text, gint row, gint col)
515 {
516   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
517
518   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
519   g_return_val_if_fail(pv, FALSE);
520
521 #if 0
522   /* Allow the user to insert a lot of blank cases, simply by skipping rows */
523   for(r = psppire_case_file_get_case_count(store->case_file); r <= row ; ++r) 
524     {
525
526       gint c;
527
528       psppire_case_array_insert_case(store->cases, r, 0, 0);
529
530
531       for (c = 0 ; c < psppire_dict_get_var_cnt(store->dict); ++c ) 
532         psppire_data_store_clear_datum(model, r, c);
533     }
534 #endif
535
536   {
537     const gint index = psppire_variable_get_fv(pv);
538
539     struct data_in d_in;
540     d_in.s = text;
541     d_in.e = text + strlen(text);
542     d_in.v = 0;
543     d_in.f1 = d_in.f2 = 0;
544     d_in.format = * psppire_variable_get_write_spec(pv);
545     d_in.flags = 0;
546
547     psppire_case_file_data_in(store->case_file, row, index, &d_in) ;
548   }
549
550   return TRUE;
551 }
552
553
554 void
555 psppire_data_store_set_font(PsppireDataStore *store, 
556                             const PangoFontDescription *fd)
557 {
558   g_return_if_fail (store);
559   g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
560
561   store->font_desc = fd;
562 #if 0
563   store->width_of_m = calc_m_width(fd);
564 #endif
565   g_signal_emit(store, signal[FONT_CHANGED], 0);  
566
567
568   g_sheet_model_range_changed (G_SHEET_MODEL(store),
569                                  -1, -1, -1, -1);
570 }
571
572
573 void
574 psppire_data_store_show_labels(PsppireDataStore *store, gboolean show_labels)
575 {
576   g_return_if_fail (store);
577   g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
578
579   store->show_labels = show_labels;
580
581   g_sheet_model_range_changed (G_SHEET_MODEL(store),
582                                  -1, -1, -1, -1);
583 }
584
585
586
587 void
588 psppire_data_store_create_system_file(PsppireDataStore *store,
589                               struct file_handle *handle)
590 {
591   const struct sfm_write_options wo = {
592     true, /* writeable */
593     false, /* dont compress */
594     3 /* version */
595   }; 
596
597   struct sfm_writer *writer ;
598
599   g_assert(handle);
600
601   writer = sfm_open_writer(handle, store->dict->dict, wo);
602
603   if ( ! writer) 
604     return;
605
606 #if 0
607   psppire_case_array_iterate_case(store->cases, write_case, writer);
608 #endif
609
610   sfm_close_writer(writer);
611 }
612
613
614
615 void 
616 psppire_data_store_clear(PsppireDataStore *data_store)
617 {
618   psppire_case_file_clear(data_store->case_file);
619
620   psppire_dict_clear(data_store->dict);
621 }
622
623
624
625
626 /* Column related funcs */
627
628 static gint
629 geometry_get_column_count(const GSheetColumn *geom)
630 {
631   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
632
633   return MAX(MIN_COLUMNS, psppire_dict_get_var_cnt(ds->dict));
634 }
635
636
637
638 static gint
639 geometry_get_width(const GSheetColumn *geom, gint unit)
640 {
641   const struct PsppireVariable *pv ;
642   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
643
644   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
645     return ds->width_of_m * 8 ;
646
647   pv = psppire_dict_get_variable(ds->dict, unit);
648
649   if ( pv == NULL ) 
650     return ds->width_of_m * 8 ;
651
652   return ds->width_of_m * psppire_variable_get_columns(pv);
653 }
654
655 static void
656 geometry_set_width(GSheetColumn *geom, gint unit, gint width)
657 {
658   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
659
660   struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
661
662   psppire_variable_set_columns(pv, width / ds->width_of_m );
663 }
664
665
666
667 static GtkJustification
668 geometry_get_justification(const GSheetColumn *geom, gint unit)
669 {
670   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
671   const struct PsppireVariable *pv ;
672
673
674   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
675     return GTK_JUSTIFY_LEFT;
676
677   pv = psppire_dict_get_variable(ds->dict, unit);
678
679   /* Kludge: Happily GtkJustification is defined similarly
680      to enum alignment from pspp/variable.h */
681   return psppire_variable_get_alignment(pv);
682 }
683
684
685 static const gchar null_var_name[]=N_("var");
686  
687 static gchar *
688 geometry_get_column_button_label(const GSheetColumn *geom, gint unit)
689 {
690   gchar *text;
691   struct PsppireVariable *pv ;
692   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
693
694   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
695     return g_locale_to_utf8(null_var_name, -1, 0, 0, 0);
696
697   pv = psppire_dict_get_variable(ds->dict, unit);
698
699   text =  pspp_locale_to_utf8(psppire_variable_get_name(pv), -1, 0);
700
701   return text;
702 }
703
704
705 static gboolean
706 geometry_get_sensitivity(const GSheetColumn *geom, gint unit)
707 {
708   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
709
710
711   return (unit < psppire_dict_get_var_cnt(ds->dict));
712 }
713
714
715 static void
716 psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
717 {
718   iface->get_column_count = geometry_get_column_count;
719   iface->get_width = geometry_get_width;
720   iface->set_width = geometry_set_width;
721   iface->get_visibility = always_true;
722   iface->get_sensitivity = geometry_get_sensitivity;
723   iface->get_justification = geometry_get_justification;
724
725   iface->get_button_label = geometry_get_column_button_label;
726 }
727
728
729 /* Row related funcs */
730
731 static gint
732 geometry_get_row_count(const GSheetRow *geom, gpointer data)
733 {
734   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
735
736   return TRAILING_ROWS + psppire_case_file_get_case_count(ds->case_file);
737 }
738
739
740 static gint
741 geometry_get_height(const GSheetRow *geom, gint unit, gpointer data)
742 {
743   return 25;
744 }
745
746
747 static gboolean
748 geometry_get_row_sensitivity(const GSheetRow *geom, gint unit, gpointer data)
749 {
750   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
751
752   
753   return (unit < psppire_case_file_get_case_count(ds->case_file));
754 }
755
756
757 static gchar *
758 geometry_get_row_button_label(const GSheetRow *geom, gint unit, gpointer data)
759 {
760   gchar *text;
761   gchar *s;
762   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
763
764   if ( unit > 
765        TRAILING_ROWS + psppire_case_file_get_case_count(ds->case_file))
766     return 0;
767
768   s = g_strdup_printf(_("%d"), unit);
769
770   text =  pspp_locale_to_utf8(s, -1, 0);
771   
772   g_free(s);
773   
774   return text;
775 }
776
777
778 static void
779 psppire_data_store_sheet_row_init (GSheetRowIface *iface)
780 {
781   iface->get_row_count = geometry_get_row_count;
782
783   iface->get_height = geometry_get_height;
784   iface->set_height = 0;
785   iface->get_visibility = always_true;
786   iface->get_sensitivity = geometry_get_row_sensitivity;
787
788   iface->get_button_label = geometry_get_row_button_label;
789 }