diff options
Diffstat (limited to 'gtk/gtkselection.c')
-rw-r--r-- | gtk/gtkselection.c | 1388 |
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; + } +} |