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 [42/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/TerraTabPaneSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTabPaneSkin.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTabPaneSkin.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTabPaneSkin.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,1208 @@
+/*
+ * 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.GradientPaint;
+import java.awt.Graphics2D;
+
+import pivot.collections.Dictionary;
+import pivot.collections.Sequence;
+import pivot.util.Vote;
+import pivot.wtk.Button;
+import pivot.wtk.Component;
+import pivot.wtk.Dimensions;
+import pivot.wtk.FlowPane;
+import pivot.wtk.HorizontalAlignment;
+import pivot.wtk.Insets;
+import pivot.wtk.Mouse;
+import pivot.wtk.Orientation;
+import pivot.wtk.Panorama;
+import pivot.wtk.Bounds;
+import pivot.wtk.TabPane;
+import pivot.wtk.TabPaneListener;
+import pivot.wtk.TabPaneSelectionListener;
+import pivot.wtk.TabPaneAttributeListener;
+import pivot.wtk.Theme;
+import pivot.wtk.VerticalAlignment;
+import pivot.wtk.Button.Group;
+import pivot.wtk.content.ButtonData;
+import pivot.wtk.content.ButtonDataRenderer;
+import pivot.wtk.effects.ClipDecorator;
+import pivot.wtk.effects.Transition;
+import pivot.wtk.effects.TransitionListener;
+import pivot.wtk.effects.easing.Easing;
+import pivot.wtk.effects.easing.Quadratic;
+import pivot.wtk.media.Image;
+import pivot.wtk.skin.ButtonSkin;
+import pivot.wtk.skin.ContainerSkin;
+
+/**
+ * Tab pane skin.
+ * <p>
+ * TODO Make tab buttons focusable?
+ * <p>
+ * TODO Disable the tab button when the component is disabled? We'd need
+ * style properties to present a disabled tab button state. We'd also need
+ * to manage button enabled state independently of tab pane enabled state.
+ * <p>
+ * TODO Add showCloseButton style.
+ *
+ * @author gbrown
+ */
+public class TerraTabPaneSkin extends ContainerSkin
+    implements TabPaneListener, TabPaneSelectionListener, TabPaneAttributeListener,
+        Button.GroupListener {
+    protected class TabButton extends Button {
+    	private boolean active = false;
+
+    	public TabButton() {
+            this(null);
+        }
+
+        public TabButton(Object buttonData) {
+            super(buttonData);
+
+            super.setToggleButton(true);
+            setDataRenderer(DEFAULT_DATA_RENDERER);
+
+            setSkin(new TabButtonSkin());
+        }
+
+        @Override
+        public void setEnabled(boolean enabled) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setToggleButton(boolean toggleButton) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setTriState(boolean triState) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void press() {
+            // If the tab pane is collapsible, toggle the button selection;
+            // otherwise, select it
+            TabPane tabPane = (TabPane)TerraTabPaneSkin.this.getComponent();
+            setSelected(tabPane.isCollapsible() ? !isSelected() : true);
+
+            super.press();
+        }
+    }
+
+    protected class TabButtonSkin extends ButtonSkin {
+        public int getPreferredWidth(int height) {
+            TabButton tabButton = (TabButton)getComponent();
+            TabPane tabPane = (TabPane)TerraTabPaneSkin.this.getComponent();
+
+            Button.DataRenderer dataRenderer = tabButton.getDataRenderer();
+            dataRenderer.render(tabButton.getButtonData(), tabButton, false);
+
+            // Include padding in constraint
+            if (height != -1) {
+                height = Math.max(height - (buttonPadding.top + buttonPadding.bottom + 2), 0);
+            }
+
+            int preferredWidth = 0;
+            switch (tabPane.getTabOrientation()) {
+                case HORIZONTAL: {
+                    preferredWidth = dataRenderer.getPreferredWidth(height)
+                        + buttonPadding.left + buttonPadding.right + 2;
+                    break;
+                }
+
+                case VERTICAL: {
+                    preferredWidth = dataRenderer.getPreferredHeight(height)
+                        + buttonPadding.top + buttonPadding.bottom + 2;
+                    break;
+                }
+            }
+
+            return preferredWidth;
+        }
+
+        public int getPreferredHeight(int width) {
+            TabButton tabButton = (TabButton)getComponent();
+            TabPane tabPane = (TabPane)TerraTabPaneSkin.this.getComponent();
+
+            Button.DataRenderer dataRenderer = tabButton.getDataRenderer();
+            dataRenderer.render(tabButton.getButtonData(), tabButton, false);
+
+            // Include padding in constraint
+            if (width != -1) {
+                width = Math.max(width - (buttonPadding.left + buttonPadding.right + 2), 0);
+            }
+
+            int preferredHeight = 0;
+            switch (tabPane.getTabOrientation()) {
+                case HORIZONTAL: {
+                    preferredHeight = dataRenderer.getPreferredHeight(width)
+                        + buttonPadding.top + buttonPadding.bottom + 2;
+                    break;
+                }
+
+                case VERTICAL: {
+                    preferredHeight = dataRenderer.getPreferredWidth(width)
+                        + buttonPadding.left + buttonPadding.right + 2;
+                    break;
+                }
+            }
+
+            return preferredHeight;
+        }
+
+        public Dimensions getPreferredSize() {
+            TabButton tabButton = (TabButton)getComponent();
+            TabPane tabPane = (TabPane)TerraTabPaneSkin.this.getComponent();
+
+            Button.DataRenderer dataRenderer = tabButton.getDataRenderer();
+            dataRenderer.render(tabButton.getButtonData(), tabButton, false);
+
+            Dimensions preferredContentSize = dataRenderer.getPreferredSize();
+
+            int preferredWidth = 0;
+            int preferredHeight = 0;
+            switch (tabPane.getTabOrientation()) {
+                case HORIZONTAL: {
+                    preferredWidth = preferredContentSize.width
+                        + buttonPadding.left + buttonPadding.right + 2;
+
+                    preferredHeight = preferredContentSize.height
+                        + buttonPadding.top + buttonPadding.bottom + 2;
+
+                    break;
+                }
+
+                case VERTICAL: {
+                    preferredWidth = preferredContentSize.height
+                        + buttonPadding.top + buttonPadding.bottom + 2;
+
+                    preferredHeight = preferredContentSize.width
+                        + buttonPadding.left + buttonPadding.right + 2;
+
+                    break;
+                }
+            }
+
+            return new Dimensions(preferredWidth, preferredHeight);
+        }
+
+        public void paint(Graphics2D graphics) {
+            TabButton tabButton = (TabButton)getComponent();
+            TabPane tabPane = (TabPane)TerraTabPaneSkin.this.getComponent();
+            Orientation tabOrientation = tabPane.getTabOrientation();
+
+            Color backgroundColor = (tabButton.isSelected()
+        		|| tabButton.active) ?
+                activeTabColor : inactiveTabColor;
+
+            int width = getWidth();
+            int height = getHeight();
+
+            graphics.setStroke(new BasicStroke());
+
+            // Draw the background
+            graphics.setPaint(backgroundColor);
+            graphics.fillRect(0, 0, width, height);
+
+            // Draw the bevel
+            graphics.setPaint(new GradientPaint(width / 2, 1, buttonBevelColor,
+                width / 2, GRADIENT_BEVEL_THICKNESS, backgroundColor));
+
+            switch(tabOrientation) {
+	            case HORIZONTAL: {
+	                graphics.fillRect(1, 1, width - 2, GRADIENT_BEVEL_THICKNESS);
+	                break;
+	            }
+
+	            case VERTICAL: {
+	            	graphics.fillRect(1, 1, width - 1, GRADIENT_BEVEL_THICKNESS);
+	                break;
+	            }
+	        }
+
+            // Draw the border
+            graphics.setPaint(borderColor);
+
+            if (tabButton.isSelected()
+        		|| tabButton.active) {
+                switch(tabOrientation) {
+		            case HORIZONTAL: {
+		                graphics.drawLine(0, height - 1, 0, 0);
+		                graphics.drawLine(0, 0, width - 1, 0);
+		                graphics.drawLine(width - 1, 0, width - 1, height - 1);
+		                break;
+		            }
+
+		            case VERTICAL: {
+		                graphics.drawLine(width, 0, 0, 0);
+		                graphics.drawLine(0, 0, 0, height - 1);
+		                graphics.drawLine(0, height - 1, width - 1, height - 1);
+		                break;
+		            }
+	            }
+            } else {
+            	graphics.drawRect(0, 0, width - 1, height - 1);
+            }
+
+            // Paint the content
+            Button.DataRenderer dataRenderer = tabButton.getDataRenderer();
+            dataRenderer.render(tabButton.getButtonData(), tabButton, false);
+
+            Graphics2D contentGraphics = (Graphics2D)graphics.create();
+
+            switch (tabOrientation) {
+                case HORIZONTAL: {
+                    dataRenderer.setSize(Math.max(getWidth() - (buttonPadding.left + buttonPadding.right + 2), 0),
+                        Math.max(getHeight() - (buttonPadding.top + buttonPadding.bottom + 2), 0));
+
+                    contentGraphics.translate(buttonPadding.left + 1, buttonPadding.top + 1);
+                    break;
+                }
+
+                case VERTICAL: {
+                    dataRenderer.setSize(Math.max(getHeight() - (buttonPadding.top + buttonPadding.bottom + 2), 0),
+                        Math.max(getWidth() - (buttonPadding.left + buttonPadding.right + 2), 0));
+
+                    contentGraphics.translate(buttonPadding.top + 1, buttonPadding.left + 1);
+                    contentGraphics.rotate(-Math.PI / 2d);
+                    contentGraphics.translate(-dataRenderer.getWidth(), 0);
+                    break;
+                }
+            }
+
+            contentGraphics.clipRect(0, 0, dataRenderer.getWidth(), dataRenderer.getHeight());
+            dataRenderer.paint(contentGraphics);
+
+            contentGraphics.dispose();
+        }
+
+        @Override
+        public boolean isFocusable() {
+            return false;
+        }
+
+        @Override
+        public boolean mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+            boolean consumed = super.mouseClick(component, button, x, y, count);
+
+            TabButton tabButton = (TabButton)getComponent();
+            tabButton.press();
+
+            return consumed;
+        }
+
+        public Font getFont() {
+        	return buttonFont;
+        }
+
+        public Color getColor() {
+        	return buttonColor;
+        }
+
+        public Color getDisabledColor() {
+        	return buttonColor;
+        }
+    }
+
+    public class ExpandTransition extends Transition {
+    	boolean collapse;
+        private Easing easing = new Quadratic();
+        private ClipDecorator clipDecorator = new ClipDecorator();
+
+        public ExpandTransition(boolean collapse, int duration, int rate) {
+            super(duration, rate, false);
+            this.collapse = collapse;
+        }
+
+        public float getScale() {
+            int elapsedTime = getElapsedTime();
+            int duration = getDuration();
+
+            float scale;
+            if (collapse) {
+                scale = easing.easeIn(elapsedTime, 1, -1, duration);
+            } else {
+                scale = easing.easeOut(elapsedTime, 0, 1, duration);
+            }
+
+            return scale;
+        }
+
+        @Override
+        public void start(TransitionListener transitionListener) {
+        	TabPane tabPane = (TabPane)getComponent();
+        	Component selectedTab = tabPane.getSelectedTab();
+        	selectedTab.getDecorators().add(clipDecorator);
+
+        	int selectedIndex = tabPane.getSelectedIndex();
+        	TabButton tabButton = (TabButton)buttonFlowPane.get(selectedIndex);
+        	tabButton.active = true;
+
+        	getComponent().setEnabled(false);
+
+            super.start(transitionListener);
+        }
+
+        @Override
+        public void stop() {
+        	TabPane tabPane = (TabPane)getComponent();
+        	Component selectedTab = tabPane.getSelectedTab();
+        	selectedTab.getDecorators().remove(clipDecorator);
+
+        	int selectedIndex = tabPane.getSelectedIndex();
+        	TabButton tabButton = (TabButton)buttonFlowPane.get(selectedIndex);
+        	tabButton.active = false;
+
+        	getComponent().setEnabled(true);
+
+        	super.stop();
+        }
+
+        @Override
+        protected void update() {
+            invalidateComponent();
+        }
+    }
+
+    protected Panorama buttonPanorama = new Panorama();
+    protected FlowPane buttonFlowPane = new FlowPane();
+    private Button.Group tabButtonGroup = new Button.Group();
+
+    private Color activeTabColor;
+    private Color inactiveTabColor;
+    private Color borderColor;
+    private Insets padding;
+    private Font buttonFont;
+    private Color buttonColor;
+    private Insets buttonPadding;
+
+    // Derived colors
+    private Color buttonBevelColor;
+
+    private ExpandTransition expandTransition = null;
+
+    private static final int EXPAND_DURATION = 250;
+    private static final int EXPAND_RATE = 30;
+
+	public static final int GRADIENT_BEVEL_THICKNESS = 4;
+	private static final Button.DataRenderer DEFAULT_DATA_RENDERER = new ButtonDataRenderer();
+
+    public TerraTabPaneSkin() {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        activeTabColor = theme.getColor(11);
+        inactiveTabColor = theme.getColor(9);
+        borderColor = theme.getColor(7);
+        padding = new Insets(6);
+        buttonFont = theme.getFont();
+        buttonColor = theme.getColor(1);
+        buttonPadding = new Insets(3, 4, 3, 4);
+
+        // Set the derived colors
+        buttonBevelColor = TerraTheme.brighten(inactiveTabColor);
+
+        tabButtonGroup.getGroupListeners().add(this);
+
+        buttonFlowPane.getStyles().put("spacing", 2);
+    }
+
+	@Override
+	public void setSize(int width, int height) {
+		TabPane tabPane = (TabPane)getComponent();
+		Orientation tabOrientation = tabPane.getTabOrientation();
+
+		if (expandTransition != null) {
+			if ((tabOrientation == Orientation.HORIZONTAL && width != getWidth())
+				|| (tabOrientation == Orientation.VERTICAL && height != getHeight())) {
+				expandTransition.end();
+				expandTransition = null;
+			}
+		}
+
+		super.setSize(width, height);
+	}
+
+    public void install(Component component) {
+        super.install(component);
+
+        TabPane tabPane = (TabPane)component;
+
+        // Add this as a listener on the tab pane
+        tabPane.getTabPaneListeners().add(this);
+        tabPane.getTabPaneSelectionListeners().add(this);
+        tabPane.getTabPaneAttributeListeners().add(this);
+
+        // Add the button panorama and flow pane
+        buttonPanorama.getStyles().put("buttonBackgroundColor", borderColor);
+        buttonPanorama.getStyles().put("buttonPadding", 6);
+        buttonPanorama.setView(buttonFlowPane);
+        tabPane.add(buttonPanorama);
+
+        // Apply the current tab orientation
+        tabOrientationChanged(tabPane);
+
+        // Add buttons for all existing tabs
+        for (Component tab : tabPane.getTabs()) {
+            TabButton tabButton = new TabButton(new ButtonData(TabPane.getIcon(tab),
+                TabPane.getName(tab)));
+            tabButton.setGroup(tabButtonGroup);
+
+            buttonFlowPane.add(tabButton);
+        }
+
+        selectedIndexChanged(tabPane, -1);
+    }
+
+    public void uninstall() {
+        TabPane tabPane = (TabPane)getComponent();
+
+        // Remove this as a listener on the tab pane
+        tabPane.getTabPaneListeners().remove(this);
+        tabPane.getTabPaneSelectionListeners().remove(this);
+        tabPane.getTabPaneAttributeListeners().remove(this);
+
+        // Remove the button panorama
+        tabPane.remove(buttonPanorama);
+
+        super.uninstall();
+    }
+
+    public int getPreferredWidth(int height) {
+        int preferredWidth;
+
+        TabPane tabPane = (TabPane)getComponent();
+        Orientation tabOrientation = tabPane.getTabOrientation();
+
+        if (expandTransition == null
+    		|| tabOrientation == Orientation.VERTICAL) {
+        	preferredWidth = 0;
+
+            Component selectedTab = tabPane.getSelectedTab();
+            Component corner = tabPane.getCorner();
+
+            switch (tabOrientation) {
+                case HORIZONTAL: {
+                    if (height != -1) {
+                        if (corner != null
+                            && corner.isDisplayable()) {
+                            height = Math.max(height - Math.max(corner.getPreferredHeight(-1),
+                                Math.max(buttonPanorama.getPreferredHeight(-1) - 1, 0)), 0);
+                        } else {
+                            height = Math.max(height - (buttonPanorama.getPreferredHeight(-1) - 1), 0);
+                        }
+
+                        height = Math.max(height - (padding.top + padding.bottom + 2), 0);
+                    }
+
+                    if (selectedTab != null) {
+                        for (Component tab : tabPane.getTabs()) {
+                            preferredWidth = Math.max(preferredWidth,
+                                tab.getPreferredWidth(height));
+                        }
+
+                        preferredWidth += (padding.left + padding.right + 2);
+                    }
+
+                    int buttonAreaPreferredWidth = buttonPanorama.getPreferredWidth(-1);
+
+                    if (corner != null
+                        && corner.isDisplayable()) {
+                        buttonAreaPreferredWidth += corner.getPreferredWidth(-1);
+                    }
+
+                    preferredWidth = Math.max(preferredWidth,
+                        buttonAreaPreferredWidth);
+
+                    break;
+                }
+
+                case VERTICAL: {
+                    if (height != -1) {
+                        height = Math.max(height - (padding.top + padding.bottom + 2), 0);
+                    }
+
+                    if (selectedTab != null) {
+                    	if (expandTransition == null) {
+                        	int contentWidth = 0;
+                            for (Component tab : tabPane.getTabs()) {
+                                contentWidth = Math.max(contentWidth,
+                                    tab.getPreferredWidth(height));
+                            }
+
+                            preferredWidth += (padding.left + padding.right + contentWidth);
+                    	} else {
+                            float scale = expandTransition.getScale();
+                            preferredWidth += (int)(scale * (float)(padding.left + padding.right
+                        		+ selectedTab.getWidth()));
+                    	}
+
+                        preferredWidth += 2;
+                    } else {
+                        preferredWidth += 1;
+                    }
+
+                    if (corner != null
+                        && corner.isDisplayable()) {
+                        preferredWidth += Math.max(corner.getPreferredWidth(-1),
+                            Math.max(buttonPanorama.getPreferredWidth(-1) - 1, 0));
+                    } else {
+                        preferredWidth += Math.max(buttonPanorama.getPreferredWidth(-1) - 1, 0);
+                    }
+
+                    break;
+                }
+            }
+        } else {
+        	preferredWidth = getWidth();
+        }
+
+        return preferredWidth;
+    }
+
+    public int getPreferredHeight(int width) {
+        int preferredHeight;
+
+        TabPane tabPane = (TabPane)getComponent();
+        Orientation tabOrientation = tabPane.getTabOrientation();
+
+        if (expandTransition == null
+    		|| tabOrientation == Orientation.HORIZONTAL) {
+        	preferredHeight = 0;
+            Component selectedTab = tabPane.getSelectedTab();
+            Component corner = tabPane.getCorner();
+
+            switch (tabOrientation) {
+                case HORIZONTAL: {
+                    if (width != -1) {
+                        width = Math.max(width - (padding.left + padding.right + 2), 0);
+                    }
+
+                    if (selectedTab != null) {
+                    	if (expandTransition == null) {
+                    		int contentHeight = 0;
+                            for (Component tab : tabPane.getTabs()) {
+                            	contentHeight = Math.max(contentHeight,
+                                    tab.getPreferredHeight(width));
+                            }
+
+                            preferredHeight += (padding.top + padding.bottom + contentHeight);
+                    	} else {
+                    		float scale = expandTransition.getScale();
+                            preferredHeight += (int)(scale * (float)(padding.top + padding.bottom
+                        		+ selectedTab.getHeight()));
+                    	}
+
+                        preferredHeight += 2;
+                    } else {
+                        preferredHeight += 1;
+                    }
+
+                    if (corner != null
+                        && corner.isDisplayable()) {
+                        preferredHeight += Math.max(corner.getPreferredHeight(-1),
+                            Math.max(buttonPanorama.getPreferredHeight(-1) - 1, 0));
+                    } else {
+                        preferredHeight += Math.max(buttonPanorama.getPreferredHeight(-1) - 1, 0);
+                    }
+
+                    break;
+                }
+
+                case VERTICAL: {
+                    if (width != -1) {
+                        if (corner != null
+                            && corner.isDisplayable()) {
+                            width = Math.max(width - Math.max(corner.getPreferredWidth(-1),
+                                Math.max(buttonPanorama.getPreferredWidth(-1) - 1, 0)), 0);
+                        } else {
+                            width = Math.max(width - (buttonPanorama.getPreferredWidth(-1) - 1), 0);
+                        }
+
+                        width = Math.max(width - (padding.left + padding.right + 2), 0);
+                    }
+
+                    if (selectedTab != null) {
+                        for (Component tab : tabPane.getTabs()) {
+                            preferredHeight = Math.max(preferredHeight,
+                                tab.getPreferredHeight(width));
+                        }
+
+                        preferredHeight += (padding.top + padding.bottom + 2);
+                    }
+
+                    int buttonAreaPreferredHeight = buttonPanorama.getPreferredHeight(-1);
+
+                    if (corner != null
+                        && corner.isDisplayable()) {
+                        buttonAreaPreferredHeight += corner.getPreferredHeight(-1);
+                    }
+
+                    preferredHeight = Math.max(preferredHeight,
+                        buttonAreaPreferredHeight);
+
+                    break;
+                }
+            }
+        } else {
+        	preferredHeight = getHeight();
+        }
+
+        return preferredHeight;
+    }
+
+    public Dimensions getPreferredSize() {
+        // TODO Optimize
+        return new Dimensions(getPreferredWidth(-1), getPreferredHeight(-1));
+    }
+
+    public void layout() {
+        TabPane tabPane = (TabPane)getComponent();
+        int width = getWidth();
+        int height = getHeight();
+
+        int tabX = 0;
+        int tabY = 0;
+        int tabWidth = 0;
+        int tabHeight = 0;
+
+        Component selectedTab = tabPane.getSelectedTab();
+        Component corner = tabPane.getCorner();
+
+        Dimensions buttonPanoramaSize;
+        if (expandTransition == null) {
+        	buttonPanoramaSize = buttonPanorama.getPreferredSize();
+        } else {
+        	buttonPanoramaSize = buttonPanorama.getSize();
+        }
+
+        Orientation tabOrientation = tabPane.getTabOrientation();
+        switch (tabOrientation) {
+            case HORIZONTAL: {
+                int buttonPanoramaWidth = Math.min(width,
+                    buttonPanoramaSize.width);
+                int buttonPanoramaHeight = buttonPanoramaSize.height;
+                int buttonPanoramaX = 0;
+                int buttonPanoramaY = 0;
+
+                if (corner != null) {
+                    if (corner.isDisplayable()) {
+                        int cornerWidth = width - buttonPanoramaWidth;
+                        int cornerHeight = Math.max(corner.getPreferredHeight(-1), buttonPanoramaSize.height - 1);
+                        int cornerX = buttonPanoramaWidth;
+                        int cornerY = Math.max(buttonPanoramaHeight - cornerHeight - 1, 0);
+
+                        buttonPanoramaY = Math.max(cornerHeight - buttonPanoramaHeight + 1, 0);
+
+                        corner.setVisible(true);
+                        corner.setLocation(cornerX, cornerY);
+                        corner.setSize(cornerWidth, cornerHeight);
+                    } else {
+                        corner.setVisible(false);
+                    }
+                }
+
+                buttonPanorama.setLocation(buttonPanoramaX, buttonPanoramaY);
+                buttonPanorama.setSize(buttonPanoramaWidth, buttonPanoramaHeight);
+
+                tabX = padding.left + 1;
+                tabY = padding.top + buttonPanoramaY + buttonPanoramaHeight;
+                tabWidth = Math.max(width - (padding.left + padding.right + 2), 0);
+                tabHeight = Math.max(height - (padding.top + padding.bottom
+                    + buttonPanoramaY + buttonPanoramaHeight + 1), 0);
+
+                break;
+            }
+
+            case VERTICAL: {
+                int buttonPanoramaWidth = buttonPanoramaSize.width;
+                int buttonPanoramaHeight = Math.min(height,
+                    buttonPanoramaSize.height);
+                int buttonPanoramaX = 0;
+                int buttonPanoramaY = 0;
+
+                if (corner != null) {
+                    if (corner.isDisplayable()) {
+                        int cornerWidth = corner.getPreferredWidth(-1);
+                        int cornerHeight = height - buttonPanoramaHeight;
+                        int cornerX = Math.max(buttonPanoramaWidth - cornerWidth - 1, 0);
+                        int cornerY = buttonPanoramaHeight;
+
+                        buttonPanoramaX = Math.max(cornerWidth - buttonPanoramaWidth + 1, 0);
+
+                        corner.setVisible(true);
+                        corner.setLocation(cornerX, cornerY);
+                        corner.setSize(cornerWidth, cornerHeight);
+                    } else {
+                        corner.setVisible(false);
+                    }
+                }
+
+                buttonPanorama.setLocation(buttonPanoramaX, buttonPanoramaY);
+                buttonPanorama.setSize(buttonPanoramaWidth, buttonPanoramaHeight);
+
+                tabX = padding.left + buttonPanoramaX + buttonPanoramaWidth;
+                tabY = padding.top + 1;
+                tabWidth = Math.max(width - (padding.left + padding.right
+                    + buttonPanoramaX + buttonPanoramaWidth + 1), 0);
+                tabHeight = Math.max(height - (padding.top + padding.bottom + 2), 0);
+
+                break;
+            }
+        }
+
+        if (expandTransition == null) {
+            for (Component tab : tabPane.getTabs()) {
+                if (tab == selectedTab) {
+                    // Show the selected tab
+                    tab.setVisible(true);
+
+                    // Set the tab's size and location
+                    tab.setLocation(tabX, tabY);
+                    tab.setSize(tabWidth, tabHeight);
+                } else {
+                    tab.setVisible(false);
+                }
+            }
+        } else {
+        	if (expandTransition.isRunning()) {
+            	// Update the clip
+            	expandTransition.clipDecorator.setWidth(tabWidth);
+            	expandTransition.clipDecorator.setHeight(tabHeight);
+        	} else {
+            	switch (tabOrientation) {
+            		case HORIZONTAL: {
+            			tabHeight = 0;
+            			for (Component tab : tabPane.getTabs()) {
+            				tabHeight = Math.max(tab.getPreferredHeight(tabWidth), tabHeight);
+            			}
+
+            			break;
+            		}
+
+            		case VERTICAL: {
+            			tabWidth = 0;
+            			for (Component tab : tabPane.getTabs()) {
+            				tabWidth = Math.max(tab.getPreferredWidth(tabHeight), tabWidth);
+            			}
+
+            			break;
+            		}
+            	}
+
+                selectedTab.setLocation(tabX, tabY);
+                selectedTab.setSize(tabWidth, tabHeight);
+
+            	selectedTab.setVisible(true);
+        	}
+
+        }
+    }
+
+    @Override
+    public void paint(Graphics2D graphics) {
+        TabPane tabPane = (TabPane)getComponent();
+
+        Bounds tabPaneBounds = tabPane.getBounds();
+
+        // Call the base class to paint the background
+        super.paint(graphics);
+
+        graphics.setStroke(new BasicStroke());
+
+        // Paint the content background and border
+        int x = 0;
+        int y = 0;
+        int width = 0;
+        int height = 0;
+
+        Orientation tabOrientation = tabPane.getTabOrientation();
+
+        switch (tabOrientation) {
+            case HORIZONTAL: {
+                x = 0;
+                y = Math.max(buttonPanorama.getY() + buttonPanorama.getHeight() - 1, 0);
+                width = tabPaneBounds.width;
+                height = Math.max(tabPaneBounds.height - y, 0);
+
+                break;
+            }
+
+            case VERTICAL: {
+                x = Math.max(buttonPanorama.getX() + buttonPanorama.getWidth() - 1, 0);
+                y = 0;
+                width = Math.max(tabPaneBounds.width - x, 0);
+                height = tabPaneBounds.height;
+
+                break;
+            }
+        }
+
+        Bounds contentBounds = new Bounds(x, y, width, height);
+
+        if (!contentBounds.isEmpty()) {
+            // If a tab is selected, paint the active background color; otherwise,
+            // paint the inactive background color
+            int selectedIndex = tabPane.getSelectedIndex();
+            graphics.setPaint((selectedIndex == -1) ? inactiveTabColor : activeTabColor);
+            graphics.fillRect(contentBounds.x, contentBounds.y,
+                contentBounds.width, contentBounds.height);
+
+            // Draw the border
+            graphics.setPaint(borderColor);
+            graphics.drawRect(contentBounds.x, contentBounds.y,
+                Math.max(contentBounds.width - 1, 0),
+                Math.max(contentBounds.height - 1, 0));
+
+            // Draw the bevel for vertical tabs
+            if (tabOrientation == Orientation.VERTICAL) {
+	            graphics.setPaint(new GradientPaint(width / 2, contentBounds.y + 1, buttonBevelColor,
+	                width / 2, contentBounds.y + 1 + GRADIENT_BEVEL_THICKNESS, activeTabColor));
+	            graphics.fillRect(contentBounds.x + 1, contentBounds.y + 1,
+                    contentBounds.width - 2, GRADIENT_BEVEL_THICKNESS);
+            }
+        }
+    }
+
+    public Color getActiveTabColor() {
+        return activeTabColor;
+    }
+
+    public void setActiveTabColor(Color activeTabColor) {
+        if (activeTabColor == null) {
+            throw new IllegalArgumentException("activeTabColor is null.");
+        }
+
+        this.activeTabColor = activeTabColor;
+        repaintComponent();
+    }
+
+    public final void setActiveTabColor(String activeTabColor) {
+        if (activeTabColor == null) {
+            throw new IllegalArgumentException("activeTabColor is null.");
+        }
+
+        setActiveTabColor(decodeColor(activeTabColor));
+    }
+
+    public Color getInactiveTabColor() {
+        return inactiveTabColor;
+    }
+
+    public void setInactiveTabColor(Color inactiveTabColor) {
+        if (inactiveTabColor == null) {
+            throw new IllegalArgumentException("inactiveTabColor is null.");
+        }
+
+        this.inactiveTabColor = inactiveTabColor;
+        buttonBevelColor = TerraTheme.brighten(inactiveTabColor);
+        repaintComponent();
+    }
+
+    public final void setInactiveTabColor(String inactiveTabColor) {
+        if (inactiveTabColor == null) {
+            throw new IllegalArgumentException("inactiveTabColor is null.");
+        }
+
+        setInactiveTabColor(decodeColor(inactiveTabColor));
+    }
+
+    public Color getBorderColor() {
+        return borderColor;
+    }
+
+    public void setBorderColor(Color borderColor) {
+        if (borderColor == null) {
+            throw new IllegalArgumentException("borderColor is null.");
+        }
+
+        this.borderColor = borderColor;
+        buttonPanorama.getStyles().put("buttonBackgroundColor", borderColor);
+        repaintComponent();
+    }
+
+    public final void setBorderColor(String borderColor) {
+        if (borderColor == null) {
+            throw new IllegalArgumentException("borderColor is null.");
+        }
+
+        setBorderColor(decodeColor(borderColor));
+    }
+
+    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());
+    }
+
+    public Font getButtonFont() {
+        return buttonFont;
+    }
+
+    public void setButtonFont(Font buttonFont) {
+        if (buttonFont == null) {
+            throw new IllegalArgumentException("buttonFont is null.");
+        }
+
+        this.buttonFont = buttonFont;
+        invalidateComponent();
+    }
+
+    public final void setButtonFont(String buttonFont) {
+        if (buttonFont == null) {
+            throw new IllegalArgumentException("buttonFont is null.");
+        }
+
+        setButtonFont(Font.decode(buttonFont));
+    }
+
+    public Color getButtonColor() {
+        return buttonColor;
+    }
+
+    public void setButtonColor(Color buttonColor) {
+        if (buttonColor == null) {
+            throw new IllegalArgumentException("buttonColor is null.");
+        }
+
+        this.buttonColor = buttonColor;
+        repaintComponent();
+    }
+
+    public final void setButtonColor(String buttonColor) {
+        if (buttonColor == null) {
+            throw new IllegalArgumentException("buttonColor is null.");
+        }
+
+        setButtonColor(decodeColor(buttonColor));
+    }
+
+    public Insets getButtonPadding() {
+        return buttonPadding;
+    }
+
+    public void setButtonPadding(Insets buttonPadding) {
+        if (buttonPadding == null) {
+            throw new IllegalArgumentException("buttonPadding is null.");
+        }
+
+        this.buttonPadding = buttonPadding;
+        invalidateComponent();
+    }
+
+    public final void setButtonPadding(int buttonPadding) {
+        setButtonPadding(new Insets(buttonPadding));
+    }
+
+    public int getButtonSpacing() {
+        return (Integer)buttonFlowPane.getStyles().get("spacing");
+    }
+
+    public void setButtonSpacing(int buttonSpacing) {
+        buttonFlowPane.getStyles().put("spacing", buttonSpacing);
+    }
+
+    protected void updateButtonData(Component tab) {
+        TabPane tabPane = (TabPane)getComponent();
+        int tabIndex = tabPane.getTabs().indexOf(tab);
+
+        if (tabIndex != -1) {
+            TabButton tabButton =
+                (TabButton)buttonFlowPane.get(tabIndex);
+
+            tabButton.setButtonData(new ButtonData(TabPane.getIcon(tab),
+                TabPane.getName(tab)));
+        }
+    }
+
+    // Tab pane events
+    public void tabOrientationChanged(TabPane tabPane) {
+        Orientation tabOrientation = tabPane.getTabOrientation();
+
+        buttonFlowPane.setOrientation(tabOrientation);
+
+        Component.StyleDictionary buttonFlowPaneStyles = buttonFlowPane.getStyles();
+        switch (tabOrientation) {
+            case HORIZONTAL: {
+                buttonFlowPaneStyles.put("horizontalAlignment", HorizontalAlignment.LEFT);
+                buttonFlowPaneStyles.put("verticalAlignment", VerticalAlignment.JUSTIFY);
+                break;
+            }
+
+            case VERTICAL: {
+                buttonFlowPaneStyles.put("horizontalAlignment", HorizontalAlignment.JUSTIFY);
+                buttonFlowPaneStyles.put("verticalAlignment", VerticalAlignment.TOP);
+                break;
+            }
+        }
+    }
+
+    public void collapsibleChanged(TabPane tabPane) {
+        // No-op
+    }
+
+    public void tabInserted(TabPane tabPane, int index) {
+    	if (expandTransition != null) {
+    		expandTransition.stop();
+    		expandTransition = null;
+    	}
+
+        // Create a new button for the tab
+        Component tab = tabPane.getTabs().get(index);
+        TabButton tabButton = new TabButton(new ButtonData(TabPane.getIcon(tab),
+            TabPane.getName(tab)));
+        tabButton.setGroup(tabButtonGroup);
+
+        buttonFlowPane.insert(tabButton, index);
+    }
+
+    public void tabsRemoved(TabPane tabPane, int index, Sequence<Component> tabs) {
+    	if (expandTransition != null) {
+    		expandTransition.stop();
+    		expandTransition = null;
+    	}
+
+    	// Remove the buttons
+        Sequence<Component> removed = buttonFlowPane.remove(index, tabs.getLength());
+
+        for (int i = 0, n = removed.getLength(); i < n; i++) {
+            TabButton tabButton = (TabButton)removed.get(i);
+            tabButton.setGroup((Group)null);
+        }
+    }
+
+    public void cornerChanged(TabPane tabPane, Component previousCorner) {
+        invalidateComponent();
+    }
+
+    // Tab pane selection events
+	public Vote previewSelectedIndexChange(final TabPane tabPane, final int selectedIndex) {
+        Vote vote = Vote.APPROVE;
+
+        if (tabPane.isShowing()) {
+            if (expandTransition == null) {
+                if (selectedIndex == -1) {
+                    expandTransition = new ExpandTransition(true, EXPAND_DURATION, EXPAND_RATE);
+
+                    layout();
+                    expandTransition.start(new TransitionListener() {
+                        public void transitionCompleted(Transition transition) {
+                            tabPane.setSelectedIndex(-1);
+                            expandTransition = null;
+                        }
+                    });
+
+                    vote = Vote.DEFER;
+                }
+            } else {
+            	if (expandTransition.isRunning()) {
+                	vote = Vote.DEFER;
+            	}
+            }
+        }
+
+        return vote;
+	}
+
+	public void selectedIndexChangeVetoed(TabPane tabPane, Vote reason) {
+        if (reason == Vote.DENY
+            && expandTransition != null) {
+            expandTransition.stop();
+            expandTransition = null;
+            invalidateComponent();
+        }
+	}
+
+	public void selectedIndexChanged(TabPane tabPane, int previousSelectedIndex) {
+        int selectedIndex = tabPane.getSelectedIndex();
+
+        if (selectedIndex == -1) {
+            Button button = tabButtonGroup.getSelection();
+            if (button != null) {
+                button.setSelected(false);
+            }
+        } else {
+            Button button = (Button)buttonFlowPane.get(selectedIndex);
+            button.setSelected(true);
+
+            if (previousSelectedIndex == -1) {
+                if (tabPane.isShowing()) {
+                    expandTransition = new ExpandTransition(false, EXPAND_DURATION, EXPAND_RATE);
+
+                    layout();
+                    expandTransition.start(new TransitionListener() {
+                        public void transitionCompleted(Transition transition) {
+                            expandTransition = null;
+                        }
+                    });
+                }
+            }
+        }
+
+        invalidateComponent();
+    }
+
+    // Tab pane attribute events
+    public void nameChanged(TabPane tabPane, Component component, String previousName) {
+        updateButtonData(component);
+    }
+
+    public void iconChanged(TabPane tabPane, Component component, Image previousIcon) {
+        updateButtonData(component);
+    }
+
+    // Button group events
+    public void selectionChanged(Group group, Button previousSelection) {
+        Button button = tabButtonGroup.getSelection();
+        int index = (button == null) ? -1 : buttonFlowPane.indexOf(button);
+
+        TabPane tabPane = (TabPane)getComponent();
+        tabPane.setSelectedIndex(index);
+    }
+}

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTablePaneSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTablePaneSkin.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTablePaneSkin.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTablePaneSkin.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,46 @@
+/*
+ * 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 pivot.wtk.Theme;
+import pivot.wtk.skin.TablePaneSkin;
+
+/**
+ * Terra table pane skin.
+ *
+ * @author tvolkert
+ */
+public class TerraTablePaneSkin extends TablePaneSkin {
+    public TerraTablePaneSkin() {
+        setGridColor(7);
+        setSelectionBackgroundColor(10);
+    }
+
+    public final void setBackgroundColor(int backgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBackgroundColor(theme.getColor(backgroundColor));
+    }
+
+    public final void setGridColor(int gridColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setGridColor(theme.getColor(gridColor));
+    }
+
+    public final void setSelectionBackgroundColor(int selectionBackgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setSelectionBackgroundColor(theme.getColor(selectionBackgroundColor));
+    }
+}

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTableViewHeaderSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTableViewHeaderSkin.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTableViewHeaderSkin.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/terra/TerraTableViewHeaderSkin.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,773 @@
+/*
+ * 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.GradientPaint;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+
+import pivot.collections.Sequence;
+import pivot.wtk.Component;
+import pivot.wtk.Cursor;
+import pivot.wtk.Dimensions;
+import pivot.wtk.Mouse;
+import pivot.wtk.Bounds;
+import pivot.wtk.SortDirection;
+import pivot.wtk.TableView;
+import pivot.wtk.TableViewColumnListener;
+import pivot.wtk.TableViewHeader;
+import pivot.wtk.TableViewHeaderListener;
+import pivot.wtk.Theme;
+import pivot.wtk.media.Image;
+import pivot.wtk.skin.ComponentSkin;
+
+/**
+ * Table view header skin.
+ *
+ * @author gbrown
+ */
+public class TerraTableViewHeaderSkin extends ComponentSkin
+    implements TableViewHeader.Skin, TableViewHeaderListener, TableViewColumnListener {
+    private class SortIndicatorImage extends Image {
+        private SortDirection sortDirection = null;
+
+        public SortIndicatorImage(SortDirection sortDirection) {
+            this.sortDirection = sortDirection;
+        }
+
+        public int getWidth() {
+            return 7;
+        }
+
+        public int getHeight() {
+            return 4;
+        }
+
+        public Dimensions getPreferredSize() {
+            return new Dimensions(getPreferredWidth(-1), getPreferredHeight(-1));
+        }
+
+        public void paint(Graphics2D graphics) {
+            GeneralPath shape = new GeneralPath();
+
+            switch (sortDirection) {
+                case ASCENDING: {
+                    shape.moveTo(0, 3);
+                    shape.lineTo(3, 0);
+                    shape.lineTo(6, 3);
+                    break;
+                }
+
+                case DESCENDING: {
+                    shape.moveTo(0, 0);
+                    shape.lineTo(3, 3);
+                    shape.lineTo(6, 0);
+                    break;
+                }
+            }
+
+            shape.closePath();
+
+            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+
+            graphics.setStroke(new BasicStroke(0));
+            graphics.setPaint(borderColor);
+
+            graphics.draw(shape);
+            graphics.fill(shape);
+        }
+    }
+
+    private Font font;
+    private Color color;
+    private Color disabledColor;
+    private Color backgroundColor;
+    private Color disabledBackgroundColor;
+    private Color borderColor;
+    private Color disabledBorderColor;
+    private boolean headersPressable;
+    private boolean columnsResizable;
+    private boolean includeTrailingVerticalGridLine;
+
+    // Derived colors
+    private Color bevelColor;
+    private Color pressedBevelColor;
+    private Color disabledBevelColor;
+
+    private int pressedHeaderIndex = -1;
+    private int resizeHeaderIndex = -1;
+
+    private static final int SORT_INDICATOR_PADDING = 2;
+    private static final int RESIZE_HANDLE_SIZE = 6;
+    public static final int MINIMUM_COLUMN_WIDTH = 2;
+
+    protected SortIndicatorImage sortAscendingImage = new SortIndicatorImage(SortDirection.ASCENDING);
+    protected SortIndicatorImage sortDescendingImage = new SortIndicatorImage(SortDirection.DESCENDING);
+
+    public TerraTableViewHeaderSkin() {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+
+        font = theme.getFont();
+        color = theme.getColor(1);
+        disabledColor = theme.getColor(7);
+        backgroundColor = theme.getColor(10);
+        disabledBackgroundColor = theme.getColor(10);
+        borderColor = theme.getColor(7);
+        disabledBorderColor = theme.getColor(7);
+        headersPressable = true;
+        columnsResizable = true;
+        includeTrailingVerticalGridLine = false;
+
+        // Set the derived colors
+        bevelColor = TerraTheme.brighten(backgroundColor);
+        pressedBevelColor = TerraTheme.darken(backgroundColor);
+        disabledBevelColor = disabledBackgroundColor;
+    }
+
+    public void install(Component component) {
+        super.install(component);
+
+        TableViewHeader tableViewHeader = (TableViewHeader)component;
+        tableViewHeader.getTableViewHeaderListeners().add(this);
+
+        TableView tableView = tableViewHeader.getTableView();
+        if (tableView != null) {
+            tableView.getTableViewColumnListeners().add(this);
+        }
+    }
+
+    public void uninstall() {
+        TableViewHeader tableViewHeader = (TableViewHeader)getComponent();
+        tableViewHeader.getTableViewHeaderListeners().remove(this);
+
+        TableView tableView = tableViewHeader.getTableView();
+        if (tableView != null) {
+            tableView.getTableViewColumnListeners().remove(this);
+        }
+
+        super.uninstall();
+    }
+
+    public int getPreferredWidth(int height) {
+        int preferredWidth = 0;
+
+        TableViewHeader tableViewHeader = (TableViewHeader)getComponent();
+        TableView tableView = tableViewHeader.getTableView();
+
+        if (tableView != null) {
+            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
+                    if (i < gridLineStop) {
+                        preferredWidth++;
+                    }
+                }
+            }
+        }
+
+        return preferredWidth;
+    }
+
+    public int getPreferredHeight(int width) {
+        int preferredHeight = 0;
+
+        TableViewHeader tableViewHeader = (TableViewHeader)getComponent();
+        TableView tableView = tableViewHeader.getTableView();
+
+        if (tableView != null) {
+            TableView.ColumnSequence columns = tableView.getColumns();
+            TableViewHeader.DataRenderer dataRenderer = tableViewHeader.getDataRenderer();
+
+            for (int i = 0, n = columns.getLength(); i < n; i++) {
+                TableView.Column column = columns.get(i);
+                dataRenderer.render(column.getHeaderData(), tableViewHeader, false);
+                preferredHeight = Math.max(preferredHeight, dataRenderer.getPreferredHeight(-1));
+            }
+
+            // Include the bottom border
+            preferredHeight++;
+        }
+
+        return preferredHeight;
+    }
+
+    public Dimensions getPreferredSize() {
+        return new Dimensions(getPreferredWidth(-1), getPreferredHeight(-1));
+    }
+
+    public void layout() {
+        // No-op
+    }
+
+    public void paint(Graphics2D graphics) {
+        int width = getWidth();
+        int height = getHeight();
+
+        TableViewHeader tableViewHeader = (TableViewHeader)getComponent();
+
+        Color backgroundColor = null;
+        Color bevelColor = null;
+        Color borderColor = null;
+
+        if (tableViewHeader.isEnabled()) {
+            backgroundColor = this.backgroundColor;
+            bevelColor = this.bevelColor;
+            borderColor = this.borderColor;
+        } else {
+            backgroundColor = disabledBackgroundColor;
+            bevelColor = disabledBevelColor;
+            borderColor = disabledBorderColor;
+        }
+
+        graphics.setStroke(new BasicStroke());
+
+        graphics.setPaint(new GradientPaint(width / 2, 0, bevelColor,
+            width / 2, height, backgroundColor));
+        graphics.fillRect(0, 0, width, height);
+
+        // Paint the border
+        graphics.setPaint(borderColor);
+        graphics.drawLine(0, height - 1, width, height - 1);
+
+        // Paint the content
+        TableView tableView = tableViewHeader.getTableView();
+
+        if (tableView != null) {
+            TableView.ColumnSequence columns = tableView.getColumns();
+            Sequence<Integer> columnWidths =
+                TerraTableViewSkin.getColumnWidths(columns, getWidth());
+
+            TableViewHeader.DataRenderer dataRenderer = tableViewHeader.getDataRenderer();
+
+            int cellX = 0;
+            for (int columnIndex = 0, columnCount = columns.getLength();
+                columnIndex < columnCount; columnIndex++) {
+                TableView.Column column = columns.get(columnIndex);
+                int columnWidth = columnWidths.get(columnIndex);
+
+                // Paint the pressed bevel
+                if (columnIndex == pressedHeaderIndex) {
+                    graphics.setPaint(new GradientPaint(width / 2, 0, pressedBevelColor,
+                        width / 2, height, backgroundColor));
+                    graphics.fillRect(0, 0, width, height);
+                }
+
+                // Paint the header data
+                Object headerData = column.getHeaderData();
+                dataRenderer.render(headerData, tableViewHeader, false);
+                dataRenderer.setSize(columnWidth, height - 1);
+
+                Graphics2D rendererGraphics = (Graphics2D)graphics.create(cellX, 0,
+                    columnWidth, height - 1);
+                dataRenderer.paint(rendererGraphics);
+                rendererGraphics.dispose();
+
+                // Draw the sort image
+                Image sortImage = null;
+                SortDirection sortDirection = column.getSortDirection();
+
+                if (sortDirection != null) {
+                    switch (sortDirection) {
+                        case ASCENDING: {
+                            sortImage = sortAscendingImage;
+                            break;
+                        }
+
+                        case DESCENDING: {
+                            sortImage = sortDescendingImage;
+                            break;
+                        }
+                    }
+                }
+
+                if (sortImage != null) {
+                    int sortImageMargin = sortImage.getWidth()
+                    + SORT_INDICATOR_PADDING * 2;
+
+                    if (columnWidth >= dataRenderer.getPreferredWidth(-1) + sortImageMargin) {
+                        Graphics2D sortImageGraphics = (Graphics2D)graphics.create();
+                        sortImageGraphics.translate(cellX + columnWidth - sortImageMargin,
+                            (height - sortImage.getHeight()) / 2);
+                        sortImage.paint(sortImageGraphics);
+                        sortImageGraphics.dispose();
+                    }
+                }
+
+                // Draw the divider
+                cellX += columnWidth;
+
+                Line2D dividerLine = new Line2D.Double(cellX, 0, cellX, height - 1);
+                graphics.setPaint(borderColor);
+                graphics.draw(dividerLine);
+
+                cellX++;
+            }
+        }
+    }
+
+    public int getHeaderAt(int x) {
+        if (x < 0) {
+            throw new IllegalArgumentException("x is negative");
+        }
+
+        int index = -1;
+
+        TableViewHeader tableViewHeader = (TableViewHeader)getComponent();
+        TableView tableView = tableViewHeader.getTableView();
+
+        if (tableView != null) {
+            Sequence<Integer> columnWidths =
+                TerraTableViewSkin.getColumnWidths(tableView.getColumns(), getWidth());
+
+            int i = 0;
+            int n = columnWidths.getLength();
+            int columnX = 0;
+            while (i < n
+                && x > columnX) {
+                columnX += (columnWidths.get(i) + 1);
+                i++;
+            }
+
+            if (x <= columnX) {
+                index = i - 1;
+            }
+        }
+
+        return index;
+    }
+
+    public Bounds getHeaderBounds(int index) {
+        Bounds headerBounds = null;
+
+        TableViewHeader tableViewHeader = (TableViewHeader)getComponent();
+        TableView tableView = tableViewHeader.getTableView();
+
+        if (tableView != null) {
+            Sequence<Integer> columnWidths =
+                TerraTableViewSkin.getColumnWidths(tableView.getColumns(), getWidth());
+
+            if (index < 0
+                || index >= columnWidths.getLength()) {
+                throw new IndexOutOfBoundsException();
+            }
+
+            int cellX = 0;
+            for (int i = 0; i < index; i++) {
+                cellX += (columnWidths.get(i) + 1);
+            }
+
+            headerBounds = new Bounds(cellX, 0, columnWidths.get(index), getHeight() - 1);
+        }
+
+        return headerBounds;
+    }
+
+    @Override
+    public boolean isFocusable() {
+        return false;
+    }
+
+    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;
+        bevelColor = TerraTheme.brighten(backgroundColor);
+        pressedBevelColor = 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 backgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBackgroundColor(theme.getColor(backgroundColor));
+    }
+
+    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 disabledBackgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setDisabledBackgroundColor(theme.getColor(disabledBackgroundColor));
+    }
+
+    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 borderColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBorderColor(theme.getColor(borderColor));
+    }
+
+    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 disabledBorderColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setDisabledBorderColor(theme.getColor(disabledBorderColor));
+    }
+
+    public boolean getHeadersPressable() {
+        return headersPressable;
+    }
+
+    public void setHeadersPressable(boolean headersPressable) {
+        this.headersPressable = headersPressable;
+
+        pressedHeaderIndex = -1;
+        repaintComponent();
+    }
+
+    public boolean getColumnsResizable() {
+        return columnsResizable;
+    }
+
+    public void setColumnsResizable(boolean columnsResizable) {
+        this.columnsResizable = columnsResizable;
+    }
+
+    public boolean getIncludeTrailingVerticalGridLine() {
+        return includeTrailingVerticalGridLine;
+    }
+
+    public void setIncludeTrailingVerticalGridLine(boolean includeTrailingVerticalGridLine) {
+        this.includeTrailingVerticalGridLine = includeTrailingVerticalGridLine;
+        invalidateComponent();
+    }
+
+    @Override
+    public void enabledChanged(Component component) {
+        super.enabledChanged(component);
+
+        pressedHeaderIndex = -1;
+        resizeHeaderIndex = -1;
+        repaintComponent();
+    }
+
+    @Override
+    public boolean mouseMove(Component component, int x, int y) {
+        boolean consumed = super.mouseMove(component, x, y);
+        TableViewHeader tableViewHeader = (TableViewHeader)getComponent();
+        TableView tableView = tableViewHeader.getTableView();
+
+        if (tableView != null) {
+            if (Mouse.getCapturer() == tableViewHeader) {
+                TableView.Column column = tableView.getColumns().get(resizeHeaderIndex);
+                Bounds headerBounds = getHeaderBounds(resizeHeaderIndex);
+                int columnWidth = Math.max(x - headerBounds.x, MINIMUM_COLUMN_WIDTH);
+                column.setWidth(columnWidth, false);
+            } else {
+                int headerIndex = getHeaderAt(x);
+
+                if (headerIndex != -1
+                    && columnsResizable) {
+                    Bounds headerBounds = getHeaderBounds(headerIndex);
+                    TableView.Column column = tableView.getColumns().get(headerIndex);
+
+                    if (!column.isRelative()
+                        && x > headerBounds.x + headerBounds.width - RESIZE_HANDLE_SIZE) {
+                        tableViewHeader.setCursor(Cursor.RESIZE_EAST);
+                    } else {
+                        tableViewHeader.setCursor((Cursor)null);
+                    }
+                } else {
+                    tableViewHeader.setCursor((Cursor)null);
+                }
+            }
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public void mouseOut(Component component) {
+        super.mouseOut(component);
+
+        if (pressedHeaderIndex != -1) {
+            repaintComponent(getHeaderBounds(pressedHeaderIndex));
+        }
+
+        pressedHeaderIndex = -1;
+    }
+
+    @Override
+    public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseDown(component, button, x, y);
+
+        TableViewHeader tableViewHeader = (TableViewHeader)getComponent();
+        TableView tableView = tableViewHeader.getTableView();
+
+        if (tableView != null) {
+            int headerIndex = getHeaderAt(x);
+
+            if (headerIndex != -1) {
+                Bounds headerBounds = getHeaderBounds(headerIndex);
+                TableView.Column column = tableView.getColumns().get(headerIndex);
+
+                if (columnsResizable
+                    && !column.isRelative()
+                    && x > headerBounds.x + headerBounds.width - RESIZE_HANDLE_SIZE) {
+                    resizeHeaderIndex = headerIndex;
+                    Mouse.capture(tableViewHeader);
+                } else if (headersPressable) {
+                    pressedHeaderIndex = headerIndex;
+                    repaintComponent(getHeaderBounds(pressedHeaderIndex));
+                }
+            }
+        }
+
+        return consumed;
+    }
+
+    @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) {
+            Mouse.release();
+        } else {
+            if (pressedHeaderIndex != -1) {
+                repaintComponent(getHeaderBounds(pressedHeaderIndex));
+            }
+        }
+
+        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);
+
+        TableViewHeader tableViewHeader = (TableViewHeader)getComponent();
+
+        if (pressedHeaderIndex != -1
+            && headersPressable) {
+            tableViewHeader.pressHeader(pressedHeaderIndex);
+        }
+
+        pressedHeaderIndex = -1;
+
+        return consumed;
+    }
+
+    // Table view header events
+    public void tableViewChanged(TableViewHeader tableViewHeader,
+        TableView previousTableView) {
+        if (previousTableView != null) {
+            previousTableView.getTableViewColumnListeners().remove(this);
+        }
+
+        TableView tableView = tableViewHeader.getTableView();
+        if (tableView != null) {
+            tableView.getTableViewColumnListeners().add(this);
+        }
+
+        invalidateComponent();
+    }
+
+    public void dataRendererChanged(TableViewHeader tableViewHeader,
+        TableViewHeader.DataRenderer previousDataRenderer) {
+        invalidateComponent();
+    }
+
+    // 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) {
+        // No-op
+    }
+
+    public void columnHeaderDataChanged(TableView.Column column, Object previousHeaderData) {
+        invalidateComponent();
+    }
+
+    public void columnWidthChanged(TableView.Column column, int previousWidth, boolean previousRelative) {
+        invalidateComponent();
+    }
+
+    public void columnSortDirectionChanged(TableView.Column column, SortDirection previousSortDirection) {
+        repaintComponent();
+    }
+
+    public void columnFilterChanged(TableView.Column column, Object previousFilter) {
+        // No-op
+    }
+
+    public void columnCellRendererChanged(TableView.Column column, TableView.CellRenderer previousCellRenderer) {
+        // No-op
+    }
+}