03d43dd9c2549ac1d1d9b4015103e1fbad1a4f20
[pspp] / src / ui / gui / psppire-var-sheet.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 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-var-sheet.h"
20
21 #include "data/format.h"
22 #include "data/value-labels.h"
23 #include "libpspp/range-set.h"
24 #include "ui/gui/builder-wrapper.h"
25 #include "ui/gui/helper.h"
26 #include "ui/gui/missing-val-dialog.h"
27 #include "ui/gui/pspp-sheet-selection.h"
28 #include "ui/gui/psppire-cell-renderer-button.h"
29 #include "ui/gui/psppire-data-editor.h"
30 #include "ui/gui/psppire-data-window.h"
31 #include "ui/gui/psppire-dialog-action-var-info.h"
32 #include "ui/gui/psppire-empty-list-store.h"
33 #include "ui/gui/psppire-marshal.h"
34 #include "ui/gui/val-labs-dialog.h"
35 #include "ui/gui/var-display.h"
36 #include "ui/gui/var-type-dialog.h"
37
38 #include "gl/intprops.h"
39
40 #include <gettext.h>
41 #define _(msgid) gettext (msgid)
42 #define N_(msgid) msgid
43
44 enum vs_column
45   {
46     VS_NAME,
47     VS_TYPE,
48     VS_WIDTH,
49     VS_DECIMALS,
50     VS_LABEL,
51     VS_VALUES,
52     VS_MISSING,
53     VS_COLUMNS,
54     VS_ALIGN,
55     VS_MEASURE
56   };
57
58 G_DEFINE_TYPE (PsppireVarSheet, psppire_var_sheet, PSPP_TYPE_SHEET_VIEW);
59
60 static void
61 set_spin_cell (GtkCellRenderer *cell, int value, int min, int max, int step)
62 {
63   char text[INT_BUFSIZE_BOUND (int)];
64   GtkAdjustment *adjust;
65
66   if (max > min)
67     adjust = GTK_ADJUSTMENT (gtk_adjustment_new (value, min, max,
68                                                  step, step, 0.0));
69   else
70     adjust = NULL;
71
72   sprintf (text, "%d", value);
73   g_object_set (cell,
74                 "text", text,
75                 "adjustment", adjust,
76                 "editable", TRUE,
77                 NULL);
78 }
79
80 static void
81 error_dialog (GtkWindow *w, gchar *primary_text, gchar *secondary_text)
82 {
83   GtkWidget *dialog =
84     gtk_message_dialog_new (w,
85                             GTK_DIALOG_DESTROY_WITH_PARENT,
86                             GTK_MESSAGE_ERROR,
87                             GTK_BUTTONS_CLOSE, "%s", primary_text);
88
89   g_object_set (dialog, "icon-name", "psppicon", NULL);
90
91   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
92                                             "%s", secondary_text);
93
94   gtk_dialog_run (GTK_DIALOG (dialog));
95
96   gtk_widget_destroy (dialog);
97 }
98
99 static void
100 on_name_column_editing_started (GtkCellRenderer *cell,
101                                 GtkCellEditable *editable,
102                                 const gchar     *path,
103                                 gpointer         user_data)
104 {
105   PsppireVarSheet *var_sheet = g_object_get_data (G_OBJECT (cell), "var-sheet");
106   PsppireDict *dict = psppire_var_sheet_get_dictionary (var_sheet);
107
108   g_return_if_fail (var_sheet);
109           g_return_if_fail (dict);
110
111   if (GTK_IS_ENTRY (editable))
112     {
113       GtkEntry *entry = GTK_ENTRY (editable);
114       if (gtk_entry_get_text (entry)[0] == '\0')
115         {
116           gchar name[64];
117           if (psppire_dict_generate_name (dict, name, sizeof name))
118             gtk_entry_set_text (entry, name);
119         }
120     }
121 }
122
123 static void
124 scroll_to_bottom (GtkWidget      *widget,
125                   GtkRequisition *requisition,
126                   gpointer        unused UNUSED)
127 {
128   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
129   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
130   GtkAdjustment *vadjust;
131
132   vadjust = pspp_sheet_view_get_vadjustment (sheet_view);
133   gtk_adjustment_set_value (vadjust, gtk_adjustment_get_upper (vadjust));
134
135   if (var_sheet->scroll_to_bottom_signal)
136     {
137       g_signal_handler_disconnect (var_sheet,
138                                    var_sheet->scroll_to_bottom_signal);
139       var_sheet->scroll_to_bottom_signal = 0;
140     }
141 }
142
143 static void
144 on_var_column_edited (GtkCellRendererText *cell,
145                       gchar               *path_string,
146                       gchar               *new_text,
147                       gpointer             user_data)
148 {
149   PsppireVarSheet *var_sheet = user_data;
150   GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (var_sheet)));
151   struct dictionary *dict = var_sheet->dict->dict;
152   enum vs_column column_id;
153   struct variable *var;
154   int width, decimals;
155   GtkTreePath *path;
156   gint row;
157
158   path = gtk_tree_path_new_from_string (path_string);
159   row = gtk_tree_path_get_indices (path)[0];
160   gtk_tree_path_free (path);
161
162   column_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell),
163                                                   "column-id"));
164
165   var = psppire_dict_get_variable (var_sheet->dict, row);
166   if (var == NULL)
167     {
168       g_return_if_fail (column_id == VS_NAME);
169
170       if (!dict_id_is_valid (dict, new_text, false))
171         error_dialog (window,
172                       g_strdup (_("Cannot create variable.")),
173                       g_strdup_printf (_("\"%s\" is not a valid variable "
174                                          "name."), new_text));
175       else if (dict_lookup_var (dict, new_text) != NULL)
176         error_dialog (window,
177                       g_strdup (_("Cannot create variable.")),
178                       g_strdup_printf (_("This dictionary already contains "
179                                          "a variable named \"%s\"."),
180                                          new_text));
181       else
182         {
183           dict_create_var (var_sheet->dict->dict, new_text, 0);
184           if (!var_sheet->scroll_to_bottom_signal)
185             {
186               gtk_widget_queue_resize (GTK_WIDGET (var_sheet));
187               var_sheet->scroll_to_bottom_signal =
188                 g_signal_connect (var_sheet, "size-request",
189                                   G_CALLBACK (scroll_to_bottom), NULL);
190             }
191         }
192
193       return;
194     }
195
196   switch (column_id)
197     {
198     case VS_NAME:
199       if (!dict_id_is_valid (dict, new_text, false))
200         error_dialog (window,
201                       g_strdup (_("Cannot rename variable.")),
202                       g_strdup_printf (_("\"%s\" is not a valid variable "
203                                          "name."), new_text));
204       else if (dict_lookup_var (dict, new_text) != NULL
205                && dict_lookup_var (dict, new_text) != var)
206         error_dialog (window,
207                       g_strdup (_("Cannot rename variable.")),
208                       g_strdup_printf (_("This dictionary already contains "
209                                          "a variable named \"%s\"."),
210                                          new_text));
211       else
212         dict_rename_var (dict, var, new_text);
213       break;
214
215     case VS_TYPE:
216       /* Not reachable. */
217       break;
218
219     case VS_WIDTH:
220       width = atoi (new_text);
221       if (width > 0)
222         {
223           struct fmt_spec format;
224
225           format = *var_get_print_format (var);
226           fmt_change_width (&format, width, var_sheet->format_use);
227           var_set_print_format (var, &format);
228           var_set_width (var, fmt_var_width (&format));
229         }
230       break;
231
232     case VS_DECIMALS:
233       decimals = atoi (new_text);
234       if (decimals >= 0)
235         {
236           struct fmt_spec format;
237
238           format = *var_get_print_format (var);
239           fmt_change_decimals (&format, decimals, var_sheet->format_use);
240           var_set_print_format (var, &format);
241         }
242       break;
243
244     case VS_LABEL:
245       var_set_label (var, new_text, false);
246       break;
247
248     case VS_VALUES:
249     case VS_MISSING:
250       break;
251
252     case VS_COLUMNS:
253       width = atoi (new_text);
254       if (width > 0 && width < 2 * MAX_STRING)
255         var_set_display_width (var, width);
256       break;
257
258     case VS_ALIGN:
259       if (!strcmp (new_text, alignment_to_string (ALIGN_LEFT)))
260         var_set_alignment (var, ALIGN_LEFT);
261       else if (!strcmp (new_text, alignment_to_string (ALIGN_CENTRE)))
262         var_set_alignment (var, ALIGN_CENTRE);
263       else if (!strcmp (new_text, alignment_to_string (ALIGN_RIGHT)))
264         var_set_alignment (var, ALIGN_RIGHT);
265       break;
266
267     case VS_MEASURE:
268       if (!strcmp (new_text, measure_to_string (MEASURE_NOMINAL)))
269         var_set_measure (var, MEASURE_NOMINAL);
270       else if (!strcmp (new_text, measure_to_string (MEASURE_ORDINAL)))
271         var_set_measure (var, MEASURE_ORDINAL);
272       else if (!strcmp (new_text, measure_to_string (MEASURE_SCALE)))
273         var_set_measure (var, MEASURE_SCALE);
274       break;
275     }
276 }
277
278 static void
279 render_popup_cell (PsppSheetViewColumn *tree_column,
280                    GtkCellRenderer *cell,
281                    GtkTreeModel *model,
282                    GtkTreeIter *iter,
283                    void *user_data)
284 {
285   PsppireVarSheet *var_sheet = user_data;
286   gint row;
287
288   row = GPOINTER_TO_INT (iter->user_data);
289   g_object_set (cell,
290                 "editable", row < psppire_dict_get_var_cnt (var_sheet->dict),
291                 NULL);
292 }
293
294 static void
295 render_var_cell (PsppSheetViewColumn *tree_column,
296                  GtkCellRenderer *cell,
297                  GtkTreeModel *model,
298                  GtkTreeIter *iter,
299                  void *user_data)
300 {
301   PsppireVarSheet *var_sheet = user_data;
302   const struct fmt_spec *print;
303   enum vs_column column_id;
304   struct variable *var;
305   gint row;
306
307   column_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
308                                                   "column-number")) - 1;
309   row = GPOINTER_TO_INT (iter->user_data);
310
311   if (row >= psppire_dict_get_var_cnt (var_sheet->dict))
312     {
313       g_object_set (cell,
314                     "text", "",
315                     "editable", column_id == VS_NAME,
316                     NULL);
317       if (column_id == VS_WIDTH
318           || column_id == VS_DECIMALS
319           || column_id == VS_COLUMNS)
320         g_object_set (cell, "adjustment", NULL, NULL);
321       return;
322     }
323
324   var = psppire_dict_get_variable (var_sheet->dict, row);
325
326   print = var_get_print_format (var);
327   switch (column_id)
328     {
329     case VS_NAME:
330       g_object_set (cell,
331                     "text", var_get_name (var),
332                     "editable", TRUE,
333                     NULL);
334       break;
335
336     case VS_TYPE:
337       g_object_set (cell,
338                     "text", fmt_gui_name (print->type),
339                     "editable", FALSE,
340                     NULL);
341       break;
342
343     case VS_WIDTH:
344       {
345         int step = fmt_step_width (print->type);
346         if (var_is_numeric (var))
347           set_spin_cell (cell, print->w,
348                          fmt_min_width (print->type, var_sheet->format_use),
349                          fmt_max_width (print->type, var_sheet->format_use),
350                          step);
351         else
352           set_spin_cell (cell, print->w, 0, 0, step);
353       }
354       break;
355
356     case VS_DECIMALS:
357       if (fmt_takes_decimals (print->type))
358         {
359           int max_w = fmt_max_width (print->type, var_sheet->format_use);
360           int max_d = fmt_max_decimals (print->type, max_w,
361                                         var_sheet->format_use);
362           set_spin_cell (cell, print->d, 0, max_d, 1);
363         }
364       else
365         g_object_set (cell,
366                       "text", "",
367                       "editable", FALSE,
368                       "adjustment", NULL,
369                       NULL);
370       break;
371
372     case VS_LABEL:
373       g_object_set (cell,
374                     "text", var_has_label (var) ? var_get_label (var) : "",
375                     "editable", TRUE,
376                     NULL);
377       break;
378
379     case VS_VALUES:
380       g_object_set (cell, "editable", FALSE, NULL);
381       if ( ! var_has_value_labels (var))
382         g_object_set (cell, "text", _("None"), NULL);
383       else
384         {
385           const struct val_labs *vls = var_get_value_labels (var);
386           const struct val_lab **labels = val_labs_sorted (vls);
387           const struct val_lab *vl = labels[0];
388           gchar *vstr = value_to_text (vl->value, var);
389           char *text = xasprintf (_("{%s, %s}..."), vstr,
390                                   val_lab_get_escaped_label (vl));
391           free (vstr);
392
393           g_object_set (cell, "text", text, NULL);
394           free (text);
395           free (labels);
396         }
397       break;
398
399     case VS_MISSING:
400       {
401         char *text = missing_values_to_string (var_sheet->dict, var, NULL);
402         g_object_set (cell,
403                       "text", text,
404                       "editable", FALSE,
405                       NULL);
406         free (text);
407       }
408       break;
409
410     case VS_COLUMNS:
411       set_spin_cell (cell, var_get_display_width (var), 1, 2 * MAX_STRING, 1);
412       break;
413
414     case VS_ALIGN:
415       g_object_set (cell,
416                     "text", alignment_to_string (var_get_alignment (var)),
417                     "editable", TRUE,
418                     NULL);
419       break;
420
421     case VS_MEASURE:
422       g_object_set (cell,
423                     "text", measure_to_string (var_get_measure (var)),
424                     "editable", TRUE,
425                     NULL);
426       break;
427     }
428 }
429
430 static struct variable *
431 path_string_to_variable (PsppireVarSheet *var_sheet, gchar *path_string)
432 {
433   PsppireDict *dict;
434   GtkTreePath *path;
435   gint row;
436
437   path = gtk_tree_path_new_from_string (path_string);
438   row = gtk_tree_path_get_indices (path)[0];
439   gtk_tree_path_free (path);
440
441   dict = psppire_var_sheet_get_dictionary (var_sheet);
442   g_return_val_if_fail (dict != NULL, NULL);
443
444   return psppire_dict_get_variable (dict, row);
445 }
446
447 static void
448 on_type_click (PsppireCellRendererButton *cell,
449                gchar *path,
450                PsppireVarSheet *var_sheet)
451 {
452   var_sheet->var_type_dialog->pv = path_string_to_variable (var_sheet, path);
453   var_type_dialog_show (var_sheet->var_type_dialog);
454 }
455
456 static void
457 on_value_labels_click (PsppireCellRendererButton *cell,
458                        gchar *path,
459                        PsppireVarSheet *var_sheet)
460 {
461   struct variable *var = path_string_to_variable (var_sheet, path);
462   val_labs_dialog_set_target_variable (var_sheet->val_labs_dialog, var);
463   val_labs_dialog_show (var_sheet->val_labs_dialog);
464 }
465
466 static void
467 on_missing_values_click (PsppireCellRendererButton *cell,
468                          gchar *path,
469                          PsppireVarSheet *var_sheet)
470 {
471   var_sheet->missing_val_dialog->pv = path_string_to_variable (var_sheet,
472                                                                path);
473   missing_val_dialog_show (var_sheet->missing_val_dialog);
474 }
475
476 static gint
477 get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
478                   const char *string)
479 {
480   gint width;
481   g_object_set (G_OBJECT (renderer),
482                 PSPPIRE_IS_CELL_RENDERER_BUTTON (renderer) ? "label" : "text",
483                 string, (void *) NULL);
484   gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
485                               NULL, NULL, NULL, &width, NULL);
486   return width;
487 }
488
489 static gint
490 get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
491                      size_t char_cnt)
492 {
493   struct string s;
494   gint width;
495
496   ds_init_empty (&s);
497   ds_put_byte_multiple (&s, '0', char_cnt);
498   ds_put_byte (&s, ' ');
499   width = get_string_width (treeview, renderer, ds_cstr (&s));
500   ds_destroy (&s);
501
502   return width;
503 }
504
505 static PsppSheetViewColumn *
506 add_var_sheet_column (PsppireVarSheet *var_sheet, GtkCellRenderer *renderer,
507                       enum vs_column column_id,
508                       const char *title, int width)
509 {
510   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
511   int title_width, content_width;
512   PsppSheetViewColumn *column;
513
514   column = pspp_sheet_view_column_new_with_attributes (title, renderer, NULL);
515   g_object_set_data (G_OBJECT (column), "column-number",
516                      GINT_TO_POINTER (column_id) + 1);
517
518   pspp_sheet_view_column_set_cell_data_func (
519     column, renderer, render_var_cell, var_sheet, NULL);
520
521   title_width = get_string_width (sheet_view, renderer, title);
522   content_width = get_monospace_width (sheet_view, renderer, width);
523   g_object_set_data (G_OBJECT (column), "content-width",
524                      GINT_TO_POINTER (content_width));
525
526   pspp_sheet_view_column_set_fixed_width (column,
527                                           MAX (title_width, content_width));
528   pspp_sheet_view_column_set_resizable (column, TRUE);
529
530   pspp_sheet_view_append_column (sheet_view, column);
531
532   g_signal_connect (renderer, "edited",
533                     G_CALLBACK (on_var_column_edited),
534                     var_sheet);
535   g_object_set_data (G_OBJECT (renderer), "column-id",
536                      GINT_TO_POINTER (column_id));
537   g_object_set_data (G_OBJECT (renderer), "var-sheet", var_sheet);
538
539   return column;
540 }
541
542 static PsppSheetViewColumn *
543 add_text_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
544                  const char *title, int width)
545 {
546   return add_var_sheet_column (var_sheet, gtk_cell_renderer_text_new (),
547                                column_id, title, width);
548 }
549
550 static PsppSheetViewColumn *
551 add_spin_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
552                  const char *title, int width)
553 {
554   return add_var_sheet_column (var_sheet, gtk_cell_renderer_spin_new (),
555                                column_id, title, width);
556 }
557
558 static PsppSheetViewColumn *
559 add_combo_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
560                   const char *title, int width,
561                   ...)
562 {
563   GtkCellRenderer *cell;
564   GtkListStore *store;
565   const char *name;
566   va_list args;
567
568   store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
569   va_start (args, width);
570   while ((name = va_arg (args, const char *)) != NULL)
571     {
572       int value = va_arg (args, int);
573       gtk_list_store_insert_with_values (store, NULL, G_MAXINT,
574                                          0, value,
575                                          1, name,
576                                          -1);
577     }
578   va_end (args);
579
580   cell = gtk_cell_renderer_combo_new ();
581   g_object_set (cell,
582                 "has-entry", FALSE,
583                 "model", GTK_TREE_MODEL (store),
584                 "text-column", 1,
585                 NULL);
586
587   return add_var_sheet_column (var_sheet, cell, column_id, title, width);
588
589 }
590
591 static void
592 add_popup_menu (PsppireVarSheet *var_sheet,
593                 PsppSheetViewColumn *column,
594                 void (*on_click) (PsppireCellRendererButton *,
595                                   gchar *path,
596                                   PsppireVarSheet *var_sheet))
597 {
598   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
599   const char *button_label = "...";
600   GtkCellRenderer *button_renderer;
601   gint content_width;
602
603   button_renderer = psppire_cell_renderer_button_new ();
604   g_object_set (button_renderer,
605                 "label", button_label,
606                 "editable", TRUE,
607                 NULL);
608   g_signal_connect (button_renderer, "clicked", G_CALLBACK (on_click),
609                     var_sheet);
610   pspp_sheet_view_column_pack_start (column, button_renderer, FALSE);
611   pspp_sheet_view_column_set_cell_data_func (
612     column, button_renderer, render_popup_cell, var_sheet, NULL);
613
614   content_width = GPOINTER_TO_INT (g_object_get_data (
615                                      G_OBJECT (column), "content-width"));
616   content_width += get_string_width (sheet_view, button_renderer,
617                                      button_label);
618   if (content_width > pspp_sheet_view_column_get_fixed_width (column))
619     pspp_sheet_view_column_set_fixed_width (column, content_width);
620 }
621
622 static gboolean
623 get_tooltip_location (GtkWidget *widget, GtkTooltip *tooltip,
624                       gint wx, gint wy, size_t *row, size_t *column)
625 {
626   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
627   gint bx, by;
628   GtkTreePath *path;
629   GtkTreeIter iter;
630   PsppSheetViewColumn *tree_column;
631   GtkTreeModel *tree_model;
632   gpointer column_ptr;
633   bool ok;
634
635   /* Check that WIDGET is really visible on the screen before we
636      do anything else.  This is a bug fix for a sticky situation:
637      when text_data_import_assistant() returns, it frees the data
638      necessary to compose the tool tip message, but there may be
639      a tool tip under preparation at that point (even if there is
640      no visible tool tip) that will call back into us a little
641      bit later.  Perhaps the correct solution to this problem is
642      to make the data related to the tool tips part of a GObject
643      that only gets destroyed when all references are released,
644      but this solution appears to be effective too. */
645   if (!gtk_widget_get_mapped (widget))
646     return FALSE;
647
648   pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
649                                                      wx, wy, &bx, &by);
650   if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
651                                       &path, &tree_column, NULL, NULL))
652     return FALSE;
653
654   column_ptr = g_object_get_data (G_OBJECT (tree_column), "column-number");
655   if (column_ptr == NULL)
656     return FALSE;
657   *column = GPOINTER_TO_INT (column_ptr) - 1;
658
659   pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, tree_column,
660                                     NULL);
661
662   tree_model = pspp_sheet_view_get_model (tree_view);
663   ok = gtk_tree_model_get_iter (tree_model, &iter, path);
664   gtk_tree_path_free (path);
665   if (!ok)
666     return FALSE;
667
668   *row = GPOINTER_TO_INT (iter.user_data);
669   return TRUE;
670 }
671
672 static gboolean
673 on_query_var_tooltip (GtkWidget *widget, gint wx, gint wy,
674                       gboolean keyboard_mode UNUSED,
675                       GtkTooltip *tooltip, gpointer *user_data UNUSED)
676 {
677   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
678   PsppireDict *dict;
679   struct variable *var;
680   size_t row, column;
681
682   if (!get_tooltip_location (widget, tooltip, wx, wy, &row, &column))
683     return FALSE;
684
685   dict = psppire_var_sheet_get_dictionary (var_sheet);
686   g_return_val_if_fail (dict != NULL, FALSE);
687
688   if (row >= psppire_dict_get_var_cnt (dict))
689     {
690       gtk_tooltip_set_text (tooltip, _("Enter a variable name to add a "
691                                        "new variable."));
692       return TRUE;
693     }
694
695   var = psppire_dict_get_variable (dict, row);
696   g_return_val_if_fail (var != NULL, FALSE);
697
698   switch (column)
699     {
700     case VS_TYPE:
701       {
702         char text[FMT_STRING_LEN_MAX + 1];
703
704         fmt_to_string (var_get_print_format (var), text);
705         gtk_tooltip_set_text (tooltip, text);
706         return TRUE;
707       }
708
709     case VS_VALUES:
710       if (var_has_value_labels (var))
711         {
712           const struct val_labs *vls = var_get_value_labels (var);
713           const struct val_lab **labels = val_labs_sorted (vls);
714           struct string s;
715           size_t i;
716
717           ds_init_empty (&s);
718           for (i = 0; i < val_labs_count (vls); i++)
719             {
720               const struct val_lab *vl = labels[i];
721               gchar *vstr;
722
723               if (i >= 10 || ds_length (&s) > 500)
724                 {
725                   ds_put_cstr (&s, "...");
726                   break;
727                 }
728
729               vstr = value_to_text (vl->value, var);
730               ds_put_format (&s, _("{%s, %s}\n"), vstr,
731                              val_lab_get_escaped_label (vl));
732               free (vstr);
733
734             }
735           ds_chomp_byte (&s, '\n');
736
737           gtk_tooltip_set_text (tooltip, ds_cstr (&s));
738           ds_destroy (&s);
739           free (labels);
740
741           return TRUE;
742         }
743     }
744
745   return FALSE;
746 }
747
748 static void
749 do_popup_menu (GtkWidget *widget, guint button, guint32 time)
750 {
751   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
752   GtkWidget *menu;
753
754   menu = get_widget_assert (var_sheet->builder, "varsheet-variable-popup");
755   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
756 }
757
758 static void
759 on_popup_menu (GtkWidget *widget, gpointer user_data UNUSED)
760 {
761   do_popup_menu (widget, 0, gtk_get_current_event_time ());
762 }
763
764 static gboolean
765 on_button_pressed (GtkWidget *widget, GdkEventButton *event,
766                    gpointer user_data UNUSED)
767 {
768   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
769
770   if (event->type == GDK_BUTTON_PRESS && event->button == 3)
771     {
772       PsppSheetSelection *selection;
773
774       selection = pspp_sheet_view_get_selection (sheet_view);
775       if (pspp_sheet_selection_count_selected_rows (selection) <= 1)
776         {
777           GtkTreePath *path;
778
779           if (pspp_sheet_view_get_path_at_pos (sheet_view, event->x, event->y,
780                                                &path, NULL, NULL, NULL))
781             {
782               pspp_sheet_selection_unselect_all (selection);
783               pspp_sheet_selection_select_path (selection, path);
784               gtk_tree_path_free (path);
785             }
786         }
787
788       do_popup_menu (widget, event->button, event->time);
789       return TRUE;
790     }
791
792   return FALSE;
793 }
794 \f
795 GType
796 psppire_fmt_use_get_type (void)
797 {
798   static GType etype = 0;
799   if (etype == 0)
800     {
801       static const GEnumValue values[] =
802         {
803           { FMT_FOR_INPUT, "FMT_FOR_INPUT", "input" },
804           { FMT_FOR_OUTPUT, "FMT_FOR_OUTPUT", "output" },
805           { 0, NULL, NULL }
806         };
807
808       etype = g_enum_register_static
809         (g_intern_static_string ("PsppireFmtUse"), values);
810     }
811   return etype;
812 }
813
814 enum
815   {
816     PROP_0,
817     PROP_DICTIONARY,
818     PROP_MAY_CREATE_VARS,
819     PROP_MAY_DELETE_VARS,
820     PROP_FORMAT_TYPE,
821     PROP_UI_MANAGER
822   };
823
824 static void
825 psppire_var_sheet_set_property (GObject      *object,
826                              guint         prop_id,
827                              const GValue *value,
828                              GParamSpec   *pspec)
829 {
830   PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
831
832   switch (prop_id)
833     {
834     case PROP_DICTIONARY:
835       psppire_var_sheet_set_dictionary (obj,
836                                         PSPPIRE_DICT (g_value_get_object (
837                                                         value)));
838       break;
839
840     case PROP_MAY_CREATE_VARS:
841       psppire_var_sheet_set_may_create_vars (obj,
842                                              g_value_get_boolean (value));
843       break;
844
845     case PROP_MAY_DELETE_VARS:
846       psppire_var_sheet_set_may_delete_vars (obj,
847                                              g_value_get_boolean (value));
848       break;
849
850     case PROP_FORMAT_TYPE:
851       obj->format_use = g_value_get_enum (value);
852       break;
853
854     case PROP_UI_MANAGER:
855     default:
856       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
857       break;
858     }
859 }
860
861 static void
862 psppire_var_sheet_get_property (GObject      *object,
863                                 guint         prop_id,
864                                 GValue       *value,
865                                 GParamSpec   *pspec)
866 {
867   PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
868
869   switch (prop_id)
870     {
871     case PROP_DICTIONARY:
872       g_value_set_object (value,
873                           G_OBJECT (psppire_var_sheet_get_dictionary (obj)));
874       break;
875
876     case PROP_MAY_CREATE_VARS:
877       g_value_set_boolean (value, obj->may_create_vars);
878       break;
879
880     case PROP_MAY_DELETE_VARS:
881       g_value_set_boolean (value, obj->may_delete_vars);
882       break;
883
884     case PROP_FORMAT_TYPE:
885       g_value_set_enum (value, obj->format_use);
886       break;
887
888     case PROP_UI_MANAGER:
889       g_value_set_object (value, psppire_var_sheet_get_ui_manager (obj));
890       break;
891
892     default:
893       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
894       break;
895     }
896 }
897
898 static void
899 psppire_var_sheet_realize (GtkWidget *w)
900 {
901   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (w);
902   GtkWindow *toplevel;
903
904   GTK_WIDGET_CLASS (psppire_var_sheet_parent_class)->realize (w);
905
906   toplevel = GTK_WINDOW (gtk_widget_get_toplevel (w));
907   var_sheet->val_labs_dialog = val_labs_dialog_create (toplevel);
908   var_sheet->missing_val_dialog = missing_val_dialog_create (toplevel);
909   var_sheet->var_type_dialog = var_type_dialog_create (toplevel);
910 }
911
912 static void
913 psppire_var_sheet_dispose (GObject *obj)
914 {
915   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (obj);
916   int i;
917
918   if (var_sheet->dispose_has_run)
919     return;
920
921   var_sheet->dispose_has_run = TRUE;
922
923   for (i = 0; i < PSPPIRE_VAR_SHEET_N_SIGNALS; i++)
924     if ( var_sheet->dict_signals[i])
925       g_signal_handler_disconnect (var_sheet->dict,
926                                    var_sheet->dict_signals[i]);
927
928   if (var_sheet->dict)
929     g_object_unref (var_sheet->dict);
930   
931   if (var_sheet->uim)
932     g_object_unref (var_sheet->uim);
933
934   /* These dialogs are not GObjects (although they should be!)
935     But for now, unreffing them only causes a GCritical Error
936     so comment them out for now. (and accept the memory leakage)
937
938   g_object_unref (var_sheet->val_labs_dialog);
939   g_object_unref (var_sheet->missing_val_dialog);
940   g_object_unref (var_sheet->var_type_dialog);
941   */
942
943   G_OBJECT_CLASS (psppire_var_sheet_parent_class)->dispose (obj);
944 }
945
946 static void
947 psppire_var_sheet_class_init (PsppireVarSheetClass *class)
948 {
949   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
950   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
951   GParamSpec *pspec;
952
953   gobject_class->set_property = psppire_var_sheet_set_property;
954   gobject_class->get_property = psppire_var_sheet_get_property;
955   gobject_class->dispose = psppire_var_sheet_dispose;
956
957   widget_class->realize = psppire_var_sheet_realize;
958
959   g_signal_new ("var-double-clicked",
960                 G_OBJECT_CLASS_TYPE (gobject_class),
961                 G_SIGNAL_RUN_LAST,
962                 0,
963                 g_signal_accumulator_true_handled, NULL,
964                 psppire_marshal_BOOLEAN__INT,
965                 G_TYPE_BOOLEAN, 1, G_TYPE_INT);
966
967   pspec = g_param_spec_object ("dictionary",
968                                "Dictionary displayed by the sheet",
969                                "The PsppireDict that the sheet displays "
970                                "may allow the user to edit",
971                                PSPPIRE_TYPE_DICT,
972                                G_PARAM_READWRITE);
973   g_object_class_install_property (gobject_class, PROP_DICTIONARY, pspec);
974
975   pspec = g_param_spec_boolean ("may-create-vars",
976                                 "May create variables",
977                                 "Whether the user may create more variables",
978                                 TRUE,
979                                 G_PARAM_READWRITE);
980   g_object_class_install_property (gobject_class, PROP_MAY_CREATE_VARS, pspec);
981
982   pspec = g_param_spec_boolean ("may-delete-vars",
983                                 "May delete variables",
984                                 "Whether the user may delete variables",
985                                 TRUE,
986                                 G_PARAM_READWRITE);
987   g_object_class_install_property (gobject_class, PROP_MAY_DELETE_VARS, pspec);
988
989   pspec = g_param_spec_enum ("format-use",
990                              "Use of variable format",
991                              ("Whether variables have input or output "
992                               "formats"),
993                              PSPPIRE_TYPE_FMT_USE,
994                              FMT_FOR_OUTPUT,
995                              G_PARAM_READWRITE);
996   g_object_class_install_property (gobject_class, PROP_FORMAT_TYPE, pspec);
997
998   pspec = g_param_spec_object ("ui-manager",
999                                "UI Manager",
1000                                "UI manager for the variable sheet.  The client should merge this UI manager with the active UI manager to obtain variable sheet specific menu items and tool bar items.",
1001                                GTK_TYPE_UI_MANAGER,
1002                                G_PARAM_READABLE);
1003   g_object_class_install_property (gobject_class, PROP_UI_MANAGER, pspec);
1004 }
1005
1006 static void
1007 render_row_number_cell (PsppSheetViewColumn *tree_column,
1008                         GtkCellRenderer *cell,
1009                         GtkTreeModel *model,
1010                         GtkTreeIter *iter,
1011                         gpointer user_data)
1012 {
1013   PsppireVarSheet *var_sheet = user_data;
1014   GValue gvalue = { 0, };
1015   gint row;
1016
1017   row = GPOINTER_TO_INT (iter->user_data);
1018
1019   g_value_init (&gvalue, G_TYPE_INT);
1020   g_value_set_int (&gvalue, row + 1);
1021   g_object_set_property (G_OBJECT (cell), "label", &gvalue);
1022   g_value_unset (&gvalue);
1023
1024   if (!var_sheet->dict || row < psppire_dict_get_var_cnt (var_sheet->dict))
1025     g_object_set (cell, "editable", TRUE, NULL);
1026   else
1027     g_object_set (cell, "editable", FALSE, NULL);
1028 }
1029
1030 static void
1031 psppire_var_sheet_row_number_double_clicked (PsppireCellRendererButton *button,
1032                                              gchar *path_string,
1033                                              PsppireVarSheet *var_sheet)
1034 {
1035   GtkTreePath *path;
1036
1037   g_return_if_fail (var_sheet->dict != NULL);
1038
1039   path = gtk_tree_path_new_from_string (path_string);
1040   if (gtk_tree_path_get_depth (path) == 1)
1041     {
1042       gint *indices = gtk_tree_path_get_indices (path);
1043       if (indices[0] < psppire_dict_get_var_cnt (var_sheet->dict))
1044         {
1045           gboolean handled;
1046           g_signal_emit_by_name (var_sheet, "var-double-clicked",
1047                                  indices[0], &handled);
1048         }
1049     }
1050   gtk_tree_path_free (path);
1051 }
1052
1053 static PsppSheetViewColumn *
1054 make_row_number_column (PsppireVarSheet *var_sheet)
1055 {
1056   PsppSheetViewColumn *column;
1057   GtkCellRenderer *renderer;
1058
1059   renderer = psppire_cell_renderer_button_new ();
1060   g_object_set (renderer, "xalign", 1.0, NULL);
1061   g_signal_connect (renderer, "double-clicked",
1062                     G_CALLBACK (psppire_var_sheet_row_number_double_clicked),
1063                     var_sheet);
1064
1065   column = pspp_sheet_view_column_new_with_attributes (_("Variable"),
1066                                                        renderer, NULL);
1067   pspp_sheet_view_column_set_cell_data_func (
1068     column, renderer, render_row_number_cell, var_sheet, NULL);
1069   pspp_sheet_view_column_set_fixed_width (column, 50);
1070   return column;
1071 }
1072
1073 static void
1074 on_edit_clear_variables (GtkAction *action, PsppireVarSheet *var_sheet)
1075 {
1076   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1077   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1078   PsppireDict *dict = var_sheet->dict;
1079   const struct range_set_node *node;
1080   struct range_set *selected;
1081
1082   selected = pspp_sheet_selection_get_range_set (selection);
1083   for (node = range_set_last (selected); node != NULL;
1084        node = range_set_prev (selected, node))
1085     {
1086       int i;
1087
1088       for (i = 1; i <= range_set_node_get_width (node); i++)
1089         {
1090           unsigned long row = range_set_node_get_end (node) - i;
1091           if (row >= 0 && row < psppire_dict_get_var_cnt (dict))
1092             psppire_dict_delete_variables (dict, row, 1);
1093         }
1094     }
1095   range_set_destroy (selected);
1096 }
1097
1098 static void
1099 on_selection_changed (PsppSheetSelection *selection,
1100                       gpointer user_data UNUSED)
1101 {
1102   PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
1103   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (sheet_view);
1104   gint n_selected_rows;
1105   gboolean may_delete;
1106   GtkTreePath *path;
1107   GtkAction *action;
1108
1109   n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
1110
1111   action = get_action_assert (var_sheet->builder, "edit_insert-variable");
1112   gtk_action_set_sensitive (action, (var_sheet->may_create_vars
1113                                      && n_selected_rows > 0));
1114
1115   switch (n_selected_rows)
1116     {
1117     case 0:
1118       may_delete = FALSE;
1119       break;
1120
1121     case 1:
1122       /* The row used for inserting new variables cannot be deleted. */
1123       path = gtk_tree_path_new_from_indices (
1124         psppire_dict_get_var_cnt (var_sheet->dict), -1);
1125       may_delete = !pspp_sheet_selection_path_is_selected (selection, path);
1126       gtk_tree_path_free (path);
1127       break;
1128
1129     default:
1130       may_delete = TRUE;
1131       break;
1132     }
1133   action = get_action_assert (var_sheet->builder, "edit_clear-variables");
1134   gtk_action_set_sensitive (action, var_sheet->may_delete_vars && may_delete);
1135 }
1136
1137 static void
1138 on_edit_insert_variable (GtkAction *action, PsppireVarSheet *var_sheet)
1139 {
1140   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1141   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1142   PsppireDict *dict = var_sheet->dict;
1143   struct range_set *selected;
1144   unsigned long row;
1145
1146   selected = pspp_sheet_selection_get_range_set (selection);
1147   row = range_set_scan (selected, 0);
1148   range_set_destroy (selected);
1149
1150   if (row <= psppire_dict_get_var_cnt (dict))
1151     {
1152       gchar name[64];;
1153       if (psppire_dict_generate_name (dict, name, sizeof name))
1154         psppire_dict_insert_variable (dict, row, name);
1155     }
1156 }
1157
1158 static void
1159 psppire_var_sheet_init (PsppireVarSheet *obj)
1160 {
1161   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
1162   PsppSheetViewColumn *column;
1163   GtkAction *action;
1164   GList *list;
1165
1166   obj->dict = NULL;
1167   obj->format_use = FMT_FOR_OUTPUT;
1168   obj->may_create_vars = TRUE;
1169   obj->may_delete_vars = TRUE;
1170
1171   obj->scroll_to_bottom_signal = 0;
1172
1173   obj->container = NULL;
1174   obj->dispose_has_run = FALSE;
1175   obj->uim = NULL;
1176
1177   pspp_sheet_view_append_column (sheet_view, make_row_number_column (obj));
1178
1179   column = add_text_column (obj, VS_NAME, _("Name"), 12);
1180   list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
1181   g_signal_connect (list->data, "editing-started",
1182                     G_CALLBACK (on_name_column_editing_started), NULL);
1183   g_list_free (list);
1184
1185   column = add_text_column (obj, VS_TYPE, _("Type"), 8);
1186   add_popup_menu (obj, column, on_type_click);
1187
1188   add_spin_column (obj, VS_WIDTH, _("Width"), 5);
1189
1190   add_spin_column (obj, VS_DECIMALS, _("Decimals"), 2);
1191
1192   add_text_column (obj, VS_LABEL, _("Label"), 20);
1193
1194   column = add_text_column (obj, VS_VALUES, _("Value Labels"), 20);
1195   add_popup_menu (obj, column, on_value_labels_click);
1196
1197   column = add_text_column (obj, VS_MISSING, _("Missing Values"), 20);
1198   add_popup_menu (obj, column, on_missing_values_click);
1199
1200   add_spin_column (obj, VS_COLUMNS, _("Columns"), 3);
1201
1202   add_combo_column (obj, VS_ALIGN, _("Align"), 6,
1203                     alignment_to_string (ALIGN_LEFT), ALIGN_LEFT,
1204                     alignment_to_string (ALIGN_CENTRE), ALIGN_CENTRE,
1205                     alignment_to_string (ALIGN_RIGHT), ALIGN_RIGHT,
1206                     NULL);
1207
1208   add_combo_column (obj, VS_MEASURE, _("Measure"), 10,
1209                     measure_to_string (MEASURE_NOMINAL), MEASURE_NOMINAL,
1210                     measure_to_string (MEASURE_ORDINAL), MEASURE_ORDINAL,
1211                     measure_to_string (MEASURE_SCALE), MEASURE_SCALE,
1212                     NULL);
1213
1214   pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
1215   pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
1216                                  PSPP_SHEET_SELECTION_MULTIPLE);
1217
1218   g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, NULL);
1219   g_signal_connect (obj, "query-tooltip",
1220                     G_CALLBACK (on_query_var_tooltip), NULL);
1221   g_signal_connect (obj, "button-press-event",
1222                     G_CALLBACK (on_button_pressed), NULL);
1223   g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
1224
1225   obj->builder = builder_new ("var-sheet.ui");
1226
1227   action = get_action_assert (obj->builder, "edit_clear-variables");
1228   g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
1229                     obj);
1230   gtk_action_set_sensitive (action, FALSE);
1231   g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
1232                     "changed", G_CALLBACK (on_selection_changed), NULL);
1233
1234   action = get_action_assert (obj->builder, "edit_insert-variable");
1235   gtk_action_set_sensitive (action, FALSE);
1236   g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_variable),
1237                     obj);
1238 }
1239
1240 GtkWidget *
1241 psppire_var_sheet_new (void)
1242 {
1243   return g_object_new (PSPPIRE_VAR_SHEET_TYPE, NULL);
1244 }
1245
1246 PsppireDict *
1247 psppire_var_sheet_get_dictionary (PsppireVarSheet *var_sheet)
1248 {
1249   return var_sheet->dict;
1250 }
1251
1252 static void
1253 refresh_model (PsppireVarSheet *var_sheet)
1254 {
1255   pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet), NULL);
1256
1257   if (var_sheet->dict != NULL)
1258     {
1259       PsppireEmptyListStore *store;
1260       int n_rows;
1261
1262       n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1263                 + var_sheet->may_create_vars);
1264       store = psppire_empty_list_store_new (n_rows);
1265       pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet),
1266                                  GTK_TREE_MODEL (store));
1267       g_object_unref (store);
1268     }
1269 }
1270
1271 static void
1272 on_var_changed (PsppireDict *dict, glong row, PsppireVarSheet *var_sheet)
1273 {
1274   PsppireEmptyListStore *store;
1275
1276   g_return_if_fail (dict == var_sheet->dict);
1277
1278   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1279                                       PSPP_SHEET_VIEW (var_sheet)));
1280   g_return_if_fail (store != NULL);
1281
1282   psppire_empty_list_store_row_changed (store, row);
1283 }
1284
1285 static void
1286 on_var_inserted (PsppireDict *dict, glong row, PsppireVarSheet *var_sheet)
1287 {
1288   PsppireEmptyListStore *store;
1289   int n_rows;
1290
1291   g_return_if_fail (dict == var_sheet->dict);
1292
1293   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1294                                       PSPP_SHEET_VIEW (var_sheet)));
1295   g_return_if_fail (store != NULL);
1296
1297   n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1298             + var_sheet->may_create_vars);
1299   psppire_empty_list_store_set_n_rows (store, n_rows);
1300   psppire_empty_list_store_row_inserted (store, row);
1301 }
1302
1303 static void
1304 on_var_deleted (PsppireDict *dict,
1305                 const struct variable *var, int dict_idx, int case_idx,
1306                 PsppireVarSheet *var_sheet)
1307 {
1308   PsppireEmptyListStore *store;
1309   int n_rows;
1310
1311   g_return_if_fail (dict == var_sheet->dict);
1312
1313   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1314                                       PSPP_SHEET_VIEW (var_sheet)));
1315   g_return_if_fail (store != NULL);
1316
1317   n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1318             + var_sheet->may_create_vars);
1319   psppire_empty_list_store_set_n_rows (store, n_rows);
1320   psppire_empty_list_store_row_deleted (store, dict_idx);
1321 }
1322
1323 static void
1324 on_backend_changed (PsppireDict *dict, PsppireVarSheet *var_sheet)
1325 {
1326   g_return_if_fail (dict == var_sheet->dict);
1327   refresh_model (var_sheet);
1328 }
1329
1330 void
1331 psppire_var_sheet_set_dictionary (PsppireVarSheet *var_sheet,
1332                                   PsppireDict *dict)
1333 {
1334   if (var_sheet->dict != NULL)
1335     {
1336       int i;
1337       
1338       for (i = 0; i < PSPPIRE_VAR_SHEET_N_SIGNALS; i++)
1339         {
1340           if (var_sheet->dict_signals[i])
1341             g_signal_handler_disconnect (var_sheet->dict,
1342                                          var_sheet->dict_signals[i]);
1343           
1344           var_sheet->dict_signals[i] = 0;
1345         }
1346
1347       g_object_unref (var_sheet->dict);
1348     }
1349
1350   var_sheet->dict = dict;
1351
1352   if (dict != NULL)
1353     {
1354       g_object_ref (dict);
1355
1356       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_BACKEND_CHANGED]
1357         = g_signal_connect (dict, "backend-changed",
1358                             G_CALLBACK (on_backend_changed), var_sheet);
1359
1360       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_CHANGED]
1361         = g_signal_connect (dict, "variable-changed",
1362                             G_CALLBACK (on_var_changed), var_sheet);
1363
1364       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_DELETED]
1365         = g_signal_connect (dict, "variable-inserted",
1366                             G_CALLBACK (on_var_inserted), var_sheet);
1367
1368       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_INSERTED]
1369         = g_signal_connect (dict, "variable-deleted",
1370                             G_CALLBACK (on_var_deleted), var_sheet);
1371     }
1372
1373   refresh_model (var_sheet);
1374 }
1375
1376 gboolean
1377 psppire_var_sheet_get_may_create_vars (PsppireVarSheet *var_sheet)
1378 {
1379   return var_sheet->may_create_vars;
1380 }
1381
1382 void
1383 psppire_var_sheet_set_may_create_vars (PsppireVarSheet *var_sheet,
1384                                        gboolean may_create_vars)
1385 {
1386   if (var_sheet->may_create_vars != may_create_vars)
1387     {
1388       PsppireEmptyListStore *store;
1389       gint n_rows;
1390
1391       var_sheet->may_create_vars = may_create_vars;
1392
1393       store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1394                                           PSPP_SHEET_VIEW (var_sheet)));
1395       g_return_if_fail (store != NULL);
1396
1397       n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1398                 + var_sheet->may_create_vars);
1399       psppire_empty_list_store_set_n_rows (store, n_rows);
1400
1401       if (may_create_vars)
1402         psppire_empty_list_store_row_inserted (store, n_rows - 1);
1403       else
1404         psppire_empty_list_store_row_deleted (store, n_rows);
1405
1406       on_selection_changed (pspp_sheet_view_get_selection (
1407                               PSPP_SHEET_VIEW (var_sheet)), NULL);
1408     }
1409 }
1410
1411 gboolean
1412 psppire_var_sheet_get_may_delete_vars (PsppireVarSheet *var_sheet)
1413 {
1414   return var_sheet->may_delete_vars;
1415 }
1416
1417 void
1418 psppire_var_sheet_set_may_delete_vars (PsppireVarSheet *var_sheet,
1419                                        gboolean may_delete_vars)
1420 {
1421   if (var_sheet->may_delete_vars != may_delete_vars)
1422     {
1423       var_sheet->may_delete_vars = may_delete_vars;
1424       on_selection_changed (pspp_sheet_view_get_selection (
1425                               PSPP_SHEET_VIEW (var_sheet)), NULL);
1426     }
1427 }
1428
1429 void
1430 psppire_var_sheet_goto_variable (PsppireVarSheet *var_sheet, int dict_index)
1431 {
1432   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1433   GtkTreePath *path;
1434
1435   path = gtk_tree_path_new_from_indices (dict_index, -1);
1436   pspp_sheet_view_scroll_to_cell (sheet_view, path, NULL, FALSE, 0.0, 0.0);
1437   pspp_sheet_view_set_cursor (sheet_view, path, NULL, FALSE);
1438   gtk_tree_path_free (path);
1439 }
1440
1441 GtkUIManager *
1442 psppire_var_sheet_get_ui_manager (PsppireVarSheet *var_sheet)
1443 {
1444   if (var_sheet->uim == NULL)
1445     {
1446       var_sheet->uim = GTK_UI_MANAGER (get_object_assert (var_sheet->builder,
1447                                                           "var_sheet_uim",
1448                                                           GTK_TYPE_UI_MANAGER));
1449       g_object_ref (var_sheet->uim);
1450     }
1451
1452   return var_sheet->uim;
1453 }
1454