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