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