1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 
5 
6 module dfl.imagelist;
7 
8 import dfl.base, dfl.drawing, dfl.internal.winapi;
9 import dfl.collections;
10 
11 
12 version(DFL_NO_IMAGELIST) {
13 }
14 else {
15 
16    class ImageList { // docmain
17 
18       class ImageCollection {
19          protected this() {
20          }
21 
22 
23          void insert(int index, Image img) {
24             if(index >= _images.length) {
25                add(img);
26             } else {
27                assert(0, "Must add images to the end of the image list");
28             }
29          }
30 
31 
32          final void addStrip(Image img) {
33             HGDIOBJ hgo;
34             if(1 != img._imgtype(&hgo)) {
35                debug {
36                   assert(0, "Image list: addStrip needs bitmap");
37                }
38                else {
39                   _unableimg();
40                }
41             }
42 
43             auto sz = imageSize;
44             if(img.height != sz.height
45                   || img.width % sz.width) {
46                debug {
47                   assert(0, "Image list: invalid image size");
48                }
49                else {
50                   _unableimg();
51                }
52             }
53             int num = img.width / sz.width;
54 
55             /+
56             if(1 == num) {
57                add(img);
58                return;
59             }
60             +/
61 
62             auto _hdl = handle; // _addhbitmap needs the handle! Could avoid this in the future.
63             _addhbitmap(hgo);
64 
65             int x = 0;
66             for(; num; num--) {
67                auto sp = new StripPart();
68                sp.origImg = img;
69                sp.hbm = hgo;
70                sp.partBounds = Rect(x, 0, sz.width, sz.height);
71 
72                _images ~= sp;
73 
74                x += sz.width;
75             }
76          }
77 
78 
79        package:
80 
81          Image[] _images;
82 
83 
84          static class StripPart: Image {
85             override @property Size size() { // getter
86                return partBounds.size;
87             }
88 
89 
90             override void draw(Graphics g, Point pt) {
91                HDC memdc;
92                memdc = CreateCompatibleDC(g.handle);
93                try {
94                   HGDIOBJ hgo;
95                   hgo = SelectObject(memdc, hbm);
96                   BitBlt(g.handle, pt.x, pt.y, partBounds.width, partBounds.height, memdc, partBounds.x, partBounds.y, SRCCOPY);
97                   SelectObject(memdc, hgo); // Old bitmap.
98                }
99                finally {
100                   DeleteDC(memdc);
101                }
102             }
103 
104 
105             override void drawStretched(Graphics g, Rect r) {
106                HDC memdc;
107                memdc = CreateCompatibleDC(g.handle);
108                try {
109                   HGDIOBJ hgo;
110                   int lstretch;
111                   hgo = SelectObject(memdc, hbm);
112                   lstretch = SetStretchBltMode(g.handle, COLORONCOLOR);
113                   StretchBlt(g.handle, r.x, r.y, r.width, r.height,
114                              memdc, partBounds.x, partBounds.y, partBounds.width, partBounds.height, SRCCOPY);
115                   SetStretchBltMode(g.handle, lstretch);
116                   SelectObject(memdc, hgo); // Old bitmap.
117                }
118                finally {
119                   DeleteDC(memdc);
120                }
121             }
122 
123 
124             Image origImg; // Hold this so the HBITMAP doesn't get collected.
125             HBITMAP hbm;
126             Rect partBounds;
127          }
128 
129 
130          void _adding(size_t idx, Image val) {
131             assert(val !is null);
132 
133             switch(val._imgtype(null)) {
134                case 1:
135                case 2:
136                   break;
137                default:
138                   debug {
139                      assert(0, "Image list: invalid image type");
140                   }
141                   else {
142                      _unableimg();
143                   }
144             }
145 
146             if(val.size != imageSize) {
147                debug {
148                   assert(0, "Image list: invalid image size");
149                }
150                else {
151                   _unableimg();
152                }
153             }
154          }
155 
156 
157          void _added(size_t idx, Image val) {
158             if(isHandleCreated) {
159                //if(idx >= _images.length) // Can't test for this here because -val- is already added to the array.
160                _addimg(val);
161             }
162          }
163 
164 
165          void _removed(size_t idx, Image val) {
166             if(isHandleCreated) {
167                if(size_t.max == idx) { // Clear all.
168                   imageListRemove(handle, -1);
169                } else {
170                   imageListRemove(handle, idx);
171                }
172             }
173          }
174 
175 
176        public:
177 
178          mixin ListWrapArray!(Image, _images,
179                               _adding, _added,
180                               _blankListCallback!(Image), _removed,
181                               false, false, false);
182       }
183 
184 
185       this() {
186          InitCommonControls();
187 
188          _cimages = new ImageCollection();
189          _transcolor = Color.transparent;
190       }
191 
192 
193 
194       final @property void colorDepth(ColorDepth depth) { // setter
195          assert(!isHandleCreated);
196 
197          this._depth = depth;
198       }
199 
200       /// ditto
201       final @property ColorDepth colorDepth() { // getter
202          return _depth;
203       }
204 
205 
206 
207       final @property void transparentColor(Color tc) { // setter
208          assert(!isHandleCreated);
209 
210          _transcolor = tc;
211       }
212 
213       /// ditto
214       final @property Color transparentColor() { // getter
215          return _transcolor;
216       }
217 
218 
219 
220       final @property void imageSize(Size sz) { // setter
221          assert(!isHandleCreated);
222 
223          assert(sz.width && sz.height);
224 
225          _w = sz.width;
226          _h = sz.height;
227       }
228 
229       /// ditto
230       final @property Size imageSize() { // getter
231          return Size(_w, _h);
232       }
233 
234 
235 
236       final @property ImageCollection images() { // getter
237          return _cimages;
238       }
239 
240 
241 
242       final @property void tag(Object t) { // setter
243          this._tag = t;
244       }
245 
246       /// ditto
247       final @property Object tag() { // getter
248          return this._tag;
249       }
250 
251 
252       /+ // Actually, forget about these; just draw with the actual images.
253 
254       final void draw(Graphics g, Point pt, int index) {
255          return draw(g, pt.x, pt.y, index);
256       }
257 
258       /// ditto
259       final void draw(Graphics g, int x, int y, int index) {
260          imageListDraw(handle, index, g.handle, x, y, ILD_NORMAL);
261       }
262 
263       /// ditto
264       // stretch
265       final void draw(Graphics g, int x, int y, int width, int height, int index) {
266          // ImageList_DrawEx operates differently if the width or height is zero
267          // so bail out if zero and pretend the zero size image was drawn.
268          if(!width) {
269             return;
270          }
271          if(!height) {
272             return;
273          }
274 
275          imageListDrawEx(handle, index, g.handle, x, y, width, height,
276                          CLR_NONE, CLR_NONE, ILD_NORMAL); // ?
277       }
278       +/
279 
280 
281 
282       final @property bool isHandleCreated() { // getter
283          return HIMAGELIST.init != _hil;
284       }
285 
286       deprecated alias isHandleCreated handleCreated;
287 
288 
289 
290       final @property HIMAGELIST handle() { // getter
291          if(!isHandleCreated) {
292             _createimagelist();
293          }
294          return _hil;
295       }
296 
297 
298 
299       void dispose() {
300          return dispose(true);
301       }
302 
303       /// ditto
304       void dispose(bool disposing) {
305          if(isHandleCreated) {
306             imageListDestroy(_hil);
307          }
308          _hil = HIMAGELIST.init;
309 
310          if(disposing) {
311             //_cimages._images = null; // Not GC-safe in dtor.
312             //_cimages = null; // Could cause bad things.
313          }
314       }
315 
316 
317       ~this() {
318          dispose();
319       }
320 
321 
322     private:
323 
324       ColorDepth _depth = ColorDepth.DEPTH_8BIT;
325       Color _transcolor;
326       ImageCollection _cimages;
327       HIMAGELIST _hil;
328       int _w = 16, _h = 16;
329       Object _tag;
330 
331 
332       void _createimagelist() {
333          if(isHandleCreated) {
334             imageListDestroy(_hil);
335             _hil = HIMAGELIST.init;
336          }
337 
338          UINT flags = ILC_MASK;
339          switch(_depth) {
340             case ColorDepth.DEPTH_4BIT:          flags |= ILC_COLOR4;  break;
341          default: case ColorDepth.DEPTH_8BIT: flags |= ILC_COLOR8;  break;
342             case ColorDepth.DEPTH_16BIT:         flags |= ILC_COLOR16; break;
343             case ColorDepth.DEPTH_24BIT:         flags |= ILC_COLOR24; break;
344             case ColorDepth.DEPTH_32BIT:         flags |= ILC_COLOR32; break;
345          }
346 
347          // Note: cGrow is not a limit, but how many images to preallocate each grow.
348          _hil = imageListCreate(_w, _h, flags, _cimages._images.length, 4 + _cimages._images.length / 4);
349          if(!_hil) {
350             throw new DflException("Unable to create image list");
351          }
352 
353          foreach(img; _cimages._images) {
354             _addimg(img);
355          }
356       }
357 
358 
359       void _unableimg() {
360          throw new DflException("Unable to add image to image list");
361       }
362 
363 
364       int _addimg(Image img) {
365          assert(isHandleCreated);
366 
367          HGDIOBJ hgo;
368          int result;
369          switch(img._imgtype(&hgo)) {
370             case 1:
371                result = _addhbitmap(hgo);
372                break;
373 
374             case 2:
375                result = imageListAddIcon(_hil, cast(HICON)hgo);
376                break;
377 
378             default:
379                result = -1;
380          }
381 
382          //if(-1 == result)
383          // _unableimg();
384          return result;
385       }
386 
387       int _addhbitmap(HBITMAP hbm) {
388          assert(isHandleCreated);
389 
390          COLORREF cr;
391          if(_transcolor == Color.empty
392                || _transcolor == Color.transparent) {
393             cr = CLR_NONE; // ?
394          } else {
395             cr = _transcolor.toRgb();
396          }
397          return imageListAddMasked(_hil, cast(HBITMAP)hbm, cr);
398       }
399    }
400 
401 
402    private extern(Windows) {
403       // This was the only way I could figure out how to use the current actctx (Windows issue).
404 
405       HIMAGELIST imageListCreate(
406          int cx, int cy, UINT flags, int cInitial, int cGrow) {
407          alias typeof(&ImageList_Create) TProc;
408          static TProc proc = null;
409          if(!proc) {
410             proc = cast(typeof(proc))GetProcAddress(GetModuleHandleA("comctl32.dll"), "ImageList_Create");
411          }
412          return proc(cx, cy, flags, cInitial, cGrow);
413       }
414 
415       int imageListAddIcon(
416          HIMAGELIST himl, HICON hicon) {
417          alias typeof(&ImageList_AddIcon) TProc;
418          static TProc proc = null;
419          if(!proc) {
420             proc = cast(typeof(proc))GetProcAddress(GetModuleHandleA("comctl32.dll"), "ImageList_AddIcon");
421          }
422          return proc(himl, hicon);
423       }
424 
425       int imageListAddMasked(
426          HIMAGELIST himl, HBITMAP hbmImage, COLORREF crMask) {
427          alias typeof(&ImageList_AddMasked) TProc;
428          static TProc proc = null;
429          if(!proc) {
430             proc = cast(typeof(proc))GetProcAddress(GetModuleHandleA("comctl32.dll"), "ImageList_AddMasked");
431          }
432          return proc(himl, hbmImage, crMask);
433       }
434 
435       BOOL imageListRemove(
436          HIMAGELIST himl, int i) {
437          alias typeof(&ImageList_Remove) TProc;
438          static TProc proc = null;
439          if(!proc) {
440             proc = cast(typeof(proc))GetProcAddress(GetModuleHandleA("comctl32.dll"), "ImageList_Remove");
441          }
442          return proc(himl, i);
443       }
444 
445       BOOL imageListDestroy(
446          HIMAGELIST himl) {
447          alias typeof(&ImageList_Destroy) TProc;
448          static TProc proc = null;
449          if(!proc) {
450             proc = cast(typeof(proc))GetProcAddress(GetModuleHandleA("comctl32.dll"), "ImageList_Destroy");
451          }
452          return proc(himl);
453       }
454    }
455 }
456