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