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