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"
29 #include "wx/dataobj.h"
32 #include "wx/scopedarray.h"
33 #include "wx/scopeguard.h"
35 #include "wx/gtk/private.h"
37 typedef wxScopedArray
<wxDataFormat
> wxDataFormatArray
;
39 // ----------------------------------------------------------------------------
41 // ----------------------------------------------------------------------------
43 static GdkAtom g_clipboardAtom
= 0;
44 static GdkAtom g_targetsAtom
= 0;
45 static GdkAtom g_timestampAtom
= 0;
48 extern GdkAtom g_altTextAtom
;
51 // the trace mask we use with wxLogTrace() - call
52 // wxLog::AddTraceMask(TRACE_CLIPBOARD) to enable the trace messages from here
53 // (there will be a *lot* of them!)
54 #define TRACE_CLIPBOARD _T("clipboard")
56 // ----------------------------------------------------------------------------
57 // wxClipboardSync: used to perform clipboard operations synchronously
58 // ----------------------------------------------------------------------------
60 // constructing this object on stack will wait wait until the latest clipboard
61 // operation is finished on block exit
63 // notice that there can be no more than one such object alive at any moment,
64 // i.e. reentrancies are not allowed
68 wxClipboardSync(wxClipboard
& clipboard
)
70 wxASSERT_MSG( !ms_clipboard
, _T("reentrancy in clipboard code") );
71 ms_clipboard
= &clipboard
;
76 while ( ms_clipboard
)
80 // this method must be called by GTK+ callbacks to indicate that we got the
81 // result for our clipboard operation
82 static void OnDone(wxClipboard
* WXUNUSED_UNLESS_DEBUG(clipboard
))
84 wxASSERT_MSG( clipboard
== ms_clipboard
,
85 _T("got notification for alien clipboard") );
90 // this method should be called if it's possible that no async clipboard
91 // operation is currently in progress (like it can be the case when the
92 // clipboard is cleared but not because we asked about it), it should only
93 // be called if such situation is expected -- otherwise call OnDone() which
94 // would assert in this case
95 static void OnDoneIfInProgress(wxClipboard
*clipboard
)
102 static wxClipboard
*ms_clipboard
;
104 DECLARE_NO_COPY_CLASS(wxClipboardSync
)
107 wxClipboard
*wxClipboardSync::ms_clipboard
= NULL
;
109 // ============================================================================
110 // clipboard callbacks implementation
111 // ============================================================================
113 //-----------------------------------------------------------------------------
114 // "selection_received" for targets
115 //-----------------------------------------------------------------------------
119 targets_selection_received( GtkWidget
*WXUNUSED(widget
),
120 GtkSelectionData
*selection_data
,
121 guint32
WXUNUSED(time
),
122 wxClipboard
*clipboard
)
127 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
129 if ( !selection_data
|| selection_data
->length
<= 0 )
132 // make sure we got the data in the correct form
133 GdkAtom type
= selection_data
->type
;
134 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
136 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
138 wxLogTrace( TRACE_CLIPBOARD
,
139 _T("got unsupported clipboard target") );
146 // it's not really a format, of course, but we can reuse its GetId() method
147 // to format this atom as string
148 wxDataFormat
clip(selection_data
->selection
);
149 wxLogTrace( TRACE_CLIPBOARD
,
150 wxT("Received available formats for clipboard %s"),
151 clip
.GetId().c_str() );
152 #endif // __WXDEBUG__
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
);
285 wxLogTrace(TRACE_CLIPBOARD
,
286 _T("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
287 format
.GetId().c_str(),
288 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->target
))).c_str(),
289 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->type
))).c_str(),
290 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->selection
))).c_str(),
291 GPOINTER_TO_UINT( signal_data
)
293 #endif // __WXDEBUG__
295 if ( !data
->IsSupportedFormat( format
) )
298 int size
= data
->GetDataSize( format
);
302 wxCharBuffer
buf(size
- 1); // it adds 1 internally (for NUL)
304 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
305 if ( !data
->GetDataHere(format
, buf
.data()) )
308 // use UTF8_STRING format if requested in Unicode build but just plain
309 // STRING one in ANSI or if explicitly asked in Unicode
311 if (format
== wxDataFormat(wxDF_UNICODETEXT
))
313 gtk_selection_data_set_text(
315 (const gchar
*)buf
.data(),
319 #endif // wxUSE_UNICODE
321 gtk_selection_data_set(
323 GDK_SELECTION_TYPE_STRING
,
325 (const guchar
*)buf
.data(),
331 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData
& sel
)
333 wxCHECK_RET( m_receivedData
, _T("should be inside GetData()") );
335 const wxDataFormat
format(sel
.target
);
336 wxLogTrace(TRACE_CLIPBOARD
, _T("Received selection %s"),
337 format
.GetId().c_str());
339 if ( !m_receivedData
->IsSupportedFormat(format
) )
342 m_receivedData
->SetData(format
, sel
.length
, sel
.data
);
343 m_formatSupported
= true;
346 //-----------------------------------------------------------------------------
347 // asynchronous "selection_received" for targets
348 //-----------------------------------------------------------------------------
352 async_targets_selection_received( GtkWidget
*WXUNUSED(widget
),
353 GtkSelectionData
*selection_data
,
354 guint32
WXUNUSED(time
),
355 wxClipboard
*clipboard
)
357 if ( !clipboard
) // Assert?
360 if (!clipboard
->m_sink
)
363 wxClipboardEvent
*event
= new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED
);
364 event
->SetEventObject( clipboard
);
366 if ( !selection_data
|| selection_data
->length
<= 0 )
368 clipboard
->m_sink
->QueueEvent( event
);
369 clipboard
->m_sink
.Release();
373 // make sure we got the data in the correct form
374 GdkAtom type
= selection_data
->type
;
375 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
377 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
379 wxLogTrace( TRACE_CLIPBOARD
,
380 _T("got unsupported clipboard target") );
382 clipboard
->m_sink
->QueueEvent( event
);
383 clipboard
->m_sink
.Release();
389 // it's not really a format, of course, but we can reuse its GetId() method
390 // to format this atom as string
391 wxDataFormat
clip(selection_data
->selection
);
392 wxLogTrace( TRACE_CLIPBOARD
,
393 wxT("Received available formats for clipboard %s"),
394 clip
.GetId().c_str() );
395 #endif // __WXDEBUG__
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 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
, _T("Failed to %sset selection owner"),
507 set
? _T("") : _T("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 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
572 m_clipboardWidget
->window
)
574 wxClipboardSync
sync(*this);
576 // this will result in selection_clear_clip callback being called and
577 // it will free our data
578 SetSelectionOwner(false);
581 m_targetRequested
= 0;
582 m_formatSupported
= false;
585 bool wxClipboard::Open()
587 wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") );
594 bool wxClipboard::SetData( wxDataObject
*data
)
596 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
598 wxCHECK_MSG( data
, false, wxT("data is invalid") );
602 return AddData( data
);
605 bool wxClipboard::AddData( wxDataObject
*data
)
607 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
609 wxCHECK_MSG( data
, false, wxT("data is invalid") );
611 // we can only store one wxDataObject so clear the old one
616 // get formats from wxDataObjects
617 const size_t count
= data
->GetFormatCount();
618 wxDataFormatArray
formats(new wxDataFormat
[count
]);
619 data
->GetAllFormats(formats
.get());
621 // always provide TIMESTAMP as a target, see comments in selection_handler
623 AddSupportedTarget(g_timestampAtom
);
625 for ( size_t i
= 0; i
< count
; i
++ )
627 const wxDataFormat
format(formats
[i
]);
629 wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"),
630 format
.GetId().c_str());
632 AddSupportedTarget(format
);
635 g_signal_connect (m_clipboardWidget
, "selection_get",
636 G_CALLBACK (selection_handler
),
637 GUINT_TO_POINTER (gtk_get_current_event_time()) );
639 // tell the world we offer clipboard data
640 return SetSelectionOwner();
643 void wxClipboard::Close()
645 wxCHECK_RET( m_open
, wxT("clipboard not open") );
650 bool wxClipboard::IsOpened() const
655 bool wxClipboard::IsSupported( const wxDataFormat
& format
)
657 if ( DoIsSupported(format
) )
661 if ( format
== wxDF_UNICODETEXT
)
663 // also with plain STRING format
664 return DoIsSupported(g_altTextAtom
);
666 #endif // wxUSE_UNICODE
671 bool wxClipboard::GetData( wxDataObject
& data
)
673 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
675 // get all supported formats from wxDataObjects
676 const size_t count
= data
.GetFormatCount();
677 wxDataFormatArray
formats(new wxDataFormat
[count
]);
678 data
.GetAllFormats(formats
.get());
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