1 /** DGui project file. 2 3 Copyright: Trogu Antonio Davide 2011-2013 4 5 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 7 Authors: Trogu Antonio Davide 8 */ 9 module dgui.core.menu.abstractmenu; 10 11 import std.utf: toUTFz; 12 public import dgui.core.winapi; 13 public import dgui.core.collection; 14 public import dgui.imagelist; 15 public import dgui.core.events.eventargs; 16 public import dgui.core.events.event; 17 import dgui.canvas; 18 import dgui.core.interfaces.idisposable; 19 import dgui.core.handle; 20 import dgui.core.utils; 21 import dgui.core.wincomp; 22 23 enum: uint 24 { 25 MIIM_STRING = 64, 26 MIIM_FTYPE = 256, 27 28 MIM_MAXHEIGHT = 1, 29 MIM_BACKGROUND = 2, 30 MIM_HELPID = 4, 31 MIM_MENUDATA = 8, 32 MIM_STYLE = 16, 33 MIM_APPLYTOSUBMENUS = 0x80000000L, 34 35 MNS_NOCHECK = 0x80000000, 36 MNS_MODELESS = 0x40000000, 37 MNS_DRAGDROP = 0x20000000, 38 MNS_AUTODISMISS = 0x10000000, 39 MNS_NOTIFYBYPOS = 0x08000000, 40 MNS_CHECKORBMP = 0x04000000, 41 } 42 43 enum MenuBits: ubyte 44 { 45 enabled = 1, 46 checked = 2, 47 } 48 49 enum MenuStyle: ubyte 50 { 51 normal = 1, 52 separator = 2, 53 } 54 55 abstract class Menu: Handle!(HMENU), IDisposable 56 { 57 public Event!(Menu, EventArgs) popup; 58 59 private Collection!(MenuItem) _items; 60 protected Menu _parent; 61 62 public ~this() 63 { 64 this.dispose(); 65 } 66 67 public abstract void create(); 68 69 public void dispose() 70 { 71 //From MSDN: DestroyMenu is recursive, it will destroy the menu and its submenus. 72 if(this.created) 73 { 74 DestroyMenu(this._handle); 75 } 76 } 77 78 @property public final MenuItem[] items() 79 { 80 if(this._items) 81 { 82 return this._items.get(); 83 } 84 85 return null; 86 } 87 88 @property public Menu parent() 89 { 90 return this._parent; 91 } 92 93 public final MenuItem addItem(string t) 94 { 95 return this.addItem(t, -1, true); 96 } 97 98 public final MenuItem addItem(string t, bool e) 99 { 100 return this.addItem(t, -1, e); 101 } 102 103 public final MenuItem addItem(string t, int imgIdx) 104 { 105 return this.addItem(t, imgIdx, true); 106 } 107 108 public final MenuItem addItem(string t, int imgIdx, bool e) 109 { 110 if(!this._items) 111 { 112 this._items = new Collection!(MenuItem)(); 113 } 114 115 MenuItem mi = new MenuItem(this, MenuStyle.normal, t, e); 116 mi.imageIndex = imgIdx; 117 118 this._items.add(mi); 119 120 if(this.created) 121 { 122 mi.create(); 123 } 124 125 return mi; 126 } 127 128 public final MenuItem addSeparator() 129 { 130 if(!this._items) 131 { 132 this._items = new Collection!(MenuItem)(); 133 } 134 135 MenuItem mi = new MenuItem(this, MenuStyle.separator, null, true); 136 this._items.add(mi); 137 138 if(this.created) 139 { 140 mi.create(); 141 } 142 143 return mi; 144 } 145 146 public final void removeItem(int idx) 147 { 148 if(this._items) 149 { 150 this._items.removeAt(idx); 151 } 152 153 if(this.created) 154 { 155 DeleteMenu(this._handle, idx, MF_BYPOSITION); 156 } 157 } 158 159 public void onPopup(EventArgs e) 160 { 161 this.popup(this, e); 162 } 163 } 164 165 class RootMenu: Menu 166 { 167 protected Collection!(HBITMAP) _bitmaps; 168 protected ImageList _imgList; 169 170 public override void dispose() 171 { 172 if(this._bitmaps) 173 { 174 foreach(HBITMAP hBitmap; this._bitmaps) 175 { 176 DeleteObject(hBitmap); 177 } 178 } 179 180 if(this._imgList) 181 { 182 this._imgList.dispose(); 183 } 184 185 super.dispose(); 186 } 187 188 @property package Collection!(HBITMAP) bitmaps() 189 { 190 return this._bitmaps; 191 } 192 193 @property public ImageList imageList() 194 { 195 return this._imgList; 196 } 197 198 @property public void imageList(ImageList imgList) 199 { 200 this._imgList = imgList; 201 202 if(!this._bitmaps) 203 { 204 this._bitmaps = new Collection!(HBITMAP)(); 205 } 206 } 207 208 public override void create() 209 { 210 MENUINFO mi; 211 212 mi.cbSize = MENUINFO.sizeof; 213 mi.fMask = MIM_MENUDATA | MIM_APPLYTOSUBMENUS | MIM_STYLE; 214 mi.dwStyle = MNS_NOTIFYBYPOS | MNS_CHECKORBMP; 215 mi.dwMenuData = winCast!(uint)(this); 216 217 SetMenuInfo(this._handle, &mi); 218 219 if(this._items) 220 { 221 foreach(MenuItem mi; this._items) 222 { 223 mi.create(); 224 } 225 } 226 } 227 } 228 229 class MenuItem: Menu 230 { 231 public Event!(MenuItem, EventArgs) click; 232 233 private MenuStyle _style = MenuStyle.normal; 234 private MenuBits _mBits = MenuBits.enabled; 235 private int _imgIndex = -1; 236 private int _index = -1; 237 private string _text; 238 239 protected this(Menu parent, MenuStyle mt, string t, bool e) 240 { 241 this._parent = parent; 242 this._style = mt; 243 this._text = t; 244 245 if(!e) 246 { 247 this._mBits &= ~MenuBits.enabled; 248 } 249 } 250 251 public void performClick() 252 { 253 this.onClick(EventArgs.empty); 254 } 255 256 private static void createMenuItem(MenuItem mi, HMENU hPopupMenu) 257 { 258 MENUITEMINFOW minfo; 259 260 minfo.cbSize = MENUITEMINFOW.sizeof; 261 minfo.fMask = MIIM_FTYPE; 262 minfo.dwItemData = winCast!(uint)(mi); 263 264 switch(mi.style) 265 { 266 case MenuStyle.normal: 267 { 268 WindowsVersion ver = getWindowsVersion(); 269 270 minfo.fMask |= MIIM_DATA | MIIM_STRING | MIIM_STATE; 271 minfo.fState = (mi.enabled ? MFS_ENABLED : MFS_DISABLED) | (mi.checked ? MFS_CHECKED : 0); 272 minfo.dwTypeData = toUTFz!(wchar*)(mi.text); 273 274 RootMenu root = mi.rootMenu; 275 276 if(root.imageList && mi.imageIndex != -1) 277 { 278 minfo.fMask |= MIIM_BITMAP; 279 280 if(ver > WindowsVersion.windowsXP) // Is Vista or 7 281 { 282 HBITMAP hBitmap = iconToBitmapPARGB32(root.imageList.images[mi.imageIndex].handle); 283 root.bitmaps.add(hBitmap); 284 285 minfo.hbmpItem = hBitmap; 286 } 287 else // Is 2000 or XP 288 { 289 minfo.hbmpItem = HBMMENU_CALLBACK; 290 } 291 } 292 } 293 break; 294 295 case MenuStyle.separator: 296 minfo.fType = MFT_SEPARATOR; 297 break; 298 299 default: 300 break; 301 } 302 303 if(mi._items) 304 { 305 HMENU hChildMenu = CreatePopupMenu(); 306 minfo.fMask |= MIIM_SUBMENU; 307 minfo.hSubMenu = hChildMenu; 308 309 foreach(MenuItem smi; mi._items) 310 { 311 MenuItem.createMenuItem(smi, hChildMenu); 312 } 313 } 314 315 InsertMenuItemW(hPopupMenu ? hPopupMenu : mi._parent.handle, -1, TRUE, &minfo); 316 } 317 318 @property public final int index() 319 { 320 if(this._parent) 321 { 322 int i = 0; 323 324 foreach(MenuItem mi; this._parent.items) 325 { 326 if(mi is this) 327 { 328 return i; 329 } 330 331 i++; 332 } 333 } 334 335 return -1; 336 } 337 338 @property public final MenuStyle style() 339 { 340 return this._style; 341 } 342 343 @property public RootMenu rootMenu() 344 { 345 Menu p = this._parent; 346 347 while(p.parent) 348 { 349 p = p.parent; 350 } 351 352 return cast(RootMenu)p; 353 } 354 355 @property public int imageIndex() 356 { 357 return this._imgIndex; 358 } 359 360 @property public void imageIndex(int imgIdx) 361 { 362 this._imgIndex = imgIdx; 363 364 if(this._parent && this._parent.created) 365 { 366 RootMenu root = this.rootMenu; 367 368 int idx = this.index; 369 370 HBITMAP hBitmap = null; 371 if(imgIdx != -1) 372 { 373 hBitmap = iconToBitmapPARGB32(root.imageList.images[imgIdx].handle); 374 root.bitmaps.add(hBitmap); 375 } 376 377 MENUITEMINFOW minfo; 378 379 minfo.cbSize = MENUITEMINFOW.sizeof; 380 minfo.fMask = MIIM_BITMAP; 381 minfo.hbmpItem = hBitmap; 382 383 SetMenuItemInfoW(this._parent.handle, idx, true, &minfo); 384 } 385 } 386 387 @property public final bool enabled() 388 { 389 return cast(bool)(this._mBits & MenuBits.enabled); 390 } 391 392 @property public final void enabled(bool b) 393 { 394 this._mBits |= MenuBits.enabled; 395 396 if(this._parent && this._parent.created) 397 { 398 int idx = this.index; 399 400 MENUITEMINFOW minfo; 401 402 minfo.cbSize = MENUITEMINFOW.sizeof; 403 minfo.fMask = MIIM_STATE; 404 minfo.fState = b ? MFS_ENABLED : MFS_DISABLED; 405 406 SetMenuItemInfoW(this._parent.handle, idx, true, &minfo); 407 } 408 } 409 410 @property public final string text() 411 { 412 return this._text; 413 } 414 415 @property public final void text(string s) 416 { 417 this._text = s; 418 419 if(this._parent && this._parent.created) 420 { 421 int idx = this.index; 422 423 MENUITEMINFOW minfo; 424 425 minfo.cbSize = MENUITEMINFOW.sizeof; 426 minfo.fMask = MIIM_STRING; 427 minfo.dwTypeData = toUTFz!(wchar*)(s); 428 429 SetMenuItemInfoW(this._parent.handle, idx, true, &minfo); 430 } 431 } 432 433 @property public final bool checked() 434 { 435 return cast(bool)(this._mBits & MenuBits.checked); 436 } 437 438 @property public final void checked(bool b) 439 { 440 this._mBits |= MenuBits.checked; 441 442 if(this._parent && this._parent.created) 443 { 444 int idx = this.index; 445 446 MENUITEMINFOW minfo; 447 448 minfo.cbSize = MENUITEMINFOW.sizeof; 449 minfo.fMask = MIIM_STATE; 450 451 if(b) 452 { 453 minfo.fState |= MFS_CHECKED; 454 } 455 else 456 { 457 minfo.fState &= ~MFS_CHECKED; 458 } 459 460 SetMenuItemInfoW(this._parent.handle, idx, true, &minfo); 461 } 462 } 463 464 protected override void create() 465 { 466 MenuItem.createMenuItem(this, null); 467 } 468 469 protected void onClick(EventArgs e) 470 { 471 this.click(this, e); 472 } 473 }