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 }