]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/gtk/clipbrd.cpp
Correctly align background brush when erasing owner drawn bitmaps in wxMSW.
[wxWidgets.git] / src / gtk / clipbrd.cpp
... / ...
CommitLineData
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/app.h"
28 #include "wx/log.h"
29 #include "wx/utils.h"
30 #include "wx/dataobj.h"
31#endif
32
33#include "wx/scopedarray.h"
34#include "wx/scopeguard.h"
35#include "wx/evtloop.h"
36
37#include "wx/gtk/private.h"
38
39typedef wxScopedArray<wxDataFormat> wxDataFormatArray;
40
41// ----------------------------------------------------------------------------
42// data
43// ----------------------------------------------------------------------------
44
45static GdkAtom g_clipboardAtom = 0;
46static GdkAtom g_targetsAtom = 0;
47static GdkAtom g_timestampAtom = 0;
48
49#if wxUSE_UNICODE
50extern GdkAtom g_altTextAtom;
51#endif
52
53// the trace mask we use with wxLogTrace() - call
54// wxLog::AddTraceMask(TRACE_CLIPBOARD) to enable the trace messages from here
55// (there will be a *lot* of them!)
56#define TRACE_CLIPBOARD wxT("clipboard")
57
58// ----------------------------------------------------------------------------
59// wxClipboardSync: used to perform clipboard operations synchronously
60// ----------------------------------------------------------------------------
61
62// constructing this object on stack will wait wait until the latest clipboard
63// operation is finished on block exit
64//
65// notice that there can be no more than one such object alive at any moment,
66// i.e. reentrancies are not allowed
67class wxClipboardSync
68{
69public:
70 wxClipboardSync(wxClipboard& clipboard)
71 {
72 wxASSERT_MSG( !ms_clipboard, wxT("reentrancy in clipboard code") );
73 ms_clipboard = &clipboard;
74 }
75
76 ~wxClipboardSync()
77 {
78#if wxUSE_CONSOLE_EVENTLOOP
79 // ensure that there is a running event loop: this might not be the
80 // case if we're called before the main event loop startup
81 wxEventLoopGuarantor ensureEventLoop;
82#endif
83 while (ms_clipboard)
84 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_CLIPBOARD);
85 }
86
87 // this method must be called by GTK+ callbacks to indicate that we got the
88 // result for our clipboard operation
89 static void OnDone(wxClipboard * WXUNUSED_UNLESS_DEBUG(clipboard))
90 {
91 wxASSERT_MSG( clipboard == ms_clipboard,
92 wxT("got notification for alien clipboard") );
93
94 ms_clipboard = NULL;
95 }
96
97 // this method should be called if it's possible that no async clipboard
98 // operation is currently in progress (like it can be the case when the
99 // clipboard is cleared but not because we asked about it), it should only
100 // be called if such situation is expected -- otherwise call OnDone() which
101 // would assert in this case
102 static void OnDoneIfInProgress(wxClipboard *clipboard)
103 {
104 if ( ms_clipboard )
105 OnDone(clipboard);
106 }
107
108private:
109 static wxClipboard *ms_clipboard;
110
111 wxDECLARE_NO_COPY_CLASS(wxClipboardSync);
112};
113
114wxClipboard *wxClipboardSync::ms_clipboard = NULL;
115
116// ============================================================================
117// clipboard callbacks implementation
118// ============================================================================
119
120//-----------------------------------------------------------------------------
121// "selection_received" for targets
122//-----------------------------------------------------------------------------
123
124extern "C" {
125static void
126targets_selection_received( GtkWidget *WXUNUSED(widget),
127 GtkSelectionData *selection_data,
128 guint32 WXUNUSED(time),
129 wxClipboard *clipboard )
130{
131 if ( !clipboard )
132 return;
133
134 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
135
136 if ( !selection_data || selection_data->length <= 0 )
137 return;
138
139 // make sure we got the data in the correct form
140 GdkAtom type = selection_data->type;
141 if ( type != GDK_SELECTION_TYPE_ATOM )
142 {
143 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
144 {
145 wxLogTrace( TRACE_CLIPBOARD,
146 wxT("got unsupported clipboard target") );
147
148 return;
149 }
150 }
151
152 // it's not really a format, of course, but we can reuse its GetId() method
153 // to format this atom as string
154 wxDataFormat clip(selection_data->selection);
155 wxLogTrace( TRACE_CLIPBOARD,
156 wxT("Received available formats for clipboard %s"),
157 clip.GetId().c_str() );
158
159 // the atoms we received, holding a list of targets (= formats)
160 const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
161 for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
162 {
163 const wxDataFormat format(atoms[i]);
164
165 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
166
167 if ( clipboard->GTKOnTargetReceived(format) )
168 return;
169 }
170}
171}
172
173bool wxClipboard::GTKOnTargetReceived(const wxDataFormat& format)
174{
175 if ( format != m_targetRequested )
176 return false;
177
178 m_formatSupported = true;
179 return true;
180}
181
182//-----------------------------------------------------------------------------
183// "selection_received" for the actual data
184//-----------------------------------------------------------------------------
185
186extern "C" {
187static void
188selection_received( GtkWidget *WXUNUSED(widget),
189 GtkSelectionData *selection_data,
190 guint32 WXUNUSED(time),
191 wxClipboard *clipboard )
192{
193 if ( !clipboard )
194 return;
195
196 wxON_BLOCK_EXIT1(wxClipboardSync::OnDone, clipboard);
197
198 if ( !selection_data || selection_data->length <= 0 )
199 return;
200
201 clipboard->GTKOnSelectionReceived(*selection_data);
202}
203}
204
205//-----------------------------------------------------------------------------
206// "selection_clear"
207//-----------------------------------------------------------------------------
208
209extern "C" {
210static gint
211selection_clear_clip( GtkWidget *WXUNUSED(widget), GdkEventSelection *event )
212{
213 wxClipboard * const clipboard = wxTheClipboard;
214 if ( !clipboard )
215 return TRUE;
216
217 // notice the use of OnDoneIfInProgress() here instead of just OnDone():
218 // it's perfectly possible that we're receiving this notification from GTK+
219 // even though we hadn't cleared the clipboard ourselves but because
220 // another application (or even another window in the same program)
221 // acquired it
222 wxON_BLOCK_EXIT1(wxClipboardSync::OnDoneIfInProgress, clipboard);
223
224 wxClipboard::Kind kind;
225 if (event->selection == GDK_SELECTION_PRIMARY)
226 {
227 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost primary selection" ));
228
229 kind = wxClipboard::Primary;
230 }
231 else if (event->selection == g_clipboardAtom)
232 {
233 wxLogTrace(TRACE_CLIPBOARD, wxT("Lost clipboard" ));
234
235 kind = wxClipboard::Clipboard;
236 }
237 else // some other selection, we're not concerned
238 {
239 return FALSE;
240 }
241
242 // the clipboard is no longer in our hands, we don't need data any more
243 clipboard->GTKClearData(kind);
244
245 return TRUE;
246}
247}
248
249//-----------------------------------------------------------------------------
250// selection handler for supplying data
251//-----------------------------------------------------------------------------
252
253extern "C" {
254static void
255selection_handler( GtkWidget *WXUNUSED(widget),
256 GtkSelectionData *selection_data,
257 guint WXUNUSED(info),
258 guint WXUNUSED(time),
259 gpointer signal_data )
260{
261 wxClipboard * const clipboard = wxTheClipboard;
262 if ( !clipboard )
263 return;
264
265 wxDataObject * const data = clipboard->GTKGetDataObject(selection_data->selection);
266 if ( !data )
267 return;
268
269 // ICCCM says that TIMESTAMP is a required atom.
270 // In particular, it satisfies Klipper, which polls
271 // TIMESTAMP to see if the clipboards content has changed.
272 // It shall return the time which was used to set the data.
273 if (selection_data->target == g_timestampAtom)
274 {
275 guint timestamp = GPOINTER_TO_UINT (signal_data);
276 gtk_selection_data_set(selection_data,
277 GDK_SELECTION_TYPE_INTEGER,
278 32,
279 (guchar*)&(timestamp),
280 sizeof(timestamp));
281 wxLogTrace(TRACE_CLIPBOARD,
282 wxT("Clipboard TIMESTAMP requested, returning timestamp=%u"),
283 timestamp);
284 return;
285 }
286
287 wxDataFormat format( selection_data->target );
288
289 wxLogTrace(TRACE_CLIPBOARD,
290 wxT("clipboard data in format %s, GtkSelectionData is target=%s type=%s selection=%s timestamp=%u"),
291 format.GetId().c_str(),
292 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->target))).c_str(),
293 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->type))).c_str(),
294 wxString::FromAscii(wxGtkString(gdk_atom_name(selection_data->selection))).c_str(),
295 GPOINTER_TO_UINT( signal_data )
296 );
297
298 if ( !data->IsSupportedFormat( format ) )
299 return;
300
301 int size = data->GetDataSize( format );
302 if ( !size )
303 return;
304
305 wxCharBuffer buf(size - 1); // it adds 1 internally (for NUL)
306
307 // text data must be returned in UTF8 if format is wxDF_UNICODETEXT
308 if ( !data->GetDataHere(format, buf.data()) )
309 return;
310
311 // use UTF8_STRING format if requested in Unicode build but just plain
312 // STRING one in ANSI or if explicitly asked in Unicode
313#if wxUSE_UNICODE
314 if (format == wxDataFormat(wxDF_UNICODETEXT))
315 {
316 gtk_selection_data_set_text(
317 selection_data,
318 (const gchar*)buf.data(),
319 size );
320 }
321 else
322#endif // wxUSE_UNICODE
323 {
324 gtk_selection_data_set(
325 selection_data,
326 GDK_SELECTION_TYPE_STRING,
327 8*sizeof(gchar),
328 (const guchar*)buf.data(),
329 size );
330 }
331}
332}
333
334void wxClipboard::GTKOnSelectionReceived(const GtkSelectionData& sel)
335{
336 wxCHECK_RET( m_receivedData, wxT("should be inside GetData()") );
337
338 const wxDataFormat format(sel.target);
339 wxLogTrace(TRACE_CLIPBOARD, wxT("Received selection %s"),
340 format.GetId().c_str());
341
342 if ( !m_receivedData->IsSupportedFormat(format) )
343 return;
344
345 m_receivedData->SetData(format, sel.length, sel.data);
346 m_formatSupported = true;
347}
348
349//-----------------------------------------------------------------------------
350// asynchronous "selection_received" for targets
351//-----------------------------------------------------------------------------
352
353extern "C" {
354static void
355async_targets_selection_received( GtkWidget *WXUNUSED(widget),
356 GtkSelectionData *selection_data,
357 guint32 WXUNUSED(time),
358 wxClipboard *clipboard )
359{
360 if ( !clipboard ) // Assert?
361 return;
362
363 if (!clipboard->m_sink)
364 return;
365
366 wxClipboardEvent *event = new wxClipboardEvent(wxEVT_CLIPBOARD_CHANGED);
367 event->SetEventObject( clipboard );
368
369 if ( !selection_data || selection_data->length <= 0 )
370 {
371 clipboard->m_sink->QueueEvent( event );
372 clipboard->m_sink.Release();
373 return;
374 }
375
376 // make sure we got the data in the correct form
377 GdkAtom type = selection_data->type;
378 if ( type != GDK_SELECTION_TYPE_ATOM )
379 {
380 if ( strcmp(wxGtkString(gdk_atom_name(type)), "TARGETS") != 0 )
381 {
382 wxLogTrace( TRACE_CLIPBOARD,
383 wxT("got unsupported clipboard target") );
384
385 clipboard->m_sink->QueueEvent( event );
386 clipboard->m_sink.Release();
387 return;
388 }
389 }
390
391 // it's not really a format, of course, but we can reuse its GetId() method
392 // to format this atom as string
393 wxDataFormat clip(selection_data->selection);
394 wxLogTrace( TRACE_CLIPBOARD,
395 wxT("Received available formats for clipboard %s"),
396 clip.GetId().c_str() );
397
398 // the atoms we received, holding a list of targets (= formats)
399 const GdkAtom * const atoms = (GdkAtom *)selection_data->data;
400 for ( size_t i = 0; i < selection_data->length/sizeof(GdkAtom); i++ )
401 {
402 const wxDataFormat format(atoms[i]);
403
404 wxLogTrace(TRACE_CLIPBOARD, wxT("\t%s"), format.GetId().c_str());
405
406 event->AddFormat( format );
407 }
408
409 clipboard->m_sink->QueueEvent( event );
410 clipboard->m_sink.Release();
411}
412}
413
414// ============================================================================
415// wxClipboard implementation
416// ============================================================================
417
418// ----------------------------------------------------------------------------
419// wxClipboard ctor/dtor
420// ----------------------------------------------------------------------------
421
422IMPLEMENT_DYNAMIC_CLASS(wxClipboard,wxObject)
423
424wxClipboard::wxClipboard()
425{
426 m_open = false;
427
428 m_dataPrimary =
429 m_dataClipboard =
430 m_receivedData = NULL;
431
432 m_formatSupported = false;
433 m_targetRequested = 0;
434
435 // we use m_targetsWidget to query what formats are available
436 m_targetsWidget = gtk_window_new( GTK_WINDOW_POPUP );
437 gtk_widget_realize( m_targetsWidget );
438
439 g_signal_connect (m_targetsWidget, "selection_received",
440 G_CALLBACK (targets_selection_received), this);
441
442 // we use m_targetsWidgetAsync to query what formats are available asynchronously
443 m_targetsWidgetAsync = gtk_window_new( GTK_WINDOW_POPUP );
444 gtk_widget_realize( m_targetsWidgetAsync );
445
446 g_signal_connect (m_targetsWidgetAsync, "selection_received",
447 G_CALLBACK (async_targets_selection_received), this);
448
449 // we use m_clipboardWidget to get and to offer data
450 m_clipboardWidget = gtk_window_new( GTK_WINDOW_POPUP );
451 gtk_widget_realize( m_clipboardWidget );
452
453 g_signal_connect (m_clipboardWidget, "selection_received",
454 G_CALLBACK (selection_received), this);
455
456 g_signal_connect (m_clipboardWidget, "selection_clear_event",
457 G_CALLBACK (selection_clear_clip), NULL);
458
459 // initialize atoms we use if not done yet
460 if ( !g_clipboardAtom )
461 g_clipboardAtom = gdk_atom_intern( "CLIPBOARD", FALSE );
462 if ( !g_targetsAtom )
463 g_targetsAtom = gdk_atom_intern ("TARGETS", FALSE);
464 if ( !g_timestampAtom )
465 g_timestampAtom = gdk_atom_intern ("TIMESTAMP", FALSE);
466}
467
468wxClipboard::~wxClipboard()
469{
470 Clear();
471
472 gtk_widget_destroy( m_clipboardWidget );
473 gtk_widget_destroy( m_targetsWidget );
474}
475
476// ----------------------------------------------------------------------------
477// wxClipboard helper functions
478// ----------------------------------------------------------------------------
479
480GdkAtom wxClipboard::GTKGetClipboardAtom() const
481{
482 return m_usePrimary ? (GdkAtom)GDK_SELECTION_PRIMARY
483 : g_clipboardAtom;
484}
485
486void wxClipboard::GTKClearData(Kind kind)
487{
488 wxDataObject *&data = Data(kind);
489 wxDELETE(data);
490}
491
492bool 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, wxT("Failed to %sset selection owner"),
504 set ? wxT("") : wxT("un"));
505 }
506
507 return rc;
508}
509
510void 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
521bool wxClipboard::IsSupportedAsync(wxEvtHandler *sink)
522{
523 if (m_sink.get())
524 return false; // currently busy, come back later
525
526 wxCHECK_MSG( sink, false, wxT("no sink given") );
527
528 m_sink = sink;
529 gtk_selection_convert( m_targetsWidgetAsync,
530 GTKGetClipboardAtom(),
531 g_targetsAtom,
532 (guint32) GDK_CURRENT_TIME );
533
534 return true;
535}
536
537bool wxClipboard::DoIsSupported(const wxDataFormat& format)
538{
539 wxCHECK_MSG( format, false, wxT("invalid clipboard format") );
540
541 wxLogTrace(TRACE_CLIPBOARD, wxT("Checking if format %s is available"),
542 format.GetId().c_str());
543
544 // these variables will be used by our GTKOnTargetReceived()
545 m_targetRequested = format;
546 m_formatSupported = false;
547
548 // block until m_formatSupported is set from targets_selection_received
549 // callback
550 {
551 wxClipboardSync sync(*this);
552
553 gtk_selection_convert( m_targetsWidget,
554 GTKGetClipboardAtom(),
555 g_targetsAtom,
556 (guint32) GDK_CURRENT_TIME );
557 }
558
559 return m_formatSupported;
560}
561
562// ----------------------------------------------------------------------------
563// wxClipboard public API implementation
564// ----------------------------------------------------------------------------
565
566void wxClipboard::Clear()
567{
568 gtk_selection_clear_targets( m_clipboardWidget, GTKGetClipboardAtom() );
569
570 if ( gdk_selection_owner_get(GTKGetClipboardAtom()) ==
571 m_clipboardWidget->window )
572 {
573 wxClipboardSync sync(*this);
574
575 // this will result in selection_clear_clip callback being called and
576 // it will free our data
577 SetSelectionOwner(false);
578 }
579
580 m_targetRequested = 0;
581 m_formatSupported = false;
582}
583
584bool wxClipboard::Open()
585{
586 wxCHECK_MSG( !m_open, false, wxT("clipboard already open") );
587
588 m_open = true;
589
590 return true;
591}
592
593bool wxClipboard::SetData( wxDataObject *data )
594{
595 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
596
597 wxCHECK_MSG( data, false, wxT("data is invalid") );
598
599 Clear();
600
601 return AddData( data );
602}
603
604bool wxClipboard::AddData( wxDataObject *data )
605{
606 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
607
608 wxCHECK_MSG( data, false, wxT("data is invalid") );
609
610 // we can only store one wxDataObject so clear the old one
611 Clear();
612
613 Data() = data;
614
615 // get formats from wxDataObjects
616 const size_t count = data->GetFormatCount();
617 wxDataFormatArray formats(new wxDataFormat[count]);
618 data->GetAllFormats(formats.get());
619
620 // always provide TIMESTAMP as a target, see comments in selection_handler
621 // for explanation
622 AddSupportedTarget(g_timestampAtom);
623
624 for ( size_t i = 0; i < count; i++ )
625 {
626 const wxDataFormat format(formats[i]);
627
628 wxLogTrace(TRACE_CLIPBOARD, wxT("Adding support for %s"),
629 format.GetId().c_str());
630
631 AddSupportedTarget(format);
632 }
633
634 g_signal_connect (m_clipboardWidget, "selection_get",
635 G_CALLBACK (selection_handler),
636 GUINT_TO_POINTER (gtk_get_current_event_time()) );
637
638 // tell the world we offer clipboard data
639 return SetSelectionOwner();
640}
641
642void wxClipboard::Close()
643{
644 wxCHECK_RET( m_open, wxT("clipboard not open") );
645
646 m_open = false;
647}
648
649bool wxClipboard::IsOpened() const
650{
651 return m_open;
652}
653
654bool wxClipboard::IsSupported( const wxDataFormat& format )
655{
656 if ( DoIsSupported(format) )
657 return true;
658
659#if wxUSE_UNICODE
660 if ( format == wxDF_UNICODETEXT )
661 {
662 // also with plain STRING format
663 return DoIsSupported(g_altTextAtom);
664 }
665#endif // wxUSE_UNICODE
666
667 return false;
668}
669
670bool wxClipboard::GetData( wxDataObject& data )
671{
672 wxCHECK_MSG( m_open, false, wxT("clipboard not open") );
673
674 // get all supported formats from wxDataObjects: notice that we are setting
675 // the object data, so we need them in "Set" direction
676 const size_t count = data.GetFormatCount(wxDataObject::Set);
677 wxDataFormatArray formats(new wxDataFormat[count]);
678 data.GetAllFormats(formats.get(), wxDataObject::Set);
679
680 for ( size_t i = 0; i < count; i++ )
681 {
682 const wxDataFormat format(formats[i]);
683
684 // is this format supported by clipboard ?
685 if ( !DoIsSupported(format) )
686 continue;
687
688 wxLogTrace(TRACE_CLIPBOARD, wxT("Requesting format %s"),
689 format.GetId().c_str());
690
691 // these variables will be used by our GTKOnSelectionReceived()
692 m_receivedData = &data;
693 m_formatSupported = false;
694
695 {
696 wxClipboardSync sync(*this);
697
698 gtk_selection_convert(m_clipboardWidget,
699 GTKGetClipboardAtom(),
700 format,
701 (guint32) GDK_CURRENT_TIME );
702 } // wait until we get the results
703
704 /*
705 Normally this is a true error as we checked for the presence of such
706 data before, but there are applications that may return an empty
707 string (e.g. Gnumeric-1.6.1 on Linux if an empty cell is copied)
708 which would produce a false error message here, so we check for the
709 size of the string first. With ANSI, GetDataSize returns an extra
710 value (for the closing null?), with unicode, the exact number of
711 tokens is given (that is more than 1 for non-ASCII characters)
712 (tested with Gnumeric-1.6.1 and OpenOffice.org-2.0.2)
713 */
714#if wxUSE_UNICODE
715 if ( format != wxDF_UNICODETEXT || data.GetDataSize(format) > 0 )
716#else // !UNICODE
717 if ( format != wxDF_TEXT || data.GetDataSize(format) > 1 )
718#endif // UNICODE / !UNICODE
719 {
720 wxCHECK_MSG( m_formatSupported, false,
721 wxT("error retrieving data from clipboard") );
722 }
723
724 return true;
725 }
726
727 wxLogTrace(TRACE_CLIPBOARD, wxT("GetData(): format not found"));
728
729 return false;
730}
731
732wxDataObject* wxClipboard::GTKGetDataObject( GdkAtom atom )
733{
734 if ( atom == GDK_NONE )
735 return Data();
736
737 if ( atom == GDK_SELECTION_PRIMARY )
738 {
739 wxLogTrace(TRACE_CLIPBOARD, wxT("Primary selection requested" ));
740
741 return Data( wxClipboard::Primary );
742 }
743 else if ( atom == g_clipboardAtom )
744 {
745 wxLogTrace(TRACE_CLIPBOARD, wxT("Clipboard data requested" ));
746
747 return Data( wxClipboard::Clipboard );
748 }
749 else // some other selection, we're not concerned
750 {
751 return (wxDataObject*)NULL;
752 }
753}
754
755#endif // wxUSE_CLIPBOARD