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