aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/itmill/toolkit/ui/GridLayout.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/itmill/toolkit/ui/GridLayout.java')
-rw-r--r--src/com/itmill/toolkit/ui/GridLayout.java757
1 files changed, 757 insertions, 0 deletions
diff --git a/src/com/itmill/toolkit/ui/GridLayout.java b/src/com/itmill/toolkit/ui/GridLayout.java
new file mode 100644
index 0000000000..3af8f34ac7
--- /dev/null
+++ b/src/com/itmill/toolkit/ui/GridLayout.java
@@ -0,0 +1,757 @@
+/* *************************************************************************
+
+ IT Mill Toolkit
+
+ Development of Browser User Intarfaces Made Easy
+
+ Copyright (C) 2000-2006 IT Mill Ltd
+
+ *************************************************************************
+
+ This product is distributed under commercial license that can be found
+ from the product package on license/license.txt. Use of this product might
+ require purchasing a commercial license from IT Mill Ltd. For guidelines
+ on usage, see license/licensing-guidelines.html
+
+ *************************************************************************
+
+ For more information, contact:
+
+ IT Mill Ltd phone: +358 2 4802 7180
+ Ruukinkatu 2-4 fax: +358 2 4802 7181
+ 20540, Turku email: info@itmill.com
+ Finland company www: www.itmill.com
+
+ Primary source for information and releases: www.itmill.com
+
+ ********************************************************************** */
+
+package com.itmill.toolkit.ui;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import com.itmill.toolkit.terminal.PaintException;
+import com.itmill.toolkit.terminal.PaintTarget;
+
+/** <p>A container that consists of components with certain coordinates on a
+ * grid. It also maintains cursor for adding component in left to right,
+ * top to bottom order.</p>
+ *
+ * <p>Each component in a <code>GridLayout</code> uses a certain
+ * {@link GridLayout.Area area} (x1,y1,x2,y2) from the grid. One should not
+ * add components that would overlap with the existing components because in
+ * such case an {@link OverlapsException} is thrown. Adding component with
+ * cursor automatically extends the grid by increasing the grid height.</p>
+ *
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+public class GridLayout extends AbstractComponentContainer implements Layout {
+
+ /** Initial grid x size */
+ private int width = 0;
+
+ /** Initial grid y size */
+ private int height = 0;
+
+ /** Cursor X position: this is where the next component with
+ * unspecified x,y is inserted
+ */
+ private int cursorX = 0;
+
+ /** Cursor Y position: this is where the next component with
+ * unspecified x,y is inserted
+ */
+ private int cursorY = 0;
+
+ /** Contains all items that are placed on the grid.
+ * These are components with grid area definition.
+ */
+ private LinkedList areas = new LinkedList();
+
+ /** Mapping from components to threir respective areas. */
+ private LinkedList components = new LinkedList();
+
+ /** Constructor for grid of given size.
+ * Note that grid's final size depends on the items that are added into the grid.
+ * Grid grows if you add components outside the grid's area.
+ * @param width Width of the grid.
+ * @param height Height of the grid.
+ */
+ public GridLayout(int width, int height) {
+ setWidth(width);
+ setHeight(height);
+ }
+
+ /** Constructs an empty grid layout that is extended as needed. */
+ public GridLayout() {
+ this(1, 1);
+ }
+
+ /** <p>Adds a component with a specified area to the grid. The area the
+ * new component should take is defined by specifying the upper left
+ * corner (x1, y1) and the lower right corner (x2, y2) of the area.</p>
+ *
+ * <p>If the new component overlaps with any of the existing components
+ * already present in the grid the operation will fail and an
+ * {@link OverlapsException} is thrown.</p>
+ *
+ * @param c The component to be added.
+ * @param x1 The X-coordinate of the upper left corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param y1 The Y-coordinate of the upper left corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param x2 The X-coordinate of the lower right corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param y2 The Y-coordinate of the lower right corner of the area
+ * <code>c</code> is supposed to occupy
+ * @throws OverlapsException if the new component overlaps with any
+ * of the components already in the grid
+ * @throws OutOfBoundsException if the coordinates are outside of the
+ * grid area.
+ */
+ public void addComponent(
+ Component component,
+ int x1,
+ int y1,
+ int x2,
+ int y2)
+ throws OverlapsException, OutOfBoundsException {
+
+ if (component == null)
+ throw new NullPointerException("Component must not be null");
+
+ // Check that the component does not already exist in the container
+ if (components.contains(component))
+ throw new IllegalArgumentException("Component is already in the container");
+
+ // Create area
+ Area area = new Area(component, x1, y1, x2, y2);
+
+ // Check the validity of the coordinates
+ if (x2 < x1 || y2 < y2)
+ throw new IllegalArgumentException("Illegal coordinates for the component");
+ if (x1 < 0 || y1 < 0 || x2 >= width || y2 >= height)
+ throw new OutOfBoundsException(area);
+
+ // Check that newItem does not overlap with existing items
+ checkExistingOverlaps(area);
+
+ // Insert the component to right place at the list
+ // Respect top-down, left-right ordering
+ component.setParent(this);
+ Iterator i = areas.iterator();
+ int index = 0;
+ boolean done = false;
+ while (!done && i.hasNext()) {
+ Area existingArea = (Area) i.next();
+ if ((existingArea.y1 >= y1 && existingArea.x1 > x1)
+ || existingArea.y1 > y1) {
+ areas.add(index, area);
+ components.add(index, component);
+ done = true;
+ }
+ index++;
+ }
+ if (!done) {
+ areas.addLast(area);
+ components.addLast(component);
+ }
+
+ super.addComponent(component);
+ requestRepaint();
+ }
+
+ /** Tests if the given area overlaps with any of the items already on
+ * the grid.
+ *
+ * @param area Area to be checked for overlapping
+ * @throws OverlapsException if <code>area</code> overlaps with
+ * any existing area
+ */
+ private void checkExistingOverlaps(Area area) throws OverlapsException {
+ for (Iterator i = areas.iterator(); i.hasNext();) {
+ Area existingArea = (Area) i.next();
+ if (existingArea.overlaps(area))
+
+ // Component not added, overlaps with existing component
+ throw new OverlapsException(existingArea);
+ }
+ }
+
+ /** Add component into this container to coordinates x1,y1 (NortWest corner of the area.)
+ * End coordinates (SouthEast corner of the area) are the same as x1,y1. Component width
+ * and height is 1.
+ * @param c The component to be added.
+ * @param x X-coordinate
+ * @param y Y-coordinate
+ */
+ public void addComponent(Component c, int x, int y) {
+ this.addComponent(c, x, y, x, y);
+ }
+
+ /** Force the next component to be added to the beginning of the next line.
+ * By calling this function user can ensure that no more components are
+ * added to the right of the previous component.
+ *
+ * @see #space()
+ */
+ public void newLine() {
+ cursorX = 0;
+ cursorY++;
+ }
+
+ /** Move cursor forwards by one. If the cursor goes out of the right grid border,
+ * move it to next line.
+ *
+ * @see #newLine()
+ */
+ public void space() {
+ cursorX++;
+ if (cursorX >= width) {
+ cursorX = 0;
+ cursorY++;
+ }
+ }
+
+ /** Add a component into this container to the cursor position.
+ * If the cursor position is already occupied, the cursor is
+ * moved forwards to find free position. If the cursor goes out
+ * from the bottom of the grid, the grid is automaticly extended.
+ * @param c The component to be added.
+ */
+ public void addComponent(Component component) {
+
+ // Find first available place from the grid
+ Area area;
+ boolean done = false;
+ while (!done)
+ try {
+ area = new Area(component, cursorX, cursorY, cursorX, cursorY);
+ checkExistingOverlaps(area);
+ done = true;
+ } catch (OverlapsException ignored) {
+ space();
+ }
+
+ // Extend the grid if needed
+ width = cursorX >= width ? cursorX + 1 : width;
+ height = cursorY >= height ? cursorY + 1 : height;
+
+ addComponent(component, cursorX, cursorY);
+ }
+
+ /** Removes the given component from this
+ * container.
+ *
+ * @param c The component to be removed.
+ */
+ public void removeComponent(Component component) {
+
+ // Check that the component is contained in the container
+ if (component == null || !components.contains(component))
+ return;
+
+ super.removeComponent(component);
+
+ Area area = null;
+ for (Iterator i = areas.iterator(); area == null && i.hasNext();) {
+ Area a = (Area) i.next();
+ if (a.getComponent() == component)
+ area = a;
+ }
+
+ components.remove(component);
+ if (area != null)
+ areas.remove(area);
+
+ requestRepaint();
+ }
+
+ /** Removes a component specified with it's top-left corner coordinates
+ * from this grid.
+ *
+ * @param x Component's top-left corner's X-coordinate
+ * @param y Component's top-left corner's Y-coordinate
+ */
+ public void removeComponent(int x, int y) {
+
+ // Find area
+ for (Iterator i = areas.iterator(); i.hasNext();) {
+ Area area = (Area) i.next();
+ if (area.getX1() == x && area.getY1() == y) {
+ removeComponent(area.getComponent());
+ return;
+ }
+ }
+ }
+
+ /** Gets an Iterator to the component container contents. Using the
+ * Iterator it's possible to step through the contents of the container.
+ *
+ * @return Iterator of the components inside the container.
+ */
+ public Iterator getComponentIterator() {
+ return Collections.unmodifiableCollection(components).iterator();
+ }
+
+ /** Paints the contents of this component.
+ *
+ * @param event PaintEvent.
+ * @throws PaintException The paint operation failed.
+ */
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ target.addAttribute("h", height);
+ target.addAttribute("w", width);
+
+ // Area iterator
+ Iterator areaiterator = areas.iterator();
+
+ // Current item to be processed (fetch first item)
+ Area area = areaiterator.hasNext() ? (Area) areaiterator.next() : null;
+
+ // Collect rowspan related information here
+ HashMap cellUsed = new HashMap();
+
+ // Empty cell collector
+ int emptyCells = 0;
+
+ // Iterate every applicable row
+ for (int cury = 0; cury < height; cury++) {
+ target.startTag("gr");
+
+ // Iterate every applicable column
+ for (int curx = 0; curx < width; curx++) {
+
+ // Check if current item is located at curx,cury
+ if (area != null && (area.y1 == cury) && (area.x1 == curx)) {
+
+ // First check if empty cell needs to be rendered
+ if (emptyCells > 0) {
+ target.startTag("gc");
+ target.addAttribute("x", curx - emptyCells);
+ target.addAttribute("y", cury);
+ if (emptyCells > 1) {
+ target.addAttribute("w", emptyCells);
+ }
+ target.endTag("gc");
+ emptyCells = 0;
+ }
+
+ // Now proceed rendering current item
+ int cols = (area.x2 - area.x1) + 1;
+ int rows = (area.y2 - area.y1) + 1;
+ target.startTag("gc");
+
+ target.addAttribute("x", curx);
+ target.addAttribute("y", cury);
+
+ if (cols > 1) {
+ target.addAttribute("w", cols);
+ }
+ if (rows > 1) {
+ target.addAttribute("h", rows);
+ }
+ area.getComponent().paint(target);
+
+ target.endTag("gc");
+
+ // Fetch next item
+ if (areaiterator.hasNext()) {
+ area = (Area) areaiterator.next();
+ } else {
+ area = null;
+ }
+
+ // Update cellUsed if rowspan needed
+ if (rows > 1) {
+ int spannedx = curx;
+ for (int j = 1; j <= cols; j++) {
+ cellUsed.put(
+ new Integer(spannedx),
+ new Integer(cury + rows - 1));
+ spannedx++;
+ }
+ }
+
+ // Skip current item's spanned columns
+ if (cols > 1) {
+ curx += cols - 1;
+ }
+
+ } else {
+
+ // Check against cellUsed, render space or ignore cell
+ if (cellUsed.containsKey(new Integer(curx))) {
+
+ // Current column contains already an item,
+ // check if rowspan affects at current x,y position
+ int rowspanDepth =
+ ((Integer) cellUsed.get(new Integer(curx)))
+ .intValue();
+
+ if (rowspanDepth >= cury) {
+
+ // ignore cell
+ // Check if empty cell needs to be rendered
+ if (emptyCells > 0) {
+ target.startTag("gc");
+ target.addAttribute("x", curx - emptyCells);
+ target.addAttribute("y", cury);
+ if (emptyCells > 1) {
+ target.addAttribute("w", emptyCells);
+ }
+ target.endTag("gc");
+
+ emptyCells = 0;
+ }
+ } else {
+
+ // empty cell is needed
+ emptyCells++;
+
+ // Remove cellUsed key as it has become obsolete
+ cellUsed.remove(new Integer(curx));
+ }
+ } else {
+
+ // empty cell is needed
+ emptyCells++;
+ }
+ }
+
+ } // iterate every column
+
+ // Last column handled of current row
+
+ // Check if empty cell needs to be rendered
+ if (emptyCells > 0) {
+ target.startTag("gc");
+ target.addAttribute("x", width - emptyCells);
+ target.addAttribute("y", cury);
+ if (emptyCells > 1) {
+ target.addAttribute("w", emptyCells);
+ }
+ target.endTag("gc");
+
+ emptyCells = 0;
+ }
+
+ target.endTag("gr");
+ } // iterate every row
+
+ // Last row handled
+ }
+
+ /** Gets the components UIDL tag.
+ *
+ * @return Component UIDL tag as string.
+ * @see com.itmill.toolkit.ui.AbstractComponent#getTag()
+ */
+ public String getTag() {
+ return "gridlayout";
+ }
+
+ /** This class defines an area on a grid. An Area is defined by the
+ * coordinates of its upper left corner (x1,y1) and lower right corner
+ * (x2,y2)
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public class Area {
+
+ /** X-coordinate of the upper left corner of the area */
+ private int x1;
+
+ /** Y-coordinate of the upper left corner of the area */
+ private int y1;
+
+ /** X-coordinate of the lower right corner of the area */
+ private int x2;
+
+ /** Y-coordinate of the lower right corner of the area */
+ private int y2;
+
+ /** Component painted on the area */
+ private Component component;
+
+ /** <p>Construct a new area on a grid.
+ *
+ * @param x1 The X-coordinate of the upper left corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param y1 The Y-coordinate of the upper left corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param x2 The X-coordinate of the lower right corner of the area
+ * <code>c</code> is supposed to occupy
+ * @param y2 The Y-coordinate of the lower right corner of the area
+ * <code>c</code> is supposed to occupy
+ * @throws OverlapsException if the new component overlaps with any
+ * of the components already in the grid
+ */
+ public Area(Component component, int x1, int y1, int x2, int y2) {
+ this.x1 = x1;
+ this.y1 = y1;
+ this.x2 = x2;
+ this.y2 = y2;
+ this.component = component;
+ }
+
+ /** Tests if the given Area overlaps with an another Area.
+ *
+ * @param other Another Area that's to be tested for overlap with
+ * this area
+ * @return <code>true</code> if <code>other</code> overlaps with
+ * this area, <code>false</code> if it doesn't
+ */
+ public boolean overlaps(Area other) {
+ return x1 <= other.getX2()
+ && y1 <= other.getY2()
+ && x2 >= other.getX1()
+ && y2 >= other.getY1();
+
+ }
+
+ /** Returns the component connected to the area.
+ * @return Component
+ */
+ public Component getComponent() {
+ return component;
+ }
+
+ /** Sets the component connected to the area.
+ *
+ * <p>This function only sets the value in the datastructure and does not
+ * send any events or set parents</p>
+ *
+ * @param newComponent The new connected overriding the existing one
+ */
+ protected void setComponent(Component newComponent) {
+ component= newComponent;
+ }
+
+ /** Returns the top-left corner x-coordinate.
+ * @return int
+ */
+ public int getX1() {
+ return x1;
+ }
+
+ /**
+ * Returns the bottom-right corner x-coordinate.
+ * @return int
+ */
+ public int getX2() {
+ return x2;
+ }
+
+ /**
+ * Returns the top-left corner y-coordinate.
+ * @return int
+ */
+ public int getY1() {
+ return y1;
+ }
+
+ /**
+ * Returns the bottom-right corner y-coordinate.
+ * @return int
+ */
+ public int getY2() {
+ return y2;
+ }
+
+ }
+
+ /** An <code>Exception</code> object which is thrown when two Items
+ * occupy the same space on a grid.
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public class OverlapsException extends java.lang.RuntimeException {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3978144339870101561L;
+
+ private Area existingArea;
+
+ /** Constructs an <code>OverlapsException</code>.
+ * @param msg the detail message.
+ */
+ public OverlapsException(Area existingArea) {
+ this.existingArea = existingArea;
+ }
+
+ /** Get the area */
+ public Area getArea() {
+ return existingArea;
+ }
+ }
+
+ /** An <code>Exception</code> object which is thrown when an area exceeds the
+ * bounds of the grid.
+ * @author IT Mill Ltd.
+ * @version @VERSION@
+ * @since 3.0
+ */
+ public class OutOfBoundsException extends java.lang.RuntimeException {
+
+ /**
+ * Serial generated by eclipse.
+ */
+ private static final long serialVersionUID = 3618985589664592694L;
+
+ private Area areaOutOfBounds;
+
+ /** Constructs an <code>OoutOfBoundsException</code> with the specified
+ * detail message.
+ *
+ * @param msg the detail message.
+ */
+ public OutOfBoundsException(Area areaOutOfBounds) {
+ this.areaOutOfBounds = areaOutOfBounds;
+ }
+
+ /** Get the area that is out of bounds */
+ public Area getArea() {
+ return areaOutOfBounds;
+ }
+ }
+
+ /** Set the width of the grid. The width can not be reduced if there are
+ * any areas that would be outside of the shrunk grid.
+ * @param width New width of the grid.
+ * @throws OutOfBoundsException if the one of the areas would exceed the
+ * bounds of the grid after the modification of the grid size.
+ */
+ public void setWidth(int width) {
+
+ // The the param
+ if (width < 1)
+ throw new IllegalArgumentException("The grid width and height must be at least 1");
+
+ // In case of no change
+ if (this.width == width)
+ return;
+
+ // Check for overlaps
+ if (this.width > width)
+ for (Iterator i = areas.iterator(); i.hasNext();) {
+ Area area = (Area) i.next();
+ if (area.x2 >= width)
+ throw new OutOfBoundsException(area);
+ }
+
+ this.width = width;
+
+ requestRepaint();
+ }
+
+ /** Get the width of the grids.
+ * @return The width of the grid
+ */
+ public final int getWidth() {
+ return this.width;
+ }
+
+ /** Set the height of the grid. The width can not be reduced if there are
+ * any areas that would be outside of the shrunk grid.
+ * @param Height of the grid
+ */
+ public void setHeight(int height) {
+
+ // The the param
+ if (height < 1)
+ throw new IllegalArgumentException("The grid width and height must be at least 1");
+
+ // In case of no change
+ if (this.height == height)
+ return;
+
+ // Check for overlaps
+ if (this.height > height)
+ for (Iterator i = areas.iterator(); i.hasNext();) {
+ Area area = (Area) i.next();
+ if (area.y2 >= height)
+ throw new OutOfBoundsException(area);
+ }
+
+ this.height = height;
+
+ requestRepaint();
+ }
+
+ /** Get the height of the grid.
+ * @return int - how many cells high the grid is
+ */
+ public final int getHeight() {
+ return this.height;
+ }
+
+ /** Get the current cursor x-position.
+ * The cursor position points the position for the next component
+ * that is added without specifying its coordinates. When the
+ * cursor position is occupied, the next component will be added
+ * to first free position after the cursor.
+ * @return Cursor x-coordinate.
+ */
+ public int getCursorX() {
+ return cursorX;
+ }
+
+ /** Get the current cursor y-position.
+ * The cursor position points the position for the next component
+ * that is added without specifying its coordinates. When the
+ * cursor position is occupied, the next component will be added
+ * to first free position after the cursor.
+ * @return Cursor y-coordinate.
+ */
+ public int getCursorY() {
+ return cursorY;
+ }
+
+ /* Documented in superclass */
+ public void replaceComponent(
+ Component oldComponent,
+ Component newComponent) {
+
+ // Get the locations
+ Area oldLocation = null;
+ Area newLocation = null;
+ for (Iterator i=areas.iterator(); i.hasNext();) {
+ Area location = (Area) i.next();
+ Component component = (Component) location.getComponent();
+ if (component == oldComponent) oldLocation = location;
+ if (component == newComponent) newLocation = location;
+ }
+
+ if (oldLocation == null)
+ addComponent(newComponent);
+ else if (newLocation == null) {
+ removeComponent(oldComponent);
+ addComponent(newComponent,oldLocation.getX1(),oldLocation.getY1(),oldLocation.getX2(),oldLocation.getY2());
+ } else {
+ oldLocation.setComponent(newComponent);
+ newLocation.setComponent(oldComponent);
+ requestRepaint();
+ }
+ }
+
+ /*
+ * @see com.itmill.toolkit.ui.ComponentContainer#removeAllComponents()
+ */
+ public void removeAllComponents() {
+ super.removeAllComponents();
+ this.cursorX = 0;
+ this.cursorY = 0;
+ }
+
+}