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