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