1 /*
   2  * Copyright (c) 2011, 2020, 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 sun.lwawt.macosx;
  27 
  28 import java.awt.AWTError;
  29 import java.awt.AWTException;
  30 import java.awt.CheckboxMenuItem;
  31 import java.awt.Color;
  32 import java.awt.Component;
  33 import java.awt.Cursor;
  34 import java.awt.Desktop;
  35 import java.awt.Dialog;
  36 import java.awt.Dimension;
  37 import java.awt.Event;
  38 import java.awt.EventQueue;
  39 import java.awt.FileDialog;
  40 import java.awt.Frame;
  41 import java.awt.GraphicsConfiguration;
  42 import java.awt.GraphicsDevice;
  43 import java.awt.GraphicsEnvironment;
  44 import java.awt.HeadlessException;
  45 import java.awt.Image;
  46 import java.awt.Insets;
  47 import java.awt.Menu;
  48 import java.awt.MenuBar;
  49 import java.awt.MenuItem;
  50 import java.awt.Point;
  51 import java.awt.PopupMenu;
  52 import java.awt.RenderingHints;
  53 import java.awt.SystemTray;
  54 import java.awt.Taskbar;
  55 import java.awt.Toolkit;
  56 import java.awt.TrayIcon;
  57 import java.awt.Window;
  58 import java.awt.datatransfer.Clipboard;
  59 import java.awt.dnd.DragGestureEvent;
  60 import java.awt.dnd.DragGestureListener;
  61 import java.awt.dnd.DragGestureRecognizer;
  62 import java.awt.dnd.DragSource;
  63 import java.awt.dnd.DropTarget;
  64 import java.awt.dnd.InvalidDnDOperationException;
  65 import java.awt.dnd.MouseDragGestureRecognizer;
  66 import java.awt.dnd.peer.DragSourceContextPeer;
  67 import java.awt.event.InputEvent;
  68 import java.awt.event.InvocationEvent;
  69 import java.awt.event.KeyEvent;
  70 import java.awt.font.TextAttribute;
  71 import java.awt.im.InputMethodHighlight;
  72 import java.awt.im.spi.InputMethodDescriptor;
  73 import java.awt.peer.CheckboxMenuItemPeer;
  74 import java.awt.peer.DesktopPeer;
  75 import java.awt.peer.DialogPeer;
  76 import java.awt.peer.FileDialogPeer;
  77 import java.awt.peer.FontPeer;
  78 import java.awt.peer.MenuBarPeer;
  79 import java.awt.peer.MenuItemPeer;
  80 import java.awt.peer.MenuPeer;
  81 import java.awt.peer.PopupMenuPeer;
  82 import java.awt.peer.RobotPeer;
  83 import java.awt.peer.SystemTrayPeer;
  84 import java.awt.peer.TaskbarPeer;
  85 import java.awt.peer.TrayIconPeer;
  86 import java.lang.reflect.InvocationTargetException;
  87 import java.lang.reflect.UndeclaredThrowableException;
  88 import java.net.MalformedURLException;
  89 import java.net.URL;
  90 import java.security.AccessController;
  91 import java.security.PrivilegedAction;
  92 import java.util.HashMap;
  93 import java.util.Locale;
  94 import java.util.Map;
  95 import java.util.MissingResourceException;
  96 import java.util.Objects;
  97 import java.util.ResourceBundle;
  98 import java.util.concurrent.Callable;
  99 
 100 import javax.swing.UIManager;
 101 
 102 import com.apple.laf.AquaMenuBarUI;
 103 import sun.awt.AWTAccessor;
 104 import sun.awt.AppContext;
 105 import sun.awt.CGraphicsDevice;
 106 import sun.awt.LightweightFrame;
 107 import sun.awt.PlatformGraphicsInfo;
 108 import sun.awt.SunToolkit;
 109 import sun.awt.datatransfer.DataTransferer;
 110 import sun.awt.dnd.SunDragSourceContextPeer;
 111 import sun.awt.util.ThreadGroupUtils;
 112 import sun.java2d.opengl.OGLRenderQueue;
 113 import sun.lwawt.LWComponentPeer;
 114 import sun.lwawt.LWCursorManager;
 115 import sun.lwawt.LWToolkit;
 116 import sun.lwawt.LWWindowPeer;
 117 import sun.lwawt.LWWindowPeer.PeerType;
 118 import sun.lwawt.PlatformComponent;
 119 import sun.lwawt.PlatformDropTarget;
 120 import sun.lwawt.PlatformWindow;
 121 import sun.lwawt.SecurityWarningWindow;
 122 
 123 @SuppressWarnings("serial") // JDK implementation class
 124 final class NamedCursor extends Cursor {
 125     NamedCursor(String name) {
 126         super(name);
 127     }
 128 }
 129 
 130 /**
 131  * Mac OS X Cocoa-based AWT Toolkit.
 132  */
 133 public final class LWCToolkit extends LWToolkit {
 134     // While it is possible to enumerate all mouse devices
 135     // and query them for the number of buttons, the code
 136     // that does it is rather complex. Instead, we opt for
 137     // the easy way and just support up to 5 mouse buttons,
 138     // like Windows.
 139     private static final int BUTTONS = 5;
 140 
 141     private static native void initIDs();
 142     private static native void initAppkit(ThreadGroup appKitThreadGroup, boolean headless);
 143     private static CInputMethodDescriptor sInputMethodDescriptor;
 144 
 145     static {
 146         System.err.flush();
 147 
 148         ResourceBundle platformResources = java.security.AccessController.doPrivileged(
 149                 new java.security.PrivilegedAction<ResourceBundle>() {
 150             @Override
 151             public ResourceBundle run() {
 152                 ResourceBundle platformResources = null;
 153                 try {
 154                     platformResources = ResourceBundle.getBundle("sun.awt.resources.awtosx");
 155                 } catch (MissingResourceException e) {
 156                     // No resource file; defaults will be used.
 157                 }
 158 
 159                 System.loadLibrary("awt");
 160                 System.loadLibrary("fontmanager");
 161 
 162                 return platformResources;
 163             }
 164         });
 165 
 166         if (!GraphicsEnvironment.isHeadless() &&
 167             !PlatformGraphicsInfo.isInAquaSession())
 168         {
 169             throw new AWTError("WindowServer is not available");
 170         }
 171 
 172         AWTAccessor.getToolkitAccessor().setPlatformResources(platformResources);
 173 
 174         if (!GraphicsEnvironment.isHeadless()) {
 175             initIDs();
 176         }
 177         inAWT = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
 178             @Override
 179             public Boolean run() {
 180                 return !Boolean.parseBoolean(System.getProperty("javafx.embed.singleThread", "false"));
 181             }
 182         });
 183     }
 184 
 185     /*
 186      * If true  we operate in normal mode and nested runloop is executed in JavaRunLoopMode
 187      * If false we operate in singleThreaded FX/AWT interop mode and nested loop uses NSDefaultRunLoopMode
 188      */
 189     private static final boolean inAWT;
 190 
 191     public LWCToolkit() {
 192         final String extraButtons = "sun.awt.enableExtraMouseButtons";
 193         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 194             areExtraMouseButtonsEnabled =
 195                  Boolean.parseBoolean(System.getProperty(extraButtons, "true"));
 196             //set system property if not yet assigned
 197             System.setProperty(extraButtons, ""+areExtraMouseButtonsEnabled);
 198             initAppkit(ThreadGroupUtils.getRootThreadGroup(),
 199                        GraphicsEnvironment.isHeadless());
 200             return null;
 201         });
 202     }
 203 
 204     /*
 205      * System colors with default initial values, overwritten by toolkit if system values differ and are available.
 206      */
 207     private static final int NUM_APPLE_COLORS = 3;
 208     public static final int KEYBOARD_FOCUS_COLOR = 0;
 209     public static final int INACTIVE_SELECTION_BACKGROUND_COLOR = 1;
 210     public static final int INACTIVE_SELECTION_FOREGROUND_COLOR = 2;
 211     private static int[] appleColors = {
 212         0xFF808080, // keyboardFocusColor = Color.gray;
 213         0xFFC0C0C0, // secondarySelectedControlColor
 214         0xFF303030, // controlDarkShadowColor
 215     };
 216 
 217     private native void loadNativeColors(final int[] systemColors, final int[] appleColors);
 218 
 219     @Override
 220     protected void loadSystemColors(final int[] systemColors) {
 221         if (systemColors == null) return;
 222         loadNativeColors(systemColors, appleColors);
 223     }
 224 
 225     @SuppressWarnings("serial") // JDK implementation class
 226     private static class AppleSpecificColor extends Color {
 227         private final int index;
 228         AppleSpecificColor(int index) {
 229             super(appleColors[index]);
 230             this.index = index;
 231         }
 232 
 233         @Override
 234         public int getRGB() {
 235             return appleColors[index];
 236         }
 237     }
 238 
 239     /**
 240      * Returns Apple specific colors that we may expose going forward.
 241      */
 242     public static Color getAppleColor(int color) {
 243         return new AppleSpecificColor(color);
 244     }
 245 
 246     // This is only called from native code.
 247     static void systemColorsChanged() {
 248         EventQueue.invokeLater(() -> {
 249             AccessController.doPrivileged( (PrivilegedAction<Object>) () -> {
 250                 AWTAccessor.getSystemColorAccessor().updateSystemColors();
 251                 return null;
 252             });
 253         });
 254     }
 255 
 256     public static LWCToolkit getLWCToolkit() {
 257         return (LWCToolkit)Toolkit.getDefaultToolkit();
 258     }
 259 
 260     @Override
 261     protected PlatformWindow createPlatformWindow(PeerType peerType) {
 262         if (peerType == PeerType.EMBEDDED_FRAME) {
 263             return new CPlatformEmbeddedFrame();
 264         } else if (peerType == PeerType.VIEW_EMBEDDED_FRAME) {
 265             return new CViewPlatformEmbeddedFrame();
 266         } else if (peerType == PeerType.LW_FRAME) {
 267             return new CPlatformLWWindow();
 268         } else {
 269             assert (peerType == PeerType.SIMPLEWINDOW
 270                     || peerType == PeerType.DIALOG
 271                     || peerType == PeerType.FRAME);
 272             return new CPlatformWindow();
 273         }
 274     }
 275 
 276     LWWindowPeer createEmbeddedFrame(CEmbeddedFrame target) {
 277         PlatformComponent platformComponent = createPlatformComponent();
 278         PlatformWindow platformWindow = createPlatformWindow(PeerType.EMBEDDED_FRAME);
 279         return createDelegatedPeer(target, platformComponent, platformWindow, PeerType.EMBEDDED_FRAME);
 280     }
 281 
 282     LWWindowPeer createEmbeddedFrame(CViewEmbeddedFrame target) {
 283         PlatformComponent platformComponent = createPlatformComponent();
 284         PlatformWindow platformWindow = createPlatformWindow(PeerType.VIEW_EMBEDDED_FRAME);
 285         return createDelegatedPeer(target, platformComponent, platformWindow, PeerType.VIEW_EMBEDDED_FRAME);
 286     }
 287 
 288     private CPrinterDialogPeer createCPrinterDialog(CPrinterDialog target) {
 289         PlatformComponent platformComponent = createPlatformComponent();
 290         PlatformWindow platformWindow = createPlatformWindow(PeerType.DIALOG);
 291         CPrinterDialogPeer peer = new CPrinterDialogPeer(target, platformComponent, platformWindow);
 292         targetCreatedPeer(target, peer);
 293         return peer;
 294     }
 295 
 296     @Override
 297     public DialogPeer createDialog(Dialog target) {
 298         if (target instanceof CPrinterDialog) {
 299             return createCPrinterDialog((CPrinterDialog)target);
 300         }
 301         return super.createDialog(target);
 302     }
 303 
 304     @Override
 305     protected SecurityWarningWindow createSecurityWarning(Window ownerWindow,
 306                                                           LWWindowPeer ownerPeer) {
 307         return new CWarningWindow(ownerWindow, ownerPeer);
 308     }
 309 
 310     @Override
 311     protected PlatformComponent createPlatformComponent() {
 312         return new CPlatformComponent();
 313     }
 314 
 315     @Override
 316     protected PlatformComponent createLwPlatformComponent() {
 317         return new CPlatformLWComponent();
 318     }
 319 
 320     @Override
 321     protected FileDialogPeer createFileDialogPeer(FileDialog target) {
 322         return new CFileDialog(target);
 323     }
 324 
 325     @Override
 326     public MenuPeer createMenu(Menu target) {
 327         MenuPeer peer = new CMenu(target);
 328         targetCreatedPeer(target, peer);
 329         return peer;
 330     }
 331 
 332     @Override
 333     public MenuBarPeer createMenuBar(MenuBar target) {
 334         MenuBarPeer peer = new CMenuBar(target);
 335         targetCreatedPeer(target, peer);
 336         return peer;
 337     }
 338 
 339     @Override
 340     public MenuItemPeer createMenuItem(MenuItem target) {
 341         MenuItemPeer peer = new CMenuItem(target);
 342         targetCreatedPeer(target, peer);
 343         return peer;
 344     }
 345 
 346     @Override
 347     public CheckboxMenuItemPeer createCheckboxMenuItem(CheckboxMenuItem target) {
 348         CheckboxMenuItemPeer peer = new CCheckboxMenuItem(target);
 349         targetCreatedPeer(target, peer);
 350         return peer;
 351     }
 352 
 353     @Override
 354     public PopupMenuPeer createPopupMenu(PopupMenu target) {
 355         PopupMenuPeer peer = new CPopupMenu(target);
 356         targetCreatedPeer(target, peer);
 357         return peer;
 358     }
 359 
 360     @Override
 361     public SystemTrayPeer createSystemTray(SystemTray target) {
 362         return new CSystemTray();
 363     }
 364 
 365     @Override
 366     public TrayIconPeer createTrayIcon(TrayIcon target) {
 367         TrayIconPeer peer = new CTrayIcon(target);
 368         targetCreatedPeer(target, peer);
 369         return peer;
 370     }
 371 
 372     @Override
 373     public DesktopPeer createDesktopPeer(Desktop target) {
 374         return new CDesktopPeer();
 375     }
 376 
 377     @Override
 378     public TaskbarPeer createTaskbarPeer(Taskbar target) {
 379         return new CTaskbarPeer();
 380     }
 381 
 382     @Override
 383     public LWCursorManager getCursorManager() {
 384         return CCursorManager.getInstance();
 385     }
 386 
 387     @Override
 388     public Cursor createCustomCursor(final Image cursor, final Point hotSpot,
 389                                      final String name)
 390             throws IndexOutOfBoundsException, HeadlessException {
 391         return new CCustomCursor(cursor, hotSpot, name);
 392     }
 393 
 394     @Override
 395     public Dimension getBestCursorSize(final int preferredWidth,
 396                                        final int preferredHeight)
 397             throws HeadlessException {
 398         return CCustomCursor.getBestCursorSize(preferredWidth, preferredHeight);
 399     }
 400 
 401     @Override
 402     protected void platformCleanup() {
 403         // TODO Auto-generated method stub
 404     }
 405 
 406     @Override
 407     protected void platformInit() {
 408         // TODO Auto-generated method stub
 409     }
 410 
 411     @Override
 412     protected void platformRunMessage() {
 413         // TODO Auto-generated method stub
 414     }
 415 
 416     @Override
 417     protected void platformShutdown() {
 418         // TODO Auto-generated method stub
 419     }
 420 
 421     class OSXPlatformFont extends sun.awt.PlatformFont
 422     {
 423         OSXPlatformFont(String name, int style)
 424         {
 425             super(name, style);
 426         }
 427         @Override
 428         protected char getMissingGlyphCharacter()
 429         {
 430             // Follow up for real implementation
 431             return (char)0xfff8; // see http://developer.apple.com/fonts/LastResortFont/
 432         }
 433     }
 434     @Override
 435     public FontPeer getFontPeer(String name, int style) {
 436         return new OSXPlatformFont(name, style);
 437     }
 438 
 439     @Override
 440     protected void initializeDesktopProperties() {
 441         super.initializeDesktopProperties();
 442         Map <Object, Object> fontHints = new HashMap<>();
 443         fontHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
 444         desktopProperties.put(SunToolkit.DESKTOPFONTHINTS, fontHints);
 445         desktopProperties.put("awt.mouse.numButtons", BUTTONS);
 446         desktopProperties.put("awt.multiClickInterval", getMultiClickTime());
 447 
 448         // These DnD properties must be set, otherwise Swing ends up spewing NPEs
 449         // all over the place. The values came straight off of MToolkit.
 450         desktopProperties.put("DnD.Autoscroll.initialDelay", Integer.valueOf(50));
 451         desktopProperties.put("DnD.Autoscroll.interval", Integer.valueOf(50));
 452         desktopProperties.put("DnD.Autoscroll.cursorHysteresis", Integer.valueOf(5));
 453 
 454         desktopProperties.put("DnD.isDragImageSupported", Boolean.TRUE);
 455 
 456         // Register DnD cursors
 457         desktopProperties.put("DnD.Cursor.CopyDrop", new NamedCursor("DnD.Cursor.CopyDrop"));
 458         desktopProperties.put("DnD.Cursor.MoveDrop", new NamedCursor("DnD.Cursor.MoveDrop"));
 459         desktopProperties.put("DnD.Cursor.LinkDrop", new NamedCursor("DnD.Cursor.LinkDrop"));
 460         desktopProperties.put("DnD.Cursor.CopyNoDrop", new NamedCursor("DnD.Cursor.CopyNoDrop"));
 461         desktopProperties.put("DnD.Cursor.MoveNoDrop", new NamedCursor("DnD.Cursor.MoveNoDrop"));
 462         desktopProperties.put("DnD.Cursor.LinkNoDrop", new NamedCursor("DnD.Cursor.LinkNoDrop"));
 463     }
 464 
 465     @Override
 466     protected boolean syncNativeQueue(long timeout) {
 467         if (SunDragSourceContextPeer.isDragDropInProgress()
 468                 || EventQueue.isDispatchThread()) {
 469             // The java code started the DnD, but the native drag may still not
 470             // start, the last attempt to flush the native events,
 471             // also do not block EDT for a long time
 472             timeout = 50;
 473         }
 474         return nativeSyncQueue(timeout);
 475     }
 476 
 477     @Override
 478     public native void beep();
 479 
 480     @Override
 481     public int getScreenResolution() throws HeadlessException {
 482         return (int) ((CGraphicsDevice) GraphicsEnvironment
 483                 .getLocalGraphicsEnvironment().getDefaultScreenDevice())
 484                 .getXResolution();
 485     }
 486 
 487     @Override
 488     public Insets getScreenInsets(final GraphicsConfiguration gc) {
 489         GraphicsDevice gd = gc.getDevice();
 490         if (!(gd instanceof CGraphicsDevice)) {
 491             return super.getScreenInsets(gc);
 492         }
 493         return ((CGraphicsDevice)gd).getScreenInsets();
 494     }
 495 
 496     @Override
 497     public void sync() {
 498         // flush the OGL pipeline (this is a no-op if OGL is not enabled)
 499         OGLRenderQueue.sync();
 500         // setNeedsDisplay() selector was sent to the appropriate CALayer so now
 501         // we have to flush the native selectors queue.
 502         flushNativeSelectors();
 503     }
 504 
 505     @Override
 506     public RobotPeer createRobot(GraphicsDevice screen) throws AWTException {
 507         if (screen instanceof CGraphicsDevice) {
 508             return new CRobot((CGraphicsDevice) screen);
 509         }
 510         return super.createRobot(screen);
 511     }
 512 
 513     private native boolean isCapsLockOn();
 514 
 515     /*
 516      * NOTE: Among the keys this method is supposed to check,
 517      * only Caps Lock works as a true locking key with OS X.
 518      * There is no Scroll Lock key on modern Apple keyboards,
 519      * and with a PC keyboard plugged in Scroll Lock is simply
 520      * ignored: no LED lights up if you press it.
 521      * The key located at the same position on Apple keyboards
 522      * as Num Lock on PC keyboards is called Clear, doesn't lock
 523      * anything and is used for entirely different purpose.
 524      */
 525     @Override
 526     public boolean getLockingKeyState(int keyCode) throws UnsupportedOperationException {
 527         switch (keyCode) {
 528             case KeyEvent.VK_NUM_LOCK:
 529             case KeyEvent.VK_SCROLL_LOCK:
 530             case KeyEvent.VK_KANA_LOCK:
 531                 throw new UnsupportedOperationException("Toolkit.getLockingKeyState");
 532 
 533             case KeyEvent.VK_CAPS_LOCK:
 534                 return isCapsLockOn();
 535 
 536             default:
 537                 throw new IllegalArgumentException("invalid key for Toolkit.getLockingKeyState");
 538         }
 539     }
 540 
 541     //Is it allowed to generate events assigned to extra mouse buttons.
 542     //Set to true by default.
 543     private static boolean areExtraMouseButtonsEnabled = true;
 544 
 545     @Override
 546     public boolean areExtraMouseButtonsEnabled() throws HeadlessException {
 547         return areExtraMouseButtonsEnabled;
 548     }
 549 
 550     @Override
 551     public int getNumberOfButtons(){
 552         return BUTTONS;
 553     }
 554 
 555     /**
 556      * Returns the double-click time interval in ms.
 557      */
 558     private static native int getMultiClickTime();
 559 
 560     @Override
 561     public boolean isTraySupported() {
 562         return true;
 563     }
 564 
 565     @Override
 566     public DataTransferer getDataTransferer() {
 567         return CDataTransferer.getInstanceImpl();
 568     }
 569 
 570     @Override
 571     public boolean isAlwaysOnTopSupported() {
 572         return true;
 573     }
 574 
 575     private static final String APPKIT_THREAD_NAME = "AppKit Thread";
 576 
 577     // Intended to be called from the LWCToolkit.m only.
 578     private static void installToolkitThreadInJava() {
 579         Thread.currentThread().setName(APPKIT_THREAD_NAME);
 580         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 581             Thread.currentThread().setContextClassLoader(null);
 582             return null;
 583         });
 584     }
 585 
 586     @Override
 587     public boolean isWindowOpacitySupported() {
 588         return true;
 589     }
 590 
 591     @Override
 592     public boolean isFrameStateSupported(int state) throws HeadlessException {
 593         switch (state) {
 594             case Frame.NORMAL:
 595             case Frame.ICONIFIED:
 596             case Frame.MAXIMIZED_BOTH:
 597                 return true;
 598             default:
 599                 return false;
 600         }
 601     }
 602 
 603     @Override
 604     @Deprecated(since = "10")
 605     public int getMenuShortcutKeyMask() {
 606         return Event.META_MASK;
 607     }
 608 
 609     @Override
 610     public int getMenuShortcutKeyMaskEx() {
 611         return InputEvent.META_DOWN_MASK;
 612     }
 613 
 614     @Override
 615     public Image getImage(final String filename) {
 616         final Image nsImage = checkForNSImage(filename);
 617         if (nsImage != null) {
 618             return nsImage;
 619         }
 620 
 621         if (imageCached(filename)) {
 622             return super.getImage(filename);
 623         }
 624 
 625         String filename2x = getScaledImageName(filename);
 626         return (imageExists(filename2x))
 627                 ? getImageWithResolutionVariant(filename, filename2x)
 628                 : super.getImage(filename);
 629     }
 630 
 631     @Override
 632     public Image getImage(URL url) {
 633 
 634         if (imageCached(url)) {
 635             return super.getImage(url);
 636         }
 637 
 638         URL url2x = getScaledImageURL(url);
 639         return (imageExists(url2x))
 640                 ? getImageWithResolutionVariant(url, url2x) : super.getImage(url);
 641     }
 642 
 643     private static final String nsImagePrefix = "NSImage://";
 644     private Image checkForNSImage(final String imageName) {
 645         if (imageName == null) return null;
 646         if (!imageName.startsWith(nsImagePrefix)) return null;
 647         return CImage.getCreator().createImageFromName(imageName.substring(nsImagePrefix.length()));
 648     }
 649 
 650     // Thread-safe Object.equals() called from native
 651     public static boolean doEquals(final Object a, final Object b, Component c) {
 652         if (a == b) return true;
 653 
 654         final boolean[] ret = new boolean[1];
 655 
 656         try {  invokeAndWait(new Runnable() { @Override
 657                                               public void run() { synchronized(ret) {
 658             ret[0] = a.equals(b);
 659         }}}, c); } catch (Exception e) { e.printStackTrace(); }
 660 
 661         synchronized(ret) { return ret[0]; }
 662     }
 663 
 664     public static <T> T invokeAndWait(final Callable<T> callable,
 665                                       Component component) throws Exception {
 666         final CallableWrapper<T> wrapper = new CallableWrapper<>(callable);
 667         invokeAndWait(wrapper, component);
 668         return wrapper.getResult();
 669     }
 670 
 671     static final class CallableWrapper<T> implements Runnable {
 672         final Callable<T> callable;
 673         T object;
 674         Exception e;
 675 
 676         CallableWrapper(final Callable<T> callable) {
 677             this.callable = callable;
 678         }
 679 
 680         @Override
 681         public void run() {
 682             try {
 683                 object = callable.call();
 684             } catch (final Exception e) {
 685                 this.e = e;
 686             }
 687         }
 688 
 689         public T getResult() throws Exception {
 690             if (e != null) throw e;
 691             return object;
 692         }
 693     }
 694 
 695     /**
 696      * Kicks an event over to the appropriate event queue and waits for it to
 697      * finish To avoid deadlocking, we manually run the NSRunLoop while waiting
 698      * Any selector invoked using ThreadUtilities performOnMainThread will be
 699      * processed in doAWTRunLoop The InvocationEvent will call
 700      * LWCToolkit.stopAWTRunLoop() when finished, which will stop our manual
 701      * run loop. Does not dispatch native events while in the loop
 702      */
 703     public static void invokeAndWait(Runnable runnable, Component component)
 704             throws InvocationTargetException {
 705         Objects.requireNonNull(component, "Null component provided to invokeAndWait");
 706 
 707         long mediator = createAWTRunLoopMediator();
 708         InvocationEvent invocationEvent =
 709                 new InvocationEvent(component,
 710                         runnable,
 711                         () -> {
 712                             if (mediator != 0) {
 713                                 stopAWTRunLoop(mediator);
 714                             }
 715                         },
 716                         true);
 717 
 718         AppContext appContext = SunToolkit.targetToAppContext(component);
 719         SunToolkit.postEvent(appContext, invocationEvent);
 720         // 3746956 - flush events from PostEventQueue to prevent them from getting stuck and causing a deadlock
 721         SunToolkit.flushPendingEvents(appContext);
 722         doAWTRunLoop(mediator, false);
 723 
 724         checkException(invocationEvent);
 725     }
 726 
 727     public static void invokeLater(Runnable event, Component component)
 728             throws InvocationTargetException {
 729         Objects.requireNonNull(component, "Null component provided to invokeLater");
 730 
 731         InvocationEvent invocationEvent = new InvocationEvent(component, event);
 732 
 733         AppContext appContext = SunToolkit.targetToAppContext(component);
 734         SunToolkit.postEvent(SunToolkit.targetToAppContext(component), invocationEvent);
 735         // 3746956 - flush events from PostEventQueue to prevent them from getting stuck and causing a deadlock
 736         SunToolkit.flushPendingEvents(appContext);
 737 
 738         checkException(invocationEvent);
 739     }
 740 
 741     /**
 742      * Checks if exception occurred while {@code InvocationEvent} was processed and rethrows it as
 743      * an {@code InvocationTargetException}
 744      *
 745      * @param event the event to check for an exception
 746      * @throws InvocationTargetException if exception occurred when event was processed
 747      */
 748     private static void checkException(InvocationEvent event) throws InvocationTargetException {
 749         Throwable eventException = event.getException();
 750         if (eventException == null) return;
 751 
 752         if (eventException instanceof UndeclaredThrowableException) {
 753             eventException = ((UndeclaredThrowableException)eventException).getUndeclaredThrowable();
 754         }
 755         throw new InvocationTargetException(eventException);
 756     }
 757 
 758     /**
 759      * Schedules a {@code Runnable} execution on the Appkit thread after a delay
 760      * @param r a {@code Runnable} to execute
 761      * @param delay a delay in milliseconds
 762      */
 763     static native void performOnMainThreadAfterDelay(Runnable r, long delay);
 764 
 765 // DnD support
 766 
 767     @Override
 768     public DragSourceContextPeer createDragSourceContextPeer(
 769             DragGestureEvent dge) throws InvalidDnDOperationException {
 770         final LightweightFrame f = SunToolkit.getLightweightFrame(dge.getComponent());
 771         if (f != null) {
 772             return f.createDragSourceContextPeer(dge);
 773         }
 774 
 775         return CDragSourceContextPeer.createDragSourceContextPeer(dge);
 776     }
 777 
 778     @Override
 779     @SuppressWarnings("unchecked")
 780     public <T extends DragGestureRecognizer> T createDragGestureRecognizer(
 781             Class<T> abstractRecognizerClass, DragSource ds, Component c,
 782             int srcActions, DragGestureListener dgl) {
 783         final LightweightFrame f = SunToolkit.getLightweightFrame(c);
 784         if (f != null) {
 785             return f.createDragGestureRecognizer(abstractRecognizerClass, ds, c, srcActions, dgl);
 786         }
 787 
 788         DragGestureRecognizer dgr = null;
 789 
 790         // Create a new mouse drag gesture recognizer if we have a class match:
 791         if (MouseDragGestureRecognizer.class.equals(abstractRecognizerClass))
 792             dgr = new CMouseDragGestureRecognizer(ds, c, srcActions, dgl);
 793 
 794         return (T)dgr;
 795     }
 796 
 797     @Override
 798     protected PlatformDropTarget createDropTarget(DropTarget dropTarget,
 799                                                   Component component,
 800                                                   LWComponentPeer<?, ?> peer) {
 801         return new CDropTarget(dropTarget, component, peer);
 802     }
 803 
 804     // InputMethodSupport Method
 805     /**
 806      * Returns the default keyboard locale of the underlying operating system
 807      */
 808     @Override
 809     public Locale getDefaultKeyboardLocale() {
 810         Locale locale = CInputMethod.getNativeLocale();
 811 
 812         if (locale == null) {
 813             return super.getDefaultKeyboardLocale();
 814         }
 815 
 816         return locale;
 817     }
 818 
 819     public static boolean isLocaleUSInternationalPC(Locale locale) {
 820         return (locale != null ?
 821             locale.toString().equals("_US_UserDefined_15000") : false);
 822     }
 823 
 824     public static boolean isCharModifierKeyInUSInternationalPC(char ch) {
 825         // 5 characters: APOSTROPHE, QUOTATION MARK, ACCENT GRAVE, SMALL TILDE,
 826         // CIRCUMFLEX ACCENT
 827         final char[] modifierKeys = {'\'', '"', '`', '\u02DC', '\u02C6'};
 828         for (char modKey : modifierKeys) {
 829             if (modKey == ch) {
 830                 return true;
 831             }
 832         }
 833         return false;
 834     }
 835 
 836     @Override
 837     public InputMethodDescriptor getInputMethodAdapterDescriptor() {
 838         if (sInputMethodDescriptor == null)
 839             sInputMethodDescriptor = new CInputMethodDescriptor();
 840 
 841         return sInputMethodDescriptor;
 842     }
 843 
 844     /**
 845      * Returns a map of visual attributes for thelevel description
 846      * of the given input method highlight, or null if no mapping is found.
 847      * The style field of the input method highlight is ignored. The map
 848      * returned is unmodifiable.
 849      * @param highlight input method highlight
 850      * @return style attribute map, or null
 851      * @since 1.3
 852      */
 853     @Override
 854     public Map<TextAttribute, ?> mapInputMethodHighlight(InputMethodHighlight highlight) {
 855         return CInputMethod.mapInputMethodHighlight(highlight);
 856     }
 857 
 858     /**
 859      * Returns key modifiers used by Swing to set up a focus accelerator key
 860      * stroke.
 861      */
 862     @Override
 863     @SuppressWarnings("deprecation")
 864     public int getFocusAcceleratorKeyMask() {
 865         return InputEvent.CTRL_MASK | InputEvent.ALT_MASK;
 866     }
 867 
 868     /**
 869      * Tests whether specified key modifiers mask can be used to enter a
 870      * printable character.
 871      */
 872     @Override
 873     @SuppressWarnings("deprecation")
 874     public boolean isPrintableCharacterModifiersMask(int mods) {
 875         return ((mods & (InputEvent.META_MASK | InputEvent.CTRL_MASK)) == 0);
 876     }
 877 
 878     /**
 879      * Returns whether popup is allowed to be shown above the task bar.
 880      */
 881     @Override
 882     public boolean canPopupOverlapTaskBar() {
 883         return false;
 884     }
 885 
 886     /*
 887      * Returns true if the application (one of its windows) owns keyboard focus.
 888      */
 889     native boolean isApplicationActive();
 890 
 891     /**
 892      * Returns true if AWT toolkit is embedded, false otherwise.
 893      *
 894      * @return true if AWT toolkit is embedded, false otherwise
 895      */
 896     public static native boolean isEmbedded();
 897 
 898     /*
 899      * Activates application ignoring other apps.
 900      */
 901     public native void activateApplicationIgnoringOtherApps();
 902 
 903     /************************
 904      * Native methods section
 905      ************************/
 906 
 907     static native long createAWTRunLoopMediator();
 908     /**
 909      * Method to run a nested run-loop. The nested loop is spinned in the javaRunLoop mode, so selectors sent
 910      * by [JNFRunLoop performOnMainThreadWaiting] are processed.
 911      * @param mediator a native pointer to the mediator object created by createAWTRunLoopMediator
 912      * @param processEvents if true - dispatches event while in the nested loop. Used in DnD.
 913      *                      Additional attention is needed when using this feature as we short-circuit normal event
 914      *                      processing which could break Appkit.
 915      *                      (One known example is when the window is resized with the mouse)
 916      *
 917      *                      if false - all events come after exit form the nested loop
 918      */
 919     static void doAWTRunLoop(long mediator, boolean processEvents) {
 920         doAWTRunLoopImpl(mediator, processEvents, inAWT);
 921     }
 922     private static native void doAWTRunLoopImpl(long mediator, boolean processEvents, boolean inAWT);
 923     static native void stopAWTRunLoop(long mediator);
 924 
 925     private native boolean nativeSyncQueue(long timeout);
 926 
 927     /**
 928      * Just spin a single empty block synchronously.
 929      */
 930     static native void flushNativeSelectors();
 931 
 932     @Override
 933     public Clipboard createPlatformClipboard() {
 934         return new CClipboard("System");
 935     }
 936 
 937     @Override
 938     public boolean isModalExclusionTypeSupported(Dialog.ModalExclusionType exclusionType) {
 939         return (exclusionType == null) ||
 940             (exclusionType == Dialog.ModalExclusionType.NO_EXCLUDE) ||
 941             (exclusionType == Dialog.ModalExclusionType.APPLICATION_EXCLUDE) ||
 942             (exclusionType == Dialog.ModalExclusionType.TOOLKIT_EXCLUDE);
 943     }
 944 
 945     @Override
 946     public boolean isModalityTypeSupported(Dialog.ModalityType modalityType) {
 947         //TODO: FileDialog blocks excluded windows...
 948         //TODO: Test: 2 file dialogs, separate AppContexts: a) Dialog 1 blocked, shouldn't be. Frame 4 blocked (shouldn't be).
 949         return (modalityType == null) ||
 950             (modalityType == Dialog.ModalityType.MODELESS) ||
 951             (modalityType == Dialog.ModalityType.DOCUMENT_MODAL) ||
 952             (modalityType == Dialog.ModalityType.APPLICATION_MODAL) ||
 953             (modalityType == Dialog.ModalityType.TOOLKIT_MODAL);
 954     }
 955 
 956     @Override
 957     public boolean isWindowShapingSupported() {
 958         return true;
 959     }
 960 
 961     @Override
 962     public boolean isWindowTranslucencySupported() {
 963         return true;
 964     }
 965 
 966     @Override
 967     public boolean isTranslucencyCapable(GraphicsConfiguration gc) {
 968         return true;
 969     }
 970 
 971     @Override
 972     public boolean isSwingBackbufferTranslucencySupported() {
 973         return true;
 974     }
 975 
 976     @Override
 977     public boolean enableInputMethodsForTextComponent() {
 978         return true;
 979     }
 980 
 981     private static URL getScaledImageURL(URL url) {
 982         try {
 983             String scaledImagePath = getScaledImageName(url.getPath());
 984             return scaledImagePath == null ? null : new URL(url.getProtocol(),
 985                     url.getHost(), url.getPort(), scaledImagePath);
 986         } catch (MalformedURLException e) {
 987             return null;
 988         }
 989     }
 990 
 991     private static String getScaledImageName(String path) {
 992         if (!isValidPath(path)) {
 993             return null;
 994         }
 995 
 996         int slash = path.lastIndexOf('/');
 997         String name = (slash < 0) ? path : path.substring(slash + 1);
 998 
 999         if (name.contains("@2x")) {
1000             return null;
1001         }
1002 
1003         int dot = name.lastIndexOf('.');
1004         String name2x = (dot < 0) ? name + "@2x"
1005                 : name.substring(0, dot) + "@2x" + name.substring(dot);
1006         return (slash < 0) ? name2x : path.substring(0, slash + 1) + name2x;
1007     }
1008 
1009     private static boolean isValidPath(String path) {
1010         return path != null &&
1011                 !path.isEmpty() &&
1012                 !path.endsWith("/") &&
1013                 !path.endsWith(".");
1014     }
1015 
1016     @Override
1017     protected PlatformWindow getPlatformWindowUnderMouse() {
1018         return CPlatformWindow.nativeGetTopmostPlatformWindowUnderMouse();
1019     }
1020 
1021     @Override
1022     public void updateScreenMenuBarUI() {
1023         if (AquaMenuBarUI.getScreenMenuBarProperty())  {
1024             UIManager.put("MenuBarUI", "com.apple.laf.AquaMenuBarUI");
1025         } else {
1026             UIManager.put("MenuBarUI", null);
1027         }
1028     }
1029 }