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 }