--- old/javafx-embed-swing/build-closed.xml 2013-03-12 19:40:14.000000000 +0400 +++ new/javafx-embed-swing/build-closed.xml 2013-03-12 19:40:14.000000000 +0400 @@ -6,7 +6,10 @@ --- old/javafx-embed-swing/project.properties 2013-03-12 19:40:15.000000000 +0400 +++ new/javafx-embed-swing/project.properties 2013-03-12 19:40:15.000000000 +0400 @@ -3,6 +3,9 @@ javac.classpath=\ ${rt.dist.root.dir}/javafx-common/dist/javafx-common.jar:\ ${rt.dist.root.dir}/javafx-ui-common/dist/javafx-ui-common.jar:\ + ${rt.dist.root.dir}/javafx-geom/dist/javafx-geom.jar:\ + ${rt.dist.root.dir}/javafx-sg-common/dist/javafx-sg-common.jar:\ + ${rt.dist.root.dir}/javafx-beans/dist/javafx-beans.jar:\ ${JFXRT_HOME}/lib/ext/jfxrt.jar javac.test.classpath=\ ${javac.classpath}:\ --- old/javafx-embed-swing/src/javafx/embed/swing/SwingEvents.java 2013-03-12 19:40:15.000000000 +0400 +++ new/javafx-embed-swing/src/javafx/embed/swing/SwingEvents.java 2013-03-12 19:40:15.000000000 +0400 @@ -31,6 +31,7 @@ import java.awt.event.MouseWheelEvent; import com.sun.javafx.embed.AbstractEvents; +import javafx.event.EventType; /** * An utility class to translate cursor types between embedded @@ -121,4 +122,105 @@ } return embedModifiers; } + + // FX -> Swing conversion methods + + static int fxMouseEventTypeToMouseID(javafx.scene.input.MouseEvent event) { + EventType type = event.getEventType(); + if (type == javafx.scene.input.MouseEvent.MOUSE_MOVED) { + return MouseEvent.MOUSE_MOVED; + } + if (type == javafx.scene.input.MouseEvent.MOUSE_PRESSED) { + return MouseEvent.MOUSE_PRESSED; + } + if (type == javafx.scene.input.MouseEvent.MOUSE_RELEASED) { + return MouseEvent.MOUSE_RELEASED; + } + if (type == javafx.scene.input.MouseEvent.MOUSE_CLICKED) { + return MouseEvent.MOUSE_CLICKED; + } + if (type == javafx.scene.input.MouseEvent.MOUSE_ENTERED) { + return MouseEvent.MOUSE_ENTERED; + } + if (type == javafx.scene.input.MouseEvent.MOUSE_EXITED) { + return MouseEvent.MOUSE_EXITED; + } + if (type == javafx.scene.input.MouseEvent.MOUSE_DRAGGED) { + return MouseEvent.MOUSE_DRAGGED; + } + if (type == javafx.scene.input.MouseEvent.DRAG_DETECTED) { + return -1; + } + throw new RuntimeException("Unknown MouseEvent type: " + type); + } + + static int fxMouseModsToMouseMods(javafx.scene.input.MouseEvent event) { + int mods = 0; + if (event.isAltDown()) { + mods |= InputEvent.ALT_DOWN_MASK; + } + if (event.isControlDown()) { + mods |= InputEvent.CTRL_DOWN_MASK; + } + if (event.isMetaDown()) { + mods |= InputEvent.META_DOWN_MASK; + } + if (event.isShiftDown()) { + mods |= InputEvent.SHIFT_DOWN_MASK; + } + if (event.isPrimaryButtonDown()) { + mods |= MouseEvent.BUTTON1_DOWN_MASK; + } + if (event.isSecondaryButtonDown()) { + mods |= MouseEvent.BUTTON2_DOWN_MASK; + } + if (event.isMiddleButtonDown()) { + mods |= MouseEvent.BUTTON3_DOWN_MASK; + } + return mods; + } + + static int fxMouseButtonToMouseButton(javafx.scene.input.MouseEvent event) { + switch (event.getButton()) { + case PRIMARY: + return MouseEvent.BUTTON1; + case SECONDARY: + return MouseEvent.BUTTON2; + case MIDDLE: + return MouseEvent.BUTTON3; + } + return 0; + } + + static int fxKeyEventTypeToKeyID(javafx.scene.input.KeyEvent event) { + EventType eventType = event.getEventType(); + if (eventType == javafx.scene.input.KeyEvent.KEY_PRESSED) { + return KeyEvent.KEY_PRESSED; + } + if (eventType == javafx.scene.input.KeyEvent.KEY_RELEASED) { + return KeyEvent.KEY_RELEASED; + } + if (eventType == javafx.scene.input.KeyEvent.KEY_TYPED) { + return KeyEvent.KEY_TYPED; + } + throw new RuntimeException("Unknown KeyEvent type: " + eventType); + } + + static int fxKeyModsToKeyMods(javafx.scene.input.KeyEvent event) { + int mods = 0; + if (event.isAltDown()) { + mods |= InputEvent.ALT_DOWN_MASK; + } + if (event.isControlDown()) { + mods |= InputEvent.CTRL_DOWN_MASK; + } + if (event.isMetaDown()) { + mods |= InputEvent.META_DOWN_MASK; + } + if (event.isShiftDown()) { + mods |= InputEvent.SHIFT_DOWN_MASK; + } + return mods; + } + } --- old/javafx-ui-common/src/com/sun/javafx/tk/DummyToolkit.java 2013-03-12 19:40:16.000000000 +0400 +++ new/javafx-ui-common/src/com/sun/javafx/tk/DummyToolkit.java 2013-03-12 19:40:16.000000000 +0400 @@ -63,6 +63,7 @@ import com.sun.javafx.sg.PGCubicCurve; import com.sun.javafx.sg.PGCylinder; import com.sun.javafx.sg.PGEllipse; +import com.sun.javafx.sg.PGExternalNode; import com.sun.javafx.sg.PGGroup; import com.sun.javafx.sg.PGImageView; import com.sun.javafx.sg.PGLightBase; @@ -379,6 +380,11 @@ } @Override + public PGExternalNode createPGExternalNode() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override public Object createSVGPathObject(SVGPath svgpath) { throw new UnsupportedOperationException("Not supported yet."); } --- old/javafx-ui-common/src/com/sun/javafx/tk/Toolkit.java 2013-03-12 19:40:17.000000000 +0400 +++ new/javafx-ui-common/src/com/sun/javafx/tk/Toolkit.java 2013-03-12 19:40:17.000000000 +0400 @@ -81,6 +81,7 @@ import com.sun.javafx.sg.PGCircle; import com.sun.javafx.sg.PGCubicCurve; import com.sun.javafx.sg.PGEllipse; +import com.sun.javafx.sg.PGExternalNode; import com.sun.javafx.sg.PGGroup; import com.sun.javafx.sg.PGImageView; import com.sun.javafx.sg.PGLine; @@ -657,6 +658,8 @@ public abstract boolean isLightsDirty(); public abstract void setLightsDirty(boolean lightsDirty); + public abstract PGExternalNode createPGExternalNode(); + /** * Tests whether the pixel on the given coordinates in the given image * is non-empty (not fully transparent). Return value is not defined --- old/javafx-ui-quantum/src/com/sun/javafx/tk/quantum/QuantumToolkit.java 2013-03-12 19:40:18.000000000 +0400 +++ new/javafx-ui-quantum/src/com/sun/javafx/tk/quantum/QuantumToolkit.java 2013-03-12 19:40:17.000000000 +0400 @@ -93,6 +93,7 @@ import com.sun.javafx.sg.PGCircle; import com.sun.javafx.sg.PGCubicCurve; import com.sun.javafx.sg.PGEllipse; +import com.sun.javafx.sg.PGExternalNode; import com.sun.javafx.sg.PGGroup; import com.sun.javafx.sg.PGImageView; import com.sun.javafx.sg.PGLine; @@ -187,6 +188,7 @@ import com.sun.javafx.sg.prism.NGPointLight; import com.sun.javafx.sg.prism.NGSphere; import com.sun.javafx.sg.prism.NGTriangleMesh; +import com.sun.javafx.sg.prism.NGExternalNode; public final class QuantumToolkit extends DesktopToolkit implements ToolkitInterface { @@ -1130,6 +1132,10 @@ return new NGText(); } + @Override public PGExternalNode createPGExternalNode() { + return new NGExternalNode(); + } + @Override public Object createSVGPathObject(SVGPath svgpath) { int windingRule = svgpath.getFillRule() == FillRule.NON_ZERO ? PathIterator.WIND_NON_ZERO : PathIterator.WIND_EVEN_ODD; Path2D path = new Path2D(windingRule); --- old/test-stub-toolkit/src/com/sun/javafx/pgstub/StubToolkit.java 2013-03-12 19:40:18.000000000 +0400 +++ new/test-stub-toolkit/src/com/sun/javafx/pgstub/StubToolkit.java 2013-03-12 19:40:18.000000000 +0400 @@ -81,6 +81,7 @@ import com.sun.javafx.sg.PGCubicCurve; import com.sun.javafx.sg.PGCylinder; import com.sun.javafx.sg.PGEllipse; +import com.sun.javafx.sg.PGExternalNode; import com.sun.javafx.sg.PGGroup; import com.sun.javafx.sg.PGImageView; import com.sun.javafx.sg.PGLightBase; @@ -438,6 +439,10 @@ return new StubText(); } + @Override public PGExternalNode createPGExternalNode() { + return new StubExternalNode(); + } + /* * additional testing functions */ --- /dev/null 2013-03-12 19:40:19.000000000 +0400 +++ new/javafx-embed-swing/src/javafx/embed/swing/SwingNode.java 2013-03-12 19:40:19.000000000 +0400 @@ -0,0 +1,626 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javafx.embed.swing; + +import com.sun.javafx.geom.BaseBounds; +import com.sun.javafx.geom.transform.BaseTransform; +import com.sun.javafx.jmx.MXNodeAlgorithm; +import com.sun.javafx.jmx.MXNodeAlgorithmContext; +import com.sun.javafx.scene.DirtyBits; +import com.sun.javafx.scene.traversal.Direction; +import com.sun.javafx.sg.PGNode; +import com.sun.javafx.sg.PGExternalNode; +import com.sun.javafx.stage.FocusUngrabEvent; + +import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.beans.value.ChangeListener; +import javafx.geometry.Point2D; +import javafx.scene.input.KeyCode; + +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import java.awt.AWTEvent; +import java.awt.EventQueue; +import java.awt.Toolkit; + +import java.nio.IntBuffer; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import javafx.scene.Scene; +import javafx.stage.Window; +import sun.awt.UngrabEvent; +import sun.swing.LightweightContent; +import sun.swing.JLightweightFrame; + +/** + * This class is used to embed a Swing content into a JavaFX application. + * The content to be displayed is specified with the {@link #setContent} method + * that accepts an instance of Swing {@code JComponent}. The hierarchy of components + * contained in the {@code JComponent} instance should not contain any heavyweight + * components, otherwise {@code SwingNode} may fail to paint it. The content gets + * repainted automatically. All the input and focus events are forwarded to the + * {@code JComponent} instance transparently to the developer. + *

+ * Here is a typical pattern which demonstrates how {@code SwingNode} can be used: + *

+ *     public class SwingFx extends Application {
+ *
+ *         private SwingNode swingNode;
+ *
+ *         @Override
+ *         public void start(Stage stage) {
+ *             swingNode = new SwingNode();
+ *
+ *             createAndSetSwingContent();
+ *
+ *             StackPane pane = new StackPane();
+ *             pane.getChildren().add(swingNode);
+ *
+ *             stage.setScene(new Scene(pane, 100, 50));
+ *             stage.show();
+ *         }
+ *
+ *         private void createAndSetSwingContent() {
+ *             SwingUtilities.invokeLater(new Runnable() {
+ *                 @Override
+ *                 public void run() {
+ *                     swingNode.setContent(new JButton("Click me!"));
+ *                 }
+ *             });
+ *         }
+ *     }
+ * 
+ */ +public class SwingNode extends Node { + + private double width; + private double height; + + private volatile JComponent content; + private SwingNodeContent contentProvider; + private JLightweightFrame lwFrame; + + private volatile PGExternalNode peer; + + private final ReentrantLock paintLock = new ReentrantLock(); + + private boolean skipBackwardUnrgabNotification; + + /** + * Constructs a new instance of {@code SwingNode}. + */ + public SwingNode() { + setFocusTraversable(true); + setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler()); + setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler()); + + focusedProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, final Boolean newValue) { + activateLwFrame(newValue); + } + }); + } + + /** + * Attaches a {@code JComponent} instance to display in this {@code SwingNode}. + *

+ * The method can be called either on the JavaFX Application thread or the Swing thread. + * Note however, that access to a Swing component must occur from the Swing thread according + * to the Swing threading restrictions. + * + * @param content a Swing component to display in this {@code SwingNode} + * + * @see java.awt.EventQueue#isDispatchThread() + * @see javafx.application.Platform#isFxApplicationThread() + */ + public void setContent(final JComponent content) { + this.content = content; + + invokeOnEDT(new Runnable() { + @Override + public void run() { + setContentImpl(content); + } + }); + } + + /** + * Returns the {@code JComponent} instance attached to this {@code SwingNode}. + *

+ * The method can be called either on the JavaFX Application thread or the Swing thread. + * Note however, that access to a Swing component must occur from the Swing thread according + * to the Swing threading restrictions. + * + * @see java.awt.EventQueue#isDispatchThread() + * @see javafx.application.Platform#isFxApplicationThread() + * + * @return the Swing component attached to this {@code SwingNode} + */ + public JComponent getContent() { + return content; + } + + /* + * Called on Swing thread + */ + private void setContentImpl(JComponent content) { + if (lwFrame != null) { + lwFrame.dispose(); + lwFrame = null; + } + if (content != null) { + lwFrame = new JLightweightFrame(); + contentProvider = new SwingNodeContent(content); + lwFrame.setContent(contentProvider); + lwFrame.setVisible(true); + + locateLwFrame(); // initialize location + + if (focusedProperty().get()) { + activateLwFrame(true); + } + } + } + + private List peerRequests = new ArrayList<>(); + + /* + * Called on Swing thread + */ + void setImageBuffer(final int[] data, + final int x, final int y, + final int w, final int h, + final int linestride) + { + Runnable r = new Runnable() { + @Override + public void run() { + peer.setImageBuffer(IntBuffer.wrap(data), x, y, w, h, linestride); + impl_markDirty(DirtyBits.NODE_CONTENTS); + } + }; + if (peer != null) { + Platform.runLater(r); + } else { + peerRequests.clear(); + peerRequests.add(r); + } + } + + /* + * Called on Swing thread + */ + void setImageBounds(final int x, final int y, final int w, final int h) { + Runnable r = new Runnable() { + @Override + public void run() { + peer.setImageBounds(x, y, w, h); + impl_markDirty(DirtyBits.NODE_CONTENTS); + } + }; + if (peer != null) { + Platform.runLater(r); + } else { + peerRequests.add(r); + } + } + + /* + * Called on Swing thread + */ + void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) { + Runnable r = new Runnable() { + @Override + public void run() { + peer.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); + impl_markDirty(DirtyBits.NODE_CONTENTS); + } + }; + if (peer != null) { + Platform.runLater(r); + } else { + peerRequests.add(r); + } + } + + @Override public boolean isResizable() { + return true; + } + + @Override public void resize(final double width, final double height) { + this.width = width; + this.height = height; + super.resize(width, height); + impl_geomChanged(); + impl_markDirty(DirtyBits.NODE_GEOMETRY); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (lwFrame != null) { + lwFrame.setSize((int)width, (int)height); + } + } + }); + } + + @Override + public double maxWidth(double height) { + return Double.MAX_VALUE; + } + + @Override + public double maxHeight(double width) { + return Double.MAX_VALUE; + } + + @Override + public double prefWidth(double height) { + return -1; + } + + @Override + public double prefHeight(double width) { + return -1; + } + + @Override + public double minWidth(double height) { + return 0; + } + + @Override + public double minHeight(double width) { + return 0; + } + + @Override + protected boolean impl_computeContains(double localX, double localY) { + return true; + } + + private InvalidationListener locationListener = new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + locateLwFrame(); + } + }; + + private EventHandler ungrabHandler = new EventHandler() { + @Override + public void handle(FocusUngrabEvent event) { + if (!skipBackwardUnrgabNotification) { + AccessController.doPrivileged(new PostEventAction(new UngrabEvent(lwFrame))); + } + } + }; + + private ChangeListener windowVisibleListener = new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { + if (!newValue) { + disposeLwFrame(); + + } else { + setContent(content); + } + } + }; + + private void removeListeners(Scene scene) { + Window window = scene.getWindow(); + if (window != null) { + window.xProperty().removeListener(locationListener); + window.yProperty().removeListener(locationListener); + window.removeEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); + window.showingProperty().removeListener(windowVisibleListener); + } + } + + private void addListeners(Scene scene) { + Window window = scene.getWindow(); + if (window != null) { + window.xProperty().addListener(locationListener); + window.yProperty().addListener(locationListener); + window.addEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); + window.showingProperty().addListener(windowVisibleListener); + } + } + + @Override + protected PGNode impl_createPGNode() { + peer = com.sun.javafx.tk.Toolkit.getToolkit().createPGExternalNode(); + peer.setLock(paintLock); + for (Runnable request : peerRequests) { + request.run(); + } + peerRequests = null; + + if (content != null) { + setContent(content); // in case the Node is re-added to Scene + } + addListeners(getScene()); + + sceneProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Scene oldValue, Scene newValue) { + // Removed from scene, or added to another scene. + // The lwFrame will be recreated from impl_createPGNode(). + removeListeners(oldValue); + disposeLwFrame(); + } + }); + + impl_treeVisibleProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { + setLwFrameVisible(newValue); + } + }); + + return peer; + } + + @Override + public void impl_updatePG() { + super.impl_updatePG(); + + if (impl_isDirty(DirtyBits.NODE_VISIBLE)) { + locateLwFrame(); // initialize location + } + } + + private void locateLwFrame() { + if (getScene() == null || lwFrame == null) { + return; + } + final Point2D loc = localToScene(0, 0); + final int windowX = (int)getScene().getWindow().getX(); + final int windowY = (int)getScene().getWindow().getY(); + final int sceneX = (int)getScene().getX(); + final int sceneY = (int)getScene().getY(); + + invokeOnEDT(new Runnable() { + @Override + public void run() { + if (lwFrame != null) { + lwFrame.setLocation(windowX + sceneX + (int)loc.getX(), + windowY + sceneY + (int)loc.getY()); + } + } + }); + } + + private void activateLwFrame(final boolean activate) { + if (lwFrame == null) { + return; + } + invokeOnEDT(new Runnable() { + @Override + public void run() { + if (lwFrame != null) { + lwFrame.emulateActivation(activate); + } + } + }); + } + + private void disposeLwFrame() { + if (lwFrame == null) { + return; + } + invokeOnEDT(new Runnable() { + @Override + public void run() { + if (lwFrame != null) { + lwFrame.dispose(); + lwFrame = null; + } + } + }); + } + + private void setLwFrameVisible(final boolean visible) { + if (lwFrame == null) { + return; + } + invokeOnEDT(new Runnable() { + @Override + public void run() { + if (lwFrame != null) { + lwFrame.setVisible(visible); + } + } + }); + } + + @Override + public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { + bounds.deriveWithNewBounds(0, 0, 0, (float)width, (float)height, 0); + tx.transform(bounds, bounds); + return bounds; + } + + @Override + public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) { + return alg.processLeafNode(this, ctx); + } + + private class SwingNodeContent implements LightweightContent { + private JComponent comp; + public SwingNodeContent(JComponent comp) { + this.comp = comp; + } + @Override + public JComponent getComponent() { + return comp; + } + @Override + public void paintLock() { + paintLock.lock(); + } + @Override + public void paintUnlock() { + paintLock.unlock(); + } + @Override + public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride) { + SwingNode.this.setImageBuffer(data, x, y, width, height, linestride); + } + @Override + public void imageReshaped(int x, int y, int width, int height) { + SwingNode.this.setImageBounds(x, y, width, height); + } + @Override + public void imageUpdated(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) { + SwingNode.this.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); + } + @Override + public void focusGrabbed() { + Platform.runLater(new Runnable() { + @Override + public void run() { + if (getScene() != null && getScene().getWindow() != null) { + getScene().getWindow().impl_getPeer().grabFocus(); + } + } + }); + } + @Override + public void focusUngrabbed() { + Platform.runLater(new Runnable() { + @Override + public void run() { + if (getScene() != null && getScene().getWindow() != null) { + skipBackwardUnrgabNotification = true; + getScene().getWindow().impl_getPeer().ungrabFocus(); + skipBackwardUnrgabNotification = false; + } + } + }); + } + } + + private class PostEventAction implements PrivilegedAction { + private AWTEvent event; + public PostEventAction(AWTEvent event) { + this.event = event; + } + @Override + public Void run() { + EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); + eq.postEvent(event); + return null; + } + } + + private class SwingMouseEventHandler implements EventHandler { + @Override + public void handle(MouseEvent event) { + if (event.getEventType() == MouseEvent.MOUSE_PRESSED && + !SwingNode.this.isFocused() && SwingNode.this.isFocusTraversable()) + { + SwingNode.this.requestFocus(); + } + int swingID = SwingEvents.fxMouseEventTypeToMouseID(event); + if (swingID < 0) { + return; + } + int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event); + // TODO: popupTrigger + boolean swingPopupTrigger = event.getButton() == MouseButton.SECONDARY; + int swingButton = SwingEvents.fxMouseButtonToMouseButton(event); + long swingWhen = System.currentTimeMillis(); + java.awt.event.MouseEvent mouseEvent = + new java.awt.event.MouseEvent( + lwFrame, swingID, swingWhen, swingModifiers, + (int)event.getX(), (int)event.getY(), (int)event.getScreenX(), (int)event.getSceneY(), + event.getClickCount(), swingPopupTrigger, swingButton); + AccessController.doPrivileged(new PostEventAction(mouseEvent)); + } + } + + private class SwingKeyEventHandler implements EventHandler { + @Override + public void handle(KeyEvent event) { + if (event.getCharacter().isEmpty()) { + // TODO: should we post an "empty" character? + return; + } + // Let Ctrl+Tab, Shift+Strl+Tab traverse focus out. + if (event.getCode() == KeyCode.TAB && event.isControlDown()) { + Direction d = event.isShiftDown() ? Direction.PREVIOUS : Direction.NEXT; + getParent().getImpl_traversalEngine().trav(SwingNode.this, d); + return; + } + // Don't let Arrows, Tab, Shift+Tab traverse focus out. + if (event.getCode() == KeyCode.LEFT || + event.getCode() == KeyCode.RIGHT || + event.getCode() == KeyCode.TAB) + { + event.consume(); + } + + int swingID = SwingEvents.fxKeyEventTypeToKeyID(event); + if (swingID < 0) { + return; + } + int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event); + int swingKeyCode = event.getCode().impl_getCode(); + char swingChar = event.getCharacter().charAt(0); + long swingWhen = System.currentTimeMillis(); + java.awt.event.KeyEvent keyEvent = new java.awt.event.KeyEvent( + lwFrame, swingID, swingWhen, swingModifiers, + swingKeyCode, swingChar); + AccessController.doPrivileged(new PostEventAction(keyEvent)); + } + } + + private static void invokeOnEDT(final Runnable r) { + if (SwingUtilities.isEventDispatchThread()) { + r.run(); + } else { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + r.run(); + } + }); + } + } +} --- /dev/null 2013-03-12 19:40:20.000000000 +0400 +++ new/javafx-sg-common/src/com/sun/javafx/sg/PGExternalNode.java 2013-03-12 19:40:19.000000000 +0400 @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javafx.sg; + +import java.nio.Buffer; +import java.util.concurrent.locks.ReentrantLock; + +public interface PGExternalNode extends PGNode { + public void setLock(ReentrantLock lock); + + public void setImageBuffer(Buffer buffer, int x, int y, int width, int height, int linestride); + + public void setImageBounds(int x, int y, int width, int height); + + public void repaintDirtyRegion(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight); +} --- /dev/null 2013-03-12 19:40:20.000000000 +0400 +++ new/javafx-sg-prism/src/com/sun/javafx/sg/prism/NGExternalNode.java 2013-03-12 19:40:20.000000000 +0400 @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javafx.sg.prism; + +import com.sun.javafx.geom.Rectangle; +import com.sun.javafx.sg.PGExternalNode; + +import com.sun.prism.Graphics; +import com.sun.prism.PixelFormat; +import com.sun.prism.ResourceFactory; +import com.sun.prism.Texture; + +import java.nio.Buffer; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class NGExternalNode extends NGNode implements PGExternalNode { + + // pixel buffer of the source image + volatile private Buffer srcbuffer; + + // line stride of the source buffer + volatile private int linestride; + + // source image bounds + volatile private int srcx; + volatile private int srcy; + volatile private int srcwidth; + volatile private int srcheight; + + // of the same size as the source image buffer + private Texture dsttexture; + + // size of the source image buffer + volatile private int dstwidth; + volatile private int dstheight; + + // when the content shrinks, we need to clear the target + volatile private boolean clearTarget = false; + + // relative to the [srcx, srcy] + volatile private Rectangle dirtyRect; + volatile private boolean isDirty; + + volatile private Lock paintLock; + + @Override + protected void renderContent(Graphics g) { + paintLock.lock(); + try { + if (srcbuffer == null) { + // the buffer may be initialized with some delay, asynchronously + return; + } + if ((dsttexture == null) || + (dsttexture.getContentWidth() != dstwidth) || + (dsttexture.getContentHeight() != dstheight)) + { + ResourceFactory factory = g.getResourceFactory(); + if (!factory.isDeviceReady()) { + System.err.println("NGExternalNode: graphics device is not ready"); + return; + } + if (dsttexture != null) { + dsttexture.dispose(); + } + dsttexture = + factory.createTexture(PixelFormat.INT_ARGB_PRE, + Texture.Usage.DYNAMIC, + Texture.WrapMode.CLAMP_NOT_NEEDED, + dstwidth, dstheight); + + if (dsttexture == null) { + System.err.println("NGExternalNode: failed to create a texture"); + return; + } + } + if (dirtyRect == null) return; + + dsttexture.update(srcbuffer, + PixelFormat.INT_ARGB_PRE, + srcx + dirtyRect.x, srcy + dirtyRect.y, // dst + srcx + dirtyRect.x, srcy + dirtyRect.y, dirtyRect.width, dirtyRect.height, // src + linestride * 4, + false); + + if (clearTarget) { + clearTarget = false; + g.clear(); + } + g.drawTexture(dsttexture, + srcx, srcy, srcx + srcwidth, srcy + srcheight, // dst + srcx, srcy, srcx + srcwidth, srcy + srcheight); // src + + isDirty = false; + + } finally { + paintLock.unlock(); + } + } + + @Override + public void setLock(ReentrantLock lock) { + this.paintLock = lock; + } + + @Override + public void setImageBuffer(Buffer buffer, int x, int y, int width, int height, int linestride) { + paintLock.lock(); + try { + this.srcbuffer = buffer; + + this.srcx = x; + this.srcy = y; + this.srcwidth = width; + this.srcheight = height; + + this.linestride = linestride; + + this.dstwidth = linestride; + this.dstheight = buffer.capacity() / linestride; + + dirtyRect = new Rectangle(x, y, width, height); + + } finally { + paintLock.unlock(); + } + } + + @Override + public void setImageBounds(int x, int y, int width, int height) { + paintLock.lock(); + try { + // content shrinked + if (width < srcwidth || height < srcheight) { + clearTarget = true; + } + this.srcx = x; + this.srcy = y; + this.srcwidth = width; + this.srcheight = height; + + dirtyRect.setBounds(x, y, width, height); + + } finally { + paintLock.unlock(); + } + } + + @Override + public void repaintDirtyRegion(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) { + paintLock.lock(); + try { + if (isDirty) { + dirtyRect.add(new Rectangle(dirtyX, dirtyY, dirtyWidth, dirtyHeight)); + } else { + dirtyRect.setBounds(dirtyX, dirtyY, dirtyWidth, dirtyHeight); + } + // System.out.println("NGExternalNode.repaintDirtyRegion: " + dirtyRect); + + isDirty = true; + visualsChanged(); + + } finally { + paintLock.unlock(); + } + } + + @Override + protected boolean hasOverlappingContents() { return false; } +} --- /dev/null 2013-03-12 19:40:21.000000000 +0400 +++ new/test-stub-toolkit/src/com/sun/javafx/pgstub/StubExternalNode.java 2013-03-12 19:40:21.000000000 +0400 @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.javafx.pgstub; + +import com.sun.javafx.sg.PGExternalNode; + +import java.nio.Buffer; +import java.util.concurrent.locks.ReentrantLock; + +public class StubExternalNode extends StubNode implements PGExternalNode { + @Override + public void setLock(ReentrantLock lock) {} + + @Override + public void setImageBuffer(Buffer buffer, int x, int y, int width, int height, int linestride) {} + + @Override + public void setImageBounds(int x, int y, int width, int height) {} + + @Override + public void repaintDirtyRegion(int x, int y, int width, int height) {} +}