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