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