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
);
139 const int selection_data_length
= gtk_selection_data_get_length(selection_data
);
140 if (selection_data_length
<= 0)
143 // make sure we got the data in the correct form
144 GdkAtom type
= gtk_selection_data_get_data_type(selection_data
);
145 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
147 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
149 wxLogTrace( TRACE_CLIPBOARD
,
150 wxT("got unsupported clipboard target") );
156 // it's not really a format, of course, but we can reuse its GetId() method
157 // to format this atom as string
158 wxDataFormat
clip(gtk_selection_data_get_selection(selection_data
));
159 wxLogTrace( TRACE_CLIPBOARD
,
160 wxT("Received available formats for clipboard %s"),
161 clip
.GetId().c_str() );
163 // the atoms we received, holding a list of targets (= formats)
164 const GdkAtom
* const atoms
= (GdkAtom
*)gtk_selection_data_get_data(selection_data
);
165 for (size_t i
= 0; i
< selection_data_length
/ sizeof(GdkAtom
); i
++)
167 const wxDataFormat
format(atoms
[i
]);
169 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
171 if ( clipboard
->GTKOnTargetReceived(format
) )
177 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat
& format
)
179 if ( format
!= m_targetRequested
)
182 m_formatSupported
= true;
186 //-----------------------------------------------------------------------------
187 // "selection_received" for the actual data
188 //-----------------------------------------------------------------------------
192 selection_received( GtkWidget
*WXUNUSED(widget
),
193 GtkSelectionData
*selection_data
,
194 guint32
WXUNUSED(time
),
195 wxClipboard
*clipboard
)
200 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
202 if (!selection_data
|| gtk_selection_data_get_length(selection_data
) <= 0)
205 clipboard
->GTKOnSelectionReceived(*selection_data
);
209 //-----------------------------------------------------------------------------
211 //-----------------------------------------------------------------------------
215 selection_clear_clip( GtkWidget
*WXUNUSED(widget
), GdkEventSelection
*event
)
217 wxClipboard
* const clipboard
= wxTheClipboard
;
221 // notice the use of OnDoneIfInProgress() here instead of just OnDone():
222 // it's perfectly possible that we're receiving this notification from GTK+
223 // even though we hadn't cleared the clipboard ourselves but because
224 // another application (or even another window in the same program)
226 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress
, clipboard
);
228 wxClipboard::Kind kind
;
229 if (event
->selection
== GDK_SELECTION_PRIMARY
)
231 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost primary selection" ));
233 kind
= wxClipboard::Primary
;
235 else if (event
->selection
== g_clipboardAtom
)
237 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost clipboard" ));
239 kind
= wxClipboard::Clipboard
;
241 else // some other selection, we're not concerned
246 // the clipboard is no longer in our hands, we don't need data any more
247 clipboard
->GTKClearData(kind
);
253 //-----------------------------------------------------------------------------
254 // selection handler for supplying data
255 //-----------------------------------------------------------------------------
259 selection_handler( GtkWidget
*WXUNUSED(widget
),
260 GtkSelectionData
*selection_data
,
261 guint
WXUNUSED(info
),
262 guint
WXUNUSED(time
),
263 gpointer signal_data
)
265 wxClipboard
* const clipboard
= wxTheClipboard
;
269 wxDataObject
* const data
= clipboard
->GTKGetDataObject(
270 gtk_selection_data_get_selection(selection_data
));
274 // ICCCM says that TIMESTAMP is a required atom.
275 // In particular, it satisfies Klipper, which polls
276 // TIMESTAMP to see if the clipboards content has changed.
277 // It shall return the time which was used to set the data.
278 if (gtk_selection_data_get_target(selection_data
) == g_timestampAtom
)
280 guint timestamp
= GPOINTER_TO_UINT (signal_data
);
281 gtk_selection_data_set(selection_data
,
282 GDK_SELECTION_TYPE_INTEGER
,
284 (guchar
*)&(timestamp
),
286 wxLogTrace(TRACE_CLIPBOARD
,
287 wxT("Clipboard TIMESTAMP requested, returning timestamp=%u"),
292 wxDataFormat
format(gtk_selection_data_get_target(selection_data
));
294 wxLogTrace(TRACE_CLIPBOARD
,
295 wxT("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
296 format
.GetId().c_str(),
297 wxString::FromAscii(wxGtkString(gdk_atom_name(gtk_selection_data_get_target(selection_data
)))).c_str(),
298 wxString::FromAscii(wxGtkString(gdk_atom_name(gtk_selection_data_get_data_type(selection_data
)))).c_str(),
299 wxString::FromAscii(wxGtkString(gdk_atom_name(gtk_selection_data_get_selection(selection_data
)))).c_str(),
300 GPOINTER_TO_UINT( signal_data
)
303 if ( !data
->IsSupportedFormat( format
) )
306 int size
= data
->GetDataSize( format
);
310 wxCharBuffer
buf(size
- 1); // it adds 1 internally (for NUL)
312 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
313 if ( !data
->GetDataHere(format
, buf
.data()) )
316 // use UTF8_STRING format if requested in Unicode build but just plain
317 // STRING one in ANSI or if explicitly asked in Unicode
319 if (format
== wxDataFormat(wxDF_UNICODETEXT
))
321 gtk_selection_data_set_text(
323 (const gchar
*)buf
.data(),
327 #endif // wxUSE_UNICODE
329 gtk_selection_data_set(
331 GDK_SELECTION_TYPE_STRING
,
333 (const guchar
*)buf
.data(),
339 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData
& sel
)
341 wxCHECK_RET( m_receivedData
, wxT("should be inside GetData()") );
343 const wxDataFormat
format(gtk_selection_data_get_target(const_cast<GtkSelectionData
*>(&sel
)));
344 wxLogTrace(TRACE_CLIPBOARD
, wxT("Received selection %s"),
345 format
.GetId().c_str());
347 if ( !m_receivedData
->IsSupportedFormat(format
, wxDataObject::Set
) )
350 m_receivedData
->SetData(format
,
351 gtk_selection_data_get_length(const_cast<GtkSelectionData
*>(&sel
)),
352 gtk_selection_data_get_data(const_cast<GtkSelectionData
*>(&sel
)));
353 m_formatSupported
= true;
356 //-----------------------------------------------------------------------------
357 // asynchronous "selection_received" for targets
358 //-----------------------------------------------------------------------------
362 async_targets_selection_received( GtkWidget
*WXUNUSED(widget
),
363 GtkSelectionData
*selection_data
,
364 guint32
WXUNUSED(time
),
365 wxClipboard
*clipboard
)
367 if ( !clipboard
) // Assert?
370 if (!clipboard
->m_sink
)
373 wxClipboardEvent
*event
= new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED
);
374 event
->SetEventObject( clipboard
);
376 int selection_data_length
= 0;
378 selection_data_length
= gtk_selection_data_get_length(selection_data
);
380 if (selection_data_length
<= 0)
382 clipboard
->m_sink
->QueueEvent( event
);
383 clipboard
->m_sink
.Release();
387 // make sure we got the data in the correct form
388 GdkAtom type
= gtk_selection_data_get_data_type(selection_data
);
389 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
391 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
393 wxLogTrace( TRACE_CLIPBOARD
,
394 wxT("got unsupported clipboard target") );
396 clipboard
->m_sink
->QueueEvent( event
);
397 clipboard
->m_sink
.Release();
402 // it's not really a format, of course, but we can reuse its GetId() method
403 // to format this atom as string
404 wxDataFormat
clip(gtk_selection_data_get_selection(selection_data
));
405 wxLogTrace( TRACE_CLIPBOARD
,
406 wxT("Received available formats for clipboard %s"),
407 clip
.GetId().c_str() );
409 // the atoms we received, holding a list of targets (= formats)
410 const GdkAtom
* const atoms
= (GdkAtom
*)gtk_selection_data_get_data(selection_data
);
411 for (size_t i
= 0; i
< selection_data_length
/ sizeof(GdkAtom
); i
++)
413 const wxDataFormat
format(atoms
[i
]);
415 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
417 event
->AddFormat( format
);
420 clipboard
->m_sink
->QueueEvent( event
);
421 clipboard
->m_sink
.Release();
425 // ============================================================================
426 // wxClipboard implementation
427 // ============================================================================
429 // ----------------------------------------------------------------------------
430 // wxClipboard ctor/dtor
431 // ----------------------------------------------------------------------------
433 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
)
435 wxClipboard::wxClipboard()
441 m_receivedData
= NULL
;
443 m_formatSupported
= false;
444 m_targetRequested
= 0;
446 // we use m_targetsWidget to query what formats are available
447 m_targetsWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
448 gtk_widget_realize( m_targetsWidget
);
450 g_signal_connect (m_targetsWidget
, "selection_received",
451 G_CALLBACK (targets_selection_received
), this);
453 // we use m_targetsWidgetAsync to query what formats are available asynchronously
454 m_targetsWidgetAsync
= gtk_window_new( GTK_WINDOW_POPUP
);
455 gtk_widget_realize( m_targetsWidgetAsync
);
457 g_signal_connect (m_targetsWidgetAsync
, "selection_received",
458 G_CALLBACK (async_targets_selection_received
), this);
460 // we use m_clipboardWidget to get and to offer data
461 m_clipboardWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
462 gtk_widget_realize( m_clipboardWidget
);
464 g_signal_connect (m_clipboardWidget
, "selection_received",
465 G_CALLBACK (selection_received
), this);
467 g_signal_connect (m_clipboardWidget
, "selection_clear_event",
468 G_CALLBACK (selection_clear_clip
), NULL
);
470 // initialize atoms we use if not done yet
471 if ( !g_clipboardAtom
)
472 g_clipboardAtom
= gdk_atom_intern( "CLIPBOARD", FALSE
);
473 if ( !g_targetsAtom
)
474 g_targetsAtom
= gdk_atom_intern ("TARGETS", FALSE
);
475 if ( !g_timestampAtom
)
476 g_timestampAtom
= gdk_atom_intern ("TIMESTAMP", FALSE
);
479 wxClipboard::~wxClipboard()
483 gtk_widget_destroy( m_clipboardWidget
);
484 gtk_widget_destroy( m_targetsWidget
);
487 // ----------------------------------------------------------------------------
488 // wxClipboard helper functions
489 // ----------------------------------------------------------------------------
491 GdkAtom
wxClipboard::GTKGetClipboardAtom() const
493 return m_usePrimary
? (GdkAtom
)GDK_SELECTION_PRIMARY
497 void wxClipboard::GTKClearData(Kind kind
)
499 wxDataObject
*&data
= Data(kind
);
503 bool wxClipboard::SetSelectionOwner(bool set
)
505 bool rc
= gtk_selection_owner_set
507 set
? m_clipboardWidget
: NULL
,
508 GTKGetClipboardAtom(),
509 (guint32
)GDK_CURRENT_TIME
514 wxLogTrace(TRACE_CLIPBOARD
, wxT("Failed to %sset selection owner"),
515 set
? wxT("") : wxT("un"));
521 void wxClipboard::AddSupportedTarget(GdkAtom atom
)
523 gtk_selection_add_target
526 GTKGetClipboardAtom(),
528 0 // info (same as client data) unused
532 bool wxClipboard::IsSupportedAsync(wxEvtHandler
*sink
)
535 return false; // currently busy, come back later
537 wxCHECK_MSG( sink
, false, wxT("no sink given") );
540 gtk_selection_convert( m_targetsWidgetAsync
,
541 GTKGetClipboardAtom(),
543 (guint32
) GDK_CURRENT_TIME
);
548 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
)
550 wxCHECK_MSG( format
, false, wxT("invalid clipboard format") );
552 wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"),
553 format
.GetId().c_str());
555 // these variables will be used by our GTKOnTargetReceived()
556 m_targetRequested
= format
;
557 m_formatSupported
= false;
559 // block until m_formatSupported is set from targets_selection_received
562 wxClipboardSync
sync(*this);
564 gtk_selection_convert( m_targetsWidget
,
565 GTKGetClipboardAtom(),
567 (guint32
) GDK_CURRENT_TIME
);
570 return m_formatSupported
;
573 // ----------------------------------------------------------------------------
574 // wxClipboard public API implementation
575 // ----------------------------------------------------------------------------
577 void wxClipboard::Clear()
579 gtk_selection_clear_targets( m_clipboardWidget
, GTKGetClipboardAtom() );
581 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
582 gtk_widget_get_window(m_clipboardWidget
) )
584 wxClipboardSync
sync(*this);
586 // this will result in selection_clear_clip callback being called and
587 // it will free our data
588 SetSelectionOwner(false);
591 m_targetRequested
= 0;
592 m_formatSupported
= false;
595 bool wxClipboard::Open()
597 wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") );
604 bool wxClipboard::SetData( wxDataObject
*data
)
606 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
608 wxCHECK_MSG( data
, false, wxT("data is invalid") );
612 return AddData( data
);
615 bool wxClipboard::AddData( wxDataObject
*data
)
617 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
619 wxCHECK_MSG( data
, false, wxT("data is invalid") );
621 // we can only store one wxDataObject so clear the old one
626 // get formats from wxDataObjects
627 const size_t count
= data
->GetFormatCount();
628 wxDataFormatArray
formats(new wxDataFormat
[count
]);
629 data
->GetAllFormats(formats
.get());
631 // always provide TIMESTAMP as a target, see comments in selection_handler
633 AddSupportedTarget(g_timestampAtom
);
635 for ( size_t i
= 0; i
< count
; i
++ )
637 const wxDataFormat
format(formats
[i
]);
639 wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"),
640 format
.GetId().c_str());
642 AddSupportedTarget(format
);
645 g_signal_connect (m_clipboardWidget
, "selection_get",
646 G_CALLBACK (selection_handler
),
647 GUINT_TO_POINTER (gtk_get_current_event_time()) );
649 // tell the world we offer clipboard data
650 return SetSelectionOwner();
653 void wxClipboard::Close()
655 wxCHECK_RET( m_open
, wxT("clipboard not open") );
660 bool wxClipboard::IsOpened() const
665 bool wxClipboard::IsSupported( const wxDataFormat
& format
)
667 if ( DoIsSupported(format
) )
671 if ( format
== wxDF_UNICODETEXT
)
673 // also with plain STRING format
674 return DoIsSupported(g_altTextAtom
);
676 #endif // wxUSE_UNICODE
681 bool wxClipboard::GetData( wxDataObject
& data
)
683 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
685 // get all supported formats from wxDataObjects: notice that we are setting
686 // the object data, so we need them in "Set" direction
687 const size_t count
= data
.GetFormatCount(wxDataObject::Set
);
688 wxDataFormatArray
formats(new wxDataFormat
[count
]);
689 data
.GetAllFormats(formats
.get(), wxDataObject::Set
);
691 for ( size_t i
= 0; i
< count
; i
++ )
693 const wxDataFormat
format(formats
[i
]);
695 // is this format supported by clipboard ?
696 if ( !DoIsSupported(format
) )
699 wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"),
700 format
.GetId().c_str());
702 // these variables will be used by our GTKOnSelectionReceived()
703 m_receivedData
= &data
;
704 m_formatSupported
= false;
707 wxClipboardSync
sync(*this);
709 gtk_selection_convert(m_clipboardWidget
,
710 GTKGetClipboardAtom(),
712 (guint32
) GDK_CURRENT_TIME
);
713 } // wait until we get the results
716 Normally this is a true error as we checked for the presence of such
717 data before, but there are applications that may return an empty
718 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
719 which would produce a false error message here, so we check for the
720 size of the string first. With ANSI, GetDataSize returns an extra
721 value (for the closing null?), with unicode, the exact number of
722 tokens is given (that is more than 1 for non-ASCII characters)
723 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
726 if ( format
!= wxDF_UNICODETEXT
|| data
.GetDataSize(format
) > 0 )
728 if ( format
!= wxDF_TEXT
|| data
.GetDataSize(format
) > 1 )
729 #endif // UNICODE / !UNICODE
731 wxCHECK_MSG( m_formatSupported
, false,
732 wxT("error retrieving data from clipboard") );
738 wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found"));
743 wxDataObject
* wxClipboard::GTKGetDataObject( GdkAtom atom
)
745 if ( atom
== GDK_NONE
)
748 if ( atom
== GDK_SELECTION_PRIMARY
)
750 wxLogTrace(TRACE_CLIPBOARD
, wxT("Primary selection requested" ));
752 return Data( wxClipboard::Primary
);
754 else if ( atom
== g_clipboardAtom
)
756 wxLogTrace(TRACE_CLIPBOARD
, wxT("Clipboard data requested" ));
758 return Data( wxClipboard::Clipboard
);
760 else // some other selection, we're not concerned
762 return (wxDataObject
*)NULL
;
766 #endif // wxUSE_CLIPBOARD