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
); 
 365     event
->SetEventObject( clipboard 
); 
 367     if ( !selection_data 
|| selection_data
->length 
<= 0 ) 
 369         clipboard
->m_sink
->QueueEvent( event 
); 
 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 
); 
 388     // it's not really a format, of course, but we can reuse its GetId() method 
 389     // to format this atom as string 
 390     wxDataFormat 
clip(selection_data
->selection
); 
 391     wxLogTrace( TRACE_CLIPBOARD
, 
 392                 wxT("Received available formats for clipboard %s"), 
 393                 clip
.GetId().c_str() ); 
 394 #endif // __WXDEBUG__ 
 396     // the atoms we received, holding a list of targets (= formats) 
 397     const GdkAtom 
* const atoms 
= (GdkAtom 
*)selection_data
->data
; 
 398     for ( size_t i 
= 0; i 
< selection_data
->length
/sizeof(GdkAtom
); i
++ ) 
 400         const wxDataFormat 
format(atoms
[i
]); 
 402         wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str()); 
 404         event
->AddFormat( format 
); 
 407     clipboard
->m_sink
->QueueEvent( event 
); 
 411 // ============================================================================ 
 412 // wxClipboard implementation 
 413 // ============================================================================ 
 415 // ---------------------------------------------------------------------------- 
 416 // wxClipboard ctor/dtor 
 417 // ---------------------------------------------------------------------------- 
 419 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
) 
 421 wxClipboard::wxClipboard() 
 427     m_receivedData 
= NULL
; 
 429     m_formatSupported 
= false; 
 430     m_targetRequested 
= 0; 
 432     // we use m_targetsWidget to query what formats are available 
 433     m_targetsWidget 
= gtk_window_new( GTK_WINDOW_POPUP 
); 
 434     gtk_widget_realize( m_targetsWidget 
); 
 436     g_signal_connect (m_targetsWidget
, "selection_received", 
 437                       G_CALLBACK (targets_selection_received
), this); 
 439     // we use m_targetsWidgetAsync to query what formats asynchronously 
 440     m_targetsWidgetAsync 
= gtk_window_new( GTK_WINDOW_POPUP 
); 
 441     gtk_widget_realize( m_targetsWidgetAsync 
); 
 443     g_signal_connect (m_targetsWidgetAsync
, "selection_received", 
 444                       G_CALLBACK (async_targets_selection_received
), this); 
 446     // we use m_clipboardWidget to get and to offer data 
 447     m_clipboardWidget 
= gtk_window_new( GTK_WINDOW_POPUP 
); 
 448     gtk_widget_realize( m_clipboardWidget 
); 
 450     g_signal_connect (m_clipboardWidget
, "selection_received", 
 451                       G_CALLBACK (selection_received
), this); 
 453     g_signal_connect (m_clipboardWidget
, "selection_clear_event", 
 454                       G_CALLBACK (selection_clear_clip
), NULL
); 
 456     // initialize atoms we use if not done yet 
 457     if ( !g_clipboardAtom 
) 
 458         g_clipboardAtom 
= gdk_atom_intern( "CLIPBOARD", FALSE 
); 
 459     if ( !g_targetsAtom 
) 
 460         g_targetsAtom 
= gdk_atom_intern ("TARGETS", FALSE
); 
 461     if ( !g_timestampAtom 
) 
 462         g_timestampAtom 
= gdk_atom_intern ("TIMESTAMP", FALSE
); 
 465 wxClipboard::~wxClipboard() 
 469     gtk_widget_destroy( m_clipboardWidget 
); 
 470     gtk_widget_destroy( m_targetsWidget 
); 
 473 // ---------------------------------------------------------------------------- 
 474 // wxClipboard helper functions 
 475 // ---------------------------------------------------------------------------- 
 477 GdkAtom 
wxClipboard::GTKGetClipboardAtom() const 
 479     return m_usePrimary 
? (GdkAtom
)GDK_SELECTION_PRIMARY
 
 483 void wxClipboard::GTKClearData(Kind kind
) 
 485     wxDataObject 
*&data 
= Data(kind
); 
 493 bool wxClipboard::SetSelectionOwner(bool set
) 
 495     bool rc 
= gtk_selection_owner_set
 
 497                 set 
? m_clipboardWidget 
: NULL
, 
 498                 GTKGetClipboardAtom(), 
 499                 (guint32
)GDK_CURRENT_TIME
 
 504         wxLogTrace(TRACE_CLIPBOARD
, _T("Failed to %sset selection owner"), 
 505                    set 
? _T("") : _T("un")); 
 511 void wxClipboard::AddSupportedTarget(GdkAtom atom
) 
 513     gtk_selection_add_target
 
 516         GTKGetClipboardAtom(), 
 518         0 // info (same as client data) unused 
 522 bool wxClipboard::IsSupportedAsync(wxEvtHandler 
*sink
) 
 526     gtk_selection_convert( m_targetsWidgetAsync
, 
 527                            GTKGetClipboardAtom(), 
 529                            (guint32
) GDK_CURRENT_TIME 
); 
 534 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
) 
 536     wxCHECK_MSG( format
, false, wxT("invalid clipboard format") ); 
 538     wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"), 
 539                format
.GetId().c_str()); 
 541     // these variables will be used by our GTKOnTargetReceived() 
 542     m_targetRequested 
= format
; 
 543     m_formatSupported 
= false; 
 545     // block until m_formatSupported is set from targets_selection_received 
 548         wxClipboardSync 
sync(*this); 
 550         gtk_selection_convert( m_targetsWidget
, 
 551                                GTKGetClipboardAtom(), 
 553                                (guint32
) GDK_CURRENT_TIME 
); 
 556     return m_formatSupported
; 
 559 // ---------------------------------------------------------------------------- 
 560 // wxClipboard public API implementation 
 561 // ---------------------------------------------------------------------------- 
 563 void wxClipboard::Clear() 
 565     if ( gdk_selection_owner_get(GTKGetClipboardAtom()) == 
 566             m_clipboardWidget
->window 
) 
 568         wxClipboardSync 
sync(*this); 
 570         // this will result in selection_clear_clip callback being called and 
 571         // it will free our data 
 572         SetSelectionOwner(false); 
 575     m_targetRequested 
= 0; 
 576     m_formatSupported 
= false; 
 579 bool wxClipboard::Open() 
 581     wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") ); 
 588 bool wxClipboard::SetData( wxDataObject 
*data 
) 
 590     wxCHECK_MSG( m_open
, false, wxT("clipboard not open") ); 
 592     wxCHECK_MSG( data
, false, wxT("data is invalid") ); 
 596     return AddData( data 
); 
 599 bool wxClipboard::AddData( wxDataObject 
*data 
) 
 601     wxCHECK_MSG( m_open
, false, wxT("clipboard not open") ); 
 603     wxCHECK_MSG( data
, false, wxT("data is invalid") ); 
 605     // we can only store one wxDataObject so clear the old one 
 610     // get formats from wxDataObjects 
 611     const size_t count 
= data
->GetFormatCount(); 
 612     wxDataFormatArray 
formats(new wxDataFormat
[count
]); 
 613     data
->GetAllFormats(formats
.get()); 
 615     // always provide TIMESTAMP as a target, see comments in selection_handler 
 617     AddSupportedTarget(g_timestampAtom
); 
 619     for ( size_t i 
= 0; i 
< count
; i
++ ) 
 621         const wxDataFormat 
format(formats
[i
]); 
 623         wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"), 
 624                    format
.GetId().c_str()); 
 626         AddSupportedTarget(format
); 
 629     g_signal_connect (m_clipboardWidget
, "selection_get", 
 630                       G_CALLBACK (selection_handler
), 
 631                       GUINT_TO_POINTER (gtk_get_current_event_time()) ); 
 633     // tell the world we offer clipboard data 
 634     return SetSelectionOwner(); 
 637 void wxClipboard::Close() 
 639     wxCHECK_RET( m_open
, wxT("clipboard not open") ); 
 644 bool wxClipboard::IsOpened() const 
 649 bool wxClipboard::IsSupported( const wxDataFormat
& format 
) 
 651     if ( DoIsSupported(format
) ) 
 655     if ( format 
== wxDF_UNICODETEXT 
) 
 657         // also with plain STRING format 
 658         return DoIsSupported(g_altTextAtom
); 
 660 #endif // wxUSE_UNICODE 
 665 bool wxClipboard::GetData( wxDataObject
& data 
) 
 667     wxCHECK_MSG( m_open
, false, wxT("clipboard not open") ); 
 669     // get all supported formats from wxDataObjects 
 670     const size_t count 
= data
.GetFormatCount(); 
 671     wxDataFormatArray 
formats(new wxDataFormat
[count
]); 
 672     data
.GetAllFormats(formats
.get()); 
 674     for ( size_t i 
= 0; i 
< count
; i
++ ) 
 676         const wxDataFormat 
format(formats
[i
]); 
 678         // is this format supported by clipboard ? 
 679         if ( !DoIsSupported(format
) ) 
 682         wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"), 
 683                    format
.GetId().c_str()); 
 685         // these variables will be used by our GTKOnSelectionReceived() 
 686         m_receivedData 
= &data
; 
 687         m_formatSupported 
= false; 
 690             wxClipboardSync 
sync(*this); 
 692             gtk_selection_convert(m_clipboardWidget
, 
 693                                   GTKGetClipboardAtom(), 
 695                                   (guint32
) GDK_CURRENT_TIME 
); 
 696         } // wait until we get the results 
 699            Normally this is a true error as we checked for the presence of such 
 700            data before, but there are applications that may return an empty 
 701            string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied) 
 702            which would produce a false error message here, so we check for the 
 703            size of the string first. With ANSI, GetDataSize returns an extra 
 704            value (for the closing null?), with unicode, the exact number of 
 705            tokens is given (that is more than 1 for non-ASCII characters) 
 706            (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2) 
 709         if ( format 
!= wxDF_UNICODETEXT 
|| data
.GetDataSize(format
) > 0 ) 
 711         if ( format 
!= wxDF_TEXT 
|| data
.GetDataSize(format
) > 1 ) 
 712 #endif // UNICODE / !UNICODE 
 714             wxCHECK_MSG( m_formatSupported
, false, 
 715                          wxT("error retrieving data from clipboard") ); 
 721     wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found")); 
 726 #endif // wxUSE_CLIPBOARD