diff options
Diffstat (limited to 'server/src/com/vaadin/ui/Tree.java')
-rw-r--r-- | server/src/com/vaadin/ui/Tree.java | 1981 |
1 files changed, 0 insertions, 1981 deletions
diff --git a/server/src/com/vaadin/ui/Tree.java b/server/src/com/vaadin/ui/Tree.java deleted file mode 100644 index 5e6ac55027..0000000000 --- a/server/src/com/vaadin/ui/Tree.java +++ /dev/null @@ -1,1981 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.ui; - -import java.io.Serializable; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; -import java.util.Stack; -import java.util.StringTokenizer; - -import org.jsoup.nodes.Element; - -import com.vaadin.data.Container; -import com.vaadin.data.Item; -import com.vaadin.data.util.ContainerHierarchicalWrapper; -import com.vaadin.data.util.HierarchicalContainer; -import com.vaadin.event.Action; -import com.vaadin.event.Action.Handler; -import com.vaadin.event.ContextClickEvent; -import com.vaadin.event.DataBoundTransferable; -import com.vaadin.event.ItemClickEvent; -import com.vaadin.event.ItemClickEvent.ItemClickListener; -import com.vaadin.event.ItemClickEvent.ItemClickNotifier; -import com.vaadin.event.Transferable; -import com.vaadin.event.dd.DragAndDropEvent; -import com.vaadin.event.dd.DragSource; -import com.vaadin.event.dd.DropHandler; -import com.vaadin.event.dd.DropTarget; -import com.vaadin.event.dd.TargetDetails; -import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion; -import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion; -import com.vaadin.event.dd.acceptcriteria.TargetDetailIs; -import com.vaadin.server.KeyMapper; -import com.vaadin.server.PaintException; -import com.vaadin.server.PaintTarget; -import com.vaadin.server.Resource; -import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.ui.MultiSelectMode; -import com.vaadin.shared.ui.dd.VerticalDropLocation; -import com.vaadin.shared.ui.tree.TreeConstants; -import com.vaadin.shared.ui.tree.TreeServerRpc; -import com.vaadin.shared.ui.tree.TreeState; -import com.vaadin.ui.declarative.DesignAttributeHandler; -import com.vaadin.ui.declarative.DesignContext; -import com.vaadin.ui.declarative.DesignException; -import com.vaadin.util.ReflectTools; - -/** - * Tree component. A Tree can be used to select an item (or multiple items) from - * a hierarchical set of items. - * - * @author Vaadin Ltd. - * @since 3.0 - */ -@SuppressWarnings({ "serial", "deprecation" }) -public class Tree extends AbstractSelect implements Container.Hierarchical, - Action.Container, ItemClickNotifier, DragSource, DropTarget { - - /** - * ContextClickEvent for the Tree Component. - * - * @since 7.6 - */ - public static class TreeContextClickEvent extends ContextClickEvent { - - private final Object itemId; - - public TreeContextClickEvent(Tree source, Object itemId, - MouseEventDetails mouseEventDetails) { - super(source, mouseEventDetails); - this.itemId = itemId; - } - - @Override - public Tree getComponent() { - return (Tree) super.getComponent(); - } - - /** - * Returns the item id of context clicked row. - * - * @return item id of clicked row; <code>null</code> if no row is - * present at the location - */ - public Object getItemId() { - return itemId; - } - } - - /* Private members */ - - private static final String NULL_ALT_EXCEPTION_MESSAGE = "Parameter 'altText' needs to be non null"; - - /** - * Item icons alt texts. - */ - private final HashMap<Object, String> itemIconAlts = new HashMap<Object, String>(); - - /** - * Set of expanded nodes. - */ - private HashSet<Object> expanded = new HashSet<Object>(); - - /** - * List of action handlers. - */ - private LinkedList<Action.Handler> actionHandlers = null; - - /** - * Action mapper. - */ - private KeyMapper<Action> actionMapper = null; - - /** - * Is the tree selectable on the client side. - */ - private boolean selectable = true; - - /** - * Flag to indicate sub-tree loading - */ - private boolean partialUpdate = false; - - /** - * Holds a itemId which was recently expanded - */ - private Object expandedItemId; - - /** - * a flag which indicates initial paint. After this flag set true partial - * updates are allowed. - */ - private boolean initialPaint = true; - - /** - * Item tooltip generator - */ - private ItemDescriptionGenerator itemDescriptionGenerator; - - /** - * Supported drag modes for Tree. - */ - public enum TreeDragMode { - /** - * When drag mode is NONE, dragging from Tree is not supported. Browsers - * may still support selecting text/icons from Tree which can initiate - * HTML 5 style drag and drop operation. - */ - NONE, - /** - * When drag mode is NODE, users can initiate drag from Tree nodes that - * represent {@link Item}s in from the backed {@link Container}. - */ - NODE - // , SUBTREE - } - - private TreeDragMode dragMode = TreeDragMode.NONE; - - private MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT; - - /* Tree constructors */ - - /** - * Creates a new empty tree. - */ - public Tree() { - this(null); - - registerRpc(new TreeServerRpc() { - @Override - public void contextClick(String rowKey, MouseEventDetails details) { - fireEvent(new TreeContextClickEvent(Tree.this, - itemIdMapper.get(rowKey), details)); - } - }); - } - - /** - * Creates a new empty tree with caption. - * - * @param caption - */ - public Tree(String caption) { - this(caption, new HierarchicalContainer()); - } - - /** - * Creates a new tree with caption and connect it to a Container. - * - * @param caption - * @param dataSource - */ - public Tree(String caption, Container dataSource) { - super(caption, dataSource); - } - - @Override - public void setItemIcon(Object itemId, Resource icon) { - setItemIcon(itemId, icon, ""); - } - - /** - * Sets the icon for an item. - * - * @param itemId - * the id of the item to be assigned an icon. - * @param icon - * the icon to use or null. - * - * @param altText - * the alternative text for the icon - */ - public void setItemIcon(Object itemId, Resource icon, String altText) { - if (itemId != null) { - super.setItemIcon(itemId, icon); - - if (icon == null) { - itemIconAlts.remove(itemId); - } else if (altText == null) { - throw new IllegalArgumentException(NULL_ALT_EXCEPTION_MESSAGE); - } else { - itemIconAlts.put(itemId, altText); - } - markAsDirty(); - } - } - - /** - * Set the alternate text for an item. - * - * Used when the item has an icon. - * - * @param itemId - * the id of the item to be assigned an icon. - * @param altText - * the alternative text for the icon - */ - public void setItemIconAlternateText(Object itemId, String altText) { - if (itemId != null) { - if (altText == null) { - throw new IllegalArgumentException(NULL_ALT_EXCEPTION_MESSAGE); - } else { - itemIconAlts.put(itemId, altText); - } - } - } - - /** - * Return the alternate text of an icon in a tree item. - * - * @param itemId - * Object with the ID of the item - * @return String with the alternate text of the icon, or null when no icon - * was set - */ - public String getItemIconAlternateText(Object itemId) { - String storedAlt = itemIconAlts.get(itemId); - return storedAlt == null ? "" : storedAlt; - } - - /* Expanding and collapsing */ - - /** - * Check is an item is expanded - * - * @param itemId - * the item id. - * @return true iff the item is expanded. - */ - public boolean isExpanded(Object itemId) { - return expanded.contains(itemId); - } - - /** - * Expands an item. - * - * @param itemId - * the item id. - * @return True iff the expand operation succeeded - */ - public boolean expandItem(Object itemId) { - boolean success = expandItem(itemId, true); - markAsDirty(); - return success; - } - - /** - * Expands an item. - * - * @param itemId - * the item id. - * @param sendChildTree - * flag to indicate if client needs subtree or not (may be - * cached) - * @return True if the expand operation succeeded - */ - private boolean expandItem(Object itemId, boolean sendChildTree) { - - // Succeeds if the node is already expanded - if (isExpanded(itemId)) { - return true; - } - - // Nodes that can not have children are not expandable - if (!areChildrenAllowed(itemId)) { - return false; - } - - // Expands - expanded.add(itemId); - - expandedItemId = itemId; - if (initialPaint) { - markAsDirty(); - } else if (sendChildTree) { - requestPartialRepaint(); - } - fireExpandEvent(itemId); - - return true; - } - - @Override - public void markAsDirty() { - super.markAsDirty(); - partialUpdate = false; - } - - private void requestPartialRepaint() { - super.markAsDirty(); - partialUpdate = true; - } - - /** - * Expands the items recursively - * - * Expands all the children recursively starting from an item. Operation - * succeeds only if all expandable items are expanded. - * - * @param startItemId - * @return True iff the expand operation succeeded - */ - public boolean expandItemsRecursively(Object startItemId) { - - boolean result = true; - - // Initial stack - final Stack<Object> todo = new Stack<Object>(); - todo.add(startItemId); - - // Expands recursively - while (!todo.isEmpty()) { - final Object id = todo.pop(); - if (areChildrenAllowed(id) && !expandItem(id, false)) { - result = false; - } - if (hasChildren(id)) { - todo.addAll(getChildren(id)); - } - } - markAsDirty(); - return result; - } - - /** - * Collapses an item. - * - * @param itemId - * the item id. - * @return True iff the collapse operation succeeded - */ - public boolean collapseItem(Object itemId) { - - // Succeeds if the node is already collapsed - if (!isExpanded(itemId)) { - return true; - } - - // Collapse - expanded.remove(itemId); - markAsDirty(); - fireCollapseEvent(itemId); - - return true; - } - - /** - * Collapses the items recursively. - * - * Collapse all the children recursively starting from an item. Operation - * succeeds only if all expandable items are collapsed. - * - * @param startItemId - * @return True iff the collapse operation succeeded - */ - public boolean collapseItemsRecursively(Object startItemId) { - - boolean result = true; - - // Initial stack - final Stack<Object> todo = new Stack<Object>(); - todo.add(startItemId); - - // Collapse recursively - while (!todo.isEmpty()) { - final Object id = todo.pop(); - if (areChildrenAllowed(id) && !collapseItem(id)) { - result = false; - } - if (hasChildren(id)) { - todo.addAll(getChildren(id)); - } - } - - return result; - } - - /** - * Returns the current selectable state. Selectable determines if the a node - * can be selected on the client side. Selectable does not affect - * {@link #setValue(Object)} or {@link #select(Object)}. - * - * <p> - * The tree is selectable by default. - * </p> - * - * @return the current selectable state. - */ - public boolean isSelectable() { - return selectable; - } - - /** - * Sets the selectable state. Selectable determines if the a node can be - * selected on the client side. Selectable does not affect - * {@link #setValue(Object)} or {@link #select(Object)}. - * - * <p> - * The tree is selectable by default. - * </p> - * - * @param selectable - * The new selectable state. - */ - public void setSelectable(boolean selectable) { - if (this.selectable != selectable) { - this.selectable = selectable; - markAsDirty(); - } - } - - /** - * Sets the behavior of the multiselect mode - * - * @param mode - * The mode to set - */ - public void setMultiselectMode(MultiSelectMode mode) { - if (multiSelectMode != mode && mode != null) { - multiSelectMode = mode; - markAsDirty(); - } - } - - /** - * Returns the mode the multiselect is in. The mode controls how - * multiselection can be done. - * - * @return The mode - */ - public MultiSelectMode getMultiselectMode() { - return multiSelectMode; - } - - /* Component API */ - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractSelect#changeVariables(java.lang.Object, - * java.util.Map) - */ - @Override - public void changeVariables(Object source, Map<String, Object> variables) { - - if (variables.containsKey("clickedKey")) { - String key = (String) variables.get("clickedKey"); - - Object id = itemIdMapper.get(key); - MouseEventDetails details = MouseEventDetails - .deSerialize((String) variables.get("clickEvent")); - Item item = getItem(id); - if (item != null) { - fireEvent(new ItemClickEvent(this, item, id, null, details)); - } - } - - if (!isSelectable() && variables.containsKey("selected")) { - // Not-selectable is a special case, AbstractSelect does not support - // TODO could be optimized. - variables = new HashMap<String, Object>(variables); - variables.remove("selected"); - } - - // Collapses the nodes - if (variables.containsKey("collapse")) { - final String[] keys = (String[]) variables.get("collapse"); - for (int i = 0; i < keys.length; i++) { - final Object id = itemIdMapper.get(keys[i]); - if (id != null && isExpanded(id)) { - expanded.remove(id); - if (expandedItemId == id) { - expandedItemId = null; - } - fireCollapseEvent(id); - } - } - } - - // Expands the nodes - if (variables.containsKey("expand")) { - boolean sendChildTree = false; - if (variables.containsKey("requestChildTree")) { - sendChildTree = true; - } - final String[] keys = (String[]) variables.get("expand"); - for (int i = 0; i < keys.length; i++) { - final Object id = itemIdMapper.get(keys[i]); - if (id != null) { - expandItem(id, sendChildTree); - } - } - } - - // AbstractSelect cannot handle multiselection so we handle - // it ourself - if (variables.containsKey("selected") && isMultiSelect() - && multiSelectMode == MultiSelectMode.DEFAULT) { - handleSelectedItems(variables); - variables = new HashMap<String, Object>(variables); - variables.remove("selected"); - } - - // Selections are handled by the select component - super.changeVariables(source, variables); - - // Actions - if (variables.containsKey("action")) { - final StringTokenizer st = new StringTokenizer( - (String) variables.get("action"), ","); - if (st.countTokens() == 2) { - final Object itemId = itemIdMapper.get(st.nextToken()); - final Action action = actionMapper.get(st.nextToken()); - if (action != null && (itemId == null || containsId(itemId)) - && actionHandlers != null) { - for (Handler ah : actionHandlers) { - ah.handleAction(action, this, itemId); - } - } - } - } - } - - /** - * Handles the selection - * - * @param variables - * The variables sent to the server from the client - */ - private void handleSelectedItems(Map<String, Object> variables) { - final String[] ka = (String[]) variables.get("selected"); - - // Converts the key-array to id-set - final LinkedList<Object> s = new LinkedList<Object>(); - for (int i = 0; i < ka.length; i++) { - final Object id = itemIdMapper.get(ka[i]); - if (!isNullSelectionAllowed() - && (id == null || id == getNullSelectionItemId())) { - // skip empty selection if nullselection is not allowed - markAsDirty(); - } else if (id != null && containsId(id)) { - s.add(id); - } - } - - if (!isNullSelectionAllowed() && s.size() < 1) { - // empty selection not allowed, keep old value - markAsDirty(); - return; - } - - setValue(s, true); - } - - /** - * Paints any needed component-specific things to the given UIDL stream. - * - * @see com.vaadin.ui.AbstractComponent#paintContent(PaintTarget) - */ - @Override - public void paintContent(PaintTarget target) throws PaintException { - initialPaint = false; - - if (partialUpdate) { - target.addAttribute("partialUpdate", true); - target.addAttribute("rootKey", itemIdMapper.key(expandedItemId)); - } else { - getCaptionChangeListener().clear(); - - // The tab ordering number - if (getTabIndex() > 0) { - target.addAttribute("tabindex", getTabIndex()); - } - - // Paint tree attributes - if (isSelectable()) { - target.addAttribute("selectmode", (isMultiSelect() ? "multi" - : "single")); - if (isMultiSelect()) { - target.addAttribute("multiselectmode", - multiSelectMode.toString()); - } - } else { - target.addAttribute("selectmode", "none"); - } - if (isNewItemsAllowed()) { - target.addAttribute("allownewitem", true); - } - - if (isNullSelectionAllowed()) { - target.addAttribute("nullselect", true); - } - - if (dragMode != TreeDragMode.NONE) { - target.addAttribute("dragMode", dragMode.ordinal()); - } - - if (isHtmlContentAllowed()) { - target.addAttribute(TreeConstants.ATTRIBUTE_HTML_ALLOWED, true); - } - - } - - // Initialize variables - final Set<Action> actionSet = new LinkedHashSet<Action>(); - - // rendered selectedKeys - LinkedList<String> selectedKeys = new LinkedList<String>(); - - final LinkedList<String> expandedKeys = new LinkedList<String>(); - - // Iterates through hierarchical tree using a stack of iterators - final Stack<Iterator<?>> iteratorStack = new Stack<Iterator<?>>(); - Collection<?> ids; - if (partialUpdate) { - ids = getChildren(expandedItemId); - } else { - ids = rootItemIds(); - } - - if (ids != null) { - iteratorStack.push(ids.iterator()); - } - - /* - * Body actions - Actions which has the target null and can be invoked - * by right clicking on the Tree body - */ - if (actionHandlers != null) { - final ArrayList<String> keys = new ArrayList<String>(); - for (Handler ah : actionHandlers) { - - // Getting action for the null item, which in this case - // means the body item - final Action[] aa = ah.getActions(null, this); - if (aa != null) { - for (int ai = 0; ai < aa.length; ai++) { - final String akey = actionMapper.key(aa[ai]); - actionSet.add(aa[ai]); - keys.add(akey); - } - } - } - target.addAttribute("alb", keys.toArray()); - } - - while (!iteratorStack.isEmpty()) { - - // Gets the iterator for current tree level - final Iterator<?> i = iteratorStack.peek(); - - // If the level is finished, back to previous tree level - if (!i.hasNext()) { - - // Removes used iterator from the stack - iteratorStack.pop(); - - // Closes node - if (!iteratorStack.isEmpty()) { - target.endTag("node"); - } - } - - // Adds the item on current level - else { - final Object itemId = i.next(); - - // Starts the item / node - final boolean isNode = areChildrenAllowed(itemId); - if (isNode) { - target.startTag("node"); - } else { - target.startTag("leaf"); - } - - if (itemStyleGenerator != null) { - String stylename = itemStyleGenerator - .getStyle(this, itemId); - if (stylename != null) { - target.addAttribute(TreeConstants.ATTRIBUTE_NODE_STYLE, - stylename); - } - } - - if (itemDescriptionGenerator != null) { - String description = itemDescriptionGenerator - .generateDescription(this, itemId, null); - if (description != null && !description.equals("")) { - target.addAttribute("descr", description); - } - } - - // Adds the attributes - target.addAttribute(TreeConstants.ATTRIBUTE_NODE_CAPTION, - getItemCaption(itemId)); - final Resource icon = getItemIcon(itemId); - if (icon != null) { - target.addAttribute(TreeConstants.ATTRIBUTE_NODE_ICON, - getItemIcon(itemId)); - target.addAttribute(TreeConstants.ATTRIBUTE_NODE_ICON_ALT, - getItemIconAlternateText(itemId)); - } - final String key = itemIdMapper.key(itemId); - target.addAttribute("key", key); - if (isSelected(itemId)) { - target.addAttribute("selected", true); - selectedKeys.add(key); - } - if (areChildrenAllowed(itemId) && isExpanded(itemId)) { - target.addAttribute("expanded", true); - expandedKeys.add(key); - } - - // Add caption change listener - getCaptionChangeListener().addNotifierForItem(itemId); - - // Actions - if (actionHandlers != null) { - final ArrayList<String> keys = new ArrayList<String>(); - final Iterator<Action.Handler> ahi = actionHandlers - .iterator(); - while (ahi.hasNext()) { - final Action[] aa = ahi.next().getActions(itemId, this); - if (aa != null) { - for (int ai = 0; ai < aa.length; ai++) { - final String akey = actionMapper.key(aa[ai]); - actionSet.add(aa[ai]); - keys.add(akey); - } - } - } - target.addAttribute("al", keys.toArray()); - } - - // Adds the children if expanded, or close the tag - if (isExpanded(itemId) && hasChildren(itemId) - && areChildrenAllowed(itemId)) { - iteratorStack.push(getChildren(itemId).iterator()); - } else { - if (isNode) { - target.endTag("node"); - } else { - target.endTag("leaf"); - } - } - } - } - - // Actions - if (!actionSet.isEmpty()) { - target.addVariable(this, "action", ""); - target.startTag("actions"); - final Iterator<Action> i = actionSet.iterator(); - while (i.hasNext()) { - final Action a = i.next(); - target.startTag("action"); - if (a.getCaption() != null) { - target.addAttribute(TreeConstants.ATTRIBUTE_ACTION_CAPTION, - a.getCaption()); - } - if (a.getIcon() != null) { - target.addAttribute(TreeConstants.ATTRIBUTE_ACTION_ICON, - a.getIcon()); - } - target.addAttribute("key", actionMapper.key(a)); - target.endTag("action"); - } - target.endTag("actions"); - } - - if (partialUpdate) { - partialUpdate = false; - } else { - // Selected - target.addVariable(this, "selected", - selectedKeys.toArray(new String[selectedKeys.size()])); - - // Expand and collapse - target.addVariable(this, "expand", new String[] {}); - target.addVariable(this, "collapse", new String[] {}); - - // New items - target.addVariable(this, "newitem", new String[] {}); - - if (dropHandler != null) { - dropHandler.getAcceptCriterion().paint(target); - } - - } - } - - /* Container.Hierarchical API */ - - /** - * Tests if the Item with given ID can have any children. - * - * @see com.vaadin.data.Container.Hierarchical#areChildrenAllowed(Object) - */ - @Override - public boolean areChildrenAllowed(Object itemId) { - return ((Container.Hierarchical) items).areChildrenAllowed(itemId); - } - - /** - * Gets the IDs of all Items that are children of the specified Item. - * - * @see com.vaadin.data.Container.Hierarchical#getChildren(Object) - */ - @Override - public Collection<?> getChildren(Object itemId) { - return ((Container.Hierarchical) items).getChildren(itemId); - } - - /** - * Gets the ID of the parent Item of the specified Item. - * - * @see com.vaadin.data.Container.Hierarchical#getParent(Object) - */ - @Override - public Object getParent(Object itemId) { - return ((Container.Hierarchical) items).getParent(itemId); - } - - /** - * Tests if the Item specified with <code>itemId</code> has child Items. - * - * @see com.vaadin.data.Container.Hierarchical#hasChildren(Object) - */ - @Override - public boolean hasChildren(Object itemId) { - return ((Container.Hierarchical) items).hasChildren(itemId); - } - - /** - * Tests if the Item specified with <code>itemId</code> is a root Item. - * - * @see com.vaadin.data.Container.Hierarchical#isRoot(Object) - */ - @Override - public boolean isRoot(Object itemId) { - return ((Container.Hierarchical) items).isRoot(itemId); - } - - /** - * Gets the IDs of all Items in the container that don't have a parent. - * - * @see com.vaadin.data.Container.Hierarchical#rootItemIds() - */ - @Override - public Collection<?> rootItemIds() { - return ((Container.Hierarchical) items).rootItemIds(); - } - - /** - * Sets the given Item's capability to have children. - * - * @see com.vaadin.data.Container.Hierarchical#setChildrenAllowed(Object, - * boolean) - */ - @Override - public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) { - final boolean success = ((Container.Hierarchical) items) - .setChildrenAllowed(itemId, areChildrenAllowed); - if (success) { - markAsDirty(); - } - return success; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.data.Container.Hierarchical#setParent(java.lang.Object , - * java.lang.Object) - */ - @Override - public boolean setParent(Object itemId, Object newParentId) { - final boolean success = ((Container.Hierarchical) items).setParent( - itemId, newParentId); - if (success) { - markAsDirty(); - } - return success; - } - - /* Overriding select behavior */ - - /** - * Sets the Container that serves as the data source of the viewer. - * - * @see com.vaadin.data.Container.Viewer#setContainerDataSource(Container) - */ - @Override - public void setContainerDataSource(Container newDataSource) { - if (newDataSource == null) { - newDataSource = new HierarchicalContainer(); - } - - // Assure that the data source is ordered by making unordered - // containers ordered by wrapping them - if (Container.Hierarchical.class.isAssignableFrom(newDataSource - .getClass())) { - super.setContainerDataSource(newDataSource); - } else { - super.setContainerDataSource(new ContainerHierarchicalWrapper( - newDataSource)); - } - - /* - * Ensure previous expanded items are cleaned up if they don't exist in - * the new container - */ - if (expanded != null) { - /* - * We need to check that the expanded-field is not null since - * setContainerDataSource() is called from the parent constructor - * (AbstractSelect()) and at that time the expanded field is not yet - * initialized. - */ - cleanupExpandedItems(); - } - - } - - @Override - public void containerItemSetChange( - com.vaadin.data.Container.ItemSetChangeEvent event) { - super.containerItemSetChange(event); - if (getContainerDataSource() instanceof Filterable) { - boolean hasFilters = !((Filterable) getContainerDataSource()) - .getContainerFilters().isEmpty(); - if (!hasFilters) { - /* - * If Container is not filtered then the itemsetchange is caused - * by either adding or removing items to the container. To - * prevent a memory leak we should cleanup the expanded list - * from items which was removed. - * - * However, there will still be a leak if the container is - * filtered to show only a subset of the items in the tree and - * later unfiltered items are removed from the container. In - * that case references to the unfiltered item ids will remain - * in the expanded list until the Tree instance is removed and - * the list is destroyed, or the container data source is - * replaced/updated. To force the removal of the removed items - * the application developer needs to a) remove the container - * filters temporarly or b) re-apply the container datasource - * using setContainerDataSource(getContainerDataSource()) - */ - cleanupExpandedItems(); - } - } - - } - - /* Expand event and listener */ - - /** - * Event to fired when a node is expanded. ExapandEvent is fired when a node - * is to be expanded. it can me used to dynamically fill the sub-nodes of - * the node. - * - * @author Vaadin Ltd. - * @since 3.0 - */ - public static class ExpandEvent extends Component.Event { - - private final Object expandedItemId; - - /** - * New instance of options change event - * - * @param source - * the Source of the event. - * @param expandedItemId - */ - public ExpandEvent(Component source, Object expandedItemId) { - super(source); - this.expandedItemId = expandedItemId; - } - - /** - * Node where the event occurred. - * - * @return the Source of the event. - */ - public Object getItemId() { - return expandedItemId; - } - } - - /** - * Expand event listener. - * - * @author Vaadin Ltd. - * @since 3.0 - */ - public interface ExpandListener extends Serializable { - - public static final Method EXPAND_METHOD = ReflectTools.findMethod( - ExpandListener.class, "nodeExpand", ExpandEvent.class); - - /** - * A node has been expanded. - * - * @param event - * the Expand event. - */ - public void nodeExpand(ExpandEvent event); - } - - /** - * Adds the expand listener. - * - * @param listener - * the Listener to be added. - */ - public void addExpandListener(ExpandListener listener) { - addListener(ExpandEvent.class, listener, ExpandListener.EXPAND_METHOD); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #addExpandListener(ExpandListener)} - **/ - @Deprecated - public void addListener(ExpandListener listener) { - addExpandListener(listener); - } - - /** - * Removes the expand listener. - * - * @param listener - * the Listener to be removed. - */ - public void removeExpandListener(ExpandListener listener) { - removeListener(ExpandEvent.class, listener, - ExpandListener.EXPAND_METHOD); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #removeExpandListener(ExpandListener)} - **/ - @Deprecated - public void removeListener(ExpandListener listener) { - removeExpandListener(listener); - } - - /** - * Emits the expand event. - * - * @param itemId - * the item id. - */ - protected void fireExpandEvent(Object itemId) { - fireEvent(new ExpandEvent(this, itemId)); - } - - /* Collapse event */ - - /** - * Collapse event - * - * @author Vaadin Ltd. - * @since 3.0 - */ - public static class CollapseEvent extends Component.Event { - - private final Object collapsedItemId; - - /** - * New instance of options change event. - * - * @param source - * the Source of the event. - * @param collapsedItemId - */ - public CollapseEvent(Component source, Object collapsedItemId) { - super(source); - this.collapsedItemId = collapsedItemId; - } - - /** - * Gets tge Collapsed Item id. - * - * @return the collapsed item id. - */ - public Object getItemId() { - return collapsedItemId; - } - } - - /** - * Collapse event listener. - * - * @author Vaadin Ltd. - * @since 3.0 - */ - public interface CollapseListener extends Serializable { - - public static final Method COLLAPSE_METHOD = ReflectTools.findMethod( - CollapseListener.class, "nodeCollapse", CollapseEvent.class); - - /** - * A node has been collapsed. - * - * @param event - * the Collapse event. - */ - public void nodeCollapse(CollapseEvent event); - } - - /** - * Adds the collapse listener. - * - * @param listener - * the Listener to be added. - */ - public void addCollapseListener(CollapseListener listener) { - addListener(CollapseEvent.class, listener, - CollapseListener.COLLAPSE_METHOD); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #addCollapseListener(CollapseListener)} - **/ - @Deprecated - public void addListener(CollapseListener listener) { - addCollapseListener(listener); - } - - /** - * Removes the collapse listener. - * - * @param listener - * the Listener to be removed. - */ - public void removeCollapseListener(CollapseListener listener) { - removeListener(CollapseEvent.class, listener, - CollapseListener.COLLAPSE_METHOD); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #removeCollapseListener(CollapseListener)} - **/ - @Deprecated - public void removeListener(CollapseListener listener) { - removeCollapseListener(listener); - } - - /** - * Emits collapse event. - * - * @param itemId - * the item id. - */ - protected void fireCollapseEvent(Object itemId) { - fireEvent(new CollapseEvent(this, itemId)); - } - - /* Action container */ - - /** - * Adds an action handler. - * - * @see com.vaadin.event.Action.Container#addActionHandler(Action.Handler) - */ - @Override - public void addActionHandler(Action.Handler actionHandler) { - - if (actionHandler != null) { - - if (actionHandlers == null) { - actionHandlers = new LinkedList<Action.Handler>(); - actionMapper = new KeyMapper<Action>(); - } - - if (!actionHandlers.contains(actionHandler)) { - actionHandlers.add(actionHandler); - markAsDirty(); - } - } - } - - /** - * Removes an action handler. - * - * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler) - */ - @Override - public void removeActionHandler(Action.Handler actionHandler) { - - if (actionHandlers != null && actionHandlers.contains(actionHandler)) { - - actionHandlers.remove(actionHandler); - - if (actionHandlers.isEmpty()) { - actionHandlers = null; - actionMapper = null; - } - - markAsDirty(); - } - } - - /** - * Removes all action handlers - */ - public void removeAllActionHandlers() { - actionHandlers = null; - actionMapper = null; - markAsDirty(); - } - - /** - * Gets the visible item ids. - * - * @see com.vaadin.ui.Select#getVisibleItemIds() - */ - @Override - public Collection<?> getVisibleItemIds() { - - final LinkedList<Object> visible = new LinkedList<Object>(); - - // Iterates trough hierarchical tree using a stack of iterators - final Stack<Iterator<?>> iteratorStack = new Stack<Iterator<?>>(); - final Collection<?> ids = rootItemIds(); - if (ids != null) { - iteratorStack.push(ids.iterator()); - } - while (!iteratorStack.isEmpty()) { - - // Gets the iterator for current tree level - final Iterator<?> i = iteratorStack.peek(); - - // If the level is finished, back to previous tree level - if (!i.hasNext()) { - - // Removes used iterator from the stack - iteratorStack.pop(); - } - - // Adds the item on current level - else { - final Object itemId = i.next(); - - visible.add(itemId); - - // Adds children if expanded, or close the tag - if (isExpanded(itemId) && hasChildren(itemId)) { - iteratorStack.push(getChildren(itemId).iterator()); - } - } - } - - return visible; - } - - /** - * Tree does not support <code>setNullSelectionItemId</code>. - * - * @see com.vaadin.ui.AbstractSelect#setNullSelectionItemId(java.lang.Object) - */ - @Override - public void setNullSelectionItemId(Object nullSelectionItemId) - throws UnsupportedOperationException { - if (nullSelectionItemId != null) { - throw new UnsupportedOperationException(); - } - - } - - /** - * Adding new items is not supported. - * - * @throws UnsupportedOperationException - * if set to true. - * @see com.vaadin.ui.Select#setNewItemsAllowed(boolean) - */ - @Override - public void setNewItemsAllowed(boolean allowNewOptions) - throws UnsupportedOperationException { - if (allowNewOptions) { - throw new UnsupportedOperationException(); - } - } - - private ItemStyleGenerator itemStyleGenerator; - - private DropHandler dropHandler; - - private boolean htmlContentAllowed; - - @Override - public void addItemClickListener(ItemClickListener listener) { - addListener(TreeConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class, - listener, ItemClickEvent.ITEM_CLICK_METHOD); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #addItemClickListener(ItemClickListener)} - **/ - @Override - @Deprecated - public void addListener(ItemClickListener listener) { - addItemClickListener(listener); - } - - @Override - public void removeItemClickListener(ItemClickListener listener) { - removeListener(TreeConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class, - listener); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #removeItemClickListener(ItemClickListener)} - **/ - @Override - @Deprecated - public void removeListener(ItemClickListener listener) { - removeItemClickListener(listener); - } - - /** - * Sets the {@link ItemStyleGenerator} to be used with this tree. - * - * @param itemStyleGenerator - * item style generator or null to remove generator - */ - public void setItemStyleGenerator(ItemStyleGenerator itemStyleGenerator) { - if (this.itemStyleGenerator != itemStyleGenerator) { - this.itemStyleGenerator = itemStyleGenerator; - markAsDirty(); - } - } - - /** - * @return the current {@link ItemStyleGenerator} for this tree. Null if - * {@link ItemStyleGenerator} is not set. - */ - public ItemStyleGenerator getItemStyleGenerator() { - return itemStyleGenerator; - } - - /** - * ItemStyleGenerator can be used to add custom styles to tree items. The - * CSS class name that will be added to the item content is - * <tt>v-tree-node-[style name]</tt>. - */ - public interface ItemStyleGenerator extends Serializable { - - /** - * Called by Tree when an item is painted. - * - * @param source - * the source Tree - * @param itemId - * The itemId of the item to be painted - * @return The style name to add to this item. (the CSS class name will - * be v-tree-node-[style name] - */ - public abstract String getStyle(Tree source, Object itemId); - } - - // Overriden so javadoc comes from Container.Hierarchical - @Override - public boolean removeItem(Object itemId) - throws UnsupportedOperationException { - return super.removeItem(itemId); - } - - @Override - public DropHandler getDropHandler() { - return dropHandler; - } - - public void setDropHandler(DropHandler dropHandler) { - this.dropHandler = dropHandler; - } - - /** - * A {@link TargetDetails} implementation with Tree specific api. - * - * @since 6.3 - */ - public class TreeTargetDetails extends AbstractSelectTargetDetails { - - TreeTargetDetails(Map<String, Object> rawVariables) { - super(rawVariables); - } - - @Override - public Tree getTarget() { - return (Tree) super.getTarget(); - } - - /** - * If the event is on a node that can not have children (see - * {@link Tree#areChildrenAllowed(Object)}), this method returns the - * parent item id of the target item (see {@link #getItemIdOver()} ). - * The identifier of the parent node is also returned if the cursor is - * on the top part of node. Else this method returns the same as - * {@link #getItemIdOver()}. - * <p> - * In other words this method returns the identifier of the "folder" - * into the drag operation is targeted. - * <p> - * If the method returns null, the current target is on a root node or - * on other undefined area over the tree component. - * <p> - * The default Tree implementation marks the targetted tree node with - * CSS classnames v-tree-node-dragfolder and - * v-tree-node-caption-dragfolder (for the caption element). - */ - public Object getItemIdInto() { - - Object itemIdOver = getItemIdOver(); - if (areChildrenAllowed(itemIdOver) - && getDropLocation() == VerticalDropLocation.MIDDLE) { - return itemIdOver; - } - return getParent(itemIdOver); - } - - /** - * If drop is targeted into "folder node" (see {@link #getItemIdInto()} - * ), this method returns the item id of the node after the drag was - * targeted. This method is useful when implementing drop into specific - * location (between specific nodes) in tree. - * - * @return the id of the item after the user targets the drop or null if - * "target" is a first item in node list (or the first in root - * node list) - */ - public Object getItemIdAfter() { - Object itemIdOver = getItemIdOver(); - Object itemIdInto2 = getItemIdInto(); - if (itemIdOver.equals(itemIdInto2)) { - return null; - } - VerticalDropLocation dropLocation = getDropLocation(); - if (VerticalDropLocation.TOP == dropLocation) { - // if on top of the caption area, add before - Collection<?> children; - Object itemIdInto = getItemIdInto(); - if (itemIdInto != null) { - // seek the previous from child list - children = getChildren(itemIdInto); - } else { - children = rootItemIds(); - } - Object ref = null; - for (Object object : children) { - if (object.equals(itemIdOver)) { - return ref; - } - ref = object; - } - } - return itemIdOver; - } - - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.event.dd.DropTarget#translateDropTargetDetails(java.util.Map) - */ - @Override - public TreeTargetDetails translateDropTargetDetails( - Map<String, Object> clientVariables) { - return new TreeTargetDetails(clientVariables); - } - - /** - * Helper API for {@link TreeDropCriterion} - * - * @param itemId - * @return - */ - private String key(Object itemId) { - return itemIdMapper.key(itemId); - } - - /** - * Sets the drag mode that controls how Tree behaves as a {@link DragSource} - * . - * - * @param dragMode - */ - public void setDragMode(TreeDragMode dragMode) { - this.dragMode = dragMode; - markAsDirty(); - } - - /** - * @return the drag mode that controls how Tree behaves as a - * {@link DragSource}. - * - * @see TreeDragMode - */ - public TreeDragMode getDragMode() { - return dragMode; - } - - /** - * Concrete implementation of {@link DataBoundTransferable} for data - * transferred from a tree. - * - * @see {@link DataBoundTransferable}. - * - * @since 6.3 - */ - protected class TreeTransferable extends DataBoundTransferable { - - public TreeTransferable(Component sourceComponent, - Map<String, Object> rawVariables) { - super(sourceComponent, rawVariables); - } - - @Override - public Object getItemId() { - return getData("itemId"); - } - - @Override - public Object getPropertyId() { - return getItemCaptionPropertyId(); - } - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.event.dd.DragSource#getTransferable(java.util.Map) - */ - @Override - public Transferable getTransferable(Map<String, Object> payload) { - TreeTransferable transferable = new TreeTransferable(this, payload); - // updating drag source variables - Object object = payload.get("itemId"); - if (object != null) { - transferable.setData("itemId", itemIdMapper.get((String) object)); - } - - return transferable; - } - - /** - * Lazy loading accept criterion for Tree. Accepted target nodes are loaded - * from server once per drag and drop operation. Developer must override one - * method that decides accepted tree nodes for the whole Tree. - * - * <p> - * Initially pretty much no data is sent to client. On first required - * criterion check (per drag request) the client side data structure is - * initialized from server and no subsequent requests requests are needed - * during that drag and drop operation. - */ - public static abstract class TreeDropCriterion extends ServerSideCriterion { - - private Tree tree; - - private Set<Object> allowedItemIds; - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.event.dd.acceptCriteria.ServerSideCriterion#getIdentifier - * () - */ - @Override - protected String getIdentifier() { - return TreeDropCriterion.class.getCanonicalName(); - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.event.dd.acceptCriteria.AcceptCriterion#accepts(com.vaadin - * .event.dd.DragAndDropEvent) - */ - @Override - public boolean accept(DragAndDropEvent dragEvent) { - AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dragEvent - .getTargetDetails(); - tree = (Tree) dragEvent.getTargetDetails().getTarget(); - allowedItemIds = getAllowedItemIds(dragEvent, tree); - - return allowedItemIds.contains(dropTargetData.getItemIdOver()); - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.event.dd.acceptCriteria.AcceptCriterion#paintResponse( - * com.vaadin.server.PaintTarget) - */ - @Override - public void paintResponse(PaintTarget target) throws PaintException { - /* - * send allowed nodes to client so subsequent requests can be - * avoided - */ - Object[] array = allowedItemIds.toArray(); - for (int i = 0; i < array.length; i++) { - String key = tree.key(array[i]); - array[i] = key; - } - target.addAttribute("allowedIds", array); - } - - protected abstract Set<Object> getAllowedItemIds( - DragAndDropEvent dragEvent, Tree tree); - - } - - /** - * A criterion that accepts {@link Transferable} only directly on a tree - * node that can have children. - * <p> - * Class is singleton, use {@link TargetItemAllowsChildren#get()} to get the - * instance. - * - * @see Tree#setChildrenAllowed(Object, boolean) - * - * @since 6.3 - */ - public static class TargetItemAllowsChildren extends TargetDetailIs { - - private static TargetItemAllowsChildren instance = new TargetItemAllowsChildren(); - - public static TargetItemAllowsChildren get() { - return instance; - } - - private TargetItemAllowsChildren() { - super("itemIdOverIsNode", Boolean.TRUE); - } - - /* - * Uses enhanced server side check - */ - @Override - public boolean accept(DragAndDropEvent dragEvent) { - try { - // must be over tree node and in the middle of it (not top or - // bottom - // part) - TreeTargetDetails eventDetails = (TreeTargetDetails) dragEvent - .getTargetDetails(); - - Object itemIdOver = eventDetails.getItemIdOver(); - if (!eventDetails.getTarget().areChildrenAllowed(itemIdOver)) { - return false; - } - // return true if directly over - return eventDetails.getDropLocation() == VerticalDropLocation.MIDDLE; - } catch (Exception e) { - return false; - } - } - - } - - /** - * An accept criterion that checks the parent node (or parent hierarchy) for - * the item identifier given in constructor. If the parent is found, content - * is accepted. Criterion can be used to accepts drags on a specific sub - * tree only. - * <p> - * The root items is also consider to be valid target. - */ - public class TargetInSubtree extends ClientSideCriterion { - - private Object rootId; - private int depthToCheck = -1; - - /** - * Constructs a criteria that accepts the drag if the targeted Item is a - * descendant of Item identified by given id - * - * @param parentItemId - * the item identifier of the parent node - */ - public TargetInSubtree(Object parentItemId) { - rootId = parentItemId; - } - - /** - * Constructs a criteria that accepts drops within given level below the - * subtree root identified by given id. - * - * @param rootId - * the item identifier to be sought for - * @param depthToCheck - * the depth that tree is traversed upwards to seek for the - * parent, -1 means that the whole structure should be - * checked - */ - public TargetInSubtree(Object rootId, int depthToCheck) { - this.rootId = rootId; - this.depthToCheck = depthToCheck; - } - - @Override - public boolean accept(DragAndDropEvent dragEvent) { - try { - TreeTargetDetails eventDetails = (TreeTargetDetails) dragEvent - .getTargetDetails(); - - if (eventDetails.getItemIdOver() != null) { - Object itemId = eventDetails.getItemIdOver(); - int i = 0; - while (itemId != null - && (depthToCheck == -1 || i <= depthToCheck)) { - if (itemId.equals(rootId)) { - return true; - } - itemId = getParent(itemId); - i++; - } - } - return false; - } catch (Exception e) { - return false; - } - } - - @Override - public void paintContent(PaintTarget target) throws PaintException { - super.paintContent(target); - target.addAttribute("depth", depthToCheck); - target.addAttribute("key", key(rootId)); - } - } - - /** - * Set the item description generator which generates tooltips for the tree - * items - * - * @param generator - * The generator to use or null to disable - */ - public void setItemDescriptionGenerator(ItemDescriptionGenerator generator) { - if (generator != itemDescriptionGenerator) { - itemDescriptionGenerator = generator; - markAsDirty(); - } - } - - /** - * Get the item description generator which generates tooltips for tree - * items - */ - public ItemDescriptionGenerator getItemDescriptionGenerator() { - return itemDescriptionGenerator; - } - - private void cleanupExpandedItems() { - Set<Object> removedItemIds = new HashSet<Object>(); - for (Object expandedItemId : expanded) { - if (getItem(expandedItemId) == null) { - removedItemIds.add(expandedItemId); - if (this.expandedItemId == expandedItemId) { - this.expandedItemId = null; - } - } - } - expanded.removeAll(removedItemIds); - } - - /** - * Reads an Item from a design and inserts it into the data source. - * Recursively handles any children of the item as well. - * - * @since 7.5.0 - * @param node - * an element representing the item (tree node). - * @param selected - * A set accumulating selected items. If the item that is read is - * marked as selected, its item id should be added to this set. - * @param context - * the DesignContext instance used in parsing - * @return the item id of the new item - * - * @throws DesignException - * if the tag name of the {@code node} element is not - * {@code node}. - */ - @Override - protected String readItem(Element node, Set<String> selected, - DesignContext context) { - - if (!"node".equals(node.tagName())) { - throw new DesignException("Unrecognized child element in " - + getClass().getSimpleName() + ": " + node.tagName()); - } - - String itemId = node.attr("text"); - addItem(itemId); - if (node.hasAttr("icon")) { - Resource icon = DesignAttributeHandler.readAttribute("icon", - node.attributes(), Resource.class); - setItemIcon(itemId, icon); - } - if (node.hasAttr("selected")) { - selected.add(itemId); - } - - for (Element child : node.children()) { - String childItemId = readItem(child, selected, context); - setParent(childItemId, itemId); - } - return itemId; - } - - /** - * Recursively writes the root items and their children to a design. - * - * @since 7.5.0 - * @param design - * the element into which to insert the items - * @param context - * the DesignContext instance used in writing - */ - @Override - protected void writeItems(Element design, DesignContext context) { - for (Object itemId : rootItemIds()) { - writeItem(design, itemId, context); - } - } - - /** - * Recursively writes a data source Item and its children to a design. - * - * @since 7.5.0 - * @param design - * the element into which to insert the item - * @param itemId - * the id of the item to write - * @param context - * the DesignContext instance used in writing - * @return - */ - @Override - protected Element writeItem(Element design, Object itemId, - DesignContext context) { - Element element = design.appendElement("node"); - - element.attr("text", itemId.toString()); - - Resource icon = getItemIcon(itemId); - if (icon != null) { - DesignAttributeHandler.writeAttribute("icon", element.attributes(), - icon, null, Resource.class); - } - - if (isSelected(itemId)) { - element.attr("selected", ""); - } - - Collection<?> children = getChildren(itemId); - if (children != null) { - // Yeah... see #5864 - for (Object childItemId : children) { - writeItem(element, childItemId, context); - } - } - - return element; - } - - /** - * Sets whether html is allowed in the item captions. If set to - * <code>true</code>, the captions are passed to the browser as html and the - * developer is responsible for ensuring no harmful html is used. If set to - * <code>false</code>, the content is passed to the browser as plain text. - * The default setting is <code>false</code> - * - * @since 7.6 - * @param htmlContentAllowed - * <code>true</code> if the captions are used as html, - * <code>false</code> if used as plain text - */ - public void setHtmlContentAllowed(boolean htmlContentAllowed) { - this.htmlContentAllowed = htmlContentAllowed; - markAsDirty(); - } - - /** - * Checks whether captions are interpreted as html or plain text. - * - * @since 7.6 - * @return <code>true</code> if the captions are displayed as html, - * <code>false</code> if displayed as plain text - * @see #setHtmlContentAllowed(boolean) - */ - public boolean isHtmlContentAllowed() { - return htmlContentAllowed; - } - - @Override - protected TreeState getState() { - return (TreeState) super.getState(); - } -} |