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         public Dimension preferredLayoutSize(Container parent) {
 445             Dimension cpd, mbd, tpd;
 446             int cpWidth = 0;
 447             int cpHeight = 0;
 448             int mbWidth = 0;
 449             int mbHeight = 0;
 450             int tpWidth = 0;
 451             int tpHeight = 0;
 452             Insets i = parent.getInsets();
 453             JRootPane root = (JRootPane) parent;
 454 
 455             if(root.getContentPane() != null) {
 456                 cpd = root.getContentPane().getPreferredSize();
 457             } else {
 458                 cpd = root.getSize();
 459             }
 460             if (cpd != null) {
 461                 cpWidth = cpd.width;
 462                 cpHeight = cpd.height;
 463             }
 464 
 465             if(root.getMenuBar() != null) {
 466                 mbd = root.getMenuBar().getPreferredSize();
 467                 if (mbd != null) {
 468                     mbWidth = mbd.width;
 469                     mbHeight = mbd.height;
 470                 }
 471             }
 472 
 473             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
 474                      (root.getUI() instanceof MetalRootPaneUI)) {
 475                 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
 476                                        getTitlePane();
 477                 if (titlePane != null) {
 478                     tpd = titlePane.getPreferredSize();
 479                     if (tpd != null) {
 480                         tpWidth = tpd.width;
 481                         tpHeight = tpd.height;
 482                     }
 483                 }
 484             }
 485 
 486             return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth) + i.left + i.right,
 487                                  cpHeight + mbHeight + tpWidth + i.top + i.bottom);
 488         }
 489 
 490         /**
 491          * Returns the minimum amount of space the layout needs.
 492          *
 493          * @param the Container for which this layout manager is being used
 494          * @return a Dimension object containing the layout's minimum size
 495          */
 496         public Dimension minimumLayoutSize(Container parent) {
 497             Dimension cpd, mbd, tpd;
 498             int cpWidth = 0;
 499             int cpHeight = 0;
 500             int mbWidth = 0;
 501             int mbHeight = 0;
 502             int tpWidth = 0;
 503             int tpHeight = 0;
 504             Insets i = parent.getInsets();
 505             JRootPane root = (JRootPane) parent;
 506 
 507             if(root.getContentPane() != null) {
 508                 cpd = root.getContentPane().getMinimumSize();
 509             } else {
 510                 cpd = root.getSize();
 511             }
 512             if (cpd != null) {
 513                 cpWidth = cpd.width;
 514                 cpHeight = cpd.height;
 515             }
 516 
 517             if(root.getMenuBar() != null) {
 518                 mbd = root.getMenuBar().getMinimumSize();
 519                 if (mbd != null) {
 520                     mbWidth = mbd.width;
 521                     mbHeight = mbd.height;
 522                 }
 523             }
 524             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
 525                      (root.getUI() instanceof MetalRootPaneUI)) {
 526                 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
 527                                        getTitlePane();
 528                 if (titlePane != null) {
 529                     tpd = titlePane.getMinimumSize();
 530                     if (tpd != null) {
 531                         tpWidth = tpd.width;
 532                         tpHeight = tpd.height;
 533                     }
 534                 }
 535             }
 536 
 537             return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth) + i.left + i.right,
 538                                  cpHeight + mbHeight + tpWidth + i.top + i.bottom);
 539         }
 540 
 541         /**
 542          * Returns the maximum amount of space the layout can use.
 543          *
 544          * @param the Container for which this layout manager is being used
 545          * @return a Dimension object containing the layout's maximum size
 546          */
 547         public Dimension maximumLayoutSize(Container target) {
 548             Dimension cpd, mbd, tpd;
 549             int cpWidth = Integer.MAX_VALUE;
 550             int cpHeight = Integer.MAX_VALUE;
 551             int mbWidth = Integer.MAX_VALUE;
 552             int mbHeight = Integer.MAX_VALUE;
 553             int tpWidth = Integer.MAX_VALUE;
 554             int tpHeight = Integer.MAX_VALUE;
 555             Insets i = target.getInsets();
 556             JRootPane root = (JRootPane) target;
 557 
 558             if(root.getContentPane() != null) {
 559                 cpd = root.getContentPane().getMaximumSize();
 560                 if (cpd != null) {
 561                     cpWidth = cpd.width;
 562                     cpHeight = cpd.height;
 563                 }
 564             }
 565 
 566             if(root.getMenuBar() != null) {
 567                 mbd = root.getMenuBar().getMaximumSize();
 568                 if (mbd != null) {
 569                     mbWidth = mbd.width;
 570                     mbHeight = mbd.height;
 571                 }
 572             }
 573 
 574             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
 575                      (root.getUI() instanceof MetalRootPaneUI)) {
 576                 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
 577                                        getTitlePane();
 578                 if (titlePane != null)
 579                 {
 580                     tpd = titlePane.getMaximumSize();
 581                     if (tpd != null) {
 582                         tpWidth = tpd.width;
 583                         tpHeight = tpd.height;
 584                     }
 585                 }
 586             }
 587 
 588             int maxHeight = Math.max(Math.max(cpHeight, mbHeight), tpHeight);
 589             // Only overflows if 3 real non-MAX_VALUE heights, sum to > MAX_VALUE
 590             // Only will happen if sums to more than 2 billion units.  Not likely.
 591             if (maxHeight != Integer.MAX_VALUE) {
 592                 maxHeight = cpHeight + mbHeight + tpHeight + i.top + i.bottom;
 593             }
 594 
 595             int maxWidth = Math.max(Math.max(cpWidth, mbWidth), tpWidth);
 596             // Similar overflow comment as above
 597             if (maxWidth != Integer.MAX_VALUE) {
 598                 maxWidth += i.left + i.right;
 599             }
 600 
 601             return new Dimension(maxWidth, maxHeight);
 602         }
 603 
 604         /**
 605          * Instructs the layout manager to perform the layout for the specified
 606          * container.
 607          *
 608          * @param the Container for which this layout manager is being used
 609          */
 610         public void layoutContainer(Container parent) {
 611             JRootPane root = (JRootPane) parent;
 612             Rectangle b = root.getBounds();
 613             Insets i = root.getInsets();
 614             int nextY = 0;
 615             int w = b.width - i.right - i.left;
 616             int h = b.height - i.top - i.bottom;
 617 
 618             if(root.getLayeredPane() != null) {
 619                 root.getLayeredPane().setBounds(i.left, i.top, w, h);
 620             }
 621             if(root.getGlassPane() != null) {
 622                 root.getGlassPane().setBounds(i.left, i.top, w, h);
 623             }
 624             // Note: This is laying out the children in the layeredPane,
 625             // technically, these are not our children.
 626             if (root.getWindowDecorationStyle() != JRootPane.NONE &&
 627                      (root.getUI() instanceof MetalRootPaneUI)) {
 628                 JComponent titlePane = ((MetalRootPaneUI)root.getUI()).
 629                                        getTitlePane();
 630                 if (titlePane != null) {
 631                     Dimension tpd = titlePane.getPreferredSize();
 632                     if (tpd != null) {
 633                         int tpHeight = tpd.height;
 634                         titlePane.setBounds(0, 0, w, tpHeight);
 635                         nextY += tpHeight;
 636                     }
 637                 }
 638             }
 639             if(root.getMenuBar() != null) {
 640                 Dimension mbd = root.getMenuBar().getPreferredSize();
 641                 root.getMenuBar().setBounds(0, nextY, w, mbd.height);
 642                 nextY += mbd.height;
 643             }
 644             if(root.getContentPane() != null) {
 645                 Dimension cpd = root.getContentPane().getPreferredSize();
 646                 root.getContentPane().setBounds(0, nextY, w,
 647                 h < nextY ? 0 : h - nextY);
 648             }
 649         }
 650 
 651         public void addLayoutComponent(String name, Component comp) {}
 652         public void removeLayoutComponent(Component comp) {}
 653         public void addLayoutComponent(Component comp, Object constraints) {}
 654         public float getLayoutAlignmentX(Container target) { return 0.0f; }
 655         public float getLayoutAlignmentY(Container target) { return 0.0f; }
 656         public void invalidateLayout(Container target) {}
 657     }
 658 
 659 
 660     /**
 661      * Maps from positions to cursor type. Refer to calculateCorner and
 662      * calculatePosition for details of this.
 663      */
 664     private static final int[] cursorMapping = new int[]
 665     { Cursor.NW_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.N_RESIZE_CURSOR,
 666              Cursor.NE_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR,
 667       Cursor.NW_RESIZE_CURSOR, 0, 0, 0, Cursor.NE_RESIZE_CURSOR,
 668       Cursor.W_RESIZE_CURSOR, 0, 0, 0, Cursor.E_RESIZE_CURSOR,
 669       Cursor.SW_RESIZE_CURSOR, 0, 0, 0, Cursor.SE_RESIZE_CURSOR,
 670       Cursor.SW_RESIZE_CURSOR, Cursor.SW_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR,
 671              Cursor.SE_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR
 672     };
 673 
 674     /**
 675      * MouseInputHandler is responsible for handling resize/moving of
 676      * the Window. It sets the cursor directly on the Window when then
 677      * mouse moves over a hot spot.
 678      */
 679     private class MouseInputHandler implements MouseInputListener {
 680         /**
 681          * Set to true if the drag operation is moving the window.
 682          */
 683         private boolean isMovingWindow;
 684 
 685         /**
 686          * Used to determine the corner the resize is occurring from.
 687          */
 688         private int dragCursor;
 689 
 690         /**
 691          * X location the mouse went down on for a drag operation.
 692          */
 693         private int dragOffsetX;
 694 
 695         /**
 696          * Y location the mouse went down on for a drag operation.
 697          */
 698         private int dragOffsetY;
 699 
 700         /**
 701          * Width of the window when the drag started.
 702          */
 703         private int dragWidth;
 704 
 705         /**
 706          * Height of the window when the drag started.
 707          */
 708         private int dragHeight;
 709 
 710         public void mousePressed(MouseEvent ev) {
 711             JRootPane rootPane = getRootPane();
 712 
 713             if (rootPane.getWindowDecorationStyle() == JRootPane.NONE) {
 714                 return;
 715             }
 716             Point dragWindowOffset = ev.getPoint();
 717             Window w = (Window)ev.getSource();
 718             if (w != null) {
 719                 w.toFront();
 720             }
 721             Point convertedDragWindowOffset = SwingUtilities.convertPoint(
 722                            w, dragWindowOffset, getTitlePane());
 723 
 724             Frame f = null;
 725             Dialog d = null;
 726 
 727             if (w instanceof Frame) {
 728                 f = (Frame)w;
 729             } else if (w instanceof Dialog) {
 730                 d = (Dialog)w;
 731             }
 732 
 733             int frameState = (f != null) ? f.getExtendedState() : 0;
 734 
 735             if (getTitlePane() != null &&
 736                         getTitlePane().contains(convertedDragWindowOffset)) {
 737                 if ((f != null && ((frameState & Frame.MAXIMIZED_BOTH) == 0)
 738                         || (d != null))
 739                         && dragWindowOffset.y >= BORDER_DRAG_THICKNESS
 740                         && dragWindowOffset.x >= BORDER_DRAG_THICKNESS
 741                         && dragWindowOffset.x < w.getWidth()
 742                             - BORDER_DRAG_THICKNESS) {
 743                     isMovingWindow = true;
 744                     dragOffsetX = dragWindowOffset.x;
 745                     dragOffsetY = dragWindowOffset.y;
 746                 }
 747             }
 748             else if (f != null && f.isResizable()
 749                     && ((frameState & Frame.MAXIMIZED_BOTH) == 0)
 750                     || (d != null && d.isResizable())) {
 751                 dragOffsetX = dragWindowOffset.x;
 752                 dragOffsetY = dragWindowOffset.y;
 753                 dragWidth = w.getWidth();
 754                 dragHeight = w.getHeight();
 755                 dragCursor = getCursor(calculateCorner(
 756                              w, dragWindowOffset.x, dragWindowOffset.y));
 757             }
 758         }
 759 
 760         public void mouseReleased(MouseEvent ev) {
 761             if (dragCursor != 0 && window != null && !window.isValid()) {
 762                 // Some Window systems validate as you resize, others won't,
 763                 // thus the check for validity before repainting.
 764                 window.validate();
 765                 getRootPane().repaint();
 766             }
 767             isMovingWindow = false;
 768             dragCursor = 0;
 769         }
 770 
 771         public void mouseMoved(MouseEvent ev) {
 772             JRootPane root = getRootPane();
 773 
 774             if (root.getWindowDecorationStyle() == JRootPane.NONE) {
 775                 return;
 776             }
 777 
 778             Window w = (Window)ev.getSource();
 779 
 780             Frame f = null;
 781             Dialog d = null;
 782 
 783             if (w instanceof Frame) {
 784                 f = (Frame)w;
 785             } else if (w instanceof Dialog) {
 786                 d = (Dialog)w;
 787             }
 788 
 789             // Update the cursor
 790             int cursor = getCursor(calculateCorner(w, ev.getX(), ev.getY()));
 791 
 792             if (cursor != 0 && ((f != null && (f.isResizable() &&
 793                     (f.getExtendedState() & Frame.MAXIMIZED_BOTH) == 0))
 794                     || (d != null && d.isResizable()))) {
 795                 w.setCursor(Cursor.getPredefinedCursor(cursor));
 796             }
 797             else {
 798                 w.setCursor(lastCursor);
 799             }
 800         }
 801 
 802         private void adjust(Rectangle bounds, Dimension min, int deltaX,
 803                             int deltaY, int deltaWidth, int deltaHeight) {
 804             bounds.x += deltaX;
 805             bounds.y += deltaY;
 806             bounds.width += deltaWidth;
 807             bounds.height += deltaHeight;
 808             if (min != null) {
 809                 if (bounds.width < min.width) {
 810                     int correction = min.width - bounds.width;
 811                     if (deltaX != 0) {
 812                         bounds.x -= correction;
 813                     }
 814                     bounds.width = min.width;
 815                 }
 816                 if (bounds.height < min.height) {
 817                     int correction = min.height - bounds.height;
 818                     if (deltaY != 0) {
 819                         bounds.y -= correction;
 820                     }
 821                     bounds.height = min.height;
 822                 }
 823             }
 824         }
 825 
 826         public void mouseDragged(MouseEvent ev) {
 827             Window w = (Window)ev.getSource();
 828             Point pt = ev.getPoint();
 829 
 830             if (isMovingWindow) {
 831                 Point eventLocationOnScreen = ev.getLocationOnScreen();
 832                 w.setLocation(eventLocationOnScreen.x - dragOffsetX,
 833                               eventLocationOnScreen.y - dragOffsetY);
 834             }
 835             else if (dragCursor != 0) {
 836                 Rectangle r = w.getBounds();
 837                 Rectangle startBounds = new Rectangle(r);
 838                 Dimension min = w.getMinimumSize();
 839 
 840                 switch (dragCursor) {
 841                 case Cursor.E_RESIZE_CURSOR:
 842                     adjust(r, min, 0, 0, pt.x + (dragWidth - dragOffsetX) -
 843                            r.width, 0);
 844                     break;
 845                 case Cursor.S_RESIZE_CURSOR:
 846                     adjust(r, min, 0, 0, 0, pt.y + (dragHeight - dragOffsetY) -
 847                            r.height);
 848                     break;
 849                 case Cursor.N_RESIZE_CURSOR:
 850                     adjust(r, min, 0, pt.y -dragOffsetY, 0,
 851                            -(pt.y - dragOffsetY));
 852                     break;
 853                 case Cursor.W_RESIZE_CURSOR:
 854                     adjust(r, min, pt.x - dragOffsetX, 0,
 855                            -(pt.x - dragOffsetX), 0);
 856                     break;
 857                 case Cursor.NE_RESIZE_CURSOR:
 858                     adjust(r, min, 0, pt.y - dragOffsetY,
 859                            pt.x + (dragWidth - dragOffsetX) - r.width,
 860                            -(pt.y - dragOffsetY));
 861                     break;
 862                 case Cursor.SE_RESIZE_CURSOR:
 863                     adjust(r, min, 0, 0,
 864                            pt.x + (dragWidth - dragOffsetX) - r.width,
 865                            pt.y + (dragHeight - dragOffsetY) -
 866                            r.height);
 867                     break;
 868                 case Cursor.NW_RESIZE_CURSOR:
 869                     adjust(r, min, pt.x - dragOffsetX,
 870                            pt.y - dragOffsetY,
 871                            -(pt.x - dragOffsetX),
 872                            -(pt.y - dragOffsetY));
 873                     break;
 874                 case Cursor.SW_RESIZE_CURSOR:
 875                     adjust(r, min, pt.x - dragOffsetX, 0,
 876                            -(pt.x - dragOffsetX),
 877                            pt.y + (dragHeight - dragOffsetY) - r.height);
 878                     break;
 879                 default:
 880                     break;
 881                 }
 882                 if (!r.equals(startBounds)) {
 883                     w.setBounds(r);
 884                     // Defer repaint/validate on mouseReleased unless dynamic
 885                     // layout is active.
 886                     if (Toolkit.getDefaultToolkit().isDynamicLayoutActive()) {
 887                         w.validate();
 888                         getRootPane().repaint();
 889                     }
 890                 }
 891             }
 892         }
 893 
 894         public void mouseEntered(MouseEvent ev) {
 895             Window w = (Window)ev.getSource();
 896             lastCursor = w.getCursor();
 897             mouseMoved(ev);
 898         }
 899 
 900         public void mouseExited(MouseEvent ev) {
 901             Window w = (Window)ev.getSource();
 902             w.setCursor(lastCursor);
 903         }
 904 
 905         public void mouseClicked(MouseEvent ev) {
 906             Window w = (Window)ev.getSource();
 907             Frame f = null;
 908 
 909             if (w instanceof Frame) {
 910                 f = (Frame)w;
 911             } else {
 912                 return;
 913             }
 914 
 915             Point convertedPoint = SwingUtilities.convertPoint(
 916                            w, ev.getPoint(), getTitlePane());
 917 
 918             int state = f.getExtendedState();
 919             if (getTitlePane() != null &&
 920                     getTitlePane().contains(convertedPoint)) {
 921                 if ((ev.getClickCount() % 2) == 0 &&
 922                         ((ev.getModifiers() & InputEvent.BUTTON1_MASK) != 0)) {
 923                     if (f.isResizable()) {
 924                         if ((state & Frame.MAXIMIZED_BOTH) != 0) {
 925                             f.setExtendedState(state & ~Frame.MAXIMIZED_BOTH);
 926                         }
 927                         else {
 928                             f.setExtendedState(state | Frame.MAXIMIZED_BOTH);
 929                         }
 930                         return;
 931                     }
 932                 }
 933             }
 934         }
 935 
 936         /**
 937          * Returns the corner that contains the point <code>x</code>,
 938          * <code>y</code>, or -1 if the position doesn't match a corner.
 939          */
 940         private int calculateCorner(Window w, int x, int y) {
 941             Insets insets = w.getInsets();
 942             int xPosition = calculatePosition(x - insets.left,
 943                     w.getWidth() - insets.left - insets.right);
 944             int yPosition = calculatePosition(y - insets.top,
 945                     w.getHeight() - insets.top - insets.bottom);
 946 
 947             if (xPosition == -1 || yPosition == -1) {
 948                 return -1;
 949             }
 950             return yPosition * 5 + xPosition;
 951         }
 952 
 953         /**
 954          * Returns the Cursor to render for the specified corner. This returns
 955          * 0 if the corner doesn't map to a valid Cursor
 956          */
 957         private int getCursor(int corner) {
 958             if (corner == -1) {
 959                 return 0;
 960             }
 961             return cursorMapping[corner];
 962         }
 963 
 964         /**
 965          * Returns an integer indicating the position of <code>spot</code>
 966          * in <code>width</code>. The return value will be:
 967          * 0 if < BORDER_DRAG_THICKNESS
 968          * 1 if < CORNER_DRAG_WIDTH
 969          * 2 if >= CORNER_DRAG_WIDTH && < width - BORDER_DRAG_THICKNESS
 970          * 3 if >= width - CORNER_DRAG_WIDTH
 971          * 4 if >= width - BORDER_DRAG_THICKNESS
 972          * 5 otherwise
 973          */
 974         private int calculatePosition(int spot, int width) {
 975             if (spot < BORDER_DRAG_THICKNESS) {
 976                 return 0;
 977             }
 978             if (spot < CORNER_DRAG_WIDTH) {
 979                 return 1;
 980             }
 981             if (spot >= (width - BORDER_DRAG_THICKNESS)) {
 982                 return 4;
 983             }
 984             if (spot >= (width - CORNER_DRAG_WIDTH)) {
 985                 return 3;
 986             }
 987             return 2;
 988         }
 989     }
 990 }