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 if(GetApplication()!=null) { 540 GetApplication()._leaveNestedEventLoop(retValue); 541 } 542 } 543 544 public static boolean isNestedLoopRunning() { 545 checkEventThread(); 546 return nestedEventLoopCounter > 0; 547 } 548 549 //TODO: move to the EventHandler 550 public void menuAboutAction() { 551 System.err.println("about"); 552 } 553 554 555 // FACTORY METHODS 556 557 /** 558 * Create a window. 559 * 560 * The styleMask argument is a bitmask of window styles as defined in the 561 * Window class. Note, however, that visual kinds (UNTITLED, TITLED, 562 * or TRANSPARENT) can't be combined together. Also, functional types 563 * (NORMAL, POPUP, or UTILITY) can't be combined together. A window is 564 * allowed to be of exactly one visual kind, and exactly one functional 565 * type. 566 */ 567 public abstract Window createWindow(Window owner, Screen screen, int styleMask); 568 569 /** 570 * Create a window. 571 * 572 * The styleMask argument is a bitmask of window styles as defined in the 573 * Window class. Note, however, that visual kinds (UNTITLED, TITLED, 574 * or TRANSPARENT) can't be combined together. Also, functional types 575 * (NORMAL, POPUP, or UTILITY) can't be combined together. A window is 576 * allowed to be of exactly one visual kind, and exactly one functional 577 * type. 578 */ 579 public final Window createWindow(Screen screen, int styleMask) { 580 return createWindow(null, screen, styleMask); 581 } 582 583 public abstract Window createWindow(long parent); 584 585 public abstract View createView(); 586 587 public abstract Cursor createCursor(int type); 588 public abstract Cursor createCursor(int x, int y, Pixels pixels); 589 590 protected abstract void staticCursor_setVisible(boolean visible); 591 protected abstract Size staticCursor_getBestSize(int width, int height); 592 593 public final Menu createMenu(String title) { 594 return new Menu(title); 595 } 596 597 public final Menu createMenu(String title, boolean enabled) { 598 return new Menu(title, enabled); 599 } 600 601 public final MenuBar createMenuBar() { 602 return new MenuBar(); 603 } 604 605 public final MenuItem createMenuItem(String title) { 606 return createMenuItem(title, null); 607 } 608 609 public final MenuItem createMenuItem(String title, MenuItem.Callback callback) { 610 return createMenuItem(title, callback, KeyEvent.VK_UNDEFINED, KeyEvent.MODIFIER_NONE); 611 } 612 613 public final MenuItem createMenuItem(String title, MenuItem.Callback callback, 614 int shortcutKey, int shortcutModifiers) { 615 return createMenuItem(title, callback, shortcutKey, shortcutModifiers, null); 616 } 617 618 public final MenuItem createMenuItem(String title, MenuItem.Callback callback, 619 int shortcutKey, int shortcutModifiers, Pixels pixels) { 620 return new MenuItem(title, callback, shortcutKey, shortcutModifiers, pixels); 621 } 622 623 public abstract Pixels createPixels(int width, int height, ByteBuffer data); 624 public abstract Pixels createPixels(int width, int height, IntBuffer data); 625 public abstract Pixels createPixels(int width, int height, IntBuffer data, float scalex, float scaley); 626 protected abstract int staticPixels_getNativeFormat(); 627 628 /* utility method called from native code */ 629 static Pixels createPixels(int width, int height, int[] data, float scalex, float scaley) { 630 return Application.GetApplication().createPixels(width, height, IntBuffer.wrap(data), scalex, scaley); 631 } 632 633 /* utility method called from native code */ 634 static float getScaleFactor(final int x, final int y, final int w, final int h) { 635 float scale = 0.0f; 636 // Find the maximum scale for screens this area overlaps 637 for (Screen s : Screen.getScreens()) { 638 final int sx = s.getX(), sy = s.getY(), sw = s.getWidth(), sh = s.getHeight(); 639 if (x < (sx + sw) && (x + w) > sx && y < (sy + sh) && (y + h) > sy) { 640 if (scale < s.getRecommendedOutputScaleX()) { 641 scale = s.getRecommendedOutputScaleX(); 642 } 643 if (scale < s.getRecommendedOutputScaleY()) { 644 scale = s.getRecommendedOutputScaleY(); 645 } 646 } 647 } 648 return scale == 0.0f ? 1.0f : scale; 649 } 650 651 652 public abstract Robot createRobot(); 653 654 protected abstract double staticScreen_getVideoRefreshPeriod(); 655 protected abstract Screen[] staticScreen_getScreens(); 656 657 public abstract Timer createTimer(Runnable runnable); 658 protected abstract int staticTimer_getMinPeriod(); 659 protected abstract int staticTimer_getMaxPeriod(); 660 661 public final EventLoop createEventLoop() { 662 return new EventLoop(); 663 } 664 665 public Accessible createAccessible() { return null; } 666 667 protected abstract FileChooserResult staticCommonDialogs_showFileChooser(Window owner, String folder, String filename, String title, int type, 668 boolean multipleMode, ExtensionFilter[] extensionFilters, int defaultFilterIndex); 669 670 protected abstract File staticCommonDialogs_showFolderChooser(Window owner, String folder, String title); 671 672 protected abstract long staticView_getMultiClickTime(); 673 protected abstract int staticView_getMultiClickMaxX(); 674 protected abstract int staticView_getMultiClickMaxY(); 675 676 /** 677 * Gets the Name of the currently active high contrast theme. 678 * If null, then high contrast is not enabled. 679 */ 680 public String getHighContrastTheme() { 681 checkEventThread(); 682 return null; 683 } 684 685 protected boolean _supportsInputMethods() { 686 // Overridden in subclasses 687 return false; 688 } 689 public final boolean supportsInputMethods() { 690 checkEventThread(); 691 return _supportsInputMethods(); 692 } 693 694 protected abstract boolean _supportsTransparentWindows(); 695 public final boolean supportsTransparentWindows() { 696 checkEventThread(); 697 return _supportsTransparentWindows(); 698 } 699 700 public boolean hasTwoLevelFocus() { 701 return false; 702 } 703 704 public boolean hasVirtualKeyboard() { 705 return false; 706 } 707 708 public boolean hasTouch() { 709 return false; 710 } 711 712 public boolean hasMultiTouch() { 713 return false; 714 } 715 716 public boolean hasPointer() { 717 return true; 718 } 719 720 protected abstract boolean _supportsUnifiedWindows(); 721 public final boolean supportsUnifiedWindows() { 722 checkEventThread(); 723 return _supportsUnifiedWindows(); 724 } 725 726 protected boolean _supportsSystemMenu() { 727 // Overridden in subclasses 728 return false; 729 } 730 public final boolean supportsSystemMenu() { 731 checkEventThread(); 732 return _supportsSystemMenu(); 733 } 734 735 protected abstract int _getKeyCodeForChar(char c); 736 /** 737 * Returns a VK_ code of a key capable of producing the given unicode 738 * character with respect to the currently active keyboard layout or 739 * VK_UNDEFINED if the character isn't present in the current layout. 740 * 741 * @param c the character 742 * @return integer code for the given char 743 */ 744 public static int getKeyCodeForChar(char c) { 745 return application._getKeyCodeForChar(c); 746 } 747 }