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