/*
 * Copyright (c) 2009 Kathryn Huxtable and Kenneth Orr.
 *
 * This file is part of the SeaGlass Pluggable Look and Feel.
 *
 * 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.
 *
 * $Id$
 */
package com.seaglasslookandfeel.component;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;

import javax.accessibility.AccessibleContext;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.UIManager;
import javax.swing.event.InternalFrameEvent;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.synth.ColorType;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthGraphicsUtils;
import javax.swing.plaf.synth.SynthStyle;

import sun.swing.SwingUtilities2;

import com.seaglasslookandfeel.SeaGlassContext;
import com.seaglasslookandfeel.SeaGlassLookAndFeel;
import com.seaglasslookandfeel.ui.SeaglassUI;

/**
 * Manage a title bar for an internal frame.
 *
 * <p>Copied from SynthInternalFrameTitlePane, which is package local.</p>
 *
 * @see javax.swing.plaf.synth.SynthInternalFrameTitlePane
 */
public class SeaGlassInternalFrameTitlePane extends JComponent implements SeaglassUI, PropertyChangeListener {
    private static final long serialVersionUID = 6852740789566412690L;

    private static final String CLOSE_CMD    = UIManager.getString("InternalFrameTitlePane.closeButtonText");
    private static final String ICONIFY_CMD  = UIManager.getString("InternalFrameTitlePane.minimizeButtonText");
    private static final String RESTORE_CMD  = UIManager.getString("InternalFrameTitlePane.restoreButtonText");
    private static final String MAXIMIZE_CMD = UIManager.getString("InternalFrameTitlePane.maximizeButtonText");
    private static final String MOVE_CMD     = UIManager.getString("InternalFrameTitlePane.moveButtonText");
    private static final String SIZE_CMD     = UIManager.getString("InternalFrameTitlePane.sizeButtonText");

    // Basic
    private JButton iconButton;
    private JButton maxButton;
    private JButton closeButton;

    private JMenu          windowMenu;
    private JInternalFrame frame;

    private Action closeAction;
    private Action maximizeAction;
    private Action iconifyAction;
    private Action restoreAction;
    private Action moveAction;
    private Action sizeAction;

    private String closeButtonToolTip;
    private String iconButtonToolTip;
    private String restoreButtonToolTip;
    private String maxButtonToolTip;

    // Synth
    private JPopupMenu systemPopupMenu;
    private JButton    menuButton;

    private SynthStyle style;
    private int        titleSpacing;

    /**
     * Creates a new SeaGlassInternalFrameTitlePane object.
     *
     * @param f the internal frame containing the title pane.
     */
    public SeaGlassInternalFrameTitlePane(JInternalFrame f) {
        frame = f;
        installTitlePane();
    }

    /**
     * @see javax.swing.JComponent#getUIClassID()
     */
    public String getUIClassID() {
        return "InternalFrameTitlePaneUI";
    }

    /**
     * @see SeaglassUI#getContext(javax.swing.JComponent)
     */
    public SeaGlassContext getContext(JComponent c) {
        return getContext(c, getComponentState(c));
    }

    /**
     * Get a SynthContext for the title pane.
     *
     * @param  c     the title pane.
     * @param  state the state.
     *
     * @return the SynthContext describing the component.
     */
    public SeaGlassContext getContext(JComponent c, int state) {
        return SeaGlassContext.getContext(SeaGlassContext.class, c, SeaGlassLookAndFeel.getRegion(c), style, state);
    }

    /**
     * Get the state for the component.
     *
     * @param  c the component.
     *
     * @return the state for the component.
     */
    private int getComponentState(JComponent c) {
        if (frame != null) {

            if (frame.isSelected()) {
                return SELECTED;
            }
        }

        return SeaGlassLookAndFeel.getComponentState(c);
    }

    /**
     * Install the UI and listeners for the title pane.
     */
    private void installTitlePane() {
        installDefaults();
        installListeners();

        createActions();
        enableActions();
        createActionMap();

        setLayout(createLayout());

        assembleSystemMenu();
        createButtons();
        addSubComponents();
    }

    /**
     * Add the button subcomponents to the title pane.
     */
    private void addSubComponents() {
        menuButton.setName("InternalFrameTitlePane.menuButton");
        iconButton.setName("InternalFrameTitlePane.iconifyButton");
        maxButton.setName("InternalFrameTitlePane.maximizeButton");
        closeButton.setName("InternalFrameTitlePane.closeButton");

        add(menuButton);
        add(iconButton);
        add(maxButton);
        add(closeButton);
    }

    /**
     * Create the actions for the buttons.
     */
    private void createActions() {
        maximizeAction = new MaximizeAction();
        iconifyAction  = new IconifyAction();
        closeAction    = new CloseAction();
        restoreAction  = new RestoreAction();
        moveAction     = new MoveAction();
        sizeAction     = new SizeAction();
    }

    /**
     * Create the action map for the system menu.
     *
     * @return the action map.
     */
    ActionMap createActionMap() {
        ActionMap map = new ActionMapUIResource();

        map.put("showSystemMenu", new ShowSystemMenuAction(true));
        map.put("hideSystemMenu", new ShowSystemMenuAction(false));

        return map;
    }

    /**
     * Install the listeners on the title pane.
     */
    protected void installListeners() {
        frame.addPropertyChangeListener(this);
        addPropertyChangeListener(this);
    }

    /**
     * Uninstall the listeners from the title pane.
     */
    protected void uninstallListeners() {
        frame.removePropertyChangeListener(this);
        removePropertyChangeListener(this);
    }

    /**
     * Install defaults, update the Synth Style.
     */
    private void installDefaults() {
        // Basic
        setFont(UIManager.getFont("InternalFrame.titleFont"));
        closeButtonToolTip   = UIManager.getString("InternalFrame.closeButtonToolTip");
        iconButtonToolTip    = UIManager.getString("InternalFrame.iconButtonToolTip");
        restoreButtonToolTip = UIManager.getString("InternalFrame.restoreButtonToolTip");
        maxButtonToolTip     = UIManager.getString("InternalFrame.maxButtonToolTip");

        // Synth
        updateStyle(this);
    }

    /**
     * Uninstall the defaults.
     */
    public void uninstallDefaults() {
        SeaGlassContext context = getContext(this, ENABLED);

        style.uninstallDefaults(context);
        context.dispose();
        style = null;
        JInternalFrame.JDesktopIcon di = frame.getDesktopIcon();

        if (di != null && di.getComponentPopupMenu() == systemPopupMenu) {

            // Release link to systemMenu from the JInternalFrame
            di.setComponentPopupMenu(null);
        }
    }

    /**
     * Update the Synth Style.
     *
     * @param c the component.
     */
    private void updateStyle(JComponent c) {
        SeaGlassContext context  = getContext(this, ENABLED);
        SynthStyle      oldStyle = style;

        style = SeaGlassLookAndFeel.updateStyle(context, this);

        if (style != oldStyle) {
            titleSpacing = style.getInt(context, "InternalFrameTitlePane.titleSpacing", 2);
        }

        context.dispose();
    }

    /**
     * Create the buttons and add action listeners.
     */
    private void createButtons() {
        iconButton = new NoFocusButton("InternalFrameTitlePane.iconifyButtonAccessibleName");
        iconButton.addActionListener(iconifyAction);

        if (iconButtonToolTip != null && iconButtonToolTip.length() != 0) {
            iconButton.setToolTipText(iconButtonToolTip);
        }

        maxButton = new NoFocusButton("InternalFrameTitlePane.maximizeButtonAccessibleName");
        maxButton.addActionListener(maximizeAction);

        closeButton = new NoFocusButton("InternalFrameTitlePane.closeButtonAccessibleName");
        closeButton.addActionListener(closeAction);

        if (closeButtonToolTip != null && closeButtonToolTip.length() != 0) {
            closeButton.setToolTipText(closeButtonToolTip);
        }

        setButtonTooltips();
    }

    /**
     * Set the buttons' tooltip text.
     */
    private void setButtonTooltips() {
        if (frame.isIcon()) {

            if (restoreButtonToolTip != null && restoreButtonToolTip.length() != 0) {
                iconButton.setToolTipText(restoreButtonToolTip);
            }

            if (maxButtonToolTip != null && maxButtonToolTip.length() != 0) {
                maxButton.setToolTipText(maxButtonToolTip);
            }
        } else if (frame.isMaximum()) {

            if (iconButtonToolTip != null && iconButtonToolTip.length() != 0) {
                iconButton.setToolTipText(iconButtonToolTip);
            }

            if (restoreButtonToolTip != null && restoreButtonToolTip.length() != 0) {
                maxButton.setToolTipText(restoreButtonToolTip);
            }
        } else {

            if (iconButtonToolTip != null && iconButtonToolTip.length() != 0) {
                iconButton.setToolTipText(iconButtonToolTip);
            }

            if (maxButtonToolTip != null && maxButtonToolTip.length() != 0) {
                maxButton.setToolTipText(maxButtonToolTip);
            }
        }
    }

    /**
     * Create the system menu.
     */
    private void assembleSystemMenu() {
        systemPopupMenu = new JPopupMenuUIResource();
        addSystemMenuItems(systemPopupMenu);
        enableActions();
        menuButton = new NoFocusButton("InternalFrameTitlePane.menuButtonAccessibleName");
        updateMenuIcon();
        menuButton.addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent e) {
                    try {
                        frame.setSelected(true);
                    } catch (PropertyVetoException pve) {
                    }

                    showSystemMenu();
                }
            });

        JPopupMenu p = frame.getComponentPopupMenu();

        if (p == null || p instanceof UIResource) {
            frame.setComponentPopupMenu(systemPopupMenu);
        }

        if (frame.getDesktopIcon() != null) {
            p = frame.getDesktopIcon().getComponentPopupMenu();

            if (p == null || p instanceof UIResource) {
                frame.getDesktopIcon().setComponentPopupMenu(systemPopupMenu);
            }
        }

        setInheritsPopupMenu(true);
    }

    /**
     * Add the items to the system menu.
     *
     * @param menu the system menu popup.
     */
    private void addSystemMenuItems(JPopupMenu menu) {
        // PENDING: this should all be localizable!
        JMenuItem mi = (JMenuItem) menu.add(restoreAction);

        mi.setMnemonic('R');
        mi = (JMenuItem) menu.add(moveAction);
        mi.setMnemonic('M');
        mi = (JMenuItem) menu.add(sizeAction);
        mi.setMnemonic('S');
        mi = (JMenuItem) menu.add(iconifyAction);
        mi.setMnemonic('n');
        mi = (JMenuItem) menu.add(maximizeAction);
        mi.setMnemonic('x');
        menu.add(new JSeparator());
        mi = (JMenuItem) menu.add(closeAction);
        mi.setMnemonic('C');
    }

    /**
     * Show the system menu.
     */
    private void showSystemMenu() {
        Insets insets = frame.getInsets();

        if (!frame.isIcon()) {
            systemPopupMenu.show(frame, insets.left, getY() + getHeight());
        } else {
            systemPopupMenu.show(menuButton, getX() - insets.left - insets.right,
                                 getY() - systemPopupMenu.getPreferredSize().height - insets.bottom - insets.top);
        }
    }

    // SeaGlassInternalFrameTitlePane has no UI, we'll invoke paint on it.
    /**
     * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
     */
    public void paintComponent(Graphics g) {
        SeaGlassContext context = getContext(this);

        SeaGlassLookAndFeel.update(context, g);
        context.getPainter().paintInternalFrameTitlePaneBackground(context, g, 0, 0, getWidth(), getHeight());
        paint(context, g);
        context.dispose();
    }

    /**
     * Paint the title pane.
     *
     * @param context the SynthContext to get the component from.
     * @param g       the Graphics context to paint with.
     */
    private void paint(SeaGlassContext context, Graphics g) {
        String title = frame.getTitle();

        if (title != null) {
            SynthStyle style = context.getStyle();

            Color color = style.getColor(context, ColorType.TEXT_FOREGROUND);

            // TODO style.getColor returns improper color for state? Why?
            if ((context.getComponentState() & 512) != 0) {
                Object obj = style.get(context, "[WindowFocused].textForeground");

                if (obj != null && obj instanceof Color) {
                    color = (Color) obj;
                }

                // FIXME The state *still* doesn't get the color right!!!
                color = Color.WHITE;
            }

            g.setColor(color);
            g.setFont(style.getFont(context));

            // Center text vertically.
            FontMetrics fm         = SwingUtilities2.getFontMetrics(frame, g);
            int         baseline   = (getHeight() + fm.getAscent() - fm.getLeading() - fm.getDescent()) / 2;
            JButton     lastButton = null;

            if (frame.isIconifiable()) {
                lastButton = iconButton;
            } else if (frame.isMaximizable()) {
                lastButton = maxButton;
            } else if (frame.isClosable()) {
                lastButton = closeButton;
            }

            int     maxX;
            int     minX;
            boolean ltr = SeaGlassLookAndFeel.isLeftToRight(frame);

            if (ltr) {

                if (lastButton != null) {
                    maxX = lastButton.getX() - titleSpacing;
                } else {
                    maxX = frame.getWidth() - frame.getInsets().right - titleSpacing;
                }

                minX = menuButton.getX() + menuButton.getWidth() + titleSpacing;
            } else {

                if (lastButton != null) {
                    minX = lastButton.getX() + lastButton.getWidth() + titleSpacing;
                } else {
                    minX = frame.getInsets().left + titleSpacing;
                }

                maxX = menuButton.getX() - titleSpacing;
            }

            String clippedTitle = getTitle(title, fm, maxX - minX);

            if (clippedTitle == title) {
                int width = style.getGraphicsUtils(context).computeStringWidth(context, g.getFont(), fm, title);

                minX = Math.max(minX, (getWidth() - width) / 2);
                minX = Math.min(maxX - width, minX);
            }

            style.getGraphicsUtils(context).paintText(context, g, clippedTitle, minX, baseline - fm.getAscent(), -1);
        }
    }

    /**
     * Get the title text, clipped if necessary.
     *
     * @param  text           the title text.
     * @param  fm             the font metrics to compute size with.
     * @param  availTextWidth the available space to display the title in.
     *
     * @return the text, clipped to fit the available space.
     */
    private String getTitle(String text, FontMetrics fm, int availTextWidth) {
        return SwingUtilities2.clipStringIfNecessary(frame, fm, text, availTextWidth);
    }

    /**
     * @see SeaglassUI#paintBorder(javax.swing.plaf.synth.SynthContext,
     *      java.awt.Graphics, int, int, int, int)
     */
    public void paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h) {
        ((SeaGlassContext) context).getPainter().paintInternalFrameTitlePaneBorder(context, g, x, y, w, h);
    }

    /**
     * Create the layout manager for the title pane.
     *
     * @return the layout manager.
     */
    private LayoutManager createLayout() {
        SeaGlassContext context = getContext(this);
        LayoutManager   lm      = (LayoutManager) style.get(context, "InternalFrameTitlePane.titlePaneLayout");

        context.dispose();

        return (lm != null) ? lm : new SeaGlassTitlePaneLayout();
    }

    /**
     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
     */
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getSource() == this) {

            if (SeaGlassLookAndFeel.shouldUpdateStyle(evt)) {
                updateStyle(this);
            }
        } else {

            // Changes for the internal frame
            if (evt.getPropertyName() == JInternalFrame.FRAME_ICON_PROPERTY) {
                updateMenuIcon();
            }
        }

        // Basic (from Handler inner class)
        String prop = (String) evt.getPropertyName();

        if (prop == JInternalFrame.IS_SELECTED_PROPERTY) {
            repaint();

            return;
        }

        if (prop == JInternalFrame.IS_ICON_PROPERTY || prop == JInternalFrame.IS_MAXIMUM_PROPERTY) {
            setButtonTooltips();
            enableActions();

            return;
        }

        if ("closable" == prop) {

            if ((Boolean) evt.getNewValue() == Boolean.TRUE) {
                add(closeButton);
            } else {
                remove(closeButton);
            }
        } else if ("maximizable" == prop) {

            if ((Boolean) evt.getNewValue() == Boolean.TRUE) {
                add(maxButton);
            } else {
                remove(maxButton);
            }
        } else if ("iconable" == prop) {

            if ((Boolean) evt.getNewValue() == Boolean.TRUE) {
                add(iconButton);
            } else {
                remove(iconButton);
            }
        }

        enableActions();

        revalidate();
        repaint();
    }

    /**
     * Resets the menuButton icon to match that of the frame.
     */
    private void updateMenuIcon() {
        Icon            frameIcon = frame.getFrameIcon();
        SeaGlassContext context   = getContext(this);

        if (frameIcon != null) {
            Dimension maxSize   = (Dimension) context.getStyle().get(context, "InternalFrameTitlePane.maxFrameIconSize");
            int       maxWidth  = 16;
            int       maxHeight = 16;

            if (maxSize != null) {
                maxWidth  = maxSize.width;
                maxHeight = maxSize.height;
            }

            if ((frameIcon.getIconWidth() > maxWidth || frameIcon.getIconHeight() > maxHeight) && (frameIcon instanceof ImageIcon)) {
                frameIcon = new ImageIcon(((ImageIcon) frameIcon).getImage().getScaledInstance(maxWidth, maxHeight, Image.SCALE_SMOOTH));
            }
        }

        context.dispose();
        menuButton.setIcon(frameIcon);
    }

    /**
     * Post a WINDOW_CLOSING-like event to the frame, so that it can be treated
     * like a regular Frame.
     *
     * @param frame the internal frame to be closed.
     */
    protected void postClosingEvent(JInternalFrame frame) {
        InternalFrameEvent e = new InternalFrameEvent(frame, InternalFrameEvent.INTERNAL_FRAME_CLOSING);

        // Try posting event, unless there's a SecurityManager.
        if (JInternalFrame.class.getClassLoader() == null) {

            try {
                Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(e);

                return;
            } catch (SecurityException se) {
                // Use dispatchEvent instead.
            }
        }

        frame.dispatchEvent(e);
    }

    /**
     * Set the enable/disabled state for the buttons.
     */
    private void enableActions() {
        restoreAction.setEnabled(frame.isMaximum() || frame.isIcon());
        maximizeAction.setEnabled((frame.isMaximizable() && !frame.isMaximum() && !frame.isIcon())
                                      || (frame.isMaximizable() && frame.isIcon()));
        iconifyAction.setEnabled(frame.isIconifiable() && !frame.isIcon());
        closeAction.setEnabled(frame.isClosable());
        sizeAction.setEnabled(false);
        moveAction.setEnabled(false);
    }

    /**
     * A special UIResource used for the system menu.
     */
    private static class JPopupMenuUIResource extends JPopupMenu implements UIResource {
        private static final long serialVersionUID = 4511298062453723303L;
    }

    /**
     * The layout manager for the title pane.
     */
    class SeaGlassTitlePaneLayout implements LayoutManager {

        /**
         * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String, java.awt.Component)
         */
        public void addLayoutComponent(String name, Component c) {
        }

        /**
         * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
         */
        public void removeLayoutComponent(Component c) {
        }

        /**
         * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
         */
        public Dimension preferredLayoutSize(Container c) {
            return minimumLayoutSize(c);
        }

        /**
         * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
         */
        public Dimension minimumLayoutSize(Container c) {
            SeaGlassContext context = getContext(SeaGlassInternalFrameTitlePane.this);
            int             width   = 8;
            int             height  = 0;

            int       buttonCount = 0;
            Dimension pref;

            if (frame.isClosable()) {
                pref   = closeButton.getPreferredSize();
                width  += pref.width;
                height = Math.max(pref.height, height);
                buttonCount++;
            }

            if (frame.isMaximizable()) {
                pref   = maxButton.getPreferredSize();
                width  += pref.width;
                height = Math.max(pref.height, height);
                buttonCount++;
            }

            if (frame.isIconifiable()) {
                pref   = iconButton.getPreferredSize();
                width  += pref.width;
                height = Math.max(pref.height, height);
                buttonCount++;
            }

            pref   = menuButton.getPreferredSize();
            width  += pref.width;
            height = Math.max(pref.height, height);

            FontMetrics        fm            = SeaGlassInternalFrameTitlePane.this.getFontMetrics(getFont());
            SynthGraphicsUtils graphicsUtils = context.getStyle().getGraphicsUtils(context);
            String             frameTitle    = frame.getTitle();
            int                title_w       = frameTitle != null ? graphicsUtils.computeStringWidth(context, fm.getFont(), fm, frameTitle)
                                                                  : 0;
            int                title_length  = frameTitle != null ? frameTitle.length() : 0;

            // Leave room for three characters in the title.
            if (title_length > 3) {
                int subtitle_w = graphicsUtils.computeStringWidth(context, fm.getFont(), fm, frameTitle.substring(0, 3) + "...");

                width += (title_w < subtitle_w) ? title_w : subtitle_w;
            } else {
                width += title_w;
            }

            height = Math.max(fm.getHeight() + 2, height);

            width += titleSpacing + titleSpacing;

            Insets insets = getInsets();

            height += insets.top + insets.bottom;
            width  += insets.left + insets.right;
            context.dispose();

            return new Dimension(width, height);
        }

        /**
         * Determine the position of a button.
         *
         * @param  c        the button.
         * @param  insets   the title pane insets.
         * @param  x        the x position of the button.
         * @param  trailing Are we at the right edge?
         *
         * @return the modified x position for the button.
         */
        private int center(Component c, Insets insets, int x, boolean trailing) {
            Dimension pref  = c.getPreferredSize();
            int       width = pref.width;

            if (c instanceof JButton && ((JButton) c).getIcon() != null) {
                width = ((JButton) c).getIcon().getIconWidth();
            }

            if (trailing) {
                x -= width;
            }

            int y = 0;

            if (c == menuButton) {
                // y = insets.top + (getHeight() - insets.top - insets.bottom -
                // pref.height) / 2;
            }

            c.setBounds(x, y, pref.width, pref.height);

            if (pref.width > 0) {

                if (!trailing) {
                    return x + width;
                }
            }

            return x;
        }

        /**
         * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
         */
        public void layoutContainer(Container c) {
            Insets insets = c.getInsets();

            if (SeaGlassLookAndFeel.isLeftToRight(frame)) {
                center(menuButton, insets, insets.left + 4, false);
                int x = getWidth() - insets.right - 4;

                if (frame.isClosable()) {
                    x = center(closeButton, insets, x, true);
                }

                if (frame.isMaximizable()) {
                    x = center(maxButton, insets, x, true);
                }

                if (frame.isIconifiable()) {
                    x = center(iconButton, insets, x, true);
                }
            } else {
                center(menuButton, insets, getWidth() - insets.right - 4, true);
                int x = insets.left + 4;

                if (frame.isClosable()) {
                    x = center(closeButton, insets, x, false);
                }

                if (frame.isMaximizable()) {
                    x = center(maxButton, insets, x, false);
                }

                if (frame.isIconifiable()) {
                    x = center(iconButton, insets, x, false);
                }
            }
        }
    }

    /**
     * Handles closing internal frame.
     */
    private class CloseAction extends AbstractAction {
        private static final long serialVersionUID = 1214165629626107441L;

        /**
         * Creates a new CloseAction object.
         */
        public CloseAction() {
            super(CLOSE_CMD);
        }

        /**
         * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
         */
        public void actionPerformed(ActionEvent e) {
            if (frame.isClosable()) {
                frame.doDefaultCloseAction();
            }
        }
    }

    /**
     * Handles maximizing/restoring internal frame.
     */
    private class MaximizeAction extends AbstractAction {
        private static final long serialVersionUID = 6286449065315966825L;

        /**
         * Creates a new MaximizeAction object.
         */
        public MaximizeAction() {
            super(MAXIMIZE_CMD);
        }

        /**
         * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
         */
        public void actionPerformed(ActionEvent evt) {
            if (frame.isMaximizable()) {

                if (frame.isMaximum() && frame.isIcon()) {

                    try {
                        frame.setIcon(false);
                    } catch (PropertyVetoException e) {
                    }
                } else if (!frame.isMaximum()) {

                    try {
                        frame.setMaximum(true);
                    } catch (PropertyVetoException e) {
                    }
                } else {

                    try {
                        frame.setMaximum(false);
                    } catch (PropertyVetoException e) {
                    }
                }
            }
        }
    }

    /**
     * Handles iconifying/uniconifying internal frame.
     */
    private class IconifyAction extends AbstractAction {
        private static final long serialVersionUID = 4337075125371606258L;

        /**
         * Creates a new IconifyAction object.
         */
        public IconifyAction() {
            super(ICONIFY_CMD);
        }

        /**
         * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
         */
        public void actionPerformed(ActionEvent e) {
            if (frame.isIconifiable()) {

                if (!frame.isIcon()) {

                    try {
                        frame.setIcon(true);
                    } catch (PropertyVetoException e1) {
                    }
                } else {

                    try {
                        frame.setIcon(false);
                    } catch (PropertyVetoException e1) {
                    }
                }
            }
        }
    }

    /**
     * Restores internal frame to regular state.
     */
    private class RestoreAction extends AbstractAction {
        private static final long serialVersionUID = -961768442993544104L;

        /**
         * Creates a new RestoreAction object.
         */
        public RestoreAction() {
            super(RESTORE_CMD);
        }

        /**
         * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
         */
        public void actionPerformed(ActionEvent evt) {
            if (frame.isMaximizable() && frame.isMaximum() && frame.isIcon()) {

                try {
                    frame.setIcon(false);
                } catch (PropertyVetoException e) {
                }
            } else if (frame.isMaximizable() && frame.isMaximum()) {

                try {
                    frame.setMaximum(false);
                } catch (PropertyVetoException e) {
                }
            } else if (frame.isIconifiable() && frame.isIcon()) {

                try {
                    frame.setIcon(false);
                } catch (PropertyVetoException e) {
                }
            }
        }
    }

    /**
     * Handles moving internal frame.
     */
    private class MoveAction extends AbstractAction {
        private static final long serialVersionUID = 7940900832761279111L;

        /**
         * Creates a new MoveAction object.
         */
        public MoveAction() {
            super(MOVE_CMD);
        }

        /**
         * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
         */
        public void actionPerformed(ActionEvent e) {
            // This action is currently undefined
        }
    }

    /**
     * Handles showing and hiding the system menu.
     */
    private class ShowSystemMenuAction extends AbstractAction {
        private static final long serialVersionUID = 4020962135221310152L;
        private boolean           show; // whether to show the menu

        /**
         * Creates a new ShowSystemMenuAction object.
         *
         * @param show the ShowSystemMenuAction object.
         */
        public ShowSystemMenuAction(boolean show) {
            this.show = show;
        }

        /**
         * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
         */
        public void actionPerformed(ActionEvent e) {
            if (show) {
                windowMenu.doClick();
            } else {
                windowMenu.setVisible(false);
            }
        }
    }

    /**
     * Handles resizing internal frame.
     */
    private class SizeAction extends AbstractAction {
        private static final long serialVersionUID = -1079290485715681580L;

        /**
         * Creates a new SizeAction object.
         */
        public SizeAction() {
            super(SIZE_CMD);
        }

        /**
         * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
         */
        public void actionPerformed(ActionEvent e) {
            // This action is currently undefined
        }
    }

    /**
     * Window decoration button class.
     */
    private class NoFocusButton extends JButton {
        private static final long serialVersionUID = 797986964401472674L;
        private String            uiKey;

        /**
         * Creates a new NoFocusButton object.
         *
         * @param uiKey the UI key for the button properties.
         */
        public NoFocusButton(String uiKey) {
            setFocusPainted(false);
            setMargin(new Insets(0, 0, 0, 0));
            setFocusable(false);
            this.uiKey = uiKey;
        }

        /**
         * @see java.awt.Component#isFocusTraversable()
         */
        public boolean isFocusTraversable() {
            return false;
        }

        /**
         * @see javax.swing.JComponent#requestFocus()
         */
        public void requestFocus() {
        }

        /**
         * @see javax.swing.JButton#getAccessibleContext()
         */
        public AccessibleContext getAccessibleContext() {
            AccessibleContext ac = super.getAccessibleContext();

            if (uiKey != null) {
                ac.setAccessibleName(UIManager.getString(uiKey));
                uiKey = null;
            }

            return ac;
        }
    }
}