1 /*
2 * Copyright (c) 2010, 2019, 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 javafx.embed.swing;
27
28 import java.awt.AlphaComposite;
29 import java.awt.Component;
30 import java.awt.Cursor;
31 import java.awt.Dimension;
32 import java.awt.Graphics;
33 import java.awt.Graphics2D;
34 import java.awt.KeyboardFocusManager;
35 import java.awt.Point;
36 import java.awt.Window;
37 import java.awt.Insets;
38 import java.awt.EventQueue;
39 import java.awt.SecondaryLoop;
40 import java.awt.GraphicsEnvironment;
41 import java.awt.event.AWTEventListener;
42 import java.awt.event.ComponentEvent;
43 import java.awt.event.FocusEvent;
44 import java.awt.event.HierarchyEvent;
45 import java.awt.event.InputEvent;
46 import java.awt.event.InputMethodEvent;
47 import java.awt.event.KeyEvent;
48 import java.awt.event.MouseEvent;
49 import java.awt.event.MouseWheelEvent;
50 import java.awt.event.InvocationEvent;
51 import java.awt.im.InputMethodRequests;
52 import java.awt.image.BufferedImage;
53 import java.awt.image.DataBufferInt;
54 import java.nio.IntBuffer;
55 import java.util.concurrent.atomic.AtomicInteger;
56 import java.security.AccessController;
57 import java.security.PrivilegedAction;
58 import javax.swing.JComponent;
59 import javax.swing.SwingUtilities;
60
61 import javafx.application.Platform;
62 import javafx.scene.Scene;
63
64 import com.sun.javafx.application.PlatformImpl;
65 import com.sun.javafx.cursor.CursorFrame;
66 import com.sun.javafx.stage.EmbeddedWindow;
67 import com.sun.javafx.tk.Toolkit;
68 import com.sun.javafx.PlatformUtil;
69
70 import com.sun.javafx.logging.PlatformLogger;
71 import com.sun.javafx.embed.AbstractEvents;
72 import com.sun.javafx.embed.EmbeddedSceneInterface;
73 import com.sun.javafx.embed.EmbeddedStageInterface;
74 import com.sun.javafx.embed.HostInterface;
75
76 import com.sun.javafx.embed.swing.SwingDnD;
77 import com.sun.javafx.embed.swing.SwingEvents;
78 import com.sun.javafx.embed.swing.SwingCursors;
79 import com.sun.javafx.embed.swing.SwingNodeHelper;
80 import com.sun.javafx.embed.swing.newimpl.JFXPanelInteropN;
81
82 /**
83 * {@code JFXPanel} is a component to embed JavaFX content into
84 * Swing applications. The content to be displayed is specified
85 * with the {@link #setScene} method that accepts an instance of
86 * JavaFX {@code Scene}. After the scene is assigned, it gets
87 * repainted automatically. All the input and focus events are
88 * forwarded to the scene transparently to the developer.
89 * <p>
90 * There are some restrictions related to {@code JFXPanel}. As a
91 * Swing component, it should only be accessed from the event
92 * dispatch thread, except the {@link #setScene} method, which can
93 * be called either on the event dispatch thread or on the JavaFX
94 * application thread.
95 * <p>
96 * Here is a typical pattern how {@code JFXPanel} can used:
97 * <pre>
98 * public class Test {
99 *
100 * private static void initAndShowGUI() {
101 * // This method is invoked on Swing thread
102 * JFrame frame = new JFrame("FX");
103 * final JFXPanel fxPanel = new JFXPanel();
104 * frame.add(fxPanel);
105 * frame.setVisible(true);
106 *
107 * Platform.runLater(new Runnable() {
108 * @Override
109 * public void run() {
110 * initFX(fxPanel);
111 * }
112 * });
113 * }
114 *
115 * private static void initFX(JFXPanel fxPanel) {
116 * // This method is invoked on JavaFX thread
117 * Scene scene = createScene();
118 * fxPanel.setScene(scene);
119 * }
120 *
121 * public static void main(String[] args) {
122 * SwingUtilities.invokeLater(new Runnable() {
123 * @Override
124 * public void run() {
125 * initAndShowGUI();
126 * }
127 * });
128 * }
129 * }
130 * </pre>
131 *
132 * @since JavaFX 2.0
133 */
134 public class JFXPanel extends JComponent {
135
136 private final static PlatformLogger log = PlatformLogger.getLogger(JFXPanel.class.getName());
137
138 private static AtomicInteger instanceCount = new AtomicInteger(0);
139 private static PlatformImpl.FinishListener finishListener;
140
141 private transient HostContainer hostContainer;
142
143 private transient volatile EmbeddedWindow stage;
144 private transient volatile Scene scene;
145
146 // Accessed on EDT only
147 private transient SwingDnD dnd;
148
149 private transient EmbeddedStageInterface stagePeer;
150 private transient EmbeddedSceneInterface scenePeer;
151
152 // The logical size of the FX content
153 private int pWidth;
154 private int pHeight;
155
156 // The scale factor, used to translate b/w the logical (the FX content dimension)
157 // and physical (the back buffer's dimension) coordinate spaces
158 private double scaleFactorX = 1.0;
159 private double scaleFactorY = 1.0;
160
161 // Preferred size set from FX
162 private volatile int pPreferredWidth = -1;
163 private volatile int pPreferredHeight = -1;
164
165 // Cached copy of this component's location on screen to avoid
166 // calling getLocationOnScreen() under the tree lock on FX thread
167 private volatile int screenX = 0;
168 private volatile int screenY = 0;
169
170 // Accessed on EDT only
171 private BufferedImage pixelsIm;
172
173 private volatile float opacity = 1.0f;
174
175 // Indicates how many times setFxEnabled(false) has been called.
176 // A value of 0 means the component is enabled.
177 private AtomicInteger disableCount = new AtomicInteger(0);
178
179 private boolean isCapturingMouse = false;
180
181 private static boolean fxInitialized;
182
183 private JFXPanelInteropN jfxPanelIOP;
184
185 private synchronized void registerFinishListener() {
186 if (instanceCount.getAndIncrement() > 0) {
187 // Already registered
188 return;
189 }
190 // Need to install a finish listener to catch calls to Platform.exit
191 finishListener = new PlatformImpl.FinishListener() {
192 @Override public void idle(boolean implicitExit) {
193 }
194 @Override public void exitCalled() {
195 }
196 };
197 PlatformImpl.addListener(finishListener);
198 }
199
200 private synchronized void deregisterFinishListener() {
201 if (instanceCount.decrementAndGet() > 0) {
202 // Other JFXPanels still alive
203 return;
204 }
205 PlatformImpl.removeListener(finishListener);
206 finishListener = null;
207 }
208
209 // Initialize FX runtime when the JFXPanel instance is constructed
210 private synchronized static void initFx() {
211 // Note that calling PlatformImpl.startup more than once is OK
212 if (fxInitialized) {
213 return;
214 }
215 EventQueue eventQueue = AccessController.doPrivileged(
216 (PrivilegedAction<EventQueue>) java.awt.Toolkit
217 .getDefaultToolkit()::getSystemEventQueue);
218 if (eventQueue.isDispatchThread()) {
219 // We won't block EDT by FX initialization
220 SecondaryLoop secondaryLoop = eventQueue.createSecondaryLoop();
221 final Throwable[] th = {null};
222 new Thread(() -> {
223 try {
224 PlatformImpl.startup(() -> {});
225 } catch (Throwable t) {
226 th[0] = t;
227 } finally {
228 secondaryLoop.exit();
229 }
230 }).start();
231 secondaryLoop.enter();
232 if (th[0] != null) {
233 if (th[0] instanceof RuntimeException) {
234 throw (RuntimeException) th[0];
235 } else if (th[0] instanceof Error) {
236 throw (Error) th[0];
237 }
238 throw new RuntimeException("FX initialization failed", th[0]);
239 }
240 } else {
241 PlatformImpl.startup(() -> {});
242 }
243 fxInitialized = true;
244 }
245
246 /**
247 * Creates a new {@code JFXPanel} object.
248 * <p>
249 * <b>Implementation note</b>: when the first {@code JFXPanel} object
250 * is created, it implicitly initializes the JavaFX runtime. This is the
251 * preferred way to initialize JavaFX in Swing.
252 */
253 public JFXPanel() {
254 super();
255
256 jfxPanelIOP = new JFXPanelInteropN();
257 initFx();
258
259 hostContainer = new HostContainer();
260
261 enableEvents(InputEvent.COMPONENT_EVENT_MASK |
262 InputEvent.FOCUS_EVENT_MASK |
263 InputEvent.HIERARCHY_BOUNDS_EVENT_MASK |
264 InputEvent.HIERARCHY_EVENT_MASK |
265 InputEvent.MOUSE_EVENT_MASK |
266 InputEvent.MOUSE_MOTION_EVENT_MASK |
267 InputEvent.MOUSE_WHEEL_EVENT_MASK |
268 InputEvent.KEY_EVENT_MASK |
269 InputEvent.INPUT_METHOD_EVENT_MASK);
270
271 setFocusable(true);
272 setFocusTraversalKeysEnabled(false);
273 }
274
275 /**
276 * Returns the JavaFX scene attached to this {@code JFXPanel}.
277 *
278 * @return the {@code Scene} attached to this {@code JFXPanel}
279 */
280 public Scene getScene() {
281 return scene;
282 }
283
284 /**
285 * Attaches a {@code Scene} object to display in this {@code
286 * JFXPanel}. This method can be called either on the event
287 * dispatch thread or the JavaFX application thread.
288 *
289 * @param newScene a scene to display in this {@code JFXpanel}
290 *
291 * @see java.awt.EventQueue#isDispatchThread()
292 * @see javafx.application.Platform#isFxApplicationThread()
293 */
294 public void setScene(final Scene newScene) {
295 if (Toolkit.getToolkit().isFxUserThread()) {
296 setSceneImpl(newScene);
297 } else {
298 EventQueue eventQueue = AccessController.doPrivileged(
299 (PrivilegedAction<EventQueue>) java.awt.Toolkit
300 .getDefaultToolkit()::getSystemEventQueue);
301 SecondaryLoop secondaryLoop = eventQueue.createSecondaryLoop();
302 if (secondaryLoop.enter()) {
303 Platform.runLater(() -> {
304 setSceneImpl(newScene);
305 });
306 secondaryLoop.exit();
307 }
308 }
309 }
310
311 /*
312 * Called on JavaFX app thread.
313 */
314 private void setSceneImpl(Scene newScene) {
315 if ((stage != null) && (newScene == null)) {
316 stage.hide();
317 stage = null;
318 }
319 scene = newScene;
320 if ((stage == null) && (newScene != null)) {
321 stage = new EmbeddedWindow(hostContainer);
322 }
323 if (stage != null) {
324 stage.setScene(newScene);
325 if (!stage.isShowing()) {
326 stage.show();
327 }
328 }
329 }
330
331 /**
332 * {@code JFXPanel}'s opacity is controlled by the JavaFX content
333 * which is displayed in this component, so this method overrides
334 * {@link javax.swing.JComponent#setOpaque(boolean)} to only accept a
335 * {@code false} value. If this method is called with a {@code true}
336 * value, no action is performed.
337 *
338 * @param opaque must be {@code false}
339 */
340 @Override
341 public final void setOpaque(boolean opaque) {
342 // Don't let user control opacity
343 if (!opaque) {
344 super.setOpaque(opaque);
345 }
346 }
347
348 /**
349 * {@code JFXPanel}'s opacity is controlled by the JavaFX content
350 * which is displayed in this component, so this method overrides
351 * {@link javax.swing.JComponent#isOpaque()} to always return a
352 * {@code false} value.
353 *
354 * @return a {@code false} value
355 */
356 @Override
357 public final boolean isOpaque() {
358 return false;
359 }
360
361 private void sendMouseEventToFX(MouseEvent e) {
362 if (scenePeer == null || !isFxEnabled()) {
363 return;
364 }
365
366 // FX only supports 5 buttons so don't send the event for other buttons
367 switch (e.getID()) {
368 case MouseEvent.MOUSE_DRAGGED:
369 case MouseEvent.MOUSE_PRESSED:
370 case MouseEvent.MOUSE_RELEASED:
371 if (e.getButton() > 5) return;
372 break;
373 }
374
375 int extModifiers = e.getModifiersEx();
376 // Fix for RT-15457: we should report no mouse button upon mouse release, so
377 // *BtnDown values are calculated based on extMofifiers, not e.getButton()
378 boolean primaryBtnDown = (extModifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0;
379 boolean middleBtnDown = (extModifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0;
380 boolean secondaryBtnDown = (extModifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0;
381 boolean backBtnDown = (extModifiers & MouseEvent.getMaskForButton(4)) != 0;
382 boolean forwardBtnDown = (extModifiers & MouseEvent.getMaskForButton(5)) != 0;
383
384 // Fix for RT-16558: if a PRESSED event is consumed, e.g. by a Swing Popup,
385 // subsequent DRAGGED and RELEASED events should not be sent to FX as well
386 if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
387 if (!isCapturingMouse) {
388 return;
389 }
390 } else if (e.getID() == MouseEvent.MOUSE_PRESSED) {
391 isCapturingMouse = true;
392 } else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
393 if (!isCapturingMouse) {
394 return;
395 }
396 isCapturingMouse = primaryBtnDown || middleBtnDown || secondaryBtnDown || backBtnDown || forwardBtnDown;
397 } else if (e.getID() == MouseEvent.MOUSE_CLICKED) {
398 // Don't send click events to FX, as they are generated in Scene
399 return;
400 }
401 // A workaround until JDK-8065131 is fixed.
402 boolean popupTrigger = false;
403 if (e.getID() == MouseEvent.MOUSE_PRESSED || e.getID() == MouseEvent.MOUSE_RELEASED) {
404 popupTrigger = e.isPopupTrigger();
405 }
406
407 if(e.getID() == MouseEvent.MOUSE_WHEEL) {
408 scenePeer.scrollEvent(AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL,
409 0, -SwingEvents.getWheelRotation(e),
410 0, 0, // total scroll
411 40, 40, // multiplier
412 e.getX(), e.getY(),
413 e.getXOnScreen(), e.getYOnScreen(),
414 (extModifiers & MouseEvent.SHIFT_DOWN_MASK) != 0,
415 (extModifiers & MouseEvent.CTRL_DOWN_MASK) != 0,
416 (extModifiers & MouseEvent.ALT_DOWN_MASK) != 0,
417 (extModifiers & MouseEvent.META_DOWN_MASK) != 0, false);
418 } else {
419 scenePeer.mouseEvent(
420 SwingEvents.mouseIDToEmbedMouseType(e.getID()),
421 SwingEvents.mouseButtonToEmbedMouseButton(e.getButton(), extModifiers),
422 primaryBtnDown, middleBtnDown, secondaryBtnDown,
423 backBtnDown, forwardBtnDown,
424 e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(),
425 (extModifiers & MouseEvent.SHIFT_DOWN_MASK) != 0,
426 (extModifiers & MouseEvent.CTRL_DOWN_MASK) != 0,
427 (extModifiers & MouseEvent.ALT_DOWN_MASK) != 0,
428 (extModifiers & MouseEvent.META_DOWN_MASK) != 0,
429 popupTrigger);
430 }
431 if (e.isPopupTrigger()) {
432 scenePeer.menuEvent(e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(), false);
433 }
434 }
435
436 /**
437 * Overrides the {@link java.awt.Component#processMouseEvent(MouseEvent)}
438 * method to dispatch the mouse event to the JavaFX scene attached to this
439 * {@code JFXPanel}.
440 *
441 * @param e the mouse event to dispatch to the JavaFX scene
442 */
443 @Override
444 protected void processMouseEvent(MouseEvent e) {
445 if ((e.getID() == MouseEvent.MOUSE_PRESSED) &&
446 (e.getButton() == MouseEvent.BUTTON1)) {
447 if (isFocusable() && !hasFocus()) {
448 requestFocus();
449 // The extra simulated mouse pressed event is removed by making the JavaFX scene focused.
450 // It is safe, because in JavaFX only the method "setFocused(true)" is called,
451 // which doesn't have any side-effects when called multiple times.
452 if(stagePeer != null) {
453 int focusCause = AbstractEvents.FOCUSEVENT_ACTIVATED;
454 stagePeer.setFocused(true, focusCause);
455 }
456 }
457 }
458
459 sendMouseEventToFX(e);
460 super.processMouseEvent(e);
461 }
462
463 /**
464 * Overrides the {@link java.awt.Component#processMouseMotionEvent(MouseEvent)}
465 * method to dispatch the mouse motion event to the JavaFX scene attached to
466 * this {@code JFXPanel}.
467 *
468 * @param e the mouse motion event to dispatch to the JavaFX scene
469 */
470 @Override
471 protected void processMouseMotionEvent(MouseEvent e) {
472 sendMouseEventToFX(e);
473 super.processMouseMotionEvent(e);
474 }
475
476 /**
477 * Overrides the
478 * {@link java.awt.Component#processMouseWheelEvent(MouseWheelEvent)}
479 * method to dispatch the mouse wheel event to the JavaFX scene attached
480 * to this {@code JFXPanel}.
481 *
482 * @param e the mouse wheel event to dispatch to the JavaFX scene
483 */
484 @Override
485 protected void processMouseWheelEvent(MouseWheelEvent e) {
486 sendMouseEventToFX(e);
487 super.processMouseWheelEvent(e);
488 }
489
490 private void sendKeyEventToFX(final KeyEvent e) {
491 if (scenePeer == null || !isFxEnabled()) {
492 return;
493 }
494
495 char[] chars = (e.getKeyChar() == KeyEvent.CHAR_UNDEFINED)
496 ? new char[] {}
497 : new char[] { SwingEvents.keyCharToEmbedKeyChar(e.getKeyChar()) };
498
499 scenePeer.keyEvent(
500 SwingEvents.keyIDToEmbedKeyType(e.getID()),
501 e.getKeyCode(), chars,
502 SwingEvents.keyModifiersToEmbedKeyModifiers(e.getModifiersEx()));
503 }
504
505 /**
506 * Overrides the {@link java.awt.Component#processKeyEvent(KeyEvent)}
507 * method to dispatch the key event to the JavaFX scene attached to this
508 * {@code JFXPanel}.
509 *
510 * @param e the key event to dispatch to the JavaFX scene
511 */
512 @Override
513 protected void processKeyEvent(KeyEvent e) {
514 sendKeyEventToFX(e);
515 super.processKeyEvent(e);
516 }
517
518 private void sendResizeEventToFX() {
519 if (stagePeer != null) {
520 stagePeer.setSize(pWidth, pHeight);
521 }
522 if (scenePeer != null) {
523 scenePeer.setSize(pWidth, pHeight);
524 }
525 }
526
527 /**
528 * Overrides the
529 * {@link java.awt.Component#processComponentEvent(ComponentEvent)}
530 * method to dispatch {@link java.awt.event.ComponentEvent#COMPONENT_RESIZED}
531 * events to the JavaFX scene attached to this {@code JFXPanel}. The JavaFX
532 * scene object is then resized to match the {@code JFXPanel} size.
533 *
534 * @param e the component event to dispatch to the JavaFX scene
535 */
536 @Override
537 protected void processComponentEvent(ComponentEvent e) {
538 switch (e.getID()) {
539 case ComponentEvent.COMPONENT_RESIZED: {
540 updateComponentSize();
541 break;
542 }
543 case ComponentEvent.COMPONENT_MOVED: {
544 if (updateScreenLocation()) {
545 sendMoveEventToFX();
546 }
547 break;
548 }
549 default: {
550 break;
551 }
552 }
553 super.processComponentEvent(e);
554 }
555
556 // called on EDT only
557 private void updateComponentSize() {
558 int oldWidth = pWidth;
559 int oldHeight = pHeight;
560 // It's quite possible to get negative values here, this is not
561 // what JavaFX embedded scenes/stages are ready to
562 pWidth = Math.max(0, getWidth());
563 pHeight = Math.max(0, getHeight());
564 if (getBorder() != null) {
565 Insets i = getBorder().getBorderInsets(this);
566 pWidth -= (i.left + i.right);
567 pHeight -= (i.top + i.bottom);
568 }
569 double newScaleFactorX = scaleFactorX;
570 double newScaleFactorY = scaleFactorY;
571 Graphics g = getGraphics();
572 newScaleFactorX = GraphicsEnvironment.getLocalGraphicsEnvironment().
573 getDefaultScreenDevice().getDefaultConfiguration().
574 getDefaultTransform().getScaleX();
575 newScaleFactorY = GraphicsEnvironment.getLocalGraphicsEnvironment().
576 getDefaultScreenDevice().getDefaultConfiguration().
577 getDefaultTransform().getScaleY();
578 if (oldWidth != pWidth || oldHeight != pHeight ||
579 newScaleFactorX != scaleFactorX || newScaleFactorY != scaleFactorY)
580 {
581 createResizePixelBuffer(newScaleFactorX, newScaleFactorY);
582 if (scenePeer != null) {
583 scenePeer.setPixelScaleFactors((float) newScaleFactorX,
584 (float) newScaleFactorY);
585 }
586 scaleFactorX = newScaleFactorX;
587 scaleFactorY = newScaleFactorY;
588 sendResizeEventToFX();
589 }
590 }
591
592 // This methods should only be called on EDT
593 private boolean updateScreenLocation() {
594 synchronized (getTreeLock()) {
595 if (isShowing()) {
596 Point p = getLocationOnScreen();
597 screenX = p.x;
598 screenY = p.y;
599 return true;
600 }
601 }
602 return false;
603 }
604
605 private void sendMoveEventToFX() {
606 if (stagePeer == null) {
607 return;
608 }
609
610 stagePeer.setLocation(screenX, screenY);
611 }
612
613 /**
614 * Overrides the
615 * {@link java.awt.Component#processHierarchyBoundsEvent(HierarchyEvent)}
616 * method to process {@link java.awt.event.HierarchyEvent#ANCESTOR_MOVED}
617 * events and update the JavaFX scene location to match the {@code
618 * JFXPanel} location on the screen.
619 *
620 * @param e the hierarchy bounds event to process
621 */
622 @Override
623 protected void processHierarchyBoundsEvent(HierarchyEvent e) {
624 if (e.getID() == HierarchyEvent.ANCESTOR_MOVED) {
625 if (updateScreenLocation()) {
626 sendMoveEventToFX();
627 }
628 }
629 super.processHierarchyBoundsEvent(e);
630 }
631
632 @Override
633 protected void processHierarchyEvent(HierarchyEvent e) {
634 if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
635 if (updateScreenLocation()) {
636 sendMoveEventToFX();
637 }
638 }
639 super.processHierarchyEvent(e);
640 }
641
642 private void sendFocusEventToFX(final FocusEvent e) {
643 if ((stage == null) || (stagePeer == null) || !isFxEnabled()) {
644 return;
645 }
646
647 boolean focused = (e.getID() == FocusEvent.FOCUS_GAINED);
648 int focusCause = (focused ? AbstractEvents.FOCUSEVENT_ACTIVATED :
649 AbstractEvents.FOCUSEVENT_DEACTIVATED);
650
651 if (focused) {
652 if (e.getCause() == FocusEvent.Cause.TRAVERSAL_FORWARD) {
653 focusCause = AbstractEvents.FOCUSEVENT_TRAVERSED_FORWARD;
654 } else if (e.getCause() == FocusEvent.Cause.TRAVERSAL_BACKWARD) {
655 focusCause = AbstractEvents.FOCUSEVENT_TRAVERSED_BACKWARD;
656 }
657 }
658 stagePeer.setFocused(focused, focusCause);
659 }
660
661 /**
662 * Overrides the
663 * {@link java.awt.Component#processFocusEvent(FocusEvent)}
664 * method to dispatch focus events to the JavaFX scene attached to this
665 * {@code JFXPanel}.
666 *
667 * @param e the focus event to dispatch to the JavaFX scene
668 */
669 @Override
670 protected void processFocusEvent(FocusEvent e) {
671 sendFocusEventToFX(e);
672 super.processFocusEvent(e);
673 }
674
675 // called on EDT only
676 private void createResizePixelBuffer(double newScaleFactorX, double newScaleFactorY) {
677 if (scenePeer == null || pWidth <= 0 || pHeight <= 0) {
678 pixelsIm = null;
679 } else {
680 BufferedImage oldIm = pixelsIm;
681 int newPixelW = (int) Math.ceil(pWidth * newScaleFactorX);
682 int newPixelH = (int) Math.ceil(pHeight * newScaleFactorY);
683 pixelsIm = new BufferedImage(newPixelW, newPixelH,
684 SwingFXUtils.getBestBufferedImageType(
685 scenePeer.getPixelFormat(), null, false));
686 if (oldIm != null) {
687 double ratioX = newScaleFactorX / scaleFactorX;
688 double ratioY = newScaleFactorY / scaleFactorY;
689 // Transform old size to the new coordinate space.
690 int oldW = (int)Math.round(oldIm.getWidth() * ratioX);
691 int oldH = (int)Math.round(oldIm.getHeight() * ratioY);
692
693 Graphics g = pixelsIm.getGraphics();
694 try {
695 g.drawImage(oldIm, 0, 0, oldW, oldH, null);
696 } finally {
697 g.dispose();
698 }
699 }
700 }
701 }
702
703 @Override
704 protected void processInputMethodEvent(InputMethodEvent e) {
705 if (e.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) {
706 sendInputMethodEventToFX(e);
707 }
708 super.processInputMethodEvent(e);
709 }
710
711 private void sendInputMethodEventToFX(InputMethodEvent e) {
712 String t = InputMethodSupport.getTextForEvent(e);
713
714 int insertionIndex = 0;
715 if (e.getCaret() != null) {
716 insertionIndex = e.getCaret().getInsertionIndex();
717 }
718 scenePeer.inputMethodEvent(
719 javafx.scene.input.InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
720 InputMethodSupport.inputMethodEventComposed(t, e.getCommittedCharacterCount()),
721 t.substring(0, e.getCommittedCharacterCount()),
722 insertionIndex);
723 }
724
725 /**
726 * Overrides the {@link javax.swing.JComponent#paintComponent(Graphics)}
727 * method to paint the content of the JavaFX scene attached to this
728 * {@code JFXpanel}.
729 *
730 * @param g the Graphics context in which to paint
731 *
732 * @see #isOpaque()
733 */
734 @Override
735 protected void paintComponent(Graphics g) {
736 if (scenePeer == null) {
737 return;
738 }
739 if (pixelsIm == null) {
740 createResizePixelBuffer(scaleFactorX, scaleFactorY);
741 if (pixelsIm == null) {
742 return;
743 }
744 }
745 DataBufferInt dataBuf = (DataBufferInt)pixelsIm.getRaster().getDataBuffer();
746 int[] pixelsData = dataBuf.getData();
747 IntBuffer buf = IntBuffer.wrap(pixelsData);
748 if (!scenePeer.getPixels(buf, pWidth, pHeight)) {
749 // In this case we just render what we have so far in the buffer.
750 }
751
752 Graphics gg = null;
753 try {
754 gg = g.create();
755 if ((opacity < 1.0f) && (gg instanceof Graphics2D)) {
756 Graphics2D g2d = (Graphics2D)gg;
757 AlphaComposite c = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity);
758 g2d.setComposite(c);
759 }
760 if (getBorder() != null) {
761 Insets i = getBorder().getBorderInsets(this);
762 gg.translate(i.left, i.top);
763 }
764 gg.drawImage(pixelsIm, 0, 0, pWidth, pHeight, null);
765
766 double newScaleFactorX = scaleFactorX;
767 double newScaleFactorY = scaleFactorY;
768 newScaleFactorX = GraphicsEnvironment.getLocalGraphicsEnvironment().
769 getDefaultScreenDevice().getDefaultConfiguration().
770 getDefaultTransform().getScaleX();
771 newScaleFactorY = GraphicsEnvironment.getLocalGraphicsEnvironment().
772 getDefaultScreenDevice().getDefaultConfiguration().
773 getDefaultTransform().getScaleY();
774 if (scaleFactorX != newScaleFactorX || scaleFactorY != newScaleFactorY) {
775 createResizePixelBuffer(newScaleFactorX, newScaleFactorY);
776 // The scene will request repaint.
777 scenePeer.setPixelScaleFactors((float) newScaleFactorX,
778 (float) newScaleFactorY);
779 scaleFactorX = newScaleFactorX;
780 scaleFactorY = newScaleFactorY;
781 }
782 } catch (Throwable th) {
783 th.printStackTrace();
784 } finally {
785 if (gg != null) {
786 gg.dispose();
787 }
788 }
789 }
790
791 /**
792 * Returns the preferred size of this {@code JFXPanel}, either
793 * previously set with {@link #setPreferredSize(Dimension)} or
794 * based on the content of the JavaFX scene attached to this {@code
795 * JFXPanel}.
796 *
797 * @return prefSize this {@code JFXPanel} preferred size
798 */
799 @Override
800 public Dimension getPreferredSize() {
801 if (isPreferredSizeSet() || scenePeer == null) {
802 return super.getPreferredSize();
803 }
804 return new Dimension(pPreferredWidth, pPreferredHeight);
805 }
806
807 private boolean isFxEnabled() {
808 return this.disableCount.get() == 0;
809 }
810
811 private void setFxEnabled(boolean enabled) {
812 if (!enabled) {
813 if (disableCount.incrementAndGet() == 1) {
814 if (dnd != null) {
815 dnd.removeNotify();
816 }
817 }
818 } else {
819 if (disableCount.get() == 0) {
820 //should report a warning about an extra enable call ?
821 return;
822 }
823 if (disableCount.decrementAndGet() == 0) {
824 if (dnd != null) {
825 dnd.addNotify();
826 }
827 }
828 }
829 }
830
831 private transient AWTEventListener ungrabListener = event -> {
832 if (jfxPanelIOP.isUngrabEvent(event)) {
833 SwingNodeHelper.runOnFxThread(() -> {
834 if (JFXPanel.this.stagePeer != null &&
835 getScene() != null &&
836 getScene().getFocusOwner() != null &&
837 getScene().getFocusOwner().isFocused()) {
838 JFXPanel.this.stagePeer.focusUngrab();
839 }
840 });
841 }
842 if (event instanceof MouseEvent) {
843 // Synthesize FOCUS_UNGRAB if user clicks the AWT top-level window
844 // that contains the JFXPanel.
845 if (event.getID() == MouseEvent.MOUSE_PRESSED && event.getSource() instanceof Component) {
846 final Window jfxPanelWindow = SwingUtilities.getWindowAncestor(JFXPanel.this);
847 final Component source = (Component)event.getSource();
848 final Window eventWindow = source instanceof Window ? (Window)source : SwingUtilities.getWindowAncestor(source);
849
850 if (jfxPanelWindow == eventWindow) {
851 SwingNodeHelper.runOnFxThread(() -> {
852 if (JFXPanel.this.stagePeer != null) {
853 // No need to check if grab is active or not.
854 // NoAutoHide popups don't request the grab and
855 // ignore the Ungrab event anyway.
856 // AutoHide popups actually should be hidden when
857 // user clicks some non-FX content, even if for
858 // some reason they didn't install the grab when
859 // they were shown.
860 JFXPanel.this.stagePeer.focusUngrab();
861 }
862 });
863 }
864 }
865 }
866 };
867
868 /**
869 * Notifies this component that it now has a parent component. When this
870 * method is invoked, the chain of parent components is set up with
871 * KeyboardAction event listeners.
872 */
873 @Override
874 public void addNotify() {
875 super.addNotify();
876
877 registerFinishListener();
878
879 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
880 JFXPanel.this.getToolkit().addAWTEventListener(ungrabListener,
881 jfxPanelIOP.getMask());
882 return null;
883 });
884 updateComponentSize(); // see RT-23603
885 SwingNodeHelper.runOnFxThread(() -> {
886 if ((stage != null) && !stage.isShowing()) {
887 stage.show();
888 sendMoveEventToFX();
889 }
890 });
891 }
892
893 @Override
894 public InputMethodRequests getInputMethodRequests() {
895 EmbeddedSceneInterface scene = scenePeer;
896 if (scene == null) {
897 return null;
898 }
899 return new InputMethodSupport.InputMethodRequestsAdapter(scene.getInputMethodRequests());
900 }
901
902 /**
903 * Notifies this component that it no longer has a parent component.
904 * When this method is invoked, any KeyboardActions set up in the the
905 * chain of parent components are removed.
906 */
907 @Override public void removeNotify() {
908 SwingNodeHelper.runOnFxThread(() -> {
909 if ((stage != null) && stage.isShowing()) {
910 stage.hide();
911 }
912 });
913
914 pixelsIm = null;
915 pWidth = 0;
916 pHeight = 0;
917
918 super.removeNotify();
919
920 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
921 JFXPanel.this.getToolkit().removeAWTEventListener(ungrabListener);
922 return null;
923 });
924
925 /* see CR 4867453 */
926 getInputContext().removeNotify(this);
927
928 deregisterFinishListener();
929 }
930
931 private void invokeOnClientEDT(Runnable r) {
932 jfxPanelIOP.postEvent(this, new InvocationEvent(this, r));
933 }
934
935 private class HostContainer implements HostInterface {
936
937 @Override
938 public void setEmbeddedStage(EmbeddedStageInterface embeddedStage) {
939 stagePeer = embeddedStage;
940 if (stagePeer == null) {
941 return;
942 }
943 if (pWidth > 0 && pHeight > 0) {
944 stagePeer.setSize(pWidth, pHeight);
945 }
946 invokeOnClientEDT(() -> {
947 if (stagePeer != null && JFXPanel.this.isFocusOwner()) {
948 stagePeer.setFocused(true, AbstractEvents.FOCUSEVENT_ACTIVATED);
949 }
950 });
951 sendMoveEventToFX();
952 }
953
954 @Override
955 public void setEmbeddedScene(EmbeddedSceneInterface embeddedScene) {
956 if (scenePeer == embeddedScene) {
957 return;
958 }
959 scenePeer = embeddedScene;
960 if (scenePeer == null) {
961 invokeOnClientEDT(() -> {
962 if (dnd != null) {
963 dnd.removeNotify();
964 dnd = null;
965 }
966 });
967 return;
968 }
969 if (pWidth > 0 && pHeight > 0) {
970 scenePeer.setSize(pWidth, pHeight);
971 }
972 scenePeer.setPixelScaleFactors((float) scaleFactorX, (float) scaleFactorY);
973
974 invokeOnClientEDT(() -> {
975 dnd = new SwingDnD(JFXPanel.this, scenePeer);
976 dnd.addNotify();
977 if (scenePeer != null) {
978 scenePeer.setDragStartListener(dnd.getDragStartListener());
979 }
980 });
981 }
982
983 @Override
984 public boolean requestFocus() {
985 return requestFocusInWindow();
986 }
987
988 @Override
989 public boolean traverseFocusOut(boolean forward) {
990 KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
991 if (forward) {
992 kfm.focusNextComponent(JFXPanel.this);
993 } else {
994 kfm.focusPreviousComponent(JFXPanel.this);
995 }
996 return true;
997 }
998
999 @Override
1000 public void setPreferredSize(final int width, final int height) {
1001 invokeOnClientEDT(() -> {
1002 JFXPanel.this.pPreferredWidth = width;
1003 JFXPanel.this.pPreferredHeight = height;
1004 JFXPanel.this.revalidate();
1005 });
1006 }
1007
1008 @Override
1009 public void repaint() {
1010 invokeOnClientEDT(() -> {
1011 JFXPanel.this.repaint();
1012 });
1013 }
1014
1015 @Override
1016 public void setEnabled(final boolean enabled) {
1017 JFXPanel.this.setFxEnabled(enabled);
1018 }
1019
1020 @Override
1021 public void setCursor(CursorFrame cursorFrame) {
1022 final Cursor cursor = getPlatformCursor(cursorFrame);
1023 invokeOnClientEDT(() -> {
1024 JFXPanel.this.setCursor(cursor);
1025 });
1026 }
1027
1028 private Cursor getPlatformCursor(final CursorFrame cursorFrame) {
1029 final Cursor cachedPlatformCursor =
1030 cursorFrame.getPlatformCursor(Cursor.class);
1031 if (cachedPlatformCursor != null) {
1032 // platform cursor already cached
1033 return cachedPlatformCursor;
1034 }
1035
1036 // platform cursor not cached yet
1037 final Cursor platformCursor =
1038 SwingCursors.embedCursorToCursor(cursorFrame);
1039 cursorFrame.setPlatforCursor(Cursor.class, platformCursor);
1040
1041 return platformCursor;
1042 }
1043
1044 @Override
1045 public boolean grabFocus() {
1046 // On X11 grab is limited to a single XDisplay connection,
1047 // so we can't delegate it to another GUI toolkit.
1048 if (PlatformUtil.isLinux()) return true;
1049
1050 invokeOnClientEDT(() -> {
1051 Window window = SwingUtilities.getWindowAncestor(JFXPanel.this);
1052 if (window != null) {
1053 jfxPanelIOP.grab(JFXPanel.this.getToolkit(), window);
1054 }
1055 });
1056
1057 return true; // Oh, well...
1058 }
1059
1060 @Override
1061 public void ungrabFocus() {
1062 // On X11 grab is limited to a single XDisplay connection,
1063 // so we can't delegate it to another GUI toolkit.
1064 if (PlatformUtil.isLinux()) return;
1065
1066 invokeOnClientEDT(() -> {
1067 Window window = SwingUtilities.getWindowAncestor(JFXPanel.this);
1068 if (window != null) {
1069 jfxPanelIOP.ungrab(JFXPanel.this.getToolkit(), window);
1070 }
1071 });
1072 }
1073 }
1074 }