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