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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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          */
 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          */
 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          */
 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          */
 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 
 647         public void addLayoutComponent(String name, Component comp) {}
 648         public void removeLayoutComponent(Component comp) {}
 649         public void addLayoutComponent(Component comp, Object constraints) {}
 650         public float getLayoutAlignmentX(Container target) { return 0.0f; }
 651         public float getLayoutAlignmentY(Container target) { return 0.0f; }
 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 
 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 
 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 
 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 
 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 
 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 
 890         public void mouseEntered(MouseEvent ev) {
 891             Window w = (Window)ev.getSource();
 892             lastCursor = w.getCursor();
 893             mouseMoved(ev);
 894         }
 895 
 896         public void mouseExited(MouseEvent ev) {
 897             Window w = (Window)ev.getSource();
 898             w.setCursor(lastCursor);
 899         }
 900 
 901         @SuppressWarnings("deprecation")
 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          */
 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          */
 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          */
 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 }