1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/clipbrd.cpp
3 // Purpose:
4 // Author:      Robert Roebling
5 // Id:          $Id: clipbrd.cpp 62148 2009-09-26 16:27:08Z JS $
6 // Copyright:   (c) 1998 Robert Roebling
7 // Licence:     wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9 
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12 
13 #if wxUSE_CLIPBOARD
14 
15 #include "wx/clipbrd.h"
16 
17 #ifndef WX_PRECOMP
18     #include "wx/log.h"
19     #include "wx/utils.h"
20     #include "wx/dataobj.h"
21 #endif
22 
23 #include "wx/gtk/private.h"
24 
25 //-----------------------------------------------------------------------------
26 // data
27 //-----------------------------------------------------------------------------
28 
29 GdkAtom  g_clipboardAtom   = 0;
30 GdkAtom  g_targetsAtom     = 0;
31 GdkAtom  g_timestampAtom   = 0;
32 
33 #if wxUSE_UNICODE
34 extern GdkAtom g_altTextAtom;
35 #endif
36 
37 // the trace mask we use with wxLogTrace() - call
38 // wxLog::AddTraceMask(TRACE_CLIPBOARD) to enable the trace messages from here
39 // (there will be a *lot* of them!)
40 #define TRACE_CLIPBOARD _T("clipboard")
41 
42 //-----------------------------------------------------------------------------
43 // reminder
44 //-----------------------------------------------------------------------------
45 
46 /* The contents of a selection are returned in a GtkSelectionData
47    structure. selection/target identify the request.
48    type specifies the type of the return; if length < 0, and
49    the data should be ignored. This structure has object semantics -
50    no fields should be modified directly, they should not be created
51    directly, and pointers to them should not be stored beyond the duration of
52    a callback. (If the last is changed, we'll need to add reference
53    counting)
54 
55 struct _GtkSelectionData
56 {
57   GdkAtom selection;
58   GdkAtom target;
59   GdkAtom type;
60   gint    format;
61   guchar *data;
62   gint    length;
63 };
64 
65 */
66 
67 //-----------------------------------------------------------------------------
68 // "selection_received" for targets
69 //-----------------------------------------------------------------------------
70 
71 extern "C" {
72 static void
targets_selection_received(GtkWidget * WXUNUSED (widget),GtkSelectionData * selection_data,guint32 WXUNUSED (time),wxClipboard * clipboard)73 targets_selection_received( GtkWidget *WXUNUSED(widget),
74                             GtkSelectionData *selection_data,
75                             guint32 WXUNUSED(time),
76                             wxClipboard *clipboard )
77 {
78     if ( wxTheClipboard && selection_data->length > 0 )
79     {
80         // make sure we got the data in the correct form
81         GdkAtom type = selection_data->type;
82         if ( type != GDK_SELECTION_TYPE_ATOM )
83         {
84             if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") )
85             {
86                 wxLogTrace( TRACE_CLIPBOARD,
87                             _T("got unsupported clipboard target") );
88 
89                 clipboard->m_waiting = false;
90                 return;
91             }
92         }
93 
94 #ifdef __WXDEBUG__
95         wxDataFormat clip( selection_data->selection );
96         wxLogTrace( TRACE_CLIPBOARD,
97                     wxT("selection received for targets, clipboard %s"),
98                     clip.GetId().c_str() );
99 #endif // __WXDEBUG__
100 
101         // the atoms we received, holding a list of targets (= formats)
102         GdkAtom *atoms = (GdkAtom *)selection_data->data;
103 
104         for (unsigned int i=0; i<selection_data->length/sizeof(GdkAtom); i++)
105         {
106             wxDataFormat format( atoms[i] );
107 
108             wxLogTrace( TRACE_CLIPBOARD,
109                         wxT("selection received for targets, format %s"),
110                         format.GetId().c_str() );
111 
112 //            printf( "format %s requested %s\n",
113 //                    gdk_atom_name( atoms[i] ),
114 //                    gdk_atom_name( clipboard->m_targetRequested ) );
115 
116             if (format == clipboard->m_targetRequested)
117             {
118                 clipboard->m_waiting = false;
119                 clipboard->m_formatSupported = true;
120                 return;
121             }
122         }
123     }
124 
125     clipboard->m_waiting = false;
126 }
127 }
128 
129 //-----------------------------------------------------------------------------
130 // "selection_received" for the actual data
131 //-----------------------------------------------------------------------------
132 
133 extern "C" {
134 static void
selection_received(GtkWidget * WXUNUSED (widget),GtkSelectionData * selection_data,guint32 WXUNUSED (time),wxClipboard * clipboard)135 selection_received( GtkWidget *WXUNUSED(widget),
136                     GtkSelectionData *selection_data,
137                     guint32 WXUNUSED(time),
138                     wxClipboard *clipboard )
139 {
140     if (!wxTheClipboard)
141     {
142         clipboard->m_waiting = false;
143         return;
144     }
145 
146     wxDataObject *data_object = clipboard->m_receivedData;
147 
148     if (!data_object)
149     {
150         clipboard->m_waiting = false;
151         return;
152     }
153 
154     if (selection_data->length <= 0)
155     {
156         clipboard->m_waiting = false;
157         return;
158     }
159 
160     wxDataFormat format( selection_data->target );
161 
162     // make sure we got the data in the correct format
163     if (!data_object->IsSupportedFormat( format ) )
164     {
165         clipboard->m_waiting = false;
166         return;
167     }
168 
169 #if 0
170     This seems to cause problems somehow
171     // make sure we got the data in the correct form (selection type).
172     // if so, copy data to target object
173     if (selection_data->type != GDK_SELECTION_TYPE_STRING)
174     {
175         clipboard->m_waiting = false;
176         return;
177     }
178 #endif
179 
180     data_object->SetData( format, (size_t) selection_data->length, (const char*) selection_data->data );
181 
182     wxTheClipboard->m_formatSupported = true;
183     clipboard->m_waiting = false;
184 }
185 }
186 
187 //-----------------------------------------------------------------------------
188 // "selection_clear"
189 //-----------------------------------------------------------------------------
190 
191 extern "C" {
192 static gint
selection_clear_clip(GtkWidget * WXUNUSED (widget),GdkEventSelection * event)193 selection_clear_clip( GtkWidget *WXUNUSED(widget), GdkEventSelection *event )
194 {
195     if (!wxTheClipboard) return true;
196 
197     if (event->selection == GDK_SELECTION_PRIMARY)
198     {
199         wxTheClipboard->m_ownsPrimarySelection = false;
200     }
201     else
202     if (event->selection == g_clipboardAtom)
203     {
204         wxTheClipboard->m_ownsClipboard = false;
205     }
206     else
207     {
208         wxTheClipboard->m_waiting = false;
209         return FALSE;
210     }
211 
212     if ((!wxTheClipboard->m_ownsPrimarySelection) &&
213         (!wxTheClipboard->m_ownsClipboard))
214     {
215         /* the clipboard is no longer in our hands. we can the delete clipboard data. */
216         if (wxTheClipboard->m_data)
217         {
218             wxLogTrace(TRACE_CLIPBOARD, wxT("wxClipboard will get cleared" ));
219 
220             delete wxTheClipboard->m_data;
221             wxTheClipboard->m_data = (wxDataObject*) NULL;
222         }
223     }
224 
225     wxTheClipboard->m_waiting = false;
226     return TRUE;
227 }
228 }
229 
230 //-----------------------------------------------------------------------------
231 // selection handler for supplying data
232 //-----------------------------------------------------------------------------
233 
234 extern "C" {
235 static void
selection_handler(GtkWidget * WXUNUSED (widget),GtkSelectionData * selection_data,guint WXUNUSED (info),guint WXUNUSED (time),gpointer signal_data)236 selection_handler( GtkWidget *WXUNUSED(widget),
237                    GtkSelectionData *selection_data,
238                    guint WXUNUSED(info),
239                    guint WXUNUSED(time),
240                    gpointer signal_data )
241 {
242     if (!wxTheClipboard) return;
243 
244     if (!wxTheClipboard->m_data) return;
245 
246     wxDataObject *data = wxTheClipboard->m_data;
247 
248     // ICCCM says that TIMESTAMP is a required atom.
249     // In particular, it satisfies Klipper, which polls
250     // TIMESTAMP to see if the clipboards content has changed.
251     // It shall return the time which was used to set the data.
252     if (selection_data->target == g_timestampAtom)
253     {
254         guint timestamp = GPOINTER_TO_UINT (signal_data);
255         gtk_selection_data_set(selection_data,
256                                GDK_SELECTION_TYPE_INTEGER,
257                                32,
258                                (guchar*)&(timestamp),
259                                sizeof(timestamp));
260         wxLogTrace(TRACE_CLIPBOARD,
261                    _T("Clipboard TIMESTAMP requested, returning timestamp=%u"),
262                    timestamp);
263         return;
264     }
265 
266     wxDataFormat format( selection_data->target );
267 
268 #ifdef __WXDEBUG__
269     wxLogTrace(TRACE_CLIPBOARD,
270                _T("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
271                format.GetId().c_str(),
272                wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->target))).c_str(),
273                wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->type))).c_str(),
274                wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->selection))).c_str(),
275                GPOINTER_TO_UINT( signal_data )
276                );
277 #endif
278 
279     if (!data->IsSupportedFormat( format )) return;
280 
281     int size = data->GetDataSize( format );
282 
283     if (size == 0) return;
284 
285     wxCharBuffer buf(size);
286 
287     // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
288     data->GetDataHere( format, buf.data() );
289 
290     // use UTF8_STRING format if requested in Unicode build but just plain
291     // STRING one in ANSI or if explicitly asked in Unicode
292 #if wxUSE_UNICODE
293     // NB: GTK+ requires special treatment of UTF8_STRING data, the text
294     //     would show as UTF-8 data interpreted as latin1 (?) in other
295     //     GTK+ apps if we used gtk_selection_data_set()
296     if (format == wxDataFormat(wxDF_UNICODETEXT))
297     {
298         gtk_selection_data_set_text(
299             selection_data,
300             (const gchar*)buf.data(),
301             size );
302     }
303     else
304 #endif // wxUSE_UNICODE
305     {
306         gtk_selection_data_set(
307             selection_data,
308             GDK_SELECTION_TYPE_STRING,
309             8*sizeof(gchar),
310             (const guchar*)buf.data(),
311             size );
312     }
313 }
314 }
315 
316 //-----------------------------------------------------------------------------
317 // wxClipboard
318 //-----------------------------------------------------------------------------
319 
IMPLEMENT_DYNAMIC_CLASS(wxClipboard,wxObject)320 IMPLEMENT_DYNAMIC_CLASS(wxClipboard,wxObject)
321 
322 wxClipboard::wxClipboard()
323 {
324     m_open = false;
325     m_waiting = false;
326 
327     m_ownsClipboard = false;
328     m_ownsPrimarySelection = false;
329 
330     m_data = (wxDataObject*) NULL;
331     m_receivedData = (wxDataObject*) NULL;
332 
333     /* we use m_targetsWidget to query what formats are available */
334 
335     m_targetsWidget = gtk_window_new( GTK_WINDOW_POPUP );
336     gtk_widget_realize( m_targetsWidget );
337 
338     g_signal_connect (m_targetsWidget, "selection_received",
339                       G_CALLBACK (targets_selection_received), this);
340 
341     /* we use m_clipboardWidget to get and to offer data */
342 
343     m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
344     gtk_widget_realize( m_clipboardWidget );
345 
346     g_signal_connect (m_clipboardWidget, "selection_received",
347                       G_CALLBACK (selection_received), this);
348 
349     g_signal_connect (m_clipboardWidget, "selection_clear_event",
350                       G_CALLBACK (selection_clear_clip), NULL);
351 
352     if (!g_clipboardAtom) g_clipboardAtom = gdk_atom_intern( "CLIPBOARD", FALSE );
353     if (!g_targetsAtom) g_targetsAtom = gdk_atom_intern ("TARGETS", FALSE);
354     if (!g_timestampAtom) g_timestampAtom = gdk_atom_intern ("TIMESTAMP", FALSE);
355 
356     m_formatSupported = false;
357     m_targetRequested = 0;
358 
359     m_usePrimary = false;
360 }
361 
~wxClipboard()362 wxClipboard::~wxClipboard()
363 {
364     Clear();
365 
366     if (m_clipboardWidget) gtk_widget_destroy( m_clipboardWidget );
367     if (m_targetsWidget) gtk_widget_destroy( m_targetsWidget );
368 }
369 
Clear()370 void wxClipboard::Clear()
371 {
372     gtk_selection_clear_targets(m_clipboardWidget, m_usePrimary ? GDK_SELECTION_PRIMARY : g_clipboardAtom);
373     if (m_data)
374     {
375 #if wxUSE_THREADS
376         /* disable GUI threads */
377 #endif
378 
379         //  As we have data we also own the clipboard. Once we no longer own
380         //  it, clear_selection is called which will set m_data to zero
381         if (gdk_selection_owner_get( g_clipboardAtom ) == m_clipboardWidget->window)
382         {
383             m_waiting = true;
384 
385             gtk_selection_owner_set( (GtkWidget*) NULL, g_clipboardAtom,
386                                      (guint32) GDK_CURRENT_TIME );
387 
388             while (m_waiting) gtk_main_iteration();
389         }
390 
391         if (gdk_selection_owner_get( GDK_SELECTION_PRIMARY ) == m_clipboardWidget->window)
392         {
393             m_waiting = true;
394 
395             gtk_selection_owner_set( (GtkWidget*) NULL, GDK_SELECTION_PRIMARY,
396                                      (guint32) GDK_CURRENT_TIME );
397 
398             while (m_waiting) gtk_main_iteration();
399         }
400 
401         delete m_data;
402         m_data = NULL;
403 
404 #if wxUSE_THREADS
405         /* re-enable GUI threads */
406 #endif
407     }
408 
409     m_targetRequested = 0;
410     m_formatSupported = false;
411 }
412 
Open()413 bool wxClipboard::Open()
414 {
415     wxCHECK_MSG( !m_open, false, wxT("clipboard already open") );
416 
417     m_open = true;
418 
419     return true;
420 }
421 
SetData(wxDataObject * data)422 bool wxClipboard::SetData( wxDataObject *data )
423 {
424     wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
425 
426     wxCHECK_MSG( data, false, wxT("data is invalid") );
427 
428     Clear();
429 
430     return AddData( data );
431 }
432 
AddData(wxDataObject * data)433 bool wxClipboard::AddData( wxDataObject *data )
434 {
435     wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
436 
437     wxCHECK_MSG( data, false, wxT("data is invalid") );
438 
439     // we can only store one wxDataObject
440     Clear();
441 
442     m_data = data;
443 
444     // get formats from wxDataObjects
445     wxDataFormat *array = new wxDataFormat[ m_data->GetFormatCount() ];
446     m_data->GetAllFormats( array );
447 
448     // primary selection or clipboard
449     GdkAtom clipboard = m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
450                                      : g_clipboardAtom;
451 
452     // by default provide TIMESTAMP as a target
453     gtk_selection_add_target( GTK_WIDGET(m_clipboardWidget),
454                               clipboard,
455                               g_timestampAtom,
456                               0 );
457 
458     for (size_t i = 0; i < m_data->GetFormatCount(); i++)
459     {
460         wxLogTrace( TRACE_CLIPBOARD,
461                     wxT("wxClipboard now supports atom %s"),
462                     array[i].GetId().c_str() );
463 
464 //        printf( "added %s\n",
465 //                    gdk_atom_name( array[i].GetFormatId() ) );
466 
467         gtk_selection_add_target( GTK_WIDGET(m_clipboardWidget),
468                                   clipboard,
469                                   array[i],
470                                   0 );  /* what is info ? */
471     }
472 
473     delete[] array;
474 
475     g_signal_connect (m_clipboardWidget, "selection_get",
476                       G_CALLBACK (selection_handler),
477                       GUINT_TO_POINTER (gtk_get_current_event_time()) );
478 
479 #if wxUSE_THREADS
480     /* disable GUI threads */
481 #endif
482 
483     /* Tell the world we offer clipboard data */
484     bool res = (gtk_selection_owner_set( m_clipboardWidget,
485                                          clipboard,
486                                          (guint32) GDK_CURRENT_TIME ));
487 
488     if (m_usePrimary)
489         m_ownsPrimarySelection = res;
490     else
491         m_ownsClipboard = res;
492 
493 #if wxUSE_THREADS
494     /* re-enable GUI threads */
495 #endif
496 
497     return res;
498 }
499 
Close()500 void wxClipboard::Close()
501 {
502     wxCHECK_RET( m_open, wxT("clipboard not open") );
503 
504     m_open = false;
505 }
506 
IsOpened() const507 bool wxClipboard::IsOpened() const
508 {
509     return m_open;
510 }
511 
IsSupported(const wxDataFormat & format)512 bool wxClipboard::IsSupported( const wxDataFormat& format )
513 {
514     /* reentrance problems */
515     if (m_waiting) return false;
516 
517     /* store requested format to be asked for by callbacks */
518     m_targetRequested = format;
519 
520     wxLogTrace( TRACE_CLIPBOARD,
521                 wxT("wxClipboard:IsSupported: requested format: %s"),
522                 format.GetId().c_str() );
523 
524     wxCHECK_MSG( m_targetRequested, false, wxT("invalid clipboard format") );
525 
526     m_formatSupported = false;
527 
528     /* perform query. this will set m_formatSupported to
529        true if m_targetRequested is supported.
530        also, we have to wait for the "answer" from the
531        clipboard owner which is an asynchronous process.
532        therefore we set m_waiting = true here and wait
533        until the callback "targets_selection_received"
534        sets it to false */
535 
536     m_waiting = true;
537 
538     gtk_selection_convert( m_targetsWidget,
539                            m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
540                                         : g_clipboardAtom,
541                            g_targetsAtom,
542                            (guint32) GDK_CURRENT_TIME );
543 
544     while (m_waiting) gtk_main_iteration();
545 
546 #if wxUSE_UNICODE
547     if (!m_formatSupported && format == wxDataFormat(wxDF_UNICODETEXT))
548     {
549         // Another try with plain STRING format
550         extern GdkAtom g_altTextAtom;
551         return IsSupported(g_altTextAtom);
552     }
553 #endif
554 
555     return m_formatSupported;
556 }
557 
GetData(wxDataObject & data)558 bool wxClipboard::GetData( wxDataObject& data )
559 {
560     wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
561 
562     /* get formats from wxDataObjects */
563     wxDataFormat *array = new wxDataFormat[ data.GetFormatCount() ];
564     data.GetAllFormats( array );
565 
566     for (size_t i = 0; i < data.GetFormatCount(); i++)
567     {
568         wxDataFormat format( array[i] );
569 
570         wxLogTrace( TRACE_CLIPBOARD,
571                     wxT("wxClipboard::GetData: requested format: %s"),
572                     format.GetId().c_str() );
573 
574         /* is data supported by clipboard ? */
575 
576         /* store requested format to be asked for by callbacks */
577         m_targetRequested = format;
578 
579         wxCHECK_MSG( m_targetRequested, false, wxT("invalid clipboard format") );
580 
581         m_formatSupported = false;
582 
583        /* perform query. this will set m_formatSupported to
584           true if m_targetRequested is supported.
585           also, we have to wait for the "answer" from the
586           clipboard owner which is an asynchronous process.
587           therefore we set m_waiting = true here and wait
588           until the callback "targets_selection_received"
589           sets it to false */
590 
591         m_waiting = true;
592 
593         gtk_selection_convert( m_targetsWidget,
594                            m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
595                                         : g_clipboardAtom,
596                            g_targetsAtom,
597                            (guint32) GDK_CURRENT_TIME );
598 
599         while (m_waiting) gtk_main_iteration();
600 
601         if (!m_formatSupported) continue;
602 
603         /* store pointer to data object to be filled up by callbacks */
604         m_receivedData = &data;
605 
606         /* store requested format to be asked for by callbacks */
607         m_targetRequested = format;
608 
609         wxCHECK_MSG( m_targetRequested, false, wxT("invalid clipboard format") );
610 
611         /* start query */
612         m_formatSupported = false;
613 
614         /* ask for clipboard contents.  this will set
615            m_formatSupported to true if m_targetRequested
616            is supported.
617            also, we have to wait for the "answer" from the
618            clipboard owner which is an asynchronous process.
619            therefore we set m_waiting = true here and wait
620            until the callback "targets_selection_received"
621            sets it to false */
622 
623         m_waiting = true;
624 
625         wxLogTrace( TRACE_CLIPBOARD,
626                     wxT("wxClipboard::GetData: format found, start convert") );
627 
628         gtk_selection_convert( m_clipboardWidget,
629                                m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
630                                             : g_clipboardAtom,
631                                m_targetRequested,
632                                (guint32) GDK_CURRENT_TIME );
633 
634         while (m_waiting) gtk_main_iteration();
635 
636         /*
637            Normally this is a true error as we checked for the presence of such
638            data before, but there are applications that may return an empty
639            string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
640            which would produce a false error message here, so we check for the
641            size of the string first. In ansi, GetDataSize returns an extra
642            value (for the closing null?), with unicode, the exact number of
643            tokens is given (that is more than 1 for special characters)
644            (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
645          */
646 #if wxUSE_UNICODE
647         if ( format != wxDF_UNICODETEXT || data.GetDataSize(format) > 0 )
648 #else // !UNICODE
649         if ( format != wxDF_TEXT || data.GetDataSize(format) > 1 )
650 #endif // UNICODE / !UNICODE
651         {
652             wxCHECK_MSG( m_formatSupported, false,
653                          wxT("error retrieving data from clipboard") );
654         }
655 
656         /* return success */
657         delete[] array;
658         return true;
659     }
660 
661     wxLogTrace( TRACE_CLIPBOARD,
662                 wxT("wxClipboard::GetData: format not found") );
663 
664     /* return failure */
665     delete[] array;
666     return false;
667 }
668 
669 #endif
670   // wxUSE_CLIPBOARD
671