7838e5f810d206bc7cb5fabe5ba41dd13f013f4d
[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 void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
43 static void psppire_data_editor_init                (PsppireDataEditor      *de);
44
45 static void disconnect_data_sheets (PsppireDataEditor *);
46 static void refresh_entry (PsppireDataEditor *);
47 static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
48
49 GType
50 psppire_data_editor_get_type (void)
51 {
52   static GType de_type = 0;
53
54   if (!de_type)
55     {
56       static const GTypeInfo de_info =
57       {
58         sizeof (PsppireDataEditorClass),
59         NULL, /* base_init */
60         NULL, /* base_finalize */
61         (GClassInitFunc) psppire_data_editor_class_init,
62         NULL, /* class_finalize */
63         NULL, /* class_data */
64         sizeof (PsppireDataEditor),
65         0,
66         (GInstanceInitFunc) psppire_data_editor_init,
67       };
68
69       de_type = g_type_register_static (GTK_TYPE_NOTEBOOK, "PsppireDataEditor",
70                                         &de_info, 0);
71     }
72
73   return de_type;
74 }
75
76 static GObjectClass * parent_class = NULL;
77
78 static void
79 psppire_data_editor_dispose (GObject *obj)
80 {
81   PsppireDataEditor *de = (PsppireDataEditor *) obj;
82
83   disconnect_data_sheets (de);
84
85   if (de->data_store)
86     {
87       g_object_unref (de->data_store);
88       de->data_store = NULL;
89     }
90
91   if (de->dict)
92     {
93       g_object_unref (de->dict);
94       de->dict = NULL;
95     }
96
97   if (de->font != NULL)
98     {
99       pango_font_description_free (de->font);
100       de->font = NULL;
101     }
102
103   if (de->ui_manager)
104     {
105       g_object_unref (de->ui_manager);
106       de->ui_manager = NULL;
107     }
108
109   /* Chain up to the parent class */
110   G_OBJECT_CLASS (parent_class)->dispose (obj);
111 }
112
113 enum
114   {
115     PROP_0,
116     PROP_DATA_STORE,
117     PROP_DICTIONARY,
118     PROP_VALUE_LABELS,
119     PROP_SPLIT_WINDOW,
120     PROP_UI_MANAGER
121   };
122
123 static void
124 psppire_data_editor_refresh_model (PsppireDataEditor *de)
125 {
126 }
127
128 static void
129 psppire_data_editor_set_property (GObject         *object,
130                                   guint            prop_id,
131                                   const GValue    *value,
132                                   GParamSpec      *pspec)
133 {
134   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
135
136   switch (prop_id)
137     {
138     case PROP_SPLIT_WINDOW:
139       psppire_data_editor_split_window (de, g_value_get_boolean (value));
140       break;
141     case PROP_DATA_STORE:
142       if ( de->data_store)
143         {
144           g_signal_handlers_disconnect_by_func (de->data_store,
145                                                 G_CALLBACK (refresh_entry),
146                                                 de);
147           g_object_unref (de->data_store);
148         }
149
150       de->data_store = g_value_get_pointer (value);
151       g_object_ref (de->data_store);
152       g_print ("NEW STORE\n");
153
154       g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
155       psppire_data_editor_refresh_model (de);
156
157       g_signal_connect_swapped (de->data_store, "case-changed",
158                                 G_CALLBACK (refresh_entry), de);
159
160       break;
161     case PROP_DICTIONARY:
162       if (de->dict)
163         g_object_unref (de->dict);
164       de->dict = g_value_get_pointer (value);
165       g_object_ref (de->dict);
166
167       g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
168       g_object_set (de->var_sheet, "data-model", de->dict, NULL);
169
170       break;
171     case PROP_VALUE_LABELS:
172       break;
173     case PROP_UI_MANAGER:
174     default:
175       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
176       break;
177     };
178 }
179
180 static void
181 psppire_data_editor_get_property (GObject         *object,
182                                   guint            prop_id,
183                                   GValue          *value,
184                                   GParamSpec      *pspec)
185 {
186   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
187
188   switch (prop_id)
189     {
190     case PROP_SPLIT_WINDOW:
191       g_value_set_boolean (value, de->split);
192       break;
193     case PROP_DATA_STORE:
194       g_value_set_pointer (value, de->data_store);
195       break;
196     case PROP_DICTIONARY:
197       g_value_set_pointer (value, de->dict);
198       break;
199     case PROP_VALUE_LABELS:
200       break;
201     case PROP_UI_MANAGER:
202       g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
203       break;
204     default:
205       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
206       break;
207     }
208 }
209
210 static void
211 psppire_data_editor_switch_page (GtkNotebook     *notebook,
212                                  GtkWidget *w,
213                                  guint            page_num)
214 {
215   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
216   psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
217 }
218
219 static void
220 psppire_data_editor_set_focus_child (GtkContainer *container,
221                                      GtkWidget    *widget)
222 {
223   GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
224   psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
225 }
226
227 static void
228 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
229 {
230   GParamSpec *data_store_spec ;
231   GParamSpec *dict_spec ;
232   GParamSpec *value_labels_spec;
233   GParamSpec *split_window_spec;
234   GParamSpec *ui_manager_spec;
235   GObjectClass *object_class = G_OBJECT_CLASS (klass);
236   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
237   GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
238
239   parent_class = g_type_class_peek_parent (klass);
240
241   object_class->dispose = psppire_data_editor_dispose;
242   object_class->set_property = psppire_data_editor_set_property;
243   object_class->get_property = psppire_data_editor_get_property;
244
245   container_class->set_focus_child = psppire_data_editor_set_focus_child;
246
247   notebook_class->switch_page = psppire_data_editor_switch_page;
248
249   data_store_spec =
250     g_param_spec_pointer ("data-store",
251                           "Data Store",
252                           "A pointer to the data store associated with this editor",
253                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
254
255   g_object_class_install_property (object_class,
256                                    PROP_DATA_STORE,
257                                    data_store_spec);
258
259   dict_spec =
260     g_param_spec_pointer ("dictionary",
261                           "Dictionary",
262                           "A pointer to the dictionary associated with this editor",
263                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
264
265   g_object_class_install_property (object_class,
266                                    PROP_DICTIONARY,
267                                    dict_spec);
268
269   value_labels_spec =
270     g_param_spec_boolean ("value-labels",
271                          "Value Labels",
272                          "Whether or not the data sheet should display labels instead of values",
273                           FALSE,
274                          G_PARAM_WRITABLE | G_PARAM_READABLE);
275
276   g_object_class_install_property (object_class,
277                                    PROP_VALUE_LABELS,
278                                    value_labels_spec);
279
280
281   split_window_spec =
282     g_param_spec_boolean ("split",
283                           "Split Window",
284                           "True iff the data sheet is split",
285                           FALSE,
286                           G_PARAM_READABLE | G_PARAM_WRITABLE);
287
288   g_object_class_install_property (object_class,
289                                    PROP_SPLIT_WINDOW,
290                                    split_window_spec);
291
292   ui_manager_spec =
293     g_param_spec_object ("ui-manager",
294                          "UI Manager",
295                          "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.",
296                          GTK_TYPE_UI_MANAGER,
297                          G_PARAM_READABLE);
298   g_object_class_install_property (object_class,
299                                    PROP_UI_MANAGER,
300                                    ui_manager_spec);
301 }
302
303
304 static void
305 on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
306                                  PsppireDataEditor *de)
307 {
308   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
309                                  PSPPIRE_DATA_EDITOR_DATA_VIEW);
310
311   jmd_sheet_scroll_to (de->data_sheet, dict_index, -1);
312 }
313
314 static void
315 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
316                                  PsppireDataEditor *de)
317 {
318   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
319                                  PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
320
321   jmd_sheet_scroll_to (de->var_sheet, -1, dict_index);
322 }
323
324
325 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
326    active cell or cells. */
327 static void
328 refresh_entry (PsppireDataEditor *de)
329 {
330 }
331
332 static void
333 on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
334 {
335 }
336
337
338 static void
339 disconnect_data_sheets (PsppireDataEditor *de)
340 {
341 }
342
343
344 static void set_font_recursively (GtkWidget *w, gpointer data);
345
346 static void
347 psppire_data_editor_init (PsppireDataEditor *de)
348 {
349   GtkWidget *hbox;
350   gchar *fontname = NULL;
351
352   de->font = NULL;
353   de->ui_manager = NULL;
354   de->old_vbox_widget = NULL;
355
356   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
357
358   de->cell_ref_label = gtk_label_new ("");
359   gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
360   gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
361
362   de->datum_entry = psppire_value_entry_new ();
363   g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
364                     "activate", G_CALLBACK (on_datum_entry_activate), de);
365
366   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
367   gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
368   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
369
370   de->split = FALSE;
371   de->data_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
372   GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
373   gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
374   de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
375   gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
376   gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
377
378   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
379                             gtk_label_new_with_mnemonic (_("Data View")));
380
381   gtk_widget_show_all (de->vbox);
382
383   de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
384
385   PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
386   
387   g_object_set (de->var_sheet, "hmodel", vsh, NULL);
388
389   
390   GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
391   gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
392   
393   gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
394                             gtk_label_new_with_mnemonic (_("Variable View")));
395
396   gtk_widget_show_all (de->var_sheet);
397   
398   g_signal_connect (de->var_sheet, "row-header-double-clicked",
399                     G_CALLBACK (on_var_sheet_var_double_clicked), de);
400
401   g_signal_connect (de->data_sheet, "column-header-double-clicked",
402                     G_CALLBACK (on_data_sheet_var_double_clicked), de);
403
404   g_object_set (de, "can-focus", FALSE, NULL);
405
406   if (psppire_conf_get_string (psppire_conf_new (),
407                            "Data Editor", "font",
408                                 &fontname) )
409     {
410       de->font = pango_font_description_from_string (fontname);
411       g_free (fontname);
412       set_font_recursively (GTK_WIDGET (de), de->font);
413     }
414
415   psppire_data_editor_update_ui_manager (de);
416 }
417
418 GtkWidget*
419 psppire_data_editor_new (PsppireDict *dict,
420                          PsppireDataStore *data_store)
421 {
422   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
423                         "dictionary",  dict,
424                         "data-store",  data_store,
425                         NULL);
426 }
427 \f
428 /* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
429    sheet(s) and variable sheet. */
430 void
431 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
432 {
433   GtkTreeViewGridLines grid;
434
435   grid = (grid_visible
436           ? GTK_TREE_VIEW_GRID_LINES_BOTH
437           : GTK_TREE_VIEW_GRID_LINES_NONE);
438
439   pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
440 }
441
442
443 static void
444 set_font_recursively (GtkWidget *w, gpointer data)
445 {
446   PangoFontDescription *font_desc = data;
447
448   gtk_widget_override_font (w, font_desc);
449
450   if ( GTK_IS_CONTAINER (w))
451     gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
452 }
453
454 /* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
455 void
456 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
457 {
458   gchar *font_name;
459   set_font_recursively (GTK_WIDGET (de), font_desc);
460
461   if (de->font)
462     pango_font_description_free (de->font);
463   de->font = pango_font_description_copy (font_desc);
464   font_name = pango_font_description_to_string (de->font);
465
466   psppire_conf_set_string (psppire_conf_new (),
467                            "Data Editor", "font",
468                            font_name);
469
470 }
471
472 /* If SPLIT is TRUE, splits DE's data sheet into four panes.
473    If SPLIT is FALSE, un-splits it into a single pane. */
474 void
475 psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
476 {
477   if (split == de->split)
478     return;
479
480   disconnect_data_sheets (de);
481
482   psppire_data_editor_refresh_model (de);
483
484   gtk_widget_show_all (de->vbox);
485
486   if (de->font)
487     set_font_recursively (GTK_WIDGET (de), de->font);
488
489   de->split = split;
490   g_object_notify (G_OBJECT (de), "split");
491   psppire_data_editor_update_ui_manager (de);
492 }
493
494 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
495    visible and selected in the active view in DE. */
496 void
497 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
498 {
499 }
500
501
502 /* Returns the UI manager that should be merged into DE's toplevel widget's UI
503    manager to display menu items and toolbar items specific to DE's current
504    page and data sheet.
505
506    DE's toplevel widget can watch for changes by connecting to DE's
507    notify::ui-manager signal. */
508 GtkUIManager *
509 psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
510 {
511   psppire_data_editor_update_ui_manager (de);
512   return de->ui_manager;
513 }
514
515 static void
516 psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
517 {
518   GtkUIManager *ui_manager;
519
520   ui_manager = NULL;
521
522   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
523     {
524     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
525       break;
526
527     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
528       break;
529
530     default:
531       /* This happens transiently in psppire_data_editor_init(). */
532       break;
533     }
534
535   if (ui_manager != de->ui_manager)
536     {
537       if (de->ui_manager)
538         g_object_unref (de->ui_manager);
539       if (ui_manager)
540         g_object_ref (ui_manager);
541       de->ui_manager = ui_manager;
542
543       g_object_notify (G_OBJECT (de), "ui-manager");
544     }
545 }
546
547