1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module org.eclipse.swt.widgets.MenuItem;
14 
15 
16 import org.eclipse.swt.widgets.Menu;
17 import org.eclipse.swt.widgets.Item;
18 import org.eclipse.swt.widgets.Decorations;
19 import org.eclipse.swt.graphics.Rectangle;
20 import org.eclipse.swt.graphics.Image;
21 import org.eclipse.swt.widgets.ImageList;
22 import org.eclipse.swt.SWT;
23 import org.eclipse.swt.internal.gtk.OS;
24 import org.eclipse.swt.events.ArmListener;
25 import org.eclipse.swt.events.HelpListener;
26 import org.eclipse.swt.events.SelectionListener;
27 import org.eclipse.swt.events.SelectionEvent;
28 import org.eclipse.swt.widgets.TypedListener;
29 import org.eclipse.swt.widgets.Event;
30 import org.eclipse.swt.widgets.Display;
31 
32 import java.lang.all;
33 
34 /**
35  * Instances of this class represent a selectable user interface object
36  * that issues notification when pressed and released.
37  * <dl>
38  * <dt><b>Styles:</b></dt>
39  * <dd>CHECK, CASCADE, PUSH, RADIO, SEPARATOR</dd>
40  * <dt><b>Events:</b></dt>
41  * <dd>Arm, Help, Selection</dd>
42  * </dl>
43  * <p>
44  * Note: Only one of the styles CHECK, CASCADE, PUSH, RADIO and SEPARATOR
45  * may be specified.
46  * </p><p>
47  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
48  * </p>
49  *
50  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
51  */
52 public class MenuItem : Item {
53     Menu parent, menu;
54     GtkAccelGroup* groupHandle;
55     int accelerator;
56 
57 /**
58  * Constructs a new instance of this class given its parent
59  * (which must be a <code>Menu</code>) and a style value
60  * describing its behavior and appearance. The item is added
61  * to the end of the items maintained by its parent.
62  * <p>
63  * The style value is either one of the style constants defined in
64  * class <code>SWT</code> which is applicable to instances of this
65  * class, or must be built by <em>bitwise OR</em>'ing together
66  * (that is, using the <code>int</code> "|" operator) two or more
67  * of those <code>SWT</code> style constants. The class description
68  * lists the style constants that are applicable to the class.
69  * Style bits are also inherited from superclasses.
70  * </p>
71  *
72  * @param parent a menu control which will be the parent of the new instance (cannot be null)
73  * @param style the style of control to construct
74  *
75  * @exception IllegalArgumentException <ul>
76  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
77  * </ul>
78  * @exception SWTException <ul>
79  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
80  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
81  * </ul>
82  *
83  * @see SWT#CHECK
84  * @see SWT#CASCADE
85  * @see SWT#PUSH
86  * @see SWT#RADIO
87  * @see SWT#SEPARATOR
88  * @see Widget#checkSubclass
89  * @see Widget#getStyle
90  */
91 public this (Menu parent, int style) {
92     super (parent, checkStyle (style));
93     this.parent = parent;
94     createWidget (parent.getItemCount ());
95 }
96 
97 /**
98  * Constructs a new instance of this class given its parent
99  * (which must be a <code>Menu</code>), a style value
100  * describing its behavior and appearance, and the index
101  * at which to place it in the items maintained by its parent.
102  * <p>
103  * The style value is either one of the style constants defined in
104  * class <code>SWT</code> which is applicable to instances of this
105  * class, or must be built by <em>bitwise OR</em>'ing together
106  * (that is, using the <code>int</code> "|" operator) two or more
107  * of those <code>SWT</code> style constants. The class description
108  * lists the style constants that are applicable to the class.
109  * Style bits are also inherited from superclasses.
110  * </p>
111  *
112  * @param parent a menu control which will be the parent of the new instance (cannot be null)
113  * @param style the style of control to construct
114  * @param index the zero-relative index to store the receiver in its parent
115  *
116  * @exception IllegalArgumentException <ul>
117  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
118  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the parent (inclusive)</li>
119  * </ul>
120  * @exception SWTException <ul>
121  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
122  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
123  * </ul>
124  *
125  * @see SWT#CHECK
126  * @see SWT#CASCADE
127  * @see SWT#PUSH
128  * @see SWT#RADIO
129  * @see SWT#SEPARATOR
130  * @see Widget#checkSubclass
131  * @see Widget#getStyle
132  */
133 public this (Menu parent, int style, int index) {
134     super (parent, checkStyle (style));
135     this.parent = parent;
136     int count = parent.getItemCount ();
137     if (!(0 <= index && index <= count)) {
138         error (SWT.ERROR_INVALID_RANGE);
139     }
140     createWidget (index);
141 }
142 
143 void addAccelerator (GtkAccelGroup* accelGroup) {
144     updateAccelerator (accelGroup, true);
145 }
146 
147 void addAccelerators (GtkAccelGroup* accelGroup) {
148     addAccelerator (accelGroup);
149     if (menu !is null) menu.addAccelerators (accelGroup);
150 }
151 
152 /**
153  * Adds the listener to the collection of listeners who will
154  * be notified when the arm events are generated for the control, by sending
155  * it one of the messages defined in the <code>ArmListener</code>
156  * interface.
157  *
158  * @param listener the listener which should be notified
159  *
160  * @exception IllegalArgumentException <ul>
161  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
162  * </ul>
163  * @exception SWTException <ul>
164  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
165  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
166  * </ul>
167  *
168  * @see ArmListener
169  * @see #removeArmListener
170  */
171 public void addArmListener (ArmListener listener) {
172     checkWidget();
173     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
174     TypedListener typedListener = new TypedListener (listener);
175     addListener (SWT.Arm, typedListener);
176 }
177 
178 /**
179  * Adds the listener to the collection of listeners who will
180  * be notified when the help events are generated for the control, by sending
181  * it one of the messages defined in the <code>HelpListener</code>
182  * interface.
183  *
184  * @param listener the listener which should be notified
185  *
186  * @exception IllegalArgumentException <ul>
187  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
188  * </ul>
189  * @exception SWTException <ul>
190  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
191  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
192  * </ul>
193  *
194  * @see HelpListener
195  * @see #removeHelpListener
196  */
197 public void addHelpListener (HelpListener listener) {
198     checkWidget();
199     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
200     TypedListener typedListener = new TypedListener (listener);
201     addListener (SWT.Help, typedListener);
202 }
203 
204 /**
205  * Adds the listener to the collection of listeners who will
206  * be notified when the menu item is selected by the user, by sending
207  * it one of the messages defined in the <code>SelectionListener</code>
208  * interface.
209  * <p>
210  * When <code>widgetSelected</code> is called, the stateMask field of the event object is valid.
211  * <code>widgetDefaultSelected</code> is not called.
212  * </p>
213  *
214  * @param listener the listener which should be notified when the menu item is selected by the user
215  *
216  * @exception IllegalArgumentException <ul>
217  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
218  * </ul>
219  * @exception SWTException <ul>
220  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
221  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
222  * </ul>
223  *
224  * @see SelectionListener
225  * @see #removeSelectionListener
226  * @see SelectionEvent
227  */
228 public void addSelectionListener (SelectionListener listener) {
229     checkWidget();
230     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
231     TypedListener typedListener = new TypedListener(listener);
232     addListener (SWT.Selection,typedListener);
233     addListener (SWT.DefaultSelection,typedListener);
234 }
235 
236 static int checkStyle (int style) {
237     return checkBits (style, SWT.PUSH, SWT.CHECK, SWT.RADIO, SWT.SEPARATOR, SWT.CASCADE, 0);
238 }
239 
240 protected override void checkSubclass () {
241     if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
242 }
243 
244 override void createHandle (int index) {
245     state |= HANDLE;
246     String buffer = "\0";
247     int bits = SWT.CHECK | SWT.RADIO | SWT.PUSH | SWT.SEPARATOR;
248     switch (style & bits) {
249         case SWT.SEPARATOR:
250             handle = OS.gtk_separator_menu_item_new ();
251             break;
252         case SWT.RADIO:
253             /*
254             * Feature in GTK.  In GTK, radio button must always be part of
255             * a radio button group.  In a GTK radio group, one button is always
256             * selected.  This means that it is not possible to have a single
257             * radio button that is unselected.  This is necessary to allow
258             * applications to implement their own radio behavior or use radio
259             * buttons outside of radio groups.  The fix is to create a hidden
260             * radio button for each radio button we create and add them
261             * to the same group.  This allows the visible button to be
262             * unselected.
263             */
264             groupHandle = cast(GtkAccelGroup*) OS.gtk_radio_menu_item_new (null);
265             if (groupHandle is null) error (SWT.ERROR_NO_HANDLES);
266             OS.g_object_ref (groupHandle);
267             OS.gtk_object_sink (cast(GtkObject*)groupHandle);
268             auto group = OS.gtk_radio_menu_item_get_group (cast(GtkRadioMenuItem*) groupHandle);
269             handle = OS.gtk_radio_menu_item_new_with_label (group, buffer.ptr);
270             break;
271         case SWT.CHECK:
272             handle = OS.gtk_check_menu_item_new_with_label (buffer.ptr);
273             break;
274         case SWT.PUSH:
275         default:
276             handle = OS.gtk_image_menu_item_new_with_label (buffer.ptr);
277             break;
278     }
279     if (handle is null) error (SWT.ERROR_NO_HANDLES);
280     if ((style & SWT.SEPARATOR) is 0) {
281         auto label = OS.gtk_bin_get_child (cast(GtkBin*)handle);
282         OS.gtk_accel_label_set_accel_widget (cast(GtkAccelLabel*)label, null);
283     }
284     auto parentHandle = parent.handle;
285     bool enabled = OS.GTK_WIDGET_SENSITIVE (parentHandle);
286     if (!enabled) OS.GTK_WIDGET_SET_FLAGS (parentHandle, OS.GTK_SENSITIVE);
287     OS.gtk_menu_shell_insert (cast(GtkMenuShell*)parentHandle, handle, index);
288     if (!enabled) OS.GTK_WIDGET_UNSET_FLAGS (parentHandle, OS.GTK_SENSITIVE);
289     OS.gtk_widget_show (handle);
290 }
291 
292 void fixMenus (Decorations newParent) {
293     if (menu !is null) menu.fixMenus (newParent);
294 }
295 
296 /**
297  * Returns the widget accelerator.  An accelerator is the bit-wise
298  * OR of zero or more modifier masks and a key. Examples:
299  * <code>SWT.CONTROL | SWT.SHIFT | 'T', SWT.ALT | SWT.F2</code>.
300  * The default value is zero, indicating that the menu item does
301  * not have an accelerator.
302  *
303  * @return the accelerator or 0
304  *
305  * </ul>
306  * @exception SWTException <ul>
307  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
308  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
309  * </ul>
310  */
311 public int getAccelerator () {
312     checkWidget();
313     return accelerator;
314 }
315 
316 GtkAccelGroup* getAccelGroup () {
317     Menu menu = parent;
318     while (menu !is null && menu.cascade !is null) {
319         menu = menu.cascade.parent;
320     }
321     if (menu is null) return null;
322     Decorations shell = menu.parent;
323     return shell.menuBar is menu ? shell.accelGroup : null;
324 }
325 
326 /*public*/ Rectangle getBounds () {
327     checkWidget();
328     if (!OS.GTK_WIDGET_MAPPED (handle)) {
329         return new Rectangle (0, 0, 0, 0);
330     }
331     int x = OS.GTK_WIDGET_X (handle);
332     int y = OS.GTK_WIDGET_Y (handle);
333     int width = OS.GTK_WIDGET_WIDTH (handle);
334     int height = OS.GTK_WIDGET_HEIGHT (handle);
335     return new Rectangle (x, y, width, height);
336 }
337 
338 /**
339  * Returns <code>true</code> if the receiver is enabled, and
340  * <code>false</code> otherwise. A disabled menu item is typically
341  * not selectable from the user interface and draws with an
342  * inactive or "grayed" look.
343  *
344  * @return the receiver's enabled state
345  *
346  * @exception SWTException <ul>
347  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
348  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
349  * </ul>
350  *
351  * @see #isEnabled
352  */
353 public bool getEnabled () {
354     checkWidget();
355     return OS.GTK_WIDGET_SENSITIVE (handle);
356 }
357 
358 /**
359  * Returns the receiver's cascade menu if it has one or null
360  * if it does not. Only <code>CASCADE</code> menu items can have
361  * a pull down menu. The sequence of key strokes, button presses
362  * and/or button releases that are used to request a pull down
363  * menu is platform specific.
364  *
365  * @return the receiver's menu
366  *
367  * @exception SWTException <ul>
368  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
369  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
370  * </ul>
371  */
372 public Menu getMenu () {
373     checkWidget();
374     return menu;
375 }
376 
377 override
378 String getNameText () {
379     if ((style & SWT.SEPARATOR) !is 0) return "|";
380     return super.getNameText ();
381 }
382 
383 /**
384  * Returns the receiver's parent, which must be a <code>Menu</code>.
385  *
386  * @return the receiver's parent
387  *
388  * @exception SWTException <ul>
389  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
390  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
391  * </ul>
392  */
393 public Menu getParent () {
394     checkWidget();
395     return parent;
396 }
397 
398 /**
399  * Returns <code>true</code> if the receiver is selected,
400  * and false otherwise.
401  * <p>
402  * When the receiver is of type <code>CHECK</code> or <code>RADIO</code>,
403  * it is selected when it is checked.
404  *
405  * @return the selection state
406  *
407  * @exception SWTException <ul>
408  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
409  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
410  * </ul>
411  */
412 public bool getSelection () {
413     checkWidget();
414     if ((style & (SWT.CHECK | SWT.RADIO)) is 0) return false;
415     return cast(bool)OS.gtk_check_menu_item_get_active(cast(GtkCheckMenuItem*)handle);
416 }
417 
418 override int gtk_activate (GtkWidget* widget) {
419     if ((style & SWT.CASCADE) !is 0 && menu !is null) return 0;
420     /*
421     * Bug in GTK.  When an ancestor menu is disabled and
422     * the user types an accelerator key, GTK delivers the
423     * the activate signal even though the menu item cannot
424     * be invoked using the mouse.  The fix is to ignore
425     * activate signals when an ancestor menu is disabled.
426     */
427     if (!isEnabled ()) return 0;
428     Event event = new Event ();
429     auto ptr = OS.gtk_get_current_event ();
430     if (ptr !is null) {
431         GdkEvent* gdkEvent = ptr;
432         switch (gdkEvent.type) {
433             case OS.GDK_KEY_PRESS:
434             case OS.GDK_KEY_RELEASE:
435             case OS.GDK_BUTTON_PRESS:
436             case OS.GDK_2BUTTON_PRESS:
437             case OS.GDK_BUTTON_RELEASE: {
438                 int state;
439                 OS.gdk_event_get_state (ptr, &state);
440                 setInputState (event, state);
441                 break;
442             }
443             default:
444         }
445         OS.gdk_event_free (ptr);
446     }
447     if ((style & SWT.RADIO) !is 0) {
448         if ((parent.getStyle () & SWT.NO_RADIO_GROUP) is 0) {
449             selectRadio ();
450         }
451     }
452     postEvent (SWT.Selection, event);
453     return 0;
454 }
455 
456 override int gtk_select (int item) {
457     parent.selectedItem = this;
458     sendEvent (SWT.Arm);
459     return 0;
460 }
461 
462 override int gtk_show_help (GtkWidget* widget, ptrdiff_t helpType) {
463     bool handled = hooks (SWT.Help);
464     if (handled) {
465         postEvent (SWT.Help);
466     } else {
467         handled = parent.sendHelpEvent (helpType);
468     }
469     if (handled) {
470         OS.gtk_menu_shell_deactivate (cast(GtkMenuShell*)parent.handle);
471         return 1;
472     }
473     return 0;
474 }
475 
476 override void hookEvents () {
477     super.hookEvents ();
478     OS.g_signal_connect_closure (handle, OS.activate.ptr, display.closures [ACTIVATE], false);
479     OS.g_signal_connect_closure (handle, OS.select.ptr, display.closures [SELECT], false);
480     OS.g_signal_connect_closure_by_id (handle, display.signalIds [SHOW_HELP], 0, display.closures [SHOW_HELP], false);
481 }
482 
483 /**
484  * Returns <code>true</code> if the receiver is enabled and all
485  * of the receiver's ancestors are enabled, and <code>false</code>
486  * otherwise. A disabled menu item is typically not selectable from the
487  * user interface and draws with an inactive or "grayed" look.
488  *
489  * @return the receiver's enabled state
490  *
491  * @exception SWTException <ul>
492  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
493  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
494  * </ul>
495  *
496  * @see #getEnabled
497  */
498 public bool isEnabled () {
499     return getEnabled () && parent.isEnabled ();
500 }
501 
502 override void releaseChildren (bool destroy) {
503     if (menu !is null) {
504         menu.release (false);
505         menu = null;
506     }
507     super.releaseChildren (destroy);
508 }
509 
510 override void releaseParent () {
511     super.releaseParent ();
512     if (menu !is null) {
513         if (menu.selectedItem is this) menu.selectedItem = null;
514         menu.dispose ();
515     }
516     menu = null;
517 }
518 
519 override void releaseWidget () {
520     super.releaseWidget ();
521     auto accelGroup = getAccelGroup ();
522     if (accelGroup !is null) removeAccelerator (accelGroup);
523     if (groupHandle !is null) OS.g_object_unref (groupHandle);
524     groupHandle = null;
525     accelerator = 0;
526     parent = null;
527 }
528 
529 void removeAccelerator (GtkAccelGroup* accelGroup) {
530     updateAccelerator (accelGroup, false);
531 }
532 
533 void removeAccelerators (GtkAccelGroup* accelGroup) {
534     removeAccelerator (accelGroup);
535     if (menu !is null) menu.removeAccelerators (accelGroup);
536 }
537 
538 /**
539  * Removes the listener from the collection of listeners who will
540  * be notified when the arm events are generated for the control.
541  *
542  * @param listener the listener which should no longer be notified
543  *
544  * @exception IllegalArgumentException <ul>
545  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
546  * </ul>
547  * @exception SWTException <ul>
548  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
549  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
550  * </ul>
551  *
552  * @see ArmListener
553  * @see #addArmListener
554  */
555 public void removeArmListener (ArmListener listener) {
556     checkWidget();
557     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
558     if (eventTable is null) return;
559     eventTable.unhook (SWT.Arm, listener);
560 }
561 
562 /**
563  * Removes the listener from the collection of listeners who will
564  * be notified when the help events are generated for the control.
565  *
566  * @param listener the listener which should no longer be notified
567  *
568  * @exception IllegalArgumentException <ul>
569  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
570  * </ul>
571  * @exception SWTException <ul>
572  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
573  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
574  * </ul>
575  *
576  * @see HelpListener
577  * @see #addHelpListener
578  */
579 public void removeHelpListener (HelpListener listener) {
580     checkWidget();
581     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
582     if (eventTable is null) return;
583     eventTable.unhook (SWT.Help, listener);
584 }
585 
586 /**
587  * Removes the listener from the collection of listeners who will
588  * be notified when the control is selected by the user.
589  *
590  * @param listener the listener which should no longer be notified
591  *
592  * @exception IllegalArgumentException <ul>
593  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
594  * </ul>
595  * @exception SWTException <ul>
596  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
597  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
598  * </ul>
599  *
600  * @see SelectionListener
601  * @see #addSelectionListener
602  */
603 public void removeSelectionListener (SelectionListener listener) {
604     checkWidget();
605     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
606     if (eventTable is null) return;
607     eventTable.unhook (SWT.Selection, listener);
608     eventTable.unhook (SWT.DefaultSelection,listener);
609 }
610 void selectRadio () {
611     int index = 0;
612     MenuItem [] items = parent.getItems ();
613     while (index < items.length && items [index] !is this) index++;
614     int i = index - 1;
615     while (i >= 0 && items [i].setRadioSelection (false)) --i;
616     int j = index + 1;
617     while (j < items.length && items [j].setRadioSelection (false)) j++;
618     setSelection (true);
619 }
620 /**
621  * Sets the widget accelerator.  An accelerator is the bit-wise
622  * OR of zero or more modifier masks and a key. Examples:
623  * <code>SWT.MOD1 | SWT.MOD2 | 'T', SWT.MOD3 | SWT.F2</code>.
624  * <code>SWT.CONTROL | SWT.SHIFT | 'T', SWT.ALT | SWT.F2</code>.
625  * The default value is zero, indicating that the menu item does
626  * not have an accelerator.
627  *
628  * @param accelerator an integer that is the bit-wise OR of masks and a key
629  *
630  * </ul>
631  * @exception SWTException <ul>
632  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
633  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
634  * </ul>
635  */
636 public void setAccelerator (int accelerator) {
637     checkWidget();
638     if (this.accelerator is accelerator) return;
639     auto accelGroup = getAccelGroup ();
640     if (accelGroup !is null) removeAccelerator (accelGroup);
641     this.accelerator = accelerator;
642     if (accelGroup !is null) addAccelerator (accelGroup);
643 }
644 
645 /**
646  * Enables the receiver if the argument is <code>true</code>,
647  * and disables it otherwise. A disabled menu item is typically
648  * not selectable from the user interface and draws with an
649  * inactive or "grayed" look.
650  *
651  * @param enabled the new enabled state
652  *
653  * @exception SWTException <ul>
654  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
655  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
656  * </ul>
657  */
658 public void setEnabled (bool enabled) {
659     checkWidget();
660     if (OS.GTK_WIDGET_SENSITIVE (handle) is enabled) return;
661     auto accelGroup = getAccelGroup ();
662     if (accelGroup !is null) removeAccelerator (accelGroup);
663     OS.gtk_widget_set_sensitive (handle, enabled);
664     if (accelGroup !is null) addAccelerator (accelGroup);
665 }
666 
667 /**
668  * Sets the image the receiver will display to the argument.
669  * <p>
670  * Note: This operation is a hint and is not supported on
671  * platforms that do not have this concept (for example, Windows NT).
672  * </p>
673  *
674  * @param image the image to display
675  *
676  * @exception SWTException <ul>
677  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
678  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
679  * </ul>
680  */
681 public override void setImage (Image image) {
682     checkWidget();
683     if ((style & SWT.SEPARATOR) !is 0) return;
684     super.setImage (image);
685     if (!OS.GTK_IS_IMAGE_MENU_ITEM (cast(GTypeInstance*)handle)) return;
686     if (image !is null) {
687         ImageList imageList = parent.imageList;
688         if (imageList is null) imageList = parent.imageList = new ImageList ();
689         int imageIndex = imageList.indexOf (image);
690         if (imageIndex is -1) {
691             imageIndex = imageList.add (image);
692         } else {
693             imageList.put (imageIndex, image);
694         }
695         auto pixbuf = imageList.getPixbuf (imageIndex);
696         auto imageHandle = OS.gtk_image_new_from_pixbuf (pixbuf);
697         OS.gtk_image_menu_item_set_image (cast(GtkImageMenuItem*)handle, imageHandle);
698         OS.gtk_widget_show (imageHandle);
699     } else {
700         OS.gtk_image_menu_item_set_image (cast(GtkImageMenuItem*)handle, null);
701     }
702 }
703 
704 /**
705  * Sets the receiver's pull down menu to the argument.
706  * Only <code>CASCADE</code> menu items can have a
707  * pull down menu. The sequence of key strokes, button presses
708  * and/or button releases that are used to request a pull down
709  * menu is platform specific.
710  * <p>
711  * Note: Disposing of a menu item that has a pull down menu
712  * will dispose of the menu.  To avoid this behavior, set the
713  * menu to null before the menu item is disposed.
714  * </p>
715  *
716  * @param menu the new pull down menu
717  *
718  * @exception IllegalArgumentException <ul>
719  *    <li>ERROR_MENU_NOT_DROP_DOWN - if the menu is not a drop down menu</li>
720  *    <li>ERROR_MENUITEM_NOT_CASCADE - if the menu item is not a <code>CASCADE</code></li>
721  *    <li>ERROR_INVALID_ARGUMENT - if the menu has been disposed</li>
722  *    <li>ERROR_INVALID_PARENT - if the menu is not in the same widget tree</li>
723  * </ul>
724  * @exception SWTException <ul>
725  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
726  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
727  * </ul>
728  */
729 public void setMenu (Menu menu) {
730     checkWidget ();
731 
732     /* Check to make sure the new menu is valid */
733     if ((style & SWT.CASCADE) is 0) {
734         error (SWT.ERROR_MENUITEM_NOT_CASCADE);
735     }
736     if (menu !is null) {
737         if ((menu.style & SWT.DROP_DOWN) is 0) {
738             error (SWT.ERROR_MENU_NOT_DROP_DOWN);
739         }
740         if (menu.parent !is parent.parent) {
741             error (SWT.ERROR_INVALID_PARENT);
742         }
743     }
744 
745     /* Assign the new menu */
746     Menu oldMenu = this.menu;
747     if (oldMenu is menu) return;
748     auto accelGroup = getAccelGroup ();
749     if (accelGroup !is null) removeAccelerators (accelGroup);
750     if (oldMenu !is null) {
751         oldMenu.cascade = null;
752         /*
753         * Add a reference to the menu we are about
754         * to replace or GTK will destroy it.
755         */
756         OS.g_object_ref (oldMenu.handle);
757         OS.gtk_menu_item_remove_submenu (cast(GtkMenuItem*)handle);
758     }
759     if ((this.menu = menu) !is null) {
760         menu.cascade = this;
761         OS.gtk_menu_item_set_submenu (cast(GtkMenuItem*)handle, menu.handle);
762     }
763     if (accelGroup !is null) addAccelerators (accelGroup);
764 }
765 
766 override void setOrientation() {
767     if ((parent.style & SWT.RIGHT_TO_LEFT) !is 0) {
768         if (handle !is null) {
769             OS.gtk_widget_set_direction (handle, OS.GTK_TEXT_DIR_RTL);
770             OS.gtk_container_forall (cast(GtkContainer*)handle, cast(GtkCallback)&Display.setDirectionProcFunc, cast(void*)OS.GTK_TEXT_DIR_RTL);
771         }
772     }
773 }
774 
775 bool setRadioSelection (bool value) {
776     if ((style & SWT.RADIO) is 0) return false;
777     if (getSelection () !is value) {
778         setSelection (value);
779         postEvent (SWT.Selection);
780     }
781     return true;
782 }
783 
784 /**
785  * Sets the selection state of the receiver.
786  * <p>
787  * When the receiver is of type <code>CHECK</code> or <code>RADIO</code>,
788  * it is selected when it is checked.
789  *
790  * @param selected the new selection state
791  *
792  * @exception SWTException <ul>
793  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
794  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
795  * </ul>
796  */
797 public void setSelection (bool selected) {
798     checkWidget();
799     if ((style & (SWT.CHECK | SWT.RADIO)) is 0) return;
800     OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udACTIVATE);
801     OS.gtk_check_menu_item_set_active (cast(GtkCheckMenuItem*)handle, selected);
802     if ((style & SWT.RADIO) !is 0) OS.gtk_check_menu_item_set_active (cast(GtkCheckMenuItem*)groupHandle, !selected);
803     OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, null, null, udACTIVATE);
804 }
805 
806 /**
807  * Sets the receiver's text. The string may include
808  * the mnemonic character and accelerator text.
809  * <p>
810  * Mnemonics are indicated by an '&amp;' that causes the next
811  * character to be the mnemonic.  When the user presses a
812  * key sequence that matches the mnemonic, a selection
813  * event occurs. On most platforms, the mnemonic appears
814  * underlined but may be emphasised in a platform specific
815  * manner.  The mnemonic indicator character '&amp;' can be
816  * escaped by doubling it in the string, causing a single
817  * '&amp;' to be displayed.
818  * </p>
819  * <p>
820  * Accelerator text is indicated by the '\t' character.
821  * On platforms that support accelerator text, the text
822  * that follows the '\t' character is displayed to the user,
823  * typically indicating the key stroke that will cause
824  * the item to become selected.  On most platforms, the
825  * accelerator text appears right aligned in the menu.
826  * Setting the accelerator text does not install the
827  * accelerator key sequence. The accelerator key sequence
828  * is installed using #setAccelerator.
829  * </p>
830  *
831  * @param string the new text
832  *
833  * @exception SWTException <ul>
834  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
835  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
836  * </ul>
837  *
838  * @see #setAccelerator
839  */
840 public override void setText (String string) {
841     checkWidget();
842     // SWT extension: allow null for zero length string
843     //if (string is null) error (SWT.ERROR_NULL_ARGUMENT);
844     if ((style & SWT.SEPARATOR) !is 0) return;
845     if (text.equals(string)) return;
846     super.setText (string);
847     String accelString = "";
848     int index = string.indexOf ('\t');
849     if (index !is -1) {
850         bool isRTL = (parent.style & SWT.RIGHT_TO_LEFT) !is 0;
851         accelString = (isRTL? "" : "  ") ~ string.substring (index+1, cast(int)/*64bit*/string.length) ~ (isRTL? "  " : "");
852         string = string.substring (0, index);
853     }
854     char [] chars = fixMnemonic (string);
855     auto label = OS.gtk_bin_get_child (cast(GtkBin*)handle);
856     OS.gtk_label_set_text_with_mnemonic (cast(GtkLabel*)label, chars.toStringzValidPtr() );
857 
858     auto ptr = cast(char*)OS.g_malloc (accelString.length + 1);
859     ptr[ 0 .. accelString.length ] = accelString;
860     ptr[ accelString.length ] = '\0';
861 
862     auto oldPtr = OS.GTK_ACCEL_LABEL_GET_ACCEL_STRING (cast(GtkAccelLabel*)label);
863     OS.GTK_ACCEL_LABEL_SET_ACCEL_STRING (cast(GtkAccelLabel*)label, ptr);
864     if (oldPtr !is null) OS.g_free (oldPtr);
865 }
866 
867 void updateAccelerator (GtkAccelGroup* accelGroup, bool add) {
868     if (accelerator is 0 || !getEnabled ()) return;
869     if ((accelerator & SWT.COMMAND) !is 0) return;
870     int mask = 0;
871     if ((accelerator & SWT.ALT) !is 0) mask |= OS.GDK_MOD1_MASK;
872     if ((accelerator & SWT.SHIFT) !is 0) mask |= OS.GDK_SHIFT_MASK;
873     if ((accelerator & SWT.CONTROL) !is 0) mask |= OS.GDK_CONTROL_MASK;
874     int keysym = accelerator & SWT.KEY_MASK;
875     int newKey = Display.untranslateKey (keysym);
876     if (newKey !is 0) {
877         keysym = newKey;
878     } else {
879         switch (keysym) {
880             case '\r': keysym = OS.GDK_Return; break;
881             default: keysym = Display.wcsToMbcs (cast(char) keysym);
882         }
883     }
884     /* When accel_key is zero, it causes GTK warnings */
885     if (keysym !is 0) {
886         if (add) {
887             OS.gtk_widget_add_accelerator (handle, OS.activate.ptr, accelGroup, keysym, mask, OS.GTK_ACCEL_VISIBLE);
888         } else {
889             OS.gtk_widget_remove_accelerator (handle, accelGroup, keysym, mask);
890         }
891     }
892 }
893 }