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