check in the 'selective yield' patch (see ticket #10320):
[wxWidgets.git] / src / gtk / clipbrd.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/clipbrd.cpp
3 // Purpose: wxClipboard implementation for wxGTK
4 // Author: Robert Roebling, Vadim Zeitlin
5 // Id: $Id$
6 // Copyright: (c) 1998 Robert Roebling
7 // (c) 2007 Vadim Zeitlin
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #if wxUSE_CLIPBOARD
23
24 #include "wx/clipbrd.h"
25
26 #ifndef WX_PRECOMP
27 #include "wx/log.h"
28 #include "wx/utils.h"
29 #include "wx/dataobj.h"
30 #endif
31
32 #include "wx/scopedarray.h"
33 #include "wx/scopeguard.h"
34
35 #include "wx/gtk/private.h"
36
37 typedef wxScopedArray<wxDataFormat> wxDataFormatArray;
38
39 // ----------------------------------------------------------------------------
40 // data
41 // ----------------------------------------------------------------------------
42
43 static GdkAtom g_clipboardAtom = 0;
44 static GdkAtom g_targetsAtom = 0;
45 static GdkAtom g_timestampAtom = 0;
46
47 #if wxUSE_UNICODE
48 extern GdkAtom g_altTextAtom;
49 #endif
50
51 // the trace mask we use with wxLogTrace() - call
52 // wxLog::AddTraceMask(TRACE_CLIPBOARD) to enable the trace messages from here
53 // (there will be a *lot* of them!)
54 #define TRACE_CLIPBOARD _T("clipboard")
55
56 // ----------------------------------------------------------------------------
57 // wxClipboardSync: used to perform clipboard operations synchronously
58 // ----------------------------------------------------------------------------
59
60 // constructing this object on stack will wait wait until the latest clipboard
61 // operation is finished on block exit
62 //
63 // notice that there can be no more than one such object alive at any moment,
64 // i.e. reentrancies are not allowed
65 class wxClipboardSync
66 {
67 public:
68 wxClipboardSync(wxClipboard& clipboard)
69 {
70 wxASSERT_MSG( !ms_clipboard, _T("reentrancy in clipboard code") );
71 ms_clipboard = &clipboard;
72 }
73
74 ~wxClipboardSync()
75 {
76 while (ms_clipboard)
77 wxTheApp->YieldFor(wxEVT_CATEGORY_CLIPBOARD);
78 }
79
80 // this method must be called by GTK+ callbacks to indicate that we got the
81 // result for our clipboard operation
82 static void OnDone(wxClipboard * WXUNUSED_UNLESS_DEBUG(clipboard))
83 {
84 wxASSERT_MSG( clipboard == ms_clipboard,
85 _T("got notification for alien clipboard") );
86
87 ms_clipboard = NULL;
88 }
89
90 // this method should be called if it's possible that no async clipboard
91 // operation is currently in progress (like it can be the case when the
92 // clipboard is cleared but not because we asked about it), it should only
93 // be called if such situation is expected -- otherwise call OnDone() which
94 // would assert in this case
95 static void OnDoneIfInProgress(wxClipboard *clipboard)
96 {
97 if ( ms_clipboard )
98 OnDone(clipboard);
99 }
100
101 private:
102 static wxClipboard *ms_clipboard;
103
104 DECLARE_NO_COPY_CLASS(wxClipboardSync)
105 };
106
107 wxClipboard *wxClipboardSync::ms_clipboard = NULL;
108
109 // ============================================================================
110 // clipboard callbacks implementation
111 // ============================================================================
112
113 //-----------------------------------------------------------------------------
114 // "selection_received" for targets
115 //-----------------------------------------------------------------------------
116
117 extern "C" {
118 static void
119 targets_selection_received( GtkWidget *WXUNUSED(widget),
120 GtkSelectionData *selection_data,
121 guint32 WXUNUSED(time),
122 wxClipboard *clipboard )
123 {
124 if ( !clipboard )
125 return;
126
127 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
128
129 if ( !selection_data || selection_data->length <= 0 )
130 return;
131
132 // make sure we got the data in the correct form
133 GdkAtom type = selection_data->type;
134 if ( type != GDK_SELECTION_TYPE_ATOM )
135 {
136 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
137 {
138 wxLogTrace( TRACE_CLIPBOARD,
139 _T("got unsupported clipboard target") );
140
141 return;
142 }
143 }
144
145 #ifdef __WXDEBUG__
146 // it's not really a format, of course, but we can reuse its GetId() method
147 // to format this atom as string
148 wxDataFormat clip(selection_data->selection);
149 wxLogTrace( TRACE_CLIPBOARD,
150 wxT("Received available formats for clipboard %s"),
151 clip.GetId().c_str() );
152 #endif // __WXDEBUG__
153
154 // the atoms we received, holding a list of targets (= formats)
155 const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
156 for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
157 {
158 const wxDataFormat format(atoms[i]);
159
160 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
161
162 if ( clipboard->GTKOnTargetReceived(format) )
163 return;
164 }
165 }
166 }
167
168 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat& format)
169 {
170 if ( format != m_targetRequested )
171 return false;
172
173 m_formatSupported = true;
174 return true;
175 }
176
177 //-----------------------------------------------------------------------------
178 // "selection_received" for the actual data
179 //-----------------------------------------------------------------------------
180
181 extern "C" {
182 static void
183 selection_received( GtkWidget *WXUNUSED(widget),
184 GtkSelectionData *selection_data,
185 guint32 WXUNUSED(time),
186 wxClipboard *clipboard )
187 {
188 if ( !clipboard )
189 return;
190
191 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
192
193 if ( !selection_data || selection_data->length <= 0 )
194 return;
195
196 clipboard->GTKOnSelectionReceived(*selection_data);
197 }
198 }
199
200 //-----------------------------------------------------------------------------
201 // "selection_clear"
202 //-----------------------------------------------------------------------------
203
204 extern "C" {
205 static gint
206 selection_clear_clip( GtkWidget *WXUNUSED(widget), GdkEventSelection *event )
207 {
208 wxClipboard * const clipboard = wxTheClipboard;
209 if ( !clipboard )
210 return TRUE;
211
212 // notice the use of OnDoneIfInProgress() here instead of just OnDone():
213 // it's perfectly possible that we're receiving this notification from GTK+
214 // even though we hadn't cleared the clipboard ourselves but because
215 // another application (or even another window in the same program)
216 // acquired it
217 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress, clipboard);
218
219 wxClipboard::Kind kind;
220 if (event->selection == GDK_SELECTION_PRIMARY)
221 {
222 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost primary selection" ));
223
224 kind = wxClipboard::Primary;
225 }
226 else if (event->selection == g_clipboardAtom)
227 {
228 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost clipboard" ));
229
230 kind = wxClipboard::Clipboard;
231 }
232 else // some other selection, we're not concerned
233 {
234 return FALSE;
235 }
236
237 // the clipboard is no longer in our hands, we don't need data any more
238 clipboard->GTKClearData(kind);
239
240 return TRUE;
241 }
242 }
243
244 //-----------------------------------------------------------------------------
245 // selection handler for supplying data
246 //-----------------------------------------------------------------------------
247
248 extern "C" {
249 static void
250 selection_handler( GtkWidget *WXUNUSED(widget),
251 GtkSelectionData *selection_data,
252 guint WXUNUSED(info),
253 guint WXUNUSED(time),
254 gpointer signal_data )
255 {
256 wxClipboard * const clipboard = wxTheClipboard;
257 if ( !clipboard )
258 return;
259
260 wxDataObject * const data = clipboard->GTKGetDataObject();
261 if ( !data )
262 return;
263
264 // ICCCM says that TIMESTAMP is a required atom.
265 // In particular, it satisfies Klipper, which polls
266 // TIMESTAMP to see if the clipboards content has changed.
267 // It shall return the time which was used to set the data.
268 if (selection_data->target == g_timestampAtom)
269 {
270 guint timestamp = GPOINTER_TO_UINT (signal_data);
271 gtk_selection_data_set(selection_data,
272 GDK_SELECTION_TYPE_INTEGER,
273 32,
274 (guchar*)&(timestamp),
275 sizeof(timestamp));
276 wxLogTrace(TRACE_CLIPBOARD,
277 _T("Clipboard TIMESTAMP requested, returning timestamp=%u"),
278 timestamp);
279 return;
280 }
281
282 wxDataFormat format( selection_data->target );
283
284 #ifdef __WXDEBUG__
285 wxLogTrace(TRACE_CLIPBOARD,
286 _T("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
287 format.GetId().c_str(),
288 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->target))).c_str(),
289 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->type))).c_str(),
290 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->selection))).c_str(),
291 GPOINTER_TO_UINT( signal_data )
292 );
293 #endif // __WXDEBUG__
294
295 if ( !data->IsSupportedFormat( format ) )
296 return;
297
298 int size = data->GetDataSize( format );
299 if ( !size )
300 return;
301
302 wxCharBuffer buf(size - 1); // it adds 1 internally (for NUL)
303
304 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
305 if ( !data->GetDataHere(format, buf.data()) )
306 return;
307
308 // use UTF8_STRING format if requested in Unicode build but just plain
309 // STRING one in ANSI or if explicitly asked in Unicode
310 #if wxUSE_UNICODE
311 if (format == wxDataFormat(wxDF_UNICODETEXT))
312 {
313 gtk_selection_data_set_text(
314 selection_data,
315 (const gchar*)buf.data(),
316 size );
317 }
318 else
319 #endif // wxUSE_UNICODE
320 {
321 gtk_selection_data_set(
322 selection_data,
323 GDK_SELECTION_TYPE_STRING,
324 8*sizeof(gchar),
325 (const guchar*)buf.data(),
326 size );
327 }
328 }
329 }
330
331 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData& sel)
332 {
333 wxCHECK_RET( m_receivedData, _T("should be inside GetData()") );
334
335 const wxDataFormat format(sel.target);
336 wxLogTrace(TRACE_CLIPBOARD, _T("Received selection %s"),
337 format.GetId().c_str());
338
339 if ( !m_receivedData->IsSupportedFormat(format) )
340 return;
341
342 m_receivedData->SetData(format, sel.length, sel.data);
343 m_formatSupported = true;
344 }
345
346 //-----------------------------------------------------------------------------
347 // asynchronous "selection_received" for targets
348 //-----------------------------------------------------------------------------
349
350 extern "C" {
351 static void
352 async_targets_selection_received( GtkWidget *WXUNUSED(widget),
353 GtkSelectionData *selection_data,
354 guint32 WXUNUSED(time),
355 wxClipboard *clipboard )
356 {
357 if ( !clipboard ) // Assert?
358 return;
359
360 if (!clipboard->m_sink)
361 return;
362
363 wxClipboardEvent *event = new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED);
364 event->SetEventObject( clipboard );
365
366 if ( !selection_data || selection_data->length <= 0 )
367 {
368 clipboard->m_sink->QueueEvent( event );
369 clipboard->m_sink.Release();
370 return;
371 }
372
373 // make sure we got the data in the correct form
374 GdkAtom type = selection_data->type;
375 if ( type != GDK_SELECTION_TYPE_ATOM )
376 {
377 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
378 {
379 wxLogTrace( TRACE_CLIPBOARD,
380 _T("got unsupported clipboard target") );
381
382 clipboard->m_sink->QueueEvent( event );
383 clipboard->m_sink.Release();
384 return;
385 }
386 }
387
388 #ifdef __WXDEBUG__
389 // it's not really a format, of course, but we can reuse its GetId() method
390 // to format this atom as string
391 wxDataFormat clip(selection_data->selection);
392 wxLogTrace( TRACE_CLIPBOARD,
393 wxT("Received available formats for clipboard %s"),
394 clip.GetId().c_str() );
395 #endif // __WXDEBUG__
396
397 // the atoms we received, holding a list of targets (= formats)
398 const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
399 for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
400 {
401 const wxDataFormat format(atoms[i]);
402
403 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
404
405 event->AddFormat( format );
406 }
407
408 clipboard->m_sink->QueueEvent( event );
409 clipboard->m_sink.Release();
410 }
411 }
412
413 // ============================================================================
414 // wxClipboard implementation
415 // ============================================================================
416
417 // ----------------------------------------------------------------------------
418 // wxClipboard ctor/dtor
419 // ----------------------------------------------------------------------------
420
421 IMPLEMENT_DYNAMIC_CLASS(wxClipboard,wxObject)
422
423 wxClipboard::wxClipboard()
424 {
425 m_open = false;
426
427 m_dataPrimary =
428 m_dataClipboard =
429 m_receivedData = NULL;
430
431 m_formatSupported = false;
432 m_targetRequested = 0;
433
434 // we use m_targetsWidget to query what formats are available
435 m_targetsWidget = gtk_window_new( GTK_WINDOW_POPUP );
436 gtk_widget_realize( m_targetsWidget );
437
438 g_signal_connect (m_targetsWidget, "selection_received",
439 G_CALLBACK (targets_selection_received), this);
440
441 // we use m_targetsWidgetAsync to query what formats are available asynchronously
442 m_targetsWidgetAsync = gtk_window_new( GTK_WINDOW_POPUP );
443 gtk_widget_realize( m_targetsWidgetAsync );
444
445 g_signal_connect (m_targetsWidgetAsync, "selection_received",
446 G_CALLBACK (async_targets_selection_received), this);
447
448 // we use m_clipboardWidget to get and to offer data
449 m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
450 gtk_widget_realize( m_clipboardWidget );
451
452 g_signal_connect (m_clipboardWidget, "selection_received",
453 G_CALLBACK (selection_received), this);
454
455 g_signal_connect (m_clipboardWidget, "selection_clear_event",
456 G_CALLBACK (selection_clear_clip), NULL);
457
458 // initialize atoms we use if not done yet
459 if ( !g_clipboardAtom )
460 g_clipboardAtom = gdk_atom_intern( "CLIPBOARD", FALSE );
461 if ( !g_targetsAtom )
462 g_targetsAtom = gdk_atom_intern ("TARGETS", FALSE);
463 if ( !g_timestampAtom )
464 g_timestampAtom = gdk_atom_intern ("TIMESTAMP", FALSE);
465 }
466
467 wxClipboard::~wxClipboard()
468 {
469 Clear();
470
471 gtk_widget_destroy( m_clipboardWidget );
472 gtk_widget_destroy( m_targetsWidget );
473 }
474
475 // ----------------------------------------------------------------------------
476 // wxClipboard helper functions
477 // ----------------------------------------------------------------------------
478
479 GdkAtom wxClipboard::GTKGetClipboardAtom() const
480 {
481 return m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
482 : g_clipboardAtom;
483 }
484
485 void wxClipboard::GTKClearData(Kind kind)
486 {
487 wxDataObject *&data = Data(kind);
488 if ( data )
489 {
490 delete data;
491 data = NULL;
492 }
493 }
494
495 bool wxClipboard::SetSelectionOwner(bool set)
496 {
497 bool rc = gtk_selection_owner_set
498 (
499 set ? m_clipboardWidget : NULL,
500 GTKGetClipboardAtom(),
501 (guint32)GDK_CURRENT_TIME
502 );
503
504 if ( !rc )
505 {
506 wxLogTrace(TRACE_CLIPBOARD, _T("Failed to %sset selection owner"),
507 set ? _T("") : _T("un"));
508 }
509
510 return rc;
511 }
512
513 void wxClipboard::AddSupportedTarget(GdkAtom atom)
514 {
515 gtk_selection_add_target
516 (
517 m_clipboardWidget,
518 GTKGetClipboardAtom(),
519 atom,
520 0 // info (same as client data) unused
521 );
522 }
523
524 bool wxClipboard::IsSupportedAsync(wxEvtHandler *sink)
525 {
526 if (m_sink.get())
527 return false; // currently busy, come back later
528
529 wxCHECK_MSG( sink, false, wxT("no sink given") );
530
531 m_sink = sink;
532 gtk_selection_convert( m_targetsWidgetAsync,
533 GTKGetClipboardAtom(),
534 g_targetsAtom,
535 (guint32) GDK_CURRENT_TIME );
536
537 return true;
538 }
539
540 bool wxClipboard::DoIsSupported(const wxDataFormat& format)
541 {
542 wxCHECK_MSG( format, false, wxT("invalid clipboard format") );
543
544 wxLogTrace(TRACE_CLIPBOARD, wxT("Checking if format %s is available"),
545 format.GetId().c_str());
546
547 // these variables will be used by our GTKOnTargetReceived()
548 m_targetRequested = format;
549 m_formatSupported = false;
550
551 // block until m_formatSupported is set from targets_selection_received
552 // callback
553 {
554 wxClipboardSync sync(*this);
555
556 gtk_selection_convert( m_targetsWidget,
557 GTKGetClipboardAtom(),
558 g_targetsAtom,
559 (guint32) GDK_CURRENT_TIME );
560 }
561
562 return m_formatSupported;
563 }
564
565 // ----------------------------------------------------------------------------
566 // wxClipboard public API implementation
567 // ----------------------------------------------------------------------------
568
569 void wxClipboard::Clear()
570 {
571 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
572 m_clipboardWidget->window )
573 {
574 wxClipboardSync sync(*this);
575
576 // this will result in selection_clear_clip callback being called and
577 // it will free our data
578 SetSelectionOwner(false);
579 }
580
581 m_targetRequested = 0;
582 m_formatSupported = false;
583 }
584
585 bool wxClipboard::Open()
586 {
587 wxCHECK_MSG( !m_open, false, wxT("clipboard already open") );
588
589 m_open = true;
590
591 return true;
592 }
593
594 bool wxClipboard::SetData( wxDataObject *data )
595 {
596 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
597
598 wxCHECK_MSG( data, false, wxT("data is invalid") );
599
600 Clear();
601
602 return AddData( data );
603 }
604
605 bool wxClipboard::AddData( wxDataObject *data )
606 {
607 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
608
609 wxCHECK_MSG( data, false, wxT("data is invalid") );
610
611 // we can only store one wxDataObject so clear the old one
612 Clear();
613
614 Data() = data;
615
616 // get formats from wxDataObjects
617 const size_t count = data->GetFormatCount();
618 wxDataFormatArray formats(new wxDataFormat[count]);
619 data->GetAllFormats(formats.get());
620
621 // always provide TIMESTAMP as a target, see comments in selection_handler
622 // for explanation
623 AddSupportedTarget(g_timestampAtom);
624
625 for ( size_t i = 0; i < count; i++ )
626 {
627 const wxDataFormat format(formats[i]);
628
629 wxLogTrace(TRACE_CLIPBOARD, wxT("Adding support for %s"),
630 format.GetId().c_str());
631
632 AddSupportedTarget(format);
633 }
634
635 g_signal_connect (m_clipboardWidget, "selection_get",
636 G_CALLBACK (selection_handler),
637 GUINT_TO_POINTER (gtk_get_current_event_time()) );
638
639 // tell the world we offer clipboard data
640 return SetSelectionOwner();
641 }
642
643 void wxClipboard::Close()
644 {
645 wxCHECK_RET( m_open, wxT("clipboard not open") );
646
647 m_open = false;
648 }
649
650 bool wxClipboard::IsOpened() const
651 {
652 return m_open;
653 }
654
655 bool wxClipboard::IsSupported( const wxDataFormat& format )
656 {
657 if ( DoIsSupported(format) )
658 return true;
659
660 #if wxUSE_UNICODE
661 if ( format == wxDF_UNICODETEXT )
662 {
663 // also with plain STRING format
664 return DoIsSupported(g_altTextAtom);
665 }
666 #endif // wxUSE_UNICODE
667
668 return false;
669 }
670
671 bool wxClipboard::GetData( wxDataObject& data )
672 {
673 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
674
675 // get all supported formats from wxDataObjects
676 const size_t count = data.GetFormatCount();
677 wxDataFormatArray formats(new wxDataFormat[count]);
678 data.GetAllFormats(formats.get());
679
680 for ( size_t i = 0; i < count; i++ )
681 {
682 const wxDataFormat format(formats[i]);
683
684 // is this format supported by clipboard ?
685 if ( !DoIsSupported(format) )
686 continue;
687
688 wxLogTrace(TRACE_CLIPBOARD, wxT("Requesting format %s"),
689 format.GetId().c_str());
690
691 // these variables will be used by our GTKOnSelectionReceived()
692 m_receivedData = &data;
693 m_formatSupported = false;
694
695 {
696 wxClipboardSync sync(*this);
697
698 gtk_selection_convert(m_clipboardWidget,
699 GTKGetClipboardAtom(),
700 format,
701 (guint32) GDK_CURRENT_TIME );
702 } // wait until we get the results
703
704 /*
705 Normally this is a true error as we checked for the presence of such
706 data before, but there are applications that may return an empty
707 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
708 which would produce a false error message here, so we check for the
709 size of the string first. With ANSI, GetDataSize returns an extra
710 value (for the closing null?), with unicode, the exact number of
711 tokens is given (that is more than 1 for non-ASCII characters)
712 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
713 */
714 #if wxUSE_UNICODE
715 if ( format != wxDF_UNICODETEXT || data.GetDataSize(format) > 0 )
716 #else // !UNICODE
717 if ( format != wxDF_TEXT || data.GetDataSize(format) > 1 )
718 #endif // UNICODE / !UNICODE
719 {
720 wxCHECK_MSG( m_formatSupported, false,
721 wxT("error retrieving data from clipboard") );
722 }
723
724 return true;
725 }
726
727 wxLogTrace(TRACE_CLIPBOARD, wxT("GetData(): format not found"));
728
729 return false;
730 }
731
732 #endif // wxUSE_CLIPBOARD