]> git.saurik.com Git - wxWidgets.git/blobdiff - src/gtk/clipbrd.cpp
Copy recently added wxGenericValidator fields in Copy().
[wxWidgets.git] / src / gtk / clipbrd.cpp
index 628a663fbd4a8de9357e351628c793a1ea28ee78..6e02151f4be01e56acd769335aeb58da61497047 100644 (file)
 #include "wx/clipbrd.h"
 
 #ifndef WX_PRECOMP
+    #include "wx/app.h"
     #include "wx/log.h"
     #include "wx/utils.h"
     #include "wx/dataobj.h"
 #endif
 
-#include "wx/ptr_scpd.h"
+#include "wx/scopedarray.h"
 #include "wx/scopeguard.h"
+#include "wx/evtloop.h"
 
 #include "wx/gtk/private.h"
 
-wxDECLARE_SCOPED_ARRAY(wxDataFormat, wxDataFormatArray)
-wxDEFINE_SCOPED_ARRAY(wxDataFormat, wxDataFormatArray)
+typedef wxScopedArray<wxDataFormat> wxDataFormatArray;
 
 // ----------------------------------------------------------------------------
 // data
@@ -52,7 +53,7 @@ extern GdkAtom g_altTextAtom;
 // the trace mask we use with wxLogTrace() - call
 // wxLog::AddTraceMask(TRACE_CLIPBOARD) to enable the trace messages from here
 // (there will be a *lot* of them!)
-#define TRACE_CLIPBOARD _T("clipboard")
+#define TRACE_CLIPBOARD wxT("clipboard")
 
 // ----------------------------------------------------------------------------
 // wxClipboardSync: used to perform clipboard operations synchronously
@@ -68,36 +69,52 @@ class wxClipboardSync
 public:
     wxClipboardSync(wxClipboard& clipboard)
     {
-        wxASSERT_MSG( !ms_clipboard, _T("reentrancy in clipboard code") );
+        wxASSERT_MSG( !ms_clipboard, wxT("reentrancy in clipboard code") );
         ms_clipboard = &clipboard;
     }
 
     ~wxClipboardSync()
     {
-        while ( ms_clipboard )
-            gtk_main_iteration();
+#if wxUSE_CONSOLE_EVENTLOOP
+        // ensure that there is a running event loop: this might not be the
+        // case if we're called before the main event loop startup
+        wxEventLoopGuarantor ensureEventLoop;
+#endif
+        while (ms_clipboard)
+            wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_CLIPBOARD);
     }
 
     // this method must be called by GTK+ callbacks to indicate that we got the
     // result for our clipboard operation
-    static void OnDone(wxClipboard *clipboard)
+    static void OnDone(wxClipboard * WXUNUSED_UNLESS_DEBUG(clipboard))
     {
         wxASSERT_MSG( clipboard == ms_clipboard,
-                        _T("got notification for alien clipboard") );
+                        wxT("got notification for alien clipboard") );
 
         ms_clipboard = NULL;
     }
 
+    // this method should be called if it's possible that no async clipboard
+    // operation is currently in progress (like it can be the case when the
+    // clipboard is cleared but not because we asked about it), it should only
+    // be called if such situation is expected -- otherwise call OnDone() which
+    // would assert in this case
+    static void OnDoneIfInProgress(wxClipboard *clipboard)
+    {
+        if ( ms_clipboard )
+            OnDone(clipboard);
+    }
+
 private:
     static wxClipboard *ms_clipboard;
 
-    DECLARE_NO_COPY_CLASS(wxClipboardSync)
+    wxDECLARE_NO_COPY_CLASS(wxClipboardSync);
 };
 
 wxClipboard *wxClipboardSync::ms_clipboard = NULL;
 
 // ============================================================================
-// clipboard ca;backs implementation
+// clipboard callbacks implementation
 // ============================================================================
 
 //-----------------------------------------------------------------------------
@@ -116,34 +133,36 @@ targets_selection_received( GtkWidget *WXUNUSED(widget),
 
     wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
 
-    if ( !selection_data || selection_data->length <= 0 )
+    if (!selection_data)
+        return;
+
+    const int selection_data_length = gtk_selection_data_get_length(selection_data);
+    if (selection_data_length <= 0)
         return;
 
     // make sure we got the data in the correct form
-    GdkAtom type = selection_data->type;
+    GdkAtom type = gtk_selection_data_get_data_type(selection_data);
     if ( type != GDK_SELECTION_TYPE_ATOM )
     {
         if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
         {
             wxLogTrace( TRACE_CLIPBOARD,
-                        _T("got unsupported clipboard target") );
+                        wxT("got unsupported clipboard target") );
 
             return;
         }
     }
 
-#ifdef __WXDEBUG__
     // it's not really a format, of course, but we can reuse its GetId() method
     // to format this atom as string
-    wxDataFormat clip(selection_data->selection);
+    wxDataFormat clip(gtk_selection_data_get_selection(selection_data));
     wxLogTrace( TRACE_CLIPBOARD,
                 wxT("Received available formats for clipboard %s"),
                 clip.GetId().c_str() );
-#endif // __WXDEBUG__
 
     // the atoms we received, holding a list of targets (= formats)
-    const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
-    for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
+    const GdkAtom* const atoms = (GdkAtom*)gtk_selection_data_get_data(selection_data);
+    for (size_t i = 0; i < selection_data_length / sizeof(GdkAtom); i++)
     {
         const wxDataFormat format(atoms[i]);
 
@@ -180,7 +199,7 @@ selection_received( GtkWidget *WXUNUSED(widget),
 
     wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
 
-    if ( !selection_data || selection_data->length <= 0 )
+    if (!selection_data || gtk_selection_data_get_length(selection_data) <= 0)
         return;
 
     clipboard->GTKOnSelectionReceived(*selection_data);
@@ -199,7 +218,12 @@ selection_clear_clip( GtkWidget *WXUNUSED(widget), GdkEventSelection *event )
     if ( !clipboard )
         return TRUE;
 
-    wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
+    // notice the use of OnDoneIfInProgress() here instead of just OnDone():
+    // it's perfectly possible that we're receiving this notification from GTK+
+    // even though we hadn't cleared the clipboard ourselves but because
+    // another application (or even another window in the same program)
+    // acquired it
+    wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress, clipboard);
 
     wxClipboard::Kind kind;
     if (event->selection == GDK_SELECTION_PRIMARY)
@@ -242,7 +266,8 @@ selection_handler( GtkWidget *WXUNUSED(widget),
     if ( !clipboard )
         return;
 
-    wxDataObject * const data = clipboard->GTKGetDataObject();
+    wxDataObject * const data = clipboard->GTKGetDataObject(
+        gtk_selection_data_get_selection(selection_data));
     if ( !data )
         return;
 
@@ -250,7 +275,7 @@ selection_handler( GtkWidget *WXUNUSED(widget),
     // In particular, it satisfies Klipper, which polls
     // TIMESTAMP to see if the clipboards content has changed.
     // It shall return the time which was used to set the data.
-    if (selection_data->target == g_timestampAtom)
+    if (gtk_selection_data_get_target(selection_data) == g_timestampAtom)
     {
         guint timestamp = GPOINTER_TO_UINT (signal_data);
         gtk_selection_data_set(selection_data,
@@ -259,53 +284,53 @@ selection_handler( GtkWidget *WXUNUSED(widget),
                                (guchar*)&(timestamp),
                                sizeof(timestamp));
         wxLogTrace(TRACE_CLIPBOARD,
-                   _T("Clipboard TIMESTAMP requested, returning timestamp=%u"),
+                   wxT("Clipboard TIMESTAMP requested, returning timestamp=%u"),
                    timestamp);
         return;
     }
 
-    wxDataFormat format( selection_data->target );
+    wxDataFormat format(gtk_selection_data_get_target(selection_data));
 
-#ifdef __WXDEBUG__
     wxLogTrace(TRACE_CLIPBOARD,
-               _T("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
+               wxT("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
                format.GetId().c_str(),
-               wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->target))).c_str(),
-               wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->type))).c_str(),
-               wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->selection))).c_str(),
+               wxString::FromAscii(wxGtkString(gdk_atom_name(gtk_selection_data_get_target(selection_data)))).c_str(),
+               wxString::FromAscii(wxGtkString(gdk_atom_name(gtk_selection_data_get_data_type(selection_data)))).c_str(),
+               wxString::FromAscii(wxGtkString(gdk_atom_name(gtk_selection_data_get_selection(selection_data)))).c_str(),
                GPOINTER_TO_UINT( signal_data )
                );
-#endif
 
-    if (!data->IsSupportedFormat( format )) return;
+    if ( !data->IsSupportedFormat( format ) )
+        return;
 
     int size = data->GetDataSize( format );
+    if ( !size )
+        return;
 
-    if (size == 0) return;
-
-    void *d = malloc(size);
-    wxON_BLOCK_EXIT1(free, d);
+    wxCharBuffer buf(size - 1); // it adds 1 internally (for NUL)
 
-    // Text data will be in UTF8 in Unicode mode.
-    data->GetDataHere( selection_data->target, d );
+    // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
+    if ( !data->GetDataHere(format, buf.data()) )
+        return;
 
-    // NB: GTK+ requires special treatment of UTF8_STRING data, the text
-    //     would show as UTF-8 data interpreted as latin1 (?) in other
-    //     GTK+ apps if we used gtk_selection_data_set()
+    // use UTF8_STRING format if requested in Unicode build but just plain
+    // STRING one in ANSI or if explicitly asked in Unicode
+#if wxUSE_UNICODE
     if (format == wxDataFormat(wxDF_UNICODETEXT))
     {
         gtk_selection_data_set_text(
             selection_data,
-            (const gchar*)d,
+            (const gchar*)buf.data(),
             size );
     }
     else
+#endif // wxUSE_UNICODE
     {
         gtk_selection_data_set(
             selection_data,
             GDK_SELECTION_TYPE_STRING,
             8*sizeof(gchar),
-            (unsigned char*) d,
+            (const guchar*)buf.data(),
             size );
     }
 }
@@ -313,19 +338,90 @@ selection_handler( GtkWidget *WXUNUSED(widget),
 
 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData& sel)
 {
-    wxCHECK_RET( m_receivedData, _T("should be inside GetData()") );
+    wxCHECK_RET( m_receivedData, wxT("should be inside GetData()") );
 
-    const wxDataFormat format(sel.target);
-    wxLogTrace(TRACE_CLIPBOARD, _T("Received selection %s"),
+    const wxDataFormat format(gtk_selection_data_get_target(const_cast<GtkSelectionData*>(&sel)));
+    wxLogTrace(TRACE_CLIPBOARD, wxT("Received selection %s"),
                format.GetId().c_str());
 
     if ( !m_receivedData->IsSupportedFormat(format) )
         return;
 
-    m_receivedData->SetData(format, sel.length, sel.data);
+    m_receivedData->SetData(format,
+        gtk_selection_data_get_length(const_cast<GtkSelectionData*>(&sel)),
+        gtk_selection_data_get_data(const_cast<GtkSelectionData*>(&sel)));
     m_formatSupported = true;
 }
 
+//-----------------------------------------------------------------------------
+// asynchronous "selection_received" for targets
+//-----------------------------------------------------------------------------
+
+extern "C" {
+static void
+async_targets_selection_received( GtkWidget *WXUNUSED(widget),
+                            GtkSelectionData *selection_data,
+                            guint32 WXUNUSED(time),
+                            wxClipboard *clipboard )
+{
+    if ( !clipboard ) // Assert?
+        return;
+
+    if (!clipboard->m_sink)
+        return;
+
+    wxClipboardEvent *event = new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED);
+    event->SetEventObject( clipboard );
+
+    int selection_data_length = 0;
+    if (selection_data)
+        selection_data_length = gtk_selection_data_get_length(selection_data);
+
+    if (selection_data_length <= 0)
+    {
+        clipboard->m_sink->QueueEvent( event );
+        clipboard->m_sink.Release();
+        return;
+    }
+
+    // make sure we got the data in the correct form
+    GdkAtom type = gtk_selection_data_get_data_type(selection_data);
+    if ( type != GDK_SELECTION_TYPE_ATOM )
+    {
+        if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
+        {
+            wxLogTrace( TRACE_CLIPBOARD,
+                        wxT("got unsupported clipboard target") );
+
+            clipboard->m_sink->QueueEvent( event );
+            clipboard->m_sink.Release();
+            return;
+        }
+    }
+
+    // it's not really a format, of course, but we can reuse its GetId() method
+    // to format this atom as string
+    wxDataFormat clip(gtk_selection_data_get_selection(selection_data));
+    wxLogTrace( TRACE_CLIPBOARD,
+                wxT("Received available formats for clipboard %s"),
+                clip.GetId().c_str() );
+
+    // the atoms we received, holding a list of targets (= formats)
+    const GdkAtom* const atoms = (GdkAtom*)gtk_selection_data_get_data(selection_data);
+    for (size_t i = 0; i < selection_data_length / sizeof(GdkAtom); i++)
+    {
+        const wxDataFormat format(atoms[i]);
+
+        wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
+
+        event->AddFormat( format );
+    }
+
+    clipboard->m_sink->QueueEvent( event );
+    clipboard->m_sink.Release();
+}
+}
+
 // ============================================================================
 // wxClipboard implementation
 // ============================================================================
@@ -354,6 +450,13 @@ wxClipboard::wxClipboard()
     g_signal_connect (m_targetsWidget, "selection_received",
                       G_CALLBACK (targets_selection_received), this);
 
+    // we use m_targetsWidgetAsync to query what formats are available asynchronously
+    m_targetsWidgetAsync = gtk_window_new( GTK_WINDOW_POPUP );
+    gtk_widget_realize( m_targetsWidgetAsync );
+
+    g_signal_connect (m_targetsWidgetAsync, "selection_received",
+                      G_CALLBACK (async_targets_selection_received), this);
+
     // we use m_clipboardWidget to get and to offer data
     m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
     gtk_widget_realize( m_clipboardWidget );
@@ -377,10 +480,8 @@ wxClipboard::~wxClipboard()
 {
     Clear();
 
-    if ( m_clipboardWidget )
-        gtk_widget_destroy( m_clipboardWidget );
-    if ( m_targetsWidget )
-        gtk_widget_destroy( m_targetsWidget );
+    gtk_widget_destroy( m_clipboardWidget );
+    gtk_widget_destroy( m_targetsWidget );
 }
 
 // ----------------------------------------------------------------------------
@@ -395,12 +496,8 @@ GdkAtom wxClipboard::GTKGetClipboardAtom() const
 
 void wxClipboard::GTKClearData(Kind kind)
 {
-    wxDataObject *&data = Data();
-    if ( data )
-    {
-        delete data;
-        data = NULL;
-    }
+    wxDataObject *&data = Data(kind);
+    wxDELETE(data);
 }
 
 bool wxClipboard::SetSelectionOwner(bool set)
@@ -414,8 +511,8 @@ bool wxClipboard::SetSelectionOwner(bool set)
 
     if ( !rc )
     {
-        wxLogTrace(TRACE_CLIPBOARD, _T("Failed to %sset selection owner"),
-                   set ? _T("") : _T("un"));
+        wxLogTrace(TRACE_CLIPBOARD, wxT("Failed to %sset selection owner"),
+                   set ? wxT("") : wxT("un"));
     }
 
     return rc;
@@ -425,13 +522,29 @@ void wxClipboard::AddSupportedTarget(GdkAtom atom)
 {
     gtk_selection_add_target
     (
-        GTK_WIDGET(m_clipboardWidget),
+        m_clipboardWidget,
         GTKGetClipboardAtom(),
         atom,
         0 // info (same as client data) unused
     );
 }
 
+bool wxClipboard::IsSupportedAsync(wxEvtHandler *sink)
+{
+    if (m_sink.get())
+        return false;  // currently busy, come back later
+
+    wxCHECK_MSG( sink, false, wxT("no sink given") );
+
+    m_sink = sink;
+    gtk_selection_convert( m_targetsWidgetAsync,
+                           GTKGetClipboardAtom(),
+                           g_targetsAtom,
+                           (guint32) GDK_CURRENT_TIME );
+
+    return true;
+}
+
 bool wxClipboard::DoIsSupported(const wxDataFormat& format)
 {
     wxCHECK_MSG( format, false, wxT("invalid clipboard format") );
@@ -463,8 +576,10 @@ bool wxClipboard::DoIsSupported(const wxDataFormat& format)
 
 void wxClipboard::Clear()
 {
+    gtk_selection_clear_targets( m_clipboardWidget, GTKGetClipboardAtom() );
+
     if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
-            m_clipboardWidget->window )
+            gtk_widget_get_window(m_clipboardWidget) )
     {
         wxClipboardSync sync(*this);
 
@@ -567,10 +682,11 @@ bool wxClipboard::GetData( wxDataObject& data )
 {
     wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
 
-    // get all supported formats from wxDataObjects
-    const size_t count = data.GetFormatCount();
+    // get all supported formats from wxDataObjects: notice that we are setting
+    // the object data, so we need them in "Set" direction
+    const size_t count = data.GetFormatCount(wxDataObject::Set);
     wxDataFormatArray formats(new wxDataFormat[count]);
-    data.GetAllFormats(formats.get());
+    data.GetAllFormats(formats.get(), wxDataObject::Set);
 
     for ( size_t i = 0; i < count; i++ )
     {
@@ -624,4 +740,27 @@ bool wxClipboard::GetData( wxDataObject& data )
     return false;
 }
 
+wxDataObject* wxClipboard::GTKGetDataObject( GdkAtom atom )
+{
+    if ( atom == GDK_NONE )
+        return Data();
+
+    if ( atom == GDK_SELECTION_PRIMARY )
+    {
+        wxLogTrace(TRACE_CLIPBOARD, wxT("Primary selection requested" ));
+
+        return Data( wxClipboard::Primary );
+    }
+    else if ( atom == g_clipboardAtom )
+    {
+        wxLogTrace(TRACE_CLIPBOARD, wxT("Clipboard data requested" ));
+
+        return Data( wxClipboard::Clipboard );
+    }
+    else // some other selection, we're not concerned
+    {
+        return (wxDataObject*)NULL;
+    }
+}
+
 #endif // wxUSE_CLIPBOARD