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