/*
* Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
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<
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.
*
* @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<
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
*/
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<
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 {@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
scale);
protected abstract int
staticPixels_getNativeFormat();
/* utility method called from native code */
static
Pixels createPixels(int
width, int
height, int[]
data, float
scale) {
return
Application.
GetApplication().
createPixels(
width,
height,
IntBuffer.
wrap(
data),
scale);
}
/* 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.
getRenderScale()) {
scale =
s.
getRenderScale();
}
}
}
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);
}
}