Package com.vaadin.ui

Source Code of com.vaadin.ui.GridLayout$OutOfBoundsException

/*
* Copyright 2010 IT Mill Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

package com.vaadin.ui;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;

import com.vaadin.event.LayoutEvents.LayoutClickEvent;
import com.vaadin.event.LayoutEvents.LayoutClickListener;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.terminal.gwt.client.MouseEventDetails;
import com.vaadin.terminal.gwt.client.ui.VGridLayout;
import com.vaadin.ui.GridLayout.OverlapsException;

/**
* <p>
* A container that consists of components with certain coordinates (cell
* position) 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} (column1,row1,column2,row2) 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
* 6.4.8
* @since 3.0
*/
@SuppressWarnings("serial")
@ClientWidget(VGridLayout.class)
public class GridLayout extends AbstractLayout implements
        Layout.AlignmentHandler, Layout.SpacingHandler {

    private static final String CLICK_EVENT = VGridLayout.CLICK_EVENT_IDENTIFIER;

    /**
     * Initial grid columns.
     */
    private int cols = 0;

    /**
     * Initial grid rows.
     */
    private int rows = 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 final LinkedList areas = new LinkedList();

    /**
     * Mapping from components to their respective areas.
     */
    private final LinkedList<Component> components = new LinkedList<Component>();

    /**
     * Mapping from components to alignments (horizontal + vertical).
     */
    private Map<Component, Alignment> componentToAlignment = new HashMap<Component, Alignment>();

    /**
     * Is spacing between contained components enabled. Defaults to false.
     */
    private boolean spacing = false;

    private static final Alignment ALIGNMENT_DEFAULT = Alignment.TOP_LEFT;

    /**
     * Has there been rows inserted or deleted in the middle of the layout since
     * the last paint operation.
     */
    private boolean structuralChange = false;

    private Map<Integer, Float> columnExpandRatio = new HashMap<Integer, Float>();
    private Map<Integer, Float> rowExpandRatio = new HashMap<Integer, Float>();

    /**
     * Constructor for grid of given size (number of cells). 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 columns
     *            Number of columns in the grid.
     * @param rows
     *            Number of rows in the grid.
     */
    public GridLayout(int columns, int rows) {
        setColumns(columns);
        setRows(rows);
    }

    /**
     * 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
     * (column1, row1) and the lower right corner (column2, row2) 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 column1
     *            the column of the upper left corner of the area <code>c</code>
     *            is supposed to occupy.
     * @param row1
     *            the row of the upper left corner of the area <code>c</code> is
     *            supposed to occupy.
     * @param column2
     *            the column of the lower right corner of the area
     *            <code>c</code> is supposed to occupy.
     * @param row2
     *            the row 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 cells are outside the grid area.
     */
    public void addComponent(Component component, int column1, int row1,
            int column2, int row2) throws OverlapsException,
            OutOfBoundsException {

        if (component == null) {
            throw new NullPointerException("Component must not be null");
        }

        // Checks that the component does not already exist in the container
        if (components.contains(component)) {
            throw new IllegalArgumentException(
                    "Component is already in the container");
        }

        // Creates the area
        final Area area = new Area(component, column1, row1, column2, row2);

        // Checks the validity of the coordinates
        if (column2 < column1 || row2 < row1) {
            throw new IllegalArgumentException(
                    "Illegal coordinates for the component");
        }
        if (column1 < 0 || row1 < 0 || column2 >= cols || row2 >= rows) {
            throw new OutOfBoundsException(area);
        }

        // Checks that newItem does not overlap with existing items
        checkExistingOverlaps(area);

        // Inserts the component to right place at the list
        // Respect top-down, left-right ordering
        // component.setParent(this);
        final Iterator i = areas.iterator();
        int index = 0;
        boolean done = false;
        while (!done && i.hasNext()) {
            final Area existingArea = (Area) i.next();
            if ((existingArea.row1 >= row1 && existingArea.column1 > column1)
                    || existingArea.row1 > row1) {
                areas.add(index, area);
                components.add(index, component);
                done = true;
            }
            index++;
        }
        if (!done) {
            areas.addLast(area);
            components.addLast(component);
        }

        // Attempt to add to super
        try {
            super.addComponent(component);
        } catch (IllegalArgumentException e) {
            areas.remove(area);
            components.remove(component);
            throw e;
        }

        // update cursor position, if it's within this area; use first position
        // outside this area, even if it's occupied
        if (cursorX >= column1 && cursorX <= column2 && cursorY >= row1
                && cursorY <= row2) {
            // cursor within area
            cursorX = column2 + 1; // one right of area
            if (cursorX >= cols) {
                // overflowed columns
                cursorX = 0; // first col
                // move one row down, or one row under the area
                cursorY = (column1 == 0 ? row2 : row1) + 1;
            } else {
                cursorY = row1;
            }
        }

        requestRepaint();
    }

    /**
     * Tests if the given area overlaps with any of the items already on the
     * grid.
     *
     * @param area
     *            the 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 (final Iterator i = areas.iterator(); i.hasNext();) {
            final Area existingArea = (Area) i.next();
            if (existingArea.overlaps(area)) {
                // Component not added, overlaps with existing component
                throw new OverlapsException(existingArea);
            }
        }
    }

    /**
     * Adds the component into this container to cells column1,row1 (NortWest
     * corner of the area.) End coordinates (SouthEast corner of the area) are
     * the same as column1,row1. Component width and height is 1.
     *
     * @param c
     *            the component to be added.
     * @param column
     *            the column index.
     * @param row
     *            the row index.
     * @throws OverlapsException
     *             if the new component overlaps with any of the components
     *             already in the grid.
     * @throws OutOfBoundsException
     *             if the cell is outside the grid area.
     */
    public void addComponent(Component c, int column, int row)
            throws OverlapsException, OutOfBoundsException {
        this.addComponent(c, column, row, column, row);
    }

    /**
     * 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++;
    }

    /**
     * Moves the 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 >= cols) {
            cursorX = 0;
            cursorY++;
        }
    }

    /**
     * Adds the 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 automatically extended.
     *
     * @param c
     *            the component to be added.
     */
    @Override
    public void addComponent(Component component) {

        // Finds 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 (final OverlapsException e) {
                space();
            }
        }

        // Extends the grid if needed
        cols = cursorX >= cols ? cursorX + 1 : cols;
        rows = cursorY >= rows ? cursorY + 1 : rows;

        addComponent(component, cursorX, cursorY);
    }

    /**
     * Removes the given component from this container.
     *
     * @param c
     *            the component to be removed.
     */
    @Override
    public void removeComponent(Component component) {

        // Check that the component is contained in the container
        if (component == null || !components.contains(component)) {
            return;
        }

        Area area = null;
        for (final Iterator i = areas.iterator(); area == null && i.hasNext();) {
            final Area a = (Area) i.next();
            if (a.getComponent() == component) {
                area = a;
            }
        }

        components.remove(component);
        if (area != null) {
            areas.remove(area);
        }

        componentToAlignment.remove(component);

        super.removeComponent(component);

        requestRepaint();
    }

    /**
     * Removes the component specified with it's cell index.
     *
     * @param column
     *            the Component's column.
     * @param row
     *            the Component's row.
     */
    public void removeComponent(int column, int row) {

        // Finds the area
        for (final Iterator i = areas.iterator(); i.hasNext();) {
            final Area area = (Area) i.next();
            if (area.getColumn1() == column && area.getRow1() == row) {
                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 the Iterator of the components inside the container.
     */
    public Iterator<Component> getComponentIterator() {
        return Collections.unmodifiableCollection(components).iterator();
    }

    /**
     * Paints the contents of this component.
     *
     * @param target
     *            the Paint Event.
     * @throws PaintException
     *             if the paint operation failed.
     */
    @Override
    public void paintContent(PaintTarget target) throws PaintException {

        super.paintContent(target);

        // TODO refactor attribute names in future release.
        target.addAttribute("h", rows);
        target.addAttribute("w", cols);

        target.addAttribute("structuralChange", structuralChange);
        structuralChange = false;

        if (spacing) {
            target.addAttribute("spacing", spacing);
        }

        // Area iterator
        final Iterator areaiterator = areas.iterator();

        // Current item to be processed (fetch first item)
        Area area = areaiterator.hasNext() ? (Area) areaiterator.next() : null;

        // Collects rowspan related information here
        final HashMap cellUsed = new HashMap();

        // Empty cell collector
        int emptyCells = 0;

        final String[] alignmentsArray = new String[components.size()];
        final Integer[] columnExpandRatioArray = new Integer[cols];
        final Integer[] rowExpandRatioArray = new Integer[rows];

        int realColExpandRatioSum = 0;
        float colSum = getExpandRatioSum(columnExpandRatio);
        if (colSum == 0) {
            // no columns has been expanded, all cols have same expand
            // rate
            float equalSize = 1 / (float) cols;
            int myRatio = Math.round(equalSize * 1000);
            for (int i = 0; i < cols; i++) {
                columnExpandRatioArray[i] = myRatio;
            }
            realColExpandRatioSum = myRatio * cols;
        } else {
            for (int i = 0; i < cols; i++) {
                int myRatio = Math
                        .round((getColumnExpandRatio(i) / colSum) * 1000);
                columnExpandRatioArray[i] = myRatio;
                realColExpandRatioSum += myRatio;
            }
        }

        boolean equallyDividedRows = false;
        int realRowExpandRatioSum = 0;
        float rowSum = getExpandRatioSum(rowExpandRatio);
        if (rowSum == 0) {
            // no rows have been expanded
            equallyDividedRows = true;
            float equalSize = 1 / (float) rows;
            int myRatio = Math.round(equalSize * 1000);
            for (int i = 0; i < rows; i++) {
                rowExpandRatioArray[i] = myRatio;
            }
            realRowExpandRatioSum = myRatio * rows;
        }

        int index = 0;

        // Iterates every applicable row
        for (int cury = 0; cury < rows; cury++) {
            target.startTag("gr");

            if (!equallyDividedRows) {
                int myRatio = Math
                        .round((getRowExpandRatio(cury) / rowSum) * 1000);
                rowExpandRatioArray[cury] = myRatio;
                realRowExpandRatioSum += myRatio;

            }
            // Iterates every applicable column
            for (int curx = 0; curx < cols; curx++) {

                // Checks if current item is located at curx,cury
                if (area != null && (area.row1 == cury)
                        && (area.column1 == 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
                    final int cols = (area.column2 - area.column1) + 1;
                    final int rows = (area.row2 - area.row1) + 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);

                    alignmentsArray[index++] = String
                            .valueOf(getComponentAlignment(area.getComponent())
                                    .getBitMask());

                    target.endTag("gc");

                    // Fetch next item
                    if (areaiterator.hasNext()) {
                        area = (Area) areaiterator.next();
                    } else {
                        area = null;
                    }

                    // Updates the 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++;
                        }
                    }

                    // Skips the current item's spanned columns
                    if (cols > 1) {
                        curx += cols - 1;
                    }

                } else {

                    // Checks 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
                        final 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++;

                            // Removes the cellUsed key as it has become
                            // obsolete
                            cellUsed.remove(Integer.valueOf(curx));
                        }
                    } else {

                        // empty cell is needed
                        emptyCells++;
                    }
                }

            } // iterates every column

            // Last column handled of current row

            // Checks if empty cell needs to be rendered
            if (emptyCells > 0) {
                target.startTag("gc");
                target.addAttribute("x", cols - emptyCells);
                target.addAttribute("y", cury);
                if (emptyCells > 1) {
                    target.addAttribute("w", emptyCells);
                }
                target.endTag("gc");

                emptyCells = 0;
            }

            target.endTag("gr");
        } // iterates every row

        // Last row handled

        // correct possible rounding error
        if (rowExpandRatioArray.length > 0) {
            rowExpandRatioArray[0] -= realRowExpandRatioSum - 1000;
        }
        if (columnExpandRatioArray.length > 0) {
            columnExpandRatioArray[0] -= realColExpandRatioSum - 1000;
        }

        target.addAttribute("colExpand", columnExpandRatioArray);
        target.addAttribute("rowExpand", rowExpandRatioArray);

        // Add child component alignment info to layout tag
        target.addAttribute("alignments", alignmentsArray);

    }

    private float getExpandRatioSum(Map<Integer, Float> ratioMap) {
        float sum = 0;
        for (Iterator<Entry<Integer, Float>> iterator = ratioMap.entrySet()
                .iterator(); iterator.hasNext();) {
            sum += iterator.next().getValue();
        }
        return sum;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.vaadin.ui.Layout.AlignmentHandler#getComponentAlignment(com
     * .vaadin.ui.Component)
     */
    public Alignment getComponentAlignment(Component childComponent) {
        Alignment alignment = componentToAlignment.get(childComponent);
        if (alignment == null) {
            return ALIGNMENT_DEFAULT;
        } else {
            return alignment;
        }
    }

    /**
     * This class defines an area on a grid. An Area is defined by the cells of
     * its upper left corner (column1,row1) and lower right corner
     * (column2,row2).
     *
     * @author IT Mill Ltd.
     * @version
     * 6.4.8
     * @since 3.0
     */
    public class Area implements Serializable {

        /**
         * The column of the upper left corner cell of the area.
         */
        private final int column1;

        /**
         * The row of the upper left corner cell of the area.
         */
        private int row1;

        /**
         * The column of the lower right corner cell of the area.
         */
        private final int column2;

        /**
         * The row of the lower right corner cell of the area.
         */
        private int row2;

        /**
         * Component painted on the area.
         */
        private Component component;

        /**
         * <p>
         * Construct a new area on a grid.
         * </p>
         *
         * @param component
         *            the component connected to the area.
         * @param column1
         *            The column of the upper left corner cell of the area
         *            <code>c</code> is supposed to occupy.
         * @param row1
         *            The row of the upper left corner cell of the area
         *            <code>c</code> is supposed to occupy.
         * @param column2
         *            The column of the lower right corner cell of the area
         *            <code>c</code> is supposed to occupy.
         * @param row2
         *            The row of the lower right corner cell 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 column1, int row1, int column2,
                int row2) {
            this.column1 = column1;
            this.row1 = row1;
            this.column2 = column2;
            this.row2 = row2;
            this.component = component;
        }

        /**
         * Tests if the given Area overlaps with an another Area.
         *
         * @param other
         *            the 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 column1 <= other.getColumn2() && row1 <= other.getRow2()
                    && column2 >= other.getColumn1() && row2 >= other.getRow1();

        }

        /**
         * Gets the component connected to the area.
         *
         * @return the 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;
        }

        /**
         * @deprecated Use getColumn1() instead.
         *
         * @see com.vaadin.ui.GridLayout#getColumn1()
         */
        @Deprecated
        public int getX1() {
            return getColumn1();
        }

        /**
         * Gets the column of the top-left corner cell.
         *
         * @return the column of the top-left corner cell.
         */
        public int getColumn1() {
            return column1;
        }

        /**
         * @deprecated Use getColumn2() instead.
         *
         * @see com.vaadin.ui.GridLayout#getColumn2()
         */
        @Deprecated
        public int getX2() {
            return getColumn2();
        }

        /**
         * Gets the column of the bottom-right corner cell.
         *
         * @return the column of the bottom-right corner cell.
         */
        public int getColumn2() {
            return column2;
        }

        /**
         * @deprecated Use getRow1() instead.
         *
         * @see com.vaadin.ui.GridLayout#getRow1()
         */
        @Deprecated
        public int getY1() {
            return getRow1();
        }

        /**
         * Gets the row of the top-left corner cell.
         *
         * @return the row of the top-left corner cell.
         */
        public int getRow1() {
            return row1;
        }

        /**
         * @deprecated Use getRow2() instead.
         *
         * @see com.vaadin.ui.GridLayout#getRow2()
         */
        @Deprecated
        public int getY2() {
            return getRow2();
        }

        /**
         * Gets the row of the bottom-right corner cell.
         *
         * @return the row of the bottom-right corner cell.
         */
        public int getRow2() {
            return row2;
        }

    }

    /**
     * Gridlayout does not support laying components on top of each other. An
     * <code>OverlapsException</code> is thrown when a component already exists
     * (even partly) at the same space on a grid with the new component.
     *
     * @author IT Mill Ltd.
     * @version
     * 6.4.8
     * @since 3.0
     */
    public class OverlapsException extends java.lang.RuntimeException {

        private final Area existingArea;

        /**
         * Constructs an <code>OverlapsException</code>.
         *
         * @param existingArea
         */
        public OverlapsException(Area existingArea) {
            this.existingArea = existingArea;
        }

        @Override
        public String getMessage() {
            StringBuilder sb = new StringBuilder();
            Component component = existingArea.getComponent();
            sb.append(component);
            sb.append("( type = ");
            sb.append(component.getClass().getName());
            if (component.getCaption() != null) {
                sb.append(", caption = \"");
                sb.append(component.getCaption());
                sb.append("\"");
            }
            sb.append(")");
            sb.append(" is already added to ");
            sb.append(existingArea.column1);
            sb.append(",");
            sb.append(existingArea.column1);
            sb.append(",");
            sb.append(existingArea.row1);
            sb.append(",");
            sb.append(existingArea.row2);
            sb.append("(column1, column2, row1, row2).");

            return sb.toString();
        }

        /**
         * Gets the area .
         *
         * @return the existing 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
     * 6.4.8
     * @since 3.0
     */
    public class OutOfBoundsException extends java.lang.RuntimeException {

        private final Area areaOutOfBounds;

        /**
         * Constructs an <code>OoutOfBoundsException</code> with the specified
         * detail message.
         *
         * @param areaOutOfBounds
         */
        public OutOfBoundsException(Area areaOutOfBounds) {
            this.areaOutOfBounds = areaOutOfBounds;
        }

        /**
         * Gets the area that is out of bounds.
         *
         * @return the area out of Bound.
         */
        public Area getArea() {
            return areaOutOfBounds;
        }
    }

    /**
     * Sets the number of columns in the grid. The column count can not be
     * reduced if there are any areas that would be outside of the shrunk grid.
     *
     * @param columns
     *            the new number of columns in the grid.
     */
    public void setColumns(int columns) {

        // The the param
        if (columns < 1) {
            throw new IllegalArgumentException(
                    "The number of columns and rows in the grid must be at least 1");
        }

        // In case of no change
        if (cols == columns) {
            return;
        }

        // Checks for overlaps
        if (cols > columns) {
            for (final Iterator i = areas.iterator(); i.hasNext();) {
                final Area area = (Area) i.next();
                if (area.column2 >= columns) {
                    throw new OutOfBoundsException(area);
                }
            }
        }

        cols = columns;

        requestRepaint();
    }

    /**
     * Get the number of columns in the grid.
     *
     * @return the number of columns in the grid.
     */
    public final int getColumns() {
        return cols;
    }

    /**
     * Sets the number of rows in the grid. The number of rows can not be
     * reduced if there are any areas that would be outside of the shrunk grid.
     *
     * @param rows
     *            the new number of rows in the grid.
     */
    public void setRows(int rows) {

        // The the param
        if (rows < 1) {
            throw new IllegalArgumentException(
                    "The number of columns and rows in the grid must be at least 1");
        }

        // In case of no change
        if (this.rows == rows) {
            return;
        }

        // Checks for overlaps
        if (this.rows > rows) {
            for (final Iterator i = areas.iterator(); i.hasNext();) {
                final Area area = (Area) i.next();
                if (area.row2 >= rows) {
                    throw new OutOfBoundsException(area);
                }
            }
        }

        this.rows = rows;

        requestRepaint();
    }

    /**
     * Get the number of rows in the grid.
     *
     * @return the number of rows in the grid.
     */
    public final int getRows() {
        return rows;
    }

    /**
     * Gets the current cursor x-position. The cursor position points the
     * position for the next component that is added without specifying its
     * coordinates (grid cell). When the cursor position is occupied, the next
     * component will be added to first free position after the cursor.
     *
     * @return the grid column the Cursor is on.
     */
    public int getCursorX() {
        return cursorX;
    }

    /**
     * Sets the current cursor x-position. This is usually handled automatically
     * by GridLayout.
     *
     * @param cursorX
     */
    public void setCursorX(int cursorX) {
        this.cursorX = cursorX;
    }

    /**
     * Gets the current cursor y-position. The cursor position points the
     * position for the next component that is added without specifying its
     * coordinates (grid cell). When the cursor position is occupied, the next
     * component will be added to first free position after the cursor.
     *
     * @return the grid row the Cursor is on.
     */
    public int getCursorY() {
        return cursorY;
    }

    /**
     * Sets the current cursor y-position. This is usually handled automatically
     * by GridLayout.
     *
     * @param cursorY
     */
    public void setCursorY(int cursorY) {
        this.cursorY = cursorY;
    }

    /* Documented in superclass */
    public void replaceComponent(Component oldComponent, Component newComponent) {

        // Gets the locations
        Area oldLocation = null;
        Area newLocation = null;
        for (final Iterator i = areas.iterator(); i.hasNext();) {
            final Area location = (Area) i.next();
            final 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.getColumn1(),
                    oldLocation.getRow1(), oldLocation.getColumn2(),
                    oldLocation.getRow2());
        } else {
            oldLocation.setComponent(newComponent);
            newLocation.setComponent(oldComponent);
            requestRepaint();
        }
    }

    /*
     * Removes all components from this container.
     *
     * @see com.vaadin.ui.ComponentContainer#removeAllComponents()
     */
    @Override
    public void removeAllComponents() {
        super.removeAllComponents();
        componentToAlignment = new HashMap();
        cursorX = 0;
        cursorY = 0;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.vaadin.ui.Layout.AlignmentHandler#setComponentAlignment(com
     * .vaadin.ui.Component, int, int)
     */
    public void setComponentAlignment(Component childComponent,
            int horizontalAlignment, int verticalAlignment) {
        componentToAlignment.put(childComponent, new Alignment(
                horizontalAlignment + verticalAlignment));
        requestRepaint();
    }

    public void setComponentAlignment(Component childComponent,
            Alignment alignment) {
        componentToAlignment.put(childComponent, alignment);
        requestRepaint();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean)
     */
    public void setSpacing(boolean enabled) {
        spacing = enabled;
        requestRepaint();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
     */
    @Deprecated
    public boolean isSpacingEnabled() {
        return spacing;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
     */
    public boolean isSpacing() {
        return spacing;
    }

    /**
     * Inserts an empty row at the chosen position in the grid.
     *
     * @param row
     *            Number of the row the new row will be inserted before
     */
    public void insertRow(int row) {
        if (row > rows) {
            throw new IllegalArgumentException("Cannot insert row at " + row
                    + " in a gridlayout with height " + rows);
        }

        for (Iterator i = areas.iterator(); i.hasNext();) {
            Area existingArea = (Area) i.next();
            // Areas ending below the row needs to be moved down or stretched
            if (existingArea.row2 >= row) {
                existingArea.row2++;

                // Stretch areas that span over the selected row
                if (existingArea.row1 >= row) {
                    existingArea.row1++;
                }

            }
        }

        if (cursorY >= row) {
            cursorY++;
        }

        setRows(rows + 1);
        structuralChange = true;
        requestRepaint();
    }

    /**
     * Removes row and all components in the row. Components which span over
     * several rows are removed if the selected row is the component's first
     * row.
     * <p>
     * If the last row is removed then all remaining components will be removed
     * and the grid will be reduced to one row. The cursor will be moved to the
     * upper left cell of the grid.
     * </p>
     *
     * @param row
     *            The row number to remove
     */
    public void removeRow(int row) {
        if (row >= rows) {
            throw new IllegalArgumentException("Cannot delete row " + row
                    + " from a gridlayout with height " + rows);
        }

        // Remove all components in row
        for (int col = 0; col < getColumns(); col++) {
            removeComponent(col, row);
        }

        // Shrink or remove areas in the selected row
        for (Iterator i = areas.iterator(); i.hasNext();) {
            Area existingArea = (Area) i.next();
            if (existingArea.row2 >= row) {
                existingArea.row2--;

                if (existingArea.row1 > row) {
                    existingArea.row1--;
                }
            }
        }

        if (rows == 1) {
            /*
             * Removing the last row means that the dimensions of the Grid
             * layout will be truncated to 1 empty row and the cursor is moved
             * to the first cell
             */
            cursorX = 0;
            cursorY = 0;
        } else {
            setRows(rows - 1);
            if (cursorY > row) {
                cursorY--;
            }
        }

        structuralChange = true;
        requestRepaint();

    }

    /**
     * Sets the expand ratio of given column. Expand ratio defines how excess
     * space is distributed among columns. Excess space means the space not
     * consumed by non relatively sized components.
     *
     * <p>
     * By default excess space is distributed evenly.
     *
     * <p>
     * Note, that width needs to be defined for this method to have any effect.
     *
     * @see #setWidth(float, int)
     *
     * @param columnIndex
     * @param ratio
     */
    public void setColumnExpandRatio(int columnIndex, float ratio) {
        columnExpandRatio.put(columnIndex, ratio);
        requestRepaint();
    }

    /**
     * Returns the expand ratio of given column
     *
     * @see #setColumnExpandRatio(int, float)
     *
     * @param columnIndex
     * @return the expand ratio, 0.0f by default
     */
    public float getColumnExpandRatio(int columnIndex) {
        Float r = columnExpandRatio.get(columnIndex);
        return r == null ? 0 : r.floatValue();
    }

    /**
     * Sets the expand ratio of given row. Expand ratio defines how excess space
     * is distributed among rows. Excess space means the space not consumed by
     * non relatively sized components.
     *
     * <p>
     * By default excess space is distributed evenly.
     *
     * <p>
     * Note, that height needs to be defined for this method to have any effect.
     *
     * @see #setHeight(float, int)
     *
     * @param rowIndex
     * @param ratio
     */
    public void setRowExpandRatio(int rowIndex, float ratio) {
        rowExpandRatio.put(rowIndex, ratio);
        requestRepaint();
    }

    /**
     * Returns the expand ratio of given row.
     *
     * @see #setRowExpandRatio(int, float)
     *
     * @param rowIndex
     * @return the expand ratio, 0.0f by default
     */
    public float getRowExpandRatio(int rowIndex) {
        Float r = rowExpandRatio.get(rowIndex);
        return r == null ? 0 : r.floatValue();
    }

    /**
     * Gets the Component at given index.
     *
     * @param x
     *            x-index
     * @param y
     *            y-index
     * @return Component in given cell or null if empty
     */
    public Component getComponent(int x, int y) {
        for (final Iterator iterator = areas.iterator(); iterator.hasNext();) {
            final Area area = (Area) iterator.next();
            if (area.getColumn1() <= x && x <= area.getColumn2()
                    && area.getRow1() <= y && y <= area.getRow2()) {
                return area.getComponent();
            }
        }
        return null;
    }

    /**
     * Returns information about the area where given component is layed in the
     * GridLayout.
     *
     * @param component
     *            the component whose area information is requested.
     * @return an Area object that contains information how component is layed
     *         in the grid
     */
    public Area getComponentArea(Component component) {
        for (final Iterator iterator = areas.iterator(); iterator.hasNext();) {
            final Area area = (Area) iterator.next();
            if (area.getComponent() == component) {
                return area;
            }
        }
        return null;
    }

    public void setComponentAlignment(Component component, String alignment) {
        AlignmentUtils.setComponentAlignment(this, component, alignment);
    }

    @Override
    public void changeVariables(Object source, Map variables) {
        super.changeVariables(source, variables);

        if (variables.containsKey(CLICK_EVENT)) {
            fireClick((Map<String, Object>) variables.get(CLICK_EVENT));
        }

    }

    private void fireClick(Map<String, Object> parameters) {
        MouseEventDetails mouseDetails = MouseEventDetails
                .deSerialize((String) parameters.get("mouseDetails"));
        Component childComponent = (Component) parameters.get("component");

        fireEvent(new LayoutClickEvent(this, mouseDetails, childComponent));
    }

    /**
     * Add a click listener to the layout. The listener is called whenever the
     * user clicks inside the layout. Also when the click targets a component
     * inside the Panel, provided the targeted component does not prevent the
     * click event from propagating.
     *
     * The child component that was clicked is included in the
     * {@link LayoutClickEvent}.
     *
     * Use {@link #removeListener(LayoutClickListener)} to remove the listener.
     *
     * @param listener
     *            The listener to add
     */
    public void addListener(LayoutClickListener listener) {
        addListener(CLICK_EVENT, LayoutClickEvent.class, listener,
                LayoutClickListener.clickMethod);
    }

    /**
     * Remove a click listener from the layout. The listener should earlier have
     * been added using {@link #addListener(LayoutClickListener)}.
     *
     * @param listener
     *            The listener to remove
     */
    public void removeListener(LayoutClickListener listener) {
        removeListener(CLICK_EVENT, LayoutClickEvent.class, listener);
    }

}
TOP

Related Classes of com.vaadin.ui.GridLayout$OutOfBoundsException

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.