You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by tv...@apache.org on 2009/03/16 17:36:28 UTC

svn commit: r754936 [20/38] - in /incubator/pivot/tags/v1.0.1: ./ charts-test/ charts-test/src/ charts-test/src/pivot/ charts-test/src/pivot/charts/ charts-test/src/pivot/charts/test/ charts/ charts/lib/ charts/src/ charts/src/pivot/ charts/src/pivot/c...

Added: incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePane.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePane.java?rev=754936&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePane.java (added)
+++ incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePane.java Mon Mar 16 16:36:10 2009
@@ -0,0 +1,988 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * 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 pivot.wtk;
+
+import pivot.collections.ArrayList;
+import pivot.collections.Sequence;
+import pivot.util.ListenerList;
+
+/**
+ * Container that arranges components in a two-dimensional grid, optionally
+ * spanning multiple rows and columns.
+ *
+ * @author tvolkert
+ */
+public class TablePane extends Container {
+    /**
+     * Represents a table pane row.
+     *
+     * @author tvolkert
+     */
+    public static final class Row implements Sequence<Component> {
+        private int height;
+        private boolean relative;
+        private boolean selected;
+
+        private ArrayList<Component> cells = new ArrayList<Component>();
+
+        private TablePane tablePane = null;
+
+        public Row() {
+            this(-1, false, false);
+        }
+
+        public Row(int height) {
+            this(height, false, false);
+        }
+
+        public Row(int height, boolean relative) {
+            this(height, relative, false);
+        }
+
+        public Row(int height, boolean relative, boolean selected) {
+            this.height = height;
+            this.relative = relative;
+            this.selected = selected;
+        }
+
+        /**
+         * Returns the table pane with which this row is associated.
+         *
+         * @return
+         * The row's table pane, or <tt>null</tt> if the row does not
+         * currently belong to a table.
+         */
+        public TablePane getTablePane() {
+            return tablePane;
+        }
+
+        /**
+         * Sets the table pane with which this row is associated.
+         *
+         * @param tablePane
+         * The row's table pane, or <tt>null</tt> if the row does not
+         * currently belong to a table.
+         */
+        private void setTablePane(TablePane tablePane) {
+            this.tablePane = tablePane;
+        }
+
+        /**
+         * Returns the row height.
+         *
+         * @return
+         * The height of the row.
+         */
+        public int getHeight() {
+            return height;
+        }
+
+        /**
+         * Returns the relative flag.
+         *
+         * @return
+         * <tt>true</tt> if the row height is relative, <tt>false</tt> if it
+         * is fixed.
+         */
+        public boolean isRelative() {
+            return relative;
+        }
+
+        /**
+         * Set the row height.
+         *
+         * @param height
+         * The absolute height of the row.
+         */
+        public void setHeight(int height) {
+            setHeight(height, false);
+        }
+
+        /**
+         * Set the row height.
+         *
+         * @param height
+         * The encoded height of the row. If the string ends with the '*'
+         * character, it is treated as a relative value. Otherwise, it is
+         * considered an absolute value.
+         */
+        public void setHeight(String height) {
+            boolean relative = false;
+
+            if (height.endsWith(RELATIVE_SIZE_INDICATOR)) {
+                relative = true;
+                height = height.substring(0, height.length() - 1);
+            }
+
+            setHeight(Integer.parseInt(height), relative);
+        }
+
+        /**
+         * Sets the row height.
+         *
+         * @param height
+         * The height of the row.
+         *
+         * @param relative
+         * <tt>true</tt> if the row height is relative, <tt>false</tt> if it
+         * is fixed.
+         */
+        public void setHeight(int height, boolean relative) {
+            int previousHeight = this.height;
+            boolean previousRelative = this.relative;
+
+            if (previousHeight != height
+                || previousRelative != relative) {
+                this.height = height;
+                this.relative = relative;
+
+                if (tablePane != null) {
+                    tablePane.tablePaneListeners.rowHeightChanged(this,
+                        previousHeight, previousRelative);
+                }
+            }
+        }
+
+        /**
+         * Returns the selected flag.
+         *
+         * @return
+         * <tt>true</tt> if the row is selected, <tt>false</tt> if it is not
+         */
+        public boolean isSelected() {
+            return selected;
+        }
+
+        /**
+         * Sets the selected flag.
+         *
+         * @param selected
+         * <tt>true</tt> to set the row as selected, <tt>false</tt> to set
+         * it as not selected
+         */
+        public void setSelected(boolean selected) {
+            if (selected != this.selected) {
+                this.selected = selected;
+
+                if (tablePane != null) {
+                    tablePane.tablePaneListeners.rowSelectedChanged(this);
+                }
+            }
+        }
+
+        public int add(Component component) {
+            int i = getLength();
+            insert(component, i);
+
+            return i;
+        }
+
+        public void insert(Component component, int index) {
+            if (index < 0
+                || index > cells.getLength()) {
+                throw new IndexOutOfBoundsException();
+            }
+
+            if (component != null
+                && tablePane != null) {
+                // Add the component to the table pane
+                tablePane.add(component);
+
+                // Attach the attributes
+                component.setAttributes(new TablePaneAttributes());
+            }
+
+            cells.insert(component, index);
+
+            if (tablePane != null) {
+                // Notify table pane listeners
+                tablePane.tablePaneListeners.cellInserted(this, index);
+            }
+        }
+
+        public Component update(int index, Component component) {
+            Component previousComponent = cells.get(index);
+
+            if (component != null
+                && tablePane != null) {
+                // Add the component to the table pane
+                tablePane.add(component);
+
+                // Attach the attributes
+                component.setAttributes(new TablePaneAttributes());
+            }
+
+            cells.update(index, component);
+
+            if (previousComponent != null
+                && tablePane != null) {
+                // Detach the attributes
+                component.setAttributes(null);
+            }
+
+            if (tablePane != null
+                && component != previousComponent) {
+                // Notify table pane listeners
+                tablePane.tablePaneListeners.cellUpdated(this, index,
+                    previousComponent);
+            }
+
+            if (previousComponent != null
+                && tablePane != null) {
+                // Remove the component from the table pane
+                tablePane.remove(component);
+            }
+
+            return previousComponent;
+        }
+
+        public int remove(Component component) {
+            int index = indexOf(component);
+            if (index != -1) {
+                remove(index, 1);
+            }
+
+            return index;
+        }
+
+        public Sequence<Component> remove(int index, int count) {
+            Sequence<Component> removed = cells.remove(index, count);
+
+            if (tablePane != null) {
+                for (int i = 0, n = removed.getLength(); i < n; i++) {
+                    Component component = removed.get(i);
+                    if (component != null) {
+                        component.setAttributes(null);
+                    }
+                }
+
+                // Notify table pane listeners
+                tablePane.tablePaneListeners.cellsRemoved(this, index, removed);
+
+                for (int i = 0, n = removed.getLength(); i < n; i++) {
+                    Component component = removed.get(i);
+                    if (component != null) {
+                        tablePane.remove(component);
+                    }
+                }
+            }
+
+            return removed;
+        }
+
+        public Component get(int index) {
+            return cells.get(index);
+        }
+
+        public int indexOf(Component component) {
+            return cells.indexOf(component);
+        }
+
+        public int getLength() {
+            return cells.getLength();
+        }
+    }
+
+    /**
+     * Represents a table pane column.
+     *
+     * @author tvolkert
+     */
+    public static class Column {
+        private TablePane tablePane = null;
+
+        private int width;
+        private boolean relative;
+        private boolean selected;
+
+        public Column() {
+            this(-1, false, false);
+        }
+
+        public Column(int width) {
+            this(width, false, false);
+        }
+
+        public Column(int width, boolean relative) {
+            this(width, relative, false);
+        }
+
+        public Column(int width, boolean relative, boolean selected) {
+            this.width = width;
+            this.relative = relative;
+            this.selected = selected;
+        }
+
+        /**
+         * Returns the table pane with which this column is associated.
+         *
+         * @return
+         * The column's table pane, or <tt>null</tt> if the column does not
+         * currently belong to a table.
+         */
+        public TablePane getTablePane() {
+            return tablePane;
+        }
+
+        /**
+         * Sets the table pane with which this column is associated.
+         *
+         * @param tablePane
+         * The column's table pane, or <tt>null</tt> if the column does not
+         * currently belong to a table.
+         */
+        private void setTablePane(TablePane tablePane) {
+            this.tablePane = tablePane;
+        }
+
+        /**
+         * Returns the column width.
+         *
+         * @return
+         * The width of the column.
+         */
+        public int getWidth() {
+            return width;
+        }
+
+        /**
+         * Returns the relative flag.
+         *
+         * @return
+         * <tt>true</tt> if the column width is relative, <tt>false</tt> if it
+         * is fixed.
+         */
+        public boolean isRelative() {
+            return relative;
+        }
+
+        /**
+         * Set the column width.
+         *
+         * @param width
+         * The absolute width of the column.
+         */
+        public void setWidth(int width) {
+            setWidth(width, false);
+        }
+
+        /**
+         * Set the column width.
+         *
+         * @param width
+         * The encoded width of the row. If the string ends with the '*'
+         * character, it is treated as a relative value. Otherwise, it is
+         * considered an absolute value.
+         */
+        public void setWidth(String width) {
+            boolean relative = false;
+
+            if (width.endsWith(RELATIVE_SIZE_INDICATOR)) {
+                relative = true;
+                width = width.substring(0, width.length() - 1);
+            }
+
+            setWidth(Integer.parseInt(width), relative);
+        }
+
+        /**
+         * Sets the column width.
+         *
+         * @param width
+         * The width of the column.
+         *
+         * @param relative
+         * <tt>true</tt> if the column width is relative, <tt>false</tt> if it
+         * is fixed.
+         */
+        public void setWidth(int width, boolean relative) {
+            int previousWidth = this.width;
+            boolean previousRelative = this.relative;
+
+            if (previousWidth != width
+                || previousRelative != relative) {
+                this.width = width;
+                this.relative = relative;
+
+                if (tablePane != null) {
+                    tablePane.tablePaneListeners.columnWidthChanged(this,
+                        previousWidth, previousRelative);
+                }
+            }
+        }
+
+        /**
+         * Returns the selected flag.
+         *
+         * @return
+         * <tt>true</tt> if the column is selected, <tt>false</tt> if it is not
+         */
+        public boolean isSelected() {
+            return selected;
+        }
+
+        /**
+         * Sets the selected flag.
+         *
+         * @param selected
+         * <tt>true</tt> to set the column as selected, <tt>false</tt> to set
+         * it as not selected
+         */
+        public void setSelected(boolean selected) {
+            if (selected != this.selected) {
+                this.selected = selected;
+
+                if (tablePane != null) {
+                    tablePane.tablePaneListeners.columnSelectedChanged(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * Defines table pane component attributes.
+     *
+     * @author gbrown
+     */
+    protected static class TablePaneAttributes extends Attributes {
+        private int rowSpan = 1;
+        private int columnSpan = 1;
+
+        public int getRowSpan() {
+            return rowSpan;
+        }
+
+        public void setRowSpan(int rowSpan) {
+            int previousRowSpan = this.rowSpan;
+            this.rowSpan = rowSpan;
+
+            Component component = getComponent();
+            TablePane tablePane = (TablePane)component.getParent();
+            if (tablePane != null) {
+                tablePane.tablePaneAttributeListeners.rowSpanChanged(tablePane,
+                    component, previousRowSpan);
+            }
+        }
+
+        public int getColumnSpan() {
+            return columnSpan;
+        }
+
+        public void setColumnSpan(int columnSpan) {
+            int previousColumnSpan = this.columnSpan;
+            this.columnSpan = columnSpan;
+
+            Component component = getComponent();
+            TablePane tablePane = (TablePane)component.getParent();
+            if (tablePane != null) {
+                tablePane.tablePaneAttributeListeners.columnSpanChanged(tablePane,
+                    component, previousColumnSpan);
+            }
+        }
+    }
+
+    /**
+     * Table pane skin interface. Table pane skins must implement
+     * this interface to facilitate additional communication between the
+     * component and the skin.
+     *
+     * @author tvolkert
+     */
+    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 table pane's row list. Callers get access to the
+     * row sequence via {@link TablePane#getRows()}.
+     *
+     * @author tvolkert
+     */
+    public final class RowSequence implements Sequence<Row> {
+        private RowSequence() {
+        }
+
+        public int add(Row row) {
+            int i = getLength();
+            insert(row, i);
+
+            return i;
+        }
+
+        public void insert(Row row, int index) {
+            if (row == null) {
+                throw new IllegalArgumentException("row is null.");
+            }
+
+            if (row.getTablePane() != null) {
+                throw new IllegalArgumentException
+                    ("row is already in use by another table pane.");
+            }
+
+            rows.insert(row, index);
+            row.setTablePane(TablePane.this);
+
+            for (int i = 0, n = row.getLength(); i < n; i++) {
+                Component component = row.get(i);
+
+                // Add each component in the row to the table pane
+                TablePane.this.add(component);
+
+                // Attach attributes to each row component
+                component.setAttributes(new TablePaneAttributes());
+            }
+
+            // Notify listeners
+            tablePaneListeners.rowInserted(TablePane.this, index);
+        }
+
+        public Row update(int index, Row row) {
+            throw new UnsupportedOperationException();
+        }
+
+        public int remove(Row row) {
+            int index = indexOf(row);
+            if (index != -1) {
+                remove(index, 1);
+            }
+
+            return index;
+        }
+
+        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.setTablePane(null);
+
+                    for (int j = 0, m = row.getLength(); j < m; j++) {
+                        Component component = row.get(j);
+
+                        if (component != null) {
+                            // Detach attributes from each row component
+                            component.setAttributes(null);
+
+                            // Remove each component in the row from the table pane
+                            TablePane.this.remove(component);
+                        }
+                    }
+                }
+
+                tablePaneListeners.rowsRemoved(TablePane.this, index, removed);
+            }
+
+            return removed;
+        }
+
+        public Row get(int index) {
+            return rows.get(index);
+        }
+
+        public int indexOf(Row row) {
+            return rows.indexOf(row);
+        }
+
+        public int getLength() {
+            return rows.getLength();
+        }
+    }
+
+    /**
+     * Class that manages a table pane's column list. Callers get access to the
+     * column sequence via {@link TablePane#getColumns()}.
+     *
+     * @author tvolkert
+     */
+    public final class ColumnSequence implements Sequence<Column> {
+        private ColumnSequence() {
+        }
+
+        public int add(Column column) {
+            int i = getLength();
+            insert(column, i);
+
+            return i;
+        }
+
+        public void insert(Column column, int index) {
+            if (column == null) {
+                throw new IllegalArgumentException("column is null.");
+            }
+
+            if (column.getTablePane() != null) {
+                throw new IllegalArgumentException
+                    ("column is already in use by another table pane.");
+            }
+
+            columns.insert(column, index);
+            column.setTablePane(TablePane.this);
+
+            // Notify listeners
+            tablePaneListeners.columnInserted(TablePane.this, index);
+        }
+
+        public Column update(int index, Column column) {
+            throw new UnsupportedOperationException();
+        }
+
+        public int remove(Column column) {
+            int index = indexOf(column);
+            if (index != -1) {
+                remove(index, 1);
+            }
+
+            return index;
+        }
+
+        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++) {
+                    removed.get(i).setTablePane(null);
+                }
+
+                tablePaneListeners.columnsRemoved(TablePane.this, index, removed);
+            }
+
+            return removed;
+        }
+
+        public Column get(int index) {
+            return columns.get(index);
+        }
+
+        public int indexOf(Column column) {
+            return columns.indexOf(column);
+        }
+
+        public int getLength() {
+            return columns.getLength();
+        }
+    }
+
+    /**
+     * Internal listener list.
+     */
+    private static class TablePaneListenerList extends ListenerList<TablePaneListener>
+        implements TablePaneListener {
+        public void rowInserted(TablePane tablePane, int index) {
+            for (TablePaneListener listener : this) {
+                listener.rowInserted(tablePane, index);
+            }
+        }
+
+        public void rowsRemoved(TablePane tablePane, int index,
+            Sequence<TablePane.Row> rows) {
+            for (TablePaneListener listener : this) {
+                listener.rowsRemoved(tablePane, index, rows);
+            }
+        }
+
+        public void rowHeightChanged(TablePane.Row row, int previousHeight,
+            boolean previousRelative) {
+            for (TablePaneListener listener : this) {
+                listener.rowHeightChanged(row, previousHeight, previousRelative);
+            }
+        }
+
+        public void rowSelectedChanged(TablePane.Row row) {
+            for (TablePaneListener listener : this) {
+                listener.rowSelectedChanged(row);
+            }
+        }
+
+        public void columnInserted(TablePane tablePane, int index) {
+            for (TablePaneListener listener : this) {
+                listener.columnInserted(tablePane, index);
+            }
+        }
+
+        public void columnsRemoved(TablePane tablePane, int index,
+            Sequence<TablePane.Column> columns) {
+            for (TablePaneListener listener : this) {
+                listener.columnsRemoved(tablePane, index, columns);
+            }
+        }
+
+        public void columnWidthChanged(TablePane.Column column, int previousWidth,
+            boolean previousRelative) {
+            for (TablePaneListener listener : this) {
+                listener.columnWidthChanged(column, previousWidth, previousRelative);
+            }
+        }
+
+        public void columnSelectedChanged(TablePane.Column column) {
+            for (TablePaneListener listener : this) {
+                listener.columnSelectedChanged(column);
+            }
+        }
+
+        public void cellInserted(TablePane.Row row, int column) {
+            for (TablePaneListener listener : this) {
+                listener.cellInserted(row, column);
+            }
+        }
+
+        public void cellsRemoved(TablePane.Row row, int column,
+            Sequence<Component> removed) {
+            for (TablePaneListener listener : this) {
+                listener.cellsRemoved(row, column, removed);
+            }
+        }
+
+        public void cellUpdated(TablePane.Row row, int column,
+            Component previousComponent) {
+            for (TablePaneListener listener : this) {
+                listener.cellUpdated(row, column, previousComponent);
+            }
+        }
+    }
+
+    private static class TablePaneAttributeListenerList extends ListenerList<TablePaneAttributeListener>
+        implements TablePaneAttributeListener {
+        public void rowSpanChanged(TablePane tablePane, Component component,
+            int previousRowSpan) {
+            for (TablePaneAttributeListener listener : this) {
+                listener.rowSpanChanged(tablePane, component, previousRowSpan);
+            }
+        }
+
+        public void columnSpanChanged(TablePane tablePane, Component component,
+            int previousColumnSpan) {
+            for (TablePaneAttributeListener listener : this) {
+                listener.columnSpanChanged(tablePane, component, previousColumnSpan);
+            }
+        }
+    }
+
+    private ArrayList<Row> rows = null;
+    private RowSequence rowSequence = new RowSequence();
+
+    private ArrayList<Column> columns = null;
+    private ColumnSequence columnSequence = new ColumnSequence();
+
+    private TablePaneListenerList tablePaneListeners = new TablePaneListenerList();
+    private TablePaneAttributeListenerList tablePaneAttributeListeners = new TablePaneAttributeListenerList();
+
+    public static final String RELATIVE_SIZE_INDICATOR = "*";
+
+    /**
+     * Creates a new <tt>TablePane</tt> with empty row and column sequences.
+     */
+    public TablePane() {
+        this(new ArrayList<Column>());
+    }
+
+    /**
+     * Creates a new <tt>TablePane</tt> with the specified columns.
+     *
+     * @param columns
+     * The column sequence to use. A copy of this sequence will be made
+     */
+    public TablePane(Sequence<Column> columns) {
+        if (columns == null) {
+            throw new IllegalArgumentException("columns is null");
+        }
+
+        this.rows = new ArrayList<Row>();
+        this.columns = new ArrayList<Column>(columns);
+
+        installSkin(TablePane.class);
+    }
+
+    @Override
+    protected void setSkin(pivot.wtk.Skin skin) {
+        if (!(skin instanceof TablePane.Skin)) {
+            throw new IllegalArgumentException("Skin class must implement "
+                + TablePane.Skin.class.getName());
+        }
+
+        super.setSkin(skin);
+    }
+
+    /**
+     * Returns the table pane row sequence.
+     *
+     * @return
+     * The table 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) {
+        TablePane.Skin tablePaneSkin = (TablePane.Skin)getSkin();
+        return tablePaneSkin.getRowAt(y);
+    }
+
+    /**
+     * Returns the table pane column sequence.
+     *
+     * @return
+     * The table 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) {
+        TablePane.Skin tablePaneSkin = (TablePane.Skin)getSkin();
+        return tablePaneSkin.getColumnAt(x);
+    }
+
+    /**
+     * Gets the component at the specified cell in this table 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 table 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);
+            if (component.getAttributes() != null) {
+                throw new UnsupportedOperationException();
+            }
+        }
+
+        // Call the base method to remove the components
+        return super.remove(index, count);
+    }
+
+    /**
+     * Gets this component's table pane listener list.
+     *
+     * @return
+     * The table pane listeners on this component
+     */
+    public ListenerList<TablePaneListener> getTablePaneListeners() {
+        return tablePaneListeners;
+    }
+
+    /**
+     * Gets this component's table pane attribute listener list.
+     *
+     * @return
+     * The table pane attribute listeners on this component
+     */
+    public ListenerList<TablePaneAttributeListener> getTablePaneAttributeListeners() {
+        return tablePaneAttributeListeners;
+    }
+
+    public static int getRowSpan(Component component) {
+        TablePaneAttributes tablePaneAttributes = (TablePaneAttributes)component.getAttributes();
+        return (tablePaneAttributes == null) ? -1 : tablePaneAttributes.getRowSpan();
+    }
+
+    public static void setRowSpan(Component component, int rowSpan) {
+        TablePaneAttributes tablePaneAttributes = (TablePaneAttributes)component.getAttributes();
+        if (tablePaneAttributes == null) {
+            throw new UnsupportedOperationException();
+        }
+
+        tablePaneAttributes.setRowSpan(rowSpan);
+    }
+
+    public static int getColumnSpan(Component component) {
+        TablePaneAttributes tablePaneAttributes = (TablePaneAttributes)component.getAttributes();
+        return (tablePaneAttributes == null) ? -1 : tablePaneAttributes.getColumnSpan();
+    }
+
+    public static void setColumnSpan(Component component, int columnSpan) {
+        TablePaneAttributes tablePaneAttributes = (TablePaneAttributes)component.getAttributes();
+        if (tablePaneAttributes == null) {
+            throw new UnsupportedOperationException();
+        }
+
+        tablePaneAttributes.setColumnSpan(columnSpan);
+    }
+}

Added: incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePaneAttributeListener.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePaneAttributeListener.java?rev=754936&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePaneAttributeListener.java (added)
+++ incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePaneAttributeListener.java Mon Mar 16 16:36:10 2009
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * 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 pivot.wtk;
+
+/**
+ * Table pane attribute listener interface.
+ *
+ * @author gbrown
+ */
+public interface TablePaneAttributeListener {
+    /**
+     * Called when a component's row span attribute has changed.
+     *
+     * @param tablePane
+     * @param component
+     * @param previousRowSpan
+     */
+    public void rowSpanChanged(TablePane tablePane, Component component,
+        int previousRowSpan);
+
+    /**
+     * Called when a component's column span attribute has changed.
+     *
+     * @param tablePane
+     * @param component
+     * @param previousColumnSpan
+     */
+    public void columnSpanChanged(TablePane tablePane, Component component,
+        int previousColumnSpan);
+}

Added: incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePaneListener.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePaneListener.java?rev=754936&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePaneListener.java (added)
+++ incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TablePaneListener.java Mon Mar 16 16:36:10 2009
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * 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 pivot.wtk;
+
+import pivot.collections.Sequence;
+
+/**
+ * Table pane listener interface.
+ *
+ * @author gbrown
+ */
+public interface TablePaneListener {
+    /**
+     * Called when a row has been inserted into a table pane.
+     *
+     * @param tablePane
+     * @param index
+     */
+    public void rowInserted(TablePane tablePane, int index);
+
+    /**
+     * Called when rows have been removed from a table pane.
+     *
+     * @param tablePane
+     * @param index
+     * @param rows
+     */
+    public void rowsRemoved(TablePane tablePane, int index,
+        Sequence<TablePane.Row> rows);
+
+    /**
+     * Called when a row's height has changed.
+     *
+     * @param row
+     * @param previousHeight
+     * @param previousRelative
+     */
+    public void rowHeightChanged(TablePane.Row row, int previousHeight,
+        boolean previousRelative);
+
+    /**
+     * Called when a row's selected state has changed.
+     *
+     * @param row
+     */
+    public void rowSelectedChanged(TablePane.Row row);
+
+    /**
+     * Called when a column has been inserted into a table pane.
+     *
+     * @param tablePane
+     * @param index
+     */
+    public void columnInserted(TablePane tablePane, int index);
+
+    /**
+     * Called when column's have been removed from a table pane.
+     *
+     * @param tablePane
+     * @param index
+     * @param columns
+     */
+    public void columnsRemoved(TablePane tablePane, int index,
+        Sequence<TablePane.Column> columns);
+
+    /**
+     * Called when a column's width has changed.
+     *
+     * @param column
+     * @param previousWidth
+     * @param previousRelative
+     */
+    public void columnWidthChanged(TablePane.Column column, int previousWidth,
+        boolean previousRelative);
+
+    /**
+     * Called when a column's selected state has changed.
+     *
+     * @param column
+     */
+    public void columnSelectedChanged(TablePane.Column column);
+
+    /**
+     * Called when a cell has been inserted into a table pane.
+     *
+     * @param row
+     * @param column
+     */
+    public void cellInserted(TablePane.Row row, int column);
+
+    /**
+     * Called when cell's have been removed from a table pane.
+     *
+     * @param row
+     * @param column
+     * @param removed
+     */
+    public void cellsRemoved(TablePane.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(TablePane.Row row, int column,
+        Component previousComponent);
+}

Added: incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableView.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableView.java?rev=754936&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableView.java (added)
+++ incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableView.java Mon Mar 16 16:36:10 2009
@@ -0,0 +1,1422 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * 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 pivot.wtk;
+
+import java.util.Comparator;
+
+import pivot.collections.ArrayList;
+import pivot.collections.Dictionary;
+import pivot.collections.List;
+import pivot.collections.ListListener;
+import pivot.collections.Sequence;
+import pivot.serialization.JSONSerializer;
+import pivot.util.ListenerList;
+import pivot.util.Vote;
+import pivot.wtk.content.TableViewCellRenderer;
+import pivot.wtk.content.TableViewHeaderData;
+
+/**
+ * Component that displays a sequence of items partitioned into columns,
+ * optionally allowing a user to select one or more rows.
+ *
+ * @author gbrown
+ */
+@ComponentInfo(icon="TableView.png")
+public class TableView extends Component {
+    /**
+     * Contains information about a table column.
+     *
+     * @author gbrown
+     */
+    public static class Column {
+        private TableView tableView = null;
+
+        private String name = null;
+        private Object headerData = null;
+        private int width = 0;
+        private boolean relative = false;
+        private SortDirection sortDirection = null;
+        private Object filter = null;
+        private CellRenderer cellRenderer = DEFAULT_CELL_RENDERER;
+
+        private static final CellRenderer DEFAULT_CELL_RENDERER = new TableViewCellRenderer();
+
+        /**
+         * Default column width.
+         */
+        public static final int DEFAULT_WIDTH = 100;
+
+        /**
+         * Creates an empty column.
+         */
+        public Column() {
+            this(null, null, DEFAULT_WIDTH, false);
+        }
+
+        /**
+         * Creates a new column with no header data and a fixed default width.
+         *
+         * @param name
+         * The column name.
+         */
+        public Column(String name) {
+            this(name, null, DEFAULT_WIDTH, false);
+        }
+
+        /**
+         * Creates a new column with a fixed default width.
+         *
+         * @param name
+         * The column name.
+         *
+         * @param headerData
+         * The column header data.
+         */
+        public Column(String name, Object headerData) {
+            this(name, headerData, DEFAULT_WIDTH, false);
+        }
+
+        /**
+         * Creates a new column with a fixed width.
+         *
+         * @param name
+         * The column name.
+         *
+         * @param headerData
+         * The column header data.
+         *
+         * @param width
+         * The width of the column.
+         */
+        public Column(String name, Object headerData, int width) {
+            this(name, headerData, width, false);
+        }
+
+        /**
+         * Creates a new column.
+         *
+         * @param name
+         * The column name.
+         *
+         * @param headerData
+         * The column header data.
+         *
+         * @param width
+         * The width of the column.
+         *
+         * @param relative
+         * If <tt>true</tt>, specifies a relative column width; otherwise,
+         * specifies a fixed column width.
+         */
+        public Column(String name, Object headerData, int width, boolean relative) {
+            if (width < 0) {
+                throw new IllegalArgumentException("width is negative.");
+            }
+
+            this.name = name;
+            this.headerData = headerData;
+            this.width = width;
+            this.relative = relative;
+        }
+
+        /**
+         * Returns the table view with which this column is associated.
+         *
+         * @return
+         * The column's table view, or <tt>null</tt> if the column does not
+         * currently belong to a table.
+         */
+        public TableView getTableView() {
+            return tableView;
+        }
+
+        /**
+         * Sets the table view with which this column is associated.
+         *
+         * @param tableView
+         * The column's table view, or <tt>null</tt> if the column does not
+         * currently belong to a table.
+         */
+        private void setTableView(TableView tableView) {
+            this.tableView = tableView;
+        }
+
+        /**
+         * Returns the column name.
+         *
+         * @return
+         * The column name.
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Sets the column name.
+         *
+         * @param name
+         * The column name.
+         */
+        public void setName(String name) {
+            if (name == null) {
+                throw new IllegalArgumentException("name is null.");
+            }
+
+            String previousName = this.name;
+
+            if (previousName != name) {
+                this.name = name;
+
+                if (tableView != null) {
+                    tableView.tableViewColumnListeners.columnNameChanged(this,
+                        previousName);
+                }
+            }
+        }
+
+        /**
+         * Returns the column header data.
+         *
+         * @return
+         * The column header data, or <tt>null</tt> if the column has no
+         * header data.
+         */
+        public Object getHeaderData() {
+            return headerData;
+        }
+
+        /**
+         * Sets the column header data.
+         *
+         * @param headerData
+         * The column header data, or <tt>null</tt> for no header data.
+         */
+        public void setHeaderData(Object headerData) {
+            Object previousHeaderData = this.headerData;
+
+            if (previousHeaderData != headerData) {
+                this.headerData = headerData;
+
+                if (tableView != null) {
+                    tableView.tableViewColumnListeners.columnHeaderDataChanged(this,
+                        previousHeaderData);
+                }
+            }
+        }
+
+        /**
+         * Returns the column width.
+         *
+         * @return
+         * The width of the column.
+         */
+        public int getWidth() {
+            return width;
+        }
+
+        /**
+         * Returns the relative flag.
+         *
+         * @return
+         * <tt>true</tt> if the column width is relative, <tt>false</tt> if it
+         * is fixed.
+         */
+        public boolean isRelative() {
+            return relative;
+        }
+
+        /**
+         * Set the column width.
+         *
+         * @param width
+         * The absolute width of the column.
+         */
+        public void setWidth(int width) {
+            setWidth(width, false);
+        }
+
+        /**
+         * Set the column width.
+         *
+         * @param width
+         * The encoded width of the row. If the string ends with the '*'
+         * character, it is treated as a relative value. Otherwise, it is
+         * considered an absolute value.
+         */
+        public void setWidth(String width) {
+            boolean relative = false;
+
+            if (width.endsWith("*")) {
+                relative = true;
+                width = width.substring(0, width.length() - 1);
+            }
+
+            setWidth(Integer.parseInt(width), relative);
+        }
+
+        /**
+         * Sets the column width.
+         *
+         * @param width
+         * The width of the column.
+         *
+         * @param relative
+         * <tt>true</tt> if the column width is relative, <tt>false</tt> if it
+         * is fixed.
+         */
+        public void setWidth(int width, boolean relative) {
+            int previousWidth = this.width;
+            boolean previousRelative = this.relative;
+
+            if (previousWidth != width
+                || previousRelative != relative) {
+                this.width = width;
+                this.relative = relative;
+
+                if (tableView != null) {
+                    tableView.tableViewColumnListeners.columnWidthChanged(this,
+                        previousWidth, previousRelative);
+                }
+            }
+        }
+
+        /**
+         * Returns the column's sort direction.
+         *
+         * @return
+         * The column's sort direction, or <tt>null</tt> if the column is not
+         * sorted.
+         */
+        public SortDirection getSortDirection() {
+            return sortDirection;
+        }
+
+        /**
+         * Sets the column's sort direction.
+         *
+         * @param sortDirection
+         * The column's sort direction, or <tt>null</tt> to specify no
+         * sort direction
+         */
+        public void setSortDirection(SortDirection sortDirection) {
+            SortDirection previousSortDirection = this.sortDirection;
+
+            if (previousSortDirection != sortDirection) {
+                this.sortDirection = sortDirection;
+
+                if (tableView != null) {
+                    tableView.tableViewColumnListeners.columnSortDirectionChanged(this,
+                        previousSortDirection);
+                }
+            }
+        }
+
+        /**
+         * Sets the column's sort direction.
+         *
+         * @param sortDirection
+         * The column's sort direction, or <tt>null</tt> to specify no
+         * sort direction
+         */
+        public final void setSortDirection(String sortDirection) {
+            setSortDirection(sortDirection == null ? (SortDirection)null :
+                SortDirection.decode(sortDirection));
+        }
+
+        /**
+         * Returns the column's filter.
+         *
+         * @return
+         * The column's filter, or <tt>null</tt> if the column does not have
+         * a filter.
+         */
+        public Object getFilter() {
+            return filter;
+        }
+
+        /**
+         * Sets the column's filter.
+         *
+         * @param filter
+         * The column's filter, or <tt>null</tt> for no filter.
+         */
+        public void setFilter(Object filter) {
+            Object previousFilter = this.filter;
+
+            if (previousFilter != filter) {
+                this.filter = filter;
+
+                if (tableView != null) {
+                    tableView.tableViewColumnListeners.columnFilterChanged(this,
+                        previousFilter);
+                }
+            }
+        }
+
+        /**
+         * Returns the column's cell renderer.
+         *
+         * @return
+         * The cell renderer that is used to draw the contents of this column.
+         */
+        public CellRenderer getCellRenderer() {
+            return cellRenderer;
+        }
+
+        /**
+         * Sets the column's cell renderer.
+         *
+         * @param cellRenderer
+         * The cell renderer that is used to draw the contents of this column.
+         */
+        public void setCellRenderer(CellRenderer cellRenderer) {
+            if (cellRenderer == null) {
+                throw new IllegalArgumentException("cellRenderer is null.");
+            }
+
+            CellRenderer previousCellRenderer = this.cellRenderer;
+
+            if (previousCellRenderer != cellRenderer) {
+                this.cellRenderer = cellRenderer;
+
+                if (tableView != null) {
+                    tableView.tableViewColumnListeners.columnCellRendererChanged(this,
+                        previousCellRenderer);
+                }
+            }
+        }
+    }
+
+    /**
+     * Enumeration defining supported selection modes.
+     */
+    public enum SelectMode {
+        /**
+         * Selection is disabled.
+         */
+        NONE,
+
+        /**
+         * A single index may be selected at a time.
+         */
+        SINGLE,
+
+        /**
+         * Multiple indexes may be concurrently selected.
+         */
+        MULTI;
+
+        public static SelectMode decode(String value) {
+            return valueOf(value.toUpperCase());
+        }
+    }
+
+    /**
+     * Table cell renderer interface.
+     *
+     * @author gbrown
+     */
+    public interface CellRenderer extends Renderer {
+        public void render(Object value, TableView tableView, TableView.Column column,
+            boolean rowSelected, boolean rowHighlighted, boolean rowDisabled);
+    }
+
+    /**
+     * Table view skin interface. Table view skins must implement this.
+     *
+     * @author gbrown
+     */
+    public interface Skin {
+        public int getRowAt(int y);
+        public int getColumnAt(int x);
+        public Bounds getRowBounds(int rowIndex);
+        public Bounds getColumnBounds(int columnIndex);
+        public Bounds getCellBounds(int rowIndex, int columnIndex);
+    }
+
+    /**
+     * Compares two rows. The dictionary values must implement
+     * {@link Comparable}.
+     * <p>
+     * TODO Allow a caller to sort on multiple columns.
+     */
+    public static class RowComparator implements Comparator<Dictionary<String, ?>> {
+        private String columnName = null;
+        private SortDirection sortDirection = null;
+
+        public RowComparator(String columnName, SortDirection sortDirection) {
+            this.columnName = columnName;
+            this.sortDirection = sortDirection;
+        }
+
+        @SuppressWarnings("unchecked")
+        public int compare(Dictionary<String, ?> row1, Dictionary<String, ?> row2) {
+            Comparable<Object> comparable = (Comparable<Object>)row1.get(columnName);
+            Object value = row2.get(columnName);
+
+            return (comparable.compareTo(value)) * (sortDirection == SortDirection.ASCENDING ? 1 : -1);
+        }
+    }
+
+    /**
+     * Default sort handler class. Sorts rows using {@link RowComparator}.
+     */
+    public static class SortHandler implements TableViewHeaderPressListener {
+        @SuppressWarnings("unchecked")
+        public void headerPressed(TableViewHeader tableViewHeader, int index) {
+            TableView tableView = tableViewHeader.getTableView();
+            TableView.ColumnSequence columns = tableView.getColumns();
+            TableView.Column column = columns.get(index);
+
+            Object headerData = column.getHeaderData();
+            if (!(headerData instanceof TableViewHeaderData)) {
+                headerData = new TableViewHeaderData((String)headerData);
+                column.setHeaderData(headerData);
+            }
+
+            SortDirection sortDirection = column.getSortDirection();
+
+            if (sortDirection == null
+                || sortDirection == SortDirection.DESCENDING) {
+                sortDirection = SortDirection.ASCENDING;
+            } else {
+                sortDirection = SortDirection.DESCENDING;
+            }
+
+            List<Dictionary<String, ?>> tableData =
+                (List<Dictionary<String, ?>>)tableView.getTableData();
+            tableData.setComparator(new TableView.RowComparator(column.getName(), sortDirection));
+
+            for (int i = 0, n = columns.getLength(); i < n; i++) {
+                column = columns.get(i);
+                column.setSortDirection(i == index ? sortDirection : null);
+            }
+        }
+    }
+
+    /**
+     * Column sequence implementation.
+     *
+     * @author gbrown
+     */
+    public final class ColumnSequence implements Sequence<Column> {
+        public int add(Column column) {
+            int i = getLength();
+            insert(column, i);
+
+            return i;
+        }
+
+        public void insert(Column column, int index) {
+            if (column == null) {
+                throw new IllegalArgumentException("column is null.");
+            }
+
+            if (column.getTableView() != null) {
+                throw new IllegalArgumentException("column is already in use by another table view.");
+            }
+
+            columns.insert(column, index);
+            column.setTableView(TableView.this);
+
+            tableViewColumnListeners.columnInserted(TableView.this, index);
+        }
+
+        public Column update(int index, Column column) {
+            throw new UnsupportedOperationException();
+        }
+
+        public int remove(Column column) {
+            int index = indexOf(column);
+            if (index != -1) {
+                remove(index, 1);
+            }
+
+            return index;
+        }
+
+        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++) {
+                    removed.get(i).setTableView(null);
+                }
+
+                tableViewColumnListeners.columnsRemoved(TableView.this, index, removed);
+            }
+
+            return removed;
+        }
+
+        public Column get(int index) {
+            return columns.get(index);
+        }
+
+        public int indexOf(Column column) {
+            return columns.indexOf(column);
+        }
+
+        public int getLength() {
+            return columns.getLength();
+        }
+    }
+
+    /**
+     * List event handler.
+     *
+     * @author gbrown
+     */
+    private class ListHandler implements ListListener<Object> {
+        public void itemInserted(List<Object> list, int index) {
+            int m = selectedRanges.insertIndex(index);
+
+            // Notify listeners that items were inserted
+            tableViewRowListeners.rowInserted(TableView.this, index);
+
+            // If any spans were modified, notify listeners of selection change
+            if (m > 0) {
+                tableViewSelectionListeners.selectionChanged(TableView.this);
+            }
+        }
+
+        public void itemsRemoved(List<Object> list, int index, Sequence<Object> items) {
+            if (items == null) {
+                // All items were removed; clear the selection and notify
+                // listeners
+                selectedRanges.clear();
+                tableViewRowListeners.rowsRemoved(TableView.this, index, -1);
+                tableViewSelectionListeners.selectionChanged(TableView.this);
+            } else {
+                int count = items.getLength();
+
+                int s = selectedRanges.getLength();
+                int m = selectedRanges.removeIndexes(index, count);
+
+                // Notify listeners that items were removed
+                tableViewRowListeners.rowsRemoved(TableView.this, index, count);
+
+                // If any selection values were removed or any spans were modified,
+                // notify listeners of selection change
+                if (s != selectedRanges.getLength()
+                    || m > 0) {
+                    tableViewSelectionListeners.selectionChanged(TableView.this);
+                }
+            }
+        }
+
+        public void itemUpdated(List<Object> list, int index, Object previousItem) {
+            tableViewRowListeners.rowUpdated(TableView.this, index);
+        }
+
+        public void comparatorChanged(List<Object> list,
+            Comparator<Object> previousComparator) {
+            if (list.getComparator() != null) {
+                int s = selectedRanges.getLength();
+                selectedRanges.clear();
+
+                tableViewRowListeners.rowsSorted(TableView.this);
+
+                if (s > 0) {
+                    tableViewSelectionListeners.selectionChanged(TableView.this);
+                }
+            }
+        }
+    }
+
+    /**
+     * Table view listener list.
+     *
+     * @author gbrown
+     */
+    private static class TableViewListenerList extends ListenerList<TableViewListener>
+        implements TableViewListener {
+        public void tableDataChanged(TableView tableView, List<?> previousTableData) {
+            for (TableViewListener listener : this) {
+                listener.tableDataChanged(tableView, previousTableData);
+            }
+        }
+
+        public void selectModeChanged(TableView tableView, SelectMode previousSelectMode) {
+            for (TableViewListener listener : this) {
+                listener.selectModeChanged(tableView, previousSelectMode);
+            }
+        }
+    }
+
+    /**
+     * Table view column listener list.
+     *
+     * @author gbrown
+     */
+    private static class TableViewColumnListenerList extends ListenerList<TableViewColumnListener>
+        implements TableViewColumnListener {
+        public void columnInserted(TableView tableView, int index) {
+            for (TableViewColumnListener listener : this) {
+                listener.columnInserted(tableView, index);
+            }
+        }
+
+        public void columnsRemoved(TableView tableView, int index, Sequence<TableView.Column> columns) {
+            for (TableViewColumnListener listener : this) {
+                listener.columnsRemoved(tableView, index, columns);
+            }
+        }
+
+        public void columnNameChanged(Column column, String previousName) {
+            for (TableViewColumnListener listener : this) {
+                listener.columnNameChanged(column, previousName);
+            }
+        }
+
+        public void columnHeaderDataChanged(Column column, Object previousHeaderData) {
+            for (TableViewColumnListener listener : this) {
+                listener.columnHeaderDataChanged(column, previousHeaderData);
+            }
+        }
+
+        public void columnWidthChanged(Column column, int previousWidth, boolean previousRelative) {
+            for (TableViewColumnListener listener : this) {
+                listener.columnWidthChanged(column, previousWidth, previousRelative);
+            }
+        }
+
+        public void columnSortDirectionChanged(Column column, SortDirection previousSortDirection) {
+            for (TableViewColumnListener listener : this) {
+                listener.columnSortDirectionChanged(column, previousSortDirection);
+            }
+        }
+
+        public void columnFilterChanged(Column column, Object previousFilter) {
+            for (TableViewColumnListener listener : this) {
+                listener.columnFilterChanged(column, previousFilter);
+            }
+        }
+
+        public void columnCellRendererChanged(Column column, TableView.CellRenderer previousCellRenderer) {
+            for (TableViewColumnListener listener : this) {
+                listener.columnCellRendererChanged(column, previousCellRenderer);
+            }
+        }
+    }
+
+    /**
+     * Table view item listener list.
+     *
+     * @author gbrown
+     */
+    private static class TableViewRowListenerList extends ListenerList<TableViewRowListener>
+        implements TableViewRowListener {
+        public void rowInserted(TableView tableView, int index) {
+            for (TableViewRowListener listener : this) {
+                listener.rowInserted(tableView, index);
+            }
+        }
+
+        public void rowsRemoved(TableView tableView, int index, int count) {
+            for (TableViewRowListener listener : this) {
+                listener.rowsRemoved(tableView, index, count);
+            }
+        }
+
+        public void rowUpdated(TableView tableView, int index) {
+            for (TableViewRowListener listener : this) {
+                listener.rowUpdated(tableView, index);
+            }
+        }
+
+        public void rowsSorted(TableView tableView) {
+            for (TableViewRowListener listener : this) {
+                listener.rowsSorted(tableView);
+            }
+        }
+    }
+
+
+    /**
+     * List view item state listener list.
+     *
+     * @author gbrown
+     */
+    private static class TableViewRowStateListenerList extends ListenerList<TableViewRowStateListener>
+        implements TableViewRowStateListener {
+        public Vote previewRowDisabledChange(TableView tableView, int index) {
+            Vote vote = Vote.APPROVE;
+
+            for (TableViewRowStateListener listener : this) {
+                vote = vote.tally(listener.previewRowDisabledChange(tableView, index));
+            }
+
+            return vote;
+        }
+
+        public void rowDisabledChangeVetoed(TableView tableView, int index, Vote reason) {
+            for (TableViewRowStateListener listener : this) {
+                listener.rowDisabledChangeVetoed(tableView, index, reason);
+            }
+        }
+
+        public void rowDisabledChanged(TableView tableView, int index) {
+            for (TableViewRowStateListener listener : this) {
+                listener.rowDisabledChanged(tableView, index);
+            }
+        }
+    }
+
+    /**
+     * Table view selection listener list.
+     *
+     * @author gbrown
+     */
+    private static class TableViewSelectionListenerList extends ListenerList<TableViewSelectionListener>
+        implements TableViewSelectionListener {
+        public void selectionChanged(TableView tableView) {
+            for (TableViewSelectionListener listener : this) {
+                listener.selectionChanged(tableView);
+            }
+        }
+    }
+
+    /**
+     * Table view selection detail listener list.
+     *
+     * @author gbrown
+     */
+    private static class TableViewSelectionDetailListenerList extends ListenerList<TableViewSelectionDetailListener>
+        implements TableViewSelectionDetailListener {
+        public void selectedRangeAdded(TableView tableView, int rangeStart, int rangeEnd) {
+            for (TableViewSelectionDetailListener listener : this) {
+                listener.selectedRangeAdded(tableView, rangeStart, rangeEnd);
+            }
+        }
+
+        public void selectedRangeRemoved(TableView tableView, int rangeStart, int rangeEnd) {
+            for (TableViewSelectionDetailListener listener : this) {
+                listener.selectedRangeRemoved(tableView, rangeStart, rangeEnd);
+            }
+        }
+
+        public void selectionReset(TableView tableView, Sequence<Span> previousSelection) {
+            for (TableViewSelectionDetailListener listener : this) {
+                listener.selectionReset(tableView, previousSelection);
+            }
+        }
+    }
+
+    private ArrayList<Column> columns = new ArrayList<Column>();
+    private ColumnSequence columnSequence = new ColumnSequence();
+
+    private List<?> tableData = null;
+    private ListHandler tableDataHandler = new ListHandler();
+
+    private SpanSequence selectedRanges = new SpanSequence();
+    private SelectMode selectMode = SelectMode.SINGLE;
+
+    private ArrayList<Integer> disabledIndexes = new ArrayList<Integer>();
+
+    private TableViewListenerList tableViewListeners = new TableViewListenerList();
+    private TableViewColumnListenerList tableViewColumnListeners =
+        new TableViewColumnListenerList();
+    private TableViewRowListenerList tableViewRowListeners = new TableViewRowListenerList();
+    private TableViewRowStateListenerList tableViewRowStateListeners =
+        new TableViewRowStateListenerList();
+    private TableViewSelectionListenerList tableViewSelectionListeners
+        = new TableViewSelectionListenerList();
+    private TableViewSelectionDetailListenerList tableViewSelectionDetailListeners
+        = new TableViewSelectionDetailListenerList();
+
+    /**
+     * Creates a new table view populated with an empty array list.
+     */
+    public TableView() {
+        this(new ArrayList<Object>());
+    }
+
+    /**
+     * Creates a new table view populated with the given table data.
+     *
+     * @param tableData
+     */
+    public TableView(List<?> tableData) {
+        setTableData(tableData);
+        installSkin(TableView.class);
+    }
+
+    @Override
+    protected void setSkin(pivot.wtk.Skin skin) {
+        if (!(skin instanceof TableView.Skin)) {
+            throw new IllegalArgumentException("Skin class must implement "
+                + TableView.Skin.class.getName());
+        }
+
+        super.setSkin(skin);
+    }
+
+    /**
+     * Returns the table column sequence.
+     *
+     * @return
+     * The table column sequence.
+     */
+    public ColumnSequence getColumns() {
+        return columnSequence;
+    }
+
+    /**
+     * Returns the table data.
+     *
+     * @return
+     * The data currently presented by the table view.
+     */
+    public List<?> getTableData() {
+        return this.tableData;
+    }
+
+    /**
+     * Sets the table data. Clears any existing selection state.
+     *
+     * @param tableData
+     * The data to be presented by the table.
+     */
+    @SuppressWarnings("unchecked")
+    public void setTableData(List<?> tableData) {
+        if (tableData == null) {
+            throw new IllegalArgumentException("tableData is null.");
+        }
+
+        List<?> previousTableData = this.tableData;
+
+        if (previousTableData != tableData) {
+            if (previousTableData != null) {
+                // Clear any existing selection
+                clearSelection();
+
+                ((List<Object>)previousTableData).getListListeners().remove(tableDataHandler);
+            }
+
+            ((List<Object>)tableData).getListListeners().add(tableDataHandler);
+
+            // Update the list data and fire change event
+            this.tableData = tableData;
+            tableViewListeners.tableDataChanged(this, previousTableData);
+        }
+    }
+
+    /**
+     * Sets the table data. Clears any existing selection state.
+     *
+     * @param tableData
+     * A JSON string (must begin with <tt>[</tt> and end with <tt>]</tt>)
+     * denoting the data to be presented by this table.
+     */
+    public void setTableData(String tableData) {
+        if (tableData == null) {
+            throw new IllegalArgumentException("tableData is null.");
+        }
+
+        setTableData(JSONSerializer.parseList(tableData));
+    }
+
+    /**
+     * When in single-select mode, returns the currently selected index.
+     *
+     * @return
+     * The currently selected index.
+     */
+    public int getSelectedIndex() {
+        if (selectMode != SelectMode.SINGLE) {
+            throw new IllegalStateException("Table view is not in single-select mode.");
+        }
+
+        return (selectedRanges.getLength() == 0) ? -1 : selectedRanges.get(0).getStart();
+    }
+
+    /**
+     * Sets the selection to a single index.
+     *
+     * @param index
+     * The index to select, or <tt>-1</tt> to clear the selection.
+     */
+    public void setSelectedIndex(int index) {
+        ArrayList<Span> selectedRanges = new ArrayList<Span>();
+
+        if (index >= 0) {
+            selectedRanges.add(new Span(index, index));
+        }
+
+        setSelectedRanges(selectedRanges);
+    }
+
+    /**
+     * Returns the table's current selection.
+     */
+    public Sequence<Span> getSelectedRanges() {
+        // Return a copy of the selection list (including copies of the
+        // list contents)
+        ArrayList<Span> selectedRanges = new ArrayList<Span>();
+
+        for (int i = 0, n = this.selectedRanges.getLength(); i < n; i++) {
+            selectedRanges.add(new Span(this.selectedRanges.get(i)));
+        }
+
+        return selectedRanges;
+    }
+
+    /**
+     * Sets the selection to the given span sequence. Any overlapping or
+     * connecting spans will be consolidated, and the resulting selection will
+     * be sorted in ascending order.
+     *
+     * @param selectedRanges
+     * The new selection
+     */
+    public void setSelectedRanges(Sequence<Span> selectedRanges) {
+        if (selectedRanges == null) {
+            throw new IllegalArgumentException("selectedRanges is null.");
+        }
+
+        if (selectMode == SelectMode.NONE) {
+            throw new IllegalArgumentException("Selection is not enabled.");
+        }
+
+        if (selectMode == SelectMode.SINGLE) {
+            int n = selectedRanges.getLength();
+
+            if (n > 1) {
+                throw new IllegalArgumentException("Selection length is greater than 1.");
+            } else {
+                if (n > 0) {
+                    Span selectedRange = selectedRanges.get(0);
+
+                    if (selectedRange.getLength() > 1) {
+                        throw new IllegalArgumentException("Selected range length is greater than 1.");
+                    }
+                }
+            }
+        }
+
+        // Update the selection
+        SpanSequence ranges = new SpanSequence();
+
+        for (int i = 0, n = selectedRanges.getLength(); i < n; i++) {
+            Span range = selectedRanges.get(i);
+
+            if (range == null) {
+                throw new IllegalArgumentException("range is null.");
+            }
+
+            if (range.getStart() < 0 || range.getEnd() >= tableData.getLength()) {
+                throw new IndexOutOfBoundsException();
+            }
+
+            ranges.add(range);
+        }
+
+        SpanSequence previousSelectedRanges = this.selectedRanges;
+        this.selectedRanges = ranges;
+
+        // Notify listeners
+        tableViewSelectionDetailListeners.selectionReset(this, previousSelectedRanges);
+        tableViewSelectionListeners.selectionChanged(this);
+    }
+
+    /**
+     * Returns the first selected index.
+     *
+     * @return
+     * The first selected index, or <tt>-1</tt> if nothing is selected.
+     */
+    public int getFirstSelectedIndex() {
+        return (selectedRanges.getLength() > 0) ?
+            selectedRanges.get(0).getStart() : -1;
+    }
+
+    /**
+     * Returns the last selected index.
+     *
+     * @return
+     * The last selected index, or <tt>-1</tt> if nothing is selected.
+     */
+    public int getLastSelectedIndex() {
+        return (selectedRanges.getLength() > 0) ?
+            selectedRanges.get(selectedRanges.getLength() - 1).getEnd() : -1;
+    }
+
+    /**
+     * Adds a single index to the selection.
+     *
+     * @param index
+     * The index to add.
+     */
+    public void addSelectedIndex(int index) {
+        addSelectedRange(index, index);
+    }
+
+    /**
+     * Adds a range of indexes to the selection.
+     *
+     * @param rangeStart
+     * The first index in the range.
+     *
+     * @param rangeEnd
+     * The last index in the range.
+     */
+    public void addSelectedRange(int rangeStart, int rangeEnd) {
+        addSelectedRange(new Span(rangeStart, rangeEnd));
+    }
+
+    /**
+     * Adds a range of indexes to the selection.
+     *
+     * @param range
+     * The range to add.
+     */
+    public void addSelectedRange(Span range) {
+        if (selectMode != SelectMode.MULTI) {
+            throw new IllegalStateException("Table view is not in multi-select mode.");
+        }
+
+        if (range == null) {
+            throw new IllegalArgumentException("range is null.");
+        }
+
+        if (range.getStart() < 0 || range.getEnd() >= tableData.getLength()) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        selectedRanges.add(range);
+
+        tableViewSelectionDetailListeners.selectedRangeAdded(this,
+            range.getStart(), range.getEnd());
+        tableViewSelectionListeners.selectionChanged(this);
+    }
+
+    /**
+     * Removes a single index from the selection.
+     *
+     * @param index
+     * The index to remove.
+     */
+    public void removeSelectedIndex(int index) {
+        removeSelectedRange(index, index);
+    }
+
+    /**
+     * Removes a range of indexes from the selection.
+     *
+     * @param rangeStart
+     * The start of the range to remove.
+     *
+     * @param rangeEnd
+     * The end of the range to remove.
+     */
+    public void removeSelectedRange(int rangeStart, int rangeEnd) {
+        removeSelectedRange(new Span(rangeStart, rangeEnd));
+    }
+
+    /**
+     * Removes a range of indexes from the selection.
+     *
+     * @param range
+     * The range to remove.
+     */
+    public void removeSelectedRange(Span range) {
+        if (selectMode != SelectMode.MULTI) {
+            throw new IllegalStateException("Table view is not in multi-select mode.");
+        }
+
+        if (range == null) {
+            throw new IllegalArgumentException("range is null.");
+        }
+
+        if (range.getStart() < 0 || range.getEnd() >= tableData.getLength()) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        selectedRanges.remove(range);
+
+        tableViewSelectionDetailListeners.selectedRangeRemoved(this,
+            range.getStart(), range.getEnd());
+        tableViewSelectionListeners.selectionChanged(this);
+    }
+
+    /**
+     * Clears the selection.
+     */
+    public void clearSelection() {
+        if (selectedRanges.getLength() > 0) {
+            SpanSequence previousSelectedSpans = this.selectedRanges;
+            selectedRanges = new SpanSequence();
+
+            tableViewSelectionDetailListeners.selectionReset(this, previousSelectedSpans);
+            tableViewSelectionListeners.selectionChanged(this);
+        }
+    }
+
+    /**
+     * Returns the selection state of a given index.
+     *
+     * @param index
+     * The index whose selection state is to be tested.
+     *
+     * @return <tt>true</tt> if the index is selected; <tt>false</tt>,
+     * otherwise.
+     */
+    public boolean isIndexSelected(int index) {
+        return isRangeSelected(index, index);
+    }
+
+    /**
+     * Returns the selection state of a given range.
+     *
+     * @param rangeStart
+     * The first index in the range.
+     *
+     * @param rangeEnd
+     * The last index in the range.
+     *
+     * @return <tt>true</tt> if the entire range is selected; <tt>false</tt>,
+     * otherwise.
+     */
+    public boolean isRangeSelected(int rangeStart, int rangeEnd) {
+        return isRangeSelected(new Span(rangeStart, rangeEnd));
+    }
+
+    /**
+     * Returns the selection state of a given range.
+     *
+     * @param range
+     * The range whose selection state is to be tested.
+     *
+     * @return <tt>true</tt> if the entire range is selected; <tt>false</tt>,
+     * otherwise.
+     */
+    public boolean isRangeSelected(Span range) {
+        boolean selected = false;
+
+        if (range == null) {
+            throw new IllegalArgumentException("range is null.");
+        }
+
+        if (range.getStart() < 0 || range.getEnd() >= tableData.getLength()) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        // Locate the span in the selection
+        int i = selectedRanges.indexOf(range);
+
+        // If the selected span contains the given span, it is considered
+        // selected
+        if (i >= 0) {
+            Span selectedSpan = selectedRanges.get(i);
+            selected = selectedSpan.contains(range);
+        }
+
+        return selected;
+    }
+
+    /**
+     * Returns the current selection mode.
+     */
+    public SelectMode getSelectMode() {
+        return selectMode;
+    }
+
+    /**
+     * Sets the selection mode. Clears the selection if the mode has changed.
+     *
+     * @param selectMode
+     * The new selection mode.
+     */
+    public void setSelectMode(SelectMode selectMode) {
+        if (selectMode == null) {
+            throw new IllegalArgumentException("selectMode is null.");
+        }
+
+        SelectMode previousSelectMode = this.selectMode;
+
+        if (previousSelectMode != selectMode) {
+            // Clear any current selection
+            clearSelection();
+
+            // Update the selection mode
+            this.selectMode = selectMode;
+
+            // Fire select mode change event
+            tableViewListeners.selectModeChanged(this, previousSelectMode);
+        }
+    }
+
+    public void setSelectMode(String selectMode) {
+        if (selectMode == null) {
+            throw new IllegalArgumentException("selectMode is null.");
+        }
+
+        setSelectMode(SelectMode.decode(selectMode));
+    }
+
+    /**
+     * Returns the disabled state of a given row.
+     *
+     * @param index
+     * The index of the row whose disabled state is to be tested.
+     *
+     * @return
+     * <tt>true</tt> if the row is disabled; <tt>false</tt>,
+     * otherwise.
+     */
+    public boolean isRowDisabled(int index) {
+        return (Sequence.Search.binarySearch(disabledIndexes, index) >= 0);
+    }
+
+    /**
+     * Sets the disabled state of a row.
+     *
+     * @param index
+     * The index of the row whose disabled state is to be set.
+     *
+     * @param disabled
+     * <tt>true</tt> to disable the row; <tt>false</tt>, otherwise.
+     */
+    public void setRowDisabled(int index, boolean disabled) {
+        int i = Sequence.Search.binarySearch(disabledIndexes, index);
+
+        if ((i < 0 && disabled)
+            || (i >= 0 && !disabled)) {
+            Vote vote = tableViewRowStateListeners.previewRowDisabledChange(this, index);
+            if (vote.isApproved()) {
+                if (disabled) {
+                    disabledIndexes.insert(index, -(i + 1));
+                } else {
+                    disabledIndexes.remove(i, 1);
+                }
+
+                tableViewRowStateListeners.rowDisabledChanged(this, index);
+            } else {
+                tableViewRowStateListeners.rowDisabledChangeVetoed(this, index, vote);
+            }
+        }
+    }
+
+    public Sequence<Integer> getDisabledIndexes() {
+        ArrayList<Integer> disabledIndexes = new ArrayList<Integer>();
+
+        for (int i = 0, n = this.disabledIndexes.getLength(); i < n; i++) {
+            disabledIndexes.add(this.disabledIndexes.get(i));
+        }
+
+        return disabledIndexes;
+    }
+
+    /**
+     * 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) {
+        TableView.Skin tableViewSkin = (TableView.Skin)getSkin();
+        return tableViewSkin.getRowAt(y);
+    }
+
+    /**
+     * 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) {
+        TableView.Skin tableViewSkin = (TableView.Skin)getSkin();
+        return tableViewSkin.getColumnAt(x);
+    }
+
+    /**
+     * Returns the bounding area of a given row.
+     *
+     * @param rowIndex
+     * The row index.
+     *
+     * @return
+     * The bounding area of the row.
+     */
+    public Bounds getRowBounds(int rowIndex) {
+        TableView.Skin tableViewSkin = (TableView.Skin)getSkin();
+        return tableViewSkin.getRowBounds(rowIndex);
+    }
+
+    /**
+     * Returns the bounding area of a given column.
+     *
+     * @param columnIndex
+     * The column index.
+     *
+     * @return
+     * The bounding area of the column.
+     */
+    public Bounds getColumnBounds(int columnIndex) {
+        TableView.Skin tableViewSkin = (TableView.Skin)getSkin();
+        return tableViewSkin.getColumnBounds(columnIndex);
+    }
+
+    /**
+     * Returns the bounding area of a given cell.
+     *
+     * @param rowIndex
+     * The row index of the cell.
+     *
+     * @param columnIndex
+     * The column index of the cell.
+     *
+     * @return
+     * The bounding area of the cell.
+     */
+    public Bounds getCellBounds(int rowIndex, int columnIndex) {
+        TableView.Skin tableViewSkin = (TableView.Skin)getSkin();
+        return tableViewSkin.getCellBounds(rowIndex, columnIndex);
+    }
+
+    public ListenerList<TableViewListener> getTableViewListeners() {
+        return tableViewListeners;
+    }
+
+    public ListenerList<TableViewColumnListener> getTableViewColumnListeners() {
+        return tableViewColumnListeners;
+    }
+
+    public ListenerList<TableViewRowListener> getTableViewRowListeners() {
+        return tableViewRowListeners;
+    }
+
+    public ListenerList<TableViewRowStateListener> getTableViewRowStateListeners() {
+        return tableViewRowStateListeners;
+    }
+
+    public ListenerList<TableViewSelectionListener> getTableViewSelectionListeners() {
+        return tableViewSelectionListeners;
+    }
+
+    public ListenerList<TableViewSelectionDetailListener> getTableViewSelectionDetailListeners() {
+        return tableViewSelectionDetailListeners;
+    }
+}

Added: incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableView.png
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableView.png?rev=754936&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableView.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableViewColumnListener.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableViewColumnListener.java?rev=754936&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableViewColumnListener.java (added)
+++ incubator/pivot/tags/v1.0.1/wtk/src/pivot/wtk/TableViewColumnListener.java Mon Mar 16 16:36:10 2009
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * 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 pivot.wtk;
+
+import pivot.collections.Sequence;
+
+/**
+ * Table view column listener interface.
+ *
+ * @author gbrown
+ */
+public interface TableViewColumnListener {
+    /**
+     * Called when a column is inserted into a table view's column sequence.
+     *
+     * @param tableView
+     * @param index
+     */
+    public void columnInserted(TableView tableView, int index);
+
+    /**
+     * Called when columns are removed from a table view's column sequence.
+     *
+     * @param tableView
+     * @param index
+     * @param columns
+     */
+    public void columnsRemoved(TableView tableView, int index, Sequence<TableView.Column> columns);
+
+    /**
+     * Called when a column's name has changed.
+     *
+     * @param column
+     * @param previousName
+     */
+    public void columnNameChanged(TableView.Column column, String previousName);
+
+    /**
+     * Called when a column's header data has changed.
+     *
+     * @param column
+     * @param previousHeaderData
+     */
+    public void columnHeaderDataChanged(TableView.Column column, Object previousHeaderData);
+
+    /**
+     * Called when a column's width has changed.
+     *
+     * @param column
+     * @param previousWidth
+     * @param previousRelative
+     */
+    public void columnWidthChanged(TableView.Column column, int previousWidth, boolean previousRelative);
+
+    /**
+     * Called when a column's sort direction has changed.
+     *
+     * @param column
+     * @param previousSortDirection
+     */
+    public void columnSortDirectionChanged(TableView.Column column, SortDirection previousSortDirection);
+
+    /**
+     * Called when a column's filter has changed.
+     *
+     * @param column
+     * @param previousFilter
+     */
+    public void columnFilterChanged(TableView.Column column, Object previousFilter);
+
+    /**
+     * Called when a column's cell renderer has changed.
+     *
+     * @param column
+     * @param previousCellRenderer
+     */
+    public void columnCellRendererChanged(TableView.Column column, TableView.CellRenderer previousCellRenderer);
+}