+ info = (GtkRetrievalInfo *)tmp_list->data;
+ if (info->widget == widget)
+ return FALSE;
+ tmp_list = tmp_list->next;
+ }
+
+ info = g_new (GtkRetrievalInfo, 1);
+
+ info->widget = widget;
+ info->selection = selection;
+ info->target = target;
+ info->idle_time = 0;
+ info->buffer = NULL;
+ info->offset = -1;
+
+ /* Check if this process has current owner. If so, call handler
+ procedure directly to avoid deadlocks with INCR. */
+
+ display = gtk_widget_get_display (widget);
+ owner_window = gdk_selection_owner_get_for_display (display, selection);
+
+ if (owner_window != NULL)
+ {
+ GtkWidget *owner_widget;
+ GtkSelectionData selection_data;
+
+ selection_data.selection = selection;
+ selection_data.target = target;
+ selection_data.data = NULL;
+ selection_data.length = -1;
+ selection_data.display = display;
+
+ gdk_window_get_user_data (owner_window, (gpointer *)&owner_widget);
+
+ if (owner_widget != NULL)
+ {
+ gtk_selection_invoke_handler (owner_widget,
+ &selection_data,
+ time_);
+
+ gtk_selection_retrieval_report (info,
+ selection_data.type,
+ selection_data.format,
+ selection_data.data,
+ selection_data.length,
+ time_);
+
+ g_free (selection_data.data);
+
+ g_free (info);
+ return TRUE;
+ }
+ }
+
+ /* Otherwise, we need to go through X */
+
+ current_retrievals = g_list_append (current_retrievals, info);
+ gdk_selection_convert (widget->window, selection, target, time_);
+ gdk_threads_add_timeout (1000,
+ (GSourceFunc) gtk_selection_retrieval_timeout, info);
+
+ return TRUE;
+}
+
+
+/**
+ * gtk_selection_data_set:
+ * @selection_data: a pointer to a #GtkSelectionData structure.
+ * @type: the type of selection data
+ * @format: format (number of bits in a unit)
+ * @data: pointer to the data (will be copied)
+ * @length: length of the data
+ *
+ * Stores new data into a #GtkSelectionData object. Should
+ * <emphasis>only</emphasis> be called from a selection handler callback.
+ * Zero-terminates the stored data.
+ **/
+void
+gtk_selection_data_set (GtkSelectionData *selection_data,
+ GdkAtom type,
+ gint format,
+ const guchar *data,
+ gint length)
+{
+ g_free (selection_data->data);
+
+ selection_data->type = type;
+ selection_data->format = format;
+
+ if (data)
+ {
+ selection_data->data = g_new (guchar, length+1);
+ memcpy (selection_data->data, data, length);
+ selection_data->data[length] = 0;
+ }
+ else
+ {
+ g_return_if_fail (length <= 0);
+
+ if (length < 0)
+ selection_data->data = NULL;
+ else
+ selection_data->data = g_strdup("");
+ }
+
+ selection_data->length = length;
+}
+
+static gboolean
+selection_set_string (GtkSelectionData *selection_data,
+ const gchar *str,
+ gint len)
+{
+ gchar *tmp = g_strndup (str, len);
+ gchar *latin1 = gdk_utf8_to_string_target (tmp);
+ g_free (tmp);
+
+ if (latin1)
+ {
+ gtk_selection_data_set (selection_data,
+ GDK_SELECTION_TYPE_STRING,
+ 8, latin1, strlen (latin1));
+ g_free (latin1);
+
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static gboolean
+selection_set_compound_text (GtkSelectionData *selection_data,
+ const gchar *str,
+ gint len)
+{
+ gchar *tmp;
+ guchar *text;
+ GdkAtom encoding;
+ gint format;
+ gint new_length;
+ gboolean result = FALSE;
+
+ tmp = g_strndup (str, len);
+ if (gdk_utf8_to_compound_text_for_display (selection_data->display, tmp,
+ &encoding, &format, &text, &new_length))
+ {
+ gtk_selection_data_set (selection_data, encoding, format, text, new_length);
+ gdk_free_compound_text (text);
+
+ result = TRUE;
+ }
+
+ g_free (tmp);
+
+ return result;
+}
+
+/* Normalize \r and \n into \r\n
+ */
+static gchar *
+normalize_to_crlf (const gchar *str,
+ gint len)
+{
+ GString *result = g_string_sized_new (len);
+ const gchar *p = str;
+
+ while (1)
+ {
+ if (*p == '\n')
+ g_string_append_c (result, '\r');
+
+ if (*p == '\r')
+ {
+ g_string_append_c (result, *p);
+ p++;
+ if (*p != '\n')
+ g_string_append_c (result, '\n');
+ }
+
+ if (*p == '\0')
+ break;
+
+ g_string_append_c (result, *p);
+ p++;
+ }
+
+ return g_string_free (result, FALSE);
+}
+
+/* Normalize \r and \r\n into \n
+ */
+static gchar *
+normalize_to_lf (gchar *str,
+ gint len)
+{
+ GString *result = g_string_sized_new (len);
+ const gchar *p = str;
+
+ while (1)
+ {
+ if (*p == '\r')
+ {
+ p++;
+ if (*p != '\n')
+ g_string_append_c (result, '\n');
+ }
+
+ if (*p == '\0')
+ break;
+
+ g_string_append_c (result, *p);
+ p++;
+ }
+
+ return g_string_free (result, FALSE);
+}
+
+static gboolean
+selection_set_text_plain (GtkSelectionData *selection_data,
+ const gchar *str,
+ gint len)
+{
+ const gchar *charset = NULL;
+ gchar *result;
+ GError *error = NULL;
+
+ result = normalize_to_crlf (str, len);
+ if (selection_data->target == text_plain_atom)
+ charset = "ASCII";
+ else if (selection_data->target == text_plain_locale_atom)
+ g_get_charset (&charset);
+
+ if (charset)
+ {
+ gchar *tmp = result;
+ result = g_convert_with_fallback (tmp, -1,
+ charset, "UTF-8",
+ NULL, NULL, NULL, &error);
+ g_free (tmp);
+ }
+
+ if (!result)
+ {
+ g_warning ("Error converting from %s to %s: %s",
+ "UTF-8", charset, error->message);
+ g_error_free (error);
+
+ return FALSE;
+ }
+
+ gtk_selection_data_set (selection_data,
+ selection_data->target,
+ 8, result, strlen (result));
+ g_free (result);
+
+ return TRUE;
+}
+
+static gchar *
+selection_get_text_plain (GtkSelectionData *selection_data)
+{
+ const gchar *charset = NULL;
+ gchar *str, *result;
+ gsize len;
+ GError *error = NULL;
+
+ str = g_strdup (selection_data->data);
+ len = selection_data->length;
+
+ if (selection_data->type == text_plain_atom)
+ charset = "ISO-8859-1";
+ else if (selection_data->type == text_plain_locale_atom)
+ g_get_charset (&charset);
+
+ if (charset)
+ {
+ gchar *tmp = str;
+ str = g_convert_with_fallback (tmp, len,
+ "UTF-8", charset,
+ NULL, NULL, &len, &error);
+ g_free (tmp);
+
+ if (!str)
+ {
+ g_warning ("Error converting from %s to %s: %s",
+ charset, "UTF-8", error->message);
+ g_error_free (error);
+
+ return NULL;
+ }
+ }
+ else if (!g_utf8_validate (str, -1, NULL))
+ {
+ g_warning ("Error converting from %s to %s: %s",
+ "text/plain;charset=utf-8", "UTF-8", "invalid UTF-8");
+ g_free (str);
+
+ return NULL;
+ }
+
+ result = normalize_to_lf (str, len);
+ g_free (str);
+
+ return result;
+}
+
+/**
+ * gtk_selection_data_set_text:
+ * @selection_data: a #GtkSelectionData
+ * @str: a UTF-8 string
+ * @len: the length of @str, or -1 if @str is nul-terminated.
+ *
+ * Sets the contents of the selection from a UTF-8 encoded string.
+ * The string is converted to the form determined by
+ * @selection_data->target.
+ *
+ * Return value: %TRUE if the selection was successfully set,
+ * otherwise %FALSE.
+ **/
+gboolean
+gtk_selection_data_set_text (GtkSelectionData *selection_data,
+ const gchar *str,
+ gint len)
+{
+ if (len < 0)
+ len = strlen (str);
+
+ init_atoms ();
+
+ if (selection_data->target == utf8_atom)
+ {
+ gtk_selection_data_set (selection_data,
+ utf8_atom,
+ 8, (guchar *)str, len);
+ return TRUE;
+ }
+ else if (selection_data->target == GDK_TARGET_STRING)
+ {
+ return selection_set_string (selection_data, str, len);
+ }
+ else if (selection_data->target == ctext_atom ||
+ selection_data->target == text_atom)
+ {
+ if (selection_set_compound_text (selection_data, str, len))
+ return TRUE;
+ else if (selection_data->target == text_atom)
+ return selection_set_string (selection_data, str, len);
+ }
+ else if (selection_data->target == text_plain_atom ||
+ selection_data->target == text_plain_utf8_atom ||
+ selection_data->target == text_plain_locale_atom)
+ {
+ return selection_set_text_plain (selection_data, str, len);
+ }
+
+ return FALSE;
+}
+
+/**
+ * gtk_selection_data_get_text:
+ * @selection_data: a #GtkSelectionData
+ *
+ * Gets the contents of the selection data as a UTF-8 string.
+ *
+ * Return value: if the selection data contained a recognized
+ * text type and it could be converted to UTF-8, a newly allocated
+ * string containing the converted text, otherwise %NULL.
+ * If the result is non-%NULL it must be freed with g_free().
+ **/
+guchar *
+gtk_selection_data_get_text (GtkSelectionData *selection_data)
+{
+ guchar *result = NULL;
+
+ init_atoms ();
+
+ if (selection_data->length >= 0 &&
+ (selection_data->type == GDK_TARGET_STRING ||
+ selection_data->type == ctext_atom ||
+ selection_data->type == utf8_atom))
+ {
+ gchar **list;
+ gint i;
+ gint count = gdk_text_property_to_utf8_list_for_display (selection_data->display,
+ selection_data->type,
+ selection_data->format,
+ selection_data->data,
+ selection_data->length,
+ &list);
+ if (count > 0)
+ result = list[0];
+
+ for (i = 1; i < count; i++)
+ g_free (list[i]);
+ g_free (list);
+ }
+ else if (selection_data->length >= 0 &&
+ (selection_data->type == text_plain_atom ||
+ selection_data->type == text_plain_utf8_atom ||
+ selection_data->type == text_plain_locale_atom))
+ {
+ result = selection_get_text_plain (selection_data);
+ }
+
+ return result;
+}
+
+/**
+ * gtk_selection_data_set_pixbuf:
+ * @selection_data: a #GtkSelectionData
+ * @pixbuf: a #GdkPixbuf
+ *
+ * Sets the contents of the selection from a #GdkPixbuf
+ * The pixbuf is converted to the form determined by
+ * @selection_data->target.
+ *
+ * Return value: %TRUE if the selection was successfully set,
+ * otherwise %FALSE.
+ *
+ * Since: 2.6
+ **/
+gboolean
+gtk_selection_data_set_pixbuf (GtkSelectionData *selection_data,
+ GdkPixbuf *pixbuf)
+{
+ GSList *formats, *f;
+ gchar **mimes, **m;
+ GdkAtom atom;
+ gboolean result;
+ gchar *str, *type;
+ gsize len;
+
+ formats = gdk_pixbuf_get_formats ();
+
+ for (f = formats; f; f = f->next)
+ {
+ GdkPixbufFormat *fmt = f->data;
+
+ mimes = gdk_pixbuf_format_get_mime_types (fmt);
+ for (m = mimes; *m; m++)
+ {
+ atom = gdk_atom_intern (*m, FALSE);
+ if (selection_data->target == atom)
+ {
+ str = NULL;
+ type = gdk_pixbuf_format_get_name (fmt);
+ result = gdk_pixbuf_save_to_buffer (pixbuf, &str, &len,
+ type, NULL,
+ ((strcmp (type, "png") == 0) ?
+ "compression" : NULL), "2",
+ NULL);
+ if (result)
+ gtk_selection_data_set (selection_data,
+ atom, 8, (guchar *)str, len);
+ g_free (type);
+ g_free (str);
+ g_strfreev (mimes);
+ g_slist_free (formats);
+
+ return result;
+ }
+ }
+
+ g_strfreev (mimes);
+ }
+
+ g_slist_free (formats);
+
+ return FALSE;
+}
+
+/**
+ * gtk_selection_data_get_pixbuf:
+ * @selection_data: a #GtkSelectionData
+ *
+ * Gets the contents of the selection data as a #GdkPixbuf.
+ *
+ * Return value: if the selection data contained a recognized
+ * image type and it could be converted to a #GdkPixbuf, a
+ * newly allocated pixbuf is returned, otherwise %NULL.
+ * If the result is non-%NULL it must be freed with g_object_unref().
+ *
+ * Since: 2.6
+ **/
+GdkPixbuf *
+gtk_selection_data_get_pixbuf (GtkSelectionData *selection_data)
+{
+ GdkPixbufLoader *loader;
+ GdkPixbuf *result = NULL;
+
+ if (selection_data->length > 0)
+ {
+ loader = gdk_pixbuf_loader_new ();
+
+ gdk_pixbuf_loader_write (loader,
+ selection_data->data,
+ selection_data->length,
+ NULL);
+ gdk_pixbuf_loader_close (loader, NULL);
+ result = gdk_pixbuf_loader_get_pixbuf (loader);
+
+ if (result)
+ g_object_ref (result);
+
+ g_object_unref (loader);
+ }
+
+ return result;
+}
+
+/**
+ * gtk_selection_data_set_uris:
+ * @selection_data: a #GtkSelectionData
+ * @uris: a %NULL-terminated array of strings hilding URIs
+ *
+ * Sets the contents of the selection from a list of URIs.
+ * The string is converted to the form determined by
+ * @selection_data->target.
+ *
+ * Return value: %TRUE if the selection was successfully set,
+ * otherwise %FALSE.
+ *
+ * Since: 2.6
+ **/
+gboolean
+gtk_selection_data_set_uris (GtkSelectionData *selection_data,
+ gchar **uris)
+{
+ init_atoms ();
+
+ if (selection_data->target == text_uri_list_atom)
+ {
+ GString *list;
+ gint i;
+ gchar *result;
+ gsize length;
+
+ list = g_string_new (NULL);
+ for (i = 0; uris[i]; i++)
+ {
+ g_string_append (list, uris[i]);
+ g_string_append (list, "\r\n");
+ }
+
+ result = g_convert (list->str, list->len,
+ "ASCII", "UTF-8",
+ NULL, &length, NULL);
+ g_string_free (list, TRUE);
+
+ if (result)
+ {
+ gtk_selection_data_set (selection_data,
+ text_uri_list_atom,
+ 8, (guchar *)result, length);
+
+ g_free (result);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * gtk_selection_data_get_uris:
+ * @selection_data: a #GtkSelectionData
+ *
+ * Gets the contents of the selection data as array of URIs.
+ *
+ * Return value: if the selection data contains a list of
+ * URIs, a newly allocated %NULL-terminated string array
+ * containing the URIs, otherwise %NULL. If the result is
+ * non-%NULL it must be freed with g_strfreev().
+ *
+ * Since: 2.6
+ **/
+gchar **
+gtk_selection_data_get_uris (GtkSelectionData *selection_data)
+{
+ gchar **result = NULL;
+
+ init_atoms ();
+
+ if (selection_data->length >= 0 &&
+ selection_data->type == text_uri_list_atom)
+ {
+ gchar **list;
+ gint count = gdk_text_property_to_utf8_list_for_display (selection_data->display,
+ utf8_atom,
+ selection_data->format,
+ selection_data->data,
+ selection_data->length,
+ &list);
+ if (count > 0)
+ result = g_uri_list_extract_uris (list[0]);
+
+ g_strfreev (list);
+ }
+
+ return result;
+}
+
+
+/**
+ * gtk_selection_data_get_targets:
+ * @selection_data: a #GtkSelectionData object
+ * @targets: location to store an array of targets. The result
+ * stored here must be freed with g_free().
+ * @n_atoms: location to store number of items in @targets.
+ *
+ * Gets the contents of @selection_data as an array of targets.
+ * This can be used to interpret the results of getting
+ * the standard TARGETS target that is always supplied for
+ * any selection.
+ *
+ * Return value: %TRUE if @selection_data contains a valid
+ * array of targets, otherwise %FALSE.
+ **/
+gboolean
+gtk_selection_data_get_targets (GtkSelectionData *selection_data,
+ GdkAtom **targets,
+ gint *n_atoms)
+{
+ if (selection_data->length >= 0 &&
+ selection_data->format == 32 &&
+ selection_data->type == GDK_SELECTION_TYPE_ATOM)
+ {
+ if (targets)
+ *targets = g_memdup (selection_data->data, selection_data->length);
+ if (n_atoms)
+ *n_atoms = selection_data->length / sizeof (GdkAtom);
+
+ return TRUE;
+ }
+ else
+ {
+ if (targets)
+ *targets = NULL;
+ if (n_atoms)
+ *n_atoms = -1;
+
+ return FALSE;
+ }
+}
+
+/**
+ * gtk_targets_include_text:
+ * @targets: an array of #GdkAtom<!-- -->s
+ * @n_targets: the length of @targets
+ *
+ * Determines if any of the targets in @targets can be used to
+ * provide text.
+ *
+ * Return value: %TRUE if @targets include a suitable target for text,
+ * otherwise %FALSE.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_targets_include_text (GdkAtom *targets,
+ gint n_targets)
+{
+ gint i;
+ gboolean result = FALSE;
+
+ /* Keep in sync with gtk_target_list_add_text_targets()
+ */
+
+ init_atoms ();
+
+ for (i = 0; i < n_targets; i++)
+ {
+ if (targets[i] == utf8_atom ||
+ targets[i] == text_atom ||
+ targets[i] == GDK_TARGET_STRING ||
+ targets[i] == ctext_atom ||
+ targets[i] == text_plain_atom ||
+ targets[i] == text_plain_utf8_atom ||
+ targets[i] == text_plain_locale_atom)
+ {
+ result = TRUE;
+ break;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * gtk_targets_include_rich_text:
+ * @targets: an array of #GdkAtom<!-- -->s
+ * @n_targets: the length of @targets
+ * @buffer: a #GtkTextBuffer
+ *
+ * Determines if any of the targets in @targets can be used to
+ * provide rich text.
+ *
+ * Return value: %TRUE if @targets include a suitable target for rich text,
+ * otherwise %FALSE.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_targets_include_rich_text (GdkAtom *targets,
+ gint n_targets,
+ GtkTextBuffer *buffer)
+{
+ GdkAtom *rich_targets;
+ gint n_rich_targets;
+ gint i, j;
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+
+ init_atoms ();
+
+ rich_targets = gtk_text_buffer_get_deserialize_formats (buffer,
+ &n_rich_targets);
+
+ for (i = 0; i < n_targets; i++)
+ {
+ for (j = 0; j < n_rich_targets; j++)
+ {
+ if (targets[i] == rich_targets[j])
+ {
+ result = TRUE;
+ goto done;
+ }
+ }
+ }
+
+ done:
+ g_free (rich_targets);
+
+ return result;
+}
+
+/**
+ * gtk_selection_data_targets_include_text:
+ * @selection_data: a #GtkSelectionData object
+ *
+ * Given a #GtkSelectionData object holding a list of targets,
+ * determines if any of the targets in @targets can be used to
+ * provide text.
+ *
+ * Return value: %TRUE if @selection_data holds a list of targets,
+ * and a suitable target for text is included, otherwise %FALSE.
+ **/
+gboolean
+gtk_selection_data_targets_include_text (GtkSelectionData *selection_data)
+{
+ GdkAtom *targets;
+ gint n_targets;
+ gboolean result = FALSE;
+
+ init_atoms ();
+
+ if (gtk_selection_data_get_targets (selection_data, &targets, &n_targets))
+ {
+ result = gtk_targets_include_text (targets, n_targets);
+ g_free (targets);