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