1 /* 2 * Copyright (c) 2011, 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 sun.lwawt; 27 28 import java.awt.*; 29 import java.awt.event.*; 30 import java.awt.peer.*; 31 import java.util.List; 32 33 import javax.swing.*; 34 35 import sun.awt.*; 36 import sun.java2d.*; 37 import sun.java2d.loops.Blit; 38 import sun.java2d.loops.CompositeType; 39 import sun.java2d.pipe.Region; 40 import sun.util.logging.PlatformLogger; 41 42 public class LWWindowPeer 43 extends LWContainerPeer<Window, JComponent> 44 implements FramePeer, DialogPeer, FullScreenCapable, DisplayChangedListener, PlatformEventNotifier 45 { 46 public enum PeerType { 47 SIMPLEWINDOW, 48 FRAME, 49 DIALOG, 50 EMBEDDED_FRAME, 51 VIEW_EMBEDDED_FRAME, 52 LW_FRAME 53 } 54 55 private static final PlatformLogger focusLog = PlatformLogger.getLogger("sun.lwawt.focus.LWWindowPeer"); 56 57 private final PlatformWindow platformWindow; 58 59 private static final int MINIMUM_WIDTH = 1; 60 private static final int MINIMUM_HEIGHT = 1; 61 62 private Insets insets = new Insets(0, 0, 0, 0); 63 64 private GraphicsDevice graphicsDevice; 65 private GraphicsConfiguration graphicsConfig; 66 67 private SurfaceData surfaceData; 68 private final Object surfaceDataLock = new Object(); 69 70 private volatile int windowState = Frame.NORMAL; 71 72 // check that the mouse is over the window 73 private volatile boolean isMouseOver = false; 74 75 // A peer where the last mouse event came to. Used by cursor manager to 76 // find the component under cursor 77 private static volatile LWComponentPeer<?, ?> lastCommonMouseEventPeer; 78 79 // A peer where the last mouse event came to. Used to generate 80 // MOUSE_ENTERED/EXITED notifications 81 private volatile LWComponentPeer<?, ?> lastMouseEventPeer; 82 83 // Peers where all dragged/released events should come to, 84 // depending on what mouse button is being dragged according to Cocoa 85 private static final LWComponentPeer<?, ?>[] mouseDownTarget = new LWComponentPeer<?, ?>[3]; 86 87 // A bitmask that indicates what mouse buttons produce MOUSE_CLICKED events 88 // on MOUSE_RELEASE. Click events are only generated if there were no drag 89 // events between MOUSE_PRESSED and MOUSE_RELEASED for particular button 90 private static int mouseClickButtons = 0; 91 92 private volatile boolean isOpaque = true; 93 94 private static final Font DEFAULT_FONT = new Font("Lucida Grande", Font.PLAIN, 13); 95 96 private static LWWindowPeer grabbingWindow; 97 98 private volatile boolean skipNextFocusChange; 99 100 private static final Color nonOpaqueBackground = new Color(0, 0, 0, 0); 101 102 private volatile boolean textured; 103 104 private final PeerType peerType; 105 106 private final SecurityWarningWindow warningWindow; 107 108 /** 109 * Current modal blocker or null. 110 * 111 * Synchronization: peerTreeLock. 112 */ 113 private LWWindowPeer blocker; 114 115 public LWWindowPeer(Window target, PlatformComponent platformComponent, 116 PlatformWindow platformWindow, PeerType peerType) 117 { 118 super(target, platformComponent); 119 this.platformWindow = platformWindow; 120 this.peerType = peerType; 121 122 Window owner = target.getOwner(); 123 LWWindowPeer ownerPeer = owner == null ? null : 124 (LWWindowPeer) AWTAccessor.getComponentAccessor().getPeer(owner); 125 PlatformWindow ownerDelegate = (ownerPeer != null) ? ownerPeer.getPlatformWindow() : null; 126 127 // The delegate.initialize() needs a non-null GC on X11. 128 GraphicsConfiguration gc = getTarget().getGraphicsConfiguration(); 129 synchronized (getStateLock()) { 130 // graphicsConfig should be updated according to the real window 131 // bounds when the window is shown, see 4868278 132 this.graphicsConfig = gc; 133 } 134 135 if (!target.isFontSet()) { 136 target.setFont(DEFAULT_FONT); 137 } 138 139 if (!target.isBackgroundSet()) { 140 target.setBackground(SystemColor.window); 141 } else { 142 // first we check if user provided alpha for background. This is 143 // similar to what Apple's Java do. 144 // Since JDK7 we should rely on setOpacity() only. 145 // this.opacity = c.getAlpha(); 146 } 147 148 if (!target.isForegroundSet()) { 149 target.setForeground(SystemColor.windowText); 150 // we should not call setForeground because it will call a repaint 151 // which the peer may not be ready to do yet. 152 } 153 154 platformWindow.initialize(target, this, ownerDelegate); 155 156 // Init warning window(for applets) 157 SecurityWarningWindow warn = null; 158 if (target.getWarningString() != null) { 159 // accessSystemTray permission allows to display TrayIcon, TrayIcon tooltip 160 // and TrayIcon balloon windows without a warning window. 161 if (!AWTAccessor.getWindowAccessor().isTrayIconWindow(target)) { 162 LWToolkit toolkit = (LWToolkit)Toolkit.getDefaultToolkit(); 163 warn = toolkit.createSecurityWarning(target, this); 164 } 165 } 166 167 warningWindow = warn; 168 } 169 170 @Override 171 void initializeImpl() { 172 super.initializeImpl(); 173 174 175 if (getTarget() instanceof Frame) { 176 setTitle(((Frame) getTarget()).getTitle()); 177 setState(((Frame) getTarget()).getExtendedState()); 178 } else if (getTarget() instanceof Dialog) { 179 setTitle(((Dialog) getTarget()).getTitle()); 180 } 181 182 updateAlwaysOnTopState(); 183 updateMinimumSize(); 184 185 final Shape shape = getTarget().getShape(); 186 if (shape != null) { 187 applyShape(Region.getInstance(shape, null)); 188 } 189 190 final float opacity = getTarget().getOpacity(); 191 if (opacity < 1.0f) { 192 setOpacity(opacity); 193 } 194 195 setOpaque(getTarget().isOpaque()); 196 197 updateInsets(platformWindow.getInsets()); 198 if (getSurfaceData() == null) { 199 replaceSurfaceData(false); 200 } 201 activateDisplayListener(); 202 } 203 204 // Just a helper method 205 @Override 206 public PlatformWindow getPlatformWindow() { 207 return platformWindow; 208 } 209 210 @Override 211 protected LWWindowPeer getWindowPeerOrSelf() { 212 return this; 213 } 214 215 // ---- PEER METHODS ---- // 216 217 @Override 218 protected void disposeImpl() { 219 deactivateDisplayListener(); 220 SurfaceData oldData = getSurfaceData(); 221 synchronized (surfaceDataLock){ 222 surfaceData = null; 223 } 224 if (oldData != null) { 225 oldData.invalidate(); 226 } 227 if (isGrabbing()) { 228 ungrab(); 229 } 230 if (warningWindow != null) { 231 warningWindow.dispose(); 232 } 233 234 platformWindow.dispose(); 235 super.disposeImpl(); 236 } 237 238 @Override 239 protected void setVisibleImpl(final boolean visible) { 240 if (!visible && warningWindow != null) { 241 warningWindow.setVisible(false, false); 242 } 243 244 super.setVisibleImpl(visible); 245 // TODO: update graphicsConfig, see 4868278 246 platformWindow.setVisible(visible); 247 if (isSimpleWindow()) { 248 KeyboardFocusManagerPeer kfmPeer = LWKeyboardFocusManagerPeer.getInstance(); 249 250 if (visible) { 251 if (!getTarget().isAutoRequestFocus()) { 252 return; 253 } else { 254 requestWindowFocus(CausedFocusEvent.Cause.ACTIVATION); 255 } 256 // Focus the owner in case this window is focused. 257 } else if (kfmPeer.getCurrentFocusedWindow() == getTarget()) { 258 // Transfer focus to the owner. 259 LWWindowPeer owner = getOwnerFrameDialog(LWWindowPeer.this); 260 if (owner != null) { 261 owner.requestWindowFocus(CausedFocusEvent.Cause.ACTIVATION); 262 } 263 } 264 } 265 } 266 267 @Override 268 public final GraphicsConfiguration getGraphicsConfiguration() { 269 synchronized (getStateLock()) { 270 return graphicsConfig; 271 } 272 } 273 274 @Override 275 public boolean updateGraphicsData(GraphicsConfiguration gc) { 276 setGraphicsConfig(gc); 277 return false; 278 } 279 280 protected final Graphics getOnscreenGraphics(Color fg, Color bg, Font f) { 281 if (getSurfaceData() == null) { 282 return null; 283 } 284 if (fg == null) { 285 fg = SystemColor.windowText; 286 } 287 if (bg == null) { 288 bg = SystemColor.window; 289 } 290 if (f == null) { 291 f = DEFAULT_FONT; 292 } 293 return platformWindow.transformGraphics(new SunGraphics2D(getSurfaceData(), fg, bg, f)); 294 } 295 296 @Override 297 public void setBounds(int x, int y, int w, int h, int op) { 298 299 if((op & NO_EMBEDDED_CHECK) == 0 && getPeerType() == PeerType.VIEW_EMBEDDED_FRAME) { 300 return; 301 } 302 303 if ((op & SET_CLIENT_SIZE) != 0) { 304 // SET_CLIENT_SIZE is only applicable to window peers, so handle it here 305 // instead of pulling 'insets' field up to LWComponentPeer 306 // no need to add insets since Window's notion of width and height includes insets. 307 op &= ~SET_CLIENT_SIZE; 308 op |= SET_SIZE; 309 } 310 311 // Don't post ComponentMoved/Resized and Paint events 312 // until we've got a notification from the delegate 313 Rectangle cb = constrainBounds(x, y, w, h); 314 315 Rectangle newBounds = new Rectangle(getBounds()); 316 if ((op & (SET_LOCATION | SET_BOUNDS)) != 0) { 317 newBounds.x = cb.x; 318 newBounds.y = cb.y; 319 } 320 if ((op & (SET_SIZE | SET_BOUNDS)) != 0) { 321 newBounds.width = cb.width; 322 newBounds.height = cb.height; 323 } 324 // Native system could constraint bounds, so the peer wold be updated in the callback 325 platformWindow.setBounds(newBounds.x, newBounds.y, newBounds.width, newBounds.height); 326 } 327 328 public Rectangle constrainBounds(Rectangle bounds) { 329 return constrainBounds(bounds.x, bounds.y, bounds.width, bounds.height); 330 } 331 332 public Rectangle constrainBounds(int x, int y, int w, int h) { 333 334 if (w < MINIMUM_WIDTH) { 335 w = MINIMUM_WIDTH; 336 } 337 338 if (h < MINIMUM_HEIGHT) { 339 h = MINIMUM_HEIGHT; 340 } 341 342 final int maxW = getLWGC().getMaxTextureWidth(); 343 final int maxH = getLWGC().getMaxTextureHeight(); 344 345 if (w > maxW) { 346 w = maxW; 347 } 348 if (h > maxH) { 349 h = maxH; 350 } 351 352 return new Rectangle(x, y, w, h); 353 } 354 355 @Override 356 public Point getLocationOnScreen() { 357 return platformWindow.getLocationOnScreen(); 358 } 359 360 /** 361 * Overridden from LWContainerPeer to return the correct insets. 362 * Insets are queried from the delegate and are kept up to date by 363 * requiering when needed (i.e. when the window geometry is changed). 364 */ 365 @Override 366 public Insets getInsets() { 367 synchronized (getStateLock()) { 368 return insets; 369 } 370 } 371 372 @Override 373 public FontMetrics getFontMetrics(Font f) { 374 // TODO: check for "use platform metrics" settings 375 return platformWindow.getFontMetrics(f); 376 } 377 378 @Override 379 public void toFront() { 380 platformWindow.toFront(); 381 } 382 383 @Override 384 public void toBack() { 385 platformWindow.toBack(); 386 } 387 388 @Override 389 public void setZOrder(ComponentPeer above) { 390 throw new RuntimeException("not implemented"); 391 } 392 393 @Override 394 public void updateAlwaysOnTopState() { 395 platformWindow.setAlwaysOnTop(getTarget().isAlwaysOnTop()); 396 } 397 398 @Override 399 public void updateFocusableWindowState() { 400 platformWindow.updateFocusableWindowState(); 401 } 402 403 @Override 404 public void setModalBlocked(Dialog blocker, boolean blocked) { 405 synchronized (getPeerTreeLock()) { 406 ComponentPeer peer = AWTAccessor.getComponentAccessor().getPeer(blocker); 407 if (blocked && (peer instanceof LWWindowPeer)) { 408 this.blocker = (LWWindowPeer) peer; 409 } else { 410 this.blocker = null; 411 } 412 } 413 414 platformWindow.setModalBlocked(blocked); 415 } 416 417 @Override 418 public void updateMinimumSize() { 419 final Dimension min; 420 if (getTarget().isMinimumSizeSet()) { 421 min = getTarget().getMinimumSize(); 422 min.width = Math.max(min.width, MINIMUM_WIDTH); 423 min.height = Math.max(min.height, MINIMUM_HEIGHT); 424 } else { 425 min = new Dimension(MINIMUM_WIDTH, MINIMUM_HEIGHT); 426 } 427 428 final Dimension max; 429 if (getTarget().isMaximumSizeSet()) { 430 max = getTarget().getMaximumSize(); 431 max.width = Math.min(max.width, getLWGC().getMaxTextureWidth()); 432 max.height = Math.min(max.height, getLWGC().getMaxTextureHeight()); 433 } else { 434 max = new Dimension(getLWGC().getMaxTextureWidth(), 435 getLWGC().getMaxTextureHeight()); 436 } 437 438 platformWindow.setSizeConstraints(min.width, min.height, max.width, max.height); 439 } 440 441 @Override 442 public void updateIconImages() { 443 getPlatformWindow().updateIconImages(); 444 } 445 446 @Override 447 public void setOpacity(float opacity) { 448 getPlatformWindow().setOpacity(opacity); 449 repaintPeer(); 450 } 451 452 @Override 453 public final void setOpaque(final boolean isOpaque) { 454 if (this.isOpaque != isOpaque) { 455 this.isOpaque = isOpaque; 456 updateOpaque(); 457 } 458 } 459 460 private void updateOpaque() { 461 getPlatformWindow().setOpaque(!isTranslucent()); 462 replaceSurfaceData(false); 463 repaintPeer(); 464 } 465 466 @Override 467 public void updateWindow() { 468 } 469 470 public final boolean isTextured() { 471 return textured; 472 } 473 474 public final void setTextured(final boolean isTextured) { 475 textured = isTextured; 476 } 477 478 @Override 479 public final boolean isTranslucent() { 480 synchronized (getStateLock()) { 481 /* 482 * Textured window is a special case of translucent window. 483 * The difference is only in nswindow background. So when we set 484 * texture property our peer became fully translucent. It doesn't 485 * fill background, create non opaque backbuffers and layer etc. 486 */ 487 return !isOpaque || isShaped() || isTextured(); 488 } 489 } 490 491 @Override 492 final void applyShapeImpl(final Region shape) { 493 super.applyShapeImpl(shape); 494 updateOpaque(); 495 } 496 497 @Override 498 public void repositionSecurityWarning() { 499 if (warningWindow != null) { 500 AWTAccessor.ComponentAccessor compAccessor = AWTAccessor.getComponentAccessor(); 501 Window target = getTarget(); 502 int x = compAccessor.getX(target); 503 int y = compAccessor.getY(target); 504 int width = compAccessor.getWidth(target); 505 int height = compAccessor.getHeight(target); 506 warningWindow.reposition(x, y, width, height); 507 } 508 } 509 510 // ---- FRAME PEER METHODS ---- // 511 512 @Override // FramePeer and DialogPeer 513 public void setTitle(String title) { 514 platformWindow.setTitle(title == null ? "" : title); 515 } 516 517 @Override 518 public void setMenuBar(MenuBar mb) { 519 platformWindow.setMenuBar(mb); 520 } 521 522 @Override // FramePeer and DialogPeer 523 public void setResizable(boolean resizable) { 524 platformWindow.setResizable(resizable); 525 } 526 527 @Override 528 public void setState(int state) { 529 platformWindow.setWindowState(state); 530 } 531 532 @Override 533 public int getState() { 534 return windowState; 535 } 536 537 @Override 538 public void setMaximizedBounds(Rectangle bounds) { 539 // TODO: not implemented 540 } 541 542 @Override 543 public void setBoundsPrivate(int x, int y, int width, int height) { 544 setBounds(x, y, width, height, SET_BOUNDS | NO_EMBEDDED_CHECK); 545 } 546 547 @Override 548 public Rectangle getBoundsPrivate() { 549 throw new RuntimeException("not implemented"); 550 } 551 552 // ---- DIALOG PEER METHODS ---- // 553 554 @Override 555 public void blockWindows(List<Window> windows) { 556 //TODO: LWX will probably need some collectJavaToplevels to speed this up 557 for (Window w : windows) { 558 WindowPeer wp = 559 (WindowPeer) AWTAccessor.getComponentAccessor().getPeer(w); 560 if (wp != null) { 561 wp.setModalBlocked((Dialog)getTarget(), true); 562 } 563 } 564 } 565 566 // ---- PEER NOTIFICATIONS ---- // 567 568 @Override 569 public void notifyIconify(boolean iconify) { 570 //The toplevel target is Frame and states are applicable to it. 571 //Otherwise, the target is Window and it don't have state property. 572 //Hopefully, no such events are posted in the queue so consider the 573 //target as Frame in all cases. 574 575 // REMIND: should we send it anyway if the state not changed since last 576 // time? 577 WindowEvent iconifyEvent = new WindowEvent(getTarget(), 578 iconify ? WindowEvent.WINDOW_ICONIFIED 579 : WindowEvent.WINDOW_DEICONIFIED); 580 postEvent(iconifyEvent); 581 582 int newWindowState = iconify ? Frame.ICONIFIED : Frame.NORMAL; 583 postWindowStateChangedEvent(newWindowState); 584 585 // REMIND: RepaintManager doesn't repaint iconified windows and 586 // hence ignores any repaint request during deiconification. 587 // So, we need to repaint window explicitly when it becomes normal. 588 if (!iconify) { 589 repaintPeer(); 590 } 591 } 592 593 @Override 594 public void notifyZoom(boolean isZoomed) { 595 int newWindowState = isZoomed ? Frame.MAXIMIZED_BOTH : Frame.NORMAL; 596 postWindowStateChangedEvent(newWindowState); 597 } 598 599 /** 600 * Called by the {@code PlatformWindow} when any part of the window should 601 * be repainted. 602 */ 603 @Override 604 public void notifyExpose(final Rectangle r) { 605 repaintPeer(r); 606 } 607 608 /** 609 * Called by the {@code PlatformWindow} when this window is moved/resized by 610 * user or window insets are changed. There's no notifyReshape() in 611 * LWComponentPeer as the only components which could be resized by user are 612 * top-level windows. 613 */ 614 @Override 615 public void notifyReshape(int x, int y, int w, int h) { 616 Rectangle oldBounds = getBounds(); 617 final boolean invalid = updateInsets(platformWindow.getInsets()); 618 final boolean moved = (x != oldBounds.x) || (y != oldBounds.y); 619 final boolean resized = (w != oldBounds.width) || (h != oldBounds.height); 620 621 // Check if anything changed 622 if (!moved && !resized && !invalid) { 623 return; 624 } 625 // First, update peer's bounds 626 setBounds(x, y, w, h, SET_BOUNDS, false, false); 627 628 // Second, update the graphics config and surface data 629 final boolean isNewDevice = updateGraphicsDevice(); 630 if (resized || isNewDevice) { 631 replaceSurfaceData(); 632 } 633 634 // Third, COMPONENT_MOVED/COMPONENT_RESIZED/PAINT events 635 if (moved || invalid) { 636 handleMove(x, y, true); 637 } 638 if (resized || invalid || isNewDevice) { 639 handleResize(w, h, true); 640 repaintPeer(); 641 } 642 643 repositionSecurityWarning(); 644 } 645 646 private void clearBackground(final int w, final int h) { 647 final Graphics g = getOnscreenGraphics(getForeground(), getBackground(), 648 getFont()); 649 if (g != null) { 650 try { 651 if (g instanceof Graphics2D) { 652 ((Graphics2D) g).setComposite(AlphaComposite.Src); 653 } 654 if (isTranslucent()) { 655 g.setColor(nonOpaqueBackground); 656 g.fillRect(0, 0, w, h); 657 } 658 if (!isTextured()) { 659 if (g instanceof SunGraphics2D) { 660 ((SunGraphics2D) g).constrain(0, 0, w, h, getRegion()); 661 } 662 g.setColor(getBackground()); 663 g.fillRect(0, 0, w, h); 664 } 665 } finally { 666 g.dispose(); 667 } 668 } 669 } 670 671 @Override 672 public void notifyUpdateCursor() { 673 getLWToolkit().getCursorManager().updateCursorLater(this); 674 } 675 676 @Override 677 public void notifyActivation(boolean activation, LWWindowPeer opposite) { 678 Window oppositeWindow = (opposite == null)? null : opposite.getTarget(); 679 changeFocusedWindow(activation, oppositeWindow); 680 } 681 682 // MouseDown in non-client area 683 @Override 684 public void notifyNCMouseDown() { 685 // Ungrab except for a click on a Dialog with the grabbing owner 686 if (grabbingWindow != null && 687 !grabbingWindow.isOneOfOwnersOf(this)) 688 { 689 grabbingWindow.ungrab(); 690 } 691 } 692 693 // ---- EVENTS ---- // 694 695 /* 696 * Called by the delegate to dispatch the event to Java. Event 697 * coordinates are relative to non-client window are, i.e. the top-left 698 * point of the client area is (insets.top, insets.left). 699 */ 700 @Override 701 public void notifyMouseEvent(int id, long when, int button, 702 int x, int y, int screenX, int screenY, 703 int modifiers, int clickCount, boolean popupTrigger, 704 byte[] bdata) 705 { 706 // TODO: fill "bdata" member of AWTEvent 707 Rectangle r = getBounds(); 708 // findPeerAt() expects parent coordinates 709 LWComponentPeer<?, ?> targetPeer = findPeerAt(r.x + x, r.y + y); 710 711 if (id == MouseEvent.MOUSE_EXITED) { 712 isMouseOver = false; 713 if (lastMouseEventPeer != null) { 714 if (lastMouseEventPeer.isEnabled()) { 715 Point lp = lastMouseEventPeer.windowToLocal(x, y, 716 this); 717 Component target = lastMouseEventPeer.getTarget(); 718 postMouseExitedEvent(target, when, modifiers, lp, 719 screenX, screenY, clickCount, popupTrigger, button); 720 } 721 722 // Sometimes we may get MOUSE_EXITED after lastCommonMouseEventPeer is switched 723 // to a peer from another window. So we must first check if this peer is 724 // the same as lastWindowPeer 725 if (lastCommonMouseEventPeer != null && lastCommonMouseEventPeer.getWindowPeerOrSelf() == this) { 726 lastCommonMouseEventPeer = null; 727 } 728 lastMouseEventPeer = null; 729 } 730 } else if(id == MouseEvent.MOUSE_ENTERED) { 731 isMouseOver = true; 732 if (targetPeer != null) { 733 if (targetPeer.isEnabled()) { 734 Point lp = targetPeer.windowToLocal(x, y, this); 735 Component target = targetPeer.getTarget(); 736 postMouseEnteredEvent(target, when, modifiers, lp, 737 screenX, screenY, clickCount, popupTrigger, button); 738 } 739 lastCommonMouseEventPeer = targetPeer; 740 lastMouseEventPeer = targetPeer; 741 } 742 } else { 743 PlatformWindow topmostPlatforWindow = 744 platformWindow.getTopmostPlatformWindowUnderMouse(); 745 746 LWWindowPeer topmostWindowPeer = 747 topmostPlatforWindow != null ? topmostPlatforWindow.getPeer() : null; 748 749 // topmostWindowPeer == null condition is added for the backward 750 // compatibility with applets. It can be removed when the 751 // getTopmostPlatformWindowUnderMouse() method will be properly 752 // implemented in CPlatformEmbeddedFrame class 753 if (topmostWindowPeer == this || topmostWindowPeer == null) { 754 generateMouseEnterExitEventsForComponents(when, button, x, y, 755 screenX, screenY, modifiers, clickCount, popupTrigger, 756 targetPeer); 757 } else { 758 LWComponentPeer<?, ?> topmostTargetPeer = 759 topmostWindowPeer != null ? topmostWindowPeer.findPeerAt(r.x + x, r.y + y) : null; 760 topmostWindowPeer.generateMouseEnterExitEventsForComponents(when, button, x, y, 761 screenX, screenY, modifiers, clickCount, popupTrigger, 762 topmostTargetPeer); 763 } 764 765 // TODO: fill "bdata" member of AWTEvent 766 767 int eventButtonMask = (button > 0)? MouseEvent.getMaskForButton(button) : 0; 768 int otherButtonsPressed = modifiers & ~eventButtonMask; 769 770 // For pressed/dragged/released events OS X treats other 771 // mouse buttons as if they were BUTTON2, so we do the same 772 int targetIdx = (button > 3) ? MouseEvent.BUTTON2 - 1 : button - 1; 773 774 // MOUSE_ENTERED/EXITED are generated for the components strictly under 775 // mouse even when dragging. That's why we first update lastMouseEventPeer 776 // based on initial targetPeer value and only then recalculate targetPeer 777 // for MOUSE_DRAGGED/RELEASED events 778 if (id == MouseEvent.MOUSE_PRESSED) { 779 780 // Ungrab only if this window is not an owned window of the grabbing one. 781 if (!isGrabbing() && grabbingWindow != null && 782 !grabbingWindow.isOneOfOwnersOf(this)) 783 { 784 grabbingWindow.ungrab(); 785 } 786 if (otherButtonsPressed == 0) { 787 mouseClickButtons = eventButtonMask; 788 } else { 789 mouseClickButtons |= eventButtonMask; 790 } 791 792 // The window should be focused on mouse click. If it gets activated by the native platform, 793 // this request will be no op. It will take effect when: 794 // 1. A simple not focused window is clicked. 795 // 2. An active but not focused owner frame/dialog is clicked. 796 // The mouse event then will trigger a focus request "in window" to the component, so the window 797 // should gain focus before. 798 requestWindowFocus(CausedFocusEvent.Cause.MOUSE_EVENT); 799 800 mouseDownTarget[targetIdx] = targetPeer; 801 } else if (id == MouseEvent.MOUSE_DRAGGED) { 802 // Cocoa dragged event has the information about which mouse 803 // button is being dragged. Use it to determine the peer that 804 // should receive the dragged event. 805 targetPeer = mouseDownTarget[targetIdx]; 806 mouseClickButtons &= ~modifiers; 807 } else if (id == MouseEvent.MOUSE_RELEASED) { 808 // TODO: currently, mouse released event goes to the same component 809 // that received corresponding mouse pressed event. For most cases, 810 // it's OK, however, we need to make sure that our behavior is consistent 811 // with 1.6 for cases where component in question have been 812 // hidden/removed in between of mouse pressed/released events. 813 targetPeer = mouseDownTarget[targetIdx]; 814 815 if ((modifiers & eventButtonMask) == 0) { 816 mouseDownTarget[targetIdx] = null; 817 } 818 819 // mouseClickButtons is updated below, after MOUSE_CLICK is sent 820 } 821 822 if (targetPeer == null) { 823 //TODO This can happen if this window is invisible. this is correct behavior in this case? 824 targetPeer = this; 825 } 826 827 828 Point lp = targetPeer.windowToLocal(x, y, this); 829 if (targetPeer.isEnabled()) { 830 MouseEvent event = new MouseEvent(targetPeer.getTarget(), id, 831 when, modifiers, lp.x, lp.y, 832 screenX, screenY, clickCount, 833 popupTrigger, button); 834 postEvent(event); 835 } 836 837 if (id == MouseEvent.MOUSE_RELEASED) { 838 if ((mouseClickButtons & eventButtonMask) != 0 839 && targetPeer.isEnabled()) { 840 postEvent(new MouseEvent(targetPeer.getTarget(), 841 MouseEvent.MOUSE_CLICKED, 842 when, modifiers, 843 lp.x, lp.y, screenX, screenY, 844 clickCount, popupTrigger, button)); 845 } 846 mouseClickButtons &= ~eventButtonMask; 847 } 848 } 849 notifyUpdateCursor(); 850 } 851 852 private void generateMouseEnterExitEventsForComponents(long when, 853 int button, int x, int y, int screenX, int screenY, 854 int modifiers, int clickCount, boolean popupTrigger, 855 final LWComponentPeer<?, ?> targetPeer) { 856 857 if (!isMouseOver || targetPeer == lastMouseEventPeer) { 858 return; 859 } 860 861 // Generate Mouse Exit for components 862 if (lastMouseEventPeer != null && lastMouseEventPeer.isEnabled()) { 863 Point oldp = lastMouseEventPeer.windowToLocal(x, y, this); 864 Component target = lastMouseEventPeer.getTarget(); 865 postMouseExitedEvent(target, when, modifiers, oldp, screenX, screenY, 866 clickCount, popupTrigger, button); 867 } 868 lastCommonMouseEventPeer = targetPeer; 869 lastMouseEventPeer = targetPeer; 870 871 // Generate Mouse Enter for components 872 if (targetPeer != null && targetPeer.isEnabled()) { 873 Point newp = targetPeer.windowToLocal(x, y, this); 874 Component target = targetPeer.getTarget(); 875 postMouseEnteredEvent(target, when, modifiers, newp, screenX, screenY, clickCount, popupTrigger, button); 876 } 877 } 878 879 private void postMouseEnteredEvent(Component target, long when, int modifiers, 880 Point loc, int xAbs, int yAbs, 881 int clickCount, boolean popupTrigger, int button) { 882 883 updateSecurityWarningVisibility(); 884 885 postEvent(new MouseEvent(target, 886 MouseEvent.MOUSE_ENTERED, 887 when, modifiers, 888 loc.x, loc.y, xAbs, yAbs, 889 clickCount, popupTrigger, button)); 890 } 891 892 private void postMouseExitedEvent(Component target, long when, int modifiers, 893 Point loc, int xAbs, int yAbs, 894 int clickCount, boolean popupTrigger, int button) { 895 896 updateSecurityWarningVisibility(); 897 898 postEvent(new MouseEvent(target, 899 MouseEvent.MOUSE_EXITED, 900 when, modifiers, 901 loc.x, loc.y, xAbs, yAbs, 902 clickCount, popupTrigger, button)); 903 } 904 905 @Override 906 public void notifyMouseWheelEvent(long when, int x, int y, int modifiers, 907 int scrollType, int scrollAmount, 908 int wheelRotation, double preciseWheelRotation, 909 byte[] bdata) 910 { 911 // TODO: could we just use the last mouse event target here? 912 Rectangle r = getBounds(); 913 // findPeerAt() expects parent coordinates 914 final LWComponentPeer<?, ?> targetPeer = findPeerAt(r.x + x, r.y + y); 915 if (targetPeer == null || !targetPeer.isEnabled()) { 916 return; 917 } 918 919 Point lp = targetPeer.windowToLocal(x, y, this); 920 // TODO: fill "bdata" member of AWTEvent 921 // TODO: screenX/screenY 922 postEvent(new MouseWheelEvent(targetPeer.getTarget(), 923 MouseEvent.MOUSE_WHEEL, 924 when, modifiers, 925 lp.x, lp.y, 926 0, 0, /* screenX, Y */ 927 0 /* clickCount */, false /* popupTrigger */, 928 scrollType, scrollAmount, 929 wheelRotation, preciseWheelRotation)); 930 } 931 932 /* 933 * Called by the delegate when a key is pressed. 934 */ 935 @Override 936 public void notifyKeyEvent(int id, long when, int modifiers, 937 int keyCode, char keyChar, int keyLocation) 938 { 939 LWKeyboardFocusManagerPeer kfmPeer = LWKeyboardFocusManagerPeer.getInstance(); 940 Component focusOwner = kfmPeer.getCurrentFocusOwner(); 941 942 if (focusOwner == null) { 943 focusOwner = kfmPeer.getCurrentFocusedWindow(); 944 if (focusOwner == null) { 945 focusOwner = this.getTarget(); 946 } 947 } 948 949 KeyEvent keyEvent = new KeyEvent(focusOwner, id, when, modifiers, 950 keyCode, keyChar, keyLocation); 951 AWTAccessor.getKeyEventAccessor().setExtendedKeyCode(keyEvent, 952 (keyChar == KeyEvent.CHAR_UNDEFINED) ? keyCode 953 : ExtendedKeyCodes.getExtendedKeyCodeForChar(keyChar)); 954 postEvent(keyEvent); 955 } 956 957 // ---- UTILITY METHODS ---- // 958 959 private void activateDisplayListener() { 960 final GraphicsEnvironment ge = 961 GraphicsEnvironment.getLocalGraphicsEnvironment(); 962 ((SunGraphicsEnvironment) ge).addDisplayChangedListener(this); 963 } 964 965 private void deactivateDisplayListener() { 966 final GraphicsEnvironment ge = 967 GraphicsEnvironment.getLocalGraphicsEnvironment(); 968 ((SunGraphicsEnvironment) ge).removeDisplayChangedListener(this); 969 } 970 971 private void postWindowStateChangedEvent(int newWindowState) { 972 if (getTarget() instanceof Frame) { 973 AWTAccessor.getFrameAccessor().setExtendedState( 974 (Frame)getTarget(), newWindowState); 975 } 976 977 WindowEvent stateChangedEvent = new WindowEvent(getTarget(), 978 WindowEvent.WINDOW_STATE_CHANGED, 979 windowState, newWindowState); 980 postEvent(stateChangedEvent); 981 windowState = newWindowState; 982 983 updateSecurityWarningVisibility(); 984 } 985 986 private static int getGraphicsConfigScreen(GraphicsConfiguration gc) { 987 // TODO: this method can be implemented in a more 988 // efficient way by forwarding to the delegate 989 GraphicsDevice gd = gc.getDevice(); 990 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 991 GraphicsDevice[] gds = ge.getScreenDevices(); 992 for (int i = 0; i < gds.length; i++) { 993 if (gds[i] == gd) { 994 return i; 995 } 996 } 997 // Should never happen if gc is a screen device config 998 return 0; 999 } 1000 1001 /* 1002 * This method is called when window's graphics config is changed from 1003 * the app code (e.g. when the window is made non-opaque) or when 1004 * the window is moved to another screen by user. 1005 * 1006 * Returns true if the graphics config has been changed, false otherwise. 1007 */ 1008 private boolean setGraphicsConfig(GraphicsConfiguration gc) { 1009 synchronized (getStateLock()) { 1010 if (graphicsConfig == gc) { 1011 return false; 1012 } 1013 // If window's graphics config is changed from the app code, the 1014 // config correspond to the same device as before; when the window 1015 // is moved by user, graphicsDevice is updated in notifyReshape(). 1016 // In either case, there's nothing to do with screenOn here 1017 graphicsConfig = gc; 1018 } 1019 // SurfaceData is replaced later in updateGraphicsData() 1020 return true; 1021 } 1022 1023 /** 1024 * Returns true if the GraphicsDevice has been changed, false otherwise. 1025 */ 1026 public boolean updateGraphicsDevice() { 1027 GraphicsDevice newGraphicsDevice = platformWindow.getGraphicsDevice(); 1028 synchronized (getStateLock()) { 1029 if (graphicsDevice == newGraphicsDevice) { 1030 return false; 1031 } 1032 graphicsDevice = newGraphicsDevice; 1033 } 1034 1035 final GraphicsConfiguration newGC = newGraphicsDevice.getDefaultConfiguration(); 1036 1037 if (!setGraphicsConfig(newGC)) return false; 1038 1039 SunToolkit.executeOnEventHandlerThread(getTarget(), new Runnable() { 1040 public void run() { 1041 AWTAccessor.getComponentAccessor().setGraphicsConfiguration(getTarget(), newGC); 1042 } 1043 }); 1044 return true; 1045 } 1046 1047 @Override 1048 public final void displayChanged() { 1049 updateGraphicsDevice(); 1050 // Replace surface unconditionally, because internal state of the 1051 // GraphicsDevice could be changed. 1052 replaceSurfaceData(); 1053 repaintPeer(); 1054 } 1055 1056 @Override 1057 public final void paletteChanged() { 1058 // components do not need to react to this event. 1059 } 1060 1061 /* 1062 * May be called by delegate to provide SD to Java2D code. 1063 */ 1064 public SurfaceData getSurfaceData() { 1065 synchronized (surfaceDataLock) { 1066 return surfaceData; 1067 } 1068 } 1069 1070 private void replaceSurfaceData() { 1071 replaceSurfaceData(true); 1072 } 1073 1074 private void replaceSurfaceData(final boolean blit) { 1075 synchronized (surfaceDataLock) { 1076 final SurfaceData oldData = getSurfaceData(); 1077 surfaceData = platformWindow.replaceSurfaceData(); 1078 final Rectangle size = getSize(); 1079 if (getSurfaceData() != null && oldData != getSurfaceData()) { 1080 clearBackground(size.width, size.height); 1081 } 1082 1083 if (blit) { 1084 blitSurfaceData(oldData, getSurfaceData()); 1085 } 1086 1087 if (oldData != null && oldData != getSurfaceData()) { 1088 // TODO: drop oldData for D3D/WGL pipelines 1089 // This can only happen when this peer is being created 1090 oldData.flush(); 1091 } 1092 } 1093 flushOnscreenGraphics(); 1094 } 1095 1096 private void blitSurfaceData(final SurfaceData src, final SurfaceData dst) { 1097 //TODO blit. proof-of-concept 1098 if (src != dst && src != null && dst != null 1099 && !(dst instanceof NullSurfaceData) 1100 && !(src instanceof NullSurfaceData) 1101 && src.getSurfaceType().equals(dst.getSurfaceType()) 1102 && src.getDefaultScale() == dst.getDefaultScale()) { 1103 final Rectangle size = src.getBounds(); 1104 final Blit blit = Blit.locate(src.getSurfaceType(), 1105 CompositeType.Src, 1106 dst.getSurfaceType()); 1107 if (blit != null) { 1108 blit.Blit(src, dst, AlphaComposite.Src, null, 0, 0, 0, 0, 1109 size.width, size.height); 1110 } 1111 } 1112 } 1113 1114 /** 1115 * Request the window insets from the delegate and compares it with the 1116 * current one. This method is mostly called by the delegate, e.g. when the 1117 * window state is changed and insets should be recalculated. 1118 * <p/> 1119 * This method may be called on the toolkit thread. 1120 */ 1121 public final boolean updateInsets(final Insets newInsets) { 1122 synchronized (getStateLock()) { 1123 if (insets.equals(newInsets)) { 1124 return false; 1125 } 1126 insets = newInsets; 1127 } 1128 return true; 1129 } 1130 1131 public static LWWindowPeer getWindowUnderCursor() { 1132 return lastCommonMouseEventPeer != null ? lastCommonMouseEventPeer.getWindowPeerOrSelf() : null; 1133 } 1134 1135 public static LWComponentPeer<?, ?> getPeerUnderCursor() { 1136 return lastCommonMouseEventPeer; 1137 } 1138 1139 /* 1140 * Requests platform to set native focus on a frame/dialog. 1141 * In case of a simple window, triggers appropriate java focus change. 1142 */ 1143 public boolean requestWindowFocus(CausedFocusEvent.Cause cause) { 1144 if (focusLog.isLoggable(PlatformLogger.Level.FINE)) { 1145 focusLog.fine("requesting native focus to " + this); 1146 } 1147 1148 if (!focusAllowedFor()) { 1149 focusLog.fine("focus is not allowed"); 1150 return false; 1151 } 1152 1153 if (platformWindow.rejectFocusRequest(cause)) { 1154 return false; 1155 } 1156 1157 AppContext targetAppContext = AWTAccessor.getComponentAccessor().getAppContext(getTarget()); 1158 KeyboardFocusManager kfm = AWTAccessor.getKeyboardFocusManagerAccessor() 1159 .getCurrentKeyboardFocusManager(targetAppContext); 1160 Window currentActive = kfm.getActiveWindow(); 1161 1162 1163 Window opposite = LWKeyboardFocusManagerPeer.getInstance(). 1164 getCurrentFocusedWindow(); 1165 1166 // Make the owner active window. 1167 if (isSimpleWindow()) { 1168 LWWindowPeer owner = getOwnerFrameDialog(this); 1169 1170 // If owner is not natively active, request native 1171 // activation on it w/o sending events up to java. 1172 if (owner != null && !owner.platformWindow.isActive()) { 1173 if (focusLog.isLoggable(PlatformLogger.Level.FINE)) { 1174 focusLog.fine("requesting native focus to the owner " + owner); 1175 } 1176 LWWindowPeer currentActivePeer = currentActive == null ? null : 1177 (LWWindowPeer) AWTAccessor.getComponentAccessor().getPeer( 1178 currentActive); 1179 1180 // Ensure the opposite is natively active and suppress sending events. 1181 if (currentActivePeer != null && currentActivePeer.platformWindow.isActive()) { 1182 if (focusLog.isLoggable(PlatformLogger.Level.FINE)) { 1183 focusLog.fine("the opposite is " + currentActivePeer); 1184 } 1185 currentActivePeer.skipNextFocusChange = true; 1186 } 1187 owner.skipNextFocusChange = true; 1188 1189 owner.platformWindow.requestWindowFocus(); 1190 } 1191 1192 // DKFM will synthesize all the focus/activation events correctly. 1193 changeFocusedWindow(true, opposite); 1194 return true; 1195 1196 // In case the toplevel is active but not focused, change focus directly, 1197 // as requesting native focus on it will not have effect. 1198 } else if (getTarget() == currentActive && !getTarget().hasFocus()) { 1199 1200 changeFocusedWindow(true, opposite); 1201 return true; 1202 } 1203 1204 return platformWindow.requestWindowFocus(); 1205 } 1206 1207 protected boolean focusAllowedFor() { 1208 Window window = getTarget(); 1209 // TODO: check if modal blocked 1210 return window.isVisible() && window.isEnabled() && isFocusableWindow(); 1211 } 1212 1213 private boolean isFocusableWindow() { 1214 boolean focusable = getTarget().isFocusableWindow(); 1215 if (isSimpleWindow()) { 1216 LWWindowPeer ownerPeer = getOwnerFrameDialog(this); 1217 if (ownerPeer == null) { 1218 return false; 1219 } 1220 return focusable && ownerPeer.getTarget().isFocusableWindow(); 1221 } 1222 return focusable; 1223 } 1224 1225 public boolean isSimpleWindow() { 1226 Window window = getTarget(); 1227 return !(window instanceof Dialog || window instanceof Frame); 1228 } 1229 1230 @Override 1231 public void emulateActivation(boolean activate) { 1232 changeFocusedWindow(activate, null); 1233 } 1234 1235 private boolean isOneOfOwnersOf(LWWindowPeer peer) { 1236 Window owner = (peer != null ? peer.getTarget().getOwner() : null); 1237 while (owner != null) { 1238 if ((LWWindowPeer)owner.getPeer() == this) { 1239 return true; 1240 } 1241 owner = owner.getOwner(); 1242 } 1243 return false; 1244 } 1245 1246 /* 1247 * Changes focused window on java level. 1248 */ 1249 protected void changeFocusedWindow(boolean becomesFocused, Window opposite) { 1250 if (focusLog.isLoggable(PlatformLogger.Level.FINE)) { 1251 focusLog.fine((becomesFocused?"gaining":"loosing") + " focus window: " + this); 1252 } 1253 if (skipNextFocusChange) { 1254 focusLog.fine("skipping focus change"); 1255 skipNextFocusChange = false; 1256 return; 1257 } 1258 if (!isFocusableWindow() && becomesFocused) { 1259 focusLog.fine("the window is not focusable"); 1260 return; 1261 } 1262 if (becomesFocused) { 1263 synchronized (getPeerTreeLock()) { 1264 if (blocker != null) { 1265 if (focusLog.isLoggable(PlatformLogger.Level.FINEST)) { 1266 focusLog.finest("the window is blocked by " + blocker); 1267 } 1268 return; 1269 } 1270 } 1271 } 1272 1273 // Note, the method is not called: 1274 // - when the opposite (gaining focus) window is an owned/owner window. 1275 // - for a simple window in any case. 1276 if (!becomesFocused && 1277 (isGrabbing() || getOwnerFrameDialog(grabbingWindow) == this)) 1278 { 1279 if (focusLog.isLoggable(PlatformLogger.Level.FINE)) { 1280 focusLog.fine("ungrabbing on " + grabbingWindow); 1281 } 1282 // ungrab a simple window if its owner looses activation. 1283 grabbingWindow.ungrab(); 1284 } 1285 1286 KeyboardFocusManagerPeer kfmPeer = LWKeyboardFocusManagerPeer.getInstance(); 1287 kfmPeer.setCurrentFocusedWindow(becomesFocused ? getTarget() : null); 1288 1289 int eventID = becomesFocused ? WindowEvent.WINDOW_GAINED_FOCUS : WindowEvent.WINDOW_LOST_FOCUS; 1290 WindowEvent windowEvent = new TimedWindowEvent(getTarget(), eventID, opposite, System.currentTimeMillis()); 1291 1292 // TODO: wrap in SequencedEvent 1293 postEvent(windowEvent); 1294 } 1295 1296 static LWWindowPeer getOwnerFrameDialog(LWWindowPeer peer) { 1297 Window owner = (peer != null ? peer.getTarget().getOwner() : null); 1298 while (owner != null && !(owner instanceof Frame || owner instanceof Dialog)) { 1299 owner = owner.getOwner(); 1300 } 1301 return owner == null ? null : 1302 (LWWindowPeer) AWTAccessor.getComponentAccessor().getPeer(owner); 1303 } 1304 1305 /** 1306 * Returns the foremost modal blocker of this window, or null. 1307 */ 1308 public LWWindowPeer getBlocker() { 1309 synchronized (getPeerTreeLock()) { 1310 LWWindowPeer blocker = this.blocker; 1311 if (blocker == null) { 1312 return null; 1313 } 1314 while (blocker.blocker != null) { 1315 blocker = blocker.blocker; 1316 } 1317 return blocker; 1318 } 1319 } 1320 1321 @Override 1322 public void enterFullScreenMode() { 1323 platformWindow.enterFullScreenMode(); 1324 updateSecurityWarningVisibility(); 1325 } 1326 1327 @Override 1328 public void exitFullScreenMode() { 1329 platformWindow.exitFullScreenMode(); 1330 updateSecurityWarningVisibility(); 1331 } 1332 1333 public long getLayerPtr() { 1334 return getPlatformWindow().getLayerPtr(); 1335 } 1336 1337 void grab() { 1338 if (grabbingWindow != null && !isGrabbing()) { 1339 grabbingWindow.ungrab(); 1340 } 1341 grabbingWindow = this; 1342 } 1343 1344 final void ungrab(boolean doPost) { 1345 if (isGrabbing()) { 1346 grabbingWindow = null; 1347 if (doPost) { 1348 postEvent(new UngrabEvent(getTarget())); 1349 } 1350 } 1351 } 1352 1353 void ungrab() { 1354 ungrab(true); 1355 } 1356 1357 private boolean isGrabbing() { 1358 return this == grabbingWindow; 1359 } 1360 1361 public PeerType getPeerType() { 1362 return peerType; 1363 } 1364 1365 public void updateSecurityWarningVisibility() { 1366 if (warningWindow == null) { 1367 return; 1368 } 1369 1370 if (!isVisible()) { 1371 return; // The warning window should already be hidden. 1372 } 1373 1374 boolean show = false; 1375 1376 if (!platformWindow.isFullScreenMode()) { 1377 if (isVisible()) { 1378 if (LWKeyboardFocusManagerPeer.getInstance().getCurrentFocusedWindow() == 1379 getTarget()) { 1380 show = true; 1381 } 1382 1383 if (platformWindow.isUnderMouse() || warningWindow.isUnderMouse()) { 1384 show = true; 1385 } 1386 } 1387 } 1388 1389 warningWindow.setVisible(show, true); 1390 } 1391 1392 @Override 1393 public String toString() { 1394 return super.toString() + " [target is " + getTarget() + "]"; 1395 } 1396 }