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