/* PSPPIRE - a graphical user interface for PSPP.
- Copyright (C) 2011, 2012 Free Software Foundation, Inc.
+ Copyright (C) 2011, 2012, 2013 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
static void psppire_data_sheet_unset_data_store (PsppireDataSheet *);
static void psppire_data_sheet_update_clip_actions (PsppireDataSheet *);
-static void psppire_data_sheet_set_clip (PsppireDataSheet *);
+static void psppire_data_sheet_update_primary_selection (PsppireDataSheet *,
+ gboolean should_own);
+static void psppire_data_sheet_set_clip (PsppireDataSheet *, gboolean cut);
static void on_selection_changed (PsppSheetSelection *, gpointer);
+static void on_owner_change (GtkClipboard *, GdkEventOwnerChange *, gpointer);
+static void psppire_data_sheet_clip_received_cb (GtkClipboard *,
+ GtkSelectionData *, gpointer);
G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, PSPP_TYPE_SHEET_VIEW);
column = pspp_sheet_view_column_new_with_attributes (_("Case"),
renderer, NULL);
- g_object_set (column, "selectable", FALSE, "row-head", TRUE, NULL);
+ pspp_sheet_view_column_set_selectable (column, TRUE);
+ pspp_sheet_view_column_set_row_head (column, TRUE);
+ pspp_sheet_view_column_set_tabbable (column, FALSE);
+ pspp_sheet_view_column_set_clickable (column, TRUE);
pspp_sheet_view_column_set_cell_data_func (
column, renderer, render_row_number_cell, ds, NULL);
pspp_sheet_view_column_set_fixed_width (column, 50);
if (var_has_value_labels (var) && GTK_IS_COMBO_BOX (editable))
{
const struct val_labs *labels = var_get_value_labels (var);
- const struct val_lab *vl;
+ const struct val_lab **vls = val_labs_sorted (labels);
+ size_t n_vls = val_labs_count (labels);
GtkListStore *list_store;
+ int i;
list_store = gtk_list_store_new (1, G_TYPE_STRING);
- for (vl = val_labs_first (labels); vl != NULL;
- vl = val_labs_next (labels, vl))
+ for (i = 0; i < n_vls; ++i)
{
+ const struct val_lab *vl = vls[i];
GtkTreeIter iter;
gtk_list_store_append (list_store, &iter);
0, val_lab_get_label (vl),
-1);
}
+ free (vls);
gtk_combo_box_set_model (GTK_COMBO_BOX (editable),
GTK_TREE_MODEL (list_store));
width = display_width_to_pixel_width (data_sheet, 8, base_width, incr_width);
pspp_sheet_view_column_set_min_width (column, 10);
pspp_sheet_view_column_set_fixed_width (column, width);
+ pspp_sheet_view_column_set_tabbable (column, FALSE);
g_object_set_data (G_OBJECT (cell), "data-sheet", data_sheet);
g_signal_connect (column, "button-press-event",
}
void
-psppire_data_sheet_show_variable (PsppireDataSheet *data_sheet,
+psppire_data_sheet_goto_variable (PsppireDataSheet *data_sheet,
gint dict_index)
{
PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
column = psppire_data_sheet_find_column_for_variable (data_sheet,
dict_index);
if (column != NULL)
- pspp_sheet_view_scroll_to_cell (sheet_view, NULL, column,
- FALSE, 0.0, 0.0);
+ {
+ GtkTreePath *path;
+
+ gint row = psppire_data_sheet_get_current_case (data_sheet);
+ path = gtk_tree_path_new_from_indices (row >= 0 ? row : 0, -1);
+
+ pspp_sheet_view_scroll_to_cell (sheet_view, path, column,
+ FALSE, 0.0, 0.0);
+ pspp_sheet_view_set_cursor (sheet_view, path, column, FALSE);
+ gtk_tree_path_free (path);
+ }
}
struct variable *
{
PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (object);
+ if (data_sheet->clip != NULL && data_sheet->on_owner_change_signal != 0)
+ {
+ g_signal_handler_disconnect (data_sheet->clip,
+ data_sheet->on_owner_change_signal);
+ data_sheet->on_owner_change_signal = 0;
+ }
+
if (data_sheet->dispose_has_run)
return;
G_OBJECT_CLASS (psppire_data_sheet_parent_class)->dispose (object);
}
+static void
+psppire_data_sheet_map (GtkWidget *widget)
+{
+ PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
+
+ GTK_WIDGET_CLASS (psppire_data_sheet_parent_class)->map (widget);
+
+ data_sheet->clip = gtk_widget_get_clipboard (widget,
+ GDK_SELECTION_CLIPBOARD);
+ if (data_sheet->on_owner_change_signal)
+ g_signal_handler_disconnect (data_sheet->clip,
+ data_sheet->on_owner_change_signal);
+ data_sheet->on_owner_change_signal
+ = g_signal_connect (data_sheet->clip, "owner-change",
+ G_CALLBACK (on_owner_change), widget);
+ on_owner_change (data_sheet->clip, NULL, widget);
+}
+
static void
psppire_data_sheet_class_init (PsppireDataSheetClass *class)
{
GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = psppire_data_sheet_set_property;
gobject_class->get_property = psppire_data_sheet_get_property;
-
gobject_class->dispose = psppire_data_sheet_dispose;
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->map = psppire_data_sheet_map;
+
g_signal_new ("var-double-clicked",
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST,
PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
gint n_selected_rows;
+ gboolean any_variables_selected;
gboolean may_delete_cases, may_delete_vars, may_insert_vars;
GList *list, *iter;
GtkTreePath *path;
action = get_action_assert (data_sheet->builder, "edit_clear-cases");
gtk_action_set_sensitive (action, may_delete_cases);
+ any_variables_selected = FALSE;
may_delete_vars = may_insert_vars = FALSE;
list = pspp_sheet_selection_get_selected_columns (selection);
for (iter = list; iter != NULL; iter = iter->next)
if (var != NULL)
{
may_delete_vars = may_insert_vars = TRUE;
+ any_variables_selected = TRUE;
break;
}
if (g_object_get_data (G_OBJECT (column), "new-var-column") != NULL)
gtk_action_set_sensitive (action, may_delete_vars);
psppire_data_sheet_update_clip_actions (data_sheet);
+ psppire_data_sheet_update_primary_selection (data_sheet,
+ (n_selected_rows > 0
+ && any_variables_selected));
}
static gboolean
void
on_edit_copy (GtkAction *action, PsppireDataSheet *data_sheet)
{
- psppire_data_sheet_set_clip (data_sheet);
+ psppire_data_sheet_set_clip (data_sheet, FALSE);
+}
+
+void
+on_edit_cut (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+ psppire_data_sheet_set_clip (data_sheet, TRUE);
+}
+
+void
+on_edit_paste (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+ GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (data_sheet));
+ GtkClipboard *clipboard =
+ gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD);
+
+ gtk_clipboard_request_contents (clipboard,
+ gdk_atom_intern ("UTF8_STRING", TRUE),
+ psppire_data_sheet_clip_received_cb,
+ data_sheet);
}
static void
obj->may_create_vars = TRUE;
obj->may_delete_vars = TRUE;
+ obj->owns_primary_selection = FALSE;
+
obj->scroll_to_bottom_signal = 0;
obj->scroll_to_right_signal = 0;
+ obj->on_owner_change_signal = 0;
obj->new_variable_column = NULL;
obj->container = NULL;
action = get_action_assert (obj->builder, "edit_copy");
g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy), obj);
+ action = get_action_assert (obj->builder, "edit_cut");
+ g_signal_connect (action, "activate", G_CALLBACK (on_edit_cut), obj);
+
+ action = get_action_assert (obj->builder, "edit_paste");
+ g_signal_connect (action, "activate", G_CALLBACK (on_edit_paste), obj);
+
action = get_action_assert (obj->builder, "edit_clear-variables");
g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
obj);
static void
on_variable_changed (PsppireDict *dict, int dict_index,
+ guint what, const struct variable *oldvar,
PsppireDataSheet *data_sheet)
{
PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
g_return_if_fail (data_sheet->data_store != NULL);
g_return_if_fail (dict == data_sheet->data_store->dict);
+
+ if (what & VAR_TRAIT_DISPLAY_WIDTH)
+ on_variable_display_width_changed (dict, dict_index, data_sheet);
+
column = psppire_data_sheet_find_column_for_variable (data_sheet,
dict_index);
if (column == NULL)
return;
+
var = psppire_dict_get_variable (data_store->dict, dict_index);
g_return_if_fail (var != NULL);
g_signal_connect (data_store->dict, "variable-changed",
G_CALLBACK (on_variable_changed),
data_sheet);
- g_signal_connect (data_store->dict, "variable-display-width-changed",
- G_CALLBACK (on_variable_display_width_changed),
- data_sheet);
g_signal_connect (data_store->dict, "variable-inserted",
G_CALLBACK (on_variable_inserted), data_sheet);
g_signal_connect (data_store->dict, "variable-deleted",
static void psppire_data_sheet_update_clipboard (PsppireDataSheet *);
-/* Set the clip according to the currently
- selected range in the data sheet */
-static void
-psppire_data_sheet_set_clip (PsppireDataSheet *data_sheet)
+static gboolean
+psppire_data_sheet_fetch_clip (PsppireDataSheet *data_sheet, gboolean cut,
+ struct casereader **readerp,
+ struct dictionary **dictp)
{
struct casewriter *writer ;
PsppireDataStore *ds = psppire_data_sheet_get_data_store (data_sheet);
struct case_map *map = NULL;
struct range_set *rows, *cols;
const struct range_set_node *node;
+ struct dictionary *dict;
if (!psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols))
- return;
-
-
- /* Destroy any existing clip */
- if ( clip_datasheet )
{
- casereader_destroy (clip_datasheet);
- clip_datasheet = NULL;
- }
-
- if ( clip_dict )
- {
- dict_destroy (clip_dict);
- clip_dict = NULL;
+ *readerp = NULL;
+ *dictp = NULL;
+ return FALSE;
}
/* Construct clip dictionary. */
- clip_dict = dict_create (dict_get_encoding (ds->dict->dict));
+ *dictp = dict = dict_create (dict_get_encoding (ds->dict->dict));
RANGE_SET_FOR_EACH (node, cols)
{
int dict_index;
for (dict_index = node->start; dict_index < node->end; dict_index++)
{
struct variable *var = dict_get_var (ds->dict->dict, dict_index);
- dict_clone_var_assert (clip_dict, var);
+ dict_clone_var_assert (dict, var);
}
}
/* Construct clip data. */
- map = case_map_by_name (ds->dict->dict, clip_dict);
- writer = autopaging_writer_create (dict_get_proto (clip_dict));
+ map = case_map_by_name (ds->dict->dict, dict);
+ writer = autopaging_writer_create (dict_get_proto (dict));
RANGE_SET_FOR_EACH (node, rows)
{
unsigned long int row;
}
case_map_destroy (map);
+ /* Clear data that we copied out, if we're doing a "cut". */
+ if (cut && !casewriter_error (writer))
+ {
+ RANGE_SET_FOR_EACH (node, rows)
+ {
+ unsigned long int row;
+
+ for (row = node->start; row < node->end; row++)
+ {
+ const struct range_set_node *node2;
+
+ RANGE_SET_FOR_EACH (node2, cols)
+ {
+ int dict_index;
+
+ for (dict_index = node2->start; dict_index < node2->end;
+ dict_index++)
+ {
+ struct variable *var;
+
+ var = dict_get_var (ds->dict->dict, dict_index);
+ psppire_data_store_set_string (ds, "", row,
+ var, false);
+ }
+ }
+ }
+ }
+ }
+
range_set_destroy (rows);
range_set_destroy (cols);
- clip_datasheet = casewriter_make_reader (writer);
+ *readerp = casewriter_make_reader (writer);
- psppire_data_sheet_update_clipboard (data_sheet);
+ return TRUE;
+}
+
+/* Set the clip from the currently selected range in DATA_SHEET. If CUT is
+ true, clears the original data from DATA_SHEET, otherwise leaves the
+ original data in-place. */
+static void
+psppire_data_sheet_set_clip (PsppireDataSheet *data_sheet,
+ gboolean cut)
+{
+ struct casereader *reader;
+ struct dictionary *dict;
+
+ if (psppire_data_sheet_fetch_clip (data_sheet, cut, &reader, &dict))
+ {
+ casereader_destroy (clip_datasheet);
+ dict_destroy (clip_dict);
+
+ clip_datasheet = reader;
+ clip_dict = dict;
+
+ psppire_data_sheet_update_clipboard (data_sheet);
+ }
}
enum {
}
static GString *
-clip_to_text (void)
+clip_to_text (struct casereader *datasheet, struct dictionary *dict)
{
casenumber r;
GString *string;
- const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
- const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
- const size_t var_cnt = dict_get_var_cnt (clip_dict);
+ const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (datasheet));
+ const casenumber case_cnt = casereader_get_case_cnt (datasheet);
+ const size_t var_cnt = dict_get_var_cnt (dict);
string = g_string_sized_new (10 * val_cnt * case_cnt);
int c;
struct ccase *cc;
- cc = casereader_peek (clip_datasheet, r);
+ cc = casereader_peek (datasheet, r);
if (cc == NULL)
{
g_warning ("Clipboard seems to have inexplicably shrunk");
for (c = 0 ; c < var_cnt ; ++c)
{
- const struct variable *v = dict_get_var (clip_dict, c);
+ const struct variable *v = dict_get_var (dict, c);
data_out_g_string (string, v, cc);
if ( c < val_cnt - 1 )
g_string_append (string, "\t");
static GString *
-clip_to_html (void)
+clip_to_html (struct casereader *datasheet, struct dictionary *dict)
{
casenumber r;
GString *string;
- const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
- const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
- const size_t var_cnt = dict_get_var_cnt (clip_dict);
+ const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (datasheet));
+ const casenumber case_cnt = casereader_get_case_cnt (datasheet);
+ const size_t var_cnt = dict_get_var_cnt (dict);
/* Guestimate the size needed */
string = g_string_sized_new (80 + 20 * val_cnt * case_cnt);
for (r = 0 ; r < case_cnt ; ++r )
{
int c;
- struct ccase *cc = casereader_peek (clip_datasheet, r);
+ struct ccase *cc = casereader_peek (datasheet, r);
if (cc == NULL)
{
g_warning ("Clipboard seems to have inexplicably shrunk");
for (c = 0 ; c < var_cnt ; ++c)
{
- const struct variable *v = dict_get_var (clip_dict, c);
+ const struct variable *v = dict_get_var (dict, c);
g_string_append (string, "<td>");
data_out_g_string (string, v, cc);
g_string_append (string, "</td>\n");
static void
-psppire_data_sheet_clipboard_get_cb (GtkClipboard *clipboard,
- GtkSelectionData *selection_data,
- guint info,
- gpointer data)
+psppire_data_sheet_clipboard_set (GtkSelectionData *selection_data,
+ guint info,
+ struct casereader *reader,
+ struct dictionary *dict)
{
GString *string = NULL;
switch (info)
{
case SELECT_FMT_TEXT:
- string = clip_to_text ();
+ string = clip_to_text (reader, dict);
break;
case SELECT_FMT_HTML:
- string = clip_to_html ();
+ string = clip_to_html (reader, dict);
break;
default:
g_assert_not_reached ();
g_string_free (string, TRUE);
}
+static void
+psppire_data_sheet_clipboard_get_cb (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint info,
+ gpointer data)
+{
+ psppire_data_sheet_clipboard_set (selection_data, info,
+ clip_datasheet, clip_dict);
+}
+
static void
psppire_data_sheet_clipboard_clear_cb (GtkClipboard *clipboard,
gpointer data)
gtk_action_set_sensitive (action, enable);
}
+static void
+psppire_data_sheet_primary_get_cb (GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint info,
+ gpointer data)
+{
+ PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (data);
+ struct casereader *reader;
+ struct dictionary *dict;
+
+ if (psppire_data_sheet_fetch_clip (data_sheet, FALSE, &reader, &dict))
+ {
+ psppire_data_sheet_clipboard_set (selection_data, info,
+ reader, dict);
+ casereader_destroy (reader);
+ dict_destroy (dict);
+ }
+}
+
+static void
+psppire_data_sheet_update_primary_selection (PsppireDataSheet *data_sheet,
+ gboolean should_own)
+{
+ GtkClipboard *clipboard;
+ GdkDisplay *display;
+
+ display = gtk_widget_get_display (GTK_WIDGET (data_sheet));
+ clipboard = gtk_clipboard_get_for_display (display, GDK_SELECTION_PRIMARY);
+ g_return_if_fail (clipboard != NULL);
+
+ if (data_sheet->owns_primary_selection && !should_own)
+ {
+ data_sheet->owns_primary_selection = FALSE;
+ gtk_clipboard_clear (clipboard);
+ }
+ else if (should_own)
+ data_sheet->owns_primary_selection =
+ gtk_clipboard_set_with_owner (clipboard, targets, G_N_ELEMENTS (targets),
+ psppire_data_sheet_primary_get_cb,
+ NULL, G_OBJECT (data_sheet));
+}
+\f
+/* A callback for when the clipboard contents have been received. */
+static void
+psppire_data_sheet_clip_received_cb (GtkClipboard *clipboard,
+ GtkSelectionData *sd,
+ gpointer data)
+{
+ PsppireDataSheet *data_sheet = data;
+ PsppireDataStore *store = data_sheet->data_store;
+ struct range_set *rows, *cols;
+ gint count = 0;
+ gint next_row, next_column;
+ gint first_column;
+ char *c;
+
+ if ( sd->length < 0 )
+ return;
+
+ if ( sd->type != gdk_atom_intern ("UTF8_STRING", FALSE))
+ return;
+
+ c = (char *) sd->data;
+
+ /* Get the starting selected position in the data sheet. (Possibly we should
+ only paste into the selected range if it's larger than one cell?) */
+ if (!psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols))
+ return;
+ next_row = range_set_first (rows)->start;
+ first_column = next_column = range_set_first (cols)->start;
+ range_set_destroy (rows);
+ range_set_destroy (cols);
+
+ g_return_if_fail (next_row >= 0);
+ g_return_if_fail (next_column >= 0);
+
+ while (count < sd->length)
+ {
+ gint row = next_row;
+ gint column = next_column;
+ struct variable *var;
+ char *s = c;
+
+ while (*c != '\t' && *c != '\n' && count < sd->length)
+ {
+ c++;
+ count++;
+ }
+ if ( *c == '\t')
+ {
+ next_row = row ;
+ next_column = column + 1;
+ }
+ else if ( *c == '\n')
+ {
+ next_row = row + 1;
+ next_column = first_column;
+ }
+ *c++ = '\0';
+ count++;
+
+ var = psppire_dict_get_variable (store->dict, column);
+ if (var != NULL)
+ psppire_data_store_set_string (store, s, row, var, FALSE);
+ }
+}
+
+static void
+psppire_data_sheet_targets_received_cb (GtkClipboard *clipboard,
+ GdkAtom *atoms,
+ gint n_atoms,
+ gpointer data)
+{
+ GtkAction *action = GTK_ACTION (data);
+ gboolean compatible_target;
+ gint i;
+
+ compatible_target = FALSE;
+ for (i = 0; i < G_N_ELEMENTS (targets); i++)
+ {
+ GdkAtom target = gdk_atom_intern (targets[i].target, TRUE);
+ gint j;
+
+ for (j = 0; j < n_atoms; j++)
+ if (target == atoms[j])
+ {
+ compatible_target = TRUE;
+ break;
+ }
+ }
+
+ gtk_action_set_sensitive (action, compatible_target);
+ g_object_unref (action);
+}
+
+static void
+on_owner_change (GtkClipboard *clip, GdkEventOwnerChange *event, gpointer data)
+{
+ PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (data);
+ GtkAction *action = get_action_assert (data_sheet->builder, "edit_paste");
+
+ g_object_ref (action);
+ gtk_clipboard_request_targets (clip, psppire_data_sheet_targets_received_cb,
+ action);
+}