001    /* MetalRootPaneUI.java
002       Copyright (C) 2005, 2006 Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.plaf.metal;
040    
041    import java.awt.Color;
042    import java.awt.Component;
043    import java.awt.Container;
044    import java.awt.Dimension;
045    import java.awt.Frame;
046    import java.awt.Graphics;
047    import java.awt.Insets;
048    import java.awt.LayoutManager;
049    import java.awt.LayoutManager2;
050    import java.awt.Point;
051    import java.awt.Rectangle;
052    import java.awt.Window;
053    import java.awt.event.ActionEvent;
054    import java.awt.event.MouseEvent;
055    import java.beans.PropertyChangeEvent;
056    
057    import javax.swing.AbstractAction;
058    import javax.swing.Action;
059    import javax.swing.Icon;
060    import javax.swing.JButton;
061    import javax.swing.JComponent;
062    import javax.swing.JDialog;
063    import javax.swing.JFrame;
064    import javax.swing.JLabel;
065    import javax.swing.JLayeredPane;
066    import javax.swing.JMenu;
067    import javax.swing.JMenuBar;
068    import javax.swing.JRootPane;
069    import javax.swing.SwingConstants;
070    import javax.swing.SwingUtilities;
071    import javax.swing.UIManager;
072    import javax.swing.border.AbstractBorder;
073    import javax.swing.event.MouseInputAdapter;
074    import javax.swing.plaf.ComponentUI;
075    import javax.swing.plaf.basic.BasicRootPaneUI;
076    
077    /**
078     * A UI delegate for the {@link JRootPane} component. This implementation
079     * supports the JRootPane <code>windowDecorationStyle</code> property.  
080     * 
081     * @author Roman Kennke (kennke@aicas.com)
082     *
083     * @since 1.4
084     */
085    public class MetalRootPaneUI
086      extends BasicRootPaneUI
087    {
088    
089      /**
090       * The border that is used on JRootPane when the windowDecorationStyle
091       * property of the JRootPane is set to a different value than NONE.
092       *
093       * @author Roman Kennke (kennke@aicas.com)
094       */
095      private static class MetalFrameBorder
096        extends AbstractBorder
097      {
098        /**
099         * Returns the border insets.
100         *
101         * @param c the component
102         * @param newInsets the insets to be filled with the return value, may be
103         *        <code>null</code> in which case a new object is created
104         *
105         * @return the border insets
106         */
107        public Insets getBorderInsets(Component c, Insets newInsets)
108        {
109          if (newInsets == null)
110            newInsets = new Insets(5, 5, 5, 5);
111          else
112            {
113              newInsets.top = 5;
114              newInsets.left = 5;
115              newInsets.bottom = 5;
116              newInsets.right = 5;
117            }
118          return newInsets;  
119        }
120    
121        /**
122         * Returns the border insets.
123         *
124         * @param c the component
125         *
126         * @return the border insets
127         */
128        public Insets getBorderInsets(Component c)
129        {
130          return getBorderInsets(c, null);
131        }
132    
133        /**
134         * Paints the border for the specified component.
135         * 
136         * @param c  the component
137         * @param g  the graphics device
138         * @param x  the x-coordinate
139         * @param y  the y-coordinate
140         * @param w  the width
141         * @param h  the height
142         */
143        public void paintBorder(Component c, Graphics g, int x, int y, int w, 
144                                int h)
145        {
146          JRootPane f = (JRootPane) c;
147          Window frame = SwingUtilities.getWindowAncestor(f);
148          if (frame.isActive())
149            g.setColor(MetalLookAndFeel.getPrimaryControlDarkShadow());
150          else
151            g.setColor(MetalLookAndFeel.getControlDarkShadow());
152          
153          // Fill the border background.
154          g.fillRect(x, y, w, 5);
155          g.fillRect(x, y, 5, h);
156          g.fillRect(x + w - 5, y, 5, h);
157          g.fillRect(x, y + h - 5, w, 5);
158          
159          // Draw a dot in each corner.
160          g.setColor(MetalLookAndFeel.getControl());
161          g.fillRect(x, y, 1, 1);
162          g.fillRect(x + w - 1, y, 1, 1);
163          g.fillRect(x + w - 1, y + h - 1, 1, 1);
164          g.fillRect(x, y + h - 1, 1, 1);
165          
166          // Draw the lines.
167          g.setColor(MetalLookAndFeel.getBlack());
168          g.drawLine(x + 14, y + 2, x + w - 15, y + 2);
169          g.drawLine(x + 14, y + h - 3, x + w - 15, y + h - 3);
170          g.drawLine(x + 2, y + 14, x + 2, y + h - 15);
171          g.drawLine(x + w - 3, y + 14, x + w - 3, y + h - 15);
172          
173          // Draw the line highlights.
174          if (frame.isActive())
175            g.setColor(MetalLookAndFeel.getPrimaryControlShadow());
176          else 
177            g.setColor(MetalLookAndFeel.getControlShadow());
178          g.drawLine(x + 15, y + 3, x + w - 14, y + 3);
179          g.drawLine(x + 15, y + h - 2, x + w - 14, y + h - 2);
180          g.drawLine(x + 3, y + 15, x + 3, y + h - 14);
181          g.drawLine(x + w - 2, y + 15, x + w - 2, y + h - 14);
182        }
183      }
184    
185      /**
186       * The component that renders the title bar for frames. This duplicates
187       * most of {@link MetalInternalFrameTitlePane}. It is not reasonably possible
188       * to reuse that class because that is bound to the JInternalFrame and we
189       * need to handle JFrames/JRootPanes here.
190       *
191       * @author Roman Kennke (kennke@aicas.com)
192       */
193      private static class MetalTitlePane extends JComponent
194      {
195    
196        /**
197         * Handles dragging of the title pane and moves the window accordingly.
198         */
199        private class MouseHandler
200          extends MouseInputAdapter
201        {
202          /**
203           * The point where the dragging started.
204           */
205          Point lastDragLocation;
206    
207          /**
208           * Receives notification when the mouse gets pressed on the title pane.
209           * This updates the lastDragLocation.
210           *
211           * @param ev the mouse event
212           */
213          public void mousePressed(MouseEvent ev)
214          {
215            lastDragLocation = ev.getPoint();
216          }
217    
218          /**
219           * Receives notification when the mouse is dragged on the title pane.
220           * This will move the nearest window accordingly.
221           *
222           * @param ev the mouse event
223           */
224          public void mouseDragged(MouseEvent ev)
225          {
226            Point dragLocation = ev.getPoint();
227            int deltaX = dragLocation.x - lastDragLocation.x;
228            int deltaY = dragLocation.y - lastDragLocation.y;
229            Window window = SwingUtilities.getWindowAncestor(rootPane);
230            Point loc = window.getLocation();
231            window.setLocation(loc.x + deltaX, loc.y + deltaY);
232            // Note that we do not update the lastDragLocation. This is because
233            // we move the underlying window while dragging the component, which
234            // results in having the same lastDragLocation under the mouse while
235            // dragging.
236          }
237        }
238    
239        /**
240         * The Action responsible for closing the JInternalFrame.
241         */
242        private class CloseAction extends AbstractAction
243        {
244          /**
245           * Creates a new action.
246           */
247          public CloseAction()
248          {
249            super("Close");
250          }
251          
252          /**
253           * This method is called when something closes the frame.
254           *
255           * @param e the ActionEvent
256           */
257          public void actionPerformed(ActionEvent e)
258          {
259            Window frame = SwingUtilities.getWindowAncestor(rootPane);
260            if (frame instanceof JFrame)
261              {
262                JFrame jframe = (JFrame) frame;
263                switch (jframe.getDefaultCloseOperation())
264                {
265                  case JFrame.EXIT_ON_CLOSE:
266                    jframe.setVisible(false);
267                    jframe.dispose();
268                    System.exit(0);
269                    break;
270                  case JFrame.DISPOSE_ON_CLOSE:
271                    jframe.setVisible(false);
272                    jframe.dispose();
273                    break;
274                  case JFrame.HIDE_ON_CLOSE:
275                    jframe.setVisible(false);
276                    break;
277                  case JFrame.DO_NOTHING_ON_CLOSE:
278                  default:
279                      break;
280                }
281              }
282            else if (frame instanceof JDialog)
283              {
284                JDialog jdialog = (JDialog) frame;
285                switch (jdialog.getDefaultCloseOperation())
286                {
287                  case JFrame.DISPOSE_ON_CLOSE:
288                    jdialog.setVisible(false);
289                    jdialog.dispose();
290                    break;
291                  case JFrame.HIDE_ON_CLOSE:
292                    jdialog.setVisible(false);
293                    break;
294                  case JFrame.DO_NOTHING_ON_CLOSE:
295                  default:
296                      break;
297                }
298              }
299          }
300        }
301    
302        /**
303         * This action is performed when the iconify button is pressed.
304         */
305        private class IconifyAction
306          extends AbstractAction
307        {
308    
309          public void actionPerformed(ActionEvent event)
310          {
311            Window w = SwingUtilities.getWindowAncestor(rootPane);
312            if (w instanceof Frame)
313              {
314                Frame f = (Frame) w;
315                int state = f.getExtendedState();
316                f.setExtendedState(Frame.ICONIFIED);
317              }
318          }
319            
320        }
321    
322        /**
323         * This action is performed when the maximize button is pressed.
324         */
325        private class MaximizeAction
326          extends AbstractAction
327        {
328    
329          public void actionPerformed(ActionEvent event)
330          {
331            Window w = SwingUtilities.getWindowAncestor(rootPane);
332            if (w instanceof Frame)
333              {
334                Frame f = (Frame) w;
335                int state = f.getExtendedState();
336                f.setExtendedState(Frame.MAXIMIZED_BOTH);
337              }
338          }
339        }
340    
341        /**
342         * This helper class is used to create the minimize, maximize and close
343         * buttons in the top right corner of the Title Pane. These buttons are
344         * special since they cannot be given focus and have no border.
345         */
346        private class PaneButton extends JButton
347        {
348          /**
349           * Creates a new PaneButton object with the given Action.
350           *
351           * @param a The Action that the button uses.
352           */
353          public PaneButton(Action a)
354          {
355            super(a);
356            setMargin(new Insets(0, 0, 0, 0));
357          }
358    
359          /**
360           * This method returns true if the Component can be focused.
361           *
362           * @return false.
363           */
364          public boolean isFocusable()
365          {
366            // These buttons cannot be given focus.
367            return false;
368          }
369    
370        }
371    
372        /**
373         * The layout for the JRootPane when the <code>windowDecorationStyle</code>
374         * property is set. In addition to the usual JRootPane.RootLayout behaviour
375         * this lays out the titlePane.
376         *
377         * @author Roman Kennke (kennke@aicas.com)
378         */
379        private class MetalTitlePaneLayout implements LayoutManager
380        {
381          /**
382           * Creates a new <code>TitlePaneLayout</code> object.
383           */
384          public MetalTitlePaneLayout()
385          {
386            // Do nothing.
387          }
388    
389          /**
390           * Adds a Component to the Container.
391           *
392           * @param name The name to reference the added Component by.
393           * @param c The Component to add.
394           */
395          public void addLayoutComponent(String name, Component c)
396          {
397            // Do nothing.
398          }
399    
400          /**
401           * This method is called to lay out the children of the Title Pane.
402           *
403           * @param c The Container to lay out.
404           */
405          public void layoutContainer(Container c)
406          {
407    
408            Dimension size = c.getSize();
409            Insets insets = c.getInsets();
410            int width = size.width - insets.left - insets.right;
411            int height = size.height - insets.top - insets.bottom;
412    
413            int loc = width - insets.right - 1;
414            int top = insets.top + 2;
415            int buttonHeight = height - 4;
416            if (closeButton.isVisible())
417              {
418                int buttonWidth = closeIcon.getIconWidth();
419                loc -= buttonWidth + 2;
420                closeButton.setBounds(loc, top, buttonWidth, buttonHeight);
421                loc -= 6;
422              }
423    
424            if (maxButton.isVisible())
425              {
426                int buttonWidth = maxIcon.getIconWidth();
427                loc -= buttonWidth + 4;
428                maxButton.setBounds(loc, top, buttonWidth, buttonHeight);
429              }
430    
431            if (iconButton.isVisible())
432              {
433                int buttonWidth = minIcon.getIconWidth();
434                loc -= buttonWidth + 4;
435                iconButton.setBounds(loc, top, buttonWidth, buttonHeight);
436                loc -= 2;
437              }
438    
439            Dimension titlePreferredSize = title.getPreferredSize();
440            title.setBounds(insets.left + 5, insets.top, 
441                    Math.min(titlePreferredSize.width, loc - insets.left - 10), 
442                    height);
443    
444          }
445    
446          /**
447           * This method returns the minimum size of the given Container given the
448           * children that it has.
449           *
450           * @param c The Container to get a minimum size for.
451           *
452           * @return The minimum size of the Container.
453           */
454          public Dimension minimumLayoutSize(Container c)
455          {
456            return preferredLayoutSize(c);
457          }
458    
459          /**
460           * Returns the preferred size of the given Container taking
461           * into account the children that it has.
462           *
463           * @param c The Container to lay out.
464           *
465           * @return The preferred size of the Container.
466           */
467          public Dimension preferredLayoutSize(Container c)
468          {
469            return new Dimension(22, 22);
470          }
471    
472          /**
473           * Removes a Component from the Container.
474           *
475           * @param c The Component to remove.
476           */
477          public void removeLayoutComponent(Component c)
478          {
479            // Nothing to do here.
480          }
481        }
482    
483        JRootPane rootPane;
484    
485        /** The button that closes the JInternalFrame. */
486        JButton closeButton;
487    
488        /** The button that iconifies the JInternalFrame. */
489        JButton iconButton;
490    
491        /** The button that maximizes the JInternalFrame. */
492        JButton maxButton;
493    
494        Icon minIcon;
495    
496        /** The icon displayed in the maximize button. */
497        Icon maxIcon;
498    
499        /** The icon displayed in the iconify button. */
500        private Icon iconIcon;
501    
502        /** The icon displayed in the close button. */
503        Icon closeIcon;
504        
505        /**
506         * The background color of the TitlePane when the JInternalFrame is not
507         * selected.
508         */
509        private Color notSelectedTitleColor;
510    
511        /**
512         * The background color of the TitlePane when the JInternalFrame is
513         * selected.
514         */
515        private Color selectedTitleColor;
516    
517        /**
518         * The label used to display the title. This label is not added to the
519         * TitlePane.
520         */
521        JLabel title;
522    
523        /** The action associated with closing the JInternalFrame. */
524        private Action closeAction;
525    
526        /** The action associated with iconifying the JInternalFrame. */
527        private Action iconifyAction;
528    
529        /** The action associated with maximizing the JInternalFrame. */
530        private Action maximizeAction;
531    
532        /** The JMenuBar that is located at the top left of the Title Pane. */
533        private JMenuBar menuBar;
534    
535        /** The JMenu inside the menuBar. */
536        protected JMenu windowMenu;
537    
538        MetalTitlePane(JRootPane rp)
539        {
540          rootPane = rp;
541          setLayout(createLayout());
542          title = new JLabel();
543          title.setHorizontalAlignment(SwingConstants.LEFT);
544          title.setHorizontalTextPosition(SwingConstants.LEFT);
545          title.setOpaque(false);
546          installTitlePane();
547        }
548    
549        protected LayoutManager createLayout()
550        {
551          return new MetalTitlePaneLayout();
552        }
553    
554        /**
555         * This method installs the TitlePane onto the JInternalFrameTitlePane. It
556         * also creates any children components that need to be created and adds
557         * listeners to the appropriate components.
558         */
559        protected void installTitlePane()
560        {
561          installDefaults();
562          installListeners();
563          createActions();
564          assembleSystemMenu();
565          createButtons();
566          setButtonIcons();
567          addSubComponents();
568          enableActions();
569        }
570    
571        private void enableActions()
572        {
573          // TODO: Implement this.
574        }
575    
576        private void addSubComponents()
577        {
578          add(menuBar);
579          add(closeButton);
580          add(iconButton);
581          add(maxButton);
582        }
583    
584        private void installListeners()
585        {
586          MouseInputAdapter mouseHandler = new MouseHandler();
587          addMouseListener(mouseHandler);
588          addMouseMotionListener(mouseHandler);
589        }
590    
591        private void createActions()
592        {
593          closeAction = new CloseAction();
594          iconifyAction = new IconifyAction();
595          maximizeAction = new MaximizeAction();
596        }
597    
598        private void assembleSystemMenu()
599        {
600          menuBar = createSystemMenuBar();
601          windowMenu = createSystemMenu();
602          menuBar.add(windowMenu);
603          addSystemMenuItems(windowMenu);
604          enableActions();
605        }
606    
607        protected JMenuBar createSystemMenuBar()
608        {
609          if (menuBar == null)
610            menuBar = new JMenuBar();
611          menuBar.removeAll();
612          return menuBar;
613        }
614    
615        protected JMenu createSystemMenu()
616        {
617          if (windowMenu == null)
618            windowMenu = new JMenu();
619          windowMenu.removeAll();
620          return windowMenu;
621        }
622    
623        private void addSystemMenuItems(JMenu menu)
624        {
625          // TODO: Implement this.
626        }
627    
628        protected void createButtons()
629        {
630          closeButton = new PaneButton(closeAction);
631          closeButton.setText(null);
632          iconButton = new PaneButton(iconifyAction);
633          iconButton.setText(null);
634          maxButton = new PaneButton(maximizeAction);
635          maxButton.setText(null);
636          closeButton.setBorderPainted(false);
637          closeButton.setContentAreaFilled(false);
638          iconButton.setBorderPainted(false);
639          iconButton.setContentAreaFilled(false);
640          maxButton.setBorderPainted(false);
641          maxButton.setContentAreaFilled(false);
642        }
643    
644        protected void setButtonIcons()
645        {
646          if (closeIcon != null && closeButton != null)
647            closeButton.setIcon(closeIcon);
648          if (iconIcon != null && iconButton != null)
649            iconButton.setIcon(iconIcon);
650          if (maxIcon != null && maxButton != null)
651            maxButton.setIcon(maxIcon);
652        }
653    
654        /**
655         * Paints a representation of the current state of the internal frame.
656         * 
657         * @param g  the graphics device.
658         */
659        public void paintComponent(Graphics g)
660        {
661          Window frame = SwingUtilities.getWindowAncestor(rootPane);
662          Color savedColor = g.getColor();
663          paintTitleBackground(g);
664          paintChildren(g);
665          Dimension d = getSize();
666          if (frame.isActive())
667            g.setColor(MetalLookAndFeel.getPrimaryControlDarkShadow());
668          else
669            g.setColor(MetalLookAndFeel.getControlDarkShadow());
670              
671          // put a dot in each of the top corners
672          g.drawLine(0, 0, 0, 0);
673          g.drawLine(d.width - 1, 0, d.width - 1, 0);
674              
675          g.drawLine(0, d.height - 1, d.width - 1, d.height - 1);
676              
677          // draw the metal pattern
678          if (UIManager.get("InternalFrame.activeTitleGradient") != null
679              && frame.isActive())
680            {
681              MetalUtils.paintGradient(g, 0, 0, getWidth(), getHeight(),
682                                       SwingConstants.VERTICAL,
683              "InternalFrame.activeTitleGradient");
684            }
685    
686          Rectangle b = title.getBounds();
687          int startX = b.x + b.width + 5;
688          int endX = startX;
689          if (iconButton.isVisible())
690            endX = Math.max(iconButton.getX(), endX);
691          else if (maxButton.isVisible()) 
692            endX = Math.max(maxButton.getX(), endX);
693          else if (closeButton.isVisible())
694            endX = Math.max(closeButton.getX(), endX);
695          endX -= 7;
696          if (endX > startX)
697            MetalUtils.fillMetalPattern(this, g, startX, 3, endX - startX, getHeight() - 6, Color.white, Color.gray);
698          g.setColor(savedColor);
699        }
700    
701        /**
702         * This method paints the TitlePane's background.
703         *
704         * @param g The Graphics object to paint with.
705         */
706        protected void paintTitleBackground(Graphics g)
707        {
708          Window frame = SwingUtilities.getWindowAncestor(rootPane);
709    
710          if (!isOpaque())
711            return;
712          
713          Color saved = g.getColor();
714          Dimension dims = getSize();
715          
716          Color bg = getBackground();
717          if (frame.isActive())
718            bg = selectedTitleColor;
719          else
720            bg = notSelectedTitleColor;
721          g.setColor(bg);
722          g.fillRect(0, 0, dims.width, dims.height);
723          g.setColor(saved);
724        }
725    
726        /**
727         * This method installs the defaults determined by the look and feel.
728         */
729        private void installDefaults()
730        {
731          title.setFont(UIManager.getFont("InternalFrame.titleFont"));
732          selectedTitleColor = UIManager.getColor("InternalFrame.activeTitleBackground");
733          notSelectedTitleColor = UIManager.getColor("InternalFrame.inactiveTitleBackground");
734          closeIcon = UIManager.getIcon("InternalFrame.closeIcon");
735          iconIcon = UIManager.getIcon("InternalFrame.iconifyIcon");
736          maxIcon = UIManager.getIcon("InternalFrame.maximizeIcon");
737          minIcon = MetalIconFactory.getInternalFrameAltMaximizeIcon(16);
738          Frame frame = (Frame) SwingUtilities.getWindowAncestor(rootPane);
739          title = new JLabel(frame.getTitle(), 
740                  MetalIconFactory.getInternalFrameDefaultMenuIcon(), 
741                  SwingConstants.LEFT);
742        }
743      }
744    
745      private static class MetalRootLayout
746        implements LayoutManager2
747      {
748    
749        /**
750         * The cached layout info for the glass pane.
751         */
752        private Rectangle glassPaneBounds;
753    
754        /**
755         * The cached layout info for the layered pane.
756         */
757        private Rectangle layeredPaneBounds;
758    
759        /**
760         * The cached layout info for the content pane.
761         */
762        private Rectangle contentPaneBounds;
763    
764        /**
765         * The cached layout info for the menu bar.
766         */
767        private Rectangle menuBarBounds;
768    
769        /**
770         * The cached layout info for the title pane.
771         */
772        private Rectangle titlePaneBounds;
773        
774        /**
775         * The cached preferred size.
776         */
777        private Dimension prefSize;
778    
779        /**
780         * The title pane for l&f decorated frames.
781         */
782        private MetalTitlePane titlePane;
783    
784        /**
785         * Creates a new MetalRootLayout.
786         *
787         * @param tp the title pane
788         */
789        MetalRootLayout(MetalTitlePane tp)
790        {
791          titlePane = tp;
792        }
793    
794        public void addLayoutComponent(Component component, Object constraints)
795        {
796          // Nothing to do here.
797        }
798    
799        public Dimension maximumLayoutSize(Container target)
800        {
801          return preferredLayoutSize(target);
802        }
803    
804        public float getLayoutAlignmentX(Container target)
805        {
806          return 0.0F;
807        }
808    
809        public float getLayoutAlignmentY(Container target)
810        {
811          return 0.0F;
812        }
813    
814        public void invalidateLayout(Container target)
815        {
816          synchronized (this)
817          {
818            glassPaneBounds = null;
819            layeredPaneBounds = null;
820            contentPaneBounds = null;
821            menuBarBounds = null;
822            titlePaneBounds = null;
823            prefSize = null;
824          }
825        }
826    
827        public void addLayoutComponent(String name, Component component)
828        {
829          // Nothing to do here.
830        }
831    
832        public void removeLayoutComponent(Component component)
833        {
834          // TODO Auto-generated method stub
835          
836        }
837    
838        public Dimension preferredLayoutSize(Container parent)
839        {
840          JRootPane rp = (JRootPane) parent;
841          JLayeredPane layeredPane = rp.getLayeredPane();
842          Component contentPane = rp.getContentPane();
843          Component menuBar = rp.getJMenuBar();
844    
845          // We must synchronize here, otherwise we cannot guarantee that the
846          // prefSize is still non-null when returning.
847          synchronized (this)
848            {
849              if (prefSize == null)
850                {
851                  Insets i = parent.getInsets();
852                  prefSize = new Dimension(i.left + i.right, i.top + i.bottom);
853                  Dimension contentPrefSize = contentPane.getPreferredSize();
854                  prefSize.width += contentPrefSize.width;
855                  prefSize.height += contentPrefSize.height
856                                     + titlePane.getPreferredSize().height;
857                  if (menuBar != null)
858                    {
859                      Dimension menuBarSize = menuBar.getPreferredSize();
860                      if (menuBarSize.width > contentPrefSize.width)
861                        prefSize.width += menuBarSize.width - contentPrefSize.width;
862                      prefSize.height += menuBarSize.height;
863                    }
864                }
865              // Return a copy here so the cached value won't get trashed by some
866              // other component.
867              return new Dimension(prefSize);
868          }
869        }
870    
871        public Dimension minimumLayoutSize(Container parent)
872        {
873          return preferredLayoutSize(parent);
874        }
875    
876        public void layoutContainer(Container parent)
877        {
878          JRootPane rp = (JRootPane) parent;
879          JLayeredPane layeredPane = rp.getLayeredPane();
880          Component contentPane = rp.getContentPane();
881          Component menuBar = rp.getJMenuBar();
882          Component glassPane = rp.getGlassPane();
883    
884          if (glassPaneBounds == null || layeredPaneBounds == null
885              || contentPaneBounds == null || menuBarBounds == null)
886            {
887              Insets i = rp.getInsets();
888              int containerWidth = parent.getBounds().width - i.left - i.right;
889              int containerHeight = parent.getBounds().height - i.top - i.bottom;
890    
891              // 1. The glassPane fills entire viewable region (bounds - insets).
892              // 2. The layeredPane filles entire viewable region.
893              // 3. The titlePane is placed at the upper edge of the layeredPane.
894              // 4. The menuBar is positioned at the upper edge of layeredPane.
895              // 5. The contentPane fills viewable region minus menuBar minus
896              //    titlePane, if present.
897          
898              // +-------------------------------+
899              // |  JLayeredPane                 |
900              // |  +--------------------------+ |
901              // |  | titlePane                + |
902              // |  +--------------------------+ |
903              // |  +--------------------------+ |
904              // |  | menuBar                  | |
905              // |  +--------------------------+ |
906              // |  +--------------------------+ |
907              // |  |contentPane               | |
908              // |  |                          | |
909              // |  |                          | |
910              // |  |                          | |
911              // |  +--------------------------+ |
912              // +-------------------------------+
913    
914              // Setup titlePaneBounds.
915              if (titlePaneBounds == null)
916                titlePaneBounds = new Rectangle();
917              titlePaneBounds.width = containerWidth;
918              titlePaneBounds.height = titlePane.getPreferredSize().height;
919    
920              // Setup menuBarBounds.
921              if (menuBarBounds == null)
922                menuBarBounds = new Rectangle();
923              menuBarBounds.setBounds(0,
924                                      titlePaneBounds.y + titlePaneBounds.height,
925                                      containerWidth, 0);
926              if (menuBar != null)
927                {
928                  Dimension menuBarSize = menuBar.getPreferredSize();
929                  if (menuBarSize.height > containerHeight)
930                    menuBarBounds.height = containerHeight;
931                  else
932                    menuBarBounds.height = menuBarSize.height;
933                }
934    
935              // Setup contentPaneBounds.
936              if (contentPaneBounds == null)
937                contentPaneBounds = new Rectangle();
938              contentPaneBounds.setBounds(0,
939                                          menuBarBounds.y + menuBarBounds.height,
940                                          containerWidth,
941                                          containerHeight - menuBarBounds.y
942                                          - menuBarBounds.height);
943              glassPaneBounds = new Rectangle(i.left, i.top, containerWidth, containerHeight);
944              layeredPaneBounds = new Rectangle(i.left, i.top, containerWidth, containerHeight);
945            }
946    
947          // Layout components.
948          glassPane.setBounds(glassPaneBounds);
949          layeredPane.setBounds(layeredPaneBounds);
950          if (menuBar != null)
951            menuBar.setBounds(menuBarBounds);
952          contentPane.setBounds(contentPaneBounds);
953          titlePane.setBounds(titlePaneBounds);
954        }
955          
956      }
957    
958      /**
959       * The shared UI instance for MetalRootPaneUIs.
960       */
961      private static MetalRootPaneUI instance;
962    
963      /**
964       * Constructs a shared instance of <code>MetalRootPaneUI</code>.
965       */
966      public MetalRootPaneUI()
967      {
968        super();
969      }
970    
971      /**
972       * Returns a shared instance of <code>MetalRootPaneUI</code>.
973       *
974       * @param component the component for which we return an UI instance
975       *
976       * @return A shared instance of <code>MetalRootPaneUI</code>.
977       */
978      public static ComponentUI createUI(JComponent component)
979      {
980        if (instance == null)
981          instance = new MetalRootPaneUI();
982        return instance;
983      }
984    
985      /**
986       * Installs this UI to the root pane. If the
987       * <code>windowDecorationsStyle</code> property is set on the root pane,
988       * the Metal window decorations are installed on the root pane.
989       *
990       * @param c
991       */
992      public void installUI(JComponent c)
993      {
994        super.installUI(c);
995        JRootPane rp = (JRootPane) c;
996        if (rp.getWindowDecorationStyle() != JRootPane.NONE)
997          installWindowDecorations(rp);
998      }
999    
1000      /**
1001       * Uninstalls the UI from the root pane. This performs the superclass
1002       * behaviour and uninstalls the window decorations that have possibly been
1003       * installed by {@link #installUI}.
1004       *
1005       * @param c the root pane
1006       */
1007      public void uninstallUI(JComponent c)
1008      {
1009        JRootPane rp = (JRootPane) c;
1010        if (rp.getWindowDecorationStyle() != JRootPane.NONE)
1011          uninstallWindowDecorations(rp);
1012        super.uninstallUI(c);
1013      }
1014    
1015      /**
1016       * Receives notification if any of the JRootPane's property changes. In
1017       * particular this catches changes to the <code>windowDecorationStyle</code>
1018       * property and installs the window decorations accordingly.
1019       *
1020       * @param ev the property change event
1021       */
1022      public void propertyChange(PropertyChangeEvent ev)
1023      {
1024        super.propertyChange(ev);
1025        String propertyName = ev.getPropertyName();
1026        if (propertyName.equals("windowDecorationStyle"))
1027          {
1028            JRootPane rp = (JRootPane) ev.getSource();
1029            if (rp.getWindowDecorationStyle() != JRootPane.NONE)
1030              installWindowDecorations(rp);
1031            else
1032              uninstallWindowDecorations(rp);
1033          }
1034      }
1035    
1036      /**
1037       * Installs the window decorations to the root pane. This sets up a border,
1038       * a title pane and a layout manager that can layout the root pane with that
1039       * title pane.
1040       *
1041       * @param rp the root pane.
1042       */
1043      private void installWindowDecorations(JRootPane rp)
1044      {
1045        rp.setBorder(new MetalFrameBorder());
1046        MetalTitlePane titlePane = new MetalTitlePane(rp);
1047        rp.setLayout(new MetalRootLayout(titlePane));
1048        // We should have a contentPane already.
1049        assert rp.getLayeredPane().getComponentCount() > 0
1050               : "We should have a contentPane already";
1051    
1052        rp.getLayeredPane().add(titlePane,
1053                                JLayeredPane.FRAME_CONTENT_LAYER, 1);
1054      }
1055    
1056      /**
1057       * Uninstalls the window decorations from the root pane. This should rarely
1058       * be necessary, but we do it anyway.
1059       *
1060       * @param rp the root pane
1061       */
1062      private void uninstallWindowDecorations(JRootPane rp)
1063      {
1064        rp.setBorder(null);
1065        JLayeredPane lp = rp.getLayeredPane();
1066        for (int i = lp.getComponentCount() - 1; i >= 0; --i)
1067          {
1068            if (lp.getComponent(i) instanceof MetalTitlePane)
1069              {
1070                lp.remove(i);
1071                break;
1072              }
1073          }
1074      }
1075    }