1 /* 2 * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package com.sun.glass.ui; 26 27 import com.sun.glass.events.KeyEvent; 28 import com.sun.glass.ui.CommonDialogs.ExtensionFilter; 29 import com.sun.glass.ui.CommonDialogs.FileChooserResult; 30 31 import java.io.File; 32 import java.nio.ByteBuffer; 33 import java.nio.IntBuffer; 34 import java.security.AccessController; 35 import java.security.PrivilegedAction; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.LinkedList; 39 40 public abstract class Application { 41 42 private final static String DEFAULT_NAME = "java"; 43 protected String name = DEFAULT_NAME; 44 45 public static class EventHandler { 46 // currently used only on Mac OS X 47 public void handleWillFinishLaunchingAction(Application app, long time) { 48 } 49 // currently used only on Mac OS X 50 public void handleDidFinishLaunchingAction(Application app, long time) { 51 } 52 // currently used only on Mac OS X 53 public void handleWillBecomeActiveAction(Application app, long time) { 54 } 55 // currently used only on Mac OS X 56 public void handleDidBecomeActiveAction(Application app, long time) { 57 } 58 // currently used only on Mac OS X 59 public void handleWillResignActiveAction(Application app, long time) { 60 } 61 // currently used only on Mac OS X 62 public void handleDidResignActiveAction(Application app, long time) { 63 } 64 // currently used only on iOS 65 public void handleDidReceiveMemoryWarning(Application app, long time) { 66 } 67 // currently used only on Mac OS X 68 public void handleWillHideAction(Application app, long time) { 69 } 70 // currently used only on Mac OS X 71 public void handleDidHideAction(Application app, long time) { 72 } 73 // currently used only on Mac OS X 74 public void handleWillUnhideAction(Application app, long time) { 75 } 76 // currently used only on Mac OS X 77 public void handleDidUnhideAction(Application app, long time) { 78 } 79 // currently used only on Mac OS X 80 // the open files which started up the app will arrive before app becomes active 81 public void handleOpenFilesAction(Application app, long time, String files[]) { 82 } 83 // currently used only on Mac OS X 84 public void handleQuitAction(Application app, long time) { 85 } 86 public boolean handleThemeChanged(String themeName) { 87 return false; 88 } 89 } 90 91 private EventHandler eventHandler; 92 private boolean initialActiveEventReceived = false; 93 private String initialOpenedFiles[] = null; 94 95 private static boolean loaded = false; 96 private static Application application; 97 private static Thread eventThread; 98 private static final boolean disableThreadChecks = 99 AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> { 100 final String str = 101 System.getProperty("glass.disableThreadChecks", "false"); 102 return "true".equalsIgnoreCase(str); 103 }); 104 105 // May be called on any thread. 106 protected static synchronized void loadNativeLibrary(final String libname) { 107 // load the native library of the specified libname. 108 // the platform default by convention is "glass", all others should have a suffix, ie glass-x11 109 if (!loaded) { 110 com.sun.glass.utils.NativeLibLoader.loadLibrary(libname); 111 loaded = true; 112 } 113 } 114 115 // May be called on any thread. 116 protected static synchronized void loadNativeLibrary() { 117 // use the "platform default" name of "glass" 118 loadNativeLibrary("glass"); 119 } 120 121 private static volatile Map deviceDetails = null; 122 123 // provides a means for the user to pass platorm specific details 124 // to the native glass impl. Can be null. 125 // May need be called before Run. 126 // May be called on any thread. 127 public static void setDeviceDetails(Map details) { 128 deviceDetails = details; 129 } 130 131 // May be called on any thread. 132 public static Map getDeviceDetails() { 133 return deviceDetails; 134 } 135 136 protected Application() { 137 } 138 139 // May be called on any thread. 140 public static void run(final Runnable launchable) { 141 if (application != null) { 142 throw new IllegalStateException("Application is already running"); 143 } 144 application = PlatformFactory.getPlatformFactory().createApplication(); 145 // each concrete Application should set the app name using its own platform mechanism: 146 // on Mac OS X - use NSBundle info, which can be overriden by -Xdock:name 147 // on Windows - TODO 148 // on Linux - TODO 149 //application.name = DEFAULT_NAME; // default 150 try { 151 application.runLoop(() -> { 152 Screen.initScreens(); 153 launchable.run(); 154 }); 155 } catch (Throwable t) { 156 t.printStackTrace(); 157 } 158 } 159 160 // runLoop never exits until app terminates 161 protected abstract void runLoop(Runnable launchable); 162 163 // should return after loop termination completion 164 protected void finishTerminating() { 165 // To make sure application object is not used outside of the run loop 166 application = null; 167 // The eventThread is null at this point, no need to check it 168 } 169 170 /** 171 * Gets the name for the application. The application name may 172 * be used to identify the application in the user interface or 173 * as part of the platform specific path used to store application 174 * data. 175 * 176 * This is a hint and may not be used on some platforms. 177 * 178 * @return the application name 179 */ 180 public String getName() { 181 checkEventThread(); 182 return name; 183 } 184 185 /** 186 * Sets the name for the application. The application name may 187 * be used to identify the application in the user interface or 188 * as part of the platform specific path used to store application 189 * data. 190 * 191 * The name could be set only once. All subsequent calls are ignored. 192 * 193 * This is a hint and may not be used on some platforms. 194 * 195 * @param name the new application name 196 */ 197 public void setName(String name) { 198 checkEventThread(); 199 if (name != null && DEFAULT_NAME.equals(this.name)) { 200 this.name = name; 201 } 202 } 203 204 /** 205 * Gets a platform specific path that can be used to store 206 * application data. The application name typically appears 207 * as part of the path. 208 * 209 * On some platforms, the path may not yet exist and the caller 210 * will need to create it. 211 * 212 * @return the platform specific path for the application data 213 */ 214 public String getDataDirectory() { 215 checkEventThread(); 216 String userHome = AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty("user.home")); 217 return userHome + File.separator + "." + name + File.separator; 218 } 219 220 private void notifyWillFinishLaunching() { 221 EventHandler handler = getEventHandler(); 222 if (handler != null) { 223 handler.handleWillFinishLaunchingAction(this, System.nanoTime()); 224 } 225 } 226 227 private void notifyDidFinishLaunching() { 228 EventHandler handler = getEventHandler(); 229 if (handler != null) { 230 handler.handleDidFinishLaunchingAction(this, System.nanoTime()); 231 } 232 } 233 234 private void notifyWillBecomeActive() { 235 EventHandler handler = getEventHandler(); 236 if (handler != null) { 237 handler.handleWillBecomeActiveAction(this, System.nanoTime()); 238 } 239 } 240 241 private void notifyDidBecomeActive() { 242 this.initialActiveEventReceived = true; 243 EventHandler handler = getEventHandler(); 244 if (handler != null) { 245 handler.handleDidBecomeActiveAction(this, System.nanoTime()); 246 } 247 } 248 249 private void notifyWillResignActive() { 250 EventHandler handler = getEventHandler(); 251 if (handler != null) { 252 handler.handleWillResignActiveAction(this, System.nanoTime()); 253 } 254 } 255 256 private boolean notifyThemeChanged(String themeName) { 257 EventHandler handler = getEventHandler(); 258 if (handler != null) { 259 return handler.handleThemeChanged(themeName); 260 } 261 return false; 262 } 263 264 private void notifyDidResignActive() { 265 EventHandler handler = getEventHandler(); 266 if (handler != null) { 267 handler.handleDidResignActiveAction(this, System.nanoTime()); 268 } 269 } 270 271 private void notifyDidReceiveMemoryWarning() { 272 EventHandler handler = getEventHandler(); 273 if (handler != null) { 274 handler.handleDidReceiveMemoryWarning(this, System.nanoTime()); 275 } 276 } 277 278 private void notifyWillHide() { 279 EventHandler handler = getEventHandler(); 280 if (handler != null) { 281 handler.handleWillHideAction(this, System.nanoTime()); 282 } 283 } 284 285 private void notifyDidHide() { 286 EventHandler handler = getEventHandler(); 287 if (handler != null) { 288 handler.handleDidHideAction(this, System.nanoTime()); 289 } 290 } 291 292 private void notifyWillUnhide() { 293 EventHandler handler = getEventHandler(); 294 if (handler != null) { 295 handler.handleWillUnhideAction(this, System.nanoTime()); 296 } 297 } 298 299 private void notifyDidUnhide() { 300 EventHandler handler = getEventHandler(); 301 if (handler != null) { 302 handler.handleDidUnhideAction(this, System.nanoTime()); 303 } 304 } 305 306 // notificiation when user drag and drops files onto app icon 307 private void notifyOpenFiles(String files[]) { 308 if ((this.initialActiveEventReceived == false) && (this.initialOpenedFiles == null)) { 309 // rememeber the initial opened files 310 this.initialOpenedFiles = files; 311 } 312 EventHandler handler = getEventHandler(); 313 if ((handler != null) && (files != null)) { 314 handler.handleOpenFilesAction(this, System.nanoTime(), files); 315 } 316 } 317 318 private void notifyWillQuit() { 319 EventHandler handler = getEventHandler(); 320 if (handler != null) { 321 handler.handleQuitAction(this, System.nanoTime()); 322 } 323 } 324 325 /** 326 * Install app's default native menus: 327 * on Mac OS X - Apple menu (showing the app name) with a single Quit menu item 328 * on Windows - NOP 329 * on Linux - NOP 330 */ 331 public void installDefaultMenus(MenuBar menubar) { 332 checkEventThread(); 333 // To override in subclasses 334 } 335 336 public EventHandler getEventHandler() { 337 //checkEventThread(); // Glass (Mac) 338 // When an app is closing, Mac calls notify- Will/DidHide, Will/DidResignActive 339 // on a thread other than the Main thread 340 return eventHandler; 341 } 342 343 public void setEventHandler(EventHandler eventHandler) { 344 checkEventThread(); 345 boolean resendOpenFiles = ((this.eventHandler != null) && (this.initialOpenedFiles != null)); 346 this.eventHandler = eventHandler; 347 if (resendOpenFiles == true) { 348 // notify the new event handler with initial opened files 349 notifyOpenFiles(this.initialOpenedFiles); 350 } 351 } 352 353 private boolean terminateWhenLastWindowClosed = true; 354 public final boolean shouldTerminateWhenLastWindowClosed() { 355 checkEventThread(); 356 return terminateWhenLastWindowClosed; 357 } 358 public final void setTerminateWhenLastWindowClosed(boolean b) { 359 checkEventThread(); 360 terminateWhenLastWindowClosed = b; 361 } 362 363 public boolean shouldUpdateWindow() { 364 checkEventThread(); 365 return false; // overridden in platform application class 366 } 367 368 public boolean hasWindowManager() { 369 //checkEventThread(); // Prism (Mac) 370 return true; // overridden in platform application class 371 } 372 373 /** 374 * Notifies the Application that rendering has completed for current pulse. 375 * 376 * This is called on the render thread. 377 */ 378 public void notifyRenderingFinished() { 379 } 380 381 public void terminate() { 382 checkEventThread(); 383 try { 384 final List<Window> windows = new LinkedList<>(Window.getWindows()); 385 for (Window window : windows) { 386 // first make windows invisible 387 window.setVisible(false); 388 } 389 for (Window window : windows) { 390 // now we can close windows 391 window.close(); 392 } 393 } catch (Throwable t) { 394 t.printStackTrace(); 395 } finally { 396 finishTerminating(); 397 } 398 } 399 400 // May be called on any thread 401 static public Application GetApplication() { 402 return Application.application; 403 } 404 405 // May be called on any thread 406 protected static void setEventThread(Thread thread) { 407 Application.eventThread = thread; 408 } 409 410 // May be called on any thread 411 protected static Thread getEventThread() { 412 return Application.eventThread; 413 } 414 415 /** 416 * Returns {@code true} if the current thread is the event thread. 417 */ 418 public static boolean isEventThread() { 419 return Thread.currentThread() == Application.eventThread; 420 } 421 422 /** 423 * Verifies that the current thread is the event thread, and throws 424 * an exception if this is not so. 425 * 426 * The check can be disabled by setting the "glass.disableThreadChecks" 427 * system property. It is preferred, however, to fix the application code 428 * instead. 429 * 430 * @throws IllegalStateException if the current thread is not the event thread 431 */ 432 public static void checkEventThread() { 433 //TODO: we do NOT advertise the "glass.disableThreadChecks". 434 // If we never get a complaint about this check, we can consider 435 // dropping the system property and perform the check unconditionally 436 if (!disableThreadChecks && 437 Thread.currentThread() != Application.eventThread) 438 { 439 throw new IllegalStateException( 440 "This operation is permitted on the event thread only; currentThread = " 441 + Thread.currentThread().getName()); 442 443 } 444 } 445 446 // Called from native, when a JNI exception has occurred 447 public static void reportException(Throwable t) { 448 Thread currentThread = Thread.currentThread(); 449 Thread.UncaughtExceptionHandler handler = 450 currentThread.getUncaughtExceptionHandler(); 451 handler.uncaughtException(currentThread, t); 452 } 453 454 abstract protected void _invokeAndWait(java.lang.Runnable runnable); 455 /** 456 * Block the current thread and wait until the given runnable finishes 457 * running on the native event loop thread. 458 */ 459 public static void invokeAndWait(java.lang.Runnable runnable) { 460 if (runnable == null) { 461 return; 462 } 463 if (isEventThread()) { 464 runnable.run(); 465 } else { 466 GetApplication()._invokeAndWait(runnable); 467 } 468 } 469 470 abstract protected void _invokeLater(java.lang.Runnable runnable); 471 /** 472 * Schedule the given runnable to run on the native event loop thread 473 * some time in the future, and return immediately. 474 */ 475 public static void invokeLater(java.lang.Runnable runnable) { 476 if (runnable == null) { 477 return; 478 } 479 GetApplication()._invokeLater(runnable); 480 } 481 482 protected abstract Object _enterNestedEventLoop(); 483 protected abstract void _leaveNestedEventLoop(Object retValue); 484 485 private static int nestedEventLoopCounter = 0; 486 487 /** 488 * Starts a nested event loop. 489 * 490 * Calling this method temporarily blocks processing of the current event, 491 * and starts a nested event loop to handle other native events. To 492 * proceed with the blocked execution path, the application should call the 493 * {@link #leaveNestedEventLoop(Object)} method. 494 * 495 * Note that this method may only be invoked on the main (event handling) 496 * thread. 497 * 498 * An application may enter several nested loops recursively. There's no 499 * limit of recursion other than that imposed by the native stack size. 500 * 501 * @return an object passed to the leaveNestedEventLoop() method 502 * @throws RuntimeException if the current thread is not the main thread 503 */ 504 static Object enterNestedEventLoop() { 505 checkEventThread(); 506 507 nestedEventLoopCounter++; 508 try { 509 return GetApplication()._enterNestedEventLoop(); 510 } finally { 511 nestedEventLoopCounter--; 512 } 513 } 514 515 /** 516 * Terminates the current nested event loop. 517 * 518 * After calling this method and returning from the current event handler, 519 * the execusion returns to the point where the {@link #enterNestedEventLoop} 520 * was called previously. You may specify a return value for the 521 * enterNestedEventLoop() method by passing the argument {@code retValue} to 522 * the leaveNestedEventLoop(). 523 * 524 * Note that this method may only be invoked on the main (event handling) 525 * thread. 526 * 527 * @throws RuntimeException if the current thread is not the main thread 528 * @throws IllegalStateException if the application hasn't started a nested 529 * event loop 530 */ 531 static void leaveNestedEventLoop(Object retValue) { 532 checkEventThread(); 533 534 if (nestedEventLoopCounter == 0) { 535 throw new IllegalStateException("Not in a nested event loop"); 536 } 537 538 GetApplication()._leaveNestedEventLoop(retValue); 539 } 540 541 public static boolean isNestedLoopRunning() { 542 checkEventThread(); 543 return nestedEventLoopCounter > 0; 544 } 545 546 //TODO: move to the EventHandler 547 public void menuAboutAction() { 548 System.err.println("about"); 549 } 550 551 552 // FACTORY METHODS 553 554 /** 555 * Create a window. 556 * 557 * The styleMask argument is a bitmask of window styles as defined in the 558 * Window class. Note, however, that visual kinds (UNTITLED, TITLED, 559 * or TRANSPARENT) can't be combined together. Also, functional types 560 * (NORMAL, POPUP, or UTILITY) can't be combined together. A window is 561 * allowed to be of exactly one visual kind, and exactly one functional 562 * type. 563 */ 564 public abstract Window createWindow(Window owner, Screen screen, int styleMask); 565 566 /** 567 * Create a window. 568 * 569 * The styleMask argument is a bitmask of window styles as defined in the 570 * Window class. Note, however, that visual kinds (UNTITLED, TITLED, 571 * or TRANSPARENT) can't be combined together. Also, functional types 572 * (NORMAL, POPUP, or UTILITY) can't be combined together. A window is 573 * allowed to be of exactly one visual kind, and exactly one functional 574 * type. 575 */ 576 public final Window createWindow(Screen screen, int styleMask) { 577 return createWindow(null, screen, styleMask); 578 } 579 580 public abstract Window createWindow(long parent); 581 582 public abstract View createView(); 583 584 public abstract Cursor createCursor(int type); 585 public abstract Cursor createCursor(int x, int y, Pixels pixels); 586 587 protected abstract void staticCursor_setVisible(boolean visible); 588 protected abstract Size staticCursor_getBestSize(int width, int height); 589 590 public final Menu createMenu(String title) { 591 return new Menu(title); 592 } 593 594 public final Menu createMenu(String title, boolean enabled) { 595 return new Menu(title, enabled); 596 } 597 598 public final MenuBar createMenuBar() { 599 return new MenuBar(); 600 } 601 602 public final MenuItem createMenuItem(String title) { 603 return createMenuItem(title, null); 604 } 605 606 public final MenuItem createMenuItem(String title, MenuItem.Callback callback) { 607 return createMenuItem(title, callback, KeyEvent.VK_UNDEFINED, KeyEvent.MODIFIER_NONE); 608 } 609 610 public final MenuItem createMenuItem(String title, MenuItem.Callback callback, 611 int shortcutKey, int shortcutModifiers) { 612 return createMenuItem(title, callback, shortcutKey, shortcutModifiers, null); 613 } 614 615 public final MenuItem createMenuItem(String title, MenuItem.Callback callback, 616 int shortcutKey, int shortcutModifiers, Pixels pixels) { 617 return new MenuItem(title, callback, shortcutKey, shortcutModifiers, pixels); 618 } 619 620 public abstract Pixels createPixels(int width, int height, ByteBuffer data); 621 public abstract Pixels createPixels(int width, int height, IntBuffer data); 622 public abstract Pixels createPixels(int width, int height, IntBuffer data, float scalex, float scaley); 623 protected abstract int staticPixels_getNativeFormat(); 624 625 /* utility method called from native code */ 626 static Pixels createPixels(int width, int height, int[] data, float scalex, float scaley) { 627 return Application.GetApplication().createPixels(width, height, IntBuffer.wrap(data), scalex, scaley); 628 } 629 630 /* utility method called from native code */ 631 static float getScaleFactor(final int x, final int y, final int w, final int h) { 632 float scale = 0.0f; 633 // Find the maximum scale for screens this area overlaps 634 for (Screen s : Screen.getScreens()) { 635 final int sx = s.getX(), sy = s.getY(), sw = s.getWidth(), sh = s.getHeight(); 636 if (x < (sx + sw) && (x + w) > sx && y < (sy + sh) && (y + h) > sy) { 637 if (scale < s.getRecommendedOutputScaleX()) { 638 scale = s.getRecommendedOutputScaleX(); 639 } 640 if (scale < s.getRecommendedOutputScaleY()) { 641 scale = s.getRecommendedOutputScaleY(); 642 } 643 } 644 } 645 return scale == 0.0f ? 1.0f : scale; 646 } 647 648 649 public abstract GlassRobot createRobot(); 650 651 protected abstract double staticScreen_getVideoRefreshPeriod(); 652 protected abstract Screen[] staticScreen_getScreens(); 653 654 public abstract Timer createTimer(Runnable runnable); 655 protected abstract int staticTimer_getMinPeriod(); 656 protected abstract int staticTimer_getMaxPeriod(); 657 658 public final EventLoop createEventLoop() { 659 return new EventLoop(); 660 } 661 662 public Accessible createAccessible() { return null; } 663 664 protected abstract FileChooserResult staticCommonDialogs_showFileChooser(Window owner, String folder, String filename, String title, int type, 665 boolean multipleMode, ExtensionFilter[] extensionFilters, int defaultFilterIndex); 666 667 protected abstract File staticCommonDialogs_showFolderChooser(Window owner, String folder, String title); 668 669 protected abstract long staticView_getMultiClickTime(); 670 protected abstract int staticView_getMultiClickMaxX(); 671 protected abstract int staticView_getMultiClickMaxY(); 672 673 /** 674 * Gets the Name of the currently active high contrast theme. 675 * If null, then high contrast is not enabled. 676 */ 677 public String getHighContrastTheme() { 678 checkEventThread(); 679 return null; 680 } 681 682 protected boolean _supportsInputMethods() { 683 // Overridden in subclasses 684 return false; 685 } 686 public final boolean supportsInputMethods() { 687 checkEventThread(); 688 return _supportsInputMethods(); 689 } 690 691 protected abstract boolean _supportsTransparentWindows(); 692 public final boolean supportsTransparentWindows() { 693 checkEventThread(); 694 return _supportsTransparentWindows(); 695 } 696 697 public boolean hasTwoLevelFocus() { 698 return false; 699 } 700 701 public boolean hasVirtualKeyboard() { 702 return false; 703 } 704 705 public boolean hasTouch() { 706 return false; 707 } 708 709 public boolean hasMultiTouch() { 710 return false; 711 } 712 713 public boolean hasPointer() { 714 return true; 715 } 716 717 protected abstract boolean _supportsUnifiedWindows(); 718 public final boolean supportsUnifiedWindows() { 719 checkEventThread(); 720 return _supportsUnifiedWindows(); 721 } 722 723 protected boolean _supportsSystemMenu() { 724 // Overridden in subclasses 725 return false; 726 } 727 public final boolean supportsSystemMenu() { 728 checkEventThread(); 729 return _supportsSystemMenu(); 730 } 731 732 protected abstract int _getKeyCodeForChar(char c); 733 /** 734 * Returns a VK_ code of a key capable of producing the given unicode 735 * character with respect to the currently active keyboard layout or 736 * VK_UNDEFINED if the character isn't present in the current layout. 737 * 738 * @param c the character 739 * @return integer code for the given char 740 */ 741 public static int getKeyCodeForChar(char c) { 742 return application._getKeyCodeForChar(c); 743 } 744 }