Clear targets in wxClipboard::Clear().
[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 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 wxT("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 wxT("got unsupported clipboard target") );
142
143 return;
144 }
145 }
146
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
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 wxT("Clipboard TIMESTAMP requested, returning timestamp=%u"),
278 timestamp);
279 return;
280 }
281
282 wxDataFormat format( selection_data->target );
283
284 wxLogTrace(TRACE_CLIPBOARD,
285 wxT("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
286 format.GetId().c_str(),
287 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->target))).c_str(),
288 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->type))).c_str(),
289 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->selection))).c_str(),
290 GPOINTER_TO_UINT( signal_data )
291 );
292
293 if ( !data->IsSupportedFormat( format ) )
294 return;
295
296 int size = data->GetDataSize( format );
297 if ( !size )
298 return;
299
300 wxCharBuffer buf(size - 1); // it adds 1 internally (for NUL)
301
302 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
303 if ( !data->GetDataHere(format, buf.data()) )
304 return;
305
306 // use UTF8_STRING format if requested in Unicode build but just plain
307 // STRING one in ANSI or if explicitly asked in Unicode
308 #if wxUSE_UNICODE
309 if (format == wxDataFormat(wxDF_UNICODETEXT))
310 {
311 gtk_selection_data_set_text(
312 selection_data,
313 (const gchar*)buf.data(),
314 size );
315 }
316 else
317 #endif // wxUSE_UNICODE
318 {
319 gtk_selection_data_set(
320 selection_data,
321 GDK_SELECTION_TYPE_STRING,
322 8*sizeof(gchar),
323 (const guchar*)buf.data(),
324 size );
325 }
326 }
327 }
328
329 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData& sel)
330 {
331 wxCHECK_RET( m_receivedData, wxT("should be inside GetData()") );
332
333 const wxDataFormat format(sel.target);
334 wxLogTrace(TRACE_CLIPBOARD, wxT("Received selection %s"),
335 format.GetId().c_str());
336
337 if ( !m_receivedData->IsSupportedFormat(format) )
338 return;
339
340 m_receivedData->SetData(format, sel.length, sel.data);
341 m_formatSupported = true;
342 }
343
344 //-----------------------------------------------------------------------------
345 // asynchronous "selection_received" for targets
346 //-----------------------------------------------------------------------------
347
348 extern "C" {
349 static void
350 async_targets_selection_received( GtkWidget *WXUNUSED(widget),
351 GtkSelectionData *selection_data,
352 guint32 WXUNUSED(time),
353 wxClipboard *clipboard )
354 {
355 if ( !clipboard ) // Assert?
356 return;
357
358 if (!clipboard->m_sink)
359 return;
360
361 wxClipboardEvent *event = new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED);
362 event->SetEventObject( clipboard );
363
364 if ( !selection_data || selection_data->length <= 0 )
365 {
366 clipboard->m_sink->QueueEvent( event );
367 clipboard->m_sink.Release();
368 return;
369 }
370
371 // make sure we got the data in the correct form
372 GdkAtom type = selection_data->type;
373 if ( type != GDK_SELECTION_TYPE_ATOM )
374 {
375 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
376 {
377 wxLogTrace( TRACE_CLIPBOARD,
378 wxT("got unsupported clipboard target") );
379
380 clipboard->m_sink->QueueEvent( event );
381 clipboard->m_sink.Release();
382 return;
383 }
384 }
385
386 // it's not really a format, of course, but we can reuse its GetId() method
387 // to format this atom as string
388 wxDataFormat clip(selection_data->selection);
389 wxLogTrace( TRACE_CLIPBOARD,
390 wxT("Received available formats for clipboard %s"),
391 clip.GetId().c_str() );
392
393 // the atoms we received, holding a list of targets (= formats)
394 const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
395 for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
396 {
397 const wxDataFormat format(atoms[i]);
398
399 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
400
401 event->AddFormat( format );
402 }
403
404 clipboard->m_sink->QueueEvent( event );
405 clipboard->m_sink.Release();
406 }
407 }
408
409 // ============================================================================
410 // wxClipboard implementation
411 // ============================================================================
412
413 // ----------------------------------------------------------------------------
414 // wxClipboard ctor/dtor
415 // ----------------------------------------------------------------------------
416
417 IMPLEMENT_DYNAMIC_CLASS(wxClipboard,wxObject)
418
419 wxClipboard::wxClipboard()
420 {
421 m_open = false;
422
423 m_dataPrimary =
424 m_dataClipboard =
425 m_receivedData = NULL;
426
427 m_formatSupported = false;
428 m_targetRequested = 0;
429
430 // we use m_targetsWidget to query what formats are available
431 m_targetsWidget = gtk_window_new( GTK_WINDOW_POPUP );
432 gtk_widget_realize( m_targetsWidget );
433
434 g_signal_connect (m_targetsWidget, "selection_received",
435 G_CALLBACK (targets_selection_received), this);
436
437 // we use m_targetsWidgetAsync to query what formats are available asynchronously
438 m_targetsWidgetAsync = gtk_window_new( GTK_WINDOW_POPUP );
439 gtk_widget_realize( m_targetsWidgetAsync );
440
441 g_signal_connect (m_targetsWidgetAsync, "selection_received",
442 G_CALLBACK (async_targets_selection_received), this);
443
444 // we use m_clipboardWidget to get and to offer data
445 m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
446 gtk_widget_realize( m_clipboardWidget );
447
448 g_signal_connect (m_clipboardWidget, "selection_received",
449 G_CALLBACK (selection_received), this);
450
451 g_signal_connect (m_clipboardWidget, "selection_clear_event",
452 G_CALLBACK (selection_clear_clip), NULL);
453
454 // initialize atoms we use if not done yet
455 if ( !g_clipboardAtom )
456 g_clipboardAtom = gdk_atom_intern( "CLIPBOARD", FALSE );
457 if ( !g_targetsAtom )
458 g_targetsAtom = gdk_atom_intern ("TARGETS", FALSE);
459 if ( !g_timestampAtom )
460 g_timestampAtom = gdk_atom_intern ("TIMESTAMP", FALSE);
461 }
462
463 wxClipboard::~wxClipboard()
464 {
465 Clear();
466
467 gtk_widget_destroy( m_clipboardWidget );
468 gtk_widget_destroy( m_targetsWidget );
469 }
470
471 // ----------------------------------------------------------------------------
472 // wxClipboard helper functions
473 // ----------------------------------------------------------------------------
474
475 GdkAtom wxClipboard::GTKGetClipboardAtom() const
476 {
477 return m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
478 : g_clipboardAtom;
479 }
480
481 void wxClipboard::GTKClearData(Kind kind)
482 {
483 wxDataObject *&data = Data(kind);
484 if ( data )
485 {
486 delete data;
487 data = NULL;
488 }
489 }
490
491 bool wxClipboard::SetSelectionOwner(bool set)
492 {
493 bool rc = gtk_selection_owner_set
494 (
495 set ? m_clipboardWidget : NULL,
496 GTKGetClipboardAtom(),
497 (guint32)GDK_CURRENT_TIME
498 );
499
500 if ( !rc )
501 {
502 wxLogTrace(TRACE_CLIPBOARD, wxT("Failed to %sset selection owner"),
503 set ? wxT("") : wxT("un"));
504 }
505
506 return rc;
507 }
508
509 void wxClipboard::AddSupportedTarget(GdkAtom atom)
510 {
511 gtk_selection_add_target
512 (
513 m_clipboardWidget,
514 GTKGetClipboardAtom(),
515 atom,
516 0 // info (same as client data) unused
517 );
518 }
519
520 bool wxClipboard::IsSupportedAsync(wxEvtHandler *sink)
521 {
522 if (m_sink.get())
523 return false; // currently busy, come back later
524
525 wxCHECK_MSG( sink, false, wxT("no sink given") );
526
527 m_sink = sink;
528 gtk_selection_convert( m_targetsWidgetAsync,
529 GTKGetClipboardAtom(),
530 g_targetsAtom,
531 (guint32) GDK_CURRENT_TIME );
532
533 return true;
534 }
535
536 bool wxClipboard::DoIsSupported(const wxDataFormat& format)
537 {
538 wxCHECK_MSG( format, false, wxT("invalid clipboard format") );
539
540 wxLogTrace(TRACE_CLIPBOARD, wxT("Checking if format %s is available"),
541 format.GetId().c_str());
542
543 // these variables will be used by our GTKOnTargetReceived()
544 m_targetRequested = format;
545 m_formatSupported = false;
546
547 // block until m_formatSupported is set from targets_selection_received
548 // callback
549 {
550 wxClipboardSync sync(*this);
551
552 gtk_selection_convert( m_targetsWidget,
553 GTKGetClipboardAtom(),
554 g_targetsAtom,
555 (guint32) GDK_CURRENT_TIME );
556 }
557
558 return m_formatSupported;
559 }
560
561 // ----------------------------------------------------------------------------
562 // wxClipboard public API implementation
563 // ----------------------------------------------------------------------------
564
565 void wxClipboard::Clear()
566 {
567 gtk_selection_clear_targets( m_clipboardWidget, GTKGetClipboardAtom() );
568
569 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
570 m_clipboardWidget->window )
571 {
572 wxClipboardSync sync(*this);
573
574 // this will result in selection_clear_clip callback being called and
575 // it will free our data
576 SetSelectionOwner(false);
577 }
578
579 m_targetRequested = 0;
580 m_formatSupported = false;
581 }
582
583 bool wxClipboard::Open()
584 {
585 wxCHECK_MSG( !m_open, false, wxT("clipboard already open") );
586
587 m_open = true;
588
589 return true;
590 }
591
592 bool wxClipboard::SetData( wxDataObject *data )
593 {
594 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
595
596 wxCHECK_MSG( data, false, wxT("data is invalid") );
597
598 Clear();
599
600 return AddData( data );
601 }
602
603 bool wxClipboard::AddData( wxDataObject *data )
604 {
605 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
606
607 wxCHECK_MSG( data, false, wxT("data is invalid") );
608
609 // we can only store one wxDataObject so clear the old one
610 Clear();
611
612 Data() = data;
613
614 // get formats from wxDataObjects
615 const size_t count = data->GetFormatCount();
616 wxDataFormatArray formats(new wxDataFormat[count]);
617 data->GetAllFormats(formats.get());
618
619 // always provide TIMESTAMP as a target, see comments in selection_handler
620 // for explanation
621 AddSupportedTarget(g_timestampAtom);
622
623 for ( size_t i = 0; i < count; i++ )
624 {
625 const wxDataFormat format(formats[i]);
626
627 wxLogTrace(TRACE_CLIPBOARD, wxT("Adding support for %s"),
628 format.GetId().c_str());
629
630 AddSupportedTarget(format);
631 }
632
633 g_signal_connect (m_clipboardWidget, "selection_get",
634 G_CALLBACK (selection_handler),
635 GUINT_TO_POINTER (gtk_get_current_event_time()) );
636
637 // tell the world we offer clipboard data
638 return SetSelectionOwner();
639 }
640
641 void wxClipboard::Close()
642 {
643 wxCHECK_RET( m_open, wxT("clipboard not open") );
644
645 m_open = false;
646 }
647
648 bool wxClipboard::IsOpened() const
649 {
650 return m_open;
651 }
652
653 bool wxClipboard::IsSupported( const wxDataFormat& format )
654 {
655 if ( DoIsSupported(format) )
656 return true;
657
658 #if wxUSE_UNICODE
659 if ( format == wxDF_UNICODETEXT )
660 {
661 // also with plain STRING format
662 return DoIsSupported(g_altTextAtom);
663 }
664 #endif // wxUSE_UNICODE
665
666 return false;
667 }
668
669 bool wxClipboard::GetData( wxDataObject& data )
670 {
671 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
672
673 // get all supported formats from wxDataObjects
674 const size_t count = data.GetFormatCount();
675 wxDataFormatArray formats(new wxDataFormat[count]);
676 data.GetAllFormats(formats.get());
677
678 for ( size_t i = 0; i < count; i++ )
679 {
680 const wxDataFormat format(formats[i]);
681
682 // is this format supported by clipboard ?
683 if ( !DoIsSupported(format) )
684 continue;
685
686 wxLogTrace(TRACE_CLIPBOARD, wxT("Requesting format %s"),
687 format.GetId().c_str());
688
689 // these variables will be used by our GTKOnSelectionReceived()
690 m_receivedData = &data;
691 m_formatSupported = false;
692
693 {
694 wxClipboardSync sync(*this);
695
696 gtk_selection_convert(m_clipboardWidget,
697 GTKGetClipboardAtom(),
698 format,
699 (guint32) GDK_CURRENT_TIME );
700 } // wait until we get the results
701
702 /*
703 Normally this is a true error as we checked for the presence of such
704 data before, but there are applications that may return an empty
705 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
706 which would produce a false error message here, so we check for the
707 size of the string first. With ANSI, GetDataSize returns an extra
708 value (for the closing null?), with unicode, the exact number of
709 tokens is given (that is more than 1 for non-ASCII characters)
710 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
711 */
712 #if wxUSE_UNICODE
713 if ( format != wxDF_UNICODETEXT || data.GetDataSize(format) > 0 )
714 #else // !UNICODE
715 if ( format != wxDF_TEXT || data.GetDataSize(format) > 1 )
716 #endif // UNICODE / !UNICODE
717 {
718 wxCHECK_MSG( m_formatSupported, false,
719 wxT("error retrieving data from clipboard") );
720 }
721
722 return true;
723 }
724
725 wxLogTrace(TRACE_CLIPBOARD, wxT("GetData(): format not found"));
726
727 return false;
728 }
729
730 #endif // wxUSE_CLIPBOARD