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" 
  30     #include "wx/dataobj.h" 
  33 #include "wx/scopedarray.h" 
  34 #include "wx/scopeguard.h" 
  35 #include "wx/evtloop.h" 
  37 #include "wx/gtk/private.h" 
  39 typedef wxScopedArray
<wxDataFormat
> wxDataFormatArray
; 
  41 // ---------------------------------------------------------------------------- 
  43 // ---------------------------------------------------------------------------- 
  45 static GdkAtom  g_clipboardAtom   
= 0; 
  46 static GdkAtom  g_targetsAtom     
= 0; 
  47 static GdkAtom  g_timestampAtom   
= 0; 
  50 extern GdkAtom g_altTextAtom
; 
  53 // the trace mask we use with wxLogTrace() - call 
  54 // wxLog::AddTraceMask(TRACE_CLIPBOARD) to enable the trace messages from here 
  55 // (there will be a *lot* of them!) 
  56 #define TRACE_CLIPBOARD wxT("clipboard") 
  58 // ---------------------------------------------------------------------------- 
  59 // wxClipboardSync: used to perform clipboard operations synchronously 
  60 // ---------------------------------------------------------------------------- 
  62 // constructing this object on stack will wait wait until the latest clipboard 
  63 // operation is finished on block exit 
  65 // notice that there can be no more than one such object alive at any moment, 
  66 // i.e. reentrancies are not allowed 
  70     wxClipboardSync(wxClipboard
& clipboard
) 
  72         wxASSERT_MSG( !ms_clipboard
, wxT("reentrancy in clipboard code") ); 
  73         ms_clipboard 
= &clipboard
; 
  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
); 
 135     if ( !selection_data 
|| selection_data
->length 
<= 0 ) 
 138     // make sure we got the data in the correct form 
 139     GdkAtom type 
= selection_data
->type
; 
 140     if ( type 
!= GDK_SELECTION_TYPE_ATOM 
) 
 142         if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 ) 
 144             wxLogTrace( TRACE_CLIPBOARD
, 
 145                         wxT("got unsupported clipboard target") ); 
 151     // it's not really a format, of course, but we can reuse its GetId() method 
 152     // to format this atom as string 
 153     wxDataFormat 
clip(selection_data
->selection
); 
 154     wxLogTrace( TRACE_CLIPBOARD
, 
 155                 wxT("Received available formats for clipboard %s"), 
 156                 clip
.GetId().c_str() ); 
 158     // the atoms we received, holding a list of targets (= formats) 
 159     const GdkAtom 
* const atoms 
= (GdkAtom 
*)selection_data
->data
; 
 160     for ( size_t i 
= 0; i 
< selection_data
->length
/sizeof(GdkAtom
); i
++ ) 
 162         const wxDataFormat 
format(atoms
[i
]); 
 164         wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str()); 
 166         if ( clipboard
->GTKOnTargetReceived(format
) ) 
 172 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat
& format
) 
 174     if ( format 
!= m_targetRequested 
) 
 177     m_formatSupported 
= true; 
 181 //----------------------------------------------------------------------------- 
 182 // "selection_received" for the actual data 
 183 //----------------------------------------------------------------------------- 
 187 selection_received( GtkWidget 
*WXUNUSED(widget
), 
 188                     GtkSelectionData 
*selection_data
, 
 189                     guint32 
WXUNUSED(time
), 
 190                     wxClipboard 
*clipboard 
) 
 195     wxON_BLOCK_EXIT1(wxClipboardSync::OnDone
, clipboard
); 
 197     if ( !selection_data 
|| selection_data
->length 
<= 0 ) 
 200     clipboard
->GTKOnSelectionReceived(*selection_data
); 
 204 //----------------------------------------------------------------------------- 
 206 //----------------------------------------------------------------------------- 
 210 selection_clear_clip( GtkWidget 
*WXUNUSED(widget
), GdkEventSelection 
*event 
) 
 212     wxClipboard 
* const clipboard 
= wxTheClipboard
; 
 216     // notice the use of OnDoneIfInProgress() here instead of just OnDone(): 
 217     // it's perfectly possible that we're receiving this notification from GTK+ 
 218     // even though we hadn't cleared the clipboard ourselves but because 
 219     // another application (or even another window in the same program) 
 221     wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress
, clipboard
); 
 223     wxClipboard::Kind kind
; 
 224     if (event
->selection 
== GDK_SELECTION_PRIMARY
) 
 226         wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost primary selection" )); 
 228         kind 
= wxClipboard::Primary
; 
 230     else if (event
->selection 
== g_clipboardAtom
) 
 232         wxLogTrace(TRACE_CLIPBOARD
, wxT("Lost clipboard" )); 
 234         kind 
= wxClipboard::Clipboard
; 
 236     else // some other selection, we're not concerned 
 241     // the clipboard is no longer in our hands, we don't need data any more 
 242     clipboard
->GTKClearData(kind
); 
 248 //----------------------------------------------------------------------------- 
 249 // selection handler for supplying data 
 250 //----------------------------------------------------------------------------- 
 254 selection_handler( GtkWidget 
*WXUNUSED(widget
), 
 255                    GtkSelectionData 
*selection_data
, 
 256                    guint 
WXUNUSED(info
), 
 257                    guint 
WXUNUSED(time
), 
 258                    gpointer signal_data 
) 
 260     wxClipboard 
* const clipboard 
= wxTheClipboard
; 
 264     wxDataObject 
* const data 
= clipboard
->GTKGetDataObject(); 
 268     // ICCCM says that TIMESTAMP is a required atom. 
 269     // In particular, it satisfies Klipper, which polls 
 270     // TIMESTAMP to see if the clipboards content has changed. 
 271     // It shall return the time which was used to set the data. 
 272     if (selection_data
->target 
== g_timestampAtom
) 
 274         guint timestamp 
= GPOINTER_TO_UINT (signal_data
); 
 275         gtk_selection_data_set(selection_data
, 
 276                                GDK_SELECTION_TYPE_INTEGER
, 
 278                                (guchar
*)&(timestamp
), 
 280         wxLogTrace(TRACE_CLIPBOARD
, 
 281                    wxT("Clipboard TIMESTAMP requested, returning timestamp=%u"), 
 286     wxDataFormat 
format( selection_data
->target 
); 
 288     wxLogTrace(TRACE_CLIPBOARD
, 
 289                wxT("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"), 
 290                format
.GetId().c_str(), 
 291                wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->target
))).c_str(), 
 292                wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->type
))).c_str(), 
 293                wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data
->selection
))).c_str(), 
 294                GPOINTER_TO_UINT( signal_data 
) 
 297     if ( !data
->IsSupportedFormat( format 
) ) 
 300     int size 
= data
->GetDataSize( format 
); 
 304     wxCharBuffer 
buf(size 
- 1); // it adds 1 internally (for NUL) 
 306     // text data must be returned in UTF8 if format is wxDF_UNICODETEXT 
 307     if ( !data
->GetDataHere(format
, buf
.data()) ) 
 310     // use UTF8_STRING format if requested in Unicode build but just plain 
 311     // STRING one in ANSI or if explicitly asked in Unicode 
 313     if (format 
== wxDataFormat(wxDF_UNICODETEXT
)) 
 315         gtk_selection_data_set_text( 
 317             (const gchar
*)buf
.data(), 
 321 #endif // wxUSE_UNICODE 
 323         gtk_selection_data_set( 
 325             GDK_SELECTION_TYPE_STRING
, 
 327             (const guchar
*)buf
.data(), 
 333 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData
& sel
) 
 335     wxCHECK_RET( m_receivedData
, wxT("should be inside GetData()") ); 
 337     const wxDataFormat 
format(sel
.target
); 
 338     wxLogTrace(TRACE_CLIPBOARD
, wxT("Received selection %s"), 
 339                format
.GetId().c_str()); 
 341     if ( !m_receivedData
->IsSupportedFormat(format
) ) 
 344     m_receivedData
->SetData(format
, sel
.length
, sel
.data
); 
 345     m_formatSupported 
= true; 
 348 //----------------------------------------------------------------------------- 
 349 // asynchronous "selection_received" for targets 
 350 //----------------------------------------------------------------------------- 
 354 async_targets_selection_received( GtkWidget 
*WXUNUSED(widget
), 
 355                             GtkSelectionData 
*selection_data
, 
 356                             guint32 
WXUNUSED(time
), 
 357                             wxClipboard 
*clipboard 
) 
 359     if ( !clipboard 
) // Assert? 
 362     if (!clipboard
->m_sink
) 
 365     wxClipboardEvent 
*event 
= new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED
); 
 366     event
->SetEventObject( clipboard 
); 
 368     if ( !selection_data 
|| selection_data
->length 
<= 0 ) 
 370         clipboard
->m_sink
->QueueEvent( event 
); 
 371         clipboard
->m_sink
.Release(); 
 375     // make sure we got the data in the correct form 
 376     GdkAtom type 
= selection_data
->type
; 
 377     if ( type 
!= GDK_SELECTION_TYPE_ATOM 
) 
 379         if ( strcmp(wxGtkString(gdk_atom_name(type
)), "TARGETS") != 0 ) 
 381             wxLogTrace( TRACE_CLIPBOARD
, 
 382                         wxT("got unsupported clipboard target") ); 
 384             clipboard
->m_sink
->QueueEvent( event 
); 
 385             clipboard
->m_sink
.Release(); 
 390     // it's not really a format, of course, but we can reuse its GetId() method 
 391     // to format this atom as string 
 392     wxDataFormat 
clip(selection_data
->selection
); 
 393     wxLogTrace( TRACE_CLIPBOARD
, 
 394                 wxT("Received available formats for clipboard %s"), 
 395                 clip
.GetId().c_str() ); 
 397     // the atoms we received, holding a list of targets (= formats) 
 398     const GdkAtom 
* const atoms 
= (GdkAtom 
*)selection_data
->data
; 
 399     for ( size_t i 
= 0; i 
< selection_data
->length
/sizeof(GdkAtom
); i
++ ) 
 401         const wxDataFormat 
format(atoms
[i
]); 
 403         wxLogTrace(TRACE_CLIPBOARD
, wxT("\t%s"), format
.GetId().c_str()); 
 405         event
->AddFormat( format 
); 
 408     clipboard
->m_sink
->QueueEvent( event 
); 
 409     clipboard
->m_sink
.Release(); 
 413 // ============================================================================ 
 414 // wxClipboard implementation 
 415 // ============================================================================ 
 417 // ---------------------------------------------------------------------------- 
 418 // wxClipboard ctor/dtor 
 419 // ---------------------------------------------------------------------------- 
 421 IMPLEMENT_DYNAMIC_CLASS(wxClipboard
,wxObject
) 
 423 wxClipboard::wxClipboard() 
 429     m_receivedData 
= NULL
; 
 431     m_formatSupported 
= false; 
 432     m_targetRequested 
= 0; 
 434     // we use m_targetsWidget to query what formats are available 
 435     m_targetsWidget 
= gtk_window_new( GTK_WINDOW_POPUP 
); 
 436     gtk_widget_realize( m_targetsWidget 
); 
 438     g_signal_connect (m_targetsWidget
, "selection_received", 
 439                       G_CALLBACK (targets_selection_received
), this); 
 441     // we use m_targetsWidgetAsync to query what formats are available asynchronously 
 442     m_targetsWidgetAsync 
= gtk_window_new( GTK_WINDOW_POPUP 
); 
 443     gtk_widget_realize( m_targetsWidgetAsync 
); 
 445     g_signal_connect (m_targetsWidgetAsync
, "selection_received", 
 446                       G_CALLBACK (async_targets_selection_received
), this); 
 448     // we use m_clipboardWidget to get and to offer data 
 449     m_clipboardWidget 
= gtk_window_new( GTK_WINDOW_POPUP 
); 
 450     gtk_widget_realize( m_clipboardWidget 
); 
 452     g_signal_connect (m_clipboardWidget
, "selection_received", 
 453                       G_CALLBACK (selection_received
), this); 
 455     g_signal_connect (m_clipboardWidget
, "selection_clear_event", 
 456                       G_CALLBACK (selection_clear_clip
), NULL
); 
 458     // initialize atoms we use if not done yet 
 459     if ( !g_clipboardAtom 
) 
 460         g_clipboardAtom 
= gdk_atom_intern( "CLIPBOARD", FALSE 
); 
 461     if ( !g_targetsAtom 
) 
 462         g_targetsAtom 
= gdk_atom_intern ("TARGETS", FALSE
); 
 463     if ( !g_timestampAtom 
) 
 464         g_timestampAtom 
= gdk_atom_intern ("TIMESTAMP", FALSE
); 
 467 wxClipboard::~wxClipboard() 
 471     gtk_widget_destroy( m_clipboardWidget 
); 
 472     gtk_widget_destroy( m_targetsWidget 
); 
 475 // ---------------------------------------------------------------------------- 
 476 // wxClipboard helper functions 
 477 // ---------------------------------------------------------------------------- 
 479 GdkAtom 
wxClipboard::GTKGetClipboardAtom() const 
 481     return m_usePrimary 
? (GdkAtom
)GDK_SELECTION_PRIMARY
 
 485 void wxClipboard::GTKClearData(Kind kind
) 
 487     wxDataObject 
*&data 
= Data(kind
); 
 491 bool wxClipboard::SetSelectionOwner(bool set
) 
 493     bool rc 
= gtk_selection_owner_set
 
 495                 set 
? m_clipboardWidget 
: NULL
, 
 496                 GTKGetClipboardAtom(), 
 497                 (guint32
)GDK_CURRENT_TIME
 
 502         wxLogTrace(TRACE_CLIPBOARD
, wxT("Failed to %sset selection owner"), 
 503                    set 
? wxT("") : wxT("un")); 
 509 void wxClipboard::AddSupportedTarget(GdkAtom atom
) 
 511     gtk_selection_add_target
 
 514         GTKGetClipboardAtom(), 
 516         0 // info (same as client data) unused 
 520 bool wxClipboard::IsSupportedAsync(wxEvtHandler 
*sink
) 
 523         return false;  // currently busy, come back later 
 525     wxCHECK_MSG( sink
, false, wxT("no sink given") ); 
 528     gtk_selection_convert( m_targetsWidgetAsync
, 
 529                            GTKGetClipboardAtom(), 
 531                            (guint32
) GDK_CURRENT_TIME 
); 
 536 bool wxClipboard::DoIsSupported(const wxDataFormat
& format
) 
 538     wxCHECK_MSG( format
, false, wxT("invalid clipboard format") ); 
 540     wxLogTrace(TRACE_CLIPBOARD
, wxT("Checking if format %s is available"), 
 541                format
.GetId().c_str()); 
 543     // these variables will be used by our GTKOnTargetReceived() 
 544     m_targetRequested 
= format
; 
 545     m_formatSupported 
= false; 
 547     // block until m_formatSupported is set from targets_selection_received 
 550         wxClipboardSync 
sync(*this); 
 552         gtk_selection_convert( m_targetsWidget
, 
 553                                GTKGetClipboardAtom(), 
 555                                (guint32
) GDK_CURRENT_TIME 
); 
 558     return m_formatSupported
; 
 561 // ---------------------------------------------------------------------------- 
 562 // wxClipboard public API implementation 
 563 // ---------------------------------------------------------------------------- 
 565 void wxClipboard::Clear() 
 567     gtk_selection_clear_targets( m_clipboardWidget
, GTKGetClipboardAtom() ); 
 569     if ( gdk_selection_owner_get(GTKGetClipboardAtom()) == 
 570             m_clipboardWidget
->window 
) 
 572         wxClipboardSync 
sync(*this); 
 574         // this will result in selection_clear_clip callback being called and 
 575         // it will free our data 
 576         SetSelectionOwner(false); 
 579     m_targetRequested 
= 0; 
 580     m_formatSupported 
= false; 
 583 bool wxClipboard::Open() 
 585     wxCHECK_MSG( !m_open
, false, wxT("clipboard already open") ); 
 592 bool wxClipboard::SetData( wxDataObject 
*data 
) 
 594     wxCHECK_MSG( m_open
, false, wxT("clipboard not open") ); 
 596     wxCHECK_MSG( data
, false, wxT("data is invalid") ); 
 600     return AddData( data 
); 
 603 bool wxClipboard::AddData( wxDataObject 
*data 
) 
 605     wxCHECK_MSG( m_open
, false, wxT("clipboard not open") ); 
 607     wxCHECK_MSG( data
, false, wxT("data is invalid") ); 
 609     // we can only store one wxDataObject so clear the old one 
 614     // get formats from wxDataObjects 
 615     const size_t count 
= data
->GetFormatCount(); 
 616     wxDataFormatArray 
formats(new wxDataFormat
[count
]); 
 617     data
->GetAllFormats(formats
.get()); 
 619     // always provide TIMESTAMP as a target, see comments in selection_handler 
 621     AddSupportedTarget(g_timestampAtom
); 
 623     for ( size_t i 
= 0; i 
< count
; i
++ ) 
 625         const wxDataFormat 
format(formats
[i
]); 
 627         wxLogTrace(TRACE_CLIPBOARD
, wxT("Adding support for %s"), 
 628                    format
.GetId().c_str()); 
 630         AddSupportedTarget(format
); 
 633     g_signal_connect (m_clipboardWidget
, "selection_get", 
 634                       G_CALLBACK (selection_handler
), 
 635                       GUINT_TO_POINTER (gtk_get_current_event_time()) ); 
 637     // tell the world we offer clipboard data 
 638     return SetSelectionOwner(); 
 641 void wxClipboard::Close() 
 643     wxCHECK_RET( m_open
, wxT("clipboard not open") ); 
 648 bool wxClipboard::IsOpened() const 
 653 bool wxClipboard::IsSupported( const wxDataFormat
& format 
) 
 655     if ( DoIsSupported(format
) ) 
 659     if ( format 
== wxDF_UNICODETEXT 
) 
 661         // also with plain STRING format 
 662         return DoIsSupported(g_altTextAtom
); 
 664 #endif // wxUSE_UNICODE 
 669 bool wxClipboard::GetData( wxDataObject
& data 
) 
 671     wxCHECK_MSG( m_open
, false, wxT("clipboard not open") ); 
 673     // get all supported formats from wxDataObjects: notice that we are setting 
 674     // the object data, so we need them in "Set" direction 
 675     const size_t count 
= data
.GetFormatCount(wxDataObject::Set
); 
 676     wxDataFormatArray 
formats(new wxDataFormat
[count
]); 
 677     data
.GetAllFormats(formats
.get(), wxDataObject::Set
); 
 679     for ( size_t i 
= 0; i 
< count
; i
++ ) 
 681         const wxDataFormat 
format(formats
[i
]); 
 683         // is this format supported by clipboard ? 
 684         if ( !DoIsSupported(format
) ) 
 687         wxLogTrace(TRACE_CLIPBOARD
, wxT("Requesting format %s"), 
 688                    format
.GetId().c_str()); 
 690         // these variables will be used by our GTKOnSelectionReceived() 
 691         m_receivedData 
= &data
; 
 692         m_formatSupported 
= false; 
 695             wxClipboardSync 
sync(*this); 
 697             gtk_selection_convert(m_clipboardWidget
, 
 698                                   GTKGetClipboardAtom(), 
 700                                   (guint32
) GDK_CURRENT_TIME 
); 
 701         } // wait until we get the results 
 704            Normally this is a true error as we checked for the presence of such 
 705            data before, but there are applications that may return an empty 
 706            string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied) 
 707            which would produce a false error message here, so we check for the 
 708            size of the string first. With ANSI, GetDataSize returns an extra 
 709            value (for the closing null?), with unicode, the exact number of 
 710            tokens is given (that is more than 1 for non-ASCII characters) 
 711            (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2) 
 714         if ( format 
!= wxDF_UNICODETEXT 
|| data
.GetDataSize(format
) > 0 ) 
 716         if ( format 
!= wxDF_TEXT 
|| data
.GetDataSize(format
) > 1 ) 
 717 #endif // UNICODE / !UNICODE 
 719             wxCHECK_MSG( m_formatSupported
, false, 
 720                          wxT("error retrieving data from clipboard") ); 
 726     wxLogTrace(TRACE_CLIPBOARD
, wxT("GetData(): format not found")); 
 731 #endif // wxUSE_CLIPBOARD