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