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