View Javadoc
1   package org.vaadin.peter.contextmenu.client;
2   
3   import com.google.gwt.dom.client.Document;
4   import com.google.gwt.dom.client.ImageElement;
5   import com.google.gwt.dom.client.SpanElement;
6   import com.google.gwt.user.client.DOM;
7   import com.google.gwt.user.client.Event;
8   import com.google.gwt.user.client.ui.FlowPanel;
9   import com.google.gwt.user.client.ui.FocusWidget;
10  import com.google.gwt.user.client.ui.Label;
11  
12  /**
13   * ContextMenuItemWidget is client side widget that represents one menu item in
14   * context menu.
15   *
16   * @author Peter Lehto / Vaadin Ltd
17   */
18  public class ContextMenuItemWidget extends FocusWidget {
19  	private final FlowPanel root;
20  
21  	protected ImageElement icon;
22  	private final FlowPanel iconContainer;
23  	private final Label text;
24  
25  	private ContextMenuOverlay subMenu;
26  
27  	private ContextMenuItemWidget parentItem;
28  	private ContextMenuOverlay overlay;
29  
30  	private ContextMenuWidget rootComponent;
31  
32  	private String id;
33  
34  	public ContextMenuItemWidget() {
35  		root = new FlowPanel();
36  		root.setStylePrimaryName("v-context-menu-item-basic");
37  
38  		setElement(root.getElement());
39  
40  		root.addStyleName("v-context-submenu");
41  
42  		iconContainer = new FlowPanel();
43  		iconContainer.setStyleName("v-context-menu-item-basic-icon-container");
44  
45  		text = new Label();
46  		text.setStyleName("v-context-menu-item-basic-text");
47  
48  		root.add(iconContainer);
49  		root.add(text);
50  	}
51  
52  	@Override
53  	public void setFocus(boolean focused) {
54  		if (hasSubMenu()) {
55  			subMenu.setFocus(false);
56  		}
57  
58  		super.setFocus(focused);
59  
60  		if (!focused) {
61  			DOM.releaseCapture(getElement());
62  		}
63  	}
64  
65  	/**
66  	 * @return true if this item has a sub menu
67  	 */
68  	public boolean hasSubMenu() {
69  		return subMenu != null && subMenu.getNumberOfItems() > 0;
70  	}
71  
72  	/**
73  	 * Hides the sub menu that's been opened from this item
74  	 */
75  	public void hideSubMenu() {
76  		if (hasSubMenu()) {
77  			subMenu.hide();
78  			removeStyleName("v-context-menu-item-basic-open");
79  		}
80  	}
81  
82  	/**
83  	 * @return true if this item is an item in the root menu, false otherwise
84  	 */
85  	public boolean isRootItem() {
86  		return parentItem == null;
87  	}
88  
89  	        /**
90       * Sets the menu component to which this item belongs to
91       *
92       * @param owner
93       */
94  	public void setOverlay(ContextMenuOverlay owner) {
95  		this.overlay = owner;
96  	}
97  
98  	        /**
99       * Sets parent item meaning that this item is in the sub menu of given
100      * parent item.
101      *
102      * @param parentItem
103      */
104 	public void setParentItem(ContextMenuItemWidget parentItem) {
105 		this.parentItem = parentItem;
106 	}
107 
108 	/**
109 	 * @return menu item that opened the menu to which this item belongs
110 	 */
111 	public ContextMenuItemWidget getParentItem() {
112 		return parentItem;
113 	}
114 
115 	/**
116 	 * @return true if this menu has a sub menu and it's open
117 	 */
118 	public boolean isSubmenuOpen() {
119 		return hasSubMenu() && subMenu.isShowing();
120 	}
121 
122 	/**
123 	 * Removes all the items from the submenu of this item. If this menu item
124 	 * does not have a sub menu, this call has no effect.
125 	 */
126 	public void clearItems() {
127 		if (hasSubMenu()) {
128 			subMenu.clearItems();
129 		}
130 	}
131 
132     /**
133      * Adds given context menu item into the sub menu of this item.
134      * 
135      * @param contextMenuItem
136      */
137 	public void addSubMenuItem(ContextMenuItemWidget contextMenuItem) {
138 		if (!hasSubMenu()) {
139 			subMenu = new ContextMenuOverlay();
140 			setStylePrimaryName("v-context-menu-item-basic-submenu");
141 		}
142 
143 		contextMenuItem.setParentItem(this);
144 		subMenu.addMenuItem(contextMenuItem);
145 	}
146 
147 	public void setCaption(String caption) {
148 		text.setText(caption);
149 	}
150 
151     public void setIcon(String url) {
152 		if (url == null) {
153 			iconContainer.clear();
154 			icon = null;
155 		} else {
156 
157             // >>> This is the Magnolia IconFont patch. (MGNLUI-1323)
158             String ICON_FONT_CODE = "iconfont#";
159             if (url.startsWith(ICON_FONT_CODE)) {
160                 SpanElement iconFont;
161                 iconFont = Document.get().createSpanElement();
162                 String iconFontCssClass = url.substring(ICON_FONT_CODE.length());
163                 iconFont.setClassName("v-icon " + iconFontCssClass);
164                 iconContainer.getElement().appendChild(iconFont);
165             }
166             else {
167                 // This is the code from before the patch. (MGNLUI-1323)
168                 icon = Document.get().createImageElement();
169                 icon.setClassName("v-icon");
170                 icon.setSrc(url);
171                 iconContainer.getElement().appendChild(icon);
172             }
173 
174 		}
175 	}
176 
177 	public void setRootComponent(ContextMenuWidget rootComponent) {
178 		this.rootComponent = rootComponent;
179 	}
180 
181 	public void setId(String id) {
182 		this.id = id;
183 	}
184 
185 	public String getId() {
186 		return id;
187 	}
188 
189 	public void closeSiblingMenus() {
190 		overlay.closeSubMenus();
191 	}
192 
193 	protected void selectLowerSibling() {
194 		setFocus(false);
195 		overlay.selectItemAfter(ContextMenuItemWidget.this);
196 
197 	}
198 
199 	protected void selectUpperSibling() {
200 		setFocus(false);
201 		overlay.selectItemBefore(ContextMenuItemWidget.this);
202 	}
203 
204 	protected void closeThisAndSelectParent() {
205 		if (!isRootItem()) {
206 			setFocus(false);
207 			parentItem.hideSubMenu();
208 			parentItem.setFocus(true);
209 		}
210 	}
211 
212 	        /**
213      * Called when context menu item is clicked or is focused and enter is
214      * pressed.
215      *
216      * @return true if context menu was closed after the click, false otherwise
217      */
218 	protected boolean onItemClicked() {
219 		if (isEnabled()) {
220 			overlay.closeSubMenus();
221 
222 			if (hasSubMenu()) {
223 				openSubMenu();
224 				return false;
225 			} else {
226 				if (isRootItem()) {
227 					closeContextMenu();
228 				} else {
229 					parentItem.closeContextMenu();
230 				}
231 
232 				return true;
233 			}
234 		}
235 
236 		return false;
237 	}
238 
239 	private void closeContextMenu() {
240 		if (isRootItem()) {
241 			rootComponent.hide();
242 		} else {
243 			parentItem.closeContextMenu();
244 		}
245 	}
246 
247 	/**
248 	 * Programmatically opens the sub menu of this item.
249 	 */
250 	private void openSubMenu() {
251 		if (isEnabled() && hasSubMenu() && !subMenu.isShowing()) {
252 			overlay.closeSubMenus();
253 
254 			setFocus(false);
255 			addStyleName("v-context-menu-item-basic-open");
256 			subMenu.openNextTo(this);
257 		}
258 	}
259 
260 	/**
261 	 * @param nativeEvent
262 	 * @return true if given event targets the overlay of this menu item or
263 	 *         overlay of any of this item's child item.
264 	 */
265 	public boolean eventTargetsPopup(Event nativeEvent) {
266 		if (overlay.eventTargetsPopup(nativeEvent)) {
267 			return true;
268 		}
269 
270 		if (hasSubMenu()) {
271 			for (ContextMenuItemWidget item : subMenu.getMenuItems()) {
272 				if (item.eventTargetsPopup(nativeEvent)) {
273 					return true;
274 				}
275 			}
276 		}
277 
278 		return false;
279 	}
280 
281 	public void setSeparatorVisible(boolean separatorVisible) {
282 		if (separatorVisible) {
283 			root.addStyleName("v-context-menu-item-separator");
284 		} else {
285 			root.removeStyleName("v-context-menu-item-separator");
286 		}
287 	}
288 
289 	@Override
290 	public void setEnabled(boolean enabled) {
291 		super.setEnabled(enabled);
292 		if (enabled) {
293 			root.removeStyleName("v-context-menu-item-disabled");
294 		} else {
295 			root.addStyleName("v-context-menu-item-disabled");
296 		}
297 	}
298 }