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
* WXUNUSED_UNLESS_DEBUG(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 callbacks 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
)
294 #endif // __WXDEBUG__
296 if ( !data
->IsSupportedFormat( format
) )
299 int size
= data
->GetDataSize( format
);
303 wxCharBuffer
buf(size
- 1); // it adds 1 internally (for NUL)
305 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
306 if ( !data
->GetDataHere(format
, buf
.data()) )
309 // use UTF8_STRING format if requested in Unicode build but just plain
310 // STRING one in ANSI or if explicitly asked in Unicode
312 if (format
== wxDataFormat(wxDF_UNICODETEXT
))
314 gtk_selection_data_set_text(
316 (const gchar
*)buf
.data(),
320 #endif // wxUSE_UNICODE
322 gtk_selection_data_set(
324 GDK_SELECTION_TYPE_STRING
,
326 (const guchar
*)buf
.data(),
332 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData
& sel
)
334 wxCHECK_RET( m_receivedData
, _T("should be inside GetData()") );
336 const wxDataFormat
format(sel
.target
);
337 wxLogTrace(TRACE_CLIPBOARD
, _T("Received selection %s"),
338 format
.GetId().c_str());
340 if ( !m_receivedData
->IsSupportedFormat(format
) )
343 m_receivedData
->SetData(format
, sel
.length
, sel
.data
);
344 m_formatSupported
= true;
347 //-----------------------------------------------------------------------------
348 // asynchronous "selection_received" for targets
349 //-----------------------------------------------------------------------------
353 async_targets_selection_received( GtkWidget
*WXUNUSED(widget
),
354 GtkSelectionData
*selection_data
,
355 guint32
WXUNUSED(time
),
356 wxClipboard
*clipboard
)
358 if ( !clipboard
) // Assert?
361 if (!clipboard
->m_sink
)
364 wxClipboardEvent
*event
= new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED
);
366 if ( !selection_data
|| selection_data
->length
<= 0 )
368 clipboard
->m_sink
->QueueEvent( event
);
372 // make sure we got the data in the correct form
373 GdkAtom type
= selection_data
->type
;
374 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
376 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
378 wxLogTrace( TRACE_CLIPBOARD
,
379 _T("got unsupported clipboard target") );
381 clipboard
->m_sink
->QueueEvent( event
);
387 // it's not really a format, of course, but we can reuse its GetId() method
388 // to format this atom as string
389 wxDataFormat
clip(selection_data
->selection
);
390 wxLogTrace( TRACE_CLIPBOARD
,
391 wxT("Received available formats for clipboard %s"),
392 clip
.GetId().c_str() );
393 #endif // __WXDEBUG__
395 // the atoms we received, holding a list of targets (= formats)
396 const GdkAtom
* const atoms
= (GdkAtom
*)selection_data
->data
;
397 for ( size_t i
= 0; i
< selection_data
->length
/sizeof(GdkAtom
); i
++ )
399 const wxDataFormat
format(atoms
[i
]);
401 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
403 event
->AddFormat( format
);
406 clipboard
->m_sink
->QueueEvent( event
);
410 // ============================================================================
411 // wxClipboard implementation
412 // ============================================================================
414 // ----------------------------------------------------------------------------
415 // wxClipboard ctor/dtor
416 // ----------------------------------------------------------------------------
418 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
)
420 wxClipboard::wxClipboard()
426 m_receivedData
= NULL
;
428 m_formatSupported
= false;
429 m_targetRequested
= 0;
431 // we use m_targetsWidget to query what formats are available
432 m_targetsWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
433 gtk_widget_realize( m_targetsWidget
);
435 g_signal_connect (m_targetsWidget
, "selection_received",
436 G_CALLBACK (targets_selection_received
), this);
438 // we use m_targetsWidgetAsync to query what formats asynchronously
439 m_targetsWidgetAsync
= gtk_window_new( GTK_WINDOW_POPUP
);
440 gtk_widget_realize( m_targetsWidgetAsync
);
442 g_signal_connect (m_targetsWidgetAsync
, "selection_received",
443 G_CALLBACK (async_targets_selection_received
), this);
445 // we use m_clipboardWidget to get and to offer data
446 m_clipboardWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
447 gtk_widget_realize( m_clipboardWidget
);
449 g_signal_connect (m_clipboardWidget
, "selection_received",
450 G_CALLBACK (selection_received
), this);
452 g_signal_connect (m_clipboardWidget
, "selection_clear_event",
453 G_CALLBACK (selection_clear_clip
), NULL
);
455 // initialize atoms we use if not done yet
456 if ( !g_clipboardAtom
)
457 g_clipboardAtom
= gdk_atom_intern( "CLIPBOARD", FALSE
);
458 if ( !g_targetsAtom
)
459 g_targetsAtom
= gdk_atom_intern ("TARGETS", FALSE
);
460 if ( !g_timestampAtom
)
461 g_timestampAtom
= gdk_atom_intern ("TIMESTAMP", FALSE
);
464 wxClipboard::~wxClipboard()
468 gtk_widget_destroy( m_clipboardWidget
);
469 gtk_widget_destroy( m_targetsWidget
);
472 // ----------------------------------------------------------------------------
473 // wxClipboard helper functions
474 // ----------------------------------------------------------------------------
476 GdkAtom
wxClipboard::GTKGetClipboardAtom() const
478 return m_usePrimary
? (GdkAtom
)GDK_SELECTION_PRIMARY
482 void wxClipboard::GTKClearData(Kind kind
)
484 wxDataObject
*&data
= Data(kind
);
492 bool wxClipboard::SetSelectionOwner(bool set
)
494 bool rc
= gtk_selection_owner_set
496 set
? m_clipboardWidget
: NULL
,
497 GTKGetClipboardAtom(),
498 (guint32
)GDK_CURRENT_TIME
503 wxLogTrace(TRACE_CLIPBOARD
, _T("Failed to %sset selection owner"),
504 set
? _T("") : _T("un"));
510 void wxClipboard::AddSupportedTarget(GdkAtom atom
)
512 gtk_selection_add_target
515 GTKGetClipboardAtom(),
517 0 // info (same as client data) unused
521 bool wxClipboard::IsSupportedAsync(wxEvtHandler
*sink
)
525 gtk_selection_convert( m_targetsWidgetAsync
,
526 GTKGetClipboardAtom(),
528 (guint32
) GDK_CURRENT_TIME
);
533 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
)
535 wxCHECK_MSG( format
, false, wxT("invalid clipboard format") );
537 wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"),
538 format
.GetId().c_str());
540 // these variables will be used by our GTKOnTargetReceived()
541 m_targetRequested
= format
;
542 m_formatSupported
= false;
544 // block until m_formatSupported is set from targets_selection_received
547 wxClipboardSync
sync(*this);
549 gtk_selection_convert( m_targetsWidget
,
550 GTKGetClipboardAtom(),
552 (guint32
) GDK_CURRENT_TIME
);
555 return m_formatSupported
;
558 // ----------------------------------------------------------------------------
559 // wxClipboard public API implementation
560 // ----------------------------------------------------------------------------
562 void wxClipboard::Clear()
564 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
565 m_clipboardWidget
->window
)
567 wxClipboardSync
sync(*this);
569 // this will result in selection_clear_clip callback being called and
570 // it will free our data
571 SetSelectionOwner(false);
574 m_targetRequested
= 0;
575 m_formatSupported
= false;
578 bool wxClipboard::Open()
580 wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") );
587 bool wxClipboard::SetData( wxDataObject
*data
)
589 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
591 wxCHECK_MSG( data
, false, wxT("data is invalid") );
595 return AddData( data
);
598 bool wxClipboard::AddData( wxDataObject
*data
)
600 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
602 wxCHECK_MSG( data
, false, wxT("data is invalid") );
604 // we can only store one wxDataObject so clear the old one
609 // get formats from wxDataObjects
610 const size_t count
= data
->GetFormatCount();
611 wxDataFormatArray
formats(new wxDataFormat
[count
]);
612 data
->GetAllFormats(formats
.get());
614 // always provide TIMESTAMP as a target, see comments in selection_handler
616 AddSupportedTarget(g_timestampAtom
);
618 for ( size_t i
= 0; i
< count
; i
++ )
620 const wxDataFormat
format(formats
[i
]);
622 wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"),
623 format
.GetId().c_str());
625 AddSupportedTarget(format
);
628 g_signal_connect (m_clipboardWidget
, "selection_get",
629 G_CALLBACK (selection_handler
),
630 GUINT_TO_POINTER (gtk_get_current_event_time()) );
632 // tell the world we offer clipboard data
633 return SetSelectionOwner();
636 void wxClipboard::Close()
638 wxCHECK_RET( m_open
, wxT("clipboard not open") );
643 bool wxClipboard::IsOpened() const
648 bool wxClipboard::IsSupported( const wxDataFormat
& format
)
650 if ( DoIsSupported(format
) )
654 if ( format
== wxDF_UNICODETEXT
)
656 // also with plain STRING format
657 return DoIsSupported(g_altTextAtom
);
659 #endif // wxUSE_UNICODE
664 bool wxClipboard::GetData( wxDataObject
& data
)
666 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
668 // get all supported formats from wxDataObjects
669 const size_t count
= data
.GetFormatCount();
670 wxDataFormatArray
formats(new wxDataFormat
[count
]);
671 data
.GetAllFormats(formats
.get());
673 for ( size_t i
= 0; i
< count
; i
++ )
675 const wxDataFormat
format(formats
[i
]);
677 // is this format supported by clipboard ?
678 if ( !DoIsSupported(format
) )
681 wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"),
682 format
.GetId().c_str());
684 // these variables will be used by our GTKOnSelectionReceived()
685 m_receivedData
= &data
;
686 m_formatSupported
= false;
689 wxClipboardSync
sync(*this);
691 gtk_selection_convert(m_clipboardWidget
,
692 GTKGetClipboardAtom(),
694 (guint32
) GDK_CURRENT_TIME
);
695 } // wait until we get the results
698 Normally this is a true error as we checked for the presence of such
699 data before, but there are applications that may return an empty
700 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
701 which would produce a false error message here, so we check for the
702 size of the string first. With ANSI, GetDataSize returns an extra
703 value (for the closing null?), with unicode, the exact number of
704 tokens is given (that is more than 1 for non-ASCII characters)
705 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
708 if ( format
!= wxDF_UNICODETEXT
|| data
.GetDataSize(format
) > 0 )
710 if ( format
!= wxDF_TEXT
|| data
.GetDataSize(format
) > 1 )
711 #endif // UNICODE / !UNICODE
713 wxCHECK_MSG( m_formatSupported
, false,
714 wxT("error retrieving data from clipboard") );
720 wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found"));
725 #endif // wxUSE_CLIPBOARD