1 /*
2  * @(#)Quaqua14RootPaneUI.java  2.0.2  2009-03-16
3  *
4  * Copyright (c) 2005-2009 Werner Randelshofer
5  * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
6  * All rights reserved.
7  *
8  * The copyright of this software is owned by Werner Randelshofer.
9  * You may not use, copy or modify this software, except in
10  * accordance with the license agreement you entered into with
11  * Werner Randelshofer. For details see accompanying license terms.
12  */
13 package ch.randelshofer.quaqua;
14 
15 import ch.randelshofer.quaqua.color.PaintableColor;
16 import java.awt.*;
17 import java.awt.event.*;
18 import java.awt.peer.*;
19 import java.beans.*;
20 import java.lang.reflect.*;
21 import java.security.*;
22 import java.util.ConcurrentModificationException;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.WeakHashMap;
26 import javax.swing.*;
27 import javax.swing.event.*;
28 import javax.swing.plaf.*;
29 import javax.swing.plaf.basic.*;
30 
31 /**
32  * Quaqua14RootPaneUI.
33  *
34  * @author  Werner Randelshofer
35  * @version 2.0.9 2009-03-16 Handle ConcurrentModificationException in
36  * allRootPanes WeakHashMap.
37  * <br>2.0.1 2008-07-07 Don't process mouse dragged events when
38  * the root pane is not showing on screen.
39  * <br>2.0 2008-05-10 Added support for client property "Window.documentModified".
40  * <br>1.1.4 2008-03-30 Fixed memory leak in allRootPanes has map, by putting
41  * a null value into the weak hash map, instead of the RootPane object.
42  * <br>1.1.3 2007-09-29 Fixed NPE in Window snapping behavior code.
43  * <br>1.1.2 2007-08-02 Only snap to the edges of a window, if the
44  * is not far away. Don't snap, if the user
45  * holds down the alt key.
46  * <br>1.1 2007-07-26 Look and Feel decorated windows snap to other
47  * windows. Windows on secondary screens couldn't always be dragged back
48  * to primary screen.
49  * <br>1.0.6 2007-07-05 Resize icon wasn't painted for dialog windows.
50  * <br>1.0.5 2007-04-29 Repaint the root paint 100 milliseconds after it
51  * has been resized. This is a workaround for the repaint happening twice after
52  * a window has been resized.
53  * <br>1.0.4 2005-08-03 Removed error output on System.err, when no
54  * native support for windows modified property is available.
55  * <br>1.0.3 2005-06-29 Fixed NPE in method propertyChanged. Method propertyChange must call super in
56  * order to make default button work.
57  * <br>1.0.2 2005-06-19 Ancestor window was not properly determined
58  * when running under Java 1.5.
59  * <br>1.0.1 2005-04-07 Fixed NPE in method ancestorRemoved.
60  * <br>1.0  06 February 2005  Created.
61  */
62 public class Quaqua14RootPaneUI extends BasicRootPaneUI {
63 
64     /**
65      * Keys to lookup borders in defaults table.
66      */
67     private static final String[] borderKeys = new String[]{
68         null, "RootPane.frameBorder", "RootPane.plainDialogBorder",
69         "RootPane.informationDialogBorder",
70         "RootPane.errorDialogBorder", "RootPane.colorChooserDialogBorder",
71         "RootPane.fileChooserDialogBorder", "RootPane.questionDialogBorder",
72         "RootPane.warningDialogBorder"
73     };
74     /**
75      * Height and width of resize handle on the lower right corner of the window.
76      * FIXME - This value depends on font size.
77      */
78     private static final int BORDER_DRAG_THICKNESS = 15;
79     /**
80      * Window the <code>JRootPane</code> is in.
81      */
82     private Window window;
83     /**
84      * <code>JComponent</code> providing window decorations. This will be
85      * null if not providing window decorations.
86      */
87     private JComponent titlePane;
88     /**
89      * <code>MouseInputListener</code> that is added to the parent
90      * <code>Window</code> the <code>JRootPane</code> is contained in.
91      */
92     private MouseInputListener mouseInputListener;
93     /**
94      * The <code>LayoutManager</code> that is set on the
95      * <code>JRootPane</code>.
96      */
97     private LayoutManager layoutManager;
98     /**
99      * <code>LayoutManager</code> of the <code>JRootPane</code> before we
100      * replaced it.
101      */
102     private LayoutManager savedOldLayout;
103     private AncestorListener ancestorListener;
104     private ComponentListener componentListener;
105     /**
106      * This variable is set to false, if we fail to invoke the
107      * setWindowModifiedMethod.
108      */
109     private static boolean isWindowModifiedSupported = true;
110     /**
111      * This method is used to access the non-API peer methods of Apple's
112      * Window peers. The method is different for the different MRJ versions.
113      */
114     private static Method setWindowModifiedMethod = null;
115     /**
116      * <code>JRootPane</code> providing the look and feel for.
117      */
118     private JRootPane root;
119     /**
120      * Since method Window.getWindows() is only available since Java 1.6,
121      * we indirectly keep track of all windows by ourselves by storing
122      * all JRootPanes in this weak hash map.
123      * We add a JRootPane to this map, upon installUI, and remove a JRootPane
124      * from this upoin deinstallUI.
125      */
126     private static WeakHashMap allRootPanes = new WeakHashMap();
127     /**
128      * <code>Cursor</code> used to track the cursor set by the user.
129      * This is initially <code>Cursor.DEFAULT_CURSOR</code>.
130      */
131     private Cursor lastCursor =
132             Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
133 
createUI(JComponent c)134     public static ComponentUI createUI(JComponent c) {
135         return new Quaqua14RootPaneUI();
136     }
137 
138     /** Creates a new instance. */
Quaqua14RootPaneUI()139     public Quaqua14RootPaneUI() {
140     }
141 
142     /**
143      * Invokes supers implementation of <code>installUI</code> to install
144      * the necessary state onto the passed in <code>JRootPane</code>
145      * to render the metal look and feel implementation of
146      * <code>RootPaneUI</code>. If
147      * the <code>windowDecorationStyle</code> property of the
148      * <code>JRootPane</code> is other than <code>JRootPane.NONE</code>,
149      * this will add a custom <code>Component</code> to render the widgets to
150      * <code>JRootPane</code>, as well as installing a custom
151      * <code>Border</code> and <code>LayoutManager</code> on the
152      * <code>JRootPane</code>.
153      *
154      * @param c the JRootPane to install state onto
155      */
installUI(JComponent c)156     public void installUI(JComponent c) {
157         super.installUI(c);
158         root = (JRootPane) c;
159         c.putClientProperty(
160                 "apple.awt.draggableWindowBackground",
161                 UIManager.get("RootPane.draggableWindowBackground"));
162         c.putClientProperty(
163                 "apple.awt.windowShadow",
164                 UIManager.get("RootPane.windowShadow"));
165         Window window = SwingUtilities.getWindowAncestor(c);
166 
167         int style = root.getWindowDecorationStyle();
168         if (style != JRootPane.NONE) {
169             installClientDecorations(root);
170         }
171         allRootPanes.put(c, null);
172     }
173 
paint(Graphics g, JComponent c)174     public void paint(Graphics g, JComponent c) {
175         int style = getRootPane().getWindowDecorationStyle();
176         if (style != JRootPane.NONE) {
177             boolean needsResizeIcon = false;
178             if (window instanceof Frame) {
179                 Frame frame = (Frame) window;
180                 needsResizeIcon = frame.isResizable();
181             } else if (window instanceof Dialog) {
182                 Dialog dialog = (Dialog) window;
183                 needsResizeIcon = dialog.isResizable();
184             }
185 
186             if (needsResizeIcon) {
187                 Icon resizeIcon = UIManager.getIcon("InternalFrame.resizeIcon");
188                 int w = c.getWidth();
189                 int h = c.getHeight();
190                 Insets insets = c.getInsets();
191                 resizeIcon.paintIcon(c, g,
192                         w - resizeIcon.getIconWidth() - insets.right,
193                         h - resizeIcon.getIconHeight() - insets.bottom);
194             }
195         }
196     }
197 
198     /**
199      * Invokes supers implementation to uninstall any of its state. This will
200      * also reset the <code>LayoutManager</code> of the <code>JRootPane</code>.
201      * If a <code>Component</code> has been added to the <code>JRootPane</code>
202      * to render the window decoration style, this method will remove it.
203      * Similarly, this will revert the Border and LayoutManager of the
204      * <code>JRootPane</code> to what it was before <code>installUI</code>
205      * was invoked.
206      *
207      * @param c the JRootPane to uninstall state from
208      */
uninstallUI(JComponent c)209     public void uninstallUI(JComponent c) {
210         super.uninstallUI(c);
211         uninstallClientDecorations(root);
212 
213         layoutManager = null;
214         mouseInputListener = null;
215         root = null;
216         allRootPanes.remove(c);
217     }
218 
installDefaults(JRootPane c)219     protected void installDefaults(JRootPane c) {
220         super.installDefaults(c);
221         LookAndFeel.installColorsAndFont(c,
222                 "RootPane.background",
223                 "RootPane.foreground",
224                 "RootPane.font");
225         LookAndFeel.installBorder(c, "RootPane.border");
226         // Root Pane must always be opaque
227         //LookAndFeel.installProperty(c, "opaque", UIManager.get("RootPane.opaque"));
228 
229         // By default, we should delay window ordering, but
230         // it does not seem to work as expected. It appears that we need to
231         // at more code.
232         // c.putClientProperty("apple.awt.delayWindowOrdering", Boolean.TRUE);
233 
234 
235         c.setOpaque(true);
236     }
237 
update(Graphics gr, final JComponent c)238     public void update(Graphics gr, final JComponent c) {
239         if (c.isOpaque()) {
240             Graphics2D g = (Graphics2D) gr;
241             g.setPaint(PaintableColor.getPaint(c.getBackground(), c));
242             g.fillRect(0, 0, c.getWidth(), c.getHeight());
243         }
244         /*
245         root.putClientProperty(
246         "apple.awt.windowShadow.revalidateNow", Boolean.TRUE
247         );
248          */
249         paint(gr, c);
250     }
251 
installListeners(JRootPane root)252     protected void installListeners(JRootPane root) {
253         super.installListeners(root);
254 
255         ancestorListener = createAncestorListener();
256         if (ancestorListener != null) {
257             root.addAncestorListener(ancestorListener);
258         }
259         componentListener = createComponentListener();
260         if (componentListener != null) {
261             root.addComponentListener(componentListener);
262         }
263     }
264 
uninstallListeners(JRootPane root)265     protected void uninstallListeners(JRootPane root) {
266         super.uninstallListeners(root);
267 
268         if (ancestorListener != null) {
269             root.removeAncestorListener(ancestorListener);
270         }
271         if (componentListener != null) {
272             root.removeComponentListener(componentListener);
273         }
274     }
275 
createComponentListener()276     protected ComponentListener createComponentListener() {
277         return new ComponentAdapter() {
278 
279             public void componentResized(final ComponentEvent e) {
280                 Timer t = new Timer(200, new ActionListener() {
281 
282                     public void actionPerformed(ActionEvent evt) {
283                         e.getComponent().repaint();
284                     }
285                 });
286                 t.setRepeats(false);
287                 t.start();
288             }
289         };
290     }
291 
292     protected AncestorListener createAncestorListener() {
293         return new RootPaneAncestorListener();
294     }
295 
296     /**
297      * Returns the <code>JComponent</code> to render the window decoration
298      * style.
299      */
300     private JComponent createTitlePane(JRootPane root) {
301         return new Quaqua14TitlePane(root, this);
302     }
303 
304     /**
305      * Returns a <code>MouseListener</code> that will be added to the
306      * <code>Window</code> containing the <code>JRootPane</code>.
307      */
308     private MouseInputListener createWindowMouseInputListener(JRootPane root) {
309         return new MouseInputHandler();
310     }
311 
312     /**
313      * Returns a <code>LayoutManager</code> that will be set on the
314      * <code>JRootPane</code>.
315      */
316     private LayoutManager createLayoutManager() {
317         return new QuaquaRootLayout();
318     }
319 
320     private static void updateWindowModified(JRootPane rootpane) {
321         if (isWindowModifiedSupported) {
322             Container parent = rootpane.getParent();
323             if (parent != null && (parent instanceof Window)) {
324                 ComponentPeer peer = parent.getPeer();
325                 if (peer != null) {
326                     if (setWindowModifiedMethod == null) {
327                         try {
328                             setWindowModifiedMethod = peer.getClass().getMethod("setDocumentEdited", new Class[]{Boolean.TYPE});
329                         } catch (NoSuchMethodException ex1) {
330                             try {
331                                 setWindowModifiedMethod = peer.getClass().getMethod("setModified", new Class[]{Boolean.TYPE});
332                             } catch (NoSuchMethodException ex2) {
333                                 isWindowModifiedSupported = false;
334                             //ex2.printStackTrace();
335                             }
336                         } catch (AccessControlException ex1) {
337                             isWindowModifiedSupported = false;
338                         //System.err.println("Sorry. Quaqua14RootPaneUI can not access the native window modified API");
339                         }
340                     }
341                     if (setWindowModifiedMethod != null) {
342                         try {
343                             Object value = rootpane.getClientProperty("Window.documentModified");
344                             if (value == null) {
345                                 value = rootpane.getClientProperty("windowModified");
346                             }
347                             if (value == null) {
348                                 value = Boolean.FALSE;
349                             }
350                             setWindowModifiedMethod.invoke(peer, new Object[]{value});
351                         } catch (IllegalAccessException ex) {
352                             isWindowModifiedSupported = false;
353                         //ex.printStackTrace();
354                         } catch (InvocationTargetException ex) {
355                             isWindowModifiedSupported = false;
356                         //ex.printStackTrace();
357                         }
358                     }
359                 }
360             }
361         }
362     }
363 
364     /**
365      * Installs the appropriate <code>Border</code> onto the
366      * <code>JRootPane</code>.
367      */
368     void installBorder(JRootPane root) {
369         int style = root.getWindowDecorationStyle();
370 
371         if (style == JRootPane.NONE) {
372             LookAndFeel.uninstallBorder(root);
373         } else {
374             LookAndFeel.installBorder(root, borderKeys[style]);
375         }
376     }
377 
378     /**
379      * Removes any border that may have been installed.
380      */
381     private void uninstallBorder(JRootPane root) {
382         LookAndFeel.uninstallBorder(root);
383     }
384 
385     /**
386      * Installs the necessary state onto the JRootPane to render client
387      * decorations. This is ONLY invoked if the <code>JRootPane</code>
388      * has a decoration style other than <code>JRootPane.NONE</code>.
389      */
390     private void installClientDecorations(JRootPane root) {
391         installBorder(root);
392 
393         /*
394         window = SwingUtilities.getWindowAncestor(root);
395         if (window != null) {
396         window.setBackground(new Color(0, true));
397         }*/
398 
399         root.putClientProperty(
400                 "apple.awt.draggableWindowBackground", Boolean.FALSE);
401         root.putClientProperty(
402                 "apple.awt.windowShadow", Boolean.TRUE);
403 
404         JComponent titlePane = createTitlePane(root);
405 
406         setTitlePane(root, titlePane);
407         installWindowListeners(root, root.getParent());
408         installLayout(root);
409         if (window != null) {
410             root.revalidate();
411             root.repaint();
412         }
413 
414         root.putClientProperty(
415                 "apple.awt.windowShadow.revalidateNow", new Object());
416     }
417 
418     /**
419      * Installs the necessary Listeners on the parent <code>Window</code>,
420      * if there is one.
421      * <p>
422      * This takes the parent so that cleanup can be done from
423      * <code>removeNotify</code>, at which point the parent hasn't been
424      * reset yet.
425      *
426      * @param parent The parent of the JRootPane
427      */
428     private void installWindowListeners(JRootPane root, Component parent) {
429         if (parent instanceof Window) {
430             window = (Window) parent;
431         } else {
432             window = SwingUtilities.getWindowAncestor(parent);
433         }
434         if (window != null) {
435             if (mouseInputListener == null) {
436                 mouseInputListener = createWindowMouseInputListener(root);
437             }
438             window.addMouseListener(mouseInputListener);
439             window.addMouseMotionListener(mouseInputListener);
440         }
441     }
442 
443     /**
444      * Uninstalls the necessary Listeners on the <code>Window</code> the
445      * Listeners were last installed on.
446      */
447     private void uninstallWindowListeners(JRootPane root) {
448         if (window != null) {
449             window.removeMouseListener(mouseInputListener);
450             window.removeMouseMotionListener(mouseInputListener);
451         }
452     }
453 
454     /**
455      * Installs the appropriate LayoutManager on the <code>JRootPane</code>
456      * to render the window decorations.
457      */
458     private void installLayout(JRootPane root) {
459         if (layoutManager == null) {
460             layoutManager = createLayoutManager();
461         }
462         savedOldLayout = root.getLayout();
463         root.setLayout(layoutManager);
464     }
465 
466     /**
467      * Uninstalls the previously installed <code>LayoutManager</code>.
468      */
469     private void uninstallLayout(JRootPane root) {
470         if (savedOldLayout != null) {
471             root.setLayout(savedOldLayout);
472             savedOldLayout = null;
473         }
474     }
475 
476     private boolean isVertical(JRootPane root) {
477         return root.getClientProperty("Quaqua.RootPane.isVertical") == Boolean.TRUE;
478     }
479 
480     /**
481      * Sets the window title pane -- the JComponent used to provide a plaf a
482      * way to override the native operating system's window title pane with
483      * one whose look and feel are controlled by the plaf.  The plaf creates
484      * and sets this value; the default is null, implying a native operating
485      * system window title pane.
486      *
487      * @param content the <code>JComponent</code> to use for the window title pane.
488      */
489     private void setTitlePane(JRootPane root, JComponent titlePane) {
490         JLayeredPane layeredPane = root.getLayeredPane();
491         JComponent oldTitlePane = getTitlePane();
492 
493         if (oldTitlePane != null) {
494             oldTitlePane.setVisible(false);
495             layeredPane.remove(oldTitlePane);
496         }
497         if (titlePane != null) {
498             layeredPane.add(titlePane, JLayeredPane.FRAME_CONTENT_LAYER);
499             titlePane.setVisible(true);
500         }
501         this.titlePane = titlePane;
502     }
503 
504     /**
505      * Returns the <code>JComponent</code> rendering the title pane. If this
506      * returns null, it implies there is no need to render window decorations.
507      *
508      * @return the current window title pane, or null
509      * @see #setTitlePane
510      */
511     private JComponent getTitlePane() {
512         return titlePane;
513     }
514 
515     /**
516      * Returns the <code>JRootPane</code> we're providing the look and
517      * feel for.
518      */
519     private JRootPane getRootPane() {
520         return root;
521     }
522 
523     /**
524      * Uninstalls any state that <code>installClientDecorations</code> has
525      * installed.
526      * <p>
527      * NOTE: This may be called if you haven't installed client decorations
528      * yet (ie before <code>installClientDecorations</code> has been invoked).
529      */
530     private void uninstallClientDecorations(JRootPane root) {
531         uninstallBorder(root);
532         uninstallWindowListeners(root);
533         setTitlePane(root, null);
534         uninstallLayout(root);
535         // We have to revalidate/repaint root if the style is JRootPane.NONE
536         // only. When we needs to call revalidate/repaint with other styles
537         // the installClientDecorations is always called after this method
538         // imediatly and it will cause the revalidate/repaint at the proper
539         // time.
540         int style = root.getWindowDecorationStyle();
541         if (style == JRootPane.NONE) {
542             root.repaint();
543             root.revalidate();
544         }
545         // Reset the cursor, as we may have changed it to a resize cursor
546         if (window != null) {
547             window.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
548         }
549         window = null;
550     }
551 
552     /**
553      * Invoked when a property changes on the root pane. If the event
554      * indicates the <code>defaultButton</code> has changed, this will
555      * reinstall the keyboard actions.
556      */
557     public void propertyChange(PropertyChangeEvent e) {
558         super.propertyChange(e);
559         String name = e.getPropertyName();
560 
561         JRootPane rootpane = (JRootPane) e.getSource();
562         if (name.equals("Window.windowModified") || name.equals("windowModified")) {
563             updateWindowModified(rootpane);
564         } else if (name.equals("windowDecorationStyle")) {
565             int style = root.getWindowDecorationStyle();
566 
567             // This is potentially more than needs to be done,
568             // but it rarely happens and makes the install/uninstall process
569             // simpler. MetalTitlePane also assumes it will be recreated if
570             // the decoration style changes.
571             uninstallClientDecorations(root);
572             if (style != JRootPane.NONE) {
573                 installClientDecorations(root);
574             }
575         } else if (name.equals("JComponent.sizeVariant")) {
576             QuaquaUtilities.applySizeVariant(rootpane);
577         }
578     }
579 
580     private static class RootPaneAncestorListener implements AncestorListener, WindowListener {
581 
582         public void ancestorAdded(AncestorEvent evt) {
583             Container ancestor = evt.getAncestor();
584             Window window = (ancestor instanceof Window)
585                     ? (Window) ancestor
586                     : SwingUtilities.getWindowAncestor(ancestor);
587             if (window != null) {
588                 window.addWindowListener(this);
589                 updateWindowModified((JRootPane) evt.getSource());
590             }
591         }
592 
593         public void ancestorMoved(AncestorEvent evt) {
594         }
595 
596         public void ancestorRemoved(AncestorEvent evt) {
597             Container ancestorParent = evt.getAncestorParent();
598             if (ancestorParent != null) {
599                 Window window = (ancestorParent instanceof Window)
600                         ? (Window) ancestorParent
601                         : SwingUtilities.getWindowAncestor(ancestorParent);
602                 //Window window = SwingUtilities.getWindowAncestor(ancestorParent);
603                 if (window != null) {
604                     window.removeWindowListener(this);
605                 }
606             }
607         }
608 
609         public void windowActivated(WindowEvent e) {
610             updateComponentTreeUIActivation(e.getComponent(), Boolean.TRUE);
611         }
612 
613         public void windowClosed(WindowEvent e) {
614         }
615 
616         public void windowClosing(WindowEvent e) {
617         }
618 
619         public void windowDeactivated(WindowEvent e) {
620             updateComponentTreeUIActivation(e.getComponent(), Boolean.FALSE);
621         }
622 
623         public void windowDeiconified(WindowEvent e) {
624         }
625 
626         public void windowIconified(WindowEvent e) {
627         }
628 
629         public void windowOpened(WindowEvent e) {
630         }
631 
632         private static void updateComponentTreeUIActivation(Component c, Boolean isActive) {
633             if (c instanceof JComponent) {
634                 ((JComponent) c).putClientProperty("Frame.active", isActive);
635             }
636             Component[] children = null;
637             if (c instanceof JMenu) {
638                 children = ((JMenu) c).getMenuComponents();
639             } else if (c instanceof Container) {
640                 children = ((Container) c).getComponents();
641             }
642             if (children != null) {
643                 for (int i = 0; i < children.length; i++) {
644                     updateComponentTreeUIActivation(children[i], isActive);
645                 }
646             }
647         }
648     }
649 
650     /**
651      * A custom layout manager that is responsible for the layout of
652      * layeredPane, glassPane, menuBar and titlePane, if one has been
653      * installed.
654      */
655 // NOTE: Ideally this would extends JRootPane.RootLayout, but that
656 //       would force this to be non-static.
657     private static class QuaquaRootLayout implements LayoutManager2 {
658 
659         private boolean isVertical(Container parent) {
660             if (parent instanceof JComponent) {
661                 return ((JComponent) parent).getClientProperty("Quaqua.RootPane.isVertical") == Boolean.TRUE;
662             }
663             return false;
664         }
665 
666         /**
667          * Returns the amount of space the layout would like to have.
668          *
669          * @param the Container for which this layout manager is being used
670          * @return a Dimension object containing the layout's preferred size
671          */
672         public Dimension preferredLayoutSize(Container parent) {
673             boolean isVertical = isVertical(parent);
674 
675             Dimension cpd, mbd, tpd;
676             int cpWidth = 0;
677             int cpHeight = 0;
678             int mbWidth = 0;
679             int mbHeight = 0;
680             int tpWidth = 0;
681             int tpHeight = 0;
682             Insets i = parent.getInsets();
683             JRootPane root = (JRootPane) parent;
684 
685             if (root.getContentPane() != null) {
686                 cpd = root.getContentPane().getPreferredSize();
687             } else {
688                 cpd = root.getSize();
689             }
690             if (cpd != null) {
691                 cpWidth = cpd.width;
692                 cpHeight = cpd.height;
693             }
694 
695             if (root.getJMenuBar() != null) {
696                 mbd = root.getJMenuBar().getPreferredSize();
697                 if (mbd != null) {
698                     mbWidth = mbd.width;
699                     mbHeight = mbd.height;
700                 }
701             }
702 
703             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
704                     (root.getUI() instanceof Quaqua14RootPaneUI)) {
705                 JComponent titlePane = ((Quaqua14RootPaneUI) root.getUI()).getTitlePane();
706                 if (titlePane != null) {
707                     tpd = titlePane.getPreferredSize();
708                     if (tpd != null) {
709                         tpWidth = tpd.width;
710                         tpHeight = tpd.height;
711                     }
712                 }
713             }
714 
715             if (isVertical) {
716                 return new Dimension(
717                         Math.max(cpWidth, mbWidth) + tpWidth + i.left + i.right,
718                         Math.max(cpHeight + mbHeight, tpHeight) + i.top + i.bottom);
719             } else {
720                 return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth) + i.left + i.right,
721                         cpHeight + mbHeight + tpWidth + i.top + i.bottom);
722             }
723         }
724 
725         /**
726          * Returns the minimum amount of space the layout needs.
727          *
728          * @param the Container for which this layout manager is being used
729          * @return a Dimension object containing the layout's minimum size
730          */
731         public Dimension minimumLayoutSize(Container parent) {
732             boolean isVertical = isVertical(parent);
733 
734             Dimension cpd, mbd, tpd;
735             int cpWidth = 0;
736             int cpHeight = 0;
737             int mbWidth = 0;
738             int mbHeight = 0;
739             int tpWidth = 0;
740             int tpHeight = 0;
741             Insets i = parent.getInsets();
742             JRootPane root = (JRootPane) parent;
743 
744             if (root.getContentPane() != null) {
745                 cpd = root.getContentPane().getMinimumSize();
746             } else {
747                 cpd = root.getSize();
748             }
749             if (cpd != null) {
750                 cpWidth = cpd.width;
751                 cpHeight = cpd.height;
752             }
753 
754             if (root.getJMenuBar() != null) {
755                 mbd = root.getJMenuBar().getMinimumSize();
756                 if (mbd != null) {
757                     mbWidth = mbd.width;
758                     mbHeight = mbd.height;
759                 }
760             }
761             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
762                     (root.getUI() instanceof Quaqua14RootPaneUI)) {
763                 JComponent titlePane = ((Quaqua14RootPaneUI) root.getUI()).getTitlePane();
764                 if (titlePane != null) {
765                     tpd = titlePane.getMinimumSize();
766                     if (tpd != null) {
767                         tpWidth = tpd.width;
768                         tpHeight = tpd.height;
769                     }
770                 }
771             }
772 
773             if (isVertical) {
774                 return new Dimension(
775                         Math.max(cpWidth, mbWidth) + tpWidth + i.left + i.right,
776                         Math.max(cpHeight + mbHeight, tpHeight) + i.top + i.bottom);
777             } else {
778                 return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth) + i.left + i.right,
779                         cpHeight + mbHeight + tpWidth + i.top + i.bottom);
780             }
781         }
782 
783         /**
784          * Returns the maximum amount of space the layout can use.
785          *
786          * @param the Container for which this layout manager is being used
787          * @return a Dimension object containing the layout's maximum size
788          */
789         public Dimension maximumLayoutSize(Container target) {
790             boolean isVertical = isVertical(target);
791 
792             Dimension cpd, mbd, tpd;
793             int cpWidth = Integer.MAX_VALUE;
794             int cpHeight = Integer.MAX_VALUE;
795             int mbWidth = Integer.MAX_VALUE;
796             int mbHeight = Integer.MAX_VALUE;
797             int tpWidth = Integer.MAX_VALUE;
798             int tpHeight = Integer.MAX_VALUE;
799             Insets i = target.getInsets();
800             JRootPane root = (JRootPane) target;
801 
802             if (root.getContentPane() != null) {
803                 cpd = root.getContentPane().getMaximumSize();
804                 if (cpd != null) {
805                     cpWidth = cpd.width;
806                     cpHeight = cpd.height;
807                 }
808             }
809 
810             if (root.getJMenuBar() != null) {
811                 mbd = root.getJMenuBar().getMaximumSize();
812                 if (mbd != null) {
813                     mbWidth = mbd.width;
814                     mbHeight = mbd.height;
815                 }
816             }
817 
818             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
819                     (root.getUI() instanceof Quaqua14RootPaneUI)) {
820                 JComponent titlePane = ((Quaqua14RootPaneUI) root.getUI()).getTitlePane();
821                 if (titlePane != null) {
822                     tpd = titlePane.getMaximumSize();
823                     if (tpd != null) {
824                         tpWidth = tpd.width;
825                         tpHeight = tpd.height;
826                     }
827                 }
828             }
829 
830             int maxHeight = Math.max(Math.max(cpHeight, mbHeight), tpHeight);
831 
832             // Only overflows if 3 real non-MAX_VALUE heights, sum to > MAX_VALUE
833             // Only will happen if sums to more than 2 billion units.  Not likely.
834             if (maxHeight != Integer.MAX_VALUE) {
835                 maxHeight = cpHeight + mbHeight + tpHeight + i.top + i.bottom;
836             }
837 
838             int maxWidth = Math.max(Math.max(cpWidth, mbWidth), tpWidth);
839             // Similar overflow comment as above
840             if (maxWidth != Integer.MAX_VALUE) {
841                 maxWidth += i.left + i.right;
842             }
843 
844             return new Dimension(maxWidth, maxHeight);
845         }
846 
847         /**
848          * Instructs the layout manager to perform the layout for the specified
849          * container.
850          *
851          * @param the Container for which this layout manager is being used
852          */
853         public void layoutContainer(Container parent) {
854             boolean isVertical = isVertical(parent);
855 
856             JRootPane root = (JRootPane) parent;
857             Rectangle b = root.getBounds();
858             Insets i = root.getInsets();
859             int nextY = 0;
860             int nextX = 0;
861             int w = b.width - i.right - i.left;
862             int h = b.height - i.top - i.bottom;
863 
864             if (root.getLayeredPane() != null) {
865                 root.getLayeredPane().setBounds(i.left, i.top, w, h);
866             }
867             if (root.getGlassPane() != null) {
868                 root.getGlassPane().setBounds(i.left, i.top, w, h);
869             }
870             // Note: This is laying out the children in the layeredPane,
871             // technically, these are not our children.
872             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
873                     (root.getUI() instanceof Quaqua14RootPaneUI)) {
874                 JComponent titlePane = ((Quaqua14RootPaneUI) root.getUI()).getTitlePane();
875                 if (titlePane != null) {
876                     Dimension tpd = titlePane.getPreferredSize();
877                     if (tpd != null) {
878                         if (isVertical) {
879                             int tpWidth = tpd.width;
880                             titlePane.setBounds(0, 0, tpWidth, h);
881                             nextX += tpWidth;
882                         } else {
883                             int tpHeight = tpd.height;
884                             titlePane.setBounds(0, 0, w, tpHeight);
885                             nextY += tpHeight;
886                         }
887                     }
888                 }
889             }
890             if (root.getJMenuBar() != null) {
891                 Dimension mbd = root.getJMenuBar().getPreferredSize();
892                 root.getJMenuBar().setBounds(nextX, nextY, w - nextX, mbd.height);
893                 nextY += mbd.height;
894             }
895             if (root.getContentPane() != null) {
896                 Dimension cpd = root.getContentPane().getPreferredSize();
897                 root.getContentPane().setBounds(nextX, nextY, w - nextX,
898                         h < nextY ? 0 : h - nextY);
899             }
900         }
901 
902         public void addLayoutComponent(String name, Component comp) {
903         }
904 
905         public void removeLayoutComponent(Component comp) {
906         }
907 
908         public void addLayoutComponent(Component comp, Object constraints) {
909         }
910 
911         public float getLayoutAlignmentX(Container target) {
912             return 0.0f;
913         }
914 
915         public float getLayoutAlignmentY(Container target) {
916             return 0.0f;
917         }
918 
919         public void invalidateLayout(Container target) {
920         }
921     }
922 
923     /**
924      * MouseInputHandler is responsible for handling resize/moving of
925      * the Window. It sets the cursor directly on the Window when then
926      * mouse moves over a hot spot.
927      */
928     private class MouseInputHandler implements MouseInputListener {
929 
930         /**
931          * Set to true if the drag operation is moving the window.
932          */
933         private boolean isMovingWindow;
934         /**
935          * Used to determine the corner the resize is occuring from.
936          */
937         private int dragCursor;
938         /**
939          * X location the mouse went down on for a drag operation.
940          */
941         private int dragOffsetX;
942         /**
943          * Y location the mouse went down on for a drag operation.
944          */
945         private int dragOffsetY;
946         /**
947          * Width of the window when the drag started.
948          */
949         private int dragWidth;
950         /**
951          * Height of the window when the drag started.
952          */
953         private int dragHeight;
954         /**
955          * We cache the screen bounds here, so that we don't have to retrieve
956          * them for each mouseDragged event. We clear the cache on mouseReleased.
957          */
958         private Rectangle cachedScreenBounds;
959 
960         public void mousePressed(MouseEvent ev) {
961             JRootPane rootPane = getRootPane();
962 
963             if (rootPane.getWindowDecorationStyle() == JRootPane.NONE) {
964                 return;
965             }
966             Point dragWindowOffset = ev.getPoint();
967             Window w = (Window) ev.getSource();
968             if (w != null) {
969                 w.toFront();
970             }
971             Point convertedDragWindowOffset = SwingUtilities.convertPoint(
972                     w, dragWindowOffset, getTitlePane());
973 
974             Frame f = null;
975             Dialog d = null;
976 
977             if (w instanceof Frame) {
978                 f = (Frame) w;
979             } else if (w instanceof Dialog) {
980                 d = (Dialog) w;
981             }
982 
983             int frameState = (f != null) ? f.getExtendedState() : 0;
984 
985             if (getTitlePane() != null &&
986                     getTitlePane().contains(convertedDragWindowOffset)) {
987                 if (f != null && ((frameState & Frame.MAXIMIZED_BOTH) == 0) || (d != null)) {
988                     isMovingWindow = true;
989                     dragOffsetX = dragWindowOffset.x;
990                     dragOffsetY = dragWindowOffset.y;
991                 }
992             } else if (f != null && f.isResizable() && ((frameState & Frame.MAXIMIZED_BOTH) == 0) || (d != null && d.isResizable())) {
993                 dragOffsetX = dragWindowOffset.x;
994                 dragOffsetY = dragWindowOffset.y;
995                 dragWidth = w.getWidth();
996                 dragHeight = w.getHeight();
997                 dragCursor =
998                         (dragWindowOffset.x >= dragWidth - BORDER_DRAG_THICKNESS &&
999                         dragWindowOffset.y >= dragHeight - BORDER_DRAG_THICKNESS) ? Cursor.SE_RESIZE_CURSOR : 0;
1000             }
1001         }
1002 
1003         public void mouseReleased(MouseEvent ev) {
1004             if (dragCursor != 0 && window != null && !window.isValid()) {
1005                 // Some Window systems validate as you resize, others won't,
1006                 // thus the check for validity before repainting.
1007                 window.validate();
1008                 getRootPane().repaint();
1009             }
1010             isMovingWindow = false;
1011             dragCursor = 0;
1012             cachedScreenBounds = null;
1013         }
1014 
1015         public void mouseMoved(MouseEvent ev) {
1016         }
1017 
1018         private void adjust(Rectangle bounds, Dimension min, int deltaX,
1019                 int deltaY, int deltaWidth, int deltaHeight) {
1020             bounds.x += deltaX;
1021             bounds.y += deltaY;
1022             bounds.width += deltaWidth;
1023             bounds.height += deltaHeight;
1024             if (min != null) {
1025                 if (bounds.width < min.width) {
1026                     int correction = min.width - bounds.width;
1027                     if (deltaX != 0) {
1028                         bounds.x -= correction;
1029                     }
1030                     bounds.width = min.width;
1031                 }
1032                 if (bounds.height < min.height) {
1033                     int correction = min.height - bounds.height;
1034                     if (deltaY != 0) {
1035                         bounds.y -= correction;
1036                     }
1037                     bounds.height = min.height;
1038                 }
1039             }
1040         }
1041 
1042         public void mouseDragged(MouseEvent ev) {
1043             Window w = (Window) ev.getSource();
1044             Point pt = ev.getPoint();
1045 
1046             if (isMovingWindow) {
1047                 // Sometimes we get mouse dragged events even when we are not
1048                 // showing on screen (?)
1049                 if (w.isShowing()) {
1050                     Point windowPt = w.getLocationOnScreen();
1051 
1052                     windowPt.x += pt.x - dragOffsetX;
1053                     windowPt.y += pt.y - dragOffsetY;
1054 
1055                     boolean isOnDefaultScreen =
1056                             w.getGraphicsConfiguration().getDevice() ==
1057                             GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
1058 
1059                     // If an edge of the window is within the snap distance of the
1060                     // edge of another window, then align it to it.
1061                     // ----------------------------------------------------------
1062                     int snap = UIManager.getInt("RootPane.windowSnapDistance");
1063                     if (snap > 0 && (ev.getModifiersEx() & InputEvent.ALT_DOWN_MASK) == 0) {
1064                         // Collects all bounds to which we want to snap to
1065                         LinkedList snapBounds;
1066                         // Collect window bounds
1067                         do {
1068                             snapBounds = new LinkedList();
1069                             try {
1070                                 for (Iterator i = allRootPanes.keySet().iterator(); i.hasNext();) {
1071                                     JRootPane otherRootPane = (JRootPane) i.next();
1072                                     Window other = SwingUtilities.getWindowAncestor(otherRootPane);
1073                                     if (other != null && other.isShowing() && other != w) {
1074                                         snapBounds.add(other.getBounds());
1075                                     }
1076                                 }
1077                             } catch (ConcurrentModificationException e) {
1078                                 // allRootPanes is a WeakHashMap, thus iterating over
1079                                 // it may fail sometimes because the garbage collector
1080                                 // removes items in a worker thread.
1081                                 snapBounds = null;
1082                             }
1083                         } while (snapBounds == null);
1084 
1085                         // Collect screen bounds
1086                         snapBounds.add(w.getGraphicsConfiguration().getBounds());
1087                         if (isOnDefaultScreen) {
1088                             Rectangle r = w.getGraphicsConfiguration().getBounds();
1089                             Insets insets = w.getToolkit().getScreenInsets(w.getGraphicsConfiguration());
1090                             r.x += insets.left;
1091                             r.y += insets.top;
1092                             r.width -= insets.left + insets.right;
1093                             r.height -= insets.top + insets.bottom;
1094                             snapBounds.add(r);
1095                         }
1096 
1097                         Dimension windowDim = w.getSize();
1098                         Rectangle windowRect = new Rectangle(windowPt.x, windowPt.y, windowDim.width, windowDim.height);
1099                         Rectangle snapper = new Rectangle();
1100                         for (Iterator i = snapBounds.iterator(); i.hasNext();) {
1101                             Rectangle r = (Rectangle) i.next();
1102 
1103                             snapper.setBounds(r);
1104                             snapper.grow(snap, snap);
1105                             if (snapper.intersects(windowRect)) {
1106 
1107                                 if (windowPt.x > r.x - snap &&
1108                                         windowPt.x < r.x + snap) {
1109                                     // align my left edge to frame left edge
1110                                     windowPt.x = r.x;
1111                                 } else if (windowPt.x > r.x + r.width - snap &&
1112                                         windowPt.x < r.x + r.width + snap) {
1113                                     // align my left edge to frame right edge
1114                                     windowPt.x = r.x + r.width;
1115                                 } else if (windowPt.x + windowDim.width > r.x - snap &&
1116                                         windowPt.x + windowDim.width < r.x + snap) {
1117                                     // align my right edge to frame left edge
1118                                     windowPt.x = r.x - windowDim.width;
1119                                 } else if (windowPt.x + windowDim.width > r.x + r.width - snap &&
1120                                         windowPt.x + windowDim.width < r.x + r.width + snap) {
1121                                     // align my right edge to frame right edge
1122                                     windowPt.x = r.x + r.width - windowDim.width;
1123                                 }
1124                                 if (windowPt.y > r.y - snap && windowPt.y < r.y + snap) {
1125                                     // align my top edge to frame top edge
1126                                     windowPt.y = r.y;
1127                                 } else if (windowPt.y > r.y + r.height - snap &&
1128                                         windowPt.y < r.y + r.height + snap) {
1129                                     // align my top edge to frame bottom edge
1130                                     windowPt.y = r.y + r.height;
1131                                 } else if (windowPt.y + windowDim.height > r.y - snap &&
1132                                         windowPt.y + windowDim.height < r.y + snap) {
1133                                     // align my bottom edge to frame top edge
1134                                     windowPt.y = r.y - windowDim.height;
1135                                 } else if (windowPt.y + windowDim.height > r.y + r.height - snap &&
1136                                         windowPt.y + windowDim.height < r.y + r.height + snap) {
1137                                     // align my bottom edge to frame bottom edge
1138                                     windowPt.y = r.y + r.height - windowDim.height;
1139                                 }
1140                             }
1141                         }
1142                     }
1143                     // Constrain windowPt in order to ensure that a portion of the
1144                     // title pane is always visible on screen
1145                     // ----------------------------------------------------------
1146                     // Get usable screen bounds
1147                     if (isOnDefaultScreen) {
1148                         if (cachedScreenBounds == null) {
1149                             cachedScreenBounds = w.getGraphicsConfiguration().getBounds();
1150                             Insets screenInsets = w.getToolkit().getScreenInsets(w.getGraphicsConfiguration());
1151                             cachedScreenBounds.x += screenInsets.left;
1152                             cachedScreenBounds.y += screenInsets.top;
1153                             cachedScreenBounds.width -= screenInsets.left + screenInsets.right;
1154                             cachedScreenBounds.height -= screenInsets.top + screenInsets.bottom;
1155                         }
1156                         Rectangle titlePaneBounds = getTitlePane().getBounds();
1157                         Dimension windowSize = window.getSize();
1158 
1159                         if (isVertical(getRootPane())) {
1160                             // For vertical title bar, title pane must be fully visible
1161                             // on x-axis, and at least 20 pixel on y-axis.
1162                             windowPt.x = Math.max(cachedScreenBounds.x + titlePaneBounds.x, windowPt.x);
1163                             windowPt.x = Math.min(cachedScreenBounds.x + cachedScreenBounds.width -
1164                                     titlePaneBounds.x - titlePaneBounds.width, windowPt.x);
1165 
1166                             windowPt.y = Math.max(cachedScreenBounds.y - windowSize.height + 20, windowPt.y);
1167                             windowPt.y = Math.min(cachedScreenBounds.y + cachedScreenBounds.height - 20, windowPt.y);
1168 
1169                         } else {
1170                             // For horizontal title bar, title pane must be fully visible
1171                             // on y-axis, and at least 20 pixel on x-axis.
1172                             windowPt.y = Math.max(cachedScreenBounds.y + titlePaneBounds.y, windowPt.y);
1173                             windowPt.y = Math.min(cachedScreenBounds.y + cachedScreenBounds.height -
1174                                     titlePaneBounds.y - titlePaneBounds.height, windowPt.y);
1175 
1176                             windowPt.x = Math.max(cachedScreenBounds.x - windowSize.width + 20, windowPt.x);
1177                             windowPt.x = Math.min(cachedScreenBounds.x + cachedScreenBounds.width - 20, windowPt.x);
1178                         }
1179                     }
1180                     w.setLocation(windowPt);
1181                 }
1182             } else if (dragCursor != 0) {
1183                 Rectangle r = w.getBounds();
1184                 Rectangle startBounds = new Rectangle(r);
1185                 Dimension min = w.getMinimumSize();
1186 
1187                 switch (dragCursor) {
1188                     case Cursor.E_RESIZE_CURSOR:
1189                         adjust(r, min, 0, 0, pt.x + (dragWidth - dragOffsetX) -
1190                                 r.width, 0);
1191                         break;
1192                     case Cursor.S_RESIZE_CURSOR:
1193                         adjust(r, min, 0, 0, 0, pt.y + (dragHeight - dragOffsetY) -
1194                                 r.height);
1195                         break;
1196                     case Cursor.N_RESIZE_CURSOR:
1197                         adjust(r, min, 0, pt.y - dragOffsetY, 0,
1198                                 -(pt.y - dragOffsetY));
1199                         break;
1200                     case Cursor.W_RESIZE_CURSOR:
1201                         adjust(r, min, pt.x - dragOffsetX, 0,
1202                                 -(pt.x - dragOffsetX), 0);
1203                         break;
1204                     case Cursor.NE_RESIZE_CURSOR:
1205                         adjust(r, min, 0, pt.y - dragOffsetY,
1206                                 pt.x + (dragWidth - dragOffsetX) - r.width,
1207                                 -(pt.y - dragOffsetY));
1208                         break;
1209                     case Cursor.SE_RESIZE_CURSOR:
1210                         adjust(r, min, 0, 0,
1211                                 pt.x + (dragWidth - dragOffsetX) - r.width,
1212                                 pt.y + (dragHeight - dragOffsetY) -
1213                                 r.height);
1214                         break;
1215                     case Cursor.NW_RESIZE_CURSOR:
1216                         adjust(r, min, pt.x - dragOffsetX,
1217                                 pt.y - dragOffsetY,
1218                                 -(pt.x - dragOffsetX),
1219                                 -(pt.y - dragOffsetY));
1220                         break;
1221                     case Cursor.SW_RESIZE_CURSOR:
1222                         adjust(r, min, pt.x - dragOffsetX, 0,
1223                                 -(pt.x - dragOffsetX),
1224                                 pt.y + (dragHeight - dragOffsetY) - r.height);
1225                         break;
1226                     default:
1227                         break;
1228                 }
1229                 if (!r.equals(startBounds)) {
1230                     w.setBounds(r);
1231                     // Defer repaint/validate on mouseReleased unless dynamic
1232                     // layout is active.
1233                     if (Toolkit.getDefaultToolkit().isDynamicLayoutActive()) {
1234                         w.validate();
1235                         getRootPane().repaint();
1236                     }
1237                 }
1238             }
1239         }
1240 
1241         public void mouseEntered(MouseEvent ev) {
1242             Window w = (Window) ev.getSource();
1243             lastCursor = w.getCursor();
1244             mouseMoved(ev);
1245         }
1246 
1247         public void mouseExited(MouseEvent ev) {
1248             Window w = (Window) ev.getSource();
1249             w.setCursor(lastCursor);
1250         }
1251 
1252         public void mouseClicked(MouseEvent ev) {
1253             Window w = (Window) ev.getSource();
1254             Frame f = null;
1255 
1256             if (w instanceof Frame) {
1257                 f = (Frame) w;
1258             } else {
1259                 return;
1260             }
1261 
1262             Point convertedPoint = SwingUtilities.convertPoint(
1263                     w, ev.getPoint(), getTitlePane());
1264 
1265             int state = f.getExtendedState();
1266             if (getTitlePane() != null &&
1267                     getTitlePane().contains(convertedPoint)) {
1268                 if ((ev.getClickCount() % 2) == 0 &&
1269                         ((ev.getModifiers() & InputEvent.BUTTON1_MASK) != 0)) {
1270                     if (f.isResizable()) {
1271                         if ((state & Frame.MAXIMIZED_BOTH) != 0) {
1272                             f.setExtendedState(state & ~Frame.MAXIMIZED_BOTH);
1273                         } else {
1274                             f.setExtendedState(state | Frame.MAXIMIZED_BOTH);
1275                         }
1276                         return;
1277                     }
1278                 }
1279             }
1280         }
1281     }
1282 }
1283