/*
* Copyright (c) 2010, 2018, 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.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<Boolean>) () -> {
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.
Returns: the application name
/**
* 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.
Params: - name – the new application 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.
Returns: the platform specific path for the application data
/**
* 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<String>) () -> 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
/**
* 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.
/**
* 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<Window> 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 true
if the current thread is the event thread. /**
* 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
/**
* 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.
/**
* 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.
/**
* 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 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. Throws: - RuntimeException – if the current thread is not the main thread
Returns: an object passed to the leaveNestedEventLoop() method
/**
* 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 enterNestedEventLoop
was called previously. You may specify a return value for the enterNestedEventLoop() method by passing the argument 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
- IllegalStateException – if the application hasn't started a nested
event loop
/**
* 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.
/**
* 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.
/**
* 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 GlassRobot 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.
/**
* 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.
Params: - c – the character
Returns: integer code for the given char
/**
* 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);
}
}