View Javadoc
1   package com.vaadin.contextmenu;
2   
3   import info.magnolia.vaadin.addons.contextmenu.shared.MgnlContextMenuClientRpc;
4   import info.magnolia.vaadin.addons.contextmenu.shared.MgnlContextMenuServerRpc;
5   
6   import java.io.Serializable;
7   import java.lang.reflect.Method;
8   import java.util.ArrayList;
9   import java.util.EventListener;
10  import java.util.EventObject;
11  import java.util.List;
12  
13  import com.vaadin.contextmenu.ContextMenu.ContextMenuOpenListener.ContextMenuOpenEvent;
14  import com.vaadin.contextmenu.client.ContextMenuClientRpc;
15  import com.vaadin.contextmenu.client.ContextMenuServerRpc;
16  import com.vaadin.contextmenu.client.MenuSharedState;
17  import com.vaadin.contextmenu.client.MenuSharedState.MenuItemState;
18  import com.vaadin.event.ContextClickEvent;
19  import com.vaadin.event.ContextClickEvent.ContextClickListener;
20  import com.vaadin.event.ContextClickEvent.ContextClickNotifier;
21  import com.vaadin.server.AbstractExtension;
22  import com.vaadin.server.Resource;
23  import com.vaadin.server.ResourceReference;
24  import com.vaadin.ui.AbstractComponent;
25  import com.vaadin.ui.Component;
26  import com.vaadin.util.ReflectTools;
27  
28  @SuppressWarnings("serial")
29  public class ContextMenu extends AbstractExtension implements Menu {
30  
31      private AbstractMenu menu = new AbstractMenu();
32  
33      private ContextClickListener contextClickListener = new ContextClickListener() {
34          @Override
35          public void contextClick(ContextClickEvent event) {
36              fireEvent(new ContextMenuOpenEvent(ContextMenu.this, event));
37  
38              open(event.getClientX(), event.getClientY());
39          }
40      };
41  
42      /**
43       * ContextMenuClosedListener is used to listen for the event that the context menu is closed, either
44       * when a item is clicked or when the popup is canceled.
45       */
46      public interface ContextMenuClosedListener extends EventListener {
47          Method MENU_CLOSED = ReflectTools
48                  .findMethod(ContextMenuClosedListener.class,
49                          "onContextMenuClosed",
50                          ContextMenuClosedEvent.class);
51  
52          /**
53           * Called when the context menu is closed.
54           * @param event
55           */
56          void onContextMenuClosed(ContextMenuClosedEvent event);
57      }
58  
59  
60      /**
61       * ContextMenuClosedEvent is an event fired by the context menu
62       * when it's closed.
63       */
64      public static class ContextMenuClosedEvent extends EventObject {
65          private static final long serialVersionUID = -5705205542849351984L;
66  
67          private ContextMenu contextMenu;
68  
69          public ContextMenuClosedEvent(ContextMenu contextMenu) {
70              super(contextMenu);
71              this.contextMenu = contextMenu;
72          }
73  
74          public ContextMenu getContextMenu() {
75              return contextMenu;
76          }
77      }
78  
79      /**
80       * Adds listener that will be invoked when context menu is closed.
81       *
82       * @param contextMenuClosedListener
83       */
84      public void addContextMenuCloseListener(
85              ContextMenuClosedListener contextMenuClosedListener) {
86          addListener(
87                  ContextMenuClosedEvent.class,
88                  contextMenuClosedListener,
89                  ContextMenuClosedListener.MENU_CLOSED);
90      }
91  
92      public void open() {
93          getRpcProxy(MgnlContextMenuClientRpc.class).showContextMenu();
94      }
95  
96      /**
97       * @param parentComponent
98       *            The component to whose lifecycle the context menu is tied to.
99       * @param setAsMenuForParentComponent
100      *            Determines if this menu will be shown for the parent
101      *            component.
102      */
103     public ContextMenu(AbstractComponent parentComponent,
104             boolean setAsMenuForParentComponent) {
105         extend(parentComponent);
106 
107         registerRpc((MgnlContextMenuServerRpc) () -> fireEvent(new ContextMenuClosedEvent(ContextMenu.this)));
108         registerRpc(new ContextMenuServerRpc() {
109             @Override
110             public void itemClicked(int itemId, boolean menuClosed) {
111                 menu.itemClicked(itemId);
112             }
113         });
114 
115         if (setAsMenuForParentComponent) {
116             setAsContextMenuOf(parentComponent);
117         }
118     }
119 
120     /**
121      * Sets this as a context menu of the component. You can set one menu to as
122      * many components as you wish.
123      *
124      * @param component
125      *            the component to set the context menu to
126      */
127     public void setAsContextMenuOf(ContextClickNotifier component) {
128         component.addContextClickListener(contextClickListener);
129     }
130 
131     public void addContextMenuOpenListener(
132             ContextMenuOpenListener contextMenuComponentListener) {
133         addListener(ContextMenuOpenEvent.class, contextMenuComponentListener,
134                 ContextMenuOpenListener.MENU_OPENED);
135     }
136 
137     @Override
138     public void beforeClientResponse(boolean initial) {
139         super.beforeClientResponse(initial);
140 
141         // FIXME: think about where this is supposed to be
142 
143         /*
144          * This should also be used by MenuBar, upgrading it from Vaadin 6 to
145          * Vaadin 7 communication mechanism. Thus to be moved e.g. to the
146          * AbstractMenu.
147          */
148         MenuSharedState menuSharedState = getState();
149         menuSharedState.htmlContentAllowed = isHtmlContentAllowed();
150         menuSharedState.menuItems = convertItemsToState(getItems());
151     }
152 
153     public void open(int x, int y) {
154         getRpcProxy(ContextMenuClientRpc.class).showContextMenu(x, y);
155     }
156 
157     private List<MenuItemState> convertItemsToState(List<MenuItem> items) {
158         if (items == null || items.size() == 0) {
159             return null;
160         }
161 
162         List<MenuItemState> state = new ArrayList<>();
163 
164         for (MenuItem item : items) {
165             MenuItemState menuItemState = new MenuItemState();
166 
167             if (!item.isVisible()) {
168                 continue;
169             }
170 
171             menuItemState.id = item.getId();
172             menuItemState.text = item.getText();
173             menuItemState.checkable = item.isCheckable();
174             menuItemState.checked = item.isChecked();
175             menuItemState.description = item.getDescription();
176             menuItemState.enabled = item.isEnabled();
177             menuItemState.separator = item.isSeparator();
178             menuItemState.icon = ResourceReference.create(item.getIcon(), this,
179                     "");
180             menuItemState.styleName = item.getStyleName();
181 
182             menuItemState.childItems = convertItemsToState(item.getChildren());
183 
184             state.add(menuItemState);
185         }
186 
187         return state;
188     }
189 
190     @Override
191     protected MenuSharedState getState() {
192         return (MenuSharedState) super.getState();
193     }
194 
195     protected ContextClickListener getContextClickListener() {
196         return contextClickListener;
197     }
198 
199     // Should these also be in MenuInterface and then throw exception for
200     // MenuBar?
201     public MenuItem addSeparator() {
202         // FIXME: this is a wrong way
203         MenuItemImpl item = (MenuItemImpl) addItem("", null);
204         item.setSeparator(true);
205         return item;
206     }
207 
208     public MenuItem addSeparatorBefore(MenuItem itemToAddBefore) {
209         // FIXME: this is a wrong way
210         MenuItemImpl item = (MenuItemImpl) addItemBefore("", null, null,
211                 itemToAddBefore);
212         item.setSeparator(true);
213         return item;
214     }
215 
216     /**** Delegates to AbstractMenu ****/
217 
218     @Override
219     public MenuItem addItem(String caption, Command command) {
220         return menu.addItem(caption, command);
221     }
222 
223     @Override
224     public MenuItem addItem(String caption, Resource icon, Command command) {
225         return menu.addItem(caption, icon, command);
226     }
227 
228     @Override
229     public MenuItem addItemBefore(String caption, Resource icon,
230             Command command, MenuItem itemToAddBefore) {
231         return menu.addItemBefore(caption, icon, command, itemToAddBefore);
232     }
233 
234     @Override
235     public List<MenuItem> getItems() {
236         return menu.getItems();
237     }
238 
239     @Override
240     public void removeItem(MenuItem item) {
241         menu.removeItem(item);
242     }
243 
244     @Override
245     public void removeItems() {
246         menu.removeItems();
247     }
248 
249     @Override
250     public int getSize() {
251         return menu.getSize();
252     }
253 
254     @Override
255     public void setHtmlContentAllowed(boolean htmlContentAllowed) {
256         menu.setHtmlContentAllowed(htmlContentAllowed);
257     }
258 
259     @Override
260     public boolean isHtmlContentAllowed() {
261         return menu.isHtmlContentAllowed();
262     }
263 
264     /**** End of delegates to AbstractMenu ****/
265 
266     public interface ContextMenuOpenListener
267             extends EventListener, Serializable {
268 
269         public static final Method MENU_OPENED = ReflectTools.findMethod(
270                 ContextMenuOpenListener.class, "onContextMenuOpen",
271                 ContextMenuOpenEvent.class);
272 
273         public void onContextMenuOpen(ContextMenuOpenEvent event);
274 
275         public static class ContextMenuOpenEvent extends EventObject {
276             private final ContextMenu contextMenu;
277 
278             private final int x;
279             private final int y;
280 
281             private ContextClickEvent contextClickEvent;
282 
283             public ContextMenuOpenEvent(ContextMenu contextMenu,
284                     ContextClickEvent contextClickEvent) {
285                 super(contextClickEvent.getComponent());
286 
287                 this.contextMenu = contextMenu;
288                 this.contextClickEvent = contextClickEvent;
289                 x = contextClickEvent.getClientX();
290                 y = contextClickEvent.getClientY();
291             }
292 
293             /**
294              * @return ContextMenu that was opened.
295              */
296             public ContextMenu getContextMenu() {
297                 return contextMenu;
298             }
299 
300             /**
301              * @return Component which initiated the context menu open request.
302              */
303             public Component getSourceComponent() {
304                 return (Component) getSource();
305             }
306 
307             /**
308              * @return x-coordinate of open position.
309              */
310             public int getX() {
311                 return x;
312             }
313 
314             /**
315              * @return y-coordinate of open position.
316              */
317             public int getY() {
318                 return y;
319             }
320 
321             public ContextClickEvent getContextClickEvent() {
322                 return contextClickEvent;
323             }
324         }
325     }
326 }