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