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