tests: Add pspp-convert.at to Git.
[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, "icon-name",
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, "icon-name",
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, "icon-name",
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, "icon-name", 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 = gtk_menu_new ();
941
942   int i = 0;
943
944   GtkWidget *insert_variable = gtk_menu_item_new_with_mnemonic (_("_Insert Variable"));
945   GtkWidget *clear_variables = gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
946
947   g_signal_connect_swapped (insert_variable, "activate", G_CALLBACK (psppire_var_sheet_insert_variable), var_sheet);
948   g_signal_connect_swapped (clear_variables, "activate", G_CALLBACK (psppire_var_sheet_clear_variables), var_sheet);
949
950   gtk_menu_attach (GTK_MENU (menu), insert_variable,  0, 1, i, i + 1);  ++i;
951   gtk_menu_attach (GTK_MENU (menu), clear_variables,  0, 1, i, i + 1);  ++i;
952
953   gtk_widget_show_all (menu);
954
955   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
956 }
957
958 static void
959 on_popup_menu (GtkWidget *widget, gpointer user_data UNUSED)
960 {
961   do_popup_menu (widget, 0, gtk_get_current_event_time ());
962 }
963
964 static gboolean
965 on_button_pressed (GtkWidget *widget, GdkEventButton *event,
966                    gpointer user_data UNUSED)
967 {
968   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
969
970   if (event->type == GDK_BUTTON_PRESS && event->button == 3)
971     {
972       PsppSheetSelection *selection;
973
974       selection = pspp_sheet_view_get_selection (sheet_view);
975       if (pspp_sheet_selection_count_selected_rows (selection) <= 1)
976         {
977           GtkTreePath *path;
978
979           if (pspp_sheet_view_get_path_at_pos (sheet_view, event->x, event->y,
980                                                &path, NULL, NULL, NULL))
981             {
982               pspp_sheet_selection_unselect_all (selection);
983               pspp_sheet_selection_select_path (selection, path);
984               gtk_tree_path_free (path);
985             }
986         }
987
988       do_popup_menu (widget, event->button, event->time);
989       return TRUE;
990     }
991
992   return FALSE;
993 }
994
995 \f
996 GType
997 psppire_fmt_use_get_type (void)
998 {
999   static GType etype = 0;
1000   if (etype == 0)
1001     {
1002       static const GEnumValue values[] =
1003         {
1004           { FMT_FOR_INPUT, "FMT_FOR_INPUT", "input" },
1005           { FMT_FOR_OUTPUT, "FMT_FOR_OUTPUT", "output" },
1006           { 0, NULL, NULL }
1007         };
1008
1009       etype = g_enum_register_static
1010         (g_intern_static_string ("PsppireFmtUse"), values);
1011     }
1012   return etype;
1013 }
1014
1015 enum
1016   {
1017     PROP_0,
1018     PROP_DICTIONARY,
1019     PROP_MAY_CREATE_VARS,
1020     PROP_MAY_DELETE_VARS,
1021     PROP_FORMAT_TYPE
1022   };
1023
1024 static void
1025 psppire_var_sheet_set_property (GObject      *object,
1026                              guint         prop_id,
1027                              const GValue *value,
1028                              GParamSpec   *pspec)
1029 {
1030   PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
1031
1032   switch (prop_id)
1033     {
1034     case PROP_DICTIONARY:
1035       psppire_var_sheet_set_dictionary (obj,
1036                                         PSPPIRE_DICT (g_value_get_object (
1037                                                         value)));
1038       break;
1039
1040     case PROP_MAY_CREATE_VARS:
1041       psppire_var_sheet_set_may_create_vars (obj,
1042                                              g_value_get_boolean (value));
1043       break;
1044
1045     case PROP_MAY_DELETE_VARS:
1046       psppire_var_sheet_set_may_delete_vars (obj,
1047                                              g_value_get_boolean (value));
1048       break;
1049
1050     case PROP_FORMAT_TYPE:
1051       obj->format_use = g_value_get_enum (value);
1052       break;
1053
1054     default:
1055       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1056       break;
1057     }
1058 }
1059
1060 static void
1061 psppire_var_sheet_get_property (GObject      *object,
1062                                 guint         prop_id,
1063                                 GValue       *value,
1064                                 GParamSpec   *pspec)
1065 {
1066   PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
1067
1068   switch (prop_id)
1069     {
1070     case PROP_DICTIONARY:
1071       g_value_set_object (value,
1072                           G_OBJECT (psppire_var_sheet_get_dictionary (obj)));
1073       break;
1074
1075     case PROP_MAY_CREATE_VARS:
1076       g_value_set_boolean (value, obj->may_create_vars);
1077       break;
1078
1079     case PROP_MAY_DELETE_VARS:
1080       g_value_set_boolean (value, obj->may_delete_vars);
1081       break;
1082
1083     case PROP_FORMAT_TYPE:
1084       g_value_set_enum (value, obj->format_use);
1085       break;
1086
1087     default:
1088       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1089       break;
1090     }
1091 }
1092
1093 static void
1094 psppire_var_sheet_dispose (GObject *obj)
1095 {
1096   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (obj);
1097   int i;
1098
1099   if (var_sheet->dispose_has_run)
1100     return;
1101
1102   var_sheet->dispose_has_run = TRUE;
1103
1104   for (i = 0; i < PSPPIRE_VAR_SHEET_N_SIGNALS; i++)
1105     if ( var_sheet->dict_signals[i])
1106       g_signal_handler_disconnect (var_sheet->dict,
1107                                    var_sheet->dict_signals[i]);
1108
1109   if (var_sheet->dict)
1110     g_object_unref (var_sheet->dict);
1111
1112   /* These dialogs are not GObjects (although they should be!)
1113     But for now, unreffing them only causes a GCritical Error
1114     so comment them out for now. (and accept the memory leakage)
1115
1116   g_object_unref (var_sheet->val_labs_dialog);
1117   g_object_unref (var_sheet->missing_val_dialog);
1118   g_object_unref (var_sheet->var_type_dialog);
1119   */
1120
1121   G_OBJECT_CLASS (psppire_var_sheet_parent_class)->dispose (obj);
1122 }
1123
1124 static void
1125 psppire_var_sheet_class_init (PsppireVarSheetClass *class)
1126 {
1127   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
1128   GParamSpec *pspec;
1129
1130   gobject_class->set_property = psppire_var_sheet_set_property;
1131   gobject_class->get_property = psppire_var_sheet_get_property;
1132   gobject_class->dispose = psppire_var_sheet_dispose;
1133
1134   g_signal_new ("var-double-clicked",
1135                 G_OBJECT_CLASS_TYPE (gobject_class),
1136                 G_SIGNAL_RUN_LAST,
1137                 0,
1138                 g_signal_accumulator_true_handled, NULL,
1139                 psppire_marshal_BOOLEAN__INT,
1140                 G_TYPE_BOOLEAN, 1, G_TYPE_INT);
1141
1142   pspec = g_param_spec_object ("dictionary",
1143                                "Dictionary displayed by the sheet",
1144                                "The PsppireDict that the sheet displays "
1145                                "may allow the user to edit",
1146                                PSPPIRE_TYPE_DICT,
1147                                G_PARAM_READWRITE);
1148   g_object_class_install_property (gobject_class, PROP_DICTIONARY, pspec);
1149
1150   pspec = g_param_spec_boolean ("may-create-vars",
1151                                 "May create variables",
1152                                 "Whether the user may create more variables",
1153                                 TRUE,
1154                                 G_PARAM_READWRITE);
1155   g_object_class_install_property (gobject_class, PROP_MAY_CREATE_VARS, pspec);
1156
1157   pspec = g_param_spec_boolean ("may-delete-vars",
1158                                 "May delete variables",
1159                                 "Whether the user may delete variables",
1160                                 TRUE,
1161                                 G_PARAM_READWRITE);
1162   g_object_class_install_property (gobject_class, PROP_MAY_DELETE_VARS, pspec);
1163
1164   pspec = g_param_spec_enum ("format-use",
1165                              "Use of variable format",
1166                              ("Whether variables have input or output "
1167                               "formats"),
1168                              PSPPIRE_TYPE_FMT_USE,
1169                              FMT_FOR_OUTPUT,
1170                              G_PARAM_READWRITE);
1171   g_object_class_install_property (gobject_class, PROP_FORMAT_TYPE, pspec);
1172 }
1173
1174 static void
1175 render_row_number_cell (PsppSheetViewColumn *tree_column,
1176                         GtkCellRenderer *cell,
1177                         GtkTreeModel *model,
1178                         GtkTreeIter *iter,
1179                         gpointer user_data)
1180 {
1181   PsppireVarSheet *var_sheet = user_data;
1182   GValue gvalue = { 0, };
1183   gint row;
1184
1185   row = GPOINTER_TO_INT (iter->user_data);
1186
1187   g_value_init (&gvalue, G_TYPE_INT);
1188   g_value_set_int (&gvalue, row + 1);
1189   g_object_set_property (G_OBJECT (cell), "label", &gvalue);
1190   g_value_unset (&gvalue);
1191
1192   if (!var_sheet->dict || row < psppire_dict_get_var_cnt (var_sheet->dict))
1193     g_object_set (cell, "editable", TRUE, NULL);
1194   else
1195     g_object_set (cell, "editable", FALSE, NULL);
1196 }
1197
1198 static void
1199 psppire_var_sheet_row_number_double_clicked (PsppireCellRendererButton *button,
1200                                              gchar *path_string,
1201                                              PsppireVarSheet *var_sheet)
1202 {
1203   GtkTreePath *path;
1204
1205   g_return_if_fail (var_sheet->dict != NULL);
1206
1207   path = gtk_tree_path_new_from_string (path_string);
1208   if (gtk_tree_path_get_depth (path) == 1)
1209     {
1210       gint *indices = gtk_tree_path_get_indices (path);
1211       if (indices[0] < psppire_dict_get_var_cnt (var_sheet->dict))
1212         {
1213           gboolean handled;
1214           g_signal_emit_by_name (var_sheet, "var-double-clicked",
1215                                  indices[0], &handled);
1216         }
1217     }
1218   gtk_tree_path_free (path);
1219 }
1220
1221 static void
1222 psppire_var_sheet_variables_column_clicked (PsppSheetViewColumn *column,
1223                                             PsppireVarSheet *var_sheet)
1224 {
1225   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1226   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1227
1228   pspp_sheet_selection_select_all (selection);
1229 }
1230
1231 static PsppSheetViewColumn *
1232 make_row_number_column (PsppireVarSheet *var_sheet)
1233 {
1234   PsppSheetViewColumn *column;
1235   GtkCellRenderer *renderer;
1236
1237   renderer = psppire_cell_renderer_button_new ();
1238   g_object_set (renderer, "xalign", 1.0, NULL);
1239   g_signal_connect (renderer, "double-clicked",
1240                     G_CALLBACK (psppire_var_sheet_row_number_double_clicked),
1241                     var_sheet);
1242
1243   column = pspp_sheet_view_column_new_with_attributes (_("Variable"),
1244                                                        renderer, NULL);
1245   pspp_sheet_view_column_set_clickable (column, TRUE);
1246   pspp_sheet_view_column_set_cell_data_func (
1247     column, renderer, render_row_number_cell, var_sheet, NULL);
1248   pspp_sheet_view_column_set_fixed_width (column, 50);
1249   g_signal_connect (column, "clicked",
1250                     G_CALLBACK (psppire_var_sheet_variables_column_clicked),
1251                     var_sheet);
1252
1253   return column;
1254 }
1255
1256 void
1257 psppire_var_sheet_clear_variables (PsppireVarSheet *var_sheet)
1258 {
1259   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1260   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1261   PsppireDict *dict = var_sheet->dict;
1262   const struct range_set_node *node;
1263   struct range_set *selected = pspp_sheet_selection_get_range_set (selection);
1264
1265   for (node = range_set_last (selected); node != NULL;
1266        node = range_set_prev (selected, node))
1267     {
1268       int i;
1269
1270       for (i = 1; i <= range_set_node_get_width (node); i++)
1271         {
1272           unsigned long row = range_set_node_get_end (node) - i;
1273           if (row < psppire_dict_get_var_cnt (dict))
1274             psppire_dict_delete_variables (dict, row, 1);
1275         }
1276     }
1277   range_set_destroy (selected);
1278 }
1279
1280 static void
1281 on_selection_changed (PsppSheetSelection *selection,
1282                       gpointer user_data UNUSED)
1283 {
1284   PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
1285   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (sheet_view);
1286   gint n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
1287   gboolean may_delete;
1288   GtkTreePath *path;
1289
1290   GtkWidget *top = gtk_widget_get_toplevel (GTK_WIDGET (var_sheet));
1291   if (! PSPPIRE_IS_DATA_WINDOW (top))
1292     return;
1293
1294   PsppireDataWindow *dw = PSPPIRE_DATA_WINDOW (top);
1295   gtk_widget_set_sensitive (dw->mi_insert_var, n_selected_rows > 0);
1296
1297   switch (n_selected_rows)
1298     {
1299     case 0:
1300       may_delete = FALSE;
1301       break;
1302
1303     case 1:
1304       /* The row used for inserting new variables cannot be deleted. */
1305       path = gtk_tree_path_new_from_indices (
1306         psppire_dict_get_var_cnt (var_sheet->dict), -1);
1307       may_delete = !pspp_sheet_selection_path_is_selected (selection, path);
1308       gtk_tree_path_free (path);
1309       break;
1310
1311     default:
1312       may_delete = TRUE;
1313       break;
1314     }
1315
1316   gtk_widget_set_sensitive (dw->mi_clear_variables, var_sheet->may_delete_vars && may_delete);
1317 }
1318
1319 void
1320 psppire_var_sheet_insert_variable (PsppireVarSheet *var_sheet)
1321 {
1322   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1323   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1324   PsppireDict *dict = var_sheet->dict;
1325   struct range_set *selected;
1326   unsigned long row;
1327
1328   selected = pspp_sheet_selection_get_range_set (selection);
1329   row = range_set_scan (selected, 0);
1330   range_set_destroy (selected);
1331
1332   if (row <= psppire_dict_get_var_cnt (dict))
1333     {
1334       gchar name[64];;
1335       if (psppire_dict_generate_name (dict, name, sizeof name))
1336         psppire_dict_insert_variable (dict, row, name);
1337     }
1338 }
1339
1340 static void
1341 psppire_var_sheet_init (PsppireVarSheet *obj)
1342 {
1343   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
1344   PsppSheetViewColumn *column;
1345   GList *list;
1346
1347   obj->dict = NULL;
1348   obj->format_use = FMT_FOR_OUTPUT;
1349   obj->may_create_vars = TRUE;
1350   obj->may_delete_vars = TRUE;
1351
1352   obj->scroll_to_bottom_signal = 0;
1353
1354   obj->container = NULL;
1355   obj->dispose_has_run = FALSE;
1356
1357   pspp_sheet_view_append_column (sheet_view, make_row_number_column (obj));
1358
1359   column = add_text_column (obj, VS_NAME, _("Name"), 12);
1360   list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
1361   g_signal_connect (list->data, "editing-started",
1362                     G_CALLBACK (on_name_column_editing_started), NULL);
1363   g_list_free (list);
1364
1365   column = add_text_column (obj, VS_TYPE, _("Type"), 8);
1366   add_popup_menu (obj, column, on_type_click);
1367
1368   add_spin_column (obj, VS_WIDTH, _("Width"), 5);
1369
1370   add_spin_column (obj, VS_DECIMALS, _("Decimals"), 2);
1371
1372   add_text_column (obj, VS_LABEL, _("Label"), 20);
1373
1374   column = add_text_column (obj, VS_VALUES, _("Value Labels"), 20);
1375   add_popup_menu (obj, column, on_value_labels_click);
1376
1377   column = add_text_column (obj, VS_MISSING, _("Missing Values"), 20);
1378   add_popup_menu (obj, column, on_missing_values_click);
1379
1380   add_spin_column (obj, VS_COLUMNS, _("Columns"), 3);
1381
1382   add_combo_column (obj, VS_ALIGN, _("Align"), 8, alignment_to_stock_id,
1383                     alignment_to_string (ALIGN_LEFT), ALIGN_LEFT,
1384                     alignment_to_string (ALIGN_CENTRE), ALIGN_CENTRE,
1385                     alignment_to_string (ALIGN_RIGHT), ALIGN_RIGHT,
1386                     NULL);
1387
1388   add_combo_column (obj, VS_MEASURE, _("Measure"), 11, measure_to_stock_id,
1389                     measure_to_string (MEASURE_NOMINAL), MEASURE_NOMINAL,
1390                     measure_to_string (MEASURE_ORDINAL), MEASURE_ORDINAL,
1391                     measure_to_string (MEASURE_SCALE), MEASURE_SCALE,
1392                     NULL);
1393
1394   add_combo_column (obj, VS_ROLE, _("Role"), 11, role_to_stock_id,
1395                     var_role_to_string (ROLE_INPUT), ROLE_INPUT,
1396                     var_role_to_string (ROLE_TARGET), ROLE_TARGET,
1397                     var_role_to_string (ROLE_BOTH), ROLE_BOTH,
1398                     var_role_to_string (ROLE_NONE), ROLE_NONE,
1399                     var_role_to_string (ROLE_PARTITION), ROLE_PARTITION,
1400                     var_role_to_string (ROLE_SPLIT), ROLE_SPLIT,
1401                     NULL);
1402
1403   pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
1404   pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
1405                                  PSPP_SHEET_SELECTION_MULTIPLE);
1406
1407   g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, NULL);
1408   g_signal_connect (obj, "query-tooltip",
1409                     G_CALLBACK (on_query_var_tooltip), NULL);
1410   g_signal_connect (obj, "button-press-event",
1411                     G_CALLBACK (on_button_pressed), NULL);
1412
1413   g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
1414
1415   g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
1416                     "changed", G_CALLBACK (on_selection_changed), NULL);
1417 }
1418
1419 GtkWidget *
1420 psppire_var_sheet_new (void)
1421 {
1422   return g_object_new (PSPPIRE_VAR_SHEET_TYPE, NULL);
1423 }
1424
1425 PsppireDict *
1426 psppire_var_sheet_get_dictionary (PsppireVarSheet *var_sheet)
1427 {
1428   return var_sheet->dict;
1429 }
1430
1431 static void
1432 refresh_model (PsppireVarSheet *var_sheet)
1433 {
1434   pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet), NULL);
1435
1436   if (var_sheet->dict != NULL)
1437     {
1438       PsppireEmptyListStore *store;
1439       int n_rows;
1440
1441       n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1442                 + var_sheet->may_create_vars);
1443       store = psppire_empty_list_store_new (n_rows);
1444       pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet),
1445                                  GTK_TREE_MODEL (store));
1446       g_object_unref (store);
1447     }
1448 }
1449
1450 static void
1451 on_var_changed (PsppireDict *dict, glong row,
1452                 guint what, const struct variable *oldvar,
1453                 PsppireVarSheet *var_sheet)
1454 {
1455   PsppireEmptyListStore *store;
1456
1457   g_return_if_fail (dict == var_sheet->dict);
1458
1459   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1460                                       PSPP_SHEET_VIEW (var_sheet)));
1461   g_return_if_fail (store != NULL);
1462
1463   psppire_empty_list_store_row_changed (store, row);
1464 }
1465
1466 static void
1467 on_var_inserted (PsppireDict *dict, glong row, PsppireVarSheet *var_sheet)
1468 {
1469   PsppireEmptyListStore *store;
1470   int n_rows;
1471
1472   g_return_if_fail (dict == var_sheet->dict);
1473
1474   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1475                                       PSPP_SHEET_VIEW (var_sheet)));
1476   g_return_if_fail (store != NULL);
1477
1478   n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1479             + var_sheet->may_create_vars);
1480   psppire_empty_list_store_set_n_rows (store, n_rows);
1481   psppire_empty_list_store_row_inserted (store, row);
1482 }
1483
1484 static void
1485 on_var_deleted (PsppireDict *dict,
1486                 const struct variable *var, int dict_idx, int case_idx,
1487                 PsppireVarSheet *var_sheet)
1488 {
1489   PsppireEmptyListStore *store;
1490   int n_rows;
1491
1492   g_return_if_fail (dict == var_sheet->dict);
1493
1494   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1495                                       PSPP_SHEET_VIEW (var_sheet)));
1496   g_return_if_fail (store != NULL);
1497
1498   n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1499             + var_sheet->may_create_vars);
1500   psppire_empty_list_store_set_n_rows (store, n_rows);
1501   psppire_empty_list_store_row_deleted (store, dict_idx);
1502 }
1503
1504 static void
1505 on_backend_changed (PsppireDict *dict, PsppireVarSheet *var_sheet)
1506 {
1507   g_return_if_fail (dict == var_sheet->dict);
1508   refresh_model (var_sheet);
1509 }
1510
1511 void
1512 psppire_var_sheet_set_dictionary (PsppireVarSheet *var_sheet,
1513                                   PsppireDict *dict)
1514 {
1515   if (var_sheet->dict != NULL)
1516     {
1517       int i;
1518
1519       for (i = 0; i < PSPPIRE_VAR_SHEET_N_SIGNALS; i++)
1520         {
1521           if (var_sheet->dict_signals[i])
1522             g_signal_handler_disconnect (var_sheet->dict,
1523                                          var_sheet->dict_signals[i]);
1524
1525           var_sheet->dict_signals[i] = 0;
1526         }
1527
1528       g_object_unref (var_sheet->dict);
1529     }
1530
1531   var_sheet->dict = dict;
1532
1533   if (dict != NULL)
1534     {
1535       g_object_ref (dict);
1536
1537       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_BACKEND_CHANGED]
1538         = g_signal_connect (dict, "backend-changed",
1539                             G_CALLBACK (on_backend_changed), var_sheet);
1540
1541       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_CHANGED]
1542         = g_signal_connect (dict, "variable-changed",
1543                             G_CALLBACK (on_var_changed), var_sheet);
1544
1545       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_DELETED]
1546         = g_signal_connect (dict, "variable-inserted",
1547                             G_CALLBACK (on_var_inserted), var_sheet);
1548
1549       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_INSERTED]
1550         = g_signal_connect (dict, "variable-deleted",
1551                             G_CALLBACK (on_var_deleted), var_sheet);
1552     }
1553
1554   refresh_model (var_sheet);
1555 }
1556
1557 gboolean
1558 psppire_var_sheet_get_may_create_vars (PsppireVarSheet *var_sheet)
1559 {
1560   return var_sheet->may_create_vars;
1561 }
1562
1563 void
1564 psppire_var_sheet_set_may_create_vars (PsppireVarSheet *var_sheet,
1565                                        gboolean may_create_vars)
1566 {
1567   if (var_sheet->may_create_vars != may_create_vars)
1568     {
1569       PsppireEmptyListStore *store;
1570       gint n_rows;
1571
1572       var_sheet->may_create_vars = may_create_vars;
1573
1574       store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1575                                           PSPP_SHEET_VIEW (var_sheet)));
1576       g_return_if_fail (store != NULL);
1577
1578       n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1579                 + var_sheet->may_create_vars);
1580       psppire_empty_list_store_set_n_rows (store, n_rows);
1581
1582       if (may_create_vars)
1583         psppire_empty_list_store_row_inserted (store, n_rows - 1);
1584       else
1585         psppire_empty_list_store_row_deleted (store, n_rows);
1586
1587       on_selection_changed (pspp_sheet_view_get_selection (
1588                               PSPP_SHEET_VIEW (var_sheet)), NULL);
1589     }
1590 }
1591
1592 gboolean
1593 psppire_var_sheet_get_may_delete_vars (PsppireVarSheet *var_sheet)
1594 {
1595   return var_sheet->may_delete_vars;
1596 }
1597
1598 void
1599 psppire_var_sheet_set_may_delete_vars (PsppireVarSheet *var_sheet,
1600                                        gboolean may_delete_vars)
1601 {
1602   if (var_sheet->may_delete_vars != may_delete_vars)
1603     {
1604       var_sheet->may_delete_vars = may_delete_vars;
1605       on_selection_changed (pspp_sheet_view_get_selection (
1606                               PSPP_SHEET_VIEW (var_sheet)), NULL);
1607     }
1608 }
1609
1610 void
1611 psppire_var_sheet_goto_variable (PsppireVarSheet *var_sheet, int dict_index)
1612 {
1613   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1614   GtkTreePath *path;
1615
1616   path = gtk_tree_path_new_from_indices (dict_index, -1);
1617   pspp_sheet_view_scroll_to_cell (sheet_view, path, NULL, FALSE, 0.0, 0.0);
1618   pspp_sheet_view_set_cursor (sheet_view, path, NULL, FALSE);
1619   gtk_tree_path_free (path);
1620 }
1621
1622