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 _T("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
, _T("reentrancy in clipboard code") );
73 ms_clipboard
= &clipboard
;
79 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_CLIPBOARD
);
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
))
86 wxASSERT_MSG( clipboard
== ms_clipboard
,
87 _T("got notification for alien clipboard") );
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
)
104 static wxClipboard
*ms_clipboard
;
106 wxDECLARE_NO_COPY_CLASS(wxClipboardSync
);
109 wxClipboard
*wxClipboardSync::ms_clipboard
= NULL
;
111 // ============================================================================
112 // clipboard callbacks implementation
113 // ============================================================================
115 //-----------------------------------------------------------------------------
116 // "selection_received" for targets
117 //-----------------------------------------------------------------------------
121 targets_selection_received( GtkWidget
*WXUNUSED(widget
),
122 GtkSelectionData
*selection_data
,
123 guint32
WXUNUSED(time
),
124 wxClipboard
*clipboard
)
129 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
131 if ( !selection_data
|| selection_data
->length
<= 0 )
134 // make sure we got the data in the correct form
135 GdkAtom type
= selection_data
->type
;
136 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
138 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
140 wxLogTrace( TRACE_CLIPBOARD
,
141 _T("got unsupported clipboard target") );
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__
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
++ )
160 const wxDataFormat
format(atoms
[i
]);
162 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
164 if ( clipboard
->GTKOnTargetReceived(format
) )
170 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat
& format
)
172 if ( format
!= m_targetRequested
)
175 m_formatSupported
= true;
179 //-----------------------------------------------------------------------------
180 // "selection_received" for the actual data
181 //-----------------------------------------------------------------------------
185 selection_received( GtkWidget
*WXUNUSED(widget
),
186 GtkSelectionData
*selection_data
,
187 guint32
WXUNUSED(time
),
188 wxClipboard
*clipboard
)
193 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
195 if ( !selection_data
|| selection_data
->length
<= 0 )
198 clipboard
->GTKOnSelectionReceived(*selection_data
);
202 //-----------------------------------------------------------------------------
204 //-----------------------------------------------------------------------------
208 selection_clear_clip( GtkWidget
*WXUNUSED(widget
), GdkEventSelection
*event
)
210 wxClipboard
* const clipboard
= wxTheClipboard
;
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)
219 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress
, clipboard
);
221 wxClipboard::Kind kind
;
222 if (event
->selection
== GDK_SELECTION_PRIMARY
)
224 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost primary selection" ));
226 kind
= wxClipboard::Primary
;
228 else if (event
->selection
== g_clipboardAtom
)
230 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost clipboard" ));
232 kind
= wxClipboard::Clipboard
;
234 else // some other selection, we're not concerned
239 // the clipboard is no longer in our hands, we don't need data any more
240 clipboard
->GTKClearData(kind
);
246 //-----------------------------------------------------------------------------
247 // selection handler for supplying data
248 //-----------------------------------------------------------------------------
252 selection_handler( GtkWidget
*WXUNUSED(widget
),
253 GtkSelectionData
*selection_data
,
254 guint
WXUNUSED(info
),
255 guint
WXUNUSED(time
),
256 gpointer signal_data
)
258 wxClipboard
* const clipboard
= wxTheClipboard
;
262 wxDataObject
* const data
= clipboard
->GTKGetDataObject();
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
)
272 guint timestamp
= GPOINTER_TO_UINT (signal_data
);
273 gtk_selection_data_set(selection_data
,
274 GDK_SELECTION_TYPE_INTEGER
,
276 (guchar
*)&(timestamp
),
278 wxLogTrace(TRACE_CLIPBOARD
,
279 _T("Clipboard TIMESTAMP requested, returning timestamp=%u"),
284 wxDataFormat
format( selection_data
->target
);
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
)
295 #endif // __WXDEBUG__
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
, _T("should be inside GetData()") );
337 const wxDataFormat
format(sel
.target
);
338 wxLogTrace(TRACE_CLIPBOARD
, _T("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 _T("got unsupported clipboard target") );
384 clipboard
->m_sink
->QueueEvent( event
);
385 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() );
397 #endif // __WXDEBUG__
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
++ )
403 const wxDataFormat
format(atoms
[i
]);
405 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
407 event
->AddFormat( format
);
410 clipboard
->m_sink
->QueueEvent( event
);
411 clipboard
->m_sink
.Release();
415 // ============================================================================
416 // wxClipboard implementation
417 // ============================================================================
419 // ----------------------------------------------------------------------------
420 // wxClipboard ctor/dtor
421 // ----------------------------------------------------------------------------
423 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
)
425 wxClipboard::wxClipboard()
431 m_receivedData
= NULL
;
433 m_formatSupported
= false;
434 m_targetRequested
= 0;
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
);
440 g_signal_connect (m_targetsWidget
, "selection_received",
441 G_CALLBACK (targets_selection_received
), this);
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
);
447 g_signal_connect (m_targetsWidgetAsync
, "selection_received",
448 G_CALLBACK (async_targets_selection_received
), this);
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
);
454 g_signal_connect (m_clipboardWidget
, "selection_received",
455 G_CALLBACK (selection_received
), this);
457 g_signal_connect (m_clipboardWidget
, "selection_clear_event",
458 G_CALLBACK (selection_clear_clip
), NULL
);
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
);
469 wxClipboard::~wxClipboard()
473 gtk_widget_destroy( m_clipboardWidget
);
474 gtk_widget_destroy( m_targetsWidget
);
477 // ----------------------------------------------------------------------------
478 // wxClipboard helper functions
479 // ----------------------------------------------------------------------------
481 GdkAtom
wxClipboard::GTKGetClipboardAtom() const
483 return m_usePrimary
? (GdkAtom
)GDK_SELECTION_PRIMARY
487 void wxClipboard::GTKClearData(Kind kind
)
489 wxDataObject
*&data
= Data(kind
);
497 bool wxClipboard::SetSelectionOwner(bool set
)
499 bool rc
= gtk_selection_owner_set
501 set
? m_clipboardWidget
: NULL
,
502 GTKGetClipboardAtom(),
503 (guint32
)GDK_CURRENT_TIME
508 wxLogTrace(TRACE_CLIPBOARD
, _T("Failed to %sset selection owner"),
509 set
? _T("") : _T("un"));
515 void wxClipboard::AddSupportedTarget(GdkAtom atom
)
517 gtk_selection_add_target
520 GTKGetClipboardAtom(),
522 0 // info (same as client data) unused
526 bool wxClipboard::IsSupportedAsync(wxEvtHandler
*sink
)
529 return false; // currently busy, come back later
531 wxCHECK_MSG( sink
, false, wxT("no sink given") );
534 gtk_selection_convert( m_targetsWidgetAsync
,
535 GTKGetClipboardAtom(),
537 (guint32
) GDK_CURRENT_TIME
);
542 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
)
544 wxCHECK_MSG( format
, false, wxT("invalid clipboard format") );
546 wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"),
547 format
.GetId().c_str());
549 // these variables will be used by our GTKOnTargetReceived()
550 m_targetRequested
= format
;
551 m_formatSupported
= false;
553 // block until m_formatSupported is set from targets_selection_received
556 wxClipboardSync
sync(*this);
558 gtk_selection_convert( m_targetsWidget
,
559 GTKGetClipboardAtom(),
561 (guint32
) GDK_CURRENT_TIME
);
564 return m_formatSupported
;
567 // ----------------------------------------------------------------------------
568 // wxClipboard public API implementation
569 // ----------------------------------------------------------------------------
571 void wxClipboard::Clear()
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
678 const size_t count
= data
.GetFormatCount();
679 wxDataFormatArray
formats(new wxDataFormat
[count
]);
680 data
.GetAllFormats(formats
.get());
682 for ( size_t i
= 0; i
< count
; i
++ )
684 const wxDataFormat
format(formats
[i
]);
686 // is this format supported by clipboard ?
687 if ( !DoIsSupported(format
) )
690 wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"),
691 format
.GetId().c_str());
693 // these variables will be used by our GTKOnSelectionReceived()
694 m_receivedData
= &data
;
695 m_formatSupported
= false;
698 wxClipboardSync
sync(*this);
700 gtk_selection_convert(m_clipboardWidget
,
701 GTKGetClipboardAtom(),
703 (guint32
) GDK_CURRENT_TIME
);
704 } // wait until we get the results
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)
717 if ( format
!= wxDF_UNICODETEXT
|| data
.GetDataSize(format
) > 0 )
719 if ( format
!= wxDF_TEXT
|| data
.GetDataSize(format
) > 1 )
720 #endif // UNICODE / !UNICODE
722 wxCHECK_MSG( m_formatSupported
, false,
723 wxT("error retrieving data from clipboard") );
729 wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found"));
734 #endif // wxUSE_CLIPBOARD