]> Pileus Git - ~andy/gtk/blob - gdk/win32/gdkselection-win32.c
use SWP_NOACTIVATE in gdk_window_set_keep_(above|below) to make DND work
[~andy/gtk] / gdk / win32 / gdkselection-win32.c
1 /* GDK - The GIMP Drawing Kit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  * Copyright (C) 1998-2002 Tor Lillqvist
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /*
22  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23  * file for a list of people on the GTK+ Team.  See the ChangeLog
24  * files for a list of changes.  These files are distributed with
25  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
26  */
27
28 #include <string.h>
29 #include <stdlib.h>
30
31 #include "gdkproperty.h"
32 #include "gdkselection.h"
33 #include "gdkdisplay.h"
34 #include "gdkprivate-win32.h"
35
36 /* We emulate the GDK_SELECTION window properties of windows (as used
37  * in the X11 backend) by using a hash table from GdkWindows to
38  * GdkSelProp structs.
39  */
40
41 typedef struct {
42   guchar *data;
43   gint length;
44   gint format;
45   GdkAtom type;
46 } GdkSelProp;
47
48 static GHashTable *sel_prop_table = NULL;
49
50 static GdkSelProp *dropfiles_prop = NULL;
51
52 /* We store the owner of each selection in this table. Obviously, this only
53  * is valid intra-app, and in fact it is necessary for the intra-app DND to work.
54  */
55 static GHashTable *sel_owner_table = NULL;
56
57 void
58 _gdk_win32_selection_init (void)
59 {
60   sel_prop_table = g_hash_table_new (NULL, NULL);
61   sel_owner_table = g_hash_table_new (NULL, NULL);
62 }
63
64 void
65 _gdk_selection_property_store (GdkWindow *owner,
66                                GdkAtom    type,
67                                gint       format,
68                                guchar    *data,
69                                gint       length)
70 {
71   GdkSelProp *prop;
72
73   prop = g_hash_table_lookup (sel_prop_table, GDK_WINDOW_HWND (owner));
74   if (prop != NULL)
75     {
76       g_free (prop->data);
77       g_hash_table_remove (sel_prop_table, GDK_WINDOW_HWND (owner));
78     }
79   prop = g_new (GdkSelProp, 1);
80   prop->data = data;
81   prop->length = length;
82   prop->format = format;
83   prop->type = type;
84   g_hash_table_insert (sel_prop_table, GDK_WINDOW_HWND (owner), prop);
85 }
86
87 void
88 _gdk_dropfiles_store (gchar *data)
89 {
90   if (data != NULL)
91     {
92       g_assert (dropfiles_prop == NULL);
93
94       dropfiles_prop = g_new (GdkSelProp, 1);
95       dropfiles_prop->data = data;
96       dropfiles_prop->length = strlen (data);
97       dropfiles_prop->format = 8;
98       dropfiles_prop->type = _text_uri_list;
99     }
100   else
101     {
102       if (dropfiles_prop != NULL)
103         {
104           g_free (dropfiles_prop->data);
105           g_free (dropfiles_prop);
106         }
107       dropfiles_prop = NULL;
108     }
109 }
110
111 gboolean
112 gdk_selection_owner_set_for_display (GdkDisplay *display,
113                                      GdkWindow  *owner,
114                                      GdkAtom     selection,
115                                      guint32     time,
116                                      gboolean    send_event)
117 {
118   HWND hwnd;
119   GdkEvent tmp_event;
120   gchar *sel_name;
121
122   g_return_val_if_fail (display == gdk_display_get_default (), FALSE);
123
124   GDK_NOTE (DND,
125             (sel_name = gdk_atom_name (selection),
126              g_print ("gdk_selection_owner_set: %p %#x (%s)\n",
127                       (owner ? GDK_WINDOW_HWND (owner) : NULL),
128                       (guint) selection, sel_name),
129              g_free (sel_name)));
130
131   if (selection != GDK_SELECTION_CLIPBOARD)
132     {
133       if (owner != NULL)
134         g_hash_table_insert (sel_owner_table, selection, GDK_WINDOW_HWND (owner));
135       else
136         g_hash_table_remove (sel_owner_table, selection);
137       return TRUE;
138     }
139
140   /* Rest of this function handles the CLIPBOARD selection */
141   if (owner != NULL)
142     {
143       if (GDK_WINDOW_DESTROYED (owner))
144         return FALSE;
145
146       hwnd = GDK_WINDOW_HWND (owner);
147     }
148   else
149     hwnd = NULL;
150
151   if (!API_CALL (OpenClipboard, (hwnd)))
152     return FALSE;
153
154   if (!API_CALL (EmptyClipboard, ()))
155     {
156       API_CALL (CloseClipboard, ());
157       return FALSE;
158     }
159 #if 0
160   /* No delayed rendering */
161   if (hwnd != NULL)
162     SetClipboardData (CF_TEXT, NULL);
163 #endif
164   if (!API_CALL (CloseClipboard, ()))
165     return FALSE;
166
167   if (owner != NULL)
168     {
169       /* Send ourselves a selection request message so that
170        * gdk_property_change will be called to store the clipboard
171        * data.
172        */
173       GDK_NOTE (DND, g_print ("...sending GDK_SELECTION_REQUEST to ourselves\n"));
174       tmp_event.selection.type = GDK_SELECTION_REQUEST;
175       tmp_event.selection.window = owner;
176       tmp_event.selection.send_event = FALSE;
177       tmp_event.selection.selection = selection;
178       tmp_event.selection.target = GDK_TARGET_STRING;
179       tmp_event.selection.property = _gdk_selection_property;
180       tmp_event.selection.requestor = (guint32) hwnd;
181       tmp_event.selection.time = time;
182
183       gdk_event_put (&tmp_event);
184     }
185
186   return TRUE;
187 }
188
189 GdkWindow*
190 gdk_selection_owner_get_for_display (GdkDisplay *display,
191                                      GdkAtom     selection)
192 {
193   GdkWindow *window;
194   gchar *sel_name;
195
196   g_return_val_if_fail (display == gdk_display_get_default (), NULL);
197
198   /* Return NULL for CLIPBOARD, because otherwise cut&paste
199    * inside the same application doesn't work. We must pretend to gtk
200    * that we don't have the selection, so that we always fetch it from
201    * the Windows clipboard. See also comments in
202    * gdk_selection_send_notify().
203    */
204   if (selection == GDK_SELECTION_CLIPBOARD)
205     return NULL;
206
207   window = gdk_window_lookup ((GdkNativeWindow) g_hash_table_lookup (sel_owner_table, selection));
208
209   GDK_NOTE (DND,
210             (sel_name = gdk_atom_name (selection),
211              g_print ("gdk_selection_owner_get: %#x (%s) = %p\n",
212                       (guint) selection, sel_name,
213                       (window ? GDK_WINDOW_HWND (window) : NULL)),
214              g_free (sel_name)));
215
216   return window;
217 }
218
219 static void
220 generate_selection_notify (GdkWindow *requestor,
221                            GdkAtom    selection,
222                            GdkAtom    target,
223                            GdkAtom    property,
224                            guint32    time)
225 {
226   GdkEvent tmp_event;
227
228   tmp_event.selection.type = GDK_SELECTION_NOTIFY;
229   tmp_event.selection.window = requestor;
230   tmp_event.selection.send_event = FALSE;
231   tmp_event.selection.selection = selection;
232   tmp_event.selection.target = target;
233   tmp_event.selection.property = property;
234   tmp_event.selection.requestor = 0;
235   tmp_event.selection.time = time;
236
237   gdk_event_put (&tmp_event);
238 }
239
240 void
241 gdk_selection_convert (GdkWindow *requestor,
242                        GdkAtom    selection,
243                        GdkAtom    target,
244                        guint32    time)
245 {
246   HGLOBAL hdata;
247   GdkAtom property = _gdk_selection_property;
248   gchar *sel_name, *tgt_name;
249
250   g_return_if_fail (requestor != NULL);
251   if (GDK_WINDOW_DESTROYED (requestor))
252     return;
253
254   GDK_NOTE (DND,
255             (sel_name = gdk_atom_name (selection),
256              tgt_name = gdk_atom_name (target),
257              g_print ("gdk_selection_convert: %p %#x (%s) %#x (%s)\n",
258                       GDK_WINDOW_HWND (requestor),
259                       (guint) selection, sel_name,
260                       (guint) target, tgt_name),
261              g_free (sel_name),
262              g_free (tgt_name)));
263
264   if (selection == GDK_SELECTION_CLIPBOARD &&
265       target == gdk_atom_intern ("TARGETS", FALSE))
266     {
267       /* He wants to know what formats are on the clipboard.  If there
268        * is some kind of text, tell him so.
269        */
270       if (!API_CALL (OpenClipboard, (GDK_WINDOW_HWND (requestor))))
271         return;
272
273       if (IsClipboardFormatAvailable (CF_UNICODETEXT) ||
274           IsClipboardFormatAvailable (_cf_utf8_string) ||
275           IsClipboardFormatAvailable (CF_TEXT))
276         {
277           GdkAtom *data = g_new (GdkAtom, 1);
278           *data = GDK_TARGET_STRING;
279           _gdk_selection_property_store (requestor, GDK_SELECTION_TYPE_ATOM,
280                                          32, (guchar *) data, 1 * sizeof (GdkAtom));
281         }
282       else
283         property = GDK_NONE;
284
285       API_CALL (CloseClipboard, ());
286     }
287   else if (selection == GDK_SELECTION_CLIPBOARD &&
288            (target == _compound_text ||
289             target == GDK_TARGET_STRING))
290     {
291       /* Converting the CLIPBOARD selection means he wants the
292        * contents of the clipboard. Get the clipboard data,
293        * and store it for later.
294        */
295       if (!API_CALL (OpenClipboard, (GDK_WINDOW_HWND (requestor))))
296         return;
297
298       /* Try various formats. First the simplest, CF_UNICODETEXT. */
299       if ((hdata = GetClipboardData (CF_UNICODETEXT)) != NULL)
300         {
301           wchar_t *ptr, *wcs, *p, *q;
302           guchar *data;
303           gint length, wclen;
304
305           if ((ptr = GlobalLock (hdata)) != NULL)
306             {
307               length = GlobalSize (hdata);
308               
309               GDK_NOTE (DND, g_print ("...CF_UNICODETEXT: %d bytes\n",
310                                       length));
311
312               /* Strip out \r */
313               wcs = g_new (wchar_t, (length + 1) * 2);
314               p = ptr;
315               q = wcs;
316               wclen = 0;
317               while (*p)
318                 {
319                   if (*p != '\r')
320                     {
321                       *q++ = *p;
322                       wclen++;
323                     }
324                   p++;
325                 }
326
327               data = _gdk_ucs2_to_utf8 (wcs, wclen);
328               g_free (wcs);
329               
330               _gdk_selection_property_store (requestor, target, 8,
331                                              data, strlen (data) + 1);
332               GlobalUnlock (hdata);
333             }
334         }
335       else if ((hdata = GetClipboardData (_cf_utf8_string)) != NULL)
336         {
337           /* UTF8_STRING is a format we store ourselves when necessary */
338           guchar *ptr;
339           gint length;
340
341           if ((ptr = GlobalLock (hdata)) != NULL)
342             {
343               length = GlobalSize (hdata);
344               
345               GDK_NOTE (DND, g_print ("...UTF8_STRING: %d bytes: %.10s\n",
346                                       length, ptr));
347               
348               _gdk_selection_property_store (requestor, target, 8,
349                                              g_strdup (ptr), strlen (ptr) + 1);
350               GlobalUnlock (hdata);
351             }
352         }
353       else if ((hdata = GetClipboardData (CF_TEXT)) != NULL)
354         {
355           /* We must always assume the data can contain non-ASCII
356            * in either the current code page, or if there is CF_LOCALE
357            * data, in that locale's default code page.
358            */
359           HGLOBAL hlcid;
360           UINT cp = CP_ACP;
361           wchar_t *wcs, *wcs2, *p, *q;
362           guchar *ptr, *data;
363           gint length, wclen;
364
365           if ((ptr = GlobalLock (hdata)) != NULL)
366             {
367               length = GlobalSize (hdata);
368               
369               GDK_NOTE (DND, g_print ("...CF_TEXT: %d bytes: %.10s\n",
370                                        length, ptr));
371               
372               if ((hlcid = GetClipboardData (CF_LOCALE)) != NULL)
373                 {
374                   gchar buf[10];
375                   LCID *lcidptr = GlobalLock (hlcid);
376                   if (GetLocaleInfo (*lcidptr, LOCALE_IDEFAULTANSICODEPAGE,
377                                      buf, sizeof (buf)))
378                     {
379                       cp = atoi (buf);
380                       GDK_NOTE (DND, g_print ("...CF_LOCALE: %#lx cp:%d\n",
381                                               *lcidptr, cp));
382                     }
383                   GlobalUnlock (hlcid);
384                 }
385
386               wcs = g_new (wchar_t, length + 1);
387               wclen = MultiByteToWideChar (cp, 0, ptr, -1,
388                                            wcs, length + 1);
389
390               /* Strip out \r */
391               wcs2 = g_new (wchar_t, wclen);
392               p = wcs;
393               q = wcs2;
394               wclen = 0;
395               while (*p)
396                 {
397                   if (*p != '\r')
398                     {
399                       *q++ = *p;
400                       wclen++;
401                     }
402                   p++;
403                 }
404               g_free (wcs);
405
406               data = _gdk_ucs2_to_utf8 (wcs2, wclen);
407               g_free (wcs2);
408               
409               _gdk_selection_property_store (requestor, target, 8,
410                                              data, strlen (data) + 1);
411               GlobalUnlock (hdata);
412             }
413         }
414       else
415         property = GDK_NONE;
416
417       API_CALL (CloseClipboard, ());
418     }
419   else if (selection == _gdk_win32_dropfiles)
420     {
421       /* This means he wants the names of the dropped files.
422        * gdk_dropfiles_filter already has stored the text/uri-list
423        * data temporarily in dropfiles_prop.
424        */
425       if (dropfiles_prop != NULL)
426         {
427           _gdk_selection_property_store
428             (requestor, selection, dropfiles_prop->format,
429              dropfiles_prop->data, dropfiles_prop->length);
430           g_free (dropfiles_prop);
431           dropfiles_prop = NULL;
432         }
433     }
434   else
435     property = GDK_NONE;
436
437   /* Generate a selection notify message so that we actually fetch
438    * the data (if property == _gdk_selection_property) or indicating failure
439    * (if property == GDK_NONE).
440    */
441   generate_selection_notify (requestor, selection, target, property, time);
442 }
443
444 gint
445 gdk_selection_property_get (GdkWindow  *requestor,
446                             guchar    **data,
447                             GdkAtom    *ret_type,
448                             gint       *ret_format)
449 {
450   GdkSelProp *prop;
451
452   g_return_val_if_fail (requestor != NULL, 0);
453   g_return_val_if_fail (GDK_IS_WINDOW (requestor), 0);
454
455   if (GDK_WINDOW_DESTROYED (requestor))
456     return 0;
457   
458   GDK_NOTE (DND, g_print ("gdk_selection_property_get: %p\n",
459                            GDK_WINDOW_HWND (requestor)));
460
461   prop = g_hash_table_lookup (sel_prop_table, GDK_WINDOW_HWND (requestor));
462
463   if (prop == NULL)
464     {
465       *data = NULL;
466       return 0;
467     }
468
469   *data = g_malloc (prop->length);
470   if (prop->length > 0)
471     memmove (*data, prop->data, prop->length);
472
473   if (ret_type)
474     *ret_type = prop->type;
475
476   if (ret_format)
477     *ret_format = prop->format;
478
479   return prop->length;
480 }
481
482 void
483 _gdk_selection_property_delete (GdkWindow *window)
484 {
485   GdkSelProp *prop;
486   
487   GDK_NOTE (DND, g_print ("_gdk_selection_property_delete: %p\n",
488                            GDK_WINDOW_HWND (window)));
489
490   prop = g_hash_table_lookup (sel_prop_table, GDK_WINDOW_HWND (window));
491   if (prop != NULL)
492     {
493       g_free (prop->data);
494       g_hash_table_remove (sel_prop_table, GDK_WINDOW_HWND (window));
495     }
496 }
497
498 void
499 gdk_selection_send_notify_for_display (GdkDisplay *display,
500                                        guint32  requestor,
501                                        GdkAtom  selection,
502                                        GdkAtom  target,
503                                        GdkAtom  property,
504                                        guint32  time)
505 {
506   GdkEvent tmp_event;
507   gchar *sel_name, *tgt_name, *prop_name;
508
509   g_return_if_fail (display == gdk_display_get_default ());
510
511   GDK_NOTE (DND,
512             (sel_name = gdk_atom_name (selection),
513              tgt_name = gdk_atom_name (target),
514              prop_name = gdk_atom_name (property),
515              g_print ("gdk_selection_send_notify: %#x %#x (%s) %#x (%s) %#x (%s)\n",
516                       requestor,
517                       (guint) selection, sel_name,
518                       (guint) target, tgt_name,
519                       (guint) property, prop_name),
520              g_free (sel_name),
521              g_free (tgt_name),
522              g_free (prop_name)));
523
524   /* Send ourselves a selection clear message so that gtk thinks we don't
525    * have the selection, and will claim it anew when needed, and
526    * we thus get a chance to store data in the Windows clipboard.
527    * Otherwise, if a gtkeditable does a copy to CLIPBOARD several times
528    * only the first one actually gets copied to the Windows clipboard,
529    * as only the first one causes a call to gdk_property_change().
530    *
531    * Hmm, there is something fishy with this. Cut and paste inside the
532    * same app didn't work, the gtkeditable immediately forgot the
533    * clipboard contents in gtk_editable_selection_clear() as a result
534    * of this message. OTOH, when I changed gdk_selection_owner_get to
535    * return NULL for CLIPBOARD, it works. Sigh.
536    */
537
538   tmp_event.selection.type = GDK_SELECTION_CLEAR;
539   tmp_event.selection.window = gdk_window_lookup (requestor);
540   tmp_event.selection.send_event = FALSE;
541   tmp_event.selection.selection = selection;
542   tmp_event.selection.target = 0;
543   tmp_event.selection.property = 0;
544   tmp_event.selection.requestor = 0;
545   tmp_event.selection.time = time;
546
547   gdk_event_put (&tmp_event);
548 }
549
550 /* Simplistic implementations of text list and compound text functions */
551
552 gint
553 gdk_text_property_to_text_list_for_display (GdkDisplay     *display,
554                                             GdkAtom       encoding,
555                                             gint          format, 
556                                             const guchar *text,
557                                             gint          length,
558                                             gchar      ***list)
559 {
560   gchar *enc_name;
561
562   g_return_val_if_fail (GDK_IS_DISPLAY (display), 0);
563
564   GDK_NOTE (DND, (enc_name = gdk_atom_name (encoding),
565                   g_print ("gdk_text_property_to_text_list: %s %d %.20s %d\n",
566                            enc_name, format, text, length),
567                   g_free (enc_name)));
568
569   if (!list)
570     return 0;
571
572   *list = g_new (gchar *, 1);
573   **list = g_strdup (text);
574   
575   return 1;
576 }
577
578 void
579 gdk_free_text_list (gchar **list)
580 {
581   g_return_if_fail (list != NULL);
582
583   g_free (*list);
584   g_free (list);
585 }
586
587 gint
588 gdk_string_to_compound_text_for_display (GdkDisplay  *display,
589                                          const gchar *str,
590                                          GdkAtom     *encoding,
591                                          gint        *format,
592                                          guchar     **ctext,
593                                          gint        *length)
594 {
595   g_return_val_if_fail (str != NULL, 0);
596   g_return_val_if_fail (length >= 0, 0);
597   g_return_val_if_fail (GDK_IS_DISPLAY (display), 0);
598
599   GDK_NOTE (DND, g_print ("gdk_string_to_compound_text: %.20s\n", str));
600
601   if (encoding)
602     *encoding = _compound_text;
603
604   if (format)
605     *format = 8;
606
607   if (ctext)
608     *ctext = g_strdup (str);
609
610   if (length)
611     *length = strlen (str);
612
613   return 0;
614 }
615
616 void
617 gdk_free_compound_text (guchar *ctext)
618 {
619   g_free (ctext);
620 }
621
622 /* These are lifted from gdkselection-x11.c, just to get GTK+ to build.
623  * These functions probably don't make much sense at all in Windows.
624  */
625
626 /* FIXME */
627
628 static gint
629 make_list (const gchar  *text,
630            gint          length,
631            gboolean      latin1,
632            gchar      ***list)
633 {
634   GSList *strings = NULL;
635   gint n_strings = 0;
636   gint i;
637   const gchar *p = text;
638   const gchar *q;
639   GSList *tmp_list;
640   GError *error = NULL;
641
642   while (p < text + length)
643     {
644       gchar *str;
645       
646       q = p;
647       while (*q && q < text + length)
648         q++;
649
650       if (latin1)
651         {
652           str = g_convert (p, q - p,
653                            "UTF-8", "ISO-8859-1",
654                            NULL, NULL, &error);
655
656           if (!str)
657             {
658               g_warning ("Error converting selection from STRING: %s",
659                          error->message);
660               g_error_free (error);
661             }
662         }
663       else
664         str = g_strndup (p, q - p);
665
666       if (str)
667         {
668           strings = g_slist_prepend (strings, str);
669           n_strings++;
670         }
671
672       p = q + 1;
673     }
674
675   if (list)
676     *list = g_new (gchar *, n_strings + 1);
677
678   (*list)[n_strings] = NULL;
679   
680   i = n_strings;
681   tmp_list = strings;
682   while (tmp_list)
683     {
684       if (list)
685         (*list)[--i] = tmp_list->data;
686       else
687         g_free (tmp_list->data);
688
689       tmp_list = tmp_list->next;
690     }
691
692   g_slist_free (strings);
693
694   return n_strings;
695 }
696
697 gint 
698 gdk_text_property_to_utf8_list_for_display (GdkDisplay    *display,
699                                             GdkAtom        encoding,
700                                             gint           format,
701                                             const guchar  *text,
702                                             gint           length,
703                                             gchar       ***list)
704 {
705   g_return_val_if_fail (text != NULL, 0);
706   g_return_val_if_fail (length >= 0, 0);
707   g_return_val_if_fail (display == gdk_display_get_default (), 0);
708
709   if (encoding == GDK_TARGET_STRING)
710     {
711       return make_list ((gchar *)text, length, TRUE, list);
712     }
713   else if (encoding == _utf8_string)
714     {
715       return make_list ((gchar *)text, length, FALSE, list);
716     }
717   else
718     {
719       gchar **local_list;
720       gint local_count;
721       gint i;
722       const gchar *charset = NULL;
723       gboolean need_conversion = g_get_charset (&charset);
724       gint count = 0;
725       GError *error = NULL;
726       
727       /* Probably COMPOUND text, we fall back to Xlib routines
728        */
729       local_count = gdk_text_property_to_text_list (encoding,
730                                                     format, 
731                                                     text,
732                                                     length,
733                                                     &local_list);
734       if (list)
735         *list = g_new (gchar *, local_count + 1);
736       
737       for (i=0; i<local_count; i++)
738         {
739           /* list contains stuff in our default encoding
740            */
741           if (need_conversion)
742             {
743               gchar *utf = g_convert (local_list[i], -1,
744                                       "UTF-8", charset,
745                                       NULL, NULL, &error);
746               if (utf)
747                 {
748                   if (list)
749                     (*list)[count++] = utf;
750                   else
751                     g_free (utf);
752                 }
753               else
754                 {
755                   g_warning ("Error converting to UTF-8 from '%s': %s",
756                              charset, error->message);
757                   g_error_free (error);
758                   error = NULL;
759                 }
760             }
761           else
762             {
763               if (list)
764                 (*list)[count++] = g_strdup (local_list[i]);
765             }
766         }
767       
768       gdk_free_text_list (local_list);
769       (*list)[count] = NULL;
770
771       return count;
772     }
773 }
774
775 /* The specifications for COMPOUND_TEXT and STRING specify that C0 and
776  * C1 are not allowed except for \n and \t, however the X conversions
777  * routines for COMPOUND_TEXT only enforce this in one direction,
778  * causing cut-and-paste of \r and \r\n separated text to fail.
779  * This routine strips out all non-allowed C0 and C1 characters
780  * from the input string and also canonicalizes \r, and \r\n to \n
781  */
782 static gchar * 
783 sanitize_utf8 (const gchar *src)
784 {
785   gint len = strlen (src);
786   GString *result = g_string_sized_new (len);
787   const gchar *p = src;
788
789   while (*p)
790     {
791       if (*p == '\r')
792         {
793           p++;
794           if (*p == '\n')
795             p++;
796
797           g_string_append_c (result, '\n');
798         }
799       else
800         {
801           gunichar ch = g_utf8_get_char (p);
802           char buf[7];
803           gint buflen;
804           
805           if (!((ch < 0x20 && ch != '\t' && ch != '\n') || (ch >= 0x7f && ch < 0xa0)))
806             {
807               buflen = g_unichar_to_utf8 (ch, buf);
808               g_string_append_len (result, buf, buflen);
809             }
810
811           p = g_utf8_next_char (p);
812         }
813     }
814
815   return g_string_free (result, FALSE);
816 }
817
818 gchar *
819 gdk_utf8_to_string_target (const gchar *str)
820 {
821   return sanitize_utf8 (str);
822 }
823
824 gboolean
825 gdk_utf8_to_compound_text_for_display (GdkDisplay *display,
826                                        const gchar *str,
827                                        GdkAtom     *encoding,
828                                        gint        *format,
829                                        guchar     **ctext,
830                                        gint        *length)
831 {
832   gboolean need_conversion;
833   const gchar *charset;
834   gchar *locale_str, *tmp_str;
835   GError *error = NULL;
836   gboolean result;
837
838   g_return_val_if_fail (str != NULL, FALSE);
839   g_return_val_if_fail (display == gdk_display_get_default (), FALSE);
840
841   need_conversion = !g_get_charset (&charset);
842
843   tmp_str = sanitize_utf8 (str);
844
845   if (need_conversion)
846     {
847       locale_str = g_convert_with_fallback (tmp_str, -1,
848                                             charset, "UTF-8",
849                                             NULL, NULL, NULL, &error);
850       g_free (tmp_str);
851
852       if (!locale_str)
853         {
854           g_warning ("Error converting from UTF-8 to '%s': %s",
855                      charset, error->message);
856           g_error_free (error);
857
858           if (encoding)
859             *encoding = GDK_NONE;
860           if (format)
861             *format = GPOINTER_TO_UINT (GDK_ATOM_TO_POINTER (GDK_NONE));
862           if (ctext)
863             *ctext = NULL;
864           if (length)
865             *length = 0;
866
867           return FALSE;
868         }
869     }
870   else
871     locale_str = tmp_str;
872     
873   result = gdk_string_to_compound_text (locale_str,
874                                         encoding, format, ctext, length);
875   
876   g_free (locale_str);
877
878   return result;
879 }