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