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") );
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() );
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
++ )
158 const wxDataFormat
format(atoms
[i
]);
160 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
162 if ( clipboard
->GTKOnTargetReceived(format
) )
168 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat
& format
)
170 if ( format
!= m_targetRequested
)
173 m_formatSupported
= true;
177 //-----------------------------------------------------------------------------
178 // "selection_received" for the actual data
179 //-----------------------------------------------------------------------------
183 selection_received( GtkWidget
*WXUNUSED(widget
),
184 GtkSelectionData
*selection_data
,
185 guint32
WXUNUSED(time
),
186 wxClipboard
*clipboard
)
191 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
193 if ( !selection_data
|| selection_data
->length
<= 0 )
196 clipboard
->GTKOnSelectionReceived(*selection_data
);
200 //-----------------------------------------------------------------------------
202 //-----------------------------------------------------------------------------
206 selection_clear_clip( GtkWidget
*WXUNUSED(widget
), GdkEventSelection
*event
)
208 wxClipboard
* const clipboard
= wxTheClipboard
;
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)
217 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress
, clipboard
);
219 wxClipboard::Kind kind
;
220 if (event
->selection
== GDK_SELECTION_PRIMARY
)
222 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost primary selection" ));
224 kind
= wxClipboard::Primary
;
226 else if (event
->selection
== g_clipboardAtom
)
228 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost clipboard" ));
230 kind
= wxClipboard::Clipboard
;
232 else // some other selection, we're not concerned
237 // the clipboard is no longer in our hands, we don't need data any more
238 clipboard
->GTKClearData(kind
);
244 //-----------------------------------------------------------------------------
245 // selection handler for supplying data
246 //-----------------------------------------------------------------------------
250 selection_handler( GtkWidget
*WXUNUSED(widget
),
251 GtkSelectionData
*selection_data
,
252 guint
WXUNUSED(info
),
253 guint
WXUNUSED(time
),
254 gpointer signal_data
)
256 wxClipboard
* const clipboard
= wxTheClipboard
;
260 wxDataObject
* const data
= clipboard
->GTKGetDataObject();
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
)
270 guint timestamp
= GPOINTER_TO_UINT (signal_data
);
271 gtk_selection_data_set(selection_data
,
272 GDK_SELECTION_TYPE_INTEGER
,
274 (guchar
*)&(timestamp
),
276 wxLogTrace(TRACE_CLIPBOARD
,
277 _T("Clipboard TIMESTAMP requested, returning timestamp=%u"),
282 wxDataFormat
format( selection_data
->target
);
284 wxLogTrace(TRACE_CLIPBOARD
,
285 _T("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
)
293 if ( !data
->IsSupportedFormat( format
) )
296 int size
= data
->GetDataSize( format
);
300 wxCharBuffer
buf(size
- 1); // it adds 1 internally (for NUL)
302 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
303 if ( !data
->GetDataHere(format
, buf
.data()) )
306 // use UTF8_STRING format if requested in Unicode build but just plain
307 // STRING one in ANSI or if explicitly asked in Unicode
309 if (format
== wxDataFormat(wxDF_UNICODETEXT
))
311 gtk_selection_data_set_text(
313 (const gchar
*)buf
.data(),
317 #endif // wxUSE_UNICODE
319 gtk_selection_data_set(
321 GDK_SELECTION_TYPE_STRING
,
323 (const guchar
*)buf
.data(),
329 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData
& sel
)
331 wxCHECK_RET( m_receivedData
, _T("should be inside GetData()") );
333 const wxDataFormat
format(sel
.target
);
334 wxLogTrace(TRACE_CLIPBOARD
, _T("Received selection %s"),
335 format
.GetId().c_str());
337 if ( !m_receivedData
->IsSupportedFormat(format
) )
340 m_receivedData
->SetData(format
, sel
.length
, sel
.data
);
341 m_formatSupported
= true;
344 //-----------------------------------------------------------------------------
345 // asynchronous "selection_received" for targets
346 //-----------------------------------------------------------------------------
350 async_targets_selection_received( GtkWidget
*WXUNUSED(widget
),
351 GtkSelectionData
*selection_data
,
352 guint32
WXUNUSED(time
),
353 wxClipboard
*clipboard
)
355 if ( !clipboard
) // Assert?
358 if (!clipboard
->m_sink
)
361 wxClipboardEvent
*event
= new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED
);
362 event
->SetEventObject( clipboard
);
364 if ( !selection_data
|| selection_data
->length
<= 0 )
366 clipboard
->m_sink
->QueueEvent( event
);
367 clipboard
->m_sink
.Release();
371 // make sure we got the data in the correct form
372 GdkAtom type
= selection_data
->type
;
373 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
375 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
377 wxLogTrace( TRACE_CLIPBOARD
,
378 _T("got unsupported clipboard target") );
380 clipboard
->m_sink
->QueueEvent( event
);
381 clipboard
->m_sink
.Release();
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() );
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
++ )
397 const wxDataFormat
format(atoms
[i
]);
399 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
401 event
->AddFormat( format
);
404 clipboard
->m_sink
->QueueEvent( event
);
405 clipboard
->m_sink
.Release();
409 // ============================================================================
410 // wxClipboard implementation
411 // ============================================================================
413 // ----------------------------------------------------------------------------
414 // wxClipboard ctor/dtor
415 // ----------------------------------------------------------------------------
417 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
)
419 wxClipboard::wxClipboard()
425 m_receivedData
= NULL
;
427 m_formatSupported
= false;
428 m_targetRequested
= 0;
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
);
434 g_signal_connect (m_targetsWidget
, "selection_received",
435 G_CALLBACK (targets_selection_received
), this);
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
);
441 g_signal_connect (m_targetsWidgetAsync
, "selection_received",
442 G_CALLBACK (async_targets_selection_received
), this);
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
);
448 g_signal_connect (m_clipboardWidget
, "selection_received",
449 G_CALLBACK (selection_received
), this);
451 g_signal_connect (m_clipboardWidget
, "selection_clear_event",
452 G_CALLBACK (selection_clear_clip
), NULL
);
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
);
463 wxClipboard::~wxClipboard()
467 gtk_widget_destroy( m_clipboardWidget
);
468 gtk_widget_destroy( m_targetsWidget
);
471 // ----------------------------------------------------------------------------
472 // wxClipboard helper functions
473 // ----------------------------------------------------------------------------
475 GdkAtom
wxClipboard::GTKGetClipboardAtom() const
477 return m_usePrimary
? (GdkAtom
)GDK_SELECTION_PRIMARY
481 void wxClipboard::GTKClearData(Kind kind
)
483 wxDataObject
*&data
= Data(kind
);
491 bool wxClipboard::SetSelectionOwner(bool set
)
493 bool rc
= gtk_selection_owner_set
495 set
? m_clipboardWidget
: NULL
,
496 GTKGetClipboardAtom(),
497 (guint32
)GDK_CURRENT_TIME
502 wxLogTrace(TRACE_CLIPBOARD
, _T("Failed to %sset selection owner"),
503 set
? _T("") : _T("un"));
509 void wxClipboard::AddSupportedTarget(GdkAtom atom
)
511 gtk_selection_add_target
514 GTKGetClipboardAtom(),
516 0 // info (same as client data) unused
520 bool wxClipboard::IsSupportedAsync(wxEvtHandler
*sink
)
523 return false; // currently busy, come back later
525 wxCHECK_MSG( sink
, false, wxT("no sink given") );
528 gtk_selection_convert( m_targetsWidgetAsync
,
529 GTKGetClipboardAtom(),
531 (guint32
) GDK_CURRENT_TIME
);
536 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
)
538 wxCHECK_MSG( format
, false, wxT("invalid clipboard format") );
540 wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"),
541 format
.GetId().c_str());
543 // these variables will be used by our GTKOnTargetReceived()
544 m_targetRequested
= format
;
545 m_formatSupported
= false;
547 // block until m_formatSupported is set from targets_selection_received
550 wxClipboardSync
sync(*this);
552 gtk_selection_convert( m_targetsWidget
,
553 GTKGetClipboardAtom(),
555 (guint32
) GDK_CURRENT_TIME
);
558 return m_formatSupported
;
561 // ----------------------------------------------------------------------------
562 // wxClipboard public API implementation
563 // ----------------------------------------------------------------------------
565 void wxClipboard::Clear()
567 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
568 m_clipboardWidget
->window
)
570 wxClipboardSync
sync(*this);
572 // this will result in selection_clear_clip callback being called and
573 // it will free our data
574 SetSelectionOwner(false);
577 m_targetRequested
= 0;
578 m_formatSupported
= false;
581 bool wxClipboard::Open()
583 wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") );
590 bool wxClipboard::SetData( wxDataObject
*data
)
592 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
594 wxCHECK_MSG( data
, false, wxT("data is invalid") );
598 return AddData( data
);
601 bool wxClipboard::AddData( wxDataObject
*data
)
603 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
605 wxCHECK_MSG( data
, false, wxT("data is invalid") );
607 // we can only store one wxDataObject so clear the old one
612 // get formats from wxDataObjects
613 const size_t count
= data
->GetFormatCount();
614 wxDataFormatArray
formats(new wxDataFormat
[count
]);
615 data
->GetAllFormats(formats
.get());
617 // always provide TIMESTAMP as a target, see comments in selection_handler
619 AddSupportedTarget(g_timestampAtom
);
621 for ( size_t i
= 0; i
< count
; i
++ )
623 const wxDataFormat
format(formats
[i
]);
625 wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"),
626 format
.GetId().c_str());
628 AddSupportedTarget(format
);
631 g_signal_connect (m_clipboardWidget
, "selection_get",
632 G_CALLBACK (selection_handler
),
633 GUINT_TO_POINTER (gtk_get_current_event_time()) );
635 // tell the world we offer clipboard data
636 return SetSelectionOwner();
639 void wxClipboard::Close()
641 wxCHECK_RET( m_open
, wxT("clipboard not open") );
646 bool wxClipboard::IsOpened() const
651 bool wxClipboard::IsSupported( const wxDataFormat
& format
)
653 if ( DoIsSupported(format
) )
657 if ( format
== wxDF_UNICODETEXT
)
659 // also with plain STRING format
660 return DoIsSupported(g_altTextAtom
);
662 #endif // wxUSE_UNICODE
667 bool wxClipboard::GetData( wxDataObject
& data
)
669 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
671 // get all supported formats from wxDataObjects
672 const size_t count
= data
.GetFormatCount();
673 wxDataFormatArray
formats(new wxDataFormat
[count
]);
674 data
.GetAllFormats(formats
.get());
676 for ( size_t i
= 0; i
< count
; i
++ )
678 const wxDataFormat
format(formats
[i
]);
680 // is this format supported by clipboard ?
681 if ( !DoIsSupported(format
) )
684 wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"),
685 format
.GetId().c_str());
687 // these variables will be used by our GTKOnSelectionReceived()
688 m_receivedData
= &data
;
689 m_formatSupported
= false;
692 wxClipboardSync
sync(*this);
694 gtk_selection_convert(m_clipboardWidget
,
695 GTKGetClipboardAtom(),
697 (guint32
) GDK_CURRENT_TIME
);
698 } // wait until we get the results
701 Normally this is a true error as we checked for the presence of such
702 data before, but there are applications that may return an empty
703 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
704 which would produce a false error message here, so we check for the
705 size of the string first. With ANSI, GetDataSize returns an extra
706 value (for the closing null?), with unicode, the exact number of
707 tokens is given (that is more than 1 for non-ASCII characters)
708 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
711 if ( format
!= wxDF_UNICODETEXT
|| data
.GetDataSize(format
) > 0 )
713 if ( format
!= wxDF_TEXT
|| data
.GetDataSize(format
) > 1 )
714 #endif // UNICODE / !UNICODE
716 wxCHECK_MSG( m_formatSupported
, false,
717 wxT("error retrieving data from clipboard") );
723 wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found"));
728 #endif // wxUSE_CLIPBOARD