/*
* Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.sun.dtv.lwuit;
import com.sun.dtv.lwuit.animations.Animation;
import com.sun.dtv.lwuit.animations.Transition;
import com.sun.dtv.lwuit.layouts.BorderLayout;
import com.sun.dtv.lwuit.layouts.FlowLayout;
import com.sun.dtv.lwuit.layouts.Layout;
import com.sun.dtv.lwuit.plaf.UIManager;
import com.sun.dtv.ui.Matte;
import com.sun.dtv.ui.MatteException;
import java.util.Enumeration;
import com.sun.dtv.lwuit.geom.Dimension;
import com.sun.dtv.lwuit.geom.Rectangle;
import com.sun.dtv.ui.MatteEnabled;
/**
* A composite pattern with {@link Component}, allows nesting and arranging multiple
* components using a pluggable layout manager architecture. Containers can be nested
* one within the other to form elaborate UI's.
*
* @see com.sun.lwuit.layouts
* @see Component
* @author Chen Fishbein
*/
public class Container extends Component implements MatteEnabled {
private Layout layout;
private java.util.Vector components = new java.util.Vector();
private boolean shouldLayout = true;
private boolean scrollableX;
private boolean scrollableY;
/**
* Constructs a new Container with a new layout manager.
*
* @param layout the specified layout manager
*/
public Container(Layout layout) {
super();
this.layout = layout;
setFocusable(false);
}
/**
* Constructs a new Container, with a {@link FlowLayout}.
*/
public Container() {
this(new FlowLayout());
}
/**
* Returns the layout manager responsible for arranging this container
*
* Presente no JAVADTV 1.1
* @return the container layout manager
*/
public Layout getLayout() {
return layout;
}
/**
* Sets the layout manager responsible for arranging this container
*
* @param layout the specified layout manager
*/
public void setLayout(Layout layout) {
this.layout = layout;
}
/**
* Same as setShouldCalcPreferredSize(true) but made accessible for
* layout managers
*/
public void invalidate() {
setShouldCalcPreferredSize(true);
}
/**
* @inheritDoc
*/
protected void setShouldCalcPreferredSize(boolean shouldCalcPreferredSize) {
super.setShouldCalcPreferredSize(shouldCalcPreferredSize);
shouldLayout = shouldCalcPreferredSize;
Enumeration enums = components.elements();
if (shouldLayout) {
while (enums.hasMoreElements()) {
Component cmp = (Component) enums.nextElement();
if (cmp instanceof Container) {
((Container) cmp).setShouldCalcPreferredSize(shouldCalcPreferredSize);
}
}
}
Form f = getComponentForm();
if (f != null) {
f.clearFocusVectors();
}
}
/**
* Returns the width for layout manager purposes, this takes scrolling
* into consideration unlike the getWidth method.
*
* Presente no JAVADTV 1.1
* @return the layout width
*/
public int getLayoutWidth() {
if (isScrollableX()) {
int scrollH = UIManager.getInstance().getLookAndFeel().getHorizontalScrollHeight();
return Math.max(getWidth() + scrollH, getPreferredSize().getWidth() + scrollH);
} else {
Container parent = getScrollableParent();
if (parent != null && parent.isScrollableX()) {
return Math.max(getWidth(), getPreferredSize().getWidth());
}
int width = getWidth();
if (width <= 0) {
return getPreferredSize().getWidth();
}
return width;
}
}
/**
* Returns the height for layout manager purposes, this takes scrolling
* into consideration unlike the getWidth method.
*
* Presente no JAVADTV 1.1
* @return the layout height
*/
public int getLayoutHeight() {
if (isScrollableY()) {
int scrollW = UIManager.getInstance().getLookAndFeel().getVerticalScrollWidth();
return Math.max(getHeight() + scrollW, getPreferredSize().getHeight() + scrollW);
} else {
Container parent = getScrollableParent();
if (parent != null && parent.isScrollableY()) {
return Math.max(getHeight(), getPreferredSize().getHeight());
}
int height = getHeight();
if (height <= 1) {
return getPreferredSize().getHeight();
}
return height;
}
}
/**
* Returns a parent container that is scrollable or null if no parent is
* scrollable.
*
* @return a parent container that is scrollable or null if no parent is
* scrollable.
*/
private Container getScrollableParent() {
Container parent = getParent();
while (parent != null) {
if (parent.isScrollable()) {
return parent;
}
parent = parent.getParent();
}
return null;
}
/**
* Adds a Component to the Container
*
* Presente no JAVADTV 1.1
* @param cmp the component to be added
*/
public void addComponent(Component cmp) {
// helper check for a common mistake...
if (layout instanceof BorderLayout) {
throw new IllegalArgumentException("Cannot add component to BorderLayout Container without constraint parameter");
}
insertComponentAt(components.size(), cmp);
}
/**
* Adds a Component to the Container
*
* Presente no JAVADTV 1.1
* @param constraints this method is useful when the Layout requires a constraint
* such as the BorderLayout.
* In this case you need to specify an additional data when you add a Component,
* such as "CENTER", "NORTH"...
*
* @param cmp component to add
*/
public void addComponent(Object constraints, Component cmp) {
layout.addLayoutComponent(constraints, cmp, this);
insertComponentAt(components.size(), cmp);
}
private void insertComponentAt(int index, Component cmp) {
if (cmp.getParent() != null) {
throw new IllegalArgumentException("Component is already contained in Container: " + cmp.getParent());
}
if (cmp instanceof Form) {
throw new IllegalArgumentException("A form cannot be added to a container");
}
cmp.setParent(this);
components.insertElementAt(cmp, index);
setShouldCalcPreferredSize(true);
if (isInitialized()) {
cmp.initComponentImpl();
}
Form f = getComponentForm();
if (f != null) {
f.clearFocusVectors();
}
//repaint();
}
/**
* This method adds the Component at a specific index location in the Conatiner
* Components array.
*
* Presente no JAVADTV 1.1
* @param index location to insert the Component
* @param cmp the Component to add
* @throws ArrayIndexOutOfBoundsException if index is out of bounds
* @throws IllegalArgumentException if Component is already contained or
* the cmp is a Form Component
*/
public void addComponent(int index, Component cmp) {
insertComponentAt(index, cmp);
}
/**
* This method replaces the current Component with the next Component.
* Current Component must be contained in this Container.
* This method return immediately.
*
* @param current a Component to remove from the Container
* @param next a Component that replaces the current Component
* @param t a Transition between the add and removal of the Components
* a Transition can be null
*/
public void replace(final Component current, final Component next, final Transition t) {
if (!contains(current)) {
throw new IllegalArgumentException("Component " + current + " is not contained in this Container");
}
if (t == null) {
replace(current, next);
return;
}
next.setX(current.getX());
next.setY(current.getY());
next.setWidth(current.getWidth());
next.setHeight(current.getHeight());
next.setParent(this);
if (next instanceof Container) {
((Container) next).layoutContainer();
}
Animation anim = new Anim(this, current, next, t);
// block events as long as the transition is animating
Display.getInstance().blockEvents(true);
// register the transition animation
getComponentForm().registerAnimated(anim);
}
void replace(final Component current, final Component next) {
int index = components.indexOf(current);
boolean currentFocused = false;
if (current.getComponentForm() != null && current.getComponentForm().getFocused() == current) {
currentFocused = true;
}
if (layout instanceof BorderLayout) {
Object constraint = layout.getComponentConstraint(current);
removeComponent(current);
layout.addLayoutComponent(constraint, next, Container.this);
} else {
removeComponent(current);
}
next.setParent(null);
if (index < 0) {
index = 0;
}
insertComponentAt(index, next);
if (currentFocused && next.isFocusable()) {
next.requestFocus();
}
}
/**
* @inheritDoc
*/
void initComponentImpl() {
if (!isInitialized()) {
if (isSmoothScrolling()) {
getComponentForm().registerAnimated(this);
}
super.initComponentImpl();
}
Enumeration e = components.elements();
while (e.hasMoreElements()) {
((Component) e.nextElement()).initComponentImpl();
}
}
/**
* removes a Component from the Container
*
* @param cmp the removed component
*/
public void removeComponent(Component cmp) {
Form parentForm = cmp.getComponentForm();
layout.removeLayoutComponent(cmp);
cmp.deinitializeImpl();
components.removeElement(cmp);
cmp.setParent(null);
cmp.setShouldCalcPreferredSize(true);
if (parentForm != null) {
if (parentForm.getFocused() == cmp || cmp instanceof Container && ((Container) cmp).contains(parentForm.getFocused())) {
parentForm.setFocused(null);
}
parentForm.clearFocusVectors();
if (cmp.isSmoothScrolling()) {
parentForm.deregisterAnimated(cmp);
}
}
setShouldCalcPreferredSize(true);
}
/**
* Cleansup the initialization flags in the hierachy
*/
void deinitializeImpl() {
super.deinitializeImpl();
int size = components.size();
for (int iter = 0; iter < size; iter++) {
((Component) components.elementAt(iter)).deinitializeImpl();
}
}
/**
* remove all Components from container
*/
public void removeAll() {
Form parentForm = getComponentForm();
if (parentForm != null) {
Component focus = parentForm.getFocused();
if (focus != null && contains(focus)) {
parentForm.setFocused(null);
}
}
Object[] arr = new Object[components.size()];
components.copyInto(arr);
for (int i = 0; i < arr.length; i++) {
removeComponent((Component) arr[i]);
}
}
/**
* Re-layout the container, this is useful when we modify the container hierarchy and
* need to redo the layout
*/
public void revalidate() {
setShouldCalcPreferredSize(true);
Form root = getComponentForm();
if (root != null) {
root.layoutContainer();
root.repaint();
} else {
layoutContainer();
repaint();
}
}
/**
* @inheritDoc
*/
public void paint(Graphics g) {
layoutContainer();
g.translate(getX(), getY());
Enumeration enums = components.elements();
while (enums.hasMoreElements()) {
Component cmp = (Component) enums.nextElement();
cmp.paintInternal(g);
}
g.translate(-getX(), -getY());
}
void paintIntersecting(Graphics g, Component cmp, Rectangle bounds, boolean above) {
if (layout.isOverlapSupported() && components.contains(cmp)) {
int indexOfComponent = components.indexOf(cmp);
int startIndex;
int endIndex;
if (above) {
startIndex = indexOfComponent + 1;
endIndex = components.size();
} else {
startIndex = 0;
endIndex = indexOfComponent;
}
for (int i = startIndex; i < endIndex; i++) {
Component cmp2 = (Component) components.elementAt(i);
Rectangle rect = new Rectangle(cmp2.getBounds());
rect.setX(cmp2.getAbsoluteX());
rect.setY(cmp2.getAbsoluteY());
if (rect.intersects(bounds)) {
cmp2.paintInternal(g, false);
}
}
}
}
/**
* Performs the layout of the container if a layout is necessary
*/
public void layoutContainer() {
//will compute the container + components and will layout the components.
if (shouldLayout) {
shouldLayout = false;
doLayout();
}
}
/**
* Lays out the container
*/
void doLayout() {
layout.layoutContainer(this);
int count = getComponentCount();
for (int i = 0; i <
count; i++) {
Component c = getComponentAt(i);
if (c instanceof Container) {
((Container) c).doLayout();
}
}
}
/**
* Returns the number of components
*
* Presente no JAVADTV 1.1
* @return the Component count
*/
public int getComponentCount() {
return components.size();
}
/**
* Returns the Component at a given index
*
* Presente no JAVADTV 1.1
* @param index of the Component you wish to get
* @return a Component
* @throws ArrayIndexOutOfBoundsException if an invalid index was given.
*/
public Component getComponentAt(int index) {
return (Component) components.elementAt(index);
}
/**
* Returns the Component index in the Container
*
* Presente no JAVADTV 1.1
* @param cmp the component to search for
* @return the Component index in the Container or -1 if not found
*/
public int getComponentIndex(Component cmp) {
int count = getComponentCount();
for (int i = 0; i <
count; i++) {
Component c = getComponentAt(i);
if (c.equals(cmp)) {
return i;
}
}
return -1;
}
/**
* Returns true if the given component is within the hierarchy of this container
*
* Presente no JAVADTV 1.1
* @param cmp a Component to check
* @return true if this Component contains in this Container
*/
public boolean contains(Component cmp) {
boolean found = false;
int count = getComponentCount();
for (int i = 0; i < count; i++) {
Component c = getComponentAt(i);
if (c.equals(cmp)) {
return true;
}
if (c instanceof Container) {
found = ((Container) c).contains(cmp);
if (found) {
return true;
}
}
}
return false;
}
/**
* Makes sure the component is visible in the scroll if this container is
* scrollable
*
* @param c the component that will be scrolling for visibility
*/
protected void scrollComponentToVisible(Component c) {
if (isScrollable()) {
if (c != null) {
if (c.getParent() != null) {
// special case for the first component to allow the user to scroll all the
// way to the top
Form f = getComponentForm();
if (f != null && f.getFocusPosition(c) == 0) {
scrollRectToVisible(new Rectangle(0, 0, c.getX() + c.getWidth(), c.getY() + c.getHeight()), c);
return;
}
}
scrollRectToVisible(c.getBounds(), c);
}
}
}
/**
* Returns a Component that exists in the given x, y coordinates by traversing
* component objects and invoking contains
*
* Presente no JAVADTV 1.1
* @param x absolute screen location
* @param y absolute screen location
* @return a Component if found, null otherwise
* @see Component#contains
*/
public Component getComponentAt(int x, int y) {
int count = getComponentCount();
for (int i = count - 1; i >= 0; i--) {
Component cmp = getComponentAt(i);
if (cmp.contains(x, y)) {
if (cmp instanceof Container) {
return ((Container) cmp).getComponentAt(x, y);
}
return cmp;
}
}
return null;
}
/**
* @inheritDoc
*/
public void pointerPressed(int x, int y) {
if (!isDragActivated()) {
Component cmp = getComponentAt(x, y);
if (cmp != null) {
cmp.pointerPressed(x, y);
}
}
}
/**
* @inheritDoc
*/
public void pointerReleased(int x, int y) {
if (isDragActivated()) {
super.pointerReleased(x, y);
return;
}
Component cmp = getComponentAt(x, y);
if (cmp != null) {
cmp.pointerReleased(x, y);
}
}
/**
* @inheritDoc
*/
Dimension calcPreferredSize() {
Dimension d = layout.getPreferredSize(this);
return d;
}
// /**
// * @inheritDoc
// */
// public boolean isFocusable() {
// return false;
// }
/**
* @inheritDoc
*/
protected String paramString() {
String className = layout.getClass().getName();
String layoutStr = className.substring(className.lastIndexOf('.') + 1);
return super.paramString() + ", layout = " + layoutStr +
", scrollableX = " + scrollableX +
", scrollableY = " + scrollableY +
", components = " + getComponentsNames();
}
/**
* Return the conatainer components objects as list of Strings
* @return the conatainer components objects as list of Strings
*/
private String getComponentsNames() {
String ret = "[";
Enumeration enums = components.elements();
while (enums.hasMoreElements()) {
String className = enums.nextElement().getClass().getName();
ret += className.substring(className.lastIndexOf('.') + 1) + ", ";
}
return ret.substring(0, ret.length() - 2) + "]";
}
/**
* @inheritDoc
*/
public void refreshTheme() {
super.refreshTheme();
Enumeration enums = components.elements();
while (enums.hasMoreElements()) {
Component cmp = (Component) enums.nextElement();
cmp.refreshTheme();
}
}
/**
* @inheritDoc
*/
public boolean isScrollableX() {
return scrollableX && getPreferredSize().getWidth() > getWidth();
}
/**
* @inheritDoc
*/
public boolean isScrollableY() {
return scrollableY && getPreferredSize().getHeight() > getHeight();
}
/**
* Sets whether the component should/could scroll on the X axis
*
* @param scrollableX whether the component should/could scroll on the X axis
*/
public void setScrollableX(boolean scrollableX) {
this.scrollableX = scrollableX;
}
/**
* Sets whether the component should/could scroll on the Y axis
*
* @param scrollableY whether the component should/could scroll on the Y axis
*/
public void setScrollableY(boolean scrollableY) {
this.scrollableY = scrollableY;
}
/**
* The equivalent of calling both setScrollableY and setScrollableX
*
* @param scrollable whether the component should/could scroll on the
* X and Y axis
*/
public void setScrollable(boolean scrollable) {
setScrollableX(scrollable);
setScrollableY(scrollable);
}
/**
* @inheritDoc
*/
public void setCellRenderer(boolean cellRenderer) {
if (isCellRenderer() != cellRenderer) {
super.setCellRenderer(cellRenderer);
int size = getComponentCount();
for (int iter = 0; iter <
size; iter++) {
getComponentAt(iter).setCellRenderer(cellRenderer);
}
}
}
/**
* @inheritDoc
*/
String getUIID() {
return "Container";
}
/**
* @return
*/
public Matte getMatte() {
// TODO implementar
throw new UnsupportedOperationException("Not supported yet.");
}
public void setMatte(Matte matte) throws MatteException {
// TODO implementar
throw new UnsupportedOperationException("Not supported yet.");
}
}
class Anim implements Animation {
private Transition t;
private Component current;
private Component next;
private boolean started = false;
private Container thisContainer;
public Anim(Container thisContainer, Component current, Component next, Transition t) {
this.t = t;
this.next = next;
this.current = current;
this.thisContainer = thisContainer;
}
public boolean animate() {
if (!started) {
t.init(current, next);
t.initTransition();
started = true;
}
boolean notFinished = t.animate();
if (!notFinished) {
next.setParent(null);
thisContainer.replace(current, next);
//release the events blocking
Display.getInstance().blockEvents(false);
t.cleanup();
thisContainer.getComponentForm().deregisterAnimated(this);
}
return notFinished;
}
public void paint(Graphics g) {
t.paint(g);
}
}