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