--- /dev/null
+/*
+ * CoordinateLayout
+ */
+
+.i-coordinatelayout-margin-values{
+ margin-top: 0px;
+ margin-right: 0px;
+ margin-bottom: 0px;
+ margin-left: 0px;
+}
\ No newline at end of file
font-size:xx-small;
}
+/*
+ * CoordinateLayout
+ */
+
+.i-coordinatelayout-margin-values{
+ margin-top: 0px;
+ margin-right: 0px;
+ margin-bottom: 0px;
+ margin-left: 0px;
+}
.i-datefield-button {
font-size:13px;
width: 22px;
import com.itmill.toolkit.terminal.gwt.client.ui.IAccordion;
import com.itmill.toolkit.terminal.gwt.client.ui.IButton;
import com.itmill.toolkit.terminal.gwt.client.ui.ICheckBox;
+import com.itmill.toolkit.terminal.gwt.client.ui.ICoordinateLayout;
import com.itmill.toolkit.terminal.gwt.client.ui.ICustomComponent;
import com.itmill.toolkit.terminal.gwt.client.ui.ICustomLayout;
import com.itmill.toolkit.terminal.gwt.client.ui.IDateFieldCalendar;
} else if ("com.itmill.toolkit.terminal.gwt.client.ui.IPopupView"
.equals(className)) {
return new IPopupView();
+ } else if ("com.itmill.toolkit.terminal.gwt.client.ui.ICoordinateLayout"
+ .equals(className)) {
+ return new ICoordinateLayout();
}
return new IUnknownComponent();
return "com.itmill.toolkit.terminal.gwt.client.ui.IMenuBar";
} else if ("popupview".equals(tag)) {
return "com.itmill.toolkit.terminal.gwt.client.ui.IPopupView";
+ } else if ("coordinatelayout".equals(tag)) {
+ return "com.itmill.toolkit.terminal.gwt.client.ui.ICoordinateLayout";
}
return "com.itmill.toolkit.terminal.gwt.client.ui.IUnknownComponent";
--- /dev/null
+package com.itmill.toolkit.terminal.gwt.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
+import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
+import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
+import com.itmill.toolkit.terminal.gwt.client.Container;
+import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
+import com.itmill.toolkit.terminal.gwt.client.ICaption;
+import com.itmill.toolkit.terminal.gwt.client.Paintable;
+import com.itmill.toolkit.terminal.gwt.client.UIDL;
+import com.itmill.toolkit.terminal.gwt.client.Util;
+import com.itmill.toolkit.terminal.gwt.client.Util.WidgetSpaceAllocator;
+
+public class ICoordinateLayout extends ComplexPanel implements Container,
+ ContainerResizedListener, WidgetSpaceAllocator {
+
+ /** Class name, prefix in styling */
+ public static final String CLASSNAME = "i-coordinatelayout";
+
+ /** CSS class identifier for margin values */
+ public static final String CSSID = "margin-values";
+
+ /** For server-client communication */
+ protected String uidlId;
+ protected ApplicationConnection client;
+
+ /** For inner classes */
+ protected final ICoordinateLayout hostReference = this;
+
+ /** Indexes to coordinate arrays */
+ protected static final int LEFT = 0;
+ protected static final int TOP = 1;
+ protected static final int WIDTH = 2;
+ protected static final int HEIGHT = 3;
+ protected static final int RIGHT = 4;
+ protected static final int BOTTOM = 5;
+
+ /** Data structures for components */
+ // current components, including captions
+ protected final ArrayList<Widget> componentList;
+
+ // component -> caption mappers
+ protected final HashMap<Paintable, CustomCaption> componentToHeader;
+ protected final HashMap<Paintable, CustomCaption> componentToMarker;
+
+ // component -> properties mappers
+ protected final HashMap<Widget, String> componentToCoords;
+ protected final HashMap<Widget, Integer> componentToZ;
+ protected final HashMap<Widget, Element> componentToArea;
+
+ // components to draw on the next layout update
+ protected ArrayList<Widget> toUpdate;
+
+ // current layout margins and width/height
+ protected int[] layout = new int[6];
+
+ protected MarginInfo marginInfo; // from UIDL
+ protected int[] margins; // from CSS
+
+ public ICoordinateLayout() {
+ super();
+ setElement(DOM.createDiv());
+
+ // Set style attributes
+ setStyleName(CLASSNAME);
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+
+ if (BrowserInfo.get().isIE()) {
+ DOM.setStyleAttribute(getElement(), "zoom", "1");
+ }
+
+ marginInfo = new MarginInfo(0);
+
+ // Init data structures
+ componentList = new ArrayList<Widget>();
+ toUpdate = new ArrayList<Widget>();
+ componentToCoords = new HashMap<Widget, String>();
+ componentToHeader = new HashMap<Paintable, CustomCaption>();
+ componentToMarker = new HashMap<Paintable, CustomCaption>();
+ componentToZ = new HashMap<Widget, Integer>();
+ componentToArea = new HashMap<Widget, Element>();
+
+ }
+
+ /**
+ * This enables us to read the margins when the layout is actually attached
+ * to the DOM-tree.
+ *
+ * @see com.google.gwt.user.client.ui.Panel#onLoad()
+ */
+ protected void onLoad() {
+ margins = readMarginsFromCSS();
+ super.onLoad();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.itmill.toolkit.terminal.gwt.client.Paintable#updateFromUIDL(com.itmill
+ * .toolkit.terminal.gwt.client.UIDL,
+ * com.itmill.toolkit.terminal.gwt.client.ApplicationConnection)
+ */
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ // This call should be made first. Ensure correct implementation,
+ // and don't let the containing coordinateLayout manage caption, etc.
+
+ if (client.updateComponent(this, uidl, false)) {
+ return;
+ }
+
+ // These are for future server connections
+ this.client = client;
+
+ // Enable / disable margins
+ if (uidl.hasAttribute("margins")) {
+ marginInfo = new MarginInfo(uidl.getIntAttribute("margins"));
+ } else {
+ marginInfo = new MarginInfo(0);
+ }
+
+ // Start going through the component tree
+ UIDL componentsFromUIDL = uidl;
+ ArrayList<Widget> newComponents = new ArrayList<Widget>();
+ ArrayList<Widget> removedComponents = new ArrayList<Widget>();
+ int zIndex = 0;
+
+ for (Iterator<UIDL> componentIterator = componentsFromUIDL
+ .getChildIterator(); componentIterator.hasNext();) {
+
+ // Extract the values from the UIDL
+ final UIDL componentUIDL = componentIterator.next();
+ final UIDL componentDataUIDL = componentUIDL.getChildUIDL(0);
+ final Paintable componentDataPaintable = client
+ .getPaintable(componentDataUIDL);
+ final Widget componentWidget = (Widget) componentDataPaintable;
+ final String coordString = componentUIDL
+ .getStringAttribute("position");
+
+ if (!componentList.contains(componentWidget)) { // Initial draw
+ add(componentWidget);
+ componentDataPaintable
+ .updateFromUIDL(componentDataUIDL, client);
+
+ } else { // normal update
+ componentDataPaintable
+ .updateFromUIDL(componentDataUIDL, client);
+ }
+
+ // Save the coordinate string from the UIDL
+ if (!coordString.equals(componentToCoords.get(componentWidget))) {
+ toUpdate.add(componentWidget);
+ }
+ componentToCoords.put(componentWidget, coordString);
+
+ // Components come from the server in the correct order, client just
+ // needs to set the z-index
+ updateZ(componentWidget, zIndex++);
+
+ // Add the component to the list. This list is later checked against
+ // the list of current components
+ newComponents.add(componentWidget);
+
+ }// for
+
+ // Clean children that don't exist in the current UIDL
+ // (except for captions)
+ for (Iterator<Widget> iterator = componentList.iterator(); iterator
+ .hasNext();) {
+ Widget w = iterator.next();
+ if (!newComponents.contains(w) && !(w instanceof CustomCaption)) {
+ removedComponents.add(w);
+ }
+ }
+
+ while (!removedComponents.isEmpty()) {
+ removePaintable((Paintable) removedComponents.get(0));
+ removedComponents.remove(0);
+ }
+
+ componentList.clear();
+ componentList.addAll(newComponents);
+
+ // Make sure coordinateLayout gets done every time
+ iLayout(-1, -1);
+
+ }// updateFromUIDL
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener#iLayout
+ * (int, int)
+ */
+ public void iLayout(int availableWidth, int availableHeight) {
+ // shake
+ // TODO is this necessary?
+ getOffsetWidth();
+ getOffsetHeight();
+
+ // Get our current area and check if it has changed
+ // If it has, we need to recalculate all
+ int[] newSize = getDimensionsArray();
+ if ((newSize[WIDTH] != layout[WIDTH])
+ || (newSize[HEIGHT] != layout[HEIGHT])) {
+ toUpdate.clear();
+ toUpdate = getDrawableComponents();
+ layout = newSize;
+ }
+
+ if (!toUpdate.isEmpty()) {
+
+ // Run layout functions for children
+ Util.runDescendentsLayout(this);
+
+ // Go over all children and calculate their positions
+ for (Iterator<Widget> componentIterator = toUpdate.iterator(); componentIterator
+ .hasNext();) {
+
+ Widget componentWidget = componentIterator.next();
+ CustomCaption componentHeader = componentToHeader
+ .get((Paintable) componentWidget);
+ CustomCaption componentMarker = componentToMarker
+ .get((Paintable) componentWidget);
+
+ // Reset position info
+ resetPositionAttributes(componentWidget);
+ maximizeArea(componentWidget);
+
+ // Calculate real pixel values from the coordinate string
+ int[] coords = parseCoordString(componentToCoords
+ .get(componentWidget));
+
+ // Determine which sides of the component must be attached
+ // ( == sides with a determined position)
+ boolean[] sidesToAttach = new boolean[6];
+ for (int i = 0; i < coords.length; i++) {
+ sidesToAttach[i] = (coords[i] != -1);
+ }
+
+ // At least one of (TOP, BOTTOM) must be attached to ensure the
+ // captions are displayed properly
+ if (!sidesToAttach[TOP] && !sidesToAttach[BOTTOM]) {
+ sidesToAttach[TOP] = true;
+ }
+ // At least one of (LEFT, RIGHT) must be attached to ensure the
+ // captions are displayed properly
+ if (!sidesToAttach[LEFT] && !sidesToAttach[RIGHT]) {
+ sidesToAttach[LEFT] = true;
+ }
+
+ // If width or height is not given, try to calculate from other
+ // values
+ if (coords[WIDTH] == -1) {
+ coords[WIDTH] = layout[WIDTH];
+
+ if (coords[LEFT] != -1) {
+ coords[WIDTH] -= coords[LEFT];
+ }
+ if (coords[RIGHT] != -1) {
+ coords[WIDTH] -= coords[RIGHT];
+ }
+
+ }
+
+ if (coords[HEIGHT] == -1) {
+ coords[HEIGHT] = layout[HEIGHT];
+
+ if (coords[TOP] != -1) {
+ coords[HEIGHT] -= coords[TOP];
+ }
+ if (coords[BOTTOM] != -1) {
+ coords[HEIGHT] -= coords[BOTTOM];
+ }
+ }
+
+ // Sanity check for width & height
+ if (coords[HEIGHT] > layout[HEIGHT]) {
+ coords[HEIGHT] = layout[HEIGHT];
+ } else if (coords[HEIGHT] < 0) {
+ coords[HEIGHT] = 0;
+ }
+
+ if (coords[WIDTH] > layout[WIDTH]) {
+ coords[WIDTH] = layout[WIDTH];
+ } else if (coords[WIDTH] < 0) {
+ coords[WIDTH] = 0;
+ }
+
+ // Force width and height on margins so that they
+ // have some effect
+ calculateMargins(coords);
+
+ // Sanity checks. Some browsers render incorrectly when for
+ // example relative[LEFT] + relative[RIGHT] > layout[WIDTH]
+ for (int i = 0; i < coords.length; i++) {
+ if (coords[i] < 0) {
+ coords[i] = 0;
+ }
+ }
+
+ // height overflows
+ if ((coords[TOP] + coords[BOTTOM]) > getOffsetHeight()) {
+ if (coords[TOP] > coords[BOTTOM]) {
+ coords[TOP] -= coords[TOP] - coords[BOTTOM];
+ } else {
+ coords[BOTTOM] -= coords[BOTTOM] - coords[TOP];
+ }
+ }
+
+ // width overflows
+ if (coords[RIGHT] + coords[LEFT] > getOffsetWidth()) {
+ if (coords[RIGHT] > coords[LEFT]) {
+ coords[RIGHT] -= coords[RIGHT] - coords[LEFT];
+ } else {
+ coords[LEFT] -= coords[LEFT] - coords[RIGHT];
+ }
+ }
+
+ // this call puts the component to the componentToPosition
+ // map and sets the style attributes
+ setWidgetPosition(componentWidget, coords, sidesToAttach);
+
+ // Update caption position
+ // Header has icon and caption text
+ if (componentHeader != null) {
+ updateCaptionPosition(componentHeader, componentWidget);
+ }
+ // Marker has error and required
+ if (componentMarker != null) {
+ updateCaptionPosition(componentMarker, componentWidget);
+ }
+ }
+ }
+
+ toUpdate.clear();
+ }
+
+ /**
+ * Set positional attributes for given widget
+ *
+ * @param w
+ * @param newCoords
+ * @param sidesToAttach
+ */
+ public void setWidgetPosition(Widget w, int[] newCoords,
+ boolean[] sidesToAttach) {
+
+ resetPositionAttributes(w);
+ Element widgetElement = w.getElement();
+ Element areaElement;
+ CustomCaption componentHeader = componentToHeader.get((Paintable) w);
+ CustomCaption componentMarker = componentToMarker.get((Paintable) w);
+
+ // Attach the widget to the sides of the surrounding area element
+ if (sidesToAttach[TOP] || sidesToAttach[HEIGHT]) {
+ int margin = 0;
+ if (componentHeader != null) {
+ margin += componentHeader.getOffsetHeight();
+ }
+ DOM.setStyleAttribute(widgetElement, "top", Integer
+ .toString(margin)
+ + "px");
+ }
+ if (sidesToAttach[RIGHT] || sidesToAttach[WIDTH]) {
+ int margin = 0;
+ if (componentMarker != null) {
+ margin += componentMarker.getOffsetWidth();
+ }
+ DOM.setStyleAttribute(widgetElement, "right", Integer
+ .toString(margin)
+ + "px");
+ }
+ if (sidesToAttach[BOTTOM] || sidesToAttach[HEIGHT]) {
+ DOM.setStyleAttribute(widgetElement, "bottom", "0px");
+ }
+ if (sidesToAttach[LEFT] || sidesToAttach[WIDTH]) {
+ DOM.setStyleAttribute(widgetElement, "left", "0px");
+ }
+
+ // Create / get the area element for this component
+ if (componentToArea.get(w) == null) {
+ areaElement = DOM.createDiv();
+ // Element tree should be layoutElement -> areaElement ->
+ // componentElement
+ DOM.appendChild(getElement(), areaElement);
+ DOM.removeChild(getElement(), widgetElement);
+ DOM.appendChild(areaElement, widgetElement);
+
+ DOM.setStyleAttribute(areaElement, "position", "absolute");
+ DOM.setStyleAttribute(areaElement, "overflow", "hidden");
+
+ // Component growth areas are hidden so that they don't interfere
+ // with other components
+ DOM.setStyleAttribute(areaElement, "visibility", "hidden");
+
+ componentToArea.put(w, areaElement);
+ } else {
+ areaElement = componentToArea.get(w);
+ }
+
+ // set the margin according to the values given
+ DOM.setStyleAttribute(areaElement, "margin", (newCoords[TOP] + "px "
+ + newCoords[RIGHT] + "px " + newCoords[BOTTOM] + "px "
+ + newCoords[LEFT] + "px"));
+ DOM.setStyleAttribute(areaElement, "width", newCoords[WIDTH] + "px");
+ DOM.setStyleAttribute(areaElement, "height", newCoords[HEIGHT] + "px");
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.itmill.toolkit.terminal.gwt.client.Container#updateCaption(com.itmill
+ * .toolkit.terminal.gwt.client.Paintable,
+ * com.itmill.toolkit.terminal.gwt.client.UIDL)
+ */
+ public void updateCaption(Paintable component, UIDL uidl) {
+ CustomCaption header = componentToHeader.get(component);
+ CustomCaption marker = componentToMarker.get(component);
+
+ if (ICaption.isNeeded(uidl)) {
+ boolean headerNeeded = uidl.getStringAttribute("caption") != null
+ || uidl.hasAttribute("icon");
+
+ boolean markerNeeded = uidl.hasAttribute("error")
+ || uidl.hasAttribute("required");
+
+ // if we have both, we display them on the header
+ if (headerNeeded && markerNeeded) {
+ // header might be in wrong format (separate marker & header)
+ if (header != null
+ && !header.isMode(CustomCaption.COMBINED_MODE)) {
+ componentToHeader.remove(component);
+ remove(header);
+ header = null;
+ }
+ // header might not exist
+ if (header == null) {
+ header = new CustomCaption(
+ component,
+ client,
+ (CustomCaption.HEADER_MODE | CustomCaption.MARKER_MODE));
+ add(header);
+ componentToHeader.put(component, header);
+ }
+
+ // update
+ header.updateCaption(uidl);
+ updateCaptionPosition(header, (Widget) component);
+
+ // if we have both we don't need a separate marker
+ if (marker != null) {
+ componentToMarker.remove(component);
+ remove(marker);
+ }
+
+ }
+ // if we only need a header
+ else if (headerNeeded) {
+
+ if (header == null) {
+
+ header = new CustomCaption(component, client,
+ CustomCaption.HEADER_MODE);
+ add(header);
+ componentToHeader.put(component, header);
+
+ }
+
+ header.updateCaption(uidl);
+
+ // caption position
+ updateCaptionPosition(header, (Widget) component);
+
+ }
+
+ // if we only need a marker
+ else if (markerNeeded) {
+
+ if (marker == null) {
+
+ marker = new CustomCaption(component, client,
+ CustomCaption.MARKER_MODE);
+ add(marker);
+ componentToMarker.put(component, marker);
+
+ }
+
+ marker.updateCaption(uidl);
+
+ // caption position
+ updateCaptionPosition(marker, (Widget) component);
+ }
+ } else { // No caption
+ if (header != null) {
+ remove(header);
+ componentToHeader.remove(component);
+ } else if (marker != null) {
+ remove(marker);
+ componentToMarker.remove(component);
+ }
+ }
+
+ }
+
+ /**
+ * Update caption position according to the parent component position.
+ *
+ * @param c
+ * @param parent
+ */
+ public void updateCaptionPosition(CustomCaption c, Widget parent) {
+
+ int parentTop, parentRight, parentBottom, parentLeft, parentHeight, parentWidth;
+
+ Element captionElement = c.getElement();
+ Element parentElement = parent.getElement();
+ Element areaElement = componentToArea.get(parent);
+
+ if (areaElement != null
+ && !DOM.isOrHasChild(areaElement, c.getElement())) {
+ DOM.removeChild(getElement(), c.getElement());
+ DOM.appendChild(areaElement, c.getElement());
+ }
+
+ resetPositionAttributes((Widget) c);
+
+ parentTop = getPositionValue(parentElement, "top");
+ parentRight = getPositionValue(parentElement, "right");
+ parentBottom = getPositionValue(parentElement, "bottom");
+ parentLeft = getPositionValue(parentElement, "left");
+ parentHeight = parent.getOffsetHeight();
+ parentWidth = parent.getOffsetWidth();
+
+ if (c.isMode(CustomCaption.HEADER_MODE)) {
+ if (parentBottom == -1 || parentTop != -1) {
+ DOM.setStyleAttribute(captionElement, "top", "0px");
+ } else {
+ DOM.setStyleAttribute(captionElement, "bottom", parentHeight
+ + "px");
+ }
+
+ if (parentRight == -1 || parentLeft != -1) {
+ DOM.setStyleAttribute(captionElement, "left", "0px");
+ } else {
+ int right = parentRight + parentWidth - c.getOffsetWidth();
+ DOM.setStyleAttribute(captionElement, "right", right + "px");
+ }
+ } else if (c.isMode(CustomCaption.MARKER_MODE)) {
+ if (parentBottom == -1 || parentTop != -1) {
+ DOM.setStyleAttribute(captionElement, "top", parentTop + "px");
+ } else {
+ int bottom = parentBottom + parentHeight - c.getOffsetHeight();
+ DOM.setStyleAttribute(captionElement, "bottom", bottom + "px");
+ }
+
+ if (parentRight == -1 || parentLeft != -1) {
+ DOM.setStyleAttribute(captionElement, "left", parentWidth
+ + "px");
+ } else {
+ int right = parentRight - c.getOffsetWidth();
+ DOM.setStyleAttribute(captionElement, "right", right + "px");
+ }
+ }
+
+ }
+
+ /**
+ * Update the z-index of a component + the z-index of the caption
+ *
+ * @param component
+ * @param zIndex
+ */
+ public void updateZ(Widget component, int zIndex) {
+
+ DOM.setStyleAttribute(component.getElement(), "zIndex", ""
+ + String.valueOf(zIndex));
+ componentToZ.put(component, new Integer(zIndex));
+
+ // Set caption z-index (same as parent components z)
+ if (componentToHeader.get((Paintable) component) != null) {
+ CustomCaption h = componentToHeader.get((Paintable) component);
+ DOM.setStyleAttribute(h.getElement(), "zIndex", "" + zIndex);
+ }
+ if (componentToMarker.get((Paintable) component) != null) {
+ CustomCaption m = componentToMarker.get((Paintable) component);
+ DOM.setStyleAttribute(m.getElement(), "zIndex", "" + zIndex);
+ }
+ }
+
+ /**
+ * Remove & unregister a paintable component
+ *
+ * @param p
+ * @return
+ */
+ public boolean removePaintable(Paintable p) {
+ CustomCaption header = componentToHeader.get(p);
+ CustomCaption marker = componentToMarker.get(p);
+ if (header != null) {
+ componentToHeader.remove(p);
+ remove(header);
+ }
+ if (marker != null) {
+ componentToHeader.remove(p);
+ remove(marker);
+ }
+ client.unregisterPaintable(p);
+ return remove((Widget) p);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.itmill.toolkit.terminal.gwt.client.Container#childComponentSizesUpdated
+ * ()
+ */
+ public boolean childComponentSizesUpdated() {
+ return false;
+ }
+
+ /**
+ * Remove all position-related attributes from a given element
+ *
+ * @param e
+ */
+ protected void resetPositionAttributes(Widget w) {
+ Element e = w.getElement();
+ DOM.setStyleAttribute(e, "top", "");
+ DOM.setStyleAttribute(e, "right", "");
+ DOM.setStyleAttribute(e, "bottom", "");
+ DOM.setStyleAttribute(e, "left", "");
+ DOM.setStyleAttribute(e, "width", "");
+ DOM.setStyleAttribute(e, "height", "");
+ }
+
+ /**
+ * Calculates percentage from value if needed
+ *
+ * @param coordString
+ * @return
+ */
+ protected int[] parseCoordString(String coordString) {
+ int[] relative = new int[6];
+ boolean[] isPercent = new boolean[6];
+ String[] values = coordString.split(",");
+
+ for (int i = 0; i < values.length; i++) {
+
+ // string parsing
+ if (values[i].startsWith("-")) { // anything with '-' equals -1
+ relative[i] = -1;
+ } else {
+ if (values[i].endsWith("%")) {
+ isPercent[i] = true;
+ relative[i] = Integer.parseInt(values[i].substring(0,
+ values[i].length() - 1)); // without '%'
+ } else {
+ relative[i] = Integer.parseInt(values[i]);
+ }
+
+ }
+
+ // relative calculations
+ if (isPercent[i]) {
+ float multiplier = (float) relative[i] / 100;
+
+ if (i == LEFT || i == RIGHT || i == WIDTH) {
+ float width = (float) layout[WIDTH];
+ relative[i] = (int) (width * multiplier);
+ } else {
+ float height = (float) layout[HEIGHT];
+ relative[i] = (int) (height * multiplier);
+ }
+ }
+ }
+ return relative;
+ }
+
+ /**
+ * Get the dimensions for the layout, including margins.
+ *
+ * @return
+ */
+ protected int[] getDimensionsArray() {
+ int[] newSize = new int[6];
+
+ newSize[LEFT] = marginInfo.hasLeft() ? margins[3] : 0;
+ newSize[TOP] = marginInfo.hasTop() ? margins[0] : 0;
+ newSize[BOTTOM] = marginInfo.hasBottom() ? margins[2] : 0;
+ newSize[RIGHT] = marginInfo.hasRight() ? margins[1] : 0;
+ newSize[WIDTH] = this.getOffsetWidth() - newSize[LEFT] - newSize[RIGHT];
+ newSize[HEIGHT] = this.getOffsetHeight() - newSize[TOP]
+ - newSize[BOTTOM];
+
+ return newSize;
+ }
+
+ /*
+ * This is called to "reset" area information so we can calculate component
+ * size correctly. The layout must have correct dimensions when this is
+ * called.
+ */
+ protected void maximizeArea(Widget component) {
+ if (componentToArea.get(component) != null) {
+ Element areaElement = componentToArea.get(component);
+ DOM.setStyleAttribute(areaElement, "margin", "0");
+ DOM.setStyleAttribute(areaElement, "width", layout[WIDTH] + "px");
+ DOM.setStyleAttribute(areaElement, "height", layout[HEIGHT] + "px");
+ }
+ }
+
+ /*
+ * This method calculates the final coordinates for a given component. It
+ * "fills in" the gaps from the given coordinates.
+ */
+ protected void calculateMargins(int[] coords) {
+ int left, top, right, bottom;
+
+ // left
+ if (coords[LEFT] == -1 & coords[RIGHT] == -1) {
+ left = layout[LEFT];
+ } else if (coords[LEFT] == -1) {
+ left = layout[WIDTH] - coords[RIGHT] - coords[WIDTH] + layout[LEFT];
+
+ } else {
+ left = coords[LEFT] + layout[LEFT];
+ }
+
+ // right
+ if (coords[RIGHT] == -1 & coords[LEFT] == -1) {
+ right = layout[RIGHT];
+ } else if (coords[RIGHT] == -1) {
+ right = layout[WIDTH] - coords[LEFT] - coords[WIDTH]
+ + layout[RIGHT];
+ } else {
+ right = coords[RIGHT] + layout[RIGHT];
+ }
+
+ // top
+ if (coords[TOP] == -1 & coords[BOTTOM] == -1) {
+ top = layout[TOP];
+ } else if (coords[TOP] == -1) {
+ top = layout[HEIGHT] - coords[HEIGHT] - coords[BOTTOM]
+ + layout[TOP];
+ } else {
+ top = coords[TOP] + layout[TOP];
+ }
+
+ // bottom
+ if (coords[BOTTOM] == -1 & coords[TOP] == -1) {
+ bottom = layout[BOTTOM];
+ } else if (coords[BOTTOM] == -1) {
+ bottom = layout[HEIGHT] - coords[TOP] - coords[HEIGHT]
+ + layout[BOTTOM];
+ } else {
+ bottom = coords[BOTTOM] + layout[BOTTOM];
+ }
+
+ coords[LEFT] = left;
+ coords[TOP] = top;
+ coords[RIGHT] = right;
+ coords[BOTTOM] = bottom;
+
+ }
+
+ /*
+ * Used to retrieve e.g. the "top" and "right" values for an element
+ */
+ protected int getPositionValue(Element e, String position) {
+ String value = DOM.getStyleAttribute(e, position);
+ if (!"".equals(value)) {
+ return parsePixel(value);
+ } else {
+ return -1;
+ }
+ }
+
+ /* Read margin info from CSS */
+ protected int[] readMarginsFromCSS() {
+ int[] margins = new int[4];
+ String style = CLASSNAME + "-" + CSSID;
+ String[] cssProperties = new String[] { "margin-top", "margin-right",
+ "margin-bottom", "margin-left" };
+
+ String cssValue = "";
+ String[] cssValues = new String[cssProperties.length];
+
+ Element e = DOM.createDiv();
+ DOM.appendChild(this.getElement(), e);
+ DOM.setStyleAttribute(e, "position", "absolute");
+ DOM.setStyleAttribute(e, "height", "100px");
+ DOM.setStyleAttribute(e, "width", "100px");
+ DOM.setElementProperty(e, "className", style);
+ DOM.setInnerHTML(e, ".");
+
+ try {
+ if (BrowserInfo.get().isIE()) {
+ cssValue = getIEMargin(e);
+
+ cssValues = cssValue.split("\\s");
+
+ switch (cssValues.length) {
+
+ case 4:
+ for (int i = 0; i < 4; i++) {
+ margins[i] = parsePixel(cssValues[i]);
+ }
+ break;
+
+ case 3:
+ margins[0] = parsePixel(cssValues[0]);
+ margins[1] = margins[3] = parsePixel(cssValues[1]);
+ margins[2] = parsePixel(cssValues[2]);
+ break;
+
+ case 2:
+ margins[0] = margins[2] = parsePixel(cssValues[0]);
+ margins[1] = margins[3] = parsePixel(cssValues[1]);
+ break;
+
+ case 1:
+ int intValue = parsePixel(cssValues[0]);
+ for (int i = 0; i < margins.length; i++) {
+ margins[i] = intValue;
+ }
+ }
+ } else {
+ for (int i = 0; i < cssValues.length; i++) {
+ cssValues[i] = getMargin(e, cssProperties[i]);
+ margins[i] = parsePixel(cssValues[i]);
+ }
+ }
+
+ } catch (Exception exc) {
+ // Error in CSS margin format or missing value, defaulting all to 0
+ GWT.log("Error reading CSS", exc);
+ for (int i = 0; i < margins.length; i++) {
+ margins[i] = 0;
+ }
+ }
+ DOM.removeChild(this.getElement(), e);
+ return margins;
+ }
+
+ /* "10px" => 10 */
+ protected int parsePixel(String str) throws NumberFormatException {
+ return Integer.parseInt(str.substring(0, str.length() - 2));
+ }
+
+ /**
+ * Retrieve margin for other browsers
+ *
+ * @param e
+ * @param CSSProp
+ * @return
+ */
+ protected native String getMargin(Element e, String CSSProp)/*-{
+ return $wnd.getComputedStyle(e,null).getPropertyValue(CSSProp);
+ }-*/;
+
+ /**
+ * Retrieves margin info in IE
+ *
+ * @param e
+ * @return
+ */
+ protected native String getIEMargin(Element e)/*-{
+ return e.currentStyle.margin;
+ }-*/;
+
+ /**
+ * @return all components that are not captions
+ */
+ protected ArrayList<Widget> getDrawableComponents() {
+ ArrayList<Widget> list = new ArrayList<Widget>();
+ for (Widget w : componentList) {
+ if (!(w instanceof CustomCaption)) {
+ list.add(w);
+ }
+ }
+ return list;
+ }
+
+ /*
+ * Widget methods
+ */
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.ComplexPanel#iterator()
+ */
+ public Iterator<Widget> iterator() {
+ return componentList.iterator();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.itmill.toolkit.terminal.gwt.client.Container#replaceChildComponent
+ * (com.google.gwt.user.client.ui.Widget,
+ * com.google.gwt.user.client.ui.Widget)
+ */
+ public void replaceChildComponent(Widget from, Widget to) {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.itmill.toolkit.terminal.gwt.client.Container#hasChildComponent(com
+ * .google.gwt.user.client.ui.Widget)
+ */
+ public boolean hasChildComponent(Widget w) {
+ return componentList.contains(w);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Panel#add(com.google.gwt.user.client.ui
+ * .Widget)
+ */
+ public void add(Widget w) {
+ if (!componentList.contains(w)) {
+ w.removeFromParent();
+ }
+
+ // these attributes are common to all elements
+ DOM.setStyleAttribute(w.getElement(), "position", "absolute");
+ DOM.setStyleAttribute(w.getElement(), "overflow", "hidden");
+ DOM.setStyleAttribute(w.getElement(), "visibility", "visible");
+ componentList.add(w);
+
+ super.add(w, getElement());
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.ComplexPanel#remove(com.google.gwt.user
+ * .client.ui.Widget)
+ */
+ public boolean remove(Widget w) {
+ boolean wasRemoved = super.remove(w);
+ if (wasRemoved) {
+ resetPositionAttributes(w);
+
+ // If removing a component, remove its area
+ if (!(w instanceof CustomCaption)) {
+ DOM.removeChild(this.getElement(), componentToArea.get(w));
+ }
+
+ componentList.remove(w);
+ componentToZ.remove(w);
+ componentToArea.remove(w);
+ }
+ return wasRemoved;
+ }
+
+ public int getAllocatedHeight(Widget child) {
+ Element area = componentToArea.get(child);
+ if (area != null) {
+ return area.getOffsetHeight();
+ } else {
+ return layout[HEIGHT];
+ }
+ }
+
+ public int getAllocatedWidth(Widget child) {
+ Element area = componentToArea.get(child);
+ if (area != null) {
+ return area.getOffsetWidth();
+ } else {
+ return layout[WIDTH];
+ }
+ }
+
+ /**
+ * Custom caption class to encapsulate different caption parts
+ */
+ public class CustomCaption extends HTML {
+ public static final String CLASSNAME = "i-caption";
+
+ // Bit masks for different modes
+ public static final int HEADER_MODE = 1;
+ public static final int MARKER_MODE = 2;
+ public static final int COMBINED_MODE = HEADER_MODE | MARKER_MODE;
+
+ private final Paintable owner;
+
+ private Element errorIndicatorElement;
+
+ private Element requiredFieldIndicator;
+
+ private Icon icon;
+
+ private Element captionText;
+
+ private final ApplicationConnection client;
+
+ private int mode;
+
+ /**
+ *
+ * @param component
+ * optional owner of caption. If not set, getOwner will
+ * return null
+ * @param client
+ */
+ public CustomCaption(Paintable component, ApplicationConnection client,
+ int mode) {
+ super();
+ this.client = client;
+ owner = component;
+ setStyleName(CLASSNAME);
+ this.mode = mode;
+ }
+
+ public void updateCaption(UIDL uidl) {
+ setVisible(!uidl.getBooleanAttribute("invisible"));
+
+ setStyleName(getElement(), "i-disabled", uidl
+ .hasAttribute("disabled"));
+
+ boolean isEmpty = true;
+
+ if (uidl.hasAttribute("description")) {
+ if (captionText != null) {
+ addStyleDependentName("hasdescription");
+ } else {
+ removeStyleDependentName("hasdescription");
+ }
+ }
+
+ if (isMode(HEADER_MODE)) {
+ if (uidl.hasAttribute("icon")) {
+ if (icon == null) {
+ icon = new Icon(client);
+
+ DOM.insertChild(getElement(), icon.getElement(), 0);
+ }
+ icon.setUri(uidl.getStringAttribute("icon"));
+ isEmpty = false;
+ } else {
+ if (icon != null) {
+ DOM.removeChild(getElement(), icon.getElement());
+ icon = null;
+ }
+ }
+
+ if (uidl.hasAttribute("caption")) {
+ if (captionText == null) {
+ captionText = DOM.createSpan();
+ DOM.insertChild(getElement(), captionText,
+ icon == null ? 0 : 1);
+ }
+ String c = uidl.getStringAttribute("caption");
+ if (c == null) {
+ c = "";
+ } else {
+ isEmpty = false;
+ }
+ DOM.setInnerText(captionText, c);
+ } else {
+ // TODO should span also be removed
+ }
+ }
+
+ if (isMode(MARKER_MODE)) {
+ if (uidl.getBooleanAttribute("required")) {
+ isEmpty = false;
+ if (requiredFieldIndicator == null) {
+ requiredFieldIndicator = DOM.createSpan();
+ DOM.setInnerText(requiredFieldIndicator, "*");
+ DOM.setElementProperty(requiredFieldIndicator,
+ "className", "i-required-field-indicator");
+ DOM.appendChild(getElement(), requiredFieldIndicator);
+ }
+ } else {
+ if (requiredFieldIndicator != null) {
+ DOM.removeChild(getElement(), requiredFieldIndicator);
+ requiredFieldIndicator = null;
+ }
+ }
+
+ if (uidl.hasAttribute("error")) {
+ isEmpty = false;
+ if (errorIndicatorElement == null) {
+ errorIndicatorElement = DOM.createDiv();
+ DOM.setInnerHTML(errorIndicatorElement, " ");
+ DOM.setElementProperty(errorIndicatorElement,
+ "className", "i-errorindicator");
+ DOM.appendChild(getElement(), errorIndicatorElement);
+ }
+ } else if (errorIndicatorElement != null) {
+ DOM.removeChild(getElement(), errorIndicatorElement);
+ errorIndicatorElement = null;
+ }
+ }
+ // Workaround for IE weirdness, sometimes returns bad height in some
+ // circumstances when Caption is empty. See #1444
+ // IE7 bugs more often. I wonder what happens when IE8 arrives...
+ if (BrowserInfo.get().isIE()) {
+ if (isEmpty) {
+ setHeight("0px");
+ DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+ } else {
+ setHeight("");
+ DOM.setStyleAttribute(getElement(), "overflow", "");
+ }
+
+ }
+
+ }
+
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ final Element target = DOM.eventGetTarget(event);
+ if (client != null && !(target == getElement())) {
+ client.handleTooltipEvent(event, owner);
+ }
+ }
+
+ /**
+ * Returns Paintable for which this Caption belongs to.
+ *
+ * @return owner Widget
+ */
+ public Paintable getOwner() {
+ return owner;
+ }
+
+ /**
+ * Test if this CustomCaption is in a certain mode
+ *
+ * @param value
+ * @return
+ */
+ public boolean isMode(int value) {
+ return (mode & value) == value;
+ }
+ }
+
+}// class ICoordinateLayout
--- /dev/null
+package com.itmill.toolkit.tests.tickets;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import com.itmill.toolkit.Application;
+import com.itmill.toolkit.data.Property.ValueChangeEvent;
+import com.itmill.toolkit.data.Property.ValueChangeListener;
+import com.itmill.toolkit.data.util.HierarchicalContainer;
+import com.itmill.toolkit.data.util.IndexedContainer;
+import com.itmill.toolkit.data.validator.StringLengthValidator;
+import com.itmill.toolkit.terminal.ThemeResource;
+import com.itmill.toolkit.ui.Button;
+import com.itmill.toolkit.ui.CheckBox;
+import com.itmill.toolkit.ui.Component;
+import com.itmill.toolkit.ui.CoordinateLayout;
+import com.itmill.toolkit.ui.ExpandLayout;
+import com.itmill.toolkit.ui.GridLayout;
+import com.itmill.toolkit.ui.Label;
+import com.itmill.toolkit.ui.MenuBar;
+import com.itmill.toolkit.ui.OrderedLayout;
+import com.itmill.toolkit.ui.Panel;
+import com.itmill.toolkit.ui.Slider;
+import com.itmill.toolkit.ui.Table;
+import com.itmill.toolkit.ui.TextField;
+import com.itmill.toolkit.ui.Tree;
+import com.itmill.toolkit.ui.Window;
+import com.itmill.toolkit.ui.CoordinateLayout.Coordinates;
+import com.itmill.toolkit.ui.MenuBar.Command;
+import com.itmill.toolkit.ui.MenuBar.MenuItem;
+import com.itmill.toolkit.ui.Window.CloseEvent;
+import com.itmill.toolkit.ui.Window.CloseListener;
+
+public class Ticket1267 extends Application {
+
+ ArrayList<Component> componentList;
+
+ CoordinateLayout coordinateLayout = new CoordinateLayout();
+ Window main = new Window("Coordinatelayout demo");
+ HashMap<Component, Panel> control = new HashMap<Component, Panel>();
+ ExpandLayout mainLayout = new ExpandLayout();
+ // Components
+ MenuBar mainMenu;
+ Window controlPanel = new Window("Control panel for components");
+
+ OrderedLayout ol = new OrderedLayout();
+
+ boolean defaultmargins = false;
+
+ Application hostApp = this;
+ Class<Ticket1267> hostClass = Ticket1267.class;
+
+ private int numberOfTables;
+
+ private IndexedContainer emptyContainer = new IndexedContainer();
+ private HierarchicalContainer emptyHierarchy = new HierarchicalContainer();
+
+ private int numberOfPanels;
+
+ private int numberOfTextFields;
+
+ private int numberOfTrees;
+
+ public void init() {
+
+ // Setup container
+ emptyContainer.addContainerProperty("First Name", String.class, null);
+ emptyContainer.addContainerProperty("Last Name", String.class, null);
+ emptyContainer.addContainerProperty("Year", Integer.class, null);
+
+ // Setup menubar
+ mainMenu = getMainMenuBar();
+
+ // Setup control panel window
+ controlPanel.addListener(new CloseListener() {
+
+ public void windowClose(CloseEvent e) {
+ main.removeWindow(e.getWindow());
+
+ }
+
+ });
+
+ // TestField
+ TextField testField = new TextField("Pixel values");
+ testField.setImmediate(true);
+ final Coordinates xy = new Coordinates("0,0");
+
+ testField.addListener(new ValueChangeListener() {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.itmill.toolkit.data.Property.ValueChangeListener#valueChange
+ * (com.itmill.toolkit.data.Property.ValueChangeEvent)
+ */
+ public void valueChange(ValueChangeEvent event) {
+ String str = (String) event.getProperty().getValue();
+ try {
+ xy.setCoordinates(str);
+ } catch (Exception e) {
+ main.showNotification("Wrong string format", e.toString(),
+ Window.Notification.TYPE_WARNING_MESSAGE);
+ return;
+ }
+ main.showNotification("Component added to " + xy.toString(),
+ Window.Notification.TYPE_TRAY_NOTIFICATION);
+ }
+ });
+
+ coordinateLayout.addComponent(testField, xy);
+
+ // Setup coordinatelayout
+ coordinateLayout.setMargin(defaultmargins);
+ mainLayout.addComponent(mainMenu);
+ mainLayout.addComponent(coordinateLayout);
+ mainLayout.expand(coordinateLayout);
+
+ main.setLayout(mainLayout);
+
+ setMainWindow(main);
+
+ }
+
+ private MenuBar getMainMenuBar() {
+ MenuBar menu = new MenuBar();
+ MenuItem add = menu.addItem("Add component", null);
+ MenuItem remove = menu.addItem("Remove component", null);
+ MenuItem modify = menu.addItem("Modify component", null);
+ MenuItem cPanel = menu.addItem("Show / hide control panel", null);
+ cPanel.setCommand(new Command() {
+
+ public void menuSelected(MenuItem selectedItem) {
+ if (main.getChildWindows().contains(controlPanel)) {
+ main.removeWindow(controlPanel);
+ } else {
+ main.addWindow(controlPanel);
+
+ }
+ }
+ });
+
+ final MenuItem margins = menu.addItem("Toggle margins", null);
+ margins.setCommand(new Command() {
+
+ boolean marginsEnabled = defaultmargins;
+
+ public void menuSelected(MenuItem selectedItem) {
+ if (marginsEnabled) {
+ margins.setIcon(null);
+ marginsEnabled = false;
+ coordinateLayout.setMargin(false);
+ } else {
+ margins.setIcon(new ThemeResource("icons/16/ok.png"));
+ marginsEnabled = true;
+ coordinateLayout.setMargin(true);
+ }
+ }
+
+ });
+ try {
+ add.addItem("Table", new AddCommand(hostClass
+ .getMethod("getNewTable"), remove, modify));
+
+ add.addItem("Panel", new AddCommand(hostClass
+ .getMethod("getNewPanel"), remove, modify));
+
+ add.addItem("TextField", new AddCommand(hostClass
+ .getMethod("getNewTextField"), remove, modify));
+ add.addItem("Tree", new AddCommand(hostClass
+ .getMethod("getNewTree"), remove, modify));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return menu;
+ }
+
+ public Table getNewTable() {
+ Table table = new Table("Table " + numberOfTables++);
+ table.setContainerDataSource(emptyContainer);
+ table.setImmediate(true);
+ table.setIcon(new ThemeResource("icons/16/globe.png"));
+
+ return table;
+ }
+
+ public Panel getNewPanel() {
+ final Panel panel = new Panel("Panel " + numberOfPanels++);
+ Button addButton = new Button("Add component");
+ addButton.addListener(new Button.ClickListener() {
+ int count = 0;
+
+ public void buttonClick(Button.ClickEvent event) {
+ panel.addComponent(new Label("Component nro. " + count++));
+
+ }
+ });
+ panel.addComponent(addButton);
+
+ return panel;
+ }
+
+ public TextField getNewTextField() {
+ TextField textField = new TextField(
+ "TextField " + numberOfTextFields++,
+ "This textfield has a validator");
+ textField.setImmediate(true);
+ textField.setIcon(new ThemeResource("icons/16/document-image.png"));
+ textField.setRequired(true);
+ textField.addValidator(new StringLengthValidator(
+ "5 < String.length() < 10", 5, 10, false));
+
+ return textField;
+ }
+
+ public Tree getNewTree() {
+
+ Tree tree = new Tree("Tree " + numberOfTrees++);
+
+ return tree;
+ }
+
+ private class AddCommand implements Command {
+
+ private Method addMethod;
+ private MenuItem removeMenu;
+ private MenuItem modifyMenu;
+
+ public void menuSelected(MenuItem selectedItem) {
+ Component toAdd = null;
+ try {
+ toAdd = (Component) addMethod.invoke(hostApp);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ String caption = toAdd.getCaption();
+ Coordinates xy = coordinateLayout.addComponent(toAdd, "50%,50%");
+ getMainWindow().showNotification(
+ "Component added to " + xy.toString(),
+ Window.Notification.TYPE_TRAY_NOTIFICATION);
+
+ MenuItem modItem = getModMenuItem(modifyMenu, caption, toAdd);
+ removeMenu.addItem(caption, new RemoveCommand(toAdd, modItem,
+ modifyMenu));
+
+ controlPanel.addComponent(getControlPanel(toAdd));
+
+ }
+
+ AddCommand(Method addMethod, MenuItem removeMenu, MenuItem modifyMenu) {
+
+ this.addMethod = addMethod;
+ this.removeMenu = removeMenu;
+ this.modifyMenu = modifyMenu;
+ }
+
+ }
+
+ private class RemoveCommand implements Command {
+
+ private Component toRemove;
+ private MenuItem modifyItem;
+ private MenuItem modifyMenu;
+
+ public RemoveCommand(Component toRemove, MenuBar.MenuItem modifyItem,
+ MenuItem modifyMenu) {
+ this.toRemove = toRemove;
+ this.modifyItem = modifyItem;
+ this.modifyMenu = modifyMenu;
+ }
+
+ public void menuSelected(MenuItem selectedItem) {
+ if (coordinateLayout.contains(toRemove)) {
+ coordinateLayout.removeComponent(toRemove);
+ MenuItem parent = selectedItem.getParent();
+ parent.removeChild(selectedItem);
+
+ if (modifyItem != null) {
+ modifyMenu.removeChild(modifyItem);
+ }
+
+ controlPanel.removeComponent(getControlPanel(toRemove));
+ control.remove(toRemove);
+ }
+ }
+ }
+
+ public Panel getControlPanel(final Component c) {
+
+ if (control.get(c) == null) {
+ Panel newPanel = new Panel("Controls for " + c.getCaption());
+ newPanel.setLayout(new GridLayout(3, 6));
+
+ final int[] values = new int[6];
+ Arrays.fill(values, -1);
+
+ final CheckBox[] checkBoxArray = new CheckBox[6];
+ final Slider[] sliderArray = new Slider[6];
+
+ String[] directions = { "Left", "Top", "Width", "Height", "Right",
+ "Bottom" };
+
+ final Label[] labelArray = new Label[6];
+
+ for (int i = 0; i < checkBoxArray.length; i++) {
+ final int j = i;
+
+ checkBoxArray[i] = new CheckBox(directions[i]);
+ sliderArray[i] = new Slider(-1, 100, 1);
+
+ labelArray[j] = new Label("-1");
+ labelArray[j].setEnabled(false);
+ checkBoxArray[j].setImmediate(true);
+ sliderArray[j].setImmediate(true);
+ sliderArray[j].setEnabled(false);
+ sliderArray[j].setWidth("300px");
+
+ checkBoxArray[j].addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ Boolean isChecked = (Boolean) checkBoxArray[j]
+ .getValue();
+ if (isChecked.booleanValue()) {
+ sliderArray[j].setEnabled(true);
+ labelArray[j].setEnabled(true);
+ } else {
+ values[j] = -1;
+ labelArray[j].setValue(new Integer(values[j]));
+ sliderArray[j].setEnabled(false);
+ labelArray[j].setEnabled(false);
+ CoordinateLayout.Coordinates coords = coordinateLayout
+ .getCoordinates(c);
+ coords.setCoordinates(values[0], values[1],
+ values[2], values[3], values[4], values[5]);
+ coords.setUnitsPercent(true, true, true, true,
+ true, true);
+
+ }
+ }
+ });
+
+ sliderArray[j].addListener(new ValueChangeListener() {
+ public void valueChange(ValueChangeEvent event) {
+ Double newValue = (Double) event.getProperty()
+ .getValue();
+ values[j] = (int) newValue.doubleValue();
+ CoordinateLayout.Coordinates coords = coordinateLayout
+ .getCoordinates(c);
+ coords.setCoordinates(values[0], values[1], values[2],
+ values[3], values[4], values[5]);
+ coords.setUnitsPercent(true, true, true, true, true,
+ true);
+
+ labelArray[j].setValue(new Integer(values[j]));
+
+ }
+ });
+ }
+
+ for (int i = 0; i < sliderArray.length; i++) {
+ newPanel.addComponent(checkBoxArray[i]);
+ newPanel.addComponent(sliderArray[i]);
+ newPanel.addComponent(labelArray[i]);
+ }
+ control.put(c, newPanel);
+ return newPanel;
+ } else {
+ return control.get(c);
+ }
+ }
+
+ public MenuBar.MenuItem getModMenuItem(MenuItem parent, String id,
+ Component c) {
+ MenuBar.MenuItem item = null;
+ if (c instanceof Table) {
+ final Table table = (Table) c;
+ item = parent.addItem(id, null);
+ MenuBar.Command addContent = new MenuBar.Command() {
+
+ public void menuSelected(MenuItem selectedItem) {
+ table
+ .addContainerProperty("First Name", String.class,
+ null);
+ table.addContainerProperty("Last Name", String.class, null);
+ table.addContainerProperty("Year", Integer.class, null);
+
+ table.addItem(new Object[] { "Nicolaus", "Copernicus",
+ new Integer(1473) }, Integer.valueOf(1));
+ table.addItem(new Object[] { "Tycho", "Brahe",
+ new Integer(1546) }, Integer.valueOf(2));
+ table.addItem(new Object[] { "Giordano", "Bruno",
+ new Integer(1548) }, Integer.valueOf(3));
+ table.addItem(new Object[] { "Galileo", "Galilei",
+ new Integer(1564) }, Integer.valueOf(4));
+ table.addItem(new Object[] { "Johannes", "Kepler",
+ new Integer(1571) }, Integer.valueOf(5));
+ table.addItem(new Object[] { "Isaac", "Newton",
+ new Integer(1643) }, Integer.valueOf(6));
+ }
+
+ };
+
+ MenuBar.Command removeContent = new MenuBar.Command() {
+
+ public void menuSelected(MenuItem selectedItem) {
+ table.setContainerDataSource(emptyContainer);
+
+ }
+
+ };
+
+ item.addItem("Add content", addContent);
+ item.addItem("Remove content", removeContent);
+
+ } else if (c instanceof Tree) {
+ final Tree tree = (Tree) c;
+ item = parent.addItem(id, null);
+
+ MenuBar.Command addContent = new MenuBar.Command() {
+ public void menuSelected(MenuBar.MenuItem selectedItem) {
+ final Object[][] planets = new Object[][] {
+ new Object[] { "Mercury" },
+ new Object[] { "Venus" },
+ new Object[] { "Earth", "The Moon" },
+ new Object[] { "Mars", "Phobos", "Deimos" },
+ new Object[] { "Jupiter", "Io", "Europa",
+ "Ganymedes", "Callisto" },
+
+ new Object[] { "Saturn", "Titan", "Tethys",
+ "Dione", "Rhea", "Iapetus" },
+ new Object[] { "Uranus", "Miranda", "Ariel",
+ "Umbriel", "Titania", "Oberon" },
+ new Object[] { "Neptune", "Triton", "Proteus",
+ "Nereid", "Larissa" } };
+
+ /* Add planets as root items in the tree. */
+ for (int i = 0; i < planets.length; i++) {
+ String planet = (String) (planets[i][0]);
+ tree.addItem(planet);
+ if (planets[i].length == 1) {
+ /* The planet has no moons so make it a leaf. */
+ tree.setChildrenAllowed(planet, false);
+ } else {
+ /* Add children (moons) under the planets. */
+ for (int j = 1; j < planets[i].length; j++) {
+ String moon = (String) planets[i][j];
+ /* Add the item as a regular item. */
+ tree.addItem(moon);
+ /* Set it to be a child. */
+ tree.setParent(moon, planet);
+ /* Make the moons look like leaves. */
+ tree.setChildrenAllowed(moon, false);
+ }
+ /* Expand the subtree. */
+ tree.expandItemsRecursively(planet);
+ }
+ }
+ }
+ };
+
+ MenuBar.Command removeContent = new MenuBar.Command() {
+
+ public void menuSelected(MenuBar.MenuItem selectedItem) {
+ tree.setContainerDataSource(emptyHierarchy);
+ }
+
+ };
+
+ item.addItem("Add content", addContent);
+ item.addItem("Remove content", removeContent);
+ }
+
+ return item;
+ }
+}
--- /dev/null
+package com.itmill.toolkit.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+
+/**
+ *
+ * A layout that enables absolute positioning for its children.
+ *
+ *
+ */
+public class CoordinateLayout extends AbstractLayout {
+
+ protected final ArrayList<Component> componentList;
+ protected final HashMap<Component, Coordinates> componentToCoord;
+
+ public static final int LEFT = 0;
+ public static final int TOP = 1;
+ public static final int WIDTH = 2;
+ public static final int HEIGHT = 3;
+ public static final int RIGHT = 4;
+ public static final int BOTTOM = 5;
+
+ /**
+ * Creates an empty coordinatelayout. The coordinateLayout is full size by
+ * default.
+ */
+ public CoordinateLayout() {
+ super();
+ componentList = new ArrayList<Component>();
+ componentToCoord = new HashMap<Component, Coordinates>();
+
+ this.setSizeFull();
+ }
+
+ /**
+ *
+ * <p>
+ * Adds a component to the layout at the specified coordinates. The values
+ * are treated as pixel values by default and for example
+ * {@link com.itmill.toolkit.ui.CoordinateLayout.Coordinates#setUnitPercent(int, boolean)}
+ * can be used to set the values as percentage.
+ * </p>
+ *
+ * <p>
+ * If the value is negative, it is interpreted as automatic (terminal
+ * decides). Null argument is not permitted and will throw an
+ * IllegalArgumentException.
+ * </p>
+ * <dl>
+ * <dt><b>Examples:</b></dt>
+ *
+ * <dd>
+ * <code>addComponent(c, 0, 0, -1, -1, -1, -1)</code> to attach to
+ * upper-left corner and let terminal decide size</dd>
+ *
+ * <dd><code>Coordinates c = addComponent(c, 5, 5, 200, 200, -1, -1)</dd>
+ * <dd>c.setUnitPercent(CoordinateLayout.LEFT, true)</dd>
+ * <dd>c.setUnitPercent(CoordinateLayout.TOP, true) </code> to attach 5%
+ * from upper-left corner and define size to 200 x 200 pixels</dd>
+ *
+ * <dd>
+ * <code>addComponent(c, 0, 0, -1, -1, 0, 0)</code> to stretch component to
+ * cover layout area (attached to all four corners)</dd>
+ * </dl>
+ *
+ *
+ * @param c
+ * component to be added
+ * @param left
+ * distance of component left from layout left
+ * @param top
+ * distance of component top from layout top
+ * @param width
+ * component width
+ * @param height
+ * component height
+ * @param right
+ * distance of component right from layout right
+ * @param bottom
+ * distance of component bottom from layout bottom
+ * @return the coordinates object for the component
+ *
+ * @throws IllegalArgumentException
+ */
+ public Coordinates addComponent(Component c, int left, int top, int width,
+ int height, int right, int bottom) throws IllegalArgumentException {
+ if (c == null) {
+ throw new IllegalArgumentException();
+ }
+
+ Coordinates newCoords = new Coordinates(left, top, width, height,
+ right, bottom);
+ componentToCoord.put(c, newCoords);
+ addComponent(c);
+
+ return newCoords;
+ }
+
+ /**
+ * <p>
+ * Add a component to the specified coordinates. The string format is
+ * <i>"left[%], top[%] [,width[%], height[%] [,right[%], bottom[%]]]"</i>.
+ * Null arguments will throw an IllegalArgumentException.
+ * </p>
+ *
+ * <dl>
+ * <dt><b>Examples for string format:</b></dt>
+ *
+ * <dd>
+ * <code>addComponent(c, "0, 0")</code> to attach to upper-left corner and
+ * let terminal decide size</dd>
+ *
+ * <dd>
+ * <code>addComponent(c, "5%, 5%, 200, 200")</code> to attach 5% from
+ * upper-left corner and define size to 200 x 200 pixels</dd>
+ *
+ * <dd>
+ * <code>addComponent(c,"0, 0, -1, -1,0, 0")</code> to stretch component to
+ * cover coordinateLayout area (attached to all four corners)</dd>
+ *
+ * </dl>
+ *
+ * @param c
+ * the component to be added
+ * @param coordString
+ * coordinates in the specified string format
+ * @return the created Coordinates object for the component
+ *
+ * @throws IllegalArgumentException
+ */
+ public Coordinates addComponent(Component c, String coordString)
+ throws IllegalArgumentException {
+ if (c == null) {
+ throw new IllegalArgumentException();
+ }
+ Coordinates newCoords = new Coordinates(coordString);
+ componentToCoord.put(c, newCoords);
+ addComponent(c);
+
+ return newCoords;
+ }
+
+ /**
+ * Adds a component to the layout at the specified coordinates. Null
+ * arguments are not permitted and will throw an IllegalArgumentException.
+ *
+ * @param c
+ * the component to be added
+ * @param coord
+ * the coordinates of the component as a Coordinates object
+ * @throws IllegalArgument
+ * Exception
+ */
+ public void addComponent(Component c, CoordinateLayout.Coordinates coord)
+ throws IllegalArgumentException {
+ if (c == null || coord == null) {
+ throw new IllegalArgumentException();
+ }
+ componentToCoord.put(c, coord);
+ addComponent(c);
+ }
+
+ /**
+ * Sets the coordinates of a child component. Null arguments are not
+ * permitted and will throw an IllegalArgumentException. In addition, the
+ * user of this method must make sure that the given component is a child of
+ * this layout.
+ *
+ * @param c
+ * the target component
+ * @param left
+ * distance of component left from layout left
+ * @param top
+ * distance of component top from layout top
+ * @param width
+ * component width
+ * @param height
+ * component height
+ * @param right
+ * distance of component right from layout right
+ * @param bottom
+ * distance of component bottom from layout bottom
+ * @throws IllegalArgumentException
+ */
+ public void setCoordinates(Component c, int left, int top, int width,
+ int height, int right, int bottom) throws IllegalArgumentException {
+ if (c == null || !componentList.contains(c)) {
+ throw new IllegalArgumentException();
+ }
+ Coordinates coords = (Coordinates) componentToCoord.get(c);
+ coords.setCoordinates(left, top, width, height, right, bottom);
+ }
+
+ /**
+ * Sets the coordinates of a child component. Null arguments are not
+ * permitted and will throw an IllegalArgumentException. In addition, the
+ * user of this method must make sure that the given component is a child of
+ * this coordinateLayout.
+ *
+ * @param c
+ * the component thats position is changed
+ * @param newCoord
+ * the new coordinates as a Coordinates object
+ * @throws IllegalArgumentException
+ */
+ public void setCoordinates(Component c,
+ CoordinateLayout.Coordinates newCoord)
+ throws IllegalArgumentException {
+ if (c == null || newCoord == null || !componentList.contains(c)) {
+ throw new IllegalArgumentException();
+ }
+ // Detach old coordinate object from this layout
+ Coordinates oldCoord = (Coordinates) componentToCoord.remove(c);
+ oldCoord.removeParentLayout(this);
+
+ // Attach new
+ newCoord.addParentLayout(this);
+ componentToCoord.put(c, newCoord);
+
+ requestRepaint();
+ }
+
+ /**
+ * This will put the given component at the top of the list.
+ *
+ * @param c
+ * the component to be put to the top
+ */
+ public void sendToTop(Component c) {
+ componentList.remove(c);
+ componentList.add(c);
+
+ requestRepaint();
+ }
+
+ /**
+ * This will put the given component at the bottom of the list.
+ *
+ * @param c
+ * the component to be put to the bottom
+ */
+ public void sendToBottom(Component c) {
+ componentList.remove(c);
+ componentList.add(0, c);
+
+ requestRepaint();
+ }
+
+ /**
+ * If a component is added with this method the component is added to the
+ * top-left corner of the layout. The coordinates can later be accessed via
+ * {@link CoordinateLayout#getCoordinates(Component)}
+ *
+ * @param c
+ * the component to be added
+ * @throws IllegalArgument
+ * Exception
+ *
+ * @see com.itmill.toolkit.ui.AbstractComponentContainer#addComponent(com.itmill.toolkit.ui.Component)
+ */
+ public void addComponent(Component c) throws IllegalArgumentException {
+ if (c == null) {
+ throw new IllegalArgumentException();
+ }
+ componentList.add(c);
+
+ // Someone might call this outside this object
+ if (componentToCoord.get(c) == null) {
+ componentToCoord.put(c, new Coordinates(0, 0, -1, -1, -1, -1));
+
+ }
+
+ super.addComponent(c);
+ requestRepaint();
+ ((Coordinates) componentToCoord.get(c)).addParentLayout(this);
+ }
+
+ /**
+ * Removes the component from the coordinateLayout. Method will throw
+ * IllegalArgumentException if the given component is not a child of this
+ * layout.
+ *
+ * @param c
+ * the component to be removed
+ * @throws IllegalArgumentException
+ *
+ * @see com.itmill.toolkit.ui.AbstractComponentContainer#removeComponent(com.itmill.toolkit.ui.Component)
+ */
+ public void removeComponent(Component c) throws IllegalArgumentException {
+ if (c == null || !componentList.contains(c)) {
+ throw new IllegalArgumentException();
+ }
+ ((Coordinates) componentToCoord.get(c)).removeParentLayout(this);
+
+ super.removeComponent(c);
+ componentList.remove(c);
+ componentToCoord.remove(c);
+
+ requestRepaint();
+ }
+
+ /**
+ * Replaces a child component with a new one. The new component uses the
+ * same coordinates as the previous component. If the arguments are null or
+ * the component is not a child of this layout IllegalArgumentException is
+ * thrown.
+ *
+ * @param c1
+ * the existing child component
+ * @param c2
+ * the new component
+ *
+ * @throws IllegalArgumentException
+ *
+ * @see com.itmill.toolkit.ui.ComponentContainer#replaceComponent(com.itmill.toolkit.ui.Component,
+ * com.itmill.toolkit.ui.Component)
+ */
+ public void replaceComponent(Component c1, Component c2) {
+ if (c1 == null || c2 == null || !componentList.contains(c1)) {
+ throw new IllegalArgumentException();
+ }
+ componentList.set(componentList.indexOf(c1), c2);
+ requestRepaint();
+ }
+
+ /**
+ * Get the coordinates for a given component in this layout
+ *
+ * @param c
+ * @return the Coordinates object for the given component
+ * @throws IllegalArgumentException
+ * thrown if component is not in this coordinateLayout
+ */
+ public Coordinates getCoordinates(Component c)
+ throws IllegalArgumentException {
+ if (c == null || componentToCoord.get(c) == null) {
+ throw new IllegalArgumentException();
+ }
+ return (Coordinates) componentToCoord.get(c);
+ }
+
+ /**
+ * @see com.itmill.toolkit.ui.ComponentContainer#getComponentIterator()
+ */
+ public Iterator<Component> getComponentIterator() {
+ return componentList.iterator();
+ }
+
+ /**
+ * Returns the iterator for the coordinates of the child components in this
+ * layout.
+ *
+ * @return the iterator for the coordinate list
+ */
+ public Iterator<Coordinates> getComponentCoordinateIterator() {
+ return componentToCoord.values().iterator();
+ }
+
+ /**
+ * Check if this layout has components.
+ *
+ * @return true if this coordinateLayout has children, false otherwise
+ */
+ public boolean hasComponents() {
+ return componentList.isEmpty();
+ }
+
+ /**
+ * Check if a given component is in this layout
+ *
+ * @param c
+ * @return true if the component is in this layout, false otherwise
+ */
+ public boolean contains(Component c) {
+ return componentList.contains(c);
+ }
+
+ /**
+ * Get child component by index.
+ *
+ * @param index
+ * must be index >= 0 and index < size() -1
+ * @return the child component
+ * @throws IllegalArgumentException
+ * if given index is illegal
+ */
+ public Component getComponentByIndex(int index)
+ throws IllegalArgumentException {
+ if (index < 0 || index > componentList.size() - 1) {
+ throw new IllegalArgumentException("Illegal index");
+ }
+
+ return (Component) componentList.get(index);
+ }
+
+ /**
+ * Returns the number of child components.
+ *
+ * @return number of children
+ */
+ public int size() {
+ return componentList.size();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.itmill.toolkit.ui.AbstractLayout#getTag()
+ */
+ public String getTag() {
+ return "coordinatelayout";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.itmill.toolkit.ui.AbstractLayout#paintContent(com.itmill.toolkit.
+ * terminal.PaintTarget)
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+ // Superclass writes any common attributes in the paint target.
+ super.paintContent(target);
+
+ for (Iterator<Component> componentIterator = getComponentIterator(); componentIterator
+ .hasNext();) {
+
+ Component component = (Component) componentIterator.next();
+ Coordinates coords = (Coordinates) componentToCoord.get(component);
+
+ target.startTag("component");
+ target.addAttribute("position", coords.toString());
+
+ component.paint(target);
+
+ target.endTag("component");
+
+ }// for
+
+ }// paintContent
+
+ /**
+ *
+ * This class is used as a container for the coordinates used by
+ * CoordinateLayout. When attached to a CoordinateLayout object, changes to
+ * this object are reflected on the layout.
+ *
+ */
+ public static class Coordinates {
+
+ // Length of the property arrays
+ protected static final int NUMBEROFPROPERTIES = 6;
+
+ // The actual coordinates
+ protected final int[] properties;
+
+ // These help to decipher the coordinates
+ protected final boolean[] isUnitPercent;
+
+ // Host layout(s)
+ protected final ArrayList<CoordinateLayout> listeners = new ArrayList<CoordinateLayout>(
+ 1);
+
+ /**
+ * Creates a new Coordinates object with the specified values. If the
+ * user does not wish to specify a value, -1 can be used to let the
+ * terminal decide the value.
+ *
+ * The values are treated as absolute. You can change them to be
+ * relative with setUnitsPercent or setAllUnitsPercent.
+ *
+ * @param top
+ * distance of component top from coordinateLayout top
+ * @param right
+ * distance of component right from coordinateLayout right
+ * @param bottom
+ * distance of component bottom from coordinateLayout bottom
+ * @param left
+ * distance of component left from coordinateLayout left
+ * @param width
+ * component width
+ * @param height
+ * component height
+ *
+ * @throws NumberFormatException
+ */
+ public Coordinates(int left, int top, int width, int height, int right,
+ int bottom) throws IllegalArgumentException {
+
+ properties = new int[NUMBEROFPROPERTIES];
+ isUnitPercent = new boolean[NUMBEROFPROPERTIES];
+
+ properties[LEFT] = left;
+ properties[TOP] = top;
+ properties[WIDTH] = width;
+ properties[HEIGHT] = height;
+ properties[RIGHT] = right;
+ properties[BOTTOM] = bottom;
+
+ for (int i = 0; i < NUMBEROFPROPERTIES; i++) {
+ if (properties[i] < -1) {
+ throw new IllegalArgumentException(
+ "Illegal coordinate value " + properties[i]);
+ }
+ }
+
+ }
+
+ /**
+ * Create a new Coordinates object with the given value string.
+ *
+ * @param coordinateString
+ * @throws IllegalArgumentException
+ * @see com.itmill.toolkit.ui.CoordinateLayout#addComponent(Component,
+ * String)
+ */
+ public Coordinates(String coordinateString)
+ throws IllegalArgumentException {
+
+ properties = new int[NUMBEROFPROPERTIES];
+ isUnitPercent = new boolean[NUMBEROFPROPERTIES];
+
+ Arrays.fill(properties, -1);
+
+ setValuesFromString(coordinateString);
+
+ }
+
+ /**
+ * Set unit of the given argument to be percentage. Initially all are
+ * false.
+ *
+ * @param top
+ * @param right
+ * @param bottom
+ * @param left
+ * @param width
+ * @param height
+ */
+ public void setUnitsPercent(boolean left, boolean top, boolean width,
+ boolean height, boolean right, boolean bottom) {
+ isUnitPercent[TOP] = top;
+ isUnitPercent[LEFT] = left;
+ isUnitPercent[WIDTH] = width;
+ isUnitPercent[HEIGHT] = height;
+ isUnitPercent[RIGHT] = right;
+ isUnitPercent[BOTTOM] = bottom;
+
+ notifyParents();
+ }
+
+ /**
+ * Set if a property should be treated as a percentage value.
+ *
+ * @param valueId
+ * either CoordinateLayout.TOP, CoordinateLayout.RIGHT,
+ * CoordinateLayout.BOTTOM, CoordinateLayout.LEFT,
+ * CoordinateLayout.WIDTH or CoordinateLayout.HEIGHT
+ * @param isPercent
+ * true if the value should be treated as a percentage, false
+ * otherwise
+ */
+ public void setUnitPercent(int valueId, boolean isPercent) {
+ isUnitPercent[valueId] = isPercent;
+
+ notifyParents();
+ }
+
+ /**
+ * Set if the values should be treated as percentage.
+ *
+ * @param value
+ * true if all the values should be treated as percentage,
+ * false if all the values should in treated as abslute pixel
+ * values
+ */
+ public void setAllUnitsPercent(boolean value) {
+ java.util.Arrays.fill(isUnitPercent, value);
+ notifyParents();
+ }
+
+ /**
+ * Set a value for a direction.
+ *
+ * @param direction
+ * either CoordinateLayout.TOP, CoordinateLayout.RIGHT,
+ * CoordinateLayout.BOTTOM, CoordinateLayout.LEFT,
+ * CoordinateLayout.WIDTH or CoordinateLayout.HEIGHT
+ * @value the value to be set
+ */
+ public void setCoordinate(int direction, int value) {
+ properties[direction] = value;
+ notifyParents();
+ }
+
+ /**
+ * Set coordinates for this object.
+ *
+ * @param top
+ * @param right
+ * @param bottom
+ * @param left
+ */
+ public void setCoordinates(int left, int top, int width, int height,
+ int right, int bottom) {
+ properties[LEFT] = left;
+ properties[TOP] = top;
+ properties[WIDTH] = width;
+ properties[HEIGHT] = height;
+ properties[RIGHT] = right;
+ properties[BOTTOM] = bottom;
+
+ for (int i = 0; i < NUMBEROFPROPERTIES; i++) {
+ if (properties[i] < -1) {
+ throw new IllegalArgumentException(
+ "Illegal coordinate value " + properties[i]);
+ }
+ }
+ notifyParents();
+ }
+
+ /**
+ * Set the values in string format. For reference, see
+ * {@link CoordinateLayout#addComponent(Component, String)}
+ *
+ * @param coordinateString
+ * @throws IllegalArgumentException
+ */
+ public void setCoordinates(String coordinateString)
+ throws IllegalArgumentException {
+ setValuesFromString(coordinateString);
+ notifyParents();
+ }
+
+ /**
+ * Returns the coordinates for this object.
+ *
+ * @param direction
+ * either CoordinateLayout.TOP, CoordinateLayout.RIGHT,
+ * CoordinateLayout.BOTTOM, CoordinateLayout.LEFT,
+ * CoordinateLayout.WIDTH or CoordinateLayout.HEIGHT
+ * @return coordinates for the given direction
+ */
+ public int getCoordinate(int direction) {
+ return properties[direction];
+ }
+
+ /**
+ * Check if a given value is a percentage value. The default is false.
+ *
+ * @param value
+ * either CoordinateLayout.TOP, CoordinateLayout.RIGHT,
+ * CoordinateLayout.BOTTOM, CoordinateLayout.LEFT,
+ * CoordinateLayout.WIDTH or CoordinateLayout.HEIGHT
+ *
+ * @return true if the given value has percentages as its unit, false
+ * otherwise
+ */
+ public boolean isValuePercent(int value) {
+ return isUnitPercent[value];
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ StringBuffer returnString = new StringBuffer();
+
+ for (int i = 0; i < properties.length; i++) {
+ returnString.append(properties[i]);
+ if (isUnitPercent[i]) {
+ returnString.append("%");
+ }
+ if (i != properties.length - 1) {
+ returnString.append(",");
+ }
+ }
+
+ return returnString.toString();
+ }
+
+ /*
+ * Parses the string to integer values
+ */
+ protected void setValuesFromString(String coordinateString) {
+
+ int[] newProperties = new int[6];
+ boolean[] newPercent = new boolean[6];
+
+ resetContents(newProperties, newPercent);
+
+ String coordStringArray[] = coordinateString.split(",");
+ if (coordStringArray.length > 6) {
+ throw new IllegalArgumentException(
+ "Incorrect string syntax: too many arguments");
+ }
+
+ Pattern numberPattern = Pattern.compile("[-]??\\d+");
+ String percentRegex = ".*%.*";
+ Matcher matcher = null;
+ try {
+ for (int i = 0; i < coordStringArray.length; i++) {
+
+ matcher = numberPattern.matcher(coordStringArray[i].trim());
+ if (matcher.find()) {
+ newProperties[i] = Integer.parseInt(matcher.group());
+
+ newPercent[i] = coordStringArray[i]
+ .matches(percentRegex);
+ } else {
+ throw new IllegalArgumentException(
+ "Error parsing number: " + coordStringArray[i]);
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Error parsing string: "
+ + e.toString());
+ }
+
+ // Only set new values if there were no parsing errors
+ resetContents(properties, isUnitPercent);
+ System.arraycopy(newProperties, 0, properties, 0,
+ NUMBEROFPROPERTIES);
+ System.arraycopy(newPercent, 0, isUnitPercent, 0,
+ NUMBEROFPROPERTIES);
+ }
+
+ /*
+ * Reset coordinate properties. Does not notify listeners
+ */
+ protected void resetContents(int[] prop, boolean[] perc) {
+ Arrays.fill(prop, -1);
+ Arrays.fill(perc, false);
+ }
+
+ /*
+ * These methods ensure that changes to the coordinate objects are
+ * reflected to the host layout
+ */
+ protected void addParentLayout(CoordinateLayout listener) {
+ listeners.add(listener);
+ }
+
+ protected void removeParentLayout(CoordinateLayout listener) {
+ listeners.remove(listener);
+ }
+
+ protected void notifyParents() {
+ for (int i = 0; i < listeners.size(); i++) {
+ ((CoordinateLayout) listeners.get(i)).requestRepaint();
+ }
+ }
+ }// class Coordinates
+
+}// class CoordinateLayout
+