summaryrefslogtreecommitdiff
path: root/gtk/gtkselection.c
diff options
context:
space:
mode:
authorElliot Lee <sopwith@src.gnome.org>1997-11-24 22:37:52 +0000
committerElliot Lee <sopwith@src.gnome.org>1997-11-24 22:37:52 +0000
commit9508b76bd2401b6b9e289b5c8ec9fc0e08909283 (patch)
tree53c88a9e5ac09e1a027e56df33bdaa66d670901b /gtk/gtkselection.c
downloadgtk+-9508b76bd2401b6b9e289b5c8ec9fc0e08909283.tar.gz
Initial revision
Diffstat (limited to 'gtk/gtkselection.c')
-rw-r--r--gtk/gtkselection.c1388
1 files changed, 1388 insertions, 0 deletions
diff --git a/gtk/gtkselection.c b/gtk/gtkselection.c
new file mode 100644
index 0000000000..ca6f5742f4
--- /dev/null
+++ b/gtk/gtkselection.c
@@ -0,0 +1,1388 @@
+/* GTK - The GIMP Toolkit
+ * 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
+ * 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.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* This file implements most of the work of the ICCM 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)
+ * 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
+ * transfers, the order will depend on the timing of the requestor.
+ *
+ * By Owen Taylor <owt1@cornell.edu> 8/16/97
+ */
+
+/* Terminology note: when not otherwise specified, the term "incr" below
+ * refers to the _sending_ part of the INCR protocol. The receiving
+ * portion is referred to just as "retrieval". (Terminology borrowed
+ * from Tk, because there is no good opposite to "retrieval" in English.
+ * "send" can't be made into a noun gracefully and we're already using
+ * "emission" for something else ....)
+ */
+
+/* The MOTIF entry widget seems to ask for the TARGETS target, then
+ (regardless of the reply) ask for the TEXT target. It's slightly
+ possible though that it somehow thinks we are responding negatively
+ to the TARGETS request, though I don't really think so ... */
+
+#include <stdarg.h>
+#include <gdk/gdkx.h>
+/* we need this for gdk_window_lookup() */
+#include "gtkmain.h"
+#include "gtkselection.h"
+#include "gtksignal.h"
+
+/* #define DEBUG_SELECTION */
+
+/* Maximum size of a sent chunk, in bytes. Also the default size of
+ our buffers */
+#define GTK_SELECTION_MAX_SIZE 4000
+
+enum {
+ INCR,
+ MULTIPLE,
+ TARGETS,
+ TIMESTAMP,
+ LAST_ATOM
+};
+
+typedef struct _GtkSelectionInfo GtkSelectionInfo;
+typedef struct _GtkIncrConversion GtkIncrConversion;
+typedef struct _GtkIncrInfo GtkIncrInfo;
+typedef struct _GtkRetrievalInfo GtkRetrievalInfo;
+typedef struct _GtkSelectionHandler GtkSelectionHandler;
+
+struct _GtkSelectionInfo
+{
+ GdkAtom selection;
+ GtkWidget *widget; /* widget that owns selection */
+ guint32 time; /* time used to acquire selection */
+};
+
+struct _GtkIncrConversion
+{
+ GdkAtom target; /* Requested target */
+ GdkAtom property; /* Property to store in */
+ GtkSelectionData data; /* The data being supplied */
+ gint offset; /* Current offset in sent selection.
+ * -1 => All done
+ * -2 => Only the final (empty) portion
+ * left to send */
+};
+
+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 */
+
+ GtkIncrConversion *conversions; /* Information about requested conversions -
+ * With MULTIPLE requests (benighted 1980's
+ * hardware idea), there can be more than
+ * one */
+ gint num_conversions;
+ gint num_incrs; /* number of remaining INCR style transactions */
+ guint32 idle_time;
+};
+
+
+struct _GtkRetrievalInfo
+{
+ GtkWidget *widget;
+ GdkAtom selection; /* Selection being retrieved. */
+ GdkAtom target; /* Form of selection that we requested */
+ guint32 idle_time; /* Number of seconds since we last heard
+ from selection owner */
+ guchar *buffer; /* Buffer in which to accumulate results */
+ gint offset; /* Current offset in buffer, -1 indicates
+ not yet started */
+};
+
+struct _GtkSelectionHandler
+{
+ GdkAtom selection; /* selection thats handled */
+ GdkAtom target; /* target thats handled */
+ GtkSelectionFunction function; /* callback function */
+ GtkRemoveFunction remove_func; /* called when callback is removed */
+ gpointer data; /* callback data */
+};
+
+/* 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 void gtk_selection_retrieval_report (GtkRetrievalInfo *info,
+ GdkAtom type, gint format,
+ guchar *buffer, gint length);
+static GtkSelectionHandler *gtk_selection_find_handler (GtkWidget *widget,
+ GdkAtom selection,
+ GdkAtom target);
+static void gtk_selection_default_handler (GtkWidget *widget,
+ GtkSelectionData *data);
+
+/* Local Data */
+static gint initialize = TRUE;
+static GList *current_retrievals = NULL;
+static GList *current_incrs = NULL;
+static GList *current_selections = NULL;
+
+static GdkAtom gtk_selection_atoms[LAST_ATOM];
+static const char *gtk_selection_handler_key = "selection_handlers";
+
+/*************************************************************
+ * 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)
+ *
+ * results:
+ *************************************************************/
+
+gint
+gtk_selection_owner_set (GtkWidget *widget,
+ GdkAtom selection,
+ guint32 time)
+{
+ GList *tmp_list;
+ GtkWidget *old_owner;
+ GtkSelectionInfo *selection_info;
+ GdkWindow *window;
+
+ if (widget == NULL)
+ window = NULL;
+ else
+ {
+ if (!GTK_WIDGET_REALIZED (widget))
+ gtk_widget_realize (widget);
+
+ window = widget->window;
+ }
+
+ tmp_list = current_selections;
+ while (tmp_list)
+ {
+ selection_info = (GtkSelectionInfo *)tmp_list->data;
+
+ if (selection_info->selection == selection)
+ 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))
+ {
+ old_owner = NULL;
+
+ if (widget == NULL)
+ {
+ if (selection_info)
+ {
+ old_owner = selection_info->widget;
+ current_selections = g_list_remove_link (current_selections,
+ tmp_list);
+ g_list_free (tmp_list);
+ g_free (selection_info);
+ }
+ }
+ else
+ {
+ if (selection_info == NULL)
+ {
+ selection_info = g_new (GtkSelectionInfo, 1);
+ selection_info->selection = selection;
+ selection_info->widget = widget;
+ selection_info->time = time;
+ current_selections = g_list_append (current_selections,
+ selection_info);
+ }
+ else
+ {
+ old_owner = selection_info->widget;
+ selection_info->widget = widget;
+ selection_info->time = time;
+ }
+ }
+ /* 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))
+ {
+ GdkEventSelection event;
+
+ event.type = GDK_SELECTION_CLEAR;
+ event.window = old_owner->window;
+ event.selection = selection;
+ event.time = time;
+
+ gtk_widget_event (widget, (GdkEvent *) &event);
+ }
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+/*************************************************************
+ * gtk_selection_add_handler:
+ * Add a handler for a specified selection/target pair
+ *
+ * arguments:
+ * widget: The widget the handler applies to
+ * selection:
+ * target:
+ * format: Format in which this handler will return data
+ * function: Callback function (can be NULL)
+ * data: User data for callback
+ *
+ * results:
+ *************************************************************/
+
+void
+gtk_selection_add_handler (GtkWidget *widget,
+ GdkAtom selection,
+ GdkAtom target,
+ GtkSelectionFunction function,
+ GtkRemoveFunction remove_func,
+ gpointer data)
+{
+ GList *selection_handlers;
+ GList *tmp_list;
+ GtkSelectionHandler *handler;
+
+ g_return_if_fail (widget != NULL);
+ if (initialize)
+ gtk_selection_init ();
+
+ selection_handlers = gtk_object_get_data (GTK_OBJECT (widget),
+ gtk_selection_handler_key);
+
+ /* Reuse old handler structure, if present */
+ tmp_list = selection_handlers;
+ while (tmp_list)
+ {
+ handler = (GtkSelectionHandler *)tmp_list->data;
+ if ((handler->selection == selection) && (handler->target == target))
+ {
+ if (handler->remove_func)
+ (*handler->remove_func)(handler->data);
+ if (function)
+ {
+ handler->function = function;
+ handler->remove_func = remove_func;
+ handler->data = data;
+ }
+ else
+ {
+ selection_handlers = g_list_remove_link (selection_handlers,
+ tmp_list);
+ g_list_free (tmp_list);
+ g_free (handler);
+ }
+ return;
+ }
+ tmp_list = tmp_list->next;
+ }
+
+ if (tmp_list == NULL && function)
+ {
+ handler = g_new (GtkSelectionHandler, 1);
+ handler->selection = selection;
+ handler->target = target;
+ handler->function = function;
+ handler->remove_func = remove_func;
+ handler->data = data;
+ selection_handlers = g_list_append (selection_handlers, handler);
+ }
+
+ gtk_object_set_data (GTK_OBJECT (widget), gtk_selection_handler_key,
+ selection_handlers);
+}
+
+/*************************************************************
+ * 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:
+ *************************************************************/
+
+void
+gtk_selection_remove_all (GtkWidget *widget)
+{
+ GList *tmp_list;
+ GList *next;
+ GtkSelectionInfo *selection_info;
+ GList *selection_handlers;
+ GtkSelectionHandler *handler;
+
+ /* Remove pending requests/incrs for this widget */
+
+ tmp_list = current_incrs;
+ while (tmp_list)
+ {
+ next = tmp_list->next;
+ if (((GtkIncrInfo *)tmp_list->data)->widget == widget)
+ {
+ current_incrs = g_list_remove_link (current_incrs, tmp_list);
+ /* structure will be freed in timeout */
+ g_list_free (tmp_list);
+ }
+ tmp_list = next;
+ }
+
+ tmp_list = current_retrievals;
+ while (tmp_list)
+ {
+ next = tmp_list->next;
+ if (((GtkRetrievalInfo *)tmp_list->data)->widget == widget)
+ {
+ current_retrievals = g_list_remove_link (current_retrievals,
+ tmp_list);
+ /* structure will be freed in timeout */
+ g_list_free (tmp_list);
+ }
+ tmp_list = next;
+ }
+
+ /* Disclaim ownership of any selections */
+
+ tmp_list = current_selections;
+ 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;
+ }
+
+ /* Now remove all handlers */
+
+ selection_handlers = gtk_object_get_data (GTK_OBJECT (widget),
+ gtk_selection_handler_key);
+
+ tmp_list = selection_handlers;
+ while (tmp_list)
+ {
+ next = tmp_list->next;
+ handler = (GtkSelectionHandler *)tmp_list->data;
+
+ if (handler->remove_func)
+ (*handler->remove_func)(handler->data);
+
+ g_free (handler);
+
+ tmp_list = next;
+ }
+
+ g_list_free (selection_handlers);
+}
+
+/*************************************************************
+ * gtk_selection_convert:
+ * Request the contents of a selection. When received,
+ * a "selection_received" signal will be generated.
+ *
+ * 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
+ *
+ * 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)
+{
+ 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)
+ {
+ 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->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)
+ {
+ GtkWidget *owner_widget;
+ GtkSelectionHandler *handler;
+ 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)
+ {
+ handler = gtk_selection_find_handler (owner_widget, selection, target);
+ if (handler)
+ (* handler->function)(owner_widget,
+ &selection_data,
+ handler->data);
+ else /* try the default handler */
+ gtk_selection_default_handler (owner_widget,
+ &selection_data);
+
+ gtk_selection_retrieval_report (info,
+ selection_data.type,
+ selection_data.format,
+ selection_data.data,
+ selection_data.length);
+
+ 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);
+ gtk_timeout_add (1000, (GtkFunction) gtk_selection_retrieval_timeout, info);
+
+ return TRUE;
+}
+
+/*************************************************************
+ * 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:
+ *************************************************************/
+
+void
+gtk_selection_data_set (GtkSelectionData *selection_data,
+ GdkAtom type,
+ gint format,
+ guchar *data,
+ gint length)
+{
+ if (selection_data->data)
+ 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
+ selection_data->data = NULL;
+
+ selection_data->length = length;
+}
+
+/*************************************************************
+ * gtk_selection_init:
+ * Initialize local variables
+ * arguments:
+ *
+ * results:
+ *************************************************************/
+
+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_clear:
+ * Handler for "selection_clear_event"
+ * arguments:
+ * widget:
+ * event:
+ * results:
+ *************************************************************/
+
+gint
+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. */
+
+ GList *tmp_list;
+ GtkSelectionInfo *selection_info;
+
+ tmp_list = current_selections;
+ while (tmp_list)
+ {
+ selection_info = (GtkSelectionInfo *)tmp_list->data;
+
+ if ((selection_info->selection == event->selection) &&
+ (selection_info->widget == widget))
+ break;
+
+ tmp_list = tmp_list->next;
+ }
+
+ if (tmp_list == NULL || selection_info->time > event->time)
+ return TRUE;
+
+ current_selections = g_list_remove_link (current_selections, tmp_list);
+ g_list_free (tmp_list);
+ g_free (selection_info);
+
+ return TRUE;
+}
+
+
+/*************************************************************
+ * gtk_selection_request:
+ * Handler for "selection_request_event"
+ * arguments:
+ * widget:
+ * event:
+ * results:
+ *************************************************************/
+
+gint
+gtk_selection_request (GtkWidget *widget,
+ GdkEventSelection *event)
+{
+ GtkIncrInfo *info;
+ GtkSelectionHandler *handler;
+ GList *tmp_list;
+ guchar *mult_atoms;
+ int i;
+
+ /* Check if we own selection */
+
+ tmp_list = current_selections;
+ while (tmp_list)
+ {
+ GtkSelectionInfo *selection_info = (GtkSelectionInfo *)tmp_list->data;
+
+ if ((selection_info->selection == event->selection) &&
+ (selection_info->widget == widget))
+ break;
+
+ tmp_list = tmp_list->next;
+ }
+
+ if (tmp_list == NULL)
+ return FALSE;
+
+ info = g_new(GtkIncrInfo, 1);
+
+ info->widget = widget;
+ info->selection = event->selection;
+ info->num_incrs = 0;
+
+ /* Create GdkWindow structure for the requestor */
+
+ info->requestor = gdk_window_lookup (event->requestor);
+ if (!info->requestor)
+ info->requestor = gdk_window_foreign_new (event->requestor);
+
+ /* Determine conversions we need to perform */
+
+ if (event->target == gtk_selection_atoms[MULTIPLE])
+ {
+ GdkAtom type;
+ gint format;
+ gint length;
+
+ mult_atoms = NULL;
+ if (!gdk_property_get (info->requestor, event->property, GDK_SELECTION_TYPE_ATOM,
+ 0, GTK_SELECTION_MAX_SIZE, FALSE,
+ &type, &format, &length, &mult_atoms) ||
+ type != GDK_SELECTION_TYPE_ATOM || format != 8*sizeof(GdkAtom))
+ {
+ gdk_selection_send_notify (event->requestor, event->selection,
+ event->target, GDK_NONE, event->time);
+ g_free (mult_atoms);
+ g_free (info);
+ return TRUE;
+ }
+
+ info->num_conversions = length / (2*sizeof (GdkAtom));
+ info->conversions = g_new (GtkIncrConversion, info->num_conversions);
+
+ for (i=0; i<info->num_conversions; i++)
+ {
+ info->conversions[i].target = ((GdkAtom *)mult_atoms)[2*i];
+ info->conversions[i].property = ((GdkAtom *)mult_atoms)[2*i+1];
+ }
+ }
+ else /* only a single conversion */
+ {
+ info->conversions = g_new (GtkIncrConversion, 1);
+ 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
+ enough to require doing them via INCR */
+ for (i=0; i<info->num_conversions; i++)
+ {
+ GtkSelectionData data;
+ gint items;
+
+ data.selection = event->selection;
+ data.target = info->conversions[i].target;
+ data.data = NULL;
+ data.length = -1;
+
+#ifdef DEBUG_SELECTION
+ g_print("Selection %ld, target %ld (%s) requested by 0x%x (property = %ld)\n",
+ event->selection, info->conversions[i].target,
+ gdk_atom_name(info->conversions[i].target),
+ event->requestor, event->property);
+#endif
+
+ handler = gtk_selection_find_handler (widget, event->selection,
+ info->conversions[i].target);
+ if (handler)
+ (* handler->function)(widget, &data, handler->data);
+ else
+ gtk_selection_default_handler (widget, &data);
+
+ if (data.length < 0)
+ {
+ ((GdkAtom *)mult_atoms)[2*i+1] = GDK_NONE;
+ info->conversions[i].property = GDK_NONE;
+ continue;
+ }
+
+ g_return_val_if_fail ((data.format >= 8)
+ && (data.format % 8 == 0), FALSE)
+
+ items = (data.length + data.format/8 - 1) / (data.format/8);
+
+ if (data.length > GTK_SELECTION_MAX_SIZE)
+ {
+ /* Sending via INCR */
+
+ info->conversions[i].offset = 0;
+ info->conversions[i].data = data;
+ info->num_incrs++;
+
+ gdk_property_change (info->requestor,
+ info->conversions[i].property,
+ gtk_selection_atoms[INCR],
+ 8*sizeof (GdkAtom),
+ GDK_PROP_MODE_REPLACE,
+ (guchar *)&items, 1);
+ }
+ else
+ {
+ info->conversions[i].offset = -1;
+
+ gdk_property_change (info->requestor,
+ info->conversions[i].property,
+ data.type,
+ data.format,
+ GDK_PROP_MODE_REPLACE,
+ data.data, items);
+
+ g_free (data.data);
+ }
+ }
+
+ /* If we have some INCR's, we need to send the rest of the data in
+ a callback */
+
+ if (info->num_incrs > 0)
+ {
+ /* FIXME: this could be dangerous if window doesn't still
+ exist */
+
+#ifdef DEBUG_SELECTION
+ g_print("Starting INCR...\n");
+#endif
+
+ gdk_window_set_events (info->requestor,
+ 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);
+ }
+
+ /* If it was a MULTIPLE request, set the property to indicate which
+ conversions succeeded */
+ if (event->target == gtk_selection_atoms[MULTIPLE])
+ {
+ gdk_property_change (info->requestor, event->property,
+ GDK_SELECTION_TYPE_ATOM, 8*sizeof(GdkAtom),
+ GDK_PROP_MODE_REPLACE,
+ mult_atoms, info->num_conversions);
+ g_free (mult_atoms);
+ }
+
+ gdk_selection_send_notify (event->requestor, event->selection, event->target,
+ event->property, event->time);
+
+ if (info->num_incrs == 0)
+ {
+ g_free (info->conversions);
+ g_free (info);
+ }
+
+ return TRUE;
+}
+
+/*************************************************************
+ * 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
+ * INCR protocol has deleted a property and is ready for
+ * more data.
+ *
+ * arguments:
+ * window: the requestor window
+ * event: the property event structure
+ *
+ * results:
+ *************************************************************/
+
+gint
+gtk_selection_incr_event (GdkWindow *window,
+ GdkEventProperty *event)
+{
+ GList *tmp_list;
+ GtkIncrInfo *info;
+ gint num_bytes;
+ guchar *buffer;
+
+ int i;
+
+ if (event->state != GDK_PROPERTY_DELETE)
+ return FALSE;
+
+#ifdef DEBUG_SELECTION
+ g_print("PropertyDelete, property %ld\n", event->atom);
+#endif
+
+ /* Now find the appropriate ongoing INCR */
+ tmp_list = current_incrs;
+ while (tmp_list)
+ {
+ info = (GtkIncrInfo *)tmp_list->data;
+ if (info->requestor == event->window)
+ break;
+
+ tmp_list = tmp_list->next;
+ }
+
+ if (tmp_list == NULL)
+ return FALSE;
+
+ /* Find out which target this is for */
+ for (i=0; i<info->num_conversions; i++)
+ {
+ if (info->conversions[i].property == event->atom &&
+ info->conversions[i].offset != -1)
+ {
+ info->idle_time = 0;
+
+ if (info->conversions[i].offset == -2) /* only the last 0-length
+ piece*/
+ {
+ num_bytes = 0;
+ buffer = NULL;
+ }
+ else
+ {
+ num_bytes = info->conversions[i].data.length -
+ info->conversions[i].offset;
+ buffer = info->conversions[i].data.data +
+ info->conversions[i].offset;
+
+ if (num_bytes > GTK_SELECTION_MAX_SIZE)
+ {
+ num_bytes = GTK_SELECTION_MAX_SIZE;
+ info->conversions[i].offset += GTK_SELECTION_MAX_SIZE;
+ }
+ else
+ info->conversions[i].offset = -2;
+ }
+#ifdef DEBUG_SELECTION
+ g_print("INCR: put %d bytes (offset = %d) into window 0x%lx , property %ld\n",
+ num_bytes, info->conversions[i].offset,
+ GDK_WINDOW_XWINDOW(info->requestor), event->atom);
+#endif
+ gdk_property_change (info->requestor, event->atom,
+ info->conversions[i].data.type,
+ info->conversions[i].data.format,
+ GDK_PROP_MODE_REPLACE,
+ buffer,
+ (num_bytes + info->conversions[i].data.format/8 - 1) /
+ (info->conversions[i].data.format/8));
+
+ if (info->conversions[i].offset == -2)
+ {
+ g_free (info->conversions[i].data.data);
+ info->conversions[i].data.data = NULL;
+ }
+
+ if (num_bytes == 0)
+ {
+ info->num_incrs--;
+ info->conversions[i].offset = -1;
+ }
+ }
+ break;
+ }
+
+ /* Check if we're finished with all the targets */
+
+ if (info->num_incrs == 0)
+ {
+ current_incrs = g_list_remove_link (current_incrs, tmp_list);
+ g_list_free (tmp_list);
+ /* Let the timeout free it */
+ }
+
+ return TRUE;
+}
+
+/*************************************************************
+ * gtk_selection_incr_timeout:
+ * Timeout callback for the sending portion of the INCR
+ * protocol
+ * arguments:
+ * info: Information about this incr
+ * results:
+ *************************************************************/
+
+static gint
+gtk_selection_incr_timeout (GtkIncrInfo *info)
+{
+ GList *tmp_list;
+
+ /* Determine if retrieval has finished by checking if it still in
+ list of pending retrievals */
+
+ tmp_list = current_incrs;
+ while (tmp_list)
+ {
+ if (info == (GtkIncrInfo *)tmp_list->data)
+ break;
+ tmp_list = tmp_list->next;
+ }
+
+ /* If retrieval is finished */
+ if (!tmp_list || info->idle_time >= 5)
+ {
+ if (tmp_list && info->idle_time >= 5)
+ {
+ current_incrs = g_list_remove_link (current_incrs, tmp_list);
+ g_list_free (tmp_list);
+ }
+
+ g_free (info->conversions);
+ /* FIXME: we should check if requestor window is still in use,
+ and if not, remove it? */
+
+ g_free (info);
+
+ return FALSE; /* remove timeout */
+ }
+ else
+ {
+ info->idle_time++;
+
+ return TRUE; /* timeout will happen again */
+ }
+}
+
+/*************************************************************
+ * 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.
+ * arguments:
+ * widget: Widget getting signal
+ * event: Selection event structure
+ * info: Information about this retrieval
+ * results:
+ * was event handled?
+ *************************************************************/
+
+gint
+gtk_selection_notify (GtkWidget *widget,
+ GdkEventSelection *event)
+{
+ GList *tmp_list;
+ GtkRetrievalInfo *info;
+ guchar *buffer;
+ gint length;
+ GdkAtom type;
+ gint format;
+
+#ifdef DEBUG_SELECTION
+ g_print("Initial receipt of selection %ld, target %ld (property = %ld)\n",
+ event->selection, event->target, event->property);
+#endif
+
+ tmp_list = current_retrievals;
+ while (tmp_list)
+ {
+ info = (GtkRetrievalInfo *)tmp_list->data;
+ if (info->widget == widget && info->selection == event->selection)
+ break;
+ tmp_list = tmp_list->next;
+ }
+
+ if (!tmp_list) /* no retrieval in progress */
+ return FALSE;
+
+ if (event->property == GDK_NONE)
+ {
+ current_retrievals = g_list_remove_link (current_retrievals, tmp_list);
+ g_list_free (tmp_list);
+ /* structure will be freed in timeout */
+ gtk_selection_retrieval_report (info,
+ GDK_NONE, 0, NULL, -1);
+
+ return TRUE;
+ }
+
+ length = gdk_selection_property_get (widget->window, &buffer,
+ &type, &format);
+
+ if (type == gtk_selection_atoms[INCR])
+ {
+ /* The remainder of the selection will come through PropertyNotify
+ events */
+
+ info->idle_time = 0;
+ info->offset = 0; /* Mark as OK to proceed */
+ gdk_window_set_events (widget->window,
+ gdk_window_get_events (widget->window)
+ | GDK_PROPERTY_CHANGE_MASK);
+ }
+ else
+ {
+ /* We don't delete the info structure - that will happen in timeout */
+ current_retrievals = g_list_remove_link (current_retrievals, tmp_list);
+ g_list_free (tmp_list);
+
+ info->offset = length;
+ gtk_selection_retrieval_report (info,
+ type, format,
+ buffer, length);
+ }
+
+ gdk_property_delete (widget->window, event->property);
+
+ g_free (buffer);
+
+ return TRUE;
+}
+
+/*************************************************************
+ * 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.
+ * arguments:
+ * widget: Widget getting signal
+ * event: Property event structure
+ * info: Information about this retrieval
+ * results:
+ * was event handled?
+ *************************************************************/
+
+gint
+gtk_selection_property_notify (GtkWidget *widget,
+ GdkEventProperty *event)
+{
+ GList *tmp_list;
+ GtkRetrievalInfo *info;
+ guchar *new_buffer;
+ int length;
+ GdkAtom type;
+ gint format;
+
+ if ((event->state != GDK_PROPERTY_NEW_VALUE) || /* property was deleted */
+ (event->atom != gdk_selection_property)) /* not the right property */
+ return FALSE;
+
+#ifdef DEBUG_SELECTION
+ g_print("PropertyNewValue, property %ld\n",
+ event->atom);
+#endif
+
+ tmp_list = current_retrievals;
+ while (tmp_list)
+ {
+ info = (GtkRetrievalInfo *)tmp_list->data;
+ if (info->widget == widget)
+ break;
+ tmp_list = tmp_list->next;
+ }
+
+ if (!tmp_list) /* No retrieval in progress */
+ return FALSE;
+
+ if (info->offset < 0) /* We haven't got the SelectionNotify
+ for this retrieval yet */
+ return FALSE;
+
+ info->idle_time = 0;
+
+ length = gdk_selection_property_get (widget->window, &new_buffer,
+ &type, &format);
+ gdk_property_delete (widget->window, event->atom);
+
+ /* We could do a lot better efficiency-wise by paying attention to
+ what length was sent in the initial INCR transaction, instead of
+ doing memory allocation at every step. But its only guaranteed to
+ be a _lower bound_ (pretty useless!) */
+
+ if (length == 0 || type == GDK_NONE) /* final zero length portion */
+ {
+ /* Info structure will be freed in timeout */
+ current_retrievals = g_list_remove_link (current_retrievals, tmp_list);
+ g_list_free (tmp_list);
+ gtk_selection_retrieval_report (info,
+ type, format,
+ (type == GDK_NONE) ? NULL : info->buffer,
+ (type == GDK_NONE) ? -1 : info->offset);
+ }
+ else /* append on newly arrived data */
+ {
+ if (!info->buffer)
+ {
+#ifdef DEBUG_SELECTION
+ g_print("Start - Adding %d bytes at offset 0\n",
+ length);
+#endif
+ info->buffer = new_buffer;
+ info->offset = length;
+ }
+ else
+ {
+
+#ifdef DEBUG_SELECTION
+ g_print("Appending %d bytes at offset %d\n",
+ length,info->offset);
+#endif
+ /* We copy length+1 bytes to preserve guaranteed null termination */
+ info->buffer = g_realloc (info->buffer, info->offset+length+1);
+ memcpy (info->buffer + info->offset, new_buffer, length+1);
+ info->offset += length;
+ g_free (new_buffer);
+ }
+ }
+
+ return TRUE;
+}
+
+/*************************************************************
+ * gtk_selection_retrieval_timeout:
+ * Timeout callback while receiving a selection.
+ * arguments:
+ * info: Information about this retrieval
+ * results:
+ *************************************************************/
+
+static gint
+gtk_selection_retrieval_timeout (GtkRetrievalInfo *info)
+{
+ GList *tmp_list;
+
+ /* Determine if retrieval has finished by checking if it still in
+ list of pending retrievals */
+
+ tmp_list = current_retrievals;
+ while (tmp_list)
+ {
+ if (info == (GtkRetrievalInfo *)tmp_list->data)
+ break;
+ tmp_list = tmp_list->next;
+ }
+
+ /* If retrieval is finished */
+ if (!tmp_list || info->idle_time >= 5)
+ {
+ if (tmp_list && info->idle_time >= 5)
+ {
+ current_retrievals = g_list_remove_link (current_retrievals, tmp_list);
+ g_list_free (tmp_list);
+ gtk_selection_retrieval_report (info, GDK_NONE, 0, NULL, -1);
+ }
+
+ g_free (info->buffer);
+ g_free (info);
+
+ return FALSE; /* remove timeout */
+ }
+ else
+ {
+ info->idle_time++;
+
+ return TRUE; /* timeout will happen again */
+ }
+
+}
+
+/*************************************************************
+ * gtk_selection_retrieval_report:
+ * Emits a "selection_received" signal.
+ * arguments:
+ * info: information about the retrieval that completed
+ * buffer: buffer containing data (NULL => errror)
+ * results:
+ *************************************************************/
+
+static void
+gtk_selection_retrieval_report (GtkRetrievalInfo *info,
+ GdkAtom type, gint format,
+ guchar *buffer, gint length)
+{
+ GtkSelectionData data;
+
+ data.selection = info->selection;
+ data.target = info->target;
+ data.type = type;
+ data.format = format;
+
+ data.length = length;
+ data.data = buffer;
+
+ gtk_signal_emit_by_name (GTK_OBJECT(info->widget),
+ "selection_received", &data);
+}
+
+/*************************************************************
+ * gtk_selection_find_handler:
+ * Find handler for specified widget/selection/target
+ * arguments:
+ * widget:
+ * selection:
+ * target:
+ * results:
+ *************************************************************/
+
+static GtkSelectionHandler *
+gtk_selection_find_handler (GtkWidget *widget,
+ GdkAtom selection,
+ GdkAtom target)
+{
+ GList *tmp_list;
+ GtkSelectionHandler *handler;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+
+ tmp_list = gtk_object_get_data (GTK_OBJECT (widget),
+ gtk_selection_handler_key);
+
+ while (tmp_list)
+ {
+ handler = (GtkSelectionHandler *)tmp_list->data;
+ if ((handler->selection == selection) && (handler->target == target))
+ return handler;
+ tmp_list = tmp_list->next;
+ }
+
+ return NULL;
+}
+
+
+/*************************************************************
+ * gtk_selection_default_handler:
+ * Handles some default targets that exist for any widget
+ * If it can't fit results into buffer, returns -1. This
+ * won't happen in any conceivable case, since it would
+ * require 1000 selection targets!
+ *
+ * arguments:
+ * widget: selection owner
+ * selection: selection requested
+ * target: target requested
+ * buffer: buffer to write results into
+ * length: size of buffer
+ * type: type atom
+ * format: length of type's units in bits
+ *
+ * results:
+ * Number of bytes written to buffer, -1 if error
+ *************************************************************/
+
+static void
+gtk_selection_default_handler (GtkWidget *widget,
+ GtkSelectionData *data)
+{
+ if (data->target == gtk_selection_atoms[TIMESTAMP])
+ {
+ /* Time which was used to obtain selection */
+ GList *tmp_list;
+ GtkSelectionInfo *selection_info;
+
+ tmp_list = current_selections;
+ while (tmp_list)
+ {
+ selection_info = (GtkSelectionInfo *)tmp_list->data;
+ if ((selection_info->widget == widget) &&
+ (selection_info->selection == data->selection))
+ {
+ gtk_selection_data_set (data,
+ GDK_SELECTION_TYPE_INTEGER,
+ sizeof (guint32)*8,
+ (guchar *)&selection_info->time,
+ sizeof (guint32));
+ return;
+ }
+
+ tmp_list = tmp_list->next;
+ }
+
+ data->length = -1;
+ }
+ else if (data->target == gtk_selection_atoms[TARGETS])
+ {
+ /* List of all targets supported for this widget/selection pair */
+ GdkAtom *p;
+ gint count;
+ GList *tmp_list;
+ GtkSelectionHandler *handler;
+
+ count = 3;
+ tmp_list = gtk_object_get_data (GTK_OBJECT(widget),
+ gtk_selection_handler_key);
+ while (tmp_list)
+ {
+ handler = (GtkSelectionHandler *)tmp_list->data;
+
+ if (handler->selection == data->selection)
+ count++;
+
+ tmp_list = tmp_list->next;
+ }
+
+ data->type = GDK_SELECTION_TYPE_ATOM;
+ data->format = 8*sizeof (GdkAtom);
+ data->length = count*sizeof (GdkAtom);
+
+ p = g_new (GdkAtom, count);
+ data->data = (guchar *)p;
+
+ *p++ = gtk_selection_atoms[TIMESTAMP];
+ *p++ = gtk_selection_atoms[TARGETS];
+ *p++ = gtk_selection_atoms[MULTIPLE];
+
+ tmp_list = gtk_object_get_data (GTK_OBJECT(widget),
+ gtk_selection_handler_key);
+ while (tmp_list)
+ {
+ handler = (GtkSelectionHandler *)tmp_list->data;
+
+ if (handler->selection == data->selection)
+ *p++ = handler->target;
+
+ tmp_list = tmp_list->next;
+ }
+ }
+ else
+ {
+ data->length = -1;
+ }
+}