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