/*
 * 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.Frame;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;

import javax.accessibility.AccessibleContext;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.JSeparator;
import javax.swing.RootPaneContainer;
import javax.swing.UIManager;
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.painter.FrameAndRootPainter;
import com.seaglasslookandfeel.ui.SeaGlassButtonUI;
import com.seaglasslookandfeel.ui.SeaGlassRootPaneUI;
import com.seaglasslookandfeel.ui.SeaglassUI;
import com.seaglasslookandfeel.util.SeaGlassGraphicsUtils;

/**
 * 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.</p>
 *
 * @author Kathryn Huxtable
 */
public class SeaGlassTitlePane extends JComponent implements SeaglassUI, PropertyChangeListener {
    private static final long   serialVersionUID         = 7006086880911744060L;
    private static final String WINDOW_DOCUMENT_MODIFIED = "Window.documentModified";

    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 JButton             menuButton;

    private JPopupMenu        windowMenu;
    private JRootPane         rootPane;
    private RootPaneContainer rootParent;

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

    private Color DEFAULT_EMPHASIS_COLOR = UIManager.getColor("seaGlassTextEmphasis");

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

    private int                state        = -1;
    private SeaGlassRootPaneUI rootPaneUI;

    // Synth
    private SynthStyle         style;
    private int                titleSpacing;

    /**
     * Creates a new SeaGlassTitlePane object.
     *
     * @param rootPane the JRootPane containing the title pane.
     * @param ui       the UI delegate for the root pane.
     */
    public SeaGlassTitlePane(JRootPane rootPane, SeaGlassRootPaneUI ui) {
        this.rootPane   = rootPane;
        this.rootPaneUI = ui;
        rootParent      = (RootPaneContainer) rootPane.getParent();
        installTitlePane();
    }

    /**
     * @see javax.swing.JComponent#getRootPane()
     */
    public JRootPane getRootPane() {
        return rootPane;
    }

    /**
     * @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 the SynthContext for the title pane.
     *
     * @param  c     the component.
     * @param  state the state.
     *
     * @return the SynthContext object.
     */
    public SeaGlassContext getContext(JComponent c, int state) {
        return SeaGlassContext.getContext(SeaGlassContext.class, c, SeaGlassLookAndFeel.getRegion(c), style, state);
    }

    /**
     * Compute the state for the title pane.
     *
     * @param  c the title pane.
     *
     * @return the state.
     */
    private int getComponentState(JComponent c) {
        if (rootParent != null) {

            if (isParentSelected()) {
                return SELECTED;
            }
        }

        return SeaGlassLookAndFeel.getComponentState(c);
    }

    /**
     * Determine if the title pane's parent is active.
     *
     * @return {@code true} if the parent is active, {@code false} otherwise.
     */
    private boolean isParentSelected() {
        if (rootParent instanceof JFrame) {
            return ((JFrame) rootParent).isActive();
        } else if (rootParent instanceof JDialog) {
            return ((JDialog) rootParent).isActive();
        } else {
            return true;
        }
    }

    /**
     * Is the parent window iconified?
     *
     * @return {@code true} if the parent window is iconified, {@code false}
     *         otherwise.
     */
    private boolean isParentIcon() {
        if (rootParent instanceof JFrame) {
            return (((JFrame) rootParent).getExtendedState() & Frame.ICONIFIED) != 0;
        } else {
            return false;
        }
    }

    /**
     * Is the parent window maximized?
     *
     * @return {@code true} if the parent window is maximized, {@code false}
     *         otherwise.
     */
    private boolean isParentMaximum() {
        if (rootParent instanceof JFrame) {
            return (((JFrame) rootParent).getExtendedState() & Frame.MAXIMIZED_BOTH) != 0;
        } else {
            return false;
        }
    }

    /**
     * Is the parent window laid out left-to-right?
     *
     * @return {@code true} if the parent is laid out left-to-right,
     *         {@code false} otherwise.
     */
    private boolean isParentLeftToRight() {
        if (rootParent instanceof JFrame) {
            return SeaGlassLookAndFeel.isLeftToRight((JFrame) rootParent);
        } else if (rootParent instanceof JDialog) {
            return SeaGlassLookAndFeel.isLeftToRight((JDialog) rootParent);
        } else {
            return false;
        }
    }

    /**
     * Close the window.
     */
    private void doParentDefaultCloseAction() {
        ((Window) rootParent).dispatchEvent(new WindowEvent((Window) rootParent, WindowEvent.WINDOW_CLOSING));
    }

    /**
     * Iconify/Restore the window.
     *
     * @param iconify {@code true} if we are to iconify the window,
     *                {@code false} if we are to restore the window.
     */
    private void setParentIcon(boolean iconify) {
        if (rootParent instanceof JFrame) {
            JFrame frame = (JFrame) rootParent;
            int    state = frame.getExtendedState();

            ((JFrame) rootParent).setExtendedState(iconify ? state | Frame.ICONIFIED : state & ~Frame.ICONIFIED);
        }
    }

    /**
     * Maximize/Restore the window.
     *
     * @param maximize iconify {@code true} if we are to maximize the window,
     *                 {@code false} if we are to restore the window.
     */
    private void setParentMaximum(boolean maximize) {
        if (rootParent instanceof JFrame) {
            JFrame frame = (JFrame) rootParent;
            int    state = frame.getExtendedState();

            if (maximize) {
                GraphicsConfiguration gc = frame.getGraphicsConfiguration();
                Insets                i  = Toolkit.getDefaultToolkit().getScreenInsets(gc);
                Rectangle             r  = gc.getBounds();

                r.x      = 0;
                r.y      = 0;
                r.width  -= i.left + i.right;
                r.height -= i.top + i.bottom;
                frame.setMaximizedBounds(r);
            }

            frame.setExtendedState(maximize ? state | Frame.MAXIMIZED_BOTH : state & ~Frame.MAXIMIZED_BOTH);
        }
    }

    /**
     * Add a property change listener to the root pane.
     *
     * @param listener the propertiy change listener to add.
     */
    private void addParentPropertyChangeListener(PropertyChangeListener listener) {
        if (rootParent instanceof JFrame) {
            ((JFrame) rootParent).addPropertyChangeListener(listener);
        } else if (rootParent instanceof JDialog) {
            ((JDialog) rootParent).addPropertyChangeListener(listener);
        }

        rootPane.addPropertyChangeListener(listener);
    }

    /**
     * Remove the property change listener from the root pane.
     *
     * @param listener the property change listener to remove.
     */
    private void removeParentPropertyChangeListener(PropertyChangeListener listener) {
        if (rootParent instanceof JFrame) {
            ((JFrame) rootParent).removePropertyChangeListener(listener);
        } else if (rootParent instanceof JDialog) {
            ((JDialog) rootParent).removePropertyChangeListener(listener);
        }
    }

    /**
     * Is the parent window closable?
     *
     * @return {@code true} if the parent window is closable, {@code false}
     *         otheriwes.
     */
    private boolean isParentClosable() {
        return true;
    }

    /**
     * Is the parent window iconifiable?
     *
     * @return {@code true} if the parent window is iconifiable, {@code false}
     *         otheriwes.
     */
    private boolean isParentIconifiable() {
        if (rootParent instanceof JFrame) {
            return ((JFrame)rootParent).isResizable();
        }
        return false;
    }

    /**
     * Is the parent window maximizable?
     *
     * @return {@code true} if the parent window is maximizable, {@code false}
     *         otheriwes.
     */
    private boolean isParentMaximizable() {
        if (rootParent instanceof JFrame) {
            return ((JFrame)rootParent).isResizable();
        }
        return false;
    }

    /**
     * Install actions, buttons, and listeners on title pane.
     */
    private void installTitlePane() {
        installDefaults();
        installListeners();

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

        setLayout(createLayout());

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

    private void assembleSystemMenu() {
        windowMenu = new JPopupMenu();
        addSystemMenuItems(windowMenu);
        enableActions();
        menuButton = new JButton();
        menuButton.setName("InternalFrameTitlePane.menuButtonAccessibleName");
        updateMenuIcon();
        menuButton.addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent e) {
                    showSystemMenu();
                }
            });
        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 = rootPane.getInsets();
        windowMenu.show(menuButton, getX() - insets.left - insets.right,
            getY() + this.getPreferredSize().height);
    }
    
    /**
     * Resets the menuButton icon to match that of the frame.
     */
    private void updateMenuIcon() {
        List<Image> iconList = null;
        if (rootParent instanceof JFrame) {
            iconList = ((JFrame) rootParent).getIconImages();
        } else if (rootParent instanceof JDialog) {
            iconList = ((JDialog) rootParent).getIconImages();
        }
        
        Image frameIcon = iconList != null && iconList.size() > 0 ? iconList.get(0) : null;

        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;
            }
            menuButton.setIcon(new ImageIcon(frameIcon.getScaledInstance(maxWidth, maxHeight, Image.SCALE_SMOOTH)));
        }

        context.dispose();
    }

    /**
     * Add the buttons.
     */
    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 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 listeners.
     */
    protected void installListeners() {
        addParentPropertyChangeListener(this);
    }

    /**
     * Uninstall listeners.
     */
    protected void uninstallListeners() {
        removeParentPropertyChangeListener(this);
    }

    /**
     * Install the defaults and 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;
    }

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

        style = SeaGlassLookAndFeel.updateSeaglassStyle(context, this);

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

        context.dispose();
    }

    /**
     * Create the buttons.
     */
    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 tooltips for the buttons.
     */
    private void setButtonTooltips() {
        if (isParentIcon()) {

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

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

            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);
            }
        }
    }

    // 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 use to get the component.
     * @param g       the Graphics context to paint with.
     */
    private void paint(SeaGlassContext context, Graphics g) {
        String title = 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.BLACK;
            }

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

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

            if (isParentIconifiable()) {
                lastButton = iconButton;
            } else if (isParentMaximizable()) {
                lastButton = maxButton;
            } else if (isParentClosable()) {
                lastButton = closeButton;
            }

            int     maxX;
            int     minX;
            boolean ltr = isParentLeftToRight();

            if (ltr) {

                if (lastButton != null) {
                    maxX = lastButton.getX() - titleSpacing;
                } else {
                    maxX = getParentWidth() - getParentInsets().right - titleSpacing;
                }

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

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

                maxX = getParentWidth() - getParentInsets().right - 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);
            ((SeaGlassGraphicsUtils) style.getGraphicsUtils(context)).drawEmphasizedText(g, color, DEFAULT_EMPHASIS_COLOR, clippedTitle,
                                                                                         minX, baseline); // - fm.getAscent());
        }
    }

    /**
     * Returns the String to display as the title.
     *
     * @return the title String.
     */
    private String getTitle() {
        if (rootParent instanceof JFrame) {
            return ((JFrame) rootParent).getTitle();
        } else if (rootParent instanceof JDialog) {
            return ((JDialog) rootParent).getTitle();
        }

        return null;
    }

    /**
     * Get the parent insets.
     *
     * @return the insets.
     */
    private Insets getParentInsets() {
        if (rootParent instanceof JApplet) {
            return ((JApplet) rootParent).getInsets();
        }

        return ((Window) rootParent).getInsets();
    }

    /**
     * Get the parent width.
     *
     * @return the width.
     */
    private int getParentWidth() {
        if (rootParent instanceof JApplet) {
            return ((JApplet) rootParent).getWidth();
        }

        return ((Window) rootParent).getWidth();
    }

    /**
     * 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(rootPane, 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() == "iconImage") {
                updateMenuIcon();
            }
        }

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

        if (closeButton != null && WINDOW_DOCUMENT_MODIFIED.equals(prop)) {
            closeButton.revalidate();
            closeButton.repaint();

            return;
        }

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

            return;
        }

        // Frame.state isn't currently bound.
        if ("resizable".equals(prop) || "state".equals(prop)) {
            Frame frame = (JFrame) rootParent;

            if (frame != null) {
                setState(frame.getExtendedState(), true);
            }

            if ("resizable".equals(prop)) {
                getRootPane().repaint();
            }
        } else if ("title".equals(prop)) {
            repaint();
        } else if ("componentOrientation" == prop) {
            revalidate();
            repaint();
        } else if ("iconImage" == prop) {
            revalidate();
            repaint();
        }

        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();
    }

    /**
     * Sets the state of the window. If <code>updateRegardless</code> is true
     * and the state has not changed, this will update anyway.
     *
     * @param state            the state of the window.
     * @param updateRegardless {@code true} if we are to update regardless of
     *                         the state, {@code false} otherwise.
     */
    private void setState(int state, boolean updateRegardless) {
        Window w = (Window) rootParent;

        if (w != null && rootPane.getWindowDecorationStyle() == JRootPane.FRAME) {

            if (this.state == state && !updateRegardless) {
                return;
            }

            Frame frame = (JFrame) rootParent;

            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;
        }
    }

    /**
     * Enable/disable the button actions as needed.
     */
    private void enableActions() {
        restoreAction.setEnabled(isParentMaximum() || isParentIcon());
        maximizeAction.setEnabled((isParentMaximizable() && !isParentMaximum() && !isParentIcon())
                                      || (isParentMaximizable() && isParentIcon()));
        iconifyAction.setEnabled(isParentIconifiable() && !isParentIcon());
        closeAction.setEnabled(isParentClosable());
        sizeAction.setEnabled(false);
        moveAction.setEnabled(false);
    }

    /**
     * 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(SeaGlassTitlePane.this);
            int             width   = 10;
            int             height  = FrameAndRootPainter.TITLE_BAR_HEIGHT;

            int       buttonCount = 0;
            Dimension pref;

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

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

            if (isParentIconifiable()) {
                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            = getFontMetrics(getFont());
            SynthGraphicsUtils graphicsUtils = context.getStyle().getGraphicsUtils(context);
            String             frameTitle    = 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(), 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 = 1;

            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 (isParentLeftToRight()) {
                center(menuButton, insets, insets.left + 4, false);
                int x = getWidth() - insets.right - 5;

                if (isParentClosable()) {
                    x = center(closeButton, insets, x, true);
                }

                if (isParentMaximizable()) {
                    x = center(maxButton, insets, x, true);
                }

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

                if (isParentClosable()) {
                    x = center(closeButton, insets, x, false);
                }

                if (isParentMaximizable()) {
                    x = center(maxButton, insets, x, false);
                }

                if (isParentIconifiable()) {
                    x = center(iconButton, insets, x, false);
                }
            }
        }
    }

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

        /**
         * 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 (isParentClosable()) {
                doParentDefaultCloseAction();
            }
        }
    }

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

        /**
         * 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 (isParentMaximizable()) {

                if (isParentMaximum() && isParentIcon()) {
                    setParentIcon(false);
                } else if (!isParentMaximum()) {
                    setParentMaximum(true);
                } else {
                    setParentMaximum(false);
                }

                setButtonTooltips();
            }
        }
    }

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

        /**
         * 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 (isParentIconifiable()) {

                if (!isParentIcon()) {
                    setParentIcon(true);
                } else {
                    setParentIcon(false);
                }
            }

            setButtonTooltips();
        }
    }

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

        /**
         * 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 (isParentMaximizable() && isParentMaximum() && isParentIcon()) {
                setParentIcon(false);
            } else if (isParentMaximizable() && isParentMaximum()) {
                setParentMaximum(false);
            } else if (isParentIconifiable() && isParentIcon()) {
                setParentIcon(false);
            }

            setButtonTooltips();
        }
    }

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

        /**
         * 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 = 3363009814611622667L;
        private boolean           show; // whether to show the menu

        /**
         * Creates a new ShowSystemMenuAction object.
         *
         * @param show {@code true} if the action is to show, {@code false}
         *             otherwise.
         */
        public ShowSystemMenuAction(boolean show) {
            this.show = show;
        }

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

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

        /**
         * 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 = 5958100825982240844L;
        private String            uiKey;

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

        /**
         * @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;
        }
    }
}