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