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/26 00:12:17 UTC

svn commit: r758461 [43/47] - in /incubator/pivot/branches: ./ 1.1/ 1.1/charts-test/ 1.1/charts-test/src/ 1.1/charts-test/src/pivot/ 1.1/charts-test/src/pivot/charts/ 1.1/charts-test/src/pivot/charts/test/ 1.1/charts/ 1.1/charts/lib/ 1.1/charts/src/ 1....

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTableViewSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTableViewSkin.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTableViewSkin.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTableViewSkin.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,1068 @@
+/*
+ * 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.skin.terra;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+
+import pivot.collections.ArrayList;
+import pivot.collections.List;
+import pivot.collections.Sequence;
+import pivot.wtk.Component;
+import pivot.wtk.Dimensions;
+import pivot.wtk.Keyboard;
+import pivot.wtk.Mouse;
+import pivot.wtk.Bounds;
+import pivot.wtk.SortDirection;
+import pivot.wtk.Span;
+import pivot.wtk.TableView;
+import pivot.wtk.TableViewListener;
+import pivot.wtk.TableViewColumnListener;
+import pivot.wtk.TableViewRowListener;
+import pivot.wtk.TableViewRowStateListener;
+import pivot.wtk.TableViewSelectionListener;
+import pivot.wtk.Theme;
+import pivot.wtk.skin.ComponentSkin;
+
+/**
+ * Table view skin.
+ * <p>
+ * NOTE This skin assumes a fixed renderer height.
+ * <p>
+ * TODO Add disableMouseSelection style to support the case where selection
+ * should be enabled but the caller wants to implement the management of it;
+ * e.g. changing a message's flag state in an email client.
+ *
+ * @author gbrown
+ */
+public class TerraTableViewSkin extends ComponentSkin implements TableView.Skin,
+    TableViewListener, TableViewColumnListener, TableViewRowListener,
+    TableViewRowStateListener, TableViewSelectionListener {
+    private Font font;
+    private Color color;
+    private Color disabledColor;
+    private Color backgroundColor;
+    private Color selectionColor;
+    private Color selectionBackgroundColor;
+    private Color inactiveSelectionColor;
+    private Color inactiveSelectionBackgroundColor;
+    private Color highlightBackgroundColor;
+    private Color alternateRowColor;
+    private Color gridColor;
+    private boolean showHorizontalGridLines;
+    private boolean showVerticalGridLines;
+    private boolean showHighlight;
+    private boolean includeTrailingVerticalGridLine;
+
+    private int highlightedIndex = -1;
+    private int editIndex = -1;
+
+    public TerraTableViewSkin() {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        font = theme.getFont();
+        color = theme.getColor(1);
+        disabledColor = theme.getColor(7);
+        backgroundColor = theme.getColor(4);
+        selectionColor = theme.getColor(4);
+        selectionBackgroundColor = theme.getColor(19);
+        inactiveSelectionColor = theme.getColor(1);
+        inactiveSelectionBackgroundColor = theme.getColor(9);
+        highlightBackgroundColor = theme.getColor(10);
+        alternateRowColor = theme.getColor(11);
+        gridColor = theme.getColor(11);
+        showHorizontalGridLines = true;
+        showVerticalGridLines = true;
+        showHighlight = true;
+        includeTrailingVerticalGridLine = false;
+    }
+
+    public void install(Component component) {
+        super.install(component);
+
+        TableView tableView = (TableView)component;
+        tableView.getTableViewListeners().add(this);
+        tableView.getTableViewColumnListeners().add(this);
+        tableView.getTableViewRowListeners().add(this);
+        tableView.getTableViewRowStateListeners().add(this);
+        tableView.getTableViewSelectionListeners().add(this);
+    }
+
+    public void uninstall() {
+        TableView tableView = (TableView)getComponent();
+        tableView.getTableViewListeners().remove(this);
+        tableView.getTableViewColumnListeners().remove(this);
+        tableView.getTableViewRowListeners().remove(this);
+        tableView.getTableViewRowStateListeners().remove(this);
+        tableView.getTableViewSelectionListeners().remove(this);
+
+        super.uninstall();
+    }
+
+    public int getPreferredWidth(int height) {
+        int preferredWidth = 0;
+
+        TableView tableView = (TableView)getComponent();
+        TableView.ColumnSequence columns = tableView.getColumns();
+
+        int n = columns.getLength();
+        int gridLineStop = includeTrailingVerticalGridLine ? n : n - 1;
+
+        for (int i = 0; i < n; i++) {
+            TableView.Column column = columns.get(i);
+
+            if (!column.isRelative()) {
+                preferredWidth += column.getWidth();
+
+                // Include space for vertical gridlines; even if we are
+                // not painting them, the header does
+                if (i < gridLineStop) {
+                    preferredWidth++;
+                }
+            }
+        }
+
+        return preferredWidth;
+    }
+
+    public int getPreferredHeight(int width) {
+        int preferredHeight = 0;
+
+        TableView tableView = (TableView)getComponent();
+
+        int n = tableView.getTableData().getLength();
+        preferredHeight = getRowHeight() * n;
+
+        return preferredHeight;
+    }
+
+    public Dimensions getPreferredSize() {
+        return new Dimensions(getPreferredWidth(-1), getPreferredHeight(-1));
+    }
+
+    public void layout() {
+        // No-op
+    }
+
+    @SuppressWarnings("unchecked")
+    public void paint(Graphics2D graphics) {
+        TableView tableView = (TableView)getComponent();
+        List<Object> tableData = (List<Object>)tableView.getTableData();
+        TableView.ColumnSequence columns = tableView.getColumns();
+
+        int width = getWidth();
+        int height = getHeight();
+
+        int rowHeight = getRowHeight();
+        Sequence<Integer> columnWidths = getColumnWidths();
+
+        // Paint the background
+        graphics.setPaint(backgroundColor);
+        graphics.fillRect(0, 0, width, height);
+
+        // Paint the list contents
+        int rowStart = 0;
+        int rowEnd = tableData.getLength() - 1;
+
+        // Ensure that we only paint items that are visible
+        Rectangle clipBounds = graphics.getClipBounds();
+        if (clipBounds != null) {
+            rowStart = Math.max(rowStart, (int)Math.floor(clipBounds.y
+                / (double)rowHeight));
+            rowEnd = Math.min(rowEnd, (int)Math.ceil((clipBounds.y
+                + clipBounds.height) / (double)rowHeight) - 1);
+        }
+
+        int rowY = rowStart * rowHeight;
+
+        for (int rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) {
+            Object rowData = tableData.get(rowIndex);
+            boolean rowHighlighted = (rowIndex == highlightedIndex
+                && tableView.getSelectMode() != TableView.SelectMode.NONE);
+            boolean rowSelected = tableView.isRowSelected(rowIndex);
+            boolean rowDisabled = tableView.isRowDisabled(rowIndex);
+
+            Color rowBackgroundColor = null;
+
+            if (rowSelected) {
+                rowBackgroundColor = (tableView.isFocused())
+                    ? this.selectionBackgroundColor : inactiveSelectionBackgroundColor;
+            } else {
+                if (rowHighlighted && showHighlight && !rowDisabled) {
+                    rowBackgroundColor = highlightBackgroundColor;
+                } else {
+                    if (alternateRowColor != null
+                        && rowIndex % 2 > 0) {
+                        rowBackgroundColor = alternateRowColor;
+                    }
+                }
+            }
+
+            if (rowBackgroundColor != null) {
+                graphics.setPaint(rowBackgroundColor);
+                graphics.fillRect(0, rowY, width, rowHeight);
+            }
+
+            // Paint the cells
+            int cellX = 0;
+
+            for (int columnIndex = 0, columnCount = columns.getLength();
+                columnIndex < columnCount; columnIndex++) {
+                TableView.Column column = columns.get(columnIndex);
+
+                TableView.CellRenderer cellRenderer = column.getCellRenderer();
+
+                int columnWidth = columnWidths.get(columnIndex);
+
+                Graphics2D rendererGraphics = (Graphics2D)graphics.create(cellX, rowY,
+                    columnWidth, rowHeight);
+
+                cellRenderer.render(rowData, tableView, column, rowSelected,
+                    rowHighlighted, rowDisabled);
+                cellRenderer.setSize(columnWidth, rowHeight - 1);
+                cellRenderer.paint(rendererGraphics);
+
+                rendererGraphics.dispose();
+
+                cellX += columnWidth + 1;
+            }
+
+            rowY += rowHeight;
+        }
+
+        // Set the grid stroke and color
+        graphics.setStroke(new BasicStroke());
+        graphics.setPaint(gridColor);
+
+        // Paint the vertical grid lines
+        if (showVerticalGridLines) {
+            int gridX = 0;
+
+            for (int columnIndex = 0, columnCount = columns.getLength();
+                columnIndex < columnCount; columnIndex++) {
+                gridX += columnWidths.get(columnIndex);
+
+                graphics.drawLine(gridX, 0, gridX, height);
+                gridX++;
+            }
+        }
+
+        // Paint the horizontal grid line
+        if (showHorizontalGridLines) {
+            for (int rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) {
+                if (rowIndex > 0) {
+                    int gridY = rowIndex * rowHeight;
+                    graphics.drawLine(0, gridY, width, gridY);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the table row height, which is determined as the maximum
+     * preferred height of all cell renderers.
+     *
+     * @return
+     * The height of one table row.
+     */
+    public int getRowHeight() {
+        int rowHeight = 0;
+
+        TableView tableView = (TableView)getComponent();
+        TableView.ColumnSequence columns = tableView.getColumns();
+
+        for (int i = 0, n = columns.getLength(); i < n; i++) {
+            TableView.Column column = columns.get(i);
+            TableView.CellRenderer cellRenderer = column.getCellRenderer();
+
+            rowHeight = Math.max(rowHeight, cellRenderer.getPreferredHeight(-1));
+        }
+
+        rowHeight++;
+
+        return rowHeight;
+    }
+
+    /**
+     * Returns the column widths for this table.
+     *
+     * @return
+     * The widths of all columns based on the current overall width.
+     */
+    public Sequence<Integer> getColumnWidths() {
+        TableView tableView = (TableView)getComponent();
+
+        return getColumnWidths(tableView.getColumns(), getWidth());
+    }
+
+    /**
+     * Returns the column widths, determined by applying relative size values
+     * to the available width.
+     *
+     * TODO Cache these values and recalculate only when size changes.
+     *
+     * @param columns
+     * The columns whose widths are to be determined.
+     *
+     * @param width
+     * The total available width for the columns.
+     *
+     * @return
+     * The widths of all columns based on the current overall width.
+     */
+    public static Sequence<Integer> getColumnWidths(TableView.ColumnSequence columns, int width) {
+        int fixedWidth = 0;
+        int relativeWidth = 0;
+
+        int n = columns.getLength();
+
+        for (int i = 0; i < n; i++) {
+            TableView.Column column = columns.get(i);
+
+            if (column.isRelative()) {
+                relativeWidth += column.getWidth();
+            } else {
+                fixedWidth += column.getWidth();
+            }
+        }
+
+        fixedWidth += n - 1;
+        int variableWidth = Math.max(width - fixedWidth, 0);
+
+        ArrayList<Integer> columnWidths = new ArrayList<Integer>(columns.getLength());
+
+        for (int i = 0; i < n; i++) {
+            TableView.Column column = columns.get(i);
+
+            if (column.isRelative()) {
+                int columnWidth = (int)Math.round((double)(column.getWidth()
+                    * variableWidth) / (double)relativeWidth);
+                columnWidths.add(columnWidth);
+            } else {
+                columnWidths.add(column.getWidth());
+            }
+        }
+
+        return columnWidths;
+    }
+
+    // Table view skin methods
+    @SuppressWarnings("unchecked")
+    public int getRowAt(int y) {
+        if (y < 0) {
+            throw new IllegalArgumentException("y is negative");
+        }
+
+        TableView tableView = (TableView)getComponent();
+        List<Object> tableData = (List<Object>)tableView.getTableData();
+
+        int rowIndex = (y / getRowHeight());
+
+        if (rowIndex >= tableData.getLength()) {
+            rowIndex = -1;
+        }
+
+        return rowIndex;
+    }
+
+    public int getColumnAt(int x) {
+        if (x < 0) {
+            throw new IllegalArgumentException("x is negative");
+        }
+
+        Sequence<Integer> columnWidths = getColumnWidths();
+
+        int i = 0;
+        int n = columnWidths.getLength();
+        int columnX = 0;
+        while (i < n
+            && x > columnX) {
+            columnX += (columnWidths.get(i) + 1);
+            i++;
+        }
+
+        int columnIndex = -1;
+
+        if (x <= columnX) {
+            columnIndex = i - 1;
+        }
+
+        return columnIndex;
+    }
+
+    public Bounds getRowBounds(int rowIndex) {
+        int rowHeight = getRowHeight();
+        return new Bounds(0, rowIndex * rowHeight, getWidth(), rowHeight);
+    }
+
+    public Bounds getColumnBounds(int columnIndex) {
+        Sequence<Integer> columnWidths = getColumnWidths();
+        int columnCount = columnWidths.getLength();
+
+        if (columnIndex < 0
+            || columnIndex >= columnCount) {
+            throw new IndexOutOfBoundsException("Column index out of bounds: " +
+                columnIndex);
+        }
+
+        int columnX = 0;
+        for (int i = 0; i < columnIndex; i++) {
+            columnX += (columnWidths.get(i) + 1);
+        }
+
+        return new Bounds(columnX, 0, columnWidths.get(columnIndex), getHeight());
+    }
+
+    @SuppressWarnings("unchecked")
+    public Bounds getCellBounds(int rowIndex, int columnIndex) {
+        TableView tableView = (TableView)getComponent();
+        List<Object> tableData = (List<Object>)tableView.getTableData();
+        Sequence<Integer> columnWidths = getColumnWidths();
+
+        if (rowIndex < 0
+            || rowIndex >= tableData.getLength()) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        if (columnIndex < 0
+            || columnIndex >= columnWidths.getLength()) {
+            throw new IndexOutOfBoundsException();
+        }
+
+
+        int rowHeight = getRowHeight();
+
+        int cellX = 0;
+        for (int i = 0; i < columnIndex; i++) {
+            cellX += (columnWidths.get(i) + 1);
+        }
+
+        return new Bounds(cellX, rowIndex * rowHeight,
+            columnWidths.get(columnIndex), rowHeight);
+    }
+
+    @Override
+    public boolean isFocusable() {
+        TableView tableView = (TableView)getComponent();
+        return (tableView.getSelectMode() != TableView.SelectMode.NONE);
+    }
+
+    public Font getFont() {
+        return font;
+    }
+
+    public void setFont(Font font) {
+        if (font == null) {
+            throw new IllegalArgumentException("font is null.");
+        }
+
+        this.font = font;
+        invalidateComponent();
+    }
+
+    public final void setFont(String font) {
+        if (font == null) {
+            throw new IllegalArgumentException("font is null.");
+        }
+
+        setFont(Font.decode(font));
+    }
+
+    public Color getColor() {
+        return color;
+    }
+
+    public void setColor(Color color) {
+        if (color == null) {
+            throw new IllegalArgumentException("color is null.");
+        }
+
+        this.color = color;
+        repaintComponent();
+    }
+
+    public final void setColor(String color) {
+        if (color == null) {
+            throw new IllegalArgumentException("color is null.");
+        }
+
+        setColor(decodeColor(color));
+    }
+
+    public final void setColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setColor(theme.getColor(color));
+    }
+
+    public Color getDisabledColor() {
+        return disabledColor;
+    }
+
+    public void setDisabledColor(Color disabledColor) {
+        if (disabledColor == null) {
+            throw new IllegalArgumentException("disabledColor is null.");
+        }
+
+        this.disabledColor = disabledColor;
+        repaintComponent();
+    }
+
+    public final void setDisabledColor(String disabledColor) {
+        if (disabledColor == null) {
+            throw new IllegalArgumentException("disabledColor is null.");
+        }
+
+        setDisabledColor(decodeColor(disabledColor));
+    }
+
+    public final void setDisabledColor(int disabledColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setDisabledColor(theme.getColor(disabledColor));
+    }
+
+    public Color getBackgroundColor() {
+        return backgroundColor;
+    }
+
+    public void setBackgroundColor(Color backgroundColor) {
+        if (backgroundColor == null) {
+            throw new IllegalArgumentException("backgroundColor is null.");
+        }
+
+        this.backgroundColor = backgroundColor;
+        repaintComponent();
+    }
+
+    public final void setBackgroundColor(String backgroundColor) {
+        if (backgroundColor == null) {
+            throw new IllegalArgumentException("backgroundColor is null.");
+        }
+
+        setBackgroundColor(decodeColor(backgroundColor));
+    }
+
+    public final void setBackgroundColor(int backgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBackgroundColor(theme.getColor(backgroundColor));
+    }
+
+    public Color getSelectionColor() {
+        return selectionColor;
+    }
+
+    public void setSelectionColor(Color selectionColor) {
+        if (selectionColor == null) {
+            throw new IllegalArgumentException("selectionColor is null.");
+        }
+
+        this.selectionColor = selectionColor;
+        repaintComponent();
+    }
+
+    public final void setSelectionColor(String selectionColor) {
+        if (selectionColor == null) {
+            throw new IllegalArgumentException("selectionColor is null.");
+        }
+
+        setSelectionColor(decodeColor(selectionColor));
+    }
+
+    public final void setSelectionColor(int selectionColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setSelectionColor(theme.getColor(selectionColor));
+    }
+
+    public Color getSelectionBackgroundColor() {
+        return selectionBackgroundColor;
+    }
+
+    public void setSelectionBackgroundColor(Color selectionBackgroundColor) {
+        if (selectionBackgroundColor == null) {
+            throw new IllegalArgumentException("selectionBackgroundColor is null.");
+        }
+
+        this.selectionBackgroundColor = selectionBackgroundColor;
+        repaintComponent();
+    }
+
+    public final void setSelectionBackgroundColor(String selectionBackgroundColor) {
+        if (selectionBackgroundColor == null) {
+            throw new IllegalArgumentException("selectionBackgroundColor is null.");
+        }
+
+        setSelectionBackgroundColor(decodeColor(selectionBackgroundColor));
+    }
+
+    public final void setSelectionBackgroundColor(int selectionBackgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setSelectionBackgroundColor(theme.getColor(selectionBackgroundColor));
+    }
+
+    public Color getInactiveSelectionColor() {
+        return inactiveSelectionColor;
+    }
+
+    public void setInactiveSelectionColor(Color inactiveSelectionColor) {
+        if (inactiveSelectionColor == null) {
+            throw new IllegalArgumentException("inactiveSelectionColor is null.");
+        }
+
+        this.inactiveSelectionColor = inactiveSelectionColor;
+        repaintComponent();
+    }
+
+    public final void setInactiveSelectionColor(String inactiveSelectionColor) {
+        if (inactiveSelectionColor == null) {
+            throw new IllegalArgumentException("inactiveSelectionColor is null.");
+        }
+
+        setInactiveSelectionColor(decodeColor(inactiveSelectionColor));
+    }
+
+    public final void setInactiveSelectionColor(int inactiveSelectionColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setInactiveSelectionColor(theme.getColor(inactiveSelectionColor));
+    }
+
+    public Color getInactiveSelectionBackgroundColor() {
+        return inactiveSelectionBackgroundColor;
+    }
+
+    public void setInactiveSelectionBackgroundColor(Color inactiveSelectionBackgroundColor) {
+        if (inactiveSelectionBackgroundColor == null) {
+            throw new IllegalArgumentException("inactiveSelectionBackgroundColor is null.");
+        }
+
+        this.inactiveSelectionBackgroundColor = inactiveSelectionBackgroundColor;
+        repaintComponent();
+    }
+
+    public final void setInactiveSelectionBackgroundColor(String inactiveSelectionBackgroundColor) {
+        if (inactiveSelectionBackgroundColor == null) {
+            throw new IllegalArgumentException("inactiveSelectionBackgroundColor is null.");
+        }
+
+        setInactiveSelectionBackgroundColor(decodeColor(inactiveSelectionBackgroundColor));
+    }
+
+    public final void setInactiveSelectionBackgroundColor(int inactiveSelectionBackgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setInactiveSelectionBackgroundColor(theme.getColor(inactiveSelectionBackgroundColor));
+    }
+
+    public Color getHighlightBackgroundColor() {
+        return highlightBackgroundColor;
+    }
+
+    public void setHighlightBackgroundColor(Color highlightBackgroundColor) {
+        if (highlightBackgroundColor == null) {
+            throw new IllegalArgumentException("highlightBackgroundColor is null.");
+        }
+
+        this.highlightBackgroundColor = highlightBackgroundColor;
+        repaintComponent();
+    }
+
+    public final void setHighlightBackgroundColor(String highlightBackgroundColor) {
+        if (highlightBackgroundColor == null) {
+            throw new IllegalArgumentException("highlightBackgroundColor is null.");
+        }
+
+        setHighlightBackgroundColor(decodeColor(highlightBackgroundColor));
+    }
+
+    public final void setHighlightBackgroundColor(int highlightBackgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setHighlightBackgroundColor(theme.getColor(highlightBackgroundColor));
+    }
+
+    public Color getAlternateRowColor() {
+        return alternateRowColor;
+    }
+
+    public void setAlternateRowColor(Color alternateRowColor) {
+        this.alternateRowColor = alternateRowColor;
+        repaintComponent();
+    }
+
+    public final void setAlternateRowColor(String alternateRowColor) {
+        if (alternateRowColor == null) {
+            throw new IllegalArgumentException("alternateRowColor is null.");
+        }
+
+        setAlternateRowColor(decodeColor(alternateRowColor));
+    }
+
+    public final void setAlternateRowColor(int alternateRowColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setAlternateRowColor(theme.getColor(alternateRowColor));
+    }
+
+    public Color getGridColor() {
+        return gridColor;
+    }
+
+    public void setGridColor(Color gridColor) {
+        if (gridColor == null) {
+            throw new IllegalArgumentException("gridColor is null.");
+        }
+
+        this.gridColor = gridColor;
+        repaintComponent();
+    }
+
+    public final void setGridColor(String gridColor) {
+        if (gridColor == null) {
+            throw new IllegalArgumentException("gridColor is null.");
+        }
+
+        setGridColor(decodeColor(gridColor));
+    }
+
+    public final void setGridColor(int gridColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setGridColor(theme.getColor(gridColor));
+    }
+
+    public boolean getShowHorizontalGridLines() {
+        return showHorizontalGridLines;
+    }
+
+    public void setShowHorizontalGridLines(boolean showHorizontalGridLines) {
+        this.showHorizontalGridLines = showHorizontalGridLines;
+        repaintComponent();
+    }
+
+    public boolean getShowVerticalGridLines() {
+        return showVerticalGridLines;
+    }
+
+    public void setShowVerticalGridLines(boolean showVerticalGridLines) {
+        this.showVerticalGridLines = showVerticalGridLines;
+        repaintComponent();
+    }
+
+    public boolean getShowHighlight() {
+        return showHighlight;
+    }
+
+    public void setShowHighlight(boolean showHighlight) {
+        this.showHighlight = showHighlight;
+        repaintComponent();
+    }
+
+    public boolean getIncludeTrailingVerticalGridLine() {
+        return includeTrailingVerticalGridLine;
+    }
+
+    public void setIncludeTrailingVerticalGridLine(boolean includeTrailingVerticalGridLine) {
+        this.includeTrailingVerticalGridLine = includeTrailingVerticalGridLine;
+        invalidateComponent();
+    }
+
+    @Override
+    public boolean mouseMove(Component component, int x, int y) {
+        boolean consumed = super.mouseMove(component, x, y);
+
+        int previousHighlightedIndex = this.highlightedIndex;
+        highlightedIndex = getRowAt(y);
+
+        if (previousHighlightedIndex != highlightedIndex) {
+            if (previousHighlightedIndex != -1) {
+                repaintComponent(getRowBounds(previousHighlightedIndex));
+            }
+
+            if (highlightedIndex != -1) {
+                repaintComponent(getRowBounds(highlightedIndex));
+            }
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public void mouseOut(Component component) {
+        super.mouseOut(component);
+
+        if (highlightedIndex != -1) {
+            Bounds rowBounds = getRowBounds(highlightedIndex);
+            repaintComponent(rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height);
+        }
+
+        highlightedIndex = -1;
+        editIndex = -1;
+    }
+
+    @Override
+    public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseDown(component, button, x, y);
+
+        TableView tableView = (TableView)getComponent();
+        int rowIndex = getRowAt(y);
+
+        if (rowIndex >= 0
+            && !tableView.isRowDisabled(rowIndex)) {
+            TableView.SelectMode selectMode = tableView.getSelectMode();
+
+            if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)
+                && selectMode == TableView.SelectMode.MULTI) {
+                // Select the range
+                int startIndex = tableView.getFirstSelectedIndex();
+                int endIndex = tableView.getLastSelectedIndex();
+                Span selectedRange = (rowIndex > startIndex) ?
+                    new Span(startIndex, rowIndex) : new Span(rowIndex, endIndex);
+
+                ArrayList<Span> selectedRanges = new ArrayList<Span>();
+                Sequence<Integer> disabledIndexes = tableView.getDisabledIndexes();
+                if (disabledIndexes.getLength() == 0) {
+                    selectedRanges.add(selectedRange);
+                } else {
+                    // TODO Split the range by the disabled indexes; for now,
+                    // just return
+                    return consumed;
+                }
+
+                tableView.setSelectedRanges(selectedRanges);
+            } else if (Keyboard.isPressed(Keyboard.Modifier.CTRL)
+                && selectMode == TableView.SelectMode.MULTI) {
+                // Toggle the item's selection state
+                if (tableView.isRowSelected(rowIndex)) {
+                    tableView.removeSelectedIndex(rowIndex);
+                } else {
+                    tableView.addSelectedIndex(rowIndex);
+                }
+            } else {
+                if (selectMode != TableView.SelectMode.NONE) {
+                    if (tableView.isRowSelected(rowIndex)
+                        && tableView.isFocused()) {
+                        // Edit the row
+                        editIndex = rowIndex;
+                    } else {
+                        // Select the row
+                        tableView.setSelectedIndex(rowIndex);
+                    }
+                }
+            }
+        }
+
+        tableView.requestFocus();
+
+        return consumed;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public boolean mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+        boolean consumed = super.mouseClick(component, button, x, y, count);
+
+        TableView tableView = (TableView)getComponent();
+        if (editIndex != -1
+            && count == 1) {
+            TableView.RowEditor rowEditor = tableView.getRowEditor();
+
+            if (rowEditor != null) {
+                rowEditor.edit(tableView, editIndex, getColumnAt(x));
+            }
+        }
+
+        editIndex = -1;
+
+        return consumed;
+    }
+
+    @Override
+    public boolean mouseWheel(Component component, Mouse.ScrollType scrollType, int scrollAmount,
+        int wheelRotation, int x, int y) {
+        if (highlightedIndex != -1) {
+            Bounds rowBounds = getRowBounds(highlightedIndex);
+            repaintComponent(rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height);
+        }
+
+        highlightedIndex = -1;
+
+        return super.mouseWheel(component, scrollType, scrollAmount, wheelRotation, x, y);
+    }
+
+    @Override
+    public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = super.keyPressed(component, keyCode, keyLocation);
+
+        TableView tableView = (TableView)getComponent();
+
+        switch (keyCode) {
+            case Keyboard.KeyCode.UP: {
+                int index = tableView.getFirstSelectedIndex();
+
+                do {
+                    index--;
+                } while (index >= 0
+                    && tableView.isRowDisabled(index));
+
+                if (index >= 0) {
+                    if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)
+                        && tableView.getSelectMode() == TableView.SelectMode.MULTI) {
+                        tableView.addSelectedIndex(index);
+                    } else {
+                        tableView.setSelectedIndex(index);
+                    }
+
+                    tableView.scrollAreaToVisible(getRowBounds(index));
+                }
+
+                consumed = true;
+                break;
+            }
+
+            case Keyboard.KeyCode.DOWN: {
+                int index = tableView.getLastSelectedIndex();
+                int count = tableView.getTableData().getLength();
+
+                do {
+                    index++;
+                } while (index < count
+                    && tableView.isRowDisabled(index));
+
+                if (index < count) {
+                    if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)
+                        && tableView.getSelectMode() == TableView.SelectMode.MULTI) {
+                        tableView.addSelectedIndex(index);
+                    } else {
+                        tableView.setSelectedIndex(index);
+                    }
+
+                    tableView.scrollAreaToVisible(getRowBounds(index));
+                }
+
+                consumed = true;
+                break;
+            }
+        }
+
+        // Clear the highlight
+        if (highlightedIndex != -1) {
+            highlightedIndex = -1;
+            repaintComponent(getRowBounds(highlightedIndex));
+        }
+
+        return consumed;
+    }
+
+    // Component state events
+    @Override
+    public void enabledChanged(Component component) {
+        super.enabledChanged(component);
+
+        repaintComponent();
+    }
+
+    @Override
+    public void focusedChanged(Component component, boolean temporary) {
+        super.focusedChanged(component, temporary);
+
+        repaintComponent();
+    }
+
+    // Table view events
+    public void tableDataChanged(TableView tableView, List<?> previousTableData) {
+        invalidateComponent();
+    }
+
+    public void rowEditorChanged(TableView tableView, TableView.RowEditor previousRowEditor) {
+        // No-op
+    }
+
+    public void selectModeChanged(TableView tableView, TableView.SelectMode previousSelectMode) {
+        repaintComponent();
+    }
+
+    // Table view column events
+    public void columnInserted(TableView tableView, int index) {
+        invalidateComponent();
+    }
+
+    public void columnsRemoved(TableView tableView, int index, Sequence<TableView.Column> columns) {
+        invalidateComponent();
+    }
+
+    public void columnNameChanged(TableView.Column column, String previousName) {
+        invalidateComponent();
+    }
+
+    public void columnHeaderDataChanged(TableView.Column column, Object previousHeaderData) {
+        // No-op
+    }
+
+    public void columnWidthChanged(TableView.Column column, int previousWidth, boolean previousRelative)  {
+        invalidateComponent();
+    }
+
+    public void columnSortDirectionChanged(TableView.Column column, SortDirection previousSortDirection) {
+        // TODO Repaint; paint a "selection" color for the sorted column
+    }
+
+    public void columnFilterChanged(TableView.Column column, Object previousFilter) {
+        // No-op
+    }
+
+    public void columnCellRendererChanged(TableView.Column column, TableView.CellRenderer previousCellRenderer) {
+        invalidateComponent();
+    }
+
+    // Table view row events
+    public void rowInserted(TableView tableView, int index) {
+        invalidateComponent();
+    }
+
+    public void rowsRemoved(TableView tableView, int index, int count) {
+        invalidateComponent();
+    }
+
+    public void rowUpdated(TableView tableView, int index) {
+        repaintComponent(getRowBounds(index));
+    }
+
+    public void rowsSorted(TableView tableView) {
+        repaintComponent();
+    }
+
+    // Table view row state events
+    public void rowDisabledChanged(TableView tableView, int index) {
+        repaintComponent(getRowBounds(index));
+    }
+
+    // Table view selection detail events
+    public void selectedRangeAdded(TableView tableView, int rangeStart, int rangeEnd) {
+        // Repaint the area containing the added selection
+        int rowHeight = getRowHeight();
+        repaintComponent(0, rangeStart * rowHeight,
+            getWidth(), (rangeEnd - rangeStart + 1) * rowHeight);
+    }
+
+    public void selectedRangeRemoved(TableView tableView, int rangeStart, int rangeEnd) {
+        // Repaint the area containing the removed selection
+        int rowHeight = getRowHeight();
+        repaintComponent(0, rangeStart * rowHeight,
+            getWidth(), (rangeEnd - rangeStart + 1) * rowHeight);
+    }
+
+    public void selectedRangesChanged(TableView tableView, Sequence<Span> previousSelectedRanges) {
+        // TODO Repaint only the area that changed (intersection of previous
+        // and new selection)
+        repaintComponent();
+    }
+}

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTextInputSkin.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,1302 @@
+/*
+ * 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.skin.terra;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.font.FontRenderContext;
+import java.awt.font.LineMetrics;
+import java.awt.font.TextHitInfo;
+import java.awt.font.TextLayout;
+import java.awt.geom.Rectangle2D;
+// import java.text.AttributedCharacterIterator;
+
+import pivot.collections.Dictionary;
+import pivot.wtk.ApplicationContext;
+import pivot.wtk.Component;
+import pivot.wtk.Cursor;
+import pivot.wtk.Dimensions;
+import pivot.wtk.Direction;
+import pivot.wtk.Insets;
+import pivot.wtk.Keyboard;
+import pivot.wtk.Mouse;
+import pivot.wtk.Platform;
+import pivot.wtk.TextInput;
+import pivot.wtk.TextInputCharacterListener;
+import pivot.wtk.TextInputListener;
+import pivot.wtk.TextInputSelectionListener;
+import pivot.wtk.Theme;
+import pivot.wtk.skin.ComponentSkin;
+import pivot.wtk.text.TextNode;
+import pivot.wtk.text.validation.Validator;
+
+/**
+ * Text input skin.
+ *
+ * @author gbrown
+ */
+public class TerraTextInputSkin extends ComponentSkin
+    implements TextInputListener, TextInputCharacterListener, TextInputSelectionListener {
+    /**
+     * TODO This class will be used to optimize rendering of text, so we don't
+     * need to get a copy of the string via {@link TextInput#getText()}.
+     *
+     * NOTE We'll want to use this everywhere we are currently calling
+     * getText(), if possible. This means that the character iterator will need
+     * to return "*" characters when in password mode.
+     *
+     * @author gbrown
+     */
+    /*
+    private static class TextInputCharacterIterator implements AttributedCharacterIterator {
+        public TextInputCharacterIterator(TextInput textInput) {
+            // TODO Need index arguments
+        }
+
+        public int getBeginIndex() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public int getEndIndex() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public char setIndex(int position) {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public int getIndex() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public char current() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public char first() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public char last() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public char next() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public char previous() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public int getRunLimit() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public int getRunLimit(Attribute arg0) {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public int getRunLimit(java.util.Set<? extends Attribute> arg0) {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public int getRunStart() {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public int getRunStart(Attribute arg0) {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public int getRunStart(java.util.Set<? extends Attribute> arg0) {
+            // TODO Auto-generated method stub
+            return 0;
+        }
+
+        public Object getAttribute(Attribute arg0) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        public java.util.Map<Attribute, Object> getAttributes() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        public java.util.Set<Attribute> getAllAttributeKeys() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        public Object clone() {
+            // TODO
+            return null;
+        }
+    }
+    */
+
+    private class BlinkCursorCallback implements Runnable {
+        public void run() {
+            caretOn = !caretOn;
+
+            java.awt.Rectangle caretBounds = caretShapes[0].getBounds();
+            LineMetrics lm = font.getLineMetrics("", fontRenderContext);
+
+            int ascent = Math.round(lm.getAscent());
+            caretBounds.x += (padding.left - scrollLeft + 1);
+            caretBounds.y += (padding.top + ascent + 1);
+
+            if (caretBounds.width == 0) {
+                caretBounds.width++;
+            }
+
+            TextInput textInput = (TextInput)getComponent();
+            textInput.repaint(caretBounds.x, caretBounds.y,
+                caretBounds.width, caretBounds.height, true);
+        }
+    }
+
+    private class ScrollSelectionCallback implements Runnable {
+        private int x = 0;
+
+        public void run() {
+            TextInput textInput = (TextInput)getComponent();
+            TextNode textNode = textInput.getTextNode();
+
+            int selectionStart = textInput.getSelectionStart();
+            int selectionLength = textInput.getSelectionLength();
+
+            if (x < 0) {
+                // Add the previous character to the selection
+                if (selectionStart > 0) {
+                    selectionStart--;
+                    selectionLength++;
+                }
+            } else {
+                // Add the next character to the selection
+                if (selectionStart + selectionLength < textNode.getCharacterCount()) {
+                    selectionLength++;
+                }
+            }
+
+            textInput.setSelection(selectionStart, selectionLength);
+        }
+    }
+
+
+    protected FontRenderContext fontRenderContext = new FontRenderContext(null, true, false);
+
+    private boolean caretOn = true;
+    private Shape[] caretShapes = null;
+    private Shape logicalHighlightShape = null;
+
+    private int scrollLeft = 0;
+
+    private BlinkCursorCallback blinkCursorCallback = new BlinkCursorCallback();
+    private ApplicationContext.ScheduledCallback scheduledBlinkCursorCallback = null;
+
+    private ScrollSelectionCallback scrollSelectionCallback = new ScrollSelectionCallback();
+    private ApplicationContext.ScheduledCallback scheduledScrollSelectionCallback = null;
+
+    private Font font;
+    private Color color;
+    private Color disabledColor;
+    private Color promptColor;
+    private Color backgroundColor;
+    private Color disabledBackgroundColor;
+    private Color invalidColor;
+    private Color invalidBackgroundColor;
+    private Color borderColor;
+    private Color disabledBorderColor;
+    private Insets padding;
+
+    private Color selectionColor;
+    private Color selectionBackgroundColor;
+    private Color inactiveSelectionColor;
+    private Color inactiveSelectionBackgroundColor;
+
+    // Derived colors
+    private Color bevelColor;
+    private Color disabledBevelColor;
+    private Color invalidBevelColor;
+
+    private static final int SCROLL_RATE = 50;
+
+    public TerraTextInputSkin() {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        font = theme.getFont();
+        color = theme.getColor(1);
+        promptColor = theme.getColor(7);
+        disabledColor = theme.getColor(7);
+        backgroundColor = theme.getColor(11);
+        disabledBackgroundColor = theme.getColor(10);
+        invalidColor = theme.getColor(4);
+        invalidBackgroundColor = theme.getColor(22);
+        borderColor = theme.getColor(7);
+        disabledBorderColor = theme.getColor(7);
+        padding = new Insets(2);
+
+        selectionColor = theme.getColor(4);
+        selectionBackgroundColor = theme.getColor(19);
+        inactiveSelectionColor = theme.getColor(1);
+        inactiveSelectionBackgroundColor = theme.getColor(9);
+
+        // Set the derived colors
+        bevelColor = TerraTheme.darken(backgroundColor);
+        disabledBevelColor = disabledBackgroundColor;
+        invalidBevelColor = TerraTheme.darken(invalidBackgroundColor);
+    }
+
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        TextInput textInput = (TextInput)component;
+        textInput.getTextInputListeners().add(this);
+        textInput.getTextInputCharacterListeners().add(this);
+        textInput.getTextInputSelectionListeners().add(this);
+
+        textInput.setCursor(Cursor.TEXT);
+
+        selectionChanged(textInput, 0, 0);
+    }
+
+    @Override
+    public void uninstall() {
+        TextInput textInput = (TextInput)getComponent();
+        textInput.getTextInputListeners().remove(this);
+        textInput.getTextInputCharacterListeners().remove(this);
+        textInput.getTextInputSelectionListeners().remove(this);
+
+        textInput.setCursor(Cursor.DEFAULT);
+
+        super.uninstall();
+    }
+
+    public int getPreferredWidth(int height) {
+        TextInput textInput = (TextInput)getComponent();
+        int textSize = textInput.getTextSize();
+
+        // TODO Localize?
+        // TODO Recalculate only when font changes
+        String testString = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
+
+        Rectangle2D testStringBounds = font.getStringBounds(testString, fontRenderContext);
+        int averageCharWidth = (int)Math.round((testStringBounds.getWidth() / testString.length()));
+
+        return textSize * averageCharWidth + (padding.left + padding.right) + 2;
+    }
+
+    public int getPreferredHeight(int width) {
+        // TODO Recalculate only when font changes
+        Rectangle2D maxCharBounds = font.getMaxCharBounds(fontRenderContext);
+        int maxCharHeight = (int)Math.ceil(maxCharBounds.getHeight());
+
+        return maxCharHeight + (padding.top + padding.bottom) + 2;
+    }
+
+    public Dimensions getPreferredSize() {
+        return new Dimensions(getPreferredWidth(-1), getPreferredHeight(-1));
+    }
+
+    public void layout() {
+        // No-op
+    }
+
+    public void paint(Graphics2D graphics) {
+        TextInput textInput = (TextInput)getComponent();
+
+        int width = getWidth();
+        int height = getHeight();
+
+        Color backgroundColor;
+        Color borderColor;
+        Color bevelColor;
+
+        if (textInput.isEnabled()) {
+            if (textInput.isTextValid()) {
+                backgroundColor = this.backgroundColor;
+                bevelColor = this.bevelColor;
+            } else {
+                backgroundColor = invalidBackgroundColor;
+                bevelColor = invalidBevelColor;
+            }
+
+            borderColor = this.borderColor;
+        } else {
+            backgroundColor = disabledBackgroundColor;
+            borderColor = disabledBorderColor;
+            bevelColor = disabledBevelColor;
+        }
+
+        graphics.setStroke(new BasicStroke());
+
+        // Paint the background
+        graphics.setPaint(backgroundColor);
+        graphics.fillRect(0, 0, width, height);
+
+        // Paint the bevel
+        graphics.setPaint(bevelColor);
+        graphics.drawLine(1, 1, width - 2, 1);
+
+        // Paint the border
+        graphics.setPaint(borderColor);
+        graphics.drawRect(0, 0, width - 1, height - 1);
+
+        // Paint the content
+        String text = getText();
+
+        boolean prompt = false;
+        if (text.length() == 0
+            && !textInput.isFocused()) {
+            text = textInput.getPrompt();
+
+            if (text == null) {
+                text = "";
+            } else {
+                prompt = true;
+            }
+        }
+
+        boolean textValid = textInput.isTextValid();
+
+        LineMetrics lm = font.getLineMetrics("", fontRenderContext);
+        int ascent = Math.round(lm.getAscent());
+
+        graphics.translate(padding.left - scrollLeft + 1, padding.top + ascent + 1);
+
+        if (text.length() > 0) {
+            // Paint the text
+            if (fontRenderContext.isAntiAliased()) {
+                graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+                    Platform.getTextAntialiasingHint());
+            }
+
+            if (fontRenderContext.usesFractionalMetrics()) {
+                graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
+                    RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+            }
+
+            Color color;
+            if (textInput.isEnabled()) {
+                if (prompt) {
+                    color = promptColor;
+                } else if (!textValid) {
+                    color = invalidColor;
+                } else {
+                    color = this.color;
+                }
+            } else {
+               color = disabledColor;
+            }
+
+            graphics.setFont(font);
+            graphics.setPaint(color);
+            graphics.drawString(text, 0, 0);
+
+            if (textInput.getSelectionLength() > 0) {
+                // Paint the selection
+                Graphics2D selectionGraphics = (Graphics2D)graphics.create();
+                selectionGraphics.clip(logicalHighlightShape.getBounds());
+
+                Color selectionColor;
+                Color selectionBackgroundColor;
+
+                if (textInput.isFocused()) {
+                    selectionColor = this.selectionColor;
+                    selectionBackgroundColor = this.selectionBackgroundColor;
+                } else {
+                    selectionColor = inactiveSelectionColor;
+                    selectionBackgroundColor = inactiveSelectionBackgroundColor;
+                }
+
+                selectionGraphics.setPaint(selectionBackgroundColor);
+                selectionGraphics.fill(logicalHighlightShape);
+
+                selectionGraphics.setPaint(selectionColor);
+                selectionGraphics.drawString(text, 0, 0);
+
+                selectionGraphics.dispose();
+            }
+        }
+
+        if (textInput.getSelectionLength() == 0
+            && textInput.isFocused()
+            && caretOn) {
+            Color color;
+            if (!textValid) {
+                color = invalidColor;
+            } else {
+                color = this.color;
+            }
+
+            graphics.setPaint(color);
+            graphics.draw(caretShapes[0]);
+        }
+    }
+
+    protected String getText() {
+        TextInput textInput = (TextInput)getComponent();
+
+        // TODO Use the internal character iterator instead of getting a copy
+        // of the string
+        String text = textInput.getText();
+
+        if (textInput.isPassword()) {
+            int n = text.length();
+            StringBuilder passwordTextBuilder = new StringBuilder(n);
+            for (int i = 0; i < n; i++) {
+                passwordTextBuilder.append("*");
+            }
+
+            text = passwordTextBuilder.toString();
+        }
+
+        return text;
+    }
+
+    protected int getInsertionIndex(String text, int x) {
+        TextLayout textLayout = new TextLayout(text, font, fontRenderContext);
+        TextHitInfo textHitInfo = textLayout.hitTestChar(x + scrollLeft - padding.left - 1, 0);
+        int index = textHitInfo.getInsertionIndex();
+
+        return index;
+    }
+
+    public void showCaret(boolean show) {
+        if (show) {
+            if (scheduledBlinkCursorCallback == null) {
+                scheduledBlinkCursorCallback =
+                    ApplicationContext.scheduleRecurringCallback(blinkCursorCallback,
+                        Platform.getCursorBlinkRate());
+
+                // Run the callback once now to show the cursor immediately
+                blinkCursorCallback.run();
+            }
+        } else {
+            if (scheduledBlinkCursorCallback != null) {
+                scheduledBlinkCursorCallback.cancel();
+                scheduledBlinkCursorCallback = null;
+            }
+        }
+    }
+
+    public Font getFont() {
+        return font;
+    }
+
+    public void setFont(Font font) {
+        if (font == null) {
+            throw new IllegalArgumentException("font is null.");
+        }
+
+        this.font = font;
+        invalidateComponent();
+    }
+
+    public final void setFont(String font) {
+        if (font == null) {
+            throw new IllegalArgumentException("font is null.");
+        }
+
+        setFont(Font.decode(font));
+    }
+
+    public Color getColor() {
+        return color;
+    }
+
+    public void setColor(Color color) {
+        if (color == null) {
+            throw new IllegalArgumentException("color is null.");
+        }
+
+        this.color = color;
+        repaintComponent();
+    }
+
+    public final void setColor(String color) {
+        if (color == null) {
+            throw new IllegalArgumentException("color is null.");
+        }
+
+        setColor(decodeColor(color));
+    }
+
+    public final void setColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setColor(theme.getColor(color));
+    }
+
+    public Color getPromptColor() {
+        return promptColor;
+    }
+
+    public void setPromptColor(Color promptColor) {
+        if (promptColor == null) {
+            throw new IllegalArgumentException("promptColor is null.");
+        }
+
+        this.promptColor = promptColor;
+        repaintComponent();
+    }
+
+    public final void setPromptColor(String promptColor) {
+        if (promptColor == null) {
+            throw new IllegalArgumentException("promptColor is null.");
+        }
+
+        setPromptColor(decodeColor(promptColor));
+    }
+
+    public final void setPromptColor(int promptColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setPromptColor(theme.getColor(promptColor));
+    }
+
+    public Color getDisabledColor() {
+        return disabledColor;
+    }
+
+    public void setDisabledColor(Color disabledColor) {
+        if (disabledColor == null) {
+            throw new IllegalArgumentException("disabledColor is null.");
+        }
+
+        this.disabledColor = disabledColor;
+        repaintComponent();
+    }
+
+    public final void setDisabledColor(String disabledColor) {
+        if (disabledColor == null) {
+            throw new IllegalArgumentException("disabledColor is null.");
+        }
+
+        setDisabledColor(decodeColor(disabledColor));
+    }
+
+    public final void setDisabledColor(int disabledColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setDisabledColor(theme.getColor(disabledColor));
+    }
+
+    public Color getBackgroundColor() {
+        return backgroundColor;
+    }
+
+    public void setBackgroundColor(Color backgroundColor) {
+        if (backgroundColor == null) {
+            throw new IllegalArgumentException("backgroundColor is null.");
+        }
+
+        this.backgroundColor = backgroundColor;
+        bevelColor = TerraTheme.darken(backgroundColor);
+        repaintComponent();
+    }
+
+    public final void setBackgroundColor(String backgroundColor) {
+        if (backgroundColor == null) {
+            throw new IllegalArgumentException("backgroundColor is null.");
+        }
+
+        setBackgroundColor(decodeColor(backgroundColor));
+    }
+
+    public final void setBackgroundColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBackgroundColor(theme.getColor(color));
+    }
+
+    public Color getInvalidColor() {
+        return invalidColor;
+    }
+
+    public void setInvalidColor(Color color) {
+        if (color == null) {
+            throw new IllegalArgumentException("color is null.");
+        }
+
+        this.invalidColor = color;
+        repaintComponent();
+    }
+
+    public final void setInvalidColor(String color) {
+        if (color == null) {
+            throw new IllegalArgumentException("color is null.");
+        }
+
+        setInvalidColor(decodeColor(color));
+    }
+
+    public final void setInvalidColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setInvalidColor(theme.getColor(color));
+    }
+
+    public Color getInvalidBackgroundColor() {
+        return invalidBackgroundColor;
+    }
+
+    public void setInvalidBackgroundColor(Color color) {
+        if (color == null) {
+            throw new IllegalArgumentException("color is null.");
+        }
+
+        this.invalidBackgroundColor = color;
+        invalidBevelColor = TerraTheme.darken(color);
+        repaintComponent();
+    }
+
+    public final void setInvalidBackgroundColor(String color) {
+        if (color == null) {
+            throw new IllegalArgumentException("invalidBackgroundColor is null.");
+        }
+
+        setInvalidBackgroundColor(decodeColor(color));
+    }
+
+    public final void setInvalidBackgroundColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setInvalidBackgroundColor(theme.getColor(color));
+    }
+
+    public Color getDisabledBackgroundColor() {
+        return disabledBackgroundColor;
+    }
+
+    public void setDisabledBackgroundColor(Color disabledBackgroundColor) {
+        if (disabledBackgroundColor == null) {
+            throw new IllegalArgumentException("disabledBackgroundColor is null.");
+        }
+
+        this.disabledBackgroundColor = disabledBackgroundColor;
+        disabledBevelColor = disabledBackgroundColor;
+        repaintComponent();
+    }
+
+    public final void setDisabledBackgroundColor(String disabledBackgroundColor) {
+        if (disabledBackgroundColor == null) {
+            throw new IllegalArgumentException("disabledBackgroundColor is null.");
+        }
+
+        setDisabledBackgroundColor(decodeColor(disabledBackgroundColor));
+    }
+
+    public final void setDisabledBackgroundColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setDisabledBackgroundColor(theme.getColor(color));
+    }
+
+    public Color getBorderColor() {
+        return borderColor;
+    }
+
+    public void setBorderColor(Color borderColor) {
+        if (borderColor == null) {
+            throw new IllegalArgumentException("borderColor is null.");
+        }
+
+        this.borderColor = borderColor;
+        repaintComponent();
+    }
+
+    public final void setBorderColor(String borderColor) {
+        if (borderColor == null) {
+            throw new IllegalArgumentException("borderColor is null.");
+        }
+
+        setBorderColor(decodeColor(borderColor));
+    }
+
+    public final void setBorderColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBorderColor(theme.getColor(color));
+    }
+
+    public Color getDisabledBorderColor() {
+        return disabledBorderColor;
+    }
+
+    public void setDisabledBorderColor(Color disabledBorderColor) {
+        if (disabledBorderColor == null) {
+            throw new IllegalArgumentException("disabledBorderColor is null.");
+        }
+
+        this.disabledBorderColor = disabledBorderColor;
+        repaintComponent();
+    }
+
+    public final void setDisabledBorderColor(String disabledBorderColor) {
+        if (disabledBorderColor == null) {
+            throw new IllegalArgumentException("disabledBorderColor is null.");
+        }
+
+        setDisabledBorderColor(decodeColor(disabledBorderColor));
+    }
+
+    public final void setDisabledBorderColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setDisabledBorderColor(theme.getColor(color));
+    }
+
+    public Color getSelectionColor() {
+        return selectionColor;
+    }
+
+    public void setSelectionColor(Color selectionColor) {
+        if (selectionColor == null) {
+            throw new IllegalArgumentException("selectionColor is null.");
+        }
+
+        this.selectionColor = selectionColor;
+        repaintComponent();
+    }
+
+    public final void setSelectionColor(String selectionColor) {
+        if (selectionColor == null) {
+            throw new IllegalArgumentException("selectionColor is null.");
+        }
+
+        setSelectionColor(decodeColor(selectionColor));
+    }
+
+    public final void setSelectionColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setSelectionColor(theme.getColor(color));
+    }
+
+    public Color getSelectionBackgroundColor() {
+        return selectionBackgroundColor;
+    }
+
+    public void setSelectionBackgroundColor(Color selectionBackgroundColor) {
+        if (selectionBackgroundColor == null) {
+            throw new IllegalArgumentException("selectionBackgroundColor is null.");
+        }
+
+        this.selectionBackgroundColor = selectionBackgroundColor;
+        repaintComponent();
+    }
+
+    public final void setSelectionBackgroundColor(String selectionBackgroundColor) {
+        if (selectionBackgroundColor == null) {
+            throw new IllegalArgumentException("selectionBackgroundColor is null.");
+        }
+
+        setSelectionBackgroundColor(decodeColor(selectionBackgroundColor));
+    }
+
+    public final void setSelectionBackgroundColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setSelectionBackgroundColor(theme.getColor(color));
+    }
+
+    public Color getInactiveSelectionColor() {
+        return inactiveSelectionColor;
+    }
+
+    public void setInactiveSelectionColor(Color inactiveSelectionColor) {
+        if (inactiveSelectionColor == null) {
+            throw new IllegalArgumentException("inactiveSelectionColor is null.");
+        }
+
+        this.inactiveSelectionColor = inactiveSelectionColor;
+        repaintComponent();
+    }
+
+    public final void setInactiveSelectionColor(String inactiveSelectionColor) {
+        if (inactiveSelectionColor == null) {
+            throw new IllegalArgumentException("inactiveSelectionColor is null.");
+        }
+
+        setInactiveSelectionColor(decodeColor(inactiveSelectionColor));
+    }
+
+    public final void setInactiveSelectionColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setInactiveSelectionColor(theme.getColor(color));
+    }
+
+    public Color getInactiveSelectionBackgroundColor() {
+        return inactiveSelectionBackgroundColor;
+    }
+
+    public void setInactiveSelectionBackgroundColor(Color inactiveSelectionBackgroundColor) {
+        if (inactiveSelectionBackgroundColor == null) {
+            throw new IllegalArgumentException("inactiveSelectionBackgroundColor is null.");
+        }
+
+        this.inactiveSelectionBackgroundColor = inactiveSelectionBackgroundColor;
+        repaintComponent();
+    }
+
+    public final void setInactiveSelectionBackgroundColor(String inactiveSelectionBackgroundColor) {
+        if (inactiveSelectionBackgroundColor == null) {
+            throw new IllegalArgumentException("inactiveSelectionBackgroundColor is null.");
+        }
+
+        setInactiveSelectionBackgroundColor(decodeColor(inactiveSelectionBackgroundColor));
+    }
+
+    public final void setInactiveSelectionBackgroundColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setInactiveSelectionBackgroundColor(theme.getColor(color));
+    }
+
+    public Insets getPadding() {
+        return padding;
+    }
+
+    public void setPadding(Insets padding) {
+        if (padding == null) {
+            throw new IllegalArgumentException("padding is null.");
+        }
+
+        this.padding = padding;
+        invalidateComponent();
+    }
+
+    public final void setPadding(Dictionary<String, ?> padding) {
+        if (padding == null) {
+            throw new IllegalArgumentException("padding is null.");
+        }
+
+        setPadding(new Insets(padding));
+    }
+
+    public final void setPadding(int padding) {
+        setPadding(new Insets(padding));
+    }
+
+    public final void setPadding(Number padding) {
+        if (padding == null) {
+            throw new IllegalArgumentException("padding is null.");
+        }
+
+        setPadding(padding.intValue());
+    }
+
+    @Override
+    public boolean mouseMove(Component component, int x, int y) {
+        boolean consumed = super.mouseMove(component, x, y);
+
+        if (Mouse.getCapturer() == component) {
+            String text = getText();
+
+            if (text.length() > 0) {
+                TextInput textInput = (TextInput)getComponent();
+
+                if (x >= 0
+                    && x < textInput.getWidth()) {
+                    // Stop the scroll selection timer
+                    if (scheduledScrollSelectionCallback != null) {
+                        scheduledScrollSelectionCallback.cancel();
+                        scheduledScrollSelectionCallback = null;
+                    }
+
+                    // Get the current selection
+                    int selectionStart = textInput.getSelectionStart();
+                    int selectionLength = textInput.getSelectionLength();
+
+                    // Get the insertion index
+                    int index = getInsertionIndex(text, x);
+
+                    if (index < selectionStart) {
+                        selectionLength += (selectionStart - index);
+                        selectionStart = index;
+                    } else {
+                        if (index > selectionStart + selectionLength) {
+                            selectionLength = index - selectionStart;
+                        }
+                    }
+
+                    textInput.setSelection(selectionStart, selectionLength);
+                } else {
+                    scrollSelectionCallback.x = x;
+
+                    if (scheduledScrollSelectionCallback == null) {
+                        scheduledScrollSelectionCallback =
+                            ApplicationContext.scheduleRecurringCallback(scrollSelectionCallback,
+                                SCROLL_RATE);
+
+                        // Run the callback once now to scroll the selection immediately
+                        scrollSelectionCallback.run();
+                    }
+                }
+            }
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
+        if (button == Mouse.Button.LEFT) {
+            // Move the caret to the insertion point
+            TextInput textInput = (TextInput)getComponent();
+            String text = getText();
+
+            if (text.length() > 0) {
+                int index = getInsertionIndex(text, x);
+                textInput.setSelection(index, 0);
+            }
+
+            // Set focus to the text input
+            textInput.requestFocus();
+
+            // Capture the mouse so we can select text
+            Mouse.capture(component);
+        }
+
+        return super.mouseDown(component, button, x, y);
+    }
+
+    @Override
+    public boolean mouseUp(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseUp(component, button, x, y);
+
+        if (Mouse.getCapturer() == component) {
+            // Stop the scroll selection timer
+            if (scheduledScrollSelectionCallback != null) {
+                scheduledScrollSelectionCallback.cancel();
+                scheduledScrollSelectionCallback = null;
+            }
+
+            Mouse.release();
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+        if (button == Mouse.Button.LEFT
+            && count > 1) {
+            TextInput textInput = (TextInput)getComponent();
+            TextNode textNode = textInput.getTextNode();
+            textInput.setSelection(0, textNode.getCharacterCount());
+        }
+
+        return super.mouseClick(component, button, x, y, count);
+    }
+
+    @Override
+    public boolean keyTyped(Component component, char character) {
+        boolean consumed = super.keyTyped(component, character);
+
+        // Ignore characters in the control range and the ASCII delete
+        // character
+        if (character > 0x1F
+            && character != 0x7F) {
+            TextInput textInput = (TextInput)getComponent();
+            TextNode textNode = textInput.getTextNode();
+
+            if (textNode.getCharacterCount() < textInput.getMaximumLength()) {
+                textInput.insertText(character, textInput.getSelectionStart());
+            } else {
+                ApplicationContext.beep();
+            }
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        TextInput textInput = (TextInput)getComponent();
+        TextNode textNode = textInput.getTextNode();
+
+        if (keyCode == Keyboard.KeyCode.DELETE) {
+            textInput.delete(Direction.FORWARD);
+        } else if (keyCode == Keyboard.KeyCode.BACKSPACE) {
+            textInput.delete(Direction.BACKWARD);
+        } else if (keyCode == Keyboard.KeyCode.LEFT) {
+            int selectionStart = textInput.getSelectionStart();
+            int selectionLength = textInput.getSelectionLength();
+
+            if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)
+                && Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+                // Add all preceding text to the selection
+                selectionLength = selectionStart + selectionLength;
+                selectionStart = 0;
+            } else if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
+                // Add the previous character to the selection
+                if (selectionStart > 0) {
+                    selectionStart--;
+                    selectionLength++;
+                }
+            } else if (Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+                // Clear the selection and move the caret to the beginning of
+                // the text
+                selectionStart = 0;
+                selectionLength = 0;
+            } else {
+                // Clear the selection and move the caret back by one
+                // character
+                if (selectionLength == 0
+                    && selectionStart > 0) {
+                    selectionStart--;
+                }
+
+                selectionLength = 0;
+            }
+
+            textInput.setSelection(selectionStart, selectionLength);
+        } else if (keyCode == Keyboard.KeyCode.RIGHT) {
+            int selectionStart = textInput.getSelectionStart();
+            int selectionLength = textInput.getSelectionLength();
+
+            if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)
+                && Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+                // Add all subsequent text to the selection
+                selectionLength = textNode.getCharacterCount() - selectionStart;
+            } else if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
+                // Add the next character to the selection
+                if (selectionStart + selectionLength < textNode.getCharacterCount()) {
+                    selectionLength++;
+                }
+            } else if (Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+                // Clear the selection and move the caret to the end of
+                // the text
+                selectionStart = textNode.getCharacterCount();
+                selectionLength = 0;
+            } else {
+                // Clear the selection and move the caret forward by one
+                // character
+                selectionStart += selectionLength;
+
+                if (selectionLength == 0
+                    && selectionStart < textNode.getCharacterCount()) {
+                    selectionStart++;
+                }
+
+                selectionLength = 0;
+            }
+
+            textInput.setSelection(selectionStart, selectionLength);
+        } else if (keyCode == Keyboard.KeyCode.A
+            && Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+            // Select all
+            textInput.setSelection(0, textNode.getCharacterCount());
+        } else if (keyCode == Keyboard.KeyCode.X
+            && Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+            if (textInput.isPassword()) {
+                ApplicationContext.beep();
+            } else {
+                textInput.cut();
+            }
+        } else if (keyCode == Keyboard.KeyCode.C
+            && Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+            if (textInput.isPassword()) {
+                ApplicationContext.beep();
+            } else {
+                textInput.copy();
+            }
+        } else if (keyCode == Keyboard.KeyCode.V
+            && Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+            textInput.paste();
+        } else {
+            consumed = super.keyPressed(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+    // Component state events
+    @Override
+    public void enabledChanged(Component component) {
+        super.enabledChanged(component);
+
+        repaintComponent();
+    }
+
+    @Override
+    public void focusedChanged(Component component, boolean temporary) {
+        super.focusedChanged(component, temporary);
+
+        TextInput textInput = (TextInput)getComponent();
+        TextNode textNode = textInput.getTextNode();
+
+        if (component.isFocused()) {
+            showCaret(textInput.getSelectionLength() == 0);
+
+            if (!temporary
+                && Mouse.getCapturer() != component) {
+                textInput.setSelection(0, textNode.getCharacterCount());
+            }
+        } else {
+            if (!temporary) {
+                textInput.setSelection(textInput.getSelectionStart()
+                    + textInput.getSelectionLength(), 0);
+            }
+
+            showCaret(false);
+        }
+
+        repaintComponent();
+    }
+
+    // Text input events
+    public void textNodeChanged(TextInput textInput, TextNode previousTextNode) {
+        updateSelection();
+    }
+
+    public void textSizeChanged(TextInput textInput, int previousTextSize) {
+        invalidateComponent();
+    }
+
+    public void maximumLengthChanged(TextInput textInput, int previousMaximumLength) {
+        // No-op
+    }
+
+    public void passwordChanged(TextInput textInput) {
+        repaintComponent();
+    }
+
+    public void promptChanged(TextInput textInput, String previousPrompt) {
+      repaintComponent();
+    }
+
+    public void textKeyChanged(TextInput textInput, String previousTextKey) {
+        // No-op
+    }
+
+    public void textValidChanged(TextInput textInput) {
+        repaintComponent();
+    }
+
+    public void textValidatorChanged(TextInput textInput, Validator previousValidator) {
+        // No-op
+    }
+
+    // Text input character events
+    public void charactersInserted(TextInput textInput, int index, int count) {
+        updateSelection();
+    }
+
+    public void charactersRemoved(TextInput textInput, int index, int count) {
+        String text = getText();
+        Rectangle2D textBounds = font.getStringBounds(text, fontRenderContext);
+
+        int textWidth = (int)textBounds.getWidth();
+        int width = getWidth();
+
+        // If the right edge of the text is less than the right inset, align
+        // the text's right edge with the inset
+        if (textWidth - scrollLeft + padding.left + 1 < width - padding.right - 1) {
+            scrollLeft = Math.max(textWidth + (padding.left + padding.right + 2) - width, 0);
+        }
+
+        updateSelection();
+    }
+
+    // Text input selection events
+    public void selectionChanged(TextInput textInput, int previousSelectionStart,
+        int previousSelectionLength) {
+        updateSelection();
+    }
+
+    private void updateSelection() {
+        // Update the selection bounding box
+        String text = getText();
+
+        // NOTE For some reason, TextLayout does not accept zero-length
+        // strings. This may be a bug in AWT, since an empty string should be
+        // valid, and is necessary to determine the caret shape for an empty
+        // text input.
+        // TODO Report this issue to Sun?
+        if (text.length() == 0) {
+            text = " ";
+        }
+
+        TextInput textInput = (TextInput)getComponent();
+
+        int selectionStart = textInput.getSelectionStart();
+        int selectionLength = textInput.getSelectionLength();
+
+        TextLayout textLayout = new TextLayout(text, font, fontRenderContext);
+
+        caretShapes = textLayout.getCaretShapes(selectionStart);
+        logicalHighlightShape = textLayout.getLogicalHighlightShape(selectionStart,
+            selectionStart + selectionLength);
+
+        int width = getWidth();
+
+        if (width <= padding.left + padding.right + 2) {
+            scrollLeft = 0;
+        } else {
+            if (textInput.getSelectionLength() == 0) {
+                Rectangle2D caretBounds = caretShapes[0].getBounds();
+                int caretLeft = (int)caretBounds.getX();
+
+                if (caretLeft - scrollLeft < 0) {
+                    // Ensure that the left edge of caret is visible
+                    scrollLeft = caretLeft;
+                } else {
+                    // Ensure that the right edge of the caret is visible
+                    int caretRight = (int)caretBounds.getMaxX();
+
+                    if (caretRight - scrollLeft + padding.left + 1 > width - padding.right - 1) {
+                        scrollLeft = Math.max(caretRight
+                            - (width - (padding.left + padding.right + 2)), 0);
+                    }
+                }
+            } else {
+                Rectangle2D logicalHighlightBounds = logicalHighlightShape.getBounds();
+                int logicalHighlightLeft = (int)logicalHighlightBounds.getX();
+
+                if (logicalHighlightLeft - scrollLeft < 0) {
+                    // Ensure that the left edge of the highlight is visible
+                    scrollLeft = logicalHighlightLeft;
+                } else {
+                    // Ensure that the right edge of the highlight is visible
+                    int logicalHighlightRight = (int)logicalHighlightBounds.getMaxX();
+
+                    if (logicalHighlightRight - scrollLeft + padding.left + 1 > width - padding.right - 1) {
+                        scrollLeft = Math.max(logicalHighlightRight
+                            - (width - (padding.left + padding.right + 2)), 0);
+                    }
+                }
+            }
+        }
+
+        showCaret(textInput.isFocused()
+            && textInput.getSelectionLength() == 0);
+
+        repaintComponent();
+    }
+}