View Javadoc
1   package com.vaadin.contextmenu.client;
2   
3   import info.magnolia.vaadin.addons.contextmenu.shared.MgnlContextMenuClientRpc;
4   import info.magnolia.vaadin.addons.contextmenu.shared.MgnlContextMenuServerRpc;
5   
6   import java.util.List;
7   import java.util.logging.Logger;
8   
9   import com.google.gwt.core.client.GWT;
10  import com.google.gwt.dom.client.Document;
11  import com.google.gwt.dom.client.SpanElement;
12  import com.google.gwt.event.logical.shared.CloseEvent;
13  import com.google.gwt.user.client.Command;
14  import com.google.gwt.user.client.Event;
15  import com.google.gwt.user.client.Event.NativePreviewEvent;
16  import com.google.gwt.user.client.Event.NativePreviewHandler;
17  import com.google.gwt.user.client.ui.PopupPanel;
18  import com.google.gwt.user.client.ui.Widget;
19  import com.vaadin.client.ApplicationConnection;
20  import com.vaadin.client.ComponentConnector;
21  import com.vaadin.client.ServerConnector;
22  import com.vaadin.client.UIDL;
23  import com.vaadin.client.WidgetUtil;
24  import com.vaadin.client.communication.StateChangeEvent;
25  import com.vaadin.client.extensions.AbstractExtensionConnector;
26  import com.vaadin.client.ui.Icon;
27  import com.vaadin.client.ui.VMenuBar;
28  import com.vaadin.client.ui.VMenuBar.CustomMenuItem;
29  import com.vaadin.contextmenu.ContextMenu;
30  import com.vaadin.contextmenu.client.MenuSharedState.MenuItemState;
31  import com.vaadin.shared.ui.Connect;
32  
33  @SuppressWarnings("serial")
34  @Connect(ContextMenu.class)
35  public class ContextMenuConnector extends AbstractExtensionConnector {
36  
37  
38      private class VMgnlMenuBar extends MyVMenuBar {
39  
40          VMgnlMenuBar(boolean subMenu, VMenuBar parentMenu) {
41              super(subMenu, parentMenu);
42          }
43  
44          VMgnlMenuBar() {}
45  
46          @Override
47          public void showChildMenuAt(CustomMenuItem item, int top, int left) {
48              super.showChildMenuAt(item, top, left);
49          }
50  
51          @Override
52          public void onClose(CloseEvent<PopupPanel> event) {
53              super.onClose(event);
54              getRpcProxy(MgnlContextMenuServerRpc.class).contextMenuClosed();
55          }
56  
57          /**
58           * Build the HTML content for a menu item.
59           * <p>
60           * For internal use only. May be removed or replaced in the future.
61           */
62          public String buildItemHTML(UIDL item) {
63              // Construct html from the text and the optional icon
64              StringBuffer itemHTML = new StringBuffer();
65              if (item.hasAttribute("separator")) {
66                  itemHTML.append("<span>---</span>");
67              } else {
68                  // Add submenu indicator
69                  if (item.getChildCount() > 0) {
70                      String bgStyle = "";
71                      itemHTML.append("<span class=\"" + getStylePrimaryName()
72                              + "-submenu-indicator\"" + bgStyle
73                              + ">&#x25BA;</span>");
74                  }
75  
76                  itemHTML.append("<span class=\"" + getStylePrimaryName() + "-menuitem-caption\">");
77  
78  
79                  String itemText = item.getStringAttribute("text");
80                  if (!htmlContentAllowed) {
81                      itemText = WidgetUtil.escapeHTML(itemText);
82                  }
83                  itemHTML.append(itemText);
84                  itemHTML.append("</span>");
85              }
86              return itemHTML.toString();
87          }
88      }
89  
90      protected MgnlContextMenuClientRpc serverToClientRPC = (MgnlContextMenuClientRpc) () -> {
91          Widget clickTargetWidget = ((ComponentConnector) getParent()).getWidget();
92  
93          int targetW = clickTargetWidget.getOffsetWidth();
94          int x = clickTargetWidget.getAbsoluteLeft() + targetW;
95          int y = clickTargetWidget.getAbsoluteTop() + clickTargetWidget.getOffsetHeight();
96  
97          showMenu(x, y);
98      };
99  
100     @SuppressWarnings("unused")
101     private static Logger logger = Logger.getLogger("ContextMenuConnector");
102 
103     // TODO: make it so that we don't need this dummy root menu bar.
104     private VMgnlMenuBar dummyRootMenuBar;
105     private VMgnlMenuBar contextMenuWidget;
106 
107     @Override
108     public MenuSharedState getState() {
109         return (MenuSharedState) super.getState();
110     }
111 
112     @Override
113     public void onStateChanged(StateChangeEvent stateChangeEvent) {
114         super.onStateChanged(stateChangeEvent);
115 
116         contextMenuWidget.clearItems();
117         addMenuItemsFromState(contextMenuWidget, getState().menuItems);
118     }
119 
120     @Override
121     protected void init() {
122         super.init();
123 
124         dummyRootMenuBar = new VMgnlMenuBar();
125 
126         VMenuBar.CustomMenuItem item = GWT.create(VMgnlMenuBar.CustomMenuItem.class);
127         dummyRootMenuBar.getItems().add(item);
128 
129         contextMenuWidget = new VMgnlMenuBar(true, dummyRootMenuBar);
130         item.setSubMenu(contextMenuWidget);
131 
132         // application connection that is used for all our overlays
133         MyVOverlay.setApplicationConnection(this.getConnection());
134 
135         registerRpc(MgnlContextMenuClientRpc.class, serverToClientRPC);
136         registerRpc(ContextMenuClientRpc.class, new ContextMenuClientRpc() {
137             @Override
138             public void showContextMenu(int x, int y) {
139                 showMenu(x, y);
140             }
141         });
142 
143         Event.addNativePreviewHandler(new NativePreviewHandler() {
144             @Override
145             public void onPreviewNativeEvent(NativePreviewEvent event) {
146                 if (event.getTypeInt() == Event.ONKEYDOWN
147                         && contextMenuWidget.isPopupShowing()) {
148                     boolean handled = contextMenuWidget.handleNavigation(
149                             event.getNativeEvent().getKeyCode(),
150                             event.getNativeEvent().getCtrlKey(),
151                             event.getNativeEvent().getShiftKey());
152 
153                     if (handled) {
154                         event.cancel();
155                     }
156                 }
157             }
158         });
159     }
160 
161     private void addMenuItemsFromState(VMenuBar menuToAddTo,
162             List<MenuItemState> menuItems) {
163         if (menuItems == null)
164             return;
165 
166         for (MenuItemState menuItemState : menuItems) {
167             CustomMenuItem newItem = addMenuItemToMenu(menuToAddTo,
168                     menuItemState);
169 
170             if (menuItemState.childItems != null
171                     && menuItemState.childItems.size() > 0) {
172                 VMenuBar subMenu = new MyVMenuBar(true, menuToAddTo);
173                 addMenuItemsFromState(subMenu, menuItemState.childItems);
174                 newItem.setSubMenu(subMenu);
175             }
176         }
177     }
178 
179     private CustomMenuItem addMenuItemToMenu(VMenuBar menuToAddTo,
180             final MenuItemState menuItemState) {
181         String itemText = buildItemHTML(menuItemState,
182                 getState().htmlContentAllowed, getConnection());
183         CustomMenuItem item = menuToAddTo.addItem(itemText, new Command() {
184             @Override
185             public void execute() {
186                 if (contextMenuWidget.isAttached()) {
187                     dummyRootMenuBar.hideChildren();
188                     itemSelected(menuItemState.id);
189                 }
190             }
191         });
192 
193         updateMenuItemFromState(item, menuItemState);
194 
195         return item;
196     }
197 
198     private void updateMenuItemFromState(CustomMenuItem item,
199             MenuItemState state) {
200         item.setEnabled(state.enabled);
201         item.setCheckable(state.checkable);
202         item.setChecked(state.checked);
203         item.setStyleName(state.styleName);
204         if (item instanceof VMenuItem) { // TODO: when these are added, the
205                                          // condition must be removed
206             ((VMenuItem) item).setSeparator(state.separator);
207             ((VMenuItem) item).setDescription(state.description);
208         }
209     }
210 
211     // TODO adapted from VMenuBar.buildItemHTML, must be removed/refactored asap
212     private static String buildItemHTML(MenuItemState state,
213             boolean htmlContentAllowed, ApplicationConnection connection) {
214         // Construct html from the text and the optional icon
215         StringBuffer itemHTML = new StringBuffer();
216         if (state.separator) {
217             itemHTML.append("<span>---</span>");
218         } else {
219             // Add submenu indicator
220             if (state.childItems != null && state.childItems.size() > 0) {
221                 itemHTML.append(
222                         "<span class=\"v-menubar-submenu-indicator\">&#x25BA;</span>");
223             }
224 
225             itemHTML.append("<span class=\"v-menubar-menuitem-caption\">");
226 
227             final String iconUrl = state.icon.getURL();
228             // >>> This is the Magnolia IconFont patch. (MGNLUI-1323)
229             String ICON_FONT_CODE = "iconfont#";
230             if (iconUrl.startsWith(ICON_FONT_CODE)) {
231                 SpanElement iconFont;
232                 iconFont = Document.get().createSpanElement();
233                 String iconFontCssClass = iconUrl.substring(ICON_FONT_CODE.length());
234                 iconFont.setClassName("v-icon " + iconFontCssClass);
235                 itemHTML.append(iconFont.getString());
236             }
237             else {
238                 Icon icon = connection.getIcon(iconUrl);
239                 if (icon != null) {
240                     itemHTML.append(icon.getElement().getString());
241                 }
242             }
243 
244             String itemText = state.text;
245             if (!htmlContentAllowed) {
246                 itemText = WidgetUtil.escapeHTML(itemText);
247             }
248             itemHTML.append(itemText);
249             itemHTML.append("</span>");
250         }
251         return itemHTML.toString();
252     }
253 
254     protected void itemSelected(int id) {
255         getRpcProxy(ContextMenuServerRpc.class).itemClicked(id, true);
256     }
257 
258     private void showMenu(int eventX, int eventY) {
259         CustomMenuItem firstItem = dummyRootMenuBar.getItems().get(0);
260         dummyRootMenuBar.setSelected(firstItem);
261         dummyRootMenuBar.showChildMenuAt(firstItem, eventY, eventX);
262     }
263 
264     @Override
265     protected void extend(ServerConnector target) {
266         Logger.getLogger("ContextMenuConnector").info("extend");
267 
268         // Widget widget = ((AbstractComponentConnector) target).getWidget();
269 
270         // widget.addDomHandler(new ContextMenuHandler() {
271         //
272         // @Override
273         // public void onContextMenu(ContextMenuEvent event) {
274         // event.stopPropagation();
275         // event.preventDefault();
276         //
277         // showMenu(event.getNativeEvent().getClientX(), event
278         // .getNativeEvent().getClientY());
279         // }
280         // }, ContextMenuEvent.getType());
281 
282         // widget.addDomHandler(new KeyDownHandler() {
283         // @Override
284         // public void onKeyDown(KeyDownEvent event) {
285         // // FIXME: check if menu is shown or handleNavigation will do it?
286         //
287         // boolean handled = contextMenuWidget.handleNavigation(event
288         // .getNativeEvent().getKeyCode(), event.getNativeEvent()
289         // .getCtrlKey(), event.getNativeEvent().getShiftKey());
290         //
291         // if (handled) {
292         // event.stopPropagation();
293         // event.preventDefault();
294         // }
295         // }
296         // }, KeyDownEvent.getType());
297     }
298 }