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