View Javadoc
1   package org.vaadin.peter.contextmenu;
2   
3   import java.lang.reflect.Method;
4   import java.util.ArrayList;
5   import java.util.EventListener;
6   import java.util.EventObject;
7   import java.util.HashMap;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.UUID;
11  
12  import org.vaadin.peter.contextmenu.client.ContextMenuClientRpc;
13  import org.vaadin.peter.contextmenu.client.ContextMenuServerRpc;
14  import org.vaadin.peter.contextmenu.client.ContextMenuState;
15  import org.vaadin.peter.contextmenu.client.ContextMenuState.ContextMenuItemState;
16  
17  import com.vaadin.event.ItemClickEvent;
18  import com.vaadin.event.ItemClickEvent.ItemClickListener;
19  import com.vaadin.server.AbstractClientConnector;
20  import com.vaadin.server.AbstractExtension;
21  import com.vaadin.server.Resource;
22  import com.vaadin.shared.MouseEventDetails.MouseButton;
23  import com.vaadin.ui.Component;
24  import com.vaadin.ui.Table;
25  import com.vaadin.ui.Table.FooterClickEvent;
26  import com.vaadin.ui.Table.FooterClickListener;
27  import com.vaadin.ui.Table.HeaderClickEvent;
28  import com.vaadin.ui.Table.HeaderClickListener;
29  import com.vaadin.ui.Tree;
30  import com.vaadin.ui.UI;
31  import com.vaadin.util.ReflectTools;
32  
33  public class ContextMenu extends AbstractExtension {
34  	private static final long serialVersionUID = 4275181115413786498L;
35  
36  	private Map<String, ContextMenuItem> items;
37  
38  	private ContextMenuServerRpc serverRPC = new ContextMenuServerRpc() {
39  		private static final long serialVersionUID = 5622864428554337992L;
40  
41  		@Override
42  		public void itemClicked(String itemId, boolean menuClosed) {
43  			ContextMenuItem item = items.get(itemId);
44  
45  			item.notifyClickListeners();
46  			fireEvent(new ContextMenuItemClickEvent(item));
47  		}
48  
49  		@Override
50  		public void onContextMenuOpenRequested(int x, int y, String connectorId) {
51  			fireEvent(new ContextMenuOpenedOnComponentEvent(ContextMenu.this,
52  					x, y, (Component) UI.getCurrent().getConnectorTracker()
53  							.getConnector(connectorId)));
54  		}
55  
56          @Override
57          public void contextMenuClosed() {
58              fireEvent(new ContextMenuClosedEvent(ContextMenu.this));
59          }
60      };
61  
62  	public ContextMenu() {
63  		registerRpc(serverRPC);
64  
65  		items = new HashMap<String, ContextMenu.ContextMenuItem>();
66  
67  		setOpenAutomatically(true);
68  	}
69  
70  	protected String getNextId() {
71  		return UUID.randomUUID().toString();
72  	}
73  
74  	/**
75  	 * Enables or disables open automatically feature. If open automatically is
76  	 * on, it means that context menu will always be opened when it's host
77  	 * component is right clicked. This will happen on client side without
78  	 * server round trip. If automatic opening is turned off, context menu will
79  	 * only open when server side open(x, y) is called. If automatic opening is
80  	 * disabled you will need a listener implementation for context menu that is
81  	 * called upon client side click event. Another option is to extend context
82  	 * menu and handle the right clicking internally with case specific listener
83  	 * implementation and inside it call open(x, y) method.
84  	 * 
85  	 * @param openAutomatically
86  	 */
87  	public void setOpenAutomatically(boolean openAutomatically) {
88  		getState().setOpenAutomatically(openAutomatically);
89  	}
90  
91  	/**
92  	 * Adds new item to context menu root with given caption.
93  	 * 
94  	 * @param caption
95  	 * @return reference to newly added item
96  	 */
97  	public ContextMenuItem addItem(String caption) {
98  		ContextMenuItemState itemState = getState().addChild(caption,
99  				getNextId());
100 
101 		ContextMenuItem item = new ContextMenuItem(itemState);
102 		items.put(itemState.id, item);
103 
104 		return item;
105 	}
106 
107 	/**
108 	 * Adds new item to context menu root with given icon without caption.
109 	 * 
110 	 * @param icon
111 	 * @return reference to newly added item
112 	 */
113 	public ContextMenuItem addItem(Resource icon) {
114 		ContextMenuItem item = addItem("");
115 		item.setIcon(icon);
116 		return item;
117 	}
118 
119 	/**
120 	 * Adds new item to context menu root with given caption and icon.
121 	 * 
122 	 * @param caption
123 	 * @param icon
124 	 * @return reference to newly added item
125 	 */
126 	public ContextMenuItem addItem(String caption, Resource icon) {
127 		ContextMenuItem item = addItem(caption);
128 		item.setIcon(icon);
129 		return item;
130 	}
131 
132 	/**
133 	 * Removes given root item from the context menu
134 	 * 
135 	 * @param contextMenuItem
136 	 */
137 	public void removeRootItem(ContextMenuItem contextMenuItem) {
138 		items.remove(contextMenuItem);
139 		getState().getRootItems().remove(contextMenuItem);
140 	}
141 
142 	/**
143 	 * Removes all items from the context menu.
144 	 */
145 	public void removeAllItems() {
146 		items.clear();
147 		getState().getRootItems().clear();
148 	}
149 
150 	/**
151 	 * Assigns this as the context menu of given table.
152 	 * 
153 	 * @param table
154 	 */
155 	public void setAsTableContextMenu(final Table table) {
156 		extend(table);
157 
158 		table.addItemClickListener(new ItemClickEvent.ItemClickListener() {
159 			private static final long serialVersionUID = -348059189217149508L;
160 
161 			@Override
162 			public void itemClick(ItemClickEvent event) {
163 				if (event.getButton() == MouseButton.RIGHT) {
164 					fireEvent(new ContextMenuOpenedOnTableRowEvent(
165 							ContextMenu.this, table, event.getItemId(), event
166 									.getPropertyId()));
167 					open(event.getClientX(), event.getClientY());
168 				}
169 			}
170 		});
171 
172 		table.addHeaderClickListener(new HeaderClickListener() {
173 			private static final long serialVersionUID = -5880755689414670581L;
174 
175 			@Override
176 			public void headerClick(HeaderClickEvent event) {
177 				if (event.getButton() == MouseButton.RIGHT) {
178 					fireEvent(new ContextMenuOpenedOnTableHeaderEvent(
179 							ContextMenu.this, table, event.getPropertyId()));
180 					open(event.getClientX(), event.getClientY());
181 				}
182 			}
183 		});
184 
185 		table.addFooterClickListener(new FooterClickListener() {
186 			private static final long serialVersionUID = 2884227013964132482L;
187 
188 			@Override
189 			public void footerClick(FooterClickEvent event) {
190 				if (event.getButton() == MouseButton.RIGHT) {
191 					fireEvent(new ContextMenuOpenedOnTableHeaderEvent(
192 							ContextMenu.this, table, event.getPropertyId()));
193 					open(event.getClientX(), event.getClientY());
194 				}
195 			}
196 		});
197 	}
198 
199 	/**
200 	 * Assigns this as context menu of given tree.
201 	 * 
202 	 * @param tree
203 	 */
204 	public void setAsTreeContextMenu(final Tree tree) {
205 		extend(tree);
206 
207 		tree.addItemClickListener(new ItemClickListener() {
208 			private static final long serialVersionUID = 338499886052623304L;
209 
210 			@Override
211 			public void itemClick(ItemClickEvent event) {
212 				if (event.getButton() == MouseButton.RIGHT) {
213 					fireEvent(new ContextMenuOpenedOnTreeItemEvent(
214 							ContextMenu.this, tree, event.getItemId()));
215 					open(event.getClientX(), event.getClientY());
216 				}
217 			}
218 		});
219 	}
220 
221 	/**
222 	 * Assigns this as context menu of given component which will react to right
223 	 * mouse button click.
224 	 * 
225 	 * @param component
226 	 */
227 	public void setAsContextMenuOf(AbstractClientConnector component) {
228 		if (component instanceof Table) {
229 			setAsTableContextMenu((Table) component);
230 		} else if (component instanceof Tree) {
231 			setAsTreeContextMenu((Tree) component);
232 		} else {
233 			super.extend(component);
234 		}
235 	}
236 
237 	/**
238 	 * Opens the context menu to given coordinates. ContextMenu must extend
239 	 * component before calling this method. This method is only intended for
240 	 * opening the context menu from server side. Using this method is not
241 	 * recommended and should be used only when there is no reasonable way for
242 	 * listeners to detect that menu was otherwise opened.
243 	 * 
244 	 * @param x
245 	 * @param y
246 	 */
247 	public void open(int x, int y) {
248 		getRpcProxy(ContextMenuClientRpc.class).showContextMenu(x, y);
249 	}
250 
251 	@Override
252 	protected ContextMenuState getState() {
253 		return (ContextMenuState) super.getState();
254 	}
255 
256 	/**
257 	 * Adds click listener to context menu. This listener will be invoked when
258 	 * any of the menu items in this menu are clicked.
259 	 * 
260 	 * @param clickListener
261 	 */
262 	public void addItemClickListener(
263 			ContextMenu.ContextMenuItemClickListener clickListener) {
264 		addListener(ContextMenuItemClickEvent.class, clickListener,
265 				ContextMenuItemClickListener.ITEM_CLICK_METHOD);
266 	}
267 
268 	/**
269 	 * Adds listener that will be invoked when context menu is opened from
270 	 * com.vaadin.ui.Table component.
271 	 * 
272 	 * @param contextMenuTableListener
273 	 */
274 	public void addContextMenuTableListener(
275 			ContextMenu.ContextMenuOpenedListener.TableListener contextMenuTableListener) {
276 		addListener(
277 				ContextMenuOpenedOnTableRowEvent.class,
278 				contextMenuTableListener,
279 				ContextMenuOpenedListener.TableListener.MENU_OPENED_FROM_TABLE_ROW_METHOD);
280 		addListener(
281 				ContextMenuOpenedOnTableHeaderEvent.class,
282 				contextMenuTableListener,
283 				ContextMenuOpenedListener.TableListener.MENU_OPENED_FROM_TABLE_HEADER_METHOD);
284 		addListener(
285 				ContextMenuOpenedOnTableFooterEvent.class,
286 				contextMenuTableListener,
287 				ContextMenuOpenedListener.TableListener.MENU_OPENED_FROM_TABLE_FOOTER_METHOD);
288 	}
289 
290 	/**
291 	 * Adds listener that will be invoked when context menu is openef from
292 	 * com.vaadin.ui.Tree component.
293 	 * 
294 	 * @param contextMenuTreeListener
295 	 */
296 	public void addContextMenuTreeListener(
297 			ContextMenu.ContextMenuOpenedListener.TreeListener contextMenuTreeListener) {
298 		addListener(
299 				ContextMenuOpenedOnTreeItemEvent.class,
300 				contextMenuTreeListener,
301 				ContextMenuOpenedListener.TreeListener.MENU_OPENED_FROM_TREE_ITEM_METHOD);
302 	}
303 
304 	/**
305 	 * Adds listener that will be invoked when context menu is closed.
306 	 * 
307 	 * @param contextMenuClosedListener
308 	 */
309 	public void addContextMenuCloseListener(
310 			ContextMenuClosedListener contextMenuClosedListener) {
311 		addListener(
312 				ContextMenuClosedEvent.class,
313 				contextMenuClosedListener,
314 				ContextMenuClosedListener.MENU_CLOSED);
315 	}
316 
317 	/**
318 	 * Adds listener that will be invoked when context menu is opened from the
319 	 * component to which it's assigned to.
320 	 *
321 	 * @param contextMenuComponentListener
322 	 */
323 	public void addContextMenuComponentListener(
324 			ContextMenu.ContextMenuOpenedListener.ComponentListener contextMenuComponentListener) {
325 		addListener(
326 				ContextMenuOpenedOnComponentEvent.class,
327 				contextMenuComponentListener,
328 				ContextMenuOpenedListener.ComponentListener.MENU_OPENED_FROM_COMPONENT);
329 	}
330 
331 	/**
332 	 * ContextMenuItem represents one clickable item in the context menu. Item
333 	 * may have sub items.
334 	 * 
335 	 * @author Peter Lehto / Vaadin Ltd
336 	 * 
337 	 */
338 	public class ContextMenuItem {
339 		private final ContextMenuItemState state;
340 
341 		private List<ContextMenu.ContextMenuItemClickListener> clickListeners;
342 
343 		private Object data;
344 
345 		protected ContextMenuItem(ContextMenuItemState itemState) {
346 			if (itemState == null) {
347 				throw new NullPointerException(
348 						"Context menu item state must not be null");
349 			}
350 
351 			clickListeners = new ArrayList<ContextMenu.ContextMenuItemClickListener>();
352 			this.state = itemState;
353 		}
354 
355 		protected void notifyClickListeners() {
356 			for (ContextMenu.ContextMenuItemClickListener clickListener : clickListeners) {
357 				clickListener
358 						.contextMenuItemClicked(new ContextMenuItemClickEvent(
359 								this));
360 			}
361 		}
362 
363 		/**
364 		 * Associates given object with this menu item. Given object can be
365 		 * whatever application specific if necessary.
366 		 * 
367 		 * @param data
368 		 */
369 		public void setData(Object data) {
370 			this.data = data;
371 		}
372 
373 		/**
374 		 * @return Object associated with ContextMenuItem.
375 		 */
376 		public Object getData() {
377 			return data;
378 		}
379 
380 		/**
381 		 * Adds new item as this item's sub item with given caption
382 		 * 
383 		 * @param caption
384 		 * @return reference to newly created item.
385 		 */
386 		public ContextMenuItem addItem(String caption) {
387 			ContextMenuItemState childItemState = state.addChild(caption,
388 					getNextId());
389 			ContextMenuItem item = new ContextMenuItem(childItemState);
390 
391 			items.put(childItemState.id, item);
392 			markAsDirty();
393 			return item;
394 		}
395 
396 		/**
397 		 * Adds new item as this item's sub item with given icon
398 		 * 
399 		 * @param icon
400 		 * @return reference to newly added item
401 		 */
402 		public ContextMenuItem addItem(Resource icon) {
403 			ContextMenuItem item = this.addItem("");
404 			item.setIcon(icon);
405 
406 			return item;
407 		}
408 
409 		/**
410 		 * Adds new item as this item's sub item with given caption and icon
411 		 * 
412 		 * @param caption
413 		 * @param icon
414 		 * @return reference to newly added item
415 		 */
416 		public ContextMenuItem addItem(String caption, Resource icon) {
417 			ContextMenuItem item = this.addItem(caption);
418 			item.setIcon(icon);
419 
420 			return item;
421 		}
422 
423 		/**
424 		 * Sets given resource as icon of this menu item.
425 		 * 
426 		 * @param icon
427 		 */
428 		public void setIcon(Resource icon) {
429 			setResource(state.id, icon);
430 		}
431 
432 		/**
433 		 * @return current icon
434 		 */
435 		public Resource getIcon() {
436 			return getResource(state.id);
437 		}
438 		
439 		/**
440 		 * Sets or disables separator line under this item
441 		 * 
442 		 * @param visible
443 		 */
444 		public void setSeparatorVisible(boolean separatorVisible) {
445 			state.separator = separatorVisible;
446 		}
447 
448 		/**
449 		 * @return true if separator line is visible after this item,
450 		 *         false otherwise
451 		 */
452 		public boolean hasSeparator() {
453 			return state.separator;
454 		}
455 
456 		/**
457 		 * Enables or disables this menu item
458 		 * 
459 		 * @param enabled
460 		 */
461 		public void setEnabled(boolean enabled) {
462 			state.enabled = enabled;
463 		}
464 
465 		/**
466 		 * @return true if menu item is enabled, false otherwise
467 		 */
468 		public boolean isEnabled() {
469 			return state.enabled;
470 		}
471 		
472 		/**
473 		 * @return true if this menu item has a sub menu
474 		 */
475 		public boolean hasSubMenu() {
476 			return state.getChildren().size() > 0;
477 		}
478 
479 		/**
480 		 * Adds context menu item click listener only to this item. This
481 		 * listener will be invoked only when this item is clicked.
482 		 * 
483 		 * @param clickListener
484 		 */
485 		public void addItemClickListener(
486 				ContextMenu.ContextMenuItemClickListener clickListener) {
487 			this.clickListeners.add(clickListener);
488 		}
489 
490 		/**
491 		 * Removes given click listener from this item. Removing listener
492 		 * affects only this context menu item.
493 		 * 
494 		 * @param clickListener
495 		 */
496 		public void removeItemClickListener(
497 				ContextMenu.ContextMenuItemClickListener clickListener) {
498 			this.clickListeners.remove(clickListener);
499 		}
500 
501 		@Override
502 		public boolean equals(Object other) {
503 			if (this == other) {
504 				return true;
505 			}
506 
507 			if (other instanceof ContextMenuItem) {
508 				return state.id.equals(((ContextMenuItem) other).state.id);
509 			}
510 
511 			return false;
512 		}
513 
514 		@Override
515 		public int hashCode() {
516 			return state.id.hashCode();
517 		}
518 	}
519 
520 	/**
521 	 * ContextMenuItemClickListener is listener for context menu items wanting
522 	 * to notify listeners about item click
523 	 */
524 	public interface ContextMenuItemClickListener extends EventListener {
525 
526 		public static final Method ITEM_CLICK_METHOD = ReflectTools.findMethod(
527 				ContextMenuItemClickListener.class, "contextMenuItemClicked",
528 				ContextMenuItemClickEvent.class);
529 
530 		/**
531 		 * Called by the context menu item when it's clicked
532 		 * 
533 		 * @param event
534 		 *            containing the information of which item was clicked
535 		 */
536 		public void contextMenuItemClicked(ContextMenuItemClickEvent event);
537 	}
538 
539 	/**
540 	 * ContextMenuItemClickEvent is an event produced by the context menu item
541 	 * when it is clicked. Event contains method for retrieving the clicked item
542 	 * and menu from which the click event originated.
543 	 */
544 	public static class ContextMenuItemClickEvent extends EventObject {
545 		private static final long serialVersionUID = -3301204853129409248L;
546 
547 		public ContextMenuItemClickEvent(Object component) {
548 			super(component);
549 		}
550 	}
551 
552     /**
553      * ContextMenuClosedListener is used to listen for the event that the context menu is closed, either
554      * when a item is clicked or when the popup is canceled.
555      */
556     public interface ContextMenuClosedListener extends EventListener {
557         public static final Method MENU_CLOSED = ReflectTools
558         					.findMethod(ContextMenuClosedListener.class,
559                                 "onContextMenuClosed",
560                                 ContextMenuClosedEvent.class);
561 
562         /**
563          * Called when the context menu is closed
564          * @param event
565          */
566         public void onContextMenuClosed(ContextMenuClosedEvent event);
567     }
568 
569     /**
570    	 * ContextMenuClosedEvent is an event fired by the context menu
571    	 * when it's closed.
572    	 */
573    	public static class ContextMenuClosedEvent extends EventObject {
574    		private static final long serialVersionUID = -5705205542849351984L;
575 
576    		private ContextMenu contextMenu;
577 
578    		public ContextMenuClosedEvent(ContextMenu contextMenu) {
579    			super(contextMenu);
580    			this.contextMenu = contextMenu;
581    		}
582 
583    		public ContextMenu getContextMenu() {
584    			return contextMenu;
585    		}
586    	}
587 
588 
589 	/**
590 	 * ContextMenuOpenedListener is used to modify the content of context menu
591 	 * based on what was clicked. For example TableListener can be used to
592 	 * modify context menu based on certain table component clicks.
593 	 * 
594 	 * @author Peter Lehto / Vaadin Ltd
595 	 * 
596 	 */
597 	public interface ContextMenuOpenedListener extends EventListener {
598 
599 		public interface ComponentListener extends ContextMenuOpenedListener {
600 
601 			public static final Method MENU_OPENED_FROM_COMPONENT = ReflectTools
602 					.findMethod(
603 							ContextMenuOpenedListener.ComponentListener.class,
604 							"onContextMenuOpenFromComponent",
605 							ContextMenuOpenedOnComponentEvent.class);
606 
607 			/**
608 			 * Called by the context menu when it's opened by clicking on
609 			 * component.
610 			 * 
611 			 * @param event
612 			 */
613 			public void onContextMenuOpenFromComponent(
614 					ContextMenuOpenedOnComponentEvent event);
615 		}
616 
617 		/**
618 		 * ContextMenuOpenedListener.TableListener sub interface for table
619 		 * related context menus
620 		 * 
621 		 * @author Peter Lehto / Vaadin Ltd
622 		 */
623 		public interface TableListener extends ContextMenuOpenedListener {
624 
625 			public static final Method MENU_OPENED_FROM_TABLE_ROW_METHOD = ReflectTools
626 					.findMethod(ContextMenuOpenedListener.TableListener.class,
627 							"onContextMenuOpenFromRow",
628 							ContextMenuOpenedOnTableRowEvent.class);
629 
630 			public static final Method MENU_OPENED_FROM_TABLE_HEADER_METHOD = ReflectTools
631 					.findMethod(ContextMenuOpenedListener.TableListener.class,
632 							"onContextMenuOpenFromHeader",
633 							ContextMenuOpenedOnTableHeaderEvent.class);
634 
635 			public static final Method MENU_OPENED_FROM_TABLE_FOOTER_METHOD = ReflectTools
636 					.findMethod(ContextMenuOpenedListener.TableListener.class,
637 							"onContextMenuOpenFromFooter",
638 							ContextMenuOpenedOnTableFooterEvent.class);
639 
640 			/**
641 			 * Called by the context menu when it's opened by clicking table
642 			 * component's row
643 			 * 
644 			 * @param event
645 			 */
646 			public void onContextMenuOpenFromRow(
647 					ContextMenuOpenedOnTableRowEvent event);
648 
649 			/**
650 			 * Called by the context menu when it's opened by clicking table
651 			 * component's header
652 			 * 
653 			 * @param event
654 			 */
655 			public void onContextMenuOpenFromHeader(
656 					ContextMenuOpenedOnTableHeaderEvent event);
657 
658 			/**
659 			 * Called by the context menu when it's opened by clicking table
660 			 * component's footer
661 			 * 
662 			 * @param event
663 			 */
664 			public void onContextMenuOpenFromFooter(
665 					ContextMenuOpenedOnTableFooterEvent event);
666 		}
667 
668 		public interface TreeListener extends ContextMenuOpenedListener {
669 			public static final Method MENU_OPENED_FROM_TREE_ITEM_METHOD = ReflectTools
670 					.findMethod(ContextMenuOpenedListener.TreeListener.class,
671 							"onContextMenuOpenFromTreeItem",
672 							ContextMenuOpenedOnTreeItemEvent.class);
673 
674 			/**
675 			 * Called by the context menu when it's opened by clicking item on a
676 			 * tree.
677 			 * 
678 			 * @param event
679 			 */
680 			public void onContextMenuOpenFromTreeItem(
681 					ContextMenuOpenedOnTreeItemEvent event);
682 		}
683 
684 	}
685 
686 	/**
687 	 * ContextMenuOpenedOnTreeItemEvent is an event fired by the context menu
688 	 * when it's opened by clicking on tree item.
689 	 */
690 	public static class ContextMenuOpenedOnTreeItemEvent extends EventObject {
691 		private static final long serialVersionUID = -7705205542849351984L;
692 
693 		private Object itemId;
694 		private ContextMenu contextMenu;
695 
696 		public ContextMenuOpenedOnTreeItemEvent(ContextMenu contextMenu,
697 				Tree tree, Object itemId) {
698 			super(tree);
699 
700 			this.contextMenu = contextMenu;
701 			this.itemId = itemId;
702 		}
703 
704 		public ContextMenu getContextMenu() {
705 			return contextMenu;
706 		}
707 
708 		public Object getItemId() {
709 			return itemId;
710 		}
711 	}
712 
713 	/**
714 	 * ContextMenuOpenedOnTableHeaderEvent is an event fired by the context menu
715 	 * when it's opened by clicking on table header row.
716 	 */
717 	public static class ContextMenuOpenedOnTableHeaderEvent extends EventObject {
718 		private static final long serialVersionUID = -1220618848356241248L;
719 
720 		private Object propertyId;
721 
722 		private ContextMenu contextMenu;
723 
724 		public ContextMenuOpenedOnTableHeaderEvent(ContextMenu contextMenu,
725 				Table source, Object propertyId) {
726 			super(source);
727 
728 			this.contextMenu = contextMenu;
729 			this.propertyId = propertyId;
730 		}
731 
732 		public ContextMenu getContextMenu() {
733 			return contextMenu;
734 		}
735 
736 		public Object getPropertyId() {
737 			return propertyId;
738 		}
739 	}
740 
741 	/**
742 	 * ContextMenuOpenedOnTableFooterEvent is an event that is fired by the
743 	 * context menu when it's opened by clicking on table footer
744 	 */
745 	public static class ContextMenuOpenedOnTableFooterEvent extends EventObject {
746 		private static final long serialVersionUID = 1999781663913723438L;
747 
748 		private Object propertyId;
749 
750 		private ContextMenu contextMenu;
751 
752 		public ContextMenuOpenedOnTableFooterEvent(ContextMenu contextMenu,
753 				Table source, Object propertyId) {
754 			super(source);
755 
756 			this.contextMenu = contextMenu;
757 			this.propertyId = propertyId;
758 		}
759 
760 		public ContextMenu getContextMenu() {
761 			return contextMenu;
762 		}
763 
764 		public Object getPropertyId() {
765 			return propertyId;
766 		}
767 	}
768 
769 	/**
770 	 * ContextMenuOpenedOnTableRowEvent is an event that is fired when context
771 	 * menu is opened by clicking on table row.
772 	 */
773 	public static class ContextMenuOpenedOnTableRowEvent extends EventObject {
774 		private static final long serialVersionUID = -470218301318358912L;
775 
776 		private ContextMenu contextMenu;
777 		private Object propertyId;
778 		private Object itemId;
779 
780 		public ContextMenuOpenedOnTableRowEvent(ContextMenu contextMenu,
781 				Table table, Object itemId, Object propertyId) {
782 			super(table);
783 
784 			this.contextMenu = contextMenu;
785 			this.itemId = itemId;
786 			this.propertyId = propertyId;
787 		}
788 
789 		public ContextMenu getContextMenu() {
790 			return contextMenu;
791 		}
792 
793 		public Object getItemId() {
794 			return itemId;
795 		}
796 
797 		public Object getPropertyId() {
798 			return propertyId;
799 		}
800 	}
801 
802 	/**
803 	 * ContextMenuOpenedOnComponentEvent is an event fired by the context menu
804 	 * when it's opened from a component
805 	 * 
806 	 */
807 	public static class ContextMenuOpenedOnComponentEvent extends EventObject {
808 		private static final long serialVersionUID = 947108059398706966L;
809 
810 		private final ContextMenu contextMenu;
811 
812 		private final int x;
813 		private final int y;
814 
815 		public ContextMenuOpenedOnComponentEvent(ContextMenu contextMenu,
816 				int x, int y, Component component) {
817 			super(component);
818 
819 			this.contextMenu = contextMenu;
820 			this.x = x;
821 			this.y = y;
822 		}
823 
824 		/**
825 		 * @return ContextMenu that was opened.
826 		 */
827 		public ContextMenu getContextMenu() {
828 			return contextMenu;
829 		}
830 
831 		/**
832 		 * @return x-coordinate of open position.
833 		 */
834 		public int getX() {
835 			return x;
836 		}
837 
838 		/**
839 		 * @return y-coordinate of open position.
840 		 */
841 		public int getY() {
842 			return y;
843 		}
844 	}
845 }