1/*
2 * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package javax.swing.plaf.metal;
27
28import sun.swing.SwingUtilities2;
29import sun.awt.SunToolkit;
30import java.awt.*;
31import java.awt.event.*;
32import java.beans.*;
33import javax.swing.*;
34import javax.swing.border.*;
35import javax.swing.event.InternalFrameEvent;
36import javax.swing.plaf.*;
37import javax.swing.plaf.basic.*;
38import java.util.Locale;
39import javax.accessibility.*;
40
41
42/**
43 * Class that manages a JLF awt.Window-descendant class's title bar.
44 * <p>
45 * This class assumes it will be created with a particular window
46 * decoration style, and that if the style changes, a new one will
47 * be created.
48 *
49 * @author Terry Kellerman
50 * @since 1.4
51 */
52@SuppressWarnings("serial") // Superclass is not serializable across versions
53class MetalTitlePane extends JComponent {
54    private static final Border handyEmptyBorder = new EmptyBorder(0,0,0,0);
55    private static final int IMAGE_HEIGHT = 16;
56    private static final int IMAGE_WIDTH = 16;
57
58    /**
59     * PropertyChangeListener added to the JRootPane.
60     */
61    private PropertyChangeListener propertyChangeListener;
62
63    /**
64     * JMenuBar, typically renders the system menu items.
65     */
66    private JMenuBar menuBar;
67    /**
68     * Action used to close the Window.
69     */
70    private Action closeAction;
71
72    /**
73     * Action used to iconify the Frame.
74     */
75    private Action iconifyAction;
76
77    /**
78     * Action to restore the Frame size.
79     */
80    private Action restoreAction;
81
82    /**
83     * Action to restore the Frame size.
84     */
85    private Action maximizeAction;
86
87    /**
88     * Button used to maximize or restore the Frame.
89     */
90    private JButton toggleButton;
91
92    /**
93     * Button used to maximize or restore the Frame.
94     */
95    private JButton iconifyButton;
96
97    /**
98     * Button used to maximize or restore the Frame.
99     */
100    private JButton closeButton;
101
102    /**
103     * Icon used for toggleButton when window is normal size.
104     */
105    private Icon maximizeIcon;
106
107    /**
108     * Icon used for toggleButton when window is maximized.
109     */
110    private Icon minimizeIcon;
111
112    /**
113     * Image used for the system menu icon
114     */
115    private Image systemIcon;
116
117    /**
118     * Listens for changes in the state of the Window listener to update
119     * the state of the widgets.
120     */
121    private WindowListener windowListener;
122
123    /**
124     * Window we're currently in.
125     */
126    private Window window;
127
128    /**
129     * JRootPane rendering for.
130     */
131    private JRootPane rootPane;
132
133    /**
134     * Room remaining in title for bumps.
135     */
136    private int buttonsWidth;
137
138    /**
139     * Buffered Frame.state property. As state isn't bound, this is kept
140     * to determine when to avoid updating widgets.
141     */
142    private int state;
143
144    /**
145     * MetalRootPaneUI that created us.
146     */
147    private MetalRootPaneUI rootPaneUI;
148
149
150    // Colors
151    private Color inactiveBackground = UIManager.getColor("inactiveCaption");
152    private Color inactiveForeground = UIManager.getColor("inactiveCaptionText");
153    private Color inactiveShadow = UIManager.getColor("inactiveCaptionBorder");
154    private Color activeBumpsHighlight = MetalLookAndFeel.getPrimaryControlHighlight();
155    private Color activeBumpsShadow = MetalLookAndFeel.getPrimaryControlDarkShadow();
156    private Color activeBackground = null;
157    private Color activeForeground = null;
158    private Color activeShadow = null;
159
160    // Bumps
161    private MetalBumps activeBumps
162        = new MetalBumps( 0, 0,
163                          activeBumpsHighlight,
164                          activeBumpsShadow,
165                          MetalLookAndFeel.getPrimaryControl() );
166    private MetalBumps inactiveBumps
167        = new MetalBumps( 0, 0,
168                          MetalLookAndFeel.getControlHighlight(),
169                          MetalLookAndFeel.getControlDarkShadow(),
170                          MetalLookAndFeel.getControl() );
171
172
173    public MetalTitlePane(JRootPane root, MetalRootPaneUI ui) {
174        this.rootPane = root;
175        rootPaneUI = ui;
176
177        state = -1;
178
179        installSubcomponents();
180        determineColors();
181        installDefaults();
182
183        setLayout(createLayout());
184    }
185
186    /**
187     * Uninstalls the necessary state.
188     */
189    private void uninstall() {
190        uninstallListeners();
191        window = null;
192        removeAll();
193    }
194
195    /**
196     * Installs the necessary listeners.
197     */
198    private void installListeners() {
199        if (window != null) {
200            windowListener = createWindowListener();
201            window.addWindowListener(windowListener);
202            propertyChangeListener = createWindowPropertyChangeListener();
203            window.addPropertyChangeListener(propertyChangeListener);
204        }
205    }
206
207    /**
208     * Uninstalls the necessary listeners.
209     */
210    private void uninstallListeners() {
211        if (window != null) {
212            window.removeWindowListener(windowListener);
213            window.removePropertyChangeListener(propertyChangeListener);
214        }
215    }
216
217    /**
218     * Returns the <code>WindowListener</code> to add to the
219     * <code>Window</code>.
220     */
221    private WindowListener createWindowListener() {
222        return new WindowHandler();
223    }
224
225    /**
226     * Returns the <code>PropertyChangeListener</code> to install on
227     * the <code>Window</code>.
228     */
229    private PropertyChangeListener createWindowPropertyChangeListener() {
230        return new PropertyChangeHandler();
231    }
232
233    /**
234     * Returns the <code>JRootPane</code> this was created for.
235     */
236    public JRootPane getRootPane() {
237        return rootPane;
238    }
239
240    /**
241     * Returns the decoration style of the <code>JRootPane</code>.
242     */
243    private int getWindowDecorationStyle() {
244        return getRootPane().getWindowDecorationStyle();
245    }
246
247    public void addNotify() {
248        super.addNotify();
249
250        uninstallListeners();
251
252        window = SwingUtilities.getWindowAncestor(this);
253        if (window != null) {
254            if (window instanceof Frame) {
255                setState(((Frame)window).getExtendedState());
256            }
257            else {
258                setState(0);
259            }
260            setActive(window.isActive());
261            installListeners();
262            updateSystemIcon();
263        }
264    }
265
266    public void removeNotify() {
267        super.removeNotify();
268
269        uninstallListeners();
270        window = null;
271    }
272
273    /**
274     * Adds any sub-Components contained in the <code>MetalTitlePane</code>.
275     */
276    private void installSubcomponents() {
277        int decorationStyle = getWindowDecorationStyle();
278        if (decorationStyle == JRootPane.FRAME) {
279            createActions();
280            menuBar = createMenuBar();
281            add(menuBar);
282            createButtons();
283            add(iconifyButton);
284            add(toggleButton);
285            add(closeButton);
286        } else if (decorationStyle == JRootPane.PLAIN_DIALOG ||
287                decorationStyle == JRootPane.INFORMATION_DIALOG ||
288                decorationStyle == JRootPane.ERROR_DIALOG ||
289                decorationStyle == JRootPane.COLOR_CHOOSER_DIALOG ||
290                decorationStyle == JRootPane.FILE_CHOOSER_DIALOG ||
291                decorationStyle == JRootPane.QUESTION_DIALOG ||
292                decorationStyle == JRootPane.WARNING_DIALOG) {
293            createActions();
294            createButtons();
295            add(closeButton);
296        }
297    }
298
299    /**
300     * Determines the Colors to draw with.
301     */
302    private void determineColors() {
303        switch (getWindowDecorationStyle()) {
304        case JRootPane.FRAME:
305            activeBackground = UIManager.getColor("activeCaption");
306            activeForeground = UIManager.getColor("activeCaptionText");
307            activeShadow = UIManager.getColor("activeCaptionBorder");
308            break;
309        case JRootPane.ERROR_DIALOG:
310            activeBackground = UIManager.getColor(
311                "OptionPane.errorDialog.titlePane.background");
312            activeForeground = UIManager.getColor(
313                "OptionPane.errorDialog.titlePane.foreground");
314            activeShadow = UIManager.getColor(
315                "OptionPane.errorDialog.titlePane.shadow");
316            break;
317        case JRootPane.QUESTION_DIALOG:
318        case JRootPane.COLOR_CHOOSER_DIALOG:
319        case JRootPane.FILE_CHOOSER_DIALOG:
320            activeBackground = UIManager.getColor(
321                "OptionPane.questionDialog.titlePane.background");
322            activeForeground = UIManager.getColor(
323                "OptionPane.questionDialog.titlePane.foreground");
324            activeShadow = UIManager.getColor(
325                "OptionPane.questionDialog.titlePane.shadow");
326            break;
327        case JRootPane.WARNING_DIALOG:
328            activeBackground = UIManager.getColor(
329                "OptionPane.warningDialog.titlePane.background");
330            activeForeground = UIManager.getColor(
331                "OptionPane.warningDialog.titlePane.foreground");
332            activeShadow = UIManager.getColor(
333                "OptionPane.warningDialog.titlePane.shadow");
334            break;
335        case JRootPane.PLAIN_DIALOG:
336        case JRootPane.INFORMATION_DIALOG:
337        default:
338            activeBackground = UIManager.getColor("activeCaption");
339            activeForeground = UIManager.getColor("activeCaptionText");
340            activeShadow = UIManager.getColor("activeCaptionBorder");
341            break;
342        }
343        activeBumps.setBumpColors(activeBumpsHighlight, activeBumpsShadow,
344                                  activeBackground);
345    }
346
347    /**
348     * Installs the fonts and necessary properties on the MetalTitlePane.
349     */
350    private void installDefaults() {
351        setFont(UIManager.getFont("InternalFrame.titleFont", getLocale()));
352    }
353
354    /**
355     * Uninstalls any previously installed UI values.
356     */
357    private void uninstallDefaults() {
358    }
359
360    /**
361     * Returns the <code>JMenuBar</code> displaying the appropriate
362     * system menu items.
363     */
364    protected JMenuBar createMenuBar() {
365        menuBar = new SystemMenuBar();
366        menuBar.setFocusable(false);
367        menuBar.setBorderPainted(true);
368        menuBar.add(createMenu());
369        return menuBar;
370    }
371
372    /**
373     * Closes the Window.
374     */
375    private void close() {
376        Window window = getWindow();
377
378        if (window != null) {
379            window.dispatchEvent(new WindowEvent(
380                                 window, WindowEvent.WINDOW_CLOSING));
381        }
382    }
383
384    /**
385     * Iconifies the Frame.
386     */
387    private void iconify() {
388        Frame frame = getFrame();
389        if (frame != null) {
390            frame.setExtendedState(state | Frame.ICONIFIED);
391        }
392    }
393
394    /**
395     * Maximizes the Frame.
396     */
397    private void maximize() {
398        Frame frame = getFrame();
399        if (frame != null) {
400            frame.setExtendedState(state | Frame.MAXIMIZED_BOTH);
401        }
402    }
403
404    /**
405     * Restores the Frame size.
406     */
407    private void restore() {
408        Frame frame = getFrame();
409
410        if (frame == null) {
411            return;
412        }
413
414        if ((state & Frame.ICONIFIED) != 0) {
415            frame.setExtendedState(state & ~Frame.ICONIFIED);
416        } else {
417            frame.setExtendedState(state & ~Frame.MAXIMIZED_BOTH);
418        }
419    }
420
421    /**
422     * Create the <code>Action</code>s that get associated with the
423     * buttons and menu items.
424     */
425    private void createActions() {
426        closeAction = new CloseAction();
427        if (getWindowDecorationStyle() == JRootPane.FRAME) {
428            iconifyAction = new IconifyAction();
429            restoreAction = new RestoreAction();
430            maximizeAction = new MaximizeAction();
431        }
432    }
433
434    /**
435     * Returns the <code>JMenu</code> displaying the appropriate menu items
436     * for manipulating the Frame.
437     */
438    private JMenu createMenu() {
439        JMenu menu = new JMenu("");
440        if (getWindowDecorationStyle() == JRootPane.FRAME) {
441            addMenuItems(menu);
442        }
443        return menu;
444    }
445
446    /**
447     * Adds the necessary <code>JMenuItem</code>s to the passed in menu.
448     */
449    private void addMenuItems(JMenu menu) {
450        Locale locale = getRootPane().getLocale();
451        JMenuItem mi = menu.add(restoreAction);
452        int mnemonic = MetalUtils.getInt("MetalTitlePane.restoreMnemonic", -1);
453
454        if (mnemonic != -1) {
455            mi.setMnemonic(mnemonic);
456        }
457
458        mi = menu.add(iconifyAction);
459        mnemonic = MetalUtils.getInt("MetalTitlePane.iconifyMnemonic", -1);
460        if (mnemonic != -1) {
461            mi.setMnemonic(mnemonic);
462        }
463
464        if (Toolkit.getDefaultToolkit().isFrameStateSupported(
465                Frame.MAXIMIZED_BOTH)) {
466            mi = menu.add(maximizeAction);
467            mnemonic =
468                MetalUtils.getInt("MetalTitlePane.maximizeMnemonic", -1);
469            if (mnemonic != -1) {
470                mi.setMnemonic(mnemonic);
471            }
472        }
473
474        menu.add(new JSeparator());
475
476        mi = menu.add(closeAction);
477        mnemonic = MetalUtils.getInt("MetalTitlePane.closeMnemonic", -1);
478        if (mnemonic != -1) {
479            mi.setMnemonic(mnemonic);
480        }
481    }
482
483    /**
484     * Returns a <code>JButton</code> appropriate for placement on the
485     * TitlePane.
486     */
487    private JButton createTitleButton() {
488        JButton button = new JButton();
489
490        button.setFocusPainted(false);
491        button.setFocusable(false);
492        button.setOpaque(true);
493        return button;
494    }
495
496    /**
497     * Creates the Buttons that will be placed on the TitlePane.
498     */
499    private void createButtons() {
500        closeButton = createTitleButton();
501        closeButton.setAction(closeAction);
502        closeButton.setText(null);
503        closeButton.putClientProperty("paintActive", Boolean.TRUE);
504        closeButton.setBorder(handyEmptyBorder);
505        closeButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
506                                      "Close");
507        closeButton.setIcon(UIManager.getIcon("InternalFrame.closeIcon"));
508
509        if (getWindowDecorationStyle() == JRootPane.FRAME) {
510            maximizeIcon = UIManager.getIcon("InternalFrame.maximizeIcon");
511            minimizeIcon = UIManager.getIcon("InternalFrame.minimizeIcon");
512
513            iconifyButton = createTitleButton();
514            iconifyButton.setAction(iconifyAction);
515            iconifyButton.setText(null);
516            iconifyButton.putClientProperty("paintActive", Boolean.TRUE);
517            iconifyButton.setBorder(handyEmptyBorder);
518            iconifyButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
519                                            "Iconify");
520            iconifyButton.setIcon(UIManager.getIcon("InternalFrame.iconifyIcon"));
521
522            toggleButton = createTitleButton();
523            toggleButton.setAction(restoreAction);
524            toggleButton.putClientProperty("paintActive", Boolean.TRUE);
525            toggleButton.setBorder(handyEmptyBorder);
526            toggleButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
527                                           "Maximize");
528            toggleButton.setIcon(maximizeIcon);
529        }
530    }
531
532    /**
533     * Returns the <code>LayoutManager</code> that should be installed on
534     * the <code>MetalTitlePane</code>.
535     */
536    private LayoutManager createLayout() {
537        return new TitlePaneLayout();
538    }
539
540    /**
541     * Updates state dependent upon the Window's active state.
542     */
543    private void setActive(boolean isActive) {
544        Boolean activeB = isActive ? Boolean.TRUE : Boolean.FALSE;
545
546        closeButton.putClientProperty("paintActive", activeB);
547        if (getWindowDecorationStyle() == JRootPane.FRAME) {
548            iconifyButton.putClientProperty("paintActive", activeB);
549            toggleButton.putClientProperty("paintActive", activeB);
550        }
551        // Repaint the whole thing as the Borders that are used have
552        // different colors for active vs inactive
553        getRootPane().repaint();
554    }
555
556    /**
557     * Sets the state of the Window.
558     */
559    private void setState(int state) {
560        setState(state, false);
561    }
562
563    /**
564     * Sets the state of the window. If <code>updateRegardless</code> is
565     * true and the state has not changed, this will update anyway.
566     */
567    private void setState(int state, boolean updateRegardless) {
568        Window w = getWindow();
569
570        if (w != null && getWindowDecorationStyle() == JRootPane.FRAME) {
571            if (this.state == state && !updateRegardless) {
572                return;
573            }
574            Frame frame = getFrame();
575
576            if (frame != null) {
577                JRootPane rootPane = getRootPane();
578
579                if (((state & Frame.MAXIMIZED_BOTH) != 0) &&
580                        (rootPane.getBorder() == null ||
581                        (rootPane.getBorder() instanceof UIResource)) &&
582                            frame.isShowing()) {
583                    rootPane.setBorder(null);
584                }
585                else if ((state & Frame.MAXIMIZED_BOTH) == 0) {
586                    // This is a croak, if state becomes bound, this can
587                    // be nuked.
588                    rootPaneUI.installBorder(rootPane);
589                }
590                if (frame.isResizable()) {
591                    if ((state & Frame.MAXIMIZED_BOTH) != 0) {
592                        updateToggleButton(restoreAction, minimizeIcon);
593                        maximizeAction.setEnabled(false);
594                        restoreAction.setEnabled(true);
595                    }
596                    else {
597                        updateToggleButton(maximizeAction, maximizeIcon);
598                        maximizeAction.setEnabled(true);
599                        restoreAction.setEnabled(false);
600                    }
601                    if (toggleButton.getParent() == null ||
602                        iconifyButton.getParent() == null) {
603                        add(toggleButton);
604                        add(iconifyButton);
605                        revalidate();
606                        repaint();
607                    }
608                    toggleButton.setText(null);
609                }
610                else {
611                    maximizeAction.setEnabled(false);
612                    restoreAction.setEnabled(false);
613                    if (toggleButton.getParent() != null) {
614                        remove(toggleButton);
615                        revalidate();
616                        repaint();
617                    }
618                }
619            }
620            else {
621                // Not contained in a Frame
622                maximizeAction.setEnabled(false);
623                restoreAction.setEnabled(false);
624                iconifyAction.setEnabled(false);
625                remove(toggleButton);
626                remove(iconifyButton);
627                revalidate();
628                repaint();
629            }
630            closeAction.setEnabled(true);
631            this.state = state;
632        }
633    }
634
635    /**
636     * Updates the toggle button to contain the Icon <code>icon</code>, and
637     * Action <code>action</code>.
638     */
639    private void updateToggleButton(Action action, Icon icon) {
640        toggleButton.setAction(action);
641        toggleButton.setIcon(icon);
642        toggleButton.setText(null);
643    }
644
645    /**
646     * Returns the Frame rendering in. This will return null if the
647     * <code>JRootPane</code> is not contained in a <code>Frame</code>.
648     */
649    private Frame getFrame() {
650        Window window = getWindow();
651
652        if (window instanceof Frame) {
653            return (Frame)window;
654        }
655        return null;
656    }
657
658    /**
659     * Returns the <code>Window</code> the <code>JRootPane</code> is
660     * contained in. This will return null if there is no parent ancestor
661     * of the <code>JRootPane</code>.
662     */
663    private Window getWindow() {
664        return window;
665    }
666
667    /**
668     * Returns the String to display as the title.
669     */
670    private String getTitle() {
671        Window w = getWindow();
672
673        if (w instanceof Frame) {
674            return ((Frame)w).getTitle();
675        }
676        else if (w instanceof Dialog) {
677            return ((Dialog)w).getTitle();
678        }
679        return null;
680    }
681
682    /**
683     * Renders the TitlePane.
684     */
685    public void paintComponent(Graphics g)  {
686        // As state isn't bound, we need a convenience place to check
687        // if it has changed. Changing the state typically changes the
688        if (getFrame() != null) {
689            setState(getFrame().getExtendedState());
690        }
691        JRootPane rootPane = getRootPane();
692        Window window = getWindow();
693        boolean leftToRight = (window == null) ?
694                               rootPane.getComponentOrientation().isLeftToRight() :
695                               window.getComponentOrientation().isLeftToRight();
696        boolean isSelected = (window == null) ? true : window.isActive();
697        int width = getWidth();
698        int height = getHeight();
699
700        Color background;
701        Color foreground;
702        Color darkShadow;
703
704        MetalBumps bumps;
705
706        if (isSelected) {
707            background = activeBackground;
708            foreground = activeForeground;
709            darkShadow = activeShadow;
710            bumps = activeBumps;
711        } else {
712            background = inactiveBackground;
713            foreground = inactiveForeground;
714            darkShadow = inactiveShadow;
715            bumps = inactiveBumps;
716        }
717
718        g.setColor(background);
719        g.fillRect(0, 0, width, height);
720
721        g.setColor( darkShadow );
722        g.drawLine ( 0, height - 1, width, height -1);
723        g.drawLine ( 0, 0, 0 ,0);
724        g.drawLine ( width - 1, 0 , width -1, 0);
725
726        int xOffset = leftToRight ? 5 : width - 5;
727
728        if (getWindowDecorationStyle() == JRootPane.FRAME) {
729            xOffset += leftToRight ? IMAGE_WIDTH + 5 : - IMAGE_WIDTH - 5;
730        }
731
732        String theTitle = getTitle();
733        if (theTitle != null) {
734            FontMetrics fm = SwingUtilities2.getFontMetrics(rootPane, g);
735
736            g.setColor(foreground);
737
738            int yOffset = ( (height - fm.getHeight() ) / 2 ) + fm.getAscent();
739
740            Rectangle rect = new Rectangle(0, 0, 0, 0);
741            if (iconifyButton != null && iconifyButton.getParent() != null) {
742                rect = iconifyButton.getBounds();
743            }
744            int titleW;
745
746            if( leftToRight ) {
747                if (rect.x == 0) {
748                    rect.x = window.getWidth() - window.getInsets().right-2;
749                }
750                titleW = rect.x - xOffset - 4;
751                theTitle = SwingUtilities2.clipStringIfNecessary(
752                                rootPane, fm, theTitle, titleW);
753            } else {
754                titleW = xOffset - rect.x - rect.width - 4;
755                theTitle = SwingUtilities2.clipStringIfNecessary(
756                                rootPane, fm, theTitle, titleW);
757                xOffset -= SwingUtilities2.stringWidth(rootPane, fm,
758                                                       theTitle);
759            }
760            int titleLength = SwingUtilities2.stringWidth(rootPane, fm,
761                                                          theTitle);
762            SwingUtilities2.drawString(rootPane, g, theTitle, xOffset,
763                                       yOffset );
764            xOffset += leftToRight ? titleLength + 5  : -5;
765        }
766
767        int bumpXOffset;
768        int bumpLength;
769        if( leftToRight ) {
770            bumpLength = width - buttonsWidth - xOffset - 5;
771            bumpXOffset = xOffset;
772        } else {
773            bumpLength = xOffset - buttonsWidth - 5;
774            bumpXOffset = buttonsWidth + 5;
775        }
776        int bumpYOffset = 3;
777        int bumpHeight = getHeight() - (2 * bumpYOffset);
778        bumps.setBumpArea( bumpLength, bumpHeight );
779        bumps.paintIcon(this, g, bumpXOffset, bumpYOffset);
780    }
781
782    /**
783     * Actions used to <code>close</code> the <code>Window</code>.
784     */
785    @SuppressWarnings("serial") // Superclass is not serializable across versions
786    private class CloseAction extends AbstractAction {
787        public CloseAction() {
788            super(UIManager.getString("MetalTitlePane.closeTitle",
789                                      getLocale()));
790        }
791
792        public void actionPerformed(ActionEvent e) {
793            close();
794        }
795    }
796
797
798    /**
799     * Actions used to <code>iconfiy</code> the <code>Frame</code>.
800     */
801    @SuppressWarnings("serial") // Superclass is not serializable across versions
802    private class IconifyAction extends AbstractAction {
803        public IconifyAction() {
804            super(UIManager.getString("MetalTitlePane.iconifyTitle",
805                                      getLocale()));
806        }
807
808        public void actionPerformed(ActionEvent e) {
809            iconify();
810        }
811    }
812
813
814    /**
815     * Actions used to <code>restore</code> the <code>Frame</code>.
816     */
817    @SuppressWarnings("serial") // Superclass is not serializable across versions
818    private class RestoreAction extends AbstractAction {
819        public RestoreAction() {
820            super(UIManager.getString
821                  ("MetalTitlePane.restoreTitle", getLocale()));
822        }
823
824        public void actionPerformed(ActionEvent e) {
825            restore();
826        }
827    }
828
829
830    /**
831     * Actions used to <code>restore</code> the <code>Frame</code>.
832     */
833    @SuppressWarnings("serial") // Superclass is not serializable across versions
834    private class MaximizeAction extends AbstractAction {
835        public MaximizeAction() {
836            super(UIManager.getString("MetalTitlePane.maximizeTitle",
837                                      getLocale()));
838        }
839
840        public void actionPerformed(ActionEvent e) {
841            maximize();
842        }
843    }
844
845
846    /**
847     * Class responsible for drawing the system menu. Looks up the
848     * image to draw from the Frame associated with the
849     * <code>JRootPane</code>.
850     */
851    @SuppressWarnings("serial") // Superclass is not serializable across versions
852    private class SystemMenuBar extends JMenuBar {
853        public void paint(Graphics g) {
854            if (isOpaque()) {
855                g.setColor(getBackground());
856                g.fillRect(0, 0, getWidth(), getHeight());
857            }
858
859            if (systemIcon != null) {
860                g.drawImage(systemIcon, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null);
861            } else {
862                Icon icon = UIManager.getIcon("InternalFrame.icon");
863
864                if (icon != null) {
865                    icon.paintIcon(this, g, 0, 0);
866                }
867            }
868        }
869        public Dimension getMinimumSize() {
870            return getPreferredSize();
871        }
872        public Dimension getPreferredSize() {
873            Dimension size = super.getPreferredSize();
874
875            return new Dimension(Math.max(IMAGE_WIDTH, size.width),
876                                 Math.max(size.height, IMAGE_HEIGHT));
877        }
878    }
879
880    private class TitlePaneLayout implements LayoutManager {
881        public void addLayoutComponent(String name, Component c) {}
882        public void removeLayoutComponent(Component c) {}
883        public Dimension preferredLayoutSize(Container c)  {
884            int height = computeHeight();
885            return new Dimension(height, height);
886        }
887
888        public Dimension minimumLayoutSize(Container c) {
889            return preferredLayoutSize(c);
890        }
891
892        private int computeHeight() {
893            FontMetrics fm = rootPane.getFontMetrics(getFont());
894            int fontHeight = fm.getHeight();
895            fontHeight += 7;
896            int iconHeight = 0;
897            if (getWindowDecorationStyle() == JRootPane.FRAME) {
898                iconHeight = IMAGE_HEIGHT;
899            }
900
901            int finalHeight = Math.max( fontHeight, iconHeight );
902            return finalHeight;
903        }
904
905        public void layoutContainer(Container c) {
906            boolean leftToRight = (window == null) ?
907                getRootPane().getComponentOrientation().isLeftToRight() :
908                window.getComponentOrientation().isLeftToRight();
909
910            int w = getWidth();
911            int x;
912            int y = 3;
913            int spacing;
914            int buttonHeight;
915            int buttonWidth;
916
917            if (closeButton != null && closeButton.getIcon() != null) {
918                buttonHeight = closeButton.getIcon().getIconHeight();
919                buttonWidth = closeButton.getIcon().getIconWidth();
920            }
921            else {
922                buttonHeight = IMAGE_HEIGHT;
923                buttonWidth = IMAGE_WIDTH;
924            }
925
926            // assumes all buttons have the same dimensions
927            // these dimensions include the borders
928
929            x = leftToRight ? w : 0;
930
931            spacing = 5;
932            x = leftToRight ? spacing : w - buttonWidth - spacing;
933            if (menuBar != null) {
934                menuBar.setBounds(x, y, buttonWidth, buttonHeight);
935            }
936
937            x = leftToRight ? w : 0;
938            spacing = 4;
939            x += leftToRight ? -spacing -buttonWidth : spacing;
940            if (closeButton != null) {
941                closeButton.setBounds(x, y, buttonWidth, buttonHeight);
942            }
943
944            if( !leftToRight ) x += buttonWidth;
945
946            if (getWindowDecorationStyle() == JRootPane.FRAME) {
947                if (Toolkit.getDefaultToolkit().isFrameStateSupported(
948                        Frame.MAXIMIZED_BOTH)) {
949                    if (toggleButton.getParent() != null) {
950                        spacing = 10;
951                        x += leftToRight ? -spacing -buttonWidth : spacing;
952                        toggleButton.setBounds(x, y, buttonWidth, buttonHeight);
953                        if (!leftToRight) {
954                            x += buttonWidth;
955                        }
956                    }
957                }
958
959                if (iconifyButton != null && iconifyButton.getParent() != null) {
960                    spacing = 2;
961                    x += leftToRight ? -spacing -buttonWidth : spacing;
962                    iconifyButton.setBounds(x, y, buttonWidth, buttonHeight);
963                    if (!leftToRight) {
964                        x += buttonWidth;
965                    }
966                }
967            }
968            buttonsWidth = leftToRight ? w - x : x;
969        }
970    }
971
972
973
974    /**
975     * PropertyChangeListener installed on the Window. Updates the necessary
976     * state as the state of the Window changes.
977     */
978    private class PropertyChangeHandler implements PropertyChangeListener {
979        public void propertyChange(PropertyChangeEvent pce) {
980            String name = pce.getPropertyName();
981
982            // Frame.state isn't currently bound.
983            if ("resizable".equals(name) || "state".equals(name)) {
984                Frame frame = getFrame();
985
986                if (frame != null) {
987                    setState(frame.getExtendedState(), true);
988                }
989                if ("resizable".equals(name)) {
990                    getRootPane().repaint();
991                }
992            }
993            else if ("title".equals(name)) {
994                repaint();
995            }
996            else if ("componentOrientation" == name) {
997                revalidate();
998                repaint();
999            }
1000            else if ("iconImage" == name) {
1001                updateSystemIcon();
1002                revalidate();
1003                repaint();
1004            }
1005        }
1006    }
1007
1008    /**
1009     * Update the image used for the system icon
1010     */
1011    private void updateSystemIcon() {
1012        Window window = getWindow();
1013        if (window == null) {
1014            systemIcon = null;
1015            return;
1016        }
1017        java.util.List<Image> icons = window.getIconImages();
1018        assert icons != null;
1019
1020        if (icons.size() == 0) {
1021            systemIcon = null;
1022        }
1023        else if (icons.size() == 1) {
1024            systemIcon = icons.get(0);
1025        }
1026        else {
1027            systemIcon = SunToolkit.getScaledIconImage(icons,
1028                                                       IMAGE_WIDTH,
1029                                                       IMAGE_HEIGHT);
1030        }
1031    }
1032
1033
1034    /**
1035     * WindowListener installed on the Window, updates the state as necessary.
1036     */
1037    private class WindowHandler extends WindowAdapter {
1038        public void windowActivated(WindowEvent ev) {
1039            setActive(true);
1040        }
1041
1042        public void windowDeactivated(WindowEvent ev) {
1043            setActive(false);
1044        }
1045    }
1046}
1047