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