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