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