1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/clipbrd.cpp
3 // Purpose: wxClipboard implementation for wxGTK
4 // Author: Robert Roebling, Vadim Zeitlin
6 // Copyright: (c) 1998 Robert Roebling
7 // (c) 2007 Vadim Zeitlin
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 // ============================================================================
13 // ============================================================================
15 // ----------------------------------------------------------------------------
17 // ----------------------------------------------------------------------------
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
24 #include "wx/clipbrd.h"
30 #include "wx/dataobj.h"
33 #include "wx/scopedarray.h"
34 #include "wx/scopeguard.h"
35 #include "wx/evtloop.h"
37 #include "wx/gtk/private.h"
39 typedef wxScopedArray
<wxDataFormat
> wxDataFormatArray
;
41 // ----------------------------------------------------------------------------
43 // ----------------------------------------------------------------------------
45 static GdkAtom g_clipboardAtom
= 0;
46 static GdkAtom g_targetsAtom
= 0;
47 static GdkAtom g_timestampAtom
= 0;
50 extern GdkAtom g_altTextAtom
;
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")
58 // ----------------------------------------------------------------------------
59 // wxClipboardSync: used to perform clipboard operations synchronously
60 // ----------------------------------------------------------------------------
62 // constructing this object on stack will wait wait until the latest clipboard
63 // operation is finished on block exit
65 // notice that there can be no more than one such object alive at any moment,
66 // i.e. reentrancies are not allowed
70 wxClipboardSync(wxClipboard
& clipboard
)
72 wxASSERT_MSG( !ms_clipboard
, wxT("reentrancy in clipboard code") );
73 ms_clipboard
= &clipboard
;
78 #if wxUSE_CONSOLE_EVENTLOOP
79 // ensure that there is a running event loop: this might not be the
80 // case if we're called before the main event loop startup
81 wxEventLoopGuarantor ensureEventLoop
;
84 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_CLIPBOARD
);
87 // this method must be called by GTK+ callbacks to indicate that we got the
88 // result for our clipboard operation
89 static void OnDone(wxClipboard
* WXUNUSED_UNLESS_DEBUG(clipboard
))
91 wxASSERT_MSG( clipboard
== ms_clipboard
,
92 wxT("got notification for alien clipboard") );
97 // this method should be called if it's possible that no async clipboard
98 // operation is currently in progress (like it can be the case when the
99 // clipboard is cleared but not because we asked about it), it should only
100 // be called if such situation is expected -- otherwise call OnDone() which
101 // would assert in this case
102 static void OnDoneIfInProgress(wxClipboard
*clipboard
)
109 static wxClipboard
*ms_clipboard
;
111 wxDECLARE_NO_COPY_CLASS(wxClipboardSync
);
114 wxClipboard
*wxClipboardSync::ms_clipboard
= NULL
;
116 // ============================================================================
117 // clipboard callbacks implementation
118 // ============================================================================
120 //-----------------------------------------------------------------------------
121 // "selection_received" for targets
122 //-----------------------------------------------------------------------------
126 targets_selection_received( GtkWidget
*WXUNUSED(widget
),
127 GtkSelectionData
*selection_data
,
128 guint32
WXUNUSED(time
),
129 wxClipboard
*clipboard
)
134 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
136 if ( !selection_data
|| selection_data
->length
<= 0 )
139 // make sure we got the data in the correct form
140 GdkAtom type
= selection_data
->type
;
141 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
143 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
145 wxLogTrace( TRACE_CLIPBOARD
,
146 wxT("got unsupported clipboard target") );
152 // it's not really a format, of course, but we can reuse its GetId() method
153 // to format this atom as string
154 wxDataFormat
clip(selection_data
->selection
);
155 wxLogTrace( TRACE_CLIPBOARD
,
156 wxT("Received available formats for clipboard %s"),
157 clip
.GetId().c_str() );
159 // the atoms we received, holding a list of targets (= formats)
160 const GdkAtom
* const atoms
= (GdkAtom
*)selection_data
->data
;
161 for ( size_t i
= 0; i
< selection_data
->length
/sizeof(GdkAtom
); i
++ )
163 const wxDataFormat
format(atoms
[i
]);
165 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
167 if ( clipboard
->GTKOnTargetReceived(format
) )
173 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat
& format
)
175 if ( format
!= m_targetRequested
)
178 m_formatSupported
= true;
182 //-----------------------------------------------------------------------------
183 // "selection_received" for the actual data
184 //-----------------------------------------------------------------------------
188 selection_received( GtkWidget
*WXUNUSED(widget
),
189 GtkSelectionData
*selection_data
,
190 guint32
WXUNUSED(time
),
191 wxClipboard
*clipboard
)
196 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
198 if ( !selection_data
|| selection_data
->length
<= 0 )
201 clipboard
->GTKOnSelectionReceived(*selection_data
);
205 //-----------------------------------------------------------------------------
207 //-----------------------------------------------------------------------------
211 selection_clear_clip( GtkWidget
*WXUNUSED(widget
), GdkEventSelection
*event
)
213 wxClipboard
* const clipboard
= wxTheClipboard
;
217 // notice the use of OnDoneIfInProgress() here instead of just OnDone():
218 // it's perfectly possible that we're receiving this notification from GTK+
219 // even though we hadn't cleared the clipboard ourselves but because
220 // another application (or even another window in the same program)
222 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress
, clipboard
);
224 wxClipboard::Kind kind
;
225 if (event
->selection
== GDK_SELECTION_PRIMARY
)
227 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost primary selection" ));
229 kind
= wxClipboard::Primary
;
231 else if (event
->selection
== g_clipboardAtom
)
233 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost clipboard" ));
235 kind
= wxClipboard::Clipboard
;
237 else // some other selection, we're not concerned
242 // the clipboard is no longer in our hands, we don't need data any more
243 clipboard
->GTKClearData(kind
);
249 //-----------------------------------------------------------------------------
250 // selection handler for supplying data
251 //-----------------------------------------------------------------------------
255 selection_handler( GtkWidget
*WXUNUSED(widget
),
256 GtkSelectionData
*selection_data
,
257 guint
WXUNUSED(info
),
258 guint
WXUNUSED(time
),
259 gpointer signal_data
)
261 wxClipboard
* const clipboard
= wxTheClipboard
;
265 wxDataObject
* const data
= clipboard
->GTKGetDataObject();
269 // ICCCM says that TIMESTAMP is a required atom.
270 // In particular, it satisfies Klipper, which polls
271 // TIMESTAMP to see if the clipboards content has changed.
272 // It shall return the time which was used to set the data.
273 if (selection_data
->target
== g_timestampAtom
)
275 guint timestamp
= GPOINTER_TO_UINT (signal_data
);
276 gtk_selection_data_set(selection_data
,
277 GDK_SELECTION_TYPE_INTEGER
,
279 (guchar
*)&(timestamp
),
281 wxLogTrace(TRACE_CLIPBOARD
,
282 wxT("Clipboard TIMESTAMP requested, returning timestamp=%u"),
287 wxDataFormat
format( selection_data
->target
);
289 wxLogTrace(TRACE_CLIPBOARD
,
290 wxT("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
291 format
.GetId().c_str(),
292 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->target
))).c_str(),
293 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->type
))).c_str(),
294 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->selection
))).c_str(),
295 GPOINTER_TO_UINT( signal_data
)
298 if ( !data
->IsSupportedFormat( format
) )
301 int size
= data
->GetDataSize( format
);
305 wxCharBuffer
buf(size
- 1); // it adds 1 internally (for NUL)
307 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
308 if ( !data
->GetDataHere(format
, buf
.data()) )
311 // use UTF8_STRING format if requested in Unicode build but just plain
312 // STRING one in ANSI or if explicitly asked in Unicode
314 if (format
== wxDataFormat(wxDF_UNICODETEXT
))
316 gtk_selection_data_set_text(
318 (const gchar
*)buf
.data(),
322 #endif // wxUSE_UNICODE
324 gtk_selection_data_set(
326 GDK_SELECTION_TYPE_STRING
,
328 (const guchar
*)buf
.data(),
334 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData
& sel
)
336 wxCHECK_RET( m_receivedData
, wxT("should be inside GetData()") );
338 const wxDataFormat
format(sel
.target
);
339 wxLogTrace(TRACE_CLIPBOARD
, wxT("Received selection %s"),
340 format
.GetId().c_str());
342 if ( !m_receivedData
->IsSupportedFormat(format
) )
345 m_receivedData
->SetData(format
, sel
.length
, sel
.data
);
346 m_formatSupported
= true;
349 //-----------------------------------------------------------------------------
350 // asynchronous "selection_received" for targets
351 //-----------------------------------------------------------------------------
355 async_targets_selection_received( GtkWidget
*WXUNUSED(widget
),
356 GtkSelectionData
*selection_data
,
357 guint32
WXUNUSED(time
),
358 wxClipboard
*clipboard
)
360 if ( !clipboard
) // Assert?
363 if (!clipboard
->m_sink
)
366 wxClipboardEvent
*event
= new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED
);
367 event
->SetEventObject( clipboard
);
369 if ( !selection_data
|| selection_data
->length
<= 0 )
371 clipboard
->m_sink
->QueueEvent( event
);
372 clipboard
->m_sink
.Release();
376 // make sure we got the data in the correct form
377 GdkAtom type
= selection_data
->type
;
378 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
380 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
382 wxLogTrace( TRACE_CLIPBOARD
,
383 wxT("got unsupported clipboard target") );
385 clipboard
->m_sink
->QueueEvent( event
);
386 clipboard
->m_sink
.Release();
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() );
398 // the atoms we received, holding a list of targets (= formats)
399 const GdkAtom
* const atoms
= (GdkAtom
*)selection_data
->data
;
400 for ( size_t i
= 0; i
< selection_data
->length
/sizeof(GdkAtom
); i
++ )
402 const wxDataFormat
format(atoms
[i
]);
404 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
406 event
->AddFormat( format
);
409 clipboard
->m_sink
->QueueEvent( event
);
410 clipboard
->m_sink
.Release();
414 // ============================================================================
415 // wxClipboard implementation
416 // ============================================================================
418 // ----------------------------------------------------------------------------
419 // wxClipboard ctor/dtor
420 // ----------------------------------------------------------------------------
422 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
)
424 wxClipboard::wxClipboard()
430 m_receivedData
= NULL
;
432 m_formatSupported
= false;
433 m_targetRequested
= 0;
435 // we use m_targetsWidget to query what formats are available
436 m_targetsWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
437 gtk_widget_realize( m_targetsWidget
);
439 g_signal_connect (m_targetsWidget
, "selection_received",
440 G_CALLBACK (targets_selection_received
), this);
442 // we use m_targetsWidgetAsync to query what formats are available asynchronously
443 m_targetsWidgetAsync
= gtk_window_new( GTK_WINDOW_POPUP
);
444 gtk_widget_realize( m_targetsWidgetAsync
);
446 g_signal_connect (m_targetsWidgetAsync
, "selection_received",
447 G_CALLBACK (async_targets_selection_received
), this);
449 // we use m_clipboardWidget to get and to offer data
450 m_clipboardWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
451 gtk_widget_realize( m_clipboardWidget
);
453 g_signal_connect (m_clipboardWidget
, "selection_received",
454 G_CALLBACK (selection_received
), this);
456 g_signal_connect (m_clipboardWidget
, "selection_clear_event",
457 G_CALLBACK (selection_clear_clip
), NULL
);
459 // initialize atoms we use if not done yet
460 if ( !g_clipboardAtom
)
461 g_clipboardAtom
= gdk_atom_intern( "CLIPBOARD", FALSE
);
462 if ( !g_targetsAtom
)
463 g_targetsAtom
= gdk_atom_intern ("TARGETS", FALSE
);
464 if ( !g_timestampAtom
)
465 g_timestampAtom
= gdk_atom_intern ("TIMESTAMP", FALSE
);
468 wxClipboard::~wxClipboard()
472 gtk_widget_destroy( m_clipboardWidget
);
473 gtk_widget_destroy( m_targetsWidget
);
476 // ----------------------------------------------------------------------------
477 // wxClipboard helper functions
478 // ----------------------------------------------------------------------------
480 GdkAtom
wxClipboard::GTKGetClipboardAtom() const
482 return m_usePrimary
? (GdkAtom
)GDK_SELECTION_PRIMARY
486 void wxClipboard::GTKClearData(Kind kind
)
488 wxDataObject
*&data
= Data(kind
);
492 bool wxClipboard::SetSelectionOwner(bool set
)
494 bool rc
= gtk_selection_owner_set
496 set
? m_clipboardWidget
: NULL
,
497 GTKGetClipboardAtom(),
498 (guint32
)GDK_CURRENT_TIME
503 wxLogTrace(TRACE_CLIPBOARD
, wxT("Failed to %sset selection owner"),
504 set
? wxT("") : wxT("un"));
510 void wxClipboard::AddSupportedTarget(GdkAtom atom
)
512 gtk_selection_add_target
515 GTKGetClipboardAtom(),
517 0 // info (same as client data) unused
521 bool wxClipboard::IsSupportedAsync(wxEvtHandler
*sink
)
524 return false; // currently busy, come back later
526 wxCHECK_MSG( sink
, false, wxT("no sink given") );
529 gtk_selection_convert( m_targetsWidgetAsync
,
530 GTKGetClipboardAtom(),
532 (guint32
) GDK_CURRENT_TIME
);
537 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
)
539 wxCHECK_MSG( format
, false, wxT("invalid clipboard format") );
541 wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"),
542 format
.GetId().c_str());
544 // these variables will be used by our GTKOnTargetReceived()
545 m_targetRequested
= format
;
546 m_formatSupported
= false;
548 // block until m_formatSupported is set from targets_selection_received
551 wxClipboardSync
sync(*this);
553 gtk_selection_convert( m_targetsWidget
,
554 GTKGetClipboardAtom(),
556 (guint32
) GDK_CURRENT_TIME
);
559 return m_formatSupported
;
562 // ----------------------------------------------------------------------------
563 // wxClipboard public API implementation
564 // ----------------------------------------------------------------------------
566 void wxClipboard::Clear()
568 gtk_selection_clear_targets( m_clipboardWidget
, GTKGetClipboardAtom() );
570 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
571 m_clipboardWidget
->window
)
573 wxClipboardSync
sync(*this);
575 // this will result in selection_clear_clip callback being called and
576 // it will free our data
577 SetSelectionOwner(false);
580 m_targetRequested
= 0;
581 m_formatSupported
= false;
584 bool wxClipboard::Open()
586 wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") );
593 bool wxClipboard::SetData( wxDataObject
*data
)
595 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
597 wxCHECK_MSG( data
, false, wxT("data is invalid") );
601 return AddData( data
);
604 bool wxClipboard::AddData( wxDataObject
*data
)
606 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
608 wxCHECK_MSG( data
, false, wxT("data is invalid") );
610 // we can only store one wxDataObject so clear the old one
615 // get formats from wxDataObjects
616 const size_t count
= data
->GetFormatCount();
617 wxDataFormatArray
formats(new wxDataFormat
[count
]);
618 data
->GetAllFormats(formats
.get());
620 // always provide TIMESTAMP as a target, see comments in selection_handler
622 AddSupportedTarget(g_timestampAtom
);
624 for ( size_t i
= 0; i
< count
; i
++ )
626 const wxDataFormat
format(formats
[i
]);
628 wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"),
629 format
.GetId().c_str());
631 AddSupportedTarget(format
);
634 g_signal_connect (m_clipboardWidget
, "selection_get",
635 G_CALLBACK (selection_handler
),
636 GUINT_TO_POINTER (gtk_get_current_event_time()) );
638 // tell the world we offer clipboard data
639 return SetSelectionOwner();
642 void wxClipboard::Close()
644 wxCHECK_RET( m_open
, wxT("clipboard not open") );
649 bool wxClipboard::IsOpened() const
654 bool wxClipboard::IsSupported( const wxDataFormat
& format
)
656 if ( DoIsSupported(format
) )
660 if ( format
== wxDF_UNICODETEXT
)
662 // also with plain STRING format
663 return DoIsSupported(g_altTextAtom
);
665 #endif // wxUSE_UNICODE
670 bool wxClipboard::GetData( wxDataObject
& data
)
672 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
674 // get all supported formats from wxDataObjects: notice that we are setting
675 // the object data, so we need them in "Set" direction
676 const size_t count
= data
.GetFormatCount(wxDataObject::Set
);
677 wxDataFormatArray
formats(new wxDataFormat
[count
]);
678 data
.GetAllFormats(formats
.get(), wxDataObject::Set
);
680 for ( size_t i
= 0; i
< count
; i
++ )
682 const wxDataFormat
format(formats
[i
]);
684 // is this format supported by clipboard ?
685 if ( !DoIsSupported(format
) )
688 wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"),
689 format
.GetId().c_str());
691 // these variables will be used by our GTKOnSelectionReceived()
692 m_receivedData
= &data
;
693 m_formatSupported
= false;
696 wxClipboardSync
sync(*this);
698 gtk_selection_convert(m_clipboardWidget
,
699 GTKGetClipboardAtom(),
701 (guint32
) GDK_CURRENT_TIME
);
702 } // wait until we get the results
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)
715 if ( format
!= wxDF_UNICODETEXT
|| data
.GetDataSize(format
) > 0 )
717 if ( format
!= wxDF_TEXT
|| data
.GetDataSize(format
) > 1 )
718 #endif // UNICODE / !UNICODE
720 wxCHECK_MSG( m_formatSupported
, false,
721 wxT("error retrieving data from clipboard") );
727 wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found"));
732 #endif // wxUSE_CLIPBOARD