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