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 }