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