1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/clipbrd.cpp
3 // Purpose: wxClipboard implementation for wxGTK
4 // Author: Robert Roebling, Vadim Zeitlin
5 // Copyright: (c) 1998 Robert Roebling
6 // (c) 2007 Vadim Zeitlin
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
10 // ============================================================================
12 // ============================================================================
14 // ----------------------------------------------------------------------------
16 // ----------------------------------------------------------------------------
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
23 #include "wx/clipbrd.h"
29 #include "wx/dataobj.h"
32 #include "wx/scopedarray.h"
33 #include "wx/scopeguard.h"
34 #include "wx/evtloop.h"
36 #include "wx/gtk/private.h"
38 typedef wxScopedArray
<wxDataFormat
> wxDataFormatArray
;
40 // ----------------------------------------------------------------------------
42 // ----------------------------------------------------------------------------
44 static GdkAtom g_clipboardAtom
= 0;
45 static GdkAtom g_targetsAtom
= 0;
46 static GdkAtom g_timestampAtom
= 0;
49 extern GdkAtom g_altTextAtom
;
52 // the trace mask we use with wxLogTrace() - call
53 // wxLog::AddTraceMask(TRACE_CLIPBOARD) to enable the trace messages from here
54 // (there will be a *lot* of them!)
55 #define TRACE_CLIPBOARD wxT("clipboard")
57 // ----------------------------------------------------------------------------
58 // wxClipboardSync: used to perform clipboard operations synchronously
59 // ----------------------------------------------------------------------------
61 // constructing this object on stack will wait wait until the latest clipboard
62 // operation is finished on block exit
64 // notice that there can be no more than one such object alive at any moment,
65 // i.e. reentrancies are not allowed
69 wxClipboardSync(wxClipboard
& clipboard
)
71 wxASSERT_MSG( !ms_clipboard
, wxT("reentrancy in clipboard code") );
72 ms_clipboard
= &clipboard
;
77 #if wxUSE_CONSOLE_EVENTLOOP
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
);
138 const int selection_data_length
= gtk_selection_data_get_length(selection_data
);
139 if (selection_data_length
<= 0)
142 // make sure we got the data in the correct form
143 GdkAtom type
= gtk_selection_data_get_data_type(selection_data
);
144 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
146 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
148 wxLogTrace( TRACE_CLIPBOARD
,
149 wxT("got unsupported clipboard target") );
155 // it's not really a format, of course, but we can reuse its GetId() method
156 // to format this atom as string
157 wxDataFormat
clip(gtk_selection_data_get_selection(selection_data
));
158 wxLogTrace( TRACE_CLIPBOARD
,
159 wxT("Received available formats for clipboard %s"),
160 clip
.GetId().c_str() );
162 // the atoms we received, holding a list of targets (= formats)
163 const GdkAtom
* const atoms
= (GdkAtom
*)gtk_selection_data_get_data(selection_data
);
164 for (size_t i
= 0; i
< selection_data_length
/ sizeof(GdkAtom
); i
++)
166 const wxDataFormat
format(atoms
[i
]);
168 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
170 if ( clipboard
->GTKOnTargetReceived(format
) )
176 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat
& format
)
178 if ( format
!= m_targetRequested
)
181 m_formatSupported
= true;
185 //-----------------------------------------------------------------------------
186 // "selection_received" for the actual data
187 //-----------------------------------------------------------------------------
191 selection_received( GtkWidget
*WXUNUSED(widget
),
192 GtkSelectionData
*selection_data
,
193 guint32
WXUNUSED(time
),
194 wxClipboard
*clipboard
)
199 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
201 if (!selection_data
|| gtk_selection_data_get_length(selection_data
) <= 0)
204 clipboard
->GTKOnSelectionReceived(*selection_data
);
208 //-----------------------------------------------------------------------------
210 //-----------------------------------------------------------------------------
214 selection_clear_clip( GtkWidget
*WXUNUSED(widget
), GdkEventSelection
*event
)
216 wxClipboard
* const clipboard
= wxTheClipboard
;
220 // notice the use of OnDoneIfInProgress() here instead of just OnDone():
221 // it's perfectly possible that we're receiving this notification from GTK+
222 // even though we hadn't cleared the clipboard ourselves but because
223 // another application (or even another window in the same program)
225 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress
, clipboard
);
227 wxClipboard::Kind kind
;
228 if (event
->selection
== GDK_SELECTION_PRIMARY
)
230 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost primary selection" ));
232 kind
= wxClipboard::Primary
;
234 else if (event
->selection
== g_clipboardAtom
)
236 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost clipboard" ));
238 kind
= wxClipboard::Clipboard
;
240 else // some other selection, we're not concerned
245 // the clipboard is no longer in our hands, we don't need data any more
246 clipboard
->GTKClearData(kind
);
252 //-----------------------------------------------------------------------------
253 // selection handler for supplying data
254 //-----------------------------------------------------------------------------
258 selection_handler( GtkWidget
*WXUNUSED(widget
),
259 GtkSelectionData
*selection_data
,
260 guint
WXUNUSED(info
),
261 guint
WXUNUSED(time
),
262 gpointer signal_data
)
264 wxClipboard
* const clipboard
= wxTheClipboard
;
268 wxDataObject
* const data
= clipboard
->GTKGetDataObject(
269 gtk_selection_data_get_selection(selection_data
));
273 // ICCCM says that TIMESTAMP is a required atom.
274 // In particular, it satisfies Klipper, which polls
275 // TIMESTAMP to see if the clipboards content has changed.
276 // It shall return the time which was used to set the data.
277 if (gtk_selection_data_get_target(selection_data
) == g_timestampAtom
)
279 guint timestamp
= GPOINTER_TO_UINT (signal_data
);
280 gtk_selection_data_set(selection_data
,
281 GDK_SELECTION_TYPE_INTEGER
,
283 (guchar
*)&(timestamp
),
285 wxLogTrace(TRACE_CLIPBOARD
,
286 wxT("Clipboard TIMESTAMP requested, returning timestamp=%u"),
291 wxDataFormat
format(gtk_selection_data_get_target(selection_data
));
293 wxLogTrace(TRACE_CLIPBOARD
,
294 wxT("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
295 format
.GetId().c_str(),
296 wxString::FromAscii(wxGtkString(gdk_atom_name(gtk_selection_data_get_target(selection_data
)))).c_str(),
297 wxString::FromAscii(wxGtkString(gdk_atom_name(gtk_selection_data_get_data_type(selection_data
)))).c_str(),
298 wxString::FromAscii(wxGtkString(gdk_atom_name(gtk_selection_data_get_selection(selection_data
)))).c_str(),
299 GPOINTER_TO_UINT( signal_data
)
302 if ( !data
->IsSupportedFormat( format
) )
305 int size
= data
->GetDataSize( format
);
309 wxCharBuffer
buf(size
- 1); // it adds 1 internally (for NUL)
311 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
312 if ( !data
->GetDataHere(format
, buf
.data()) )
315 // use UTF8_STRING format if requested in Unicode build but just plain
316 // STRING one in ANSI or if explicitly asked in Unicode
318 if (format
== wxDataFormat(wxDF_UNICODETEXT
))
320 gtk_selection_data_set_text(
322 (const gchar
*)buf
.data(),
326 #endif // wxUSE_UNICODE
328 gtk_selection_data_set(
330 format
.GetFormatId(),
332 (const guchar
*)buf
.data(),
338 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData
& sel
)
340 wxCHECK_RET( m_receivedData
, wxT("should be inside GetData()") );
342 const wxDataFormat
format(gtk_selection_data_get_target(const_cast<GtkSelectionData
*>(&sel
)));
343 wxLogTrace(TRACE_CLIPBOARD
, wxT("Received selection %s"),
344 format
.GetId().c_str());
346 if ( !m_receivedData
->IsSupportedFormat(format
, wxDataObject::Set
) )
349 m_receivedData
->SetData(format
,
350 gtk_selection_data_get_length(const_cast<GtkSelectionData
*>(&sel
)),
351 gtk_selection_data_get_data(const_cast<GtkSelectionData
*>(&sel
)));
352 m_formatSupported
= true;
355 //-----------------------------------------------------------------------------
356 // asynchronous "selection_received" for targets
357 //-----------------------------------------------------------------------------
361 async_targets_selection_received( GtkWidget
*WXUNUSED(widget
),
362 GtkSelectionData
*selection_data
,
363 guint32
WXUNUSED(time
),
364 wxClipboard
*clipboard
)
366 if ( !clipboard
) // Assert?
369 if (!clipboard
->m_sink
)
372 wxClipboardEvent
*event
= new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED
);
373 event
->SetEventObject( clipboard
);
375 int selection_data_length
= 0;
377 selection_data_length
= gtk_selection_data_get_length(selection_data
);
379 if (selection_data_length
<= 0)
381 clipboard
->m_sink
->QueueEvent( event
);
382 clipboard
->m_sink
.Release();
386 // make sure we got the data in the correct form
387 GdkAtom type
= gtk_selection_data_get_data_type(selection_data
);
388 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
390 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
392 wxLogTrace( TRACE_CLIPBOARD
,
393 wxT("got unsupported clipboard target") );
395 clipboard
->m_sink
->QueueEvent( event
);
396 clipboard
->m_sink
.Release();
401 // it's not really a format, of course, but we can reuse its GetId() method
402 // to format this atom as string
403 wxDataFormat
clip(gtk_selection_data_get_selection(selection_data
));
404 wxLogTrace( TRACE_CLIPBOARD
,
405 wxT("Received available formats for clipboard %s"),
406 clip
.GetId().c_str() );
408 // the atoms we received, holding a list of targets (= formats)
409 const GdkAtom
* const atoms
= (GdkAtom
*)gtk_selection_data_get_data(selection_data
);
410 for (size_t i
= 0; i
< selection_data_length
/ sizeof(GdkAtom
); i
++)
412 const wxDataFormat
format(atoms
[i
]);
414 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
416 event
->AddFormat( format
);
419 clipboard
->m_sink
->QueueEvent( event
);
420 clipboard
->m_sink
.Release();
424 // ============================================================================
425 // wxClipboard implementation
426 // ============================================================================
428 // ----------------------------------------------------------------------------
429 // wxClipboard ctor/dtor
430 // ----------------------------------------------------------------------------
432 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
)
434 wxClipboard::wxClipboard()
436 m_idSelectionGetHandler
= 0;
442 m_receivedData
= NULL
;
444 m_formatSupported
= false;
445 m_targetRequested
= 0;
447 // we use m_targetsWidget to query what formats are available
448 m_targetsWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
449 gtk_widget_realize( m_targetsWidget
);
451 g_signal_connect (m_targetsWidget
, "selection_received",
452 G_CALLBACK (targets_selection_received
), this);
454 // we use m_targetsWidgetAsync to query what formats are available asynchronously
455 m_targetsWidgetAsync
= gtk_window_new( GTK_WINDOW_POPUP
);
456 gtk_widget_realize( m_targetsWidgetAsync
);
458 g_signal_connect (m_targetsWidgetAsync
, "selection_received",
459 G_CALLBACK (async_targets_selection_received
), this);
461 // we use m_clipboardWidget to get and to offer data
462 m_clipboardWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
463 gtk_widget_realize( m_clipboardWidget
);
465 g_signal_connect (m_clipboardWidget
, "selection_received",
466 G_CALLBACK (selection_received
), this);
468 g_signal_connect (m_clipboardWidget
, "selection_clear_event",
469 G_CALLBACK (selection_clear_clip
), NULL
);
471 // initialize atoms we use if not done yet
472 if ( !g_clipboardAtom
)
473 g_clipboardAtom
= gdk_atom_intern( "CLIPBOARD", FALSE
);
474 if ( !g_targetsAtom
)
475 g_targetsAtom
= gdk_atom_intern ("TARGETS", FALSE
);
476 if ( !g_timestampAtom
)
477 g_timestampAtom
= gdk_atom_intern ("TIMESTAMP", FALSE
);
480 wxClipboard::~wxClipboard()
484 gtk_widget_destroy( m_clipboardWidget
);
485 gtk_widget_destroy( m_targetsWidget
);
488 // ----------------------------------------------------------------------------
489 // wxClipboard helper functions
490 // ----------------------------------------------------------------------------
492 GdkAtom
wxClipboard::GTKGetClipboardAtom() const
494 return m_usePrimary
? (GdkAtom
)GDK_SELECTION_PRIMARY
498 void wxClipboard::GTKClearData(Kind kind
)
500 wxDataObject
*&data
= Data(kind
);
504 bool wxClipboard::SetSelectionOwner(bool set
)
506 bool rc
= gtk_selection_owner_set
508 set
? m_clipboardWidget
: NULL
,
509 GTKGetClipboardAtom(),
510 (guint32
)GDK_CURRENT_TIME
515 wxLogTrace(TRACE_CLIPBOARD
, wxT("Failed to %sset selection owner"),
516 set
? wxT("") : wxT("un"));
522 void wxClipboard::AddSupportedTarget(GdkAtom atom
)
524 gtk_selection_add_target
527 GTKGetClipboardAtom(),
529 0 // info (same as client data) unused
533 bool wxClipboard::IsSupportedAsync(wxEvtHandler
*sink
)
536 return false; // currently busy, come back later
538 wxCHECK_MSG( sink
, false, wxT("no sink given") );
541 gtk_selection_convert( m_targetsWidgetAsync
,
542 GTKGetClipboardAtom(),
544 (guint32
) GDK_CURRENT_TIME
);
549 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
)
551 wxCHECK_MSG( format
, false, wxT("invalid clipboard format") );
553 wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"),
554 format
.GetId().c_str());
556 // these variables will be used by our GTKOnTargetReceived()
557 m_targetRequested
= format
;
558 m_formatSupported
= false;
560 // block until m_formatSupported is set from targets_selection_received
563 wxClipboardSync
sync(*this);
565 gtk_selection_convert( m_targetsWidget
,
566 GTKGetClipboardAtom(),
568 (guint32
) GDK_CURRENT_TIME
);
571 return m_formatSupported
;
574 // ----------------------------------------------------------------------------
575 // wxClipboard public API implementation
576 // ----------------------------------------------------------------------------
578 void wxClipboard::Clear()
580 gtk_selection_clear_targets( m_clipboardWidget
, GTKGetClipboardAtom() );
582 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
583 gtk_widget_get_window(m_clipboardWidget
) )
585 wxClipboardSync
sync(*this);
587 // this will result in selection_clear_clip callback being called and
588 // it will free our data
589 SetSelectionOwner(false);
592 m_targetRequested
= 0;
593 m_formatSupported
= false;
596 bool wxClipboard::Open()
598 wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") );
605 bool wxClipboard::SetData( wxDataObject
*data
)
607 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
609 wxCHECK_MSG( data
, false, wxT("data is invalid") );
613 return AddData( data
);
616 bool wxClipboard::AddData( wxDataObject
*data
)
618 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
620 wxCHECK_MSG( data
, false, wxT("data is invalid") );
622 // we can only store one wxDataObject so clear the old one
627 // get formats from wxDataObjects
628 const size_t count
= data
->GetFormatCount();
629 wxDataFormatArray
formats(new wxDataFormat
[count
]);
630 data
->GetAllFormats(formats
.get());
632 // always provide TIMESTAMP as a target, see comments in selection_handler
634 AddSupportedTarget(g_timestampAtom
);
636 for ( size_t i
= 0; i
< count
; i
++ )
638 const wxDataFormat
format(formats
[i
]);
640 wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"),
641 format
.GetId().c_str());
643 AddSupportedTarget(format
);
646 if ( !m_idSelectionGetHandler
)
648 m_idSelectionGetHandler
= g_signal_connect (
649 m_clipboardWidget
, "selection_get",
650 G_CALLBACK (selection_handler
),
651 GUINT_TO_POINTER (gtk_get_current_event_time()) );
654 // tell the world we offer clipboard data
655 return SetSelectionOwner();
658 void wxClipboard::Close()
660 wxCHECK_RET( m_open
, wxT("clipboard not open") );
665 bool wxClipboard::IsOpened() const
670 bool wxClipboard::IsSupported( const wxDataFormat
& format
)
672 if ( DoIsSupported(format
) )
676 if ( format
== wxDF_UNICODETEXT
)
678 // also with plain STRING format
679 return DoIsSupported(g_altTextAtom
);
681 #endif // wxUSE_UNICODE
686 bool wxClipboard::GetData( wxDataObject
& data
)
688 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
690 // get all supported formats from wxDataObjects: notice that we are setting
691 // the object data, so we need them in "Set" direction
692 const size_t count
= data
.GetFormatCount(wxDataObject::Set
);
693 wxDataFormatArray
formats(new wxDataFormat
[count
]);
694 data
.GetAllFormats(formats
.get(), wxDataObject::Set
);
696 for ( size_t i
= 0; i
< count
; i
++ )
698 const wxDataFormat
format(formats
[i
]);
700 // is this format supported by clipboard ?
701 if ( !DoIsSupported(format
) )
704 wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"),
705 format
.GetId().c_str());
707 // these variables will be used by our GTKOnSelectionReceived()
708 m_receivedData
= &data
;
709 m_formatSupported
= false;
712 wxClipboardSync
sync(*this);
714 gtk_selection_convert(m_clipboardWidget
,
715 GTKGetClipboardAtom(),
717 (guint32
) GDK_CURRENT_TIME
);
718 } // wait until we get the results
721 Normally this is a true error as we checked for the presence of such
722 data before, but there are applications that may return an empty
723 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
724 which would produce a false error message here, so we check for the
725 size of the string first. With ANSI, GetDataSize returns an extra
726 value (for the closing null?), with unicode, the exact number of
727 tokens is given (that is more than 1 for non-ASCII characters)
728 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
731 if ( format
!= wxDF_UNICODETEXT
|| data
.GetDataSize(format
) > 0 )
733 if ( format
!= wxDF_TEXT
|| data
.GetDataSize(format
) > 1 )
734 #endif // UNICODE / !UNICODE
736 wxCHECK_MSG( m_formatSupported
, false,
737 wxT("error retrieving data from clipboard") );
743 wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found"));
748 wxDataObject
* wxClipboard::GTKGetDataObject( GdkAtom atom
)
750 if ( atom
== GDK_NONE
)
753 if ( atom
== GDK_SELECTION_PRIMARY
)
755 wxLogTrace(TRACE_CLIPBOARD
, wxT("Primary selection requested" ));
757 return Data( wxClipboard::Primary
);
759 else if ( atom
== g_clipboardAtom
)
761 wxLogTrace(TRACE_CLIPBOARD
, wxT("Clipboard data requested" ));
763 return Data( wxClipboard::Clipboard
);
765 else // some other selection, we're not concerned
767 return (wxDataObject
*)NULL
;
771 #endif // wxUSE_CLIPBOARD