Implemented data-store using a casefile instead of an array of cases.
[pspp-builds.git] / src / ui / gui / psppire-var-store.c
1 /* psppire-var-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
30
31 #include <gobject/gvaluecollector.h>
32
33 #include <gtksheet/gsheetmodel.h>
34
35 #include "psppire-variable.h"
36 #include "psppire-var-store.h"
37 #include "var-sheet.h"
38 #include "helper.h"
39
40 #include <data/dictionary.h>
41 #include <data/variable.h>
42 #include <data/missing-values.h>
43
44 #include "val-labs-dialog.h"
45 #include "missing-val-dialog.h"
46 #include <data/value-labels.h>
47
48
49 #define TRAILING_ROWS 40
50
51 static void         psppire_var_store_init            (PsppireVarStore      *var_store);
52 static void         psppire_var_store_class_init      (PsppireVarStoreClass *class);
53 static void         psppire_var_store_sheet_model_init (GSheetModelIface *iface);
54 static void         psppire_var_store_finalize        (GObject           *object);
55
56 static gchar *psppire_var_store_get_string(const GSheetModel *sheet_model, gint row, gint column);
57
58 static gboolean  psppire_var_store_clear(GSheetModel *model,  gint row, gint col);
59
60
61 static gboolean psppire_var_store_set_string(GSheetModel *model, 
62                                           const gchar *text, gint row, gint column);
63
64 static gint psppire_var_store_get_row_count(const GSheetModel * model);
65
66 static gchar *text_for_column(const struct PsppireVariable *pv, gint c, GError **err);
67
68
69 static void psppire_var_store_sheet_row_init (GSheetRowIface *iface);
70
71
72
73 static GObjectClass *parent_class = NULL;
74
75 GType
76 psppire_var_store_get_type (void)
77 {
78   static GType var_store_type = 0;
79
80   if (!var_store_type)
81     {
82       static const GTypeInfo var_store_info =
83       {
84         sizeof (PsppireVarStoreClass),
85         NULL,           /* base_init */
86         NULL,           /* base_finalize */
87         (GClassInitFunc) psppire_var_store_class_init,
88         NULL,           /* class_finalize */
89         NULL,           /* class_data */
90         sizeof (PsppireVarStore),
91         0,
92         (GInstanceInitFunc) psppire_var_store_init,
93       };
94
95       static const GInterfaceInfo sheet_model_info =
96       {
97         (GInterfaceInitFunc) psppire_var_store_sheet_model_init,
98         NULL,
99         NULL
100       };
101
102       static const GInterfaceInfo sheet_row_info =
103       {
104         (GInterfaceInitFunc) psppire_var_store_sheet_row_init,
105         NULL,
106         NULL
107       };
108
109       var_store_type = g_type_register_static (G_TYPE_OBJECT, "PsppireVarStore", &var_store_info, 0);
110
111       g_type_add_interface_static (var_store_type,
112                                    G_TYPE_SHEET_MODEL,
113                                    &sheet_model_info);
114
115       g_type_add_interface_static (var_store_type,
116                                    G_TYPE_SHEET_ROW,
117                                    &sheet_row_info);
118
119
120     }
121
122   return var_store_type;
123 }
124
125 static void
126 psppire_var_store_class_init (PsppireVarStoreClass *class)
127 {
128   GObjectClass *object_class;
129
130   parent_class = g_type_class_peek_parent (class);
131   object_class = (GObjectClass*) class;
132
133   object_class->finalize = psppire_var_store_finalize;
134 }
135
136
137 static void
138 psppire_var_store_init (PsppireVarStore *var_store)
139 {
140   GdkColormap *colormap = gdk_colormap_get_system();
141
142   g_assert(gdk_color_parse("gray", &var_store->disabled));
143
144   gdk_colormap_alloc_color (colormap, &var_store->disabled, FALSE, TRUE);
145
146   var_store->dict = 0;
147 }
148
149 static gboolean
150 psppire_var_store_item_editable(PsppireVarStore *var_store, gint row, gint column)
151 {
152   const struct fmt_spec *write_spec ;
153
154   struct PsppireVariable *pv = psppire_var_store_get_variable(var_store, row);
155
156   if ( !pv ) 
157     return TRUE;
158
159   if ( ALPHA == psppire_variable_get_type(pv) && column == COL_DECIMALS ) 
160     return FALSE;
161
162   write_spec = psppire_variable_get_write_spec(pv);
163
164   switch ( write_spec->type ) 
165     {
166     case FMT_DATE:      
167     case FMT_EDATE:     
168     case FMT_SDATE:     
169     case FMT_ADATE:     
170     case FMT_JDATE:     
171     case FMT_QYR:       
172     case FMT_MOYR:      
173     case FMT_WKYR:      
174     case FMT_DATETIME:  
175     case FMT_TIME:      
176     case FMT_DTIME:     
177     case FMT_WKDAY:     
178     case FMT_MONTH:     
179       if ( column == COL_DECIMALS || column == COL_WIDTH)
180         return FALSE;
181       break;
182     default:
183       break;
184     }
185
186   return TRUE;
187 }
188
189 static gboolean
190 psppire_var_store_is_editable(const GSheetModel *model, gint row, gint column)
191 {
192   PsppireVarStore *store = PSPPIRE_VAR_STORE(model);
193   return psppire_var_store_item_editable(store, row, column);
194 }
195
196
197 static const GdkColor *
198 psppire_var_store_get_foreground(const GSheetModel *model, gint row, gint column)
199 {
200   PsppireVarStore *store = PSPPIRE_VAR_STORE(model);
201
202   if ( ! psppire_var_store_item_editable(store, row, column) ) 
203     return &store->disabled;
204   
205   return NULL;
206 }
207
208
209 const PangoFontDescription *
210 psppire_var_store_get_font_desc(const GSheetModel *model,
211                               gint row, gint column)
212 {
213   PsppireVarStore *store = PSPPIRE_VAR_STORE(model);
214   
215   return store->font_desc;
216 }
217
218
219
220 static void
221 psppire_var_store_sheet_model_init (GSheetModelIface *iface)
222 {
223   iface->get_row_count = psppire_var_store_get_row_count;
224   iface->free_strings = TRUE;
225   iface->get_string = psppire_var_store_get_string;
226   iface->set_string = psppire_var_store_set_string;
227   iface->clear_datum = psppire_var_store_clear;
228   iface->is_editable = psppire_var_store_is_editable;
229   iface->is_visible = NULL;
230   iface->get_foreground = psppire_var_store_get_foreground;
231   iface->get_background = NULL;
232   iface->get_font_desc = psppire_var_store_get_font_desc;
233   iface->get_cell_border = NULL;
234 }
235
236
237
238 /**
239  * psppire_var_store_new:
240  * @dict: The dictionary for this var_store.
241  *
242  *
243  * Return value: a new #PsppireVarStore
244  **/
245 PsppireVarStore *
246 psppire_var_store_new (PsppireDict *dict)
247 {
248   PsppireVarStore *retval;
249
250   retval = g_object_new (GTK_TYPE_VAR_STORE, NULL);
251
252   psppire_var_store_set_dictionary(retval, dict);
253
254   return retval;
255 }
256
257 static void 
258 var_change_callback(GtkWidget *w, gint n, gpointer data)
259 {
260   GSheetModel *model = G_SHEET_MODEL(data);
261   g_sheet_model_range_changed (model,
262                                  n, 0, n, n_COLS);
263 }
264
265
266 static void 
267 var_delete_callback(GtkWidget *w, gint first, gint n, gpointer data)
268 {
269   GSheetModel *model = G_SHEET_MODEL(data);
270   
271   g_sheet_model_rows_deleted (model, first, n);
272 }
273
274
275
276 static void 
277 var_insert_callback(GtkWidget *w, gint row, gpointer data)
278 {
279   GSheetModel *model = G_SHEET_MODEL(data);
280
281   g_sheet_model_rows_inserted (model, row, 1);
282 }
283
284
285
286 /**
287  * psppire_var_store_replace_set_dictionary:
288  * @var_store: The variable store
289  * @dict: The dictionary to set
290  *
291  * If a dictionary is already associated with the var-store, then it will be
292  * destroyed.
293  **/
294 void
295 psppire_var_store_set_dictionary(PsppireVarStore *var_store, PsppireDict *dict)
296 {
297   if ( var_store->dict ) g_object_unref(var_store->dict);
298
299   var_store->dict = dict;
300
301   g_signal_connect(dict, "variable-changed", G_CALLBACK(var_change_callback), 
302                    var_store);
303
304   g_signal_connect(dict, "variables-deleted", G_CALLBACK(var_delete_callback), 
305                    var_store);
306
307   g_signal_connect(dict, "variable-inserted", G_CALLBACK(var_insert_callback), 
308                    var_store);
309
310
311   /* The entire model has changed */
312   g_sheet_model_range_changed (G_SHEET_MODEL(var_store), -1, -1, -1, -1);
313 }
314
315 static void
316 psppire_var_store_finalize (GObject *object)
317 {
318   /* must chain up */
319   (* parent_class->finalize) (object);
320 }
321
322 static gchar *
323 psppire_var_store_get_string(const GSheetModel *model, gint row, gint column)
324 {
325   PsppireVarStore *store = PSPPIRE_VAR_STORE(model);
326
327   struct PsppireVariable *pv;
328
329   if ( row >= psppire_dict_get_var_cnt(store->dict))
330     return 0;
331   
332   pv = psppire_dict_get_variable (store->dict, row);
333   
334   return text_for_column(pv, column, 0);
335 }
336
337
338 struct PsppireVariable *
339 psppire_var_store_get_variable(PsppireVarStore *store, gint row)
340 {
341   g_return_val_if_fail(store, NULL);
342   g_return_val_if_fail(store->dict, NULL);
343
344   if ( row >= psppire_dict_get_var_cnt(store->dict))
345     return 0;
346
347   return psppire_dict_get_variable (store->dict, row);
348 }
349
350 /* Clears that part of the variable store, if possible, which corresponds 
351    to ROW, COL.
352    Returns true if anything was updated, false otherwise.
353 */
354 static gboolean 
355 psppire_var_store_clear(GSheetModel *model,  gint row, gint col)
356 {
357   struct PsppireVariable *pv ;
358
359   PsppireVarStore *var_store = PSPPIRE_VAR_STORE(model);
360
361   if ( row >= psppire_dict_get_var_cnt(var_store->dict))
362       return FALSE;
363
364   pv = psppire_var_store_get_variable(var_store, row);
365
366   if ( !pv ) 
367     return FALSE;
368
369   switch (col)
370     {
371     case COL_LABEL:
372       psppire_variable_set_label(pv, 0);
373       return TRUE;
374       break;
375     }
376
377   return FALSE;
378 }
379
380 /* Attempts to update that part of the variable store which corresponds 
381    to ROW, COL with  the value TEXT.
382    Returns true if anything was updated, false otherwise.
383 */
384 static gboolean 
385 psppire_var_store_set_string(GSheetModel *model, 
386                           const gchar *text, gint row, gint col)
387 {
388   struct PsppireVariable *pv ;
389
390   PsppireVarStore *var_store = PSPPIRE_VAR_STORE(model);
391
392   if ( row >= psppire_dict_get_var_cnt(var_store->dict))
393       return FALSE;
394
395   pv = psppire_var_store_get_variable(var_store, row);
396   if ( !pv ) 
397     return FALSE;
398
399   switch (col)
400     {
401     case COL_NAME:
402       return psppire_variable_set_name(pv, text);
403       break;
404     case COL_COLUMNS:
405       if ( ! text) return FALSE;
406       return psppire_variable_set_columns(pv, atoi(text));
407       break;
408     case COL_WIDTH:
409       if ( ! text) return FALSE;
410       return psppire_variable_set_width(pv, atoi(text));
411       break;
412     case COL_DECIMALS:
413       if ( ! text) return FALSE;
414       return psppire_variable_set_decimals(pv, atoi(text));
415       break;
416     case COL_LABEL:
417       psppire_variable_set_label(pv, text);
418       return TRUE;
419       break;
420     case COL_TYPE:
421     case COL_VALUES:
422     case COL_MISSING:
423     case COL_ALIGN:
424     case COL_MEASURE:
425       /* These can be modified only by their respective dialog boxes */
426       return FALSE;
427       break;
428     default:
429       g_assert_not_reached();
430       return FALSE;
431     }
432
433   return TRUE;
434 }
435
436
437 static  gchar *
438 text_for_column(const struct PsppireVariable *pv, gint c, GError **err)
439 {
440   static gchar none[] = N_("None");
441
442   static const gchar *const type_label[] = 
443     {
444       N_("Numeric"),
445       N_("Comma"),
446       N_("Dot"),
447       N_("Scientific"),
448       N_("Date"),
449       N_("Dollar"),
450       N_("Custom"),
451       N_("String")
452     };
453   enum {VT_NUMERIC, VT_COMMA, VT_DOT, VT_SCIENTIFIC, VT_DATE, VT_DOLLAR, 
454         VT_CUSTOM, VT_STRING};
455
456   const struct fmt_spec *write_spec = psppire_variable_get_write_spec(pv);
457
458   switch (c)
459     {
460     case COL_NAME:
461       return pspp_locale_to_utf8(psppire_variable_get_name(pv), -1, err);
462       break;
463     case COL_TYPE:
464       {
465         switch ( write_spec->type ) 
466           {
467           case FMT_F:
468             return g_locale_to_utf8(gettext(type_label[VT_NUMERIC]), -1, 0, 0, err);
469             break;
470           case FMT_COMMA:
471             return g_locale_to_utf8(gettext(type_label[VT_COMMA]), -1, 0, 0, err);
472             break;
473           case FMT_DOT:
474             return g_locale_to_utf8(gettext(type_label[VT_DOT]), -1, 0, 0, err);
475             break;
476           case FMT_E:
477             return g_locale_to_utf8(gettext(type_label[VT_SCIENTIFIC]), -1, 0, 0, err);
478             break;
479           case FMT_DATE:        
480           case FMT_EDATE:       
481           case FMT_SDATE:       
482           case FMT_ADATE:       
483           case FMT_JDATE:       
484           case FMT_QYR: 
485           case FMT_MOYR:        
486           case FMT_WKYR:        
487           case FMT_DATETIME:    
488           case FMT_TIME:        
489           case FMT_DTIME:       
490           case FMT_WKDAY:       
491           case FMT_MONTH:       
492             return g_locale_to_utf8(type_label[VT_DATE], -1, 0, 0, err);
493             break;
494           case FMT_DOLLAR:
495             return g_locale_to_utf8(type_label[VT_DOLLAR], -1, 0, 0, err);
496             break;
497           case FMT_CCA:
498           case FMT_CCB:
499           case FMT_CCC:
500           case FMT_CCD:
501           case FMT_CCE:
502             return g_locale_to_utf8(gettext(type_label[VT_CUSTOM]), -1, 0, 0, err);
503             break;
504           case FMT_A:
505             return g_locale_to_utf8(gettext(type_label[VT_STRING]), -1, 0, 0, err);
506             break;
507           default:
508             g_warning("Unknown format: \"%s\"\n", 
509                       fmt_to_string(write_spec));
510             break;
511           }
512       }
513       break;
514     case COL_WIDTH:
515       {
516         gchar *s;
517         GString *gstr = g_string_sized_new(10);
518         g_string_printf(gstr, _("%d"), write_spec->w);
519         s = g_locale_to_utf8(gstr->str, gstr->len, 0, 0, err);
520         g_string_free(gstr, TRUE);
521         return s;
522       }
523       break;
524     case COL_DECIMALS:
525       {
526         gchar *s;
527         GString *gstr = g_string_sized_new(10);
528         g_string_printf(gstr, _("%d"), write_spec->d);
529         s = g_locale_to_utf8(gstr->str, gstr->len, 0, 0, err);
530         g_string_free(gstr, TRUE);
531         return s;
532       }
533       break;
534     case COL_COLUMNS:
535       {
536         gchar *s;
537         GString *gstr = g_string_sized_new(10);
538         g_string_printf(gstr, _("%d"), psppire_variable_get_columns(pv));
539         s = g_locale_to_utf8(gstr->str, gstr->len, 0, 0, err);
540         g_string_free(gstr, TRUE);
541         return s;
542       }
543       break;
544     case COL_LABEL:
545       return pspp_locale_to_utf8(psppire_variable_get_label(pv), -1, err);
546       break;
547
548     case COL_MISSING:
549       {
550         gchar *s;
551         const struct missing_values *miss = psppire_variable_get_missing(pv);
552         if ( mv_is_empty(miss)) 
553           return g_locale_to_utf8(gettext(none), -1, 0, 0, err);
554         else
555           {
556             if ( ! mv_has_range (miss))
557               {
558                 GString *gstr = g_string_sized_new(10);
559                 const int n = mv_n_values(miss);
560                 gchar *mv[4] = {0,0,0,0};
561                 gint i;
562                 for(i = 0 ; i < n; ++i ) 
563                   {
564                     union value v;
565                     mv_peek_value(miss, &v, i);
566                     mv[i] = value_to_text(v, *write_spec);
567                     if ( i > 0 ) 
568                       g_string_append(gstr, ", ");
569                     g_string_append(gstr, mv[i]);
570                     g_free(mv[i]);
571                   }
572                 s = pspp_locale_to_utf8(gstr->str, gstr->len, err);
573                 g_string_free(gstr, TRUE);
574               }
575             else
576               {
577                 GString *gstr = g_string_sized_new(10);
578                 gchar *l, *h;
579                 union value low, high;
580                 mv_peek_range(miss, &low.f, &high.f);
581                   
582                 l = value_to_text(low, *write_spec);
583                 h = value_to_text(high, *write_spec);
584
585                 g_string_printf(gstr, "%s - %s", l, h);
586                 g_free(l);
587                 g_free(h);
588
589                 if ( mv_has_value(miss)) 
590                   {
591                     gchar *ss = 0;
592                     union value v;
593                     mv_peek_value(miss, &v, 0);
594
595                     ss = value_to_text(v, *write_spec);
596
597                     g_string_append(gstr, ", ");
598                     g_string_append(gstr, ss);
599                     free(ss);
600                   }
601                 s = pspp_locale_to_utf8(gstr->str, gstr->len, err);
602                 g_string_free(gstr, TRUE);
603               }
604
605             return s;
606           }
607       }
608       break;
609     case COL_VALUES:
610       {
611         const struct val_labs *vls = psppire_variable_get_value_labels(pv);
612         if ( ! vls || 0 == val_labs_count(vls) ) 
613           return g_locale_to_utf8(gettext(none), -1, 0, 0, err);
614         else
615           {
616             gchar *ss;
617             GString *gstr = g_string_sized_new(10);
618             struct val_labs_iterator *ip = 0;
619             struct val_lab *vl = val_labs_first_sorted (vls, &ip);
620
621             g_assert(vl);
622
623             {
624               gchar *const vstr = value_to_text(vl->value, *write_spec);
625
626               g_string_printf(gstr, "{%s,\"%s\"}_", vstr, vl->label);
627               g_free(vstr);
628             }
629
630             val_labs_done(&ip);
631             
632             ss = pspp_locale_to_utf8(gstr->str, gstr->len, err);
633             g_string_free(gstr, TRUE);
634             return ss;
635           }
636       }
637       break;
638     case COL_ALIGN:
639       {
640         const gint align = psppire_variable_get_alignment(pv);
641
642         g_assert(align < n_ALIGNMENTS);
643         return g_locale_to_utf8(gettext(alignments[align]), -1, 0, 0, err);
644       }
645       break;
646     case COL_MEASURE:
647       {
648         const gint measure = psppire_variable_get_measure(pv);
649
650         g_assert(measure < n_MEASURES);
651         return g_locale_to_utf8(gettext(measures[measure]), -1, 0, 0, err);
652       }
653       break;
654     }
655   return 0;
656 }
657
658
659
660 /* Return the number of variables */
661 gint
662 psppire_var_store_get_var_cnt(PsppireVarStore  *store)
663 {
664   return psppire_dict_get_var_cnt(store->dict);
665 }
666
667
668 void
669 psppire_var_store_set_font(PsppireVarStore *store, const PangoFontDescription *fd)
670 {
671   g_return_if_fail (store);
672   g_return_if_fail (PSPPIRE_IS_VAR_STORE (store));
673
674   store->font_desc = fd;
675
676   g_sheet_model_range_changed (G_SHEET_MODEL(store), -1, -1, -1, -1);
677 }
678
679
680 static gint
681 psppire_var_store_get_row_count(const GSheetModel * model)
682 {
683   gint rows = 0;
684   PsppireVarStore *vs = PSPPIRE_VAR_STORE(model);
685
686   if (vs->dict) 
687     rows =  psppire_dict_get_var_cnt(vs->dict); 
688
689   return rows ;
690 }
691
692 /* Row related funcs */
693
694 static gint
695 geometry_get_row_count(const GSheetRow *geom, gpointer data)
696 {
697   gint rows = 0;
698   PsppireVarStore *vs = PSPPIRE_VAR_STORE(geom);
699
700   if (vs->dict) 
701     rows =  psppire_dict_get_var_cnt(vs->dict); 
702
703   return rows + TRAILING_ROWS;
704 }
705
706
707 static gint
708 geometry_get_height(const GSheetRow *geom, gint row, gpointer data)
709 {
710   return 25;
711 }
712
713
714 static gboolean
715 geometry_is_sensitive(const GSheetRow *geom, gint row, gpointer data)
716 {
717   PsppireVarStore *vs = PSPPIRE_VAR_STORE(geom);
718   
719   if ( ! vs->dict) 
720     return FALSE;
721
722   return  row < psppire_dict_get_var_cnt(vs->dict); 
723 }
724
725 static
726 gboolean always_true()
727 {
728   return TRUE;
729 }
730
731
732 static gchar *
733 geometry_get_button_label(const GSheetRow *geom, gint unit, gpointer data)
734 {
735   gchar *label = g_strdup_printf(_("%d"), unit);
736   
737   return label;
738 }
739
740
741 static void
742 psppire_var_store_sheet_row_init (GSheetRowIface *iface)
743 {
744   iface->get_row_count =     geometry_get_row_count;
745   iface->get_height =        geometry_get_height;
746   iface->set_height =        0;
747   iface->get_visibility =    always_true;
748   iface->get_sensitivity =   geometry_is_sensitive;
749
750   iface->get_button_label = geometry_get_button_label;
751 }