source: trunk/third/gtk/gtk/gtkselection.c @ 15781

Revision 15781, 41.5 KB checked in by ghudson, 24 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r15780, which included commits to RCS files with non-trunk default branches.
Line 
1/* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
18 */
19
20/* This file implements most of the work of the ICCM selection protocol.
21 * The code was written after an intensive study of the equivalent part
22 * of John Ousterhout's Tk toolkit, and does many things in much the
23 * same way.
24 *
25 * The one thing in the ICCM that isn't fully supported here (or in Tk)
26 * is side effects targets. For these to be handled properly, MULTIPLE
27 * targets need to be done in the order specified. This cannot be
28 * guaranteed with the way we do things, since if we are doing INCR
29 * transfers, the order will depend on the timing of the requestor.
30 *
31 * By Owen Taylor <owt1@cornell.edu>          8/16/97
32 */
33
34/* Terminology note: when not otherwise specified, the term "incr" below
35 * refers to the _sending_ part of the INCR protocol. The receiving
36 * portion is referred to just as "retrieval". (Terminology borrowed
37 * from Tk, because there is no good opposite to "retrieval" in English.
38 * "send" can't be made into a noun gracefully and we're already using
39 * "emission" for something else ....)
40 */
41
42/* The MOTIF entry widget seems to ask for the TARGETS target, then
43   (regardless of the reply) ask for the TEXT target. It's slightly
44   possible though that it somehow thinks we are responding negatively
45   to the TARGETS request, though I don't really think so ... */
46
47/*
48 * Modified by the GTK+ Team and others 1997-1999.  See the AUTHORS
49 * file for a list of people on the GTK+ Team.  See the ChangeLog
50 * files for a list of changes.  These files are distributed with
51 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
52 */
53
54#include <stdarg.h>
55#include <string.h>
56#include <gdk/gdkx.h>
57/* we need this for gdk_window_lookup() */
58#include "gtkmain.h"
59#include "gtkselection.h"
60#include "gtksignal.h"
61
62/* #define DEBUG_SELECTION */
63
64/* Maximum size of a sent chunk, in bytes. Also the default size of
65   our buffers */
66#define GTK_SELECTION_MAX_SIZE 4000
67
68enum {
69  INCR,
70  MULTIPLE,
71  TARGETS,
72  TIMESTAMP,
73  LAST_ATOM
74};
75
76typedef struct _GtkSelectionInfo GtkSelectionInfo;
77typedef struct _GtkIncrConversion GtkIncrConversion;
78typedef struct _GtkIncrInfo GtkIncrInfo;
79typedef struct _GtkRetrievalInfo GtkRetrievalInfo;
80
81struct _GtkSelectionInfo
82{
83  GdkAtom    selection;
84  GtkWidget *widget;            /* widget that owns selection */
85  guint32    time;              /* time used to acquire selection */
86};
87
88struct _GtkIncrConversion
89{
90  GdkAtom           target;     /* Requested target */
91  GdkAtom           property;   /* Property to store in */
92  GtkSelectionData  data;       /* The data being supplied */
93  gint              offset;     /* Current offset in sent selection.
94                                 *  -1 => All done
95                                 *  -2 => Only the final (empty) portion
96                                 *        left to send */
97};
98
99struct _GtkIncrInfo
100{
101  GtkWidget *widget;            /* Selection owner */
102  GdkWindow *requestor;         /* Requestor window - we create a GdkWindow
103                                   so we can receive events */
104  GdkAtom    selection;         /* Selection we're sending */
105 
106  GtkIncrConversion *conversions; /* Information about requested conversions -
107                                   * With MULTIPLE requests (benighted 1980's
108                                   * hardware idea), there can be more than
109                                   * one */
110  gint num_conversions;
111  gint num_incrs;               /* number of remaining INCR style transactions */
112  guint32 idle_time;
113};
114
115
116struct _GtkRetrievalInfo
117{
118  GtkWidget *widget;
119  GdkAtom selection;            /* Selection being retrieved. */
120  GdkAtom target;               /* Form of selection that we requested */
121  guint32 idle_time;            /* Number of seconds since we last heard
122                                   from selection owner */
123  guchar   *buffer;             /* Buffer in which to accumulate results */
124  gint     offset;              /* Current offset in buffer, -1 indicates
125                                   not yet started */
126  guint32 notify_time;          /* Timestamp from SelectionNotify */
127};
128
129/* Local Functions */
130static void gtk_selection_init              (void);
131static gint gtk_selection_incr_timeout      (GtkIncrInfo      *info);
132static gint gtk_selection_retrieval_timeout (GtkRetrievalInfo *info);
133static void gtk_selection_retrieval_report  (GtkRetrievalInfo *info,
134                                             GdkAtom           type,
135                                             gint              format,
136                                             guchar           *buffer,
137                                             gint              length,
138                                             guint32           time);
139static void gtk_selection_invoke_handler    (GtkWidget        *widget,
140                                             GtkSelectionData *data,
141                                             guint             time);
142static void gtk_selection_default_handler   (GtkWidget        *widget,
143                                             GtkSelectionData *data);
144static int  gtk_selection_bytes_per_item    (gint              format);
145
146/* Local Data */
147static gint initialize = TRUE;
148static GList *current_retrievals = NULL;
149static GList *current_incrs = NULL;
150static GList *current_selections = NULL;
151
152static GdkAtom gtk_selection_atoms[LAST_ATOM];
153static const char *gtk_selection_handler_key = "gtk-selection-handlers";
154
155/****************
156 * Target Lists *
157 ****************/
158
159/*
160 * Target lists
161 */
162
163GtkTargetList *
164gtk_target_list_new (const GtkTargetEntry *targets,
165                     guint                 ntargets)
166{
167  GtkTargetList *result = g_new (GtkTargetList, 1);
168  result->list = NULL;
169  result->ref_count = 1;
170
171  if (targets)
172    gtk_target_list_add_table (result, targets, ntargets);
173 
174  return result;
175}
176
177void               
178gtk_target_list_ref (GtkTargetList *list)
179{
180  g_return_if_fail (list != NULL);
181
182  list->ref_count++;
183}
184
185void               
186gtk_target_list_unref (GtkTargetList *list)
187{
188  g_return_if_fail (list != NULL);
189  g_return_if_fail (list->ref_count > 0);
190
191  list->ref_count--;
192  if (list->ref_count == 0)
193    {
194      GList *tmp_list = list->list;
195      while (tmp_list)
196        {
197          GtkTargetPair *pair = tmp_list->data;
198          g_free (pair);
199
200          tmp_list = tmp_list->next;
201        }
202     
203      g_list_free (list->list);
204      g_free (list);
205    }
206}
207
208void
209gtk_target_list_add (GtkTargetList *list,
210                     GdkAtom            target,
211                     guint              flags,
212                     guint              info)
213{
214  GtkTargetPair *pair;
215
216  g_return_if_fail (list != NULL);
217 
218  pair = g_new (GtkTargetPair, 1);
219  pair->target = target;
220  pair->flags = flags;
221  pair->info = info;
222
223  list->list = g_list_append (list->list, pair);
224}
225
226void               
227gtk_target_list_add_table (GtkTargetList        *list,
228                           const GtkTargetEntry *targets,
229                           guint                 ntargets)
230{
231  gint i;
232
233  for (i=ntargets-1; i >= 0; i--)
234    {
235      GtkTargetPair *pair = g_new (GtkTargetPair, 1);
236      pair->target = gdk_atom_intern (targets[i].target, FALSE);
237      pair->flags = targets[i].flags;
238      pair->info = targets[i].info;
239     
240      list->list = g_list_prepend (list->list, pair);
241    }
242}
243
244void
245gtk_target_list_remove (GtkTargetList *list,
246                        GdkAtom            target)
247{
248  GList *tmp_list;
249
250  g_return_if_fail (list != NULL);
251
252  tmp_list = list->list;
253  while (tmp_list)
254    {
255      GtkTargetPair *pair = tmp_list->data;
256     
257      if (pair->target == target)
258        {
259          g_free (pair);
260
261          list->list = g_list_remove_link (list->list, tmp_list);
262          g_list_free_1 (tmp_list);
263
264          return;
265        }
266     
267      tmp_list = tmp_list->next;
268    }
269}
270
271gboolean
272gtk_target_list_find (GtkTargetList *list,
273                      GdkAtom        target,
274                      guint         *info)
275{
276  GList *tmp_list = list->list;
277  while (tmp_list)
278    {
279      GtkTargetPair *pair = tmp_list->data;
280
281      if (pair->target == target)
282        {
283          *info = pair->info;
284          return TRUE;
285        }
286      tmp_list = tmp_list->next;
287    }
288
289  return FALSE;
290}
291
292
293/*************************************************************
294 * gtk_selection_owner_set:
295 *     Claim ownership of a selection.
296 *   arguments:
297 *     widget:          new selection owner
298 *     selection:       which selection
299 *     time:            time (use GDK_CURRENT_TIME only if necessary)
300 *
301 *   results:
302 *************************************************************/
303
304gint
305gtk_selection_owner_set (GtkWidget *widget,
306                         GdkAtom    selection,
307                         guint32    time)
308{
309  GList *tmp_list;
310  GtkWidget *old_owner;
311  GtkSelectionInfo *selection_info = NULL;
312  GdkWindow *window;
313 
314  if (widget == NULL)
315    window = NULL;
316  else
317    {
318      if (!GTK_WIDGET_REALIZED (widget))
319        gtk_widget_realize (widget);
320     
321      window = widget->window;
322    }
323 
324  tmp_list = current_selections;
325  while (tmp_list)
326    {
327      selection_info = (GtkSelectionInfo *)tmp_list->data;
328     
329      if (selection_info->selection == selection)
330        break;
331     
332      tmp_list = tmp_list->next;
333    }
334 
335  if (tmp_list == NULL)
336    selection_info = NULL;
337  else
338    if (selection_info->widget == widget)
339      return TRUE;
340 
341  if (gdk_selection_owner_set (window, selection, time, TRUE))
342    {
343      old_owner = NULL;
344     
345      if (widget == NULL)
346        {
347          if (selection_info)
348            {
349              old_owner = selection_info->widget;
350              current_selections = g_list_remove_link (current_selections,
351                                                       tmp_list);
352              g_list_free (tmp_list);
353              g_free (selection_info);
354            }
355        }
356      else
357        {
358          if (selection_info == NULL)
359            {
360              selection_info = g_new (GtkSelectionInfo, 1);
361              selection_info->selection = selection;
362              selection_info->widget = widget;
363              selection_info->time = time;
364              current_selections = g_list_append (current_selections,
365                                                  selection_info);
366            }
367          else
368            {
369              old_owner = selection_info->widget;
370              selection_info->widget = widget;
371              selection_info->time = time;
372            }
373        }
374      /* If another widget in the application lost the selection,
375       *  send it a GDK_SELECTION_CLEAR event, unless we're setting
376       *  the owner to None, in which case an event will be sent */
377      if (old_owner && (widget != NULL))
378        {
379          GdkEventSelection event;
380         
381          event.type = GDK_SELECTION_CLEAR;
382          event.window = old_owner->window;
383          event.selection = selection;
384          event.time = time;
385         
386          gtk_widget_event (old_owner, (GdkEvent *) &event);
387        }
388      return TRUE;
389    }
390  else
391    return FALSE;
392}
393
394/*************************************************************
395 * gtk_selection_add_target
396 *     Add specified target to list of supported targets
397 *
398 *   arguments:
399 *     widget:     The widget for which this target applies
400 *     selection:
401 *     target:
402 *     info:       guint to pass to to the selection_get signal
403 *
404 *   results:
405 *************************************************************/
406
407typedef struct _GtkSelectionTargetList GtkSelectionTargetList;
408
409struct _GtkSelectionTargetList {
410  GdkAtom selection;
411  GtkTargetList *list;
412};
413
414static GtkTargetList *
415gtk_selection_target_list_get (GtkWidget    *widget,
416                               GdkAtom       selection)
417{
418  GtkSelectionTargetList *sellist;
419  GList *tmp_list;
420  GList *lists;
421
422  lists = gtk_object_get_data (GTK_OBJECT (widget), gtk_selection_handler_key);
423 
424  tmp_list = lists;
425  while (tmp_list)
426    {
427      sellist = tmp_list->data;
428      if (sellist->selection == selection)
429        return sellist->list;
430      tmp_list = tmp_list->next;
431    }
432
433  sellist = g_new (GtkSelectionTargetList, 1);
434  sellist->selection = selection;
435  sellist->list = gtk_target_list_new (NULL, 0);
436
437  lists = g_list_prepend (lists, sellist);
438  gtk_object_set_data (GTK_OBJECT (widget), gtk_selection_handler_key, lists);
439
440  return sellist->list;
441}
442
443static void
444gtk_selection_target_list_remove (GtkWidget    *widget)
445{
446  GtkSelectionTargetList *sellist;
447  GList *tmp_list;
448  GList *lists;
449
450  lists = gtk_object_get_data (GTK_OBJECT (widget), gtk_selection_handler_key);
451 
452  tmp_list = lists;
453  while (tmp_list)
454    {
455      sellist = tmp_list->data;
456
457      gtk_target_list_unref (sellist->list);
458
459      g_free (sellist);
460      tmp_list = tmp_list->next;
461    }
462
463  g_list_free (lists);
464  gtk_object_set_data (GTK_OBJECT (widget), gtk_selection_handler_key, NULL);
465}
466
467void
468gtk_selection_add_target (GtkWidget         *widget,
469                          GdkAtom            selection,
470                          GdkAtom            target,
471                          guint              info)
472{
473  GtkTargetList *list;
474
475  g_return_if_fail (widget != NULL);
476
477  list = gtk_selection_target_list_get (widget, selection);
478  gtk_target_list_add (list, target, 0, info);
479}
480
481void
482gtk_selection_add_targets (GtkWidget            *widget,
483                           GdkAtom               selection,
484                           const GtkTargetEntry *targets,
485                           guint                 ntargets)
486{
487  GtkTargetList *list;
488 
489  g_return_if_fail (widget != NULL);
490  g_return_if_fail (targets != NULL);
491 
492  list = gtk_selection_target_list_get (widget, selection);
493  gtk_target_list_add_table (list, targets, ntargets);
494}
495
496/*************************************************************
497 * gtk_selection_remove_all:
498 *     Removes all handlers and unsets ownership of all
499 *     selections for a widget. Called when widget is being
500 *     destroyed
501 *     
502 *   arguments:
503 *     widget:    The widget
504 *   results:
505 *************************************************************/
506
507void
508gtk_selection_remove_all (GtkWidget *widget)
509{
510  GList *tmp_list;
511  GList *next;
512  GtkSelectionInfo *selection_info;
513 
514  /* Remove pending requests/incrs for this widget */
515 
516  tmp_list = current_incrs;
517  while (tmp_list)
518    {
519      next = tmp_list->next;
520      if (((GtkIncrInfo *)tmp_list->data)->widget == widget)
521        {
522          current_incrs = g_list_remove_link (current_incrs, tmp_list);
523          /* structure will be freed in timeout */
524          g_list_free (tmp_list);
525        }
526      tmp_list = next;
527    }
528 
529  tmp_list = current_retrievals;
530  while (tmp_list)
531    {
532      next = tmp_list->next;
533      if (((GtkRetrievalInfo *)tmp_list->data)->widget == widget)
534        {
535          current_retrievals = g_list_remove_link (current_retrievals,
536                                                   tmp_list);
537          /* structure will be freed in timeout */
538          g_list_free (tmp_list);
539        }
540      tmp_list = next;
541    }
542 
543  /* Disclaim ownership of any selections */
544 
545  tmp_list = current_selections;
546  while (tmp_list)
547    {
548      next = tmp_list->next;
549      selection_info = (GtkSelectionInfo *)tmp_list->data;
550     
551      if (selection_info->widget == widget)
552        {       
553          gdk_selection_owner_set (NULL,
554                                   selection_info->selection,
555                                   GDK_CURRENT_TIME, FALSE);
556          current_selections = g_list_remove_link (current_selections,
557                                                   tmp_list);
558          g_list_free (tmp_list);
559          g_free (selection_info);
560        }
561     
562      tmp_list = next;
563    }
564
565  /* Remove all selection lists */
566  gtk_selection_target_list_remove (widget);
567}
568
569/*************************************************************
570 * gtk_selection_convert:
571 *     Request the contents of a selection. When received,
572 *     a "selection_received" signal will be generated.
573 *
574 *   arguments:
575 *     widget:     The widget which acts as requestor
576 *     selection:  Which selection to get
577 *     target:     Form of information desired (e.g., STRING)
578 *     time:       Time of request (usually of triggering event)
579 *                 In emergency, you could use GDK_CURRENT_TIME
580 *
581 *   results:
582 *     TRUE if requested succeeded. FALSE if we could not process
583 *     request. (e.g., there was already a request in process for
584 *     this widget).
585 *************************************************************/
586
587gint
588gtk_selection_convert (GtkWidget *widget,
589                       GdkAtom    selection,
590                       GdkAtom    target,
591                       guint32    time)
592{
593  GtkRetrievalInfo *info;
594  GList *tmp_list;
595  GdkWindow *owner_window;
596 
597  g_return_val_if_fail (widget != NULL, FALSE);
598 
599  if (initialize)
600    gtk_selection_init ();
601 
602  if (!GTK_WIDGET_REALIZED (widget))
603    gtk_widget_realize (widget);
604 
605  /* Check to see if there are already any retrievals in progress for
606     this widget. If we changed GDK to use the selection for the
607     window property in which to store the retrieved information, then
608     we could support multiple retrievals for different selections.
609     This might be useful for DND. */
610 
611  tmp_list = current_retrievals;
612  while (tmp_list)
613    {
614      info = (GtkRetrievalInfo *)tmp_list->data;
615      if (info->widget == widget)
616        return FALSE;
617      tmp_list = tmp_list->next;
618    }
619 
620  info = g_new (GtkRetrievalInfo, 1);
621 
622  info->widget = widget;
623  info->selection = selection;
624  info->target = target;
625  info->buffer = NULL;
626  info->offset = -1;
627 
628  /* Check if this process has current owner. If so, call handler
629     procedure directly to avoid deadlocks with INCR. */
630 
631  owner_window = gdk_selection_owner_get (selection);
632 
633  if (owner_window != NULL)
634    {
635      GtkWidget *owner_widget;
636      GtkSelectionData selection_data;
637     
638      selection_data.selection = selection;
639      selection_data.target = target;
640      selection_data.data = NULL;
641      selection_data.length = -1;
642     
643      gdk_window_get_user_data (owner_window, (gpointer *)&owner_widget);
644     
645      if (owner_widget != NULL)
646        {
647          gtk_selection_invoke_handler (owner_widget,
648                                        &selection_data,
649                                        time);
650         
651          gtk_selection_retrieval_report (info,
652                                          selection_data.type,
653                                          selection_data.format,
654                                          selection_data.data,
655                                          selection_data.length,
656                                          time);
657         
658          g_free (selection_data.data);
659         
660          g_free (info);
661          return TRUE;
662        }
663    }
664 
665  /* Otherwise, we need to go through X */
666 
667  current_retrievals = g_list_append (current_retrievals, info);
668  gdk_selection_convert (widget->window, selection, target, time);
669  gtk_timeout_add (1000, (GtkFunction) gtk_selection_retrieval_timeout, info);
670 
671  return TRUE;
672}
673
674/*************************************************************
675 * gtk_selection_data_set:
676 *     Store new data into a GtkSelectionData object. Should
677 *     _only_ by called from a selection handler callback.
678 *     Null terminates the stored data.
679 *   arguments:
680 *     type:    the type of selection data
681 *     format:  format (number of bits in a unit)
682 *     data:    pointer to the data (will be copied)
683 *     length:  length of the data
684 *   results:
685 *************************************************************/
686
687void
688gtk_selection_data_set (GtkSelectionData *selection_data,
689                        GdkAtom           type,
690                        gint              format,
691                        const guchar     *data,
692                        gint              length)
693{
694  if (selection_data->data)
695    g_free (selection_data->data);
696 
697  selection_data->type = type;
698  selection_data->format = format;
699 
700  if (data)
701    {
702      selection_data->data = g_new (guchar, length+1);
703      memcpy (selection_data->data, data, length);
704      selection_data->data[length] = 0;
705    }
706  else
707    {
708      g_return_if_fail (length <= 0);
709     
710      if (length < 0)
711        selection_data->data = NULL;
712      else
713        selection_data->data = g_strdup("");
714    }
715 
716  selection_data->length = length;
717}
718
719/*************************************************************
720 * gtk_selection_init:
721 *     Initialize local variables
722 *   arguments:
723 *     
724 *   results:
725 *************************************************************/
726
727static void
728gtk_selection_init (void)
729{
730  gtk_selection_atoms[INCR] = gdk_atom_intern ("INCR", FALSE);
731  gtk_selection_atoms[MULTIPLE] = gdk_atom_intern ("MULTIPLE", FALSE);
732  gtk_selection_atoms[TIMESTAMP] = gdk_atom_intern ("TIMESTAMP", FALSE);
733  gtk_selection_atoms[TARGETS] = gdk_atom_intern ("TARGETS", FALSE);
734}
735
736/*************************************************************
737 * gtk_selection_clear:
738 *     Handler for "selection_clear_event"
739 *   arguments:
740 *     widget:
741 *     event:
742 *   results:
743 *************************************************************/
744
745gint
746gtk_selection_clear (GtkWidget *widget,
747                     GdkEventSelection *event)
748{
749  /* FIXME: there can be a problem if we change the selection
750     via gtk_selection_owner_set after another client claims
751     the selection, but before we get the notification event.
752     Tk filters based on serial #'s, which aren't retained by
753     GTK. Filtering based on time's will be inherently
754     somewhat unreliable. */
755 
756  GList *tmp_list;
757  GtkSelectionInfo *selection_info = NULL;
758 
759  tmp_list = current_selections;
760  while (tmp_list)
761    {
762      selection_info = (GtkSelectionInfo *)tmp_list->data;
763     
764      if ((selection_info->selection == event->selection) &&
765          (selection_info->widget == widget))
766        break;
767     
768      tmp_list = tmp_list->next;
769    }
770 
771  if (tmp_list)
772    {
773      if (selection_info->time > event->time)
774        return FALSE;           /* return FALSE to indicate that
775                                 * the selection was out of date,
776                                 * and this clear should be ignored */
777      else
778        {
779          current_selections = g_list_remove_link (current_selections, tmp_list);
780          g_list_free (tmp_list);
781          g_free (selection_info);
782        }
783    }
784 
785  return TRUE;
786}
787
788
789/*************************************************************
790 * gtk_selection_request:
791 *     Handler for "selection_request_event"
792 *   arguments:
793 *     widget:
794 *     event:
795 *   results:
796 *************************************************************/
797
798gint
799gtk_selection_request (GtkWidget *widget,
800                       GdkEventSelection *event)
801{
802  GtkIncrInfo *info;
803  GList *tmp_list;
804  guchar *mult_atoms;
805  int i;
806 
807  if (initialize)
808    gtk_selection_init ();
809 
810  /* Check if we own selection */
811 
812  tmp_list = current_selections;
813  while (tmp_list)
814    {
815      GtkSelectionInfo *selection_info = (GtkSelectionInfo *)tmp_list->data;
816     
817      if ((selection_info->selection == event->selection) &&
818          (selection_info->widget == widget))
819        break;
820     
821      tmp_list = tmp_list->next;
822    }
823 
824  if (tmp_list == NULL)
825    return FALSE;
826 
827  info = g_new(GtkIncrInfo, 1);
828 
829  info->widget = widget;
830  info->selection = event->selection;
831  info->num_incrs = 0;
832 
833  /* Create GdkWindow structure for the requestor */
834 
835  info->requestor = gdk_window_lookup (event->requestor);
836  if (!info->requestor)
837    info->requestor = gdk_window_foreign_new (event->requestor);
838 
839  /* Determine conversions we need to perform */
840 
841  if (event->target == gtk_selection_atoms[MULTIPLE])
842    {
843      GdkAtom  type;
844      gint     format;
845      gint     length;
846     
847      mult_atoms = NULL;
848     
849      gdk_error_trap_push();
850      if (!gdk_property_get (info->requestor, event->property, 0, /* AnyPropertyType */
851                             0, GTK_SELECTION_MAX_SIZE, FALSE,
852                             &type, &format, &length, &mult_atoms))
853        {
854          gdk_selection_send_notify (event->requestor, event->selection,
855                                     event->target, GDK_NONE, event->time);
856          g_free (mult_atoms);
857          g_free (info);
858          return TRUE;
859        }
860      gdk_error_trap_pop();
861     
862      info->num_conversions = length / (2*sizeof (GdkAtom));
863      info->conversions = g_new (GtkIncrConversion, info->num_conversions);
864     
865      for (i=0; i<info->num_conversions; i++)
866        {
867          info->conversions[i].target = ((GdkAtom *)mult_atoms)[2*i];
868          info->conversions[i].property = ((GdkAtom *)mult_atoms)[2*i+1];
869        }
870    }
871  else                          /* only a single conversion */
872    {
873      info->conversions = g_new (GtkIncrConversion, 1);
874      info->num_conversions = 1;
875      info->conversions[0].target = event->target;
876      info->conversions[0].property = event->property;
877      mult_atoms = (guchar *)info->conversions;
878    }
879 
880  /* Loop through conversions and determine which of these are big
881     enough to require doing them via INCR */
882  for (i=0; i<info->num_conversions; i++)
883    {
884      GtkSelectionData data;
885      glong items;
886     
887      data.selection = event->selection;
888      data.target = info->conversions[i].target;
889      data.data = NULL;
890      data.length = -1;
891     
892#ifdef DEBUG_SELECTION
893      g_message ("Selection %ld, target %ld (%s) requested by 0x%x (property = %ld)",
894                 event->selection, info->conversions[i].target,
895                 gdk_atom_name(info->conversions[i].target),
896                 event->requestor, event->property);
897#endif
898     
899      gtk_selection_invoke_handler (widget, &data, event->time);
900     
901      if (data.length < 0)
902        {
903          ((GdkAtom *)mult_atoms)[2*i+1] = GDK_NONE;
904          info->conversions[i].property = GDK_NONE;
905          continue;
906        }
907     
908      g_return_val_if_fail ((data.format >= 8) && (data.format % 8 == 0), FALSE);
909     
910      items = data.length / gtk_selection_bytes_per_item (data.format);
911     
912      if (data.length > GTK_SELECTION_MAX_SIZE)
913        {
914          /* Sending via INCR */
915         
916          info->conversions[i].offset = 0;
917          info->conversions[i].data = data;
918          info->num_incrs++;
919         
920          gdk_property_change (info->requestor,
921                               info->conversions[i].property,
922                               gtk_selection_atoms[INCR],
923                               32,
924                               GDK_PROP_MODE_REPLACE,
925                               (guchar *)&items, 1);
926        }
927      else
928        {
929          info->conversions[i].offset = -1;
930         
931          gdk_property_change (info->requestor,
932                               info->conversions[i].property,
933                               data.type,
934                               data.format,
935                               GDK_PROP_MODE_REPLACE,
936                               data.data, items);
937         
938          g_free (data.data);
939        }
940    }
941 
942  /* If we have some INCR's, we need to send the rest of the data in
943     a callback */
944 
945  if (info->num_incrs > 0)
946    {
947      /* FIXME: this could be dangerous if window doesn't still
948         exist */
949     
950#ifdef DEBUG_SELECTION
951      g_message ("Starting INCR...");
952#endif
953     
954      gdk_window_set_events (info->requestor,
955                             gdk_window_get_events (info->requestor) |
956                             GDK_PROPERTY_CHANGE_MASK);
957      current_incrs = g_list_append (current_incrs, info);
958      gtk_timeout_add (1000, (GtkFunction)gtk_selection_incr_timeout, info);
959    }
960 
961  /* If it was a MULTIPLE request, set the property to indicate which
962     conversions succeeded */
963  if (event->target == gtk_selection_atoms[MULTIPLE])
964    {
965      gdk_property_change (info->requestor, event->property,
966                           GDK_SELECTION_TYPE_ATOM, 32,
967                           GDK_PROP_MODE_REPLACE,
968                           mult_atoms, 2*info->num_conversions);
969      g_free (mult_atoms);
970    }
971
972  if (info->num_conversions == 1 &&
973      info->conversions[0].property == GDK_NONE)
974    {
975      /* Reject the entire conversion */
976      gdk_selection_send_notify (event->requestor, event->selection,
977                                 event->target, GDK_NONE, event->time);
978    }
979  else
980    {
981      gdk_selection_send_notify (event->requestor, event->selection,
982                                 event->target, event->property, event->time);
983    }
984 
985  if (info->num_incrs == 0)
986    {
987      g_free (info->conversions);
988      g_free (info);
989    }
990 
991  return TRUE;
992}
993
994/*************************************************************
995 * gtk_selection_incr_event:
996 *     Called whenever an PropertyNotify event occurs for an
997 *     GdkWindow with user_data == NULL. These will be notifications
998 *     that a window we are sending the selection to via the
999 *     INCR protocol has deleted a property and is ready for
1000 *     more data.
1001 *
1002 *   arguments:
1003 *     window:  the requestor window
1004 *     event:   the property event structure
1005 *
1006 *   results:
1007 *************************************************************/
1008
1009gint
1010gtk_selection_incr_event (GdkWindow        *window,
1011                          GdkEventProperty *event)
1012{
1013  GList *tmp_list;
1014  GtkIncrInfo *info = NULL;
1015  gint num_bytes;
1016  guchar *buffer;
1017 
1018  int i;
1019 
1020  if (event->state != GDK_PROPERTY_DELETE)
1021    return FALSE;
1022 
1023#ifdef DEBUG_SELECTION
1024  g_message ("PropertyDelete, property %ld", event->atom);
1025#endif
1026 
1027  /* Now find the appropriate ongoing INCR */
1028  tmp_list = current_incrs;
1029  while (tmp_list)
1030    {
1031      info = (GtkIncrInfo *)tmp_list->data;
1032      if (info->requestor == event->window)
1033        break;
1034     
1035      tmp_list = tmp_list->next;
1036    }
1037 
1038  if (tmp_list == NULL)
1039    return FALSE;
1040 
1041  /* Find out which target this is for */
1042  for (i=0; i<info->num_conversions; i++)
1043    {
1044      if (info->conversions[i].property == event->atom &&
1045          info->conversions[i].offset != -1)
1046        {
1047          int bytes_per_item;
1048         
1049          info->idle_time = 0;
1050         
1051          if (info->conversions[i].offset == -2) /* only the last 0-length
1052                                                    piece*/
1053            {
1054              num_bytes = 0;
1055              buffer = NULL;
1056            }
1057          else
1058            {
1059              num_bytes = info->conversions[i].data.length -
1060                info->conversions[i].offset;
1061              buffer = info->conversions[i].data.data +
1062                info->conversions[i].offset;
1063             
1064              if (num_bytes > GTK_SELECTION_MAX_SIZE)
1065                {
1066                  num_bytes = GTK_SELECTION_MAX_SIZE;
1067                  info->conversions[i].offset += GTK_SELECTION_MAX_SIZE;
1068                }
1069              else
1070                info->conversions[i].offset = -2;
1071            }
1072#ifdef DEBUG_SELECTION
1073          g_message ("INCR: put %d bytes (offset = %d) into window 0x%lx , property %ld",
1074                     num_bytes, info->conversions[i].offset,
1075                     GDK_WINDOW_XWINDOW(info->requestor), event->atom);
1076#endif
1077
1078          bytes_per_item = gtk_selection_bytes_per_item (info->conversions[i].data.format);
1079          gdk_property_change (info->requestor, event->atom,
1080                               info->conversions[i].data.type,
1081                               info->conversions[i].data.format,
1082                               GDK_PROP_MODE_REPLACE,
1083                               buffer,
1084                               num_bytes / bytes_per_item);
1085         
1086          if (info->conversions[i].offset == -2)
1087            {
1088              g_free (info->conversions[i].data.data);
1089              info->conversions[i].data.data = NULL;
1090            }
1091         
1092          if (num_bytes == 0)
1093            {
1094              info->num_incrs--;
1095              info->conversions[i].offset = -1;
1096            }
1097        }
1098      break;
1099    }
1100 
1101  /* Check if we're finished with all the targets */
1102 
1103  if (info->num_incrs == 0)
1104    {
1105      current_incrs = g_list_remove_link (current_incrs, tmp_list);
1106      g_list_free (tmp_list);
1107      /* Let the timeout free it */
1108    }
1109 
1110  return TRUE;
1111}
1112
1113/*************************************************************
1114 * gtk_selection_incr_timeout:
1115 *     Timeout callback for the sending portion of the INCR
1116 *     protocol
1117 *   arguments:
1118 *     info:    Information about this incr
1119 *   results:
1120 *************************************************************/
1121
1122static gint
1123gtk_selection_incr_timeout (GtkIncrInfo *info)
1124{
1125  GList *tmp_list;
1126  gboolean retval;
1127
1128  GDK_THREADS_ENTER ();
1129 
1130  /* Determine if retrieval has finished by checking if it still in
1131     list of pending retrievals */
1132 
1133  tmp_list = current_incrs;
1134  while (tmp_list)
1135    {
1136      if (info == (GtkIncrInfo *)tmp_list->data)
1137        break;
1138      tmp_list = tmp_list->next;
1139    }
1140 
1141  /* If retrieval is finished */
1142  if (!tmp_list || info->idle_time >= 5)
1143    {
1144      if (tmp_list && info->idle_time >= 5)
1145        {
1146          current_incrs = g_list_remove_link (current_incrs, tmp_list);
1147          g_list_free (tmp_list);
1148        }
1149     
1150      g_free (info->conversions);
1151      /* FIXME: we should check if requestor window is still in use,
1152         and if not, remove it? */
1153     
1154      g_free (info);
1155     
1156      retval =  FALSE;          /* remove timeout */
1157    }
1158  else
1159    {
1160      info->idle_time++;
1161     
1162      retval = TRUE;            /* timeout will happen again */
1163    }
1164 
1165  GDK_THREADS_LEAVE ();
1166
1167  return retval;
1168}
1169
1170/*************************************************************
1171 * gtk_selection_notify:
1172 *     Handler for "selection_notify_event" signals on windows
1173 *     where a retrieval is currently in process. The selection
1174 *     owner has responded to our conversion request.
1175 *   arguments:
1176 *     widget:          Widget getting signal
1177 *     event:           Selection event structure
1178 *     info:            Information about this retrieval
1179 *   results:
1180 *     was event handled?
1181 *************************************************************/
1182
1183gint
1184gtk_selection_notify (GtkWidget        *widget,
1185                      GdkEventSelection *event)
1186{
1187  GList *tmp_list;
1188  GtkRetrievalInfo *info = NULL;
1189  guchar  *buffer = NULL;
1190  gint length;
1191  GdkAtom type;
1192  gint    format;
1193 
1194#ifdef DEBUG_SELECTION
1195  g_message ("Initial receipt of selection %ld, target %ld (property = %ld)",
1196             event->selection, event->target, event->property);
1197#endif
1198 
1199  tmp_list = current_retrievals;
1200  while (tmp_list)
1201    {
1202      info = (GtkRetrievalInfo *)tmp_list->data;
1203      if (info->widget == widget && info->selection == event->selection)
1204        break;
1205      tmp_list = tmp_list->next;
1206    }
1207 
1208  if (!tmp_list)                /* no retrieval in progress */
1209    return FALSE;
1210
1211  if (event->property != GDK_NONE)
1212    length = gdk_selection_property_get (widget->window, &buffer,
1213                                         &type, &format);
1214
1215  if (event->property == GDK_NONE || buffer == NULL)
1216    {
1217      current_retrievals = g_list_remove_link (current_retrievals, tmp_list);
1218      g_list_free (tmp_list);
1219      /* structure will be freed in timeout */
1220      gtk_selection_retrieval_report (info,
1221                                      GDK_NONE, 0, NULL, -1, event->time);
1222     
1223      return TRUE;
1224    }
1225 
1226  if (type == gtk_selection_atoms[INCR])
1227    {
1228      /* The remainder of the selection will come through PropertyNotify
1229         events */
1230
1231      info->notify_time = event->time;
1232      info->idle_time = 0;
1233      info->offset = 0;         /* Mark as OK to proceed */
1234      gdk_window_set_events (widget->window,
1235                             gdk_window_get_events (widget->window)
1236                             | GDK_PROPERTY_CHANGE_MASK);
1237    }
1238  else
1239    {
1240      /* We don't delete the info structure - that will happen in timeout */
1241      current_retrievals = g_list_remove_link (current_retrievals, tmp_list);
1242      g_list_free (tmp_list);
1243     
1244      info->offset = length;
1245      gtk_selection_retrieval_report (info,
1246                                      type, format,
1247                                      buffer, length, event->time);
1248    }
1249 
1250  gdk_property_delete (widget->window, event->property);
1251 
1252  g_free (buffer);
1253 
1254  return TRUE;
1255}
1256
1257/*************************************************************
1258 * gtk_selection_property_notify:
1259 *     Handler for "property_notify_event" signals on windows
1260 *     where a retrieval is currently in process. The selection
1261 *     owner has added more data.
1262 *   arguments:
1263 *     widget:          Widget getting signal
1264 *     event:           Property event structure
1265 *     info:            Information about this retrieval
1266 *   results:
1267 *     was event handled?
1268 *************************************************************/
1269
1270gint
1271gtk_selection_property_notify (GtkWidget        *widget,
1272                               GdkEventProperty *event)
1273{
1274  GList *tmp_list;
1275  GtkRetrievalInfo *info = NULL;
1276  guchar *new_buffer;
1277  int length;
1278  GdkAtom type;
1279  gint    format;
1280 
1281  g_return_val_if_fail (widget != NULL, FALSE);
1282  g_return_val_if_fail (event != NULL, FALSE);
1283
1284  if ((event->state != GDK_PROPERTY_NEW_VALUE) ||  /* property was deleted */
1285      (event->atom != gdk_selection_property)) /* not the right property */
1286    return FALSE;
1287 
1288#ifdef DEBUG_SELECTION
1289  g_message ("PropertyNewValue, property %ld",
1290             event->atom);
1291#endif
1292 
1293  tmp_list = current_retrievals;
1294  while (tmp_list)
1295    {
1296      info = (GtkRetrievalInfo *)tmp_list->data;
1297      if (info->widget == widget)
1298        break;
1299      tmp_list = tmp_list->next;
1300    }
1301 
1302  if (!tmp_list)                /* No retrieval in progress */
1303    return FALSE;
1304 
1305  if (info->offset < 0)         /* We haven't got the SelectionNotify
1306                                   for this retrieval yet */
1307    return FALSE;
1308 
1309  info->idle_time = 0;
1310 
1311  length = gdk_selection_property_get (widget->window, &new_buffer,
1312                                       &type, &format);
1313  gdk_property_delete (widget->window, event->atom);
1314 
1315  /* We could do a lot better efficiency-wise by paying attention to
1316     what length was sent in the initial INCR transaction, instead of
1317     doing memory allocation at every step. But its only guaranteed to
1318     be a _lower bound_ (pretty useless!) */
1319 
1320  if (length == 0 || type == GDK_NONE)          /* final zero length portion */
1321    {
1322      /* Info structure will be freed in timeout */
1323      current_retrievals = g_list_remove_link (current_retrievals, tmp_list);
1324      g_list_free (tmp_list);
1325      gtk_selection_retrieval_report (info,
1326                                      type, format,
1327                                      (type == GDK_NONE) ?  NULL : info->buffer,
1328                                      (type == GDK_NONE) ?  -1 : info->offset,
1329                                      info->notify_time);
1330    }
1331  else                          /* append on newly arrived data */
1332    {
1333      if (!info->buffer)
1334        {
1335#ifdef DEBUG_SELECTION
1336          g_message ("Start - Adding %d bytes at offset 0",
1337                     length);
1338#endif
1339          info->buffer = new_buffer;
1340          info->offset = length;
1341        }
1342      else
1343        {
1344         
1345#ifdef DEBUG_SELECTION
1346          g_message ("Appending %d bytes at offset %d",
1347                     length,info->offset);
1348#endif
1349          /* We copy length+1 bytes to preserve guaranteed null termination */
1350          info->buffer = g_realloc (info->buffer, info->offset+length+1);
1351          memcpy (info->buffer + info->offset, new_buffer, length+1);
1352          info->offset += length;
1353          g_free (new_buffer);
1354        }
1355    }
1356 
1357  return TRUE;
1358}
1359
1360/*************************************************************
1361 * gtk_selection_retrieval_timeout:
1362 *     Timeout callback while receiving a selection.
1363 *   arguments:
1364 *     info:    Information about this retrieval
1365 *   results:
1366 *************************************************************/
1367
1368static gint
1369gtk_selection_retrieval_timeout (GtkRetrievalInfo *info)
1370{
1371  GList *tmp_list;
1372  gboolean retval;
1373
1374  GDK_THREADS_ENTER ();
1375 
1376  /* Determine if retrieval has finished by checking if it still in
1377     list of pending retrievals */
1378 
1379  tmp_list = current_retrievals;
1380  while (tmp_list)
1381    {
1382      if (info == (GtkRetrievalInfo *)tmp_list->data)
1383        break;
1384      tmp_list = tmp_list->next;
1385    }
1386 
1387  /* If retrieval is finished */
1388  if (!tmp_list || info->idle_time >= 5)
1389    {
1390      if (tmp_list && info->idle_time >= 5)
1391        {
1392          current_retrievals = g_list_remove_link (current_retrievals, tmp_list);
1393          g_list_free (tmp_list);
1394          gtk_selection_retrieval_report (info, GDK_NONE, 0, NULL, -1, GDK_CURRENT_TIME);
1395        }
1396     
1397      g_free (info->buffer);
1398      g_free (info);
1399     
1400      retval =  FALSE;          /* remove timeout */
1401    }
1402  else
1403    {
1404      info->idle_time++;
1405     
1406      retval =  TRUE;           /* timeout will happen again */
1407    }
1408
1409  GDK_THREADS_LEAVE ();
1410
1411  return retval;
1412}
1413
1414/*************************************************************
1415 * gtk_selection_retrieval_report:
1416 *     Emits a "selection_received" signal.
1417 *   arguments:
1418 *     info:      information about the retrieval that completed
1419 *     buffer:    buffer containing data (NULL => errror)
1420 *     time:      timestamp for data in buffer
1421 *   results:
1422 *************************************************************/
1423
1424static void
1425gtk_selection_retrieval_report (GtkRetrievalInfo *info,
1426                                GdkAtom type, gint format,
1427                                guchar *buffer, gint length,
1428                                guint32 time)
1429{
1430  GtkSelectionData data;
1431 
1432  data.selection = info->selection;
1433  data.target = info->target;
1434  data.type = type;
1435  data.format = format;
1436 
1437  data.length = length;
1438  data.data = buffer;
1439 
1440  gtk_signal_emit_by_name (GTK_OBJECT(info->widget),
1441                           "selection_received",
1442                           &data, time);
1443}
1444
1445/*************************************************************
1446 * gtk_selection_invoke_handler:
1447 *     Finds and invokes handler for specified
1448 *     widget/selection/target combination, calls
1449 *     gtk_selection_default_handler if none exists.
1450 *
1451 *   arguments:
1452 *     widget:      selection owner
1453 *     data:        selection data [INOUT]
1454 *     time:        time from requeset
1455 *     
1456 *   results:
1457 *     Number of bytes written to buffer, -1 if error
1458 *************************************************************/
1459
1460static void
1461gtk_selection_invoke_handler (GtkWidget        *widget,
1462                              GtkSelectionData *data,
1463                              guint             time)
1464{
1465  GtkTargetList *target_list;
1466  guint info;
1467 
1468
1469  g_return_if_fail (widget != NULL);
1470
1471  target_list = gtk_selection_target_list_get (widget, data->selection);
1472  if (target_list &&
1473      gtk_target_list_find (target_list, data->target, &info))
1474    {
1475      gtk_signal_emit_by_name (GTK_OBJECT (widget),
1476                               "selection_get",
1477                               data,
1478                               info, time);
1479    }
1480  else
1481    gtk_selection_default_handler (widget, data);
1482}
1483
1484/*************************************************************
1485 * gtk_selection_default_handler:
1486 *     Handles some default targets that exist for any widget
1487 *     If it can't fit results into buffer, returns -1. This
1488 *     won't happen in any conceivable case, since it would
1489 *     require 1000 selection targets!
1490 *
1491 *   arguments:
1492 *     widget:      selection owner
1493 *     data:        selection data [INOUT]
1494 *
1495 *************************************************************/
1496
1497static void
1498gtk_selection_default_handler (GtkWidget        *widget,
1499                               GtkSelectionData *data)
1500{
1501  if (data->target == gtk_selection_atoms[TIMESTAMP])
1502    {
1503      /* Time which was used to obtain selection */
1504      GList *tmp_list;
1505      GtkSelectionInfo *selection_info;
1506     
1507      tmp_list = current_selections;
1508      while (tmp_list)
1509        {
1510          selection_info = (GtkSelectionInfo *)tmp_list->data;
1511          if ((selection_info->widget == widget) &&
1512              (selection_info->selection == data->selection))
1513            {
1514              gulong time = selection_info->time;
1515
1516              gtk_selection_data_set (data,
1517                                      GDK_SELECTION_TYPE_INTEGER,
1518                                      32,
1519                                      (guchar *)&time,
1520                                      sizeof (time));
1521              return;
1522            }
1523         
1524          tmp_list = tmp_list->next;
1525        }
1526     
1527      data->length = -1;
1528    }
1529  else if (data->target == gtk_selection_atoms[TARGETS])
1530    {
1531      /* List of all targets supported for this widget/selection pair */
1532      GdkAtom *p;
1533      guint count;
1534      GList *tmp_list;
1535      GtkTargetList *target_list;
1536      GtkTargetPair *pair;
1537     
1538      target_list = gtk_selection_target_list_get (widget,
1539                                                   data->selection);
1540      count = g_list_length (target_list->list) + 3;
1541     
1542      data->type = GDK_SELECTION_TYPE_ATOM;
1543      data->format = 32;
1544      data->length = count * sizeof (GdkAtom);
1545     
1546      p = g_new (GdkAtom, count);
1547      data->data = (guchar *)p;
1548     
1549      *p++ = gtk_selection_atoms[TIMESTAMP];
1550      *p++ = gtk_selection_atoms[TARGETS];
1551      *p++ = gtk_selection_atoms[MULTIPLE];
1552     
1553      tmp_list = target_list->list;
1554      while (tmp_list)
1555        {
1556          pair = (GtkTargetPair *)tmp_list->data;
1557          *p++ = pair->target;
1558         
1559          tmp_list = tmp_list->next;
1560        }
1561    }
1562  else
1563    {
1564      data->length = -1;
1565    }
1566}
1567
1568
1569GtkSelectioData*
1570gtk_selection_data_copy (GtkSelectionData *data)
1571{
1572  GtkSelectionData *new_data;
1573 
1574  g_return_val_if_fail (data != NULL, NULL);
1575 
1576  new_data = g_new (GtkSelectionData, 1);
1577  *new_data = *data;
1578 
1579  return new_data;
1580}
1581
1582void
1583gtk_selection_data_free (GtkSelectionData *data)
1584{
1585  g_return_if_fail (data != NULL);
1586 
1587  g_free (data);
1588}
1589
1590static int
1591gtk_selection_bytes_per_item (gint format)
1592{
1593  switch (format)
1594    {
1595    case 8:
1596      return sizeof (char);
1597      break;
1598    case 16:
1599      return sizeof (short);
1600      break;
1601    case 32:
1602      return sizeof (long);
1603      break;
1604    default:
1605      g_assert_not_reached();
1606    }
1607  return 0;
1608}
Note: See TracBrowser for help on using the repository browser.