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