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") );
92 static wxClipboard
*ms_clipboard
;
94 DECLARE_NO_COPY_CLASS(wxClipboardSync
)
97 wxClipboard
*wxClipboardSync::ms_clipboard
= NULL
;
99 // ============================================================================
100 // clipboard ca;backs implementation
101 // ============================================================================
103 //-----------------------------------------------------------------------------
104 // "selection_received" for targets
105 //-----------------------------------------------------------------------------
109 targets_selection_received( GtkWidget
*WXUNUSED(widget
),
110 GtkSelectionData
*selection_data
,
111 guint32
WXUNUSED(time
),
112 wxClipboard
*clipboard
)
117 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
119 if ( !selection_data
|| selection_data
->length
<= 0 )
122 // make sure we got the data in the correct form
123 GdkAtom type
= selection_data
->type
;
124 if ( type
!= GDK_SELECTION_TYPE_ATOM
)
126 if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 )
128 wxLogTrace( TRACE_CLIPBOARD
,
129 _T("got unsupported clipboard target") );
136 // it's not really a format, of course, but we can reuse its GetId() method
137 // to format this atom as string
138 wxDataFormat
clip(selection_data
->selection
);
139 wxLogTrace( TRACE_CLIPBOARD
,
140 wxT("Received available formats for clipboard %s"),
141 clip
.GetId().c_str() );
142 #endif // __WXDEBUG__
144 // the atoms we received, holding a list of targets (= formats)
145 const GdkAtom
* const atoms
= (GdkAtom
*)selection_data
->data
;
146 for ( size_t i
= 0; i
< selection_data
->length
/sizeof(GdkAtom
); i
++ )
148 const wxDataFormat
format(atoms
[i
]);
150 wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str());
152 if ( clipboard
->GTKOnTargetReceived(format
) )
158 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat
& format
)
160 if ( format
!= m_targetRequested
)
163 m_formatSupported
= true;
167 //-----------------------------------------------------------------------------
168 // "selection_received" for the actual data
169 //-----------------------------------------------------------------------------
173 selection_received( GtkWidget
*WXUNUSED(widget
),
174 GtkSelectionData
*selection_data
,
175 guint32
WXUNUSED(time
),
176 wxClipboard
*clipboard
)
181 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
183 if ( !selection_data
|| selection_data
->length
<= 0 )
186 clipboard
->GTKOnSelectionReceived(*selection_data
);
190 //-----------------------------------------------------------------------------
192 //-----------------------------------------------------------------------------
196 selection_clear_clip( GtkWidget
*WXUNUSED(widget
), GdkEventSelection
*event
)
198 wxClipboard
* const clipboard
= wxTheClipboard
;
202 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
);
204 wxClipboard::Kind kind
;
205 if (event
->selection
== GDK_SELECTION_PRIMARY
)
207 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost primary selection" ));
209 kind
= wxClipboard::Primary
;
211 else if (event
->selection
== g_clipboardAtom
)
213 wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost clipboard" ));
215 kind
= wxClipboard::Clipboard
;
217 else // some other selection, we're not concerned
222 // the clipboard is no longer in our hands, we don't need data any more
223 clipboard
->GTKClearData(kind
);
229 //-----------------------------------------------------------------------------
230 // selection handler for supplying data
231 //-----------------------------------------------------------------------------
235 selection_handler( GtkWidget
*WXUNUSED(widget
),
236 GtkSelectionData
*selection_data
,
237 guint
WXUNUSED(info
),
238 guint
WXUNUSED(time
),
239 gpointer signal_data
)
241 wxClipboard
* const clipboard
= wxTheClipboard
;
245 wxDataObject
* const data
= clipboard
->GTKGetDataObject();
249 // ICCCM says that TIMESTAMP is a required atom.
250 // In particular, it satisfies Klipper, which polls
251 // TIMESTAMP to see if the clipboards content has changed.
252 // It shall return the time which was used to set the data.
253 if (selection_data
->target
== g_timestampAtom
)
255 guint timestamp
= GPOINTER_TO_UINT (signal_data
);
256 gtk_selection_data_set(selection_data
,
257 GDK_SELECTION_TYPE_INTEGER
,
259 (guchar
*)&(timestamp
),
261 wxLogTrace(TRACE_CLIPBOARD
,
262 _T("Clipboard TIMESTAMP requested, returning timestamp=%u"),
267 wxDataFormat
format( selection_data
->target
);
270 wxLogTrace(TRACE_CLIPBOARD
,
271 _T("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
272 format
.GetId().c_str(),
273 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->target
))).c_str(),
274 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->type
))).c_str(),
275 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->selection
))).c_str(),
276 GPOINTER_TO_UINT( signal_data
)
280 if (!data
->IsSupportedFormat( format
)) return;
282 int size
= data
->GetDataSize( format
);
284 if (size
== 0) return;
286 void *d
= malloc(size
);
287 wxON_BLOCK_EXIT1(free
, d
);
289 // Text data will be in UTF8 in Unicode mode.
290 data
->GetDataHere( selection_data
->target
, d
);
292 // NB: GTK+ requires special treatment of UTF8_STRING data, the text
293 // would show as UTF-8 data interpreted as latin1 (?) in other
294 // GTK+ apps if we used gtk_selection_data_set()
295 if (format
== wxDataFormat(wxDF_UNICODETEXT
))
297 gtk_selection_data_set_text(
304 gtk_selection_data_set(
306 GDK_SELECTION_TYPE_STRING
,
314 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData
& sel
)
316 wxCHECK_RET( m_receivedData
, _T("should be inside GetData()") );
318 const wxDataFormat
format(sel
.target
);
319 wxLogTrace(TRACE_CLIPBOARD
, _T("Received selection %s"),
320 format
.GetId().c_str());
322 if ( !m_receivedData
->IsSupportedFormat(format
) )
325 m_receivedData
->SetData(format
, sel
.length
, sel
.data
);
326 m_formatSupported
= true;
329 // ============================================================================
330 // wxClipboard implementation
331 // ============================================================================
333 // ----------------------------------------------------------------------------
334 // wxClipboard ctor/dtor
335 // ----------------------------------------------------------------------------
337 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
)
339 wxClipboard::wxClipboard()
345 m_receivedData
= NULL
;
347 m_formatSupported
= false;
348 m_targetRequested
= 0;
350 m_usePrimary
= false;
352 // we use m_targetsWidget to query what formats are available
353 m_targetsWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
354 gtk_widget_realize( m_targetsWidget
);
356 g_signal_connect (m_targetsWidget
, "selection_received",
357 G_CALLBACK (targets_selection_received
), this);
359 // we use m_clipboardWidget to get and to offer data
360 m_clipboardWidget
= gtk_window_new( GTK_WINDOW_POPUP
);
361 gtk_widget_realize( m_clipboardWidget
);
363 g_signal_connect (m_clipboardWidget
, "selection_received",
364 G_CALLBACK (selection_received
), this);
366 g_signal_connect (m_clipboardWidget
, "selection_clear_event",
367 G_CALLBACK (selection_clear_clip
), NULL
);
369 // initialize atoms we use if not done yet
370 if ( !g_clipboardAtom
)
371 g_clipboardAtom
= gdk_atom_intern( "CLIPBOARD", FALSE
);
372 if ( !g_targetsAtom
)
373 g_targetsAtom
= gdk_atom_intern ("TARGETS", FALSE
);
374 if ( !g_timestampAtom
)
375 g_timestampAtom
= gdk_atom_intern ("TIMESTAMP", FALSE
);
378 wxClipboard::~wxClipboard()
382 if ( m_clipboardWidget
)
383 gtk_widget_destroy( m_clipboardWidget
);
384 if ( m_targetsWidget
)
385 gtk_widget_destroy( m_targetsWidget
);
388 // ----------------------------------------------------------------------------
389 // wxClipboard helper functions
390 // ----------------------------------------------------------------------------
392 GdkAtom
wxClipboard::GTKGetClipboardAtom() const
394 return m_usePrimary
? (GdkAtom
)GDK_SELECTION_PRIMARY
398 void wxClipboard::GTKClearData(Kind kind
)
400 wxDataObject
*&data
= Data();
408 bool wxClipboard::SetSelectionOwner(bool set
)
410 bool rc
= gtk_selection_owner_set
412 set
? m_clipboardWidget
: NULL
,
413 GTKGetClipboardAtom(),
414 (guint32
)GDK_CURRENT_TIME
419 wxLogTrace(TRACE_CLIPBOARD
, _T("Failed to %sset selection owner"),
420 set
? _T("") : _T("un"));
426 void wxClipboard::AddSupportedTarget(GdkAtom atom
)
428 gtk_selection_add_target
430 GTK_WIDGET(m_clipboardWidget
),
431 GTKGetClipboardAtom(),
433 0 // info (same as client data) unused
437 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
)
439 wxCHECK_MSG( format
, false, wxT("invalid clipboard format") );
441 wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"),
442 format
.GetId().c_str());
444 // these variables will be used by our GTKOnTargetReceived()
445 m_targetRequested
= format
;
446 m_formatSupported
= false;
448 // block until m_formatSupported is set from targets_selection_received
451 wxClipboardSync
sync(*this);
453 gtk_selection_convert( m_targetsWidget
,
454 GTKGetClipboardAtom(),
456 (guint32
) GDK_CURRENT_TIME
);
459 return m_formatSupported
;
462 // ----------------------------------------------------------------------------
463 // wxClipboard public API implementation
464 // ----------------------------------------------------------------------------
466 void wxClipboard::Clear()
468 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
469 m_clipboardWidget
->window
)
471 wxClipboardSync
sync(*this);
473 // this will result in selection_clear_clip callback being called and
474 // it will free our data
475 SetSelectionOwner(false);
478 m_targetRequested
= 0;
479 m_formatSupported
= false;
482 bool wxClipboard::Open()
484 wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") );
491 bool wxClipboard::SetData( wxDataObject
*data
)
493 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
495 wxCHECK_MSG( data
, false, wxT("data is invalid") );
499 return AddData( data
);
502 bool wxClipboard::AddData( wxDataObject
*data
)
504 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
506 wxCHECK_MSG( data
, false, wxT("data is invalid") );
508 // we can only store one wxDataObject so clear the old one
513 // get formats from wxDataObjects
514 const size_t count
= data
->GetFormatCount();
515 wxDataFormatArray
formats(new wxDataFormat
[count
]);
516 data
->GetAllFormats(formats
.get());
518 // always provide TIMESTAMP as a target, see comments in selection_handler
520 AddSupportedTarget(g_timestampAtom
);
522 for ( size_t i
= 0; i
< count
; i
++ )
524 const wxDataFormat
format(formats
[i
]);
526 wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"),
527 format
.GetId().c_str());
529 AddSupportedTarget(format
);
532 g_signal_connect (m_clipboardWidget
, "selection_get",
533 G_CALLBACK (selection_handler
),
534 GUINT_TO_POINTER (gtk_get_current_event_time()) );
536 // tell the world we offer clipboard data
537 return SetSelectionOwner();
540 void wxClipboard::Close()
542 wxCHECK_RET( m_open
, wxT("clipboard not open") );
547 bool wxClipboard::IsOpened() const
552 bool wxClipboard::IsSupported( const wxDataFormat
& format
)
554 if ( DoIsSupported(format
) )
558 if ( format
== wxDF_UNICODETEXT
)
560 // also with plain STRING format
561 return DoIsSupported(g_altTextAtom
);
563 #endif // wxUSE_UNICODE
568 bool wxClipboard::GetData( wxDataObject
& data
)
570 wxCHECK_MSG( m_open
, false, wxT("clipboard not open") );
572 // get all supported formats from wxDataObjects
573 const size_t count
= data
.GetFormatCount();
574 wxDataFormatArray
formats(new wxDataFormat
[count
]);
575 data
.GetAllFormats(formats
.get());
577 for ( size_t i
= 0; i
< count
; i
++ )
579 const wxDataFormat
format(formats
[i
]);
581 // is this format supported by clipboard ?
582 if ( !DoIsSupported(format
) )
585 wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"),
586 format
.GetId().c_str());
588 // these variables will be used by our GTKOnSelectionReceived()
589 m_receivedData
= &data
;
590 m_formatSupported
= false;
593 wxClipboardSync
sync(*this);
595 gtk_selection_convert(m_clipboardWidget
,
596 GTKGetClipboardAtom(),
598 (guint32
) GDK_CURRENT_TIME
);
599 } // wait until we get the results
602 Normally this is a true error as we checked for the presence of such
603 data before, but there are applications that may return an empty
604 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
605 which would produce a false error message here, so we check for the
606 size of the string first. With ANSI, GetDataSize returns an extra
607 value (for the closing null?), with unicode, the exact number of
608 tokens is given (that is more than 1 for non-ASCII characters)
609 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
612 if ( format
!= wxDF_UNICODETEXT
|| data
.GetDataSize(format
) > 0 )
614 if ( format
!= wxDF_TEXT
|| data
.GetDataSize(format
) > 1 )
615 #endif // UNICODE / !UNICODE
617 wxCHECK_MSG( m_formatSupported
, false,
618 wxT("error retrieving data from clipboard") );
624 wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found"));
629 #endif // wxUSE_CLIPBOARD