Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.embed.swing; 027 028import com.sun.javafx.geom.BaseBounds; 029import com.sun.javafx.geom.transform.BaseTransform; 030import com.sun.javafx.jmx.MXNodeAlgorithm; 031import com.sun.javafx.jmx.MXNodeAlgorithmContext; 032import com.sun.javafx.scene.DirtyBits; 033import com.sun.javafx.scene.traversal.Direction; 034import com.sun.javafx.sg.PGNode; 035import com.sun.javafx.sg.PGExternalNode; 036import com.sun.javafx.stage.FocusUngrabEvent; 037 038import javafx.application.Platform; 039import javafx.beans.InvalidationListener; 040import javafx.beans.Observable; 041import javafx.beans.value.ObservableValue; 042import javafx.event.EventHandler; 043import javafx.scene.Node; 044import javafx.scene.input.KeyEvent; 045import javafx.scene.input.MouseButton; 046import javafx.scene.input.MouseEvent; 047import javafx.beans.value.ChangeListener; 048import javafx.geometry.Point2D; 049import javafx.scene.input.KeyCode; 050 051import javax.swing.JComponent; 052import javax.swing.SwingUtilities; 053import java.awt.AWTEvent; 054import java.awt.EventQueue; 055import java.awt.Toolkit; 056import java.awt.event.WindowEvent; 057import java.awt.event.WindowFocusListener; 058 059import java.nio.IntBuffer; 060import java.security.AccessController; 061import java.security.PrivilegedAction; 062import java.util.ArrayList; 063import java.util.List; 064import java.util.concurrent.locks.ReentrantLock; 065 066import javafx.scene.Scene; 067import javafx.stage.Window; 068import sun.awt.UngrabEvent; 069 070import sun.swing.LightweightContent; 071import sun.swing.JLightweightFrame; 072 073/** 074 * This class is used to embed a Swing content into a JavaFX application. 075 * The content to be displayed is specified with the {@link #setContent} method 076 * that accepts an instance of Swing {@code JComponent}. The hierarchy of components 077 * contained in the {@code JComponent} instance should not contain any heavyweight 078 * components, otherwise {@code SwingNode} may fail to paint it. The content gets 079 * repainted automatically. All the input and focus events are forwarded to the 080 * {@code JComponent} instance transparently to the developer. 081 * <p> 082 * Here is a typical pattern which demonstrates how {@code SwingNode} can be used: 083 * <pre> 084 * public class SwingFx extends Application { 085 * 086 * private SwingNode swingNode; 087 * 088 * @Override 089 * public void start(Stage stage) { 090 * swingNode = new SwingNode(); 091 * 092 * createAndSetSwingContent(); 093 * 094 * StackPane pane = new StackPane(); 095 * pane.getChildren().add(swingNode); 096 * 097 * stage.setScene(new Scene(pane, 100, 50)); 098 * stage.show(); 099 * } 100 * 101 * private void createAndSetSwingContent() { 102 * SwingUtilities.invokeLater(new Runnable() { 103 * @Override 104 * public void run() { 105 * swingNode.setContent(new JButton("Click me!")); 106 * } 107 * }); 108 * } 109 * } 110 * </pre> 111 */ 112public class SwingNode extends Node { 113 114 private double width; 115 private double height; 116 117 private volatile JComponent content; 118 private volatile JLightweightFrame lwFrame; 119 120 private volatile PGExternalNode peer; 121 122 private final ReentrantLock paintLock = new ReentrantLock(); 123 124 private boolean skipBackwardUnrgabNotification; 125 private boolean grabbed; // lwframe initiated grab 126 127 /** 128 * Constructs a new instance of {@code SwingNode}. 129 */ 130 public SwingNode() { 131 setFocusTraversable(true); 132 setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler()); 133 setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler()); 134 135 focusedProperty().addListener(new ChangeListener<Boolean>() { 136 @Override 137 public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, final Boolean newValue) { 138 activateLwFrame(newValue); 139 } 140 }); 141 } 142 143 /** 144 * Attaches a {@code JComponent} instance to display in this {@code SwingNode}. 145 * <p> 146 * The method can be called either on the JavaFX Application thread or the Swing thread. 147 * Note however, that access to a Swing component must occur from the Swing thread according 148 * to the Swing threading restrictions. 149 * 150 * @param content a Swing component to display in this {@code SwingNode} 151 * 152 * @see java.awt.EventQueue#isDispatchThread() 153 * @see javafx.application.Platform#isFxApplicationThread() 154 */ 155 public void setContent(final JComponent content) { 156 this.content = content; 157 158 invokeOnEDT(new Runnable() { 159 @Override 160 public void run() { 161 setContentImpl(content); 162 } 163 }); 164 } 165 166 /** 167 * Returns the {@code JComponent} instance attached to this {@code SwingNode}. 168 * <p> 169 * The method can be called either on the JavaFX Application thread or the Swing thread. 170 * Note however, that access to a Swing component must occur from the Swing thread according 171 * to the Swing threading restrictions. 172 * 173 * @see java.awt.EventQueue#isDispatchThread() 174 * @see javafx.application.Platform#isFxApplicationThread() 175 * 176 * @return the Swing component attached to this {@code SwingNode} 177 */ 178 public JComponent getContent() { 179 return content; 180 } 181 182 /* 183 * Called on Swing thread 184 */ 185 private void setContentImpl(JComponent content) { 186 if (lwFrame != null) { 187 lwFrame.dispose(); 188 lwFrame = null; 189 } 190 if (content != null) { 191 lwFrame = new JLightweightFrame(); 192 193 lwFrame.addWindowFocusListener(new WindowFocusListener() { 194 @Override 195 public void windowGainedFocus(WindowEvent e) { 196 } 197 @Override 198 public void windowLostFocus(WindowEvent e) { 199 Platform.runLater(new Runnable() { 200 @Override 201 public void run() { 202 ungrabFocus(true); 203 } 204 }); 205 } 206 }); 207 208 lwFrame.setContent(new SwingNodeContent(content)); 209 lwFrame.setVisible(true); 210 211 locateLwFrame(); // initialize location 212 213 if (focusedProperty().get()) { 214 activateLwFrame(true); 215 } 216 } 217 } 218 219 private List<Runnable> peerRequests = new ArrayList<>(); 220 221 /* 222 * Called on Swing thread 223 */ 224 void setImageBuffer(final int[] data, 225 final int x, final int y, 226 final int w, final int h, 227 final int linestride) 228 { 229 Runnable r = new Runnable() { 230 @Override 231 public void run() { 232 peer.setImageBuffer(IntBuffer.wrap(data), x, y, w, h, linestride); 233 } 234 }; 235 if (peer != null) { 236 Platform.runLater(r); 237 } else { 238 peerRequests.clear(); 239 peerRequests.add(r); 240 } 241 } 242 243 /* 244 * Called on Swing thread 245 */ 246 void setImageBounds(final int x, final int y, final int w, final int h) { 247 Runnable r = new Runnable() { 248 @Override 249 public void run() { 250 peer.setImageBounds(x, y, w, h); 251 } 252 }; 253 if (peer != null) { 254 Platform.runLater(r); 255 } else { 256 peerRequests.add(r); 257 } 258 } 259 260 /* 261 * Called on Swing thread 262 */ 263 void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) { 264 Runnable r = new Runnable() { 265 @Override 266 public void run() { 267 peer.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 268 impl_markDirty(DirtyBits.NODE_CONTENTS); 269 } 270 }; 271 if (peer != null) { 272 Platform.runLater(r); 273 } else { 274 peerRequests.add(r); 275 } 276 } 277 278 @Override public boolean isResizable() { 279 return true; 280 } 281 282 @Override public void resize(final double width, final double height) { 283 this.width = width; 284 this.height = height; 285 super.resize(width, height); 286 impl_geomChanged(); 287 impl_markDirty(DirtyBits.NODE_GEOMETRY); 288 SwingUtilities.invokeLater(new Runnable() { 289 @Override 290 public void run() { 291 if (lwFrame != null) { 292 lwFrame.setSize((int)width, (int)height); 293 } 294 } 295 }); 296 } 297 298 @Override 299 public double maxWidth(double height) { 300 return Double.MAX_VALUE; 301 } 302 303 @Override 304 public double maxHeight(double width) { 305 return Double.MAX_VALUE; 306 } 307 308 @Override 309 public double prefWidth(double height) { 310 return -1; 311 } 312 313 @Override 314 public double prefHeight(double width) { 315 return -1; 316 } 317 318 @Override 319 public double minWidth(double height) { 320 return 0; 321 } 322 323 @Override 324 public double minHeight(double width) { 325 return 0; 326 } 327 328 @Override 329 protected boolean impl_computeContains(double localX, double localY) { 330 return true; 331 } 332 333 private InvalidationListener locationListener = new InvalidationListener() { 334 @Override 335 public void invalidated(Observable observable) { 336 locateLwFrame(); 337 } 338 }; 339 340 private EventHandler<FocusUngrabEvent> ungrabHandler = new EventHandler<FocusUngrabEvent>() { 341 @Override 342 public void handle(FocusUngrabEvent event) { 343 if (!skipBackwardUnrgabNotification) { 344 AccessController.doPrivileged(new PostEventAction(new UngrabEvent(lwFrame))); 345 } 346 } 347 }; 348 349 private ChangeListener<Boolean> windowVisibleListener = new ChangeListener<Boolean>() { 350 @Override 351 public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { 352 if (!newValue) { 353 disposeLwFrame(); 354 355 } else { 356 setContent(content); 357 } 358 } 359 }; 360 361 private void removeListeners(Scene scene) { 362 Window window = scene.getWindow(); 363 if (window != null) { 364 window.xProperty().removeListener(locationListener); 365 window.yProperty().removeListener(locationListener); 366 window.removeEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 367 window.showingProperty().removeListener(windowVisibleListener); 368 } 369 } 370 371 private void addListeners(Scene scene) { 372 Window window = scene.getWindow(); 373 if (window != null) { 374 window.xProperty().addListener(locationListener); 375 window.yProperty().addListener(locationListener); 376 window.addEventHandler(FocusUngrabEvent.FOCUS_UNGRAB, ungrabHandler); 377 window.showingProperty().addListener(windowVisibleListener); 378 } 379 } 380 381 @Override 382 protected PGNode impl_createPGNode() { 383 peer = com.sun.javafx.tk.Toolkit.getToolkit().createPGExternalNode(); 384 peer.setLock(paintLock); 385 for (Runnable request : peerRequests) { 386 request.run(); 387 } 388 peerRequests = null; 389 390 if (content != null) { 391 setContent(content); // in case the Node is re-added to Scene 392 } 393 addListeners(getScene()); 394 395 sceneProperty().addListener(new ChangeListener<Scene>() { 396 @Override 397 public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) { 398 // Removed from scene, or added to another scene. 399 // The lwFrame will be recreated from impl_createPGNode(). 400 removeListeners(oldValue); 401 disposeLwFrame(); 402 } 403 }); 404 405 impl_treeVisibleProperty().addListener(new ChangeListener<Boolean>() { 406 @Override 407 public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { 408 setLwFrameVisible(newValue); 409 } 410 }); 411 412 return peer; 413 } 414 415 @Override 416 public void impl_updatePG() { 417 super.impl_updatePG(); 418 419 if (impl_isDirty(DirtyBits.NODE_VISIBLE)) { 420 locateLwFrame(); // initialize location 421 } 422 if (impl_isDirty(DirtyBits.NODE_CONTENTS)) { 423 peer.markContentDirty(); 424 } 425 } 426 427 private void locateLwFrame() { 428 if (getScene() == null || lwFrame == null) { 429 return; 430 } 431 final Point2D loc = localToScene(0, 0); 432 final int windowX = (int)getScene().getWindow().getX(); 433 final int windowY = (int)getScene().getWindow().getY(); 434 final int sceneX = (int)getScene().getX(); 435 final int sceneY = (int)getScene().getY(); 436 437 invokeOnEDT(new Runnable() { 438 @Override 439 public void run() { 440 if (lwFrame != null) { 441 lwFrame.setLocation(windowX + sceneX + (int)loc.getX(), 442 windowY + sceneY + (int)loc.getY()); 443 } 444 } 445 }); 446 } 447 448 private void activateLwFrame(final boolean activate) { 449 if (lwFrame == null) { 450 return; 451 } 452 invokeOnEDT(new Runnable() { 453 @Override 454 public void run() { 455 if (lwFrame != null) { 456 lwFrame.emulateActivation(activate); 457 } 458 } 459 }); 460 } 461 462 private void disposeLwFrame() { 463 if (lwFrame == null) { 464 return; 465 } 466 invokeOnEDT(new Runnable() { 467 @Override 468 public void run() { 469 if (lwFrame != null) { 470 lwFrame.dispose(); 471 lwFrame = null; 472 } 473 } 474 }); 475 } 476 477 private void setLwFrameVisible(final boolean visible) { 478 if (lwFrame == null) { 479 return; 480 } 481 invokeOnEDT(new Runnable() { 482 @Override 483 public void run() { 484 if (lwFrame != null) { 485 lwFrame.setVisible(visible); 486 } 487 } 488 }); 489 } 490 491 @Override 492 public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 493 bounds.deriveWithNewBounds(0, 0, 0, (float)width, (float)height, 0); 494 tx.transform(bounds, bounds); 495 return bounds; 496 } 497 498 @Override 499 public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) { 500 return alg.processLeafNode(this, ctx); 501 } 502 503 private class SwingNodeContent implements LightweightContent { 504 private JComponent comp; 505 public SwingNodeContent(JComponent comp) { 506 this.comp = comp; 507 } 508 @Override 509 public JComponent getComponent() { 510 return comp; 511 } 512 @Override 513 public void paintLock() { 514 paintLock.lock(); 515 } 516 @Override 517 public void paintUnlock() { 518 paintLock.unlock(); 519 } 520 @Override 521 public void imageBufferReset(int[] data, int x, int y, int width, int height, int linestride) { 522 SwingNode.this.setImageBuffer(data, x, y, width, height, linestride); 523 } 524 @Override 525 public void imageReshaped(int x, int y, int width, int height) { 526 SwingNode.this.setImageBounds(x, y, width, height); 527 } 528 @Override 529 public void imageUpdated(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) { 530 SwingNode.this.repaintDirtyRegion(dirtyX, dirtyY, dirtyWidth, dirtyHeight); 531 } 532 @Override 533 public void focusGrabbed() { 534 Platform.runLater(new Runnable() { 535 @Override 536 public void run() { 537 if (getScene() != null && getScene().getWindow() != null) { 538 getScene().getWindow().impl_getPeer().grabFocus(); 539 grabbed = true; 540 } 541 } 542 }); 543 } 544 @Override 545 public void focusUngrabbed() { 546 Platform.runLater(new Runnable() { 547 @Override 548 public void run() { 549 ungrabFocus(false); 550 } 551 }); 552 } 553 } 554 555 private void ungrabFocus(boolean postUngrabEvent) { 556 if (grabbed && 557 getScene() != null && getScene().getWindow() != null) 558 { 559 skipBackwardUnrgabNotification = !postUngrabEvent; 560 getScene().getWindow().impl_getPeer().ungrabFocus(); 561 skipBackwardUnrgabNotification = false; 562 grabbed = false; 563 } 564 } 565 566 private class PostEventAction implements PrivilegedAction<Void> { 567 private AWTEvent event; 568 public PostEventAction(AWTEvent event) { 569 this.event = event; 570 } 571 @Override 572 public Void run() { 573 EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); 574 eq.postEvent(event); 575 return null; 576 } 577 } 578 579 private class SwingMouseEventHandler implements EventHandler<MouseEvent> { 580 @Override 581 public void handle(MouseEvent event) { 582 if (event.getEventType() == MouseEvent.MOUSE_PRESSED && 583 !SwingNode.this.isFocused() && SwingNode.this.isFocusTraversable()) 584 { 585 SwingNode.this.requestFocus(); 586 } 587 int swingID = SwingEvents.fxMouseEventTypeToMouseID(event); 588 if (swingID < 0) { 589 return; 590 } 591 int swingModifiers = SwingEvents.fxMouseModsToMouseMods(event); 592 // TODO: popupTrigger 593 boolean swingPopupTrigger = event.getButton() == MouseButton.SECONDARY; 594 int swingButton = SwingEvents.fxMouseButtonToMouseButton(event); 595 long swingWhen = System.currentTimeMillis(); 596 java.awt.event.MouseEvent mouseEvent = 597 new java.awt.event.MouseEvent( 598 lwFrame, swingID, swingWhen, swingModifiers, 599 (int)event.getX(), (int)event.getY(), (int)event.getScreenX(), (int)event.getSceneY(), 600 event.getClickCount(), swingPopupTrigger, swingButton); 601 AccessController.doPrivileged(new PostEventAction(mouseEvent)); 602 } 603 } 604 605 private class SwingKeyEventHandler implements EventHandler<KeyEvent> { 606 @Override 607 public void handle(KeyEvent event) { 608 if (event.getCharacter().isEmpty()) { 609 // TODO: should we post an "empty" character? 610 return; 611 } 612 // Let Ctrl+Tab, Shift+Strl+Tab traverse focus out. 613 if (event.getCode() == KeyCode.TAB && event.isControlDown()) { 614 Direction d = event.isShiftDown() ? Direction.PREVIOUS : Direction.NEXT; 615 getParent().getImpl_traversalEngine().trav(SwingNode.this, d); 616 return; 617 } 618 // Don't let Arrows, Tab, Shift+Tab traverse focus out. 619 if (event.getCode() == KeyCode.LEFT || 620 event.getCode() == KeyCode.RIGHT || 621 event.getCode() == KeyCode.TAB) 622 { 623 event.consume(); 624 } 625 626 int swingID = SwingEvents.fxKeyEventTypeToKeyID(event); 627 if (swingID < 0) { 628 return; 629 } 630 int swingModifiers = SwingEvents.fxKeyModsToKeyMods(event); 631 int swingKeyCode = event.getCode().impl_getCode(); 632 char swingChar = event.getCharacter().charAt(0); 633 long swingWhen = System.currentTimeMillis(); 634 java.awt.event.KeyEvent keyEvent = new java.awt.event.KeyEvent( 635 lwFrame, swingID, swingWhen, swingModifiers, 636 swingKeyCode, swingChar); 637 AccessController.doPrivileged(new PostEventAction(keyEvent)); 638 } 639 } 640 641 private static void invokeOnEDT(final Runnable r) { 642 if (SwingUtilities.isEventDispatchThread()) { 643 r.run(); 644 } else { 645 SwingUtilities.invokeLater(new Runnable() { 646 @Override 647 public void run() { 648 r.run(); 649 } 650 }); 651 } 652 } 653}