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