From 6d518ed5c0059a3205ae578295b8ce942f8b0a63 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Mon, 12 May 2014 13:28:41 +0400 Subject: [PATCH] popup menus; popup menu for editors --- examples/example1/src/main.d | 2 +- src/dlangui/core/collections.d | 7 +++- src/dlangui/core/signals.d | 9 +++-- src/dlangui/widgets/editors.d | 33 +++++++++++++++++- src/dlangui/widgets/menu.d | 63 ++++++++++++++++++++++++++++------ src/dlangui/widgets/styles.d | 6 ++-- src/dlangui/widgets/widget.d | 4 +++ 7 files changed, 105 insertions(+), 19 deletions(-) diff --git a/examples/example1/src/main.d b/examples/example1/src/main.d index 964256c1..01052340 100644 --- a/examples/example1/src/main.d +++ b/examples/example1/src/main.d @@ -93,7 +93,7 @@ extern (C) int UIAppMain(string[] args) { mainMenuItems.add(windowItem); mainMenuItems.add(helpItem); MainMenu mainMenu = new MainMenu(mainMenuItems); - mainMenu.onMenuItemListener = delegate(MenuItem item) { + mainMenu.onMenuItemClickListener = delegate(MenuItem item) { Log.d("mainMenu.onMenuItemListener", item.label); const Action a = item.action; if (a) { diff --git a/src/dlangui/core/collections.d b/src/dlangui/core/collections.d index 7b379555..372a120c 100644 --- a/src/dlangui/core/collections.d +++ b/src/dlangui/core/collections.d @@ -96,7 +96,12 @@ struct Collection(T, bool ownItems = false) { } _items[index] = item; _len++; - } + } + /// add all items from other collection + void addAll(ref Collection!(T, ownItems) v) { + for (int i = 0; i < v.length; i++) + add(v[i]); + } /// support for appending (~=, +=) and removing by value (-=) ref Collection opOpAssign(string op)(T item) { static if (op.equal("~") || op.equal("+")) { diff --git a/src/dlangui/core/signals.d b/src/dlangui/core/signals.d index 04998159..027f8aa2 100644 --- a/src/dlangui/core/signals.d +++ b/src/dlangui/core/signals.d @@ -169,8 +169,13 @@ struct Signal(T1) if (is(T1 == interface) && __traits(allMembers, T1).length == alias return_t = ReturnType!(__traits(getMember, T1, __traits(allMembers, T1)[0])); alias params_t = ParameterTypeTuple!(__traits(getMember, T1, __traits(allMembers, T1)[0])); alias slot_t = return_t delegate(params_t); - private Collection!slot_t _listeners; - /// returns true if listener is assigned + private Collection!slot_t _listeners; + + this(ref Signal!T1 v) { + _listeners.addAll(v._listeners); + } + + /// returns true if listener is assigned final bool assigned() { return _listeners.length > 0; } diff --git a/src/dlangui/widgets/editors.d b/src/dlangui/widgets/editors.d index 094021de..45acec02 100644 --- a/src/dlangui/widgets/editors.d +++ b/src/dlangui/widgets/editors.d @@ -805,7 +805,7 @@ class EditableContent { } /// base for all editor widgets -class EditWidgetBase : WidgetGroup, EditableContentListener { +class EditWidgetBase : WidgetGroup, EditableContentListener, MenuItemActionHandler { protected EditableContent _content; protected Rect _clientRc; @@ -905,6 +905,12 @@ class EditWidgetBase : WidgetGroup, EditableContentListener { _popupMenu = popupMenu; return this; } + + /// + override bool onMenuItemAction(const Action action) { + return handleAction(action); + } + /// returns true if widget can show popup (e.g. by mouse right click at point x,y) override bool canShowPopupMenu(int x, int y) { if (_popupMenu is null) @@ -914,13 +920,38 @@ class EditWidgetBase : WidgetGroup, EditableContentListener { return false; return true; } + /// override to change popup menu items state + override bool isActionEnabled(const Action action) { + switch (action.id) { + case EditorActions.Copy: + case EditorActions.Cut: + return !_selectionRange.empty; + case EditorActions.Paste: + return Platform.instance.getClipboardText().length > 0; + case EditorActions.Undo: + return _content.hasUndo; + case EditorActions.Redo: + return _content.hasRedo; + default: + return super.isActionEnabled(action); + } + } /// shows popup at (x,y) override void showPopupMenu(int x, int y) { /// if preparation signal handler assigned, call it; don't show popup if false is returned from handler if (_popupMenu.onBeforeOpeningSubmenu.assigned) if (!_popupMenu.onBeforeOpeningSubmenu(_popupMenu)) return; + for (int i = 0; i < _popupMenu.subitemCount; i++) { + MenuItem item = _popupMenu.subitem(i); + if (item.action && isActionEnabled(item.action)) { + item.enabled = true; + } else { + item.enabled = false; + } + } PopupMenu popupMenu = new PopupMenu(_popupMenu); + popupMenu.onMenuItemActionListener = this; PopupWidget popup = window.showPopup(popupMenu, this, PopupAlign.Point | PopupAlign.Right, x, y); popup.flags = PopupFlags.CloseOnClickOutside; } diff --git a/src/dlangui/widgets/menu.d b/src/dlangui/widgets/menu.d index 7be67ba4..24cb5808 100644 --- a/src/dlangui/widgets/menu.d +++ b/src/dlangui/widgets/menu.d @@ -98,6 +98,14 @@ class MenuItem { /// sets item action @property MenuItem action(Action a) { _action = a; return this; } + /// menu item Enabled flag + @property bool enabled() { return _enabled; } + /// 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 @@ -218,16 +226,20 @@ class MenuItemWidget : WidgetGroup { _mainMenu = mainMenu; _item = item; styleId = "MENU_ITEM"; + if (!item.enabled) + resetState(State.Enabled); // icon if (_item.action && _item.action.iconId.length) { _icon = new ImageWidget("MENU_ICON", _item.action.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; @@ -235,6 +247,7 @@ class MenuItemWidget : WidgetGroup { _accel = new TextWidget("MENU_ACCEL"); _accel.styleId = "MENU_ACCEL"; _accel.text = acc; + _accel.state = State.Parent; addChild(_accel); } trackHover = true; @@ -242,6 +255,14 @@ class MenuItemWidget : WidgetGroup { } } +interface MenuItemClickHandler { + bool onMenuItemClick(MenuItem item); +} + +interface MenuItemActionHandler { + bool onMenuItemAction(const Action action); +} + /// base class for menus class MenuWidgetBase : ListWidget { protected MenuWidgetBase _parentMenu; @@ -249,11 +270,11 @@ class MenuWidgetBase : ListWidget { protected PopupMenu _openedMenu; protected PopupWidget _openedPopup; protected int _openedPopupIndex; - protected bool delegate(MenuItem item) _onMenuItemClickListener; - /// menu item click listener - @property bool delegate(MenuItem item) onMenuItemListener() { return _onMenuItemClickListener; } - /// menu item click listener - @property MenuWidgetBase onMenuItemListener(bool delegate(MenuItem item) listener) { _onMenuItemClickListener = listener; return this; } + + /// menu item click listener + Signal!MenuItemClickHandler onMenuItemClickListener; + /// menu item action listener + Signal!MenuItemActionHandler onMenuItemActionListener; this(MenuWidgetBase parentMenu, MenuItem item, Orientation orientation) { _parentMenu = parentMenu; @@ -392,13 +413,22 @@ class MenuWidgetBase : ListWidget { selectItem(-1); setHoverItem(-1); selectOnHover = false; - bool delegate(MenuItem item) listener = _onMenuItemClickListener; + + // 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(); // this pointer now can be invalid - if popup removed - if (listener !is null) - listener(item); + if (onMenuItemClickListenerCopy.assigned) + if (onMenuItemClickListenerCopy(item)) + return; + // this pointer now can be invalid - if popup removed + if (onMenuItemActionListenerCopy.assigned) + onMenuItemActionListenerCopy(item.action); } } @@ -526,10 +556,21 @@ class MainMenu : MenuWidgetBase { override protected void onMenuItem(MenuItem item) { debug Log.d("MainMenu.onMenuItem ", item.action.label); - bool delegate(MenuItem item) listener = _onMenuItemClickListener; + + // copy menu item click listeners + Signal!MenuItemClickHandler onMenuItemClickListenerCopy = onMenuItemClickListener; + // copy item action listeners + Signal!MenuItemActionHandler onMenuItemActionListenerCopy = onMenuItemActionListener; + deactivate(); - if (listener !is null) - listener(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) diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index 16836638..ad68d12d 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -757,9 +757,9 @@ Theme createDefaultTheme() { menuItem.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa); menuItem.createState(State.Hovered, State.Hovered).backgroundColor(0xC0FFFF00); res.createSubstyle("MENU_ICON").setMargins(2,2,2,2).alignment(Align.VCenter|Align.Left); - res.createSubstyle("MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TextFlag.UnderlineHotKeys); - res.createSubstyle("MAIN_MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TEXT_FLAGS_USE_PARENT); - res.createSubstyle("MENU_ACCEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left); + res.createSubstyle("MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TextFlag.UnderlineHotKeys).createState(State.Enabled,0).textColor(0x80404040); + res.createSubstyle("MAIN_MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TEXT_FLAGS_USE_PARENT).createState(State.Enabled,0).textColor(0x80404040); + res.createSubstyle("MENU_ACCEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).createState(State.Enabled,0).textColor(0x80404040); Style transparentButtonBackground = res.createSubstyle("TRANSPARENT_BUTTON_BACKGROUND").backgroundImageId("transparent_button_background").setPadding(4,2,4,2); //.backgroundColor(0xE0E080) ; //transparentButtonBackground.createState(State.Focused, State.Focused).backgroundColor(0xC0C0C000); diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index 75c8e2f5..2378134e 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -1050,6 +1050,10 @@ class Widget { void showPopupMenu(int x, int y) { // override to show popup } + /// override to change popup menu items state + bool isActionEnabled(const Action action) { + return true; + } // =========================================================== // Widget hierarhy methods