/* * Copyright 2011 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.lang.reflect.Method; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import com.vaadin.terminal.gwt.server.ComponentSizeValidator; /** * Extension to {@link AbstractComponent} that defines the default * implementation for the methods in {@link ComponentContainer}. Basic UI * components that need to contain other components inherit this class to easily * qualify as a component container. * * @author Vaadin Ltd * @since 3.0 */ @SuppressWarnings("serial") public abstract class AbstractComponentContainer extends AbstractComponent implements ComponentContainer { /** * Constructs a new component container. */ public AbstractComponentContainer() { super(); } /** * Removes all components from the container. This should probably be * re-implemented in extending classes for a more powerful implementation. */ @Override public void removeAllComponents() { final LinkedList l = new LinkedList(); // Adds all components for (final Iterator i = getComponentIterator(); i.hasNext();) { l.add(i.next()); } // Removes all component for (final Iterator i = l.iterator(); i.hasNext();) { removeComponent(i.next()); } } /* * Moves all components from an another container into this container. Don't * add a JavaDoc comment here, we use the default documentation from * implemented interface. */ @Override public void moveComponentsFrom(ComponentContainer source) { final LinkedList components = new LinkedList(); for (final Iterator i = source.getComponentIterator(); i .hasNext();) { components.add(i.next()); } for (final Iterator i = components.iterator(); i.hasNext();) { final Component c = i.next(); source.removeComponent(c); addComponent(c); } } /* Events */ private static final Method COMPONENT_ATTACHED_METHOD; private static final Method COMPONENT_DETACHED_METHOD; static { try { COMPONENT_ATTACHED_METHOD = ComponentAttachListener.class .getDeclaredMethod("componentAttachedToContainer", new Class[] { ComponentAttachEvent.class }); COMPONENT_DETACHED_METHOD = ComponentDetachListener.class .getDeclaredMethod("componentDetachedFromContainer", new Class[] { ComponentDetachEvent.class }); } catch (final java.lang.NoSuchMethodException e) { // This should never happen throw new java.lang.RuntimeException( "Internal error finding methods in AbstractComponentContainer"); } } /* documented in interface */ @Override public void addListener(ComponentAttachListener listener) { addListener(ComponentContainer.ComponentAttachEvent.class, listener, COMPONENT_ATTACHED_METHOD); } /* documented in interface */ @Override public void addListener(ComponentDetachListener listener) { addListener(ComponentContainer.ComponentDetachEvent.class, listener, COMPONENT_DETACHED_METHOD); } /* documented in interface */ @Override public void removeListener(ComponentAttachListener listener) { removeListener(ComponentContainer.ComponentAttachEvent.class, listener, COMPONENT_ATTACHED_METHOD); } /* documented in interface */ @Override public void removeListener(ComponentDetachListener listener) { removeListener(ComponentContainer.ComponentDetachEvent.class, listener, COMPONENT_DETACHED_METHOD); } /** * Fires the component attached event. This should be called by the * addComponent methods after the component have been added to this * container. * * @param component * the component that has been added to this container. */ protected void fireComponentAttachEvent(Component component) { fireEvent(new ComponentAttachEvent(this, component)); } /** * Fires the component detached event. This should be called by the * removeComponent methods after the component have been removed from this * container. * * @param component * the component that has been removed from this container. */ protected void fireComponentDetachEvent(Component component) { fireEvent(new ComponentDetachEvent(this, component)); } /** * This only implements the events and component parent calls. The extending * classes must implement component list maintenance and call this method * after component list maintenance. * * @see com.vaadin.ui.ComponentContainer#addComponent(Component) */ @Override public void addComponent(Component c) { if (c instanceof ComponentContainer) { // Make sure we're not adding the component inside it's own content for (Component parent = this; parent != null; parent = parent .getParent()) { if (parent == c) { throw new IllegalArgumentException( "Component cannot be added inside it's own content"); } } } if (c.getParent() != null) { // If the component already has a parent, try to remove it ComponentContainer oldParent = (ComponentContainer) c.getParent(); oldParent.removeComponent(c); } c.setParent(this); fireComponentAttachEvent(c); } /** * This only implements the events and component parent calls. The extending * classes must implement component list maintenance and call this method * before component list maintenance. * * @see com.vaadin.ui.ComponentContainer#removeComponent(Component) */ @Override public void removeComponent(Component c) { if (c.getParent() == this) { c.setParent(null); fireComponentDetachEvent(c); } } @Override public void setVisible(boolean visible) { if (getState().isVisible() == visible) { return; } super.setVisible(visible); // If the visibility state is toggled it might affect all children // aswell, e.g. make container visible should make children visible if // they were only hidden because the container was hidden. markAsDirtyRecursive(); } @Override public void setWidth(float width, Unit unit) { /* * child tree repaints may be needed, due to our fall back support for * invalid relative sizes */ Collection dirtyChildren = null; boolean childrenMayBecomeUndefined = false; if (getWidth() == SIZE_UNDEFINED && width != SIZE_UNDEFINED) { // children currently in invalid state may need repaint dirtyChildren = getInvalidSizedChildren(false); } else if ((width == SIZE_UNDEFINED && getWidth() != SIZE_UNDEFINED) || (unit == Unit.PERCENTAGE && getWidthUnits() != Unit.PERCENTAGE && !ComponentSizeValidator .parentCanDefineWidth(this))) { /* * relative width children may get to invalid state if width becomes * invalid. Width may also become invalid if units become percentage * due to the fallback support */ childrenMayBecomeUndefined = true; dirtyChildren = getInvalidSizedChildren(false); } super.setWidth(width, unit); repaintChangedChildTrees(dirtyChildren, childrenMayBecomeUndefined, false); } private void repaintChangedChildTrees( Collection invalidChildren, boolean childrenMayBecomeUndefined, boolean vertical) { if (childrenMayBecomeUndefined) { Collection previouslyInvalidComponents = invalidChildren; invalidChildren = getInvalidSizedChildren(vertical); if (previouslyInvalidComponents != null && invalidChildren != null) { for (Iterator iterator = invalidChildren.iterator(); iterator .hasNext();) { Component component = iterator.next(); if (previouslyInvalidComponents.contains(component)) { // still invalid don't repaint iterator.remove(); } } } } else if (invalidChildren != null) { Collection stillInvalidChildren = getInvalidSizedChildren(vertical); if (stillInvalidChildren != null) { for (Component component : stillInvalidChildren) { // didn't become valid invalidChildren.remove(component); } } } if (invalidChildren != null) { repaintChildTrees(invalidChildren); } } private Collection getInvalidSizedChildren(final boolean vertical) { HashSet components = null; if (this instanceof Panel) { Panel p = (Panel) this; ComponentContainer content = p.getContent(); boolean valid = vertical ? ComponentSizeValidator .checkHeights(content) : ComponentSizeValidator .checkWidths(content); if (!valid) { components = new HashSet(1); components.add(content); } } else { for (Iterator componentIterator = getComponentIterator(); componentIterator .hasNext();) { Component component = componentIterator.next(); boolean valid = vertical ? ComponentSizeValidator .checkHeights(component) : ComponentSizeValidator .checkWidths(component); if (!valid) { if (components == null) { components = new HashSet(); } components.add(component); } } } return components; } private void repaintChildTrees(Collection dirtyChildren) { for (Component c : dirtyChildren) { c.markAsDirtyRecursive(); } } @Override public void setHeight(float height, Unit unit) { /* * child tree repaints may be needed, due to our fall back support for * invalid relative sizes */ Collection dirtyChildren = null; boolean childrenMayBecomeUndefined = false; if (getHeight() == SIZE_UNDEFINED && height != SIZE_UNDEFINED) { // children currently in invalid state may need repaint dirtyChildren = getInvalidSizedChildren(true); } else if ((height == SIZE_UNDEFINED && getHeight() != SIZE_UNDEFINED) || (unit == Unit.PERCENTAGE && getHeightUnits() != Unit.PERCENTAGE && !ComponentSizeValidator .parentCanDefineHeight(this))) { /* * relative height children may get to invalid state if height * becomes invalid. Height may also become invalid if units become * percentage due to the fallback support. */ childrenMayBecomeUndefined = true; dirtyChildren = getInvalidSizedChildren(true); } super.setHeight(height, unit); repaintChangedChildTrees(dirtyChildren, childrenMayBecomeUndefined, true); } @Override public Iterator iterator() { return getComponentIterator(); } /* * (non-Javadoc) * * @see * com.vaadin.ui.HasComponents#isComponentVisible(com.vaadin.ui.Component) */ @Override public boolean isComponentVisible(Component childComponent) { return true; } }