Reworked very long string support for better encapsulation.
[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
33 #include <data/dictionary.h>
34 #include <data/missing-values.h>
35 #include <data/value-labels.h>
36 #include <data/data-in.h>
37
38 #include <data/file-handle-def.h>
39 #include <data/sys-file-writer.h>
40
41 #define _(A) A
42 #define N_(A) A
43
44
45 static void psppire_data_store_init            (PsppireDataStore      *data_store);
46 static void psppire_data_store_class_init      (PsppireDataStoreClass *class);
47 static void psppire_data_store_sheet_model_init (GSheetModelIface *iface);
48 static void psppire_data_store_sheet_column_init (GSheetColumnIface *iface);
49 static void psppire_data_store_finalize        (GObject           *object);
50
51 static const gchar *const psppire_data_store_get_string(GSheetModel *sheet_model, gint row, gint column);
52
53 static gboolean psppire_data_store_set_string(GSheetModel *model, 
54                                           const gchar *text, gint row, gint column);
55
56 static gboolean psppire_data_store_clear_datum(GSheetModel *model, 
57                                           gint row, gint column);
58
59
60 #define MIN_COLUMNS 10
61
62 #define max(A,B) ((A>B)?A:B)
63
64 static GObjectClass *parent_class = NULL;
65
66 inline GType
67 psppire_data_store_get_type (void)
68 {
69   static GType data_store_type = 0;
70
71   if (!data_store_type)
72     {
73       static const GTypeInfo data_store_info =
74       {
75         sizeof (PsppireDataStoreClass),
76         NULL,           /* base_init */
77         NULL,           /* base_finalize */
78         (GClassInitFunc) psppire_data_store_class_init,
79         NULL,           /* class_finalize */
80         NULL,           /* class_data */
81         sizeof (PsppireDataStore),
82         0,
83         (GInstanceInitFunc) psppire_data_store_init,
84       };
85
86       static const GInterfaceInfo sheet_model_info =
87       {
88         (GInterfaceInitFunc) psppire_data_store_sheet_model_init,
89         NULL,
90         NULL
91       };
92
93       static const GInterfaceInfo sheet_column_info =
94       {
95         (GInterfaceInitFunc) psppire_data_store_sheet_column_init,
96         NULL,
97         NULL
98       };
99
100
101
102       data_store_type = g_type_register_static (G_TYPE_OBJECT, "PsppireDataStore",
103                                                 &data_store_info, 0);
104
105       g_type_add_interface_static (data_store_type,
106                                    G_TYPE_SHEET_MODEL,
107                                    &sheet_model_info);
108
109       g_type_add_interface_static (data_store_type,
110                                    G_TYPE_SHEET_COLUMN,
111                                    &sheet_column_info);
112
113     }
114
115   return data_store_type;
116 }
117
118 static void
119 psppire_data_store_class_init (PsppireDataStoreClass *class)
120 {
121   GObjectClass *object_class;
122
123   parent_class = g_type_class_peek_parent (class);
124   object_class = (GObjectClass*) class;
125
126   object_class->finalize = psppire_data_store_finalize;
127 }
128
129
130 static void
131 psppire_data_store_init (PsppireDataStore *data_store)
132 {
133   data_store->dict = 0;
134   data_store->cases = 0;
135 }
136
137 const PangoFontDescription *
138 psppire_data_store_get_font_desc(GSheetModel *model,
139                               gint row, gint column)
140 {
141   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
142   
143   return store->font_desc;
144 }
145
146
147 static void
148 psppire_data_store_sheet_model_init (GSheetModelIface *iface)
149 {
150   iface->get_string = psppire_data_store_get_string;
151   iface->set_string = psppire_data_store_set_string;
152   iface->clear_datum = psppire_data_store_clear_datum;
153   iface->is_editable = NULL;
154   iface->is_visible = NULL;
155   iface->get_foreground = NULL;
156   iface->get_background = NULL;
157   iface->get_font_desc = psppire_data_store_get_font_desc;
158   iface->get_cell_border = NULL;
159 }
160
161 static
162 gboolean always_true()
163 {
164   return TRUE;
165 }
166
167
168
169 static void
170 delete_cases_callback(GtkWidget *w, gint first, gint n_cases, gpointer data)
171 {
172   PsppireDataStore *store  ;
173
174   g_return_if_fail (data);
175
176   store  = PSPPIRE_DATA_STORE(data);
177
178   g_assert(first >= 0);
179
180   g_sheet_model_rows_deleted (G_SHEET_MODEL(store), first, n_cases);
181 }
182
183
184 static void
185 insert_case_callback(GtkWidget *w, gint casenum, gpointer data)
186 {
187   PsppireDataStore *store  ;
188   g_return_if_fail (data);
189
190   store  = PSPPIRE_DATA_STORE(data);
191   
192   g_sheet_model_range_changed (G_SHEET_MODEL(store),
193                                  casenum, -1,
194                                  psppire_case_array_get_n_cases(store->cases),
195                                  -1);
196 }
197
198
199 static void
200 changed_case_callback(GtkWidget *w, gint casenum, gpointer data)
201 {
202   PsppireDataStore *store  ;
203   g_return_if_fail (data);
204
205   store  = PSPPIRE_DATA_STORE(data);
206   
207   g_sheet_model_range_changed (G_SHEET_MODEL(store),
208                                  casenum, -1,
209                                  casenum, -1);
210
211 }
212
213
214 static void
215 delete_variables_callback(GtkWidget *w, gint var_num, gint n_vars, gpointer data)
216 {
217   PsppireDataStore *store ;
218
219   g_return_if_fail (data);
220
221   store  = PSPPIRE_DATA_STORE(data);
222
223   g_sheet_column_columns_deleted(G_SHEET_COLUMN(store),
224                                    var_num, n_vars);
225 }
226
227
228 static void
229 insert_variable_callback(GtkWidget *w, gint var_num, gpointer data)
230 {
231   PsppireDataStore *store;
232
233   g_return_if_fail (data);
234
235   store  = PSPPIRE_DATA_STORE(data);
236   
237   /* 
238   g_sheet_model_range_changed (G_SHEET_MODEL(store),
239                                  casenum, -1,
240                                  psppire_case_array_get_n_cases(store->cases),
241                                  -1);
242   */
243
244   psppire_case_array_resize(store->cases, 
245                          dict_get_next_value_idx (store->dict->dict));
246
247 }
248
249
250
251
252 /**
253  * psppire_data_store_new:
254  * @dict: The dictionary for this data_store.
255  *
256  *
257  * Return value: a new #PsppireDataStore
258  **/
259 PsppireDataStore *
260 psppire_data_store_new (PsppireDict *dict, PsppireCaseArray *cases)
261 {
262   PsppireDataStore *retval;
263
264   retval = g_object_new (GTK_TYPE_DATA_STORE, NULL);
265
266   retval->cases = cases;
267   g_signal_connect(cases, "cases-deleted", G_CALLBACK(delete_cases_callback), 
268                    retval);
269
270   g_signal_connect(cases, "case-inserted", G_CALLBACK(insert_case_callback), 
271                    retval);
272
273
274   g_signal_connect(cases, "case-changed", G_CALLBACK(changed_case_callback), 
275                    retval);
276
277
278   psppire_data_store_set_dictionary(retval, dict);
279
280
281   return retval;
282 }
283
284
285
286 /**
287  * psppire_data_store_replace_set_dictionary:
288  * @data_store: The variable store
289  * @dict: The dictionary to set
290  *
291  * If a dictionary is already associated with the data-store, then it will be
292  * destroyed.
293  **/
294 void
295 psppire_data_store_set_dictionary(PsppireDataStore *data_store, PsppireDict *dict)
296 {
297 #if 0
298   if ( data_store->dict ) g_object_unref(data_store->dict);
299 #endif
300
301   data_store->dict = dict;
302
303   psppire_case_array_resize(data_store->cases, 
304                          dict_get_next_value_idx (data_store->dict->dict));
305
306
307   g_signal_connect(dict, "variable-inserted", 
308                    G_CALLBACK(insert_variable_callback), 
309                    data_store);
310
311   g_signal_connect(dict, "variables-deleted", 
312                    G_CALLBACK(delete_variables_callback), 
313                    data_store);
314
315
316   /* The entire model has changed */
317   g_sheet_model_range_changed (G_SHEET_MODEL(data_store), -1, -1, -1, -1);
318 }
319
320 static void
321 psppire_data_store_finalize (GObject *object)
322 {
323
324   /* must chain up */
325   (* parent_class->finalize) (object);
326 }
327
328
329 static const gchar *const 
330 psppire_data_store_get_string(GSheetModel *model, gint row, gint column)
331 {
332
333   const struct fmt_spec *fp ;
334   const struct PsppireVariable *pv ;
335   const union value *v ;
336   GString *s;
337   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
338
339   g_return_val_if_fail(store->dict, NULL);
340   g_return_val_if_fail(store->cases, NULL);
341
342   if (column >= psppire_dict_get_var_cnt(store->dict))
343     return NULL;
344
345   if ( row >= psppire_case_array_get_n_cases(store->cases))
346     return NULL;
347
348
349   pv = psppire_dict_get_variable(store->dict, column);
350
351   v =  psppire_case_array_get_value(store->cases, row, 
352                               psppire_variable_get_index(pv));
353
354   if ( store->show_labels) 
355     {
356       const struct val_labs * vl = psppire_variable_get_value_labels(pv);
357
358       const gchar *label;
359       if ( (label = val_labs_find(vl, *v)) )
360         {
361           return label;
362         }
363     }
364
365   fp = psppire_variable_get_write_spec(pv);
366
367   s = g_string_sized_new (fp->w + 1);
368   g_string_set_size(s, fp->w);
369   
370   memset(s->str, 0, fp->w);
371
372   g_assert(fp->w == s->len);
373     
374   /* Converts binary value V into printable form in the exactly
375      FP->W character in buffer S according to format specification
376      FP.  No null terminator is appended to the buffer.  */
377   data_out (s->str, fp, v);
378   
379   return g_string_free(s, FALSE);
380 #if 0
381   {
382     static gchar buf[255];
383     GError *err = NULL;
384     gchar *text = g_locale_to_utf8(s, fp->w, 0, 0, &err);
385     if ( !err ) 
386       { 
387         g_snprintf(buf, 255, text);
388         g_free(text);
389       }
390     else
391       {
392         g_warning("Cannot convert string \"%s\" to utf-8: %s\n", s, err->message);
393         g_error_free(err);
394         return NULL;
395       }
396
397   return buf ;
398   }
399 #endif
400 }
401
402
403 static gboolean
404 set_null_string_value(union value *val, gpointer data)
405 {
406   strcpy(val->s, "");
407   return TRUE;
408 }
409
410 static gboolean
411 set_sysmis_value(union value *val, gpointer data)
412 {
413   val->f = SYSMIS;
414   return TRUE;
415 }
416
417
418 static gboolean 
419 psppire_data_store_clear_datum(GSheetModel *model, 
420                                           gint row, gint col)
421
422 {
423   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
424
425   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
426
427   const gint index = psppire_variable_get_index(pv) ;
428
429   if ( psppire_variable_get_type(pv) == NUMERIC) 
430     psppire_case_array_set_value(store->cases, row, index, set_sysmis_value,0);
431   else
432     psppire_case_array_set_value(store->cases, row, index, set_null_string_value,0);
433   return TRUE;
434 }
435
436
437 static gboolean
438 fillit(union value *val, gpointer data)
439 {
440   struct data_in *d_in = data;
441
442   d_in->v = val;
443
444   if ( ! data_in(d_in) ) 
445     {
446       g_warning("Cant encode string\n");
447       return FALSE;
448     }
449
450   return TRUE;
451 }
452
453
454 /* Attempts to update that part of the variable store which corresponds 
455    to ROW, COL with  the value TEXT.
456    Returns true if anything was updated, false otherwise.
457 */
458 static gboolean 
459 psppire_data_store_set_string(GSheetModel *model, 
460                           const gchar *text, gint row, gint col)
461 {
462   gint r;
463   PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
464
465   const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
466
467   for(r = psppire_case_array_get_n_cases(store->cases) ; r <= row ; ++r ) 
468     {
469       gint c;
470       psppire_case_array_insert_case(store->cases, r);
471
472       for (c = 0 ; c < psppire_dict_get_var_cnt(store->dict); ++c ) 
473         psppire_data_store_clear_datum(model, r, c);
474     }
475
476   {
477     const gint index = psppire_variable_get_index(pv);
478
479     struct data_in d_in;
480     d_in.s = text;
481     d_in.e = text + strlen(text);
482     d_in.v = 0;
483     d_in.f1 = d_in.f2 = 0;
484     d_in.format = * psppire_variable_get_write_spec(pv);
485     d_in.flags = 0;
486
487     psppire_case_array_set_value(store->cases, row, index, fillit, &d_in);
488   }
489
490   return TRUE;
491 }
492
493
494 void
495 psppire_data_store_set_font(PsppireDataStore *store, PangoFontDescription *fd)
496 {
497   g_return_if_fail (store);
498   g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
499
500   store->font_desc = fd;
501   g_sheet_model_range_changed (G_SHEET_MODEL(store),
502                                  -1, -1, -1, -1);
503 }
504
505
506 void
507 psppire_data_store_show_labels(PsppireDataStore *store, gboolean show_labels)
508 {
509   g_return_if_fail (store);
510   g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
511
512   store->show_labels = show_labels;
513
514   g_sheet_model_range_changed (G_SHEET_MODEL(store),
515                                  -1, -1, -1, -1);
516 }
517
518
519
520 static gboolean 
521 write_case(const struct ccase *cc, 
522            gpointer aux)
523 {
524   struct sfm_writer *writer = aux;
525
526   if ( ! sfm_write_case(writer, cc) )
527     return FALSE;
528
529
530   return TRUE;
531 }
532
533 void
534 psppire_data_store_create_system_file(PsppireDataStore *store,
535                               struct file_handle *handle)
536 {
537   const struct sfm_write_options wo = {
538     true, /* writeable */
539     false, /* dont compress */
540     3 /* version */
541   }; 
542
543   struct sfm_writer *writer ;
544
545   g_assert(handle);
546
547   writer = sfm_open_writer(handle, store->dict->dict, wo);
548
549   if ( ! writer) 
550     return;
551
552   psppire_case_array_iterate_case(store->cases, write_case, writer);
553
554   sfm_close_writer(writer);
555 }
556
557
558
559 /* Column related funcs */
560
561 static gint
562 geometry_get_column_count(const GSheetColumn *geom)
563 {
564   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
565
566   return max(MIN_COLUMNS, psppire_dict_get_var_cnt(ds->dict));
567 }
568
569 /* Return the width that an  'M' character would occupy when typeset at
570    row, col */
571 static guint 
572 M_width(GtkSheet *sheet, gint row, gint col)
573 {
574   GtkSheetCellAttr attributes;
575   PangoRectangle rect;
576   /* FIXME: make this a member of the data store */
577   static PangoLayout *layout = 0;
578
579   gtk_sheet_get_attributes(sheet, row, col, &attributes);
580
581   if (! layout ) 
582     layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), "M");
583
584   g_assert(layout);
585   
586   pango_layout_set_font_description (layout, 
587                                      attributes.font_desc);
588
589   pango_layout_get_extents (layout, NULL, &rect);
590
591 #if 0
592   g_object_unref(G_OBJECT(layout));
593 #endif
594
595   return PANGO_PIXELS(rect.width);
596 }
597
598
599 /* Return the number of pixels corresponding to a column of 
600    WIDTH characters */
601 static inline guint 
602 columnWidthToPixels(GtkSheet *sheet, gint column, guint width)
603 {
604   return (M_width(sheet, 0, column) * width);
605 }
606
607
608 static gint
609 geometry_get_width(const GSheetColumn *geom, gint unit, GtkSheet *sheet)
610 {
611   const struct PsppireVariable *pv ;
612   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
613
614   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
615     return 75;
616
617   /* FIXME: We can optimise this by caching the widths until they're resized */
618   pv = psppire_dict_get_variable(ds->dict, unit);
619
620   return columnWidthToPixels(sheet, unit, psppire_variable_get_columns(pv));
621 }
622
623
624
625
626 static void
627 geometry_set_width(GSheetColumn *geom, gint unit, gint width, GtkSheet *sheet)
628 {
629   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
630
631   struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
632
633   psppire_variable_set_columns(pv, width / M_width(sheet, 1, unit));
634 }
635
636
637
638 static GtkJustification
639 geometry_get_justification(const GSheetColumn *geom, gint unit)
640 {
641   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
642   const struct PsppireVariable *pv ;
643
644
645   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
646     return GTK_JUSTIFY_LEFT;
647
648   pv = psppire_dict_get_variable(ds->dict, unit);
649
650   /* Kludge: Happily GtkJustification is defined similarly
651      to enum alignment from pspp/variable.h */
652   return psppire_variable_get_alignment(pv);
653 }
654
655
656 static const gchar null_var_name[]=_("var");
657  
658 static const gchar *
659 geometry_get_button_label(const GSheetColumn *geom, gint unit)
660 {
661   struct PsppireVariable *pv ;
662   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
663
664   if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
665     return null_var_name;
666
667   pv = psppire_dict_get_variable(ds->dict, unit);
668
669   return psppire_variable_get_name(pv);
670 }
671
672
673 static gboolean
674 geometry_get_sensitivity(const GSheetColumn *geom, gint unit)
675 {
676   PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
677
678
679   return (unit < psppire_dict_get_var_cnt(ds->dict));
680 }
681
682
683 static void
684 psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
685 {
686   iface->get_column_count = geometry_get_column_count;
687   iface->get_width = geometry_get_width;
688   iface->set_width = geometry_set_width;
689   iface->get_visibility = always_true;
690   iface->get_sensitivity = geometry_get_sensitivity;
691   iface->get_justification = geometry_get_justification;
692
693   iface->get_button_label = geometry_get_button_label;
694 }