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 [45/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/TerraTreeViewSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTreeViewSkin.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTreeViewSkin.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTreeViewSkin.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,1882 @@
+/*
+ * 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 java.awt.RenderingHints;
+import java.awt.geom.GeneralPath;
+
+import pivot.collections.ArrayList;
+import pivot.collections.List;
+import pivot.collections.Sequence;
+import pivot.wtk.Button;
+import pivot.wtk.Checkbox;
+import pivot.wtk.Component;
+import pivot.wtk.Dimensions;
+import pivot.wtk.Keyboard;
+import pivot.wtk.Mouse;
+import pivot.wtk.Bounds;
+import pivot.wtk.Theme;
+import pivot.wtk.TreeView;
+import pivot.wtk.TreeViewListener;
+import pivot.wtk.TreeViewBranchListener;
+import pivot.wtk.TreeViewNodeListener;
+import pivot.wtk.TreeViewNodeStateListener;
+import pivot.wtk.TreeViewSelectionListener;
+import pivot.wtk.skin.ComponentSkin;
+
+/**
+ * Tree view skin.
+ *
+ * @author tvolkert
+ */
+public class TerraTreeViewSkin extends ComponentSkin implements TreeView.Skin,
+    TreeViewListener, TreeViewBranchListener, TreeViewNodeListener,
+    TreeViewNodeStateListener, TreeViewSelectionListener{
+
+    /**
+     * An internal data structure that keeps track of skin-related metadata
+     * for a tree node. The justification for the existence of this class lies
+     * in the <tt>visibleNodes</tt> data structure, which is a flat list of
+     * nodes that are visible at any given time. In this context, visible means
+     * that their parent hierarchy is expanded, <b>not</b> that they are being
+     * painted. This list, combined with <tt>getNodeHeight()</tt>, enables us
+     * to quickly determine which nodes to paint given a graphics clip rect.
+     * It also enables us to quickly traverse the tree view when handling key
+     * events.
+     * <p>
+     * NOTE: some of this data is managed by <tt>TreeView</tt> and cached here
+     * to provide further optimizations during painting and user input.
+     *
+     * @author tvolkert
+     */
+    protected static class NodeInfo {
+        // Core skin metadata
+        private BranchInfo parent;
+
+        protected Object data;
+        protected int depth;
+
+        // Cached fields. Note that this is maintained as a bitmask in favor of
+        // separate properties because it allows us to easily clear any cached
+        // field for all nodes in one common method. See #clearField(byte)
+        protected byte fields = 0;
+
+        public static final byte HIGHLIGHTED_MASK = 1 << 0;
+        public static final byte SELECTED_MASK = 1 << 1;
+        public static final byte DISABLED_MASK = 1 << 2;
+        public static final byte CHECK_STATE_CHECKED_MASK = 1 << 3;
+        public static final byte CHECK_STATE_MIXED_MASK = 1 << 4;
+
+        public static final byte CHECK_STATE_MASK = CHECK_STATE_CHECKED_MASK | CHECK_STATE_MIXED_MASK;
+
+        public NodeInfo(BranchInfo parent, Object data) {
+            this.parent = parent;
+            this.data = data;
+
+            depth = (parent == null) ? 0 : parent.depth + 1;
+        }
+
+        @SuppressWarnings("unchecked")
+        public static NodeInfo createNew(BranchInfo parent, Object data) {
+            NodeInfo nodeInfo = null;
+
+            if (data instanceof List) {
+                nodeInfo = new BranchInfo(parent, (List<Object>)data);
+            } else {
+                nodeInfo = new NodeInfo(parent, data);
+            }
+
+            return nodeInfo;
+        }
+
+        @SuppressWarnings("unchecked")
+        public Sequence<Integer> getPath() {
+            Sequence<Integer> path = new ArrayList<Integer>(depth);
+
+            NodeInfo nodeInfo = this;
+
+            while (nodeInfo.parent != null) {
+                List<Object> parentData = (List<Object>)nodeInfo.parent.data;
+                int index = parentData.indexOf(nodeInfo.data);
+                path.insert(index, 0);
+
+                nodeInfo = nodeInfo.parent;
+            }
+
+            return path;
+        }
+
+        public boolean isHighlighted() {
+            return ((fields & HIGHLIGHTED_MASK) != 0);
+        }
+
+        public void setHighlighted(boolean highlighted) {
+            if (highlighted) {
+                fields |= HIGHLIGHTED_MASK;
+            } else {
+                fields &= ~HIGHLIGHTED_MASK;
+            }
+        }
+
+        public boolean isSelected() {
+            return ((fields & SELECTED_MASK) != 0);
+        }
+
+        public void setSelected(boolean selected) {
+            if (selected) {
+                fields |= SELECTED_MASK;
+            } else {
+                fields &= ~SELECTED_MASK;
+            }
+        }
+
+        public boolean isDisabled() {
+            return ((fields & DISABLED_MASK) != 0);
+        }
+
+        public void setDisabled(boolean disabled) {
+            if (disabled) {
+                fields |= DISABLED_MASK;
+            } else {
+                fields &= ~DISABLED_MASK;
+            }
+        }
+
+        public TreeView.NodeCheckState getCheckState() {
+            TreeView.NodeCheckState checkState;
+
+            switch (fields & CHECK_STATE_MASK) {
+            case CHECK_STATE_CHECKED_MASK:
+                checkState = TreeView.NodeCheckState.CHECKED;
+                break;
+            case CHECK_STATE_MIXED_MASK:
+                checkState = TreeView.NodeCheckState.MIXED;
+                break;
+            default:
+                checkState = TreeView.NodeCheckState.UNCHECKED;
+                break;
+            }
+
+            return checkState;
+        }
+
+        public boolean isChecked() {
+            return ((fields & CHECK_STATE_CHECKED_MASK) != 0);
+        }
+
+        public void setCheckState(TreeView.NodeCheckState checkState) {
+            fields &= ~CHECK_STATE_MASK;
+
+            switch (checkState) {
+            case CHECKED:
+                fields |= CHECK_STATE_CHECKED_MASK;
+                break;
+            case MIXED:
+                fields |= CHECK_STATE_MIXED_MASK;
+                break;
+            }
+        }
+
+        public void clearField(byte mask) {
+            fields &= ~mask;
+        }
+    }
+
+    /**
+     * An internal data structure that keeps track of skin-related metadata
+     * for a tree branch.
+     *
+     * @author tvolkert
+     */
+    protected static class BranchInfo extends NodeInfo {
+        // Core skin metadata
+        public Sequence<NodeInfo> children = null;
+
+        public static final byte EXPANDED_MASK = 1 << 5;
+
+        public BranchInfo(BranchInfo parent, List<Object> data) {
+            super(parent, data);
+        }
+
+        /**
+         * Loads this branch info's children. The children list is initialized
+         * to <tt>null</tt> and loaded lazily to allow the skin to only create
+         * <tt>NodeInfo</tt> objects for the nodes that it actually needs in
+         * order to paint. Thus, it is the responsibility of the skin to check
+         * if <tt>children</tt> is null and call <tt>loadChildren()</tt> if
+         * necessary.
+         */
+        @SuppressWarnings("unchecked")
+        public void loadChildren() {
+            if (children == null) {
+                List<Object> data = (List<Object>)this.data;
+                int count = data.getLength();
+
+                children = new ArrayList<NodeInfo>(count);
+
+                for (int i = 0; i < count; i++) {
+                    Object nodeData = data.get(i);
+                    children.add(NodeInfo.createNew(this, nodeData));
+                }
+            }
+        }
+
+        public boolean isExpanded() {
+            return ((fields & EXPANDED_MASK) != 0);
+        }
+
+        public void setExpanded(boolean expanded) {
+            if (expanded) {
+                fields |= EXPANDED_MASK;
+            } else {
+                fields &= ~EXPANDED_MASK;
+            }
+        }
+    }
+
+    private BranchInfo rootBranchInfo = null;
+    private List<NodeInfo> visibleNodes = new ArrayList<NodeInfo>();
+
+    private NodeInfo highlightedNode = null;
+    private NodeInfo editNode = null;
+
+    // Styles
+    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 highlightColor;
+    private Color highlightBackgroundColor;
+    private int spacing;
+    private int indent;
+    private boolean showHighlight;
+    private boolean showBranchControls;
+    private Color branchControlColor;
+    private Color branchControlDisabledColor;
+    private Color branchControlSelectionColor;
+    private Color branchControlInactiveSelectionColor;
+    private Color gridColor;
+    private boolean showGridLines;
+
+    private static final int BRANCH_CONTROL_IMAGE_WIDTH = 8;
+    private static final int BRANCH_CONTROL_IMAGE_HEIGHT = 8;
+    private static final int VERTICAL_SPACING = 1;
+
+    private static final Checkbox CHECKBOX = new Checkbox();
+    private static final int CHECKBOX_VERTICAL_PADDING = 2;
+
+    static {
+        CHECKBOX.setSize(CHECKBOX.getPreferredSize());
+        CHECKBOX.setTriState(true);
+    }
+
+    public TerraTreeViewSkin() {
+        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(10);
+        highlightColor = theme.getColor(1);
+        highlightBackgroundColor = theme.getColor(10);
+        spacing = 6;
+        indent = 16;
+        showHighlight = true;
+        showBranchControls = true;
+        branchControlColor = theme.getColor(18);
+        branchControlDisabledColor = theme.getColor(19);
+        branchControlSelectionColor = theme.getColor(4);
+        branchControlInactiveSelectionColor = theme.getColor(19);
+        gridColor = theme.getColor(11);
+        showGridLines = false;
+    }
+
+    public void install(Component component) {
+        super.install(component);
+
+        TreeView treeView = (TreeView)component;
+        treeView.getTreeViewListeners().add(this);
+        treeView.getTreeViewBranchListeners().add(this);
+        treeView.getTreeViewNodeListeners().add(this);
+        treeView.getTreeViewNodeStateListeners().add(this);
+        treeView.getTreeViewSelectionListeners().add(this);
+
+        treeDataChanged(treeView, null);
+    }
+
+    public void uninstall() {
+        TreeView treeView = (TreeView)getComponent();
+        treeView.getTreeViewListeners().remove(this);
+        treeView.getTreeViewBranchListeners().remove(this);
+        treeView.getTreeViewNodeListeners().remove(this);
+        treeView.getTreeViewNodeStateListeners().remove(this);
+        treeView.getTreeViewSelectionListeners().remove(this);
+
+        super.uninstall();
+    }
+
+    public int getPreferredWidth(int height) {
+        TreeView treeView = (TreeView)getComponent();
+        TreeView.NodeRenderer nodeRenderer = treeView.getNodeRenderer();
+
+        int preferredWidth = 0;
+
+        for (int i = 0, n = visibleNodes.getLength(); i < n; i++) {
+            NodeInfo nodeInfo = visibleNodes.get(i);
+
+            int nodeWidth = (nodeInfo.depth - 1) * (indent + spacing);
+
+            nodeRenderer.render(nodeInfo.data, treeView, false, false,
+                TreeView.NodeCheckState.UNCHECKED, false, false);
+            nodeWidth += nodeRenderer.getPreferredWidth(-1);
+
+            preferredWidth = Math.max(preferredWidth, nodeWidth);
+        }
+
+        if (showBranchControls) {
+            preferredWidth += indent + spacing;
+        }
+
+        if (treeView.getCheckmarksEnabled()) {
+            preferredWidth += Math.max(CHECKBOX.getWidth(), indent) + spacing;
+        }
+
+        return preferredWidth;
+    }
+
+    public int getPreferredHeight(int width) {
+        int nodeHeight = getNodeHeight();
+        int visibleNodeCount = visibleNodes.getLength();
+
+        int preferredHeight = nodeHeight * visibleNodeCount;
+
+        if (visibleNodeCount > 1) {
+            preferredHeight += VERTICAL_SPACING * (visibleNodeCount - 1);
+        }
+
+        return preferredHeight;
+    }
+
+    public Dimensions getPreferredSize() {
+        return new Dimensions(getPreferredWidth(-1), getPreferredHeight(-1));
+    }
+
+    public void layout() {
+        // No-op
+    }
+
+    public void paint(Graphics2D graphics) {
+        TreeView treeView = (TreeView)getComponent();
+        TreeView.NodeRenderer nodeRenderer = treeView.getNodeRenderer();
+
+        int width = getWidth();
+        int height = getHeight();
+
+        int nodeHeight = getNodeHeight();
+
+        // Paint the background
+        graphics.setPaint(backgroundColor);
+        graphics.fillRect(0, 0, width, height);
+
+        // nodeStart and nodeEnd are both inclusive
+        int nodeStart = 0;
+        int nodeEnd = visibleNodes.getLength() - 1;
+
+        // Ensure that we only paint items that are visible
+        Rectangle clipBounds = graphics.getClipBounds();
+        if (clipBounds != null) {
+            nodeStart = Math.max(nodeStart, (int)(clipBounds.y
+                / (double)(nodeHeight + VERTICAL_SPACING)));
+            nodeEnd = Math.min(nodeEnd, (int)((clipBounds.y +
+                clipBounds.height) / (double)(nodeHeight + VERTICAL_SPACING)));
+        }
+
+        BasicStroke gridStroke = new BasicStroke();
+
+        int nodeY = nodeStart * (nodeHeight + VERTICAL_SPACING);
+
+        for (int i = nodeStart; i <= nodeEnd; i++) {
+            NodeInfo nodeInfo = visibleNodes.get(i);
+
+            boolean expanded = false;
+            boolean highlighted = nodeInfo.isHighlighted();
+            boolean selected = nodeInfo.isSelected();
+            boolean disabled = nodeInfo.isDisabled();
+
+            int nodeX = (nodeInfo.depth - 1) * (indent + spacing);
+
+            if (treeView.isEnabled()) {
+                if (selected) {
+                    // Paint the selection state
+                    Color selectionBackgroundColor = treeView.isFocused() ?
+                        this.selectionBackgroundColor : inactiveSelectionBackgroundColor;
+                    graphics.setPaint(selectionBackgroundColor);
+                    graphics.fillRect(0, nodeY, width, nodeHeight);
+                } else if (highlighted && !disabled) {
+                    // Paint the highlight state
+                    graphics.setPaint(highlightBackgroundColor);
+                    graphics.fillRect(0, nodeY, width, nodeHeight);
+                }
+            }
+
+            // Paint the expand/collapse control
+            if (showBranchControls) {
+                if (nodeInfo instanceof BranchInfo) {
+                    BranchInfo branchInfo = (BranchInfo)nodeInfo;
+                    expanded = branchInfo.isExpanded();
+
+                    Color branchControlColor;
+
+                    if (treeView.isEnabled()
+                        && !disabled) {
+                        if (selected) {
+                            if (treeView.isFocused()) {
+                                branchControlColor = branchControlSelectionColor;
+                            } else {
+                                branchControlColor = branchControlInactiveSelectionColor;
+                            }
+                        } else {
+                            branchControlColor = this.branchControlColor;
+                        }
+                    } else {
+                        branchControlColor = branchControlDisabledColor;
+                    }
+
+                    GeneralPath shape = new GeneralPath();
+
+                    int imageX = nodeX + (indent - BRANCH_CONTROL_IMAGE_WIDTH) / 2;
+                    int imageY = nodeY + (nodeHeight - BRANCH_CONTROL_IMAGE_HEIGHT) / 2;
+
+                    if (expanded) {
+                        shape.moveTo(imageX, imageY + 1);
+                        shape.lineTo(imageX + 8, imageY + 1);
+                        shape.lineTo(imageX + 4, imageY + 7);
+                    } else {
+                        shape.moveTo(imageX + 1, imageY);
+                        shape.lineTo(imageX + 7, imageY + 4);
+                        shape.lineTo(imageX + 1, imageY + 8);
+                    }
+
+                    shape.closePath();
+
+                    graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                        RenderingHints.VALUE_ANTIALIAS_ON);
+                    graphics.setPaint(branchControlColor);
+
+                    graphics.fill(shape);
+                }
+
+                nodeX += indent + spacing;
+            }
+
+            // Paint the checkbox
+            TreeView.NodeCheckState checkState = TreeView.NodeCheckState.UNCHECKED;
+            if (treeView.getCheckmarksEnabled()) {
+                checkState = nodeInfo.getCheckState();
+
+                int checkboxWidth = CHECKBOX.getWidth();
+                int checkboxHeight = CHECKBOX.getHeight();
+
+                int checkboxX = Math.max(indent - checkboxWidth, 0) / 2;
+                int checkboxY = (nodeHeight - checkboxHeight) / 2;
+                Graphics2D checkboxGraphics = (Graphics2D)graphics.create(nodeX + checkboxX,
+                    nodeY + checkboxY, checkboxWidth, checkboxHeight);
+
+                Button.State state;
+                switch (checkState) {
+                case CHECKED:
+                    state = Button.State.SELECTED;
+                    break;
+                case MIXED:
+                    state = Button.State.MIXED;
+                    break;
+                default:
+                    state = Button.State.UNSELECTED;
+                    break;
+                }
+
+                CHECKBOX.setState(state);
+                CHECKBOX.paint(checkboxGraphics);
+                checkboxGraphics.dispose();
+
+                nodeX += Math.max(indent, checkboxWidth) + spacing;
+            }
+
+            int nodeWidth = width - nodeX;
+
+            // Paint the node data
+            Graphics2D rendererGraphics = (Graphics2D)graphics.create(nodeX, nodeY,
+                nodeWidth, nodeHeight);
+            nodeRenderer.render(nodeInfo.data, treeView, expanded, selected,
+                checkState, highlighted, disabled);
+            nodeRenderer.setSize(nodeWidth, nodeHeight);
+            nodeRenderer.paint(rendererGraphics);
+            rendererGraphics.dispose();
+
+            // Paint the grid line
+            if (showGridLines) {
+                graphics.setStroke(gridStroke);
+                graphics.setPaint(gridColor);
+
+                graphics.drawLine(0, nodeY + nodeHeight, width, nodeY + nodeHeight);
+            }
+
+            nodeY += nodeHeight + VERTICAL_SPACING;
+        }
+    }
+
+    public Font getFont() {
+        return font;
+    }
+
+    public void setFont(Font font) {
+        if (font == null) {
+            throw new IllegalArgumentException("font is null.");
+        }
+
+        this.font = font;
+        invalidateComponent();
+    }
+
+    public 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 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 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 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 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 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 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 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 getHighlightColor() {
+        return highlightColor;
+    }
+
+    public void setHighlightColor(Color highlightColor) {
+        if (highlightColor == null) {
+            throw new IllegalArgumentException("highlightColor is null.");
+        }
+
+        this.highlightColor = highlightColor;
+        repaintComponent();
+    }
+
+    public void setHighlightColor(String highlightColor) {
+        if (highlightColor == null) {
+            throw new IllegalArgumentException("highlightColor is null.");
+        }
+
+        setHighlightColor(decodeColor(highlightColor));
+    }
+
+    public final void setHighlightColor(int highlightColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setHighlightColor(theme.getColor(highlightColor));
+    }
+
+    public Color getHighlightBackgroundColor() {
+        return highlightBackgroundColor;
+    }
+
+    public void setHighlightBackgroundColor(Color highlightBackgroundColor) {
+        if (highlightBackgroundColor == null) {
+            throw new IllegalArgumentException("highlightBackgroundColor is null.");
+        }
+
+        this.highlightBackgroundColor = highlightBackgroundColor;
+        repaintComponent();
+    }
+
+    public 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 int getSpacing() {
+        return spacing;
+    }
+
+    public void setSpacing(int spacing) {
+        this.spacing = spacing;
+        invalidateComponent();
+    }
+
+    public void setSpacing(Number spacing) {
+        if (spacing == null) {
+            throw new IllegalArgumentException("spacing is null.");
+        }
+
+        setSpacing(spacing.intValue());
+    }
+
+    public int getIndent() {
+        return indent;
+    }
+
+    public void setIndent(int indent) {
+        this.indent = indent;
+        invalidateComponent();
+    }
+
+    public void setIndent(Number indent) {
+        if (indent == null) {
+            throw new IllegalArgumentException("indent is null.");
+        }
+
+        setIndent(indent.intValue());
+    }
+
+    public boolean getShowHighlight() {
+        return showHighlight;
+    }
+
+    public void setShowHighlight(boolean showHighlight) {
+        this.showHighlight = showHighlight;
+        repaintComponent();
+    }
+
+    public boolean getShowBranchControls() {
+        return showBranchControls;
+    }
+
+    public void setShowBranchControls(boolean showBranchControls) {
+        this.showBranchControls = showBranchControls;
+        invalidateComponent();
+    }
+
+    public Color getBranchControlColor() {
+        return branchControlColor;
+    }
+
+    public void setBranchControlColor(Color branchControlColor) {
+        if (branchControlColor == null) {
+            throw new IllegalArgumentException("branchControlColor is null.");
+        }
+
+        this.branchControlColor = branchControlColor;
+        repaintComponent();
+    }
+
+    public void setBranchControlColor(String branchControlColor) {
+        if (branchControlColor == null) {
+            throw new IllegalArgumentException("branchControlColor is null.");
+        }
+
+        setBranchControlColor(decodeColor(branchControlColor));
+    }
+
+    public final void setBranchControlColor(int branchControlColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBranchControlColor(theme.getColor(branchControlColor));
+    }
+
+    public Color getBranchControlDisabledColor() {
+        return branchControlDisabledColor;
+    }
+
+    public void setBranchControlDisabledColor(Color branchControlDisabledColor) {
+        if (branchControlDisabledColor == null) {
+            throw new IllegalArgumentException("branchControlDisabledColor is null.");
+        }
+
+        this.branchControlDisabledColor = branchControlDisabledColor;
+        repaintComponent();
+    }
+
+    public void setBranchControlDisabledColor(String branchControlDisabledColor) {
+        if (branchControlDisabledColor == null) {
+            throw new IllegalArgumentException("branchControlDisabledColor is null.");
+        }
+
+        setBranchControlDisabledColor(decodeColor(branchControlDisabledColor));
+    }
+
+    public final void setBranchControlDisabledColor(int branchControlDisabledColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBranchControlDisabledColor(theme.getColor(branchControlDisabledColor));
+    }
+
+    public Color getBranchControlSelectionColor() {
+        return branchControlSelectionColor;
+    }
+
+    public void setBranchControlSelectionColor(Color branchControlSelectionColor) {
+        if (branchControlSelectionColor == null) {
+            throw new IllegalArgumentException("branchControlSelectionColor is null.");
+        }
+
+        this.branchControlSelectionColor = branchControlSelectionColor;
+        repaintComponent();
+    }
+
+    public void setBranchControlSelectionColor(String branchControlSelectionColor) {
+        if (branchControlSelectionColor == null) {
+            throw new IllegalArgumentException("branchControlSelectionColor is null.");
+        }
+
+        setBranchControlSelectionColor(decodeColor(branchControlSelectionColor));
+    }
+
+    public final void setBranchControlSelectionColor(int branchControlSelectionColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBranchControlSelectionColor(theme.getColor(branchControlSelectionColor));
+    }
+
+    public Color getBranchControlInactiveSelectionColor() {
+        return branchControlInactiveSelectionColor;
+    }
+
+    public void setBranchControlInactiveSelectionColor(Color branchControlInactiveSelectionColor) {
+        if (branchControlInactiveSelectionColor == null) {
+            throw new IllegalArgumentException("branchControlInactiveSelectionColor is null.");
+        }
+
+        this.branchControlInactiveSelectionColor = branchControlInactiveSelectionColor;
+        repaintComponent();
+    }
+
+    public void setBranchControlInactiveSelectionColor(String branchControlInactiveSelectionColor) {
+        if (branchControlInactiveSelectionColor == null) {
+            throw new IllegalArgumentException("branchControlInactiveSelectionColor is null.");
+        }
+
+        setBranchControlInactiveSelectionColor(decodeColor(branchControlInactiveSelectionColor));
+    }
+
+    public final void setBranchControlInactiveSelectionColor(int branchControlInactiveSelectionColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBranchControlInactiveSelectionColor(theme.getColor(branchControlInactiveSelectionColor));
+    }
+
+    public Color getGridColor() {
+        return gridColor;
+    }
+
+    public void setGridColor(Color gridColor) {
+        if (gridColor == null) {
+            throw new IllegalArgumentException("gridColor is null.");
+        }
+
+        this.gridColor = gridColor;
+        repaintComponent();
+    }
+
+    public 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 getShowGridLines() {
+        return showGridLines;
+    }
+
+    public void setShowGridLines(boolean showGridLines) {
+        this.showGridLines = showGridLines;
+        repaintComponent();
+    }
+
+    /**
+     * Gets the fixed node height of this skin.
+     */
+    protected int getNodeHeight() {
+        TreeView treeView = (TreeView)getComponent();
+        TreeView.NodeRenderer nodeRenderer = treeView.getNodeRenderer();
+
+        int nodeHeight = nodeRenderer.getPreferredHeight(-1);
+
+        if (treeView.getCheckmarksEnabled()) {
+            nodeHeight = Math.max(CHECKBOX.getHeight() + (2 * CHECKBOX_VERTICAL_PADDING), nodeHeight);
+        }
+
+        return nodeHeight;
+    }
+
+    /**
+     * Gets the metadata associated with the node found at the specified
+     * y-coordinate, or <tt>null</tt> if there is no node at that location.
+     */
+    protected NodeInfo getNodeInfoAt(int y) {
+        NodeInfo nodeInfo = null;
+
+        int nodeHeight = getNodeHeight();
+        int index = y / (nodeHeight + VERTICAL_SPACING);
+
+        if (index >= 0
+            && index < visibleNodes.getLength()) {
+            nodeInfo = visibleNodes.get(index);
+        }
+
+        return nodeInfo;
+    }
+
+    /**
+     * Gets the metadata associated with the node at the specified path.
+     * The path must be valid.
+     */
+    protected NodeInfo getNodeInfoAt(Sequence<Integer> path) {
+        assert(path != null) : "Path is null";
+
+        NodeInfo result = null;
+
+        int n = path.getLength();
+
+        if (n == 0) {
+            result = rootBranchInfo;
+        } else {
+            BranchInfo branchInfo = rootBranchInfo;
+
+            for (int i = 0; i < n - 1; i++) {
+                branchInfo.loadChildren();
+                NodeInfo nodeInfo = branchInfo.children.get(path.get(i));
+
+                assert(nodeInfo instanceof BranchInfo) : "Invalid path";
+
+                branchInfo = (BranchInfo)nodeInfo;
+            }
+
+            branchInfo.loadChildren();
+            result = branchInfo.children.get(path.get(n - 1));
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the bounding box defined by the specified node, or <tt>null</tt>
+     * if the node is not currently visible.
+     */
+    protected Bounds getNodeBounds(NodeInfo nodeInfo) {
+        Bounds bounds = null;
+
+        int index = visibleNodes.indexOf(nodeInfo);
+
+        if (index >= 0) {
+            int nodeHeight = getNodeHeight();
+            int nodeY = index * (nodeHeight + VERTICAL_SPACING);
+
+            bounds = new Bounds(0, nodeY, getWidth(), nodeHeight);
+        }
+
+        return bounds;
+    }
+
+    /**
+     * Adds all children of the specified branch to the visible node list.
+     * Any children nodes that are expanded [branches] will also have their
+     * children made visible, and so on. Invalidates the component only
+     * if necessary.
+     */
+    private void addVisibleNodes(BranchInfo parentBranchInfo) {
+        int insertIndex = -1;
+
+        if (parentBranchInfo == rootBranchInfo) {
+            // Bootstrap case since the root branch is implicitly expanded
+            insertIndex = 0;
+        } else {
+            int branchIndex = visibleNodes.indexOf(parentBranchInfo);
+            if (branchIndex >= 0) {
+                insertIndex = branchIndex + 1;
+            }
+        }
+
+        if (insertIndex >= 0) {
+            Sequence<NodeInfo> nodes = new ArrayList<NodeInfo>();
+
+            // The parent branch's children are the baseline nodes to make
+            // visible
+            parentBranchInfo.loadChildren();
+            for (int i = 0, n = parentBranchInfo.children.getLength(); i < n; i++) {
+                nodes.add(parentBranchInfo.children.get(i));
+            }
+
+            while (nodes.getLength() > 0) {
+                NodeInfo nodeInfo = nodes.get(0);
+                nodes.remove(0, 1);
+
+                visibleNodes.insert(nodeInfo, insertIndex++);
+
+                // If we encounter an expanded branch, we add that branch's
+                // children to our list of nodes that are to become visible
+                if (nodeInfo instanceof BranchInfo) {
+                    BranchInfo branchInfo = (BranchInfo)nodeInfo;
+
+                    if (branchInfo.isExpanded()) {
+                        branchInfo.loadChildren();
+                        for (int i = 0, n = branchInfo.children.getLength(); i < n; i++) {
+                            nodes.insert(branchInfo.children.get(i), i);
+                        }
+                    }
+                }
+            }
+
+            invalidateComponent();
+        }
+    }
+
+    /**
+     * Adds the specified child of the specified branch to the visible node
+     * list. It is assumed that the child in question is not an expanded
+     * branch. Invalidates the component only if necessary.
+     *
+     * @param parentBranchInfo
+     * The branch info of the parent node.
+     *
+     * @param index
+     * The index of the child within its parent.
+     */
+    private void addVisibleNode(BranchInfo parentBranchInfo, int index) {
+        parentBranchInfo.loadChildren();
+
+        assert(index >= 0) : "Index is too small";
+        assert(index < parentBranchInfo.children.getLength()) : "Index is too large";
+
+        int branchIndex = visibleNodes.indexOf(parentBranchInfo);
+
+        if (parentBranchInfo == rootBranchInfo
+            || (branchIndex >= 0 && parentBranchInfo.isExpanded())) {
+
+            NodeInfo nodeInfo = parentBranchInfo.children.get(index);
+
+            int insertIndex = branchIndex + index + 1;
+
+            if (index > 0) {
+                // Siblings of the node that lie before it may be expanded
+                // branches, thus adding their own children to the
+                // visible nodes list and pushing down our insert index
+                NodeInfo youngerSibling = parentBranchInfo.children.get(index - 1);
+
+                // Try to insert after our younger sibling
+                insertIndex = visibleNodes.indexOf(youngerSibling) + 1;
+
+                // Continue looking as long as the node at our insert index
+                // has a greater depth than we do, which means that it's a
+                // descendant of our younger sibling
+                for (int n = visibleNodes.getLength(), nodeDepth = youngerSibling.depth;
+                    insertIndex < n && visibleNodes.get(insertIndex).depth > nodeDepth;
+                    insertIndex++);
+            }
+
+            visibleNodes.insert(nodeInfo, insertIndex);
+
+            invalidateComponent();
+        }
+    }
+
+    /**
+     * Removes the specified children of the specified branch from the visible
+     * node list if necessary. If they are not already in the visible node
+     * list, nothing happens. Invalidates the component only if necessary.
+     *
+     * @param parentBranchInfo
+     * The branch info of the parent node.
+     *
+     * @param index
+     * The index of the first child node to remove from the visible nodes
+     * sequence.
+     *
+     * @param count
+     * The number of child nodes to remove, or <tt>-1</tt> to remove all
+     * child nodes from the visible nodes sequence.
+     */
+    private void removeVisibleNodes(BranchInfo parentBranchInfo, int index, int count) {
+        parentBranchInfo.loadChildren();
+
+        if (count == -1) {
+            assert(index == 0) : "Non-zero index with 'remove all' count";
+            count = parentBranchInfo.children.getLength();
+        }
+
+        assert(index + count <= parentBranchInfo.children.getLength()) : "Value too big";
+
+        if (count > 0) {
+            NodeInfo first = parentBranchInfo.children.get(index);
+            NodeInfo last = parentBranchInfo.children.get(index + count - 1);
+
+            int rangeStart = visibleNodes.indexOf(first);
+
+            if (rangeStart >= 0) {
+                int rangeEnd = visibleNodes.indexOf(last) + 1;
+
+                assert(rangeEnd > rangeStart) : "Invalid visible node structure";
+
+                // Continue looking as long as the node at our endpoint has a
+                // greater depth than the last child node, which means that
+                // it's a descendant of the last child node
+                for (int n = visibleNodes.getLength(), nodeDepth = last.depth;
+                    rangeEnd < n && visibleNodes.get(rangeEnd).depth > nodeDepth;
+                    rangeEnd++);
+
+                visibleNodes.remove(rangeStart, rangeEnd - rangeStart);
+
+                invalidateComponent();
+            }
+        }
+    }
+
+    /**
+     * Repaints the region occupied by the specified node.
+     */
+    protected void repaintNode(NodeInfo nodeInfo) {
+        Bounds bounds = getNodeBounds(nodeInfo);
+        if (bounds != null) {
+            repaintComponent(bounds);
+        }
+    }
+
+    /**
+     * Clears the highlighted node if one exists.
+     */
+    protected void clearHighlightedNode() {
+        if (highlightedNode != null) {
+            highlightedNode.setHighlighted(false);
+            repaintNode(highlightedNode);
+
+            highlightedNode = null;
+        }
+    }
+
+    /**
+     * Clears our <tt>NodeInfo</tt> hierarchy of the specified cached field.
+     *
+     * @param mask
+     * The bitmask specifying which field to clear.
+     */
+    private void clearFields(byte mask) {
+        Sequence<NodeInfo> nodes = new ArrayList<NodeInfo>();
+        nodes.add(rootBranchInfo);
+
+        while (nodes.getLength() > 0) {
+            NodeInfo nodeInfo = nodes.get(0);
+            nodes.remove(0, 1);
+
+            nodeInfo.clearField(mask);
+
+            if (nodeInfo instanceof BranchInfo) {
+                BranchInfo branchInfo = (BranchInfo)nodeInfo;
+
+                if (branchInfo.children != null) {
+                    for (int i = 0, n = branchInfo.children.getLength(); i < n; i++) {
+                        nodes.insert(branchInfo.children.get(i), i);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean mouseMove(Component component, int x, int y) {
+        boolean consumed = super.mouseMove(component, x, y);
+
+        TreeView treeView = (TreeView)getComponent();
+
+        if (showHighlight
+            && treeView.getSelectMode() != TreeView.SelectMode.NONE) {
+            NodeInfo previousHighlightedNode = highlightedNode;
+            highlightedNode = getNodeInfoAt(y);
+
+            if (highlightedNode != previousHighlightedNode) {
+                if (previousHighlightedNode != null) {
+                    previousHighlightedNode.setHighlighted(false);
+                    repaintNode(previousHighlightedNode);
+                }
+
+                if (highlightedNode != null) {
+                    highlightedNode.setHighlighted(true);
+                    repaintNode(highlightedNode);
+                }
+            }
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public void mouseOut(Component component) {
+        super.mouseOut(component);
+
+        clearHighlightedNode();
+        editNode = null;
+    }
+
+    @Override
+    public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseDown(component, button, x, y);
+
+        if (!consumed) {
+            TreeView treeView = (TreeView)getComponent();
+            NodeInfo nodeInfo = getNodeInfoAt(y);
+
+            if (nodeInfo != null
+                && !nodeInfo.isDisabled()) {
+                int nodeHeight = getNodeHeight();
+                int baseNodeX = (nodeInfo.depth - 1) * (indent + spacing);
+
+                int nodeX = baseNodeX + (showBranchControls ? indent + spacing : 0);
+                int nodeY = (y / (nodeHeight + VERTICAL_SPACING)) * (nodeHeight + VERTICAL_SPACING);
+
+                int checkboxWidth = CHECKBOX.getWidth();
+                int checkboxHeight = CHECKBOX.getHeight();
+
+                int checkboxX = Math.max(indent - checkboxWidth, 0) / 2;
+                int checkboxY = (nodeHeight - checkboxHeight) / 2;
+
+                // Only proceed if the user DIDN'T click on a checkbox
+                if (!treeView.getCheckmarksEnabled()
+                    || x < nodeX + checkboxX
+                    || x >= nodeX + checkboxX + checkboxWidth
+                    || y < nodeY + checkboxY
+                    || y >= nodeY + checkboxY + checkboxHeight) {
+                    Sequence<Integer> path = nodeInfo.getPath();
+
+                    // See if the user clicked on an expand/collapse control of
+                    // a branch. If so, expand/collapse the branch
+                    if (showBranchControls
+                        && nodeInfo instanceof BranchInfo
+                        && x >= baseNodeX
+                        && x < baseNodeX + indent) {
+                        BranchInfo branchInfo = (BranchInfo)nodeInfo;
+
+                        treeView.setBranchExpanded(path, !branchInfo.isExpanded());
+                        consumed = true;
+                    }
+
+                    // If we haven't consumed the event, then proceed to manage
+                    // the selection state of the node
+                    if (!consumed) {
+                        TreeView.SelectMode selectMode = treeView.getSelectMode();
+
+                        if (selectMode == TreeView.SelectMode.SINGLE) {
+                            if (nodeInfo.isSelected()
+                                && treeView.isFocused()) {
+                                // Edit the node
+                                editNode = nodeInfo;
+                            } else {
+                                // Select the node
+                                treeView.setSelectedPath(path);
+                            }
+                        } else if (selectMode == TreeView.SelectMode.MULTI) {
+                            if (Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+                                if (nodeInfo.isSelected()) {
+                                    treeView.removeSelectedPath(path);
+                                } else {
+                                    treeView.addSelectedPath(path);
+                                }
+                            } else {
+                                // Replace the selection
+                                treeView.setSelectedPath(path);
+                            }
+                        }
+                    }
+                }
+            }
+
+            treeView.requestFocus();
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+        boolean consumed = super.mouseClick(component, button, x, y, count);
+
+        if (!consumed) {
+            TreeView treeView = (TreeView)getComponent();
+
+            NodeInfo nodeInfo = getNodeInfoAt(y);
+
+            if (nodeInfo != null
+                && !nodeInfo.isDisabled()) {
+                int nodeHeight = getNodeHeight();
+                int baseNodeX = (nodeInfo.depth - 1) * (indent + spacing);
+
+                int nodeX = baseNodeX + (showBranchControls ? indent + spacing : 0);
+                int nodeY = (y / (nodeHeight + VERTICAL_SPACING)) * (nodeHeight + VERTICAL_SPACING);
+
+                int checkboxWidth = CHECKBOX.getWidth();
+                int checkboxHeight = CHECKBOX.getHeight();
+
+                int checkboxX = Math.max(indent - checkboxWidth, 0) / 2;
+                int checkboxY = (nodeHeight - checkboxHeight) / 2;
+
+                if (treeView.getCheckmarksEnabled()
+                    && x >= nodeX + checkboxX
+                    && x < nodeX + checkboxX + checkboxWidth
+                    && y >= nodeY + checkboxY
+                    && y < nodeY + checkboxY + checkboxHeight) {
+                    Sequence<Integer> path = nodeInfo.getPath();
+                    treeView.setNodeChecked(path, !nodeInfo.isChecked());
+                } else {
+                    if (editNode != null
+                        && count == 1) {
+                        TreeView.NodeEditor nodeEditor = treeView.getNodeEditor();
+
+                        if (nodeEditor != null) {
+                            nodeEditor.edit(treeView, nodeInfo.getPath());
+                        }
+                    }
+
+                    editNode = null;
+                }
+            }
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean mouseWheel(Component component, Mouse.ScrollType scrollType, int scrollAmount,
+        int wheelRotation, int x, int y) {
+        boolean consumed = super.mouseWheel(component, scrollType, scrollAmount,
+            wheelRotation, x, y);
+
+        clearHighlightedNode();
+
+        return consumed;
+    }
+
+    @Override
+    public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        TreeView treeView = (TreeView)getComponent();
+        TreeView.SelectMode selectMode = treeView.getSelectMode();
+
+        switch (keyCode) {
+        case Keyboard.KeyCode.UP: {
+            if (selectMode != TreeView.SelectMode.NONE) {
+                Sequence<Integer> firstSelectedPath = treeView.getFirstSelectedPath();
+
+                int index;
+                if (firstSelectedPath != null) {
+                    NodeInfo previousSelectedNode = getNodeInfoAt(firstSelectedPath);
+                    index = visibleNodes.indexOf(previousSelectedNode);
+                } else {
+                    // Select the last visible node
+                    index = visibleNodes.getLength();
+                }
+
+                NodeInfo newSelectedNode = null;
+                do {
+                    newSelectedNode = (--index >= 0) ? visibleNodes.get(index) : null;
+                } while (newSelectedNode != null
+                    && newSelectedNode.isDisabled());
+
+                if (newSelectedNode != null) {
+                    treeView.setSelectedPath(newSelectedNode.getPath());
+                    treeView.scrollAreaToVisible(getNodeBounds(newSelectedNode));
+                    consumed = true;
+                }
+            }
+
+            break;
+        }
+
+        case Keyboard.KeyCode.DOWN: {
+            if (selectMode != TreeView.SelectMode.NONE) {
+                Sequence<Integer> lastSelectedPath = treeView.getLastSelectedPath();
+
+                int index;
+                if (lastSelectedPath != null) {
+                    NodeInfo previousSelectedNode = getNodeInfoAt(lastSelectedPath);
+                    index = visibleNodes.indexOf(previousSelectedNode);
+                } else {
+                    // Select the first visible node
+                    index = -1;
+                }
+
+                NodeInfo newSelectedNode = null;
+                int n = visibleNodes.getLength();
+                do {
+                    newSelectedNode = (++index <= n - 1) ? visibleNodes.get(index) : null;
+                } while (newSelectedNode != null
+                    && newSelectedNode.isDisabled());
+
+                if (newSelectedNode != null) {
+                    treeView.setSelectedPath(newSelectedNode.getPath());
+                    treeView.scrollAreaToVisible(getNodeBounds(newSelectedNode));
+                    consumed = true;
+                }
+            }
+
+            break;
+        }
+
+        case Keyboard.KeyCode.LEFT: {
+            if (showBranchControls) {
+                Sequence<Sequence<Integer>> paths = treeView.getSelectedPaths();
+
+                if (paths != null) {
+                    Sequence<Integer> path = paths.get(paths.getLength() - 1);
+                    NodeInfo nodeInfo = getNodeInfoAt(path);
+
+                    if (nodeInfo instanceof BranchInfo) {
+                        BranchInfo branchInfo = (BranchInfo)nodeInfo;
+
+                        if (branchInfo.isExpanded()) {
+                            treeView.collapseBranch(branchInfo.getPath());
+                        }
+                    }
+
+                    consumed = true;
+                }
+            }
+
+            break;
+        }
+
+        case Keyboard.KeyCode.RIGHT: {
+            if (showBranchControls) {
+                Sequence<Sequence<Integer>> paths = treeView.getSelectedPaths();
+
+                if (paths != null) {
+                    Sequence<Integer> path = paths.get(paths.getLength() - 1);
+                    NodeInfo nodeInfo = getNodeInfoAt(path);
+
+                    if (nodeInfo instanceof BranchInfo) {
+                        BranchInfo branchInfo = (BranchInfo)nodeInfo;
+
+                        if (!branchInfo.isExpanded()) {
+                            treeView.expandBranch(branchInfo.getPath());
+                        }
+                    }
+
+                    consumed = true;
+                }
+            }
+
+            break;
+        }
+
+        default:
+            consumed = super.keyPressed(component, keyCode, keyLocation);
+            break;
+        }
+
+        clearHighlightedNode();
+
+        return consumed;
+    }
+
+    @Override
+    public boolean keyReleased(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        TreeView treeView = (TreeView)getComponent();
+
+        if (keyCode == Keyboard.KeyCode.SPACE) {
+            if (treeView.getCheckmarksEnabled()
+                && treeView.getSelectMode() == TreeView.SelectMode.SINGLE) {
+                Sequence<Integer> selectedPath = treeView.getSelectedPath();
+
+                if (selectedPath != null) {
+                    treeView.setNodeChecked(selectedPath, !treeView.isNodeChecked(selectedPath));
+                }
+            }
+        } else {
+            consumed = super.keyReleased(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);
+
+        repaintComponent();
+    }
+
+    // TreeView.Skin methods
+
+    public Sequence<Integer> getNodeAt(int y) {
+        Sequence<Integer> path = null;
+
+        NodeInfo nodeInfo = getNodeInfoAt(y);
+
+        if (nodeInfo != null) {
+            path = nodeInfo.getPath();
+        }
+
+        return path;
+    }
+
+    public Bounds getNodeBounds(Sequence<Integer> path) {
+        NodeInfo nodeInfo = getNodeInfoAt(path);
+        return getNodeBounds(nodeInfo);
+    }
+
+    public int getNodeIndent(int depth) {
+        TreeView treeView = (TreeView)getComponent();
+
+        int nodeIndent = (depth - 1) * (indent + spacing);
+
+        if (showBranchControls) {
+            nodeIndent += indent + spacing;
+        }
+
+        if (treeView.getCheckmarksEnabled()) {
+            nodeIndent += Math.max(CHECKBOX.getWidth(), indent) + spacing;
+        }
+
+        return nodeIndent;
+    }
+
+    // TreeViewListener methods
+
+    @SuppressWarnings("unchecked")
+    public void treeDataChanged(TreeView treeView, List<?> previousTreeData) {
+        List<Object> treeData = (List<Object>)treeView.getTreeData();
+
+        visibleNodes.clear();
+
+        if (treeData == null) {
+            rootBranchInfo = null;
+        } else {
+            rootBranchInfo = new BranchInfo(null, treeData);
+            addVisibleNodes(rootBranchInfo);
+        }
+
+        invalidateComponent();
+    }
+
+    public void nodeRendererChanged(TreeView treeView, TreeView.NodeRenderer previousNodeRenderer) {
+        invalidateComponent();
+    }
+
+    public void nodeEditorChanged(TreeView treeView, TreeView.NodeEditor previousNodeEditor) {
+        // No-op
+    }
+
+    public void selectModeChanged(TreeView treeView,
+        TreeView.SelectMode previousSelectMode) {
+        // The selection has implicitly been cleared
+        clearFields(NodeInfo.SELECTED_MASK);
+
+        repaintComponent();
+    }
+
+    public void checkmarksEnabledChanged(TreeView treeView) {
+        // The check state of all nodes has implicitly been cleared
+        clearFields(NodeInfo.CHECK_STATE_MASK);
+
+        invalidateComponent();
+    }
+
+    public void showMixedCheckmarkStateChanged(TreeView treeView) {
+        if (treeView.getCheckmarksEnabled()) {
+            // The check state of all *branch* nodes may have changed, so we
+            // need to update the cached check state of all BranchNode
+            // instances in our hierarchy
+            Sequence<NodeInfo> nodes = new ArrayList<NodeInfo>();
+            nodes.add(rootBranchInfo);
+
+            while (nodes.getLength() > 0) {
+                NodeInfo nodeInfo = nodes.get(0);
+                nodes.remove(0, 1);
+
+                // Only branch nodes can be affected by this event
+                if (nodeInfo instanceof BranchInfo) {
+                    BranchInfo branchInfo = (BranchInfo)nodeInfo;
+
+                    // Update the cached entry for this branch
+                    Sequence<Integer> path = branchInfo.getPath();
+                    branchInfo.setCheckState(treeView.getNodeCheckState(path));
+
+                    // Add the branch's children to the queue
+                    if (branchInfo.children != null) {
+                        for (int i = 0, n = branchInfo.children.getLength(); i < n; i++) {
+                            nodes.insert(branchInfo.children.get(i), i);
+                        }
+                    }
+                }
+            }
+
+            repaintComponent();
+        }
+    }
+
+    // TreeViewBranchListener methods
+
+    public void branchExpanded(TreeView treeView, Sequence<Integer> path) {
+        BranchInfo branchInfo = (BranchInfo)getNodeInfoAt(path);
+
+        branchInfo.setExpanded(true);
+        addVisibleNodes(branchInfo);
+
+        repaintNode(branchInfo);
+    }
+
+    public void branchCollapsed(TreeView treeView, Sequence<Integer> path) {
+        BranchInfo branchInfo = (BranchInfo)getNodeInfoAt(path);
+
+        branchInfo.setExpanded(false);
+        removeVisibleNodes(branchInfo, 0, -1);
+
+        repaintNode(branchInfo);
+    }
+
+    // TreeViewNodeListener methods
+
+    @SuppressWarnings("unchecked")
+    public void nodeInserted(TreeView treeView, Sequence<Integer> path, int index) {
+        BranchInfo branchInfo = (BranchInfo)getNodeInfoAt(path);
+        List<Object> branchData = (List<Object>)branchInfo.data;
+
+        // Update our internal branch info
+        if (branchInfo.children != null) {
+            NodeInfo nodeInfo = NodeInfo.createNew(branchInfo, branchData.get(index));
+            branchInfo.children.insert(nodeInfo, index);
+        }
+
+        // Add the node to the visible nodes list
+        addVisibleNode(branchInfo, index);
+
+        branchInfo.loadChildren();
+        if (showBranchControls
+            && branchInfo.children.getLength() == 1) {
+            // The branch went from having no children to having one
+            repaintNode(branchInfo);
+        }
+    }
+
+    public void nodesRemoved(TreeView treeView, Sequence<Integer> path, int index,
+        int count) {
+        BranchInfo branchInfo = (BranchInfo)getNodeInfoAt(path);
+
+        // Remove the node from the visible nodes list
+        removeVisibleNodes(branchInfo, index, count);
+
+        // Update our internal branch info
+        if (branchInfo.children != null) {
+            branchInfo.children.remove(index,
+                (count >= 0) ? count : branchInfo.children.getLength());
+        }
+
+        branchInfo.loadChildren();
+        if (showBranchControls
+            && branchInfo.children.getLength() == 0) {
+            // The branch went from having children to having none
+            repaintNode(branchInfo);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public void nodeUpdated(TreeView treeView, Sequence<Integer> path, int index) {
+        BranchInfo branchInfo = (BranchInfo)getNodeInfoAt(path);
+        List<Object> branchData = (List<Object>)branchInfo.data;
+
+        branchInfo.loadChildren();
+        NodeInfo nodeInfo = branchInfo.children.get(index);
+
+        Object previousNodeData = nodeInfo.data;
+        Object nodeData = branchData.get(index);
+
+        if (previousNodeData != nodeData) {
+            // Remove the old node from the visible nodes list
+            removeVisibleNodes(branchInfo, index, 1);
+
+            // Update our internal branch info
+            nodeInfo = NodeInfo.createNew(branchInfo, nodeData);
+            branchInfo.children.update(index, nodeInfo);
+
+            // Add the new node to the visible nodes list
+            addVisibleNode(branchInfo, index);
+        } else if (visibleNodes.indexOf(nodeInfo) >= 0) {
+            // The updated node data might affect our preferred width
+            invalidateComponent();
+        }
+    }
+
+    public void nodesSorted(TreeView treeView, Sequence<Integer> path) {
+        // TODO
+    }
+
+    // TreeViewNodeStateListener methods
+
+    public void nodeDisabledChanged(TreeView treeView, Sequence<Integer> path) {
+        NodeInfo nodeInfo = getNodeInfoAt(path);
+
+        nodeInfo.setDisabled(treeView.isNodeDisabled(path));
+
+        repaintNode(nodeInfo);
+    }
+
+    public void nodeCheckStateChanged(TreeView treeView, Sequence<Integer> path,
+        TreeView.NodeCheckState previousCheckState) {
+        NodeInfo nodeInfo = getNodeInfoAt(path);
+
+        nodeInfo.setCheckState(treeView.getNodeCheckState(path));
+
+        repaintNode(nodeInfo);
+    }
+
+    // TreeViewSelectionListener methods
+
+    public void selectedPathAdded(TreeView treeView, Sequence<Integer> path) {
+        NodeInfo nodeInfo = getNodeInfoAt(path);
+
+        nodeInfo.setSelected(true);
+        repaintNode(nodeInfo);
+    }
+
+    public void selectedPathRemoved(TreeView treeView, Sequence<Integer> path) {
+        NodeInfo nodeInfo = getNodeInfoAt(path);
+
+        nodeInfo.setSelected(false);
+        repaintNode(nodeInfo);
+    }
+
+    public void selectedPathsChanged(TreeView treeView,
+        Sequence<Sequence<Integer>> previousSelectedPaths) {
+
+        // Un-select the previous selected paths
+        for (int i = 0, n = previousSelectedPaths.getLength(); i < n; i++) {
+            NodeInfo previousSelectedNode = getNodeInfoAt(previousSelectedPaths.get(i));
+
+            previousSelectedNode.setSelected(false);
+            repaintNode(previousSelectedNode);
+        }
+
+        Sequence<Sequence<Integer>> selectedPaths = treeView.getSelectedPaths();
+
+        // Select the current selected paths
+        for (int i = 0, n = selectedPaths.getLength(); i < n; i++) {
+            NodeInfo selectedNode = getNodeInfoAt(selectedPaths.get(i));
+
+            selectedNode.setSelected(true);
+            repaintNode(selectedNode);
+        }
+    }
+}

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/alert_skin.wtkx
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/alert_skin.wtkx?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/alert_skin.wtkx (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/alert_skin.wtkx Wed Mar 25 23:08:38 2009
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+
+<FlowPane orientation="vertical" styles="{spacing:8, horizontalAlignment:'justify'}"
+    preferredWidth="300"
+    xmlns:wtkx="http://pivot-toolkit.org/wtkx/2008"
+    xmlns:collections="pivot.collections"
+    xmlns="pivot.wtk">
+    <Border styles="{padding:12, backgroundColor:10, color:7}">
+        <content>
+            <TablePane styles="{horizontalSpacing:12, verticalSpacing:12}">
+                <columns>
+                    <TablePane.Column width="-1"/>
+                    <TablePane.Column width="1*"/>
+                </columns>
+                <rows>
+                    <TablePane.Row height="-1">
+                        <FlowPane styles="{verticalAlignment:'top'}">
+                            <ImageView wtkx:id="typeImageView"/>
+                        </FlowPane>
+                        <FlowPane wtkx:id="messageFlowPane" orientation="vertical"
+                            styles="{spacing:10, horizontalAlignment:'justify'}">
+                            <Label wtkx:id="messageLabel" styles="{fontBold:true, wrapText:true}"/>
+                        </FlowPane>
+                    </TablePane.Row>
+                </rows>
+            </TablePane>
+        </content>
+    </Border>
+    <FlowPane wtkx:id="buttonFlowPane"
+        styles="{horizontalAlignment:'right', verticalAlignment:'justify'}"/>
+</FlowPane>

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/doc-files/palette.png
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/doc-files/palette.png?rev=758461&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/doc-files/palette.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/doc-files/palette_test.png
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/doc-files/palette_test.png?rev=758461&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/doc-files/palette_test.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-error-16x16.png
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-error-16x16.png?rev=758461&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-error-16x16.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-error-32x32.png
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-error-32x32.png?rev=758461&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-error-32x32.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-info-16x16.png
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-info-16x16.png?rev=758461&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-info-16x16.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-info-32x32.png
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-info-32x32.png?rev=758461&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-info-32x32.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-question-16x16.png
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-question-16x16.png?rev=758461&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-question-16x16.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-question-32x32.png
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-question-32x32.png?rev=758461&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-question-32x32.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-warning-16x16.png
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-warning-16x16.png?rev=758461&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-warning-16x16.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-warning-32x32.png
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-warning-32x32.png?rev=758461&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/message_type-warning-32x32.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/package.html
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/package.html?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/package.html (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/package.html Wed Mar 25 23:08:38 2009
@@ -0,0 +1,22 @@
+<html>
+  <head></head>
+  <body>
+    <p>
+      Defines skin classes for the "Terra" theme.
+    </p>
+    <p>
+      Terra skins get their colors from the theme's color palette, which contains
+      24 indexed colors.  Each Terra skin exposes setter methods for each of its
+      color styles that take an integer value; such methods allow callers to style
+      the component by referencing the theme's color palette.  The default palette
+      is shown below:
+    </p>
+    <p>
+      <img src="doc-files/palette.png" border="0"/>
+      <br/>
+      <font color="#000000" size="-1" face="arial,helvetica,sanserif">
+        <i>The default color palette</i>
+      </font>
+    </p>
+  </body>
+</html>

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/prompt_skin.wtkx
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/prompt_skin.wtkx?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/prompt_skin.wtkx (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/prompt_skin.wtkx Wed Mar 25 23:08:38 2009
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+
+<FlowPane orientation="vertical" styles="{spacing:8, horizontalAlignment:'justify'}"
+    preferredWidth="300"
+    xmlns:wtkx="http://pivot-toolkit.org/wtkx/2008"
+    xmlns:collections="pivot.collections"
+    xmlns="pivot.wtk">
+    <Border styles="{padding:12, backgroundColor:4, color:7}">
+        <content>
+            <TablePane styles="{horizontalSpacing:12, verticalSpacing:12}">
+                <columns>
+                    <TablePane.Column width="-1"/>
+                    <TablePane.Column width="1*"/>
+                </columns>
+                <rows>
+                    <TablePane.Row height="-1">
+                        <FlowPane styles="{verticalAlignment:'top'}">
+                            <ImageView wtkx:id="typeImageView"/>
+                        </FlowPane>
+                        <FlowPane wtkx:id="messageFlowPane" orientation="vertical"
+                            styles="{spacing:10, horizontalAlignment:'justify'}">
+                            <Label wtkx:id="messageLabel" styles="{fontBold:true, wrapText:true}"/>
+                        </FlowPane>
+                    </TablePane.Row>
+                </rows>
+            </TablePane>
+        </content>
+    </Border>
+    <FlowPane wtkx:id="buttonFlowPane"
+        styles="{horizontalAlignment:'right', verticalAlignment:'justify'}"/>
+</FlowPane>

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/Block.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/Block.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/Block.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/Block.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2009 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.text;
+
+/**
+ * Abstract base class for block elements.
+ * <p>
+ * TODO Add horizontal alignment, margin, and line spacing properties.
+ *
+ * @author gbrown
+ */
+public abstract class Block extends Element {
+    public Block() {
+        super();
+    }
+
+    public Block(Block blockElement, boolean recursive) {
+        super(blockElement, recursive);
+    }
+}

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/BulletedList.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/BulletedList.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/BulletedList.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/BulletedList.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009 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.text;
+
+/**
+ * Element representing a bulleted list.
+ *
+ * @author gbrown
+ */
+public class BulletedList extends List {
+    /**
+     * List bullet styles.
+     *
+     * @author gbrown
+     */
+    public enum Style {
+        CIRCLE,
+        CIRCLE_OUTLINE,
+        SQUARE,
+        SQUARE_OUTLINE
+    }
+
+    private Style style = Style.CIRCLE;
+
+    public BulletedList() {
+        super();
+    }
+
+    public BulletedList(BulletedList bulletedList, boolean recursive) {
+        super(bulletedList, recursive);
+    }
+
+    public Style getStyle() {
+        return style;
+    }
+
+    public void setStyle(Style style) {
+        if (style == null) {
+            throw new IllegalArgumentException("style is null.");
+        }
+
+        this.style = style;
+
+        // TODO Fire event
+    }
+
+    @Override
+    public Node duplicate(boolean recursive) {
+        return new BulletedList(this, recursive);
+    }
+}

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/Document.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/Document.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/Document.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/text/Document.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009 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.text;
+
+/**
+ * Node representing the root of an element hierarchy.
+ *
+ * @author gbrown
+ */
+public class Document extends Block {
+    public Document() {
+        super();
+    }
+
+    public Document(Document document, boolean recursive) {
+        super(document, recursive);
+
+        // TODO?
+    }
+
+    @Override
+    protected void setParent(Element parent) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Node duplicate(boolean recursive) {
+        return new Document(this, recursive);
+    }
+
+    @Override
+    public void insert(Node node, int index) {
+        if (!(node instanceof Block)) {
+            throw new IllegalArgumentException("Child node must be an instance of "
+                + Block.class.getName());
+        }
+
+        super.insert(node, index);
+    }
+}