]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/gtk/clipbrd.cpp
don't save the initial selection if it's invalid in CBN_DROPDOWN (refixes #8474 witho...
[wxWidgets.git] / src / gtk / clipbrd.cpp
... / ...
CommitLineData
1/////////////////////////////////////////////////////////////////////////////
2// Name: src/gtk/clipbrd.cpp
3// Purpose: wxClipboard implementation for wxGTK
4// Author: Robert Roebling, Vadim Zeitlin
5// Id: $Id$
6// Copyright: (c) 1998 Robert Roebling
7// (c) 2007 Vadim Zeitlin
8// Licence: wxWindows licence
9/////////////////////////////////////////////////////////////////////////////
10
11// ============================================================================
12// declarations
13// ============================================================================
14
15// ----------------------------------------------------------------------------
16// headers
17// ----------------------------------------------------------------------------
18
19// For compilers that support precompilation, includes "wx.h".
20#include "wx/wxprec.h"
21
22#if wxUSE_CLIPBOARD
23
24#include "wx/clipbrd.h"
25
26#ifndef WX_PRECOMP
27 #include "wx/log.h"
28 #include "wx/utils.h"
29 #include "wx/dataobj.h"
30#endif
31
32#include "wx/ptr_scpd.h"
33#include "wx/scopeguard.h"
34
35#include "wx/gtk/private.h"
36
37wxDECLARE_SCOPED_ARRAY(wxDataFormat, wxDataFormatArray)
38wxDEFINE_SCOPED_ARRAY(wxDataFormat, wxDataFormatArray)
39
40// ----------------------------------------------------------------------------
41// data
42// ----------------------------------------------------------------------------
43
44static GdkAtom g_clipboardAtom = 0;
45static GdkAtom g_targetsAtom = 0;
46static GdkAtom g_timestampAtom = 0;
47
48#if wxUSE_UNICODE
49extern GdkAtom g_altTextAtom;
50#endif
51
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")
56
57// ----------------------------------------------------------------------------
58// wxClipboardSync: used to perform clipboard operations synchronously
59// ----------------------------------------------------------------------------
60
61// constructing this object on stack will wait wait until the latest clipboard
62// operation is finished on block exit
63//
64// notice that there can be no more than one such object alive at any moment,
65// i.e. reentrancies are not allowed
66class wxClipboardSync
67{
68public:
69 wxClipboardSync(wxClipboard& clipboard)
70 {
71 wxASSERT_MSG( !ms_clipboard, _T("reentrancy in clipboard code") );
72 ms_clipboard = &clipboard;
73 }
74
75 ~wxClipboardSync()
76 {
77 while ( ms_clipboard )
78 gtk_main_iteration();
79 }
80
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))
84 {
85 wxASSERT_MSG( clipboard == ms_clipboard,
86 _T("got notification for alien clipboard") );
87
88 ms_clipboard = NULL;
89 }
90
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)
97 {
98 if ( ms_clipboard )
99 OnDone(clipboard);
100 }
101
102private:
103 static wxClipboard *ms_clipboard;
104
105 DECLARE_NO_COPY_CLASS(wxClipboardSync)
106};
107
108wxClipboard *wxClipboardSync::ms_clipboard = NULL;
109
110// ============================================================================
111// clipboard ca;backs implementation
112// ============================================================================
113
114//-----------------------------------------------------------------------------
115// "selection_received" for targets
116//-----------------------------------------------------------------------------
117
118extern "C" {
119static void
120targets_selection_received( GtkWidget *WXUNUSED(widget),
121 GtkSelectionData *selection_data,
122 guint32 WXUNUSED(time),
123 wxClipboard *clipboard )
124{
125 if ( !clipboard )
126 return;
127
128 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
129
130 if ( !selection_data || selection_data->length <= 0 )
131 return;
132
133 // make sure we got the data in the correct form
134 GdkAtom type = selection_data->type;
135 if ( type != GDK_SELECTION_TYPE_ATOM )
136 {
137 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
138 {
139 wxLogTrace( TRACE_CLIPBOARD,
140 _T("got unsupported clipboard target") );
141
142 return;
143 }
144 }
145
146#ifdef __WXDEBUG__
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__
154
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++ )
158 {
159 const wxDataFormat format(atoms[i]);
160
161 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
162
163 if ( clipboard->GTKOnTargetReceived(format) )
164 return;
165 }
166}
167}
168
169bool wxClipboard::GTKOnTargetReceived(const wxDataFormat& format)
170{
171 if ( format != m_targetRequested )
172 return false;
173
174 m_formatSupported = true;
175 return true;
176}
177
178//-----------------------------------------------------------------------------
179// "selection_received" for the actual data
180//-----------------------------------------------------------------------------
181
182extern "C" {
183static void
184selection_received( GtkWidget *WXUNUSED(widget),
185 GtkSelectionData *selection_data,
186 guint32 WXUNUSED(time),
187 wxClipboard *clipboard )
188{
189 if ( !clipboard )
190 return;
191
192 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
193
194 if ( !selection_data || selection_data->length <= 0 )
195 return;
196
197 clipboard->GTKOnSelectionReceived(*selection_data);
198}
199}
200
201//-----------------------------------------------------------------------------
202// "selection_clear"
203//-----------------------------------------------------------------------------
204
205extern "C" {
206static gint
207selection_clear_clip( GtkWidget *WXUNUSED(widget), GdkEventSelection *event )
208{
209 wxClipboard * const clipboard = wxTheClipboard;
210 if ( !clipboard )
211 return TRUE;
212
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)
217 // acquired it
218 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress, clipboard);
219
220 wxClipboard::Kind kind;
221 if (event->selection == GDK_SELECTION_PRIMARY)
222 {
223 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost primary selection" ));
224
225 kind = wxClipboard::Primary;
226 }
227 else if (event->selection == g_clipboardAtom)
228 {
229 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost clipboard" ));
230
231 kind = wxClipboard::Clipboard;
232 }
233 else // some other selection, we're not concerned
234 {
235 return FALSE;
236 }
237
238 // the clipboard is no longer in our hands, we don't need data any more
239 clipboard->GTKClearData(kind);
240
241 return TRUE;
242}
243}
244
245//-----------------------------------------------------------------------------
246// selection handler for supplying data
247//-----------------------------------------------------------------------------
248
249extern "C" {
250static void
251selection_handler( GtkWidget *WXUNUSED(widget),
252 GtkSelectionData *selection_data,
253 guint WXUNUSED(info),
254 guint WXUNUSED(time),
255 gpointer signal_data )
256{
257 wxClipboard * const clipboard = wxTheClipboard;
258 if ( !clipboard )
259 return;
260
261 wxDataObject * const data = clipboard->GTKGetDataObject();
262 if ( !data )
263 return;
264
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)
270 {
271 guint timestamp = GPOINTER_TO_UINT (signal_data);
272 gtk_selection_data_set(selection_data,
273 GDK_SELECTION_TYPE_INTEGER,
274 32,
275 (guchar*)&(timestamp),
276 sizeof(timestamp));
277 wxLogTrace(TRACE_CLIPBOARD,
278 _T("Clipboard TIMESTAMP requested, returning timestamp=%u"),
279 timestamp);
280 return;
281 }
282
283 wxDataFormat format( selection_data->target );
284
285#ifdef __WXDEBUG__
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 )
293 );
294#endif // __WXDEBUG__
295
296 if ( !data->IsSupportedFormat( format ) )
297 return;
298
299 int size = data->GetDataSize( format );
300 if ( !size )
301 return;
302
303 wxCharBuffer buf(size - 1); // it adds 1 internally (for NUL)
304
305 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
306 if ( !data->GetDataHere(format, buf.data()) )
307 return;
308
309 // use UTF8_STRING format if requested in Unicode build but just plain
310 // STRING one in ANSI or if explicitly asked in Unicode
311#if wxUSE_UNICODE
312 if (format == wxDataFormat(wxDF_UNICODETEXT))
313 {
314 gtk_selection_data_set_text(
315 selection_data,
316 (const gchar*)buf.data(),
317 size );
318 }
319 else
320#endif // wxUSE_UNICODE
321 {
322 gtk_selection_data_set(
323 selection_data,
324 GDK_SELECTION_TYPE_STRING,
325 8*sizeof(gchar),
326 (const guchar*)buf.data(),
327 size );
328 }
329}
330}
331
332void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData& sel)
333{
334 wxCHECK_RET( m_receivedData, _T("should be inside GetData()") );
335
336 const wxDataFormat format(sel.target);
337 wxLogTrace(TRACE_CLIPBOARD, _T("Received selection %s"),
338 format.GetId().c_str());
339
340 if ( !m_receivedData->IsSupportedFormat(format) )
341 return;
342
343 m_receivedData->SetData(format, sel.length, sel.data);
344 m_formatSupported = true;
345}
346
347// ============================================================================
348// wxClipboard implementation
349// ============================================================================
350
351// ----------------------------------------------------------------------------
352// wxClipboard ctor/dtor
353// ----------------------------------------------------------------------------
354
355IMPLEMENT_DYNAMIC_CLASS(wxClipboard,wxObject)
356
357wxClipboard::wxClipboard()
358{
359 m_open = false;
360
361 m_dataPrimary =
362 m_dataClipboard =
363 m_receivedData = NULL;
364
365 m_formatSupported = false;
366 m_targetRequested = 0;
367
368 // we use m_targetsWidget to query what formats are available
369 m_targetsWidget = gtk_window_new( GTK_WINDOW_POPUP );
370 gtk_widget_realize( m_targetsWidget );
371
372 g_signal_connect (m_targetsWidget, "selection_received",
373 G_CALLBACK (targets_selection_received), this);
374
375 // we use m_clipboardWidget to get and to offer data
376 m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
377 gtk_widget_realize( m_clipboardWidget );
378
379 g_signal_connect (m_clipboardWidget, "selection_received",
380 G_CALLBACK (selection_received), this);
381
382 g_signal_connect (m_clipboardWidget, "selection_clear_event",
383 G_CALLBACK (selection_clear_clip), NULL);
384
385 // initialize atoms we use if not done yet
386 if ( !g_clipboardAtom )
387 g_clipboardAtom = gdk_atom_intern( "CLIPBOARD", FALSE );
388 if ( !g_targetsAtom )
389 g_targetsAtom = gdk_atom_intern ("TARGETS", FALSE);
390 if ( !g_timestampAtom )
391 g_timestampAtom = gdk_atom_intern ("TIMESTAMP", FALSE);
392}
393
394wxClipboard::~wxClipboard()
395{
396 Clear();
397
398 gtk_widget_destroy( m_clipboardWidget );
399 gtk_widget_destroy( m_targetsWidget );
400}
401
402// ----------------------------------------------------------------------------
403// wxClipboard helper functions
404// ----------------------------------------------------------------------------
405
406GdkAtom wxClipboard::GTKGetClipboardAtom() const
407{
408 return m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
409 : g_clipboardAtom;
410}
411
412void wxClipboard::GTKClearData(Kind kind)
413{
414 wxDataObject *&data = Data(kind);
415 if ( data )
416 {
417 delete data;
418 data = NULL;
419 }
420}
421
422bool wxClipboard::SetSelectionOwner(bool set)
423{
424 bool rc = gtk_selection_owner_set
425 (
426 set ? m_clipboardWidget : NULL,
427 GTKGetClipboardAtom(),
428 (guint32)GDK_CURRENT_TIME
429 );
430
431 if ( !rc )
432 {
433 wxLogTrace(TRACE_CLIPBOARD, _T("Failed to %sset selection owner"),
434 set ? _T("") : _T("un"));
435 }
436
437 return rc;
438}
439
440void wxClipboard::AddSupportedTarget(GdkAtom atom)
441{
442 gtk_selection_add_target
443 (
444 m_clipboardWidget,
445 GTKGetClipboardAtom(),
446 atom,
447 0 // info (same as client data) unused
448 );
449}
450
451bool wxClipboard::DoIsSupported(const wxDataFormat& format)
452{
453 wxCHECK_MSG( format, false, wxT("invalid clipboard format") );
454
455 wxLogTrace(TRACE_CLIPBOARD, wxT("Checking if format %s is available"),
456 format.GetId().c_str());
457
458 // these variables will be used by our GTKOnTargetReceived()
459 m_targetRequested = format;
460 m_formatSupported = false;
461
462 // block until m_formatSupported is set from targets_selection_received
463 // callback
464 {
465 wxClipboardSync sync(*this);
466
467 gtk_selection_convert( m_targetsWidget,
468 GTKGetClipboardAtom(),
469 g_targetsAtom,
470 (guint32) GDK_CURRENT_TIME );
471 }
472
473 return m_formatSupported;
474}
475
476// ----------------------------------------------------------------------------
477// wxClipboard public API implementation
478// ----------------------------------------------------------------------------
479
480void wxClipboard::Clear()
481{
482 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
483 m_clipboardWidget->window )
484 {
485 wxClipboardSync sync(*this);
486
487 // this will result in selection_clear_clip callback being called and
488 // it will free our data
489 SetSelectionOwner(false);
490 }
491
492 m_targetRequested = 0;
493 m_formatSupported = false;
494}
495
496bool wxClipboard::Open()
497{
498 wxCHECK_MSG( !m_open, false, wxT("clipboard already open") );
499
500 m_open = true;
501
502 return true;
503}
504
505bool wxClipboard::SetData( wxDataObject *data )
506{
507 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
508
509 wxCHECK_MSG( data, false, wxT("data is invalid") );
510
511 Clear();
512
513 return AddData( data );
514}
515
516bool wxClipboard::AddData( wxDataObject *data )
517{
518 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
519
520 wxCHECK_MSG( data, false, wxT("data is invalid") );
521
522 // we can only store one wxDataObject so clear the old one
523 Clear();
524
525 Data() = data;
526
527 // get formats from wxDataObjects
528 const size_t count = data->GetFormatCount();
529 wxDataFormatArray formats(new wxDataFormat[count]);
530 data->GetAllFormats(formats.get());
531
532 // always provide TIMESTAMP as a target, see comments in selection_handler
533 // for explanation
534 AddSupportedTarget(g_timestampAtom);
535
536 for ( size_t i = 0; i < count; i++ )
537 {
538 const wxDataFormat format(formats[i]);
539
540 wxLogTrace(TRACE_CLIPBOARD, wxT("Adding support for %s"),
541 format.GetId().c_str());
542
543 AddSupportedTarget(format);
544 }
545
546 g_signal_connect (m_clipboardWidget, "selection_get",
547 G_CALLBACK (selection_handler),
548 GUINT_TO_POINTER (gtk_get_current_event_time()) );
549
550 // tell the world we offer clipboard data
551 return SetSelectionOwner();
552}
553
554void wxClipboard::Close()
555{
556 wxCHECK_RET( m_open, wxT("clipboard not open") );
557
558 m_open = false;
559}
560
561bool wxClipboard::IsOpened() const
562{
563 return m_open;
564}
565
566bool wxClipboard::IsSupported( const wxDataFormat& format )
567{
568 if ( DoIsSupported(format) )
569 return true;
570
571#if wxUSE_UNICODE
572 if ( format == wxDF_UNICODETEXT )
573 {
574 // also with plain STRING format
575 return DoIsSupported(g_altTextAtom);
576 }
577#endif // wxUSE_UNICODE
578
579 return false;
580}
581
582bool wxClipboard::GetData( wxDataObject& data )
583{
584 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
585
586 // get all supported formats from wxDataObjects
587 const size_t count = data.GetFormatCount();
588 wxDataFormatArray formats(new wxDataFormat[count]);
589 data.GetAllFormats(formats.get());
590
591 for ( size_t i = 0; i < count; i++ )
592 {
593 const wxDataFormat format(formats[i]);
594
595 // is this format supported by clipboard ?
596 if ( !DoIsSupported(format) )
597 continue;
598
599 wxLogTrace(TRACE_CLIPBOARD, wxT("Requesting format %s"),
600 format.GetId().c_str());
601
602 // these variables will be used by our GTKOnSelectionReceived()
603 m_receivedData = &data;
604 m_formatSupported = false;
605
606 {
607 wxClipboardSync sync(*this);
608
609 gtk_selection_convert(m_clipboardWidget,
610 GTKGetClipboardAtom(),
611 format,
612 (guint32) GDK_CURRENT_TIME );
613 } // wait until we get the results
614
615 /*
616 Normally this is a true error as we checked for the presence of such
617 data before, but there are applications that may return an empty
618 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
619 which would produce a false error message here, so we check for the
620 size of the string first. With ANSI, GetDataSize returns an extra
621 value (for the closing null?), with unicode, the exact number of
622 tokens is given (that is more than 1 for non-ASCII characters)
623 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
624 */
625#if wxUSE_UNICODE
626 if ( format != wxDF_UNICODETEXT || data.GetDataSize(format) > 0 )
627#else // !UNICODE
628 if ( format != wxDF_TEXT || data.GetDataSize(format) > 1 )
629#endif // UNICODE / !UNICODE
630 {
631 wxCHECK_MSG( m_formatSupported, false,
632 wxT("error retrieving data from clipboard") );
633 }
634
635 return true;
636 }
637
638 wxLogTrace(TRACE_CLIPBOARD, wxT("GetData(): format not found"));
639
640 return false;
641}
642
643#endif // wxUSE_CLIPBOARD