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