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