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.application; 27 28 import static com.sun.javafx.FXPermissions.CREATE_TRANSPARENT_WINDOW_PERMISSION; 29 import com.sun.javafx.PlatformUtil; 30 import com.sun.javafx.css.StyleManager; 31 import com.sun.javafx.tk.TKListener; 32 import com.sun.javafx.tk.TKStage; 33 import com.sun.javafx.tk.Toolkit; 34 import com.sun.javafx.util.ModuleHelper; 35 36 import java.lang.reflect.InvocationTargetException; 37 import java.lang.reflect.Method; 38 import java.security.AccessControlContext; 39 import java.security.AccessController; 40 import java.security.PrivilegedAction; 41 import java.util.ArrayList; 42 import java.util.HashMap; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Optional; 46 import java.util.Set; 47 import java.util.concurrent.CopyOnWriteArraySet; 48 import java.util.concurrent.CountDownLatch; 49 import java.util.concurrent.atomic.AtomicBoolean; 50 import java.util.concurrent.atomic.AtomicInteger; 51 import java.util.function.Predicate; 52 53 import javafx.application.Application; 54 import javafx.application.ConditionalFeature; 55 import javafx.beans.property.BooleanProperty; 56 import javafx.beans.property.SimpleBooleanProperty; 57 import javafx.scene.Scene; 58 import javafx.util.FXPermission; 59 60 public class PlatformImpl { 61 62 private static AtomicBoolean initialized = new AtomicBoolean(false); 63 private static AtomicBoolean platformExit = new AtomicBoolean(false); 64 private static AtomicBoolean toolkitExit = new AtomicBoolean(false); 65 private static CountDownLatch startupLatch = new CountDownLatch(1); 66 private static AtomicBoolean listenersRegistered = new AtomicBoolean(false); 67 private static TKListener toolkitListener = null; 68 private static volatile boolean implicitExit = true; 69 private static boolean taskbarApplication = true; 70 private static boolean contextual2DNavigation; 71 private static AtomicInteger pendingRunnables = new AtomicInteger(0); 72 private static AtomicInteger numWindows = new AtomicInteger(0); 73 private static volatile boolean firstWindowShown = false; 74 private static volatile boolean lastWindowClosed = false; 75 private static AtomicBoolean reallyIdle = new AtomicBoolean(false); 76 private static Set<FinishListener> finishListeners = 77 new CopyOnWriteArraySet<FinishListener>(); 78 private final static Object runLaterLock = new Object(); 79 private static Boolean isGraphicsSupported; 80 private static Boolean isControlsSupported; 81 private static Boolean isMediaSupported; 82 private static Boolean isWebSupported; 83 private static Boolean isSWTSupported; 84 private static Boolean isSwingSupported; 85 private static Boolean isFXMLSupported; 86 private static Boolean hasTwoLevelFocus; 87 private static Boolean hasVirtualKeyboard; 88 private static Boolean hasTouch; 89 private static Boolean hasMultiTouch; 90 private static Boolean hasPointer; 91 private static boolean isThreadMerged = false; 92 private static String applicationType = ""; 93 private static BooleanProperty accessibilityActive = new SimpleBooleanProperty(); 94 private static CountDownLatch allNestedLoopsExited = new CountDownLatch(1); 95 96 private static final boolean verbose 97 = AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> 98 Boolean.getBoolean("javafx.verbose")); 99 100 private static final boolean DEBUG 101 = AccessController.doPrivileged((PrivilegedAction<Boolean>) () 102 -> Boolean.getBoolean("com.sun.javafx.application.debug")); 103 104 // Internal permission used by FXCanvas (SWT interop) 105 private static final FXPermission FXCANVAS_PERMISSION = 106 new FXPermission("accessFXCanvasInternals"); 107 108 /** 109 * Set a flag indicating whether this application should show up in the 110 * task bar. The default value is true. 111 * 112 * @param taskbarApplication the new value of this attribute 113 */ 114 public static void setTaskbarApplication(boolean taskbarApplication) { 115 PlatformImpl.taskbarApplication = taskbarApplication; 116 } 117 118 /** 119 * Returns the current value of the taskBarApplication flag. 120 * 121 * @return the current state of the flag. 122 */ 123 public static boolean isTaskbarApplication() { 124 return taskbarApplication; 125 } 126 127 /** 128 * Sets the name of the this application based on the Application class. 129 * This method is called by the launcher, and is not 130 * called from the FX Application Thread, so we need to do it in a runLater. 131 * We do not need to wait for the result since it will complete before the 132 * Application start() method is called regardless. 133 * 134 * @param appClass the Application class. 135 */ 136 public static void setApplicationName(final Class appClass) { 137 runLater(() -> com.sun.glass.ui.Application.GetApplication().setName(appClass.getName())); 138 } 139 140 /** 141 * Return whether or not focus navigation between controls is context- 142 * sensitive. 143 * @return true if the context-sensitive algorithm for focus navigation is 144 * used 145 */ 146 public static boolean isContextual2DNavigation() { 147 return contextual2DNavigation; 148 } 149 150 /** 151 * This method is invoked typically on the main thread. At this point, 152 * the JavaFX Application Thread has not been started. Any attempt 153 * to call startup more than once results in all subsequent calls turning into 154 * nothing more than a runLater call with the provided Runnable being called. 155 * @param r 156 */ 157 public static void startup(final Runnable r) { 158 startup(r, false); 159 } 160 161 /** 162 * This method is invoked typically on the main thread. At this point, 163 * the JavaFX Application Thread has not been started. If preventDuplicateCalls 164 * is true, calling this method multiple times will result in an 165 * IllegalStateException. If it is false, calling this method multiple times 166 * will result in all subsequent calls turning into 167 * nothing more than a runLater call with the provided Runnable being called. 168 * @param r 169 * @param preventDuplicateCalls 170 */ 171 public static void startup(final Runnable r, boolean preventDuplicateCalls) { 172 173 // NOTE: if we ever support re-launching an application and/or 174 // launching a second application in the same VM/classloader 175 // this will need to be changed. 176 if (platformExit.get()) { 177 throw new IllegalStateException("Platform.exit has been called"); 178 } 179 180 if (initialized.getAndSet(true)) { 181 if (preventDuplicateCalls) { 182 throw new IllegalStateException("Toolkit already initialized"); 183 } 184 185 // If we've already initialized, just put the runnable on the queue. 186 runLater(r); 187 return; 188 } 189 190 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 191 applicationType = System.getProperty("com.sun.javafx.application.type"); 192 if (applicationType == null) applicationType = ""; 193 194 contextual2DNavigation = Boolean.getBoolean( 195 "com.sun.javafx.isContextual2DNavigation"); 196 String s = System.getProperty("com.sun.javafx.twoLevelFocus"); 197 if (s != null) { 198 hasTwoLevelFocus = Boolean.valueOf(s); 199 } 200 s = System.getProperty("com.sun.javafx.virtualKeyboard"); 201 if (s != null) { 202 if (s.equalsIgnoreCase("none")) { 203 hasVirtualKeyboard = false; 204 } else if (s.equalsIgnoreCase("javafx")) { 205 hasVirtualKeyboard = true; 206 } else if (s.equalsIgnoreCase("native")) { 207 hasVirtualKeyboard = true; 208 } 209 } 210 s = System.getProperty("com.sun.javafx.touch"); 211 if (s != null) { 212 hasTouch = Boolean.valueOf(s); 213 } 214 s = System.getProperty("com.sun.javafx.multiTouch"); 215 if (s != null) { 216 hasMultiTouch = Boolean.valueOf(s); 217 } 218 s = System.getProperty("com.sun.javafx.pointer"); 219 if (s != null) { 220 hasPointer = Boolean.valueOf(s); 221 } 222 s = System.getProperty("javafx.embed.singleThread"); 223 if (s != null) { 224 isThreadMerged = Boolean.valueOf(s); 225 if (isThreadMerged && !isSupported(ConditionalFeature.SWING)) { 226 isThreadMerged = false; 227 if (verbose) { 228 System.err.println( 229 "WARNING: javafx.embed.singleThread ignored (javafx.swing module not found)"); 230 } 231 } 232 } 233 return null; 234 }); 235 236 if (DEBUG) { 237 System.err.println("PlatformImpl::startup : applicationType = " 238 + applicationType); 239 } 240 if ("FXCanvas".equals(applicationType)) { 241 initFXCanvas(); 242 } 243 244 if (!taskbarApplication) { 245 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 246 System.setProperty("glass.taskbarApplication", "false"); 247 return null; 248 }); 249 } 250 251 // Create Toolkit listener and register it with the Toolkit. 252 // Call notifyFinishListeners when we get notified. 253 toolkitListener = new TKListener() { 254 @Override public void changedTopLevelWindows(List<TKStage> windows) { 255 numWindows.set(windows.size()); 256 checkIdle(); 257 } 258 259 @Override 260 public void exitedLastNestedLoop() { 261 if (isImplicitExit()) 262 allNestedLoopsExited.countDown(); 263 checkIdle(); 264 } 265 }; 266 Toolkit.getToolkit().addTkListener(toolkitListener); 267 268 Toolkit.getToolkit().startup(() -> { 269 startupLatch.countDown(); 270 r.run(); 271 }); 272 273 //Initialize the thread merging mechanism 274 if (isThreadMerged) { 275 installFwEventQueue(); 276 } 277 } 278 279 // Pass certain system properties to glass via the device details Map 280 private static void initDeviceDetailsFXCanvas() { 281 // Read the javafx.embed.eventProc system property and store 282 // it in an entry in the glass Application device details map 283 final String eventProcProperty = "javafx.embed.eventProc"; 284 final long eventProc = AccessController.doPrivileged((PrivilegedAction<Long>) () -> 285 Long.getLong(eventProcProperty, 0)); 286 if (eventProc != 0L) { 287 // Set the value for the javafx.embed.eventProc 288 // key in the glass Application map 289 Map map = com.sun.glass.ui.Application.getDeviceDetails(); 290 if (map == null) { 291 map = new HashMap(); 292 com.sun.glass.ui.Application.setDeviceDetails(map); 293 } 294 if (map.get(eventProcProperty) == null) { 295 map.put(eventProcProperty, eventProc); 296 } 297 } 298 } 299 300 // Add the necessary qualified exports to the calling module 301 private static void addExportsToFXCanvas(Class<?> fxCanvasClass) { 302 final String[] swtNeededPackages = { 303 "com.sun.glass.ui", 304 "com.sun.javafx.cursor", 305 "com.sun.javafx.embed", 306 "com.sun.javafx.stage", 307 "com.sun.javafx.tk" 308 }; 309 310 if (DEBUG) { 311 System.err.println("addExportsToFXCanvas: class = " + fxCanvasClass); 312 } 313 Object thisModule = ModuleHelper.getModule(PlatformImpl.class); 314 Object javafxSwtModule = ModuleHelper.getModule(fxCanvasClass); 315 for (String pkg : swtNeededPackages) { 316 if (DEBUG) { 317 System.err.println("add export of " + pkg + " from " 318 + thisModule + " to " + javafxSwtModule); 319 } 320 ModuleHelper.addExports(thisModule, pkg, javafxSwtModule); 321 } 322 } 323 324 // FXCanvas-specific initialization 325 private static void initFXCanvas() { 326 // Verify that we have the appropriate permission 327 final SecurityManager sm = System.getSecurityManager(); 328 if (sm != null) { 329 try { 330 sm.checkPermission(FXCANVAS_PERMISSION); 331 } catch (SecurityException ex) { 332 System.err.println("FXCanvas: no permission to access JavaFX internals"); 333 ex.printStackTrace(); 334 return; 335 } 336 } 337 338 // Find the calling class, ignoring any stack frames from FX application classes 339 Predicate<StackWalker.StackFrame> classFilter = f -> 340 !f.getClassName().startsWith("javafx.application.") 341 && !f.getClassName().startsWith("com.sun.javafx.application."); 342 343 final StackWalker walker = AccessController.doPrivileged((PrivilegedAction<StackWalker>) () -> 344 StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)); 345 Optional<StackWalker.StackFrame> frame = walker.walk( 346 s -> s.filter(classFilter).findFirst()); 347 348 if (frame.isPresent()) { 349 Class<?> caller = frame.get().getDeclaringClass(); 350 if (DEBUG) { 351 System.err.println("callerClassName = " + caller); 352 } 353 354 // Verify that the caller is javafx.embed.swt.FXCanvas 355 if ("javafx.embed.swt.FXCanvas".equals(caller.getName())) { 356 initDeviceDetailsFXCanvas(); 357 addExportsToFXCanvas(caller); 358 } 359 } 360 } 361 362 private static void installFwEventQueue() { 363 invokeSwingFXUtilsMethod("installFwEventQueue"); 364 } 365 366 private static void removeFwEventQueue() { 367 invokeSwingFXUtilsMethod("removeFwEventQueue"); 368 } 369 370 private static void invokeSwingFXUtilsMethod(final String methodName) { 371 //Use reflection in case we are running compact profile 372 try { 373 Class swingFXUtilsClass = Class.forName("com.sun.javafx.embed.swing.SwingFXUtilsImpl"); 374 Method installFwEventQueue = swingFXUtilsClass.getDeclaredMethod(methodName); 375 376 waitForStart(); 377 installFwEventQueue.invoke(null); 378 379 } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) { 380 throw new RuntimeException("Property javafx.embed.singleThread is not supported"); 381 } catch (InvocationTargetException e) { 382 throw new RuntimeException(e); 383 } 384 } 385 386 private static void waitForStart() { 387 // If the startup runnable has not yet been called, then wait it. 388 // Note that we check the count before calling await() to avoid 389 // the try/catch which is unnecessary after startup. 390 if (startupLatch.getCount() > 0) { 391 try { 392 startupLatch.await(); 393 } catch (InterruptedException ex) { 394 ex.printStackTrace(); 395 } 396 } 397 } 398 399 public static boolean isFxApplicationThread() { 400 return Toolkit.getToolkit().isFxUserThread(); 401 } 402 403 public static void runLater(final Runnable r) { 404 runLater(r, false); 405 } 406 407 private static void runLater(final Runnable r, boolean exiting) { 408 if (!initialized.get()) { 409 throw new IllegalStateException("Toolkit not initialized"); 410 } 411 412 pendingRunnables.incrementAndGet(); 413 waitForStart(); 414 415 synchronized (runLaterLock) { 416 if (!exiting && toolkitExit.get()) { 417 // Don't schedule a runnable after we have exited the toolkit 418 pendingRunnables.decrementAndGet(); 419 return; 420 } 421 422 final AccessControlContext acc = AccessController.getContext(); 423 // Don't catch exceptions, they are handled by Toolkit.defer() 424 Toolkit.getToolkit().defer(() -> { 425 try { 426 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 427 r.run(); 428 return null; 429 }, acc); 430 } finally { 431 pendingRunnables.decrementAndGet(); 432 checkIdle(); 433 } 434 }); 435 } 436 } 437 438 public static void runAndWait(final Runnable r) { 439 runAndWait(r, false); 440 } 441 442 private static void runAndWait(final Runnable r, boolean exiting) { 443 if (isFxApplicationThread()) { 444 try { 445 r.run(); 446 } catch (Throwable t) { 447 System.err.println("Exception in runnable"); 448 t.printStackTrace(); 449 } 450 } else { 451 final CountDownLatch doneLatch = new CountDownLatch(1); 452 runLater(() -> { 453 try { 454 r.run(); 455 } finally { 456 doneLatch.countDown(); 457 } 458 }, exiting); 459 460 if (!exiting && toolkitExit.get()) { 461 throw new IllegalStateException("Toolkit has exited"); 462 } 463 464 try { 465 doneLatch.await(); 466 } catch (InterruptedException ex) { 467 ex.printStackTrace(); 468 } 469 } 470 } 471 472 public static void setImplicitExit(boolean implicitExit) { 473 PlatformImpl.implicitExit = implicitExit; 474 checkIdle(); 475 } 476 477 public static boolean isImplicitExit() { 478 return implicitExit; 479 } 480 481 public static void addListener(FinishListener l) { 482 listenersRegistered.set(true); 483 finishListeners.add(l); 484 } 485 486 public static void removeListener(FinishListener l) { 487 finishListeners.remove(l); 488 listenersRegistered.set(!finishListeners.isEmpty()); 489 if (!listenersRegistered.get()) { 490 checkIdle(); 491 } 492 } 493 494 private static void notifyFinishListeners(boolean exitCalled) { 495 // Notify listeners if any are registered, else exit directly 496 if (listenersRegistered.get()) { 497 for (FinishListener l : finishListeners) { 498 if (exitCalled) { 499 l.exitCalled(); 500 } else { 501 l.idle(implicitExit); 502 } 503 } 504 } else if (implicitExit || platformExit.get()) { 505 tkExit(); 506 } 507 } 508 509 // Check for idle, meaning the last top-level window has been closed and 510 // there are no pending Runnables waiting to be run. 511 private static void checkIdle() { 512 // If we aren't initialized yet, then this method is a no-op. 513 if (!initialized.get()) { 514 return; 515 } 516 517 if (!isFxApplicationThread()) { 518 // Add a dummy runnable to the runLater queue, which will then call 519 // checkIdle() on the FX application thread. 520 runLater(() -> { 521 }); 522 return; 523 } 524 525 boolean doNotify = false; 526 527 synchronized (PlatformImpl.class) { 528 int numWin = numWindows.get(); 529 if (numWin > 0) { 530 firstWindowShown = true; 531 lastWindowClosed = false; 532 reallyIdle.set(false); 533 } else if (numWin == 0 && firstWindowShown) { 534 lastWindowClosed = true; 535 } 536 537 // In case there is an event in process, allow for it to show 538 // another window. If no new window is shown before all pending 539 // runnables (including this one) are done and there is no running 540 // nested loops, then we will shutdown. 541 if (lastWindowClosed && pendingRunnables.get() == 0 542 && (toolkitExit.get() || !Toolkit.getToolkit().isNestedLoopRunning())) { 543 // System.err.println("Last window closed and no pending runnables"); 544 if (reallyIdle.getAndSet(true)) { 545 // System.err.println("Really idle now"); 546 doNotify = true; 547 lastWindowClosed = false; 548 } else { 549 // System.err.println("Queuing up a dummy idle check runnable"); 550 runLater(() -> { 551 // System.err.println("Dummy runnable"); 552 }); 553 } 554 } 555 } 556 557 if (doNotify) { 558 notifyFinishListeners(false); 559 } 560 } 561 562 // package scope method for testing 563 private static final CountDownLatch platformExitLatch = new CountDownLatch(1); 564 static CountDownLatch test_getPlatformExitLatch() { 565 return platformExitLatch; 566 } 567 568 public static void tkExit() { 569 570 PlatformImpl.runAndWait(() -> { 571 if (Toolkit.getToolkit().isNestedLoopRunning()) { 572 Toolkit.getToolkit().exitAllNestedEventLoops(); 573 } else { 574 allNestedLoopsExited.countDown(); 575 } 576 }); 577 578 try { 579 allNestedLoopsExited.await(); 580 } catch (InterruptedException e) { 581 throw new RuntimeException("Could not exit all nested event loops"); 582 } 583 584 if (toolkitExit.getAndSet(true)) { 585 return; 586 } 587 588 if (initialized.get()) { 589 // Always call toolkit exit on FX app thread 590 // System.err.println("PlatformImpl.tkExit: scheduling Toolkit.exit"); 591 PlatformImpl.runAndWait(() -> { 592 // System.err.println("PlatformImpl.tkExit: calling Toolkit.exit"); 593 Toolkit.getToolkit().exit(); 594 }, true); 595 596 if (isThreadMerged) { 597 removeFwEventQueue(); 598 } 599 600 Toolkit.getToolkit().removeTkListener(toolkitListener); 601 toolkitListener = null; 602 platformExitLatch.countDown(); 603 } 604 } 605 606 public static BooleanProperty accessibilityActiveProperty() { 607 return accessibilityActive; 608 } 609 610 public static void exit() { 611 platformExit.set(true); 612 notifyFinishListeners(true); 613 } 614 615 private static Boolean checkForClass(String classname) { 616 try { 617 Class.forName(classname, false, PlatformImpl.class.getClassLoader()); 618 return Boolean.TRUE; 619 } catch (ClassNotFoundException cnfe) { 620 return Boolean.FALSE; 621 } 622 } 623 624 public static boolean isSupported(ConditionalFeature feature) { 625 final boolean supported = isSupportedImpl(feature); 626 if (supported && (feature == ConditionalFeature.TRANSPARENT_WINDOW)) { 627 // some features require the application to have the corresponding 628 // permissions, if the application doesn't have them, the platform 629 // will behave as if the feature wasn't supported 630 final SecurityManager securityManager = 631 System.getSecurityManager(); 632 if (securityManager != null) { 633 try { 634 securityManager.checkPermission(CREATE_TRANSPARENT_WINDOW_PERMISSION); 635 } catch (final SecurityException e) { 636 return false; 637 } 638 } 639 640 return true; 641 } 642 643 return supported; 644 } 645 646 public static interface FinishListener { 647 public void idle(boolean implicitExit); 648 public void exitCalled(); 649 } 650 651 /** 652 * Set the platform user agent stylesheet to the default. 653 */ 654 public static void setDefaultPlatformUserAgentStylesheet() { 655 setPlatformUserAgentStylesheet(Application.STYLESHEET_MODENA); 656 } 657 658 private static boolean isModena = false; 659 private static boolean isCaspian = false; 660 661 /** 662 * Current Platform User Agent Stylesheet is Modena. 663 * 664 * Note: Please think hard before using this as we really want to avoid special cases in the platform for specific 665 * themes. This was added to allow tempory work arounds in the platform for bugs. 666 * 667 * @return true if using modena stylesheet 668 */ 669 public static boolean isModena() { 670 return isModena; 671 } 672 673 /** 674 * Current Platform User Agent Stylesheet is Caspian. 675 * 676 * Note: Please think hard before using this as we really want to avoid special cases in the platform for specific 677 * themes. This was added to allow tempory work arounds in the platform for bugs. 678 * 679 * @return true if using caspian stylesheet 680 */ 681 public static boolean isCaspian() { 682 return isCaspian; 683 } 684 685 /** 686 * Set the platform user agent stylesheet to the given URL. This method has special handling for platform theme 687 * name constants. 688 */ 689 public static void setPlatformUserAgentStylesheet(final String stylesheetUrl) { 690 if (isFxApplicationThread()) { 691 _setPlatformUserAgentStylesheet(stylesheetUrl); 692 } else { 693 runLater(() -> _setPlatformUserAgentStylesheet(stylesheetUrl)); 694 } 695 } 696 697 private static String accessibilityTheme; 698 public static boolean setAccessibilityTheme(String platformTheme) { 699 700 if (accessibilityTheme != null) { 701 StyleManager.getInstance().removeUserAgentStylesheet(accessibilityTheme); 702 accessibilityTheme = null; 703 } 704 705 _setAccessibilityTheme(platformTheme); 706 707 if (accessibilityTheme != null) { 708 StyleManager.getInstance().addUserAgentStylesheet(accessibilityTheme); 709 return true; 710 } 711 return false; 712 713 } 714 715 private static void _setAccessibilityTheme(String platformTheme) { 716 717 // check to see if there is an override to enable a high-contrast theme 718 final String userTheme = AccessController.doPrivileged( 719 (PrivilegedAction<String>) () -> System.getProperty("com.sun.javafx.highContrastTheme")); 720 721 if (isCaspian()) { 722 if (platformTheme != null || userTheme != null) { 723 // caspian has only one high contrast theme, use it regardless of the user or platform theme. 724 accessibilityTheme = "com/sun/javafx/scene/control/skin/caspian/highcontrast.css"; 725 } 726 } else if (isModena()) { 727 // User-defined property takes precedence 728 if (userTheme != null) { 729 switch (userTheme.toUpperCase()) { 730 case "BLACKONWHITE": 731 accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/blackOnWhite.css"; 732 break; 733 case "WHITEONBLACK": 734 accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/whiteOnBlack.css"; 735 break; 736 case "YELLOWONBLACK": 737 accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/yellowOnBlack.css"; 738 break; 739 default: 740 } 741 } else { 742 if (platformTheme != null) { 743 // The following names are Platform specific (Windows 7 and 8) 744 switch (platformTheme) { 745 case "High Contrast White": 746 accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/blackOnWhite.css"; 747 break; 748 case "High Contrast Black": 749 accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/whiteOnBlack.css"; 750 break; 751 case "High Contrast #1": 752 case "High Contrast #2": //TODO #2 should be green on black 753 accessibilityTheme = "com/sun/javafx/scene/control/skin/modena/yellowOnBlack.css"; 754 break; 755 default: 756 } 757 } 758 } 759 } 760 } 761 762 private static void _setPlatformUserAgentStylesheet(String stylesheetUrl) { 763 isModena = isCaspian = false; 764 // check for command line override 765 final String overrideStylesheetUrl = AccessController.doPrivileged( 766 (PrivilegedAction<String>) () -> System.getProperty("javafx.userAgentStylesheetUrl")); 767 768 if (overrideStylesheetUrl != null) { 769 stylesheetUrl = overrideStylesheetUrl; 770 } 771 772 final List<String> uaStylesheets = new ArrayList<>(); 773 774 // check for named theme constants for modena and caspian 775 if (Application.STYLESHEET_CASPIAN.equalsIgnoreCase(stylesheetUrl)) { 776 isCaspian = true; 777 778 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/caspian.css"); 779 780 if (isSupported(ConditionalFeature.INPUT_TOUCH)) { 781 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/embedded.css"); 782 if (com.sun.javafx.util.Utils.isQVGAScreen()) { 783 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/embedded-qvga.css"); 784 } 785 if (PlatformUtil.isAndroid()) { 786 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/android.css"); 787 } 788 } 789 790 if (isSupported(ConditionalFeature.TWO_LEVEL_FOCUS)) { 791 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/two-level-focus.css"); 792 } 793 794 if (isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) { 795 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/fxvk.css"); 796 } 797 798 if (!isSupported(ConditionalFeature.TRANSPARENT_WINDOW)) { 799 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/caspian-no-transparency.css"); 800 } 801 802 } else if (Application.STYLESHEET_MODENA.equalsIgnoreCase(stylesheetUrl)) { 803 isModena = true; 804 805 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/modena.css"); 806 807 if (isSupported(ConditionalFeature.INPUT_TOUCH)) { 808 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/touch.css"); 809 } 810 // when running on embedded add a extra stylesheet to tune performance of modena theme 811 if (PlatformUtil.isEmbedded()) { 812 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/modena-embedded-performance.css"); 813 } 814 if (PlatformUtil.isAndroid()) { 815 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/android.css"); 816 } 817 818 if (isSupported(ConditionalFeature.TWO_LEVEL_FOCUS)) { 819 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/two-level-focus.css"); 820 } 821 822 if (isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) { 823 uaStylesheets.add("com/sun/javafx/scene/control/skin/caspian/fxvk.css"); 824 } 825 826 if (!isSupported(ConditionalFeature.TRANSPARENT_WINDOW)) { 827 uaStylesheets.add("com/sun/javafx/scene/control/skin/modena/modena-no-transparency.css"); 828 } 829 830 } else { 831 uaStylesheets.add(stylesheetUrl); 832 } 833 834 // Ensure that accessibility starts right 835 _setAccessibilityTheme(Toolkit.getToolkit().getThemeName()); 836 if (accessibilityTheme != null) { 837 uaStylesheets.add(accessibilityTheme); 838 } 839 840 AccessController.doPrivileged((PrivilegedAction) () -> { 841 StyleManager.getInstance().setUserAgentStylesheets(uaStylesheets); 842 return null; 843 }); 844 845 } 846 847 public static void addNoTransparencyStylesheetToScene(final Scene scene) { 848 if (PlatformImpl.isCaspian()) { 849 AccessController.doPrivileged((PrivilegedAction) () -> { 850 StyleManager.getInstance().addUserAgentStylesheet(scene, 851 "com/sun/javafx/scene/control/skin/caspian/caspian-no-transparency.css"); 852 return null; 853 }); 854 } else if (PlatformImpl.isModena()) { 855 AccessController.doPrivileged((PrivilegedAction) () -> { 856 StyleManager.getInstance().addUserAgentStylesheet(scene, 857 "com/sun/javafx/scene/control/skin/modena/modena-no-transparency.css"); 858 return null; 859 }); 860 } 861 } 862 863 private static boolean isSupportedImpl(ConditionalFeature feature) { 864 switch (feature) { 865 case GRAPHICS: 866 if (isGraphicsSupported == null) { 867 isGraphicsSupported = checkForClass("javafx.stage.Stage"); 868 } 869 return isGraphicsSupported; 870 case CONTROLS: 871 if (isControlsSupported == null) { 872 isControlsSupported = checkForClass( 873 "javafx.scene.control.Control"); 874 } 875 return isControlsSupported; 876 case MEDIA: 877 if (isMediaSupported == null) { 878 isMediaSupported = checkForClass( 879 "javafx.scene.media.MediaView"); 880 if (isMediaSupported && PlatformUtil.isEmbedded()) { 881 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 882 String s = System.getProperty( 883 "com.sun.javafx.experimental.embedded.media", 884 "false"); 885 isMediaSupported = Boolean.valueOf(s); 886 return null; 887 888 }); 889 } 890 } 891 return isMediaSupported; 892 case WEB: 893 if (isWebSupported == null) { 894 isWebSupported = checkForClass("javafx.scene.web.WebView"); 895 if (isWebSupported && PlatformUtil.isEmbedded()) { 896 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 897 String s = System.getProperty( 898 "com.sun.javafx.experimental.embedded.web", 899 "false"); 900 isWebSupported = Boolean.valueOf(s); 901 return null; 902 903 }); 904 } 905 } 906 return isWebSupported; 907 case SWT: 908 if (isSWTSupported == null) { 909 isSWTSupported = checkForClass("javafx.embed.swt.FXCanvas"); 910 } 911 return isSWTSupported; 912 case SWING: 913 if (isSwingSupported == null) { 914 isSwingSupported = 915 // check for JComponent first, it may not be present 916 checkForClass("javax.swing.JComponent") && 917 checkForClass("javafx.embed.swing.JFXPanel"); 918 } 919 return isSwingSupported; 920 case FXML: 921 if (isFXMLSupported == null) { 922 isFXMLSupported = checkForClass("javafx.fxml.FXMLLoader") 923 && checkForClass("javax.xml.stream.XMLInputFactory"); 924 } 925 return isFXMLSupported; 926 case TWO_LEVEL_FOCUS: 927 if (hasTwoLevelFocus == null) { 928 return Toolkit.getToolkit().isSupported(feature); 929 } 930 return hasTwoLevelFocus; 931 case VIRTUAL_KEYBOARD: 932 if (hasVirtualKeyboard == null) { 933 return Toolkit.getToolkit().isSupported(feature); 934 } 935 return hasVirtualKeyboard; 936 case INPUT_TOUCH: 937 if (hasTouch == null) { 938 return Toolkit.getToolkit().isSupported(feature); 939 } 940 return hasTouch; 941 case INPUT_MULTITOUCH: 942 if (hasMultiTouch == null) { 943 return Toolkit.getToolkit().isSupported(feature); 944 } 945 return hasMultiTouch; 946 case INPUT_POINTER: 947 if (hasPointer == null) { 948 return Toolkit.getToolkit().isSupported(feature); 949 } 950 return hasPointer; 951 default: 952 return Toolkit.getToolkit().isSupported(feature); 953 } 954 } 955 }