Set individual cell renderers for variable sheet columns
[pspp] / src / ui / gui / psppire-data-editor.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "ui/gui/psppire-data-editor.h"
20
21 #include <gtk/gtk.h>
22 #include <gtk-contrib/gtkxpaned.h>
23
24 #include "data/datasheet.h"
25 #include "data/value-labels.h"
26 #include "libpspp/range-set.h"
27 #include "libpspp/str.h"
28 #include "ui/gui/helper.h"
29 #include "ui/gui/pspp-sheet-selection.h"
30 #include "ui/gui/psppire-data-store.h"
31 #include "ui/gui/psppire-value-entry.h"
32 #include "ui/gui/psppire-var-sheet.h"
33 #include "ui/gui/psppire-conf.h"
34 #include "ui/gui/psppire-var-sheet-header.h"
35
36 #include "ui/gui/efficient-sheet/jmd-sheet.h"
37
38 #include <gettext.h>
39 #define _(msgid) gettext (msgid)
40
41
42 static GtkCellRenderer *
43 create_spin_renderer (GType type)
44 {
45   GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
46       
47   GtkAdjustment *adj = gtk_adjustment_new (0,
48                                            0, G_MAXDOUBLE,
49                                            1, 1,
50                                            0);
51   g_object_set (r,
52                 "adjustment", adj,
53                 NULL);
54   
55   return r;
56 }
57
58 static GtkCellRenderer *
59 create_combo_renderer (GType type)
60 {
61   GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
62   
63   GEnumClass *ec = g_type_class_ref (type);
64   
65   const GEnumValue *ev ;
66   for (ev = ec->values; ev->value_name; ++ev)
67     {
68       GtkTreeIter iter;
69
70       gtk_list_store_append (list_store, &iter);
71
72       gtk_list_store_set (list_store, &iter,
73                           0, ev->value,
74                           1, ev->value_nick,
75                           -1);
76     }
77
78   GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
79
80   g_object_set (r,
81                 "model", list_store,
82                 "text-column", 1,
83                 "has-entry", TRUE,
84                 NULL);
85
86   return r;
87 }
88
89 GtkCellRenderer *xx ;
90 GtkCellRenderer *column_width_renderer ;
91 GtkCellRenderer *measure_renderer ;
92 GtkCellRenderer *alignment_renderer ;
93
94
95
96 static GtkCellRenderer *
97 select_renderer_func (gint col, gint row, GType type)
98 {
99   if (!xx)
100     xx = create_spin_renderer (type);
101
102   if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
103     column_width_renderer = create_combo_renderer (type);
104
105   if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
106     measure_renderer = create_combo_renderer (type);
107
108   if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
109     alignment_renderer = create_combo_renderer (type);
110
111   switch  (col)
112     {
113     case DICT_TVM_COL_WIDTH:
114     case DICT_TVM_COL_DECIMAL:
115     case DICT_TVM_COL_COLUMNS:
116       return xx;
117       
118     case DICT_TVM_COL_ALIGNMENT:
119       return alignment_renderer;
120
121     case DICT_TVM_COL_MEASURE:
122       return measure_renderer;
123       
124     case DICT_TVM_COL_ROLE:
125       return column_width_renderer;
126     }
127   
128   return NULL;
129 }
130
131
132 static void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
133 static void psppire_data_editor_init                (PsppireDataEditor      *de);
134
135 static void disconnect_data_sheets (PsppireDataEditor *);
136 static void refresh_entry (PsppireDataEditor *);
137 static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
138
139 GType
140 psppire_data_editor_get_type (void)
141 {
142   static GType de_type = 0;
143
144   if (!de_type)
145     {
146       static const GTypeInfo de_info =
147       {
148         sizeof (PsppireDataEditorClass),
149         NULL, /* base_init */
150         NULL, /* base_finalize */
151         (GClassInitFunc) psppire_data_editor_class_init,
152         NULL, /* class_finalize */
153         NULL, /* class_data */
154         sizeof (PsppireDataEditor),
155         0,
156         (GInstanceInitFunc) psppire_data_editor_init,
157       };
158
159       de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
160                                         &de_info, 0);
161     }
162
163   return de_type;
164 }
165
166 static GObjectClass * parent_class = NULL;
167
168 static void
169 psppire_data_editor_dispose (GObject *obj)
170 {
171   PsppireDataEditor *de = (PsppireDataEditor *) obj;
172
173   disconnect_data_sheets (de);
174
175   if (de->data_store)
176     {
177       g_object_unref (de->data_store);
178       de->data_store = NULL;
179     }
180
181   if (de->dict)
182     {
183       g_object_unref (de->dict);
184       de->dict = NULL;
185     }
186
187   if (de->font != NULL)
188     {
189       pango_font_description_free (de->font);
190       de->font = NULL;
191     }
192
193   if (de->ui_manager)
194     {
195       g_object_unref (de->ui_manager);
196       de->ui_manager = NULL;
197     }
198
199   /* Chain up to the parent class */
200   G_OBJECT_CLASS (parent_class)->dispose (obj);
201 }
202
203 enum
204   {
205     PROP_0,
206     PROP_DATA_STORE,
207     PROP_DICTIONARY,
208     PROP_VALUE_LABELS,
209     PROP_SPLIT_WINDOW,
210     PROP_UI_MANAGER
211   };
212
213 static void
214 psppire_data_editor_refresh_model (PsppireDataEditor *de)
215 {
216 }
217
218 static void
219 psppire_data_editor_set_property (GObject         *object,
220                                   guint            prop_id,
221                                   const GValue    *value,
222                                   GParamSpec      *pspec)
223 {
224   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
225
226   switch (prop_id)
227     {
228     case PROP_SPLIT_WINDOW:
229       psppire_data_editor_split_window (de, g_value_get_boolean (value));
230       break;
231     case PROP_DATA_STORE:
232       if ( de->data_store)
233         {
234           g_signal_handlers_disconnect_by_func (de->data_store,
235                                                 G_CALLBACK (refresh_entry),
236                                                 de);
237           g_object_unref (de->data_store);
238         }
239
240       de->data_store = g_value_get_pointer (value);
241       g_object_ref (de->data_store);
242       g_print ("NEW STORE\n");
243
244       g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
245       psppire_data_editor_refresh_model (de);
246
247       g_signal_connect_swapped (de->data_store, "case-changed",
248                                 G_CALLBACK (refresh_entry), de);
249
250       break;
251     case PROP_DICTIONARY:
252       if (de->dict)
253         g_object_unref (de->dict);
254       de->dict = g_value_get_pointer (value);
255       g_object_ref (de->dict);
256
257       g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
258       g_object_set (de->var_sheet, "data-model", de->dict, NULL);
259
260       break;
261     case PROP_VALUE_LABELS:
262       break;
263     case PROP_UI_MANAGER:
264     default:
265       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
266       break;
267     };
268 }
269
270 static void
271 psppire_data_editor_get_property (GObject         *object,
272                                   guint            prop_id,
273                                   GValue          *value,
274                                   GParamSpec      *pspec)
275 {
276   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
277
278   switch (prop_id)
279     {
280     case PROP_SPLIT_WINDOW:
281       g_value_set_boolean (value, de->split);
282       break;
283     case PROP_DATA_STORE:
284       g_value_set_pointer (value, de->data_store);
285       break;
286     case PROP_DICTIONARY:
287       g_value_set_pointer (value, de->dict);
288       break;
289     case PROP_VALUE_LABELS:
290       break;
291     case PROP_UI_MANAGER:
292       g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
293       break;
294     default:
295       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
296       break;
297     }
298 }
299
300 static void
301 psppire_data_editor_switch_page (GtkNotebook     *notebook,
302                                  GtkWidget *w,
303                                  guint            page_num)
304 {
305   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
306   psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
307 }
308
309 static void
310 psppire_data_editor_set_focus_child (GtkContainer *container,
311                                      GtkWidget    *widget)
312 {
313   GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
314   psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
315 }
316
317 static void
318 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
319 {
320   GParamSpec *data_store_spec ;
321   GParamSpec *dict_spec ;
322   GParamSpec *value_labels_spec;
323   GParamSpec *split_window_spec;
324   GParamSpec *ui_manager_spec;
325   GObjectClass *object_class = G_OBJECT_CLASS (klass);
326   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
327   GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
328
329   parent_class = g_type_class_peek_parent (klass);
330
331   object_class->dispose = psppire_data_editor_dispose;
332   object_class->set_property = psppire_data_editor_set_property;
333   object_class->get_property = psppire_data_editor_get_property;
334
335   container_class->set_focus_child = psppire_data_editor_set_focus_child;
336
337   notebook_class->switch_page = psppire_data_editor_switch_page;
338
339   data_store_spec =
340     g_param_spec_pointer ("data-store",
341                           "Data Store",
342                           "A pointer to the data store associated with this editor",
343                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
344
345   g_object_class_install_property (object_class,
346                                    PROP_DATA_STORE,
347                                    data_store_spec);
348
349   dict_spec =
350     g_param_spec_pointer ("dictionary",
351                           "Dictionary",
352                           "A pointer to the dictionary associated with this editor",
353                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
354
355   g_object_class_install_property (object_class,
356                                    PROP_DICTIONARY,
357                                    dict_spec);
358
359   value_labels_spec =
360     g_param_spec_boolean ("value-labels",
361                          "Value Labels",
362                          "Whether or not the data sheet should display labels instead of values",
363                           FALSE,
364                          G_PARAM_WRITABLE | G_PARAM_READABLE);
365
366   g_object_class_install_property (object_class,
367                                    PROP_VALUE_LABELS,
368                                    value_labels_spec);
369
370
371   split_window_spec =
372     g_param_spec_boolean ("split",
373                           "Split Window",
374                           "True iff the data sheet is split",
375                           FALSE,
376                           G_PARAM_READABLE | G_PARAM_WRITABLE);
377
378   g_object_class_install_property (object_class,
379                                    PROP_SPLIT_WINDOW,
380                                    split_window_spec);
381
382   ui_manager_spec =
383     g_param_spec_object ("ui-manager",
384                          "UI Manager",
385                          "UI manager for the active notebook tab.  The client should merge this UI manager with the active UI manager to obtain menu items and tool bar items specific to the active notebook tab.",
386                          GTK_TYPE_UI_MANAGER,
387                          G_PARAM_READABLE);
388   g_object_class_install_property (object_class,
389                                    PROP_UI_MANAGER,
390                                    ui_manager_spec);
391 }
392
393
394 static void
395 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
396                                  PsppireDataEditor *de)
397 {
398   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
399                                  PSPPIRE_DATA_EDITOR_DATA_VIEW);
400
401   jmd_sheet_scroll_to (de->data_sheet, dict_index, -1);
402 }
403
404 static void
405 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
406                                  PsppireDataEditor *de)
407 {
408   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
409                                  PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
410
411   jmd_sheet_scroll_to (de->var_sheet, -1, dict_index);
412 }
413
414
415 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
416    active cell or cells. */
417 static void
418 refresh_entry (PsppireDataEditor *de)
419 {
420   g_print ("%s\n", __FUNCTION__);
421 }
422
423 static void
424 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
425 {
426 }
427
428
429 static void
430 disconnect_data_sheets (PsppireDataEditor *de)
431 {
432 }
433
434 /* Called when the active cell or the selection in the data sheet changes */
435 static void
436 on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
437 {
438   gchar *ref_cell_text = NULL;
439
440   gint n_cases = abs (sel->end_y - sel->start_y) + 1;
441   gint n_vars = abs (sel->end_x - sel->start_x) + 1;
442
443   if (n_cases == 1 && n_vars == 1)
444     {
445       /* A single cell is selected */
446       const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
447       
448       ref_cell_text = g_strdup_printf (_("%d : %s"),
449                                        sel->start_y + 1, var_get_name (var));
450     }
451   else
452     {
453       struct string s;
454
455       /* The glib string library does not understand the ' printf modifier
456          on all platforms, but the "struct string" library does (because
457          Gnulib fixes that problem), so use the latter.  */
458       ds_init_empty (&s);
459       ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
460                      n_cases);
461       ds_put_byte (&s, ' ');
462       ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
463       ds_put_byte (&s, ' ');
464       ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
465                                    n_vars),
466                      n_vars);
467       ref_cell_text = ds_steal_cstr (&s);
468     }
469   
470   gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
471                        ref_cell_text ? ref_cell_text : "");
472   
473   g_free (ref_cell_text);
474 }
475
476
477 static void set_font_recursively (GtkWidget *w, gpointer data);
478
479 static void
480 psppire_data_editor_init (PsppireDataEditor *de)
481 {
482   GtkWidget *hbox;
483   gchar *fontname = NULL;
484
485   de->font = NULL;
486   de->ui_manager = NULL;
487   de->old_vbox_widget = NULL;
488
489   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
490
491   de->cell_ref_label = gtk_label_new ("");
492   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
493   gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
494
495   de->datum_entry = psppire_value_entry_new ();
496   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
497                     "activate", G_CALLBACK (on_datum_entry_activate), de);
498
499   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
500   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
501   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
502
503   de->split = FALSE;
504   de->data_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
505   GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
506   gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
507   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
508   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
509   gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
510
511
512   g_signal_connect_swapped (de->data_sheet, "selection-changed",
513                     G_CALLBACK (on_data_selection_change), de);
514   
515   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
516                             gtk_label_new_with_mnemonic (_("Data View")));
517
518   gtk_widget_show_all (de->vbox);
519
520   de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
521
522   PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
523   
524   g_object_set (de->var_sheet,
525                 "hmodel", vsh,
526                 "select-renderer-func", select_renderer_func,
527                 NULL);
528
529   
530   GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
531   gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
532   
533   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
534                             gtk_label_new_with_mnemonic (_("Variable View")));
535
536   gtk_widget_show_all (de->var_sheet);
537   
538   g_signal_connect (de->var_sheet, "row-header-double-clicked",
539                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
540
541   g_signal_connect (de->data_sheet, "column-header-double-clicked",
542                     G_CALLBACK (on_data_sheet_var_double_clicked), de);
543
544   g_object_set (de, "can-focus", FALSE, NULL);
545
546   if (psppire_conf_get_string (psppire_conf_new (),
547                            "Data Editor", "font",
548                                 &fontname) )
549     {
550       de->font = pango_font_description_from_string (fontname);
551       g_free (fontname);
552       set_font_recursively (GTK_WIDGET (de), de->font);
553     }
554
555   psppire_data_editor_update_ui_manager (de);
556 }
557
558 GtkWidget*
559 psppire_data_editor_new (PsppireDict *dict,
560                          PsppireDataStore *data_store)
561 {
562   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
563                         "dictionary",  dict,
564                         "data-store",  data_store,
565                         NULL);
566 }
567 \f
568 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
569    sheet(s) and variable sheet. */
570 void
571 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
572 {
573   GtkTreeViewGridLines grid;
574
575   grid = (grid_visible
576           ? GTK_TREE_VIEW_GRID_LINES_BOTH
577           : GTK_TREE_VIEW_GRID_LINES_NONE);
578
579   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
580 }
581
582
583 static void
584 set_font_recursively (GtkWidget *w, gpointer data)
585 {
586   PangoFontDescription *font_desc = data;
587
588   gtk_widget_override_font (w, font_desc);
589
590   if ( GTK_IS_CONTAINER (w))
591     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
592 }
593
594 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
595 void
596 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
597 {
598   gchar *font_name;
599   set_font_recursively (GTK_WIDGET (de), font_desc);
600
601   if (de->font)
602     pango_font_description_free (de->font);
603   de->font = pango_font_description_copy (font_desc);
604   font_name = pango_font_description_to_string (de->font);
605
606   psppire_conf_set_string (psppire_conf_new (),
607                            "Data Editor", "font",
608                            font_name);
609
610 }
611
612 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
613    If SPLIT is FALSE, un-splits it into a single pane. */
614 void
615 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
616 {
617   if (split == de->split)
618     return;
619
620   disconnect_data_sheets (de);
621
622   psppire_data_editor_refresh_model (de);
623
624   gtk_widget_show_all (de->vbox);
625
626   if (de->font)
627     set_font_recursively (GTK_WIDGET (de), de->font);
628
629   de->split = split;
630   g_object_notify (G_OBJECT (de), "split");
631   psppire_data_editor_update_ui_manager (de);
632 }
633
634 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
635    visible and selected in the active view in DE. */
636 void
637 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
638 {
639 }
640
641
642 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
643    manager to display menu items and toolbar items specific to DE's current
644    page and data sheet.
645
646    DE's toplevel widget can watch for changes by connecting to DE's
647    notify::ui-manager signal. */
648 GtkUIManager *
649 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
650 {
651   psppire_data_editor_update_ui_manager (de);
652   return de->ui_manager;
653 }
654
655 static void
656 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
657 {
658   GtkUIManager *ui_manager;
659
660   ui_manager = NULL;
661
662   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
663     {
664     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
665       break;
666
667     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
668       break;
669
670     default:
671       /* This happens transiently in psppire_data_editor_init(). */
672       break;
673     }
674
675   if (ui_manager != de->ui_manager)
676     {
677       if (de->ui_manager)
678         g_object_unref (de->ui_manager);
679       if (ui_manager)
680         g_object_ref (ui_manager);
681       de->ui_manager = ui_manager;
682
683       g_object_notify (G_OBJECT (de), "ui-manager");
684     }
685 }
686
687