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