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 // ensure that there is a running event loop: this might not be the
79 // case if we're called before the main event loop startup
80 wxEventLoopGuarantor ensureEventLoop
;
83 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_CLIPBOARD
);
86 // this method must be called by GTK+ callbacks to indicate that we got the
87 // result for our clipboard operation
88 static void OnDone(wxClipboard
* WXUNUSED_UNLESS_DEBUG(clipboard
))
90 wxASSERT_MSG( clipboard
== ms_clipboard
,
91 wxT("got notification for alien clipboard") );
96 // this method should be called if it's possible that no async clipboard
97 // operation is currently in progress (like it can be the case when the
98 // clipboard is cleared but not because we asked about it), it should only
99 // be called if such situation is expected -- otherwise call OnDone() which
100 // would assert in this case
101 static void OnDoneIfInProgress(wxClipboard
*clipboard
)
108 static wxClipboard
*ms_clipboard
;
110 wxDECLARE_NO_COPY_CLASS(wxClipboardSync
);
113 wxClipboard
*wxClipboardSync::ms_clipboard
= NULL
;
115 // ============================================================================
116 // clipboard callbacks implementation
117 // ============================================================================
119 //-----------------------------------------------------------------------------
120 // "selection_received" for targets
121 //-----------------------------------------------------------------------------
125 targets_selection_received( GtkWidget
*WXUNUSED(widget
),
126 GtkSelectionData
*selection_data
,
127 guint32
WXUNUSED(time
),
128 wxClipboard
*clipboard
)
133 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
135 if ( !selection_data
|| selection_data
->length
<= 0 )
138 // make sure we got the data in the correct form
139 GdkAtom type
= selection_data
->type
;
140 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
142 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
144 wxLogTrace( TRACE_CLIPBOARD
,
145 wxT("got unsupported clipboard target") );
151 // it's not really a format, of course, but we can reuse its GetId() method
152 // to format this atom as string
153 wxDataFormat
clip(selection_data
->selection
);
154 wxLogTrace( TRACE_CLIPBOARD
,
155 wxT("Received available formats for clipboard %s"),
156 clip
.GetId().c_str() );
158 // the atoms we received, holding a list of targets (= formats)
159 const GdkAtom
* const atoms
= (GdkAtom
*)selection_data
->data
;
160 for ( size_t i
= 0; i
< selection_data
->length
/sizeof(GdkAtom
); i
++ )
162 const wxDataFormat
format(atoms
[i
]);
164 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
166 if ( clipboard
->GTKOnTargetReceived(format
) )
172 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat
& format
)
174 if ( format
!= m_targetRequested
)
177 m_formatSupported
= true;
181 //-----------------------------------------------------------------------------
182 // "selection_received" for the actual data
183 //-----------------------------------------------------------------------------
187 selection_received( GtkWidget
*WXUNUSED(widget
),
188 GtkSelectionData
*selection_data
,
189 guint32
WXUNUSED(time
),
190 wxClipboard
*clipboard
)
195 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
197 if ( !selection_data
|| selection_data
->length
<= 0 )
200 clipboard
->GTKOnSelectionReceived(*selection_data
);
204 //-----------------------------------------------------------------------------
206 //-----------------------------------------------------------------------------
210 selection_clear_clip( GtkWidget
*WXUNUSED(widget
), GdkEventSelection
*event
)
212 wxClipboard
* const clipboard
= wxTheClipboard
;
216 // notice the use of OnDoneIfInProgress() here instead of just OnDone():
217 // it's perfectly possible that we're receiving this notification from GTK+
218 // even though we hadn't cleared the clipboard ourselves but because
219 // another application (or even another window in the same program)
221 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress
, clipboard
);
223 wxClipboard::Kind kind
;
224 if (event
->selection
== GDK_SELECTION_PRIMARY
)
226 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost primary selection" ));
228 kind
= wxClipboard::Primary
;
230 else if (event
->selection
== g_clipboardAtom
)
232 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost clipboard" ));
234 kind
= wxClipboard::Clipboard
;
236 else // some other selection, we're not concerned
241 // the clipboard is no longer in our hands, we don't need data any more
242 clipboard
->GTKClearData(kind
);
248 //-----------------------------------------------------------------------------
249 // selection handler for supplying data
250 //-----------------------------------------------------------------------------
254 selection_handler( GtkWidget
*WXUNUSED(widget
),
255 GtkSelectionData
*selection_data
,
256 guint
WXUNUSED(info
),
257 guint
WXUNUSED(time
),
258 gpointer signal_data
)
260 wxClipboard
* const clipboard
= wxTheClipboard
;
264 wxDataObject
* const data
= clipboard
->GTKGetDataObject();
268 // ICCCM says that TIMESTAMP is a required atom.
269 // In particular, it satisfies Klipper, which polls
270 // TIMESTAMP to see if the clipboards content has changed.
271 // It shall return the time which was used to set the data.
272 if (selection_data
->target
== g_timestampAtom
)
274 guint timestamp
= GPOINTER_TO_UINT (signal_data
);
275 gtk_selection_data_set(selection_data
,
276 GDK_SELECTION_TYPE_INTEGER
,
278 (guchar
*)&(timestamp
),
280 wxLogTrace(TRACE_CLIPBOARD
,
281 wxT("Clipboard TIMESTAMP requested, returning timestamp=%u"),
286 wxDataFormat
format( selection_data
->target
);
288 wxLogTrace(TRACE_CLIPBOARD
,
289 wxT("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
290 format
.GetId().c_str(),
291 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->target
))).c_str(),
292 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->type
))).c_str(),
293 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->selection
))).c_str(),
294 GPOINTER_TO_UINT( signal_data
)
297 if ( !data
->IsSupportedFormat( format
) )
300 int size
= data
->GetDataSize( format
);
304 wxCharBuffer
buf(size
- 1); // it adds 1 internally (for NUL)
306 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
307 if ( !data
->GetDataHere(format
, buf
.data()) )
310 // use UTF8_STRING format if requested in Unicode build but just plain
311 // STRING one in ANSI or if explicitly asked in Unicode
313 if (format
== wxDataFormat(wxDF_UNICODETEXT
))
315 gtk_selection_data_set_text(
317 (const gchar
*)buf
.data(),
321 #endif // wxUSE_UNICODE
323 gtk_selection_data_set(
325 GDK_SELECTION_TYPE_STRING
,
327 (const guchar
*)buf
.data(),
333 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData
& sel
)
335 wxCHECK_RET( m_receivedData
, wxT("should be inside GetData()") );
337 const wxDataFormat
format(sel
.target
);
338 wxLogTrace(TRACE_CLIPBOARD
, wxT("Received selection %s"),
339 format
.GetId().c_str());
341 if ( !m_receivedData
->IsSupportedFormat(format
) )
344 m_receivedData
->SetData(format
, sel
.length
, sel
.data
);
345 m_formatSupported
= true;
348 //-----------------------------------------------------------------------------
349 // asynchronous "selection_received" for targets
350 //-----------------------------------------------------------------------------
354 async_targets_selection_received( GtkWidget
*WXUNUSED(widget
),
355 GtkSelectionData
*selection_data
,
356 guint32
WXUNUSED(time
),
357 wxClipboard
*clipboard
)
359 if ( !clipboard
) // Assert?
362 if (!clipboard
->m_sink
)
365 wxClipboardEvent
*event
= new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED
);
366 event
->SetEventObject( clipboard
);
368 if ( !selection_data
|| selection_data
->length
<= 0 )
370 clipboard
->m_sink
->QueueEvent( event
);
371 clipboard
->m_sink
.Release();
375 // make sure we got the data in the correct form
376 GdkAtom type
= selection_data
->type
;
377 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
379 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
381 wxLogTrace( TRACE_CLIPBOARD
,
382 wxT("got unsupported clipboard target") );
384 clipboard
->m_sink
->QueueEvent( event
);
385 clipboard
->m_sink
.Release();
390 // it's not really a format, of course, but we can reuse its GetId() method
391 // to format this atom as string
392 wxDataFormat
clip(selection_data
->selection
);
393 wxLogTrace( TRACE_CLIPBOARD
,
394 wxT("Received available formats for clipboard %s"),
395 clip
.GetId().c_str() );
397 // the atoms we received, holding a list of targets (= formats)
398 const GdkAtom
* const atoms
= (GdkAtom
*)selection_data
->data
;
399 for ( size_t i
= 0; i
< selection_data
->length
/sizeof(GdkAtom
); i
++ )
401 const wxDataFormat
format(atoms
[i
]);
403 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
405 event
->AddFormat( format
);
408 clipboard
->m_sink
->QueueEvent( event
);
409 clipboard
->m_sink
.Release();
413 // ============================================================================
414 // wxClipboard implementation
415 // ============================================================================
417 // ----------------------------------------------------------------------------
418 // wxClipboard ctor/dtor
419 // ----------------------------------------------------------------------------
421 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
)
423 wxClipboard::wxClipboard()
429 m_receivedData
= NULL
;
431 m_formatSupported
= false;
432 m_targetRequested
= 0;
434 // we use m_targetsWidget to query what formats are available
435 m_targetsWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
436 gtk_widget_realize( m_targetsWidget
);
438 g_signal_connect (m_targetsWidget
, "selection_received",
439 G_CALLBACK (targets_selection_received
), this);
441 // we use m_targetsWidgetAsync to query what formats are available asynchronously
442 m_targetsWidgetAsync
= gtk_window_new( GTK_WINDOW_POPUP
);
443 gtk_widget_realize( m_targetsWidgetAsync
);
445 g_signal_connect (m_targetsWidgetAsync
, "selection_received",
446 G_CALLBACK (async_targets_selection_received
), this);
448 // we use m_clipboardWidget to get and to offer data
449 m_clipboardWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
450 gtk_widget_realize( m_clipboardWidget
);
452 g_signal_connect (m_clipboardWidget
, "selection_received",
453 G_CALLBACK (selection_received
), this);
455 g_signal_connect (m_clipboardWidget
, "selection_clear_event",
456 G_CALLBACK (selection_clear_clip
), NULL
);
458 // initialize atoms we use if not done yet
459 if ( !g_clipboardAtom
)
460 g_clipboardAtom
= gdk_atom_intern( "CLIPBOARD", FALSE
);
461 if ( !g_targetsAtom
)
462 g_targetsAtom
= gdk_atom_intern ("TARGETS", FALSE
);
463 if ( !g_timestampAtom
)
464 g_timestampAtom
= gdk_atom_intern ("TIMESTAMP", FALSE
);
467 wxClipboard::~wxClipboard()
471 gtk_widget_destroy( m_clipboardWidget
);
472 gtk_widget_destroy( m_targetsWidget
);
475 // ----------------------------------------------------------------------------
476 // wxClipboard helper functions
477 // ----------------------------------------------------------------------------
479 GdkAtom
wxClipboard::GTKGetClipboardAtom() const
481 return m_usePrimary
? (GdkAtom
)GDK_SELECTION_PRIMARY
485 void wxClipboard::GTKClearData(Kind kind
)
487 wxDataObject
*&data
= Data(kind
);
495 bool wxClipboard::SetSelectionOwner(bool set
)
497 bool rc
= gtk_selection_owner_set
499 set
? m_clipboardWidget
: NULL
,
500 GTKGetClipboardAtom(),
501 (guint32
)GDK_CURRENT_TIME
506 wxLogTrace(TRACE_CLIPBOARD
, wxT("Failed to %sset selection owner"),
507 set
? wxT("") : wxT("un"));
513 void wxClipboard::AddSupportedTarget(GdkAtom atom
)
515 gtk_selection_add_target
518 GTKGetClipboardAtom(),
520 0 // info (same as client data) unused
524 bool wxClipboard::IsSupportedAsync(wxEvtHandler
*sink
)
527 return false; // currently busy, come back later
529 wxCHECK_MSG( sink
, false, wxT("no sink given") );
532 gtk_selection_convert( m_targetsWidgetAsync
,
533 GTKGetClipboardAtom(),
535 (guint32
) GDK_CURRENT_TIME
);
540 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
)
542 wxCHECK_MSG( format
, false, wxT("invalid clipboard format") );
544 wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"),
545 format
.GetId().c_str());
547 // these variables will be used by our GTKOnTargetReceived()
548 m_targetRequested
= format
;
549 m_formatSupported
= false;
551 // block until m_formatSupported is set from targets_selection_received
554 wxClipboardSync
sync(*this);
556 gtk_selection_convert( m_targetsWidget
,
557 GTKGetClipboardAtom(),
559 (guint32
) GDK_CURRENT_TIME
);
562 return m_formatSupported
;
565 // ----------------------------------------------------------------------------
566 // wxClipboard public API implementation
567 // ----------------------------------------------------------------------------
569 void wxClipboard::Clear()
571 gtk_selection_clear_targets( m_clipboardWidget
, GTKGetClipboardAtom() );
573 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
574 m_clipboardWidget
->window
)
576 wxClipboardSync
sync(*this);
578 // this will result in selection_clear_clip callback being called and
579 // it will free our data
580 SetSelectionOwner(false);
583 m_targetRequested
= 0;
584 m_formatSupported
= false;
587 bool wxClipboard::Open()
589 wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") );
596 bool wxClipboard::SetData( wxDataObject
*data
)
598 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
600 wxCHECK_MSG( data
, false, wxT("data is invalid") );
604 return AddData( data
);
607 bool wxClipboard::AddData( wxDataObject
*data
)
609 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
611 wxCHECK_MSG( data
, false, wxT("data is invalid") );
613 // we can only store one wxDataObject so clear the old one
618 // get formats from wxDataObjects
619 const size_t count
= data
->GetFormatCount();
620 wxDataFormatArray
formats(new wxDataFormat
[count
]);
621 data
->GetAllFormats(formats
.get());
623 // always provide TIMESTAMP as a target, see comments in selection_handler
625 AddSupportedTarget(g_timestampAtom
);
627 for ( size_t i
= 0; i
< count
; i
++ )
629 const wxDataFormat
format(formats
[i
]);
631 wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"),
632 format
.GetId().c_str());
634 AddSupportedTarget(format
);
637 g_signal_connect (m_clipboardWidget
, "selection_get",
638 G_CALLBACK (selection_handler
),
639 GUINT_TO_POINTER (gtk_get_current_event_time()) );
641 // tell the world we offer clipboard data
642 return SetSelectionOwner();
645 void wxClipboard::Close()
647 wxCHECK_RET( m_open
, wxT("clipboard not open") );
652 bool wxClipboard::IsOpened() const
657 bool wxClipboard::IsSupported( const wxDataFormat
& format
)
659 if ( DoIsSupported(format
) )
663 if ( format
== wxDF_UNICODETEXT
)
665 // also with plain STRING format
666 return DoIsSupported(g_altTextAtom
);
668 #endif // wxUSE_UNICODE
673 bool wxClipboard::GetData( wxDataObject
& data
)
675 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
677 // get all supported formats from wxDataObjects: notice that we are setting
678 // the object data, so we need them in "Set" direction
679 const size_t count
= data
.GetFormatCount(wxDataObject::Set
);
680 wxDataFormatArray
formats(new wxDataFormat
[count
]);
681 data
.GetAllFormats(formats
.get(), wxDataObject::Set
);
683 for ( size_t i
= 0; i
< count
; i
++ )
685 const wxDataFormat
format(formats
[i
]);
687 // is this format supported by clipboard ?
688 if ( !DoIsSupported(format
) )
691 wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"),
692 format
.GetId().c_str());
694 // these variables will be used by our GTKOnSelectionReceived()
695 m_receivedData
= &data
;
696 m_formatSupported
= false;
699 wxClipboardSync
sync(*this);
701 gtk_selection_convert(m_clipboardWidget
,
702 GTKGetClipboardAtom(),
704 (guint32
) GDK_CURRENT_TIME
);
705 } // wait until we get the results
708 Normally this is a true error as we checked for the presence of such
709 data before, but there are applications that may return an empty
710 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
711 which would produce a false error message here, so we check for the
712 size of the string first. With ANSI, GetDataSize returns an extra
713 value (for the closing null?), with unicode, the exact number of
714 tokens is given (that is more than 1 for non-ASCII characters)
715 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
718 if ( format
!= wxDF_UNICODETEXT
|| data
.GetDataSize(format
) > 0 )
720 if ( format
!= wxDF_TEXT
|| data
.GetDataSize(format
) > 1 )
721 #endif // UNICODE / !UNICODE
723 wxCHECK_MSG( m_formatSupported
, false,
724 wxT("error retrieving data from clipboard") );
730 wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found"));
735 #endif // wxUSE_CLIPBOARD