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