1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 
5 
6 module dfl.data;
7 
8 private import dfl.internal.dlib;
9 
10 private import dfl.base, dfl.internal.winapi, dfl.internal.wincom, dfl.application,
11         dfl.internal.utf, dfl.internal.com;
12 
13 
14 
15 class DataFormats { // docmain
16 
17    static class Format { // docmain
18       /// Data format ID number.
19       final @property int id() { // getter
20          return _id;
21       }
22 
23 
24       /// Data format name.
25       final @property Dstring name() { // getter
26          return _name;
27       }
28 
29 
30     package:
31       int _id;
32       Dstring _name;
33 
34 
35       this() {
36       }
37    }
38 
39 
40  static:
41 
42    /// Predefined data formats.
43    @property Dstring bitmap() { // getter
44       return getFormat(CF_BITMAP).name;
45    }
46 
47    /+
48    /// ditto
49    @property Dstring commaSeparatedValue() { // getter
50       return getFormat(?).name;
51    }
52    +/
53 
54    /// ditto
55    @property Dstring dib() { // getter
56       return getFormat(CF_DIB).name;
57    }
58 
59    /// ditto
60    @property Dstring dif() { // getter
61       return getFormat(CF_DIF).name;
62    }
63 
64    /// ditto
65    @property Dstring enhandedMetaFile() { // getter
66       return getFormat(CF_ENHMETAFILE).name;
67    }
68 
69    /// ditto
70    @property Dstring fileDrop() { // getter
71       return getFormat(CF_HDROP).name;
72    }
73 
74    /// ditto
75    @property Dstring html() { // getter
76       return getFormat("HTML Format").name;
77    }
78 
79    /// ditto
80    @property Dstring locale() { // getter
81       return getFormat(CF_LOCALE).name;
82    }
83 
84    /// ditto
85    @property Dstring metafilePict() { // getter
86       return getFormat(CF_METAFILEPICT).name;
87    }
88 
89    /// ditto
90    @property Dstring oemText() { // getter
91       return getFormat(CF_OEMTEXT).name;
92    }
93 
94    /// ditto
95    @property Dstring palette() { // getter
96       return getFormat(CF_PALETTE).name;
97    }
98 
99    /// ditto
100    @property Dstring penData() { // getter
101       return getFormat(CF_PENDATA).name;
102    }
103 
104    /// ditto
105    @property Dstring riff() { // getter
106       return getFormat(CF_RIFF).name;
107    }
108 
109    /// ditto
110    @property Dstring rtf() { // getter
111       return getFormat("Rich Text Format").name;
112    }
113 
114 
115    /+
116    /// ditto
117    @property Dstring serializable() { // getter
118       return getFormat(?).name;
119    }
120    +/
121 
122    /// ditto
123    @property Dstring stringFormat() { // getter
124       return utf8; // ?
125    }
126 
127    /// ditto
128    @property Dstring utf8() { // getter
129       return getFormat("UTF-8").name;
130    }
131 
132    /// ditto
133    @property Dstring symbolicLink() { // getter
134       return getFormat(CF_SYLK).name;
135    }
136 
137    /// ditto
138    @property Dstring text() { // getter
139       return getFormat(CF_TEXT).name;
140    }
141 
142    /// ditto
143    @property Dstring tiff() { // getter
144       return getFormat(CF_TIFF).name;
145    }
146 
147    /// ditto
148    @property Dstring unicodeText() { // getter
149       return getFormat(CF_UNICODETEXT).name;
150    }
151 
152    /// ditto
153    @property Dstring waveAudio() { // getter
154       return getFormat(CF_WAVE).name;
155    }
156 
157 
158    // Assumes _init() was already called and
159    // -id- is not in -fmts-.
160    private Format _didntFindId(int id) {
161       Format result;
162       result = new Format;
163       result._id = id;
164       result._name = getName(id);
165       //synchronized // _init() would need to be synchronized with it.
166       {
167          fmts[id] = result;
168       }
169       return result;
170    }
171 
172 
173 
174    Format getFormat(int id) {
175       _init();
176 
177       if(id in fmts) {
178          return fmts[id];
179       }
180 
181       return _didntFindId(id);
182    }
183 
184    /// ditto
185    // Creates the format name if it doesn't exist.
186    Format getFormat(Dstring name) {
187       _init();
188       foreach(Format onfmt; fmts) {
189          if(!stringICmp(name, onfmt.name)) {
190             return onfmt;
191          }
192       }
193       // Didn't find it.
194       return _didntFindId(dfl.internal.utf.registerClipboardFormat(name));
195    }
196 
197    /// ditto
198    // Extra.
199    Format getFormat(TypeInfo type) {
200       return getFormatFromType(type);
201    }
202 
203 
204  private:
205    Format[int] fmts; // Indexed by identifier. Must _init() before accessing!
206 
207 
208    void _init() {
209       if(fmts.length) {
210          return;
211       }
212 
213 
214       void initfmt(int id, Dstring name)
215       in {
216          assert(!(id in fmts));
217       }
218       body {
219          Format fmt;
220          fmt = new Format;
221          fmt._id = id;
222          fmt._name = name;
223          fmts[id] = fmt;
224       }
225 
226 
227       initfmt(CF_BITMAP, "Bitmap");
228       initfmt(CF_DIB, "DeviceIndependentBitmap");
229       initfmt(CF_DIF, "DataInterchangeFormat");
230       initfmt(CF_ENHMETAFILE, "EnhancedMetafile");
231       initfmt(CF_HDROP, "FileDrop");
232       initfmt(CF_LOCALE, "Locale");
233       initfmt(CF_METAFILEPICT, "MetaFilePict");
234       initfmt(CF_OEMTEXT, "OEMText");
235       initfmt(CF_PALETTE, "Palette");
236       initfmt(CF_PENDATA, "PenData");
237       initfmt(CF_RIFF, "RiffAudio");
238       initfmt(CF_SYLK, "SymbolicLink");
239       initfmt(CF_TEXT, "Text");
240       initfmt(CF_TIFF, "TaggedImageFileFormat");
241       initfmt(CF_UNICODETEXT, "UnicodeText");
242       initfmt(CF_WAVE, "WaveAudio");
243 
244       fmts.rehash;
245    }
246 
247 
248    // Does not get the name of one of the predefined constant ones.
249    Dstring getName(int id) {
250       Dstring result;
251       result = dfl.internal.utf.getClipboardFormatName(id);
252       if(!result.length) {
253          throw new DflException("Unable to get format");
254       }
255       return result;
256    }
257 
258 
259    package Format getFormatFromType(TypeInfo type) {
260       if(type == typeid(ubyte[])) {
261          return getFormat(text);
262       }
263       if(type == typeid(Dstring)) {
264          return getFormat(stringFormat);
265       }
266       if(type == typeid(Dwstring)) {
267          return getFormat(unicodeText);
268       }
269       //if(type == typeid(Bitmap))
270       // return getFormat(bitmap);
271 
272       if(cast(TypeInfo_Class)type) {
273          throw new DflException("Unknown data format");
274       }
275 
276       return getFormat(getObjectString(type)); // ?
277    }
278 
279 
280    private Dstring[] getHDropStrings(void[] value) {
281       /+
282       if(value.length != HDROP.sizeof) {
283          return null;
284       }
285 
286       HDROP hd;
287       UINT num;
288       Dstring[] result;
289       size_t iw;
290 
291       hd = *cast(HDROP*)value.ptr;
292       num = dragQueryFile(hd);
293       if(!num) {
294          return null;
295       }
296       result = new Dstring[num];
297       for(iw = 0; iw != num; iw++) {
298          result[iw] = dragQueryFile(hd, iw);
299       }
300       return result;
301       +/
302 
303       if(value.length <= DROPFILES.sizeof) {
304          return null;
305       }
306 
307       Dstring[] result;
308       DROPFILES* df;
309       size_t iw, startiw;
310 
311       df = cast(DROPFILES*)value.ptr;
312       if(df.pFiles < DROPFILES.sizeof || df.pFiles >= value.length) {
313          return null;
314       }
315 
316       if(df.fWide) { // Unicode.
317          Dwstring uni = cast(Dwstring)((value.ptr + df.pFiles)[0 .. value.length]);
318          for(iw = startiw = 0;; iw++) {
319             if(!uni[iw]) {
320                if(startiw == iw) {
321                   break;
322                }
323                result ~= fromUnicode(uni.ptr + startiw, iw - startiw);
324                assert(result[result.length - 1].length);
325                startiw = iw + 1;
326             }
327          }
328       } else { // ANSI.
329          Dstring ansi = cast(Dstring)((value.ptr + df.pFiles)[0 .. value.length]);
330          for(iw = startiw = 0;; iw++) {
331             if(!ansi[iw]) {
332                if(startiw == iw) {
333                   break;
334                }
335                result ~= fromAnsi(ansi.ptr + startiw, iw - startiw);
336                assert(result[result.length - 1].length);
337                startiw = iw + 1;
338             }
339          }
340       }
341 
342       return result;
343    }
344 
345 
346    // Convert clipboard -value- to Data.
347    Data getDataFromFormat(int id, void[] value) {
348       switch(id) {
349          case CF_TEXT:
350             return Data(stopAtNull!(ubyte)(cast(ubyte[])value));
351 
352          case CF_UNICODETEXT:
353             return Data(stopAtNull!(Dwchar)(cast(Dwstring)value));
354 
355          case CF_HDROP:
356             return Data(getHDropStrings(value));
357 
358          default:
359             if(id == getFormat(stringFormat).id) {
360                return Data(stopAtNull!(Dchar)(cast(Dstring)value));
361             }
362       }
363 
364       //throw new DflException("Unknown data format");
365       return Data(value); // ?
366    }
367 
368 
369    void[] getCbFileDrop(Dstring[] fileNames) {
370       size_t sz = DROPFILES.sizeof;
371       void* p;
372       DROPFILES* df;
373 
374       foreach(fn; fileNames) {
375          sz += (dfl.internal.utf.toUnicodeLength(fn) + 1) << 1;
376       }
377       sz += 2;
378 
379       p = (new byte[sz]).ptr;
380       df = cast(DROPFILES*)p;
381 
382       df.pFiles = DROPFILES.sizeof;
383       df.fWide = TRUE;
384 
385       wchar* ws = cast(wchar*)(p + DROPFILES.sizeof);
386       foreach(fn; fileNames) {
387          foreach(wchar wch; fn) {
388             *ws++ = wch;
389          }
390          *ws++ = 0;
391       }
392       *ws++ = 0;
393 
394       return p[0 .. sz];
395    }
396 
397 
398    // Value the clipboard wants.
399    void[] getClipboardValueFromData(int id, Data data) {
400       //if(data.info == typeid(ubyte[]))
401       if(CF_TEXT == id) {
402          // ANSI text.
403          enum ubyte[] UBYTE_ZERO = [0];
404          return data.getText() ~ UBYTE_ZERO;
405       }
406       //else if(data.info == typeid(Dstring))
407       //else if(getFormat(stringFormat).id == id)
408       else if((getFormat(stringFormat).id == id) || (data.info == typeid(Dstring))) {
409          // UTF-8 string.
410          Dstring str;
411          str = data.getString();
412          //return toStringz(str)[0 .. str.length + 1];
413          //return unsafeStringz(str)[0 .. str.length + 1]; // ?
414          return cast(void[])unsafeStringz(str)[0 .. str.length + 1]; // ? Needed in D2.
415       }
416       //else if(data.info == typeid(Dwstring))
417       //else if(CF_UNICODETEXT == id)
418       else if((CF_UNICODETEXT == id) || (data.info == typeid(Dwstring))) {
419          // Unicode string.
420          //return data.getUnicodeText() ~ cast(Dwstring)"\0";
421          //return cast(void[])(data.getUnicodeText() ~ cast(Dwstring)"\0"); // Needed in D2. Not guaranteed safe.
422          return (data.getUnicodeText() ~ cast(Dwstring)"\0").dup; // Needed in D2.
423       } else if(data.info == typeid(Ddstring)) {
424          //return (*cast(Ddstring*)data.value) ~ "\0";
425          //return cast(void[])((*cast(Ddstring*)data.value) ~ "\0"); // Needed in D2. Not guaranteed safe.
426          return ((*cast(Ddstring*)data.value) ~ "\0").dup; // Needed in D2.
427       } else if(CF_HDROP == id) {
428          return getCbFileDrop(data.getStrings());
429       } else if(data.info == typeid(void[]) || data.info == typeid(Dstring)
430                 || data.info == typeid(ubyte[]) || data.info == typeid(byte[])) { // Hack ?
431          return *cast(void[]*)data.value; // Save the array elements, not the reference.
432       } else {
433          return data.value; // ?
434       }
435    }
436 
437 
438    this() {
439    }
440 }
441 
442 
443 private template stopAtNull(T) {
444    T[] stopAtNull(T[] array) {
445       int i;
446       for(i = 0; i != array.length; i++) {
447          if(!array[i]) {
448             return array[0 .. i];
449          }
450       }
451       //return null;
452       throw new DflException("Invalid data"); // ?
453    }
454 }
455 
456 
457 /// Data structure for holding data in a raw format with type information.
458 struct Data { // docmain
459    /// Information about the data type.
460    @property TypeInfo info() { // getter
461       return _info;
462    }
463 
464 
465    /// The data's raw value.
466    @property void[] value() { // getter
467       return _value[0 .. _info.tsize()];
468    }
469 
470 
471    /// Construct a new Data structure.
472    static Data opCall(...)
473    in {
474       assert(_arguments.length == 1);
475    }
476    body {
477       Data result;
478       result._info = _arguments[0];
479       result._value = _argptr[0 .. result._info.tsize()].dup.ptr;
480       return result;
481    }
482 
483 
484 
485    T getValue(T)() {
486       assert(_info.tsize == T.sizeof);
487       return *cast(T*)_value;
488    }
489 
490    /// ditto
491    // UTF-8.
492    Dstring getString() {
493       assert(_info == typeid(Dstring) || _info == typeid(void[]));
494       return *cast(Dstring*)_value;
495    }
496 
497    /// ditto
498    alias getString getUtf8;
499    /// ditto
500    deprecated alias getString getUTF8;
501 
502    /// ditto
503    // ANSI text.
504    ubyte[] getText() {
505       assert(_info == typeid(ubyte[]) || _info == typeid(byte[]) || _info == typeid(void[]));
506       return *cast(ubyte[]*)_value;
507    }
508 
509    /// ditto
510    Dwstring getUnicodeText() {
511       assert(_info == typeid(Dwstring) || _info == typeid(void[]));
512       return *cast(Dwstring*)_value;
513    }
514 
515    /// ditto
516    int getInt() {
517       return getValue!(int)();
518    }
519 
520    /// ditto
521    int getUint() {
522       return getValue!(uint)();
523    }
524 
525    /// ditto
526    Dstring[] getStrings() {
527       assert(_info == typeid(Dstring[]));
528       return *cast(Dstring[]*)_value;
529    }
530 
531    /// ditto
532    Object getObject() {
533       assert(!(cast(TypeInfo_Class)_info is null));
534       return cast(Object)*cast(Object**)_value;
535    }
536 
537 
538 private:
539    TypeInfo _info;
540    void* _value;
541 }
542 
543 
544 /+
545 interface IDataFormat {
546 
547 }
548 +/
549 
550 
551 /// Interface to a data object. The data can have different formats by setting different formats.
552 interface IDataObject { // docmain
553 
554    Data getData(Dstring fmt);
555    /// ditto
556    Data getData(TypeInfo type);
557    /// ditto
558    Data getData(Dstring fmt, bool doConvert);
559 
560 
561    bool getDataPresent(Dstring fmt); // Check.
562    /// ditto
563    bool getDataPresent(TypeInfo type); // Check.
564    /// ditto
565    bool getDataPresent(Dstring fmt, bool canConvert); // Check.
566 
567 
568    Dstring[] getFormats();
569    //Dstring[] getFormats(bool onlyNative);
570 
571 
572    void setData(Data obj);
573    /// ditto
574    void setData(Dstring fmt, Data obj);
575    /// ditto
576    void setData(TypeInfo type, Data obj);
577    /// ditto
578    void setData(Dstring fmt, bool canConvert, Data obj);
579 }
580 
581 
582 
583 class DataObject: dfl.data.IDataObject { // docmain
584 
585    Data getData(Dstring fmt) {
586       return getData(fmt, true);
587    }
588 
589    /// ditto
590    Data getData(TypeInfo type) {
591       return getData(DataFormats.getFormat(type).name);
592    }
593 
594    /// ditto
595    Data getData(Dstring fmt, bool doConvert) {
596       // doConvert ...
597 
598       //cprintf("Looking for format '%.*s'.\n", fmt);
599       int i;
600       i = find(fmt);
601       if(i == -1) {
602          throw new DflException("Data format not present");
603       }
604       return all[i].obj;
605    }
606 
607 
608 
609    bool getDataPresent(Dstring fmt) {
610       return getDataPresent(fmt, true);
611    }
612 
613    /// ditto
614    bool getDataPresent(TypeInfo type) {
615       return getDataPresent(DataFormats.getFormat(type).name);
616    }
617 
618    /// ditto
619    bool getDataPresent(Dstring fmt, bool canConvert) {
620       // canConvert ...
621       return find(fmt) != -1;
622    }
623 
624 
625 
626    Dstring[] getFormats() {
627       Dstring[] result;
628       result = new Dstring[all.length];
629       foreach(int i, ref Dstring fmt; result) {
630          fmt = all[i].fmt;
631       }
632       return result;
633    }
634 
635 
636    // TO-DO: remove...
637    deprecated final Dstring[] getFormats(bool onlyNative) {
638       return getFormats();
639    }
640 
641 
642    package final void _setData(Dstring fmt, Data obj, bool replace = true) {
643       int i;
644       i = find(fmt, false);
645       if(i != -1) {
646          if(replace) {
647             all[i].obj = obj;
648          }
649       } else {
650          Pair pair;
651          pair.fmt = fmt;
652          pair.obj = obj;
653          all ~= pair;
654       }
655    }
656 
657 
658 
659    void setData(Data obj) {
660       setData(DataFormats.getFormat(obj.info).name, obj);
661    }
662 
663 
664    /// ditto
665    void setData(Dstring fmt, Data obj) {
666       setData(fmt, true, obj);
667    }
668 
669 
670    /// ditto
671    void setData(TypeInfo type, Data obj) {
672       setData(DataFormats.getFormatFromType(type).name, true, obj);
673    }
674 
675 
676    /// ditto
677    void setData(Dstring fmt, bool canConvert, Data obj) {
678       /+
679       if(obj.info == typeid(Data)) {
680          void[] objv;
681          objv = obj.value;
682          assert(objv.length == Data.sizeof);
683          obj = *(cast(Data*)objv.ptr);
684       }
685       +/
686 
687       _setData(fmt, obj);
688       if(canConvert) {
689          Data cdat;
690          cdat = Data(*(cast(_DataConvert*)&obj));
691          _canConvertFormats(fmt,
692          (Dstring cfmt) {
693             _setData(cfmt, cdat, false);
694          });
695       }
696    }
697 
698 
699 private:
700    struct Pair {
701       Dstring fmt;
702       Data obj;
703    }
704 
705 
706    Pair[] all;
707 
708 
709    void fixPairEntry(ref Pair pr) {
710       assert(pr.obj.info == typeid(_DataConvert));
711       Data obj;
712       void[] objv;
713       objv = pr.obj.value;
714       assert(objv.length == Data.sizeof);
715       obj = *(cast(Data*)objv.ptr);
716       pr.obj = _doConvertFormat(obj, pr.fmt);
717    }
718 
719 
720    int find(Dstring fmt, bool fix = true) {
721       int i;
722       for(i = 0; i != all.length; i++) {
723          if(!stringICmp(all[i].fmt, fmt)) {
724             if(fix && all[i].obj.info == typeid(_DataConvert)) {
725                fixPairEntry(all[i]);
726             }
727             return i;
728          }
729       }
730       return -1;
731    }
732 }
733 
734 
735 private struct _DataConvert {
736    Data data;
737 }
738 
739 
740 package void _canConvertFormats(Dstring fmt, void delegate(Dstring cfmt) callback) {
741    //if(!stringICmp(fmt, DataFormats.utf8))
742    if(!stringICmp(fmt, "UTF-8")) {
743       callback(DataFormats.unicodeText);
744       callback(DataFormats.text);
745    } else if(!stringICmp(fmt, DataFormats.unicodeText)) {
746       //callback(DataFormats.utf8);
747       callback("UTF-8");
748       callback(DataFormats.text);
749    } else if(!stringICmp(fmt, DataFormats.text)) {
750       //callback(DataFormats.utf8);
751       callback("UTF-8");
752       callback(DataFormats.unicodeText);
753    }
754 }
755 
756 
757 package Data _doConvertFormat(Data dat, Dstring toFmt) {
758    Data result;
759    //if(!stringICmp(toFmt, DataFormats.utf8))
760    if(!stringICmp(toFmt, "UTF-8")) {
761       if(typeid(Dwstring) == dat.info) {
762          result = Data(utf16stringtoUtf8string(dat.getUnicodeText()));
763       } else if(typeid(ubyte[]) == dat.info) {
764          ubyte[] ubs;
765          ubs = dat.getText();
766          result = Data(dfl.internal.utf.fromAnsi(cast(Dstringz)ubs.ptr, ubs.length));
767       }
768    } else if(!stringICmp(toFmt, DataFormats.unicodeText)) {
769       if(typeid(Dstring) == dat.info) {
770          result = Data(utf8stringtoUtf16string(dat.getString()));
771       } else if(typeid(ubyte[]) == dat.info) {
772          ubyte[] ubs;
773          ubs = dat.getText();
774          result = Data(dfl.internal.utf.ansiToUnicode(cast(Dstringz)ubs.ptr, ubs.length));
775       }
776    } else if(!stringICmp(toFmt, DataFormats.text)) {
777       if(typeid(Dstring) == dat.info) {
778          result = Data(cast(ubyte[])dfl.internal.utf.toAnsi(dat.getString()));
779       } else if(typeid(Dwstring) == dat.info) {
780          Dwstring wcs;
781          wcs = dat.getUnicodeText();
782          result = Data(cast(ubyte[])unicodeToAnsi(wcs.ptr, wcs.length));
783       }
784    }
785    return result;
786 }
787 
788 
789 class ComToDdataObject: dfl.data.IDataObject { // package
790    this(dfl.internal.wincom.IDataObject dataObj) {
791       this.dataObj = dataObj;
792       dataObj.AddRef();
793    }
794 
795 
796    ~this() {
797       dataObj.Release(); // Must get called...
798    }
799 
800 
801    private Data _getData(int id) {
802       FORMATETC fmte;
803       STGMEDIUM stgm;
804       void[] mem;
805       void* plock;
806 
807       fmte.cfFormat = cast(CLIPFORMAT)id;
808       fmte.ptd = null;
809       fmte.dwAspect = DVASPECT_CONTENT; // ?
810       fmte.lindex = -1;
811       fmte.tymed = TYMED_HGLOBAL; // ?
812 
813       if(S_OK != dataObj.GetData(&fmte, &stgm)) {
814          throw new DflException("Unable to get data");
815       }
816 
817 
818       void release() {
819          //ReleaseStgMedium(&stgm);
820          if(stgm.pUnkForRelease) {
821             stgm.pUnkForRelease.Release();
822          } else {
823             GlobalFree(stgm.hGlobal);
824          }
825       }
826 
827 
828       plock = GlobalLock(stgm.hGlobal);
829       if(!plock) {
830          release();
831          throw new DflException("Error obtaining data");
832       }
833 
834       mem = new ubyte[GlobalSize(stgm.hGlobal)];
835       mem[] = plock[0 .. mem.length];
836       GlobalUnlock(stgm.hGlobal);
837       release();
838 
839       return DataFormats.getDataFromFormat(id, mem);
840    }
841 
842 
843    Data getData(Dstring fmt) {
844       return _getData(DataFormats.getFormat(fmt).id);
845    }
846 
847 
848    Data getData(TypeInfo type) {
849       return _getData(DataFormats.getFormatFromType(type).id);
850    }
851 
852 
853    Data getData(Dstring fmt, bool doConvert) {
854       return getData(fmt); // ?
855    }
856 
857 
858    private bool _getDataPresent(int id) {
859       FORMATETC fmte;
860 
861       fmte.cfFormat = cast(CLIPFORMAT)id;
862       fmte.ptd = null;
863       fmte.dwAspect = DVASPECT_CONTENT; // ?
864       fmte.lindex = -1;
865       fmte.tymed = TYMED_HGLOBAL; // ?
866 
867       return S_OK == dataObj.QueryGetData(&fmte);
868    }
869 
870 
871    bool getDataPresent(Dstring fmt) {
872       return _getDataPresent(DataFormats.getFormat(fmt).id);
873    }
874 
875 
876    bool getDataPresent(TypeInfo type) {
877       return _getDataPresent(DataFormats.getFormatFromType(type).id);
878    }
879 
880 
881    bool getDataPresent(Dstring fmt, bool canConvert) {
882       return getDataPresent(fmt); // ?
883    }
884 
885 
886    Dstring[] getFormats() {
887       IEnumFORMATETC fenum;
888       FORMATETC fmte;
889       Dstring[] result;
890       ULONG nfetched = 1; // ?
891 
892       if(S_OK != dataObj.EnumFormatEtc(1, &fenum)) {
893          throw new DflException("Unable to get formats");
894       }
895 
896       fenum.AddRef(); // ?
897       for(;;) {
898          if(S_OK != fenum.Next(1, &fmte, &nfetched)) {
899             break;
900          }
901          if(!nfetched) {
902             break;
903          }
904          //cprintf("\t\t{getFormats:%d}\n", fmte.cfFormat);
905          result ~= DataFormats.getFormat(fmte.cfFormat).name;
906       }
907       fenum.Release(); // ?
908 
909       return result;
910    }
911 
912 
913    // TO-DO: remove...
914    deprecated final Dstring[] getFormats(bool onlyNative) {
915       return getFormats();
916    }
917 
918 
919    private void _setData(int id, Data obj) {
920       /+
921       FORMATETC fmte;
922       STGMEDIUM stgm;
923       HANDLE hmem;
924       void[] mem;
925       void* pmem;
926 
927       mem = DataFormats.getClipboardValueFromData(id, obj);
928 
929       hmem = GlobalAlloc(GMEM_SHARE, mem.length);
930       if(!hmem) {
931          //cprintf("Unable to GlobalAlloc().\n");
932       err_set:
933          throw new DflException("Unable to set data");
934       }
935       pmem = GlobalLock(hmem);
936       if(!pmem) {
937          //cprintf("Unable to GlobalLock().\n");
938          GlobalFree(hmem);
939          goto err_set;
940       }
941       pmem[0 .. mem.length] = mem;
942       GlobalUnlock(hmem);
943 
944       fmte.cfFormat = cast(CLIPFORMAT)id;
945       fmte.ptd = null;
946       fmte.dwAspect = DVASPECT_CONTENT; // ?
947       fmte.lindex = -1;
948       fmte.tymed = TYMED_HGLOBAL;
949 
950       stgm.tymed = TYMED_HGLOBAL;
951       stgm.hGlobal = hmem;
952       stgm.pUnkForRelease = null;
953 
954       // -dataObj- now owns the handle.
955       HRESULT hr = dataObj.SetData(&fmte, &stgm, true);
956       if(S_OK != hr) {
957          //cprintf("Unable to IDataObject::SetData() = %d (0x%X).\n", hr, hr);
958          // Failed, need to free it..
959          GlobalFree(hmem);
960          goto err_set;
961       }
962       +/
963       // Don't set stuff in someone else's data object.
964    }
965 
966 
967    void setData(Data obj) {
968       _setData(DataFormats.getFormatFromType(obj.info).id, obj);
969    }
970 
971 
972    void setData(Dstring fmt, Data obj) {
973       _setData(DataFormats.getFormat(fmt).id, obj);
974    }
975 
976 
977    void setData(TypeInfo type, Data obj) {
978       _setData(DataFormats.getFormatFromType(type).id, obj);
979    }
980 
981 
982    void setData(Dstring fmt, bool canConvert, Data obj) {
983       setData(fmt, obj); // ?
984    }
985 
986 
987    final bool isSameDataObject(dfl.internal.wincom.IDataObject dataObj) {
988       return dataObj is this.dataObj;
989    }
990 
991 
992 private:
993    dfl.internal.wincom.IDataObject dataObj;
994 }
995 
996 
997 package class EnumDataObjectFORMATETC: DflComObject, IEnumFORMATETC {
998    this(dfl.data.IDataObject dataObj, Dstring[] fmts, ULONG start) {
999       this.dataObj = dataObj;
1000       this.fmts = fmts;
1001       idx = start;
1002    }
1003 
1004 
1005    this(dfl.data.IDataObject dataObj) {
1006       this(dataObj, dataObj.getFormats(), 0);
1007    }
1008 
1009 
1010    extern(Windows):
1011       override HRESULT QueryInterface(IID* riid, void** ppv) {
1012       if(*riid == _IID_IEnumFORMATETC) {
1013          *ppv = cast(void*)cast(IEnumFORMATETC)this;
1014          AddRef();
1015          return S_OK;
1016       } else if(*riid == _IID_IUnknown) {
1017          *ppv = cast(void*)cast(IUnknown)this;
1018          AddRef();
1019          return S_OK;
1020       } else {
1021          *ppv = null;
1022          return E_NOINTERFACE;
1023       }
1024    }
1025 
1026 
1027    HRESULT Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) {
1028       HRESULT result;
1029 
1030       try {
1031          if(idx < fmts.length) {
1032             ULONG end;
1033             end = idx + celt;
1034             if(end > fmts.length) {
1035                result = S_FALSE; // ?
1036                end = fmts.length;
1037 
1038                if(pceltFetched) {
1039                   *pceltFetched = end - idx;
1040                }
1041             } else {
1042                result = S_OK;
1043 
1044                if(pceltFetched) {
1045                   *pceltFetched = celt;
1046                }
1047             }
1048 
1049             for(; idx != end; idx++) {
1050                rgelt.cfFormat = cast(CLIPFORMAT)DataFormats.getFormat(fmts[idx]).id;
1051                rgelt.ptd = null;
1052                rgelt.dwAspect = DVASPECT_CONTENT; // ?
1053                rgelt.lindex = -1;
1054                //rgelt.tymed = TYMED_NULL;
1055                rgelt.tymed = TYMED_HGLOBAL;
1056 
1057                rgelt++;
1058             }
1059          } else {
1060             if(pceltFetched) {
1061                *pceltFetched = 0;
1062             }
1063             result = S_FALSE;
1064          }
1065       } catch(DThrowable e) {
1066          Application.onThreadException(e);
1067 
1068          result = E_UNEXPECTED;
1069       }
1070 
1071       return result;
1072    }
1073 
1074 
1075    HRESULT Skip(ULONG celt) {
1076       idx += celt;
1077       return (idx > fmts.length) ? S_FALSE : S_OK;
1078    }
1079 
1080 
1081    HRESULT Reset() {
1082       HRESULT result;
1083 
1084       try {
1085          idx = 0;
1086          fmts = dataObj.getFormats();
1087 
1088          result = S_OK;
1089       } catch(DThrowable e) {
1090          Application.onThreadException(e);
1091 
1092          result = E_UNEXPECTED;
1093       }
1094 
1095       return result;
1096    }
1097 
1098 
1099    HRESULT Clone(IEnumFORMATETC* ppenum) {
1100       HRESULT result;
1101 
1102       try {
1103          *ppenum = new EnumDataObjectFORMATETC(dataObj, fmts, idx);
1104          result = S_OK;
1105       } catch(DThrowable e) {
1106          Application.onThreadException(e);
1107 
1108          result = E_UNEXPECTED;
1109       }
1110 
1111       return result;
1112    }
1113 
1114 
1115    extern(D):
1116 
1117    private:
1118       dfl.data.IDataObject dataObj;
1119       Dstring[] fmts;
1120       ULONG idx;
1121    }
1122 
1123 
1124    class DtoComDataObject: DflComObject, dfl.internal.wincom.IDataObject { // package
1125    this(dfl.data.IDataObject dataObj) {
1126       this.dataObj = dataObj;
1127    }
1128 
1129 
1130    extern(Windows):
1131 
1132       override HRESULT QueryInterface(IID* riid, void** ppv) {
1133       if(*riid == _IID_IDataObject) {
1134          *ppv = cast(void*)cast(dfl.internal.wincom.IDataObject)this;
1135          AddRef();
1136          return S_OK;
1137       } else if(*riid == _IID_IUnknown) {
1138          *ppv = cast(void*)cast(IUnknown)this;
1139          AddRef();
1140          return S_OK;
1141       } else {
1142          *ppv = null;
1143          return E_NOINTERFACE;
1144       }
1145    }
1146 
1147 
1148    HRESULT GetData(FORMATETC* pFormatetc, STGMEDIUM* pmedium) {
1149       Dstring fmt;
1150       HRESULT result = S_OK;
1151       Data data;
1152 
1153       try {
1154          if(pFormatetc.lindex != -1) {
1155             result = DV_E_LINDEX;
1156          } else if(!(pFormatetc.tymed & TYMED_HGLOBAL)) {
1157             // Unsupported medium type.
1158             result = DV_E_TYMED;
1159          } else if(!(pFormatetc.dwAspect & DVASPECT_CONTENT)) {
1160             // What about the other aspects?
1161             result = DV_E_DVASPECT;
1162          } else {
1163             DataFormats.Format dfmt;
1164             dfmt = DataFormats.getFormat(pFormatetc.cfFormat);
1165             fmt = dfmt.name;
1166             data = dataObj.getData(fmt, true); // Should this be convertable?
1167 
1168             HGLOBAL hg;
1169             void* pmem;
1170             void[] src;
1171 
1172             //src = data.value;
1173             src = DataFormats.getClipboardValueFromData(dfmt.id, data);
1174             hg = GlobalAlloc(GMEM_SHARE, src.length);
1175             if(!hg) {
1176                result = STG_E_MEDIUMFULL;
1177             } else {
1178                pmem = GlobalLock(hg);
1179                if(!hg) {
1180                   result = E_UNEXPECTED;
1181                   GlobalFree(hg);
1182                } else {
1183                   pmem[0 .. src.length] = src[];
1184                   GlobalUnlock(hg);
1185 
1186                   pmedium.tymed = TYMED_HGLOBAL;
1187                   pmedium.hGlobal = hg;
1188                   pmedium.pUnkForRelease = null; // ?
1189                }
1190             }
1191          }
1192       } catch(DflException e) {
1193          //Application.onThreadException(e);
1194 
1195          result = DV_E_FORMATETC;
1196       } catch(OomException e) {
1197          Application.onThreadException(e);
1198 
1199          result = E_OUTOFMEMORY;
1200       } catch(DThrowable e) {
1201          Application.onThreadException(e);
1202 
1203          result = E_UNEXPECTED;
1204       }
1205 
1206       return result;
1207    }
1208 
1209 
1210    HRESULT GetDataHere(FORMATETC* pFormatetc, STGMEDIUM* pmedium) {
1211       return E_UNEXPECTED; // TODO: finish.
1212    }
1213 
1214 
1215    HRESULT QueryGetData(FORMATETC* pFormatetc) {
1216       Dstring fmt;
1217       HRESULT result = S_OK;
1218 
1219       try {
1220          if(pFormatetc.lindex != -1) {
1221             result = DV_E_LINDEX;
1222          } else if(!(pFormatetc.tymed & TYMED_HGLOBAL)) {
1223             // Unsupported medium type.
1224             result = DV_E_TYMED;
1225          } else if(!(pFormatetc.dwAspect & DVASPECT_CONTENT)) {
1226             // What about the other aspects?
1227             result = DV_E_DVASPECT;
1228          } else {
1229             fmt = DataFormats.getFormat(pFormatetc.cfFormat).name;
1230 
1231             if(!dataObj.getDataPresent(fmt)) {
1232                result = S_FALSE;   // ?
1233             }
1234          }
1235       } catch(DflException e) {
1236          //Application.onThreadException(e);
1237 
1238          result = DV_E_FORMATETC;
1239       } catch(OomException e) {
1240          Application.onThreadException(e);
1241 
1242          result = E_OUTOFMEMORY;
1243       } catch(DThrowable e) {
1244          Application.onThreadException(e);
1245 
1246          result = E_UNEXPECTED;
1247       }
1248 
1249       return result;
1250    }
1251 
1252 
1253    HRESULT GetCanonicalFormatEtc(FORMATETC* pFormatetcIn, FORMATETC* pFormatetcOut) {
1254       // TODO: finish.
1255 
1256       pFormatetcOut.ptd = null;
1257       return E_NOTIMPL;
1258    }
1259 
1260 
1261    HRESULT SetData(FORMATETC* pFormatetc, STGMEDIUM* pmedium, BOOL fRelease) {
1262       return E_UNEXPECTED; // TODO: finish.
1263    }
1264 
1265 
1266    HRESULT EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC* ppenumFormatetc) {
1267       // SHCreateStdEnumFmtEtc() requires Windows 2000 +
1268 
1269       HRESULT result;
1270 
1271       try {
1272          if(dwDirection == DATADIR_GET) {
1273             *ppenumFormatetc = new EnumDataObjectFORMATETC(dataObj);
1274             result = S_OK;
1275          } else {
1276             result = E_NOTIMPL;
1277          }
1278       } catch(DThrowable e) {
1279          Application.onThreadException(e);
1280 
1281          result = E_UNEXPECTED;
1282       }
1283 
1284       return result;
1285    }
1286 
1287 
1288    HRESULT DAdvise(FORMATETC* pFormatetc, DWORD advf, IAdviseSink pAdvSink, DWORD* pdwConnection) {
1289       return E_UNEXPECTED; // TODO: finish.
1290    }
1291 
1292 
1293    HRESULT DUnadvise(DWORD dwConnection) {
1294       return E_UNEXPECTED; // TODO: finish.
1295    }
1296 
1297 
1298    HRESULT EnumDAdvise(IEnumSTATDATA* ppenumAdvise) {
1299       return E_UNEXPECTED; // TODO: finish.
1300    }
1301 
1302 
1303    extern(D):
1304 
1305     private:
1306       dfl.data.IDataObject dataObj;
1307    }
1308