Fix crash when accessing clipboard before entering the main loop.
[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/app.h"
28 #include "wx/log.h"
29 #include "wx/utils.h"
30 #include "wx/dataobj.h"
31 #endif
32
33 #include "wx/scopedarray.h"
34 #include "wx/scopeguard.h"
35 #include "wx/evtloop.h"
36
37 #include "wx/gtk/private.h"
38
39 typedef wxScopedArray<wxDataFormat> wxDataFormatArray;
40
41 // ----------------------------------------------------------------------------
42 // data
43 // ----------------------------------------------------------------------------
44
45 static GdkAtom g_clipboardAtom = 0;
46 static GdkAtom g_targetsAtom = 0;
47 static GdkAtom g_timestampAtom = 0;
48
49 #if wxUSE_UNICODE
50 extern GdkAtom g_altTextAtom;
51 #endif
52
53 // the trace mask we use with wxLogTrace() - call
54 // wxLog::AddTraceMask(TRACE_CLIPBOARD) to enable the trace messages from here
55 // (there will be a *lot* of them!)
56 #define TRACE_CLIPBOARD wxT("clipboard")
57
58 // ----------------------------------------------------------------------------
59 // wxClipboardSync: used to perform clipboard operations synchronously
60 // ----------------------------------------------------------------------------
61
62 // constructing this object on stack will wait wait until the latest clipboard
63 // operation is finished on block exit
64 //
65 // notice that there can be no more than one such object alive at any moment,
66 // i.e. reentrancies are not allowed
67 class wxClipboardSync
68 {
69 public:
70 wxClipboardSync(wxClipboard& clipboard)
71 {
72 wxASSERT_MSG( !ms_clipboard, wxT("reentrancy in clipboard code") );
73 ms_clipboard = &clipboard;
74 }
75
76 ~wxClipboardSync()
77 {
78 // ensure that there is a running event loop: this might not be the
79 // case if we're called before the main event loop startup
80 wxEventLoopGuarantor ensureEventLoop;
81
82 while (ms_clipboard)
83 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_CLIPBOARD);
84 }
85
86 // this method must be called by GTK+ callbacks to indicate that we got the
87 // result for our clipboard operation
88 static void OnDone(wxClipboard * WXUNUSED_UNLESS_DEBUG(clipboard))
89 {
90 wxASSERT_MSG( clipboard == ms_clipboard,
91 wxT("got notification for alien clipboard") );
92
93 ms_clipboard = NULL;
94 }
95
96 // this method should be called if it's possible that no async clipboard
97 // operation is currently in progress (like it can be the case when the
98 // clipboard is cleared but not because we asked about it), it should only
99 // be called if such situation is expected -- otherwise call OnDone() which
100 // would assert in this case
101 static void OnDoneIfInProgress(wxClipboard *clipboard)
102 {
103 if ( ms_clipboard )
104 OnDone(clipboard);
105 }
106
107 private:
108 static wxClipboard *ms_clipboard;
109
110 wxDECLARE_NO_COPY_CLASS(wxClipboardSync);
111 };
112
113 wxClipboard *wxClipboardSync::ms_clipboard = NULL;
114
115 // ============================================================================
116 // clipboard callbacks implementation
117 // ============================================================================
118
119 //-----------------------------------------------------------------------------
120 // "selection_received" for targets
121 //-----------------------------------------------------------------------------
122
123 extern "C" {
124 static void
125 targets_selection_received( GtkWidget *WXUNUSED(widget),
126 GtkSelectionData *selection_data,
127 guint32 WXUNUSED(time),
128 wxClipboard *clipboard )
129 {
130 if ( !clipboard )
131 return;
132
133 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
134
135 if ( !selection_data || selection_data->length <= 0 )
136 return;
137
138 // make sure we got the data in the correct form
139 GdkAtom type = selection_data->type;
140 if ( type != GDK_SELECTION_TYPE_ATOM )
141 {
142 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
143 {
144 wxLogTrace( TRACE_CLIPBOARD,
145 wxT("got unsupported clipboard target") );
146
147 return;
148 }
149 }
150
151 // it's not really a format, of course, but we can reuse its GetId() method
152 // to format this atom as string
153 wxDataFormat clip(selection_data->selection);
154 wxLogTrace( TRACE_CLIPBOARD,
155 wxT("Received available formats for clipboard %s"),
156 clip.GetId().c_str() );
157
158 // the atoms we received, holding a list of targets (= formats)
159 const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
160 for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
161 {
162 const wxDataFormat format(atoms[i]);
163
164 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
165
166 if ( clipboard->GTKOnTargetReceived(format) )
167 return;
168 }
169 }
170 }
171
172 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat& format)
173 {
174 if ( format != m_targetRequested )
175 return false;
176
177 m_formatSupported = true;
178 return true;
179 }
180
181 //-----------------------------------------------------------------------------
182 // "selection_received" for the actual data
183 //-----------------------------------------------------------------------------
184
185 extern "C" {
186 static void
187 selection_received( GtkWidget *WXUNUSED(widget),
188 GtkSelectionData *selection_data,
189 guint32 WXUNUSED(time),
190 wxClipboard *clipboard )
191 {
192 if ( !clipboard )
193 return;
194
195 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
196
197 if ( !selection_data || selection_data->length <= 0 )
198 return;
199
200 clipboard->GTKOnSelectionReceived(*selection_data);
201 }
202 }
203
204 //-----------------------------------------------------------------------------
205 // "selection_clear"
206 //-----------------------------------------------------------------------------
207
208 extern "C" {
209 static gint
210 selection_clear_clip( GtkWidget *WXUNUSED(widget), GdkEventSelection *event )
211 {
212 wxClipboard * const clipboard = wxTheClipboard;
213 if ( !clipboard )
214 return TRUE;
215
216 // notice the use of OnDoneIfInProgress() here instead of just OnDone():
217 // it's perfectly possible that we're receiving this notification from GTK+
218 // even though we hadn't cleared the clipboard ourselves but because
219 // another application (or even another window in the same program)
220 // acquired it
221 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress, clipboard);
222
223 wxClipboard::Kind kind;
224 if (event->selection == GDK_SELECTION_PRIMARY)
225 {
226 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost primary selection" ));
227
228 kind = wxClipboard::Primary;
229 }
230 else if (event->selection == g_clipboardAtom)
231 {
232 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost clipboard" ));
233
234 kind = wxClipboard::Clipboard;
235 }
236 else // some other selection, we're not concerned
237 {
238 return FALSE;
239 }
240
241 // the clipboard is no longer in our hands, we don't need data any more
242 clipboard->GTKClearData(kind);
243
244 return TRUE;
245 }
246 }
247
248 //-----------------------------------------------------------------------------
249 // selection handler for supplying data
250 //-----------------------------------------------------------------------------
251
252 extern "C" {
253 static void
254 selection_handler( GtkWidget *WXUNUSED(widget),
255 GtkSelectionData *selection_data,
256 guint WXUNUSED(info),
257 guint WXUNUSED(time),
258 gpointer signal_data )
259 {
260 wxClipboard * const clipboard = wxTheClipboard;
261 if ( !clipboard )
262 return;
263
264 wxDataObject * const data = clipboard->GTKGetDataObject();
265 if ( !data )
266 return;
267
268 // ICCCM says that TIMESTAMP is a required atom.
269 // In particular, it satisfies Klipper, which polls
270 // TIMESTAMP to see if the clipboards content has changed.
271 // It shall return the time which was used to set the data.
272 if (selection_data->target == g_timestampAtom)
273 {
274 guint timestamp = GPOINTER_TO_UINT (signal_data);
275 gtk_selection_data_set(selection_data,
276 GDK_SELECTION_TYPE_INTEGER,
277 32,
278 (guchar*)&(timestamp),
279 sizeof(timestamp));
280 wxLogTrace(TRACE_CLIPBOARD,
281 wxT("Clipboard TIMESTAMP requested, returning timestamp=%u"),
282 timestamp);
283 return;
284 }
285
286 wxDataFormat format( selection_data->target );
287
288 wxLogTrace(TRACE_CLIPBOARD,
289 wxT("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
290 format.GetId().c_str(),
291 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->target))).c_str(),
292 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->type))).c_str(),
293 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->selection))).c_str(),
294 GPOINTER_TO_UINT( signal_data )
295 );
296
297 if ( !data->IsSupportedFormat( format ) )
298 return;
299
300 int size = data->GetDataSize( format );
301 if ( !size )
302 return;
303
304 wxCharBuffer buf(size - 1); // it adds 1 internally (for NUL)
305
306 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
307 if ( !data->GetDataHere(format, buf.data()) )
308 return;
309
310 // use UTF8_STRING format if requested in Unicode build but just plain
311 // STRING one in ANSI or if explicitly asked in Unicode
312 #if wxUSE_UNICODE
313 if (format == wxDataFormat(wxDF_UNICODETEXT))
314 {
315 gtk_selection_data_set_text(
316 selection_data,
317 (const gchar*)buf.data(),
318 size );
319 }
320 else
321 #endif // wxUSE_UNICODE
322 {
323 gtk_selection_data_set(
324 selection_data,
325 GDK_SELECTION_TYPE_STRING,
326 8*sizeof(gchar),
327 (const guchar*)buf.data(),
328 size );
329 }
330 }
331 }
332
333 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData& sel)
334 {
335 wxCHECK_RET( m_receivedData, wxT("should be inside GetData()") );
336
337 const wxDataFormat format(sel.target);
338 wxLogTrace(TRACE_CLIPBOARD, wxT("Received selection %s"),
339 format.GetId().c_str());
340
341 if ( !m_receivedData->IsSupportedFormat(format) )
342 return;
343
344 m_receivedData->SetData(format, sel.length, sel.data);
345 m_formatSupported = true;
346 }
347
348 //-----------------------------------------------------------------------------
349 // asynchronous "selection_received" for targets
350 //-----------------------------------------------------------------------------
351
352 extern "C" {
353 static void
354 async_targets_selection_received( GtkWidget *WXUNUSED(widget),
355 GtkSelectionData *selection_data,
356 guint32 WXUNUSED(time),
357 wxClipboard *clipboard )
358 {
359 if ( !clipboard ) // Assert?
360 return;
361
362 if (!clipboard->m_sink)
363 return;
364
365 wxClipboardEvent *event = new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED);
366 event->SetEventObject( clipboard );
367
368 if ( !selection_data || selection_data->length <= 0 )
369 {
370 clipboard->m_sink->QueueEvent( event );
371 clipboard->m_sink.Release();
372 return;
373 }
374
375 // make sure we got the data in the correct form
376 GdkAtom type = selection_data->type;
377 if ( type != GDK_SELECTION_TYPE_ATOM )
378 {
379 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
380 {
381 wxLogTrace( TRACE_CLIPBOARD,
382 wxT("got unsupported clipboard target") );
383
384 clipboard->m_sink->QueueEvent( event );
385 clipboard->m_sink.Release();
386 return;
387 }
388 }
389
390 // it's not really a format, of course, but we can reuse its GetId() method
391 // to format this atom as string
392 wxDataFormat clip(selection_data->selection);
393 wxLogTrace( TRACE_CLIPBOARD,
394 wxT("Received available formats for clipboard %s"),
395 clip.GetId().c_str() );
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, wxT("Failed to %sset selection owner"),
507 set ? wxT("") : wxT("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 gtk_selection_clear_targets( m_clipboardWidget, GTKGetClipboardAtom() );
572
573 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
574 m_clipboardWidget->window )
575 {
576 wxClipboardSync sync(*this);
577
578 // this will result in selection_clear_clip callback being called and
579 // it will free our data
580 SetSelectionOwner(false);
581 }
582
583 m_targetRequested = 0;
584 m_formatSupported = false;
585 }
586
587 bool wxClipboard::Open()
588 {
589 wxCHECK_MSG( !m_open, false, wxT("clipboard already open") );
590
591 m_open = true;
592
593 return true;
594 }
595
596 bool wxClipboard::SetData( wxDataObject *data )
597 {
598 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
599
600 wxCHECK_MSG( data, false, wxT("data is invalid") );
601
602 Clear();
603
604 return AddData( data );
605 }
606
607 bool wxClipboard::AddData( wxDataObject *data )
608 {
609 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
610
611 wxCHECK_MSG( data, false, wxT("data is invalid") );
612
613 // we can only store one wxDataObject so clear the old one
614 Clear();
615
616 Data() = data;
617
618 // get formats from wxDataObjects
619 const size_t count = data->GetFormatCount();
620 wxDataFormatArray formats(new wxDataFormat[count]);
621 data->GetAllFormats(formats.get());
622
623 // always provide TIMESTAMP as a target, see comments in selection_handler
624 // for explanation
625 AddSupportedTarget(g_timestampAtom);
626
627 for ( size_t i = 0; i < count; i++ )
628 {
629 const wxDataFormat format(formats[i]);
630
631 wxLogTrace(TRACE_CLIPBOARD, wxT("Adding support for %s"),
632 format.GetId().c_str());
633
634 AddSupportedTarget(format);
635 }
636
637 g_signal_connect (m_clipboardWidget, "selection_get",
638 G_CALLBACK (selection_handler),
639 GUINT_TO_POINTER (gtk_get_current_event_time()) );
640
641 // tell the world we offer clipboard data
642 return SetSelectionOwner();
643 }
644
645 void wxClipboard::Close()
646 {
647 wxCHECK_RET( m_open, wxT("clipboard not open") );
648
649 m_open = false;
650 }
651
652 bool wxClipboard::IsOpened() const
653 {
654 return m_open;
655 }
656
657 bool wxClipboard::IsSupported( const wxDataFormat& format )
658 {
659 if ( DoIsSupported(format) )
660 return true;
661
662 #if wxUSE_UNICODE
663 if ( format == wxDF_UNICODETEXT )
664 {
665 // also with plain STRING format
666 return DoIsSupported(g_altTextAtom);
667 }
668 #endif // wxUSE_UNICODE
669
670 return false;
671 }
672
673 bool wxClipboard::GetData( wxDataObject& data )
674 {
675 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
676
677 // get all supported formats from wxDataObjects
678 const size_t count = data.GetFormatCount();
679 wxDataFormatArray formats(new wxDataFormat[count]);
680 data.GetAllFormats(formats.get());
681
682 for ( size_t i = 0; i < count; i++ )
683 {
684 const wxDataFormat format(formats[i]);
685
686 // is this format supported by clipboard ?
687 if ( !DoIsSupported(format) )
688 continue;
689
690 wxLogTrace(TRACE_CLIPBOARD, wxT("Requesting format %s"),
691 format.GetId().c_str());
692
693 // these variables will be used by our GTKOnSelectionReceived()
694 m_receivedData = &data;
695 m_formatSupported = false;
696
697 {
698 wxClipboardSync sync(*this);
699
700 gtk_selection_convert(m_clipboardWidget,
701 GTKGetClipboardAtom(),
702 format,
703 (guint32) GDK_CURRENT_TIME );
704 } // wait until we get the results
705
706 /*
707 Normally this is a true error as we checked for the presence of such
708 data before, but there are applications that may return an empty
709 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
710 which would produce a false error message here, so we check for the
711 size of the string first. With ANSI, GetDataSize returns an extra
712 value (for the closing null?), with unicode, the exact number of
713 tokens is given (that is more than 1 for non-ASCII characters)
714 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
715 */
716 #if wxUSE_UNICODE
717 if ( format != wxDF_UNICODETEXT || data.GetDataSize(format) > 0 )
718 #else // !UNICODE
719 if ( format != wxDF_TEXT || data.GetDataSize(format) > 1 )
720 #endif // UNICODE / !UNICODE
721 {
722 wxCHECK_MSG( m_formatSupported, false,
723 wxT("error retrieving data from clipboard") );
724 }
725
726 return true;
727 }
728
729 wxLogTrace(TRACE_CLIPBOARD, wxT("GetData(): format not found"));
730
731 return false;
732 }
733
734 #endif // wxUSE_CLIPBOARD