second part of #10320: move wxApp event handling functions to wxEventLoopBase (in...
[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 _T("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, _T("reentrancy in clipboard code") );
73 ms_clipboard = &clipboard;
74 }
75
76 ~wxClipboardSync()
77 {
78 while (ms_clipboard)
79 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_CLIPBOARD);
80 }
81
82 // this method must be called by GTK+ callbacks to indicate that we got the
83 // result for our clipboard operation
84 static void OnDone(wxClipboard * WXUNUSED_UNLESS_DEBUG(clipboard))
85 {
86 wxASSERT_MSG( clipboard == ms_clipboard,
87 _T("got notification for alien clipboard") );
88
89 ms_clipboard = NULL;
90 }
91
92 // this method should be called if it's possible that no async clipboard
93 // operation is currently in progress (like it can be the case when the
94 // clipboard is cleared but not because we asked about it), it should only
95 // be called if such situation is expected -- otherwise call OnDone() which
96 // would assert in this case
97 static void OnDoneIfInProgress(wxClipboard *clipboard)
98 {
99 if ( ms_clipboard )
100 OnDone(clipboard);
101 }
102
103 private:
104 static wxClipboard *ms_clipboard;
105
106 wxDECLARE_NO_COPY_CLASS(wxClipboardSync);
107 };
108
109 wxClipboard *wxClipboardSync::ms_clipboard = NULL;
110
111 // ============================================================================
112 // clipboard callbacks implementation
113 // ============================================================================
114
115 //-----------------------------------------------------------------------------
116 // "selection_received" for targets
117 //-----------------------------------------------------------------------------
118
119 extern "C" {
120 static void
121 targets_selection_received( GtkWidget *WXUNUSED(widget),
122 GtkSelectionData *selection_data,
123 guint32 WXUNUSED(time),
124 wxClipboard *clipboard )
125 {
126 if ( !clipboard )
127 return;
128
129 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
130
131 if ( !selection_data || selection_data->length <= 0 )
132 return;
133
134 // make sure we got the data in the correct form
135 GdkAtom type = selection_data->type;
136 if ( type != GDK_SELECTION_TYPE_ATOM )
137 {
138 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
139 {
140 wxLogTrace( TRACE_CLIPBOARD,
141 _T("got unsupported clipboard target") );
142
143 return;
144 }
145 }
146
147 #ifdef __WXDEBUG__
148 // it's not really a format, of course, but we can reuse its GetId() method
149 // to format this atom as string
150 wxDataFormat clip(selection_data->selection);
151 wxLogTrace( TRACE_CLIPBOARD,
152 wxT("Received available formats for clipboard %s"),
153 clip.GetId().c_str() );
154 #endif // __WXDEBUG__
155
156 // the atoms we received, holding a list of targets (= formats)
157 const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
158 for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
159 {
160 const wxDataFormat format(atoms[i]);
161
162 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
163
164 if ( clipboard->GTKOnTargetReceived(format) )
165 return;
166 }
167 }
168 }
169
170 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat& format)
171 {
172 if ( format != m_targetRequested )
173 return false;
174
175 m_formatSupported = true;
176 return true;
177 }
178
179 //-----------------------------------------------------------------------------
180 // "selection_received" for the actual data
181 //-----------------------------------------------------------------------------
182
183 extern "C" {
184 static void
185 selection_received( GtkWidget *WXUNUSED(widget),
186 GtkSelectionData *selection_data,
187 guint32 WXUNUSED(time),
188 wxClipboard *clipboard )
189 {
190 if ( !clipboard )
191 return;
192
193 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
194
195 if ( !selection_data || selection_data->length <= 0 )
196 return;
197
198 clipboard->GTKOnSelectionReceived(*selection_data);
199 }
200 }
201
202 //-----------------------------------------------------------------------------
203 // "selection_clear"
204 //-----------------------------------------------------------------------------
205
206 extern "C" {
207 static gint
208 selection_clear_clip( GtkWidget *WXUNUSED(widget), GdkEventSelection *event )
209 {
210 wxClipboard * const clipboard = wxTheClipboard;
211 if ( !clipboard )
212 return TRUE;
213
214 // notice the use of OnDoneIfInProgress() here instead of just OnDone():
215 // it's perfectly possible that we're receiving this notification from GTK+
216 // even though we hadn't cleared the clipboard ourselves but because
217 // another application (or even another window in the same program)
218 // acquired it
219 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress, clipboard);
220
221 wxClipboard::Kind kind;
222 if (event->selection == GDK_SELECTION_PRIMARY)
223 {
224 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost primary selection" ));
225
226 kind = wxClipboard::Primary;
227 }
228 else if (event->selection == g_clipboardAtom)
229 {
230 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost clipboard" ));
231
232 kind = wxClipboard::Clipboard;
233 }
234 else // some other selection, we're not concerned
235 {
236 return FALSE;
237 }
238
239 // the clipboard is no longer in our hands, we don't need data any more
240 clipboard->GTKClearData(kind);
241
242 return TRUE;
243 }
244 }
245
246 //-----------------------------------------------------------------------------
247 // selection handler for supplying data
248 //-----------------------------------------------------------------------------
249
250 extern "C" {
251 static void
252 selection_handler( GtkWidget *WXUNUSED(widget),
253 GtkSelectionData *selection_data,
254 guint WXUNUSED(info),
255 guint WXUNUSED(time),
256 gpointer signal_data )
257 {
258 wxClipboard * const clipboard = wxTheClipboard;
259 if ( !clipboard )
260 return;
261
262 wxDataObject * const data = clipboard->GTKGetDataObject();
263 if ( !data )
264 return;
265
266 // ICCCM says that TIMESTAMP is a required atom.
267 // In particular, it satisfies Klipper, which polls
268 // TIMESTAMP to see if the clipboards content has changed.
269 // It shall return the time which was used to set the data.
270 if (selection_data->target == g_timestampAtom)
271 {
272 guint timestamp = GPOINTER_TO_UINT (signal_data);
273 gtk_selection_data_set(selection_data,
274 GDK_SELECTION_TYPE_INTEGER,
275 32,
276 (guchar*)&(timestamp),
277 sizeof(timestamp));
278 wxLogTrace(TRACE_CLIPBOARD,
279 _T("Clipboard TIMESTAMP requested, returning timestamp=%u"),
280 timestamp);
281 return;
282 }
283
284 wxDataFormat format( selection_data->target );
285
286 #ifdef __WXDEBUG__
287 wxLogTrace(TRACE_CLIPBOARD,
288 _T("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
289 format.GetId().c_str(),
290 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->target))).c_str(),
291 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->type))).c_str(),
292 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->selection))).c_str(),
293 GPOINTER_TO_UINT( signal_data )
294 );
295 #endif // __WXDEBUG__
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, _T("should be inside GetData()") );
336
337 const wxDataFormat format(sel.target);
338 wxLogTrace(TRACE_CLIPBOARD, _T("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 _T("got unsupported clipboard target") );
383
384 clipboard->m_sink->QueueEvent( event );
385 clipboard->m_sink.Release();
386 return;
387 }
388 }
389
390 #ifdef __WXDEBUG__
391 // it's not really a format, of course, but we can reuse its GetId() method
392 // to format this atom as string
393 wxDataFormat clip(selection_data->selection);
394 wxLogTrace( TRACE_CLIPBOARD,
395 wxT("Received available formats for clipboard %s"),
396 clip.GetId().c_str() );
397 #endif // __WXDEBUG__
398
399 // the atoms we received, holding a list of targets (= formats)
400 const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
401 for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
402 {
403 const wxDataFormat format(atoms[i]);
404
405 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
406
407 event->AddFormat( format );
408 }
409
410 clipboard->m_sink->QueueEvent( event );
411 clipboard->m_sink.Release();
412 }
413 }
414
415 // ============================================================================
416 // wxClipboard implementation
417 // ============================================================================
418
419 // ----------------------------------------------------------------------------
420 // wxClipboard ctor/dtor
421 // ----------------------------------------------------------------------------
422
423 IMPLEMENT_DYNAMIC_CLASS(wxClipboard,wxObject)
424
425 wxClipboard::wxClipboard()
426 {
427 m_open = false;
428
429 m_dataPrimary =
430 m_dataClipboard =
431 m_receivedData = NULL;
432
433 m_formatSupported = false;
434 m_targetRequested = 0;
435
436 // we use m_targetsWidget to query what formats are available
437 m_targetsWidget = gtk_window_new( GTK_WINDOW_POPUP );
438 gtk_widget_realize( m_targetsWidget );
439
440 g_signal_connect (m_targetsWidget, "selection_received",
441 G_CALLBACK (targets_selection_received), this);
442
443 // we use m_targetsWidgetAsync to query what formats are available asynchronously
444 m_targetsWidgetAsync = gtk_window_new( GTK_WINDOW_POPUP );
445 gtk_widget_realize( m_targetsWidgetAsync );
446
447 g_signal_connect (m_targetsWidgetAsync, "selection_received",
448 G_CALLBACK (async_targets_selection_received), this);
449
450 // we use m_clipboardWidget to get and to offer data
451 m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
452 gtk_widget_realize( m_clipboardWidget );
453
454 g_signal_connect (m_clipboardWidget, "selection_received",
455 G_CALLBACK (selection_received), this);
456
457 g_signal_connect (m_clipboardWidget, "selection_clear_event",
458 G_CALLBACK (selection_clear_clip), NULL);
459
460 // initialize atoms we use if not done yet
461 if ( !g_clipboardAtom )
462 g_clipboardAtom = gdk_atom_intern( "CLIPBOARD", FALSE );
463 if ( !g_targetsAtom )
464 g_targetsAtom = gdk_atom_intern ("TARGETS", FALSE);
465 if ( !g_timestampAtom )
466 g_timestampAtom = gdk_atom_intern ("TIMESTAMP", FALSE);
467 }
468
469 wxClipboard::~wxClipboard()
470 {
471 Clear();
472
473 gtk_widget_destroy( m_clipboardWidget );
474 gtk_widget_destroy( m_targetsWidget );
475 }
476
477 // ----------------------------------------------------------------------------
478 // wxClipboard helper functions
479 // ----------------------------------------------------------------------------
480
481 GdkAtom wxClipboard::GTKGetClipboardAtom() const
482 {
483 return m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
484 : g_clipboardAtom;
485 }
486
487 void wxClipboard::GTKClearData(Kind kind)
488 {
489 wxDataObject *&data = Data(kind);
490 if ( data )
491 {
492 delete data;
493 data = NULL;
494 }
495 }
496
497 bool wxClipboard::SetSelectionOwner(bool set)
498 {
499 bool rc = gtk_selection_owner_set
500 (
501 set ? m_clipboardWidget : NULL,
502 GTKGetClipboardAtom(),
503 (guint32)GDK_CURRENT_TIME
504 );
505
506 if ( !rc )
507 {
508 wxLogTrace(TRACE_CLIPBOARD, _T("Failed to %sset selection owner"),
509 set ? _T("") : _T("un"));
510 }
511
512 return rc;
513 }
514
515 void wxClipboard::AddSupportedTarget(GdkAtom atom)
516 {
517 gtk_selection_add_target
518 (
519 m_clipboardWidget,
520 GTKGetClipboardAtom(),
521 atom,
522 0 // info (same as client data) unused
523 );
524 }
525
526 bool wxClipboard::IsSupportedAsync(wxEvtHandler *sink)
527 {
528 if (m_sink.get())
529 return false; // currently busy, come back later
530
531 wxCHECK_MSG( sink, false, wxT("no sink given") );
532
533 m_sink = sink;
534 gtk_selection_convert( m_targetsWidgetAsync,
535 GTKGetClipboardAtom(),
536 g_targetsAtom,
537 (guint32) GDK_CURRENT_TIME );
538
539 return true;
540 }
541
542 bool wxClipboard::DoIsSupported(const wxDataFormat& format)
543 {
544 wxCHECK_MSG( format, false, wxT("invalid clipboard format") );
545
546 wxLogTrace(TRACE_CLIPBOARD, wxT("Checking if format %s is available"),
547 format.GetId().c_str());
548
549 // these variables will be used by our GTKOnTargetReceived()
550 m_targetRequested = format;
551 m_formatSupported = false;
552
553 // block until m_formatSupported is set from targets_selection_received
554 // callback
555 {
556 wxClipboardSync sync(*this);
557
558 gtk_selection_convert( m_targetsWidget,
559 GTKGetClipboardAtom(),
560 g_targetsAtom,
561 (guint32) GDK_CURRENT_TIME );
562 }
563
564 return m_formatSupported;
565 }
566
567 // ----------------------------------------------------------------------------
568 // wxClipboard public API implementation
569 // ----------------------------------------------------------------------------
570
571 void wxClipboard::Clear()
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