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 103 // String concatenation is used to build the script here instead of 104 // using format specifiers to enable supporting input which includes 105 // Tcl/Tk reserved characters and elements that could be construed as 106 // format specifiers. 107 string script = std.conv.text(parent.id, ` add cascade -menu `, this.id, ` -label "`, this._tk.escape(label), `" -underline `, underlineChar); 108 this._tk.eval(script); 109 } 110 111 /** 112 * Add a cascade menu to this menu. 113 * 114 * Params: 115 * label = The label of the menu. 116 * menu = The menu to add as a cascade menu. 117 * underlineChar = The index of the character to underline. 118 * 119 * Returns: 120 * This widget to aid method chaining. 121 */ 122 public auto addMenuEntry(this T)(string label, Menu menu, ubyte underlineChar = ubyte.max) 123 { 124 string originalId = menu.id; 125 menu._parent = this; 126 127 this._tk.eval("%s clone %s", originalId, menu.id); 128 129 // String concatenation is used to build the script here instead of 130 // using format specifiers to enable supporting input which includes 131 // Tcl/Tk reserved characters and elements that could be construed as 132 // format specifiers. 133 string script = std.conv.text(this.id, ` add cascade -menu `, menu.id, ` -label "`, this._tk.escape(label), `" -underline `, underlineChar); 134 this._tk.eval(script); 135 136 return cast(T) this; 137 } 138 139 /** 140 * Add an item to the menu. 141 * 142 * Params: 143 * label = The label of the item. 144 * callback = The callback to execute when this item is selected in the menu. 145 * shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event. 146 * 147 * Returns: 148 * This widget to aid method chaining. 149 * 150 * Callback_Arguments: 151 * These are the fields within the callback's $(LINK2 152 * ../../element/element.html#CommandArgs, CommandArgs) parameter which 153 * are populated by this method when the callback is executed. 154 * $(P 155 * $(PARAM_TABLE 156 * $(PARAM_ROW CommandArgs.element, The menu that executed the callback.) 157 * $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.) 158 * $(PARAM_ROW CommandArgs.callback, The callback which was executed.) 159 * ) 160 * ) 161 * 162 * See_Also: 163 * $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback) 164 */ 165 public auto addEntry(this T)(string label, CommandCallback callback, string shortCutText = null) 166 { 167 string command = this.createCommand(callback, label); 168 169 // String concatenation is used to build the script here instead of 170 // using format specifiers to enable supporting input which includes 171 // Tcl/Tk reserved characters and elements that could be construed as 172 // format specifiers. 173 string script = std.conv.text(this.id, ` add command -label "`, this._tk.escape(label), `" -command `, command, ` -accelerator "`, this._tk.escape(shortCutText), `"`); 174 this._tk.eval(script); 175 176 return cast(T) this; 177 } 178 179 /** 180 * Add an item to the menu with an image. 181 * 182 * Params: 183 * image = The image of the entry. 184 * label = The label of the item. 185 * callback = The callback to execute when this item is selected in the menu. 186 * shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event. 187 * imagePosition = The position of the image in relation to the text. 188 * 189 * Returns: 190 * This widget to aid method chaining. 191 * 192 * Callback_Arguments: 193 * These are the fields within the callback's $(LINK2 194 * ../../element/element.html#CommandArgs, CommandArgs) parameter which 195 * are populated by this method when the callback is executed. 196 * $(P 197 * $(PARAM_TABLE 198 * $(PARAM_ROW CommandArgs.element, The menu that executed the callback.) 199 * $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.) 200 * $(PARAM_ROW CommandArgs.callback, The callback which was executed.) 201 * ) 202 * ) 203 * 204 * See_Also: 205 * $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback) 206 * $(LINK2 ../../image/image.html, tkd.image.image) $(BR) 207 * $(LINK2 ../../image/png.html, tkd.image.png) $(BR) 208 * $(LINK2 ../../image/gif.html, tkd.image.gif) $(BR) 209 * $(LINK2 ../../image/imageposition.html, tkd.image.imageposition) $(BR) 210 */ 211 public auto addEntry(this T)(Image image, string label, CommandCallback callback, string imagePosition = ImagePosition.left, string shortCutText = null) 212 { 213 string command = this.createCommand(callback, label); 214 215 // String concatenation is used to build the script here instead of 216 // using format specifiers to enable supporting input which includes 217 // Tcl/Tk reserved characters and elements that could be construed as 218 // format specifiers. 219 string script = std.conv.text(this.id, ` add command -image `, image.id, ` -label "`, this._tk.escape(label), `" -command `, command, ` -compound `, imagePosition, ` -accelerator "`, this._tk.escape(shortCutText), `"`); 220 this._tk.eval(script); 221 222 return cast(T) this; 223 } 224 225 /** 226 * Add an item to the menu that when selected adds a checked icon. 227 * 228 * Params: 229 * label = The label of the item. 230 * callback = The callback to execute when this item is selected in the menu. 231 * shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event. 232 * 233 * Returns: 234 * This widget to aid method chaining. 235 * 236 * Callback_Arguments: 237 * These are the fields within the callback's $(LINK2 238 * ../../element/element.html#CommandArgs, CommandArgs) parameter which 239 * are populated by this method when the callback is executed. 240 * $(P 241 * $(PARAM_TABLE 242 * $(PARAM_ROW CommandArgs.element, The menu that executed the callback.) 243 * $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.) 244 * $(PARAM_ROW CommandArgs.callback, The callback which was executed.) 245 * ) 246 * ) 247 * 248 * See_Also: 249 * $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback) 250 */ 251 public auto addCheckButtonEntry(this T)(string label, CommandCallback callback, string shortCutText = null) 252 { 253 this._checkButtonVariables ~= format("variable-%s", this.generateHash(label)); 254 string command = this.createCommand(callback, label); 255 256 // String concatenation is used to build the script here instead of 257 // using format specifiers to enable supporting input which includes 258 // Tcl/Tk reserved characters and elements that could be construed as 259 // format specifiers. 260 string script = std.conv.text(this.id, ` add checkbutton -label "`, this._tk.escape(label), `" -command `, command, ` -accelerator "`, this._tk.escape(shortCutText), `" -variable `, this._checkButtonVariables.back()); 261 this._tk.eval(script); 262 263 return cast(T) this; 264 } 265 266 /** 267 * Add an item to the menu with an image that when selected adds a checked icon. 268 * 269 * Params: 270 * image = The image of the entry. 271 * label = The label of the item. 272 * callback = The callback to execute when this item is selected in the menu. 273 * shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event. 274 * imagePosition = The position of the image in relation to the text. 275 * 276 * Returns: 277 * This widget to aid method chaining. 278 * 279 * Callback_Arguments: 280 * These are the fields within the callback's $(LINK2 281 * ../../element/element.html#CommandArgs, CommandArgs) parameter which 282 * are populated by this method when the callback is executed. 283 * $(P 284 * $(PARAM_TABLE 285 * $(PARAM_ROW CommandArgs.element, The menu that executed the callback.) 286 * $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.) 287 * $(PARAM_ROW CommandArgs.callback, The callback which was executed.) 288 * ) 289 * ) 290 * 291 * See_Also: 292 * $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback) 293 * $(LINK2 ../../image/image.html, tkd.image.image) $(BR) 294 * $(LINK2 ../../image/png.html, tkd.image.png) $(BR) 295 * $(LINK2 ../../image/gif.html, tkd.image.gif) $(BR) 296 * $(LINK2 ../../image/imageposition.html, tkd.image.imageposition) $(BR) 297 */ 298 public auto addCheckButtonEntry(this T)(Image image, string label, CommandCallback callback, string imagePosition = ImagePosition.left, string shortCutText = null) 299 { 300 this._checkButtonVariables ~= format("variable-%s", this.generateHash(label)); 301 string command = this.createCommand(callback, label); 302 303 // String concatenation is used to build the script here instead of 304 // using format specifiers to enable supporting input which includes 305 // Tcl/Tk reserved characters and elements that could be construed as 306 // format specifiers. 307 string script = std.conv.text(this.id, ` add checkbutton -image `, image.id, ` -label "`, this._tk.escape(label), `" -command `, command, ` -compound `, imagePosition, ` -accelerator "`, this._tk.escape(shortCutText), `" -variable `, this._checkButtonVariables.back()); 308 this._tk.eval(script); 309 310 return cast(T) this; 311 } 312 313 /** 314 * Get if the check box entry at the passed index is checked or not. The 315 * index only applies to check box entries in the menu not any other type 316 * of entry. If there are no check box entries in the menu this method 317 * returns false. 318 * 319 * Params: 320 * index = The index of the check box entry. 321 * 322 * Returns: 323 * True if the check box entry is selected, false if not. 324 */ 325 public bool isCheckBoxEntrySelected(int index) 326 { 327 if (index < this._checkButtonVariables.length) 328 { 329 return this._tk.getVariable(this._checkButtonVariables[index]).to!(int) == 1; 330 } 331 return false; 332 } 333 334 /** 335 * Add an item to the menu that acts as a radio button. 336 * 337 * There can only be one group of radio button entries in one menu. If more 338 * than one group is needed use cascading menus to hold each group. 339 * 340 * Params: 341 * label = The label of the item. 342 * callback = The callback to execute when this item is selected in the menu. 343 * shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event. 344 * 345 * Returns: 346 * This widget to aid method chaining. 347 * 348 * Callback_Arguments: 349 * These are the fields within the callback's $(LINK2 350 * ../../element/element.html#CommandArgs, CommandArgs) parameter which 351 * are populated by this method when the callback is executed. 352 * $(P 353 * $(PARAM_TABLE 354 * $(PARAM_ROW CommandArgs.element, The menu that executed the callback.) 355 * $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.) 356 * $(PARAM_ROW CommandArgs.callback, The callback which was executed.) 357 * ) 358 * ) 359 * 360 * See_Also: 361 * $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback) 362 */ 363 public auto addRadioButtonEntry(this T)(string label, CommandCallback callback, string shortCutText = null) 364 { 365 string command = this.createCommand(callback, label); 366 367 // String concatenation is used to build the script here instead of 368 // using format specifiers to enable supporting input which includes 369 // Tcl/Tk reserved characters and elements that could be construed as 370 // format specifiers. 371 string script = std.conv.text(this.id, ` add radiobutton -label "`, this._tk.escape(label), `" -command `, command, ` -accelerator "`, this._tk.escape(shortCutText), `" -variable `, this._radioButtonVariable); 372 this._tk.eval(script); 373 374 return cast(T) this; 375 } 376 377 /** 378 * Add an item to the menu with an image that acts as a radio button. 379 * 380 * There can only be one group of radio button entries in one menu. If more 381 * than one group is needed use cascading menus to hold each group. 382 * 383 * Params: 384 * image = The image of the entry. 385 * label = The label of the item. 386 * callback = The callback to execute when this item is selected in the menu. 387 * shortCutText = The keyboard shortcut text. This is for decoration only, you must also bind this keypress to an event. 388 * imagePosition = The position of the image in relation to the text. 389 * 390 * Returns: 391 * This widget to aid method chaining. 392 * 393 * Callback_Arguments: 394 * These are the fields within the callback's $(LINK2 395 * ../../element/element.html#CommandArgs, CommandArgs) parameter which 396 * are populated by this method when the callback is executed. 397 * $(P 398 * $(PARAM_TABLE 399 * $(PARAM_ROW CommandArgs.element, The menu that executed the callback.) 400 * $(PARAM_ROW CommandArgs.uniqueData, The label of the menu entry which was selected.) 401 * $(PARAM_ROW CommandArgs.callback, The callback which was executed.) 402 * ) 403 * ) 404 * 405 * See_Also: 406 * $(LINK2 ../../element/element.html#CommandCallback, tkd.element.element.CommandCallback) 407 * $(LINK2 ../../image/image.html, tkd.image.image) $(BR) 408 * $(LINK2 ../../image/png.html, tkd.image.png) $(BR) 409 * $(LINK2 ../../image/gif.html, tkd.image.gif) $(BR) 410 * $(LINK2 ../../image/imageposition.html, tkd.image.imageposition) $(BR) 411 */ 412 public auto addRadioButtonEntry(this T)(Image image, string label, CommandCallback callback, string imagePosition = ImagePosition.left, string shortCutText = null) 413 { 414 string command = this.createCommand(callback, label); 415 416 // String concatenation is used to build the script here instead of 417 // using format specifiers to enable supporting input which includes 418 // Tcl/Tk reserved characters and elements that could be construed as 419 // format specifiers. 420 string script = std.conv.text(this.id, ` add radiobutton -image `, image.id, ` -label "`, this._tk.escape(label), `" -command `, command, ` -compound `, imagePosition, ` -accelerator "`, this._tk.escape(shortCutText), `" -variable `, this._radioButtonVariable); 421 this._tk.eval(script); 422 423 return cast(T) this; 424 } 425 426 /** 427 * Get the value of the selected radio button entry. This value will be the 428 * same as the entry's label. This method will return an empty string if no 429 * radio button entry exists or none are selected. 430 * 431 * Returns: 432 * The value of the selected radio button entry. 433 */ 434 public string getSelectedRadioEntryValue() 435 { 436 return this._tk.getVariable(this._radioButtonVariable).to!(string); 437 } 438 439 /** 440 * Add a separator to the menu. 441 * 442 * Returns: 443 * This widget to aid method chaining. 444 */ 445 public auto addSeparator(this T)() 446 { 447 this._tk.eval("%s add separator", this.id); 448 449 return cast(T) this; 450 } 451 452 /** 453 * Disable a menu item. The item indexes start at zero for the top-most 454 * entry and increase as you go down. Index refers to all menu items 455 * including separators. 456 * 457 * Params: 458 * index = The index of the item to disable. 459 * 460 * Returns: 461 * This widget to aid method chaining. 462 */ 463 public auto disableEntry(this T)(int index) 464 { 465 this._tk.eval("%s entryconfigure %s -state disable", this.id, index); 466 467 return cast(T) this; 468 } 469 470 /** 471 * Enable a menu item. The item indexes start at zero for the top-most 472 * entry and increase as you go down. Index refers to all menu items 473 * including separators. 474 * 475 * Params: 476 * index = The index of the item to enable. 477 * 478 * Returns: 479 * This widget to aid method chaining. 480 */ 481 public auto enableEntry(this T)(int index) 482 { 483 this._tk.eval("%s entryconfigure %s -state normal", this.id, index); 484 485 return cast(T) this; 486 } 487 488 /** 489 * Invoke a menu item by its index. The item indexes start at zero for the 490 * top-most entry and increase as you go down. Index refers to all menu 491 * items including separators. 492 * 493 * Params: 494 * index = The index of the check box entry. 495 * 496 * Returns: 497 * This widget to aid method chaining. 498 */ 499 public auto invoke(this T)(int index) 500 { 501 this._tk.eval("%s invoke %s", this.id, index); 502 503 return cast(T) this; 504 } 505 506 /** 507 * Show the menu. 508 * 509 * Params: 510 * xPos = The horizontal position of the menu on the screen. 511 * yPos = The vertical position of the menu on the screen. 512 * 513 * Returns: 514 * This widget to aid method chaining. 515 */ 516 public auto popup(this T)(int xPos, int yPos) 517 { 518 this._tk.eval("tk_popup %s %s %s", this.id, xPos, yPos); 519 520 return cast(T) this; 521 } 522 }