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