/* * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.glass.ui; import com.sun.glass.events.KeyEvent; import com.sun.glass.ui.CommonDialogs.ExtensionFilter; import com.sun.glass.ui.CommonDialogs.FileChooserResult; import java.io.File; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.LinkedList; public abstract class Application { private final static String DEFAULT_NAME = "java"; protected String name = DEFAULT_NAME; public static class EventHandler { // currently used only on Mac OS X public void handleWillFinishLaunchingAction(Application app, long time) { } // currently used only on Mac OS X public void handleDidFinishLaunchingAction(Application app, long time) { } // currently used only on Mac OS X public void handleWillBecomeActiveAction(Application app, long time) { } // currently used only on Mac OS X public void handleDidBecomeActiveAction(Application app, long time) { } // currently used only on Mac OS X public void handleWillResignActiveAction(Application app, long time) { } // currently used only on Mac OS X public void handleDidResignActiveAction(Application app, long time) { } // currently used only on iOS public void handleDidReceiveMemoryWarning(Application app, long time) { } // currently used only on Mac OS X public void handleWillHideAction(Application app, long time) { } // currently used only on Mac OS X public void handleDidHideAction(Application app, long time) { } // currently used only on Mac OS X public void handleWillUnhideAction(Application app, long time) { } // currently used only on Mac OS X public void handleDidUnhideAction(Application app, long time) { } // currently used only on Mac OS X // the open files which started up the app will arrive before app becomes active public void handleOpenFilesAction(Application app, long time, String files[]) { } // currently used only on Mac OS X public void handleQuitAction(Application app, long time) { } public boolean handleThemeChanged(String themeName) { return false; } } private EventHandler eventHandler; private boolean initialActiveEventReceived = false; private String initialOpenedFiles[] = null; private static boolean loaded = false; private static Application application; private static Thread eventThread; private static final boolean disableThreadChecks = AccessController.doPrivileged((PrivilegedAction) () -> { final String str = System.getProperty("glass.disableThreadChecks", "false"); return "true".equalsIgnoreCase(str); }); // May be called on any thread. protected static synchronized void loadNativeLibrary(final String libname) { // load the native library of the specified libname. // the platform default by convention is "glass", all others should have a suffix, ie glass-x11 if (!loaded) { com.sun.glass.utils.NativeLibLoader.loadLibrary(libname); loaded = true; } } // May be called on any thread. protected static synchronized void loadNativeLibrary() { // use the "platform default" name of "glass" loadNativeLibrary("glass"); } private static volatile Map deviceDetails = null; // provides a means for the user to pass platorm specific details // to the native glass impl. Can be null. // May need be called before Run. // May be called on any thread. public static void setDeviceDetails(Map details) { deviceDetails = details; } // May be called on any thread. public static Map getDeviceDetails() { return deviceDetails; } protected Application() { } // May be called on any thread. public static void run(final Runnable launchable) { if (application != null) { throw new IllegalStateException("Application is already running"); } application = PlatformFactory.getPlatformFactory().createApplication(); // each concrete Application should set the app name using its own platform mechanism: // on Mac OS X - use NSBundle info, which can be overriden by -Xdock:name // on Windows - TODO // on Linux - TODO //application.name = DEFAULT_NAME; // default try { application.runLoop(() -> { Screen.initScreens(); launchable.run(); }); } catch (Throwable t) { t.printStackTrace(); } } // runLoop never exits until app terminates protected abstract void runLoop(Runnable launchable); // should return after loop termination completion protected void finishTerminating() { // To make sure application object is not used outside of the run loop application = null; // The eventThread is null at this point, no need to check it } /** * Gets the name for the application. The application name may * be used to identify the application in the user interface or * as part of the platform specific path used to store application * data. * * This is a hint and may not be used on some platforms. * * @return the application name */ public String getName() { checkEventThread(); return name; } /** * Sets the name for the application. The application name may * be used to identify the application in the user interface or * as part of the platform specific path used to store application * data. * * The name could be set only once. All subsequent calls are ignored. * * This is a hint and may not be used on some platforms. * * @param name the new application name */ public void setName(String name) { checkEventThread(); if (name != null && DEFAULT_NAME.equals(this.name)) { this.name = name; } } /** * Gets a platform specific path that can be used to store * application data. The application name typically appears * as part of the path. * * On some platforms, the path may not yet exist and the caller * will need to create it. * * @return the platform specific path for the application data */ public String getDataDirectory() { checkEventThread(); String userHome = AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty("user.home")); return userHome + File.separator + "." + name + File.separator; } private void notifyWillFinishLaunching() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleWillFinishLaunchingAction(this, System.nanoTime()); } } private void notifyDidFinishLaunching() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleDidFinishLaunchingAction(this, System.nanoTime()); } } private void notifyWillBecomeActive() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleWillBecomeActiveAction(this, System.nanoTime()); } } private void notifyDidBecomeActive() { this.initialActiveEventReceived = true; EventHandler handler = getEventHandler(); if (handler != null) { handler.handleDidBecomeActiveAction(this, System.nanoTime()); } } private void notifyWillResignActive() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleWillResignActiveAction(this, System.nanoTime()); } } private boolean notifyThemeChanged(String themeName) { EventHandler handler = getEventHandler(); if (handler != null) { return handler.handleThemeChanged(themeName); } return false; } private void notifyDidResignActive() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleDidResignActiveAction(this, System.nanoTime()); } } private void notifyDidReceiveMemoryWarning() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleDidReceiveMemoryWarning(this, System.nanoTime()); } } private void notifyWillHide() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleWillHideAction(this, System.nanoTime()); } } private void notifyDidHide() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleDidHideAction(this, System.nanoTime()); } } private void notifyWillUnhide() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleWillUnhideAction(this, System.nanoTime()); } } private void notifyDidUnhide() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleDidUnhideAction(this, System.nanoTime()); } } // notificiation when user drag and drops files onto app icon private void notifyOpenFiles(String files[]) { if ((this.initialActiveEventReceived == false) && (this.initialOpenedFiles == null)) { // rememeber the initial opened files this.initialOpenedFiles = files; } EventHandler handler = getEventHandler(); if ((handler != null) && (files != null)) { handler.handleOpenFilesAction(this, System.nanoTime(), files); } } private void notifyWillQuit() { EventHandler handler = getEventHandler(); if (handler != null) { handler.handleQuitAction(this, System.nanoTime()); } } /** * Install app's default native menus: * on Mac OS X - Apple menu (showing the app name) with a single Quit menu item * on Windows - NOP * on Linux - NOP */ public void installDefaultMenus(MenuBar menubar) { checkEventThread(); // To override in subclasses } public EventHandler getEventHandler() { //checkEventThread(); // Glass (Mac) // When an app is closing, Mac calls notify- Will/DidHide, Will/DidResignActive // on a thread other than the Main thread return eventHandler; } public void setEventHandler(EventHandler eventHandler) { checkEventThread(); boolean resendOpenFiles = ((this.eventHandler != null) && (this.initialOpenedFiles != null)); this.eventHandler = eventHandler; if (resendOpenFiles == true) { // notify the new event handler with initial opened files notifyOpenFiles(this.initialOpenedFiles); } } private boolean terminateWhenLastWindowClosed = true; public final boolean shouldTerminateWhenLastWindowClosed() { checkEventThread(); return terminateWhenLastWindowClosed; } public final void setTerminateWhenLastWindowClosed(boolean b) { checkEventThread(); terminateWhenLastWindowClosed = b; } public boolean shouldUpdateWindow() { checkEventThread(); return false; // overridden in platform application class } public boolean hasWindowManager() { //checkEventThread(); // Prism (Mac) return true; // overridden in platform application class } /** * Notifies the Application that rendering has completed for current pulse. * * This is called on the render thread. */ public void notifyRenderingFinished() { } public void terminate() { checkEventThread(); try { final List windows = new LinkedList<>(Window.getWindows()); for (Window window : windows) { // first make windows invisible window.setVisible(false); } for (Window window : windows) { // now we can close windows window.close(); } } catch (Throwable t) { t.printStackTrace(); } finally { finishTerminating(); } } // May be called on any thread static public Application GetApplication() { return Application.application; } // May be called on any thread protected static void setEventThread(Thread thread) { Application.eventThread = thread; } // May be called on any thread protected static Thread getEventThread() { return Application.eventThread; } /** * Returns {@code true} if the current thread is the event thread. */ public static boolean isEventThread() { return Thread.currentThread() == Application.eventThread; } /** * Verifies that the current thread is the event thread, and throws * an exception if this is not so. * * The check can be disabled by setting the "glass.disableThreadChecks" * system property. It is preferred, however, to fix the application code * instead. * * @throws IllegalStateException if the current thread is not the event thread */ public static void checkEventThread() { //TODO: we do NOT advertise the "glass.disableThreadChecks". // If we never get a complaint about this check, we can consider // dropping the system property and perform the check unconditionally if (!disableThreadChecks && Thread.currentThread() != Application.eventThread) { throw new IllegalStateException( "This operation is permitted on the event thread only; currentThread = " + Thread.currentThread().getName()); } } // Called from native, when a JNI exception has occurred public static void reportException(Throwable t) { Thread currentThread = Thread.currentThread(); Thread.UncaughtExceptionHandler handler = currentThread.getUncaughtExceptionHandler(); handler.uncaughtException(currentThread, t); } abstract protected void _invokeAndWait(java.lang.Runnable runnable); /** * Block the current thread and wait until the given runnable finishes * running on the native event loop thread. */ public static void invokeAndWait(java.lang.Runnable runnable) { if (runnable == null) { return; } if (isEventThread()) { runnable.run(); } else { GetApplication()._invokeAndWait(runnable); } } abstract protected void _invokeLater(java.lang.Runnable runnable); /** * Schedule the given runnable to run on the native event loop thread * some time in the future, and return immediately. */ public static void invokeLater(java.lang.Runnable runnable) { if (runnable == null) { return; } GetApplication()._invokeLater(runnable); } protected abstract Object _enterNestedEventLoop(); protected abstract void _leaveNestedEventLoop(Object retValue); private static int nestedEventLoopCounter = 0; /** * Starts a nested event loop. * * Calling this method temporarily blocks processing of the current event, * and starts a nested event loop to handle other native events. To * proceed with the blocked execution path, the application should call the * {@link #leaveNestedEventLoop(Object)} method. * * Note that this method may only be invoked on the main (event handling) * thread. * * An application may enter several nested loops recursively. There's no * limit of recursion other than that imposed by the native stack size. * * @return an object passed to the leaveNestedEventLoop() method * @throws RuntimeException if the current thread is not the main thread */ static Object enterNestedEventLoop() { checkEventThread(); nestedEventLoopCounter++; try { return GetApplication()._enterNestedEventLoop(); } finally { nestedEventLoopCounter--; } } /** * Terminates the current nested event loop. * * After calling this method and returning from the current event handler, * the execusion returns to the point where the {@link #enterNestedEventLoop} * was called previously. You may specify a return value for the * enterNestedEventLoop() method by passing the argument {@code retValue} to * the leaveNestedEventLoop(). * * Note that this method may only be invoked on the main (event handling) * thread. * * @throws RuntimeException if the current thread is not the main thread * @throws IllegalStateException if the application hasn't started a nested * event loop */ static void leaveNestedEventLoop(Object retValue) { checkEventThread(); if (nestedEventLoopCounter == 0) { throw new IllegalStateException("Not in a nested event loop"); } GetApplication()._leaveNestedEventLoop(retValue); } public static boolean isNestedLoopRunning() { checkEventThread(); return nestedEventLoopCounter > 0; } //TODO: move to the EventHandler public void menuAboutAction() { System.err.println("about"); } // FACTORY METHODS /** * Create a window. * * The styleMask argument is a bitmask of window styles as defined in the * Window class. Note, however, that visual kinds (UNTITLED, TITLED, * or TRANSPARENT) can't be combined together. Also, functional types * (NORMAL, POPUP, or UTILITY) can't be combined together. A window is * allowed to be of exactly one visual kind, and exactly one functional * type. */ public abstract Window createWindow(Window owner, Screen screen, int styleMask); /** * Create a window. * * The styleMask argument is a bitmask of window styles as defined in the * Window class. Note, however, that visual kinds (UNTITLED, TITLED, * or TRANSPARENT) can't be combined together. Also, functional types * (NORMAL, POPUP, or UTILITY) can't be combined together. A window is * allowed to be of exactly one visual kind, and exactly one functional * type. */ public final Window createWindow(Screen screen, int styleMask) { return createWindow(null, screen, styleMask); } public abstract Window createWindow(long parent); public abstract View createView(); public abstract Cursor createCursor(int type); public abstract Cursor createCursor(int x, int y, Pixels pixels); protected abstract void staticCursor_setVisible(boolean visible); protected abstract Size staticCursor_getBestSize(int width, int height); public final Menu createMenu(String title) { return new Menu(title); } public final Menu createMenu(String title, boolean enabled) { return new Menu(title, enabled); } public final MenuBar createMenuBar() { return new MenuBar(); } public final MenuItem createMenuItem(String title) { return createMenuItem(title, null); } public final MenuItem createMenuItem(String title, MenuItem.Callback callback) { return createMenuItem(title, callback, KeyEvent.VK_UNDEFINED, KeyEvent.MODIFIER_NONE); } public final MenuItem createMenuItem(String title, MenuItem.Callback callback, int shortcutKey, int shortcutModifiers) { return createMenuItem(title, callback, shortcutKey, shortcutModifiers, null); } public final MenuItem createMenuItem(String title, MenuItem.Callback callback, int shortcutKey, int shortcutModifiers, Pixels pixels) { return new MenuItem(title, callback, shortcutKey, shortcutModifiers, pixels); } public abstract Pixels createPixels(int width, int height, ByteBuffer data); public abstract Pixels createPixels(int width, int height, IntBuffer data); public abstract Pixels createPixels(int width, int height, IntBuffer data, float scalex, float scaley); protected abstract int staticPixels_getNativeFormat(); /* utility method called from native code */ static Pixels createPixels(int width, int height, int[] data, float scalex, float scaley) { return Application.GetApplication().createPixels(width, height, IntBuffer.wrap(data), scalex, scaley); } /* utility method called from native code */ static float getScaleFactor(final int x, final int y, final int w, final int h) { float scale = 0.0f; // Find the maximum scale for screens this area overlaps for (Screen s : Screen.getScreens()) { final int sx = s.getX(), sy = s.getY(), sw = s.getWidth(), sh = s.getHeight(); if (x < (sx + sw) && (x + w) > sx && y < (sy + sh) && (y + h) > sy) { if (scale < s.getRecommendedOutputScaleX()) { scale = s.getRecommendedOutputScaleX(); } if (scale < s.getRecommendedOutputScaleY()) { scale = s.getRecommendedOutputScaleY(); } } } return scale == 0.0f ? 1.0f : scale; } public abstract Robot createRobot(); protected abstract double staticScreen_getVideoRefreshPeriod(); protected abstract Screen[] staticScreen_getScreens(); public abstract Timer createTimer(Runnable runnable); protected abstract int staticTimer_getMinPeriod(); protected abstract int staticTimer_getMaxPeriod(); public final EventLoop createEventLoop() { return new EventLoop(); } public Accessible createAccessible() { return null; } protected abstract FileChooserResult staticCommonDialogs_showFileChooser(Window owner, String folder, String filename, String title, int type, boolean multipleMode, ExtensionFilter[] extensionFilters, int defaultFilterIndex); protected abstract File staticCommonDialogs_showFolderChooser(Window owner, String folder, String title); protected abstract long staticView_getMultiClickTime(); protected abstract int staticView_getMultiClickMaxX(); protected abstract int staticView_getMultiClickMaxY(); /** * Gets the Name of the currently active high contrast theme. * If null, then high contrast is not enabled. */ public String getHighContrastTheme() { checkEventThread(); return null; } protected boolean _supportsInputMethods() { // Overridden in subclasses return false; } public final boolean supportsInputMethods() { checkEventThread(); return _supportsInputMethods(); } protected abstract boolean _supportsTransparentWindows(); public final boolean supportsTransparentWindows() { checkEventThread(); return _supportsTransparentWindows(); } public boolean hasTwoLevelFocus() { return false; } public boolean hasVirtualKeyboard() { return false; } public boolean hasTouch() { return false; } public boolean hasMultiTouch() { return false; } public boolean hasPointer() { return true; } protected abstract boolean _supportsUnifiedWindows(); public final boolean supportsUnifiedWindows() { checkEventThread(); return _supportsUnifiedWindows(); } protected boolean _supportsSystemMenu() { // Overridden in subclasses return false; } public final boolean supportsSystemMenu() { checkEventThread(); return _supportsSystemMenu(); } protected abstract int _getKeyCodeForChar(char c); /** * Returns a VK_ code of a key capable of producing the given unicode * character with respect to the currently active keyboard layout or * VK_UNDEFINED if the character isn't present in the current layout. * * @param c the character * @return integer code for the given char */ public static int getKeyCodeForChar(char c) { return application._getKeyCodeForChar(c); } }