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