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