jdk/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalTitlePane.java
changeset 25859 3317bb8137f4
parent 23697 e556a715949f
child 47020 2c55106dc37b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalTitlePane.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,1046 @@
+/*
+ * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javax.swing.plaf.metal;
+
+import sun.swing.SwingUtilities2;
+import sun.awt.SunToolkit;
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.event.InternalFrameEvent;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.*;
+import java.util.Locale;
+import javax.accessibility.*;
+
+
+/**
+ * Class that manages a JLF awt.Window-descendant class's title bar.
+ * <p>
+ * This class assumes it will be created with a particular window
+ * decoration style, and that if the style changes, a new one will
+ * be created.
+ *
+ * @author Terry Kellerman
+ * @since 1.4
+ */
+@SuppressWarnings("serial") // Superclass is not serializable across versions
+class MetalTitlePane extends JComponent {
+    private static final Border handyEmptyBorder = new EmptyBorder(0,0,0,0);
+    private static final int IMAGE_HEIGHT = 16;
+    private static final int IMAGE_WIDTH = 16;
+
+    /**
+     * PropertyChangeListener added to the JRootPane.
+     */
+    private PropertyChangeListener propertyChangeListener;
+
+    /**
+     * JMenuBar, typically renders the system menu items.
+     */
+    private JMenuBar menuBar;
+    /**
+     * Action used to close the Window.
+     */
+    private Action closeAction;
+
+    /**
+     * Action used to iconify the Frame.
+     */
+    private Action iconifyAction;
+
+    /**
+     * Action to restore the Frame size.
+     */
+    private Action restoreAction;
+
+    /**
+     * Action to restore the Frame size.
+     */
+    private Action maximizeAction;
+
+    /**
+     * Button used to maximize or restore the Frame.
+     */
+    private JButton toggleButton;
+
+    /**
+     * Button used to maximize or restore the Frame.
+     */
+    private JButton iconifyButton;
+
+    /**
+     * Button used to maximize or restore the Frame.
+     */
+    private JButton closeButton;
+
+    /**
+     * Icon used for toggleButton when window is normal size.
+     */
+    private Icon maximizeIcon;
+
+    /**
+     * Icon used for toggleButton when window is maximized.
+     */
+    private Icon minimizeIcon;
+
+    /**
+     * Image used for the system menu icon
+     */
+    private Image systemIcon;
+
+    /**
+     * Listens for changes in the state of the Window listener to update
+     * the state of the widgets.
+     */
+    private WindowListener windowListener;
+
+    /**
+     * Window we're currently in.
+     */
+    private Window window;
+
+    /**
+     * JRootPane rendering for.
+     */
+    private JRootPane rootPane;
+
+    /**
+     * Room remaining in title for bumps.
+     */
+    private int buttonsWidth;
+
+    /**
+     * Buffered Frame.state property. As state isn't bound, this is kept
+     * to determine when to avoid updating widgets.
+     */
+    private int state;
+
+    /**
+     * MetalRootPaneUI that created us.
+     */
+    private MetalRootPaneUI rootPaneUI;
+
+
+    // Colors
+    private Color inactiveBackground = UIManager.getColor("inactiveCaption");
+    private Color inactiveForeground = UIManager.getColor("inactiveCaptionText");
+    private Color inactiveShadow = UIManager.getColor("inactiveCaptionBorder");
+    private Color activeBumpsHighlight = MetalLookAndFeel.getPrimaryControlHighlight();
+    private Color activeBumpsShadow = MetalLookAndFeel.getPrimaryControlDarkShadow();
+    private Color activeBackground = null;
+    private Color activeForeground = null;
+    private Color activeShadow = null;
+
+    // Bumps
+    private MetalBumps activeBumps
+        = new MetalBumps( 0, 0,
+                          activeBumpsHighlight,
+                          activeBumpsShadow,
+                          MetalLookAndFeel.getPrimaryControl() );
+    private MetalBumps inactiveBumps
+        = new MetalBumps( 0, 0,
+                          MetalLookAndFeel.getControlHighlight(),
+                          MetalLookAndFeel.getControlDarkShadow(),
+                          MetalLookAndFeel.getControl() );
+
+
+    public MetalTitlePane(JRootPane root, MetalRootPaneUI ui) {
+        this.rootPane = root;
+        rootPaneUI = ui;
+
+        state = -1;
+
+        installSubcomponents();
+        determineColors();
+        installDefaults();
+
+        setLayout(createLayout());
+    }
+
+    /**
+     * Uninstalls the necessary state.
+     */
+    private void uninstall() {
+        uninstallListeners();
+        window = null;
+        removeAll();
+    }
+
+    /**
+     * Installs the necessary listeners.
+     */
+    private void installListeners() {
+        if (window != null) {
+            windowListener = createWindowListener();
+            window.addWindowListener(windowListener);
+            propertyChangeListener = createWindowPropertyChangeListener();
+            window.addPropertyChangeListener(propertyChangeListener);
+        }
+    }
+
+    /**
+     * Uninstalls the necessary listeners.
+     */
+    private void uninstallListeners() {
+        if (window != null) {
+            window.removeWindowListener(windowListener);
+            window.removePropertyChangeListener(propertyChangeListener);
+        }
+    }
+
+    /**
+     * Returns the <code>WindowListener</code> to add to the
+     * <code>Window</code>.
+     */
+    private WindowListener createWindowListener() {
+        return new WindowHandler();
+    }
+
+    /**
+     * Returns the <code>PropertyChangeListener</code> to install on
+     * the <code>Window</code>.
+     */
+    private PropertyChangeListener createWindowPropertyChangeListener() {
+        return new PropertyChangeHandler();
+    }
+
+    /**
+     * Returns the <code>JRootPane</code> this was created for.
+     */
+    public JRootPane getRootPane() {
+        return rootPane;
+    }
+
+    /**
+     * Returns the decoration style of the <code>JRootPane</code>.
+     */
+    private int getWindowDecorationStyle() {
+        return getRootPane().getWindowDecorationStyle();
+    }
+
+    public void addNotify() {
+        super.addNotify();
+
+        uninstallListeners();
+
+        window = SwingUtilities.getWindowAncestor(this);
+        if (window != null) {
+            if (window instanceof Frame) {
+                setState(((Frame)window).getExtendedState());
+            }
+            else {
+                setState(0);
+            }
+            setActive(window.isActive());
+            installListeners();
+            updateSystemIcon();
+        }
+    }
+
+    public void removeNotify() {
+        super.removeNotify();
+
+        uninstallListeners();
+        window = null;
+    }
+
+    /**
+     * Adds any sub-Components contained in the <code>MetalTitlePane</code>.
+     */
+    private void installSubcomponents() {
+        int decorationStyle = getWindowDecorationStyle();
+        if (decorationStyle == JRootPane.FRAME) {
+            createActions();
+            menuBar = createMenuBar();
+            add(menuBar);
+            createButtons();
+            add(iconifyButton);
+            add(toggleButton);
+            add(closeButton);
+        } else if (decorationStyle == JRootPane.PLAIN_DIALOG ||
+                decorationStyle == JRootPane.INFORMATION_DIALOG ||
+                decorationStyle == JRootPane.ERROR_DIALOG ||
+                decorationStyle == JRootPane.COLOR_CHOOSER_DIALOG ||
+                decorationStyle == JRootPane.FILE_CHOOSER_DIALOG ||
+                decorationStyle == JRootPane.QUESTION_DIALOG ||
+                decorationStyle == JRootPane.WARNING_DIALOG) {
+            createActions();
+            createButtons();
+            add(closeButton);
+        }
+    }
+
+    /**
+     * Determines the Colors to draw with.
+     */
+    private void determineColors() {
+        switch (getWindowDecorationStyle()) {
+        case JRootPane.FRAME:
+            activeBackground = UIManager.getColor("activeCaption");
+            activeForeground = UIManager.getColor("activeCaptionText");
+            activeShadow = UIManager.getColor("activeCaptionBorder");
+            break;
+        case JRootPane.ERROR_DIALOG:
+            activeBackground = UIManager.getColor(
+                "OptionPane.errorDialog.titlePane.background");
+            activeForeground = UIManager.getColor(
+                "OptionPane.errorDialog.titlePane.foreground");
+            activeShadow = UIManager.getColor(
+                "OptionPane.errorDialog.titlePane.shadow");
+            break;
+        case JRootPane.QUESTION_DIALOG:
+        case JRootPane.COLOR_CHOOSER_DIALOG:
+        case JRootPane.FILE_CHOOSER_DIALOG:
+            activeBackground = UIManager.getColor(
+                "OptionPane.questionDialog.titlePane.background");
+            activeForeground = UIManager.getColor(
+                "OptionPane.questionDialog.titlePane.foreground");
+            activeShadow = UIManager.getColor(
+                "OptionPane.questionDialog.titlePane.shadow");
+            break;
+        case JRootPane.WARNING_DIALOG:
+            activeBackground = UIManager.getColor(
+                "OptionPane.warningDialog.titlePane.background");
+            activeForeground = UIManager.getColor(
+                "OptionPane.warningDialog.titlePane.foreground");
+            activeShadow = UIManager.getColor(
+                "OptionPane.warningDialog.titlePane.shadow");
+            break;
+        case JRootPane.PLAIN_DIALOG:
+        case JRootPane.INFORMATION_DIALOG:
+        default:
+            activeBackground = UIManager.getColor("activeCaption");
+            activeForeground = UIManager.getColor("activeCaptionText");
+            activeShadow = UIManager.getColor("activeCaptionBorder");
+            break;
+        }
+        activeBumps.setBumpColors(activeBumpsHighlight, activeBumpsShadow,
+                                  activeBackground);
+    }
+
+    /**
+     * Installs the fonts and necessary properties on the MetalTitlePane.
+     */
+    private void installDefaults() {
+        setFont(UIManager.getFont("InternalFrame.titleFont", getLocale()));
+    }
+
+    /**
+     * Uninstalls any previously installed UI values.
+     */
+    private void uninstallDefaults() {
+    }
+
+    /**
+     * Returns the <code>JMenuBar</code> displaying the appropriate
+     * system menu items.
+     */
+    protected JMenuBar createMenuBar() {
+        menuBar = new SystemMenuBar();
+        menuBar.setFocusable(false);
+        menuBar.setBorderPainted(true);
+        menuBar.add(createMenu());
+        return menuBar;
+    }
+
+    /**
+     * Closes the Window.
+     */
+    private void close() {
+        Window window = getWindow();
+
+        if (window != null) {
+            window.dispatchEvent(new WindowEvent(
+                                 window, WindowEvent.WINDOW_CLOSING));
+        }
+    }
+
+    /**
+     * Iconifies the Frame.
+     */
+    private void iconify() {
+        Frame frame = getFrame();
+        if (frame != null) {
+            frame.setExtendedState(state | Frame.ICONIFIED);
+        }
+    }
+
+    /**
+     * Maximizes the Frame.
+     */
+    private void maximize() {
+        Frame frame = getFrame();
+        if (frame != null) {
+            frame.setExtendedState(state | Frame.MAXIMIZED_BOTH);
+        }
+    }
+
+    /**
+     * Restores the Frame size.
+     */
+    private void restore() {
+        Frame frame = getFrame();
+
+        if (frame == null) {
+            return;
+        }
+
+        if ((state & Frame.ICONIFIED) != 0) {
+            frame.setExtendedState(state & ~Frame.ICONIFIED);
+        } else {
+            frame.setExtendedState(state & ~Frame.MAXIMIZED_BOTH);
+        }
+    }
+
+    /**
+     * Create the <code>Action</code>s that get associated with the
+     * buttons and menu items.
+     */
+    private void createActions() {
+        closeAction = new CloseAction();
+        if (getWindowDecorationStyle() == JRootPane.FRAME) {
+            iconifyAction = new IconifyAction();
+            restoreAction = new RestoreAction();
+            maximizeAction = new MaximizeAction();
+        }
+    }
+
+    /**
+     * Returns the <code>JMenu</code> displaying the appropriate menu items
+     * for manipulating the Frame.
+     */
+    private JMenu createMenu() {
+        JMenu menu = new JMenu("");
+        if (getWindowDecorationStyle() == JRootPane.FRAME) {
+            addMenuItems(menu);
+        }
+        return menu;
+    }
+
+    /**
+     * Adds the necessary <code>JMenuItem</code>s to the passed in menu.
+     */
+    private void addMenuItems(JMenu menu) {
+        Locale locale = getRootPane().getLocale();
+        JMenuItem mi = menu.add(restoreAction);
+        int mnemonic = MetalUtils.getInt("MetalTitlePane.restoreMnemonic", -1);
+
+        if (mnemonic != -1) {
+            mi.setMnemonic(mnemonic);
+        }
+
+        mi = menu.add(iconifyAction);
+        mnemonic = MetalUtils.getInt("MetalTitlePane.iconifyMnemonic", -1);
+        if (mnemonic != -1) {
+            mi.setMnemonic(mnemonic);
+        }
+
+        if (Toolkit.getDefaultToolkit().isFrameStateSupported(
+                Frame.MAXIMIZED_BOTH)) {
+            mi = menu.add(maximizeAction);
+            mnemonic =
+                MetalUtils.getInt("MetalTitlePane.maximizeMnemonic", -1);
+            if (mnemonic != -1) {
+                mi.setMnemonic(mnemonic);
+            }
+        }
+
+        menu.add(new JSeparator());
+
+        mi = menu.add(closeAction);
+        mnemonic = MetalUtils.getInt("MetalTitlePane.closeMnemonic", -1);
+        if (mnemonic != -1) {
+            mi.setMnemonic(mnemonic);
+        }
+    }
+
+    /**
+     * Returns a <code>JButton</code> appropriate for placement on the
+     * TitlePane.
+     */
+    private JButton createTitleButton() {
+        JButton button = new JButton();
+
+        button.setFocusPainted(false);
+        button.setFocusable(false);
+        button.setOpaque(true);
+        return button;
+    }
+
+    /**
+     * Creates the Buttons that will be placed on the TitlePane.
+     */
+    private void createButtons() {
+        closeButton = createTitleButton();
+        closeButton.setAction(closeAction);
+        closeButton.setText(null);
+        closeButton.putClientProperty("paintActive", Boolean.TRUE);
+        closeButton.setBorder(handyEmptyBorder);
+        closeButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
+                                      "Close");
+        closeButton.setIcon(UIManager.getIcon("InternalFrame.closeIcon"));
+
+        if (getWindowDecorationStyle() == JRootPane.FRAME) {
+            maximizeIcon = UIManager.getIcon("InternalFrame.maximizeIcon");
+            minimizeIcon = UIManager.getIcon("InternalFrame.minimizeIcon");
+
+            iconifyButton = createTitleButton();
+            iconifyButton.setAction(iconifyAction);
+            iconifyButton.setText(null);
+            iconifyButton.putClientProperty("paintActive", Boolean.TRUE);
+            iconifyButton.setBorder(handyEmptyBorder);
+            iconifyButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
+                                            "Iconify");
+            iconifyButton.setIcon(UIManager.getIcon("InternalFrame.iconifyIcon"));
+
+            toggleButton = createTitleButton();
+            toggleButton.setAction(restoreAction);
+            toggleButton.putClientProperty("paintActive", Boolean.TRUE);
+            toggleButton.setBorder(handyEmptyBorder);
+            toggleButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
+                                           "Maximize");
+            toggleButton.setIcon(maximizeIcon);
+        }
+    }
+
+    /**
+     * Returns the <code>LayoutManager</code> that should be installed on
+     * the <code>MetalTitlePane</code>.
+     */
+    private LayoutManager createLayout() {
+        return new TitlePaneLayout();
+    }
+
+    /**
+     * Updates state dependant upon the Window's active state.
+     */
+    private void setActive(boolean isActive) {
+        Boolean activeB = isActive ? Boolean.TRUE : Boolean.FALSE;
+
+        closeButton.putClientProperty("paintActive", activeB);
+        if (getWindowDecorationStyle() == JRootPane.FRAME) {
+            iconifyButton.putClientProperty("paintActive", activeB);
+            toggleButton.putClientProperty("paintActive", activeB);
+        }
+        // Repaint the whole thing as the Borders that are used have
+        // different colors for active vs inactive
+        getRootPane().repaint();
+    }
+
+    /**
+     * Sets the state of the Window.
+     */
+    private void setState(int state) {
+        setState(state, false);
+    }
+
+    /**
+     * Sets the state of the window. If <code>updateRegardless</code> is
+     * true and the state has not changed, this will update anyway.
+     */
+    private void setState(int state, boolean updateRegardless) {
+        Window w = getWindow();
+
+        if (w != null && getWindowDecorationStyle() == JRootPane.FRAME) {
+            if (this.state == state && !updateRegardless) {
+                return;
+            }
+            Frame frame = getFrame();
+
+            if (frame != null) {
+                JRootPane rootPane = getRootPane();
+
+                if (((state & Frame.MAXIMIZED_BOTH) != 0) &&
+                        (rootPane.getBorder() == null ||
+                        (rootPane.getBorder() instanceof UIResource)) &&
+                            frame.isShowing()) {
+                    rootPane.setBorder(null);
+                }
+                else if ((state & Frame.MAXIMIZED_BOTH) == 0) {
+                    // This is a croak, if state becomes bound, this can
+                    // be nuked.
+                    rootPaneUI.installBorder(rootPane);
+                }
+                if (frame.isResizable()) {
+                    if ((state & Frame.MAXIMIZED_BOTH) != 0) {
+                        updateToggleButton(restoreAction, minimizeIcon);
+                        maximizeAction.setEnabled(false);
+                        restoreAction.setEnabled(true);
+                    }
+                    else {
+                        updateToggleButton(maximizeAction, maximizeIcon);
+                        maximizeAction.setEnabled(true);
+                        restoreAction.setEnabled(false);
+                    }
+                    if (toggleButton.getParent() == null ||
+                        iconifyButton.getParent() == null) {
+                        add(toggleButton);
+                        add(iconifyButton);
+                        revalidate();
+                        repaint();
+                    }
+                    toggleButton.setText(null);
+                }
+                else {
+                    maximizeAction.setEnabled(false);
+                    restoreAction.setEnabled(false);
+                    if (toggleButton.getParent() != null) {
+                        remove(toggleButton);
+                        revalidate();
+                        repaint();
+                    }
+                }
+            }
+            else {
+                // Not contained in a Frame
+                maximizeAction.setEnabled(false);
+                restoreAction.setEnabled(false);
+                iconifyAction.setEnabled(false);
+                remove(toggleButton);
+                remove(iconifyButton);
+                revalidate();
+                repaint();
+            }
+            closeAction.setEnabled(true);
+            this.state = state;
+        }
+    }
+
+    /**
+     * Updates the toggle button to contain the Icon <code>icon</code>, and
+     * Action <code>action</code>.
+     */
+    private void updateToggleButton(Action action, Icon icon) {
+        toggleButton.setAction(action);
+        toggleButton.setIcon(icon);
+        toggleButton.setText(null);
+    }
+
+    /**
+     * Returns the Frame rendering in. This will return null if the
+     * <code>JRootPane</code> is not contained in a <code>Frame</code>.
+     */
+    private Frame getFrame() {
+        Window window = getWindow();
+
+        if (window instanceof Frame) {
+            return (Frame)window;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the <code>Window</code> the <code>JRootPane</code> is
+     * contained in. This will return null if there is no parent ancestor
+     * of the <code>JRootPane</code>.
+     */
+    private Window getWindow() {
+        return window;
+    }
+
+    /**
+     * Returns the String to display as the title.
+     */
+    private String getTitle() {
+        Window w = getWindow();
+
+        if (w instanceof Frame) {
+            return ((Frame)w).getTitle();
+        }
+        else if (w instanceof Dialog) {
+            return ((Dialog)w).getTitle();
+        }
+        return null;
+    }
+
+    /**
+     * Renders the TitlePane.
+     */
+    public void paintComponent(Graphics g)  {
+        // As state isn't bound, we need a convenience place to check
+        // if it has changed. Changing the state typically changes the
+        if (getFrame() != null) {
+            setState(getFrame().getExtendedState());
+        }
+        JRootPane rootPane = getRootPane();
+        Window window = getWindow();
+        boolean leftToRight = (window == null) ?
+                               rootPane.getComponentOrientation().isLeftToRight() :
+                               window.getComponentOrientation().isLeftToRight();
+        boolean isSelected = (window == null) ? true : window.isActive();
+        int width = getWidth();
+        int height = getHeight();
+
+        Color background;
+        Color foreground;
+        Color darkShadow;
+
+        MetalBumps bumps;
+
+        if (isSelected) {
+            background = activeBackground;
+            foreground = activeForeground;
+            darkShadow = activeShadow;
+            bumps = activeBumps;
+        } else {
+            background = inactiveBackground;
+            foreground = inactiveForeground;
+            darkShadow = inactiveShadow;
+            bumps = inactiveBumps;
+        }
+
+        g.setColor(background);
+        g.fillRect(0, 0, width, height);
+
+        g.setColor( darkShadow );
+        g.drawLine ( 0, height - 1, width, height -1);
+        g.drawLine ( 0, 0, 0 ,0);
+        g.drawLine ( width - 1, 0 , width -1, 0);
+
+        int xOffset = leftToRight ? 5 : width - 5;
+
+        if (getWindowDecorationStyle() == JRootPane.FRAME) {
+            xOffset += leftToRight ? IMAGE_WIDTH + 5 : - IMAGE_WIDTH - 5;
+        }
+
+        String theTitle = getTitle();
+        if (theTitle != null) {
+            FontMetrics fm = SwingUtilities2.getFontMetrics(rootPane, g);
+
+            g.setColor(foreground);
+
+            int yOffset = ( (height - fm.getHeight() ) / 2 ) + fm.getAscent();
+
+            Rectangle rect = new Rectangle(0, 0, 0, 0);
+            if (iconifyButton != null && iconifyButton.getParent() != null) {
+                rect = iconifyButton.getBounds();
+            }
+            int titleW;
+
+            if( leftToRight ) {
+                if (rect.x == 0) {
+                    rect.x = window.getWidth() - window.getInsets().right-2;
+                }
+                titleW = rect.x - xOffset - 4;
+                theTitle = SwingUtilities2.clipStringIfNecessary(
+                                rootPane, fm, theTitle, titleW);
+            } else {
+                titleW = xOffset - rect.x - rect.width - 4;
+                theTitle = SwingUtilities2.clipStringIfNecessary(
+                                rootPane, fm, theTitle, titleW);
+                xOffset -= SwingUtilities2.stringWidth(rootPane, fm,
+                                                       theTitle);
+            }
+            int titleLength = SwingUtilities2.stringWidth(rootPane, fm,
+                                                          theTitle);
+            SwingUtilities2.drawString(rootPane, g, theTitle, xOffset,
+                                       yOffset );
+            xOffset += leftToRight ? titleLength + 5  : -5;
+        }
+
+        int bumpXOffset;
+        int bumpLength;
+        if( leftToRight ) {
+            bumpLength = width - buttonsWidth - xOffset - 5;
+            bumpXOffset = xOffset;
+        } else {
+            bumpLength = xOffset - buttonsWidth - 5;
+            bumpXOffset = buttonsWidth + 5;
+        }
+        int bumpYOffset = 3;
+        int bumpHeight = getHeight() - (2 * bumpYOffset);
+        bumps.setBumpArea( bumpLength, bumpHeight );
+        bumps.paintIcon(this, g, bumpXOffset, bumpYOffset);
+    }
+
+    /**
+     * Actions used to <code>close</code> the <code>Window</code>.
+     */
+    @SuppressWarnings("serial") // Superclass is not serializable across versions
+    private class CloseAction extends AbstractAction {
+        public CloseAction() {
+            super(UIManager.getString("MetalTitlePane.closeTitle",
+                                      getLocale()));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            close();
+        }
+    }
+
+
+    /**
+     * Actions used to <code>iconfiy</code> the <code>Frame</code>.
+     */
+    @SuppressWarnings("serial") // Superclass is not serializable across versions
+    private class IconifyAction extends AbstractAction {
+        public IconifyAction() {
+            super(UIManager.getString("MetalTitlePane.iconifyTitle",
+                                      getLocale()));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            iconify();
+        }
+    }
+
+
+    /**
+     * Actions used to <code>restore</code> the <code>Frame</code>.
+     */
+    @SuppressWarnings("serial") // Superclass is not serializable across versions
+    private class RestoreAction extends AbstractAction {
+        public RestoreAction() {
+            super(UIManager.getString
+                  ("MetalTitlePane.restoreTitle", getLocale()));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            restore();
+        }
+    }
+
+
+    /**
+     * Actions used to <code>restore</code> the <code>Frame</code>.
+     */
+    @SuppressWarnings("serial") // Superclass is not serializable across versions
+    private class MaximizeAction extends AbstractAction {
+        public MaximizeAction() {
+            super(UIManager.getString("MetalTitlePane.maximizeTitle",
+                                      getLocale()));
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            maximize();
+        }
+    }
+
+
+    /**
+     * Class responsible for drawing the system menu. Looks up the
+     * image to draw from the Frame associated with the
+     * <code>JRootPane</code>.
+     */
+    @SuppressWarnings("serial") // Superclass is not serializable across versions
+    private class SystemMenuBar extends JMenuBar {
+        public void paint(Graphics g) {
+            if (isOpaque()) {
+                g.setColor(getBackground());
+                g.fillRect(0, 0, getWidth(), getHeight());
+            }
+
+            if (systemIcon != null) {
+                g.drawImage(systemIcon, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null);
+            } else {
+                Icon icon = UIManager.getIcon("InternalFrame.icon");
+
+                if (icon != null) {
+                    icon.paintIcon(this, g, 0, 0);
+                }
+            }
+        }
+        public Dimension getMinimumSize() {
+            return getPreferredSize();
+        }
+        public Dimension getPreferredSize() {
+            Dimension size = super.getPreferredSize();
+
+            return new Dimension(Math.max(IMAGE_WIDTH, size.width),
+                                 Math.max(size.height, IMAGE_HEIGHT));
+        }
+    }
+
+    private class TitlePaneLayout implements LayoutManager {
+        public void addLayoutComponent(String name, Component c) {}
+        public void removeLayoutComponent(Component c) {}
+        public Dimension preferredLayoutSize(Container c)  {
+            int height = computeHeight();
+            return new Dimension(height, height);
+        }
+
+        public Dimension minimumLayoutSize(Container c) {
+            return preferredLayoutSize(c);
+        }
+
+        private int computeHeight() {
+            FontMetrics fm = rootPane.getFontMetrics(getFont());
+            int fontHeight = fm.getHeight();
+            fontHeight += 7;
+            int iconHeight = 0;
+            if (getWindowDecorationStyle() == JRootPane.FRAME) {
+                iconHeight = IMAGE_HEIGHT;
+            }
+
+            int finalHeight = Math.max( fontHeight, iconHeight );
+            return finalHeight;
+        }
+
+        public void layoutContainer(Container c) {
+            boolean leftToRight = (window == null) ?
+                getRootPane().getComponentOrientation().isLeftToRight() :
+                window.getComponentOrientation().isLeftToRight();
+
+            int w = getWidth();
+            int x;
+            int y = 3;
+            int spacing;
+            int buttonHeight;
+            int buttonWidth;
+
+            if (closeButton != null && closeButton.getIcon() != null) {
+                buttonHeight = closeButton.getIcon().getIconHeight();
+                buttonWidth = closeButton.getIcon().getIconWidth();
+            }
+            else {
+                buttonHeight = IMAGE_HEIGHT;
+                buttonWidth = IMAGE_WIDTH;
+            }
+
+            // assumes all buttons have the same dimensions
+            // these dimensions include the borders
+
+            x = leftToRight ? w : 0;
+
+            spacing = 5;
+            x = leftToRight ? spacing : w - buttonWidth - spacing;
+            if (menuBar != null) {
+                menuBar.setBounds(x, y, buttonWidth, buttonHeight);
+            }
+
+            x = leftToRight ? w : 0;
+            spacing = 4;
+            x += leftToRight ? -spacing -buttonWidth : spacing;
+            if (closeButton != null) {
+                closeButton.setBounds(x, y, buttonWidth, buttonHeight);
+            }
+
+            if( !leftToRight ) x += buttonWidth;
+
+            if (getWindowDecorationStyle() == JRootPane.FRAME) {
+                if (Toolkit.getDefaultToolkit().isFrameStateSupported(
+                        Frame.MAXIMIZED_BOTH)) {
+                    if (toggleButton.getParent() != null) {
+                        spacing = 10;
+                        x += leftToRight ? -spacing -buttonWidth : spacing;
+                        toggleButton.setBounds(x, y, buttonWidth, buttonHeight);
+                        if (!leftToRight) {
+                            x += buttonWidth;
+                        }
+                    }
+                }
+
+                if (iconifyButton != null && iconifyButton.getParent() != null) {
+                    spacing = 2;
+                    x += leftToRight ? -spacing -buttonWidth : spacing;
+                    iconifyButton.setBounds(x, y, buttonWidth, buttonHeight);
+                    if (!leftToRight) {
+                        x += buttonWidth;
+                    }
+                }
+            }
+            buttonsWidth = leftToRight ? w - x : x;
+        }
+    }
+
+
+
+    /**
+     * PropertyChangeListener installed on the Window. Updates the necessary
+     * state as the state of the Window changes.
+     */
+    private class PropertyChangeHandler implements PropertyChangeListener {
+        public void propertyChange(PropertyChangeEvent pce) {
+            String name = pce.getPropertyName();
+
+            // Frame.state isn't currently bound.
+            if ("resizable".equals(name) || "state".equals(name)) {
+                Frame frame = getFrame();
+
+                if (frame != null) {
+                    setState(frame.getExtendedState(), true);
+                }
+                if ("resizable".equals(name)) {
+                    getRootPane().repaint();
+                }
+            }
+            else if ("title".equals(name)) {
+                repaint();
+            }
+            else if ("componentOrientation" == name) {
+                revalidate();
+                repaint();
+            }
+            else if ("iconImage" == name) {
+                updateSystemIcon();
+                revalidate();
+                repaint();
+            }
+        }
+    }
+
+    /**
+     * Update the image used for the system icon
+     */
+    private void updateSystemIcon() {
+        Window window = getWindow();
+        if (window == null) {
+            systemIcon = null;
+            return;
+        }
+        java.util.List<Image> icons = window.getIconImages();
+        assert icons != null;
+
+        if (icons.size() == 0) {
+            systemIcon = null;
+        }
+        else if (icons.size() == 1) {
+            systemIcon = icons.get(0);
+        }
+        else {
+            systemIcon = SunToolkit.getScaledIconImage(icons,
+                                                       IMAGE_WIDTH,
+                                                       IMAGE_HEIGHT);
+        }
+    }
+
+
+    /**
+     * WindowListener installed on the Window, updates the state as necessary.
+     */
+    private class WindowHandler extends WindowAdapter {
+        public void windowActivated(WindowEvent ev) {
+            setActive(true);
+        }
+
+        public void windowDeactivated(WindowEvent ev) {
+            setActive(false);
+        }
+    }
+}