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 }