X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkselection.c;h=b76090be200499f7ef0b1e96d4157ee2b7d7d56a;hb=a8698a24c7a53fe4d34211053529f14747f7ce5f;hp=c443282e90fdaa726399c3e403109a30304df8d5;hpb=62e51198728e9583831b36ae442a96c185bdd2e7;p=~andy%2Fgtk diff --git a/gtk/gtkselection.c b/gtk/gtkselection.c index c443282e9..b76090be2 100644 --- a/gtk/gtkselection.c +++ b/gtk/gtkselection.c @@ -2,27 +2,27 @@ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public + * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. + * Lesser General Public License for more details. * - * You should have received a copy of the GNU Library General Public + * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ -/* This file implements most of the work of the ICCM selection protocol. +/* This file implements most of the work of the ICCCM selection protocol. * The code was written after an intensive study of the equivalent part * of John Ousterhout's Tk toolkit, and does many things in much the * same way. * - * The one thing in the ICCM that isn't fully supported here (or in Tk) + * The one thing in the ICCCM that isn't fully supported here (or in Tk) * is side effects targets. For these to be handled properly, MULTIPLE * targets need to be done in the order specified. This cannot be * guaranteed with the way we do things, since if we are doing INCR @@ -45,38 +45,50 @@ to the TARGETS request, though I don't really think so ... */ /* - * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ +#include #include #include #include "gdk.h" -#if defined (GDK_WINDOWING_X11) -#include "x11/gdkx.h" /* For gdk_window_lookup() */ -#elif defined (GDK_WINDOWING_WIN32) -#include "win32/gdkwin32.h" /* For gdk_window_lookup() */ -#elif defined (GDK_WINDOWING_NANOX) -#include "nanox/gdkprivate-nanox.h" /* For gdk_window_lookup() */ -#endif - #include "gtkmain.h" #include "gtkselection.h" -#include "gtksignal.h" +#include "gtktextbufferrichtext.h" +#include "gtkintl.h" +#include "gdk-pixbuf/gdk-pixbuf.h" + +#ifdef GDK_WINDOWING_X11 +#include "x11/gdkx.h" +#endif + +#ifdef GDK_WINDOWING_WIN32 +#include "win32/gdkwin32.h" +#endif + +#include "gtkalias.h" -/* #define DEBUG_SELECTION */ +#undef DEBUG_SELECTION /* Maximum size of a sent chunk, in bytes. Also the default size of our buffers */ -#ifdef GDK_WINDOWING_WIN32 -/* No chunks on Win32 */ -#define GTK_SELECTION_MAX_SIZE G_MAXINT +#ifdef GDK_WINDOWING_X11 +#define GTK_SELECTION_MAX_SIZE(display) \ + MIN(262144, \ + XExtendedMaxRequestSize (GDK_DISPLAY_XDISPLAY (display)) == 0 \ + ? XMaxRequestSize (GDK_DISPLAY_XDISPLAY (display)) - 100 \ + : XExtendedMaxRequestSize (GDK_DISPLAY_XDISPLAY (display)) - 100) #else -#define GTK_SELECTION_MAX_SIZE 4000 +/* No chunks on Win32 */ +#define GTK_SELECTION_MAX_SIZE(display) G_MAXINT #endif + +#define IDLE_ABORT_TIME 30 + enum { INCR, MULTIPLE, @@ -92,9 +104,10 @@ typedef struct _GtkRetrievalInfo GtkRetrievalInfo; struct _GtkSelectionInfo { - GdkAtom selection; - GtkWidget *widget; /* widget that owns selection */ - guint32 time; /* time used to acquire selection */ + GdkAtom selection; + GtkWidget *widget; /* widget that owns selection */ + guint32 time; /* time used to acquire selection */ + GdkDisplay *display; /* needed in gtk_selection_remove_all */ }; struct _GtkIncrConversion @@ -110,7 +123,6 @@ struct _GtkIncrConversion struct _GtkIncrInfo { - GtkWidget *widget; /* Selection owner */ GdkWindow *requestor; /* Requestor window - we create a GdkWindow so we can receive events */ GdkAtom selection; /* Selection we're sending */ @@ -140,8 +152,8 @@ struct _GtkRetrievalInfo /* Local Functions */ static void gtk_selection_init (void); -static gint gtk_selection_incr_timeout (GtkIncrInfo *info); -static gint gtk_selection_retrieval_timeout (GtkRetrievalInfo *info); +static gboolean gtk_selection_incr_timeout (GtkIncrInfo *info); +static gboolean gtk_selection_retrieval_timeout (GtkRetrievalInfo *info); static void gtk_selection_retrieval_report (GtkRetrievalInfo *info, GdkAtom type, gint format, @@ -162,7 +174,7 @@ static GList *current_incrs = NULL; static GList *current_selections = NULL; static GdkAtom gtk_selection_atoms[LAST_ATOM]; -static const char *gtk_selection_handler_key = "gtk-selection-handlers"; +static const char gtk_selection_handler_key[] = "gtk-selection-handlers"; /**************** * Target Lists * @@ -172,6 +184,16 @@ static const char *gtk_selection_handler_key = "gtk-selection-handlers"; * Target lists */ + +/** + * gtk_target_list_new: + * @targets: Pointer to an array of #GtkTargetEntry + * @ntargets: number of entries in @targets. + * + * Creates a new #GtkTargetList from an array of #GtkTargetEntry. + * + * Return value: the new #GtkTargetList. + **/ GtkTargetList * gtk_target_list_new (const GtkTargetEntry *targets, guint ntargets) @@ -186,14 +208,31 @@ gtk_target_list_new (const GtkTargetEntry *targets, return result; } -void +/** + * gtk_target_list_ref: + * @list: a #GtkTargetList + * + * Increases the reference count of a #GtkTargetList by one. + * + * Return value: the passed in #GtkTargetList. + **/ +GtkTargetList * gtk_target_list_ref (GtkTargetList *list) { - g_return_if_fail (list != NULL); + g_return_val_if_fail (list != NULL, NULL); list->ref_count++; + + return list; } +/** + * gtk_target_list_unref: + * @list: a #GtkTargetList + * + * Decreases the reference count of a #GtkTargetList by one. + * If the resulting reference count is zero, frees the list. + **/ void gtk_target_list_unref (GtkTargetList *list) { @@ -217,11 +256,20 @@ gtk_target_list_unref (GtkTargetList *list) } } +/** + * gtk_target_list_add: + * @list: a #GtkTargetList + * @target: the interned atom representing the target + * @flags: the flags for this target + * @info: an ID that will be passed back to the application + * + * Appends another target to a #GtkTargetList. + **/ void gtk_target_list_add (GtkTargetList *list, - GdkAtom target, - guint flags, - guint info) + GdkAtom target, + guint flags, + guint info) { GtkTargetPair *pair; @@ -235,6 +283,197 @@ gtk_target_list_add (GtkTargetList *list, list->list = g_list_append (list->list, pair); } +static GdkAtom utf8_atom; +static GdkAtom text_atom; +static GdkAtom ctext_atom; +static GdkAtom text_plain_atom; +static GdkAtom text_plain_utf8_atom; +static GdkAtom text_plain_locale_atom; +static GdkAtom text_uri_list_atom; + +static void +init_atoms (void) +{ + gchar *tmp; + const gchar *charset; + + if (!utf8_atom) + { + utf8_atom = gdk_atom_intern_static_string ("UTF8_STRING"); + text_atom = gdk_atom_intern_static_string ("TEXT"); + ctext_atom = gdk_atom_intern_static_string ("COMPOUND_TEXT"); + text_plain_atom = gdk_atom_intern_static_string ("text/plain"); + text_plain_utf8_atom = gdk_atom_intern_static_string ("text/plain;charset=utf-8"); + g_get_charset (&charset); + tmp = g_strdup_printf ("text/plain;charset=%s", charset); + text_plain_locale_atom = gdk_atom_intern (tmp, FALSE); + g_free (tmp); + + text_uri_list_atom = gdk_atom_intern_static_string ("text/uri-list"); + } +} + +/** + * gtk_target_list_add_text_targets: + * @list: a #GtkTargetList + * @info: an ID that will be passed back to the application + * + * Appends the text targets supported by #GtkSelection to + * the target list. All targets are added with the same @info. + * + * Since: 2.6 + **/ +void +gtk_target_list_add_text_targets (GtkTargetList *list, + guint info) +{ + g_return_if_fail (list != NULL); + + init_atoms (); + + /* Keep in sync with gtk_selection_data_targets_include_text() + */ + gtk_target_list_add (list, utf8_atom, 0, info); + gtk_target_list_add (list, ctext_atom, 0, info); + gtk_target_list_add (list, text_atom, 0, info); + gtk_target_list_add (list, GDK_TARGET_STRING, 0, info); + gtk_target_list_add (list, text_plain_utf8_atom, 0, info); + gtk_target_list_add (list, text_plain_locale_atom, 0, info); + gtk_target_list_add (list, text_plain_atom, 0, info); +} + +/** + * gtk_target_list_add_rich_text_targets: + * @list: a #GtkTargetList + * @info: an ID that will be passed back to the application + * @deserializable: if %TRUE, then deserializable rich text formats + * will be added, serializable formats otherwise. + * @buffer: a #GtkTextBuffer. + * + * Appends the rich text targets registered with + * gtk_text_buffer_register_serialize_format() or + * gtk_text_buffer_register_deserialize_format() to the target list. All + * targets are added with the same @info. + * + * Since: 2.10 + **/ +void +gtk_target_list_add_rich_text_targets (GtkTargetList *list, + guint info, + gboolean deserializable, + GtkTextBuffer *buffer) +{ + GdkAtom *atoms; + gint n_atoms; + gint i; + + g_return_if_fail (list != NULL); + g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer)); + + if (deserializable) + atoms = gtk_text_buffer_get_deserialize_formats (buffer, &n_atoms); + else + atoms = gtk_text_buffer_get_serialize_formats (buffer, &n_atoms); + + for (i = 0; i < n_atoms; i++) + gtk_target_list_add (list, atoms[i], 0, info); + + g_free (atoms); +} + +/** + * gtk_target_list_add_image_targets: + * @list: a #GtkTargetList + * @info: an ID that will be passed back to the application + * @writable: whether to add only targets for which GTK+ knows + * how to convert a pixbuf into the format + * + * Appends the image targets supported by #GtkSelection to + * the target list. All targets are added with the same @info. + * + * Since: 2.6 + **/ +void +gtk_target_list_add_image_targets (GtkTargetList *list, + guint info, + gboolean writable) +{ + GSList *formats, *f; + gchar **mimes, **m; + GdkAtom atom; + + g_return_if_fail (list != NULL); + + formats = gdk_pixbuf_get_formats (); + + /* Make sure png comes first */ + for (f = formats; f; f = f->next) + { + GdkPixbufFormat *fmt = f->data; + gchar *name; + + name = gdk_pixbuf_format_get_name (fmt); + if (strcmp (name, "png") == 0) + { + formats = g_slist_delete_link (formats, f); + formats = g_slist_prepend (formats, fmt); + + g_free (name); + + break; + } + + g_free (name); + } + + for (f = formats; f; f = f->next) + { + GdkPixbufFormat *fmt = f->data; + + if (writable && !gdk_pixbuf_format_is_writable (fmt)) + continue; + + mimes = gdk_pixbuf_format_get_mime_types (fmt); + for (m = mimes; *m; m++) + { + atom = gdk_atom_intern (*m, FALSE); + gtk_target_list_add (list, atom, 0, info); + } + g_strfreev (mimes); + } + + g_slist_free (formats); +} + +/** + * gtk_target_list_add_uri_targets: + * @list: a #GtkTargetList + * @info: an ID that will be passed back to the application + * + * Appends the URI targets supported by #GtkSelection to + * the target list. All targets are added with the same @info. + * + * Since: 2.6 + **/ +void +gtk_target_list_add_uri_targets (GtkTargetList *list, + guint info) +{ + g_return_if_fail (list != NULL); + + init_atoms (); + + gtk_target_list_add (list, text_uri_list_atom, 0, info); +} + +/** + * gtk_target_list_add_table: + * @list: a #GtkTargetList + * @targets: the table of #GtkTargetEntry + * @ntargets: number of targets in the table + * + * Prepends a table of #GtkTargetEntry to a target list. + **/ void gtk_target_list_add_table (GtkTargetList *list, const GtkTargetEntry *targets, @@ -253,6 +492,13 @@ gtk_target_list_add_table (GtkTargetList *list, } } +/** + * gtk_target_list_remove: + * @list: a #GtkTargetList + * @target: the interned atom representing the target + * + * Removes a target from a target list. + **/ void gtk_target_list_remove (GtkTargetList *list, GdkAtom target) @@ -280,6 +526,16 @@ gtk_target_list_remove (GtkTargetList *list, } } +/** + * gtk_target_list_find: + * @list: a #GtkTargetList + * @target: an interned atom representing the target to search for + * @info: a pointer to the location to store application info for target + * + * Looks up a given target in a #GtkTargetList. + * + * Return value: %TRUE if the target was found, otherwise %FALSE + **/ gboolean gtk_target_list_find (GtkTargetList *list, GdkAtom target, @@ -301,56 +557,120 @@ gtk_target_list_find (GtkTargetList *list, return FALSE; } +/** + * gtk_target_table_new_from_list: + * @list: a #GtkTargetList + * @n_targets: return location for the number ot targets in the table + * + * This function creates an #GtkTargetEntry array that contains the + * same targets as the passed %list. The returned table is newly + * allocated and should be freed using gtk_target_table_free() when no + * longer needed. + * + * Return value: the new table. + * + * Since: 2.10 + **/ +GtkTargetEntry * +gtk_target_table_new_from_list (GtkTargetList *list, + gint *n_targets) +{ + GtkTargetEntry *targets; + GList *tmp_list; + gint i; -/************************************************************* - * gtk_selection_owner_set: - * Claim ownership of a selection. - * arguments: - * widget: new selection owner - * selection: which selection - * time: time (use GDK_CURRENT_TIME only if necessary) + g_return_val_if_fail (list != NULL, NULL); + g_return_val_if_fail (n_targets != NULL, NULL); + + *n_targets = g_list_length (list->list); + targets = g_new0 (GtkTargetEntry, *n_targets); + + for (i = 0, tmp_list = list->list; + i < *n_targets; + i++, tmp_list = g_list_next (tmp_list)) + { + GtkTargetPair *pair = tmp_list->data; + + targets[i].target = gdk_atom_name (pair->target); + targets[i].flags = pair->flags; + targets[i].info = pair->info; + } + + return targets; +} + +/** + * gtk_target_table_free: + * @targets: a #GtkTargetEntry array + * @n_targets: the number of entries in the array * - * results: - *************************************************************/ + * This function frees a target table as returned by + * gtk_target_table_new_from_list() + * + * Since: 2.10 + **/ +void +gtk_target_table_free (GtkTargetEntry *targets, + gint n_targets) +{ + gint i; -gint -gtk_selection_owner_set (GtkWidget *widget, - GdkAtom selection, - guint32 time) + g_return_if_fail (targets == NULL || n_targets > 0); + + for (i = 0; i < n_targets; i++) + g_free (targets[i].target); + + g_free (targets); +} + +/** + * gtk_selection_owner_set_for_display: + * @display: the #Gdkdisplay where the selection is set + * @widget: new selection owner (a #GdkWidget), or %NULL. + * @selection: an interned atom representing the selection to claim. + * @time_: timestamp with which to claim the selection + * + * Claim ownership of a given selection for a particular widget, or, + * if @widget is %NULL, release ownership of the selection. + * + * Return value: TRUE if the operation succeeded + * + * Since: 2.2 + */ +gboolean +gtk_selection_owner_set_for_display (GdkDisplay *display, + GtkWidget *widget, + GdkAtom selection, + guint32 time) { GList *tmp_list; GtkWidget *old_owner; GtkSelectionInfo *selection_info = NULL; GdkWindow *window; + + g_return_val_if_fail (GDK_IS_DISPLAY (display), FALSE); + g_return_val_if_fail (selection != GDK_NONE, FALSE); + g_return_val_if_fail (widget == NULL || GTK_WIDGET_REALIZED (widget), FALSE); + g_return_val_if_fail (widget == NULL || gtk_widget_get_display (widget) == display, FALSE); if (widget == NULL) window = NULL; else - { - if (!GTK_WIDGET_REALIZED (widget)) - gtk_widget_realize (widget); - - window = widget->window; - } - + window = widget->window; + tmp_list = current_selections; while (tmp_list) { - selection_info = (GtkSelectionInfo *)tmp_list->data; - - if (selection_info->selection == selection) - break; + if (((GtkSelectionInfo *)tmp_list->data)->selection == selection) + { + selection_info = tmp_list->data; + break; + } tmp_list = tmp_list->next; } - if (tmp_list == NULL) - selection_info = NULL; - else - if (selection_info->widget == widget) - return TRUE; - - if (gdk_selection_owner_set (window, selection, time, TRUE)) + if (gdk_selection_owner_set_for_display (display, window, selection, time, TRUE)) { old_owner = NULL; @@ -373,29 +693,32 @@ gtk_selection_owner_set (GtkWidget *widget, selection_info->selection = selection; selection_info->widget = widget; selection_info->time = time; - current_selections = g_list_append (current_selections, - selection_info); + selection_info->display = display; + current_selections = g_list_prepend (current_selections, + selection_info); } else { old_owner = selection_info->widget; selection_info->widget = widget; selection_info->time = time; + selection_info->display = display; } } /* If another widget in the application lost the selection, - * send it a GDK_SELECTION_CLEAR event, unless we're setting - * the owner to None, in which case an event will be sent */ - if (old_owner && (widget != NULL)) + * send it a GDK_SELECTION_CLEAR event. + */ + if (old_owner && old_owner != widget) { - GdkEventSelection event; + GdkEvent *event = gdk_event_new (GDK_SELECTION_CLEAR); - event.type = GDK_SELECTION_CLEAR; - event.window = old_owner->window; - event.selection = selection; - event.time = time; + event->selection.window = g_object_ref (old_owner->window); + event->selection.selection = selection; + event->selection.time = time; - gtk_widget_event (old_owner, (GdkEvent *) &event); + gtk_widget_event (old_owner, event); + + gdk_event_free (event); } return TRUE; } @@ -403,18 +726,40 @@ gtk_selection_owner_set (GtkWidget *widget, return FALSE; } -/************************************************************* - * gtk_selection_add_target - * Add specified target to list of supported targets - * - * arguments: - * widget: The widget for which this target applies - * selection: - * target: - * info: guint to pass to to the selection_get signal - * - * results: - *************************************************************/ +/** + * gtk_selection_owner_set: + * @widget: a #GtkWidget, or %NULL. + * @selection: an interned atom representing the selection to claim + * @time_: timestamp with which to claim the selection + * + * Claims ownership of a given selection for a particular widget, + * or, if @widget is %NULL, release ownership of the selection. + * + * Return value: %TRUE if the operation succeeded + **/ +gboolean +gtk_selection_owner_set (GtkWidget *widget, + GdkAtom selection, + guint32 time) +{ + GdkDisplay *display; + + g_return_val_if_fail (widget == NULL || GTK_WIDGET_REALIZED (widget), FALSE); + g_return_val_if_fail (selection != GDK_NONE, FALSE); + + if (widget) + display = gtk_widget_get_display (widget); + else + { + GTK_NOTE (MULTIHEAD, + g_warning ("gtk_selection_owner_set (NULL,...) is not multihead safe")); + + display = gdk_display_get_default (); + } + + return gtk_selection_owner_set_for_display (display, widget, + selection, time); +} typedef struct _GtkSelectionTargetList GtkSelectionTargetList; @@ -431,7 +776,7 @@ gtk_selection_target_list_get (GtkWidget *widget, GList *tmp_list; GList *lists; - lists = gtk_object_get_data (GTK_OBJECT (widget), gtk_selection_handler_key); + lists = g_object_get_data (G_OBJECT (widget), gtk_selection_handler_key); tmp_list = lists; while (tmp_list) @@ -447,7 +792,7 @@ gtk_selection_target_list_get (GtkWidget *widget, sellist->list = gtk_target_list_new (NULL, 0); lists = g_list_prepend (lists, sellist); - gtk_object_set_data (GTK_OBJECT (widget), gtk_selection_handler_key, lists); + g_object_set_data (G_OBJECT (widget), I_(gtk_selection_handler_key), lists); return sellist->list; } @@ -459,7 +804,7 @@ gtk_selection_target_list_remove (GtkWidget *widget) GList *tmp_list; GList *lists; - lists = gtk_object_get_data (GTK_OBJECT (widget), gtk_selection_handler_key); + lists = g_object_get_data (G_OBJECT (widget), gtk_selection_handler_key); tmp_list = lists; while (tmp_list) @@ -473,9 +818,59 @@ gtk_selection_target_list_remove (GtkWidget *widget) } g_list_free (lists); - gtk_object_set_data (GTK_OBJECT (widget), gtk_selection_handler_key, NULL); + g_object_set_data (G_OBJECT (widget), I_(gtk_selection_handler_key), NULL); +} + +/** + * gtk_selection_clear_targets: + * @widget: a #GtkWidget + * @selection: an atom representing a selection + * + * Remove all targets registered for the given selection for the + * widget. + **/ +void +gtk_selection_clear_targets (GtkWidget *widget, + GdkAtom selection) +{ + GtkSelectionTargetList *sellist; + GList *tmp_list; + GList *lists; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (selection != GDK_NONE); + + lists = g_object_get_data (G_OBJECT (widget), gtk_selection_handler_key); + + tmp_list = lists; + while (tmp_list) + { + sellist = tmp_list->data; + if (sellist->selection == selection) + { + lists = g_list_delete_link (lists, tmp_list); + gtk_target_list_unref (sellist->list); + g_free (sellist); + + break; + } + + tmp_list = tmp_list->next; + } + + g_object_set_data (G_OBJECT (widget), I_(gtk_selection_handler_key), lists); } +/** + * gtk_selection_add_target: + * @widget: a #GtkTarget + * @selection: the selection + * @target: target to add. + * @info: A unsigned integer which will be passed back to the application. + * + * Appends a specified target to the list of supported targets for a + * given widget and selection. + **/ void gtk_selection_add_target (GtkWidget *widget, GdkAtom selection, @@ -484,12 +879,26 @@ gtk_selection_add_target (GtkWidget *widget, { GtkTargetList *list; - g_return_if_fail (widget != NULL); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (selection != GDK_NONE); list = gtk_selection_target_list_get (widget, selection); gtk_target_list_add (list, target, 0, info); +#ifdef GDK_WINDOWING_WIN32 + gdk_win32_selection_add_targets (widget->window, selection, 1, &target); +#endif } +/** + * gtk_selection_add_targets: + * @widget: a #GtkWidget + * @selection: the selection + * @targets: a table of targets to add + * @ntargets: number of entries in @targets + * + * Prepends a table of targets to the list of supported targets + * for a given widget and selection. + **/ void gtk_selection_add_targets (GtkWidget *widget, GdkAtom selection, @@ -497,25 +906,37 @@ gtk_selection_add_targets (GtkWidget *widget, guint ntargets) { GtkTargetList *list; - - g_return_if_fail (widget != NULL); + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (selection != GDK_NONE); g_return_if_fail (targets != NULL); list = gtk_selection_target_list_get (widget, selection); gtk_target_list_add_table (list, targets, ntargets); + +#ifdef GDK_WINDOWING_WIN32 + { + int i; + GdkAtom *atoms = g_new (GdkAtom, ntargets); + + for (i = 0; i < ntargets; ++i) + atoms[i] = gdk_atom_intern (targets[i].target, FALSE); + gdk_win32_selection_add_targets (widget->window, selection, ntargets, atoms); + g_free (atoms); + } +#endif } -/************************************************************* - * gtk_selection_remove_all: - * Removes all handlers and unsets ownership of all - * selections for a widget. Called when widget is being - * destroyed - * - * arguments: - * widget: The widget - * results: - *************************************************************/ +/** + * gtk_selection_remove_all: + * @widget: a #GtkWidget + * + * Removes all handlers and unsets ownership of all + * selections for a widget. Called when widget is being + * destroyed. This function will not generally be + * called by applications. + **/ void gtk_selection_remove_all (GtkWidget *widget) { @@ -525,209 +946,1042 @@ gtk_selection_remove_all (GtkWidget *widget) /* Remove pending requests/incrs for this widget */ - tmp_list = current_incrs; + tmp_list = current_retrievals; while (tmp_list) { next = tmp_list->next; - if (((GtkIncrInfo *)tmp_list->data)->widget == widget) + if (((GtkRetrievalInfo *)tmp_list->data)->widget == widget) { - current_incrs = g_list_remove_link (current_incrs, tmp_list); + current_retrievals = g_list_remove_link (current_retrievals, + tmp_list); /* structure will be freed in timeout */ g_list_free (tmp_list); } tmp_list = next; } - tmp_list = current_retrievals; + /* Disclaim ownership of any selections */ + + tmp_list = current_selections; while (tmp_list) { next = tmp_list->next; - if (((GtkRetrievalInfo *)tmp_list->data)->widget == widget) - { - current_retrievals = g_list_remove_link (current_retrievals, + selection_info = (GtkSelectionInfo *)tmp_list->data; + + if (selection_info->widget == widget) + { + gdk_selection_owner_set_for_display (selection_info->display, + NULL, + selection_info->selection, + GDK_CURRENT_TIME, FALSE); + current_selections = g_list_remove_link (current_selections, tmp_list); - /* structure will be freed in timeout */ g_list_free (tmp_list); + g_free (selection_info); } + tmp_list = next; } + + /* Remove all selection lists */ + gtk_selection_target_list_remove (widget); +} + + +/** + * gtk_selection_convert: + * @widget: The widget which acts as requestor + * @selection: Which selection to get + * @target: Form of information desired (e.g., STRING) + * @time_: Time of request (usually of triggering event) + In emergency, you could use #GDK_CURRENT_TIME + * + * Requests the contents of a selection. When received, + * a "selection_received" signal will be generated. + * + * Return value: %TRUE if requested succeeded. %FALSE if we could not process + * request. (e.g., there was already a request in process for + * this widget). + **/ +gboolean +gtk_selection_convert (GtkWidget *widget, + GdkAtom selection, + GdkAtom target, + guint32 time_) +{ + GtkRetrievalInfo *info; + GList *tmp_list; + GdkWindow *owner_window; + GdkDisplay *display; - /* Disclaim ownership of any selections */ + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + g_return_val_if_fail (selection != GDK_NONE, FALSE); - tmp_list = current_selections; + if (initialize) + gtk_selection_init (); + + if (!GTK_WIDGET_REALIZED (widget)) + gtk_widget_realize (widget); + + /* Check to see if there are already any retrievals in progress for + this widget. If we changed GDK to use the selection for the + window property in which to store the retrieved information, then + we could support multiple retrievals for different selections. + This might be useful for DND. */ + + tmp_list = current_retrievals; while (tmp_list) { - next = tmp_list->next; - selection_info = (GtkSelectionInfo *)tmp_list->data; - - if (selection_info->widget == widget) - { - gdk_selection_owner_set (NULL, - selection_info->selection, - GDK_CURRENT_TIME, FALSE); - current_selections = g_list_remove_link (current_selections, - tmp_list); - g_list_free (tmp_list); - g_free (selection_info); - } - - tmp_list = next; + 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 + * only 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 #GdkAtoms + * @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 #GdkAtoms + * @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); } - /* Remove all selection lists */ - gtk_selection_target_list_remove (widget); + return result; } -/************************************************************* - * gtk_selection_convert: - * Request the contents of a selection. When received, - * a "selection_received" signal will be generated. +/** + * gtk_selection_data_targets_include_rich_text: + * @selection_data: a #GtkSelectionData object + * @buffer: a #GtkTextBuffer * - * arguments: - * widget: The widget which acts as requestor - * selection: Which selection to get - * target: Form of information desired (e.g., STRING) - * time: Time of request (usually of triggering event) - * In emergency, you could use GDK_CURRENT_TIME + * Given a #GtkSelectionData object holding a list of targets, + * determines if any of the targets in @targets can be used to + * provide rich text. * - * results: - * TRUE if requested succeeded. FALSE if we could not process - * request. (e.g., there was already a request in process for - * this widget). - *************************************************************/ - -gint -gtk_selection_convert (GtkWidget *widget, - GdkAtom selection, - GdkAtom target, - guint32 time) + * Return value: %TRUE if @selection_data holds a list of targets, + * and a suitable target for rich text is included, + * otherwise %FALSE. + * + * Since: 2.10 + **/ +gboolean +gtk_selection_data_targets_include_rich_text (GtkSelectionData *selection_data, + GtkTextBuffer *buffer) { - GtkRetrievalInfo *info; - GList *tmp_list; - GdkWindow *owner_window; - - g_return_val_if_fail (widget != NULL, FALSE); - - if (initialize) - gtk_selection_init (); - - if (!GTK_WIDGET_REALIZED (widget)) - gtk_widget_realize (widget); - - /* Check to see if there are already any retrievals in progress for - this widget. If we changed GDK to use the selection for the - window property in which to store the retrieved information, then - we could support multiple retrievals for different selections. - This might be useful for DND. */ - - tmp_list = current_retrievals; - while (tmp_list) + GdkAtom *targets; + gint n_targets; + gboolean result = FALSE; + + g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE); + + init_atoms (); + + if (gtk_selection_data_get_targets (selection_data, &targets, &n_targets)) { - info = (GtkRetrievalInfo *)tmp_list->data; - if (info->widget == widget) - return FALSE; - tmp_list = tmp_list->next; + result = gtk_targets_include_rich_text (targets, n_targets, buffer); + g_free (targets); } - - info = g_new (GtkRetrievalInfo, 1); - - info->widget = widget; - info->selection = selection; - info->target = target; - info->buffer = NULL; - info->offset = -1; - - /* Check if this process has current owner. If so, call handler - procedure directly to avoid deadlocks with INCR. */ - - owner_window = gdk_selection_owner_get (selection); - - if (owner_window != NULL) + + return result; +} + +/** + * gtk_targets_include_image: + * @targets: an array of #GdkAtoms + * @n_targets: the length of @targets + * @writable: whether to accept only targets for which GTK+ knows + * how to convert a pixbuf into the format + * + * Determines if any of the targets in @targets can be used to + * provide a #GdkPixbuf. + * + * Return value: %TRUE if @targets include a suitable target for images, + * otherwise %FALSE. + * + * Since: 2.10 + **/ +gboolean +gtk_targets_include_image (GdkAtom *targets, + gint n_targets, + gboolean writable) +{ + GtkTargetList *list; + GList *l; + gint i; + gboolean result = FALSE; + + list = gtk_target_list_new (NULL, 0); + gtk_target_list_add_image_targets (list, 0, writable); + for (i = 0; i < n_targets && !result; i++) { - GtkWidget *owner_widget; - GtkSelectionData selection_data; - - selection_data.selection = selection; - selection_data.target = target; - selection_data.data = NULL; - selection_data.length = -1; - - gdk_window_get_user_data (owner_window, (gpointer *)&owner_widget); - - if (owner_widget != NULL) + for (l = list->list; l; l = l->next) { - 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; + GtkTargetPair *pair = (GtkTargetPair *)l->data; + if (pair->target == targets[i]) + { + result = TRUE; + break; + } } } - - /* Otherwise, we need to go through X */ - - current_retrievals = g_list_append (current_retrievals, info); - gdk_selection_convert (widget->window, selection, target, time); - gtk_timeout_add (1000, (GtkFunction) gtk_selection_retrieval_timeout, info); - - return TRUE; + gtk_target_list_unref (list); + + return result; } + +/** + * gtk_selection_data_targets_include_image: + * @selection_data: a #GtkSelectionData object + * @writable: whether to accept only targets for which GTK+ knows + * how to convert a pixbuf into the format + * + * Given a #GtkSelectionData object holding a list of targets, + * determines if any of the targets in @targets can be used to + * provide a #GdkPixbuf. + * + * Return value: %TRUE if @selection_data holds a list of targets, + * and a suitable target for images is included, otherwise %FALSE. + * + * Since: 2.6 + **/ +gboolean +gtk_selection_data_targets_include_image (GtkSelectionData *selection_data, + gboolean writable) +{ + GdkAtom *targets; + gint n_targets; + gboolean result = FALSE; -/************************************************************* - * gtk_selection_data_set: - * Store new data into a GtkSelectionData object. Should - * _only_ by called from a selection handler callback. - * Null terminates the stored data. - * arguments: - * 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 - * results: - *************************************************************/ + init_atoms (); -void -gtk_selection_data_set (GtkSelectionData *selection_data, - GdkAtom type, - gint format, - const guchar *data, - gint length) -{ - if (selection_data->data) - g_free (selection_data->data); - - selection_data->type = type; - selection_data->format = format; - - if (data) + if (gtk_selection_data_get_targets (selection_data, &targets, &n_targets)) { - selection_data->data = g_new (guchar, length+1); - memcpy (selection_data->data, data, length); - selection_data->data[length] = 0; + result = gtk_targets_include_image (targets, n_targets, writable); + g_free (targets); } - else + + return result; +} + +/** + * gtk_targets_include_uri: + * @targets: an array of #GdkAtoms + * @n_targets: the length of @targets + * + * Determines if any of the targets in @targets can be used to + * provide an uri list. + * + * Return value: %TRUE if @targets include a suitable target for uri lists, + * otherwise %FALSE. + * + * Since: 2.10 + **/ +gboolean +gtk_targets_include_uri (GdkAtom *targets, + gint n_targets) +{ + gint i; + gboolean result = FALSE; + + /* Keep in sync with gtk_target_list_add_uri_targets() + */ + + init_atoms (); + + for (i = 0; i < n_targets; i++) { - g_return_if_fail (length <= 0); - - if (length < 0) - selection_data->data = NULL; - else - selection_data->data = g_strdup(""); + if (targets[i] == text_uri_list_atom) + { + result = TRUE; + break; + } } - selection_data->length = length; + return result; +} + +/** + * gtk_selection_data_targets_include_uri: + * @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 a list or URIs. + * + * Return value: %TRUE if @selection_data holds a list of targets, + * and a suitable target for text is included, otherwise %FALSE. + * + * Since: 2.10 + **/ +gboolean +gtk_selection_data_targets_include_uri (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_uri (targets, n_targets); + g_free (targets); + } + + return result; } + /************************************************************* * gtk_selection_init: * Initialize local variables @@ -739,32 +1993,38 @@ gtk_selection_data_set (GtkSelectionData *selection_data, static void gtk_selection_init (void) { - gtk_selection_atoms[INCR] = gdk_atom_intern ("INCR", FALSE); - gtk_selection_atoms[MULTIPLE] = gdk_atom_intern ("MULTIPLE", FALSE); - gtk_selection_atoms[TIMESTAMP] = gdk_atom_intern ("TIMESTAMP", FALSE); - gtk_selection_atoms[TARGETS] = gdk_atom_intern ("TARGETS", FALSE); + gtk_selection_atoms[INCR] = gdk_atom_intern_static_string ("INCR"); + gtk_selection_atoms[MULTIPLE] = gdk_atom_intern_static_string ("MULTIPLE"); + gtk_selection_atoms[TIMESTAMP] = gdk_atom_intern_static_string ("TIMESTAMP"); + gtk_selection_atoms[TARGETS] = gdk_atom_intern_static_string ("TARGETS"); + + initialize = FALSE; } -/************************************************************* +/** * gtk_selection_clear: - * Handler for "selection_clear_event" - * arguments: - * widget: - * event: - * results: - *************************************************************/ - -gint -gtk_selection_clear (GtkWidget *widget, + * @widget: a #GtkWidget + * @event: the event + * + * The default handler for the GtkWidget::selection_clear_event + * signal. + * + * Return value: %TRUE if the event was handled, otherwise false + * + * Since: 2.2 + * + * Deprecated: 2.4: Instead of calling this function, chain up from + * your selection_clear_event handler. Calling this function + * from any other context is illegal. + **/ +gboolean +gtk_selection_clear (GtkWidget *widget, GdkEventSelection *event) { - /* FIXME: there can be a problem if we change the selection - via gtk_selection_owner_set after another client claims - the selection, but before we get the notification event. - Tk filters based on serial #'s, which aren't retained by - GTK. Filtering based on time's will be inherently - somewhat unreliable. */ - + /* Note that we filter clear events in gdkselection-x11.c, so + * that we only will get here if the clear event actually + * represents a change that we didn't do ourself. + */ GList *tmp_list; GtkSelectionInfo *selection_info = NULL; @@ -782,16 +2042,9 @@ gtk_selection_clear (GtkWidget *widget, if (tmp_list) { - if (selection_info->time > event->time) - return FALSE; /* return FALSE to indicate that - * the selection was out of date, - * and this clear should be ignored */ - else - { - current_selections = g_list_remove_link (current_selections, tmp_list); - g_list_free (tmp_list); - g_free (selection_info); - } + current_selections = g_list_remove_link (current_selections, tmp_list); + g_list_free (tmp_list); + g_free (selection_info); } return TRUE; @@ -799,7 +2052,7 @@ gtk_selection_clear (GtkWidget *widget, /************************************************************* - * gtk_selection_request: + * _gtk_selection_request: * Handler for "selection_request_event" * arguments: * widget: @@ -807,18 +2060,21 @@ gtk_selection_clear (GtkWidget *widget, * results: *************************************************************/ -gint -gtk_selection_request (GtkWidget *widget, - GdkEventSelection *event) +gboolean +_gtk_selection_request (GtkWidget *widget, + GdkEventSelection *event) { + GdkDisplay *display = gtk_widget_get_display (widget); GtkIncrInfo *info; GList *tmp_list; - guchar *mult_atoms; int i; - + gulong selection_max_size; + if (initialize) gtk_selection_init (); + selection_max_size = GTK_SELECTION_MAX_SIZE (display); + /* Check if we own selection */ tmp_list = current_selections; @@ -836,48 +2092,84 @@ gtk_selection_request (GtkWidget *widget, if (tmp_list == NULL) return FALSE; - info = g_new(GtkIncrInfo, 1); + info = g_new (GtkIncrInfo, 1); + + g_object_ref (widget); - info->widget = widget; info->selection = event->selection; info->num_incrs = 0; /* Create GdkWindow structure for the requestor */ - info->requestor = gdk_window_lookup (event->requestor); + info->requestor = gdk_window_lookup_for_display (display, + event->requestor); if (!info->requestor) - info->requestor = gdk_window_foreign_new (event->requestor); + info->requestor = gdk_window_foreign_new_for_display (display, + event->requestor); /* Determine conversions we need to perform */ if (event->target == gtk_selection_atoms[MULTIPLE]) { GdkAtom type; + guchar *mult_atoms; gint format; gint length; mult_atoms = NULL; - gdk_error_trap_push(); - if (!gdk_property_get (info->requestor, event->property, 0, /* AnyPropertyType */ - 0, GTK_SELECTION_MAX_SIZE, FALSE, + gdk_error_trap_push (); + if (!gdk_property_get (info->requestor, event->property, GDK_NONE, /* AnyPropertyType */ + 0, selection_max_size, FALSE, &type, &format, &length, &mult_atoms)) { - gdk_selection_send_notify (event->requestor, event->selection, - event->target, GDK_NONE, event->time); + gdk_selection_send_notify_for_display (display, + event->requestor, + event->selection, + event->target, + GDK_NONE, + event->time); g_free (mult_atoms); g_free (info); + gdk_error_trap_pop (); return TRUE; } - gdk_error_trap_pop(); - - info->num_conversions = length / (2*sizeof (GdkAtom)); - info->conversions = g_new (GtkIncrConversion, info->num_conversions); - - for (i=0; inum_conversions; i++) + gdk_error_trap_pop (); + + /* This is annoying; the ICCCM doesn't specify the property type + * used for the property contents, so the autoconversion for + * ATOM / ATOM_PAIR in GDK doesn't work properly. + */ +#ifdef GDK_WINDOWING_X11 + if (type != GDK_SELECTION_TYPE_ATOM && + type != gdk_atom_intern_static_string ("ATOM_PAIR")) + { + info->num_conversions = length / (2*sizeof (glong)); + info->conversions = g_new (GtkIncrConversion, info->num_conversions); + + for (i=0; inum_conversions; i++) + { + info->conversions[i].target = gdk_x11_xatom_to_atom_for_display (display, + ((glong *)mult_atoms)[2*i]); + info->conversions[i].property = gdk_x11_xatom_to_atom_for_display (display, + ((glong *)mult_atoms)[2*i + 1]); + } + + g_free (mult_atoms); + } + else +#endif { - info->conversions[i].target = ((GdkAtom *)mult_atoms)[2*i]; - info->conversions[i].property = ((GdkAtom *)mult_atoms)[2*i+1]; + info->num_conversions = length / (2*sizeof (GdkAtom)); + info->conversions = g_new (GtkIncrConversion, info->num_conversions); + + for (i=0; inum_conversions; i++) + { + info->conversions[i].target = ((GdkAtom *)mult_atoms)[2*i]; + info->conversions[i].property = ((GdkAtom *)mult_atoms)[2*i+1]; + } + + g_free (mult_atoms); } } else /* only a single conversion */ @@ -886,7 +2178,6 @@ gtk_selection_request (GtkWidget *widget, info->num_conversions = 1; info->conversions[0].target = event->target; info->conversions[0].property = event->property; - mult_atoms = (guchar *)info->conversions; } /* Loop through conversions and determine which of these are big @@ -900,19 +2191,20 @@ gtk_selection_request (GtkWidget *widget, data.target = info->conversions[i].target; data.data = NULL; data.length = -1; + data.display = gtk_widget_get_display (widget); #ifdef DEBUG_SELECTION g_message ("Selection %ld, target %ld (%s) requested by 0x%x (property = %ld)", - event->selection, info->conversions[i].target, - gdk_atom_name(info->conversions[i].target), - event->requestor, event->property); + event->selection, + info->conversions[i].target, + gdk_atom_name (info->conversions[i].target), + event->requestor, info->conversions[i].property); #endif gtk_selection_invoke_handler (widget, &data, event->time); if (data.length < 0) { - ((GdkAtom *)mult_atoms)[2*i+1] = GDK_NONE; info->conversions[i].property = GDK_NONE; continue; } @@ -921,9 +2213,13 @@ gtk_selection_request (GtkWidget *widget, items = data.length / gtk_selection_bytes_per_item (data.format); - if (data.length > GTK_SELECTION_MAX_SIZE) + if (data.length > selection_max_size) { /* Sending via INCR */ +#ifdef DEBUG_SELECTION + g_message ("Target larger (%d) than max. request size (%ld), sending incrementally\n", + data.length, selection_max_size); +#endif info->conversions[i].offset = 0; info->conversions[i].data = data; @@ -967,17 +2263,24 @@ gtk_selection_request (GtkWidget *widget, gdk_window_get_events (info->requestor) | GDK_PROPERTY_CHANGE_MASK); current_incrs = g_list_append (current_incrs, info); - gtk_timeout_add (1000, (GtkFunction)gtk_selection_incr_timeout, info); + gdk_threads_add_timeout (1000, (GSourceFunc) gtk_selection_incr_timeout, info); } /* If it was a MULTIPLE request, set the property to indicate which conversions succeeded */ if (event->target == gtk_selection_atoms[MULTIPLE]) { + GdkAtom *mult_atoms = g_new (GdkAtom, 2 * info->num_conversions); + for (i = 0; i < info->num_conversions; i++) + { + mult_atoms[2*i] = info->conversions[i].target; + mult_atoms[2*i+1] = info->conversions[i].property; + } + gdk_property_change (info->requestor, event->property, - GDK_SELECTION_TYPE_ATOM, 32, + gdk_atom_intern_static_string ("ATOM_PAIR"), 32, GDK_PROP_MODE_REPLACE, - mult_atoms, 2*info->num_conversions); + (guchar *)mult_atoms, 2*info->num_conversions); g_free (mult_atoms); } @@ -985,26 +2288,36 @@ gtk_selection_request (GtkWidget *widget, info->conversions[0].property == GDK_NONE) { /* Reject the entire conversion */ - gdk_selection_send_notify (event->requestor, event->selection, - event->target, GDK_NONE, event->time); + gdk_selection_send_notify_for_display (gtk_widget_get_display (widget), + event->requestor, + event->selection, + event->target, + GDK_NONE, + event->time); } else { - gdk_selection_send_notify (event->requestor, event->selection, - event->target, event->property, event->time); + gdk_selection_send_notify_for_display (gtk_widget_get_display (widget), + event->requestor, + event->selection, + event->target, + event->property, + event->time); } - + if (info->num_incrs == 0) { g_free (info->conversions); g_free (info); } + + g_object_unref (widget); return TRUE; } /************************************************************* - * gtk_selection_incr_event: + * _gtk_selection_incr_event: * Called whenever an PropertyNotify event occurs for an * GdkWindow with user_data == NULL. These will be notifications * that a window we are sending the selection to via the @@ -1018,14 +2331,15 @@ gtk_selection_request (GtkWidget *widget, * results: *************************************************************/ -gint -gtk_selection_incr_event (GdkWindow *window, - GdkEventProperty *event) +gboolean +_gtk_selection_incr_event (GdkWindow *window, + GdkEventProperty *event) { GList *tmp_list; GtkIncrInfo *info = NULL; gint num_bytes; guchar *buffer; + gulong selection_max_size; int i; @@ -1035,7 +2349,9 @@ gtk_selection_incr_event (GdkWindow *window, #ifdef DEBUG_SELECTION g_message ("PropertyDelete, property %ld", event->atom); #endif - + + selection_max_size = GTK_SELECTION_MAX_SIZE (gdk_drawable_get_display (window)); + /* Now find the appropriate ongoing INCR */ tmp_list = current_incrs; while (tmp_list) @@ -1073,10 +2389,10 @@ gtk_selection_incr_event (GdkWindow *window, buffer = info->conversions[i].data.data + info->conversions[i].offset; - if (num_bytes > GTK_SELECTION_MAX_SIZE) + if (num_bytes > selection_max_size) { - num_bytes = GTK_SELECTION_MAX_SIZE; - info->conversions[i].offset += GTK_SELECTION_MAX_SIZE; + num_bytes = selection_max_size; + info->conversions[i].offset += selection_max_size; } else info->conversions[i].offset = -2; @@ -1107,7 +2423,6 @@ gtk_selection_incr_event (GdkWindow *window, info->conversions[i].offset = -1; } } - break; } /* Check if we're finished with all the targets */ @@ -1137,8 +2452,6 @@ gtk_selection_incr_timeout (GtkIncrInfo *info) GList *tmp_list; gboolean retval; - GDK_THREADS_ENTER (); - /* Determine if retrieval has finished by checking if it still in list of pending retrievals */ @@ -1151,9 +2464,9 @@ gtk_selection_incr_timeout (GtkIncrInfo *info) } /* If retrieval is finished */ - if (!tmp_list || info->idle_time >= 5) + if (!tmp_list || info->idle_time >= IDLE_ABORT_TIME) { - if (tmp_list && info->idle_time >= 5) + if (tmp_list && info->idle_time >= IDLE_ABORT_TIME) { current_incrs = g_list_remove_link (current_incrs, tmp_list); g_list_free (tmp_list); @@ -1174,13 +2487,11 @@ gtk_selection_incr_timeout (GtkIncrInfo *info) retval = TRUE; /* timeout will happen again */ } - GDK_THREADS_LEAVE (); - return retval; } /************************************************************* - * gtk_selection_notify: + * _gtk_selection_notify: * Handler for "selection_notify_event" signals on windows * where a retrieval is currently in process. The selection * owner has responded to our conversion request. @@ -1192,9 +2503,9 @@ gtk_selection_incr_timeout (GtkIncrInfo *info) * was event handled? *************************************************************/ -gint -gtk_selection_notify (GtkWidget *widget, - GdkEventSelection *event) +gboolean +_gtk_selection_notify (GtkWidget *widget, + GdkEventSelection *event) { GList *tmp_list; GtkRetrievalInfo *info = NULL; @@ -1223,7 +2534,9 @@ gtk_selection_notify (GtkWidget *widget, if (event->property != GDK_NONE) length = gdk_selection_property_get (widget->window, &buffer, &type, &format); - + else + length = 0; /* silence gcc */ + if (event->property == GDK_NONE || buffer == NULL) { current_retrievals = g_list_remove_link (current_retrievals, tmp_list); @@ -1267,7 +2580,7 @@ gtk_selection_notify (GtkWidget *widget, } /************************************************************* - * gtk_selection_property_notify: + * _gtk_selection_property_notify: * Handler for "property_notify_event" signals on windows * where a retrieval is currently in process. The selection * owner has added more data. @@ -1279,9 +2592,9 @@ gtk_selection_notify (GtkWidget *widget, * was event handled? *************************************************************/ -gint -gtk_selection_property_notify (GtkWidget *widget, - GdkEventProperty *event) +gboolean +_gtk_selection_property_notify (GtkWidget *widget, + GdkEventProperty *event) { GList *tmp_list; GtkRetrievalInfo *info = NULL; @@ -1293,8 +2606,10 @@ gtk_selection_property_notify (GtkWidget *widget, g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (event != NULL, FALSE); +#if defined(GDK_WINDOWING_WIN32) || defined(GDK_WINDOWING_X11) if ((event->state != GDK_PROPERTY_NEW_VALUE) || /* property was deleted */ - (event->atom != gdk_selection_property)) /* not the right property */ + (event->atom != gdk_atom_intern_static_string ("GDK_SELECTION"))) /* not the right property */ +#endif return FALSE; #ifdef DEBUG_SELECTION @@ -1377,14 +2692,12 @@ gtk_selection_property_notify (GtkWidget *widget, * results: *************************************************************/ -static gint +static gboolean gtk_selection_retrieval_timeout (GtkRetrievalInfo *info) { GList *tmp_list; gboolean retval; - GDK_THREADS_ENTER (); - /* Determine if retrieval has finished by checking if it still in list of pending retrievals */ @@ -1397,9 +2710,9 @@ gtk_selection_retrieval_timeout (GtkRetrievalInfo *info) } /* If retrieval is finished */ - if (!tmp_list || info->idle_time >= 5) + if (!tmp_list || info->idle_time >= IDLE_ABORT_TIME) { - if (tmp_list && info->idle_time >= 5) + if (tmp_list && info->idle_time >= IDLE_ABORT_TIME) { current_retrievals = g_list_remove_link (current_retrievals, tmp_list); g_list_free (tmp_list); @@ -1418,8 +2731,6 @@ gtk_selection_retrieval_timeout (GtkRetrievalInfo *info) retval = TRUE; /* timeout will happen again */ } - GDK_THREADS_LEAVE (); - return retval; } @@ -1448,10 +2759,11 @@ gtk_selection_retrieval_report (GtkRetrievalInfo *info, data.length = length; data.data = buffer; + data.display = gtk_widget_get_display (info->widget); - gtk_signal_emit_by_name (GTK_OBJECT(info->widget), - "selection_received", - &data, time); + g_signal_emit_by_name (info->widget, + "selection_received", + &data, time); } /************************************************************* @@ -1484,10 +2796,10 @@ gtk_selection_invoke_handler (GtkWidget *widget, if (target_list && gtk_target_list_find (target_list, data->target, &info)) { - gtk_signal_emit_by_name (GTK_OBJECT (widget), - "selection_get", - data, - info, time); + g_signal_emit_by_name (widget, + "selection_get", + data, + info, time); } else gtk_selection_default_handler (widget, data); @@ -1554,9 +2866,12 @@ gtk_selection_default_handler (GtkWidget *widget, data->type = GDK_SELECTION_TYPE_ATOM; data->format = 32; data->length = count * sizeof (GdkAtom); - - p = g_new (GdkAtom, count); + + /* selection data is always terminated by a trailing \0 + */ + p = g_malloc (data->length + 1); data->data = (guchar *)p; + data->data[data->length] = '\0'; *p++ = gtk_selection_atoms[TIMESTAMP]; *p++ = gtk_selection_atoms[TARGETS]; @@ -1578,7 +2893,15 @@ gtk_selection_default_handler (GtkWidget *widget, } -GtkSelectioData* +/** + * gtk_selection_data_copy: + * @data: a pointer to a #GtkSelectionData structure. + * + * Makes a copy of a #GtkSelectionData structure and its data. + * + * Return value: a pointer to a copy of @data. + **/ +GtkSelectionData* gtk_selection_data_copy (GtkSelectionData *data) { GtkSelectionData *new_data; @@ -1587,18 +2910,59 @@ gtk_selection_data_copy (GtkSelectionData *data) new_data = g_new (GtkSelectionData, 1); *new_data = *data; + + if (data->data) + { + new_data->data = g_malloc (data->length + 1); + memcpy (new_data->data, data->data, data->length + 1); + } return new_data; } +/** + * gtk_selection_data_free: + * @data: a pointer to a #GtkSelectionData structure. + * + * Frees a #GtkSelectionData structure returned from + * gtk_selection_data_copy(). + **/ void gtk_selection_data_free (GtkSelectionData *data) { g_return_if_fail (data != NULL); + g_free (data->data); + g_free (data); } +GType +gtk_selection_data_get_type (void) +{ + static GType our_type = 0; + + if (our_type == 0) + our_type = g_boxed_type_register_static (I_("GtkSelectionData"), + (GBoxedCopyFunc) gtk_selection_data_copy, + (GBoxedFreeFunc) gtk_selection_data_free); + + return our_type; +} + +GType +gtk_target_list_get_type (void) +{ + static GType our_type = 0; + + if (our_type == 0) + our_type = g_boxed_type_register_static (I_("GtkTargetList"), + (GBoxedCopyFunc) gtk_target_list_ref, + (GBoxedFreeFunc) gtk_target_list_unref); + + return our_type; +} + static int gtk_selection_bytes_per_item (gint format) { @@ -1618,3 +2982,6 @@ gtk_selection_bytes_per_item (gint format) } return 0; } + +#define __GTK_SELECTION_C__ +#include "gtkaliasdef.c"