View Javadoc
1   /*
2    * Copyright 2000-2018 Vaadin Ltd.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  
17  package com.vaadin.contextmenu;
18  
19  import com.vaadin.contextmenu.ContextMenu.ContextMenuOpenListener.ContextMenuOpenEvent;
20  import com.vaadin.contextmenu.client.ContextMenuClientRpc;
21  import com.vaadin.contextmenu.client.ContextMenuItemState;
22  import com.vaadin.contextmenu.client.ContextMenuServerRpc;
23  import com.vaadin.contextmenu.client.ContextMenuState;
24  import com.vaadin.event.ContextClickEvent;
25  import com.vaadin.event.ContextClickEvent.ContextClickListener;
26  import com.vaadin.event.ContextClickEvent.ContextClickNotifier;
27  import com.vaadin.server.AbstractExtension;
28  import com.vaadin.server.Extension;
29  import com.vaadin.server.Resource;
30  import com.vaadin.server.ResourceReference;
31  import com.vaadin.ui.AbstractComponent;
32  import com.vaadin.ui.Component;
33  import com.vaadin.ui.MenuBar;
34  import com.vaadin.ui.MenuBar.Command;
35  import com.vaadin.ui.MenuBar.MenuItem;
36  import com.vaadin.ui.UI;
37  import com.vaadin.util.ReflectTools;
38  
39  import java.lang.reflect.Method;
40  import java.util.ArrayList;
41  import java.util.Collections;
42  import java.util.EventObject;
43  import java.util.HashMap;
44  import java.util.List;
45  import java.util.Map;
46  
47  @SuppressWarnings("serial")
48  public class ContextMenu extends AbstractExtension {
49      private MenuBar innerMenuBar = new MenuBar() {
50          @Override
51          protected void addExtension(Extension extension) {
52              ContextMenu.this.addExtension(extension);
53          }
54      };
55      private MenuItem rootItem = innerMenuBar.addItem("");
56  
57      private ContextClickListener contextClickListener = new ContextClickListener() {
58          @Override
59          public void contextClick(ContextClickEvent event) {
60              fireEvent(new ContextMenuOpenEvent(ContextMenu.this, event));
61  
62              open(event.getClientX(), event.getClientY());
63          }
64      };
65      private Map<Integer, MenuItem> itemById = Collections.emptyMap();
66  
67      @Override
68      protected ContextMenuState getState(boolean markAsDirty) {
69          return (ContextMenuState) super.getState(markAsDirty);
70      }
71  
72      @Override
73      protected ContextMenuState getState() {
74          return (ContextMenuState) super.getState();
75      }
76  
77      /**
78       * @param parentComponent
79       *            The component to whose lifecycle the context menu is tied to.
80       * @param setAsMenuForParentComponent
81       *            Determines if this menu will be shown for the parent
82       *            component.
83       */
84      public ContextMenu(AbstractComponent parentComponent,
85              boolean setAsMenuForParentComponent) {
86          extend(parentComponent);
87  
88          registerRpc(new ContextMenuServerRpc() {
89              @Override
90              public void itemClicked(int itemId) {
91                  MenuItem clickedItem = itemById.get(itemId);
92                  if (clickedItem != null) {
93                      if (clickedItem.isCheckable())
94                          clickedItem.setChecked(!clickedItem.isChecked());
95  
96                      if (clickedItem.getCommand() != null)
97                          clickedItem.getCommand().menuSelected(clickedItem);
98                  }
99              }
100         });
101 
102         if (setAsMenuForParentComponent) {
103             setAsContextMenuOf(parentComponent);
104         }
105     }
106 
107     /**
108      * Sets this as a context menu of the component. You can set one menu to as
109      * many components as you wish.
110      *
111      * @param component
112      *            the component to set the context menu to
113      */
114     public void setAsContextMenuOf(ContextClickNotifier component) {
115         component.addContextClickListener(contextClickListener);
116     }
117 
118     public void addContextMenuOpenListener(
119             ContextMenuOpenListener contextMenuComponentListener) {
120         addListener(ContextMenuOpenEvent.class, contextMenuComponentListener,
121                 ContextMenuOpenListener.MENU_OPENED);
122     }
123 
124     @Override
125     public void beforeClientResponse(boolean initial) {
126         super.beforeClientResponse(initial);
127         UI uI = getUI();
128         if (uI != null && uI.getConnectorTracker().isDirty(this)) {
129 
130             /*
131              * This should also be used by MenuBar, upgrading it from Vaadin 6
132              * to Vaadin 7 communication mechanism. Thus to be moved e.g. to the
133              * AbstractMenu.
134              */
135             ContextMenuState menuSharedState = getState();
136             itemById = new HashMap<>();
137             menuSharedState.menuItems = convertItemsToState(getItems(),
138                     itemById);
139         }
140     }
141 
142     public void open(int x, int y) {
143         if (rootItem.hasChildren()) {
144             getRpcProxy(ContextMenuClientRpc.class).showContextMenu(x, y);
145         }
146     }
147 
148     private List<ContextMenuItemState> convertItemsToState(List<MenuItem> items,
149             Map<Integer, MenuItem> itemRegistry) {
150         if (items == null || items.size() == 0) {
151             return null;
152         }
153 
154         List<ContextMenuItemState> stateItems = new ArrayList<>(items.size());
155 
156         for (MenuItem item : items) {
157             ContextMenuItemStatetml#ContextMenuItemState">ContextMenuItemState menuItemState = new ContextMenuItemState();
158 
159             if (!item.isVisible()) {
160                 continue;
161             }
162 
163             menuItemState.id = item.getId();
164             menuItemState.text = item.getText();
165             menuItemState.checkable = item.isCheckable();
166             menuItemState.command = item.getCommand() != null;
167             menuItemState.checked = item.isChecked();
168             menuItemState.description = item.getDescription();
169             menuItemState.descriptionContentMode = item
170                     .getDescriptionContentMode();
171             menuItemState.enabled = item.isEnabled();
172             menuItemState.separator = item.isSeparator();
173             menuItemState.icon = ResourceReference.create(item.getIcon(), this,
174                     "");
175             menuItemState.styleName = item.getStyleName();
176 
177             menuItemState.childItems = convertItemsToState(item.getChildren(),
178                     itemRegistry);
179 
180             stateItems.add(menuItemState);
181             itemRegistry.put(item.getId(), item);
182         }
183 
184         return stateItems;
185     }
186 
187     protected ContextClickListener getContextClickListener() {
188         return contextClickListener;
189     }
190 
191     public MenuItem addSeparator() {
192         return rootItem.addSeparator();
193     }
194 
195     public MenuItem addSeparatorBefore(MenuItem itemToAddBefore) {
196         return rootItem.addSeparatorBefore(itemToAddBefore);
197     }
198 
199     public MenuItem addItem(String caption) {
200         return rootItem.addItem(caption);
201     }
202 
203     public MenuItem addItem(String caption, Command command) {
204         return rootItem.addItem(caption, command);
205     }
206 
207     public MenuItem addItem(String caption, Resource icon, Command command) {
208         return rootItem.addItem(caption, icon, command);
209     }
210 
211     public MenuItem addItemBefore(String caption, Resource icon,
212                                   Command command, MenuItem itemToAddBefore) {
213         return rootItem.addItemBefore(caption, icon, command, itemToAddBefore);
214     }
215 
216     public List<MenuItem> getItems() {
217         return rootItem.getChildren();
218     }
219 
220     public void removeItem(MenuItem item) {
221         rootItem.removeChild(item);
222     }
223 
224     public void removeItems() {
225         rootItem.removeChildren();
226     }
227 
228     public int getSize() {
229         return rootItem.getSize();
230     }
231 
232     public void setHtmlContentAllowed(boolean htmlContentAllowed) {
233         getState().htmlContentAllowed = htmlContentAllowed;
234         innerMenuBar.setHtmlContentAllowed(htmlContentAllowed);
235     }
236 
237     public boolean isHtmlContentAllowed() {
238         return getState(false).htmlContentAllowed;
239     }
240 
241     public interface ContextMenuOpenListener extends java.util.EventListener, java.io.Serializable {
242 
243         public static final Method MENU_OPENED = ReflectTools.findMethod(
244                 ContextMenuOpenListener.class, "onContextMenuOpen",
245                 ContextMenuOpenEvent.class);
246 
247         public void onContextMenuOpen(ContextMenuOpenEvent event);
248 
249         public static class ContextMenuOpenEvent extends EventObject {
250             private final ContextMenu contextMenu;
251 
252             private final int x;
253             private final int y;
254 
255             private ContextClickEvent contextClickEvent;
256 
257             public ContextMenuOpenEvent(ContextMenu contextMenu,
258                     ContextClickEvent contextClickEvent) {
259                 super(contextClickEvent.getComponent());
260 
261                 this.contextMenu = contextMenu;
262                 this.contextClickEvent = contextClickEvent;
263                 x = contextClickEvent.getClientX();
264                 y = contextClickEvent.getClientY();
265             }
266 
267             /**
268              * @return ContextMenu that was opened.
269              */
270             public ContextMenu getContextMenu() {
271                 return contextMenu;
272             }
273 
274             /**
275              * @return Component which initiated the context menu open request.
276              */
277             public Component getSourceComponent() {
278                 return (Component) getSource();
279             }
280 
281             /**
282              * @return x-coordinate of open position.
283              */
284             public int getX() {
285                 return x;
286             }
287 
288             /**
289              * @return y-coordinate of open position.
290              */
291             public int getY() {
292                 return y;
293             }
294 
295             public ContextClickEvent getContextClickEvent() {
296                 return contextClickEvent;
297             }
298         }
299     }
300 
301 }