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}