You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by no...@apache.org on 2009/11/13 12:01:03 UTC

svn commit: r835809 - in /incubator/pivot/trunk/wtk: src/org/apache/pivot/wtk/ src/org/apache/pivot/wtk/skin/ src/org/apache/pivot/wtk/skin/terra/ test/org/apache/pivot/wtk/test/

Author: noelgrandin
Date: Fri Nov 13 11:01:02 2009
New Revision: 835809

URL: http://svn.apache.org/viewvc?rev=835809&view=rev
Log:
PIVOT-227 Add a GridPane container

Added:
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/GridPane.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/GridPaneListener.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/GridPaneFillerSkin.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/GridPaneSkin.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraGridPaneSkin.java
    incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/GridPaneTest.java
    incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/gridpane_test.wtkx
Modified:
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Theme.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraTheme.java

Added: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/GridPane.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/GridPane.java?rev=835809&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/GridPane.java (added)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/GridPane.java Fri Nov 13 11:01:02 2009
@@ -0,0 +1,768 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.wtk;
+
+import java.util.Iterator;
+
+import org.apache.pivot.collections.ArrayList;
+import org.apache.pivot.collections.Sequence;
+import org.apache.pivot.util.ImmutableIterator;
+import org.apache.pivot.util.ListenerList;
+
+
+/**
+ * Container that arranges components in a two-dimensional grid, where every cell is the same size.
+ */
+public class GridPane extends Container {
+    /**
+     * Represents a grid pane row.
+     */
+    public static final class Row implements Sequence<Component>, Iterable<Component> {
+        private boolean highlighted;
+
+        private ArrayList<Component> cells = new ArrayList<Component>();
+
+        private GridPane gridPane = null;
+
+        public Row() {
+            this(false);
+        }
+
+        public Row(boolean highlighted) {
+            this.highlighted = highlighted;
+        }
+
+        /**
+         * Returns the grid pane with which this row is associated.
+         *
+         * @return
+         * The row's grid pane, or <tt>null</tt> if the row does not
+         * currently belong to a grid.
+         */
+        public GridPane getGridPane() {
+            return gridPane;
+        }
+
+        /**
+         * Sets the grid pane with which this row is associated.
+         *
+         * @param gridPane
+         * The row's grid pane, or <tt>null</tt> if the row does not
+         * currently belong to a grid.
+         */
+        private void setGridPane(GridPane gridPane) {
+            this.gridPane = gridPane;
+        }
+
+        /**
+         * Returns the highlighted flag.
+         *
+         * @return
+         * <tt>true</tt> if the row is highlighted, <tt>false</tt> if it is not
+         */
+        public boolean isHighlighted() {
+            return highlighted;
+        }
+
+        /**
+         * Sets the highlighted flag.
+         *
+         * @param highlighted
+         * <tt>true</tt> to set the row as highlighted, <tt>false</tt> to set
+         * it as not highlighted
+         */
+        public void setHighlighted(boolean highlighted) {
+            if (highlighted != this.highlighted) {
+                this.highlighted = highlighted;
+
+                if (gridPane != null) {
+                    gridPane.gridPaneListeners.rowHighlightedChanged(this);
+                }
+            }
+        }
+
+        /**
+         * Sets the visible flag for all components in the row.
+         * <p>
+         * This is a convenience method that iterates through the row, calling
+         * <tt>setVisible</tt> on all components.
+         */
+        public void setVisible(boolean visible) {
+            if (gridPane != null) {
+                for (Component component : cells) {
+                    component.setVisible(visible);
+                }
+            }
+        }
+
+        @Override
+        public int add(Component component) {
+            int i = getLength();
+            insert(component, i);
+
+            return i;
+        }
+
+        @Override
+        public void insert(Component component, int index) {
+            if (component == null) {
+                throw new IllegalArgumentException("Component is null.");
+            }
+
+            if (component.getParent() != null) {
+                throw new IllegalArgumentException("Component already has a parent.");
+            }
+
+            cells.insert(component, index);
+
+            if (gridPane != null) {
+                gridPane.add(component);
+                gridPane.gridPaneListeners.cellInserted(this, index);
+            }
+        }
+
+        @Override
+        public Component update(int index, Component component) {
+            Component previousComponent = cells.get(index);
+
+            if (component != previousComponent) {
+                if (component == null) {
+                    throw new IllegalArgumentException("Component is null.");
+                }
+
+                if (component.getParent() != null) {
+                    throw new IllegalArgumentException("Component already has a parent.");
+                }
+
+                cells.update(index, component);
+                previousComponent.setAttributes(null);
+
+                if (gridPane != null) {
+                    gridPane.add(component);
+                    gridPane.gridPaneListeners.cellUpdated(this, index, previousComponent);
+                    gridPane.remove(previousComponent);
+                }
+            }
+
+            return previousComponent;
+        }
+
+        @Override
+        public int remove(Component component) {
+            int index = indexOf(component);
+            if (index != -1) {
+                remove(index, 1);
+            }
+
+            return index;
+        }
+
+        @Override
+        public Sequence<Component> remove(int index, int count) {
+            Sequence<Component> removed = cells.remove(index, count);
+
+            for (int i = 0, n = removed.getLength(); i < n; i++) {
+                Component component = removed.get(i);
+                component.setAttributes(null);
+            }
+
+            if (gridPane != null) {
+                gridPane.gridPaneListeners.cellsRemoved(this, index, removed);
+
+                for (int i = 0, n = removed.getLength(); i < n; i++) {
+                    Component component = removed.get(i);
+                    gridPane.remove(component);
+                }
+            }
+
+            return removed;
+        }
+
+        @Override
+        public Component get(int index) {
+            return cells.get(index);
+        }
+
+        @Override
+        public int indexOf(Component component) {
+            return cells.indexOf(component);
+        }
+
+        @Override
+        public int getLength() {
+            return cells.getLength();
+        }
+
+        @Override
+        public Iterator<Component> iterator() {
+            return new ImmutableIterator<Component>(cells.iterator());
+        }
+    }
+
+    /**
+     * Represents a grid pane column.
+     */
+    public static class Column {
+        private GridPane gridPane = null;
+
+        private boolean highlighted;
+
+        public Column() {
+            this(false);
+        }
+
+        public Column(boolean highlighted) {
+            this.highlighted = highlighted;
+        }
+
+        /**
+         * Returns the grid pane with which this column is associated.
+         *
+         * @return
+         * The column's grid pane, or <tt>null</tt> if the column does not
+         * currently belong to a grid.
+         */
+        public GridPane getGridPane() {
+            return gridPane;
+        }
+
+        /**
+         * Sets the grid pane with which this column is associated.
+         *
+         * @param gridPane
+         * The column's grid pane, or <tt>null</tt> if the column does not
+         * currently belong to a grid.
+         */
+        private void setGridPane(GridPane gridPane) {
+            this.gridPane = gridPane;
+        }
+
+        /**
+         * Returns the highlighted flag.
+         *
+         * @return
+         * <tt>true</tt> if the column is highlighted, <tt>false</tt> if it is not
+         */
+        public boolean isHighlighted() {
+            return highlighted;
+        }
+
+        /**
+         * Sets the highlighted flag.
+         *
+         * @param highlighted
+         * <tt>true</tt> to set the column as highlighted, <tt>false</tt> to set
+         * it as not highlighted
+         */
+        public void setHighlighted(boolean highlighted) {
+            if (highlighted != this.highlighted) {
+                this.highlighted = highlighted;
+
+                if (gridPane != null) {
+                    gridPane.gridPaneListeners.columnHighlightedChanged(this);
+                }
+            }
+        }
+
+        /**
+         * Sets the visible flag for all components in the column.
+         * <p>
+         * This is a convenience method that iterates through the components in
+         * the column, calling <tt>setVisible</tt> on all such components.
+         */
+        public void setVisible(boolean visible) {
+            if (gridPane != null) {
+                int columnIndex = gridPane.columns.indexOf(this);
+
+                for (Row row : gridPane.rows) {
+                    if (row.getLength() > columnIndex) {
+                        row.get(columnIndex).setVisible(visible);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * grid pane skin interface. grid pane skins must implement
+     * this interface to facilitate additional communication between the
+     * component and the skin.
+     */
+    public interface Skin {
+        public int getRowAt(int y);
+        public Bounds getRowBounds(int row);
+        public int getColumnAt(int x);
+        public Bounds getColumnBounds(int column);
+    }
+
+    /**
+     * Class that manages a grid pane's row list. Callers get access to the
+     * row sequence via {@link GridPane#getRows()}.
+     */
+    public final class RowSequence implements Sequence<Row>, Iterable<Row> {
+        private RowSequence() {
+        }
+
+        @Override
+        public int add(Row row) {
+            int i = getLength();
+            insert(row, i);
+
+            return i;
+        }
+
+        @Override
+        public void insert(Row row, int index) {
+            if (row == null) {
+                throw new IllegalArgumentException("row is null.");
+            }
+
+            if (row.getGridPane() != null) {
+                throw new IllegalArgumentException
+                    ("row is already in use by another grid pane.");
+            }
+
+            rows.insert(row, index);
+            row.setGridPane(GridPane.this);
+
+            for (int i = 0, n = row.getLength(); i < n; i++) {
+                Component component = row.get(i);
+                GridPane.this.add(component);
+            }
+
+            // Notify listeners
+            gridPaneListeners.rowInserted(GridPane.this, index);
+        }
+
+        @Override
+        public Row update(int index, Row row) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int remove(Row row) {
+            int index = indexOf(row);
+            if (index != -1) {
+                remove(index, 1);
+            }
+
+            return index;
+        }
+
+        @Override
+        public Sequence<Row> remove(int index, int count) {
+            Sequence<Row> removed = rows.remove(index, count);
+
+            if (count > 0) {
+                for (int i = 0, n = removed.getLength(); i < n; i++) {
+                    Row row = removed.get(i);
+                    row.setGridPane(null);
+
+                    for (int j = 0, m = row.getLength(); j < m; j++) {
+                        Component component = row.get(j);
+                        GridPane.this.remove(component);
+                    }
+                }
+
+                gridPaneListeners.rowsRemoved(GridPane.this, index, removed);
+            }
+
+            return removed;
+        }
+
+        @Override
+        public Row get(int index) {
+            return rows.get(index);
+        }
+
+        @Override
+        public int indexOf(Row row) {
+            return rows.indexOf(row);
+        }
+
+        @Override
+        public int getLength() {
+            return rows.getLength();
+        }
+
+        @Override
+        public Iterator<Row> iterator() {
+            return new ImmutableIterator<Row>(rows.iterator());
+        }
+    }
+
+    /**
+     * Class that manages a grid pane's column list. Callers get access to the
+     * column sequence via {@link GridPane#getColumns()}.
+     */
+    public final class ColumnSequence implements Sequence<Column>, Iterable<Column> {
+        private ColumnSequence() {
+        }
+
+        @Override
+        public int add(Column column) {
+            int i = getLength();
+            insert(column, i);
+
+            return i;
+        }
+
+        @Override
+        public void insert(Column column, int index) {
+            if (column == null) {
+                throw new IllegalArgumentException("column is null.");
+            }
+
+            if (column.getGridPane() != null) {
+                throw new IllegalArgumentException
+                    ("column is already in use by another grid pane.");
+            }
+
+            columns.insert(column, index);
+            column.setGridPane(GridPane.this);
+
+            // Notify listeners
+            gridPaneListeners.columnInserted(GridPane.this, index);
+        }
+
+        @Override
+        public Column update(int index, Column column) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int remove(Column column) {
+            int index = indexOf(column);
+            if (index != -1) {
+                remove(index, 1);
+            }
+
+            return index;
+        }
+
+        @Override
+        public Sequence<Column> remove(int index, int count) {
+            Sequence<Column> removed = columns.remove(index, count);
+
+            if (count > 0) {
+                for (int i = 0, n = removed.getLength(); i < n; i++) {
+                    Column column = removed.get(i);
+                    column.setGridPane(null);
+                }
+
+                gridPaneListeners.columnsRemoved(GridPane.this, index, removed);
+            }
+
+            return removed;
+        }
+
+        @Override
+        public Column get(int index) {
+            return columns.get(index);
+        }
+
+        @Override
+        public int indexOf(Column column) {
+            return columns.indexOf(column);
+        }
+
+        @Override
+        public int getLength() {
+            return columns.getLength();
+        }
+
+        @Override
+        public Iterator<Column> iterator() {
+            return new ImmutableIterator<Column>(columns.iterator());
+        }
+    }
+
+    /**
+     * Component that can be used as filler for empty cells.
+     */
+    public static class Filler extends Component {
+        public Filler() {
+            installThemeSkin(Filler.class);
+        }
+    }
+
+    private static class GridPaneListenerList extends ListenerList<GridPaneListener>
+        implements GridPaneListener {
+        @Override
+        public void rowInserted(GridPane gridPane, int index) {
+            for (GridPaneListener listener : this) {
+                listener.rowInserted(gridPane, index);
+            }
+        }
+
+        @Override
+        public void rowsRemoved(GridPane gridPane, int index,
+            Sequence<GridPane.Row> rows) {
+            for (GridPaneListener listener : this) {
+                listener.rowsRemoved(gridPane, index, rows);
+            }
+        }
+
+        @Override
+        public void rowHighlightedChanged(GridPane.Row row) {
+            for (GridPaneListener listener : this) {
+                listener.rowHighlightedChanged(row);
+            }
+        }
+
+        @Override
+        public void columnInserted(GridPane gridPane, int index) {
+            for (GridPaneListener listener : this) {
+                listener.columnInserted(gridPane, index);
+            }
+        }
+
+        @Override
+        public void columnsRemoved(GridPane gridPane, int index,
+            Sequence<GridPane.Column> columns) {
+            for (GridPaneListener listener : this) {
+                listener.columnsRemoved(gridPane, index, columns);
+            }
+        }
+
+        @Override
+        public void columnHighlightedChanged(GridPane.Column column) {
+            for (GridPaneListener listener : this) {
+                listener.columnHighlightedChanged(column);
+            }
+        }
+
+        @Override
+        public void cellInserted(GridPane.Row row, int column) {
+            for (GridPaneListener listener : this) {
+                listener.cellInserted(row, column);
+            }
+        }
+
+        @Override
+        public void cellsRemoved(GridPane.Row row, int column,
+            Sequence<Component> removed) {
+            for (GridPaneListener listener : this) {
+                listener.cellsRemoved(row, column, removed);
+            }
+        }
+
+        @Override
+        public void cellUpdated(GridPane.Row row, int column,
+            Component previousComponent) {
+            for (GridPaneListener listener : this) {
+                listener.cellUpdated(row, column, previousComponent);
+            }
+        }
+    }
+
+    private ArrayList<Row> rows = null;
+    private RowSequence rowSequence = new RowSequence();
+
+    private ArrayList<Column> columns = null;
+    private ColumnSequence columnSequence = new ColumnSequence();
+
+    private GridPaneListenerList gridPaneListeners = new GridPaneListenerList();
+
+    public static final String RELATIVE_SIZE_INDICATOR = "*";
+
+    /**
+     * Creates a new <tt>GridPane</tt> with empty row and column sequences.
+     */
+    public GridPane() {
+        this(new ArrayList<Column>());
+    }
+
+    /**
+     * Creates a new <tt>GridPane</tt> with the specified columns.
+     *
+     * @param columns
+     * The column sequence to use. A copy of this sequence will be made
+     */
+    public GridPane(Sequence<Column> columns) {
+        if (columns == null) {
+            throw new IllegalArgumentException("columns is null");
+        }
+
+        this.rows = new ArrayList<Row>();
+        this.columns = new ArrayList<Column>(columns);
+
+        installThemeSkin(GridPane.class);
+    }
+
+    @Override
+    protected void setSkin(org.apache.pivot.wtk.Skin skin) {
+        if (!(skin instanceof GridPane.Skin)) {
+            throw new IllegalArgumentException("Skin class must implement "
+                + GridPane.Skin.class.getName());
+        }
+
+        super.setSkin(skin);
+    }
+
+    /**
+     * Returns the grid pane row sequence.
+     *
+     * @return
+     * The grid pane row sequence
+     */
+    public RowSequence getRows() {
+        return rowSequence;
+    }
+
+    /**
+     * Returns the index of the row at a given location.
+     *
+     * @param y
+     * The y-coordinate of the row to identify.
+     *
+     * @return
+     * The row index, or <tt>-1</tt> if there is no row at the given
+     * y-coordinate.
+     */
+    public int getRowAt(int y) {
+        GridPane.Skin gridPaneSkin = (GridPane.Skin)getSkin();
+        return gridPaneSkin.getRowAt(y);
+    }
+
+    /**
+     * Returns the bounds of a given row.
+     *
+     * @param row
+     * The row index.
+     */
+    public Bounds getRowBounds(int row) {
+        GridPane.Skin gridPaneSkin = (GridPane.Skin)getSkin();
+        return gridPaneSkin.getRowBounds(row);
+    }
+
+    /**
+     * Returns the grid pane column sequence.
+     *
+     * @return
+     * The grid pane column sequence
+     */
+    public ColumnSequence getColumns() {
+        return columnSequence;
+    }
+
+    /**
+     * Returns the index of the column at a given location.
+     *
+     * @param x
+     * The x-coordinate of the column to identify.
+     *
+     * @return
+     * The column index, or <tt>-1</tt> if there is no column at the given
+     * x-coordinate.
+     */
+    public int getColumnAt(int x) {
+        GridPane.Skin gridPaneSkin = (GridPane.Skin)getSkin();
+        return gridPaneSkin.getColumnAt(x);
+    }
+
+    /**
+     * Returns the bounds of a given column.
+     *
+     * @param column
+     * The column index.
+     */
+    public Bounds getColumnBounds(int column) {
+        GridPane.Skin gridPaneSkin = (GridPane.Skin)getSkin();
+        return gridPaneSkin.getColumnBounds(column);
+    }
+
+    /**
+     * Gets the component at the specified cell in this grid pane.
+     *
+     * @param rowIndex
+     * The row index of the cell
+     *
+     * @param columnIndex
+     * The column index of the cell
+     *
+     * @return
+     * The component in the specified cell, or <tt>null</tt> if the cell is
+     * empty
+     */
+    public Component getCellComponent(int rowIndex, int columnIndex) {
+        Row row = rows.get(rowIndex);
+
+        Component component = null;
+
+        if (row.getLength() > columnIndex) {
+            component = row.get(columnIndex);
+        }
+
+        return component;
+    }
+
+    /**
+     * Sets the component at the specified cell in this grid pane.
+     *
+     * @param row
+     * The row index of the cell
+     *
+     * @param column
+     * The column index of the cell
+     *
+     * @param component
+     * The component to place in the specified cell, or <tt>null</tt> to empty
+     * the cell
+     */
+    public void setCellComponent(int row, int column, Component component) {
+        rows.get(row).update(column, component);
+    }
+
+    /**
+     * Overrides the base method to check whether or not a cell component is
+     * being removed, and fires the appropriate event in that case.
+     *
+     * @param index
+     * The index at which components were removed
+     *
+     * @param count
+     * The number of components removed
+     *
+     * @return
+     * The sequence of components that were removed
+     */
+    @Override
+    public Sequence<Component> remove(int index, int count) {
+        for (int i = index, n = index + count; i < n; i++) {
+            Component component = get(i);
+
+            for (Row row : rows) {
+                if (row.indexOf(component) >= 0) {
+                    throw new UnsupportedOperationException();
+                }
+            }
+        }
+
+        // Call the base method to remove the components
+        return super.remove(index, count);
+    }
+
+    /**
+     * Returns the grid pane listener list.
+     */
+    public ListenerList<GridPaneListener> getGridPaneListeners() {
+        return gridPaneListeners;
+    }
+
+
+}

Added: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/GridPaneListener.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/GridPaneListener.java?rev=835809&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/GridPaneListener.java (added)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/GridPaneListener.java Fri Nov 13 11:01:02 2009
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.wtk;
+
+import org.apache.pivot.collections.Sequence;
+
+/**
+ * Table pane listener interface.
+ */
+public interface GridPaneListener {
+    /**
+     * Table pane listener adapter.
+     */
+    public static class Adapter implements GridPaneListener {
+        @Override
+        public void rowInserted(GridPane GridPane, int index) {
+        }
+
+        @Override
+        public void rowsRemoved(GridPane GridPane, int index,
+            Sequence<GridPane.Row> rows) {
+        }
+
+        @Override
+        public void rowHighlightedChanged(GridPane.Row row) {
+        }
+
+        @Override
+        public void columnInserted(GridPane GridPane, int index) {
+        }
+
+        @Override
+        public void columnsRemoved(GridPane GridPane, int index,
+            Sequence<GridPane.Column> columns) {
+        }
+
+        @Override
+        public void columnHighlightedChanged(GridPane.Column column) {
+        }
+
+        @Override
+        public void cellInserted(GridPane.Row row, int column) {
+        }
+
+        @Override
+        public void cellsRemoved(GridPane.Row row, int column,
+            Sequence<Component> removed) {
+        }
+
+        @Override
+        public void cellUpdated(GridPane.Row row, int column,
+            Component previousComponent) {
+        }
+    }
+
+    /**
+     * Called when a row has been inserted into a table pane.
+     *
+     * @param GridPane
+     * @param index
+     */
+    public void rowInserted(GridPane GridPane, int index);
+
+    /**
+     * Called when rows have been removed from a table pane.
+     *
+     * @param GridPane
+     * @param index
+     * @param rows
+     */
+    public void rowsRemoved(GridPane GridPane, int index,
+        Sequence<GridPane.Row> rows);
+
+    /**
+     * Called when a row's highlighted state has changed.
+     *
+     * @param row
+     */
+    public void rowHighlightedChanged(GridPane.Row row);
+
+    /**
+     * Called when a column has been inserted into a table pane.
+     *
+     * @param GridPane
+     * @param index
+     */
+    public void columnInserted(GridPane GridPane, int index);
+
+    /**
+     * Called when column's have been removed from a table pane.
+     *
+     * @param GridPane
+     * @param index
+     * @param columns
+     */
+    public void columnsRemoved(GridPane GridPane, int index,
+        Sequence<GridPane.Column> columns);
+
+    /**
+     * Called when a column's highlighted state has changed.
+     *
+     * @param column
+     */
+    public void columnHighlightedChanged(GridPane.Column column);
+
+    /**
+     * Called when a cell has been inserted into a table pane.
+     *
+     * @param row
+     * @param column
+     */
+    public void cellInserted(GridPane.Row row, int column);
+
+    /**
+     * Called when cell's have been removed from a table pane.
+     *
+     * @param row
+     * @param column
+     * @param removed
+     */
+    public void cellsRemoved(GridPane.Row row, int column,
+        Sequence<Component> removed);
+
+    /**
+     * Called when a cell has been updated in a table pane.
+     *
+     * @param row
+     * @param column
+     * @param previousComponent
+     */
+    public void cellUpdated(GridPane.Row row, int column,
+        Component previousComponent);
+}

Modified: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Theme.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Theme.java?rev=835809&r1=835808&r2=835809&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Theme.java (original)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/Theme.java Fri Nov 13 11:01:02 2009
@@ -23,10 +23,12 @@
 import org.apache.pivot.util.Service;
 import org.apache.pivot.wtk.media.Image;
 import org.apache.pivot.wtk.skin.BorderSkin;
+import org.apache.pivot.wtk.skin.BoxPaneSkin;
 import org.apache.pivot.wtk.skin.CardPaneSkin;
 import org.apache.pivot.wtk.skin.ColorChooserButtonSkin;
-import org.apache.pivot.wtk.skin.BoxPaneSkin;
 import org.apache.pivot.wtk.skin.FlowPaneSkin;
+import org.apache.pivot.wtk.skin.GridPaneFillerSkin;
+import org.apache.pivot.wtk.skin.GridPaneSkin;
 import org.apache.pivot.wtk.skin.ImageViewSkin;
 import org.apache.pivot.wtk.skin.LabelSkin;
 import org.apache.pivot.wtk.skin.MovieViewSkin;
@@ -83,6 +85,8 @@
         componentSkinMap.put(ColorChooserButtonSkin.ColorChooserPopup.class,
             ColorChooserButtonSkin.ColorChooserPopupSkin.class);
         componentSkinMap.put(FlowPane.class, FlowPaneSkin.class);
+        componentSkinMap.put(GridPane.class, GridPaneSkin.class);
+        componentSkinMap.put(GridPane.Filler.class, GridPaneFillerSkin.class);
         componentSkinMap.put(ImageView.class, ImageViewSkin.class);
         componentSkinMap.put(Label.class, LabelSkin.class);
         componentSkinMap.put(MovieView.class, MovieViewSkin.class);

Added: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/GridPaneFillerSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/GridPaneFillerSkin.java?rev=835809&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/GridPaneFillerSkin.java (added)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/GridPaneFillerSkin.java Fri Nov 13 11:01:02 2009
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.wtk.skin;
+
+import java.awt.Graphics2D;
+
+/**
+ * Grid pane filler skin.
+ */
+public class GridPaneFillerSkin extends ComponentSkin {
+    @Override
+    public boolean isFocusable() {
+        return false;
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return false;
+    }
+
+    @Override
+    public void layout() {
+        // No-op
+    }
+
+    @Override
+    public int getPreferredHeight(int width) {
+        return 0;
+    }
+
+    @Override
+    public int getPreferredWidth(int height) {
+        return 0;
+    }
+
+    @Override
+    public void paint(Graphics2D graphics) {
+    }
+}

Added: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/GridPaneSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/GridPaneSkin.java?rev=835809&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/GridPaneSkin.java (added)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/GridPaneSkin.java Fri Nov 13 11:01:02 2009
@@ -0,0 +1,828 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.wtk.skin;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+
+import org.apache.pivot.collections.Dictionary;
+import org.apache.pivot.collections.Sequence;
+import org.apache.pivot.wtk.Bounds;
+import org.apache.pivot.wtk.Component;
+import org.apache.pivot.wtk.Dimensions;
+import org.apache.pivot.wtk.GraphicsUtilities;
+import org.apache.pivot.wtk.GridPane;
+import org.apache.pivot.wtk.GridPaneListener;
+import org.apache.pivot.wtk.Insets;
+import org.apache.pivot.wtk.Orientation;
+
+/**
+ * Grid pane skin.
+ */
+public class GridPaneSkin extends ContainerSkin implements GridPane.Skin,
+    GridPaneListener {
+    private Insets padding = Insets.NONE;
+    private int horizontalSpacing = 0;
+    private int verticalSpacing = 0;
+    private boolean showHorizontalGridLines = false;
+    private boolean showVerticalGridLines = false;
+    private Color horizontalGridColor = Color.BLACK;
+    private Color verticalGridColor = Color.BLACK;
+    private Color highlightBackgroundColor = Color.GRAY;
+
+    private int cellWidth = 0;
+    private int cellHeight = 0;
+
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        GridPane gridPane = (GridPane)component;
+        gridPane.getGridPaneListeners().add(this);
+    }
+
+    @Override
+    public int getPreferredWidth(int height) {
+        GridPane gridPane = (GridPane)getComponent();
+        GridPane.RowSequence rows = gridPane.getRows();
+        GridPane.ColumnSequence columns = gridPane.getColumns();
+
+        int rowCount = rows.getLength();
+        int columnCount = columns.getLength();
+        boolean[] visibleColumns = new boolean[columnCount];
+        boolean[] visibleRows = new boolean[rowCount];
+
+        for (int i = 0; i < rowCount; i++) {
+            GridPane.Row row = rows.get(i);
+
+            for (int j = 0, n = row.getLength(); j < n && j < columnCount; j++) {
+                Component component = row.get(j);
+
+                if (component != null
+                    && component.isVisible()) {
+                    visibleColumns[j] = true;
+                    visibleRows[i] = true;
+                }
+            }
+        }
+        
+        int visibleRowCount = 0;
+        int clientHeight = height - padding.top - padding.bottom;
+        for (int i = 0; i < rowCount; i++) {
+            if (visibleRows[i]) {
+                visibleRowCount++;
+            }
+        }
+
+        if (visibleRowCount > 1) {
+            clientHeight -= (visibleRowCount - 1) * verticalSpacing;
+        }
+        int cellHeight = 0;
+        if (visibleRowCount > 0) {
+            cellHeight = clientHeight / visibleRowCount;
+        }
+        
+        int cellPreferredWidth = 0;
+        for (int i = 0; i < rowCount; i++) {
+            GridPane.Row row = rows.get(i);
+
+            for (int j = 0, n = row.getLength(); j < n && j < columnCount; j++) {
+                Component component = row.get(j);
+
+                if (component != null
+                    && component.isVisible()) {
+                    cellPreferredWidth = Math.max(cellPreferredWidth, component.getPreferredWidth(cellHeight));
+                }
+            }
+        }
+        
+        // The preferred width of the grid pane is the sum of the column
+        // widths, plus padding and spacing
+
+        int visibleColumnCount = 0;
+        int preferredWidth = padding.left + padding.right;
+
+        for (int j = 0; j < columnCount; j++) {
+            if (visibleColumns[j]) {
+                preferredWidth += cellPreferredWidth;
+                visibleColumnCount++;
+            }
+        }
+
+        if (visibleColumnCount > 1) {
+            preferredWidth += (visibleColumnCount - 1) * horizontalSpacing;
+        }
+
+        return preferredWidth;
+    }
+
+    @Override
+    public int getPreferredHeight(int width) {
+        GridPane gridPane = (GridPane)getComponent();
+        GridPane.RowSequence rows = gridPane.getRows();
+        GridPane.ColumnSequence columns = gridPane.getColumns();
+
+        int rowCount = rows.getLength();
+        int columnCount = columns.getLength();
+        boolean[] visibleColumns = new boolean[columnCount];
+        boolean[] visibleRows = new boolean[rowCount];
+
+        for (int i = 0; i < rowCount; i++) {
+            GridPane.Row row = rows.get(i);
+
+            for (int j = 0, n = row.getLength(); j < n && j < columnCount; j++) {
+                Component component = row.get(j);
+
+                if (component != null
+                    && component.isVisible()) {
+                    visibleColumns[j] = true;
+                    visibleRows[i] = true;
+                }
+            }
+        }
+        
+        int visibleColumnCount = 0;
+        int clientWidth = width - padding.left - padding.right;
+        for (int i = 0; i < columnCount; i++) {
+            if (visibleColumns[i]) {
+                visibleColumnCount++;
+            }
+        }
+
+        if (visibleColumnCount > 1) {
+            clientWidth -= (visibleColumnCount - 1) * horizontalSpacing;
+        }
+        int cellWidth = 0;
+        if (visibleColumnCount > 0) {
+            cellWidth = clientWidth / visibleColumnCount;
+        }
+
+        // The preferred height of the grid pane is the sum of the row
+        // heights, plus padding and spacing
+
+        int visibleRowCount = 0;
+        int preferredHeight = padding.top + padding.bottom;
+
+        for (int i = 0; i < rowCount; i++) {
+
+            if (visibleRows[i]) {
+                preferredHeight += cellWidth;
+                visibleRowCount++;
+            }
+        }
+
+        if (visibleRowCount > 1) {
+            preferredHeight += (visibleRowCount - 1) * verticalSpacing;
+        }
+
+        return preferredHeight;
+    }
+
+    @Override
+    public Dimensions getPreferredSize() {
+        GridPane gridPane = (GridPane)getComponent();
+        GridPane.RowSequence rows = gridPane.getRows();
+        GridPane.ColumnSequence columns = gridPane.getColumns();
+
+        int rowCount = rows.getLength();
+        int columnCount = columns.getLength();
+        boolean[] visibleColumns = new boolean[columnCount];
+        boolean[] visibleRows = new boolean[rowCount];
+
+        // calculate the maximum preferred cellWidth and cellHeight
+        int preferredCellHeight = 0;
+        int preferredCellWidth = 0;
+        for (int i = 0; i < rowCount; i++) {
+            GridPane.Row row = rows.get(i);
+
+            for (int j = 0, n = row.getLength(); j < n && j < columnCount; j++) {
+                Component component = row.get(j);
+
+                if (component != null
+                    && component.isVisible()) {
+                    visibleColumns[j] = true;
+                    visibleRows[i] = true;
+                    Dimensions d = component.getPreferredSize();
+                    preferredCellHeight = Math.max(preferredCellHeight, d.height);
+                    preferredCellWidth = Math.max(preferredCellWidth, d.width);
+                }
+            }
+        }
+        
+        // The preferred width of the grid pane is the sum of the column
+        // widths, plus padding and spacing
+
+        int visibleColumnCount = 0;
+        int preferredWidth = padding.left + padding.right;
+
+        for (int j = 0; j < columnCount; j++) {
+            if (visibleColumns[j]) {
+                preferredWidth += preferredCellWidth;
+                visibleColumnCount++;
+            }
+        }
+
+        if (visibleColumnCount > 1) {
+            preferredWidth += (visibleColumnCount - 1) * horizontalSpacing;
+        }
+
+        // The preferred height of the grid pane is the sum of the row
+        // heights, plus padding and spacing
+        
+        int visibleRowCount = 0;
+        int preferredHeight = padding.top + padding.bottom;
+        for (int i = 0; i < rowCount; i++) {
+            if (visibleRows[i]) {
+                visibleRowCount++;
+            }
+        }
+
+        if (visibleRowCount > 1) {
+            preferredHeight += (visibleRowCount - 1) * verticalSpacing;
+        }
+        
+        return new Dimensions(preferredWidth, preferredHeight);
+    }
+
+    @Override
+    public int getBaseline(int width, int height) {
+        int baseline = -1;
+
+        // TODO Return the first available baseline by traversing cells top left to bottom right
+
+        // Include top padding value
+        if (baseline != -1) {
+            baseline += padding.top;
+        }
+
+        return baseline;
+    }
+
+    @Override
+    public void layout() {
+        GridPane gridPane = (GridPane)getComponent();
+
+        GridPane.RowSequence rows = gridPane.getRows();
+        GridPane.ColumnSequence columns = gridPane.getColumns();
+
+        int rowCount = rows.getLength();
+        int columnCount = columns.getLength();
+
+        int width = getWidth();
+        int height = getHeight();
+
+        // Determine which rows and column should be visible so we know which
+        // ones should be collapsed
+        boolean[] visibleRows = new boolean[rowCount];
+        boolean[] visibleColumns = new boolean[columnCount];
+        int cellPreferredHeight = 0;
+        int cellPreferredWidth = 0;
+
+        for (int i = 0; i < rowCount; i++) {
+            GridPane.Row row = rows.get(i);
+
+            for (int j = 0, n = row.getLength(); j < n && j < columnCount; j++) {
+                Component child = row.get(j);
+
+                if (child != null
+                    && child.isVisible()) {
+                    visibleRows[i] = true;
+                    visibleColumns[j] = true;
+                    Dimensions d = child.getPreferredSize();
+                    cellPreferredHeight = Math.max(cellPreferredHeight, d.height);
+                    cellPreferredWidth = Math.max(cellPreferredWidth, d.width);
+                }
+            }
+        }
+        
+        
+        // Calculate cell width
+        
+        int visibleColumnCount = 0;
+        int clientWidth = width - padding.left - padding.right;
+        for (int i = 0; i < columnCount; i++) {
+            if (visibleColumns[i]) {
+                visibleColumnCount++;
+            }
+        }
+
+        if (visibleColumnCount > 1) {
+            clientWidth -= (visibleColumnCount - 1) * horizontalSpacing;
+        }
+        cellWidth = 0;
+        if (visibleColumnCount > 0) {
+            cellWidth = clientWidth / visibleColumnCount;
+        }
+        
+        
+        // Calculate cell height
+        
+        int visibleRowCount = 0;
+        int clientHeight = height - padding.top - padding.bottom;
+        for (int i = 0; i < rowCount; i++) {
+            if (visibleRows[i]) {
+                visibleRowCount++;
+            }
+        }
+
+        if (visibleRowCount > 1) {
+            clientHeight -= (visibleRowCount - 1) * verticalSpacing;
+        }
+        cellHeight = 0;
+        if (visibleRowCount > 0) {
+            cellHeight = clientHeight / visibleRowCount;
+        }
+        
+        int componentY = padding.top;
+        for (int i = 0; i < rowCount; i++) {
+            GridPane.Row row = rows.get(i);
+
+            int componentX = padding.left;
+            for (int j = 0, n = row.getLength(); j < n && j < columnCount; j++) {
+                Component child = row.get(j);
+
+                if (child != null
+                    && child.isVisible()) {
+                    child.setLocation(componentX, componentY);
+
+                    child.setSize(cellWidth, cellHeight);
+                }
+
+                if (visibleColumns[j]) {
+                    componentX += (cellWidth + horizontalSpacing);
+                }
+            }
+
+            if (visibleRows[i]) {
+                componentY += (cellHeight + verticalSpacing);
+            }
+        }
+    }
+
+    @Override
+    public void paint(Graphics2D graphics) {
+        super.paint(graphics);
+
+        GridPane gridPane = (GridPane)getComponent();
+
+        GridPane.RowSequence rows = gridPane.getRows();
+        GridPane.ColumnSequence columns = gridPane.getColumns();
+
+        int rowCount = rows.getLength();
+        int columnCount = columns.getLength();
+
+        int width = getWidth();
+        int height = getHeight();
+
+        graphics.setPaint(highlightBackgroundColor);
+
+        // Paint the highlighted rows
+        for (int i = 0, rowY = padding.top; i < rowCount; i++) {
+            GridPane.Row row = rows.get(i);
+
+            if (row.isHighlighted()) {
+                graphics.fillRect(0, rowY, width, cellHeight);
+            }
+
+            rowY += cellHeight + verticalSpacing;
+        }
+
+        // Paint the highlighted columns
+        for (int j = 0, columnX = padding.left; j < columnCount; j++) {
+            GridPane.Column column = columns.get(j);
+
+            if (column.isHighlighted()) {
+                graphics.fillRect(columnX, 0, cellWidth, height);
+            }
+
+            columnX += cellWidth + horizontalSpacing;
+        }
+
+        // Paint the grid lines
+        if ((showHorizontalGridLines && verticalSpacing > 0)
+            || (showVerticalGridLines && horizontalSpacing > 0)) {
+            Graphics2D gridGraphics = (Graphics2D)graphics.create();
+
+            gridGraphics.setStroke(new BasicStroke());
+            gridGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+
+            if (showHorizontalGridLines
+                && verticalSpacing > 0
+                && rowCount > 1) {
+                gridGraphics.setPaint(horizontalGridColor);
+
+                int rowY = padding.top + (cellHeight + verticalSpacing);
+
+                for (int i = 1; i < rowCount; i++) {
+                    int gridY = Math.max(rowY - (int)Math.ceil(verticalSpacing * 0.5f), 0);
+                    GraphicsUtilities.drawLine(gridGraphics, 0, gridY,
+                        width, Orientation.HORIZONTAL);
+
+                    rowY += (cellHeight + verticalSpacing);
+                }
+            }
+
+            if (showVerticalGridLines
+                && horizontalSpacing > 0
+                && columnCount > 1) {
+                gridGraphics.setPaint(verticalGridColor);
+
+                int columnX = padding.left + (cellWidth + horizontalSpacing);
+
+                for (int j = 1; j < columnCount; j++) {
+                    int gridX = Math.max(columnX - (int)Math.ceil(horizontalSpacing * 0.5), 0);
+                    GraphicsUtilities.drawLine(gridGraphics, gridX, 0,
+                        height, Orientation.VERTICAL);
+
+                    columnX += (cellWidth + horizontalSpacing);
+                }
+            }
+
+            gridGraphics.dispose();
+        }
+    }
+
+    /**
+     * Gets the padding that will be reserved around the grid pane during
+     * layout.
+     */
+    public Insets getPadding() {
+        return padding;
+    }
+
+    /**
+     * Sets the padding that will be reserved around the grid pane during
+     * layout.
+     */
+    public void setPadding(Insets padding) {
+        if (padding == null) {
+            throw new IllegalArgumentException("padding is null.");
+        }
+
+        this.padding = padding;
+        invalidateComponent();
+    }
+
+    /**
+     * Sets the padding that will be reserved around the grid pane during
+     * layout.
+     */
+    public final void setPadding(int padding) {
+        setPadding(new Insets(padding));
+    }
+
+    /**
+     * Sets the padding that will be reserved around the grid pane during
+     * layout.
+     */
+    public final void setPadding(Dictionary<String, ?> padding) {
+        if (padding == null) {
+            throw new IllegalArgumentException("padding is null.");
+        }
+
+        setPadding(new Insets(padding));
+    }
+
+    /**
+     * Sets the padding that will be reserved around the grid pane during
+     * layout.
+     */
+    public final void setPadding(String padding) {
+        if (padding == null) {
+            throw new IllegalArgumentException("padding is null.");
+        }
+
+        setPadding(Insets.decode(padding));
+    }
+
+    /**
+     * Gets the spacing that will be applied in between the grid pane's
+     * columns during layout.
+     */
+    public int getHorizontalSpacing() {
+        return horizontalSpacing;
+    }
+
+    /**
+     * Sets the spacing that will be applied in between the grid pane's
+     * columns during layout.
+     */
+    public void setHorizontalSpacing(int horizontalSpacing) {
+        if (horizontalSpacing < 0) {
+            throw new IllegalArgumentException("horizontalSpacing is negative");
+        }
+
+        this.horizontalSpacing = horizontalSpacing;
+        invalidateComponent();
+    }
+
+    /**
+     * Gets the spacing that will be applied in between the grid pane's rows
+     * during layout.
+     */
+    public int getVerticalSpacing() {
+        return verticalSpacing;
+    }
+
+    /**
+     * Sets the spacing that will be applied in between the grid pane's rows
+     * during layout.
+     */
+    public void setVerticalSpacing(int verticalSpacing) {
+        if (verticalSpacing < 0) {
+            throw new IllegalArgumentException("verticalSpacing is negative");
+        }
+
+        this.verticalSpacing = verticalSpacing;
+        invalidateComponent();
+    }
+
+    /**
+     * Tells whether or not horizontal grid lines will be painted in between
+     * the grid pane's rows.
+     */
+    public boolean getShowHorizontalGridLines() {
+        return showHorizontalGridLines;
+    }
+
+    /**
+     * Sets whether or not horizontal grid lines will be painted in between
+     * the grid pane's rows.
+     */
+    public void setShowHorizontalGridLines(boolean showHorizontalGridLines) {
+        this.showHorizontalGridLines = showHorizontalGridLines;
+        repaintComponent();
+    }
+
+    /**
+     * Tells whether or not vertical grid lines will be painted in between
+     * the grid pane's columns.
+     */
+    public boolean getShowVerticalGridLines() {
+        return showVerticalGridLines;
+    }
+
+    /**
+     * Sets whether or not vertical grid lines will be painted in between
+     * the grid pane's columns.
+     */
+    public void setShowVerticalGridLines(boolean showVerticalGridLines) {
+        this.showVerticalGridLines = showVerticalGridLines;
+        repaintComponent();
+    }
+
+    /**
+     * Gets the color used to paint the grid pane's horizontal grid lines.
+     */
+    public Color getHorizontalGridColor() {
+        return horizontalGridColor;
+    }
+
+    /**
+     * Sets the color used to paint the grid pane's horizontal grid lines.
+     */
+    public void setHorizontalGridColor(Color horizontalGridColor) {
+        if (horizontalGridColor == null) {
+            throw new IllegalArgumentException("horizontalGridColor is null.");
+        }
+
+        this.horizontalGridColor = horizontalGridColor;
+
+        if (showHorizontalGridLines || showVerticalGridLines) {
+            repaintComponent();
+        }
+    }
+
+    /**
+     * Sets the color used to paint the grid pane's horizontal grid lines.
+     */
+    public final void setHorizontalGridColor(String horizontalGridColor) {
+        if (horizontalGridColor == null) {
+            throw new IllegalArgumentException("horizontalGridColor is null.");
+        }
+
+        setHorizontalGridColor(GraphicsUtilities.decodeColor(horizontalGridColor));
+    }
+
+    /**
+     * Gets the color used to paint the grid pane's vertical grid lines.
+     */
+    public Color getVerticalGridColor() {
+        return verticalGridColor;
+    }
+
+    /**
+     * Sets the color used to paint the grid pane's vertical grid lines.
+     */
+    public void setVerticalGridColor(Color verticalGridColor) {
+        if (verticalGridColor == null) {
+            throw new IllegalArgumentException("verticalGridColor is null.");
+        }
+
+        this.verticalGridColor = verticalGridColor;
+
+        if (showHorizontalGridLines || showVerticalGridLines) {
+            repaintComponent();
+        }
+    }
+
+    /**
+     * Sets the color used to paint the grid pane's vertical grid lines.
+     */
+    public final void setVerticalGridColor(String verticalGridColor) {
+        if (verticalGridColor == null) {
+            throw new IllegalArgumentException("verticalGridColor is null.");
+        }
+
+        setVerticalGridColor(GraphicsUtilities.decodeColor(verticalGridColor));
+    }
+
+    /**
+     * Gets the background color used to paint the highlighted rows and columns.
+     */
+    public Color getHighlightBackgroundColor() {
+        return highlightBackgroundColor;
+    }
+
+    /**
+     * Sets the background color used to paint the highlighted rows and columns.
+     */
+    public void setHighlightBackgroundColor(Color highlightBackgroundColor) {
+        if (highlightBackgroundColor == null) {
+            throw new IllegalArgumentException("highlightBackgroundColor is null.");
+        }
+
+        this.highlightBackgroundColor = highlightBackgroundColor;
+        repaintComponent();
+    }
+
+    /**
+     * Sets the background color used to paint the highlighted rows and columns.
+     */
+    public final void setHighlightBackgroundColor(String highlightBackgroundColor) {
+        if (highlightBackgroundColor == null) {
+            throw new IllegalArgumentException("highlightBackgroundColor is null.");
+        }
+
+        setHighlightBackgroundColor(GraphicsUtilities.decodeColor(highlightBackgroundColor));
+    }
+
+    // GridPane.Skin methods
+
+    @Override
+    public int getRowAt(int y) {
+        GridPane gridPane = (GridPane)getComponent();
+        GridPane.RowSequence rows = gridPane.getRows();
+
+        int rowCount = rows.getLength();
+        
+        int rowIndex = -1;
+        int rowY = padding.top;
+
+        for (int i = 0; rowY <= y && i < rowCount; i++) {
+
+            if (y < rowY + cellHeight) {
+                rowIndex = i;
+                break;
+            }
+
+            rowY += cellHeight + verticalSpacing;
+        }
+
+        return rowIndex;
+    }
+
+    @Override
+    public Bounds getRowBounds(int row) {
+        GridPane gridPane = (GridPane)getComponent();
+        GridPane.RowSequence rows = gridPane.getRows();
+
+        int rowCount = rows.getLength();
+        
+        if (row < 0
+            || row >= rowCount) {
+            throw new IndexOutOfBoundsException(String.valueOf(row));
+        }
+
+        int rowY = padding.top;
+
+        for (int i = 0; i < row; i++) {
+            rowY += cellHeight + verticalSpacing;
+        }
+
+        return new Bounds(0, rowY, getWidth(), cellHeight);
+    }
+
+    @Override
+    public int getColumnAt(int x) {
+        GridPane gridPane = (GridPane)getComponent();
+        GridPane.ColumnSequence columns = gridPane.getColumns();
+
+        int columnCount = columns.getLength();
+        
+        int columnIndex = -1;
+
+        for (int j = 0, columnX = padding.left; columnX <= x && j < columnCount; j++) {
+
+            if (x < columnX + cellWidth) {
+                columnIndex = j;
+                break;
+            }
+
+            columnX += cellWidth + horizontalSpacing;
+        }
+
+        return columnIndex;
+    }
+
+    @Override
+    public Bounds getColumnBounds(int column) {
+        GridPane gridPane = (GridPane)getComponent();
+        GridPane.ColumnSequence columns = gridPane.getColumns();
+
+        int columnCount = columns.getLength();
+        
+        if (column < 0
+            || column >= columnCount) {
+            throw new IndexOutOfBoundsException(String.valueOf(column));
+        }
+
+        int columnX = padding.left;
+
+        for (int j = 0; j < column; j++) {
+            columnX += cellWidth + horizontalSpacing;
+        }
+
+        return new Bounds(columnX, 0, cellWidth, getHeight());
+    }
+
+    // GridPaneListener methods
+
+    @Override
+    public void rowInserted(GridPane gridPane, int index) {
+        invalidateComponent();
+    }
+
+    @Override
+    public void rowsRemoved(GridPane gridPane, int index, Sequence<GridPane.Row> rows) {
+        invalidateComponent();
+    }
+
+    @Override
+    public void rowHighlightedChanged(GridPane.Row row) {
+        GridPane gridPane = row.getGridPane();
+        repaintComponent(getRowBounds(gridPane.getRows().indexOf(row)));
+    }
+
+    @Override
+    public void columnInserted(GridPane gridPane, int index) {
+        invalidateComponent();
+    }
+
+    @Override
+    public void columnsRemoved(GridPane gridPane, int index,
+        Sequence<GridPane.Column> columns) {
+        invalidateComponent();
+    }
+
+    @Override
+    public void columnHighlightedChanged(GridPane.Column column) {
+        GridPane gridPane = column.getGridPane();
+        repaintComponent(getColumnBounds(gridPane.getColumns().indexOf(column)));
+    }
+
+    @Override
+    public void cellInserted(GridPane.Row row, int column) {
+        invalidateComponent();
+    }
+
+    @Override
+    public void cellsRemoved(GridPane.Row row, int column,
+        Sequence<Component> removed) {
+        invalidateComponent();
+    }
+
+    @Override
+    public void cellUpdated(GridPane.Row row, int column,
+        Component previousComponent) {
+        invalidateComponent();
+    }
+
+}

Added: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraGridPaneSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraGridPaneSkin.java?rev=835809&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraGridPaneSkin.java (added)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraGridPaneSkin.java Fri Nov 13 11:01:02 2009
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.wtk.skin.terra;
+
+import org.apache.pivot.wtk.Theme;
+import org.apache.pivot.wtk.skin.GridPaneSkin;
+
+/**
+ * Terra grid pane skin.
+ */
+public class TerraGridPaneSkin extends GridPaneSkin {
+    public TerraGridPaneSkin() {
+        setHorizontalGridColor(7);
+        setVerticalGridColor(7);
+        setHighlightBackgroundColor(10);
+    }
+
+    public final void setBackgroundColor(int backgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBackgroundColor(theme.getColor(backgroundColor));
+    }
+
+    public final void setHorizontalGridColor(int horizontalGridColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setHorizontalGridColor(theme.getColor(horizontalGridColor));
+    }
+
+    public final void setVerticalGridColor(int verticalGridColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setVerticalGridColor(theme.getColor(verticalGridColor));
+    }
+
+    public final void setHighlightBackgroundColor(int highlightBackgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setHighlightBackgroundColor(theme.getColor(highlightBackgroundColor));
+    }
+}

Modified: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraTheme.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraTheme.java?rev=835809&r1=835808&r2=835809&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraTheme.java (original)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraTheme.java Fri Nov 13 11:01:02 2009
@@ -18,8 +18,8 @@
 
 import java.awt.Color;
 import java.awt.Font;
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 
 import org.apache.pivot.collections.List;
@@ -33,6 +33,7 @@
 import org.apache.pivot.wtk.Alert;
 import org.apache.pivot.wtk.ApplicationContext;
 import org.apache.pivot.wtk.Border;
+import org.apache.pivot.wtk.BoxPane;
 import org.apache.pivot.wtk.Calendar;
 import org.apache.pivot.wtk.CalendarButton;
 import org.apache.pivot.wtk.Checkbox;
@@ -40,11 +41,11 @@
 import org.apache.pivot.wtk.ColorChooserButton;
 import org.apache.pivot.wtk.Dialog;
 import org.apache.pivot.wtk.Expander;
-import org.apache.pivot.wtk.BoxPane;
 import org.apache.pivot.wtk.FileBrowser;
 import org.apache.pivot.wtk.FileBrowserSheet;
 import org.apache.pivot.wtk.Form;
 import org.apache.pivot.wtk.Frame;
+import org.apache.pivot.wtk.GridPane;
 import org.apache.pivot.wtk.Label;
 import org.apache.pivot.wtk.LinkButton;
 import org.apache.pivot.wtk.ListButton;
@@ -105,6 +106,7 @@
         componentSkinMap.put(Form.class, TerraFormSkin.class);
         componentSkinMap.put(BoxPane.class, TerraBoxPaneSkin.class);
         componentSkinMap.put(Frame.class, TerraFrameSkin.class);
+        componentSkinMap.put(GridPane.class, TerraGridPaneSkin.class);
         componentSkinMap.put(Label.class, TerraLabelSkin.class);
         componentSkinMap.put(LinkButton.class, TerraLinkButtonSkin.class);
         componentSkinMap.put(ListButton.class, TerraListButtonSkin.class);

Added: incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/GridPaneTest.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/GridPaneTest.java?rev=835809&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/GridPaneTest.java (added)
+++ incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/GridPaneTest.java Fri Nov 13 11:01:02 2009
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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 org.apache.pivot.wtk.test;
+
+import org.apache.pivot.collections.Map;
+import org.apache.pivot.wtk.Application;
+import org.apache.pivot.wtk.DesktopApplicationContext;
+import org.apache.pivot.wtk.Display;
+import org.apache.pivot.wtk.Window;
+import org.apache.pivot.wtkx.WTKXSerializer;
+
+public class GridPaneTest implements Application {
+    private Window window = null;
+
+    @Override
+    public void startup(Display display, Map<String, String> properties)
+        throws Exception {
+        WTKXSerializer wtkxSerializer = new WTKXSerializer();
+        window = (Window)wtkxSerializer.readObject(this, "gridpane_test.wtkx");
+        window.open(display);
+    }
+
+    @Override
+    public boolean shutdown(boolean optional) {
+        if (window != null) {
+            window.close();
+        }
+
+        return false;
+    }
+
+    @Override
+    public void suspend() {
+    }
+
+    @Override
+    public void resume() {
+    }
+
+    public static void main(String[] args) {
+        DesktopApplicationContext.main(GridPaneTest.class, args);
+    }
+}

Added: incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/gridpane_test.wtkx
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/gridpane_test.wtkx?rev=835809&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/gridpane_test.wtkx (added)
+++ incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/gridpane_test.wtkx Fri Nov 13 11:01:02 2009
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to you 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.
+-->
+
+<Window title="GridPane Test" maximized="true"
+    xmlns:wtkx="http://pivot.apache.org/wtkx"
+    xmlns="org.apache.pivot.wtk">
+    <content>
+        <GridPane styles="{padding:6, verticalSpacing:8, horizontalSpacing:4, showHorizontalGridLines:true, showVerticalGridLines:true}">
+            <columns>
+                <GridPane.Column/>
+                <GridPane.Column/>
+            </columns>
+            <rows>
+                <GridPane.Row>
+                    <PushButton buttonData="One"/>
+                    <GridPane.Filler preferredHeight="20"/>
+                </GridPane.Row>
+                <GridPane.Row>
+                    <GridPane.Filler preferredHeight="20"/>
+                    <PushButton buttonData="Two"/>
+                </GridPane.Row>
+                <GridPane.Row>
+                    <PushButton buttonData="Three" visible="false"/>
+                </GridPane.Row>
+                <GridPane.Row>
+                    <PushButton buttonData="Four"/>
+                </GridPane.Row>
+            </rows>
+        </GridPane>
+    </content>
+</Window>