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