mirror of https://github.com/buggins/dlangui.git
801 lines
24 KiB
D
801 lines
24 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
This module contains menu widgets implementation.
|
|
|
|
MenuItem - menu item properties container - to hold hierarchy of menu.
|
|
MainMenu - main menu widget
|
|
PopupMenu - popup menu widget
|
|
|
|
Synopsis:
|
|
|
|
----
|
|
import dlangui.widgets.popup;
|
|
|
|
----
|
|
|
|
Copyright: Vadim Lopatin, 2014
|
|
License: Boost License 1.0
|
|
Authors: Vadim Lopatin, coolreader.org@gmail.com
|
|
*/
|
|
module dlangui.widgets.menu;
|
|
|
|
import dlangui.core.events;
|
|
import dlangui.widgets.controls;
|
|
import dlangui.widgets.layouts;
|
|
import dlangui.widgets.lists;
|
|
import dlangui.widgets.popup;
|
|
|
|
/// menu item type
|
|
enum MenuItemType {
|
|
/// normal menu item
|
|
Normal,
|
|
/// menu item - checkbox
|
|
Check,
|
|
/// menu item - radio button
|
|
Radio,
|
|
/// menu separator (horizontal line)
|
|
Separator,
|
|
/// submenu - contains child items
|
|
Submenu
|
|
}
|
|
|
|
/// interface to handle menu item click
|
|
interface MenuItemClickHandler {
|
|
bool onMenuItemClick(MenuItem item);
|
|
}
|
|
|
|
/// interface to handle menu item action
|
|
interface MenuItemActionHandler {
|
|
bool onMenuItemAction(const Action action);
|
|
}
|
|
|
|
|
|
/// menu item properties
|
|
class MenuItem {
|
|
protected bool _checked;
|
|
protected bool _enabled;
|
|
protected MenuItemType _type = MenuItemType.Normal;
|
|
protected Action _action;
|
|
protected MenuItem[] _subitems;
|
|
protected MenuItem _parent;
|
|
/// handle menu item click (parameter is MenuItem)
|
|
Signal!MenuItemClickHandler onMenuItemClick;
|
|
/// handle menu item click action (parameter is Action)
|
|
Signal!MenuItemActionHandler onMenuItemAction;
|
|
/// item action id, 0 if no action
|
|
@property int id() { return _action is null ? 0 : _action.id; }
|
|
/// returns count of submenu items
|
|
@property int subitemCount() {
|
|
return cast(int)_subitems.length;
|
|
}
|
|
/// returns subitem index for item, -1 if item is not direct subitem of this
|
|
@property int subitemIndex(MenuItem item) {
|
|
for (int i = 0; i < _subitems.length; i++)
|
|
if (_subitems[i] is item)
|
|
return i;
|
|
return -1;
|
|
}
|
|
/// returns submenu item by index
|
|
MenuItem subitem(int index) {
|
|
return _subitems[index];
|
|
}
|
|
|
|
@property MenuItemType type() const {
|
|
if (_subitems.length > 0) // if there are children, force type to Submenu
|
|
return MenuItemType.Submenu;
|
|
return _type;
|
|
}
|
|
|
|
/// set new MenuItemType
|
|
@property MenuItem type(MenuItemType type) {
|
|
_type = type;
|
|
return this;
|
|
}
|
|
|
|
/// get check for checkbox or radio button item
|
|
@property bool checked() {
|
|
return _checked;
|
|
}
|
|
/// check radio button with specified index, uncheck other radio buttons in group (group consists of sequence of radio button items; other item type - end of group)
|
|
protected void checkRadioButton(int index) {
|
|
// find bounds of group
|
|
int start = index;
|
|
int end = index;
|
|
for (; start > 0 && _subitems[start - 1].type == MenuItemType.Radio; start--) {
|
|
// do nothing
|
|
}
|
|
for (; end < _subitems.length - 1 && _subitems[end + 1].type == MenuItemType.Radio; end++) {
|
|
// do nothing
|
|
}
|
|
// check item with specified index, uncheck others
|
|
for (int i = start; i <= end; i++)
|
|
_subitems[i]._checked = (i == index);
|
|
}
|
|
/// set check for checkbox or radio button item
|
|
@property MenuItem checked(bool flg) {
|
|
if (_checked == flg)
|
|
return this;
|
|
_checked = flg;
|
|
if (flg && _parent && type == MenuItemType.Radio) {
|
|
int index = _parent.subitemIndex(this);
|
|
if (index >= 0) {
|
|
_parent.checkRadioButton(index);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/// get hotkey character from label (e.g. 'F' for item labeled "&File"), 0 if no hotkey
|
|
dchar getHotkey() {
|
|
dstring s = label;
|
|
dchar ch = 0;
|
|
for (int i = 0; i < s.length - 1; i++) {
|
|
if (s[i] == '&') {
|
|
ch = s[i + 1];
|
|
break;
|
|
}
|
|
}
|
|
return dcharToUpper(ch);
|
|
}
|
|
|
|
/// find subitem by hotkey character, returns subitem index, -1 if not found
|
|
int findSubitemByHotkey(dchar ch) {
|
|
if (!ch)
|
|
return -1;
|
|
ch = dcharToUpper(ch);
|
|
for (int i = 0; i < _subitems.length; i++) {
|
|
if (_subitems[i].getHotkey() == ch)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// adds submenu item
|
|
MenuItem add(MenuItem subitem) {
|
|
_subitems ~= subitem;
|
|
subitem._parent = this;
|
|
return this;
|
|
}
|
|
/// adds submenu item from action
|
|
MenuItem add(Action subitemAction) {
|
|
return add(new MenuItem(subitemAction));
|
|
}
|
|
/// returns text description for first accelerator of action; null if no accelerators
|
|
@property dstring acceleratorText() {
|
|
if (!_action)
|
|
return null;
|
|
return _action.acceleratorText;
|
|
}
|
|
/// returns true if item is submenu (contains subitems)
|
|
@property bool isSubmenu() {
|
|
return _subitems.length > 0;
|
|
}
|
|
/// returns item label
|
|
@property UIString label() {
|
|
return _action.labelValue;
|
|
}
|
|
/// returns item action
|
|
@property const(Action) action() const { return _action; }
|
|
/// sets item action
|
|
@property MenuItem action(Action a) { _action = a; return this; }
|
|
|
|
/// menu item Enabled flag
|
|
@property bool enabled() { return _enabled && type != MenuItemType.Separator; }
|
|
/// menu item Enabled flag
|
|
@property MenuItem enabled(bool enabled) {
|
|
_enabled = enabled;
|
|
return this;
|
|
}
|
|
|
|
/// handle menu item click
|
|
Signal!(void, MenuItem) onMenuItem;
|
|
/// prepare for opening of submenu, return true if opening is allowed
|
|
Signal!(bool, MenuItem) onBeforeOpeningSubmenu;
|
|
|
|
this() {
|
|
_enabled = true;
|
|
}
|
|
this(Action action) {
|
|
_action = action;
|
|
_enabled = true;
|
|
}
|
|
~this() {
|
|
// TODO
|
|
}
|
|
}
|
|
|
|
/// widget to draw menu item
|
|
class MenuItemWidget : WidgetGroup {
|
|
protected bool _mainMenu;
|
|
protected MenuItem _item;
|
|
protected ImageWidget _icon;
|
|
protected TextWidget _accel;
|
|
protected TextWidget _label;
|
|
protected int _labelWidth;
|
|
protected int _iconWidth;
|
|
protected int _accelWidth;
|
|
protected int _height;
|
|
@property MenuItem item() { return _item; }
|
|
void setSubitemSizes(int maxLabelWidth, int maxHeight, int maxIconWidth, int maxAccelWidth) {
|
|
_labelWidth = maxLabelWidth;
|
|
_height = maxHeight;
|
|
_iconWidth = maxIconWidth;
|
|
_accelWidth = maxAccelWidth;
|
|
}
|
|
void measureSubitems(ref int maxLabelWidth, ref int maxHeight, ref int maxIconWidth, ref int maxAccelWidth) {
|
|
_label.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
|
|
if (maxLabelWidth < _label.measuredWidth)
|
|
maxLabelWidth = _label.measuredWidth;
|
|
if (maxHeight < _label.measuredHeight)
|
|
maxHeight = _label.measuredHeight;
|
|
if (_icon) {
|
|
_icon.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
|
|
if (maxIconWidth < _icon.measuredWidth)
|
|
maxIconWidth = _icon.measuredWidth;
|
|
if (maxHeight < _icon.measuredHeight)
|
|
maxHeight = _icon.measuredHeight;
|
|
}
|
|
if (_accel) {
|
|
_accel.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
|
|
if (maxAccelWidth < _accel.measuredWidth)
|
|
maxAccelWidth = _accel.measuredWidth;
|
|
if (maxHeight < _accel.measuredHeight)
|
|
maxHeight = _accel.measuredHeight;
|
|
}
|
|
}
|
|
/// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
|
|
override void measure(int parentWidth, int parentHeight) {
|
|
updateState();
|
|
Rect m = margins;
|
|
Rect p = padding;
|
|
// calc size constraints for children
|
|
int pwidth = parentWidth;
|
|
int pheight = parentHeight;
|
|
if (parentWidth != SIZE_UNSPECIFIED)
|
|
pwidth -= m.left + m.right + p.left + p.right;
|
|
if (parentHeight != SIZE_UNSPECIFIED)
|
|
pheight -= m.top + m.bottom + p.top + p.bottom;
|
|
if (_labelWidth)
|
|
measuredContent(parentWidth, parentHeight, _iconWidth + _labelWidth + _accelWidth, _height); // for vertical (popup menu)
|
|
else {
|
|
_label.measure(pwidth, pheight);
|
|
measuredContent(parentWidth, parentHeight, _label.measuredWidth, _label.measuredHeight); // for horizonral (main) menu
|
|
}
|
|
}
|
|
|
|
/// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout).
|
|
override void layout(Rect rc) {
|
|
_needLayout = false;
|
|
if (visibility == Visibility.Gone) {
|
|
return;
|
|
}
|
|
_pos = rc;
|
|
applyMargins(rc);
|
|
applyPadding(rc);
|
|
Rect labelRc = rc;
|
|
Rect iconRc = rc;
|
|
Rect accelRc = rc;
|
|
iconRc.right = iconRc.left + _iconWidth;
|
|
accelRc.left = accelRc.right - _accelWidth;
|
|
labelRc.left += _iconWidth;
|
|
labelRc.right -= _accelWidth;
|
|
if (_icon)
|
|
_icon.layout(iconRc);
|
|
if (_accel)
|
|
_accel.layout(accelRc);
|
|
_label.layout(labelRc);
|
|
}
|
|
|
|
protected void updateState() {
|
|
if (_item.enabled)
|
|
setState(State.Enabled);
|
|
else
|
|
resetState(State.Enabled);
|
|
if (_item.checked)
|
|
setState(State.Checked);
|
|
else
|
|
resetState(State.Checked);
|
|
}
|
|
|
|
/// Draw widget at its position to buffer
|
|
override void onDraw(DrawBuf buf) {
|
|
if (visibility != Visibility.Visible)
|
|
return;
|
|
super.onDraw(buf);
|
|
Rect rc = _pos;
|
|
applyMargins(rc);
|
|
applyPadding(rc);
|
|
updateState();
|
|
auto saver = ClipRectSaver(buf, rc, alpha);
|
|
for (int i = 0; i < _children.count; i++) {
|
|
Widget item = _children.get(i);
|
|
if (item.visibility != Visibility.Visible)
|
|
continue;
|
|
item.onDraw(buf);
|
|
}
|
|
}
|
|
|
|
this(MenuItem item, bool mainMenu) {
|
|
id="menuitem";
|
|
_mainMenu = mainMenu;
|
|
_item = item;
|
|
styleId = "MENU_ITEM";
|
|
updateState();
|
|
string iconId = _item.action.iconId;
|
|
if (_item.type == MenuItemType.Check)
|
|
iconId = "btn_check";
|
|
else if (_item.type == MenuItemType.Radio)
|
|
iconId = "btn_radio";
|
|
// icon
|
|
if (_item.action && iconId.length) {
|
|
_icon = new ImageWidget("MENU_ICON", iconId);
|
|
_icon.styleId = "MENU_ICON";
|
|
_icon.state = State.Parent;
|
|
addChild(_icon);
|
|
}
|
|
// label
|
|
_label = new TextWidget("MENU_LABEL");
|
|
_label.text = _item.label;
|
|
_label.styleId = _mainMenu ? "MAIN_MENU_LABEL" : "MENU_LABEL";
|
|
_label.state = State.Parent;
|
|
addChild(_label);
|
|
// accelerator
|
|
dstring acc = _item.acceleratorText;
|
|
if (_item.isSubmenu && !mainMenu)
|
|
acc = "‣"d;
|
|
if (acc !is null) {
|
|
_accel = new TextWidget("MENU_ACCEL");
|
|
_accel.styleId = "MENU_ACCEL";
|
|
_accel.text = acc;
|
|
_accel.state = State.Parent;
|
|
if (_item.isSubmenu && !mainMenu)
|
|
_accel.alignment = Align.Right | Align.VCenter;
|
|
addChild(_accel);
|
|
}
|
|
trackHover = true;
|
|
clickable = true;
|
|
}
|
|
}
|
|
|
|
/// base class for menus
|
|
class MenuWidgetBase : ListWidget {
|
|
protected MenuWidgetBase _parentMenu;
|
|
protected MenuItem _item;
|
|
protected PopupMenu _openedMenu;
|
|
protected PopupWidget _openedPopup;
|
|
protected int _openedPopupIndex;
|
|
|
|
/// menu item click listener
|
|
Signal!MenuItemClickHandler onMenuItemClickListener;
|
|
/// menu item action listener
|
|
Signal!MenuItemActionHandler onMenuItemActionListener;
|
|
|
|
this(MenuWidgetBase parentMenu, MenuItem item, Orientation orientation) {
|
|
_parentMenu = parentMenu;
|
|
_item = item;
|
|
this.orientation = orientation;
|
|
id = "popup_menu";
|
|
styleId = "POPUP_MENU";
|
|
WidgetListAdapter adapter = new WidgetListAdapter();
|
|
for (int i=0; i < _item.subitemCount; i++) {
|
|
MenuItem subitem = _item.subitem(i);
|
|
MenuItemWidget widget = new MenuItemWidget(subitem, orientation == Orientation.Horizontal);
|
|
if (orientation == Orientation.Horizontal)
|
|
widget.styleId = "MAIN_MENU_ITEM";
|
|
widget.parent = this;
|
|
adapter.widgets.add(widget);
|
|
}
|
|
ownAdapter = adapter;
|
|
}
|
|
|
|
@property protected bool isMainMenu() {
|
|
return _orientation == Orientation.Horizontal;
|
|
}
|
|
|
|
/// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
|
|
override void measure(int parentWidth, int parentHeight) {
|
|
if (_orientation == Orientation.Horizontal) {
|
|
// for horizontal (main) menu, don't align items
|
|
super.measure(parentWidth, parentHeight);
|
|
return;
|
|
}
|
|
|
|
if (visibility == Visibility.Gone) {
|
|
_measuredWidth = _measuredHeight = 0;
|
|
return;
|
|
}
|
|
int maxLabelWidth;
|
|
int maxHeight;
|
|
int maxIconWidth;
|
|
int maxAccelWidth;
|
|
/// find max dimensions for item icon and accelerator sizes
|
|
for (int i = 0; i < itemCount; i++) {
|
|
MenuItemWidget w = cast(MenuItemWidget)itemWidget(i);
|
|
if (w)
|
|
w.measureSubitems(maxLabelWidth, maxHeight, maxIconWidth, maxAccelWidth);
|
|
}
|
|
/// set equal dimensions for item icon and accelerator sizes
|
|
for (int i = 0; i < itemCount; i++) {
|
|
MenuItemWidget w = cast(MenuItemWidget)itemWidget(i);
|
|
if (w)
|
|
w.setSubitemSizes(maxLabelWidth, maxHeight, maxIconWidth, maxAccelWidth);
|
|
}
|
|
super.measure(parentWidth, parentHeight);
|
|
}
|
|
|
|
protected void performUndoSelection() {
|
|
selectItem(-1);
|
|
setHoverItem(-1);
|
|
}
|
|
|
|
protected void onPopupClosed(PopupWidget p) {
|
|
if (_openedPopup) {
|
|
if (_openedPopup is p) {
|
|
_openedMenu.onPopupClosed(p);
|
|
//bool undoSelection = _openedPopupIndex == _selectedItemIndex;
|
|
_openedPopup = null;
|
|
_openedMenu = null;
|
|
//if (undoSelection) {
|
|
// performUndoSelection();
|
|
//}
|
|
if (!isMainMenu)
|
|
window.setFocus(this);
|
|
} else if (thisPopup is p) {
|
|
_openedPopup.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void openSubmenu(int index, MenuItemWidget itemWidget, bool selectFirstItem) {
|
|
if (_openedPopup !is null) {
|
|
if (_openedPopupIndex == index) {
|
|
if (selectFirstItem) {
|
|
window.setFocus(_openedMenu);
|
|
_openedMenu.selectItem(0);
|
|
}
|
|
return;
|
|
} else {
|
|
_openedPopup.close();
|
|
}
|
|
}
|
|
PopupMenu popupMenu = new PopupMenu(itemWidget.item, this);
|
|
PopupWidget popup = window.showPopup(popupMenu, itemWidget, orientation == Orientation.Horizontal ? PopupAlign.Below : PopupAlign.Right);
|
|
popup.onPopupCloseListener = &onPopupClosed;
|
|
popup.flags = PopupFlags.CloseOnClickOutside;
|
|
_openedPopup = popup;
|
|
_openedMenu = popupMenu;
|
|
_openedPopupIndex = index;
|
|
if (selectFirstItem) {
|
|
window.setFocus(popupMenu);
|
|
_openedMenu.selectItem(0);
|
|
}
|
|
}
|
|
|
|
/// override to handle change of selection
|
|
override protected void selectionChanged(int index, int previouslySelectedItem = -1) {
|
|
debug Log.d("menu.selectionChanged ", index, ", ", previouslySelectedItem, " _selectedItemIndex=", _selectedItemIndex);
|
|
if (index >= 0)
|
|
setFocus();
|
|
MenuItemWidget itemWidget = index >= 0 ? cast(MenuItemWidget)_adapter.itemWidget(index) : null;
|
|
MenuItemWidget prevWidget = previouslySelectedItem >= 0 ? cast(MenuItemWidget)_adapter.itemWidget(previouslySelectedItem) : null;
|
|
if (prevWidget !is null) {
|
|
if (_openedPopup !is null)
|
|
_openedPopup.close();
|
|
}
|
|
if (itemWidget !is null) {
|
|
if (itemWidget.item.isSubmenu()) {
|
|
if (_selectOnHover) {
|
|
openSubmenu(index, itemWidget, _orientation == Orientation.Horizontal); // for main menu, select first item
|
|
}
|
|
} else {
|
|
// normal item
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void handleMenuItemClick(MenuItem item) {
|
|
// precessing for CheckBox and RadioButton menus
|
|
if (item.type == MenuItemType.Check) {
|
|
item.checked = !item.checked;
|
|
} else if (item.type == MenuItemType.Radio) {
|
|
item.checked = true;
|
|
}
|
|
if (item.onMenuItemClick.assigned)
|
|
item.onMenuItemClick(item);
|
|
if (item.onMenuItemAction.assigned && item.action)
|
|
item.onMenuItemAction(item.action);
|
|
}
|
|
|
|
protected void onMenuItem(MenuItem item) {
|
|
debug Log.d("onMenuItem ", item.action.label);
|
|
if (_openedPopup !is null) {
|
|
_openedPopup.close();
|
|
_openedPopup = null;
|
|
}
|
|
if (_parentMenu !is null)
|
|
_parentMenu.onMenuItem(item);
|
|
else {
|
|
// top level handling
|
|
Log.d("onMenuItem ", item.id);
|
|
selectItem(-1);
|
|
setHoverItem(-1);
|
|
selectOnHover = false;
|
|
|
|
// copy menu item click listeners
|
|
Signal!MenuItemClickHandler onMenuItemClickListenerCopy = onMenuItemClickListener;
|
|
// copy item action listeners
|
|
Signal!MenuItemActionHandler onMenuItemActionListenerCopy = onMenuItemActionListener;
|
|
|
|
PopupWidget popup = cast(PopupWidget)parent;
|
|
if (popup)
|
|
popup.close();
|
|
|
|
handleMenuItemClick(item);
|
|
|
|
|
|
// this pointer now can be invalid - if popup removed
|
|
if (onMenuItemClickListenerCopy.assigned)
|
|
if (onMenuItemClickListenerCopy(item))
|
|
return;
|
|
// this pointer now can be invalid - if popup removed
|
|
if (onMenuItemActionListenerCopy.assigned)
|
|
onMenuItemActionListenerCopy(item.action);
|
|
}
|
|
}
|
|
|
|
@property MenuItemWidget selectedMenuItemWidget() {
|
|
return _selectedItemIndex >= 0 ? cast(MenuItemWidget)_adapter.itemWidget(_selectedItemIndex) : null;
|
|
}
|
|
|
|
/// override to handle mouse up on item
|
|
override protected void itemClicked(int index) {
|
|
MenuItemWidget itemWidget = index >= 0 ? cast(MenuItemWidget)_adapter.itemWidget(index) : null;
|
|
if (itemWidget !is null) {
|
|
Log.d("Menu Item clicked ", itemWidget.item.action.id);
|
|
if (itemWidget.item.isSubmenu()) {
|
|
// submenu clicked
|
|
if (_clickOnButtonDown && _openedPopup !is null && _openedMenu._item is itemWidget.item) {
|
|
// second click on main menu opened item
|
|
_openedPopup.close();
|
|
_openedPopup = null;
|
|
//selectItem(-1);
|
|
selectOnHover = false;
|
|
} else {
|
|
openSubmenu(index, itemWidget, _orientation == Orientation.Horizontal); // for main menu, select first item
|
|
selectOnHover = true;
|
|
}
|
|
} else {
|
|
// normal item
|
|
onMenuItem(itemWidget.item);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// returns popup this menu is located in
|
|
@property PopupWidget thisPopup() {
|
|
return cast(PopupWidget)parent;
|
|
}
|
|
|
|
protected int _menuToggleState;
|
|
protected Widget _menuTogglePreviousFocus;
|
|
|
|
/// list navigation using keys
|
|
override bool onKeyEvent(KeyEvent event) {
|
|
if (orientation == Orientation.Horizontal) {
|
|
// no special processing
|
|
} else {
|
|
// for vertical (popup) menu
|
|
if (!focused)
|
|
return false;
|
|
if (event.action == KeyAction.KeyDown) {
|
|
if (event.keyCode == KeyCode.LEFT) {
|
|
if (_parentMenu !is null) {
|
|
if (_parentMenu.orientation == Orientation.Vertical) {
|
|
if (thisPopup !is null) {
|
|
//int selectedItem = _selectedItemIndex;
|
|
// back to parent menu on Left key
|
|
thisPopup.close();
|
|
//if (selectedItem >= 0)
|
|
// selectItem(selectedItem);
|
|
return true;
|
|
}
|
|
} else {
|
|
// parent is main menu
|
|
_parentMenu.moveSelection(-1);
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
} else if (event.keyCode == KeyCode.RIGHT) {
|
|
MenuItemWidget thisItem = selectedMenuItemWidget();
|
|
if (thisItem !is null && thisItem.item.isSubmenu) {
|
|
openSubmenu(_selectedItemIndex, thisItem, true);
|
|
return true;
|
|
} else if (_parentMenu !is null && _parentMenu.orientation == Orientation.Horizontal) {
|
|
_parentMenu.moveSelection(1);
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
} else if (event.action == KeyAction.KeyUp) {
|
|
if (event.keyCode == KeyCode.LEFT || event.keyCode == KeyCode.RIGHT) {
|
|
return true;
|
|
}
|
|
} else if (event.action == KeyAction.Text && event.flags == 0) {
|
|
dchar ch = event.text[0];
|
|
int index = _item.findSubitemByHotkey(ch);
|
|
if (index >= 0) {
|
|
itemClicked(index);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (_selectedItemIndex >= 0 && event.action == KeyAction.KeyDown && event.flags == 0 && (event.keyCode == KeyCode.RETURN || event.keyCode == KeyCode.SPACE)) {
|
|
itemClicked(_selectedItemIndex);
|
|
return true;
|
|
}
|
|
return super.onKeyEvent(event);
|
|
}
|
|
}
|
|
|
|
/// main menu (horizontal)
|
|
class MainMenu : MenuWidgetBase {
|
|
|
|
this(MenuItem item) {
|
|
super(null, item, Orientation.Horizontal);
|
|
id = "MAIN_MENU";
|
|
styleId = "MAIN_MENU";
|
|
_clickOnButtonDown = true;
|
|
}
|
|
|
|
/// override and return true to track key events even when not focused
|
|
@property override bool wantsKeyTracking() {
|
|
return true;
|
|
}
|
|
|
|
/// get text flags (bit set of TextFlag enum values)
|
|
@property override uint textFlags() {
|
|
// override text flags for main menu
|
|
if (_selectedItemIndex >= 0)
|
|
return TextFlag.UnderlineHotKeys | TextFlag.HotKeys;
|
|
else
|
|
return TextFlag.UnderlineHotKeysWhenAltPressed | TextFlag.HotKeys;
|
|
}
|
|
|
|
protected int _menuToggleState;
|
|
protected Widget _menuTogglePreviousFocus;
|
|
|
|
override protected void onMenuItem(MenuItem item) {
|
|
debug Log.d("MainMenu.onMenuItem ", item.action.label);
|
|
|
|
// copy menu item click listeners
|
|
Signal!MenuItemClickHandler onMenuItemClickListenerCopy = onMenuItemClickListener;
|
|
// copy item action listeners
|
|
Signal!MenuItemActionHandler onMenuItemActionListenerCopy = onMenuItemActionListener;
|
|
|
|
deactivate();
|
|
|
|
handleMenuItemClick(item);
|
|
|
|
// this pointer now can be invalid - if popup removed
|
|
if (onMenuItemClickListenerCopy.assigned)
|
|
if (onMenuItemClickListenerCopy(item))
|
|
return;
|
|
// this pointer now can be invalid - if popup removed
|
|
if (onMenuItemActionListenerCopy.assigned)
|
|
onMenuItemActionListenerCopy(item.action);
|
|
}
|
|
|
|
/// return true if main menu is activated (focused or has open submenu)
|
|
@property bool activated() {
|
|
return focused || _selectedItemIndex >= 0 || _openedPopup !is null;
|
|
}
|
|
|
|
override protected void performUndoSelection() {
|
|
deactivate();
|
|
}
|
|
|
|
/// bring focus to main menu, if not yet activated
|
|
void activate() {
|
|
debug Log.d("activating main menu");
|
|
if (activated)
|
|
return;
|
|
window.setFocus(this);
|
|
selectItem(0);
|
|
}
|
|
|
|
/// close and remove focus, if activated
|
|
void deactivate(bool force = false) {
|
|
debug Log.d("deactivating main menu");
|
|
if (!activated && !force)
|
|
return;
|
|
if (_openedPopup !is null)
|
|
_openedPopup.close();
|
|
selectItem(-1);
|
|
setHoverItem(-1);
|
|
selectOnHover = false;
|
|
window.setFocus(_menuTogglePreviousFocus);
|
|
}
|
|
|
|
/// activate or deactivate main menu, return true if it has been activated
|
|
bool toggle() {
|
|
if (activated) {
|
|
// unfocus
|
|
deactivate();
|
|
return false;
|
|
} else {
|
|
// focus
|
|
activate();
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
/// override to handle focus changes
|
|
override protected void handleFocusChange(bool focused) {
|
|
if (focused && _openedPopup is null) {
|
|
// activating!
|
|
_menuTogglePreviousFocus = window.focusedWidget;
|
|
}
|
|
super.handleFocusChange(focused);
|
|
}
|
|
/// list navigation using keys
|
|
override bool onKeyEvent(KeyEvent event) {
|
|
// handle MainMenu activation / deactivation (Alt, Esc...)
|
|
bool toggleMenu = false;
|
|
bool isAlt = event.keyCode == KeyCode.ALT || event.keyCode == KeyCode.LALT || event.keyCode == KeyCode.RALT;
|
|
bool noOtherModifiers = !(event.flags & (KeyFlag.Shift | KeyFlag.Control));
|
|
|
|
if (event.action == KeyAction.KeyDown && event.keyCode == KeyCode.ESCAPE && event.flags == 0 && activated) {
|
|
deactivate();
|
|
return true;
|
|
}
|
|
if (event.action == KeyAction.Text && (event.flags & KeyFlag.Alt) && !(event.flags & KeyFlag.Shift) && !(event.flags & KeyFlag.Shift)) {
|
|
dchar ch = event.text[0];
|
|
int index = _item.findSubitemByHotkey(ch);
|
|
if (index >= 0) {
|
|
activate();
|
|
//selectItem(index);
|
|
itemClicked(index);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (event.action == KeyAction.KeyDown && isAlt && noOtherModifiers) {
|
|
_menuToggleState = 1;
|
|
} else if (event.action == KeyAction.KeyUp && isAlt && noOtherModifiers) {
|
|
if (_menuToggleState == 1)
|
|
toggleMenu = true;
|
|
_menuToggleState = 0;
|
|
} else {
|
|
_menuToggleState = 0;
|
|
}
|
|
if (toggleMenu) {
|
|
toggle();
|
|
return true;
|
|
}
|
|
if (!focused)
|
|
return false;
|
|
if (_selectedItemIndex >= 0 && event.action == KeyAction.KeyDown && ((event.keyCode == KeyCode.DOWN) || (event.keyCode == KeyCode.SPACE) || (event.keyCode == KeyCode.RETURN))) {
|
|
itemClicked(_selectedItemIndex);
|
|
return true;
|
|
}
|
|
return super.onKeyEvent(event);
|
|
}
|
|
}
|
|
|
|
|
|
/// popup menu widget (vertical layout of items)
|
|
class PopupMenu : MenuWidgetBase {
|
|
|
|
this(MenuItem item, MenuWidgetBase parentMenu = null) {
|
|
super(parentMenu, item, Orientation.Vertical);
|
|
id = "POPUP_MENU";
|
|
styleId = "POPUP_MENU";
|
|
selectOnHover = true;
|
|
}
|
|
}
|