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