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