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