1 /* 2 * Copyright (c) 2010, 2017, 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 com.sun.javafx.tk.quantum; 27 28 import javafx.application.ConditionalFeature; 29 import javafx.geometry.Dimension2D; 30 import javafx.scene.image.Image; 31 import javafx.scene.input.Dragboard; 32 import javafx.scene.input.InputMethodRequests; 33 import javafx.scene.input.KeyCode; 34 import javafx.scene.input.KeyEvent; 35 import javafx.scene.input.TransferMode; 36 import javafx.scene.paint.Color; 37 import javafx.scene.paint.CycleMethod; 38 import javafx.scene.paint.ImagePattern; 39 import javafx.scene.paint.LinearGradient; 40 import javafx.scene.paint.RadialGradient; 41 import javafx.scene.paint.Stop; 42 import javafx.scene.shape.ClosePath; 43 import javafx.scene.shape.CubicCurveTo; 44 import javafx.scene.shape.FillRule; 45 import javafx.scene.shape.LineTo; 46 import javafx.scene.shape.MoveTo; 47 import javafx.scene.shape.PathElement; 48 import javafx.scene.shape.QuadCurveTo; 49 import javafx.scene.shape.SVGPath; 50 import javafx.scene.shape.StrokeLineCap; 51 import javafx.scene.shape.StrokeLineJoin; 52 import javafx.scene.shape.StrokeType; 53 import javafx.stage.FileChooser; 54 import javafx.stage.Modality; 55 import javafx.stage.StageStyle; 56 import javafx.stage.Window; 57 import java.io.File; 58 import java.io.InputStream; 59 import java.nio.ByteBuffer; 60 import java.nio.IntBuffer; 61 import java.security.AccessControlContext; 62 import java.security.AccessController; 63 import java.security.PrivilegedAction; 64 import java.util.ArrayList; 65 import java.util.Arrays; 66 import java.util.Collections; 67 import java.util.HashMap; 68 import java.util.List; 69 import java.util.Map; 70 import java.util.Set; 71 import java.util.concurrent.CountDownLatch; 72 import java.util.concurrent.Future; 73 import java.util.concurrent.TimeUnit; 74 import java.util.concurrent.atomic.AtomicBoolean; 75 import java.util.function.Supplier; 76 import com.sun.glass.ui.Application; 77 import com.sun.glass.ui.Clipboard; 78 import com.sun.glass.ui.ClipboardAssistance; 79 import com.sun.glass.ui.CommonDialogs; 80 import com.sun.glass.ui.CommonDialogs.FileChooserResult; 81 import com.sun.glass.ui.EventLoop; 82 import com.sun.glass.ui.Screen; 83 import com.sun.glass.ui.Timer; 84 import com.sun.glass.ui.View; 85 import com.sun.javafx.PlatformUtil; 86 import com.sun.javafx.application.PlatformImpl; 87 import com.sun.javafx.embed.HostInterface; 88 import com.sun.javafx.geom.Path2D; 89 import com.sun.javafx.geom.PathIterator; 90 import com.sun.javafx.geom.Shape; 91 import com.sun.javafx.geom.transform.BaseTransform; 92 import com.sun.javafx.perf.PerformanceTracker; 93 import com.sun.javafx.runtime.async.AbstractRemoteResource; 94 import com.sun.javafx.runtime.async.AsyncOperationListener; 95 import com.sun.javafx.scene.text.TextLayoutFactory; 96 import com.sun.javafx.sg.prism.NGNode; 97 import com.sun.javafx.tk.AppletWindow; 98 import com.sun.javafx.tk.CompletionListener; 99 import com.sun.javafx.tk.FileChooserType; 100 import com.sun.javafx.tk.FontLoader; 101 import com.sun.javafx.tk.ImageLoader; 102 import com.sun.javafx.tk.PlatformImage; 103 import com.sun.javafx.tk.RenderJob; 104 import com.sun.javafx.tk.ScreenConfigurationAccessor; 105 import com.sun.javafx.tk.TKClipboard; 106 import com.sun.javafx.tk.TKDragGestureListener; 107 import com.sun.javafx.tk.TKDragSourceListener; 108 import com.sun.javafx.tk.TKDropTargetListener; 109 import com.sun.javafx.tk.TKScene; 110 import com.sun.javafx.tk.TKScreenConfigurationListener; 111 import com.sun.javafx.tk.TKStage; 112 import com.sun.javafx.tk.TKSystemMenu; 113 import com.sun.javafx.tk.Toolkit; 114 import com.sun.prism.BasicStroke; 115 import com.sun.prism.Graphics; 116 import com.sun.prism.GraphicsPipeline; 117 import com.sun.prism.PixelFormat; 118 import com.sun.prism.RTTexture; 119 import com.sun.prism.ResourceFactory; 120 import com.sun.prism.ResourceFactoryListener; 121 import com.sun.prism.Texture.WrapMode; 122 import com.sun.prism.impl.Disposer; 123 import com.sun.prism.impl.PrismSettings; 124 import com.sun.scenario.DelayedRunnable; 125 import com.sun.scenario.animation.AbstractMasterTimer; 126 import com.sun.scenario.effect.FilterContext; 127 import com.sun.scenario.effect.Filterable; 128 import com.sun.scenario.effect.impl.prism.PrFilterContext; 129 import com.sun.scenario.effect.impl.prism.PrImage; 130 import com.sun.javafx.logging.PulseLogger; 131 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED; 132 import com.sun.javafx.scene.input.DragboardHelper; 133 import com.sun.prism.impl.ManagedResource; 134 135 public final class QuantumToolkit extends Toolkit { 136 137 public static final boolean verbose = 138 AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("quantum.verbose")); 139 140 public static final boolean pulseDebug = 141 AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("quantum.pulse")); 142 143 private static final boolean multithreaded = 144 AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> { 145 // If it is not specified, or it is true, then it should 146 // be true. Otherwise it should be false. 147 String value = System.getProperty("quantum.multithreaded"); 148 if (value == null) return true; 149 final boolean result = Boolean.parseBoolean(value); 150 if (verbose) { 151 System.out.println(result ? "Multi-Threading Enabled" : "Multi-Threading Disabled"); 152 } 153 return result; 154 }); 155 156 private static boolean debug = 157 AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> Boolean.getBoolean("quantum.debug")); 158 159 private static Integer pulseHZ = 160 AccessController.doPrivileged((PrivilegedAction<Integer>) () -> Integer.getInteger("javafx.animation.pulse")); 161 162 static final boolean liveResize = 163 AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> { 164 boolean isSWT = "swt".equals(System.getProperty("glass.platform")); 165 String result = (PlatformUtil.isMac() || PlatformUtil.isWindows()) && !isSWT ? "true" : "false"; 166 return "true".equals(System.getProperty("javafx.live.resize", result)); 167 }); 168 169 static final boolean drawInPaint = 170 AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> { 171 boolean isSWT = "swt".equals(System.getProperty("glass.platform")); 172 String result = PlatformUtil.isMac() && isSWT ? "true" : "false"; 173 return "true".equals(System.getProperty("javafx.draw.in.paint", result));}); 174 175 private static boolean singleThreaded = 176 AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> { 177 Boolean result = Boolean.getBoolean("quantum.singlethreaded"); 178 if (/*verbose &&*/ result) { 179 System.out.println("Warning: Single GUI Threadiong is enabled, FPS should be slower"); 180 } 181 return result; 182 }); 183 184 private static boolean noRenderJobs = 185 AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> { 186 Boolean result = Boolean.getBoolean("quantum.norenderjobs"); 187 if (/*verbose &&*/ result) { 188 System.out.println("Warning: Quantum will not submit render jobs, nothing should draw"); 189 } 190 return result; 191 }); 192 193 private AtomicBoolean toolkitRunning = new AtomicBoolean(false); 194 private AtomicBoolean animationRunning = new AtomicBoolean(false); 195 private AtomicBoolean nextPulseRequested = new AtomicBoolean(false); 196 private AtomicBoolean pulseRunning = new AtomicBoolean(false); 197 private int inPulse = 0; 198 private CountDownLatch launchLatch = new CountDownLatch(1); 199 200 final int PULSE_INTERVAL = (int)(TimeUnit.SECONDS.toMillis(1L) / getRefreshRate()); 201 final int FULLSPEED_INTERVAL = 1; // ms 202 boolean nativeSystemVsync = false; 203 private float _maxPixelScale; 204 private Runnable pulseRunnable, userRunnable, timerRunnable; 205 private Timer pulseTimer = null; 206 private Thread shutdownHook = null; 207 private PaintCollector collector; 208 private QuantumRenderer renderer; 209 private GraphicsPipeline pipeline; 210 211 private ClassLoader ccl; 212 213 private HashMap<Object,EventLoop> eventLoopMap = null; 214 215 private final PerformanceTracker perfTracker = new PerformanceTrackerImpl(); 216 217 @Override public boolean init() { 218 /* 219 * Glass Mac, X11 need Application.setDeviceDetails to happen prior to Glass Application.Run 220 */ 221 renderer = QuantumRenderer.getInstance(); 222 collector = PaintCollector.createInstance(this); 223 pipeline = GraphicsPipeline.getPipeline(); 224 225 /* shutdown the pipeline on System.exit, ^c 226 * needed with X11 and Windows, see RT-32501 227 */ 228 shutdownHook = new Thread("Glass/Prism Shutdown Hook") { 229 @Override public void run() { 230 dispose(); 231 } 232 }; 233 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 234 Runtime.getRuntime().addShutdownHook(shutdownHook); 235 return null; 236 }); 237 return true; 238 } 239 240 /** 241 * This method is invoked by PlatformImpl. It is typically called on the main 242 * thread, NOT the JavaFX Application Thread. The userStartupRunnable will 243 * be invoked on the JavaFX Application Thread. 244 * 245 * @param userStartupRunnable A runnable invoked on the JavaFX Application Thread 246 * that allows the system to perform some startup 247 * functionality after the toolkit has been initialized. 248 */ 249 @Override public void startup(final Runnable userStartupRunnable) { 250 // Save the context class loader of the launcher thread 251 ccl = Thread.currentThread().getContextClassLoader(); 252 253 try { 254 this.userRunnable = userStartupRunnable; 255 256 // Ensure that the toolkit can only be started here 257 Application.run(() -> runToolkit()); 258 } catch (RuntimeException ex) { 259 if (verbose) { 260 ex.printStackTrace(); 261 } 262 throw ex; 263 } catch (Throwable t) { 264 if (verbose) { 265 t.printStackTrace(); 266 } 267 throw new RuntimeException(t); 268 } 269 270 try { 271 launchLatch.await(); 272 } catch (InterruptedException ie) { 273 ie.printStackTrace(); 274 } 275 } 276 277 // restart the toolkit if previously terminated 278 private void assertToolkitRunning() { 279 // not implemented 280 } 281 282 boolean shouldWaitForRenderingToComplete() { 283 return !multithreaded; 284 } 285 286 /** 287 * Method to initialize the Scene Graph on the JavaFX application thread. 288 * Specifically, we will do static initialization for those classes in 289 * the javafx.stage, javafx.scene, and javafx.controls packages necessary 290 * to allow subsequent construction of the Scene or any Node, including 291 * a PopupControl, on a background thread. 292 * 293 * This method is called on the JavaFX application thread. 294 */ 295 private static void initSceneGraph() { 296 // It is both necessary and sufficient to call a static method on the 297 // Screen class to allow PopupControl instances to be created on any thread. 298 javafx.stage.Screen.getPrimary(); 299 } 300 301 // Called by Glass from Application.run() 302 void runToolkit() { 303 Thread user = Thread.currentThread(); 304 305 if (!toolkitRunning.getAndSet(true)) { 306 user.setName("JavaFX Application Thread"); 307 // Set context class loader to the same as the thread that called startup 308 user.setContextClassLoader(ccl); 309 setFxUserThread(user); 310 311 // Glass screens were inited in Application.run(), assign adapters 312 assignScreensAdapters(); 313 /* 314 * Glass Application instance is now valid - create the ResourceFactory 315 * on the render thread 316 */ 317 renderer.createResourceFactory(); 318 319 pulseRunnable = () -> QuantumToolkit.this.pulseFromQueue(); 320 timerRunnable = () -> { 321 try { 322 QuantumToolkit.this.postPulse(); 323 } catch (Throwable th) { 324 th.printStackTrace(System.err); 325 } 326 }; 327 pulseTimer = Application.GetApplication().createTimer(timerRunnable); 328 329 Application.GetApplication().setEventHandler(new Application.EventHandler() { 330 @Override public void handleQuitAction(Application app, long time) { 331 GlassStage.requestClosingAllWindows(); 332 } 333 334 @Override public boolean handleThemeChanged(String themeName) { 335 return PlatformImpl.setAccessibilityTheme(themeName); 336 } 337 }); 338 } 339 // Initialize JavaFX scene graph 340 initSceneGraph(); 341 launchLatch.countDown(); 342 try { 343 Application.invokeAndWait(this.userRunnable); 344 345 if (getMasterTimer().isFullspeed()) { 346 /* 347 * FULLSPEED_INTVERVAL workaround 348 * 349 * Application.invokeLater(pulseRunnable); 350 */ 351 pulseTimer.start(FULLSPEED_INTERVAL); 352 } else { 353 nativeSystemVsync = Screen.getVideoRefreshPeriod() != 0.0; 354 if (nativeSystemVsync) { 355 // system supports vsync 356 pulseTimer.start(); 357 } else { 358 // rely on millisecond resolution timer to provide 359 // nominal pulse sync and use pulse hinting on 360 // synchronous pipelines to fine tune the interval 361 pulseTimer.start(PULSE_INTERVAL); 362 } 363 } 364 } catch (Throwable th) { 365 th.printStackTrace(System.err); 366 } finally { 367 if (PrismSettings.verbose) { 368 System.err.println(" vsync: " + PrismSettings.isVsyncEnabled + 369 " vpipe: " + pipeline.isVsyncSupported()); 370 } 371 PerformanceTracker.logEvent("Toolkit.startup - finished"); 372 } 373 } 374 375 /** 376 * Runs the specified supplier, releasing the renderLock if needed. 377 * This is called by glass event handlers for Window, View, and 378 * Accessible. 379 * @param <T> the type of the return value 380 * @param supplier the supplier to be run 381 * @return the return value from calling supplier.get() 382 */ 383 public static <T> T runWithoutRenderLock(Supplier<T> supplier) { 384 final boolean locked = ViewPainter.renderLock.isHeldByCurrentThread(); 385 try { 386 if (locked) { 387 ViewPainter.renderLock.unlock(); 388 } 389 return supplier.get(); 390 } finally { 391 if (locked) { 392 ViewPainter.renderLock.lock(); 393 } 394 } 395 } 396 397 /** 398 * Runs the specified supplier, first acquiring the renderLock. 399 * The lock is released when done. 400 * @param <T> the type of the return value 401 * @param supplier the supplier to be run 402 * @return the return value from calling supplier.get() 403 */ 404 public static <T> T runWithRenderLock(Supplier<T> supplier) { 405 ViewPainter.renderLock.lock(); 406 try { 407 return supplier.get(); 408 } finally { 409 ViewPainter.renderLock.unlock(); 410 } 411 } 412 413 boolean hasNativeSystemVsync() { 414 return nativeSystemVsync; 415 } 416 417 boolean isVsyncEnabled() { 418 return (PrismSettings.isVsyncEnabled && 419 pipeline.isVsyncSupported()); 420 } 421 422 @Override public void checkFxUserThread() { 423 super.checkFxUserThread(); 424 renderer.checkRendererIdle(); 425 } 426 427 protected static Thread getFxUserThread() { 428 return Toolkit.getFxUserThread(); 429 } 430 431 @Override public Future addRenderJob(RenderJob r) { 432 // Do not run any render jobs (this is for benchmarking only) 433 if (noRenderJobs) { 434 CompletionListener listener = r.getCompletionListener(); 435 if (r instanceof PaintRenderJob) { 436 ((PaintRenderJob)r).getScene().setPainting(false); 437 } 438 if (listener != null) { 439 try { 440 listener.done(r); 441 } catch (Throwable th) { 442 th.printStackTrace(); 443 } 444 } 445 return null; 446 } 447 // Run the render job in the UI thread (this is for benchmarking only) 448 if (singleThreaded) { 449 r.run(); 450 return null; 451 } 452 return (renderer.submitRenderJob(r)); 453 } 454 455 void postPulse() { 456 if (toolkitRunning.get() && 457 (animationRunning.get() || nextPulseRequested.get() || collector.hasDirty()) && 458 !setPulseRunning()) { 459 460 Application.invokeLater(pulseRunnable); 461 462 if (debug) { 463 System.err.println("QT.postPulse@(" + System.nanoTime() + "): " + pulseString()); 464 } 465 } else if (debug) { 466 System.err.println("QT.postPulse#(" + System.nanoTime() + ") DROP: " + pulseString()); 467 } 468 } 469 470 private String pulseString() { 471 return ((toolkitRunning.get() ? "T" : "t") + 472 (animationRunning.get() ? "A" : "a") + 473 (pulseRunning.get() ? "P" : "p") + 474 (nextPulseRequested.get() ? "N" : "n") + 475 (collector.hasDirty() ? "D" : "d")); 476 } 477 478 private boolean setPulseRunning() { 479 return (pulseRunning.getAndSet(true)); 480 } 481 482 private void endPulseRunning() { 483 pulseRunning.set(false); 484 if (debug) { 485 System.err.println("QT.endPulse: " + System.nanoTime()); 486 } 487 } 488 489 void pulseFromQueue() { 490 try { 491 pulse(); 492 } finally { 493 endPulseRunning(); 494 } 495 } 496 497 protected void pulse() { 498 pulse(true); 499 } 500 501 void pulse(boolean collect) { 502 try { 503 inPulse++; 504 if (PULSE_LOGGING_ENABLED) { 505 PulseLogger.pulseStart(); 506 } 507 508 if (!toolkitRunning.get()) { 509 return; 510 } 511 nextPulseRequested.set(false); 512 if (animationRunnable != null) { 513 animationRunning.set(true); 514 animationRunnable.run(); 515 } else { 516 animationRunning.set(false); 517 } 518 firePulse(); 519 if (collect) collector.renderAll(); 520 } finally { 521 inPulse--; 522 if (PULSE_LOGGING_ENABLED) { 523 PulseLogger.pulseEnd(); 524 } 525 } 526 } 527 528 void vsyncHint() { 529 if (isVsyncEnabled()) { 530 if (debug) { 531 System.err.println("QT.vsyncHint: postPulse: " + System.nanoTime()); 532 } 533 postPulse(); 534 } 535 } 536 537 @Override public AppletWindow createAppletWindow(long parent, String serverName) { 538 GlassAppletWindow parentWindow = new GlassAppletWindow(parent, serverName); 539 // Make this the parent window for all future Stages 540 WindowStage.setAppletWindow(parentWindow); 541 return parentWindow; 542 } 543 544 @Override public void closeAppletWindow() { 545 GlassAppletWindow gaw = WindowStage.getAppletWindow(); 546 if (null != gaw) { 547 gaw.dispose(); 548 WindowStage.setAppletWindow(null); 549 // any further strong refs will be in the applet itself 550 } 551 } 552 553 @Override public TKStage createTKStage(Window peerWindow, boolean securityDialog, StageStyle stageStyle, boolean primary, Modality modality, TKStage owner, boolean rtl, AccessControlContext acc) { 554 assertToolkitRunning(); 555 WindowStage stage = new WindowStage(peerWindow, securityDialog, stageStyle, modality, owner); 556 stage.setSecurityContext(acc); 557 if (primary) { 558 stage.setIsPrimary(); 559 } 560 stage.setRTL(rtl); 561 stage.init(systemMenu); 562 return stage; 563 } 564 565 @Override public boolean canStartNestedEventLoop() { 566 return inPulse == 0; 567 } 568 569 @Override public Object enterNestedEventLoop(Object key) { 570 checkFxUserThread(); 571 572 if (key == null) { 573 throw new NullPointerException(); 574 } 575 576 if (!canStartNestedEventLoop()) { 577 throw new IllegalStateException("Cannot enter nested loop during animation or layout processing"); 578 } 579 580 if (eventLoopMap == null) { 581 eventLoopMap = new HashMap<>(); 582 } 583 if (eventLoopMap.containsKey(key)) { 584 throw new IllegalArgumentException( 585 "Key already associated with a running event loop: " + key); 586 } 587 EventLoop eventLoop = Application.GetApplication().createEventLoop(); 588 eventLoopMap.put(key, eventLoop); 589 590 Object ret = eventLoop.enter(); 591 592 if (!isNestedLoopRunning()) { 593 notifyLastNestedLoopExited(); 594 } 595 596 return ret; 597 } 598 599 @Override public void exitNestedEventLoop(Object key, Object rval) { 600 checkFxUserThread(); 601 602 if (key == null) { 603 throw new NullPointerException(); 604 } 605 if (eventLoopMap == null || !eventLoopMap.containsKey(key)) { 606 throw new IllegalArgumentException( 607 "Key not associated with a running event loop: " + key); 608 } 609 EventLoop eventLoop = eventLoopMap.get(key); 610 eventLoopMap.remove(key); 611 eventLoop.leave(rval); 612 } 613 614 @Override public void exitAllNestedEventLoops() { 615 checkFxUserThread(); 616 for (EventLoop eventLoop : eventLoopMap.values()) { 617 eventLoop.leave(null); 618 } 619 } 620 621 @Override public TKStage createTKPopupStage(Window peerWindow, 622 StageStyle popupStyle, 623 TKStage owner, 624 AccessControlContext acc) { 625 assertToolkitRunning(); 626 boolean securityDialog = owner instanceof WindowStage ? 627 ((WindowStage)owner).isSecurityDialog() : false; 628 WindowStage stage = new WindowStage(peerWindow, securityDialog, popupStyle, null, owner); 629 stage.setSecurityContext(acc); 630 stage.setIsPopup(); 631 stage.init(systemMenu); 632 return stage; 633 } 634 635 @Override public TKStage createTKEmbeddedStage(HostInterface host, AccessControlContext acc) { 636 assertToolkitRunning(); 637 EmbeddedStage stage = new EmbeddedStage(host); 638 stage.setSecurityContext(acc); 639 return stage; 640 } 641 642 private static ScreenConfigurationAccessor screenAccessor = 643 new ScreenConfigurationAccessor() { 644 @Override public int getMinX(Object obj) { 645 return ((Screen)obj).getX(); 646 } 647 @Override public int getMinY(Object obj) { 648 return ((Screen)obj).getY(); 649 } 650 @Override public int getWidth(Object obj) { 651 return ((Screen)obj).getWidth(); 652 } 653 @Override public int getHeight(Object obj) { 654 return ((Screen)obj).getHeight(); 655 } 656 @Override public int getVisualMinX(Object obj) { 657 return ((Screen)obj).getVisibleX(); 658 } 659 @Override public int getVisualMinY(Object obj) { 660 return ((Screen)obj).getVisibleY(); 661 } 662 @Override public int getVisualWidth(Object obj) { 663 return ((Screen)obj).getVisibleWidth(); 664 } 665 @Override public int getVisualHeight(Object obj) { 666 return ((Screen)obj).getVisibleHeight(); 667 } 668 @Override public float getDPI(Object obj) { 669 return ((Screen)obj).getResolutionX(); 670 } 671 @Override public float getRecommendedOutputScaleX(Object obj) { 672 return ((Screen)obj).getRecommendedOutputScaleX(); 673 } 674 @Override public float getRecommendedOutputScaleY(Object obj) { 675 return ((Screen)obj).getRecommendedOutputScaleY(); 676 } 677 }; 678 679 @Override public ScreenConfigurationAccessor 680 setScreenConfigurationListener(final TKScreenConfigurationListener listener) { 681 Screen.setEventHandler(new Screen.EventHandler() { 682 @Override public void handleSettingsChanged() { 683 notifyScreenListener(listener); 684 } 685 }); 686 return screenAccessor; 687 } 688 689 private static void assignScreensAdapters() { 690 GraphicsPipeline pipeline = GraphicsPipeline.getPipeline(); 691 for (Screen screen : Screen.getScreens()) { 692 screen.setAdapterOrdinal(pipeline.getAdapterOrdinal(screen)); 693 } 694 } 695 696 private static void notifyScreenListener(TKScreenConfigurationListener listener) { 697 assignScreensAdapters(); 698 listener.screenConfigurationChanged(); 699 } 700 701 @Override public Object getPrimaryScreen() { 702 return Screen.getMainScreen(); 703 } 704 705 @Override public List<?> getScreens() { 706 return Screen.getScreens(); 707 } 708 709 @Override 710 public ScreenConfigurationAccessor getScreenConfigurationAccessor() { 711 return screenAccessor; 712 } 713 714 @Override 715 public PerformanceTracker getPerformanceTracker() { 716 return perfTracker; 717 } 718 719 @Override 720 public PerformanceTracker createPerformanceTracker() { 721 return new PerformanceTrackerImpl(); 722 } 723 724 // Only currently called from the loadImage method below. We do not 725 // necessarily know what the worst render scale we will ever see is 726 // because the user has control over that, but we should be loading 727 // all dpi variants of an image at all times anyway and then using 728 // whichever one is needed to respond to a given rendering request 729 // rather than predetermining which one to use up front. If we switch 730 // to making that decision at render time then this method can go away. 731 private float getMaxRenderScale() { 732 if (_maxPixelScale == 0) { 733 for (Object o : getScreens()) { 734 _maxPixelScale = Math.max(_maxPixelScale, ((Screen) o).getRecommendedOutputScaleX()); 735 _maxPixelScale = Math.max(_maxPixelScale, ((Screen) o).getRecommendedOutputScaleY()); 736 } 737 } 738 return _maxPixelScale; 739 } 740 741 @Override public ImageLoader loadImage(String url, double width, double height, boolean preserveRatio, boolean smooth) { 742 return new PrismImageLoader2(url, width, height, preserveRatio, getMaxRenderScale(), smooth); 743 } 744 745 @Override public ImageLoader loadImage(InputStream stream, double width, double height, 746 boolean preserveRatio, boolean smooth) { 747 return new PrismImageLoader2(stream, width, height, preserveRatio, smooth); 748 } 749 750 @Override public AbstractRemoteResource<? extends ImageLoader> loadImageAsync( 751 AsyncOperationListener listener, String url, 752 double width, double height, boolean preserveRatio, boolean smooth) { 753 return new PrismImageLoader2.AsyncImageLoader(listener, url, width, height, preserveRatio, smooth); 754 } 755 756 // Note that this method should only be called by PlatformImpl.runLater 757 // It should not be called directly by other FX code since the underlying 758 // glass invokeLater method is not thread-safe with respect to toolkit 759 // shutdown. Calling Platform.runLater *is* thread-safe even when the 760 // toolkit is shutting down. 761 @Override public void defer(Runnable runnable) { 762 if (!toolkitRunning.get()) return; 763 764 Application.invokeLater(runnable); 765 } 766 767 @Override public void exit() { 768 // This method must run on the FX application thread 769 checkFxUserThread(); 770 771 // Turn off pulses so no extraneous runnables are submitted 772 pulseTimer.stop(); 773 774 // We need to wait for the last frame to finish so that the renderer 775 // is not running while we are shutting down glass. 776 PaintCollector.getInstance().waitForRenderingToComplete(); 777 778 notifyShutdownHooks(); 779 780 runWithRenderLock(() -> { 781 //TODO - should update glass scene view state 782 //TODO - doesn't matter because we are exiting 783 Application app = Application.GetApplication(); 784 app.terminate(); 785 return null; 786 }); 787 788 dispose(); 789 790 super.exit(); 791 } 792 793 public void dispose() { 794 if (toolkitRunning.compareAndSet(true, false)) { 795 pulseTimer.stop(); 796 renderer.stopRenderer(); 797 798 try { 799 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 800 Runtime.getRuntime().removeShutdownHook(shutdownHook); 801 return null; 802 }); 803 } catch (IllegalStateException ignore) { 804 // throw when shutdown hook already removed 805 } 806 } 807 } 808 809 @Override public boolean isForwardTraversalKey(KeyEvent e) { 810 return (e.getCode() == KeyCode.TAB) 811 && (e.getEventType() == KeyEvent.KEY_PRESSED) 812 && !e.isShiftDown(); 813 } 814 815 @Override public boolean isBackwardTraversalKey(KeyEvent e) { 816 return (e.getCode() == KeyCode.TAB) 817 && (e.getEventType() == KeyEvent.KEY_PRESSED) 818 && e.isShiftDown(); 819 } 820 821 private Map<Object, Object> contextMap = Collections.synchronizedMap(new HashMap<>()); 822 @Override public Map<Object, Object> getContextMap() { 823 return contextMap; 824 } 825 826 @Override public int getRefreshRate() { 827 if (pulseHZ == null) { 828 return 60; 829 } else { 830 return pulseHZ; 831 } 832 } 833 834 private DelayedRunnable animationRunnable; 835 @Override public void setAnimationRunnable(DelayedRunnable animationRunnable) { 836 if (animationRunnable != null) { 837 animationRunning.set(true); 838 } 839 this.animationRunnable = animationRunnable; 840 } 841 842 @Override public void requestNextPulse() { 843 nextPulseRequested.set(true); 844 } 845 846 @Override public void waitFor(Task t) { 847 if (t.isFinished()) { 848 return; 849 } 850 } 851 852 @Override protected Object createColorPaint(Color color) { 853 return new com.sun.prism.paint.Color( 854 (float)color.getRed(), (float)color.getGreen(), 855 (float)color.getBlue(), (float)color.getOpacity()); 856 } 857 858 private com.sun.prism.paint.Color toPrismColor(Color color) { 859 return (com.sun.prism.paint.Color) Toolkit.getPaintAccessor().getPlatformPaint(color); 860 } 861 862 private List<com.sun.prism.paint.Stop> convertStops(List<Stop> paintStops) { 863 List<com.sun.prism.paint.Stop> stops = 864 new ArrayList<>(paintStops.size()); 865 for (Stop s : paintStops) { 866 stops.add(new com.sun.prism.paint.Stop(toPrismColor(s.getColor()), 867 (float) s.getOffset())); 868 } 869 return stops; 870 } 871 872 @Override protected Object createLinearGradientPaint(LinearGradient paint) { 873 int cmi = com.sun.prism.paint.Gradient.REPEAT; 874 CycleMethod cycleMethod = paint.getCycleMethod(); 875 if (cycleMethod == CycleMethod.NO_CYCLE) { 876 cmi = com.sun.prism.paint.Gradient.PAD; 877 } else if (cycleMethod == CycleMethod.REFLECT) { 878 cmi = com.sun.prism.paint.Gradient.REFLECT; 879 } 880 // TODO: extract colors/offsets and pass them in directly... 881 List<com.sun.prism.paint.Stop> stops = convertStops(paint.getStops()); 882 return new com.sun.prism.paint.LinearGradient( 883 (float)paint.getStartX(), (float)paint.getStartY(), (float)paint.getEndX(), (float)paint.getEndY(), 884 null, paint.isProportional(), cmi, stops); 885 } 886 887 @Override 888 protected Object createRadialGradientPaint(RadialGradient paint) { 889 float cx = (float)paint.getCenterX(); 890 float cy = (float)paint.getCenterY(); 891 float fa = (float)paint.getFocusAngle(); 892 float fd = (float)paint.getFocusDistance(); 893 894 int cmi = 0; 895 if (paint.getCycleMethod() == CycleMethod.NO_CYCLE) { 896 cmi = com.sun.prism.paint.Gradient.PAD; 897 } else if (paint.getCycleMethod() == CycleMethod.REFLECT) { 898 cmi = com.sun.prism.paint.Gradient.REFLECT; 899 } else { 900 cmi = com.sun.prism.paint.Gradient.REPEAT; 901 } 902 903 // TODO: extract colors/offsets and pass them in directly... 904 List<com.sun.prism.paint.Stop> stops = convertStops(paint.getStops()); 905 return new com.sun.prism.paint.RadialGradient(cx, cy, fa, fd, 906 (float)paint.getRadius(), null, paint.isProportional(), cmi, stops); 907 } 908 909 @Override 910 protected Object createImagePatternPaint(ImagePattern paint) { 911 if (paint.getImage() == null) { 912 return com.sun.prism.paint.Color.TRANSPARENT; 913 } else { 914 return new com.sun.prism.paint.ImagePattern( 915 (com.sun.prism.Image) Toolkit.getImageAccessor().getPlatformImage(paint.getImage()), 916 (float)paint.getX(), 917 (float)paint.getY(), 918 (float)paint.getWidth(), 919 (float)paint.getHeight(), 920 paint.isProportional(), 921 Toolkit.getPaintAccessor().isMutable(paint)); 922 } 923 } 924 925 static BasicStroke tmpStroke = new BasicStroke(); 926 private void initStroke(StrokeType pgtype, double strokewidth, 927 StrokeLineCap pgcap, 928 StrokeLineJoin pgjoin, float miterLimit, 929 float[] dashArray, float dashOffset) 930 { 931 int type; 932 if (pgtype == StrokeType.CENTERED) { 933 type = BasicStroke.TYPE_CENTERED; 934 } else if (pgtype == StrokeType.INSIDE) { 935 type = BasicStroke.TYPE_INNER; 936 } else { 937 type = BasicStroke.TYPE_OUTER; 938 } 939 940 int cap; 941 if (pgcap == StrokeLineCap.BUTT) { 942 cap = BasicStroke.CAP_BUTT; 943 } else if (pgcap == StrokeLineCap.SQUARE) { 944 cap = BasicStroke.CAP_SQUARE; 945 } else { 946 cap = BasicStroke.CAP_ROUND; 947 } 948 949 int join; 950 if (pgjoin == StrokeLineJoin.BEVEL) { 951 join = BasicStroke.JOIN_BEVEL; 952 } else if (pgjoin == StrokeLineJoin.MITER) { 953 join = BasicStroke.JOIN_MITER; 954 } else { 955 join = BasicStroke.JOIN_ROUND; 956 } 957 958 tmpStroke.set(type, (float) strokewidth, cap, join, miterLimit); 959 if ((dashArray != null) && (dashArray.length > 0)) { 960 tmpStroke.set(dashArray, dashOffset); 961 } else { 962 tmpStroke.set((float[])null, 0); 963 } 964 } 965 966 @Override 967 public void accumulateStrokeBounds(Shape shape, float bbox[], 968 StrokeType pgtype, 969 double strokewidth, 970 StrokeLineCap pgcap, 971 StrokeLineJoin pgjoin, 972 float miterLimit, 973 BaseTransform tx) 974 { 975 976 initStroke(pgtype, strokewidth, pgcap, pgjoin, miterLimit, null, 0); 977 if (tx.isTranslateOrIdentity()) { 978 tmpStroke.accumulateShapeBounds(bbox, shape, tx); 979 } else { 980 Shape.accumulate(bbox, tmpStroke.createStrokedShape(shape), tx); 981 } 982 } 983 984 @Override 985 public boolean strokeContains(Shape shape, double x, double y, 986 StrokeType pgtype, 987 double strokewidth, 988 StrokeLineCap pgcap, 989 StrokeLineJoin pgjoin, 990 float miterLimit) 991 { 992 initStroke(pgtype, strokewidth, pgcap, pgjoin, miterLimit, null, 0); 993 // TODO: The contains testing could be done directly without creating a Shape 994 return tmpStroke.createStrokedShape(shape).contains((float) x, (float) y); 995 } 996 997 @Override 998 public Shape createStrokedShape(Shape shape, 999 StrokeType pgtype, 1000 double strokewidth, 1001 StrokeLineCap pgcap, 1002 StrokeLineJoin pgjoin, 1003 float miterLimit, 1004 float[] dashArray, 1005 float dashOffset) { 1006 initStroke(pgtype, strokewidth, pgcap, pgjoin, miterLimit, 1007 dashArray, dashOffset); 1008 return tmpStroke.createStrokedShape(shape); 1009 } 1010 1011 @Override public Dimension2D getBestCursorSize(int preferredWidth, int preferredHeight) { 1012 return CursorUtils.getBestCursorSize(preferredWidth, preferredHeight); 1013 } 1014 1015 @Override public int getMaximumCursorColors() { 1016 return 2; 1017 } 1018 1019 @Override public int getKeyCodeForChar(String character) { 1020 return (character.length() == 1) 1021 ? com.sun.glass.events.KeyEvent.getKeyCodeForChar( 1022 character.charAt(0)) 1023 : com.sun.glass.events.KeyEvent.VK_UNDEFINED; 1024 } 1025 1026 @Override public PathElement[] convertShapeToFXPath(Object shape) { 1027 if (shape == null) { 1028 return new PathElement[0]; 1029 } 1030 List<PathElement> elements = new ArrayList<>(); 1031 // iterate over the shape and turn it into a series of path 1032 // elements 1033 com.sun.javafx.geom.Shape geomShape = (com.sun.javafx.geom.Shape) shape; 1034 PathIterator itr = geomShape.getPathIterator(null); 1035 PathIteratorHelper helper = new PathIteratorHelper(itr); 1036 PathIteratorHelper.Struct struct = new PathIteratorHelper.Struct(); 1037 1038 while (!helper.isDone()) { 1039 // true if WIND_EVEN_ODD, false if WIND_NON_ZERO 1040 boolean windEvenOdd = helper.getWindingRule() == PathIterator.WIND_EVEN_ODD; 1041 int type = helper.currentSegment(struct); 1042 PathElement el; 1043 if (type == PathIterator.SEG_MOVETO) { 1044 el = new MoveTo(struct.f0, struct.f1); 1045 } else if (type == PathIterator.SEG_LINETO) { 1046 el = new LineTo(struct.f0, struct.f1); 1047 } else if (type == PathIterator.SEG_QUADTO) { 1048 el = new QuadCurveTo( 1049 struct.f0, 1050 struct.f1, 1051 struct.f2, 1052 struct.f3); 1053 } else if (type == PathIterator.SEG_CUBICTO) { 1054 el = new CubicCurveTo ( 1055 struct.f0, 1056 struct.f1, 1057 struct.f2, 1058 struct.f3, 1059 struct.f4, 1060 struct.f5); 1061 } else if (type == PathIterator.SEG_CLOSE) { 1062 el = new ClosePath(); 1063 } else { 1064 throw new IllegalStateException("Invalid element type: " + type); 1065 } 1066 helper.next(); 1067 elements.add(el); 1068 } 1069 1070 return elements.toArray(new PathElement[elements.size()]); 1071 } 1072 1073 @Override public Filterable toFilterable(Image img) { 1074 return PrImage.create((com.sun.prism.Image) Toolkit.getImageAccessor().getPlatformImage(img)); 1075 } 1076 1077 @Override public FilterContext getFilterContext(Object config) { 1078 if (config == null || (!(config instanceof com.sun.glass.ui.Screen))) { 1079 return PrFilterContext.getDefaultInstance(); 1080 } 1081 Screen screen = (Screen)config; 1082 return PrFilterContext.getInstance(screen); 1083 } 1084 1085 @Override public AbstractMasterTimer getMasterTimer() { 1086 return MasterTimer.getInstance(); 1087 } 1088 1089 @Override public FontLoader getFontLoader() { 1090 return com.sun.javafx.font.PrismFontLoader.getInstance(); 1091 } 1092 1093 @Override public TextLayoutFactory getTextLayoutFactory() { 1094 return com.sun.javafx.text.PrismTextLayoutFactory.getFactory(); 1095 } 1096 1097 @Override public Object createSVGPathObject(SVGPath svgpath) { 1098 int windingRule = svgpath.getFillRule() == FillRule.NON_ZERO ? PathIterator.WIND_NON_ZERO : PathIterator.WIND_EVEN_ODD; 1099 Path2D path = new Path2D(windingRule); 1100 path.appendSVGPath(svgpath.getContent()); 1101 return path; 1102 } 1103 1104 @Override public Path2D createSVGPath2D(SVGPath svgpath) { 1105 int windingRule = svgpath.getFillRule() == FillRule.NON_ZERO ? PathIterator.WIND_NON_ZERO : PathIterator.WIND_EVEN_ODD; 1106 Path2D path = new Path2D(windingRule); 1107 path.appendSVGPath(svgpath.getContent()); 1108 return path; 1109 } 1110 1111 @Override public boolean imageContains(Object image, float x, float y) { 1112 if (image == null) { 1113 return false; 1114 } 1115 1116 com.sun.prism.Image pImage = (com.sun.prism.Image)image; 1117 int intX = (int)x + pImage.getMinX(); 1118 int intY = (int)y + pImage.getMinY(); 1119 1120 if (pImage.isOpaque()) { 1121 return true; 1122 } 1123 1124 if (pImage.getPixelFormat() == PixelFormat.INT_ARGB_PRE) { 1125 IntBuffer ib = (IntBuffer) pImage.getPixelBuffer(); 1126 int index = intX + intY * pImage.getRowLength(); 1127 if (index >= ib.limit()) { 1128 return false; 1129 } else { 1130 return (ib.get(index) & 0xff000000) != 0; 1131 } 1132 } else if (pImage.getPixelFormat() == PixelFormat.BYTE_BGRA_PRE) { 1133 ByteBuffer bb = (ByteBuffer) pImage.getPixelBuffer(); 1134 int index = intX * pImage.getBytesPerPixelUnit() + intY * pImage.getScanlineStride() + 3; 1135 if (index >= bb.limit()) { 1136 return false; 1137 } else { 1138 return (bb.get(index) & 0xff) != 0; 1139 } 1140 } else if (pImage.getPixelFormat() == PixelFormat.BYTE_ALPHA) { 1141 ByteBuffer bb = (ByteBuffer) pImage.getPixelBuffer(); 1142 int index = intX * pImage.getBytesPerPixelUnit() + intY * pImage.getScanlineStride(); 1143 if (index >= bb.limit()) { 1144 return false; 1145 } else { 1146 return (bb.get(index) & 0xff) != 0; 1147 } 1148 } 1149 return true; 1150 } 1151 1152 @Override 1153 public boolean isNestedLoopRunning() { 1154 return Application.isNestedLoopRunning(); 1155 } 1156 1157 @Override 1158 public boolean isSupported(ConditionalFeature feature) { 1159 switch (feature) { 1160 case SCENE3D: 1161 return GraphicsPipeline.getPipeline().is3DSupported(); 1162 case EFFECT: 1163 return GraphicsPipeline.getPipeline().isEffectSupported(); 1164 case SHAPE_CLIP: 1165 return true; 1166 case INPUT_METHOD: 1167 return Application.GetApplication().supportsInputMethods(); 1168 case TRANSPARENT_WINDOW: 1169 return Application.GetApplication().supportsTransparentWindows(); 1170 case UNIFIED_WINDOW: 1171 return Application.GetApplication().supportsUnifiedWindows(); 1172 case TWO_LEVEL_FOCUS: 1173 return Application.GetApplication().hasTwoLevelFocus(); 1174 case VIRTUAL_KEYBOARD: 1175 return Application.GetApplication().hasVirtualKeyboard(); 1176 case INPUT_TOUCH: 1177 return Application.GetApplication().hasTouch(); 1178 case INPUT_MULTITOUCH: 1179 return Application.GetApplication().hasMultiTouch(); 1180 case INPUT_POINTER: 1181 return Application.GetApplication().hasPointer(); 1182 default: 1183 return false; 1184 } 1185 } 1186 1187 @Override 1188 public boolean isMSAASupported() { 1189 return GraphicsPipeline.getPipeline().isMSAASupported(); 1190 } 1191 1192 static TransferMode clipboardActionToTransferMode(final int action) { 1193 switch (action) { 1194 case Clipboard.ACTION_NONE: 1195 return null; 1196 case Clipboard.ACTION_COPY: 1197 //IE drop action for URL copy 1198 case Clipboard.ACTION_COPY | Clipboard.ACTION_REFERENCE: 1199 return TransferMode.COPY; 1200 case Clipboard.ACTION_MOVE: 1201 //IE drop action for URL move 1202 case Clipboard.ACTION_MOVE | Clipboard.ACTION_REFERENCE: 1203 return TransferMode.MOVE; 1204 case Clipboard.ACTION_REFERENCE: 1205 return TransferMode.LINK; 1206 case Clipboard.ACTION_ANY: 1207 return TransferMode.COPY; // select a reasonable trasnfer mode as workaround until RT-22840 1208 } 1209 return null; 1210 } 1211 1212 private QuantumClipboard clipboard; 1213 @Override public TKClipboard getSystemClipboard() { 1214 if (clipboard == null) { 1215 clipboard = QuantumClipboard.getClipboardInstance(new ClipboardAssistance(com.sun.glass.ui.Clipboard.SYSTEM)); 1216 } 1217 return clipboard; 1218 } 1219 1220 private GlassSystemMenu systemMenu = new GlassSystemMenu(); 1221 @Override public TKSystemMenu getSystemMenu() { 1222 return systemMenu; 1223 } 1224 1225 @Override public TKClipboard getNamedClipboard(String name) { 1226 return null; 1227 } 1228 1229 @Override public void startDrag(TKScene scene, Set<TransferMode> tm, TKDragSourceListener l, Dragboard dragboard) { 1230 if (dragboard == null) { 1231 throw new IllegalArgumentException("dragboard should not be null"); 1232 } 1233 1234 GlassScene view = (GlassScene)scene; 1235 view.setTKDragSourceListener(l); 1236 1237 QuantumClipboard gc = (QuantumClipboard) DragboardHelper.getPeer(dragboard); 1238 gc.setSupportedTransferMode(tm); 1239 gc.flush(); 1240 1241 // flush causes a modal DnD event loop, when we return, close the clipboard 1242 gc.close(); 1243 } 1244 1245 @Override public void enableDrop(TKScene s, TKDropTargetListener l) { 1246 1247 assert s instanceof GlassScene; 1248 1249 GlassScene view = (GlassScene)s; 1250 view.setTKDropTargetListener(l); 1251 } 1252 1253 @Override public void registerDragGestureListener(TKScene s, Set<TransferMode> tm, TKDragGestureListener l) { 1254 1255 assert s instanceof GlassScene; 1256 1257 GlassScene view = (GlassScene)s; 1258 view.setTKDragGestureListener(l); 1259 } 1260 1261 @Override 1262 public void installInputMethodRequests(TKScene scene, InputMethodRequests requests) { 1263 1264 assert scene instanceof GlassScene; 1265 1266 GlassScene view = (GlassScene)scene; 1267 view.setInputMethodRequests(requests); 1268 } 1269 1270 static class QuantumImage implements com.sun.javafx.tk.ImageLoader, ResourceFactoryListener { 1271 1272 // cache rt here 1273 private com.sun.prism.RTTexture rt; 1274 private com.sun.prism.Image image; 1275 private ResourceFactory rf; 1276 1277 QuantumImage(com.sun.prism.Image image) { 1278 this.image = image; 1279 } 1280 1281 RTTexture getRT(int w, int h, ResourceFactory rfNew) { 1282 boolean rttOk = rt != null && rf == rfNew && 1283 rt.getContentWidth() == w && rt.getContentHeight() == h; 1284 if (rttOk) { 1285 rt.lock(); 1286 if (rt.isSurfaceLost()) { 1287 rttOk = false; 1288 } 1289 } 1290 1291 if (!rttOk) { 1292 if (rt != null) { 1293 rt.dispose(); 1294 } 1295 if (rf != null) { 1296 rf.removeFactoryListener(this); 1297 rf = null; 1298 } 1299 rt = rfNew.createRTTexture(w, h, WrapMode.CLAMP_TO_ZERO); 1300 if (rt != null) { 1301 rf = rfNew; 1302 rf.addFactoryListener(this); 1303 } 1304 } 1305 1306 return rt; 1307 } 1308 1309 void dispose() { 1310 if (rt != null) { 1311 rt.dispose(); 1312 rt = null; 1313 } 1314 } 1315 1316 void setImage(com.sun.prism.Image img) { 1317 image = img; 1318 } 1319 1320 @Override 1321 public Exception getException() { 1322 return (image == null) 1323 ? new IllegalStateException("Unitialized image") 1324 : null; 1325 } 1326 @Override 1327 public int getFrameCount() { return 1; } 1328 @Override 1329 public PlatformImage getFrame(int index) { return image; } 1330 @Override 1331 public int getFrameDelay(int index) { return 0; } 1332 @Override 1333 public int getLoopCount() { return 0; } 1334 @Override 1335 public double getWidth() { return image.getWidth(); } 1336 @Override 1337 public double getHeight() { return image.getHeight(); } 1338 @Override 1339 public void factoryReset() { dispose(); } 1340 @Override 1341 public void factoryReleased() { dispose(); } 1342 } 1343 1344 @Override public ImageLoader loadPlatformImage(Object platformImage) { 1345 if (platformImage instanceof QuantumImage) { 1346 return (QuantumImage)platformImage; 1347 } 1348 1349 if (platformImage instanceof com.sun.prism.Image) { 1350 return new QuantumImage((com.sun.prism.Image) platformImage); 1351 } 1352 1353 throw new UnsupportedOperationException("unsupported class for loadPlatformImage"); 1354 } 1355 1356 @Override 1357 public PlatformImage createPlatformImage(int w, int h) { 1358 ByteBuffer bytebuf = ByteBuffer.allocate(w * h * 4); 1359 return com.sun.prism.Image.fromByteBgraPreData(bytebuf, w, h); 1360 } 1361 1362 @Override 1363 public Object renderToImage(ImageRenderingContext p) { 1364 Object saveImage = p.platformImage; 1365 final ImageRenderingContext params = p; 1366 final com.sun.prism.paint.Paint currentPaint = p.platformPaint instanceof com.sun.prism.paint.Paint ? 1367 (com.sun.prism.paint.Paint)p.platformPaint : null; 1368 1369 RenderJob re = new RenderJob(new Runnable() { 1370 1371 private com.sun.prism.paint.Color getClearColor() { 1372 if (currentPaint == null) { 1373 return com.sun.prism.paint.Color.WHITE; 1374 } else if (currentPaint.getType() == com.sun.prism.paint.Paint.Type.COLOR) { 1375 return (com.sun.prism.paint.Color) currentPaint; 1376 } else if (currentPaint.isOpaque()) { 1377 return com.sun.prism.paint.Color.TRANSPARENT; 1378 } else { 1379 return com.sun.prism.paint.Color.WHITE; 1380 } 1381 } 1382 1383 private void draw(Graphics g, int x, int y, int w, int h) { 1384 g.setLights(params.lights); 1385 g.setDepthBuffer(params.depthBuffer); 1386 1387 g.clear(getClearColor()); 1388 if (currentPaint != null && 1389 currentPaint.getType() != com.sun.prism.paint.Paint.Type.COLOR) { 1390 g.getRenderTarget().setOpaque(currentPaint.isOpaque()); 1391 g.setPaint(currentPaint); 1392 g.fillQuad(0, 0, w, h); 1393 } 1394 1395 // Set up transform 1396 if (x != 0 || y != 0) { 1397 g.translate(-x, -y); 1398 } 1399 if (params.transform != null) { 1400 g.transform(params.transform); 1401 } 1402 1403 if (params.root != null) { 1404 if (params.camera != null) { 1405 g.setCamera(params.camera); 1406 } 1407 NGNode ngNode = params.root; 1408 ngNode.render(g); 1409 } 1410 1411 } 1412 1413 @Override 1414 public void run() { 1415 1416 ResourceFactory rf = GraphicsPipeline.getDefaultResourceFactory(); 1417 1418 if (!rf.isDeviceReady()) { 1419 return; 1420 } 1421 1422 int x = params.x; 1423 int y = params.y; 1424 int w = params.width; 1425 int h = params.height; 1426 1427 if (w <= 0 || h <= 0) { 1428 return; 1429 } 1430 1431 boolean errored = false; 1432 try { 1433 QuantumImage pImage = (params.platformImage instanceof QuantumImage) ? 1434 (QuantumImage)params.platformImage : new QuantumImage(null); 1435 1436 com.sun.prism.RTTexture rt = pImage.getRT(w, h, rf); 1437 1438 if (rt == null) { 1439 return; 1440 } 1441 1442 Graphics g = rt.createGraphics(); 1443 1444 draw(g, x, y, w, h); 1445 1446 int[] pixels = pImage.rt.getPixels(); 1447 1448 if (pixels != null) { 1449 pImage.setImage(com.sun.prism.Image.fromIntArgbPreData(pixels, w, h)); 1450 } else { 1451 IntBuffer ib = IntBuffer.allocate(w*h); 1452 if (pImage.rt.readPixels(ib, pImage.rt.getContentX(), 1453 pImage.rt.getContentY(), w, h)) 1454 { 1455 pImage.setImage(com.sun.prism.Image.fromIntArgbPreData(ib, w, h)); 1456 } else { 1457 pImage.dispose(); 1458 pImage = null; 1459 } 1460 } 1461 1462 rt.unlock(); 1463 1464 params.platformImage = pImage; 1465 1466 } catch (Throwable t) { 1467 errored = true; 1468 t.printStackTrace(System.err); 1469 } finally { 1470 Disposer.cleanUp(); 1471 rf.getTextureResourcePool().freeDisposalRequestedAndCheckResources(errored); 1472 } 1473 } 1474 }); 1475 1476 final CountDownLatch latch = new CountDownLatch(1); 1477 re.setCompletionListener(job -> latch.countDown()); 1478 addRenderJob(re); 1479 1480 do { 1481 try { 1482 latch.await(); 1483 break; 1484 } catch (InterruptedException ex) { 1485 ex.printStackTrace(); 1486 } 1487 } while (true); 1488 1489 Object image = params.platformImage; 1490 params.platformImage = saveImage; 1491 1492 return image; 1493 } 1494 1495 @Override 1496 public FileChooserResult showFileChooser(final TKStage ownerWindow, 1497 final String title, 1498 final File initialDirectory, 1499 final String initialFileName, 1500 final FileChooserType fileChooserType, 1501 final List<FileChooser.ExtensionFilter> 1502 extensionFilters, 1503 final FileChooser.ExtensionFilter selectedFilter) { 1504 WindowStage blockedStage = null; 1505 try { 1506 // NOTE: we block the owner of the owner deliberately. 1507 // The native system blocks the nearest owner itself. 1508 // Otherwise sheets on Mac are unusable. 1509 blockedStage = blockOwnerStage(ownerWindow); 1510 1511 return CommonDialogs.showFileChooser( 1512 (ownerWindow instanceof WindowStage) 1513 ? ((WindowStage) ownerWindow).getPlatformWindow() 1514 : null, 1515 initialDirectory, 1516 initialFileName, 1517 title, 1518 (fileChooserType == FileChooserType.SAVE) 1519 ? CommonDialogs.Type.SAVE 1520 : CommonDialogs.Type.OPEN, 1521 (fileChooserType == FileChooserType.OPEN_MULTIPLE), 1522 convertExtensionFilters(extensionFilters), 1523 extensionFilters.indexOf(selectedFilter)); 1524 } finally { 1525 if (blockedStage != null) { 1526 blockedStage.setEnabled(true); 1527 } 1528 } 1529 } 1530 1531 @Override 1532 public File showDirectoryChooser(final TKStage ownerWindow, 1533 final String title, 1534 final File initialDirectory) { 1535 WindowStage blockedStage = null; 1536 try { 1537 // NOTE: we block the owner of the owner deliberately. 1538 // The native system blocks the nearest owner itself. 1539 // Otherwise sheets on Mac are unusable. 1540 blockedStage = blockOwnerStage(ownerWindow); 1541 1542 return CommonDialogs.showFolderChooser( 1543 (ownerWindow instanceof WindowStage) 1544 ? ((WindowStage) ownerWindow).getPlatformWindow() 1545 : null, 1546 initialDirectory, title); 1547 } finally { 1548 if (blockedStage != null) { 1549 blockedStage.setEnabled(true); 1550 } 1551 } 1552 } 1553 1554 private WindowStage blockOwnerStage(final TKStage stage) { 1555 if (stage instanceof WindowStage) { 1556 final TKStage ownerStage = ((WindowStage) stage).getOwner(); 1557 if (ownerStage instanceof WindowStage) { 1558 final WindowStage ownerWindowStage = (WindowStage) ownerStage; 1559 ownerWindowStage.setEnabled(false); 1560 return ownerWindowStage; 1561 } 1562 } 1563 1564 return null; 1565 } 1566 1567 private static List<CommonDialogs.ExtensionFilter> 1568 convertExtensionFilters(final List<FileChooser.ExtensionFilter> 1569 extensionFilters) { 1570 final CommonDialogs.ExtensionFilter[] glassExtensionFilters = 1571 new CommonDialogs.ExtensionFilter[extensionFilters.size()]; 1572 1573 int i = 0; 1574 for (final FileChooser.ExtensionFilter extensionFilter: 1575 extensionFilters) { 1576 glassExtensionFilters[i++] = 1577 new CommonDialogs.ExtensionFilter( 1578 extensionFilter.getDescription(), 1579 extensionFilter.getExtensions()); 1580 } 1581 1582 return Arrays.asList(glassExtensionFilters); 1583 } 1584 1585 @Override 1586 public long getMultiClickTime() { 1587 return View.getMultiClickTime(); 1588 } 1589 1590 @Override 1591 public int getMultiClickMaxX() { 1592 return View.getMultiClickMaxX(); 1593 } 1594 1595 @Override 1596 public int getMultiClickMaxY() { 1597 return View.getMultiClickMaxY(); 1598 } 1599 1600 @Override 1601 public String getThemeName() { 1602 return Application.GetApplication().getHighContrastTheme(); 1603 } 1604 }