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