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