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