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