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