Completely rewrite src/data/format.[ch], to achieve better
[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             {
509               char str[FMT_STRING_LEN_MAX + 1];
510               g_warning("Unknown format: \"%s\"\n", 
511                         fmt_to_string(write_spec, str)); 
512             }
513             break;
514           }
515       }
516       break;
517     case COL_WIDTH:
518       {
519         gchar *s;
520         GString *gstr = g_string_sized_new(10);
521         g_string_printf(gstr, _("%d"), write_spec->w);
522         s = g_locale_to_utf8(gstr->str, gstr->len, 0, 0, err);
523         g_string_free(gstr, TRUE);
524         return s;
525       }
526       break;
527     case COL_DECIMALS:
528       {
529         gchar *s;
530         GString *gstr = g_string_sized_new(10);
531         g_string_printf(gstr, _("%d"), write_spec->d);
532         s = g_locale_to_utf8(gstr->str, gstr->len, 0, 0, err);
533         g_string_free(gstr, TRUE);
534         return s;
535       }
536       break;
537     case COL_COLUMNS:
538       {
539         gchar *s;
540         GString *gstr = g_string_sized_new(10);
541         g_string_printf(gstr, _("%d"), psppire_variable_get_columns(pv));
542         s = g_locale_to_utf8(gstr->str, gstr->len, 0, 0, err);
543         g_string_free(gstr, TRUE);
544         return s;
545       }
546       break;
547     case COL_LABEL:
548       return pspp_locale_to_utf8(psppire_variable_get_label(pv), -1, err);
549       break;
550
551     case COL_MISSING:
552       {
553         gchar *s;
554         const struct missing_values *miss = psppire_variable_get_missing(pv);
555         if ( mv_is_empty(miss)) 
556           return g_locale_to_utf8(gettext(none), -1, 0, 0, err);
557         else
558           {
559             if ( ! mv_has_range (miss))
560               {
561                 GString *gstr = g_string_sized_new(10);
562                 const int n = mv_n_values(miss);
563                 gchar *mv[4] = {0,0,0,0};
564                 gint i;
565                 for(i = 0 ; i < n; ++i ) 
566                   {
567                     union value v;
568                     mv_peek_value(miss, &v, i);
569                     mv[i] = value_to_text(v, *write_spec);
570                     if ( i > 0 ) 
571                       g_string_append(gstr, ", ");
572                     g_string_append(gstr, mv[i]);
573                     g_free(mv[i]);
574                   }
575                 s = pspp_locale_to_utf8(gstr->str, gstr->len, err);
576                 g_string_free(gstr, TRUE);
577               }
578             else
579               {
580                 GString *gstr = g_string_sized_new(10);
581                 gchar *l, *h;
582                 union value low, high;
583                 mv_peek_range(miss, &low.f, &high.f);
584                   
585                 l = value_to_text(low, *write_spec);
586                 h = value_to_text(high, *write_spec);
587
588                 g_string_printf(gstr, "%s - %s", l, h);
589                 g_free(l);
590                 g_free(h);
591
592                 if ( mv_has_value(miss)) 
593                   {
594                     gchar *ss = 0;
595                     union value v;
596                     mv_peek_value(miss, &v, 0);
597
598                     ss = value_to_text(v, *write_spec);
599
600                     g_string_append(gstr, ", ");
601                     g_string_append(gstr, ss);
602                     free(ss);
603                   }
604                 s = pspp_locale_to_utf8(gstr->str, gstr->len, err);
605                 g_string_free(gstr, TRUE);
606               }
607
608             return s;
609           }
610       }
611       break;
612     case COL_VALUES:
613       {
614         const struct val_labs *vls = psppire_variable_get_value_labels(pv);
615         if ( ! vls || 0 == val_labs_count(vls) ) 
616           return g_locale_to_utf8(gettext(none), -1, 0, 0, err);
617         else
618           {
619             gchar *ss;
620             GString *gstr = g_string_sized_new(10);
621             struct val_labs_iterator *ip = 0;
622             struct val_lab *vl = val_labs_first_sorted (vls, &ip);
623
624             g_assert(vl);
625
626             {
627               gchar *const vstr = value_to_text(vl->value, *write_spec);
628
629               g_string_printf(gstr, "{%s,\"%s\"}_", vstr, vl->label);
630               g_free(vstr);
631             }
632
633             val_labs_done(&ip);
634             
635             ss = pspp_locale_to_utf8(gstr->str, gstr->len, err);
636             g_string_free(gstr, TRUE);
637             return ss;
638           }
639       }
640       break;
641     case COL_ALIGN:
642       {
643         const gint align = psppire_variable_get_alignment(pv);
644
645         g_assert(align < n_ALIGNMENTS);
646         return g_locale_to_utf8(gettext(alignments[align]), -1, 0, 0, err);
647       }
648       break;
649     case COL_MEASURE:
650       {
651         const gint measure = psppire_variable_get_measure(pv);
652
653         g_assert(measure < n_MEASURES);
654         return g_locale_to_utf8(gettext(measures[measure]), -1, 0, 0, err);
655       }
656       break;
657     }
658   return 0;
659 }
660
661
662
663 /* Return the number of variables */
664 gint
665 psppire_var_store_get_var_cnt(PsppireVarStore  *store)
666 {
667   return psppire_dict_get_var_cnt(store->dict);
668 }
669
670
671 void
672 psppire_var_store_set_font(PsppireVarStore *store, const PangoFontDescription *fd)
673 {
674   g_return_if_fail (store);
675   g_return_if_fail (PSPPIRE_IS_VAR_STORE (store));
676
677   store->font_desc = fd;
678
679   g_sheet_model_range_changed (G_SHEET_MODEL(store), -1, -1, -1, -1);
680 }
681
682
683 static gint
684 psppire_var_store_get_row_count(const GSheetModel * model)
685 {
686   gint rows = 0;
687   PsppireVarStore *vs = PSPPIRE_VAR_STORE(model);
688
689   if (vs->dict) 
690     rows =  psppire_dict_get_var_cnt(vs->dict); 
691
692   return rows ;
693 }
694
695 /* Row related funcs */
696
697 static gint
698 geometry_get_row_count(const GSheetRow *geom, gpointer data)
699 {
700   gint rows = 0;
701   PsppireVarStore *vs = PSPPIRE_VAR_STORE(geom);
702
703   if (vs->dict) 
704     rows =  psppire_dict_get_var_cnt(vs->dict); 
705
706   return rows + TRAILING_ROWS;
707 }
708
709
710 static gint
711 geometry_get_height(const GSheetRow *geom, gint row, gpointer data)
712 {
713   return 25;
714 }
715
716
717 static gboolean
718 geometry_is_sensitive(const GSheetRow *geom, gint row, gpointer data)
719 {
720   PsppireVarStore *vs = PSPPIRE_VAR_STORE(geom);
721   
722   if ( ! vs->dict) 
723     return FALSE;
724
725   return  row < psppire_dict_get_var_cnt(vs->dict); 
726 }
727
728 static
729 gboolean always_true()
730 {
731   return TRUE;
732 }
733
734
735 static gchar *
736 geometry_get_button_label(const GSheetRow *geom, gint unit, gpointer data)
737 {
738   gchar *label = g_strdup_printf(_("%d"), unit);
739   
740   return label;
741 }
742
743
744 static void
745 psppire_var_store_sheet_row_init (GSheetRowIface *iface)
746 {
747   iface->get_row_count =     geometry_get_row_count;
748   iface->get_height =        geometry_get_height;
749   iface->set_height =        0;
750   iface->get_visibility =    always_true;
751   iface->get_sensitivity =   geometry_is_sensitive;
752
753   iface->get_button_label = geometry_get_button_label;
754 }