]> git.saurik.com Git - wxWidgets.git/blob - src/gtk/clipbrd.cpp
removing obsolete contribs (partly moved to wxCode)
[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 *clipboard)
84 {
85 wxASSERT_MSG( clipboard == ms_clipboard,
86 _T("got notification for alien clipboard") );
87
88 ms_clipboard = NULL;
89 }
90
91 private:
92 static wxClipboard *ms_clipboard;
93
94 DECLARE_NO_COPY_CLASS(wxClipboardSync)
95 };
96
97 wxClipboard *wxClipboardSync::ms_clipboard = NULL;
98
99 // ============================================================================
100 // clipboard ca;backs implementation
101 // ============================================================================
102
103 //-----------------------------------------------------------------------------
104 // "selection_received" for targets
105 //-----------------------------------------------------------------------------
106
107 extern "C" {
108 static void
109 targets_selection_received( GtkWidget *WXUNUSED(widget),
110 GtkSelectionData *selection_data,
111 guint32 WXUNUSED(time),
112 wxClipboard *clipboard )
113 {
114 if ( !clipboard )
115 return;
116
117 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
118
119 if ( !selection_data || selection_data->length <= 0 )
120 return;
121
122 // make sure we got the data in the correct form
123 GdkAtom type = selection_data->type;
124 if ( type != GDK_SELECTION_TYPE_ATOM )
125 {
126 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
127 {
128 wxLogTrace( TRACE_CLIPBOARD,
129 _T("got unsupported clipboard target") );
130
131 return;
132 }
133 }
134
135 #ifdef __WXDEBUG__
136 // it's not really a format, of course, but we can reuse its GetId() method
137 // to format this atom as string
138 wxDataFormat clip(selection_data->selection);
139 wxLogTrace( TRACE_CLIPBOARD,
140 wxT("Received available formats for clipboard %s"),
141 clip.GetId().c_str() );
142 #endif // __WXDEBUG__
143
144 // the atoms we received, holding a list of targets (= formats)
145 const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
146 for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
147 {
148 const wxDataFormat format(atoms[i]);
149
150 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
151
152 if ( clipboard->GTKOnTargetReceived(format) )
153 return;
154 }
155 }
156 }
157
158 bool wxClipboard::GTKOnTargetReceived(const wxDataFormat& format)
159 {
160 if ( format != m_targetRequested )
161 return false;
162
163 m_formatSupported = true;
164 return true;
165 }
166
167 //-----------------------------------------------------------------------------
168 // "selection_received" for the actual data
169 //-----------------------------------------------------------------------------
170
171 extern "C" {
172 static void
173 selection_received( GtkWidget *WXUNUSED(widget),
174 GtkSelectionData *selection_data,
175 guint32 WXUNUSED(time),
176 wxClipboard *clipboard )
177 {
178 if ( !clipboard )
179 return;
180
181 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
182
183 if ( !selection_data || selection_data->length <= 0 )
184 return;
185
186 clipboard->GTKOnSelectionReceived(*selection_data);
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 wxClipboard * const clipboard = wxTheClipboard;
199 if ( !clipboard )
200 return TRUE;
201
202 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
203
204 wxClipboard::Kind kind;
205 if (event->selection == GDK_SELECTION_PRIMARY)
206 {
207 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost primary selection" ));
208
209 kind = wxClipboard::Primary;
210 }
211 else if (event->selection == g_clipboardAtom)
212 {
213 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost clipboard" ));
214
215 kind = wxClipboard::Clipboard;
216 }
217 else // some other selection, we're not concerned
218 {
219 return FALSE;
220 }
221
222 // the clipboard is no longer in our hands, we don't need data any more
223 clipboard->GTKClearData(kind);
224
225 return TRUE;
226 }
227 }
228
229 //-----------------------------------------------------------------------------
230 // selection handler for supplying data
231 //-----------------------------------------------------------------------------
232
233 extern "C" {
234 static void
235 selection_handler( GtkWidget *WXUNUSED(widget),
236 GtkSelectionData *selection_data,
237 guint WXUNUSED(info),
238 guint WXUNUSED(time),
239 gpointer signal_data )
240 {
241 wxClipboard * const clipboard = wxTheClipboard;
242 if ( !clipboard )
243 return;
244
245 wxDataObject * const data = clipboard->GTKGetDataObject();
246 if ( !data )
247 return;
248
249 // ICCCM says that TIMESTAMP is a required atom.
250 // In particular, it satisfies Klipper, which polls
251 // TIMESTAMP to see if the clipboards content has changed.
252 // It shall return the time which was used to set the data.
253 if (selection_data->target == g_timestampAtom)
254 {
255 guint timestamp = GPOINTER_TO_UINT (signal_data);
256 gtk_selection_data_set(selection_data,
257 GDK_SELECTION_TYPE_INTEGER,
258 32,
259 (guchar*)&(timestamp),
260 sizeof(timestamp));
261 wxLogTrace(TRACE_CLIPBOARD,
262 _T("Clipboard TIMESTAMP requested, returning timestamp=%u"),
263 timestamp);
264 return;
265 }
266
267 wxDataFormat format( selection_data->target );
268
269 #ifdef __WXDEBUG__
270 wxLogTrace(TRACE_CLIPBOARD,
271 _T("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
272 format.GetId().c_str(),
273 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->target))).c_str(),
274 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->type))).c_str(),
275 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->selection))).c_str(),
276 GPOINTER_TO_UINT( signal_data )
277 );
278 #endif
279
280 if (!data->IsSupportedFormat( format )) return;
281
282 int size = data->GetDataSize( format );
283
284 if (size == 0) return;
285
286 void *d = malloc(size);
287 wxON_BLOCK_EXIT1(free, d);
288
289 // Text data will be in UTF8 in Unicode mode.
290 data->GetDataHere( selection_data->target, d );
291
292 // NB: GTK+ requires special treatment of UTF8_STRING data, the text
293 // would show as UTF-8 data interpreted as latin1 (?) in other
294 // GTK+ apps if we used gtk_selection_data_set()
295 if (format == wxDataFormat(wxDF_UNICODETEXT))
296 {
297 gtk_selection_data_set_text(
298 selection_data,
299 (const gchar*)d,
300 size );
301 }
302 else
303 {
304 gtk_selection_data_set(
305 selection_data,
306 GDK_SELECTION_TYPE_STRING,
307 8*sizeof(gchar),
308 (unsigned char*) d,
309 size );
310 }
311 }
312 }
313
314 void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData& sel)
315 {
316 wxCHECK_RET( m_receivedData, _T("should be inside GetData()") );
317
318 const wxDataFormat format(sel.target);
319 wxLogTrace(TRACE_CLIPBOARD, _T("Received selection %s"),
320 format.GetId().c_str());
321
322 if ( !m_receivedData->IsSupportedFormat(format) )
323 return;
324
325 m_receivedData->SetData(format, sel.length, sel.data);
326 m_formatSupported = true;
327 }
328
329 // ============================================================================
330 // wxClipboard implementation
331 // ============================================================================
332
333 // ----------------------------------------------------------------------------
334 // wxClipboard ctor/dtor
335 // ----------------------------------------------------------------------------
336
337 IMPLEMENT_DYNAMIC_CLASS(wxClipboard,wxObject)
338
339 wxClipboard::wxClipboard()
340 {
341 m_open = false;
342
343 m_dataPrimary =
344 m_dataClipboard =
345 m_receivedData = NULL;
346
347 m_formatSupported = false;
348 m_targetRequested = 0;
349
350 // we use m_targetsWidget to query what formats are available
351 m_targetsWidget = gtk_window_new( GTK_WINDOW_POPUP );
352 gtk_widget_realize( m_targetsWidget );
353
354 g_signal_connect (m_targetsWidget, "selection_received",
355 G_CALLBACK (targets_selection_received), this);
356
357 // we use m_clipboardWidget to get and to offer data
358 m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
359 gtk_widget_realize( m_clipboardWidget );
360
361 g_signal_connect (m_clipboardWidget, "selection_received",
362 G_CALLBACK (selection_received), this);
363
364 g_signal_connect (m_clipboardWidget, "selection_clear_event",
365 G_CALLBACK (selection_clear_clip), NULL);
366
367 // initialize atoms we use if not done yet
368 if ( !g_clipboardAtom )
369 g_clipboardAtom = gdk_atom_intern( "CLIPBOARD", FALSE );
370 if ( !g_targetsAtom )
371 g_targetsAtom = gdk_atom_intern ("TARGETS", FALSE);
372 if ( !g_timestampAtom )
373 g_timestampAtom = gdk_atom_intern ("TIMESTAMP", FALSE);
374 }
375
376 wxClipboard::~wxClipboard()
377 {
378 Clear();
379
380 if ( m_clipboardWidget )
381 gtk_widget_destroy( m_clipboardWidget );
382 if ( m_targetsWidget )
383 gtk_widget_destroy( m_targetsWidget );
384 }
385
386 // ----------------------------------------------------------------------------
387 // wxClipboard helper functions
388 // ----------------------------------------------------------------------------
389
390 GdkAtom wxClipboard::GTKGetClipboardAtom() const
391 {
392 return m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
393 : g_clipboardAtom;
394 }
395
396 void wxClipboard::GTKClearData(Kind kind)
397 {
398 wxDataObject *&data = Data();
399 if ( data )
400 {
401 delete data;
402 data = NULL;
403 }
404 }
405
406 bool wxClipboard::SetSelectionOwner(bool set)
407 {
408 bool rc = gtk_selection_owner_set
409 (
410 set ? m_clipboardWidget : NULL,
411 GTKGetClipboardAtom(),
412 (guint32)GDK_CURRENT_TIME
413 );
414
415 if ( !rc )
416 {
417 wxLogTrace(TRACE_CLIPBOARD, _T("Failed to %sset selection owner"),
418 set ? _T("") : _T("un"));
419 }
420
421 return rc;
422 }
423
424 void wxClipboard::AddSupportedTarget(GdkAtom atom)
425 {
426 gtk_selection_add_target
427 (
428 GTK_WIDGET(m_clipboardWidget),
429 GTKGetClipboardAtom(),
430 atom,
431 0 // info (same as client data) unused
432 );
433 }
434
435 bool wxClipboard::DoIsSupported(const wxDataFormat& format)
436 {
437 wxCHECK_MSG( format, false, wxT("invalid clipboard format") );
438
439 wxLogTrace(TRACE_CLIPBOARD, wxT("Checking if format %s is available"),
440 format.GetId().c_str());
441
442 // these variables will be used by our GTKOnTargetReceived()
443 m_targetRequested = format;
444 m_formatSupported = false;
445
446 // block until m_formatSupported is set from targets_selection_received
447 // callback
448 {
449 wxClipboardSync sync(*this);
450
451 gtk_selection_convert( m_targetsWidget,
452 GTKGetClipboardAtom(),
453 g_targetsAtom,
454 (guint32) GDK_CURRENT_TIME );
455 }
456
457 return m_formatSupported;
458 }
459
460 // ----------------------------------------------------------------------------
461 // wxClipboard public API implementation
462 // ----------------------------------------------------------------------------
463
464 void wxClipboard::Clear()
465 {
466 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
467 m_clipboardWidget->window )
468 {
469 wxClipboardSync sync(*this);
470
471 // this will result in selection_clear_clip callback being called and
472 // it will free our data
473 SetSelectionOwner(false);
474 }
475
476 m_targetRequested = 0;
477 m_formatSupported = false;
478 }
479
480 bool wxClipboard::Open()
481 {
482 wxCHECK_MSG( !m_open, false, wxT("clipboard already open") );
483
484 m_open = true;
485
486 return true;
487 }
488
489 bool wxClipboard::SetData( wxDataObject *data )
490 {
491 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
492
493 wxCHECK_MSG( data, false, wxT("data is invalid") );
494
495 Clear();
496
497 return AddData( data );
498 }
499
500 bool wxClipboard::AddData( wxDataObject *data )
501 {
502 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
503
504 wxCHECK_MSG( data, false, wxT("data is invalid") );
505
506 // we can only store one wxDataObject so clear the old one
507 Clear();
508
509 Data() = data;
510
511 // get formats from wxDataObjects
512 const size_t count = data->GetFormatCount();
513 wxDataFormatArray formats(new wxDataFormat[count]);
514 data->GetAllFormats(formats.get());
515
516 // always provide TIMESTAMP as a target, see comments in selection_handler
517 // for explanation
518 AddSupportedTarget(g_timestampAtom);
519
520 for ( size_t i = 0; i < count; i++ )
521 {
522 const wxDataFormat format(formats[i]);
523
524 wxLogTrace(TRACE_CLIPBOARD, wxT("Adding support for %s"),
525 format.GetId().c_str());
526
527 AddSupportedTarget(format);
528 }
529
530 g_signal_connect (m_clipboardWidget, "selection_get",
531 G_CALLBACK (selection_handler),
532 GUINT_TO_POINTER (gtk_get_current_event_time()) );
533
534 // tell the world we offer clipboard data
535 return SetSelectionOwner();
536 }
537
538 void wxClipboard::Close()
539 {
540 wxCHECK_RET( m_open, wxT("clipboard not open") );
541
542 m_open = false;
543 }
544
545 bool wxClipboard::IsOpened() const
546 {
547 return m_open;
548 }
549
550 bool wxClipboard::IsSupported( const wxDataFormat& format )
551 {
552 if ( DoIsSupported(format) )
553 return true;
554
555 #if wxUSE_UNICODE
556 if ( format == wxDF_UNICODETEXT )
557 {
558 // also with plain STRING format
559 return DoIsSupported(g_altTextAtom);
560 }
561 #endif // wxUSE_UNICODE
562
563 return false;
564 }
565
566 bool wxClipboard::GetData( wxDataObject& data )
567 {
568 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
569
570 // get all supported formats from wxDataObjects
571 const size_t count = data.GetFormatCount();
572 wxDataFormatArray formats(new wxDataFormat[count]);
573 data.GetAllFormats(formats.get());
574
575 for ( size_t i = 0; i < count; i++ )
576 {
577 const wxDataFormat format(formats[i]);
578
579 // is this format supported by clipboard ?
580 if ( !DoIsSupported(format) )
581 continue;
582
583 wxLogTrace(TRACE_CLIPBOARD, wxT("Requesting format %s"),
584 format.GetId().c_str());
585
586 // these variables will be used by our GTKOnSelectionReceived()
587 m_receivedData = &data;
588 m_formatSupported = false;
589
590 {
591 wxClipboardSync sync(*this);
592
593 gtk_selection_convert(m_clipboardWidget,
594 GTKGetClipboardAtom(),
595 format,
596 (guint32) GDK_CURRENT_TIME );
597 } // wait until we get the results
598
599 /*
600 Normally this is a true error as we checked for the presence of such
601 data before, but there are applications that may return an empty
602 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
603 which would produce a false error message here, so we check for the
604 size of the string first. With ANSI, GetDataSize returns an extra
605 value (for the closing null?), with unicode, the exact number of
606 tokens is given (that is more than 1 for non-ASCII characters)
607 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
608 */
609 #if wxUSE_UNICODE
610 if ( format != wxDF_UNICODETEXT || data.GetDataSize(format) > 0 )
611 #else // !UNICODE
612 if ( format != wxDF_TEXT || data.GetDataSize(format) > 1 )
613 #endif // UNICODE / !UNICODE
614 {
615 wxCHECK_MSG( m_formatSupported, false,
616 wxT("error retrieving data from clipboard") );
617 }
618
619 return true;
620 }
621
622 wxLogTrace(TRACE_CLIPBOARD, wxT("GetData(): format not found"));
623
624 return false;
625 }
626
627 #endif // wxUSE_CLIPBOARD