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

svn commit: r754926 [26/38] - in /incubator/pivot/tags/v1.0: ./ charts-test/ charts-test/src/ charts-test/src/pivot/ charts-test/src/pivot/charts/ charts-test/src/pivot/charts/test/ charts/ charts/lib/ charts/src/ charts/src/pivot/ charts/src/pivot/cha...

Added: incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/LabelSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/LabelSkin.java?rev=754926&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/LabelSkin.java (added)
+++ incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/LabelSkin.java Mon Mar 16 16:16:40 2009
@@ -0,0 +1,486 @@
+/*
+ * 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;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.font.FontRenderContext;
+import java.awt.font.LineBreakMeasurer;
+import java.awt.font.LineMetrics;
+import java.awt.font.TextAttribute;
+import java.awt.font.TextLayout;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+
+import pivot.collections.Dictionary;
+import pivot.wtk.Component;
+import pivot.wtk.Dimensions;
+import pivot.wtk.HorizontalAlignment;
+import pivot.wtk.Insets;
+import pivot.wtk.Label;
+import pivot.wtk.LabelListener;
+import pivot.wtk.TextDecoration;
+import pivot.wtk.Theme;
+import pivot.wtk.VerticalAlignment;
+
+/**
+ * Label skin.
+ * <p>
+ * TODO showEllipsis style
+ *
+ * @author gbrown
+ */
+public class LabelSkin extends ComponentSkin implements LabelListener {
+    private FontRenderContext fontRenderContext = new FontRenderContext(null, true, true);
+
+    private Font font;
+    private Color color;
+    private TextDecoration textDecoration;
+    private HorizontalAlignment horizontalAlignment;
+    private VerticalAlignment verticalAlignment;
+    private Insets padding;
+    private boolean wrapText;
+
+    public LabelSkin() {
+        Theme theme = Theme.getTheme();
+        color = Color.BLACK;
+        font = theme.getFont();
+        textDecoration = null;
+        horizontalAlignment = HorizontalAlignment.LEFT;
+        verticalAlignment = VerticalAlignment.TOP;
+        padding = new Insets(0);
+        wrapText = false;
+    }
+
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        Label label = (Label)getComponent();
+        label.getLabelListeners().add(this);
+    }
+
+    @Override
+    public void uninstall() {
+        Label label = (Label)getComponent();
+        label.getLabelListeners().remove(this);
+
+        super.uninstall();
+    }
+
+    public int getPreferredWidth(int height) {
+        int preferredWidth = 0;
+
+        Label label = (Label)getComponent();
+        String text = label.getText();
+
+        if (text != null
+            && text.length() > 0) {
+            Rectangle2D stringBounds = font.getStringBounds(text, fontRenderContext);
+            preferredWidth = (int)Math.ceil(stringBounds.getWidth());
+        }
+
+        preferredWidth += (padding.left + padding.right);
+
+        return preferredWidth;
+    }
+
+    public int getPreferredHeight(int width) {
+        int preferredHeight = 0;
+
+        Label label = (Label)getComponent();
+        String text = label.getText();
+
+        if (text == null) {
+            text = "";
+        }
+
+        if (wrapText
+            && width != -1
+            && text.length() > 0) {
+            int contentWidth = width - (padding.left + padding.right);
+
+            AttributedString attributedText = new AttributedString(text);
+            attributedText.addAttribute(TextAttribute.FONT, font);
+
+            AttributedCharacterIterator aci = attributedText.getIterator();
+            LineBreakMeasurer lbm = new LineBreakMeasurer(aci, fontRenderContext);
+
+            float lineHeights = 0;
+            while (lbm.getPosition() < aci.getEndIndex()) {
+                int offset = lbm.nextOffset(contentWidth);
+
+                LineMetrics lm = font.getLineMetrics(aci,
+                    lbm.getPosition(), offset, fontRenderContext);
+
+                float lineHeight = lm.getAscent() + lm.getDescent()
+                    + lm.getLeading();
+                lineHeights += lineHeight;
+
+                lbm.setPosition(offset);
+            }
+
+            preferredHeight = (int)Math.ceil(lineHeights);
+        } else {
+            LineMetrics lm = font.getLineMetrics(text, fontRenderContext);
+            preferredHeight = (int)Math.ceil(lm.getAscent() + lm.getDescent()
+                + lm.getLeading());
+        }
+
+        preferredHeight += (padding.top + padding.bottom);
+
+        return preferredHeight;
+    }
+
+    public Dimensions getPreferredSize() {
+        int preferredWidth = 0;
+        int preferredHeight = 0;
+
+        Label label = (Label)getComponent();
+        String text = label.getText();
+
+        if (text != null
+            && text.length() > 0) {
+            Rectangle2D stringBounds = font.getStringBounds(text, fontRenderContext);
+            preferredWidth = (int)Math.ceil(stringBounds.getWidth());
+            preferredHeight = (int)Math.ceil(stringBounds.getHeight());
+        }
+
+        preferredWidth += (padding.left + padding.right);
+        preferredHeight += (padding.top + padding.bottom);
+
+        return new Dimensions(preferredWidth, preferredHeight);
+    }
+
+    public void layout() {
+        // No-op
+    }
+
+    public void paint(Graphics2D graphics) {
+        int width = getWidth();
+        int height = getHeight();
+
+        Label label = (Label)getComponent();
+        String text = label.getText();
+
+        if (text != null
+            && text.length() > 0) {
+            if (fontRenderContext.isAntiAliased()) {
+                // TODO Use VALUE_TEXT_ANTIALIAS_LCD_HRGB when JDK 1.6 is
+                // available on OSX?
+                graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+            }
+
+            if (fontRenderContext.usesFractionalMetrics()) {
+                graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
+                    RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+            }
+
+            graphics.setFont(font);
+            graphics.setPaint(color);
+
+            float y = 0;
+            switch (verticalAlignment) {
+                case TOP: {
+                    y = padding.top;
+                    break;
+                }
+
+                case BOTTOM: {
+                    y = height - getPreferredHeight(wrapText ? width : -1) + padding.top;
+                    break;
+                }
+
+                case CENTER: {
+                    y = (height - getPreferredHeight(wrapText ? width : -1)) / 2 + padding.top;
+                    break;
+                }
+            }
+
+            if (wrapText) {
+                AttributedString attributedText = new AttributedString(text);
+                attributedText.addAttribute(TextAttribute.FONT, font);
+
+                AttributedCharacterIterator aci = attributedText.getIterator();
+                LineBreakMeasurer lbm = new LineBreakMeasurer(aci, fontRenderContext);
+
+                int contentWidth = width - (padding.left + padding.right);
+
+                while (lbm.getPosition() < aci.getEndIndex()) {
+                    TextLayout textLayout = lbm.nextLayout(contentWidth);
+                    y += textLayout.getAscent();
+                    drawText(graphics, textLayout, y);
+                    y += textLayout.getDescent() + textLayout.getLeading();
+                }
+            } else {
+                TextLayout textLayout = new TextLayout(text, font, fontRenderContext);
+                drawText(graphics, textLayout, y + textLayout.getAscent());
+            }
+        }
+    }
+
+    private void drawText(Graphics2D graphics, TextLayout textLayout, float y) {
+        float width = getWidth();
+        Rectangle2D textBounds = textLayout.getBounds();
+
+        float x = 0;
+        switch (horizontalAlignment) {
+            case LEFT: {
+                x = padding.left;
+                break;
+            }
+
+            case RIGHT: {
+                x = width - (float)(textBounds.getX() + textBounds.getWidth()) -
+                    padding.right;
+                break;
+            }
+
+            case CENTER: {
+                x = (width - (padding.left + padding.right) -
+                    (float)(textBounds.getX() + textBounds.getWidth())) / 2f +
+                    padding.left;
+                break;
+            }
+        }
+
+        textLayout.draw(graphics, x, y);
+
+        if (textDecoration != null) {
+            graphics.setStroke(new BasicStroke());
+
+            float offset = 0;
+
+            switch (textDecoration) {
+                case UNDERLINE: {
+                    offset = y + 2;
+                    break;
+                }
+
+                case STRIKETHROUGH: {
+                    offset = y - textLayout.getAscent() / 3f + 1;
+                    break;
+                }
+            }
+
+            Line2D line = new Line2D.Float(x, offset, x + (float)textBounds.getWidth(), offset);
+            graphics.draw(line);
+        }
+    }
+
+    /**
+     * @return
+     * <tt>false</tt>; labels are not focusable.
+     */
+    @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 int getFontSize() {
+        return font.getSize();
+    }
+
+    public void setFontSize(int fontSize) {
+        font = font.deriveFont((float)fontSize);
+    }
+
+    public boolean isFontBold() {
+        return ((font.getStyle() & Font.BOLD) == Font.BOLD);
+    }
+
+    public void setFontBold(boolean fontBold) {
+        if (isFontBold() != fontBold) {
+            if (fontBold) {
+                font = font.deriveFont(Font.BOLD);
+            } else {
+                font = font.deriveFont(font.getStyle() & (~Font.BOLD));
+            }
+        }
+    }
+
+    public boolean isFontItalic() {
+        return ((font.getStyle() & Font.ITALIC) == Font.ITALIC);
+    }
+
+    public void setFontItalic(boolean fontItalic) {
+        if (isFontItalic() != fontItalic) {
+            if (fontItalic) {
+                font = font.deriveFont(Font.ITALIC);
+            } else {
+                font = font.deriveFont(font.getStyle() & (~Font.ITALIC));
+            }
+        }
+    }
+
+    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 TextDecoration getTextDecoration() {
+        return textDecoration;
+    }
+
+    public void setTextDecoration(TextDecoration textDecoration) {
+        this.textDecoration = textDecoration;
+        invalidateComponent();
+    }
+
+    public final void setTextDecoration(String textDecoration) {
+        if (textDecoration == null) {
+            throw new IllegalArgumentException("textDecoration is null.");
+        }
+
+        setTextDecoration(TextDecoration.decode(textDecoration));
+    }
+
+    public HorizontalAlignment getHorizontalAlignment() {
+        return horizontalAlignment;
+    }
+
+    public void setHorizontalAlignment(HorizontalAlignment horizontalAlignment) {
+        if (horizontalAlignment == null) {
+            throw new IllegalArgumentException("horizontalAlignment is null.");
+        }
+
+        this.horizontalAlignment = horizontalAlignment;
+        repaintComponent();
+    }
+
+    public final void setHorizontalAlignment(String horizontalAlignment) {
+        if (horizontalAlignment == null) {
+            throw new IllegalArgumentException("horizontalAlignment is null.");
+        }
+
+        setHorizontalAlignment(HorizontalAlignment.decode(horizontalAlignment));
+    }
+
+    public VerticalAlignment getVerticalAlignment() {
+        return verticalAlignment;
+    }
+
+    public void setVerticalAlignment(VerticalAlignment verticalAlignment) {
+        if (verticalAlignment == null) {
+            throw new IllegalArgumentException("verticalAlignment is null.");
+        }
+
+        this.verticalAlignment = verticalAlignment;
+        repaintComponent();
+    }
+
+    public final void setVerticalAlignment(String verticalAlignment) {
+        if (verticalAlignment == null) {
+            throw new IllegalArgumentException("verticalAlignment is null.");
+        }
+
+        setVerticalAlignment(VerticalAlignment.decode(verticalAlignment));
+    }
+
+    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 boolean getWrapText() {
+        return wrapText;
+    }
+
+    public void setWrapText(boolean wrapText) {
+        this.wrapText = wrapText;
+        invalidateComponent();
+    }
+
+    // Label events
+    public void textChanged(Label label, String previousText) {
+        invalidateComponent();
+    }
+
+    public void textKeyChanged(Label label, String previousTextKey) {
+        // No-op
+    }
+}

Added: incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/LinkButtonSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/LinkButtonSkin.java?rev=754926&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/LinkButtonSkin.java (added)
+++ incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/LinkButtonSkin.java Mon Mar 16 16:16:40 2009
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import pivot.wtk.Component;
+import pivot.wtk.LinkButton;
+import pivot.wtk.Mouse;
+
+/**
+ * Abstract base class for link button skins.
+ *
+ * @author gbrown
+ */
+public abstract class LinkButtonSkin extends ButtonSkin {
+    @Override
+    public void mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+        LinkButton linkButton = (LinkButton)getComponent();
+        linkButton.press();
+    }
+}

Added: incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/ListButtonSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/ListButtonSkin.java?rev=754926&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/ListButtonSkin.java (added)
+++ incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/ListButtonSkin.java Mon Mar 16 16:16:40 2009
@@ -0,0 +1,351 @@
+/*
+ * 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;
+
+import pivot.collections.ArrayList;
+import pivot.collections.List;
+import pivot.wtk.Button;
+import pivot.wtk.Component;
+import pivot.wtk.ComponentKeyListener;
+import pivot.wtk.ComponentMouseButtonListener;
+import pivot.wtk.Dimensions;
+import pivot.wtk.Direction;
+import pivot.wtk.Display;
+import pivot.wtk.Keyboard;
+import pivot.wtk.ListButton;
+import pivot.wtk.ListButtonListener;
+import pivot.wtk.ListButtonSelectionListener;
+import pivot.wtk.ListView;
+import pivot.wtk.Mouse;
+import pivot.wtk.Point;
+import pivot.wtk.Popup;
+import pivot.wtk.Window;
+
+/**
+ * Abstract base class for list button skins.
+ * <p>
+ * TODO Extend Popup instead of adding event listeners? May slightly simplify
+ * implementation.
+ * <p>
+ * TODO Rather than blindly closing when a mouse down is received, we could
+ * instead cache the selection state in the popup's container mouse down event
+ * and compare it to the current state in component mouse down. If different,
+ * we close the popup. This would also tie this base class less tightly to its
+ * concrete subclasses.
+ *
+ * @author gbrown
+ */
+public abstract class ListButtonSkin extends ButtonSkin
+    implements ListButtonListener, ListButtonSelectionListener {
+    protected ListView listView;
+    protected Popup listViewPopup;
+
+    private ComponentKeyListener listViewPopupKeyListener = new ComponentKeyListener() {
+        public void keyTyped(Component component, char character) {
+            // No-op
+        }
+
+        public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+            switch (keyCode) {
+                case Keyboard.KeyCode.ESCAPE: {
+                    listViewPopup.close();
+                    getComponent().requestFocus();
+                    break;
+                }
+
+                case Keyboard.KeyCode.TAB:
+                case Keyboard.KeyCode.ENTER: {
+                    ListButton listButton = (ListButton)getComponent();
+
+                    int index = listView.getSelectedIndex();
+
+                    listView.clearSelection();
+                    listButton.setSelectedIndex(index);
+
+                    listViewPopup.close();
+
+                    if (keyCode == Keyboard.KeyCode.TAB) {
+                        Direction direction = (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) ?
+                            Direction.BACKWARD : Direction.FORWARD;
+                        listButton.transferFocus(direction);
+                    } else {
+                        listButton.requestFocus();
+                    }
+
+                    break;
+                }
+            }
+
+            return false;
+        }
+
+        public boolean keyReleased(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+            return false;
+        }
+    };
+
+    private ComponentMouseButtonListener listViewPopupMouseListener = new ComponentMouseButtonListener() {
+        public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
+            return false;
+        }
+
+        public boolean mouseUp(Component component, Mouse.Button button, int x, int y) {
+            return false;
+        }
+
+        public void mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+            ListButton listButton = (ListButton)getComponent();
+
+            int index = listView.getSelectedIndex();
+
+            listView.clearSelection();
+            listButton.setSelectedIndex(index);
+
+            listViewPopup.close();
+            getComponent().requestFocus();
+        }
+    };
+
+    protected boolean pressed = false;
+
+    public ListButtonSkin() {
+        listView = new ListView();
+
+        listViewPopup = new Popup();
+        listViewPopup.getComponentMouseButtonListeners().add(listViewPopupMouseListener);
+        listViewPopup.getComponentKeyListeners().add(listViewPopupKeyListener);
+    }
+
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        ListButton listButton = (ListButton)component;
+        listButton.getListButtonListeners().add(this);
+        listButton.getListButtonSelectionListeners().add(this);
+
+        listView.setListData(listButton.getListData());
+        listView.setItemRenderer(listButton.getItemRenderer());
+    }
+
+    @Override
+    public void uninstall() {
+        listViewPopup.close();
+
+        ListButton listButton = (ListButton)getComponent();
+        listButton.getListButtonListeners().remove(this);
+        listButton.getListButtonSelectionListeners().remove(this);
+
+        listView.setListData(new ArrayList<Object>());
+
+        super.uninstall();
+    }
+
+    // Component state events
+    @Override
+    public void enabledChanged(Component component) {
+        super.enabledChanged(component);
+
+        listViewPopup.close();
+        pressed = false;
+    }
+
+    @Override
+    public void focusedChanged(Component component, boolean temporary) {
+        super.focusedChanged(component, temporary);
+
+        // Close the popup if focus was transferred to a component whose
+        // window is not the popup
+        if (!component.isFocused()
+            && !listViewPopup.containsFocus()) {
+            listViewPopup.close();
+        }
+
+        pressed = false;
+    }
+
+    // Component mouse events
+    @Override
+    public void mouseOut(Component component) {
+        super.mouseOut(component);
+
+        pressed = false;
+    }
+
+    @Override
+    public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseDown(component, button, x, y);
+
+        pressed = true;
+        repaintComponent();
+
+        return consumed;
+    }
+
+    @Override
+    public boolean mouseUp(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseUp(component, button, x, y);
+
+        pressed = false;
+        repaintComponent();
+
+        return consumed;
+    }
+
+    @Override
+    public void mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+        ListButton listButton = (ListButton)getComponent();
+
+        listButton.requestFocus();
+        listButton.press();
+
+        if (listView.isShowing()) {
+            listView.requestFocus();
+        }
+    }
+
+    @Override
+    public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        if (keyCode == Keyboard.KeyCode.SPACE) {
+            pressed = true;
+            repaintComponent();
+            consumed = true;
+        } else if (keyCode == Keyboard.KeyCode.UP) {
+            ListButton listButton = (ListButton)getComponent();
+            int selectedIndex = listButton.getSelectedIndex();
+
+            if (selectedIndex > 0) {
+                listButton.setSelectedIndex(selectedIndex - 1);
+                consumed = true;
+            }
+        } else if (keyCode == Keyboard.KeyCode.DOWN) {
+            ListButton listButton = (ListButton)getComponent();
+            int selectedIndex = listButton.getSelectedIndex();
+
+            if (selectedIndex < listButton.getListData().getLength() - 1) {
+                listButton.setSelectedIndex(selectedIndex + 1);
+                consumed = true;
+            }
+        } else {
+            consumed = super.keyPressed(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean keyReleased(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        ListButton listButton = (ListButton)getComponent();
+
+        if (keyCode == Keyboard.KeyCode.SPACE) {
+            pressed = false;
+            repaintComponent();
+
+            listButton.press();
+        } else {
+            consumed = super.keyReleased(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+    // Button events
+    @Override
+    public void buttonPressed(Button button) {
+        if (listViewPopup.isOpen()) {
+            listViewPopup.close();
+        } else {
+            ListButton listButton = (ListButton)button;
+
+            Component content = listViewPopup.getContent();
+
+            if (listButton.getListData().getLength() > 0) {
+                // Determine the popup's location and preferred size, relative
+                // to the button
+                Window window = listButton.getWindow();
+
+                if (window != null) {
+                    int width = getWidth();
+                    int height = getHeight();
+
+                    Display display = listButton.getDisplay();
+
+                    // Ensure that the popup remains within the bounds of the display
+                    Point buttonLocation = listButton.mapPointToAncestor(display, 0, 0);
+
+                    Dimensions displaySize = display.getSize();
+                    Dimensions popupSize = content.getPreferredSize();
+
+                    int x = buttonLocation.x;
+                    if (popupSize.width > width
+                        && x + popupSize.width > displaySize.width) {
+                        x = buttonLocation.x + width - popupSize.width;
+                    }
+
+                    int y = buttonLocation.y + height - 1;
+                    if (y + popupSize.height > displaySize.height) {
+                        if (buttonLocation.y - popupSize.height > 0) {
+                            y = buttonLocation.y - popupSize.height + 1;
+                        } else {
+                            popupSize.height = displaySize.height - y;
+                        }
+                    } else {
+                        popupSize.height = -1;
+                    }
+
+                    listViewPopup.setLocation(x, y);
+                    listViewPopup.setPreferredSize(popupSize);
+                    listViewPopup.open(listButton);
+
+                    if (listView.getFirstSelectedIndex() == -1
+                        && listView.getListData().getLength() > 0) {
+                        listView.setSelectedIndex(0);
+                    }
+
+                    listView.requestFocus();
+                }
+            }
+        }
+    }
+
+    // List button events
+    public void listDataChanged(ListButton listButton, List<?> previousListData) {
+        listView.setListData(listButton.getListData());
+    }
+
+    public void itemRendererChanged(ListButton listButton, ListView.ItemRenderer previousItemRenderer) {
+        listView.setItemRenderer(listButton.getItemRenderer());
+    }
+
+    public void selectedValueKeyChanged(ListButton listButton, String previousSelectedValueKey) {
+        // No-op
+    }
+
+    // List button selection events
+    public void selectedIndexChanged(ListButton listButton, int previousSelectedIndex) {
+        // Set the selected item as the button data
+        int selectedIndex = listButton.getSelectedIndex();
+
+        Object buttonData = (selectedIndex == -1) ? null : listButton.getListData().get(selectedIndex);
+        listButton.setButtonData(buttonData);
+
+        listView.setSelectedIndex(listButton.getSelectedIndex());
+    }
+}

Added: incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuBarItemSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuBarItemSkin.java?rev=754926&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuBarItemSkin.java (added)
+++ incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuBarItemSkin.java Mon Mar 16 16:16:40 2009
@@ -0,0 +1,256 @@
+/*
+ * 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;
+
+import pivot.util.Vote;
+import pivot.wtk.Button;
+import pivot.wtk.Component;
+import pivot.wtk.ComponentKeyListener;
+import pivot.wtk.Direction;
+import pivot.wtk.Display;
+import pivot.wtk.Keyboard;
+import pivot.wtk.Menu;
+import pivot.wtk.MenuBar;
+import pivot.wtk.MenuPopup;
+import pivot.wtk.Mouse;
+import pivot.wtk.Point;
+import pivot.wtk.Window;
+import pivot.wtk.WindowStateListener;
+
+/**
+ * Abstract base class for menu bar item skins.
+ *
+ * @author gbrown
+ */
+public abstract class MenuBarItemSkin extends ButtonSkin implements MenuBar.ItemListener {
+    protected MenuPopup menuPopup = new MenuPopup();
+
+    public MenuBarItemSkin() {
+        menuPopup.getWindowStateListeners().add(new WindowStateListener() {
+            public Vote previewWindowOpen(Window window, Display display) {
+                return Vote.APPROVE;
+            }
+
+            public void windowOpenVetoed(Window window, Vote reason) {
+                // No-op
+            }
+
+            public void windowOpened(Window window) {
+                // No-op
+            }
+
+            public Vote previewWindowClose(Window window) {
+                return Vote.APPROVE;
+            }
+
+            public void windowCloseVetoed(Window window, Vote reason) {
+                // No-op
+            }
+
+            public void windowClosed(Window window, Display display) {
+                MenuBar.Item menuBarItem = (MenuBar.Item)getComponent();
+                if (menuBarItem.isFocused()) {
+                    Component.clearFocus();
+                } else {
+                    repaintComponent();
+                }
+
+                MenuBar menuBar = menuBarItem.getMenuBar();
+                if (!menuBar.containsFocus()) {
+                    menuBar.setActive(false);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        MenuBar.Item menuBarItem = (MenuBar.Item)component;
+        menuBarItem.getItemListeners().add(this);
+
+        menuPopup.setMenu(menuBarItem.getMenu());
+    }
+
+    @Override
+    public void uninstall() {
+        MenuBar.Item menuBarItem = (MenuBar.Item)getComponent();
+        menuBarItem.getItemListeners().remove(this);
+
+        menuPopup.close();
+        menuPopup.setMenu(null);
+
+        super.uninstall();
+    }
+
+    @Override
+    public void mouseOver(Component component) {
+        super.mouseOver(component);
+
+        MenuBar.Item menuBarItem = (MenuBar.Item)getComponent();
+        MenuBar menuBar = menuBarItem.getMenuBar();
+
+        if (menuBar.isActive()) {
+            menuBarItem.requestFocus();
+        }
+    }
+
+    @Override
+    public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseDown(component, button, x, y);
+
+        MenuBar.Item menuBarItem = (MenuBar.Item)getComponent();
+        menuBarItem.requestFocus();
+
+        return consumed;
+    }
+
+    @Override
+    public void mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+        super.mouseClick(component, button, x, y, count);
+
+        MenuBar.Item menuBarItem = (MenuBar.Item)getComponent();
+        menuBarItem.press();
+    }
+
+    @Override
+    public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        MenuBar.Item menuBarItem = (MenuBar.Item)getComponent();
+
+        if (keyCode == Keyboard.KeyCode.UP) {
+            menuPopup.requestFocus();
+            Component focusedComponent = Component.getFocusedComponent();
+            if (focusedComponent != null) {
+                focusedComponent.transferFocus(Direction.BACKWARD);
+            }
+
+            consumed = true;
+        } else if (keyCode == Keyboard.KeyCode.DOWN) {
+            menuPopup.requestFocus();
+            consumed = true;
+        } else if (keyCode == Keyboard.KeyCode.LEFT) {
+            menuBarItem.transferFocus(Direction.BACKWARD);
+            consumed = true;
+        } else if (keyCode == Keyboard.KeyCode.RIGHT) {
+            menuBarItem.transferFocus(Direction.FORWARD);
+            consumed = true;
+        } else if (keyCode == Keyboard.KeyCode.ENTER) {
+            menuBarItem.press();
+            consumed = true;
+        } else if (keyCode == Keyboard.KeyCode.ESCAPE) {
+            Component.clearFocus();
+            consumed = true;
+        } else {
+            consumed = super.keyPressed(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean keyReleased(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        MenuBar.Item menuBarItem = (MenuBar.Item)getComponent();
+
+        if (keyCode == Keyboard.KeyCode.SPACE) {
+            menuBarItem.press();
+            consumed = true;
+        } else {
+            consumed = super.keyReleased(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public void enabledChanged(Component component) {
+        super.enabledChanged(component);
+
+        menuPopup.close();
+    }
+
+    @Override
+    public void focusedChanged(Component component, boolean temporary) {
+        super.focusedChanged(component, temporary);
+
+        final MenuBar.Item menuBarItem = (MenuBar.Item)getComponent();
+
+        if (component.isFocused()) {
+            if (!menuPopup.isOpen()) {
+                Display display = menuBarItem.getDisplay();
+                Point menuBarItemLocation = menuBarItem.mapPointToAncestor(display, 0, getHeight());
+
+                // TODO Ensure that the popup remains within the bounds of the display
+
+                menuPopup.setLocation(menuBarItemLocation.x, menuBarItemLocation.y);
+                menuPopup.open(menuBarItem);
+
+                // Listen for key events from the popup
+                menuPopup.getComponentKeyListeners().add(new ComponentKeyListener() {
+                    public void keyTyped(Component component, char character) {
+                        // No-op
+                    }
+
+                    public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+                        if (keyCode == Keyboard.KeyCode.LEFT
+                            || (keyCode == Keyboard.KeyCode.TAB
+                                && Keyboard.isPressed(Keyboard.Modifier.SHIFT))) {
+                            menuBarItem.transferFocus(Direction.BACKWARD);
+                        } else if (keyCode == Keyboard.KeyCode.RIGHT
+                            || keyCode == Keyboard.KeyCode.TAB) {
+                            menuBarItem.transferFocus(Direction.FORWARD);
+                        } else if (keyCode == Keyboard.KeyCode.ESCAPE) {
+                            Component.clearFocus();
+                        }
+
+                        return false;
+                    }
+
+                    public boolean keyReleased(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+                        return false;
+                    }
+                });
+            }
+        } else {
+            if (!temporary
+                && !menuPopup.containsFocus()) {
+                menuPopup.close();
+            }
+        }
+    }
+
+    public void buttonPressed(Button button) {
+        MenuBar.Item menuBarItem = (MenuBar.Item)getComponent();
+        MenuBar menuBar = menuBarItem.getMenuBar();
+
+        if (menuPopup.isOpen()) {
+            if (menuBar.isActive()) {
+                Component.clearFocus();
+            } else {
+                menuBar.setActive(true);
+            }
+        }
+    }
+
+    public void menuChanged(MenuBar.Item menuBarItem, Menu previousMenu) {
+        menuPopup.setMenu(menuBarItem.getMenu());
+        repaintComponent();
+    }
+}

Added: incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuButtonSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuButtonSkin.java?rev=754926&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuButtonSkin.java (added)
+++ incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuButtonSkin.java Mon Mar 16 16:16:40 2009
@@ -0,0 +1,256 @@
+/*
+ * 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;
+
+import pivot.util.Vote;
+import pivot.wtk.Button;
+import pivot.wtk.Component;
+import pivot.wtk.Dimensions;
+import pivot.wtk.Display;
+import pivot.wtk.Keyboard;
+import pivot.wtk.Menu;
+import pivot.wtk.MenuButton;
+import pivot.wtk.MenuButtonListener;
+import pivot.wtk.MenuPopup;
+import pivot.wtk.Mouse;
+import pivot.wtk.Point;
+import pivot.wtk.Window;
+import pivot.wtk.WindowStateListener;
+
+/**
+ * Abstract base class for menu button skins.
+ *
+ * @author gbrown
+ */
+public abstract class MenuButtonSkin extends ButtonSkin
+    implements MenuButtonListener {
+    protected boolean pressed = false;
+    protected MenuPopup menuPopup = new MenuPopup();
+
+    public MenuButtonSkin() {
+        menuPopup.getWindowStateListeners().add(new WindowStateListener() {
+            public Vote previewWindowOpen(Window window, Display display) {
+                return Vote.APPROVE;
+            }
+
+            public void windowOpenVetoed(Window window, Vote reason) {
+                // No-op
+            }
+
+            public void windowOpened(Window window) {
+                // No-op
+            }
+
+            public Vote previewWindowClose(Window window) {
+                return Vote.APPROVE;
+            }
+
+            public void windowCloseVetoed(Window window, Vote reason) {
+                // No-op
+            }
+
+            public void windowClosed(Window window, Display display) {
+                MenuButton menuButton = (MenuButton)getComponent();
+
+                if (menuButton.isFocusable()) {
+                    menuButton.requestFocus();
+                }
+
+                repaintComponent();
+            }
+        });
+    }
+
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        MenuButton menuButton = (MenuButton)getComponent();
+        menuButton.getMenuButtonListeners().add(this);
+
+        menuPopup.setMenu(menuButton.getMenu());
+    }
+
+    @Override
+    public void uninstall() {
+        MenuButton menuButton = (MenuButton)getComponent();
+        menuButton.getMenuButtonListeners().remove(this);
+
+        menuPopup.setMenu(null);
+
+        super.uninstall();
+    }
+
+    // Component state events
+    @Override
+    public void enabledChanged(Component component) {
+        super.enabledChanged(component);
+
+        menuPopup.close();
+        pressed = false;
+    }
+
+    @Override
+    public void focusedChanged(Component component, boolean temporary) {
+        super.focusedChanged(component, temporary);
+
+        // Close the popup if focus was transferred to a component whose
+        // window is not the popup
+        if (!component.isFocused()
+            && !menuPopup.containsFocus()) {
+            menuPopup.close();
+        }
+
+        pressed = false;
+    }
+
+    // Component mouse events
+    @Override
+    public void mouseOut(Component component) {
+        super.mouseOut(component);
+
+        pressed = false;
+    }
+
+    @Override
+    public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseDown(component, button, x, y);
+
+        // TODO Consume the event if the menu button is repeatable and the event
+        // occurs over the trigger
+
+        pressed = true;
+        repaintComponent();
+
+        return consumed;
+    }
+
+    @Override
+    public boolean mouseUp(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseUp(component, button, x, y);
+
+        pressed = false;
+        repaintComponent();
+
+        return consumed;
+    }
+
+    @Override
+    public void mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+        MenuButton menuButton = (MenuButton)getComponent();
+
+        if (menuButton.isFocusable()) {
+            menuButton.requestFocus();
+        }
+
+        menuButton.press();
+
+        if (menuPopup.isShowing()) {
+            menuPopup.requestFocus();
+        }
+    }
+
+    @Override
+    public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        if (keyCode == Keyboard.KeyCode.SPACE) {
+            pressed = true;
+            repaintComponent();
+            consumed = true;
+        } else {
+            consumed = super.keyPressed(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean keyReleased(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        MenuButton menuButton = (MenuButton)getComponent();
+
+        if (keyCode == Keyboard.KeyCode.SPACE) {
+            pressed = false;
+            repaintComponent();
+
+            menuButton.press();
+        } else {
+            consumed = super.keyReleased(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+    // Button events
+    public void buttonPressed(Button button) {
+        if (menuPopup.isOpen()) {
+            menuPopup.close();
+        } else {
+            MenuButton menuButton = (MenuButton)getComponent();
+            Component content = menuPopup.getContent();
+
+            // Determine the popup's location and preferred size, relative
+            // to the button
+            Window window = menuButton.getWindow();
+
+            if (window != null) {
+                int width = getWidth();
+                int height = getHeight();
+
+                Display display = menuButton.getDisplay();
+
+                // Ensure that the popup remains within the bounds of the display
+                Point buttonLocation = menuButton.mapPointToAncestor(display, 0, 0);
+
+                Dimensions displaySize = display.getSize();
+                Dimensions popupSize = content.getPreferredSize();
+
+                int x = buttonLocation.x;
+                if (popupSize.width > width
+                    && x + popupSize.width > displaySize.width) {
+                    x = buttonLocation.x + width - popupSize.width;
+                }
+
+                int y = buttonLocation.y + height - 1;
+                if (y + popupSize.height > displaySize.height) {
+                    if (buttonLocation.y - popupSize.height > 0) {
+                        y = buttonLocation.y - popupSize.height + 1;
+                    } else {
+                        popupSize.height = displaySize.height - y;
+                    }
+                } else {
+                    popupSize.height = -1;
+                }
+
+                menuPopup.setLocation(x, y);
+                menuPopup.setPreferredSize(popupSize);
+                menuPopup.open(menuButton);
+
+                menuPopup.requestFocus();
+            }
+        }
+    }
+
+    public void menuChanged(MenuButton menuButton, Menu previousMenu) {
+        menuPopup.setMenu(menuButton.getMenu());
+    }
+
+    public void repeatableChanged(MenuButton menuButton) {
+        invalidateComponent();
+    }
+}

Added: incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuItemSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuItemSkin.java?rev=754926&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuItemSkin.java (added)
+++ incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/MenuItemSkin.java Mon Mar 16 16:16:40 2009
@@ -0,0 +1,192 @@
+/*
+ * 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;
+
+import pivot.wtk.ApplicationContext;
+import pivot.wtk.Button;
+import pivot.wtk.Component;
+import pivot.wtk.Direction;
+import pivot.wtk.Display;
+import pivot.wtk.Keyboard;
+import pivot.wtk.Menu;
+import pivot.wtk.MenuPopup;
+import pivot.wtk.Mouse;
+import pivot.wtk.Point;
+
+/**
+ * Abstract base class for menu item skins.
+ *
+ * @author gbrown
+ */
+public abstract class MenuItemSkin extends ButtonSkin implements Menu.ItemListener {
+    protected MenuPopup menuPopup = new MenuPopup();
+
+    protected int buttonPressTimeoutID = -1;
+    protected int buttonPressInterval = 200;
+
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        Menu.Item menuItem = (Menu.Item)component;
+        menuItem.getItemListeners().add(this);
+
+        menuPopup.setMenu(menuItem.getMenu());
+    }
+
+    @Override
+    public void uninstall() {
+        Menu.Item menuItem = (Menu.Item)getComponent();
+        menuItem.getItemListeners().remove(this);
+
+        menuPopup.close();
+        menuPopup.setMenu(null);
+
+        super.uninstall();
+    }
+
+    @Override
+    public void mouseOver(Component component) {
+        super.mouseOver(component);
+
+        ApplicationContext.clearInterval(buttonPressTimeoutID);
+
+        final Menu.Item menuItem = (Menu.Item)getComponent();
+        if (menuItem.getMenu() != null) {
+            buttonPressTimeoutID = ApplicationContext.setTimeout(new Runnable() {
+                public void run() {
+                    menuItem.press();
+                }
+            }, buttonPressInterval);
+        }
+
+        menuItem.requestFocus();
+    }
+
+    @Override
+    public void mouseOut(Component component) {
+        super.mouseOut(component);
+        ApplicationContext.clearInterval(buttonPressTimeoutID);
+    }
+
+    @Override
+    public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseDown(component, button, x, y);
+        ApplicationContext.clearInterval(buttonPressTimeoutID);
+
+        return consumed;
+    }
+
+    @Override
+    public void mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+        super.mouseClick(component, button, x, y, count);
+
+        Menu.Item menuItem = (Menu.Item)getComponent();
+        menuItem.press();
+    }
+
+    @Override
+    public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        ApplicationContext.clearInterval(buttonPressTimeoutID);
+
+        Menu.Item menuItem = (Menu.Item)getComponent();
+        Menu menu = menuItem.getMenu();
+
+        if (keyCode == Keyboard.KeyCode.UP) {
+            menuItem.transferFocus(Direction.BACKWARD);
+            consumed = true;
+        } else if (keyCode == Keyboard.KeyCode.DOWN) {
+            menuItem.transferFocus(Direction.FORWARD);
+            consumed = true;
+        } else if (keyCode == Keyboard.KeyCode.LEFT) {
+            Menu parentMenu = menuItem.getSection().getMenu();
+            Menu.Item parentMenuItem = parentMenu.getItem();
+            if (parentMenuItem != null) {
+                parentMenuItem.requestFocus();
+                consumed = true;
+            }
+
+            menuPopup.close();
+        } else if (keyCode == Keyboard.KeyCode.RIGHT) {
+            if (menu != null) {
+                if (!menuPopup.isOpen()) {
+                    menuItem.press();
+                }
+
+                menu.requestFocus();
+                consumed = true;
+            }
+        } else if (keyCode == Keyboard.KeyCode.ENTER) {
+            menuItem.press();
+            consumed = true;
+        } else if (keyCode == Keyboard.KeyCode.TAB) {
+            // No-op
+        } else {
+            consumed = super.keyPressed(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean keyReleased(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        Menu.Item menuItem = (Menu.Item)getComponent();
+
+        if (keyCode == Keyboard.KeyCode.SPACE) {
+            menuItem.press();
+            consumed = true;
+        } else {
+            consumed = super.keyReleased(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public void enabledChanged(Component component) {
+        super.enabledChanged(component);
+
+        menuPopup.close();
+    }
+
+    @Override
+    public void buttonPressed(Button button) {
+        Menu.Item menuItem = (Menu.Item)getComponent();
+        Menu menu = menuItem.getMenu();
+
+        if (menu != null
+            && !menuPopup.isOpen()) {
+            // Determine the popup's location and preferred size, relative
+            // to the menu item
+            Display display = menuItem.getDisplay();
+            Point menuItemLocation = menuItem.mapPointToAncestor(display, getWidth(), 0);
+
+            // TODO Ensure that the popup remains within the bounds of the display
+
+            menuPopup.setLocation(menuItemLocation.x, menuItemLocation.y);
+            menuPopup.open(menuItem);
+        }
+    }
+
+    public void menuChanged(Menu.Item menuItem, Menu previousMenu) {
+        menuPopup.setMenu(menuItem.getMenu());
+        repaintComponent();
+    }
+}

Added: incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/PopupSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/PopupSkin.java?rev=754926&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/PopupSkin.java (added)
+++ incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/PopupSkin.java Mon Mar 16 16:16:40 2009
@@ -0,0 +1,184 @@
+/*
+ * 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;
+
+import pivot.wtk.Component;
+import pivot.wtk.ComponentListener;
+import pivot.wtk.Container;
+import pivot.wtk.ContainerMouseListener;
+import pivot.wtk.Cursor;
+import pivot.wtk.Display;
+import pivot.wtk.Mouse;
+import pivot.wtk.Popup;
+import pivot.wtk.PopupListener;
+import pivot.wtk.Window;
+
+/**
+ * Popup skin.
+ *
+ * @author gbrown
+ */
+public class PopupSkin extends WindowSkin implements PopupListener {
+    private ComponentListener affiliateComponentListener = new ComponentListener() {
+        public void parentChanged(Component component, Container previousParent) {
+            // Ignore this event if it came from the affiliate's window.
+            // The window's parent may change as a result of a z-order change or
+            // the window being closed. As an owned window, this window will remain
+            // on top of the affiliate's window if the parent change is a result of
+            // a z-order change and will close if the parent change is a result
+            // of the affiliate's window being closed.
+
+            if (!(component instanceof Window)) {
+                // Remove this as a component listener on the previous parent's
+                // ancestry
+                Component ancestor = previousParent;
+
+                while (ancestor != null) {
+                    ancestor.getComponentListeners().remove(this);
+                    ancestor = ancestor.getParent();
+                }
+
+                // Close the popup
+                Popup popup = (Popup)getComponent();
+                popup.close();
+            }
+        }
+
+        public void sizeChanged(Component component, int previousWidth, int previousHeight) {
+            // Close the popup
+            Popup popup = (Popup)getComponent();
+            popup.close();
+        }
+
+        public void locationChanged(Component component, int previousX, int previousY) {
+            // Close the popup
+            Popup popup = (Popup)getComponent();
+            popup.close();
+        }
+
+        public void visibleChanged(Component component) {
+            // Close the popup
+            Popup popup = (Popup)getComponent();
+            popup.close();
+        }
+
+        public void styleUpdated(Component component, String styleKey, Object previousValue) {
+            // No-op
+        }
+
+        public void cursorChanged(Component component, Cursor previousCursor) {
+            // No-op
+        }
+
+        public void tooltipTextChanged(Component component, String previousTooltipText) {
+            // No-op
+        }
+    };
+
+    private ContainerMouseListener displayMouseListener = new ContainerMouseListener() {
+        public void mouseMove(Container container, int x, int y) {
+        }
+
+        public void mouseDown(Container container, Mouse.Button button, int x, int y) {
+            mouseEvent(container, x, y);
+        }
+
+        public void mouseUp(Container container, Mouse.Button button, int x, int y) {
+        }
+
+        public void mouseWheel(Container container, Mouse.ScrollType scrollType,
+            int scrollAmount, int wheelRotation, int x, int y) {
+            mouseEvent(container, x, y);
+        }
+
+        private void mouseEvent(Container container, int x, int y) {
+            // If the event did not occur within a window that is owned by
+            // this popup and did not occur within the popup's affiliate, close
+            // the popup
+            Display display = (Display)container;
+            Popup popup = (Popup)getComponent();
+
+            Window window = (Window)display.getComponentAt(x, y);
+            if (window == null
+                || !popup.isOwningAncestorOf(window)) {
+                Component affiliate = popup.getAffiliate();
+                Component descendant = display.getDescendantAt(x, y);
+
+                if (affiliate == null
+                    || (affiliate instanceof Container
+                        && !((Container)affiliate).isAncestor(descendant))
+                    || descendant != affiliate) {
+                    popup.close();
+                }
+            }
+        }
+    };
+
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        Popup popup = (Popup)component;
+        popup.getPopupListeners().add(this);
+    }
+
+    @Override
+    public void uninstall() {
+        Popup popup = (Popup)getComponent();
+        popup.getPopupListeners().remove(this);
+
+        super.uninstall();
+    }
+
+    // Window events
+    @Override
+    public void windowOpened(Window window) {
+        // Add this as a component and container mouse listener on display
+        Display display = window.getDisplay();
+        display.getContainerMouseListeners().add(displayMouseListener);
+
+        // Add this as a component listener on the affiliate's ancestry
+        Popup popup = (Popup)window;
+        Component ancestor = popup.getAffiliate();
+
+        while (ancestor != null) {
+            ancestor.getComponentListeners().add(affiliateComponentListener);
+            ancestor = ancestor.getParent();
+        }
+    }
+
+    @Override
+    public void windowClosed(Window window, Display display) {
+        // Remove this as a component and container mouse listener on display
+        display.getContainerMouseListeners().remove(displayMouseListener);
+
+        // Remove this as a component listener on the affiliate's ancestry
+        Popup popup = (Popup)window;
+        Component ancestor = popup.getAffiliate();
+
+        while (ancestor != null) {
+            ancestor.getComponentListeners().remove(affiliateComponentListener);
+            ancestor = ancestor.getParent();
+        }
+    }
+
+    // Popup events
+    public void affiliateChanged(Popup popup, Component previousAffiliate) {
+        // No-op; the popup won't let the affiliate change while it is
+        // open, and we add/remove affiliate listeners in the open/close
+        // handlers
+    }
+}

Added: incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/PushButtonSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/PushButtonSkin.java?rev=754926&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/PushButtonSkin.java (added)
+++ incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/PushButtonSkin.java Mon Mar 16 16:16:40 2009
@@ -0,0 +1,99 @@
+package pivot.wtk.skin;
+
+import pivot.wtk.Component;
+import pivot.wtk.Keyboard;
+import pivot.wtk.Mouse;
+import pivot.wtk.PushButton;
+
+/**
+ * Abstract base class for push button skins.
+ *
+ * @author gbrown
+ */
+public abstract class PushButtonSkin extends ButtonSkin {
+    protected boolean pressed = false;
+
+    @Override
+    public void enabledChanged(Component component) {
+        super.enabledChanged(component);
+
+        pressed = false;
+    }
+
+    @Override
+    public void focusedChanged(Component component, boolean temporary) {
+        super.focusedChanged(component, temporary);
+
+        pressed = false;
+    }
+
+    @Override
+    public void mouseOut(Component component) {
+        super.mouseOut(component);
+
+        pressed = false;
+    }
+
+    @Override
+    public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseDown(component, button, x, y);
+
+        pressed = true;
+        repaintComponent();
+
+        return consumed;
+    }
+
+    @Override
+    public boolean mouseUp(Component component, Mouse.Button button, int x, int y) {
+        boolean consumed = super.mouseUp(component, button, x, y);
+
+        pressed = false;
+        repaintComponent();
+
+        return consumed;
+    }
+
+    @Override
+    public void mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+        PushButton pushButton = (PushButton)getComponent();
+
+        if (pushButton.isFocusable()) {
+            pushButton.requestFocus();
+        }
+
+        pushButton.press();
+    }
+
+    @Override
+    public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        if (keyCode == Keyboard.KeyCode.SPACE) {
+            pressed = true;
+            repaintComponent();
+        } else {
+            consumed = super.keyPressed(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean keyReleased(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        PushButton pushButton = (PushButton)getComponent();
+
+        if (keyCode == Keyboard.KeyCode.SPACE) {
+            pressed = false;
+            repaintComponent();
+
+            pushButton.press();
+        } else {
+            consumed = super.keyReleased(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+}

Added: incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/RadioButtonSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/RadioButtonSkin.java?rev=754926&view=auto
==============================================================================
--- incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/RadioButtonSkin.java (added)
+++ incubator/pivot/tags/v1.0/wtk/src/pivot/wtk/skin/RadioButtonSkin.java Mon Mar 16 16:16:40 2009
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import pivot.wtk.Component;
+import pivot.wtk.Keyboard;
+import pivot.wtk.Mouse;
+import pivot.wtk.RadioButton;
+
+/**
+ * Abstract base class for radio button skins.
+ *
+ * @author gbrown
+ */
+public abstract class RadioButtonSkin extends ButtonSkin {
+    @Override
+    public void mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+        RadioButton radioButton = (RadioButton)getComponent();
+
+        radioButton.requestFocus();
+        radioButton.press();
+    }
+
+    @Override
+    public boolean keyReleased(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = false;
+
+        RadioButton radioButton = (RadioButton)getComponent();
+
+        if (keyCode == Keyboard.KeyCode.SPACE) {
+            radioButton.press();
+        } else {
+            consumed = super.keyReleased(component, keyCode, keyLocation);
+        }
+
+        return consumed;
+    }
+
+}