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