001 002package org.opencms.ui.contextmenu; 003 004import org.opencms.ui.CmsVaadinUtils; 005import org.opencms.ui.shared.CmsContextMenuState; 006import org.opencms.ui.shared.CmsContextMenuState.ContextMenuItemState; 007import org.opencms.ui.shared.rpc.I_CmsContextMenuClientRpc; 008import org.opencms.ui.shared.rpc.I_CmsContextMenuServerRpc; 009 010import java.io.Serializable; 011import java.lang.reflect.Method; 012import java.util.ArrayList; 013import java.util.Collection; 014import java.util.EventListener; 015import java.util.EventObject; 016import java.util.HashMap; 017import java.util.HashSet; 018import java.util.List; 019import java.util.Locale; 020import java.util.Map; 021import java.util.Set; 022import java.util.StringTokenizer; 023import java.util.UUID; 024 025import com.vaadin.v7.event.ItemClickEvent; 026import com.vaadin.event.MouseEvents.ClickEvent; 027import com.vaadin.server.AbstractClientConnector; 028import com.vaadin.server.AbstractExtension; 029import com.vaadin.server.Resource; 030import com.vaadin.ui.Component; 031import com.vaadin.v7.ui.Table; 032import com.vaadin.v7.ui.Tree; 033import com.vaadin.ui.UI; 034import com.vaadin.util.ReflectTools; 035 036/** 037 * ContextMenu is an extension which can be attached to any Vaadin component to 038 * display a popup context menu. Most useful the menu is when attached for 039 * example to Tree or Table which support item and property based context menu 040 * detection.<p> 041 * 042 * Adapted from ContextMenu by Peter Lehto / Vaadin Ltd.<p> 043 */ 044public class CmsContextMenu extends AbstractExtension { 045 046 /** 047 * ContextMenuClosedEvent is an event fired by the context menu when it's 048 * closed. 049 */ 050 public static class ContextMenuClosedEvent extends EventObject { 051 052 /** The serial version id. */ 053 private static final long serialVersionUID = -5705205542849351984L; 054 055 /** The context menu. */ 056 private final CmsContextMenu m_contextMenu; 057 058 /** 059 * Constructor.<p> 060 * 061 * @param contextMenu the context menu 062 */ 063 public ContextMenuClosedEvent(CmsContextMenu contextMenu) { 064 super(contextMenu); 065 m_contextMenu = contextMenu; 066 } 067 068 /** 069 * Returns the context menu.<p> 070 * 071 * @return the menu 072 */ 073 public CmsContextMenu getContextMenu() { 074 075 return m_contextMenu; 076 } 077 } 078 079 /** 080 * ContextMenuClosedListener is used to listen for the event that the 081 * context menu is closed, either when a item is clicked or when the popup 082 * is canceled.<p> 083 */ 084 public interface ContextMenuClosedListener extends EventListener { 085 086 /** The menu closed method. */ 087 public static final Method MENU_CLOSED = ReflectTools.findMethod( 088 ContextMenuClosedListener.class, 089 "onContextMenuClosed", 090 ContextMenuClosedEvent.class); 091 092 /** 093 * Called when the context menu is closed.<p> 094 * 095 * @param event the event 096 */ 097 public void onContextMenuClosed(ContextMenuClosedEvent event); 098 } 099 100 /** 101 * ContextMenuItem represents one clickable item in the context menu. Item may have sub items.<p> 102 */ 103 public class ContextMenuItem implements Serializable { 104 105 /** The serial version id. */ 106 private static final long serialVersionUID = -6514832427611690050L; 107 108 /** The item state. */ 109 final ContextMenuItemState m_state; 110 111 /** The item click listeners. */ 112 private final List<CmsContextMenu.ContextMenuItemClickListener> m_clickListeners; 113 114 /** The item data. */ 115 private Object m_data; 116 117 /** The parent item. */ 118 private ContextMenuItem m_parent; 119 120 /** 121 * Constructor.<p> 122 * 123 * @param parent the parent item 124 * @param itemState the item state 125 */ 126 protected ContextMenuItem(ContextMenuItem parent, ContextMenuItemState itemState) { 127 m_parent = parent; 128 129 if (itemState == null) { 130 throw new NullPointerException("Context menu item state must not be null"); 131 } 132 133 m_clickListeners = new ArrayList<CmsContextMenu.ContextMenuItemClickListener>(); 134 m_state = itemState; 135 } 136 137 /** 138 * Adds new item as this item's sub item with given icon.<p> 139 * 140 * @param icon the icon 141 * 142 * @return reference to newly added item 143 */ 144 public ContextMenuItem addItem(Resource icon) { 145 146 ContextMenuItem item = this.addItem(""); 147 item.setIcon(icon); 148 149 return item; 150 } 151 152 /** 153 * Adds new item as this item's sub item with given caption.<p> 154 * 155 * @param caption the caption 156 * @return reference to newly created item. 157 */ 158 public ContextMenuItem addItem(String caption) { 159 160 ContextMenuItemState childItemState = m_state.addChild(caption, getNextId()); 161 ContextMenuItem item = new ContextMenuItem(this, childItemState); 162 163 m_items.put(childItemState.getId(), item); 164 markAsDirty(); 165 return item; 166 } 167 168 /** 169 * Adds new item as this item's sub item with given caption and icon.<p> 170 * 171 * @param caption the caption 172 * @param icon the icon 173 * @return reference to newly added item 174 */ 175 public ContextMenuItem addItem(String caption, Resource icon) { 176 177 ContextMenuItem item = this.addItem(caption); 178 item.setIcon(icon); 179 180 return item; 181 } 182 183 /** 184 * Adds context menu item click listener only to this item. This 185 * listener will be invoked only when this item is clicked.<p> 186 * 187 * @param clickListener the click listener 188 */ 189 public void addItemClickListener(CmsContextMenu.ContextMenuItemClickListener clickListener) { 190 191 this.m_clickListeners.add(clickListener); 192 } 193 194 /** 195 * Add a new style to the menu item. This method is following the same 196 * semantics as {@link Component#addStyleName(String)}.<p> 197 * 198 * @param style 199 * the new style to be added to the component 200 */ 201 public void addStyleName(String style) { 202 203 if ((style == null) || style.isEmpty()) { 204 return; 205 } 206 if (style.contains(" ")) { 207 // Split space separated style names and add them one by one. 208 StringTokenizer tokenizer = new StringTokenizer(style, " "); 209 while (tokenizer.hasMoreTokens()) { 210 addStyleName(tokenizer.nextToken()); 211 } 212 return; 213 } 214 215 m_state.getStyles().add(style); 216 markAsDirty(); 217 } 218 219 /** 220 * @see java.lang.Object#equals(java.lang.Object) 221 */ 222 @Override 223 public boolean equals(Object other) { 224 225 if (this == other) { 226 return true; 227 } 228 229 if (other instanceof ContextMenuItem) { 230 return m_state.getId().equals(((ContextMenuItem)other).m_state.getId()); 231 } 232 233 return false; 234 } 235 236 /** 237 * Returns the item data.<p> 238 * 239 * @return Object associated with ContextMenuItem. 240 */ 241 public Object getData() { 242 243 return m_data; 244 } 245 246 /** 247 * Returns the item description.<p> 248 * 249 * @return the description 250 */ 251 public String getDescription() { 252 253 return m_state.getDescription(); 254 } 255 256 /** 257 * Returns the icon.<p> 258 * 259 * @return current icon 260 */ 261 public Resource getIcon() { 262 263 return getResource(m_state.getId()); 264 } 265 266 /** 267 * @see java.lang.Object#hashCode() 268 */ 269 @Override 270 public int hashCode() { 271 272 return m_state.getId().hashCode(); 273 } 274 275 /** 276 * Returns whether the item has a separator.<p> 277 * 278 * @return <code>true</code> if separator line is visible after this item 279 */ 280 public boolean hasSeparator() { 281 282 return m_state.isSeparator(); 283 } 284 285 /** 286 * Returns whether the item has a sub menu.<p> 287 * 288 * @return <code>true</code> if this menu item has a sub menu 289 */ 290 public boolean hasSubMenu() { 291 292 return m_state.getChildren().size() > 0; 293 } 294 295 /** 296 * Returns if the item is enabled.<p> 297 * @return <code>true</code> if menu item is enabled 298 */ 299 public boolean isEnabled() { 300 301 return m_state.isEnabled(); 302 } 303 304 /** 305 * Returns whether this item is the root item.<p> 306 * 307 * @return <code>true</code> if this item is root item 308 */ 309 public boolean isRootItem() { 310 311 return m_parent == null; 312 } 313 314 /** 315 * Removes given click listener from this item. Removing listener 316 * affects only this context menu item.<p> 317 * 318 * @param clickListener the click listener to remove 319 */ 320 public void removeItemClickListener(CmsContextMenu.ContextMenuItemClickListener clickListener) { 321 322 this.m_clickListeners.remove(clickListener); 323 } 324 325 /** 326 * Remove a style name from this menu item. This method is following the 327 * same semantics as {@link Component#removeStyleName(String)}.<p> 328 * 329 * @param style the style name or style names to be removed 330 */ 331 public void removeStyleName(String style) { 332 333 if (m_state.getStyles().isEmpty()) { 334 return; 335 } 336 337 StringTokenizer tokenizer = new StringTokenizer(style, " "); 338 while (tokenizer.hasMoreTokens()) { 339 m_state.getStyles().remove(tokenizer.nextToken()); 340 } 341 } 342 343 /** 344 * Changes the caption of the menu item.<p> 345 * 346 * @param newCaption the caption 347 */ 348 public void setCaption(String newCaption) { 349 350 m_state.setCaption(newCaption); 351 markAsDirty(); 352 } 353 354 /** 355 * Associates given object with this menu item. Given object can be 356 * whatever application specific if necessary.<p> 357 * 358 * @param data the data 359 */ 360 public void setData(Object data) { 361 362 m_data = data; 363 } 364 365 /** 366 * Sets the item description used as tool-tip.<p> 367 * 368 * @param description the description 369 */ 370 public void setDescription(String description) { 371 372 m_state.setDescription(description); 373 markAsDirty(); 374 } 375 376 /** 377 * Enables or disables this menu item.<p> 378 * 379 * @param enabled the enabled flag 380 */ 381 public void setEnabled(boolean enabled) { 382 383 m_state.setEnabled(enabled); 384 markAsDirty(); 385 } 386 387 /** 388 * Sets given resource as icon of this menu item.<p> 389 * 390 * @param icon the icon 391 */ 392 public void setIcon(Resource icon) { 393 394 setResource(m_state.getId(), icon); 395 } 396 397 /** 398 * Sets or disables separator line under this item.<p> 399 * 400 * @param separatorVisible the visibility flag 401 */ 402 public void setSeparatorVisible(boolean separatorVisible) { 403 404 m_state.setSeparator(separatorVisible); 405 markAsDirty(); 406 } 407 408 /** 409 * Returns the item children.<p> 410 * 411 * @return the children 412 */ 413 protected Set<ContextMenuItem> getAllChildren() { 414 415 Set<ContextMenuItem> children = new HashSet<CmsContextMenu.ContextMenuItem>(); 416 417 for (ContextMenuItemState childState : m_state.getChildren()) { 418 ContextMenuItem child = m_items.get(childState.getId()); 419 children.add(child); 420 children.addAll(child.getAllChildren()); 421 } 422 423 return children; 424 } 425 426 /** 427 * Returns the parent item.<p> 428 * 429 * @return parent item of this menu item. Null if this item is a root item. 430 */ 431 protected ContextMenuItem getParent() { 432 433 return m_parent; 434 } 435 436 /** 437 * Notifies all click listeners.<p> 438 */ 439 protected void notifyClickListeners() { 440 441 for (CmsContextMenu.ContextMenuItemClickListener clickListener : m_clickListeners) { 442 clickListener.contextMenuItemClicked(new ContextMenuItemClickEvent(this)); 443 } 444 } 445 } 446 447 /** 448 * ContextMenuItemClickEvent is an event produced by the context menu item 449 * when it is clicked. Event contains method for retrieving the clicked item 450 * and menu from which the click event originated. 451 */ 452 public static class ContextMenuItemClickEvent extends EventObject { 453 454 /** The serial version id. */ 455 private static final long serialVersionUID = -3301204853129409248L; 456 457 /** 458 * Constructor.<p> 459 * 460 * @param component the component 461 */ 462 public ContextMenuItemClickEvent(Object component) { 463 super(component); 464 } 465 } 466 467 /** 468 * ContextMenuItemClickListener is listener for context menu items wanting 469 * to notify listeners about item click 470 */ 471 public interface ContextMenuItemClickListener extends EventListener { 472 473 /** The item click method. */ 474 public static final Method ITEM_CLICK_METHOD = ReflectTools.findMethod( 475 ContextMenuItemClickListener.class, 476 "contextMenuItemClicked", 477 ContextMenuItemClickEvent.class); 478 479 /** 480 * Called by the context menu item when it's clicked 481 * 482 * @param event 483 * containing the information of which item was clicked 484 */ 485 public void contextMenuItemClicked(ContextMenuItemClickEvent event); 486 } 487 488 /** 489 * ContextMenuOpenedListener is used to modify the content of context menu 490 * based on what was clicked. For example TableListener can be used to 491 * modify context menu based on certain table component clicks.<p> 492 */ 493 public interface ContextMenuOpenedListener extends EventListener { 494 495 /** 496 * ComponentListener is used when context menu is extending a component 497 * and works in mode where auto opening is disabled. For example if 498 * ContextMenu is assigned to a Layout and layout is right clicked when 499 * auto open feature is disabled, the open listener would be called 500 * instead of menu opening automatically. Example usage is for example 501 * as follows:<p> 502 * 503 * event.getContextMenu().open(event.getRequestSourceComponent());<p> 504 */ 505 public interface ComponentListener extends ContextMenuOpenedListener { 506 507 /** The open menu method. */ 508 public static final Method MENU_OPENED_FROM_COMPONENT = ReflectTools.findMethod( 509 ContextMenuOpenedListener.ComponentListener.class, 510 "onContextMenuOpenFromComponent", 511 ContextMenuOpenedOnComponentEvent.class); 512 513 /** 514 * Called by the context menu when it's opened by clicking on 515 * component.<p> 516 * 517 * @param event the event 518 */ 519 public void onContextMenuOpenFromComponent(ContextMenuOpenedOnComponentEvent event); 520 } 521 522 /** 523 * ContextMenuOpenedListener.TableListener sub interface for table 524 * related context menus.<p> 525 */ 526 public interface TableListener extends ContextMenuOpenedListener { 527 528 /** Opened from method. */ 529 public static final Method MENU_OPENED_FROM_TABLE_FOOTER_METHOD = ReflectTools.findMethod( 530 ContextMenuOpenedListener.TableListener.class, 531 "onContextMenuOpenFromFooter", 532 ContextMenuOpenedOnTableFooterEvent.class); 533 534 /** Opened from method. */ 535 public static final Method MENU_OPENED_FROM_TABLE_HEADER_METHOD = ReflectTools.findMethod( 536 ContextMenuOpenedListener.TableListener.class, 537 "onContextMenuOpenFromHeader", 538 ContextMenuOpenedOnTableHeaderEvent.class); 539 540 /** Opened from method. */ 541 public static final Method MENU_OPENED_FROM_TABLE_ROW_METHOD = ReflectTools.findMethod( 542 ContextMenuOpenedListener.TableListener.class, 543 "onContextMenuOpenFromRow", 544 ContextMenuOpenedOnTableRowEvent.class); 545 546 /** 547 * Called by the context menu when it's opened by clicking table 548 * component's footer.<p> 549 * 550 * @param event the event 551 */ 552 public void onContextMenuOpenFromFooter(ContextMenuOpenedOnTableFooterEvent event); 553 554 /** 555 * Called by the context menu when it's opened by clicking table 556 * component's header.<p> 557 * 558 * @param event the event 559 */ 560 public void onContextMenuOpenFromHeader(ContextMenuOpenedOnTableHeaderEvent event); 561 562 /** 563 * Called by the context menu when it's opened by clicking table 564 * component's row.<p> 565 * 566 * @param event the event 567 */ 568 public void onContextMenuOpenFromRow(ContextMenuOpenedOnTableRowEvent event); 569 } 570 571 /** 572 * Tree listener interface.<p> 573 */ 574 public interface TreeListener extends ContextMenuOpenedListener { 575 576 /** Opened from method. */ 577 public static final Method MENU_OPENED_FROM_TREE_ITEM_METHOD = ReflectTools.findMethod( 578 ContextMenuOpenedListener.TreeListener.class, 579 "onContextMenuOpenFromTreeItem", 580 ContextMenuOpenedOnTreeItemEvent.class); 581 582 /** 583 * Called by the context menu when it's opened by clicking item on a 584 * tree.<p> 585 * 586 * @param event the event 587 */ 588 public void onContextMenuOpenFromTreeItem(ContextMenuOpenedOnTreeItemEvent event); 589 } 590 591 } 592 593 /** 594 * ContextMenuOpenedOnComponentEvent is an event fired by the context menu 595 * when it's opened from a component.<p> 596 * 597 */ 598 public static class ContextMenuOpenedOnComponentEvent extends EventObject { 599 600 /** The serial version id. */ 601 private static final long serialVersionUID = 947108059398706966L; 602 603 /** The context menu. */ 604 private final CmsContextMenu m_contextMenu; 605 606 /** The client x position. */ 607 private final int m_x; 608 609 /** The client y position. */ 610 private final int m_y; 611 612 /** 613 * Constructor.<p> 614 * 615 * @param contextMenu the context menu 616 * @param x the client x position 617 * @param y the client y position 618 * @param component the component 619 */ 620 public ContextMenuOpenedOnComponentEvent(CmsContextMenu contextMenu, int x, int y, Component component) { 621 super(component); 622 623 m_contextMenu = contextMenu; 624 m_x = x; 625 m_y = y; 626 } 627 628 /** 629 * Returns the context menu.<p> 630 * 631 * @return ContextMenu that was opened. 632 */ 633 public CmsContextMenu getContextMenu() { 634 635 return m_contextMenu; 636 } 637 638 /** 639 * Returns the source component.<p> 640 * 641 * @return Component which initiated the context menu open request. 642 */ 643 public Component getRequestSourceComponent() { 644 645 return (Component)getSource(); 646 } 647 648 /** 649 * Returns the client x position.<p> 650 * 651 * @return x-coordinate of open position. 652 */ 653 public int getX() { 654 655 return m_x; 656 } 657 658 /** 659 * Returns the client y position.<p> 660 * 661 * @return y-coordinate of open position. 662 */ 663 public int getY() { 664 665 return m_y; 666 } 667 } 668 669 /** 670 * ContextMenuOpenedOnTableFooterEvent is an event that is fired by the 671 * context menu when it's opened by clicking on table footer 672 */ 673 public static class ContextMenuOpenedOnTableFooterEvent extends EventObject { 674 675 /** The serial version id. */ 676 private static final long serialVersionUID = 1999781663913723438L; 677 678 /** The context menu. */ 679 private final CmsContextMenu m_contextMenu; 680 681 /** The property id. */ 682 private final Object m_propertyId; 683 684 /** 685 * Constructor.<p> 686 * 687 * @param contextMenu the context menu 688 * @param sourceTable the source table 689 * @param propertyId the property id 690 */ 691 public ContextMenuOpenedOnTableFooterEvent(CmsContextMenu contextMenu, Table sourceTable, Object propertyId) { 692 super(sourceTable); 693 694 m_contextMenu = contextMenu; 695 m_propertyId = propertyId; 696 } 697 698 /** 699 * Returns the context menu.<p> 700 * 701 * @return the context menu 702 */ 703 public CmsContextMenu getContextMenu() { 704 705 return m_contextMenu; 706 } 707 708 /** 709 * Returns the property id.<p> 710 * 711 * @return the property id 712 */ 713 public Object getPropertyId() { 714 715 return m_propertyId; 716 } 717 } 718 719 /** 720 * ContextMenuOpenedOnTableHeaderEvent is an event fired by the context menu 721 * when it's opened by clicking on table header row.<p> 722 */ 723 public static class ContextMenuOpenedOnTableHeaderEvent extends EventObject { 724 725 /** The serial version id. */ 726 private static final long serialVersionUID = -1220618848356241248L; 727 728 /** The context menu. */ 729 private final CmsContextMenu m_contextMenu; 730 731 /** The property id. */ 732 private final Object m_propertyId; 733 734 /** 735 * Constructor.<p> 736 * 737 * @param contextMenu the context menu 738 * @param sourceTable the source 739 * @param propertyId the property id 740 */ 741 public ContextMenuOpenedOnTableHeaderEvent(CmsContextMenu contextMenu, Table sourceTable, Object propertyId) { 742 super(sourceTable); 743 744 m_contextMenu = contextMenu; 745 m_propertyId = propertyId; 746 } 747 748 /** 749 * Returns the context menu.<p> 750 * 751 * @return the context menu 752 */ 753 public CmsContextMenu getContextMenu() { 754 755 return m_contextMenu; 756 } 757 758 /** 759 * Returns the property id.<p> 760 * 761 * @return the property id 762 */ 763 public Object getPropertyId() { 764 765 return m_propertyId; 766 } 767 } 768 769 /** 770 * ContextMenuOpenedOnTableRowEvent is an event that is fired when context 771 * menu is opened by clicking on table row.<p> 772 */ 773 public static class ContextMenuOpenedOnTableRowEvent extends EventObject { 774 775 /** The serial version id. */ 776 private static final long serialVersionUID = -470218301318358912L; 777 778 /** The context menu. */ 779 private final CmsContextMenu m_contextMenu; 780 781 /** The item id. */ 782 private final Object m_itemId; 783 784 /** The property id. */ 785 private final Object m_propertyId; 786 787 /** 788 * Constructor.<p> 789 * 790 * @param contextMenu the context menu 791 * @param table the table 792 * @param itemId the item id 793 * @param propertyId the property id 794 */ 795 public ContextMenuOpenedOnTableRowEvent( 796 CmsContextMenu contextMenu, 797 Table table, 798 Object itemId, 799 Object propertyId) { 800 super(table); 801 802 m_contextMenu = contextMenu; 803 m_itemId = itemId; 804 m_propertyId = propertyId; 805 } 806 807 /** 808 * Returns the context menu.<p> 809 * 810 * @return the context menu 811 */ 812 public CmsContextMenu getContextMenu() { 813 814 return m_contextMenu; 815 } 816 817 /** 818 * Returns the item id.<p> 819 * 820 * @return the item id 821 */ 822 public Object getItemId() { 823 824 return m_itemId; 825 } 826 827 /** 828 * Returns the property id.<p> 829 * 830 * @return the property id 831 */ 832 public Object getPropertyId() { 833 834 return m_propertyId; 835 } 836 } 837 838 /** 839 * ContextMenuOpenedOnTreeItemEvent is an event fired by the context menu 840 * when it's opened by clicking on tree item.<p> 841 */ 842 public static class ContextMenuOpenedOnTreeItemEvent extends EventObject { 843 844 /** The serial version id. */ 845 private static final long serialVersionUID = -7705205542849351984L; 846 847 /** The context menu. */ 848 private final CmsContextMenu m_contextMenu; 849 850 /** The item id. */ 851 private final Object m_itemId; 852 853 /** 854 * Constructor.<p> 855 * 856 * @param contextMenu the context menu 857 * @param tree the tree 858 * @param itemId the item id 859 */ 860 public ContextMenuOpenedOnTreeItemEvent(CmsContextMenu contextMenu, Tree tree, Object itemId) { 861 super(tree); 862 863 m_contextMenu = contextMenu; 864 m_itemId = itemId; 865 } 866 867 /** 868 * Returns the context menu.<p> 869 * 870 * @return the context menu 871 */ 872 public CmsContextMenu getContextMenu() { 873 874 return m_contextMenu; 875 } 876 877 /** 878 * Returns the item id.<p> 879 * 880 * @return the item id 881 */ 882 public Object getItemId() { 883 884 return m_itemId; 885 } 886 } 887 888 /** The serial version id. */ 889 private static final long serialVersionUID = 4275181115413786498L; 890 891 /** The items. */ 892 final Map<String, ContextMenuItem> m_items; 893 894 /** The server RPC. */ 895 private final I_CmsContextMenuServerRpc m_serverRPC = new I_CmsContextMenuServerRpc() { 896 897 /** The serial version id. */ 898 private static final long serialVersionUID = 5622864428554337992L; 899 900 @Override 901 public void contextMenuClosed() { 902 903 fireEvent(new ContextMenuClosedEvent(CmsContextMenu.this)); 904 } 905 906 @Override 907 public void itemClicked(String itemId, boolean menuClosed) { 908 909 ContextMenuItem item = m_items.get(itemId); 910 if (item == null) { 911 return; 912 } 913 914 item.notifyClickListeners(); 915 fireEvent(new ContextMenuItemClickEvent(item)); 916 } 917 918 @Override 919 public void onContextMenuOpenRequested(int x, int y, String connectorId) { 920 921 fireEvent( 922 new ContextMenuOpenedOnComponentEvent( 923 CmsContextMenu.this, 924 x, 925 y, 926 (Component)UI.getCurrent().getConnectorTracker().getConnector(connectorId))); 927 } 928 }; 929 930 /** 931 * Constructor.<p> 932 */ 933 public CmsContextMenu() { 934 registerRpc(m_serverRPC); 935 936 m_items = new HashMap<String, CmsContextMenu.ContextMenuItem>(); 937 938 setOpenAutomatically(true); 939 setHideAutomatically(true); 940 } 941 942 /** 943 * Adds listener that will be invoked when context menu is closed.<p> 944 * 945 * @param contextMenuClosedListener menu close listener 946 */ 947 public void addContextMenuCloseListener(ContextMenuClosedListener contextMenuClosedListener) { 948 949 addListener(ContextMenuClosedEvent.class, contextMenuClosedListener, ContextMenuClosedListener.MENU_CLOSED); 950 } 951 952 /** 953 * Adds listener that will be invoked when context menu is opened from the 954 * component to which it's assigned to.<p> 955 * 956 * @param contextMenuComponentListener the component listener 957 */ 958 public void addContextMenuComponentListener( 959 CmsContextMenu.ContextMenuOpenedListener.ComponentListener contextMenuComponentListener) { 960 961 addListener( 962 ContextMenuOpenedOnComponentEvent.class, 963 contextMenuComponentListener, 964 ContextMenuOpenedListener.ComponentListener.MENU_OPENED_FROM_COMPONENT); 965 } 966 967 /** 968 * Adds listener that will be invoked when context menu is opened from 969 * com.vaadin.ui.Table component.<p> 970 * 971 * @param contextMenuTableListener the table listener 972 */ 973 public void addContextMenuTableListener( 974 CmsContextMenu.ContextMenuOpenedListener.TableListener contextMenuTableListener) { 975 976 addListener( 977 ContextMenuOpenedOnTableRowEvent.class, 978 contextMenuTableListener, 979 ContextMenuOpenedListener.TableListener.MENU_OPENED_FROM_TABLE_ROW_METHOD); 980 addListener( 981 ContextMenuOpenedOnTableHeaderEvent.class, 982 contextMenuTableListener, 983 ContextMenuOpenedListener.TableListener.MENU_OPENED_FROM_TABLE_HEADER_METHOD); 984 addListener( 985 ContextMenuOpenedOnTableFooterEvent.class, 986 contextMenuTableListener, 987 ContextMenuOpenedListener.TableListener.MENU_OPENED_FROM_TABLE_FOOTER_METHOD); 988 } 989 990 /** 991 * Adds listener that will be invoked when context menu is opened from 992 * com.vaadin.ui.Tree component.<p> 993 * 994 * @param contextMenuTreeListener the menu tree listener 995 */ 996 public void addContextMenuTreeListener( 997 CmsContextMenu.ContextMenuOpenedListener.TreeListener contextMenuTreeListener) { 998 999 addListener( 1000 ContextMenuOpenedOnTreeItemEvent.class, 1001 contextMenuTreeListener, 1002 ContextMenuOpenedListener.TreeListener.MENU_OPENED_FROM_TREE_ITEM_METHOD); 1003 } 1004 1005 /** 1006 * Adds new item to context menu root with given icon without caption.<p> 1007 * 1008 * @param icon the icon 1009 * @return reference to newly added item 1010 */ 1011 public ContextMenuItem addItem(Resource icon) { 1012 1013 ContextMenuItem item = addItem(""); 1014 item.setIcon(icon); 1015 return item; 1016 } 1017 1018 /** 1019 * Adds new item to context menu root with given caption.<p> 1020 * 1021 * @param caption the caption 1022 * @return reference to newly added item 1023 */ 1024 public ContextMenuItem addItem(String caption) { 1025 1026 ContextMenuItemState itemState = getState().addChild(caption, getNextId()); 1027 1028 ContextMenuItem item = new ContextMenuItem(null, itemState); 1029 m_items.put(itemState.getId(), item); 1030 1031 return item; 1032 } 1033 1034 /** 1035 * Adds new item to context menu root with given caption and icon.<p> 1036 * 1037 * @param caption the caption 1038 * @param icon the icon 1039 * @return reference to newly added item 1040 */ 1041 public ContextMenuItem addItem(String caption, Resource icon) { 1042 1043 ContextMenuItem item = addItem(caption); 1044 item.setIcon(icon); 1045 return item; 1046 } 1047 1048 /** 1049 * Adds click listener to context menu. This listener will be invoked when 1050 * any of the menu items in this menu are clicked.<p> 1051 * 1052 * @param clickListener the click listener 1053 */ 1054 public void addItemClickListener(CmsContextMenu.ContextMenuItemClickListener clickListener) { 1055 1056 addListener(ContextMenuItemClickEvent.class, clickListener, ContextMenuItemClickListener.ITEM_CLICK_METHOD); 1057 } 1058 1059 /** 1060 * @see com.vaadin.server.AbstractExtension#extend(com.vaadin.server.AbstractClientConnector) 1061 */ 1062 @Override 1063 public void extend(AbstractClientConnector target) { 1064 1065 super.extend(target); 1066 } 1067 1068 /** 1069 * Closes the context menu from server side.<p> 1070 */ 1071 public void hide() { 1072 1073 getRpcProxy(I_CmsContextMenuClientRpc.class).hide(); 1074 } 1075 1076 /** 1077 * Returns if the menu is set to hide automatically.<p> 1078 * 1079 * @return <code>true</code> if context menu is hiding automatically after clicks 1080 */ 1081 public boolean isHideAutomatically() { 1082 1083 return getState().isHideAutomatically(); 1084 } 1085 1086 /** 1087 * Returns if the menu is set to open automatically.<p> 1088 * 1089 * @return <code>true</code> if open automatically is on. If open automatically is on, it 1090 * means that context menu will always be opened when it's host 1091 * component is right clicked. If automatic opening is turned off, 1092 * context menu will only open when server side open(x, y) is 1093 * called. Automatic opening avoid having to make server roundtrip 1094 * whereas "manual" opening allows to have logic in menu before 1095 * opening it. 1096 */ 1097 public boolean isOpenAutomatically() { 1098 1099 return getState().isOpenAutomatically(); 1100 } 1101 1102 /** 1103 * Opens the menu for the given component.<p> 1104 * 1105 * @param component the component 1106 */ 1107 public void open(Component component) { 1108 1109 getRpcProxy(I_CmsContextMenuClientRpc.class).showContextMenuRelativeTo(component.getConnectorId()); 1110 } 1111 1112 /** 1113 * Opens the context menu to given coordinates. ContextMenu must extend 1114 * component before calling this method. This method is only intended for 1115 * opening the context menu from server side when using 1116 * {@link #ContextMenuOpenedListener.ComponentListener}.<p> 1117 * 1118 * @param x the client x position 1119 * @param y the client y position 1120 */ 1121 @SuppressWarnings("javadoc") 1122 public void open(int x, int y) { 1123 1124 getRpcProxy(I_CmsContextMenuClientRpc.class).showContextMenu(x, y); 1125 } 1126 1127 /** 1128 * Opens the context menu of the given table.<p> 1129 * 1130 * @param event the click event 1131 * @param itemId of clicked item 1132 * @param propertyId of clicked item 1133 * @param table the table 1134 */ 1135 public void openForTable(ClickEvent event, Object itemId, Object propertyId, Table table) { 1136 1137 fireEvent(new ContextMenuOpenedOnTableRowEvent(this, table, itemId, propertyId)); 1138 open(event.getClientX(), event.getClientY()); 1139 1140 } 1141 1142 /** 1143 * Opens the context menu of the given table.<p> 1144 * 1145 * @param event the click event 1146 * @param table the table 1147 */ 1148 public void openForTable(ItemClickEvent event, Table table) { 1149 1150 fireEvent(new ContextMenuOpenedOnTableRowEvent(this, table, event.getItemId(), event.getPropertyId())); 1151 open(event.getClientX(), event.getClientY()); 1152 1153 } 1154 1155 /** 1156 * Opens the context menu of the given tree.<p> 1157 * 1158 * @param event the click event 1159 * @param tree the tree 1160 */ 1161 public void openForTree(ItemClickEvent event, Tree tree) { 1162 1163 fireEvent(new ContextMenuOpenedOnTreeItemEvent(this, tree, event.getItemId())); 1164 open(event.getClientX(), event.getClientY()); 1165 } 1166 1167 /** 1168 * Removes all items from the context menu.<p> 1169 */ 1170 public void removeAllItems() { 1171 1172 m_items.clear(); 1173 getState().getRootItems().clear(); 1174 } 1175 1176 /** 1177 * Removes given context menu item from the context menu. The given item can 1178 * be a root item or leaf item or anything in between. If given given is not 1179 * found from the context menu structure, this method has no effect.<p> 1180 * 1181 * @param contextMenuItem the menu item 1182 */ 1183 public void removeItem(ContextMenuItem contextMenuItem) { 1184 1185 if (!hasMenuItem(contextMenuItem)) { 1186 return; 1187 } 1188 1189 if (contextMenuItem.isRootItem()) { 1190 getState().getRootItems().remove(contextMenuItem.m_state); 1191 } else { 1192 ContextMenuItem parent = contextMenuItem.getParent(); 1193 parent.m_state.getChildren().remove(contextMenuItem.m_state); 1194 } 1195 1196 Set<ContextMenuItem> children = contextMenuItem.getAllChildren(); 1197 1198 m_items.remove(contextMenuItem.m_state.getId()); 1199 1200 for (ContextMenuItem child : children) { 1201 m_items.remove(child.m_state.getId()); 1202 } 1203 1204 markAsDirty(); 1205 } 1206 1207 /** 1208 * Assigns this as context menu of given component which will react to right 1209 * mouse button click.<p> 1210 * 1211 * @param component the component 1212 */ 1213 public void setAsContextMenuOf(AbstractClientConnector component) { 1214 1215 if (component instanceof Table) { 1216 setAsTableContextMenu((Table)component); 1217 } else if (component instanceof Tree) { 1218 setAsTreeContextMenu((Tree)component); 1219 } else { 1220 super.extend(component); 1221 } 1222 } 1223 1224 /** 1225 * Assigns this as the context menu of given table.<p> 1226 * 1227 * @param table the table 1228 */ 1229 public void setAsTableContextMenu(final Table table) { 1230 1231 extend(table); 1232 setOpenAutomatically(false); 1233 } 1234 1235 /** 1236 * Assigns this as context menu of given tree.<p> 1237 * 1238 * @param tree the tree 1239 */ 1240 public void setAsTreeContextMenu(final Tree tree) { 1241 1242 extend(tree); 1243 setOpenAutomatically(false); 1244 } 1245 1246 /** 1247 * Sets the context menu entries. Removes all previously present entries.<p> 1248 * 1249 * @param entries the entries 1250 * @param data the context data 1251 */ 1252 public <T> void setEntries(Collection<I_CmsSimpleContextMenuEntry<T>> entries, T data) { 1253 1254 removeAllItems(); 1255 Locale locale = UI.getCurrent().getLocale(); 1256 for (final I_CmsSimpleContextMenuEntry<T> entry : entries) { 1257 CmsMenuItemVisibilityMode visibility = entry.getVisibility(data); 1258 if (!visibility.isInVisible()) { 1259 ContextMenuItem item = addItem(entry.getTitle(locale)); 1260 if (visibility.isInActive()) { 1261 item.setEnabled(false); 1262 if (visibility.getMessageKey() != null) { 1263 item.setDescription(CmsVaadinUtils.getMessageText(visibility.getMessageKey())); 1264 } 1265 } else { 1266 item.setData(data); 1267 item.addItemClickListener(new ContextMenuItemClickListener() { 1268 1269 @SuppressWarnings("unchecked") 1270 public void contextMenuItemClicked(ContextMenuItemClickEvent event) { 1271 1272 entry.executeAction((T)((ContextMenuItem)event.getSource()).getData()); 1273 } 1274 }); 1275 } 1276 if (entry instanceof I_CmsSimpleContextMenuEntry.I_HasCssStyles) { 1277 item.addStyleName(((I_CmsSimpleContextMenuEntry.I_HasCssStyles)entry).getStyles()); 1278 } 1279 } 1280 } 1281 } 1282 1283 /** 1284 * Sets menu to hide automatically after mouse cliks on menu items or area 1285 * off the menu. If automatic hiding is disabled menu will stay open as long 1286 * as hide is called from the server side.<p> 1287 * 1288 * @param hideAutomatically whether to hide automatically 1289 */ 1290 public void setHideAutomatically(boolean hideAutomatically) { 1291 1292 getState().setHideAutomatically(hideAutomatically); 1293 } 1294 1295 /** 1296 * Enables or disables open automatically feature. If open automatically is 1297 * on, it means that context menu will always be opened when it's host 1298 * component is right clicked. This will happen on client side without 1299 * server round trip. If automatic opening is turned off, context menu will 1300 * only open when server side open(x, y) is called. If automatic opening is 1301 * disabled you will need a listener implementation for context menu that is 1302 * called upon client side click event. Another option is to extend context 1303 * menu and handle the right clicking internally with case specific listener 1304 * implementation and inside it call open(x, y) method. 1305 * 1306 * @param openAutomatically whether to open automatically 1307 */ 1308 public void setOpenAutomatically(boolean openAutomatically) { 1309 1310 getState().setOpenAutomatically(openAutomatically); 1311 } 1312 1313 /** 1314 * Added to increase method visibility.<p> 1315 * 1316 * @see com.vaadin.server.AbstractClientConnector#fireEvent(java.util.EventObject) 1317 */ 1318 @Override 1319 protected void fireEvent(EventObject event) { 1320 1321 super.fireEvent(event); 1322 } 1323 1324 /** 1325 * Returns a new UUID.<p> 1326 * 1327 * @return a new UUID 1328 */ 1329 protected String getNextId() { 1330 1331 return UUID.randomUUID().toString(); 1332 } 1333 1334 /** 1335 * Added to increase visibility.<p> 1336 * 1337 * @see com.vaadin.server.AbstractClientConnector#getResource(java.lang.String) 1338 */ 1339 @Override 1340 protected Resource getResource(String key) { 1341 1342 return super.getResource(key); 1343 } 1344 1345 /** 1346 * @see com.vaadin.server.AbstractClientConnector#getState() 1347 */ 1348 @Override 1349 protected CmsContextMenuState getState() { 1350 1351 return (CmsContextMenuState)super.getState(); 1352 } 1353 1354 /** 1355 * Added to increase visibility.<p> 1356 * 1357 * @see com.vaadin.server.AbstractClientConnector#setResource(java.lang.String, com.vaadin.server.Resource) 1358 */ 1359 @Override 1360 protected void setResource(String key, Resource resource) { 1361 1362 super.setResource(key, resource); 1363 } 1364 1365 /** 1366 * Returns whether the menu has a specific item.<p> 1367 * 1368 * @param contextMenuItem the item to look for 1369 * 1370 * @return <code>true</code> if the item is present 1371 */ 1372 private boolean hasMenuItem(ContextMenuItem contextMenuItem) { 1373 1374 return m_items.containsKey(contextMenuItem.m_state.getId()); 1375 } 1376 1377}