// Name: src/gtk/clipbrd.cpp
// Purpose: wxClipboard implementation for wxGTK
// Author: Robert Roebling, Vadim Zeitlin
-// Id: $Id$
// Copyright: (c) 1998 Robert Roebling
// (c) 2007 Vadim Zeitlin
// Licence: wxWindows licence
#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
// 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
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
// ============================================================================
//-----------------------------------------------------------------------------
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]);
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);
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)
if ( !clipboard )
return;
- wxDataObject * const data = clipboard->GTKGetDataObject();
+ wxDataObject * const data = clipboard->GTKGetDataObject(
+ gtk_selection_data_get_selection(selection_data));
if ( !data )
return;
// 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,
(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,
+ format.GetFormatId(),
8*sizeof(gchar),
- (unsigned char*) d,
+ (const guchar*)buf.data(),
size );
}
}
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) )
+ if ( !m_receivedData->IsSupportedFormat(format, wxDataObject::Set) )
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
// ============================================================================
wxClipboard::wxClipboard()
{
+ m_idSelectionGetHandler = 0;
+
m_open = false;
m_dataPrimary =
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 );
{
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 );
}
// ----------------------------------------------------------------------------
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)
set ? m_clipboardWidget : NULL,
GTKGetClipboardAtom(),
(guint32)GDK_CURRENT_TIME
- );
+ ) != 0;
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;
{
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") );
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);
AddSupportedTarget(format);
}
- g_signal_connect (m_clipboardWidget, "selection_get",
+ if ( !m_idSelectionGetHandler )
+ {
+ m_idSelectionGetHandler = g_signal_connect (
+ m_clipboardWidget, "selection_get",
G_CALLBACK (selection_handler),
GUINT_TO_POINTER (gtk_get_current_event_time()) );
+ }
// tell the world we offer clipboard data
return SetSelectionOwner();
{
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++ )
{
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