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