avoid asserts when the cipboard is acquired by another application
[wxWidgets.git] / src / gtk / clipbrd.cpp
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
37 wxDECLARE_SCOPED_ARRAY(wxDataFormat, wxDataFormatArray)
38 wxDEFINE_SCOPED_ARRAY(wxDataFormat, wxDataFormatArray)
39
40 // ----------------------------------------------------------------------------
41 // data
42 // ----------------------------------------------------------------------------
43
44 static GdkAtom g_clipboardAtom = 0;
45 static GdkAtom g_targetsAtom = 0;
46 static GdkAtom g_timestampAtom = 0;
47
48 #if wxUSE_UNICODE
49 extern 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
66 class wxClipboardSync
67 {
68 public:
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 *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
102 private:
103 static wxClipboard *ms_clipboard;
104
105 DECLARE_NO_COPY_CLASS(wxClipboardSync)
106 };
107
108 wxClipboard *wxClipboardSync::ms_clipboard = NULL;
109
110 // ============================================================================
111 // clipboard ca;backs implementation
112 // ============================================================================
113
114 //-----------------------------------------------------------------------------
115 // "selection_received" for targets
116 //-----------------------------------------------------------------------------
117
118 extern "C" {
119 static void
120 targets_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
169 bool 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
182 extern "C" {
183 static void
184 selection_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
205 extern "C" {
206 static gint
207 selection_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
249 extern "C" {
250 static void
251 selection_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
295
296 if (!data->IsSupportedFormat( format )) return;
297
298 int size = data->GetDataSize( format );
299
300 if (size == 0) return;
301
302 void *d = malloc(size);
303 wxON_BLOCK_EXIT1(free, d);
304
305 // Text data will be in UTF8 in Unicode mode.
306 data->GetDataHere( selection_data->target, d );
307
308 // NB: GTK+ requires special treatment of UTF8_STRING data, the text
309 // would show as UTF-8 data interpreted as latin1 (?) in other
310 // GTK+ apps if we used gtk_selection_data_set()
311 if (format == wxDataFormat(wxDF_UNICODETEXT))
312 {
313 gtk_selection_data_set_text(
314 selection_data,
315 (const gchar*)d,
316 size );
317 }
318 else
319 {
320 gtk_selection_data_set(
321 selection_data,
322 GDK_SELECTION_TYPE_STRING,
323 8*sizeof(gchar),
324 (unsigned char*) d,
325 size );
326 }
327 }
328 }
329
330 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData& sel)
331 {
332 wxCHECK_RET( m_receivedData, _T("should be inside GetData()") );
333
334 const wxDataFormat format(sel.target);
335 wxLogTrace(TRACE_CLIPBOARD, _T("Received selection %s"),
336 format.GetId().c_str());
337
338 if ( !m_receivedData->IsSupportedFormat(format) )
339 return;
340
341 m_receivedData->SetData(format, sel.length, sel.data);
342 m_formatSupported = true;
343 }
344
345 // ============================================================================
346 // wxClipboard implementation
347 // ============================================================================
348
349 // ----------------------------------------------------------------------------
350 // wxClipboard ctor/dtor
351 // ----------------------------------------------------------------------------
352
353 IMPLEMENT_DYNAMIC_CLASS(wxClipboard,wxObject)
354
355 wxClipboard::wxClipboard()
356 {
357 m_open = false;
358
359 m_dataPrimary =
360 m_dataClipboard =
361 m_receivedData = NULL;
362
363 m_formatSupported = false;
364 m_targetRequested = 0;
365
366 // we use m_targetsWidget to query what formats are available
367 m_targetsWidget = gtk_window_new( GTK_WINDOW_POPUP );
368 gtk_widget_realize( m_targetsWidget );
369
370 g_signal_connect (m_targetsWidget, "selection_received",
371 G_CALLBACK (targets_selection_received), this);
372
373 // we use m_clipboardWidget to get and to offer data
374 m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
375 gtk_widget_realize( m_clipboardWidget );
376
377 g_signal_connect (m_clipboardWidget, "selection_received",
378 G_CALLBACK (selection_received), this);
379
380 g_signal_connect (m_clipboardWidget, "selection_clear_event",
381 G_CALLBACK (selection_clear_clip), NULL);
382
383 // initialize atoms we use if not done yet
384 if ( !g_clipboardAtom )
385 g_clipboardAtom = gdk_atom_intern( "CLIPBOARD", FALSE );
386 if ( !g_targetsAtom )
387 g_targetsAtom = gdk_atom_intern ("TARGETS", FALSE);
388 if ( !g_timestampAtom )
389 g_timestampAtom = gdk_atom_intern ("TIMESTAMP", FALSE);
390 }
391
392 wxClipboard::~wxClipboard()
393 {
394 Clear();
395
396 if ( m_clipboardWidget )
397 gtk_widget_destroy( m_clipboardWidget );
398 if ( m_targetsWidget )
399 gtk_widget_destroy( m_targetsWidget );
400 }
401
402 // ----------------------------------------------------------------------------
403 // wxClipboard helper functions
404 // ----------------------------------------------------------------------------
405
406 GdkAtom wxClipboard::GTKGetClipboardAtom() const
407 {
408 return m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
409 : g_clipboardAtom;
410 }
411
412 void wxClipboard::GTKClearData(Kind kind)
413 {
414 wxDataObject *&data = Data();
415 if ( data )
416 {
417 delete data;
418 data = NULL;
419 }
420 }
421
422 bool 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
440 void wxClipboard::AddSupportedTarget(GdkAtom atom)
441 {
442 gtk_selection_add_target
443 (
444 GTK_WIDGET(m_clipboardWidget),
445 GTKGetClipboardAtom(),
446 atom,
447 0 // info (same as client data) unused
448 );
449 }
450
451 bool 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
480 void 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
496 bool wxClipboard::Open()
497 {
498 wxCHECK_MSG( !m_open, false, wxT("clipboard already open") );
499
500 m_open = true;
501
502 return true;
503 }
504
505 bool 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
516 bool 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
554 void wxClipboard::Close()
555 {
556 wxCHECK_RET( m_open, wxT("clipboard not open") );
557
558 m_open = false;
559 }
560
561 bool wxClipboard::IsOpened() const
562 {
563 return m_open;
564 }
565
566 bool 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
582 bool 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