1 /**
2  * Menu module.
3  *
4  * License:
5  *     MIT. See LICENSE for full details.
6  */
7 module tkd.widget.menu.menu;
8 
9 /**
10  * Imports.
11  */
12 import std.array;
13 import std.conv;
14 import std.string;
15 import std.typecons;
16 import tkd.element.element;
17 import tkd.element.uielement;
18 import tkd.image.image;
19 import tkd.image.imageposition;
20 import tkd.widget.menu.menubar;
21 
22 /**
23  * The cascading menu that items are selected from.
24  *
25  * Example:
26  * ---
27  * auto menu = new Menu()
28  * 	.addEntry("Entry 1", delegate(CommandArgs args){ ... })
29  * 	.addEntry("Entry 2", delegate(CommandArgs args){ ... })
30  * 	.addSeparator()
31  * 	.addEntry("Entry 3", delegate(CommandArgs args){ ... });
32  * ---
33  *
34  * Additional_Events:
35  *     Additional events that can also be bound to using the $(LINK2 ../../element/uielement.html#UiElement.bind, bind) method.
36  *     $(P
37  *         <<MenuSelect>>,
38  *         <<PrevWindow>>,
39  *         <Alt-Key>,
40  *         <Button>,
41  *         <ButtonRelease>,
42  *         <Enter>
43  *         <Key-Down>,
44  *         <Key-Escape>,
45  *         <Key-F10>,
46  *         <Key-Left>,
47  *         <Key-Return>,
48  *         <Key-Right>,
49  *         <Key-Tab>,
50  *         <Key-Up>,
51  *         <Key-space>,
52  *         <Key>,
53  *         <Leave>,
54  *         <Motion>,
55  *     )
56  *
57  * See_Also:
58  *     $(LINK2 ../../element/uielement.html, tkd.element.uielement)
59  */
60 class Menu : UiElement
61 {
62 	/**
63 	 * Array containing variables used by check button entries in the menu.
64 	 */
65 	private string[] _checkButtonVariables;
66 
67 	/**
68 	 * The variable used by any radio button entries in the menu.
69 	 */
70 	private string _radioButtonVariable;
71 
72 	/**
73 	 * Construct the widget.
74 	 */
75 	public this()
76 	{
77 		super();
78 		this._elementId = "menu";
79 		this._radioButtonVariable = format("variable-%s", this.generateHash());
80 
81 		this._tk.eval("menu %s -type normal -tearoff 0", this.id);
82 	}
83 
84 	/**
85 	 * Construct the widget.
86 	 *
87 	 * Params:
88 	 *     parent = The parent of this widget.
89 	 *     label = The label of the menu.
90 	 *     underlineChar = The index of the character to underline.
91 	 *
92 	 * See_Also:
93 	 *     $(LINK2 ./menubar.html, tkd.widget.menu.menubar)
94 	 */
95 	public this(MenuBar parent, string label, ubyte underlineChar = ubyte.max)
96 	{
97 		super(parent);
98 		this._elementId = "menu";
99 		this._radioButtonVariable = format("variable-%s", this.generateHash());
100 
101 		this._tk.eval("menu %s -type normal -tearoff 0", this.id);
102 		this._tk.eval("%s add cascade -menu %s -label {%s} -underline %s", parent.id, this.id, label, underlineChar);
103 	}
104 
105 	/**
106 	 * Add a cascade menu to this menu.
107 	 *
108 	 * Params:
109 	 *     label = The label of the menu.
110 	 *     menu = The menu to add as a cascade menu.
111 	 *     underlineChar = The index of the character to underline.
112 	 *
113 	 * Returns:
114 	 *     This widget to aid method chaining.
115 	 */
116 	public auto addMenuEntry(this T)(string label, Menu menu, ubyte underlineChar = ubyte.max)
117 	{
118 		string originalId = menu.id;
119 		menu._parent = this;
120 
121 		this._tk.eval("%s clone %s", originalId, menu.id);
122 		this._tk.eval("%s add cascade -menu %s -label {%s} -underline %s", this.id, menu.id, label, underlineChar);
123 
124 		return cast(T) this;
125 	}
126 
127 	/**
128 	 * Add an item to the menu.
129 	 *
130 	 * Params:
131 	 *     label = The label of the item.
132 	 *     callback = The callback to execute when this item is selected in the menu.
133 	 *     shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event.
134 	 *
135 	 * Returns:
136 	 *     This widget to aid method chaining.
137 	 *
138 	 * Callback_Arguments:
139 	 *     These are the fields within the callback's $(LINK2 
140 	 *     ../../element/element.html#CommandArgs, CommandArgs) parameter which 
141 	 *     are populated by this method when the callback is executed. 
142 	 *     $(P
143 	 *         $(PARAM_TABLE
144 	 *             $(PARAM_ROW CommandArgs.element, The menu that executed the callback.)
145 	 *             $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.)
146 	 *             $(PARAM_ROW CommandArgs.callback, The callback which was executed.)
147 	 *         )
148 	 *     )
149 	 *
150 	 * See_Also:
151 	 *     $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback)
152 	 */
153 	public auto addEntry(this T)(string label, CommandCallback callback, string shortCutText = null)
154 	{
155 		string command = this.createCommand(callback, label);
156 
157 		this._tk.eval("%s add command -label {%s} -command %s -accelerator {%s}", this.id, label, command, shortCutText);
158 
159 		return cast(T) this;
160 	}
161 
162 	/**
163 	 * Add an item to the menu with an image.
164 	 *
165 	 * Params:
166 	 *     image = The image of the entry.
167 	 *     label = The label of the item.
168 	 *     callback = The callback to execute when this item is selected in the menu.
169 	 *     shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event.
170 	 *     imagePosition = The position of the image in relation to the text.
171 	 *
172 	 * Returns:
173 	 *     This widget to aid method chaining.
174 	 *
175 	 * Callback_Arguments:
176 	 *     These are the fields within the callback's $(LINK2 
177 	 *     ../../element/element.html#CommandArgs, CommandArgs) parameter which 
178 	 *     are populated by this method when the callback is executed. 
179 	 *     $(P
180 	 *         $(PARAM_TABLE
181 	 *             $(PARAM_ROW CommandArgs.element, The menu that executed the callback.)
182 	 *             $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.)
183 	 *             $(PARAM_ROW CommandArgs.callback, The callback which was executed.)
184 	 *         )
185 	 *     )
186 	 *
187 	 * See_Also:
188 	 *     $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback)
189 	 *     $(LINK2 ../../image/image.html, tkd.image.image) $(BR)
190 	 *     $(LINK2 ../../image/png.html, tkd.image.png) $(BR)
191 	 *     $(LINK2 ../../image/gif.html, tkd.image.gif) $(BR)
192 	 *     $(LINK2 ../../image/imageposition.html, tkd.image.imageposition) $(BR)
193 	 */
194 	public auto addEntry(this T)(Image image, string label, CommandCallback callback, string imagePosition = ImagePosition.left, string shortCutText = null)
195 	{
196 		string command = this.createCommand(callback, label);
197 
198 		this._tk.eval("%s add command -image %s -label {%s} -command %s -compound %s -accelerator {%s}", this.id, image.id, label, command, imagePosition, shortCutText);
199 
200 		return cast(T) this;
201 	}
202 
203 	/**
204 	 * Add an item to the menu that when selected adds a checked icon.
205 	 *
206 	 * Params:
207 	 *     label = The label of the item.
208 	 *     callback = The callback to execute when this item is selected in the menu.
209 	 *     shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event.
210 	 *
211 	 * Returns:
212 	 *     This widget to aid method chaining.
213 	 *
214 	 * Callback_Arguments:
215 	 *     These are the fields within the callback's $(LINK2 
216 	 *     ../../element/element.html#CommandArgs, CommandArgs) parameter which 
217 	 *     are populated by this method when the callback is executed. 
218 	 *     $(P
219 	 *         $(PARAM_TABLE
220 	 *             $(PARAM_ROW CommandArgs.element, The menu that executed the callback.)
221 	 *             $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.)
222 	 *             $(PARAM_ROW CommandArgs.callback, The callback which was executed.)
223 	 *         )
224 	 *     )
225 	 *
226 	 * See_Also:
227 	 *     $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback)
228 	 */
229 	public auto addCheckButtonEntry(this T)(string label, CommandCallback callback, string shortCutText = null)
230 	{
231 		this._checkButtonVariables ~= format("variable-%s", this.generateHash(label));
232 		string command = this.createCommand(callback, label);
233 
234 		this._tk.eval("%s add checkbutton -label {%s} -command %s -accelerator {%s} -variable %s", this.id, label, command, shortCutText, this._checkButtonVariables.back());
235 
236 		return cast(T) this;
237 	}
238 
239 	/**
240 	 * Add an item to the menu with an image that when selected adds a checked icon.
241 	 *
242 	 * Params:
243 	 *     image = The image of the entry.
244 	 *     label = The label of the item.
245 	 *     callback = The callback to execute when this item is selected in the menu.
246 	 *     shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event.
247 	 *     imagePosition = The position of the image in relation to the text.
248 	 *
249 	 * Returns:
250 	 *     This widget to aid method chaining.
251 	 *
252 	 * Callback_Arguments:
253 	 *     These are the fields within the callback's $(LINK2 
254 	 *     ../../element/element.html#CommandArgs, CommandArgs) parameter which 
255 	 *     are populated by this method when the callback is executed. 
256 	 *     $(P
257 	 *         $(PARAM_TABLE
258 	 *             $(PARAM_ROW CommandArgs.element, The menu that executed the callback.)
259 	 *             $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.)
260 	 *             $(PARAM_ROW CommandArgs.callback, The callback which was executed.)
261 	 *         )
262 	 *     )
263 	 *
264 	 * See_Also:
265 	 *     $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback)
266 	 *     $(LINK2 ../../image/image.html, tkd.image.image) $(BR)
267 	 *     $(LINK2 ../../image/png.html, tkd.image.png) $(BR)
268 	 *     $(LINK2 ../../image/gif.html, tkd.image.gif) $(BR)
269 	 *     $(LINK2 ../../image/imageposition.html, tkd.image.imageposition) $(BR)
270 	 */
271 	public auto addCheckButtonEntry(this T)(Image image, string label, CommandCallback callback, string imagePosition = ImagePosition.left, string shortCutText = null)
272 	{
273 		this._checkButtonVariables ~= format("variable-%s", this.generateHash(label));
274 		string command = this.createCommand(callback, label);
275 
276 		this._tk.eval("%s add checkbutton -image %s -label {%s} -command %s -compound %s -accelerator {%s} -variable %s", this.id, image.id, label, command, imagePosition, shortCutText, this._checkButtonVariables.back());
277 
278 		return cast(T) this;
279 	}
280 
281 	/**
282 	 * Get if the check box entry at the passed index is checked or not. The 
283 	 * index only applies to check box entries in the menu not any other type 
284 	 * of entry. If there are no check box entries in the menu this method 
285 	 * returns false.
286 	 *
287 	 * Params:
288 	 *     index = The index of the check box entry.
289 	 *
290 	 * Returns:
291 	 *     True if the check box entry is selected, false if not.
292 	 */
293 	public bool isCheckBoxEntrySelected(int index)
294 	{
295 		if (index < this._checkButtonVariables.length)
296 		{
297 			return this._tk.getVariable(this._checkButtonVariables[index]).to!(int) == 1;
298 		}
299 		return false;
300 	}
301 
302 	/**
303 	 * Add an item to the menu that acts as a radio button.
304 	 *
305 	 * There can only be one group of radio button entries in one menu. If more 
306 	 * than one group is needed use cascading menus to hold each group.
307 	 *
308 	 * Params:
309 	 *     label = The label of the item.
310 	 *     callback = The callback to execute when this item is selected in the menu.
311 	 *     shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event.
312 	 *
313 	 * Returns:
314 	 *     This widget to aid method chaining.
315 	 *
316 	 * Callback_Arguments:
317 	 *     These are the fields within the callback's $(LINK2 
318 	 *     ../../element/element.html#CommandArgs, CommandArgs) parameter which 
319 	 *     are populated by this method when the callback is executed. 
320 	 *     $(P
321 	 *         $(PARAM_TABLE
322 	 *             $(PARAM_ROW CommandArgs.element, The menu that executed the callback.)
323 	 *             $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.)
324 	 *             $(PARAM_ROW CommandArgs.callback, The callback which was executed.)
325 	 *         )
326 	 *     )
327 	 *
328 	 * See_Also:
329 	 *     $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback)
330 	 */
331 	public auto addRadioButtonEntry(this T)(string label, CommandCallback callback, string shortCutText = null)
332 	{
333 		string command = this.createCommand(callback, label);
334 
335 		this._tk.eval("%s add radiobutton -label {%s} -command %s -accelerator {%s} -variable %s", this.id, label, command, shortCutText, this._radioButtonVariable);
336 
337 		return cast(T) this;
338 	}
339 
340 	/**
341 	 * Add an item to the menu with an image that acts as a radio button.
342 	 *
343 	 * There can only be one group of radio button entries in one menu. If more 
344 	 * than one group is needed use cascading menus to hold each group.
345 	 *
346 	 * Params:
347 	 *     image = The image of the entry.
348 	 *     label = The label of the item.
349 	 *     callback = The callback to execute when this item is selected in the menu.
350 	 *     shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event.
351 	 *     imagePosition = The position of the image in relation to the text.
352 	 *
353 	 * Returns:
354 	 *     This widget to aid method chaining.
355 	 *
356 	 * Callback_Arguments:
357 	 *     These are the fields within the callback's $(LINK2 
358 	 *     ../../element/element.html#CommandArgs, CommandArgs) parameter which 
359 	 *     are populated by this method when the callback is executed. 
360 	 *     $(P
361 	 *         $(PARAM_TABLE
362 	 *             $(PARAM_ROW CommandArgs.element, The menu that executed the callback.)
363 	 *             $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.)
364 	 *             $(PARAM_ROW CommandArgs.callback, The callback which was executed.)
365 	 *         )
366 	 *     )
367 	 *
368 	 * See_Also:
369 	 *     $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback)
370 	 *     $(LINK2 ../../image/image.html, tkd.image.image) $(BR)
371 	 *     $(LINK2 ../../image/png.html, tkd.image.png) $(BR)
372 	 *     $(LINK2 ../../image/gif.html, tkd.image.gif) $(BR)
373 	 *     $(LINK2 ../../image/imageposition.html, tkd.image.imageposition) $(BR)
374 	 */
375 	public auto addRadioButtonEntry(this T)(Image image, string label, CommandCallback callback, string imagePosition = ImagePosition.left, string shortCutText = null)
376 	{
377 		string command = this.createCommand(callback, label);
378 
379 		this._tk.eval("%s add radiobutton -image %s -label {%s} -command %s -compound %s -accelerator {%s} -variable %s", this.id, image.id, label, command, imagePosition, shortCutText, this._radioButtonVariable);
380 
381 		return cast(T) this;
382 	}
383 
384 	/**
385 	 * Get the value of the selected radio button entry. This value will be the 
386 	 * same as the entry's label. This method will return an empty string if no 
387 	 * radio button entry exists or none are selected.
388 	 *
389 	 * Returns:
390 	 *     The value of the selected radio button entry.
391 	 */
392 	public string getSelectedRadioEntryValue()
393 	{
394 		return this._tk.getVariable(this._radioButtonVariable).to!(string);
395 	}
396 
397 	/**
398 	 * Add a separator to the menu.
399 	 *
400 	 * Returns:
401 	 *     This widget to aid method chaining.
402 	 */
403 	public auto addSeparator(this T)()
404 	{
405 		this._tk.eval("%s add separator", this.id);
406 
407 		return cast(T) this;
408 	}
409 
410 	/**
411 	 * Disable a menu item. The item indexes start at zero for the top-most 
412 	 * entry and increase as you go down. Index refers to all menu items 
413 	 * including separators.
414 	 *
415 	 * Params:
416 	 *     index = The index of the item to disable.
417 	 *
418 	 * Returns:
419 	 *     This widget to aid method chaining.
420 	 */
421 	public auto disableEntry(this T)(int index)
422 	{
423 		this._tk.eval("%s entryconfigure %s -state disable", this.id, index);
424 		
425 		return cast(T) this;
426 	}
427 
428 	/**
429 	 * Enable a menu item. The item indexes start at zero for the top-most 
430 	 * entry and increase as you go down. Index refers to all menu items 
431 	 * including separators.
432 	 *
433 	 * Params:
434 	 *     index = The index of the item to enable.
435 	 *
436 	 * Returns:
437 	 *     This widget to aid method chaining.
438 	 */
439 	public auto enableEntry(this T)(int index)
440 	{
441 		this._tk.eval("%s entryconfigure %s -state normal", this.id, index);
442 		
443 		return cast(T) this;
444 	}
445 
446 	/**
447 	 * Invoke a menu item by its index. The item indexes start at zero for the 
448 	 * top-most entry and increase as you go down. Index refers to all menu 
449 	 * items including separators.
450 	 *
451 	 * Params:
452 	 *     index = The index of the check box entry.
453 	 *
454 	 * Returns:
455 	 *     This widget to aid method chaining.
456 	 */
457 	public auto invoke(this T)(int index)
458 	{
459 		this._tk.eval("%s invoke %s", this.id, index);
460 
461 		return cast(T) this;
462 	}
463 
464 	/**
465 	 * Show the menu.
466 	 *
467 	 * Params:
468 	 *     xPos = The horizontal position of the menu on the screen.
469 	 *     yPos = The vertical position of the menu on the screen.
470 	 *
471 	 * Returns:
472 	 *     This widget to aid method chaining.
473 	 */
474 	public auto popup(this T)(int xPos, int yPos)
475 	{
476 		this._tk.eval("tk_popup %s %s %s", this.id, xPos, yPos);
477 
478 		return cast(T) this;
479 	}
480 }