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/ptr_scpd.h"
33 #include "wx/scopeguard.h"
35 #include "wx/gtk/private.h"
37 wxDECLARE_SCOPED_ARRAY(wxDataFormat
, wxDataFormatArray
)
38 wxDEFINE_SCOPED_ARRAY(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 _T("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
, _T("reentrancy in clipboard code") );
72 ms_clipboard
= &clipboard
;
77 while ( ms_clipboard
)
81 // this method must be called by GTK+ callbacks to indicate that we got the
82 // result for our clipboard operation
83 static void OnDone(wxClipboard
*clipboard
)
85 wxASSERT_MSG( clipboard
== ms_clipboard
,
86 _T("got notification for alien clipboard") );
91 // this method should be called if it's possible that no async clipboard
92 // operation is currently in progress (like it can be the case when the
93 // clipboard is cleared but not because we asked about it), it should only
94 // be called if such situation is expected -- otherwise call OnDone() which
95 // would assert in this case
96 static void OnDoneIfInProgress(wxClipboard
*clipboard
)
103 static wxClipboard
*ms_clipboard
;
105 DECLARE_NO_COPY_CLASS(wxClipboardSync
)
108 wxClipboard
*wxClipboardSync::ms_clipboard
= NULL
;
110 // ============================================================================
111 // clipboard ca;backs implementation
112 // ============================================================================
114 //-----------------------------------------------------------------------------
115 // "selection_received" for targets
116 //-----------------------------------------------------------------------------
120 targets_selection_received( GtkWidget
*WXUNUSED(widget
),
121 GtkSelectionData
*selection_data
,
122 guint32
WXUNUSED(time
),
123 wxClipboard
*clipboard
)
128 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
130 if ( !selection_data
|| selection_data
->length
<= 0 )
133 // make sure we got the data in the correct form
134 GdkAtom type
= selection_data
->type
;
135 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
137 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
139 wxLogTrace( TRACE_CLIPBOARD
,
140 _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() );
153 #endif // __WXDEBUG__
155 // the atoms we received, holding a list of targets (= formats)
156 const GdkAtom
* const atoms
= (GdkAtom
*)selection_data
->data
;
157 for ( size_t i
= 0; i
< selection_data
->length
/sizeof(GdkAtom
); i
++ )
159 const wxDataFormat
format(atoms
[i
]);
161 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
163 if ( clipboard
->GTKOnTargetReceived(format
) )
169 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat
& format
)
171 if ( format
!= m_targetRequested
)
174 m_formatSupported
= true;
178 //-----------------------------------------------------------------------------
179 // "selection_received" for the actual data
180 //-----------------------------------------------------------------------------
184 selection_received( GtkWidget
*WXUNUSED(widget
),
185 GtkSelectionData
*selection_data
,
186 guint32
WXUNUSED(time
),
187 wxClipboard
*clipboard
)
192 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
194 if ( !selection_data
|| selection_data
->length
<= 0 )
197 clipboard
->GTKOnSelectionReceived(*selection_data
);
201 //-----------------------------------------------------------------------------
203 //-----------------------------------------------------------------------------
207 selection_clear_clip( GtkWidget
*WXUNUSED(widget
), GdkEventSelection
*event
)
209 wxClipboard
* const clipboard
= wxTheClipboard
;
213 // notice the use of OnDoneIfInProgress() here instead of just OnDone():
214 // it's perfectly possible that we're receiving this notification from GTK+
215 // even though we hadn't cleared the clipboard ourselves but because
216 // another application (or even another window in the same program)
218 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress
, clipboard
);
220 wxClipboard::Kind kind
;
221 if (event
->selection
== GDK_SELECTION_PRIMARY
)
223 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost primary selection" ));
225 kind
= wxClipboard::Primary
;
227 else if (event
->selection
== g_clipboardAtom
)
229 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost clipboard" ));
231 kind
= wxClipboard::Clipboard
;
233 else // some other selection, we're not concerned
238 // the clipboard is no longer in our hands, we don't need data any more
239 clipboard
->GTKClearData(kind
);
245 //-----------------------------------------------------------------------------
246 // selection handler for supplying data
247 //-----------------------------------------------------------------------------
251 selection_handler( GtkWidget
*WXUNUSED(widget
),
252 GtkSelectionData
*selection_data
,
253 guint
WXUNUSED(info
),
254 guint
WXUNUSED(time
),
255 gpointer signal_data
)
257 wxClipboard
* const clipboard
= wxTheClipboard
;
261 wxDataObject
* const data
= clipboard
->GTKGetDataObject();
265 // ICCCM says that TIMESTAMP is a required atom.
266 // In particular, it satisfies Klipper, which polls
267 // TIMESTAMP to see if the clipboards content has changed.
268 // It shall return the time which was used to set the data.
269 if (selection_data
->target
== g_timestampAtom
)
271 guint timestamp
= GPOINTER_TO_UINT (signal_data
);
272 gtk_selection_data_set(selection_data
,
273 GDK_SELECTION_TYPE_INTEGER
,
275 (guchar
*)&(timestamp
),
277 wxLogTrace(TRACE_CLIPBOARD
,
278 _T("Clipboard TIMESTAMP requested, returning timestamp=%u"),
283 wxDataFormat
format( selection_data
->target
);
286 wxLogTrace(TRACE_CLIPBOARD
,
287 _T("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
288 format
.GetId().c_str(),
289 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->target
))).c_str(),
290 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->type
))).c_str(),
291 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->selection
))).c_str(),
292 GPOINTER_TO_UINT( signal_data
)
296 if (!data
->IsSupportedFormat( format
)) return;
298 int size
= data
->GetDataSize( format
);
300 if (size
== 0) return;
302 void *d
= malloc(size
);
303 wxON_BLOCK_EXIT1(free
, d
);
305 // Text data will be in UTF8 in Unicode mode.
306 data
->GetDataHere( selection_data
->target
, d
);
308 // NB: GTK+ requires special treatment of UTF8_STRING data, the text
309 // would show as UTF-8 data interpreted as latin1 (?) in other
310 // GTK+ apps if we used gtk_selection_data_set()
311 if (format
== wxDataFormat(wxDF_UNICODETEXT
))
313 gtk_selection_data_set_text(
320 gtk_selection_data_set(
322 GDK_SELECTION_TYPE_STRING
,
330 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData
& sel
)
332 wxCHECK_RET( m_receivedData
, _T("should be inside GetData()") );
334 const wxDataFormat
format(sel
.target
);
335 wxLogTrace(TRACE_CLIPBOARD
, _T("Received selection %s"),
336 format
.GetId().c_str());
338 if ( !m_receivedData
->IsSupportedFormat(format
) )
341 m_receivedData
->SetData(format
, sel
.length
, sel
.data
);
342 m_formatSupported
= true;
345 // ============================================================================
346 // wxClipboard implementation
347 // ============================================================================
349 // ----------------------------------------------------------------------------
350 // wxClipboard ctor/dtor
351 // ----------------------------------------------------------------------------
353 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
)
355 wxClipboard::wxClipboard()
361 m_receivedData
= NULL
;
363 m_formatSupported
= false;
364 m_targetRequested
= 0;
366 // we use m_targetsWidget to query what formats are available
367 m_targetsWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
368 gtk_widget_realize( m_targetsWidget
);
370 g_signal_connect (m_targetsWidget
, "selection_received",
371 G_CALLBACK (targets_selection_received
), this);
373 // we use m_clipboardWidget to get and to offer data
374 m_clipboardWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
375 gtk_widget_realize( m_clipboardWidget
);
377 g_signal_connect (m_clipboardWidget
, "selection_received",
378 G_CALLBACK (selection_received
), this);
380 g_signal_connect (m_clipboardWidget
, "selection_clear_event",
381 G_CALLBACK (selection_clear_clip
), NULL
);
383 // initialize atoms we use if not done yet
384 if ( !g_clipboardAtom
)
385 g_clipboardAtom
= gdk_atom_intern( "CLIPBOARD", FALSE
);
386 if ( !g_targetsAtom
)
387 g_targetsAtom
= gdk_atom_intern ("TARGETS", FALSE
);
388 if ( !g_timestampAtom
)
389 g_timestampAtom
= gdk_atom_intern ("TIMESTAMP", FALSE
);
392 wxClipboard::~wxClipboard()
396 gtk_widget_destroy( m_clipboardWidget
);
397 gtk_widget_destroy( m_targetsWidget
);
400 // ----------------------------------------------------------------------------
401 // wxClipboard helper functions
402 // ----------------------------------------------------------------------------
404 GdkAtom
wxClipboard::GTKGetClipboardAtom() const
406 return m_usePrimary
? (GdkAtom
)GDK_SELECTION_PRIMARY
410 void wxClipboard::GTKClearData(Kind kind
)
412 wxDataObject
*&data
= Data();
420 bool wxClipboard::SetSelectionOwner(bool set
)
422 bool rc
= gtk_selection_owner_set
424 set
? m_clipboardWidget
: NULL
,
425 GTKGetClipboardAtom(),
426 (guint32
)GDK_CURRENT_TIME
431 wxLogTrace(TRACE_CLIPBOARD
, _T("Failed to %sset selection owner"),
432 set
? _T("") : _T("un"));
438 void wxClipboard::AddSupportedTarget(GdkAtom atom
)
440 gtk_selection_add_target
442 GTK_WIDGET(m_clipboardWidget
),
443 GTKGetClipboardAtom(),
445 0 // info (same as client data) unused
449 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
)
451 wxCHECK_MSG( format
, false, wxT("invalid clipboard format") );
453 wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"),
454 format
.GetId().c_str());
456 // these variables will be used by our GTKOnTargetReceived()
457 m_targetRequested
= format
;
458 m_formatSupported
= false;
460 // block until m_formatSupported is set from targets_selection_received
463 wxClipboardSync
sync(*this);
465 gtk_selection_convert( m_targetsWidget
,
466 GTKGetClipboardAtom(),
468 (guint32
) GDK_CURRENT_TIME
);
471 return m_formatSupported
;
474 // ----------------------------------------------------------------------------
475 // wxClipboard public API implementation
476 // ----------------------------------------------------------------------------
478 void wxClipboard::Clear()
480 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
481 m_clipboardWidget
->window
)
483 wxClipboardSync
sync(*this);
485 // this will result in selection_clear_clip callback being called and
486 // it will free our data
487 SetSelectionOwner(false);
490 m_targetRequested
= 0;
491 m_formatSupported
= false;
494 bool wxClipboard::Open()
496 wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") );
503 bool wxClipboard::SetData( wxDataObject
*data
)
505 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
507 wxCHECK_MSG( data
, false, wxT("data is invalid") );
511 return AddData( data
);
514 bool wxClipboard::AddData( wxDataObject
*data
)
516 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
518 wxCHECK_MSG( data
, false, wxT("data is invalid") );
520 // we can only store one wxDataObject so clear the old one
525 // get formats from wxDataObjects
526 const size_t count
= data
->GetFormatCount();
527 wxDataFormatArray
formats(new wxDataFormat
[count
]);
528 data
->GetAllFormats(formats
.get());
530 // always provide TIMESTAMP as a target, see comments in selection_handler
532 AddSupportedTarget(g_timestampAtom
);
534 for ( size_t i
= 0; i
< count
; i
++ )
536 const wxDataFormat
format(formats
[i
]);
538 wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"),
539 format
.GetId().c_str());
541 AddSupportedTarget(format
);
544 g_signal_connect (m_clipboardWidget
, "selection_get",
545 G_CALLBACK (selection_handler
),
546 GUINT_TO_POINTER (gtk_get_current_event_time()) );
548 // tell the world we offer clipboard data
549 return SetSelectionOwner();
552 void wxClipboard::Close()
554 wxCHECK_RET( m_open
, wxT("clipboard not open") );
559 bool wxClipboard::IsOpened() const
564 bool wxClipboard::IsSupported( const wxDataFormat
& format
)
566 if ( DoIsSupported(format
) )
570 if ( format
== wxDF_UNICODETEXT
)
572 // also with plain STRING format
573 return DoIsSupported(g_altTextAtom
);
575 #endif // wxUSE_UNICODE
580 bool wxClipboard::GetData( wxDataObject
& data
)
582 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
584 // get all supported formats from wxDataObjects
585 const size_t count
= data
.GetFormatCount();
586 wxDataFormatArray
formats(new wxDataFormat
[count
]);
587 data
.GetAllFormats(formats
.get());
589 for ( size_t i
= 0; i
< count
; i
++ )
591 const wxDataFormat
format(formats
[i
]);
593 // is this format supported by clipboard ?
594 if ( !DoIsSupported(format
) )
597 wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"),
598 format
.GetId().c_str());
600 // these variables will be used by our GTKOnSelectionReceived()
601 m_receivedData
= &data
;
602 m_formatSupported
= false;
605 wxClipboardSync
sync(*this);
607 gtk_selection_convert(m_clipboardWidget
,
608 GTKGetClipboardAtom(),
610 (guint32
) GDK_CURRENT_TIME
);
611 } // wait until we get the results
614 Normally this is a true error as we checked for the presence of such
615 data before, but there are applications that may return an empty
616 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
617 which would produce a false error message here, so we check for the
618 size of the string first. With ANSI, GetDataSize returns an extra
619 value (for the closing null?), with unicode, the exact number of
620 tokens is given (that is more than 1 for non-ASCII characters)
621 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
624 if ( format
!= wxDF_UNICODETEXT
|| data
.GetDataSize(format
) > 0 )
626 if ( format
!= wxDF_TEXT
|| data
.GetDataSize(format
) > 1 )
627 #endif // UNICODE / !UNICODE
629 wxCHECK_MSG( m_formatSupported
, false,
630 wxT("error retrieving data from clipboard") );
636 wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found"));
641 #endif // wxUSE_CLIPBOARD