1 /*
2  * Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.swing.plaf.metal;
27 
28 import java.awt.event.*;
29 import java.beans.PropertyChangeEvent;
30 import javax.swing.*;
31 import javax.swing.event.*;
32 import javax.swing.plaf.*;
33 import javax.swing.plaf.basic.*;
34 import java.awt.*;
35 
36 /**
37  * Provides the metal look and feel implementation of <code>RootPaneUI</code>.
38  * <p>
39  * <code>MetalRootPaneUI</code> provides support for the
40  * <code>windowDecorationStyle</code> property of <code>JRootPane</code>.
41  * <code>MetalRootPaneUI</code> does this by way of installing a custom
42  * <code>LayoutManager</code>, a private <code>Component</code> to render
43  * the appropriate widgets, and a private <code>Border</code>. The
44  * <code>LayoutManager</code> is always installed, regardless of the value of
45  * the <code>windowDecorationStyle</code> property, but the
46  * <code>Border</code> and <code>Component</code> are only installed/added if
47  * the <code>windowDecorationStyle</code> is other than
48  * <code>JRootPane.NONE</code>.
49  * <p>
50  * <strong>Warning:</strong>
51  * Serialized objects of this class will not be compatible with
52  * future Swing releases. The current serialization support is
53  * appropriate for short term storage or RMI between applications running
54  * the same version of Swing.  As of 1.4, support for long term storage
55  * of all JavaBeans&trade;
56  * has been added to the <code>java.beans</code> package.
57  * Please see {@link java.beans.XMLEncoder}.
58  *
59  * @author Terry Kellerman
60  * @since 1.4
61  */
62 @SuppressWarnings("serial") // Same-version serialization only
63 public class MetalRootPaneUI extends BasicRootPaneUI
64 {
65     /**
66      * Keys to lookup borders in defaults table.
67      */
68     private static final String[] borderKeys = new String[] {
69         null, "RootPane.frameBorder", "RootPane.plainDialogBorder",
70         "RootPane.informationDialogBorder",
71         "RootPane.errorDialogBorder", "RootPane.colorChooserDialogBorder",
72         "RootPane.fileChooserDialogBorder", "RootPane.questionDialogBorder",
73         "RootPane.warningDialogBorder"
74     };
75     /**
76      * The amount of space (in pixels) that the cursor is changed on.
77      */
78     private static final int CORNER_DRAG_WIDTH = 16;
79 
80     /**
81      * Region from edges that dragging is active from.
82      */
83     private static final int BORDER_DRAG_THICKNESS = 5;
84 
85     /**
86      * Window the <code>JRootPane</code> is in.
87      */
88     private Window window;
89 
90     /**
91      * <code>JComponent</code> providing window decorations. This will be
92      * null if not providing window decorations.
93      */
94     private JComponent titlePane;
95 
96     /**
97      * <code>MouseInputListener</code> that is added to the parent
98      * <code>Window</code> the <code>JRootPane</code> is contained in.
99      */
100     private MouseInputListener mouseInputListener;
101 
102     /**
103      * The <code>LayoutManager</code> that is set on the
104      * <code>JRootPane</code>.
105      */
106     private LayoutManager layoutManager;
107 
108     /**
109      * <code>LayoutManager</code> of the <code>JRootPane</code> before we
110      * replaced it.
111      */
112     private LayoutManager savedOldLayout;
113 
114     /**
115      * <code>JRootPane</code> providing the look and feel for.
116      */
117     private JRootPane root;
118 
119     /**
120      * <code>Cursor</code> used to track the cursor set by the user.
121      * This is initially <code>Cursor.DEFAULT_CURSOR</code>.
122      */
123     private Cursor lastCursor =
124             Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
125 
126     /**
127      * Creates a UI for a <code>JRootPane</code>.
128      *
129      * @param c the JRootPane the RootPaneUI will be created for
130      * @return the RootPaneUI implementation for the passed in JRootPane
131      */
createUI(JComponent c)132     public static ComponentUI createUI(JComponent c) {
133         return new MetalRootPaneUI();
134     }
135 
136     /**
137      * Invokes supers implementation of <code>installUI</code> to install
138      * the necessary state onto the passed in <code>JRootPane</code>
139      * to render the metal look and feel implementation of
140      * <code>RootPaneUI</code>. If
141      * the <code>windowDecorationStyle</code> property of the
142      * <code>JRootPane</code> is other than <code>JRootPane.NONE</code>,
143      * this will add a custom <code>Component</code> to render the widgets to
144      * <code>JRootPane</code>, as well as installing a custom
145      * <code>Border</code> and <code>LayoutManager</code> on the
146      * <code>JRootPane</code>.
147      *
148      * @param c the JRootPane to install state onto
149      */
installUI(JComponent c)150     public void installUI(JComponent c) {
151         super.installUI(c);
152         root = (JRootPane)c;
153         int style = root.getWindowDecorationStyle();
154         if (style != JRootPane.NONE) {
155             installClientDecorations(root);
156         }
157     }
158 
159 
160     /**
161      * Invokes supers implementation to uninstall any of its state. This will
162      * also reset the <code>LayoutManager</code> of the <code>JRootPane</code>.
163      * If a <code>Component</code> has been added to the <code>JRootPane</code>
164      * to render the window decoration style, this method will remove it.
165      * Similarly, this will revert the Border and LayoutManager of the
166      * <code>JRootPane</code> to what it was before <code>installUI</code>
167      * was invoked.
168      *
169      * @param c the JRootPane to uninstall state from
170      */
uninstallUI(JComponent c)171     public void uninstallUI(JComponent c) {
172         super.uninstallUI(c);
173         uninstallClientDecorations(root);
174 
175         layoutManager = null;
176         mouseInputListener = null;
177         root = null;
178     }
179 
180     /**
181      * Installs the appropriate <code>Border</code> onto the
182      * <code>JRootPane</code>.
183      */
installBorder(JRootPane root)184     void installBorder(JRootPane root) {
185         int style = root.getWindowDecorationStyle();
186 
187         if (style == JRootPane.NONE) {
188             LookAndFeel.uninstallBorder(root);
189         }
190         else {
191             LookAndFeel.installBorder(root, borderKeys[style]);
192         }
193     }
194 
195     /**
196      * Removes any border that may have been installed.
197      */
uninstallBorder(JRootPane root)198     private void uninstallBorder(JRootPane root) {
199         LookAndFeel.uninstallBorder(root);
200     }
201 
202     /**
203      * Installs the necessary Listeners on the parent <code>Window</code>,
204      * if there is one.
205      * <p>
206      * This takes the parent so that cleanup can be done from
207      * <code>removeNotify</code>, at which point the parent hasn't been
208      * reset yet.
209      *
210      * @param parent The parent of the JRootPane
211      */
installWindowListeners(JRootPane root, Component parent)212     private void installWindowListeners(JRootPane root, Component parent) {
213         if (parent instanceof Window) {
214             window = (Window)parent;
215         }
216         else {
217             window = SwingUtilities.getWindowAncestor(parent);
218         }
219         if (window != null) {
220             if (mouseInputListener == null) {
221                 mouseInputListener = createWindowMouseInputListener(root);
222             }
223             window.addMouseListener(mouseInputListener);
224             window.addMouseMotionListener(mouseInputListener);
225         }
226     }
227 
228     /**
229      * Uninstalls the necessary Listeners on the <code>Window</code> the
230      * Listeners were last installed on.
231      */
uninstallWindowListeners(JRootPane root)232     private void uninstallWindowListeners(JRootPane root) {
233         if (window != null) {
234             window.removeMouseListener(mouseInputListener);
235             window.removeMouseMotionListener(mouseInputListener);
236         }
237     }
238 
239     /**
240      * Installs the appropriate LayoutManager on the <code>JRootPane</code>
241      * to render the window decorations.
242      */
installLayout(JRootPane root)243     private void installLayout(JRootPane root) {
244         if (layoutManager == null) {
245             layoutManager = createLayoutManager();
246         }
247         savedOldLayout = root.getLayout();
248         root.setLayout(layoutManager);
249     }
250 
251     /**
252      * Uninstalls the previously installed <code>LayoutManager</code>.
253      */
uninstallLayout(JRootPane root)254     private void uninstallLayout(JRootPane root) {
255         if (savedOldLayout != null) {
256             root.setLayout(savedOldLayout);
257             savedOldLayout = null;
258         }
259     }
260 
261     /**
262      * Installs the necessary state onto the JRootPane to render client
263      * decorations. This is ONLY invoked if the <code>JRootPane</code>
264      * has a decoration style other than <code>JRootPane.NONE</code>.
265      */
installClientDecorations(JRootPane root)266     private void installClientDecorations(JRootPane root) {
267         installBorder(root);
268 
269         JComponent titlePane = createTitlePane(root);
270 
271         setTitlePane(root, titlePane);
272         installWindowListeners(root, root.getParent());
273         installLayout(root);
274         if (window != null) {
275             root.revalidate();
276             root.repaint();
277         }
278     }
279 
280     /**
281      * Uninstalls any state that <code>installClientDecorations</code> has
282      * installed.
283      * <p>
284      * NOTE: This may be called if you haven't installed client decorations
285      * yet (ie before <code>installClientDecorations</code> has been invoked).
286      */
uninstallClientDecorations(JRootPane root)287     private void uninstallClientDecorations(JRootPane root) {
288         uninstallBorder(root);
289         uninstallWindowListeners(root);
290         setTitlePane(root, null);
291         uninstallLayout(root);
292         // We have to revalidate/repaint root if the style is JRootPane.NONE
293         // only. When we needs to call revalidate/repaint with other styles
294         // the installClientDecorations is always called after this method
295         // imediatly and it will cause the revalidate/repaint at the proper
296         // time.
297         int style = root.getWindowDecorationStyle();
298         if (style == JRootPane.NONE) {
299             root.repaint();
300             root.revalidate();
301         }
302         // Reset the cursor, as we may have changed it to a resize cursor
303         if (window != null) {
304             window.setCursor(Cursor.getPredefinedCursor
305                              (Cursor.DEFAULT_CURSOR));
306         }
307         window = null;
308     }
309 
310     /**
311      * Returns the <code>JComponent</code> to render the window decoration
312      * style.
313      */
createTitlePane(JRootPane root)314     private JComponent createTitlePane(JRootPane root) {
315         return new MetalTitlePane(root, this);
316     }
317 
318     /**
319      * Returns a <code>MouseListener</code> that will be added to the
320      * <code>Window</code> containing the <code>JRootPane</code>.
321      */
createWindowMouseInputListener(JRootPane root)322     private MouseInputListener createWindowMouseInputListener(JRootPane root) {
323         return new MouseInputHandler();
324     }
325 
326     /**
327      * Returns a <code>LayoutManager</code> that will be set on the
328      * <code>JRootPane</code>.
329      */
createLayoutManager()330     private LayoutManager createLayoutManager() {
331         return new MetalRootLayout();
332     }
333 
334     /**
335      * Sets the window title pane -- the JComponent used to provide a plaf a
336      * way to override the native operating system's window title pane with
337      * one whose look and feel are controlled by the plaf.  The plaf creates
338      * and sets this value; the default is null, implying a native operating
339      * system window title pane.
340      *
341      * @param titlePane the <code>JComponent</code> to use for the window title pane.
342      */
setTitlePane(JRootPane root, JComponent titlePane)343     private void setTitlePane(JRootPane root, JComponent titlePane) {
344         JLayeredPane layeredPane = root.getLayeredPane();
345         JComponent oldTitlePane = getTitlePane();
346 
347         if (oldTitlePane != null) {
348             oldTitlePane.setVisible(false);
349             layeredPane.remove(oldTitlePane);
350         }
351         if (titlePane != null) {
352             layeredPane.add(titlePane, JLayeredPane.FRAME_CONTENT_LAYER);
353             titlePane.setVisible(true);
354         }
355         this.titlePane = titlePane;
356     }
357 
358     /**
359      * Returns the <code>JComponent</code> rendering the title pane. If this
360      * returns null, it implies there is no need to render window decorations.
361      *
362      * @return the current window title pane, or null
363      * @see #setTitlePane
364      */
getTitlePane()365     private JComponent getTitlePane() {
366         return titlePane;
367     }
368 
369     /**
370      * Returns the <code>JRootPane</code> we're providing the look and
371      * feel for.
372      */
getRootPane()373     private JRootPane getRootPane() {
374         return root;
375     }
376 
377     /**
378      * Invoked when a property changes. <code>MetalRootPaneUI</code> is
379      * primarily interested in events originating from the
380      * <code>JRootPane</code> it has been installed on identifying the
381      * property <code>windowDecorationStyle</code>. If the
382      * <code>windowDecorationStyle</code> has changed to a value other
383      * than <code>JRootPane.NONE</code>, this will add a <code>Component</code>
384      * to the <code>JRootPane</code> to render the window decorations, as well
385      * as installing a <code>Border</code> on the <code>JRootPane</code>.
386      * On the other hand, if the <code>windowDecorationStyle</code> has
387      * changed to <code>JRootPane.NONE</code>, this will remove the
388      * <code>Component</code> that has been added to the <code>JRootPane</code>
389      * as well resetting the Border to what it was before
390      * <code>installUI</code> was invoked.
391      *
392      * @param e A PropertyChangeEvent object describing the event source
393      *          and the property that has changed.
394      */
propertyChange(PropertyChangeEvent e)395     public void propertyChange(PropertyChangeEvent e) {
396         super.propertyChange(e);
397 
398         String propertyName = e.getPropertyName();
399         if(propertyName == null) {
400             return;
401         }
402 
403         if(propertyName.equals("windowDecorationStyle")) {
404             JRootPane root = (JRootPane) e.getSource();
405             int style = root.getWindowDecorationStyle();
406 
407             // This is potentially more than needs to be done,
408             // but it rarely happens and makes the install/uninstall process
409             // simpler. MetalTitlePane also assumes it will be recreated if
410             // the decoration style changes.
411             uninstallClientDecorations(root);
412             if (style != JRootPane.NONE) {
413                 installClientDecorations(root);
414             }
415         }
416         else if (propertyName.equals("ancestor")) {
417             uninstallWindowListeners(root);
418             if (((JRootPane)e.getSource()).getWindowDecorationStyle() !=
419                                            JRootPane.NONE) {
420                 installWindowListeners(root, root.getParent());
421             }
422         }
423         return;
424     }
425 
426     /**
427      * A custom layout manager that is responsible for the layout of
428      * layeredPane, glassPane, menuBar and titlePane, if one has been
429      * installed.
430      */
431     // NOTE: Ideally this would extends JRootPane.RootLayout, but that
432     //       would force this to be non-static.
433     private static class MetalRootLayout implements LayoutManager2 {
434         /**
435          * Returns the amount of space the layout would like to have.
436          *
437          * @param parent the Container for which this layout manager is being used
438          * @return a Dimension object containing the layout's preferred size
439          */
preferredLayoutSize(Container parent)440         public Dimension preferredLayoutSize(Container parent) {
441             Dimension cpd, mbd, tpd;
442             int cpWidth = 0;
443             int cpHeight = 0;
444             int mbWidth = 0;
445             int mbHeight = 0;
446             int tpWidth = 0;
447             int tpHeight = 0;
448             Insets i = parent.getInsets();
449             JRootPane root = (JRootPane) parent;
450 
451             if(root.getContentPane() != null) {
452                 cpd = root.getContentPane().getPreferredSize();
453             } else {
454                 cpd = root.getSize();
455             }
456             if (cpd != null) {
457                 cpWidth = cpd.width;
458                 cpHeight = cpd.height;
459             }
460 
461             if(root.getJMenuBar() != null) {
462                 mbd = root.getJMenuBar().getPreferredSize();
463                 if (mbd != null) {
464                     mbWidth = mbd.width;
465                     mbHeight = mbd.height;
466                 }
467             }
468 
469             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
470                      (root.getUI() instanceof MetalRootPaneUI)) {
471                 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
472                                        getTitlePane();
473                 if (titlePane != null) {
474                     tpd = titlePane.getPreferredSize();
475                     if (tpd != null) {
476                         tpWidth = tpd.width;
477                         tpHeight = tpd.height;
478                     }
479                 }
480             }
481 
482             return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth) + i.left + i.right,
483                                  cpHeight + mbHeight + tpWidth + i.top + i.bottom);
484         }
485 
486         /**
487          * Returns the minimum amount of space the layout needs.
488          *
489          * @param parent the Container for which this layout manager is being used
490          * @return a Dimension object containing the layout's minimum size
491          */
minimumLayoutSize(Container parent)492         public Dimension minimumLayoutSize(Container parent) {
493             Dimension cpd, mbd, tpd;
494             int cpWidth = 0;
495             int cpHeight = 0;
496             int mbWidth = 0;
497             int mbHeight = 0;
498             int tpWidth = 0;
499             int tpHeight = 0;
500             Insets i = parent.getInsets();
501             JRootPane root = (JRootPane) parent;
502 
503             if(root.getContentPane() != null) {
504                 cpd = root.getContentPane().getMinimumSize();
505             } else {
506                 cpd = root.getSize();
507             }
508             if (cpd != null) {
509                 cpWidth = cpd.width;
510                 cpHeight = cpd.height;
511             }
512 
513             if(root.getJMenuBar() != null) {
514                 mbd = root.getJMenuBar().getMinimumSize();
515                 if (mbd != null) {
516                     mbWidth = mbd.width;
517                     mbHeight = mbd.height;
518                 }
519             }
520             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
521                      (root.getUI() instanceof MetalRootPaneUI)) {
522                 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
523                                        getTitlePane();
524                 if (titlePane != null) {
525                     tpd = titlePane.getMinimumSize();
526                     if (tpd != null) {
527                         tpWidth = tpd.width;
528                         tpHeight = tpd.height;
529                     }
530                 }
531             }
532 
533             return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth) + i.left + i.right,
534                                  cpHeight + mbHeight + tpWidth + i.top + i.bottom);
535         }
536 
537         /**
538          * Returns the maximum amount of space the layout can use.
539          *
540          * @param target the Container for which this layout manager is being used
541          * @return a Dimension object containing the layout's maximum size
542          */
maximumLayoutSize(Container target)543         public Dimension maximumLayoutSize(Container target) {
544             Dimension cpd, mbd, tpd;
545             int cpWidth = Integer.MAX_VALUE;
546             int cpHeight = Integer.MAX_VALUE;
547             int mbWidth = Integer.MAX_VALUE;
548             int mbHeight = Integer.MAX_VALUE;
549             int tpWidth = Integer.MAX_VALUE;
550             int tpHeight = Integer.MAX_VALUE;
551             Insets i = target.getInsets();
552             JRootPane root = (JRootPane) target;
553 
554             if(root.getContentPane() != null) {
555                 cpd = root.getContentPane().getMaximumSize();
556                 if (cpd != null) {
557                     cpWidth = cpd.width;
558                     cpHeight = cpd.height;
559                 }
560             }
561 
562             if(root.getJMenuBar() != null) {
563                 mbd = root.getJMenuBar().getMaximumSize();
564                 if (mbd != null) {
565                     mbWidth = mbd.width;
566                     mbHeight = mbd.height;
567                 }
568             }
569 
570             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
571                      (root.getUI() instanceof MetalRootPaneUI)) {
572                 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
573                                        getTitlePane();
574                 if (titlePane != null)
575                 {
576                     tpd = titlePane.getMaximumSize();
577                     if (tpd != null) {
578                         tpWidth = tpd.width;
579                         tpHeight = tpd.height;
580                     }
581                 }
582             }
583 
584             int maxHeight = Math.max(Math.max(cpHeight, mbHeight), tpHeight);
585             // Only overflows if 3 real non-MAX_VALUE heights, sum to > MAX_VALUE
586             // Only will happen if sums to more than 2 billion units.  Not likely.
587             if (maxHeight != Integer.MAX_VALUE) {
588                 maxHeight = cpHeight + mbHeight + tpHeight + i.top + i.bottom;
589             }
590 
591             int maxWidth = Math.max(Math.max(cpWidth, mbWidth), tpWidth);
592             // Similar overflow comment as above
593             if (maxWidth != Integer.MAX_VALUE) {
594                 maxWidth += i.left + i.right;
595             }
596 
597             return new Dimension(maxWidth, maxHeight);
598         }
599 
600         /**
601          * Instructs the layout manager to perform the layout for the specified
602          * container.
603          *
604          * @param parent the Container for which this layout manager is being used
605          */
layoutContainer(Container parent)606         public void layoutContainer(Container parent) {
607             JRootPane root = (JRootPane) parent;
608             Rectangle b = root.getBounds();
609             Insets i = root.getInsets();
610             int nextY = 0;
611             int w = b.width - i.right - i.left;
612             int h = b.height - i.top - i.bottom;
613 
614             if(root.getLayeredPane() != null) {
615                 root.getLayeredPane().setBounds(i.left, i.top, w, h);
616             }
617             if(root.getGlassPane() != null) {
618                 root.getGlassPane().setBounds(i.left, i.top, w, h);
619             }
620             // Note: This is laying out the children in the layeredPane,
621             // technically, these are not our children.
622             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
623                      (root.getUI() instanceof MetalRootPaneUI)) {
624                 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
625                                        getTitlePane();
626                 if (titlePane != null) {
627                     Dimension tpd = titlePane.getPreferredSize();
628                     if (tpd != null) {
629                         int tpHeight = tpd.height;
630                         titlePane.setBounds(0, 0, w, tpHeight);
631                         nextY += tpHeight;
632                     }
633                 }
634             }
635             if(root.getJMenuBar() != null) {
636                 Dimension mbd = root.getJMenuBar().getPreferredSize();
637                 root.getJMenuBar().setBounds(0, nextY, w, mbd.height);
638                 nextY += mbd.height;
639             }
640             if(root.getContentPane() != null) {
641                 Dimension cpd = root.getContentPane().getPreferredSize();
642                 root.getContentPane().setBounds(0, nextY, w,
643                 h < nextY ? 0 : h - nextY);
644             }
645         }
646 
addLayoutComponent(String name, Component comp)647         public void addLayoutComponent(String name, Component comp) {}
removeLayoutComponent(Component comp)648         public void removeLayoutComponent(Component comp) {}
addLayoutComponent(Component comp, Object constraints)649         public void addLayoutComponent(Component comp, Object constraints) {}
getLayoutAlignmentX(Container target)650         public float getLayoutAlignmentX(Container target) { return 0.0f; }
getLayoutAlignmentY(Container target)651         public float getLayoutAlignmentY(Container target) { return 0.0f; }
invalidateLayout(Container target)652         public void invalidateLayout(Container target) {}
653     }
654 
655 
656     /**
657      * Maps from positions to cursor type. Refer to calculateCorner and
658      * calculatePosition for details of this.
659      */
660     private static final int[] cursorMapping = new int[]
661     { Cursor.NW_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.N_RESIZE_CURSOR,
662              Cursor.NE_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR,
663       Cursor.NW_RESIZE_CURSOR, 0, 0, 0, Cursor.NE_RESIZE_CURSOR,
664       Cursor.W_RESIZE_CURSOR, 0, 0, 0, Cursor.E_RESIZE_CURSOR,
665       Cursor.SW_RESIZE_CURSOR, 0, 0, 0, Cursor.SE_RESIZE_CURSOR,
666       Cursor.SW_RESIZE_CURSOR, Cursor.SW_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR,
667              Cursor.SE_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR
668     };
669 
670     /**
671      * MouseInputHandler is responsible for handling resize/moving of
672      * the Window. It sets the cursor directly on the Window when then
673      * mouse moves over a hot spot.
674      */
675     private class MouseInputHandler implements MouseInputListener {
676         /**
677          * Set to true if the drag operation is moving the window.
678          */
679         private boolean isMovingWindow;
680 
681         /**
682          * Used to determine the corner the resize is occurring from.
683          */
684         private int dragCursor;
685 
686         /**
687          * X location the mouse went down on for a drag operation.
688          */
689         private int dragOffsetX;
690 
691         /**
692          * Y location the mouse went down on for a drag operation.
693          */
694         private int dragOffsetY;
695 
696         /**
697          * Width of the window when the drag started.
698          */
699         private int dragWidth;
700 
701         /**
702          * Height of the window when the drag started.
703          */
704         private int dragHeight;
705 
mousePressed(MouseEvent ev)706         public void mousePressed(MouseEvent ev) {
707             JRootPane rootPane = getRootPane();
708 
709             if (rootPane.getWindowDecorationStyle() == JRootPane.NONE) {
710                 return;
711             }
712             Point dragWindowOffset = ev.getPoint();
713             Window w = (Window)ev.getSource();
714             if (w != null) {
715                 w.toFront();
716             }
717             Point convertedDragWindowOffset = SwingUtilities.convertPoint(
718                            w, dragWindowOffset, getTitlePane());
719 
720             Frame f = null;
721             Dialog d = null;
722 
723             if (w instanceof Frame) {
724                 f = (Frame)w;
725             } else if (w instanceof Dialog) {
726                 d = (Dialog)w;
727             }
728 
729             int frameState = (f != null) ? f.getExtendedState() : 0;
730 
731             if (getTitlePane() != null &&
732                         getTitlePane().contains(convertedDragWindowOffset)) {
733                 if ((f != null && ((frameState & Frame.MAXIMIZED_BOTH) == 0)
734                         || (d != null))
735                         && dragWindowOffset.y >= BORDER_DRAG_THICKNESS
736                         && dragWindowOffset.x >= BORDER_DRAG_THICKNESS
737                         && dragWindowOffset.x < w.getWidth()
738                             - BORDER_DRAG_THICKNESS) {
739                     isMovingWindow = true;
740                     dragOffsetX = dragWindowOffset.x;
741                     dragOffsetY = dragWindowOffset.y;
742                 }
743             }
744             else if (f != null && f.isResizable()
745                     && ((frameState & Frame.MAXIMIZED_BOTH) == 0)
746                     || (d != null && d.isResizable())) {
747                 dragOffsetX = dragWindowOffset.x;
748                 dragOffsetY = dragWindowOffset.y;
749                 dragWidth = w.getWidth();
750                 dragHeight = w.getHeight();
751                 dragCursor = getCursor(calculateCorner(
752                              w, dragWindowOffset.x, dragWindowOffset.y));
753             }
754         }
755 
mouseReleased(MouseEvent ev)756         public void mouseReleased(MouseEvent ev) {
757             if (dragCursor != 0 && window != null && !window.isValid()) {
758                 // Some Window systems validate as you resize, others won't,
759                 // thus the check for validity before repainting.
760                 window.validate();
761                 getRootPane().repaint();
762             }
763             isMovingWindow = false;
764             dragCursor = 0;
765         }
766 
mouseMoved(MouseEvent ev)767         public void mouseMoved(MouseEvent ev) {
768             JRootPane root = getRootPane();
769 
770             if (root.getWindowDecorationStyle() == JRootPane.NONE) {
771                 return;
772             }
773 
774             Window w = (Window)ev.getSource();
775 
776             Frame f = null;
777             Dialog d = null;
778 
779             if (w instanceof Frame) {
780                 f = (Frame)w;
781             } else if (w instanceof Dialog) {
782                 d = (Dialog)w;
783             }
784 
785             // Update the cursor
786             int cursor = getCursor(calculateCorner(w, ev.getX(), ev.getY()));
787 
788             if (cursor != 0 && ((f != null && (f.isResizable() &&
789                     (f.getExtendedState() & Frame.MAXIMIZED_BOTH) == 0))
790                     || (d != null && d.isResizable()))) {
791                 w.setCursor(Cursor.getPredefinedCursor(cursor));
792             }
793             else {
794                 w.setCursor(lastCursor);
795             }
796         }
797 
adjust(Rectangle bounds, Dimension min, int deltaX, int deltaY, int deltaWidth, int deltaHeight)798         private void adjust(Rectangle bounds, Dimension min, int deltaX,
799                             int deltaY, int deltaWidth, int deltaHeight) {
800             bounds.x += deltaX;
801             bounds.y += deltaY;
802             bounds.width += deltaWidth;
803             bounds.height += deltaHeight;
804             if (min != null) {
805                 if (bounds.width < min.width) {
806                     int correction = min.width - bounds.width;
807                     if (deltaX != 0) {
808                         bounds.x -= correction;
809                     }
810                     bounds.width = min.width;
811                 }
812                 if (bounds.height < min.height) {
813                     int correction = min.height - bounds.height;
814                     if (deltaY != 0) {
815                         bounds.y -= correction;
816                     }
817                     bounds.height = min.height;
818                 }
819             }
820         }
821 
mouseDragged(MouseEvent ev)822         public void mouseDragged(MouseEvent ev) {
823             Window w = (Window)ev.getSource();
824             Point pt = ev.getPoint();
825 
826             if (isMovingWindow) {
827                 Point eventLocationOnScreen = ev.getLocationOnScreen();
828                 w.setLocation(eventLocationOnScreen.x - dragOffsetX,
829                               eventLocationOnScreen.y - dragOffsetY);
830             }
831             else if (dragCursor != 0) {
832                 Rectangle r = w.getBounds();
833                 Rectangle startBounds = new Rectangle(r);
834                 Dimension min = w.getMinimumSize();
835 
836                 switch (dragCursor) {
837                 case Cursor.E_RESIZE_CURSOR:
838                     adjust(r, min, 0, 0, pt.x + (dragWidth - dragOffsetX) -
839                            r.width, 0);
840                     break;
841                 case Cursor.S_RESIZE_CURSOR:
842                     adjust(r, min, 0, 0, 0, pt.y + (dragHeight - dragOffsetY) -
843                            r.height);
844                     break;
845                 case Cursor.N_RESIZE_CURSOR:
846                     adjust(r, min, 0, pt.y -dragOffsetY, 0,
847                            -(pt.y - dragOffsetY));
848                     break;
849                 case Cursor.W_RESIZE_CURSOR:
850                     adjust(r, min, pt.x - dragOffsetX, 0,
851                            -(pt.x - dragOffsetX), 0);
852                     break;
853                 case Cursor.NE_RESIZE_CURSOR:
854                     adjust(r, min, 0, pt.y - dragOffsetY,
855                            pt.x + (dragWidth - dragOffsetX) - r.width,
856                            -(pt.y - dragOffsetY));
857                     break;
858                 case Cursor.SE_RESIZE_CURSOR:
859                     adjust(r, min, 0, 0,
860                            pt.x + (dragWidth - dragOffsetX) - r.width,
861                            pt.y + (dragHeight - dragOffsetY) -
862                            r.height);
863                     break;
864                 case Cursor.NW_RESIZE_CURSOR:
865                     adjust(r, min, pt.x - dragOffsetX,
866                            pt.y - dragOffsetY,
867                            -(pt.x - dragOffsetX),
868                            -(pt.y - dragOffsetY));
869                     break;
870                 case Cursor.SW_RESIZE_CURSOR:
871                     adjust(r, min, pt.x - dragOffsetX, 0,
872                            -(pt.x - dragOffsetX),
873                            pt.y + (dragHeight - dragOffsetY) - r.height);
874                     break;
875                 default:
876                     break;
877                 }
878                 if (!r.equals(startBounds)) {
879                     w.setBounds(r);
880                     // Defer repaint/validate on mouseReleased unless dynamic
881                     // layout is active.
882                     if (Toolkit.getDefaultToolkit().isDynamicLayoutActive()) {
883                         w.validate();
884                         getRootPane().repaint();
885                     }
886                 }
887             }
888         }
889 
mouseEntered(MouseEvent ev)890         public void mouseEntered(MouseEvent ev) {
891             Window w = (Window)ev.getSource();
892             lastCursor = w.getCursor();
893             mouseMoved(ev);
894         }
895 
mouseExited(MouseEvent ev)896         public void mouseExited(MouseEvent ev) {
897             Window w = (Window)ev.getSource();
898             w.setCursor(lastCursor);
899         }
900 
901         @SuppressWarnings("deprecation")
mouseClicked(MouseEvent ev)902         public void mouseClicked(MouseEvent ev) {
903             Window w = (Window)ev.getSource();
904             Frame f = null;
905 
906             if (w instanceof Frame) {
907                 f = (Frame)w;
908             } else {
909                 return;
910             }
911 
912             Point convertedPoint = SwingUtilities.convertPoint(
913                            w, ev.getPoint(), getTitlePane());
914 
915             int state = f.getExtendedState();
916             if (getTitlePane() != null &&
917                     getTitlePane().contains(convertedPoint)) {
918                 if ((ev.getClickCount() % 2) == 0 &&
919                         ((ev.getModifiers() & InputEvent.BUTTON1_MASK) != 0)) {
920                     if (f.isResizable()) {
921                         if ((state & Frame.MAXIMIZED_BOTH) != 0) {
922                             f.setExtendedState(state & ~Frame.MAXIMIZED_BOTH);
923                         }
924                         else {
925                             f.setExtendedState(state | Frame.MAXIMIZED_BOTH);
926                         }
927                         return;
928                     }
929                 }
930             }
931         }
932 
933         /**
934          * Returns the corner that contains the point <code>x</code>,
935          * <code>y</code>, or -1 if the position doesn't match a corner.
936          */
calculateCorner(Window w, int x, int y)937         private int calculateCorner(Window w, int x, int y) {
938             Insets insets = w.getInsets();
939             int xPosition = calculatePosition(x - insets.left,
940                     w.getWidth() - insets.left - insets.right);
941             int yPosition = calculatePosition(y - insets.top,
942                     w.getHeight() - insets.top - insets.bottom);
943 
944             if (xPosition == -1 || yPosition == -1) {
945                 return -1;
946             }
947             return yPosition * 5 + xPosition;
948         }
949 
950         /**
951          * Returns the Cursor to render for the specified corner. This returns
952          * 0 if the corner doesn't map to a valid Cursor
953          */
getCursor(int corner)954         private int getCursor(int corner) {
955             if (corner == -1) {
956                 return 0;
957             }
958             return cursorMapping[corner];
959         }
960 
961         /**
962          * Returns an integer indicating the position of <code>spot</code>
963          * in <code>width</code>. The return value will be:
964          * 0 if < BORDER_DRAG_THICKNESS
965          * 1 if < CORNER_DRAG_WIDTH
966          * 2 if >= CORNER_DRAG_WIDTH && < width - BORDER_DRAG_THICKNESS
967          * 3 if >= width - CORNER_DRAG_WIDTH
968          * 4 if >= width - BORDER_DRAG_THICKNESS
969          * 5 otherwise
970          */
calculatePosition(int spot, int width)971         private int calculatePosition(int spot, int width) {
972             if (spot < BORDER_DRAG_THICKNESS) {
973                 return 0;
974             }
975             if (spot < CORNER_DRAG_WIDTH) {
976                 return 1;
977             }
978             if (spot >= (width - BORDER_DRAG_THICKNESS)) {
979                 return 4;
980             }
981             if (spot >= (width - CORNER_DRAG_WIDTH)) {
982                 return 3;
983             }
984             return 2;
985         }
986     }
987 }
988