/* * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package com.sun.glass.ui.swt; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.Arrays; import java.util.List; import java.util.Map; import com.sun.glass.events.*; import com.sun.glass.ui.*; import com.sun.glass.ui.CommonDialogs.*; import org.eclipse.swt.*; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.internal.Callback; import org.eclipse.swt.widgets.*; import org.eclipse.swt.opengl.*; //TODO - implement browser plugin //TODO - fix crash on some machines //TODO - implement keyboard (IME, NLS, Windows key ...) //TODO - implement screens (depths, associated callbacks) //TODO - implement accessibility //TODO - implement touch //TODO - implement retina // //TODO - implement focus grabs //TODO - implement robot wheel and getX/Y without thread check //TODO - implement clipboard and drag and drop images (get image) //TODO - implement clipboard and drag and drop multiple data transfer //TODO - implement missing cursors (glass has custom cursors for resize etc.) //TODO - cursor hide/show //TODO - implement file dialog multiple filters for a single description public final class SWTApplication extends Application { Object loopReturn; static final String IS_EVENTTHREAD_KEY = "javafx.embed.isEventThread"; //TODO - Prism on Mac uses exactly two GL contexts and does not destroy them //TODO - use a context per top level window to better match the platform static long context = 0, shareContext = 0; void runSWTEventLoop(Runnable launchable) { Display display = Display.getDefault(); setEventThread(display.getThread()); display.asyncExec(launchable); while (!display.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } } long getLauncherClass(final Runnable launchable, final long launcherSel) { try { Class OS = Class.forName("org.eclipse.swt.internal.cocoa.OS"); //TODO - callback free'd when we exit() to the operating system Callback callback = new Callback(new Object() { long launcherProc(long /*int*/ id, long /*int*/ sel) { //System.out.println("[launcherProc]"); if (sel == launcherSel) { runSWTEventLoop(launchable); } return 0; } }, "launcherProc", 2); long proc2 = callback.getAddress(); Method objc_getClass = OS.getDeclaredMethod("objc_getClass", String.class); long NSObject_class = (Long)objc_getClass.invoke(OS, "NSObject"); Method objc_allocateClassPair = OS.getDeclaredMethod("objc_allocateClassPair", Long.TYPE, String.class, Long.TYPE); long launcherClass = (long) objc_allocateClassPair.invoke(OS, NSObject_class, "Proc", 0); Method class_addMethod = OS.getDeclaredMethod("class_addMethod", Long.TYPE, Long.TYPE, Long.TYPE, String.class); class_addMethod.invoke(OS, launcherClass, launcherSel, proc2, "@:"); Method objc_registerClassPair = OS.getDeclaredMethod("objc_registerClassPair", Long.TYPE); objc_registerClassPair.invoke(OS, launcherClass); //System.out.println("[class registered="+launcherClass+"]"); return launcherClass; } catch (Exception e) { return 0; } // if (launcherClass == 0) { // Callback callback = new Callback(this, "launcherProc", 2); // long proc2 = callback.getAddress(); // launcherClass = OS.objc_allocateClassPair(OS.objc_getClass("NSObject"), "Proc", 0); // launcherSel = OS.sel_registerName("launcherSel"); // OS.class_addMethod(launcherClass, launcherSel, proc2, "@:"); // OS.objc_registerClassPair(launcherClass); // System.out.println("[class registered="+launcherClass+"]"); // } } void runCocoaLoop(Runnable launchable) { try { Class OS = Class.forName("org.eclipse.swt.internal.cocoa.OS"); Method objc_msgSend_bool = OS.getDeclaredMethod("objc_msgSend_bool", Long.TYPE, Long.TYPE); long class_NSThread = OS.getDeclaredField("class_NSThread").getLong(OS); long sel_isMainThread = OS.getDeclaredField("sel_isMainThread").getLong(OS); boolean isMainThread = (Boolean)objc_msgSend_bool.invoke(OS, class_NSThread, sel_isMainThread); if (isMainThread) { runSWTEventLoop(launchable); } else { //System.out.println("[wrong thread]"); Method sel_registerName = OS.getDeclaredMethod("sel_registerName", String.class); final long launcherSel = (long )sel_registerName.invoke(OS, "launcherSel"); long launcherClass = getLauncherClass(launchable, launcherSel); long sel_alloc = OS.getDeclaredField("sel_alloc").getLong(OS); long sel_init = OS.getDeclaredField("sel_init").getLong(OS); long sel_performSelectorOnMainThread_withObject_waitUntilDone_ = OS.getDeclaredField("sel_performSelectorOnMainThread_withObject_waitUntilDone_").getLong(OS); long sel_release = OS.getDeclaredField("sel_release").getLong(OS); Method objc_msgSendLL = OS.getDeclaredMethod("objc_msgSend", Long.TYPE, Long.TYPE); Method objc_msgSendLLLLZ = OS.getDeclaredMethod("objc_msgSend", Long.TYPE, Long.TYPE, Long.TYPE, Long.TYPE, Boolean.TYPE); long id = (Long)objc_msgSendLL.invoke(OS, launcherClass, sel_alloc); id = (Long)objc_msgSendLL.invoke(OS, id, sel_init); objc_msgSendLLLLZ.invoke(OS, id, sel_performSelectorOnMainThread_withObject_waitUntilDone_, launcherSel, 0, false); objc_msgSendLL.invoke(OS, id, sel_release); //System.out.println("[message sent]"); } } catch (Exception e) { e.printStackTrace(); } // if (NSThread.isMainThread()) { // runSWTEventLoop(); // } else { // System.out.println("[wrong thread]"); // long cls = getLauncherClass(); // long id = OS.objc_msgSend(cls, OS.sel_alloc); // NSObject obj = new NSObject(id); // obj.init(); // obj.performSelectorOnMainThread(launcherSel, null, false); // obj.release(); // System.out.println("[message sent]"); // } } @Override protected void runLoop(final Runnable launchable) { if ("true".equals(System.getProperty(IS_EVENTTHREAD_KEY, "false"))) { Display display = Display.getDefault(); setEventThread(display.getThread()); launchable.run(); return; } if (SWT.getPlatform().equals("cocoa")) { runCocoaLoop(launchable); } else { // the current thread can't block as the caller is waiting on it new Thread(() -> runSWTEventLoop(launchable)).start(); } } @Override protected void finishTerminating() { if ("true".equals(System.getProperty(IS_EVENTTHREAD_KEY, "false"))) { return; } Display.getDefault().dispose(); } @Override public Window createWindow(Window owner, Screen screen, int styleMask) { return new SWTWindow(owner, screen, styleMask); } final static long BROWSER_PARENT_ID = -1L; @Override public Window createWindow(long parent) { /* called by the applet code */ SWTWindow window = new SWTWindow(parent); if (parent == BROWSER_PARENT_ID) { // Special case: a Mac embedded window, which is a parent to other child Windows. // Needs implicit view, with a layer that will be provided to the plugin window.setView(createView()); } return window; } @Override public View createView() { return new SWTView(); } @Override public Cursor createCursor(int type) { return new SWTCursor(type); } @Override public Cursor createCursor(int x, int y, Pixels pixels) { return new SWTCursor(x, y, pixels); } @Override protected void staticCursor_setVisible(boolean visible) { //TODO - cursor hide/show not implemented } @Override protected Size staticCursor_getBestSize(int width, int height) { Point [] sizes = Display.getDefault().getCursorSizes(); return sizes.length > 0 ? new Size(sizes[0].x, sizes[0].y) : null; } @Override public Pixels createPixels(int width, int height, ByteBuffer data) { return new SWTPixels(width, height, data); } @Override public Pixels createPixels(int width, int height, IntBuffer data) { return new SWTPixels(width, height, data); } @Override public Pixels createPixels(int width, int height, IntBuffer data, float scale) { return new SWTPixels(width, height, data, scale); } @Override protected int staticPixels_getNativeFormat() { return Pixels.Format.BYTE_BGRA_PRE; } @Override public Robot createRobot() { return new SWTRobot(); } @Override protected double staticScreen_getVideoRefreshPeriod() { //TODO - vsync not implemented return 0; } //TODO - get rid of reflection //TODO - implement multiple screens //TODO - implement resolution changed @Override protected Screen[] staticScreen_getScreens() { Display display = Display.getDefault(); final Screen[] screens = new Screen[1]; try { Constructor screenConstructor = Screen.class.getDeclaredConstructor( long.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class, float.class); screenConstructor.setAccessible(true); Monitor monitor = display.getPrimaryMonitor(); Rectangle bounds = monitor.getBounds(); Rectangle client = monitor.getClientArea(); int depth = display.getDepth(); Point dpi = display.getDPI(); screens[0] = screenConstructor.newInstance( 1L, depth, bounds.x, bounds.y, bounds.width, bounds.height, client.x, client.y, client.width, client.height, dpi.x, dpi.y, 1.0f); return screens; } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { throw new RuntimeException("Unable to construct a Screen", e); } } @Override public Timer createTimer(Runnable runnable) { return new SWTTimer(runnable); } @Override protected int staticTimer_getMinPeriod() { return 0; } @Override protected int staticTimer_getMaxPeriod() { return 100000; } @Override protected FileChooserResult staticCommonDialogs_showFileChooser(Window owner, String folder, String filename, String title, int type, boolean multipleMode, ExtensionFilter[] extensionFilters, int defaultFilterIndex) { int bits = SWT.APPLICATION_MODAL; if (multipleMode) bits |= SWT.MULTI; switch (type) { case Type.OPEN: bits |= SWT.OPEN; break; case Type.SAVE: bits |= SWT.SAVE; break; } Shell parent = ((SWTWindow)owner).shell; FileDialog dialog = new FileDialog(parent, bits); dialog.setText(title); String [] filters = new String [extensionFilters.length]; String [] extensions = new String [extensionFilters.length]; for (int i=0; i list = extensionFilters[i].getExtensions(); if (list.size() > 0) extensions[i] = list.get(0); } dialog.setFilterNames(filters); dialog.setFilterExtensions(extensions); dialog.setFilterPath(folder); dialog.setFilterIndex(defaultFilterIndex); dialog.setFileName(filename); if (dialog.open() == null) return new FileChooserResult(); String path = dialog.getFilterPath(); String [] names = dialog.getFileNames(); String [] result = new String [names.length]; for (int i=0; i l = new java.util.ArrayList(); for (String s : result) { l.add(new File(s)); } //TODO: support FileChooserResult return new FileChooserResult(l, null); } @Override protected File staticCommonDialogs_showFolderChooser(Window owner, String folder, String title) { int bits = SWT.APPLICATION_MODAL; Shell parent = ((SWTWindow)owner).shell; DirectoryDialog dialog = new DirectoryDialog(parent, bits); dialog.setText(title); String result = dialog.open(); return result == null ? null : new File(result); } @Override protected Object _enterNestedEventLoop() { loopReturn = null; while (loopReturn == null) { if (!Display.getDefault().readAndDispatch()) { Display.getDefault().sleep(); } } try { return loopReturn; } finally { loopReturn = null; } } @Override protected void _leaveNestedEventLoop(Object retValue) { loopReturn = retValue; } @Override protected long staticView_getMultiClickTime() { return Display.getDefault().getDoubleClickTime(); } @Override protected int staticView_getMultiClickMaxX() { return 4; } @Override protected int staticView_getMultiClickMaxY() { return 4; } @Override protected void _invokeAndWait(Runnable runnable) { Display.getDefault().syncExec(runnable); } @Override protected void _invokeLater(Runnable runnable) { Display.getDefault().asyncExec(runnable); } @Override protected boolean _supportsSystemMenu() { return SWT.getPlatform().equals("cocoa"); } @Override protected boolean _supportsTransparentWindows() { return SWT.getPlatform().equals("cocoa"); } @java.lang.Override protected boolean _supportsUnifiedWindows() { return false; } static final int [] [] KeyTable = { {KeyEvent.VK_UNDEFINED, SWT.NULL}, // SWT only {'\n' /*KeyEvent.VK_?????*/, SWT.CR}, // Misc {'\n' /*KeyEvent.VK_ENTER*/, SWT.LF}, {'\b' /*KeyEvent.VK_BACKSPACE*/, SWT.BS}, {'\t' /*KeyEvent.VK_TAB*/, SWT.TAB}, // {KeyEvent.VK_CANCEL SWT.???}, // {KeyEvent.VK_CLEAR SWT.???}, // {KeyEvent.VK_PAUSE SWT.???}, {KeyEvent.VK_ESCAPE, SWT.ESC}, {KeyEvent.VK_SPACE, 0x20}, {KeyEvent.VK_DELETE, SWT.DEL}, // {KeyEvent.VK_PRINTSCREEN SWT.???; {KeyEvent.VK_INSERT, SWT.INSERT}, {KeyEvent.VK_HELP, SWT.HELP}, // Modifiers {KeyEvent.VK_SHIFT, SWT.SHIFT}, {KeyEvent.VK_CONTROL, SWT.CONTROL}, {KeyEvent.VK_ALT, SWT.ALT}, {KeyEvent.VK_WINDOWS, SWT.COMMAND}, // {KeyEvent.VK_CONTEXT_MENU, SWT.???}, {KeyEvent.VK_CAPS_LOCK, SWT.CAPS_LOCK}, {KeyEvent.VK_NUM_LOCK, SWT.NUM_LOCK}, {KeyEvent.VK_SCROLL_LOCK, SWT.SCROLL_LOCK}, // Navigation keys {KeyEvent.VK_PAGE_UP, SWT.PAGE_UP}, {KeyEvent.VK_PAGE_DOWN, SWT.PAGE_DOWN}, {KeyEvent.VK_END, SWT.END}, {KeyEvent.VK_HOME, SWT.HOME}, {KeyEvent.VK_LEFT, SWT.ARROW_LEFT}, {KeyEvent.VK_UP, SWT.ARROW_UP}, {KeyEvent.VK_RIGHT, SWT.ARROW_RIGHT}, {KeyEvent.VK_DOWN, SWT.ARROW_DOWN}, // Misc 2 //TODO - suspect this only works for English keyboard {KeyEvent.VK_COMMA, ','}, // ',' {KeyEvent.VK_MINUS, '-'}, // '-' {KeyEvent.VK_PERIOD, '.'}, // '.' {KeyEvent.VK_SLASH, '/'}, // '/' {KeyEvent.VK_SEMICOLON, ';'}, // ';' {KeyEvent.VK_EQUALS, '='}, // '=' {KeyEvent.VK_OPEN_BRACKET, '['}, // '[' {KeyEvent.VK_BACK_SLASH, '\\'}, // '\' {KeyEvent.VK_CLOSE_BRACKET, ']'}, // ']' // Numeric key pad keys {KeyEvent.VK_MULTIPLY, SWT.KEYPAD_MULTIPLY}, // '*' {KeyEvent.VK_ADD, SWT.KEYPAD_ADD}, // '+' // {KeyEvent.VK_SEPARATOR, SWT.???}, {KeyEvent.VK_SUBTRACT, SWT.KEYPAD_SUBTRACT}, {KeyEvent.VK_DECIMAL, SWT.KEYPAD_DECIMAL}, {KeyEvent.VK_DIVIDE, SWT.KEYPAD_DIVIDE}, // {KeyEvent.VK_????, SWT.KEYPAD_EQUAL}, // {KeyEvent.VK_????, SWT.KEYPAD_CR}, {KeyEvent.VK_AMPERSAND, '@'}, {KeyEvent.VK_ASTERISK, '*'}, {KeyEvent.VK_DOUBLE_QUOTE, '"'}, // '"' {KeyEvent.VK_LESS, '<'}, // '<' {KeyEvent.VK_GREATER, '>'}, // '>' {KeyEvent.VK_BRACELEFT, '{'}, // '{' {KeyEvent.VK_BRACERIGHT, '}'}, // '}' {KeyEvent.VK_BACK_QUOTE, '`'}, // '`' {KeyEvent.VK_QUOTE, '\''}, // ''' {KeyEvent.VK_AT, '@'}, // '@' {KeyEvent.VK_COLON, ':'}, // ':' {KeyEvent.VK_CIRCUMFLEX, '^'}, // '^' {KeyEvent.VK_DOLLAR, '$'}, // '$' // {KeyEvent.VK_EURO_SIGN, 0x0204}, {KeyEvent.VK_EXCLAMATION, '!'}, // '!' // {KeyEvent.VK_INV_EXCLAMATION, 0x0206}, {KeyEvent.VK_LEFT_PARENTHESIS, '('}, // '(' {KeyEvent.VK_NUMBER_SIGN, '#'}, // '#' {KeyEvent.VK_PLUS, '+'}, // '+' {KeyEvent.VK_RIGHT_PARENTHESIS, ')'}, // ')' {KeyEvent.VK_UNDERSCORE, '_'}, // '_' // Numeric keys //TODO - suspect this only works for English keyboard {KeyEvent.VK_0, '0'}, // '0' {KeyEvent.VK_1, '1'}, // '1' {KeyEvent.VK_2, '2'}, // '2' {KeyEvent.VK_3, '3'}, // '3' {KeyEvent.VK_4, '4'}, // '4' {KeyEvent.VK_5, '5'}, // '5' {KeyEvent.VK_6, '6'}, // '6' {KeyEvent.VK_7, '7'}, // '7' {KeyEvent.VK_8, '8'}, // '8' {KeyEvent.VK_9, '9'}, // '9' // Alpha keys //TODO - suspect this only works for English keyboard {KeyEvent.VK_A, 'a'}, // 'A' {KeyEvent.VK_B, 'b'}, // 'B' {KeyEvent.VK_C, 'c'}, // 'C' {KeyEvent.VK_D, 'd'}, // 'D' {KeyEvent.VK_E, 'e'}, // 'E' {KeyEvent.VK_F, 'f'}, // 'F' {KeyEvent.VK_G, 'g'}, // 'G' {KeyEvent.VK_H, 'h'}, // 'H' {KeyEvent.VK_I, 'i'}, // 'I' {KeyEvent.VK_J, 'j'}, // 'J' {KeyEvent.VK_K, 'k'}, // 'K' {KeyEvent.VK_L, 'l'}, // 'L' {KeyEvent.VK_M, 'm'}, // 'M' {KeyEvent.VK_N, 'n'}, // 'N' {KeyEvent.VK_O, 'o'}, // 'O' {KeyEvent.VK_P, 'p'}, // 'P' {KeyEvent.VK_Q, 'q'}, // 'Q' {KeyEvent.VK_R, 'r'}, // 'R' {KeyEvent.VK_S, 's'}, // 'S' {KeyEvent.VK_T, 't'}, // 'T' {KeyEvent.VK_U, 'u'}, // 'U' {KeyEvent.VK_V, 'v'}, // 'V' {KeyEvent.VK_W, 'w'}, // 'W' {KeyEvent.VK_X, 'x'}, // 'X' {KeyEvent.VK_Y, 'y'}, // 'Y' {KeyEvent.VK_Z, 'z'}, // 'Z' // Numpad keys {KeyEvent.VK_NUMPAD0, SWT.KEYPAD_0}, {KeyEvent.VK_NUMPAD1, SWT.KEYPAD_1}, {KeyEvent.VK_NUMPAD2, SWT.KEYPAD_2}, {KeyEvent.VK_NUMPAD3, SWT.KEYPAD_3}, {KeyEvent.VK_NUMPAD4, SWT.KEYPAD_4}, {KeyEvent.VK_NUMPAD5, SWT.KEYPAD_5}, {KeyEvent.VK_NUMPAD6, SWT.KEYPAD_6}, {KeyEvent.VK_NUMPAD7, SWT.KEYPAD_7}, {KeyEvent.VK_NUMPAD8, SWT.KEYPAD_8}, {KeyEvent.VK_NUMPAD9, SWT.KEYPAD_9}, // Function keys {KeyEvent.VK_F1, SWT.F1}, {KeyEvent.VK_F2, SWT.F2}, {KeyEvent.VK_F3, SWT.F3}, {KeyEvent.VK_F4, SWT.F4}, {KeyEvent.VK_F5, SWT.F5}, {KeyEvent.VK_F6, SWT.F6}, {KeyEvent.VK_F7, SWT.F7}, {KeyEvent.VK_F8, SWT.F8}, {KeyEvent.VK_F9, SWT.F9}, {KeyEvent.VK_F10, SWT.F10}, {KeyEvent.VK_F11, SWT.F11}, {KeyEvent.VK_F12, SWT.F12}, //TODO - map these to FX keys // /* Numeric Keypad Keys */ // {KeyEvent.VK_MULTIPLY, SWT.KEYPAD_MULTIPLY}, // {KeyEvent.VK_ADD, SWT.KEYPAD_ADD}, // {KeyEvent.VK_RETURN, SWT.KEYPAD_CR}, // {KeyEvent.VK_SUBTRACT, SWT.KEYPAD_SUBTRACT}, // {KeyEvent.VK_DECIMAL, SWT.KEYPAD_DECIMAL}, // {KeyEvent.VK_DIVIDE, SWT.KEYPAD_DIVIDE}, //// {KeyEvent.VK_????, SWT.KEYPAD_EQUAL}, }; static int getKeyCode(Event event) { int keyCode = event.keyCode; for (int i=0; i> 8) & 0xFF); byte r = (byte) ((pixel >> 16) & 0xFF); byte a = (byte) ((pixel >> 24) & 0xFF); // non premultiplied ? alphaData[alphaOffset++] = a; buffer[offset] = b; buffer[offset + 1] = g; buffer[offset + 2] = r; buffer[offset + 3] = 0;// alpha } } } else { throw new IllegalArgumentException("unhandled pixel buffer"); } PaletteData palette = new PaletteData(0xFF00, 0xFF0000, 0xFF000000); ImageData imageData = new ImageData(width, height, 32, palette, 4, buffer); imageData.alphaData = alphaData; return imageData; } //TODO - implement conversion from ImageData to Pixels static Pixels createPixels(ImageData data) { if (data == null) return null; // ImageData imageData = (ImageData) data; // int width = imageData.width, height = imageData.height; // int [] pixels = new int [width * height]; // imageData.getPixels(0, 0, width * height, pixels, 0); // IntBuffer buffer = IntBuffer.wrap(pixels); // return new SWTPixels(width, height, buffer); return null; } @Override protected int _getKeyCodeForChar(char c) { return KeyEvent.VK_UNDEFINED; } }