/* * Ext GWT - Ext for GWT * Copyright(c) 2007-2009, Ext JS, LLC. * licensing@extjs.com * * http://extjs.com/license */ package com.extjs.gxt.ui.client.widget; import com.extjs.gxt.ui.client.GXT; import com.extjs.gxt.ui.client.Style; import com.extjs.gxt.ui.client.aria.FocusFrame; import com.extjs.gxt.ui.client.core.El; import com.extjs.gxt.ui.client.event.BoxComponentEvent; import com.extjs.gxt.ui.client.event.ComponentEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.util.Point; import com.extjs.gxt.ui.client.util.Rectangle; import com.extjs.gxt.ui.client.util.Size; import com.extjs.gxt.ui.client.widget.Layer.ShadowPosition; import com.google.gwt.user.client.Event; /** * Base class for any visual {@link Component} that uses a box container. * {@link BoxComponent} provides automatic box model adjustments for sizing and * positioning and will work correctly within the {@link Component} rendering * model. * * <dl> * <dt>Events:</dt> * * <dd><b>Resize</b> : BoxComponentEvent(boxComponent, width, height)<br> * <div>Fires after the component is resized.</div> * <ul> * <li>boxComponent : this</li> * <li>width : the widget width</li> * <li>height : the widget height</li> * </ul> * * <dd><b>Move</b> : BoxComponentEvent(boxComponent, x, y)<br> * <div>Fires after the component is moved.</div> * <ul> * <li>boxComponent : this</li> * <li>x : the new x position</li> * <li>y : the new y position</li> * </ul> * </dd> * </dl> * * <dl> * <dt>Inherited Events:</dt> * <dd>Component Enable</dd> * <dd>Component Disable</dd> * <dd>Component BeforeHide</dd> * <dd>Component Hide</dd> * <dd>Component BeforeShow</dd> * <dd>Component Show</dd> * <dd>Component Attach</dd> * <dd>Component Detach</dd> * <dd>Component BeforeRender</dd> * <dd>Component Render</dd> * <dd>Component BrowserEvent</dd> * <dd>Component BeforeStateRestore</dd> * <dd>Component StateRestore</dd> * <dd>Component BeforeStateSave</dd> * <dd>Component SaveState</dd> * </dl> */ public class BoxComponent extends Component { /** * True to adjust sizes for box model issues to ensure actual size matches set * size. */ protected boolean adjustSize = true; /** * True to enable a shim which uses a transparent iframe to stop content from * bleeding through. */ protected boolean shim; /** * True to cache size calculation. */ protected boolean cacheSizes = true; /** * A specialized El that provides support for a shadow and shim. Will be * created if either {@link #shadow} or {@link #shim} is set to true. */ protected Layer layer; protected Size lastSize; protected String width, height; private boolean shadow; private ShadowPosition shadowPosition = ShadowPosition.SIDES; private int shadowOffset = 4; private boolean deferHeight; private boolean autoHeight; private boolean autoWidth; private int left = Style.DEFAULT, top = Style.DEFAULT; private int pageX = Style.DEFAULT, pageY = Style.DEFAULT; private boolean boxReady; /** * Gets the current box measurements of the component's underlying element. * The component must be attached to return page coordinates. * * @param local if true the element's left and top are returned instead of * page coordinates * @return the component's bounds */ public Rectangle getBounds(boolean local) { return el().getBounds(local); } /** * Returns the component's offset height. * * @return the height */ public int getHeight() { return el().getHeight(); } /** * Return the component's height. * * @param content true to get the height minus borders and padding * @return the height */ public int getHeight(boolean content) { return el().getHeight(content); } /** * Returns the component's current position. The component must be attached to * return page coordinates. * * @param local true to return the element's left and top rather than page * coordinates * @return the position */ public Point getPosition(boolean local) { if (local) { return new Point(el().getLeft(true), el().getTop(true)); } return el().getXY(); } /** * Returns true if the shadow is enabled. * * @return the shadow the shadow state */ public boolean getShadow() { return shadow; } /** * Returns the shadow position. * * @return the shadow position */ public ShadowPosition getShadowPosition() { return shadowPosition; } /** * Returns the shadow offset. * * @return the shadow offset */ public int getShadowOffset() { return shadowOffset; } /** * Returns the component's size. * * @return the size */ public Size getSize() { return el().getSize(); } /** * Returns the component's width. * * @return the width */ public int getWidth() { return getOffsetWidth(); } /** * Returns the component's width. * * @param content true to get width minus borders and padding * @return the width */ public int getWidth(boolean content) { return el().getWidth(content); } /** * @return the autoHeight */ public boolean isAutoHeight() { return autoHeight; } /** * @return the autoWidth */ public boolean isAutoWidth() { return autoWidth; } /** * Returns true if the height is being deferred * * @return the defer height state */ public boolean isDeferHeight() { return deferHeight; } /** * Returns true if shimming is enabled. * * @return the shim state */ public boolean isShim() { return shim; } /** * Sets the component's auto height value (defaults to false). * * @param autoHeight true to enable auto height */ public void setAutoHeight(boolean autoHeight) { this.autoHeight = autoHeight; } /** * True to use width:'auto', false to use fixed width (defaults to false). * * @param autoWidth the auto width state */ public void setAutoWidth(boolean autoWidth) { this.autoWidth = autoWidth; } /** * Sets the component's size. This method fires the <i>Move</i> and * <i>Resize</i> events. element. * * @param x the x coordinate * @param y the y coordinate * @param width the width * @param height the height */ public void setBounds(int x, int y, int width, int height) { setPagePosition(x, y); setSize(width, height); } /** * Sets the component's size. This method fires the <i>Move</i> and * <i>Resize</i> events. element. * * @param bounds the update box */ public void setBounds(Rectangle bounds) { setBounds(bounds.x, bounds.y, bounds.width, bounds.height); } /** * True to defer height calculations to an external component, false to allow * this component to set its own height (defaults to false). * * @param deferHeight true to defer height */ public void setDeferHeight(boolean deferHeight) { this.deferHeight = deferHeight; } /** * Sets the component's height. This method fires the <i>Resize</i> event. * element. * * @param height the new height */ public void setHeight(int height) { setSize(-1, height); } /** * Sets the height of the component. This method fires the <i>Resize</i> * event. element. * * @param height the new height to set */ public void setHeight(String height) { setSize(Style.UNDEFINED, height); } /** * Sets the page XY position of the component. To set the left and top * instead, use {@link #setPosition}. This method fires the <i>Move</i> event. * * @param x the x coordinate * @param y the y coordinate */ public void setPagePosition(int x, int y) { if (x != Style.DEFAULT) { pageX = x; } if (y != Style.DEFAULT) { pageY = y; } if (!boxReady) { return; } Point p = getPositionEl().translatePoints(new Point(x, y)); setPosition(p.x, p.y); } /** * Sets the page XY position of the component. To set the left and top * instead, use {@link #setPosition}. This method fires the <i>Move</i> event. * * @param point the new location */ public void setPagePosition(Point point) { setPagePosition(point.x, point.y); } /** * Sets the width and height of the component. This method fires the resize * event. * * @param width the new width to set * @param height the new height to set */ public void setPixelSize(int width, int height) { setSize(width, height); } /** * Sets the left and top of the component. To set the page XY position * instead, use {@link #setPagePosition}. This method fires the move event. * * @param left the new left * @param top the new top */ public void setPosition(int left, int top) { this.left = left; this.top = top; if (!boxReady) { return; } Point adj = adjustPosition(new Point(left, top)); int ax = adj.x, ay = adj.y; El pel = getPositionEl(); if (ax != Style.DEFAULT || ay != Style.DEFAULT) { if (ax != Style.DEFAULT && ay != Style.DEFAULT) { pel.setLeftTop(ax, ay); } else if (ax != Style.DEFAULT) { pel.setLeft(ax); } else if (ay != Style.DEFAULT) { pel.setTop(ay); } onPosition(ax, ay); FocusFrame.get().sync(this); BoxComponentEvent be = (BoxComponentEvent) createComponentEvent(null); be.setX(ax); be.setY(ay); fireEvent(Events.Move, be); } } /** * True to enable a shadow that will be displayed behind the component * (defaults to false). * * @param shadow true to enable the shadow */ public void setShadow(boolean shadow) { this.shadow = shadow; } /** * Sets the shadow position (defaults to SIDES). * * @param shadowPosition the position */ public void setShadowPosition(ShadowPosition shadowPosition) { this.shadowPosition = shadowPosition; if (layer != null) { layer.setShadowPosition(shadowPosition); } } /** * Sets the shadow offset (defaults to 4). * * @param shadowOffset the offset */ public void setShadowOffset(int shadowOffset) { this.shadowOffset = shadowOffset; setShadowPosition(shadowPosition); } /** * True to enable a shim which uses a transparent iframe to stop content from * bleeding through. * * @param shim true to enable a shim */ public void setShim(boolean shim) { this.shim = shim; } /** * Sets the width and height of the component. This method fires the * <i>Resize</i> event. * * @param width the new width to set * @param height the new height to set */ public void setSize(int width, int height) { if (!boxReady) { if (width != -1) { this.width = width + "px"; } if (height != -1) { this.height = height + "px"; } return; } Size size = new Size(width, height); if (cacheSizes && lastSize != null && lastSize.equals(size)) { return; } lastSize = size; Size ads = adjustSize(size); int aw = ads.width; int ah = ads.height; if (autoWidth) { setStyleAttribute("width", "auto"); } else { el().setWidth(aw, adjustSize); } if (autoHeight) { setStyleAttribute("height", "auto"); } else { if (!deferHeight) { el().setHeight(ah, adjustSize); } } onResize(aw, ah); sync(true); FocusFrame.get().sync(this); BoxComponentEvent ce = (BoxComponentEvent) createComponentEvent(null); ce.setWidth(aw); ce.setHeight(ah); fireEvent(Events.Resize, ce); } /** * Sets the width and height of the component. This method fires the * <i>Resize</i> event. * * @param width the new width to set * @param height the new height to set */ public void setSize(String width, String height) { if (!boxReady) { if (width != null && !width.equals(Style.UNDEFINED)) { this.width = width; } if (height != null && !height.equals(Style.UNDEFINED)) { this.height = height; } return; } if (width == null) { width = Style.UNDEFINED; } if (height == null) { height = Style.UNDEFINED; } if (!width.equals(Style.UNDEFINED)) { width = El.addUnits(width, "px"); } if (!height.equals(Style.UNDEFINED)) { height = El.addUnits(height, "px"); } if (autoWidth) { el().setWidth("auto"); } else if (!width.equals(Style.UNDEFINED)) { el().setWidth(width); } if (autoHeight) { el().setHeight("auto"); } else if (!height.equals(Style.UNDEFINED)) { if (!deferHeight) { el().setHeight(height); } } int w = -1; int h = -1; if (width.indexOf("px") != -1) { w = Integer.parseInt(width.substring(0, width.indexOf("px"))); } else if (autoWidth || width.equals("auto")) { w = -1; } else if (!width.equals(Style.UNDEFINED)) { w = getOffsetWidth(); } if (height.indexOf("px") != -1) { h = Integer.parseInt(height.substring(0, height.indexOf("px"))); } else if (autoHeight || height.equals("auto")) { h = -1; } else if (!height.equals(Style.UNDEFINED)) { h = getOffsetHeight(); } Size size = new Size(w, h); if (cacheSizes && lastSize != null && lastSize.equals(size)) { return; } lastSize = size; onResize(w, h); sync(true); FocusFrame.get().sync(this); BoxComponentEvent evt = (BoxComponentEvent) createComponentEvent(null); evt.setWidth(w); evt.setHeight(h); fireEvent(Events.Resize, evt); } /** * Sets the width of the component. This method fires the <i>Resize</i> event. * * @param width the new width to set */ public void setWidth(int width) { setSize(width, -1); } /** * Sets the width of the component. This method fires the <i>Resize</i> event. * * @param width the new width to set */ public void setWidth(String width) { setSize(width, Style.UNDEFINED); } /** * Syncs the layer of the component. */ public void sync(boolean show) { if (layer != null) { layer.sync(show); } } public void syncSize() { lastSize = null; if (rendered) { setSize(getWidth(), getHeight()); } } protected Point adjustPosition(Point point) { return point; } protected Size adjustSize(Size size) { return size; } protected void afterRender() { super.afterRender(); boxReady = true; if (shadow || (shim && GXT.useShims)) { layer = new Layer(getElement()); if (shadow) { layer.enableShadow(); layer.setShadowPosition(shadowPosition); layer.setShadowOffset(shadowOffset); } if (shim && GXT.useShims) { layer.enableShim(); } setEl(layer); } if (width != null || height != null) { setSize(width, height); } if (left != Style.DEFAULT || top != Style.DEFAULT) { setPosition(left, top); } if (pageX != Style.DEFAULT || pageY != Style.DEFAULT) { setPagePosition(pageX, pageY); } } @Override protected ComponentEvent createComponentEvent(Event event) { BoxComponentEvent e = new BoxComponentEvent(this, event); return e; } /** * Returns the element to be used when positioning the component. Subclasses * may override as needed. Default method returns the component's root * element. * * @return the position element */ protected El getPositionEl() { return el(); } /** * Returns the element to be used when resizing the component. Subclasses may * override as needed. Default method returns the component's root element. * * @return the resize element */ protected El getResizeEl() { return el(); } protected void hideShadow() { if (layer != null) { layer.hideShadow(); } } protected void hideShim() { if (layer != null) { layer.hideShim(); } } protected void hideUnders() { if (layer != null) { layer.hideUnders(); } } @Override protected void onDetach() { super.onDetach(); if (!hidden) { hideUnders(); } } protected void onHide() { super.onHide(); hideUnders(); } /** * Called after the component is moved, this method is empty by default but * can be implemented by any subclass that needs to perform custom logic after * a move occurs. * * @param x the new x position * @param y the new y position */ protected void onPosition(int x, int y) { } /** * Called after the component is resized, this method is empty by default but * can be implemented by any subclass that needs to perform custom logic after * a resize occurs. * * @param width the width * @param height the height */ protected void onResize(int width, int height) { if (mask) { mask(maskMessage, maskMessageStyleName); } } protected void onShow() { super.onShow(); sync(true); } }