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