GtkSelectionData: only access using functions
[pspp] / src / ui / gui / psppire-data-sheet.c
index 4960a838364c008167a8158bb3306c32c4702e7a..3da9e0455068d68e94826a26279530e312430d48 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -51,6 +51,8 @@ static void psppire_data_sheet_dispose (GObject *);
 static void psppire_data_sheet_unset_data_store (PsppireDataSheet *);
 
 static void psppire_data_sheet_update_clip_actions (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);
@@ -335,13 +337,15 @@ on_data_column_editing_started (GtkCellRenderer *cell,
   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);
@@ -349,6 +353,7 @@ on_data_column_editing_started (GtkCellRenderer *cell,
                               0, val_lab_get_label (vl),
                               -1);
         }
+      free (vls);
 
       gtk_combo_box_set_model (GTK_COMBO_BOX (editable),
                                GTK_TREE_MODEL (list_store));
@@ -1047,7 +1052,7 @@ psppire_data_sheet_find_column_for_variable (PsppireDataSheet *data_sheet,
 }
 
 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);
@@ -1201,6 +1206,13 @@ psppire_data_sheet_dispose (GObject *object)
 {
   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;
 
@@ -1219,14 +1231,19 @@ psppire_data_sheet_dispose (GObject *object)
 static void
 psppire_data_sheet_map (GtkWidget *widget)
 {
-  GtkClipboard *clip;
+  PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
 
   GTK_WIDGET_CLASS (psppire_data_sheet_parent_class)->map (widget);
 
-  clip = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
-  g_signal_connect (clip, "owner-change", G_CALLBACK (on_owner_change),
-                    widget);
-  on_owner_change (clip, NULL, 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
@@ -1389,6 +1406,7 @@ on_selection_changed (PsppSheetSelection *selection,
   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;
@@ -1421,6 +1439,7 @@ on_selection_changed (PsppSheetSelection *selection,
   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)
@@ -1431,6 +1450,7 @@ on_selection_changed (PsppSheetSelection *selection,
       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)
@@ -1454,6 +1474,9 @@ on_selection_changed (PsppSheetSelection *selection,
   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
@@ -1678,8 +1701,11 @@ psppire_data_sheet_init (PsppireDataSheet *obj)
   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;
 
@@ -2039,38 +2065,27 @@ static struct dictionary *clip_dict = NULL;
 
 static void psppire_data_sheet_update_clipboard (PsppireDataSheet *);
 
-/* 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)
+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;
@@ -2078,13 +2093,13 @@ psppire_data_sheet_set_clip (PsppireDataSheet *data_sheet,
       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;
@@ -2132,9 +2147,31 @@ psppire_data_sheet_set_clip (PsppireDataSheet *data_sheet,
   range_set_destroy (rows);
   range_set_destroy (cols);
 
-  clip_datasheet = casewriter_make_reader (writer);
+  *readerp = casewriter_make_reader (writer);
+
+  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);
+      psppire_data_sheet_update_clipboard (data_sheet);
+    }
 }
 
 enum {
@@ -2160,14 +2197,14 @@ data_out_g_string (GString *string, const struct variable *v,
 }
 
 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);
 
@@ -2176,7 +2213,7 @@ clip_to_text (void)
       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");
@@ -2185,7 +2222,7 @@ clip_to_text (void)
 
       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");
@@ -2202,14 +2239,14 @@ clip_to_text (void)
 
 
 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);
@@ -2221,7 +2258,7 @@ clip_to_html (void)
   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");
@@ -2231,7 +2268,7 @@ clip_to_html (void)
 
       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");
@@ -2249,32 +2286,42 @@ clip_to_html (void)
 
 
 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 ();
     }
 
-  gtk_selection_data_set (selection_data, selection_data->target,
+  gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data),
                          8,
                          (const guchar *) string->str, string->len);
 
   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)
@@ -2334,6 +2381,48 @@ psppire_data_sheet_update_clip_actions (PsppireDataSheet *data_sheet)
   action = get_action_assert (data_sheet->builder, "edit_cut");
   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
@@ -2349,13 +2438,13 @@ psppire_data_sheet_clip_received_cb (GtkClipboard *clipboard,
   gint first_column;
   char *c;
 
-  if ( sd->length < 0 )
+  if ( gtk_selection_data_get_length (sd) < 0 )
     return;
 
-  if ( sd->type != gdk_atom_intern ("UTF8_STRING", FALSE))
+  if ( gtk_selection_data_get_data_type (sd) != gdk_atom_intern ("UTF8_STRING", FALSE))
     return;
 
-  c = (char *) sd->data;
+  c = (char *) gtk_selection_data_get_data (sd);
 
   /* 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?) */
@@ -2369,14 +2458,14 @@ psppire_data_sheet_clip_received_cb (GtkClipboard *clipboard,
   g_return_if_fail (next_row >= 0);
   g_return_if_fail (next_column >= 0);
 
-  while (count < sd->length)
+  while (count < gtk_selection_data_get_length (sd))
     {
       gint row = next_row;
       gint column = next_column;
       struct variable *var;
       char *s = c;
 
-      while (*c != '\t' && *c != '\n' && count < sd->length)
+      while (*c != '\t' && *c != '\n' && count < gtk_selection_data_get_length (sd))
         {
           c++;
           count++;
@@ -2401,23 +2490,40 @@ psppire_data_sheet_clip_received_cb (GtkClipboard *clipboard,
 }
 
 static void
-on_owner_change (GtkClipboard *clip, GdkEventOwnerChange *event, gpointer data)
+psppire_data_sheet_targets_received_cb (GtkClipboard *clipboard,
+                                        GdkAtom *atoms,
+                                        gint n_atoms,
+                                        gpointer data)
 {
-  PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (data);
-  gboolean compatible_target = FALSE;
-  GtkAction *action;
+  GtkAction *action = GTK_ACTION (data);
+  gboolean compatible_target;
   gint i;
 
+  compatible_target = FALSE;
   for (i = 0; i < G_N_ELEMENTS (targets); i++)
     {
-      GdkAtom atom = gdk_atom_intern (targets[i].target, TRUE);
-      if (gtk_clipboard_wait_is_target_available (clip, atom))
-        {
-          compatible_target = TRUE;
-          break;
-        }
+      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;
+          }
     }
 
-  action = get_action_assert (data_sheet->builder, "edit_paste");
   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);
 }