]> git.saurik.com Git - wxWidgets.git/blob - src/gtk/clipbrd.cpp
Forgot to commit
[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 * 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
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 callbacks 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 // __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
332 void 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 // asynchronous "selection_received" for targets
349 //-----------------------------------------------------------------------------
350
351 extern "C" {
352 static void
353 async_targets_selection_received( GtkWidget *WXUNUSED(widget),
354 GtkSelectionData *selection_data,
355 guint32 WXUNUSED(time),
356 wxClipboard *clipboard )
357 {
358 if ( !clipboard ) // Assert?
359 return;
360
361 if (!clipboard->m_sink)
362 return;
363
364 wxClipboardEvent *event = new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED);
365
366 if ( !selection_data || selection_data->length <= 0 )
367 {
368 clipboard->m_sink->QueueEvent( event );
369 return;
370 }
371
372 // make sure we got the data in the correct form
373 GdkAtom type = selection_data->type;
374 if ( type != GDK_SELECTION_TYPE_ATOM )
375 {
376 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
377 {
378 wxLogTrace( TRACE_CLIPBOARD,
379 _T("got unsupported clipboard target") );
380
381 clipboard->m_sink->QueueEvent( event );
382 return;
383 }
384 }
385
386 #ifdef __WXDEBUG__
387 // it's not really a format, of course, but we can reuse its GetId() method
388 // to format this atom as string
389 wxDataFormat clip(selection_data->selection);
390 wxLogTrace( TRACE_CLIPBOARD,
391 wxT("Received available formats for clipboard %s"),
392 clip.GetId().c_str() );
393 #endif // __WXDEBUG__
394
395 // the atoms we received, holding a list of targets (= formats)
396 const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
397 for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
398 {
399 const wxDataFormat format(atoms[i]);
400
401 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
402
403 event->AddFormat( format );
404 }
405
406 clipboard->m_sink->QueueEvent( event );
407 }
408 }
409
410 // ============================================================================
411 // wxClipboard implementation
412 // ============================================================================
413
414 // ----------------------------------------------------------------------------
415 // wxClipboard ctor/dtor
416 // ----------------------------------------------------------------------------
417
418 IMPLEMENT_DYNAMIC_CLASS(wxClipboard,wxObject)
419
420 wxClipboard::wxClipboard()
421 {
422 m_open = false;
423
424 m_dataPrimary =
425 m_dataClipboard =
426 m_receivedData = NULL;
427
428 m_formatSupported = false;
429 m_targetRequested = 0;
430
431 // we use m_targetsWidget to query what formats are available
432 m_targetsWidget = gtk_window_new( GTK_WINDOW_POPUP );
433 gtk_widget_realize( m_targetsWidget );
434
435 g_signal_connect (m_targetsWidget, "selection_received",
436 G_CALLBACK (targets_selection_received), this);
437
438 // we use m_targetsWidgetAsync to query what formats asynchronously
439 m_targetsWidgetAsync = gtk_window_new( GTK_WINDOW_POPUP );
440 gtk_widget_realize( m_targetsWidgetAsync );
441
442 g_signal_connect (m_targetsWidgetAsync, "selection_received",
443 G_CALLBACK (async_targets_selection_received), this);
444
445 // we use m_clipboardWidget to get and to offer data
446 m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
447 gtk_widget_realize( m_clipboardWidget );
448
449 g_signal_connect (m_clipboardWidget, "selection_received",
450 G_CALLBACK (selection_received), this);
451
452 g_signal_connect (m_clipboardWidget, "selection_clear_event",
453 G_CALLBACK (selection_clear_clip), NULL);
454
455 // initialize atoms we use if not done yet
456 if ( !g_clipboardAtom )
457 g_clipboardAtom = gdk_atom_intern( "CLIPBOARD", FALSE );
458 if ( !g_targetsAtom )
459 g_targetsAtom = gdk_atom_intern ("TARGETS", FALSE);
460 if ( !g_timestampAtom )
461 g_timestampAtom = gdk_atom_intern ("TIMESTAMP", FALSE);
462 }
463
464 wxClipboard::~wxClipboard()
465 {
466 Clear();
467
468 gtk_widget_destroy( m_clipboardWidget );
469 gtk_widget_destroy( m_targetsWidget );
470 }
471
472 // ----------------------------------------------------------------------------
473 // wxClipboard helper functions
474 // ----------------------------------------------------------------------------
475
476 GdkAtom wxClipboard::GTKGetClipboardAtom() const
477 {
478 return m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
479 : g_clipboardAtom;
480 }
481
482 void wxClipboard::GTKClearData(Kind kind)
483 {
484 wxDataObject *&data = Data(kind);
485 if ( data )
486 {
487 delete data;
488 data = NULL;
489 }
490 }
491
492 bool wxClipboard::SetSelectionOwner(bool set)
493 {
494 bool rc = gtk_selection_owner_set
495 (
496 set ? m_clipboardWidget : NULL,
497 GTKGetClipboardAtom(),
498 (guint32)GDK_CURRENT_TIME
499 );
500
501 if ( !rc )
502 {
503 wxLogTrace(TRACE_CLIPBOARD, _T("Failed to %sset selection owner"),
504 set ? _T("") : _T("un"));
505 }
506
507 return rc;
508 }
509
510 void wxClipboard::AddSupportedTarget(GdkAtom atom)
511 {
512 gtk_selection_add_target
513 (
514 m_clipboardWidget,
515 GTKGetClipboardAtom(),
516 atom,
517 0 // info (same as client data) unused
518 );
519 }
520
521 bool wxClipboard::IsSupportedAsync(wxEvtHandler *sink)
522 {
523 m_sink = sink;
524
525 gtk_selection_convert( m_targetsWidgetAsync,
526 GTKGetClipboardAtom(),
527 g_targetsAtom,
528 (guint32) GDK_CURRENT_TIME );
529
530 return true;
531 }
532
533 bool wxClipboard::DoIsSupported(const wxDataFormat& format)
534 {
535 wxCHECK_MSG( format, false, wxT("invalid clipboard format") );
536
537 wxLogTrace(TRACE_CLIPBOARD, wxT("Checking if format %s is available"),
538 format.GetId().c_str());
539
540 // these variables will be used by our GTKOnTargetReceived()
541 m_targetRequested = format;
542 m_formatSupported = false;
543
544 // block until m_formatSupported is set from targets_selection_received
545 // callback
546 {
547 wxClipboardSync sync(*this);
548
549 gtk_selection_convert( m_targetsWidget,
550 GTKGetClipboardAtom(),
551 g_targetsAtom,
552 (guint32) GDK_CURRENT_TIME );
553 }
554
555 return m_formatSupported;
556 }
557
558 // ----------------------------------------------------------------------------
559 // wxClipboard public API implementation
560 // ----------------------------------------------------------------------------
561
562 void wxClipboard::Clear()
563 {
564 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
565 m_clipboardWidget->window )
566 {
567 wxClipboardSync sync(*this);
568
569 // this will result in selection_clear_clip callback being called and
570 // it will free our data
571 SetSelectionOwner(false);
572 }
573
574 m_targetRequested = 0;
575 m_formatSupported = false;
576 }
577
578 bool wxClipboard::Open()
579 {
580 wxCHECK_MSG( !m_open, false, wxT("clipboard already open") );
581
582 m_open = true;
583
584 return true;
585 }
586
587 bool wxClipboard::SetData( wxDataObject *data )
588 {
589 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
590
591 wxCHECK_MSG( data, false, wxT("data is invalid") );
592
593 Clear();
594
595 return AddData( data );
596 }
597
598 bool wxClipboard::AddData( wxDataObject *data )
599 {
600 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
601
602 wxCHECK_MSG( data, false, wxT("data is invalid") );
603
604 // we can only store one wxDataObject so clear the old one
605 Clear();
606
607 Data() = data;
608
609 // get formats from wxDataObjects
610 const size_t count = data->GetFormatCount();
611 wxDataFormatArray formats(new wxDataFormat[count]);
612 data->GetAllFormats(formats.get());
613
614 // always provide TIMESTAMP as a target, see comments in selection_handler
615 // for explanation
616 AddSupportedTarget(g_timestampAtom);
617
618 for ( size_t i = 0; i < count; i++ )
619 {
620 const wxDataFormat format(formats[i]);
621
622 wxLogTrace(TRACE_CLIPBOARD, wxT("Adding support for %s"),
623 format.GetId().c_str());
624
625 AddSupportedTarget(format);
626 }
627
628 g_signal_connect (m_clipboardWidget, "selection_get",
629 G_CALLBACK (selection_handler),
630 GUINT_TO_POINTER (gtk_get_current_event_time()) );
631
632 // tell the world we offer clipboard data
633 return SetSelectionOwner();
634 }
635
636 void wxClipboard::Close()
637 {
638 wxCHECK_RET( m_open, wxT("clipboard not open") );
639
640 m_open = false;
641 }
642
643 bool wxClipboard::IsOpened() const
644 {
645 return m_open;
646 }
647
648 bool wxClipboard::IsSupported( const wxDataFormat& format )
649 {
650 if ( DoIsSupported(format) )
651 return true;
652
653 #if wxUSE_UNICODE
654 if ( format == wxDF_UNICODETEXT )
655 {
656 // also with plain STRING format
657 return DoIsSupported(g_altTextAtom);
658 }
659 #endif // wxUSE_UNICODE
660
661 return false;
662 }
663
664 bool wxClipboard::GetData( wxDataObject& data )
665 {
666 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
667
668 // get all supported formats from wxDataObjects
669 const size_t count = data.GetFormatCount();
670 wxDataFormatArray formats(new wxDataFormat[count]);
671 data.GetAllFormats(formats.get());
672
673 for ( size_t i = 0; i < count; i++ )
674 {
675 const wxDataFormat format(formats[i]);
676
677 // is this format supported by clipboard ?
678 if ( !DoIsSupported(format) )
679 continue;
680
681 wxLogTrace(TRACE_CLIPBOARD, wxT("Requesting format %s"),
682 format.GetId().c_str());
683
684 // these variables will be used by our GTKOnSelectionReceived()
685 m_receivedData = &data;
686 m_formatSupported = false;
687
688 {
689 wxClipboardSync sync(*this);
690
691 gtk_selection_convert(m_clipboardWidget,
692 GTKGetClipboardAtom(),
693 format,
694 (guint32) GDK_CURRENT_TIME );
695 } // wait until we get the results
696
697 /*
698 Normally this is a true error as we checked for the presence of such
699 data before, but there are applications that may return an empty
700 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
701 which would produce a false error message here, so we check for the
702 size of the string first. With ANSI, GetDataSize returns an extra
703 value (for the closing null?), with unicode, the exact number of
704 tokens is given (that is more than 1 for non-ASCII characters)
705 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
706 */
707 #if wxUSE_UNICODE
708 if ( format != wxDF_UNICODETEXT || data.GetDataSize(format) > 0 )
709 #else // !UNICODE
710 if ( format != wxDF_TEXT || data.GetDataSize(format) > 1 )
711 #endif // UNICODE / !UNICODE
712 {
713 wxCHECK_MSG( m_formatSupported, false,
714 wxT("error retrieving data from clipboard") );
715 }
716
717 return true;
718 }
719
720 wxLogTrace(TRACE_CLIPBOARD, wxT("GetData(): format not found"));
721
722 return false;
723 }
724
725 #endif // wxUSE_CLIPBOARD