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™ 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 }