1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2011, 2012 Free Software Foundation, Inc.
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.
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.
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/>. */
19 #include "ui/gui/psppire-data-sheet.h"
21 #include "data/case-map.h"
22 #include "data/casereader.h"
23 #include "data/casewriter.h"
24 #include "data/data-out.h"
25 #include "data/datasheet.h"
26 #include "data/format.h"
27 #include "data/value-labels.h"
28 #include "libpspp/intern.h"
29 #include "libpspp/range-set.h"
30 #include "ui/gui/executor.h"
31 #include "ui/gui/find-dialog.h"
32 #include "ui/gui/goto-case-dialog.h"
33 #include "ui/gui/builder-wrapper.h"
34 #include "ui/gui/helper.h"
35 #include "ui/gui/pspp-sheet-selection.h"
36 #include "ui/gui/psppire-cell-renderer-button.h"
37 #include "ui/gui/psppire-data-store.h"
38 #include "ui/gui/psppire-data-window.h"
39 #include "ui/gui/psppire-dialog-action-var-info.h"
40 #include "ui/gui/psppire-empty-list-store.h"
41 #include "ui/gui/psppire-marshal.h"
43 #include "gl/intprops.h"
44 #include "gl/xalloc.h"
47 #define _(msgid) gettext (msgid)
48 #define N_(msgid) msgid
50 static void psppire_data_sheet_dispose (GObject *);
51 static void psppire_data_sheet_unset_data_store (PsppireDataSheet *);
53 static void psppire_data_sheet_update_clip_actions (PsppireDataSheet *);
54 static void psppire_data_sheet_set_clip (PsppireDataSheet *, gboolean cut);
56 static void on_selection_changed (PsppSheetSelection *, gpointer);
57 static void on_owner_change (GtkClipboard *, GdkEventOwnerChange *, gpointer);
58 static void psppire_data_sheet_clip_received_cb (GtkClipboard *,
59 GtkSelectionData *, gpointer);
61 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, PSPP_TYPE_SHEET_VIEW);
64 get_tooltip_location (GtkWidget *widget, GtkTooltip *tooltip,
66 size_t *row, PsppSheetViewColumn **columnp)
68 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
72 PsppSheetViewColumn *tree_column;
73 GtkTreeModel *tree_model;
76 /* Check that WIDGET is really visible on the screen before we
77 do anything else. This is a bug fix for a sticky situation:
78 when text_data_import_assistant() returns, it frees the data
79 necessary to compose the tool tip message, but there may be
80 a tool tip under preparation at that point (even if there is
81 no visible tool tip) that will call back into us a little
82 bit later. Perhaps the correct solution to this problem is
83 to make the data related to the tool tips part of a GObject
84 that only gets destroyed when all references are released,
85 but this solution appears to be effective too. */
86 if (!gtk_widget_get_mapped (widget))
89 pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
91 if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
92 &path, &tree_column, NULL, NULL))
95 *columnp = tree_column;
97 pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, tree_column,
100 tree_model = pspp_sheet_view_get_model (tree_view);
101 ok = gtk_tree_model_get_iter (tree_model, &iter, path);
102 gtk_tree_path_free (path);
106 *row = GPOINTER_TO_INT (iter.user_data);
111 on_query_tooltip (GtkWidget *widget, gint wx, gint wy,
112 gboolean keyboard_mode UNUSED,
113 GtkTooltip *tooltip, gpointer data UNUSED)
115 PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
116 PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
117 PsppSheetViewColumn *column;
118 struct variable *var;
124 g_return_val_if_fail (data_store != NULL, FALSE);
126 if (!get_tooltip_location (widget, tooltip, wx, wy, &row, &column))
129 var = g_object_get_data (G_OBJECT (column), "variable");
132 if (g_object_get_data (G_OBJECT (column), "new-var-column") == NULL)
135 gtk_tooltip_set_text (tooltip,
136 _("Enter a number to add a new variable."));
139 else if (row >= datasheet_get_n_rows (data_store->datasheet))
141 gtk_tooltip_set_text (tooltip, _("Enter a number to add a new case."));
145 width = var_get_width (var);
147 value_init (&v, width);
148 datasheet_get_value (data_store->datasheet, row, var_get_case_index (var),
151 label = var_lookup_value_label (var, &v);
154 if (data_sheet->show_value_labels)
156 char *s = value_to_text (v, var);
157 gtk_tooltip_set_text (tooltip, s);
161 gtk_tooltip_set_text (tooltip, label);
163 value_destroy (&v, width);
165 return label != NULL;
169 render_row_number_cell (PsppSheetViewColumn *tree_column,
170 GtkCellRenderer *cell,
175 PsppireDataStore *store = store_;
176 GValue gvalue = { 0, };
179 row = GPOINTER_TO_INT (iter->user_data);
181 g_value_init (&gvalue, G_TYPE_INT);
182 g_value_set_int (&gvalue, row + 1);
183 g_object_set_property (G_OBJECT (cell), "label", &gvalue);
184 g_value_unset (&gvalue);
186 if (row < datasheet_get_n_rows (store->datasheet))
187 g_object_set (cell, "editable", TRUE, NULL);
189 g_object_set (cell, "editable", FALSE, NULL);
192 "slash", psppire_data_store_filtered (store, row),
197 on_row_number_clicked (PsppireCellRendererButton *button,
199 PsppSheetView *sheet_view)
201 PsppSheetSelection *selection;
204 path = gtk_tree_path_new_from_string (path_string);
206 selection = pspp_sheet_view_get_selection (sheet_view);
207 pspp_sheet_selection_unselect_all (selection);
208 pspp_sheet_selection_select_path (selection, path);
209 pspp_sheet_selection_select_all_columns (selection);
211 gtk_tree_path_free (path);
215 make_row_number_column (PsppireDataSheet *data_sheet,
216 PsppireDataStore *ds)
218 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
219 PsppSheetViewColumn *column;
220 GtkCellRenderer *renderer;
222 renderer = psppire_cell_renderer_button_new ();
223 g_object_set (renderer, "xalign", 1.0, NULL);
224 g_signal_connect (renderer, "clicked", G_CALLBACK (on_row_number_clicked),
227 column = pspp_sheet_view_column_new_with_attributes (_("Case"),
229 pspp_sheet_view_column_set_selectable (column, TRUE);
230 pspp_sheet_view_column_set_row_head (column, TRUE);
231 pspp_sheet_view_column_set_tabbable (column, FALSE);
232 pspp_sheet_view_column_set_clickable (column, TRUE);
233 pspp_sheet_view_column_set_cell_data_func (
234 column, renderer, render_row_number_cell, ds, NULL);
235 pspp_sheet_view_column_set_fixed_width (column, 50);
236 pspp_sheet_view_column_set_visible (column, data_sheet->show_case_numbers);
237 pspp_sheet_view_append_column (sheet_view, column);
241 render_data_cell (PsppSheetViewColumn *tree_column,
242 GtkCellRenderer *cell,
245 gpointer data_sheet_)
247 PsppireDataSheet *data_sheet = data_sheet_;
248 PsppireDataStore *store = psppire_data_sheet_get_data_store (data_sheet);
249 struct variable *var;
255 row = GPOINTER_TO_INT (iter->user_data);
256 var = g_object_get_data (G_OBJECT (tree_column), "variable");
258 string = psppire_data_store_get_string (store, row, var,
259 data_sheet->show_value_labels);
262 GValue gvalue = { 0 };
264 g_value_init (&gvalue, G_TYPE_STRING);
265 g_value_take_string (&gvalue, string);
266 g_object_set_property (G_OBJECT (cell), "text", &gvalue);
267 g_value_unset (&gvalue);
270 g_object_set (G_OBJECT (cell), "text", "", NULL);
272 switch (var_get_alignment (var))
274 case ALIGN_LEFT: xalign = 0.0; break;
275 case ALIGN_RIGHT: xalign = 1.0; break;
276 case ALIGN_CENTRE: xalign = 0.5; break;
277 default: xalign = 0.0; break;
286 get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
290 g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
291 gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
292 NULL, NULL, NULL, &width, NULL);
297 get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
304 ds_put_byte_multiple (&s, '0', char_cnt);
305 ds_put_byte (&s, ' ');
306 width = get_string_width (treeview, renderer, ds_cstr (&s));
313 on_data_column_editing_started (GtkCellRenderer *cell,
314 GtkCellEditable *editable,
318 PsppSheetViewColumn *column = g_object_get_data (G_OBJECT (cell), "column");
319 PsppireDataSheet *data_sheet = g_object_get_data (G_OBJECT (cell), "data-sheet");
320 PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
321 struct variable *var;
323 g_return_if_fail (column);
324 g_return_if_fail (data_sheet);
325 g_return_if_fail (data_store);
328 g_object_ref (editable);
329 g_object_set_data_full (G_OBJECT (cell), "data-sheet-editable",
330 editable, g_object_unref);
332 var = g_object_get_data (G_OBJECT (column), "variable");
333 g_return_if_fail (var);
335 if (var_has_value_labels (var) && GTK_IS_COMBO_BOX (editable))
337 const struct val_labs *labels = var_get_value_labels (var);
338 const struct val_lab *vl;
339 GtkListStore *list_store;
341 list_store = gtk_list_store_new (1, G_TYPE_STRING);
342 for (vl = val_labs_first (labels); vl != NULL;
343 vl = val_labs_next (labels, vl))
347 gtk_list_store_append (list_store, &iter);
348 gtk_list_store_set (list_store, &iter,
349 0, val_lab_get_label (vl),
353 gtk_combo_box_set_model (GTK_COMBO_BOX (editable),
354 GTK_TREE_MODEL (list_store));
355 g_object_unref (list_store);
360 scroll_to_bottom (GtkWidget *widget,
361 GtkRequisition *requisition,
362 gpointer unused UNUSED)
364 PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
365 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
366 GtkAdjustment *vadjust;
368 vadjust = pspp_sheet_view_get_vadjustment (sheet_view);
369 gtk_adjustment_set_value (vadjust, gtk_adjustment_get_upper (vadjust));
371 if (data_sheet->scroll_to_bottom_signal)
373 g_signal_handler_disconnect (data_sheet,
374 data_sheet->scroll_to_bottom_signal);
375 data_sheet->scroll_to_bottom_signal = 0;
380 on_data_column_edited (GtkCellRendererText *cell,
385 PsppSheetViewColumn *column = g_object_get_data (G_OBJECT (cell), "column");
386 PsppireDataSheet *data_sheet = g_object_get_data (G_OBJECT (cell), "data-sheet");
387 PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
388 GtkEditable *editable;
389 struct variable *var;
395 path = gtk_tree_path_new_from_string (path_string);
396 row = gtk_tree_path_get_indices (path)[0];
397 gtk_tree_path_free (path);
399 var = g_object_get_data (G_OBJECT (column), "variable");
401 new_row = row == psppire_data_store_get_case_count (data_store);
402 if (new_row && new_text[0] == '\0')
405 editable = g_object_steal_data (G_OBJECT (cell), "data-sheet-editable");
406 g_return_if_fail (editable != NULL);
407 is_val_lab = (GTK_IS_COMBO_BOX (editable)
408 && gtk_combo_box_get_active (GTK_COMBO_BOX (editable)) >= 0);
409 g_object_unref (editable);
411 psppire_data_store_set_string (data_store, new_text, row, var, is_val_lab);
413 if (new_row && !data_sheet->scroll_to_bottom_signal)
415 gtk_widget_queue_resize (GTK_WIDGET (data_sheet));
416 data_sheet->scroll_to_bottom_signal =
417 g_signal_connect (data_sheet, "size-request",
418 G_CALLBACK (scroll_to_bottom), NULL);
422 /* We could be more specific about what to redraw, if it seems
423 important for performance. */
424 gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
429 scroll_to_right (GtkWidget *widget,
430 PsppireDataSheet *data_sheet)
432 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
433 PsppSheetViewColumn *column, *prev;
434 GList *columns, *iter;
438 columns = pspp_sheet_view_get_columns (sheet_view);
439 for (iter = columns; iter; iter = iter->next)
441 PsppSheetViewColumn *c = iter->data;
442 if (g_object_get_data (G_OBJECT (c), "new-var-column"))
449 g_list_free (columns);
454 pspp_sheet_view_scroll_to_cell (sheet_view, NULL, column, FALSE, 0, 0);
460 pspp_sheet_view_get_cursor (sheet_view, &path, NULL);
463 pspp_sheet_view_set_cursor (sheet_view, path, prev, TRUE);
464 gtk_tree_path_free (path);
468 if (data_sheet->scroll_to_right_signal)
470 g_signal_handler_disconnect (widget, data_sheet->scroll_to_right_signal);
471 data_sheet->scroll_to_right_signal = 0;
476 on_new_variable_column_edited (GtkCellRendererText *cell,
481 PsppireDataSheet *data_sheet = g_object_get_data (G_OBJECT (cell), "data-sheet");
482 PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
483 PsppireDict *dict = data_store->dict;
484 struct variable *var;
489 if (new_text[0] == '\0')
491 /* User didn't enter anything so don't create a variable. */
495 path = gtk_tree_path_new_from_string (path_string);
496 row = gtk_tree_path_get_indices (path)[0];
497 gtk_tree_path_free (path);
499 if (!psppire_dict_generate_name (dict, name, sizeof name))
502 var = psppire_dict_insert_variable (dict, psppire_dict_get_var_cnt (dict),
504 g_return_if_fail (var != NULL);
506 psppire_data_store_set_string (data_store, new_text, row, var, FALSE);
508 if (!data_sheet->scroll_to_right_signal)
510 gtk_widget_queue_resize (GTK_WIDGET (data_sheet));
511 data_sheet->scroll_to_right_signal =
512 g_signal_connect_after (gtk_widget_get_toplevel (GTK_WIDGET (data_sheet)), "check-resize",
513 G_CALLBACK (scroll_to_right), data_sheet);
517 /* We could be more specific about what to redraw, if it seems
518 important for performance. */
519 gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
524 calc_width_conversion (PsppireDataSheet *data_sheet,
525 gint *base_width, gint *incr_width)
527 GtkCellRenderer *cell;
530 cell = gtk_cell_renderer_text_new ();
531 w1 = get_monospace_width (PSPP_SHEET_VIEW (data_sheet), cell, 1);
532 w10 = get_monospace_width (PSPP_SHEET_VIEW (data_sheet), cell, 10);
533 *incr_width = MAX (1, (w10 - w1) / 9);
534 *base_width = MAX (0, w10 - *incr_width * 10);
535 g_object_ref_sink (cell);
536 g_object_unref (cell);
540 display_width_from_pixel_width (PsppireDataSheet *data_sheet,
543 gint base_width, incr_width;
545 calc_width_conversion (data_sheet, &base_width, &incr_width);
546 return MAX ((pixel_width - base_width + incr_width / 2) / incr_width, 1);
550 display_width_to_pixel_width (PsppireDataSheet *data_sheet,
555 return base_width + incr_width * display_width;
559 on_data_column_resized (GObject *gobject,
563 PsppireDataSheet *data_sheet = user_data;
564 PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
565 PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (gobject);
566 struct variable *var;
570 if (data_store == NULL)
573 pixel_width = pspp_sheet_view_column_get_width (column);
574 if (pixel_width == pspp_sheet_view_column_get_fixed_width (column))
576 /* Short-circuit the expensive display_width_from_pixel_width()
577 calculation, to make loading .sav files with 2000 columns visibly
582 var = g_object_get_data (G_OBJECT (column), "variable");
583 display_width = display_width_from_pixel_width (data_sheet, pixel_width);
584 var_set_display_width (var, display_width);
588 do_data_column_popup_menu (PsppSheetViewColumn *column,
589 guint button, guint32 time)
591 GtkWidget *sheet_view = pspp_sheet_view_column_get_tree_view (column);
592 PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
595 menu = get_widget_assert (data_sheet->builder, "datasheet-variable-popup");
596 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
600 on_data_column_popup_menu (PsppSheetViewColumn *column,
601 gpointer user_data UNUSED)
603 do_data_column_popup_menu (column, 0, gtk_get_current_event_time ());
607 on_column_button_press_event (PsppSheetViewColumn *column,
608 GdkEventButton *event,
609 gpointer user_data UNUSED)
611 PsppSheetSelection *selection;
612 PsppSheetView *sheet_view;
614 sheet_view = PSPP_SHEET_VIEW (pspp_sheet_view_column_get_tree_view (
616 g_return_val_if_fail (sheet_view != NULL, FALSE);
618 selection = pspp_sheet_view_get_selection (sheet_view);
619 g_return_val_if_fail (selection != NULL, FALSE);
621 if (event->type == GDK_BUTTON_PRESS && event->button == 3)
623 do_data_column_popup_menu (column, event->button, event->time);
626 else if (event->type == GDK_2BUTTON_PRESS && event->button == 1)
628 PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
629 struct variable *var;
631 var = g_object_get_data (G_OBJECT (column), "variable");
636 g_signal_emit_by_name (data_sheet, "var-double-clicked",
637 var_get_dict_index (var), &handled);
646 on_data_column_query_tooltip (PsppSheetViewColumn *column,
648 gpointer user_data UNUSED)
650 struct variable *var;
653 var = g_object_get_data (G_OBJECT (column), "variable");
654 g_return_val_if_fail (var != NULL, FALSE);
656 text = var_has_label (var) ? var_get_label (var) : var_get_name (var);
657 gtk_tooltip_set_text (tooltip, text);
663 add_data_column_cell_renderer (PsppireDataSheet *data_sheet,
664 PsppSheetViewColumn *column)
666 GtkCellRenderer *cell;
667 struct variable *var;
669 var = g_object_get_data (G_OBJECT (column), "variable");
670 g_return_if_fail (var != NULL);
672 if (var_has_value_labels (var))
674 cell = gtk_cell_renderer_combo_new ();
675 g_object_set (G_OBJECT (cell),
681 cell = gtk_cell_renderer_text_new ();
683 g_signal_connect (cell, "editing-started",
684 G_CALLBACK (on_data_column_editing_started), NULL);
685 g_signal_connect (cell, "edited", G_CALLBACK (on_data_column_edited), NULL);
687 g_object_set_data (G_OBJECT (cell), "column", column);
688 g_object_set_data (G_OBJECT (cell), "data-sheet", data_sheet);
690 pspp_sheet_view_column_clear (column);
691 pspp_sheet_view_column_pack_start (column, cell, TRUE);
693 pspp_sheet_view_column_set_cell_data_func (
694 column, cell, render_data_cell, data_sheet, NULL);
697 static PsppSheetViewColumn *
698 make_data_column (PsppireDataSheet *data_sheet, gint dict_idx,
699 gint base_width, gint incr_width)
701 PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
702 struct variable *var;
703 PsppSheetViewColumn *column;
707 var = psppire_dict_get_variable (data_store->dict, dict_idx);
709 column = pspp_sheet_view_column_new ();
711 name = escape_underscores (var_get_name (var));
712 pspp_sheet_view_column_set_title (column, name);
715 g_object_set_data (G_OBJECT (column), "variable", var);
717 width = display_width_to_pixel_width (data_sheet,
718 var_get_display_width (var),
719 base_width, incr_width);
720 pspp_sheet_view_column_set_min_width (column, 10);
721 pspp_sheet_view_column_set_fixed_width (column, width);
722 pspp_sheet_view_column_set_resizable (column, TRUE);
724 pspp_sheet_view_column_set_clickable (column, TRUE);
725 g_signal_connect (column, "notify::width",
726 G_CALLBACK (on_data_column_resized), data_sheet);
728 g_signal_connect (column, "button-press-event",
729 G_CALLBACK (on_column_button_press_event),
731 g_signal_connect (column, "query-tooltip",
732 G_CALLBACK (on_data_column_query_tooltip), NULL);
733 g_signal_connect (column, "popup-menu",
734 G_CALLBACK (on_data_column_popup_menu), data_sheet);
736 add_data_column_cell_renderer (data_sheet, column);
742 make_new_variable_column (PsppireDataSheet *data_sheet,
743 gint base_width, gint incr_width)
745 PsppSheetViewColumn *column;
746 GtkCellRenderer *cell;
749 cell = gtk_cell_renderer_text_new ();
750 g_object_set (cell, "editable", TRUE, NULL);
752 g_signal_connect (cell, "edited", G_CALLBACK (on_new_variable_column_edited),
755 column = pspp_sheet_view_column_new_with_attributes ("", cell, NULL);
756 g_object_set_data (G_OBJECT (column), "new-var-column", column);
758 width = display_width_to_pixel_width (data_sheet, 8, base_width, incr_width);
759 pspp_sheet_view_column_set_min_width (column, 10);
760 pspp_sheet_view_column_set_fixed_width (column, width);
761 pspp_sheet_view_column_set_tabbable (column, FALSE);
763 g_object_set_data (G_OBJECT (cell), "data-sheet", data_sheet);
764 g_signal_connect (column, "button-press-event",
765 G_CALLBACK (on_column_button_press_event),
767 g_signal_connect (column, "popup-menu",
768 G_CALLBACK (on_data_column_popup_menu), data_sheet);
770 pspp_sheet_view_column_set_visible (column, data_sheet->may_create_vars);
772 pspp_sheet_view_append_column (PSPP_SHEET_VIEW (data_sheet), column);
773 data_sheet->new_variable_column = column;
777 psppire_data_sheet_model_changed (GObject *gobject,
781 PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (gobject);
782 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
783 PsppireDataStore *data_store;
785 /* Remove old columns. */
788 PsppSheetViewColumn *column = pspp_sheet_view_get_column (sheet_view, 0);
792 pspp_sheet_view_remove_column (sheet_view, column);
794 data_sheet->new_variable_column = NULL;
796 if (pspp_sheet_view_get_model (sheet_view) == NULL)
798 /* Don't create any columns at all if there's no model. Otherwise we'll
799 create some columns as part of the "dispose" callback for the sheet
800 view, which sets the model to NULL. That causes warnings to be
801 logged and is obviously undesirable in any case. */
805 /* Add new columns. */
806 data_store = psppire_data_sheet_get_data_store (data_sheet);
807 if (data_store != NULL)
809 gint base_width, incr_width;
812 calc_width_conversion (data_sheet, &base_width, &incr_width);
814 make_row_number_column (data_sheet, data_store);
815 for (i = 0; i < psppire_dict_get_var_cnt (data_store->dict); i++)
817 PsppSheetViewColumn *column;
819 column = make_data_column (data_sheet, i, base_width, incr_width);
820 pspp_sheet_view_append_column (sheet_view, column);
822 make_new_variable_column (data_sheet, base_width, incr_width);
833 PROP_MAY_CREATE_VARS,
834 PROP_MAY_DELETE_VARS,
839 psppire_data_sheet_set_property (GObject *object,
844 PsppireDataSheet *obj = PSPPIRE_DATA_SHEET (object);
848 case PROP_DATA_STORE:
849 psppire_data_sheet_set_data_store (
850 obj, PSPPIRE_DATA_STORE (g_value_get_object (value)));
853 case PROP_VALUE_LABELS:
854 psppire_data_sheet_set_value_labels (obj, g_value_get_boolean (value));
857 case PROP_CASE_NUMBERS:
858 psppire_data_sheet_set_case_numbers (obj, g_value_get_boolean (value));
861 case PROP_CURRENT_CASE:
862 psppire_data_sheet_goto_case (obj, g_value_get_long (value));
865 case PROP_MAY_CREATE_VARS:
866 psppire_data_sheet_set_may_create_vars (obj,
867 g_value_get_boolean (value));
870 case PROP_MAY_DELETE_VARS:
871 psppire_data_sheet_set_may_delete_vars (obj,
872 g_value_get_boolean (value));
875 case PROP_UI_MANAGER:
877 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
883 psppire_data_sheet_get_property (GObject *object,
888 PsppireDataSheet *obj = PSPPIRE_DATA_SHEET (object);
892 case PROP_DATA_STORE:
893 g_value_set_object (value, psppire_data_sheet_get_data_store (obj));
896 case PROP_VALUE_LABELS:
897 g_value_set_boolean (value, psppire_data_sheet_get_value_labels (obj));
900 case PROP_CASE_NUMBERS:
901 g_value_set_boolean (value, psppire_data_sheet_get_case_numbers (obj));
904 case PROP_CURRENT_CASE:
905 g_value_set_long (value, psppire_data_sheet_get_selected_case (obj));
908 case PROP_MAY_CREATE_VARS:
909 g_value_set_boolean (value, obj->may_create_vars);
912 case PROP_MAY_DELETE_VARS:
913 g_value_set_boolean (value, obj->may_delete_vars);
916 case PROP_UI_MANAGER:
917 g_value_set_object (value, psppire_data_sheet_get_ui_manager (obj));
921 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
927 psppire_data_sheet_get_value_labels (const PsppireDataSheet *ds)
929 return ds->show_value_labels;
933 psppire_data_sheet_set_value_labels (PsppireDataSheet *ds,
934 gboolean show_value_labels)
936 show_value_labels = !!show_value_labels;
937 if (show_value_labels != ds->show_value_labels)
939 ds->show_value_labels = show_value_labels;
940 g_object_notify (G_OBJECT (ds), "value-labels");
941 gtk_widget_queue_draw (GTK_WIDGET (ds));
943 /* Make the cell being edited refresh too. */
944 pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (ds), TRUE);
949 psppire_data_sheet_get_case_numbers (const PsppireDataSheet *ds)
951 return ds->show_case_numbers;
955 psppire_data_sheet_set_case_numbers (PsppireDataSheet *ds,
956 gboolean show_case_numbers)
958 show_case_numbers = !!show_case_numbers;
959 if (show_case_numbers != ds->show_case_numbers)
961 PsppSheetViewColumn *column;
963 ds->show_case_numbers = show_case_numbers;
964 column = pspp_sheet_view_get_column (PSPP_SHEET_VIEW (ds), 0);
966 pspp_sheet_view_column_set_visible (column, show_case_numbers);
968 g_object_notify (G_OBJECT (ds), "case-numbers");
969 gtk_widget_queue_draw (GTK_WIDGET (ds));
974 psppire_data_sheet_get_may_create_vars (PsppireDataSheet *data_sheet)
976 return data_sheet->may_create_vars;
980 psppire_data_sheet_set_may_create_vars (PsppireDataSheet *data_sheet,
981 gboolean may_create_vars)
983 if (data_sheet->may_create_vars != may_create_vars)
985 data_sheet->may_create_vars = may_create_vars;
986 if (data_sheet->new_variable_column)
987 pspp_sheet_view_column_set_visible (data_sheet->new_variable_column,
990 on_selection_changed (pspp_sheet_view_get_selection (
991 PSPP_SHEET_VIEW (data_sheet)), NULL);
996 psppire_data_sheet_get_may_delete_vars (PsppireDataSheet *data_sheet)
998 return data_sheet->may_delete_vars;
1002 psppire_data_sheet_set_may_delete_vars (PsppireDataSheet *data_sheet,
1003 gboolean may_delete_vars)
1005 if (data_sheet->may_delete_vars != may_delete_vars)
1007 data_sheet->may_delete_vars = may_delete_vars;
1008 on_selection_changed (pspp_sheet_view_get_selection (
1009 PSPP_SHEET_VIEW (data_sheet)), NULL);
1013 static PsppSheetViewColumn *
1014 psppire_data_sheet_find_column_for_variable (PsppireDataSheet *data_sheet,
1017 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1018 PsppireDataStore *data_store;
1019 PsppSheetViewColumn *column;
1020 struct variable *var;
1023 data_store = psppire_data_sheet_get_data_store (data_sheet);
1024 g_return_val_if_fail (data_store != NULL, NULL);
1025 g_return_val_if_fail (data_store->dict != NULL, NULL);
1027 var = psppire_dict_get_variable (data_store->dict, dict_index);
1028 g_return_val_if_fail (var != NULL, NULL);
1031 list = pspp_sheet_view_get_columns (sheet_view);
1032 for (iter = list; iter != NULL; iter = iter->next)
1034 PsppSheetViewColumn *c = iter->data;
1037 v = g_object_get_data (G_OBJECT (c), "variable");
1050 psppire_data_sheet_goto_variable (PsppireDataSheet *data_sheet,
1053 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1054 PsppSheetViewColumn *column;
1056 column = psppire_data_sheet_find_column_for_variable (data_sheet,
1062 gint row = psppire_data_sheet_get_current_case (data_sheet);
1063 path = gtk_tree_path_new_from_indices (row >= 0 ? row : 0, -1);
1065 pspp_sheet_view_scroll_to_cell (sheet_view, path, column,
1067 pspp_sheet_view_set_cursor (sheet_view, path, column, FALSE);
1068 gtk_tree_path_free (path);
1073 psppire_data_sheet_get_current_variable (const PsppireDataSheet *data_sheet)
1075 PsppSheetSelection *selection;
1076 struct variable *var;
1077 GList *selected_columns;
1080 selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (data_sheet));
1081 selected_columns = pspp_sheet_selection_get_selected_columns (selection);
1084 for (iter = selected_columns; iter != NULL; iter = iter->next)
1086 PsppSheetViewColumn *column = iter->data;
1087 struct variable *v = g_object_get_data (G_OBJECT (column), "variable");
1100 g_list_free (selected_columns);
1106 psppire_data_sheet_goto_case (PsppireDataSheet *data_sheet, gint case_index)
1108 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1109 PsppireDataStore *store = data_sheet->data_store;
1110 PsppSheetSelection *selection;
1113 g_return_if_fail (case_index >= 0);
1114 g_return_if_fail (case_index < psppire_data_store_get_case_count (store));
1116 path = gtk_tree_path_new_from_indices (case_index, -1);
1118 /* Select the case. */
1119 selection = pspp_sheet_view_get_selection (sheet_view);
1120 pspp_sheet_selection_unselect_all (selection);
1121 pspp_sheet_selection_select_path (selection, path);
1122 pspp_sheet_selection_select_all_columns (selection);
1124 /* Scroll so that the case is visible. */
1125 pspp_sheet_view_scroll_to_cell (sheet_view, path, NULL, FALSE, 0.0, 0.0);
1127 gtk_tree_path_free (path);
1130 /* Returns the 0-based index of a selected case, if there is at least one, and
1133 If more than one case is selected, returns the one with the smallest index,
1134 that is, the index of the case closest to the beginning of the file. The
1135 row that can be used to insert a new case is not considered a case. */
1137 psppire_data_sheet_get_selected_case (const PsppireDataSheet *data_sheet)
1139 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1140 PsppireDataStore *store = data_sheet->data_store;
1141 const struct range_set_node *node;
1142 PsppSheetSelection *selection;
1143 struct range_set *rows;
1146 selection = pspp_sheet_view_get_selection (sheet_view);
1147 rows = pspp_sheet_selection_get_range_set (selection);
1148 node = range_set_first (rows);
1149 row = (node && node->start < psppire_data_store_get_case_count (store)
1152 range_set_destroy (rows);
1157 /* Returns the 0-based index of a selected case, if exactly one case is
1158 selected, and -1 otherwise. Returns -1 if the row that can be used to
1159 insert a new case is selected. */
1161 psppire_data_sheet_get_current_case (const PsppireDataSheet *data_sheet)
1163 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1164 PsppireDataStore *store = data_sheet->data_store;
1165 const struct range_set_node *node;
1166 PsppSheetSelection *selection;
1167 struct range_set *rows;
1170 selection = pspp_sheet_view_get_selection (sheet_view);
1171 if (pspp_sheet_selection_count_selected_rows (selection) != 1)
1174 rows = pspp_sheet_selection_get_range_set (selection);
1175 node = range_set_first (rows);
1176 row = (node && node->start < psppire_data_store_get_case_count (store)
1179 range_set_destroy (rows);
1185 psppire_data_sheet_get_ui_manager (PsppireDataSheet *data_sheet)
1187 if (data_sheet->uim == NULL)
1190 GTK_UI_MANAGER (get_object_assert (data_sheet->builder,
1192 GTK_TYPE_UI_MANAGER));
1193 g_object_ref (data_sheet->uim);
1196 return data_sheet->uim;
1200 psppire_data_sheet_dispose (GObject *object)
1202 PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (object);
1204 if (data_sheet->dispose_has_run)
1207 data_sheet->dispose_has_run = TRUE;
1209 psppire_data_sheet_unset_data_store (data_sheet);
1211 g_object_unref (data_sheet->builder);
1213 if (data_sheet->uim)
1214 g_object_unref (data_sheet->uim);
1216 G_OBJECT_CLASS (psppire_data_sheet_parent_class)->dispose (object);
1220 psppire_data_sheet_map (GtkWidget *widget)
1224 GTK_WIDGET_CLASS (psppire_data_sheet_parent_class)->map (widget);
1226 clip = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
1227 g_signal_connect (clip, "owner-change", G_CALLBACK (on_owner_change),
1229 on_owner_change (clip, NULL, widget);
1233 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
1235 GObjectClass *gobject_class;
1236 GtkWidgetClass *widget_class;
1238 gobject_class = G_OBJECT_CLASS (class);
1239 gobject_class->set_property = psppire_data_sheet_set_property;
1240 gobject_class->get_property = psppire_data_sheet_get_property;
1241 gobject_class->dispose = psppire_data_sheet_dispose;
1243 widget_class = GTK_WIDGET_CLASS (class);
1244 widget_class->map = psppire_data_sheet_map;
1246 g_signal_new ("var-double-clicked",
1247 G_OBJECT_CLASS_TYPE (gobject_class),
1250 g_signal_accumulator_true_handled, NULL,
1251 psppire_marshal_BOOLEAN__INT,
1252 G_TYPE_BOOLEAN, 1, G_TYPE_INT);
1254 g_object_class_install_property (
1255 gobject_class, PROP_DATA_STORE,
1256 g_param_spec_object ("data-store",
1258 "The data store for the data sheet to display.",
1259 PSPPIRE_TYPE_DATA_STORE,
1260 G_PARAM_WRITABLE | G_PARAM_READABLE));
1262 g_object_class_install_property (
1263 gobject_class, PROP_VALUE_LABELS,
1264 g_param_spec_boolean ("value-labels",
1266 "Whether or not the data sheet should display labels instead of values",
1268 G_PARAM_WRITABLE | G_PARAM_READABLE));
1270 g_object_class_install_property (
1271 gobject_class, PROP_CASE_NUMBERS,
1272 g_param_spec_boolean ("case-numbers",
1274 "Whether or not the data sheet should display case numbers",
1276 G_PARAM_WRITABLE | G_PARAM_READABLE));
1278 g_object_class_install_property (
1281 g_param_spec_long ("current-case",
1283 "Zero based number of the selected case",
1286 G_PARAM_WRITABLE | G_PARAM_READABLE));
1288 g_object_class_install_property (
1290 PROP_MAY_CREATE_VARS,
1291 g_param_spec_boolean ("may-create-vars",
1292 "May create variables",
1293 "Whether the user may create more variables",
1295 G_PARAM_READWRITE));
1297 g_object_class_install_property (
1299 PROP_MAY_DELETE_VARS,
1300 g_param_spec_boolean ("may-delete-vars",
1301 "May delete variables",
1302 "Whether the user may delete variables",
1304 G_PARAM_READWRITE));
1306 g_object_class_install_property (
1309 g_param_spec_object ("ui-manager",
1311 "UI manager for the data sheet. The client should merge this UI manager with the active UI manager to obtain data sheet specific menu items and tool bar items.",
1312 GTK_TYPE_UI_MANAGER,
1317 do_popup_menu (GtkWidget *widget, guint button, guint32 time)
1319 PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
1322 menu = get_widget_assert (data_sheet->builder, "datasheet-cases-popup");
1323 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
1327 on_popup_menu (GtkWidget *widget, gpointer user_data UNUSED)
1329 do_popup_menu (widget, 0, gtk_get_current_event_time ());
1333 on_button_pressed (GtkWidget *widget, GdkEventButton *event,
1334 gpointer user_data UNUSED)
1336 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
1338 if (event->type == GDK_BUTTON_PRESS && event->button == 3)
1340 PsppSheetSelection *selection;
1342 selection = pspp_sheet_view_get_selection (sheet_view);
1343 if (pspp_sheet_selection_count_selected_rows (selection) <= 1)
1347 if (pspp_sheet_view_get_path_at_pos (sheet_view, event->x, event->y,
1348 &path, NULL, NULL, NULL))
1350 pspp_sheet_selection_unselect_all (selection);
1351 pspp_sheet_selection_select_path (selection, path);
1352 pspp_sheet_selection_select_all_columns (selection);
1353 gtk_tree_path_free (path);
1357 do_popup_menu (widget, event->button, event->time);
1366 on_edit_clear_cases (GtkAction *action, PsppireDataSheet *data_sheet)
1368 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1369 PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1370 const struct range_set_node *node;
1371 struct range_set *selected;
1373 selected = pspp_sheet_selection_get_range_set (selection);
1374 for (node = range_set_last (selected); node != NULL;
1375 node = range_set_prev (selected, node))
1377 unsigned long int start = range_set_node_get_start (node);
1378 unsigned long int count = range_set_node_get_width (node);
1380 psppire_data_store_delete_cases (data_sheet->data_store, start, count);
1382 range_set_destroy (selected);
1386 on_selection_changed (PsppSheetSelection *selection,
1387 gpointer user_data UNUSED)
1389 PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
1390 PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
1391 gint n_selected_rows;
1392 gboolean may_delete_cases, may_delete_vars, may_insert_vars;
1397 n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
1399 action = get_action_assert (data_sheet->builder, "edit_insert-case");
1400 gtk_action_set_sensitive (action, n_selected_rows > 0);
1402 switch (n_selected_rows)
1405 may_delete_cases = FALSE;
1409 /* The row used for inserting new cases cannot be deleted. */
1410 path = gtk_tree_path_new_from_indices (
1411 psppire_data_store_get_case_count (data_sheet->data_store), -1);
1412 may_delete_cases = !pspp_sheet_selection_path_is_selected (selection,
1414 gtk_tree_path_free (path);
1418 may_delete_cases = TRUE;
1421 action = get_action_assert (data_sheet->builder, "edit_clear-cases");
1422 gtk_action_set_sensitive (action, may_delete_cases);
1424 may_delete_vars = may_insert_vars = FALSE;
1425 list = pspp_sheet_selection_get_selected_columns (selection);
1426 for (iter = list; iter != NULL; iter = iter->next)
1428 PsppSheetViewColumn *column = iter->data;
1429 struct variable *var = g_object_get_data (G_OBJECT (column), "variable");
1433 may_delete_vars = may_insert_vars = TRUE;
1436 if (g_object_get_data (G_OBJECT (column), "new-var-column") != NULL)
1437 may_insert_vars = TRUE;
1441 may_insert_vars = may_insert_vars && data_sheet->may_create_vars;
1442 may_delete_vars = may_delete_vars && data_sheet->may_delete_vars;
1444 action = get_action_assert (data_sheet->builder, "edit_insert-variable");
1445 gtk_action_set_sensitive (action, may_insert_vars);
1447 action = get_action_assert (data_sheet->builder, "edit_clear-variables");
1448 gtk_action_set_sensitive (action, may_delete_vars);
1450 action = get_action_assert (data_sheet->builder, "sort-up");
1451 gtk_action_set_sensitive (action, may_delete_vars);
1453 action = get_action_assert (data_sheet->builder, "sort-down");
1454 gtk_action_set_sensitive (action, may_delete_vars);
1456 psppire_data_sheet_update_clip_actions (data_sheet);
1460 psppire_data_sheet_get_selected_range (PsppireDataSheet *data_sheet,
1461 struct range_set **rowsp,
1462 struct range_set **colsp)
1464 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1465 PsppireDataStore *data_store = data_sheet->data_store;
1466 PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1467 unsigned long n_cases;
1468 struct range_set *rows, *cols;
1471 if (data_store == NULL)
1473 n_cases = psppire_data_store_get_case_count (data_store);
1475 rows = pspp_sheet_selection_get_range_set (selection);
1476 range_set_set0 (rows, n_cases, ULONG_MAX - n_cases);
1477 if (range_set_is_empty (rows))
1479 range_set_destroy (rows);
1483 cols = range_set_create ();
1484 list = pspp_sheet_selection_get_selected_columns (selection);
1485 for (iter = list; iter != NULL; iter = iter->next)
1487 PsppSheetViewColumn *column = iter->data;
1488 struct variable *var = g_object_get_data (G_OBJECT (column), "variable");
1491 range_set_set1 (cols, var_get_dict_index (var), 1);
1494 if (range_set_is_empty (cols))
1496 range_set_destroy (rows);
1497 range_set_destroy (cols);
1507 on_edit_insert_case (GtkAction *action, PsppireDataSheet *data_sheet)
1509 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1510 PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1511 PsppireDataStore *data_store = data_sheet->data_store;
1512 struct range_set *selected;
1515 selected = pspp_sheet_selection_get_range_set (selection);
1516 row = range_set_scan (selected, 0);
1517 range_set_destroy (selected);
1519 if (row <= psppire_data_store_get_case_count (data_store))
1520 psppire_data_store_insert_new_case (data_store, row);
1524 on_edit_insert_variable (GtkAction *action, PsppireDataSheet *data_sheet)
1526 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1527 PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1528 PsppireDict *dict = data_sheet->data_store->dict;
1529 PsppSheetViewColumn *column;
1530 struct variable *var;
1535 list = pspp_sheet_selection_get_selected_columns (selection);
1538 column = list->data;
1541 var = g_object_get_data (G_OBJECT (column), "variable");
1542 index = var ? var_get_dict_index (var) : psppire_dict_get_var_cnt (dict);
1543 if (psppire_dict_generate_name (dict, name, sizeof name))
1544 psppire_dict_insert_variable (dict, index, name);
1548 on_edit_clear_variables (GtkAction *action, PsppireDataSheet *data_sheet)
1550 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1551 PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1552 PsppireDict *dict = data_sheet->data_store->dict;
1555 list = pspp_sheet_selection_get_selected_columns (selection);
1558 list = g_list_reverse (list);
1559 for (iter = list; iter; iter = iter->next)
1561 PsppSheetViewColumn *column = iter->data;
1562 struct variable *var;
1564 var = g_object_get_data (G_OBJECT (column), "variable");
1566 psppire_dict_delete_variables (dict, var_get_dict_index (var), 1);
1578 do_sort (PsppireDataSheet *data_sheet, enum sort_order order)
1580 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1581 PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1582 PsppireDataWindow *pdw;
1587 pdw = psppire_data_window_for_data_store (data_sheet->data_store);
1588 g_return_if_fail (pdw != NULL);
1590 list = pspp_sheet_selection_get_selected_columns (selection);
1592 syntax = g_string_new ("SORT CASES BY");
1594 for (iter = list; iter; iter = iter->next)
1596 PsppSheetViewColumn *column = iter->data;
1597 struct variable *var;
1599 var = g_object_get_data (G_OBJECT (column), "variable");
1602 g_string_append_printf (syntax, " %s", var_get_name (var));
1608 if (order == SORT_DESCEND)
1609 g_string_append (syntax, " (DOWN)");
1610 g_string_append_c (syntax, '.');
1611 execute_const_syntax_string (pdw, syntax->str);
1613 g_string_free (syntax, TRUE);
1617 on_sort_up (GtkAction *action, PsppireDataSheet *data_sheet)
1619 do_sort (data_sheet, SORT_ASCEND);
1623 on_sort_down (GtkAction *action, PsppireDataSheet *data_sheet)
1625 do_sort (data_sheet, SORT_DESCEND);
1629 on_edit_goto_case (GtkAction *action, PsppireDataSheet *data_sheet)
1631 goto_case_dialog (data_sheet);
1635 on_edit_find (GtkAction *action, PsppireDataSheet *data_sheet)
1637 PsppireDataWindow *pdw;
1639 pdw = psppire_data_window_for_data_store (data_sheet->data_store);
1640 g_return_if_fail (pdw != NULL);
1646 on_edit_copy (GtkAction *action, PsppireDataSheet *data_sheet)
1648 psppire_data_sheet_set_clip (data_sheet, FALSE);
1652 on_edit_cut (GtkAction *action, PsppireDataSheet *data_sheet)
1654 psppire_data_sheet_set_clip (data_sheet, TRUE);
1658 on_edit_paste (GtkAction *action, PsppireDataSheet *data_sheet)
1660 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (data_sheet));
1661 GtkClipboard *clipboard =
1662 gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD);
1664 gtk_clipboard_request_contents (clipboard,
1665 gdk_atom_intern ("UTF8_STRING", TRUE),
1666 psppire_data_sheet_clip_received_cb,
1671 psppire_data_sheet_init (PsppireDataSheet *obj)
1673 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
1676 obj->show_value_labels = FALSE;
1677 obj->show_case_numbers = TRUE;
1678 obj->may_create_vars = TRUE;
1679 obj->may_delete_vars = TRUE;
1681 obj->scroll_to_bottom_signal = 0;
1682 obj->scroll_to_right_signal = 0;
1683 obj->new_variable_column = NULL;
1684 obj->container = NULL;
1687 obj->dispose_has_run = FALSE;
1689 pspp_sheet_view_set_special_cells (sheet_view, PSPP_SHEET_VIEW_SPECIAL_CELLS_YES);
1691 g_signal_connect (obj, "notify::model",
1692 G_CALLBACK (psppire_data_sheet_model_changed), NULL);
1694 pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
1695 pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
1696 PSPP_SHEET_SELECTION_RECTANGLE);
1698 g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, (void *) NULL);
1699 g_signal_connect (obj, "query-tooltip",
1700 G_CALLBACK (on_query_tooltip), NULL);
1701 g_signal_connect (obj, "button-press-event",
1702 G_CALLBACK (on_button_pressed), NULL);
1703 g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
1705 obj->builder = builder_new ("data-sheet.ui");
1707 action = get_action_assert (obj->builder, "edit_clear-cases");
1708 g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_cases),
1710 gtk_action_set_sensitive (action, FALSE);
1711 g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
1712 "changed", G_CALLBACK (on_selection_changed), NULL);
1714 action = get_action_assert (obj->builder, "edit_insert-case");
1715 g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_case),
1718 action = get_action_assert (obj->builder, "edit_insert-variable");
1719 g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_variable),
1722 action = get_action_assert (obj->builder, "edit_goto-case");
1723 g_signal_connect (action, "activate", G_CALLBACK (on_edit_goto_case),
1726 action = get_action_assert (obj->builder, "edit_copy");
1727 g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy), obj);
1729 action = get_action_assert (obj->builder, "edit_cut");
1730 g_signal_connect (action, "activate", G_CALLBACK (on_edit_cut), obj);
1732 action = get_action_assert (obj->builder, "edit_paste");
1733 g_signal_connect (action, "activate", G_CALLBACK (on_edit_paste), obj);
1735 action = get_action_assert (obj->builder, "edit_clear-variables");
1736 g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
1739 action = get_action_assert (obj->builder, "edit_find");
1740 g_signal_connect (action, "activate", G_CALLBACK (on_edit_find), obj);
1742 action = get_action_assert (obj->builder, "sort-up");
1743 g_signal_connect (action, "activate", G_CALLBACK (on_sort_up), obj);
1745 action = get_action_assert (obj->builder, "sort-down");
1746 g_signal_connect (action, "activate", G_CALLBACK (on_sort_down), obj);
1751 psppire_data_sheet_new (void)
1753 return g_object_new (PSPP_TYPE_DATA_SHEET, NULL);
1757 psppire_data_sheet_get_data_store (PsppireDataSheet *data_sheet)
1759 return data_sheet->data_store;
1763 refresh_model (PsppireDataSheet *data_sheet)
1765 pspp_sheet_view_set_model (PSPP_SHEET_VIEW (data_sheet), NULL);
1767 if (data_sheet->data_store != NULL)
1769 PsppireEmptyListStore *model;
1773 n_rows = psppire_data_store_get_case_count (data_sheet->data_store) + 1;
1774 model = psppire_empty_list_store_new (n_rows);
1775 pspp_sheet_view_set_model (PSPP_SHEET_VIEW (data_sheet),
1776 GTK_TREE_MODEL (model));
1777 g_object_unref (model);
1779 action = get_action_assert (data_sheet->builder, "edit_copy");
1780 g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy),
1786 on_case_inserted (PsppireDataStore *data_store, gint row,
1787 PsppireDataSheet *data_sheet)
1789 PsppireEmptyListStore *empty_list_store;
1790 GtkTreeModel *tree_model;
1793 g_return_if_fail (data_store == data_sheet->data_store);
1795 n_rows = psppire_data_store_get_case_count (data_store) + 1;
1796 if (row == n_rows - 1)
1799 tree_model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (data_sheet));
1800 empty_list_store = PSPPIRE_EMPTY_LIST_STORE (tree_model);
1801 psppire_empty_list_store_set_n_rows (empty_list_store, n_rows);
1802 psppire_empty_list_store_row_inserted (empty_list_store, row);
1806 on_cases_deleted (PsppireDataStore *data_store, gint first, gint n_cases,
1807 PsppireDataSheet *data_sheet)
1810 g_return_if_fail (data_store == data_sheet->data_store);
1814 /* This is a bit of a cop-out. We could do better, if it ever turns out
1815 that this performs too poorly. */
1816 refresh_model (data_sheet);
1820 PsppireEmptyListStore *empty_list_store;
1821 GtkTreeModel *tree_model;
1822 gint n_rows = psppire_data_store_get_case_count (data_store) + 1;
1824 tree_model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (data_sheet));
1825 empty_list_store = PSPPIRE_EMPTY_LIST_STORE (tree_model);
1826 psppire_empty_list_store_set_n_rows (empty_list_store, n_rows);
1827 psppire_empty_list_store_row_deleted (empty_list_store, first);
1832 on_case_change (PsppireDataStore *data_store, gint row,
1833 PsppireDataSheet *data_sheet)
1835 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1837 pspp_sheet_view_stop_editing (sheet_view, TRUE);
1838 gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
1842 on_backend_changed (PsppireDataStore *data_store,
1843 PsppireDataSheet *data_sheet)
1845 g_return_if_fail (data_store == data_sheet->data_store);
1846 refresh_model (data_sheet);
1850 on_variable_display_width_changed (PsppireDict *dict, int dict_index,
1851 PsppireDataSheet *data_sheet)
1853 PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
1854 PsppSheetViewColumn *column;
1855 struct variable *var;
1859 g_return_if_fail (data_sheet->data_store != NULL);
1860 g_return_if_fail (dict == data_sheet->data_store->dict);
1862 column = psppire_data_sheet_find_column_for_variable (data_sheet,
1867 var = psppire_dict_get_variable (data_store->dict, dict_index);
1868 g_return_if_fail (var != NULL);
1870 pixel_width = pspp_sheet_view_column_get_fixed_width (column);
1871 display_width = display_width_from_pixel_width (data_sheet, pixel_width);
1872 if (display_width != var_get_display_width (var))
1874 gint base_width, incr_width;
1876 display_width = var_get_display_width (var);
1877 calc_width_conversion (data_sheet, &base_width, &incr_width);
1878 pixel_width = display_width_to_pixel_width (data_sheet, display_width,
1879 base_width, incr_width);
1880 pspp_sheet_view_column_set_fixed_width (column, pixel_width);
1885 on_variable_changed (PsppireDict *dict, int dict_index,
1886 PsppireDataSheet *data_sheet)
1888 PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
1889 PsppSheetViewColumn *column;
1890 GtkCellRenderer *cell;
1891 struct variable *var;
1895 g_return_if_fail (data_sheet->data_store != NULL);
1896 g_return_if_fail (dict == data_sheet->data_store->dict);
1898 column = psppire_data_sheet_find_column_for_variable (data_sheet,
1903 var = psppire_dict_get_variable (data_store->dict, dict_index);
1904 g_return_if_fail (var != NULL);
1906 name = escape_underscores (var_get_name (var));
1907 if (strcmp (name, pspp_sheet_view_column_get_title (column)))
1908 pspp_sheet_view_column_set_title (column, name);
1911 cells = pspp_sheet_view_column_get_cell_renderers (column);
1912 g_return_if_fail (cells);
1914 g_list_free (cells);
1916 if (var_has_value_labels (var) != GTK_IS_CELL_RENDERER_COMBO (cell))
1918 /* Stop editing before we delete and replace the cell renderers.
1919 Otherwise if this column is currently being edited, an eventual call
1920 to pspp_sheet_view_stop_editing() will obtain a NULL cell and pass
1921 that to gtk_cell_renderer_stop_editing(), which causes a critical.
1923 It's possible that this is a bug in PsppSheetView, and it's possible
1924 that PsppSheetView inherits that from GtkTreeView, but I haven't
1925 investigated yet. */
1926 pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (data_sheet), TRUE);
1928 add_data_column_cell_renderer (data_sheet, column);
1933 on_variable_inserted (PsppireDict *dict, int var_index,
1934 PsppireDataSheet *data_sheet)
1936 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1937 gint base_width, incr_width;
1938 PsppSheetViewColumn *column;
1940 calc_width_conversion (data_sheet, &base_width, &incr_width);
1941 column = make_data_column (data_sheet, var_index, base_width, incr_width);
1942 pspp_sheet_view_insert_column (sheet_view, column, var_index + 1);
1946 on_variable_deleted (PsppireDict *dict,
1947 const struct variable *var, int case_idx, int width,
1948 PsppireDataSheet *data_sheet)
1950 PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
1951 GList *columns, *iter;
1953 columns = pspp_sheet_view_get_columns (sheet_view);
1954 for (iter = columns; iter != NULL; iter = iter->next)
1956 PsppSheetViewColumn *column = iter->data;
1957 const struct variable *column_var;
1959 column_var = g_object_get_data (G_OBJECT (column), "variable");
1960 if (column_var == var)
1961 pspp_sheet_view_remove_column (sheet_view, column);
1963 g_list_free (columns);
1967 psppire_data_sheet_unset_data_store (PsppireDataSheet *data_sheet)
1969 PsppireDataStore *store = data_sheet->data_store;
1974 data_sheet->data_store = NULL;
1976 g_signal_handlers_disconnect_by_func (
1977 store, G_CALLBACK (on_backend_changed), data_sheet);
1978 g_signal_handlers_disconnect_by_func (
1979 store, G_CALLBACK (on_case_inserted), data_sheet);
1980 g_signal_handlers_disconnect_by_func (
1981 store, G_CALLBACK (on_cases_deleted), data_sheet);
1982 g_signal_handlers_disconnect_by_func (
1983 store, G_CALLBACK (on_case_change), data_sheet);
1985 g_signal_handlers_disconnect_by_func (
1986 store->dict, G_CALLBACK (on_variable_changed), data_sheet);
1987 g_signal_handlers_disconnect_by_func (
1988 store->dict, G_CALLBACK (on_variable_display_width_changed), data_sheet);
1989 g_signal_handlers_disconnect_by_func (
1990 store->dict, G_CALLBACK (on_variable_inserted), data_sheet);
1991 g_signal_handlers_disconnect_by_func (
1992 store->dict, G_CALLBACK (on_variable_deleted), data_sheet);
1994 g_object_unref (store);
1998 psppire_data_sheet_set_data_store (PsppireDataSheet *data_sheet,
1999 PsppireDataStore *data_store)
2001 psppire_data_sheet_unset_data_store (data_sheet);
2003 data_sheet->data_store = data_store;
2004 if (data_store != NULL)
2006 g_object_ref (data_store);
2007 g_signal_connect (data_store, "backend-changed",
2008 G_CALLBACK (on_backend_changed), data_sheet);
2009 g_signal_connect (data_store, "case-inserted",
2010 G_CALLBACK (on_case_inserted), data_sheet);
2011 g_signal_connect (data_store, "cases-deleted",
2012 G_CALLBACK (on_cases_deleted), data_sheet);
2013 g_signal_connect (data_store, "case-changed",
2014 G_CALLBACK (on_case_change), data_sheet);
2016 /* XXX it's unclean to hook into the dict this way--what if the dict
2017 changes? As of this writing, though, nothing ever changes the
2018 data_store's dict. */
2019 g_signal_connect (data_store->dict, "variable-changed",
2020 G_CALLBACK (on_variable_changed),
2022 g_signal_connect (data_store->dict, "variable-display-width-changed",
2023 G_CALLBACK (on_variable_display_width_changed),
2025 g_signal_connect (data_store->dict, "variable-inserted",
2026 G_CALLBACK (on_variable_inserted), data_sheet);
2027 g_signal_connect (data_store->dict, "variable-deleted",
2028 G_CALLBACK (on_variable_deleted), data_sheet);
2030 refresh_model (data_sheet);
2033 /* Clipboard stuff */
2035 /* A casereader and dictionary holding the data currently in the clip */
2036 static struct casereader *clip_datasheet = NULL;
2037 static struct dictionary *clip_dict = NULL;
2040 static void psppire_data_sheet_update_clipboard (PsppireDataSheet *);
2042 /* Set the clip from the currently selected range in DATA_SHEET. If CUT is
2043 true, clears the original data from DATA_SHEET, otherwise leaves the
2044 original data in-place. */
2046 psppire_data_sheet_set_clip (PsppireDataSheet *data_sheet,
2049 struct casewriter *writer ;
2050 PsppireDataStore *ds = psppire_data_sheet_get_data_store (data_sheet);
2051 struct case_map *map = NULL;
2052 struct range_set *rows, *cols;
2053 const struct range_set_node *node;
2055 if (!psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols))
2059 /* Destroy any existing clip */
2060 if ( clip_datasheet )
2062 casereader_destroy (clip_datasheet);
2063 clip_datasheet = NULL;
2068 dict_destroy (clip_dict);
2072 /* Construct clip dictionary. */
2073 clip_dict = dict_create (dict_get_encoding (ds->dict->dict));
2074 RANGE_SET_FOR_EACH (node, cols)
2078 for (dict_index = node->start; dict_index < node->end; dict_index++)
2080 struct variable *var = dict_get_var (ds->dict->dict, dict_index);
2081 dict_clone_var_assert (clip_dict, var);
2085 /* Construct clip data. */
2086 map = case_map_by_name (ds->dict->dict, clip_dict);
2087 writer = autopaging_writer_create (dict_get_proto (clip_dict));
2088 RANGE_SET_FOR_EACH (node, rows)
2090 unsigned long int row;
2092 for (row = node->start; row < node->end; row++)
2094 struct ccase *old = psppire_data_store_get_case (ds, row);
2096 casewriter_write (writer, case_map_execute (map, old));
2098 casewriter_force_error (writer);
2101 case_map_destroy (map);
2103 /* Clear data that we copied out, if we're doing a "cut". */
2104 if (cut && !casewriter_error (writer))
2106 RANGE_SET_FOR_EACH (node, rows)
2108 unsigned long int row;
2110 for (row = node->start; row < node->end; row++)
2112 const struct range_set_node *node2;
2114 RANGE_SET_FOR_EACH (node2, cols)
2118 for (dict_index = node2->start; dict_index < node2->end;
2121 struct variable *var;
2123 var = dict_get_var (ds->dict->dict, dict_index);
2124 psppire_data_store_set_string (ds, "", row,
2132 range_set_destroy (rows);
2133 range_set_destroy (cols);
2135 clip_datasheet = casewriter_make_reader (writer);
2137 psppire_data_sheet_update_clipboard (data_sheet);
2147 /* Perform data_out for case CC, variable V, appending to STRING */
2149 data_out_g_string (GString *string, const struct variable *v,
2150 const struct ccase *cc)
2152 const struct fmt_spec *fs = var_get_print_format (v);
2153 const union value *val = case_data (cc, v);
2155 char *s = data_out (val, var_get_encoding (v), fs);
2157 g_string_append (string, s);
2168 const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
2169 const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
2170 const size_t var_cnt = dict_get_var_cnt (clip_dict);
2172 string = g_string_sized_new (10 * val_cnt * case_cnt);
2174 for (r = 0 ; r < case_cnt ; ++r )
2179 cc = casereader_peek (clip_datasheet, r);
2182 g_warning ("Clipboard seems to have inexplicably shrunk");
2186 for (c = 0 ; c < var_cnt ; ++c)
2188 const struct variable *v = dict_get_var (clip_dict, c);
2189 data_out_g_string (string, v, cc);
2190 if ( c < val_cnt - 1 )
2191 g_string_append (string, "\t");
2195 g_string_append (string, "\n");
2210 const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
2211 const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
2212 const size_t var_cnt = dict_get_var_cnt (clip_dict);
2214 /* Guestimate the size needed */
2215 string = g_string_sized_new (80 + 20 * val_cnt * case_cnt);
2217 g_string_append (string,
2218 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
2220 g_string_append (string, "<table>\n");
2221 for (r = 0 ; r < case_cnt ; ++r )
2224 struct ccase *cc = casereader_peek (clip_datasheet, r);
2227 g_warning ("Clipboard seems to have inexplicably shrunk");
2230 g_string_append (string, "<tr>\n");
2232 for (c = 0 ; c < var_cnt ; ++c)
2234 const struct variable *v = dict_get_var (clip_dict, c);
2235 g_string_append (string, "<td>");
2236 data_out_g_string (string, v, cc);
2237 g_string_append (string, "</td>\n");
2240 g_string_append (string, "</tr>\n");
2244 g_string_append (string, "</table>\n");
2252 psppire_data_sheet_clipboard_get_cb (GtkClipboard *clipboard,
2253 GtkSelectionData *selection_data,
2257 GString *string = NULL;
2261 case SELECT_FMT_TEXT:
2262 string = clip_to_text ();
2264 case SELECT_FMT_HTML:
2265 string = clip_to_html ();
2268 g_assert_not_reached ();
2271 gtk_selection_data_set (selection_data, selection_data->target,
2273 (const guchar *) string->str, string->len);
2275 g_string_free (string, TRUE);
2279 psppire_data_sheet_clipboard_clear_cb (GtkClipboard *clipboard,
2282 dict_destroy (clip_dict);
2285 casereader_destroy (clip_datasheet);
2286 clip_datasheet = NULL;
2290 static const GtkTargetEntry targets[] = {
2291 { "UTF8_STRING", 0, SELECT_FMT_TEXT },
2292 { "STRING", 0, SELECT_FMT_TEXT },
2293 { "TEXT", 0, SELECT_FMT_TEXT },
2294 { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
2295 { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
2296 { "text/plain", 0, SELECT_FMT_TEXT },
2297 { "text/html", 0, SELECT_FMT_HTML }
2303 psppire_data_sheet_update_clipboard (PsppireDataSheet *sheet)
2305 GtkClipboard *clipboard =
2306 gtk_widget_get_clipboard (GTK_WIDGET (sheet),
2307 GDK_SELECTION_CLIPBOARD);
2309 if (!gtk_clipboard_set_with_owner (clipboard, targets,
2310 G_N_ELEMENTS (targets),
2311 psppire_data_sheet_clipboard_get_cb,
2312 psppire_data_sheet_clipboard_clear_cb,
2314 psppire_data_sheet_clipboard_clear_cb (clipboard, sheet);
2318 psppire_data_sheet_update_clip_actions (PsppireDataSheet *data_sheet)
2320 struct range_set *rows, *cols;
2324 enable = psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols);
2327 range_set_destroy (rows);
2328 range_set_destroy (cols);
2331 action = get_action_assert (data_sheet->builder, "edit_copy");
2332 gtk_action_set_sensitive (action, enable);
2334 action = get_action_assert (data_sheet->builder, "edit_cut");
2335 gtk_action_set_sensitive (action, enable);
2338 /* A callback for when the clipboard contents have been received. */
2340 psppire_data_sheet_clip_received_cb (GtkClipboard *clipboard,
2341 GtkSelectionData *sd,
2344 PsppireDataSheet *data_sheet = data;
2345 PsppireDataStore *store = data_sheet->data_store;
2346 struct range_set *rows, *cols;
2348 gint next_row, next_column;
2352 if ( sd->length < 0 )
2355 if ( sd->type != gdk_atom_intern ("UTF8_STRING", FALSE))
2358 c = (char *) sd->data;
2360 /* Get the starting selected position in the data sheet. (Possibly we should
2361 only paste into the selected range if it's larger than one cell?) */
2362 if (!psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols))
2364 next_row = range_set_first (rows)->start;
2365 first_column = next_column = range_set_first (cols)->start;
2366 range_set_destroy (rows);
2367 range_set_destroy (cols);
2369 g_return_if_fail (next_row >= 0);
2370 g_return_if_fail (next_column >= 0);
2372 while (count < sd->length)
2374 gint row = next_row;
2375 gint column = next_column;
2376 struct variable *var;
2379 while (*c != '\t' && *c != '\n' && count < sd->length)
2387 next_column = column + 1;
2389 else if ( *c == '\n')
2392 next_column = first_column;
2397 var = psppire_dict_get_variable (store->dict, column);
2399 psppire_data_store_set_string (store, s, row, var, FALSE);
2404 on_owner_change (GtkClipboard *clip, GdkEventOwnerChange *event, gpointer data)
2406 PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (data);
2407 gboolean compatible_target = FALSE;
2411 for (i = 0; i < G_N_ELEMENTS (targets); i++)
2413 GdkAtom atom = gdk_atom_intern (targets[i].target, TRUE);
2414 if (gtk_clipboard_wait_is_target_available (clip, atom))
2416 compatible_target = TRUE;
2421 action = get_action_assert (data_sheet->builder, "edit_paste");
2422 gtk_action_set_sensitive (action, compatible_target);