]> git.saurik.com Git - wxWidgets.git/blob - src/gtk/clipbrd.cpp
pure CF code
[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 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 gtk_widget_destroy( m_clipboardWidget );
397 gtk_widget_destroy( m_targetsWidget );
398 }
399
400 // ----------------------------------------------------------------------------
401 // wxClipboard helper functions
402 // ----------------------------------------------------------------------------
403
404 GdkAtom wxClipboard::GTKGetClipboardAtom() const
405 {
406 return m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
407 : g_clipboardAtom;
408 }
409
410 void wxClipboard::GTKClearData(Kind kind)
411 {
412 wxDataObject *&data = Data(kind);
413 if ( data )
414 {
415 delete data;
416 data = NULL;
417 }
418 }
419
420 bool wxClipboard::SetSelectionOwner(bool set)
421 {
422 bool rc = gtk_selection_owner_set
423 (
424 set ? m_clipboardWidget : NULL,
425 GTKGetClipboardAtom(),
426 (guint32)GDK_CURRENT_TIME
427 );
428
429 if ( !rc )
430 {
431 wxLogTrace(TRACE_CLIPBOARD, _T("Failed to %sset selection owner"),
432 set ? _T("") : _T("un"));
433 }
434
435 return rc;
436 }
437
438 void wxClipboard::AddSupportedTarget(GdkAtom atom)
439 {
440 gtk_selection_add_target
441 (
442 m_clipboardWidget,
443 GTKGetClipboardAtom(),
444 atom,
445 0 // info (same as client data) unused
446 );
447 }
448
449 bool wxClipboard::DoIsSupported(const wxDataFormat& format)
450 {
451 wxCHECK_MSG( format, false, wxT("invalid clipboard format") );
452
453 wxLogTrace(TRACE_CLIPBOARD, wxT("Checking if format %s is available"),
454 format.GetId().c_str());
455
456 // these variables will be used by our GTKOnTargetReceived()
457 m_targetRequested = format;
458 m_formatSupported = false;
459
460 // block until m_formatSupported is set from targets_selection_received
461 // callback
462 {
463 wxClipboardSync sync(*this);
464
465 gtk_selection_convert( m_targetsWidget,
466 GTKGetClipboardAtom(),
467 g_targetsAtom,
468 (guint32) GDK_CURRENT_TIME );
469 }
470
471 return m_formatSupported;
472 }
473
474 // ----------------------------------------------------------------------------
475 // wxClipboard public API implementation
476 // ----------------------------------------------------------------------------
477
478 void wxClipboard::Clear()
479 {
480 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
481 m_clipboardWidget->window )
482 {
483 wxClipboardSync sync(*this);
484
485 // this will result in selection_clear_clip callback being called and
486 // it will free our data
487 SetSelectionOwner(false);
488 }
489
490 m_targetRequested = 0;
491 m_formatSupported = false;
492 }
493
494 bool wxClipboard::Open()
495 {
496 wxCHECK_MSG( !m_open, false, wxT("clipboard already open") );
497
498 m_open = true;
499
500 return true;
501 }
502
503 bool wxClipboard::SetData( wxDataObject *data )
504 {
505 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
506
507 wxCHECK_MSG( data, false, wxT("data is invalid") );
508
509 Clear();
510
511 return AddData( data );
512 }
513
514 bool wxClipboard::AddData( wxDataObject *data )
515 {
516 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
517
518 wxCHECK_MSG( data, false, wxT("data is invalid") );
519
520 // we can only store one wxDataObject so clear the old one
521 Clear();
522
523 Data() = data;
524
525 // get formats from wxDataObjects
526 const size_t count = data->GetFormatCount();
527 wxDataFormatArray formats(new wxDataFormat[count]);
528 data->GetAllFormats(formats.get());
529
530 // always provide TIMESTAMP as a target, see comments in selection_handler
531 // for explanation
532 AddSupportedTarget(g_timestampAtom);
533
534 for ( size_t i = 0; i < count; i++ )
535 {
536 const wxDataFormat format(formats[i]);
537
538 wxLogTrace(TRACE_CLIPBOARD, wxT("Adding support for %s"),
539 format.GetId().c_str());
540
541 AddSupportedTarget(format);
542 }
543
544 g_signal_connect (m_clipboardWidget, "selection_get",
545 G_CALLBACK (selection_handler),
546 GUINT_TO_POINTER (gtk_get_current_event_time()) );
547
548 // tell the world we offer clipboard data
549 return SetSelectionOwner();
550 }
551
552 void wxClipboard::Close()
553 {
554 wxCHECK_RET( m_open, wxT("clipboard not open") );
555
556 m_open = false;
557 }
558
559 bool wxClipboard::IsOpened() const
560 {
561 return m_open;
562 }
563
564 bool wxClipboard::IsSupported( const wxDataFormat& format )
565 {
566 if ( DoIsSupported(format) )
567 return true;
568
569 #if wxUSE_UNICODE
570 if ( format == wxDF_UNICODETEXT )
571 {
572 // also with plain STRING format
573 return DoIsSupported(g_altTextAtom);
574 }
575 #endif // wxUSE_UNICODE
576
577 return false;
578 }
579
580 bool wxClipboard::GetData( wxDataObject& data )
581 {
582 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
583
584 // get all supported formats from wxDataObjects
585 const size_t count = data.GetFormatCount();
586 wxDataFormatArray formats(new wxDataFormat[count]);
587 data.GetAllFormats(formats.get());
588
589 for ( size_t i = 0; i < count; i++ )
590 {
591 const wxDataFormat format(formats[i]);
592
593 // is this format supported by clipboard ?
594 if ( !DoIsSupported(format) )
595 continue;
596
597 wxLogTrace(TRACE_CLIPBOARD, wxT("Requesting format %s"),
598 format.GetId().c_str());
599
600 // these variables will be used by our GTKOnSelectionReceived()
601 m_receivedData = &data;
602 m_formatSupported = false;
603
604 {
605 wxClipboardSync sync(*this);
606
607 gtk_selection_convert(m_clipboardWidget,
608 GTKGetClipboardAtom(),
609 format,
610 (guint32) GDK_CURRENT_TIME );
611 } // wait until we get the results
612
613 /*
614 Normally this is a true error as we checked for the presence of such
615 data before, but there are applications that may return an empty
616 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
617 which would produce a false error message here, so we check for the
618 size of the string first. With ANSI, GetDataSize returns an extra
619 value (for the closing null?), with unicode, the exact number of
620 tokens is given (that is more than 1 for non-ASCII characters)
621 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
622 */
623 #if wxUSE_UNICODE
624 if ( format != wxDF_UNICODETEXT || data.GetDataSize(format) > 0 )
625 #else // !UNICODE
626 if ( format != wxDF_TEXT || data.GetDataSize(format) > 1 )
627 #endif // UNICODE / !UNICODE
628 {
629 wxCHECK_MSG( m_formatSupported, false,
630 wxT("error retrieving data from clipboard") );
631 }
632
633 return true;
634 }
635
636 wxLogTrace(TRACE_CLIPBOARD, wxT("GetData(): format not found"));
637
638 return false;
639 }
640
641 #endif // wxUSE_CLIPBOARD