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