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