All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdesktop.application.Application Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
/*
 * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved.
 * Copyright (C) 2010 Illya Yalovyy ([email protected]) 
 * Use is subject to license terms.
 */
package org.jdesktop.application;

import org.jdesktop.application.utils.AppHelper;

import java.awt.ActiveEvent;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.PaintEvent;
import java.beans.Beans;
import java.lang.reflect.Constructor;
import java.util.EventListener;
import java.util.EventObject;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import org.jdesktop.application.utils.OSXAdapter;

import org.jdesktop.application.utils.PlatformType;

/**
 * The base class for Swing applications. 
 * 
 * 

* This class defines a simple lifecyle for Swing applications: {@code * initialize}, {@code startup}, {@code ready}, and {@code shutdown}. * The {@code Application's} {@code startup} method is responsible for * creating the initial GUI and making it visible, and the {@code * shutdown} method for hiding the GUI and performing any other * cleanup actions before the application exits. The {@code initialize} * method can be used configure system properties that must be set * before the GUI is constructed and the {@code ready} * method is for applications that want to do a little bit of extra * work once the GUI is "ready" to use. Concrete subclasses must * override the {@code startup} method. *

* Applications are started with the static {@code launch} method. * Applications use the {@code ApplicationContext} {@link * Application#getContext} to find resources, * actions, local storage, and so on. *

* All {@code Application} subclasses must override {@code startup} * and they should call {@link #exit} (which * calls {@code shutdown}) to exit. * Here's an example of a complete "Hello World" Application: *

 * public class MyApplication extends Application {
 *     JFrame mainFrame = null;
 *     @Override protected void startup() {
 *         mainFrame = new JFrame("Hello World");
 *         mainFrame.add(new JLabel("Hello World"));
 *         mainFrame.addWindowListener(new MainFrameListener());
 *         mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
 *         mainFrame.pack();
 *         mainFrame.setVisible(true);
 *     }
 *     @Override protected void shutdown() {
 *         mainFrame.setVisible(false);
 *     }
 *     private class MainFrameListener extends WindowAdapter {
 *         public void windowClosing(WindowEvent e) {
 *            exit();
 *         }
 *     }
 *     public static void main(String[] args) {
 *         Application.launch(MyApplication.class, args);
 *     }
 * }
 * 
*

* The {@code mainFrame's} {@code defaultCloseOperation} is set * to {@code DO_NOTHING_ON_CLOSE} because we're handling attempts * to close the window by calling * {@code ApplicationContext} {@link #exit}. *

* Simple single frame applications like the example can be defined * more easily with the {@link SingleFrameApplication * SingleFrameApplication} {@code Application} subclass. * *

* All of the Application's methods are called (must be called) on * the EDT. * *

* All but the most trivial applications should define a ResourceBundle * in the resources subpackage with the same name as the application class (like {@code * resources/MyApplication.properties}). This ResourceBundle contains * resources shared by the entire application and should begin with the * following the standard Application resources: *

 * Application.name = A short name, typically just a few words
 * Application.id = Suitable for Application specific identifiers, like file names
 * Application.title = A title suitable for dialogs and frames
 * Application.version = A version string that can be incorporated into messages
 * Application.vendor = A proper name, like Sun Microsystems, Inc.
 * Application.vendorId = suitable for Application-vendor specific identifiers, like file names.
 * Application.homepage = A URL like http://www.javadesktop.org
 * Application.description =  One brief sentence
 * Application.lookAndFeel = either system, default, or a LookAndFeel class name
 * 
*

* The {@code Application.lookAndFeel} resource is used to initialize the * {@code UIManager lookAndFeel} as follows: *

    *
  • {@code system} - the system (native) look and feel
  • *
  • {@code default} - use the JVM default, typically the cross platform look and feel
  • *
  • {@code nimbus} - use the modern cross platform look and feel Nimbus *
  • a LookAndFeel class name - use the specified class *
* * @see SingleFrameApplication * @see ApplicationContext * @see UIManager#setLookAndFeel * @author Hans Muller ([email protected]) */ @ProxyActions({"cut", "copy", "paste", "delete"}) public abstract class Application extends AbstractBean { public static final String KEY_APPLICATION_TITLE = "Application.title"; public static final String KEY_APPLICATION_ICON = "Application.icon"; public static final String KEY_APPLICATION_VENDOR_ID = "Application.vendorId"; private static final Logger logger = Logger.getLogger(Application.class.getName()); private static Application application = null; private final List exitListeners; private final ApplicationContext context; protected boolean ready; /** * Not to be called directly, see {@link #launch launch}. *

* Subclasses can provide a no-args construtor * to initialize private final state however GUI * initialization, and anything else that might refer to * public API, should be done in the {@link #startup startup} * method. */ protected Application() { exitListeners = new CopyOnWriteArrayList(); context = new ApplicationContext(); } /** * Creates an instance of the specified {@code Application} * subclass, sets the {@code ApplicationContext} {@code * application} property, and then calls the new {@code * Application's} {@code initialize} and {@code startup} methods. * * When UI is ready, method {@code ready} is called. * * The {@code launch} method is * typically called from the Application's {@code main}: *

     *     public static void main(String[] args) {
     *         Application.launch(MyApplication.class, args);
     *     }
     * 
* The {@code applicationClass} constructor and {@code startup} methods * run on the event dispatching thread. * * @param applicationClass the {@code Application} class to launch * @param args {@code main} method arguments * @see #shutdown * @see ApplicationContext#getApplication */ public static synchronized void launch(final Class applicationClass, final String[] args) { Runnable doCreateAndShowGUI = new Runnable() { @Override public void run() { try { application = create(applicationClass); application.initialize(args); application.startup(); application.waitForReady(); } catch (Exception e) { String msg = String.format("Application %s failed to launch", applicationClass); logger.log(Level.SEVERE, msg, e); throw (new Error(msg, e)); } } }; SwingUtilities.invokeLater(doCreateAndShowGUI); } /* Initializes the ApplicationContext applicationClass and application * properties. * * Note that, as of Java SE 5, referring to a class literal * doesn't force the class to be loaded. More info: * http://java.sun.com/javase/technologies/compatibility.jsp#literal * It's important to perform these initializations early, so that * Application static blocks/initializers happen afterwards. * * @param applicationClass the {@code Application} class to create * @return created application instance */ static T create(Class applicationClass) throws Exception { if (!Beans.isDesignTime()) { /* A common mistake for privileged applications that make * network requests (and aren't applets or web started) is to * not configure the http.proxyHost/Port system properties. * We paper over that issue here. */ try { System.setProperty("java.net.useSystemProxies", "true"); } catch (SecurityException ignoreException) { // Unsigned apps can't set this property. } } /* Construct the Application object. The following * complications, relative to just calling * applicationClass.newInstance(), allow a privileged app to * have a private static inner Application subclass. */ Constructor ctor = applicationClass.getDeclaredConstructor(); if (!ctor.isAccessible()) { try { ctor.setAccessible(true); } catch (SecurityException ignore) { // ctor.newInstance() will throw an IllegalAccessException } } T application = ctor.newInstance(); /* Initialize the ApplicationContext application properties */ ApplicationContext ctx = application.getContext(); ctx.setApplicationClass(applicationClass); ctx.setApplication(application); /* Load the application resource map, notably the * Application.* properties. */ ResourceMap appResourceMap = ctx.getResourceMap(); final PlatformType platform = AppHelper.getPlatform(); appResourceMap.putResource(ResourceMap.KEY_PLATFORM, platform); //Generic registration with the Mac OS X application menu if (PlatformType.OS_X.equals(platform)) { try { OSXAdapter.setQuitHandler(application, Application.class.getDeclaredMethod("handleQuit", (Class[])null)); } catch (Exception e) { logger.log(Level.SEVERE, "Cannot set Mac Os X specific handler for Quit event", e); } } if (!Beans.isDesignTime()) { /* Initialize the UIManager lookAndFeel property with the * Application.lookAndFeel resource. If the the resource * isn't defined we default to "system". */ String key = "Application.lookAndFeel"; String lnfResource = appResourceMap.getString(key); String lnf = (lnfResource == null) ? "system" : lnfResource; try { if (lnf.equalsIgnoreCase("system")) { String name = UIManager.getSystemLookAndFeelClassName(); UIManager.setLookAndFeel(name); } else if (lnf.equalsIgnoreCase("nimbus")) { for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { if ("Nimbus".equals(info.getName())) { UIManager.setLookAndFeel(info.getClassName()); break; } } } else if (!lnf.equalsIgnoreCase("default")) { UIManager.setLookAndFeel(lnf); } } catch (Exception e) { String s = "Couldn't set LookandFeel " + key + " = \"" + lnfResource + "\""; logger.log(Level.WARNING, s, e); } } return application; } /* Calls the ready method when the eventQ is quiet. */ void waitForReady() { new DoWaitForEmptyEventQ().execute(); } /** * Responsible for initializations that must occur before the * GUI is constructed by {@code startup}. *

* This method is called by the static {@code launch} method, * before {@code startup} is called. Subclasses that want * to do any initialization work before {@code startup} must * override it. The {@code initialize} method * runs on the event dispatching thread. *

* By default initialize() does nothing. * * @param args the main method's arguments. * @see #launch * @see #startup * @see #shutdown */ protected void initialize(String[] args) { } /** * Responsible for starting the application; for creating and showing * the initial GUI. *

* This method is called by the static {@code launch} method, * subclasses must override it. It runs on the event dispatching * thread. * * @see #launch * @see #initialize * @see #shutdown */ protected abstract void startup(); /** * Called after the startup() method has returned and there * are no more events on the * {@link Toolkit#getSystemEventQueue system event queue}. * When this method is called, the application's GUI is ready * to use. *

* It's usually important for an application to start up as * quickly as possible. Applications can override this method * to do some additional start up work, after the GUI is up * and ready to use. * * @see #launch * @see #startup * @see #shutdown */ protected void ready() { } /** * Called when the application {@link #exit exits}. * Subclasses may override this method to do any cleanup * tasks that are necessary before exiting. Obviously, you'll want to try * and do as little as possible at this point. This method runs * on the event dispatching thread. * * @see #startup * @see #ready * @see #exit * @see #addExitListener */ protected void shutdown() { // TBD should call TaskService#shutdownNow() on each TaskService } /* An event that sets a flag when it's dispatched and another * flag, see isEventQEmpty(), that indicates if the event queue * was empty at dispatch time. */ @SuppressWarnings("serial") private static class NotifyingEvent extends PaintEvent implements ActiveEvent { private boolean dispatched = false; private boolean qEmpty = false; NotifyingEvent(Component c) { super(c, PaintEvent.UPDATE, null); } synchronized boolean isDispatched() { return dispatched; } synchronized boolean isEventQEmpty() { return qEmpty; } @Override public void dispatch() { EventQueue q = Toolkit.getDefaultToolkit().getSystemEventQueue(); synchronized (this) { qEmpty = (q.peekEvent() == null); dispatched = true; notifyAll(); } } } /* Keep queuing up NotifyingEvents until the event queue is * empty when the NotifyingEvent is dispatched(). */ private void waitForEmptyEventQ(JPanel placeHolder) { boolean qEmpty = false; EventQueue q = Toolkit.getDefaultToolkit().getSystemEventQueue(); while (!qEmpty) { NotifyingEvent e = new NotifyingEvent(placeHolder); q.postEvent(e); synchronized (e) { while (!e.isDispatched()) { try { e.wait(); } catch (InterruptedException ie) { //ignore } } qEmpty = e.isEventQEmpty(); } } } /* When the event queue is empty, give the app a chance to do * something, now that the GUI is "ready". */ private class DoWaitForEmptyEventQ extends Task { private final JPanel placeHolder; DoWaitForEmptyEventQ() { super(Application.this); placeHolder = new JPanel(); } @Override protected Void doInBackground() { waitForEmptyEventQ(placeHolder); return null; } @Override protected void finished() { ready = true; ready(); } } /** * Gracefully shutdowns the application, calls {@code exit(null)} * This version of exit() is convenient if the decision to exit the * application wasn't triggered by an event. * * @see #exit(EventObject) */ public final void exit() { exit(null); } /** * Handles quit even on Mac Os X * Developer should not use it directly * @return always true */ public boolean handleQuit() { exit(); return false; } /** * Gracefully shutdowns the application. *

* If none of the {@code ExitListener.canExit()} methods return false, * calls the {@code ExitListener.willExit()} methods, then * {@code shutdown()}, and then exits the Application with * {@link #end end}. Exceptions thrown while running willExit() or shutdown() * are logged but otherwise ignored. *

* If the caller is responding to an GUI event, it's helpful to pass the * event along so that ExitListeners' canExit methods that want to popup * a dialog know on which screen to show the dialog. For example: *

     * class ConfirmExit implements Application.ExitListener {
     *     public boolean canExit(EventObject e) {
     *         Object source = (e != null) ? e.getSource() : null;
     *         Component owner = (source instanceof Component) ? (Component)source : null;
     *         int option = JOptionPane.showConfirmDialog(owner, "Really Exit?");
     *         return option == JOptionPane.YES_OPTION;
     *     }
     *     public void willExit(EventObejct e) {} 
     * }
     * myApplication.addExitListener(new ConfirmExit());
     * 
* The {@code eventObject} argument may be null, e.g. if the exit * call was triggered by non-GUI code, and {@code canExit}, {@code * willExit} methods must guard against the possibility that the * {@code eventObject} argument's {@code source} is not a {@code * Component}. * * @param event the EventObject that triggered this call or null * @see #addExitListener * @see #removeExitListener * @see #shutdown * @see #end */ public void exit(final EventObject event) { Runnable runnable = new Runnable() { @Override public void run() { for (ExitListener listener : exitListeners) { if (!listener.canExit(event)) { return; } } try { for (ExitListener listener : exitListeners) { try { listener.willExit(event); } catch (Exception e) { logger.log(Level.WARNING, "ExitListener.willExit() failed", e); } } shutdown(); } catch (Exception e) { logger.log(Level.WARNING, "unexpected error in Application.shutdown()", e); } finally { end(); } } }; if (SwingUtilities.isEventDispatchThread()) { runnable.run(); } else { try { SwingUtilities.invokeAndWait(runnable); } catch (Exception ignore) { } } } /** * Called by {@link #exit exit} to terminate the application. Calls * {@code Runtime.getRuntime().exit(0)}, which halts the JVM. * * @see #exit */ protected void end() { Runtime.getRuntime().exit(0); } /** * Gives the Application a chance to veto an attempt to exit/quit. * An {@code ExitListener's} {@code canExit} method should return * false if there are pending decisions that the user must make * before the app exits. A typical {@code ExitListener} would * prompt the user with a modal dialog. *

* The {@code eventObject} argument will be the the value passed * to {@link #exit(EventObject) exit()}. It may be null. *

* The {@code willExit} method is called after the exit has * been confirmed. An ExitListener that's going to perform * some cleanup work should do so in {@code willExit}. *

* {@code ExitListeners} run on the event dispatching thread. * * @see #exit(EventObject) * @see #addExitListener * @see #removeExitListener */ public interface ExitListener extends EventListener { /** * The method is called before the Application exits. * * @param event the {@code EventObject} object. It will be the the value passed * to {@link #exit(EventObject) exit()}. * @return {@code true} if application can proceed with shutdown process; {@code false} if * there are pending decisions that the user must make before the app exits. */ boolean canExit(EventObject event); /** * The method is called after the exit has been confirmed. * * @param event the {@code EventObject} object. It will be the the value passed * to {@link #exit(EventObject) exit()}. */ void willExit(EventObject event); } /** * Adds an {@code ExitListener} to the list. * * @param listener the {@code ExitListener} * @see #removeExitListener * @see #getExitListeners */ public void addExitListener(ExitListener listener) { exitListeners.add(listener); } /** * Removes an {@code ExitListener} from the list. * * @param listener the {@code ExitListener} * @see #addExitListener * @see #getExitListeners */ public void removeExitListener(ExitListener listener) { exitListeners.remove(listener); } /** * All of the {@code ExitListeners} added so far. * * @return all of the {@code ExitListeners} added so far. */ public ExitListener[] getExitListeners() { int size = exitListeners.size(); return exitListeners.toArray(new ExitListener[size]); } /** * The default {@code Action} for quitting an application, * {@code quit} just exits the application by calling {@code exit(e)}. * * @param e the triggering event * @see #exit(EventObject) */ @Action public void quit(ActionEvent e) { exit(e); } /** * The ApplicationContext for this Application. * * @return the Application's ApplicationContext */ public final ApplicationContext getContext() { return context; } /** * The {@code Application} singleton. *

* This method is only called after an Application has * been launched. * * @param applicationClass this Application's subclass * @return the launched Application singleton. * @see Application#launch */ public static synchronized T getInstance(Class applicationClass) { if (Beans.isDesignTime() && application==null) { try { application = create(applicationClass); } catch (Exception ex) { String msg = String.format("Couldn't construct %s", applicationClass); Logger.getLogger(Application.class.getName()).log(Level.SEVERE, msg, ex); throw new Error(msg, ex); } } checkApplicationLaunched(); return applicationClass.cast(application); } /** * The {@code Application} singleton. *

* This method is only called after an Application has * been launched. * * @return the Application singleton or a placeholder * @see Application#launch * @see Application#getInstance(Class) */ public static synchronized Application getInstance() { if (Beans.isDesignTime() && application==null) { application = new DesignTimeApplication(); } checkApplicationLaunched(); return application; } private static void checkApplicationLaunched() throws IllegalStateException { if (application == null) { throw new IllegalStateException("Application is not launched."); } } /** * Shows the application {@code View} * @param view - View to show * @see View */ public void show(View view) { Window window = (Window) view.getRootPane().getParent(); if (window != null) { window.pack(); window.setVisible(true); } } /** * Hides the application {@code View} * @param view * @see View */ public void hide(View view) { view.getRootPane().getParent().setVisible(false); } /** * The state of the initial UI. * @return true if the initial UI is ready */ public boolean isReady() { return ready; } /** * Application placeholder class * * Instance of this class is created when client * invokes static method {@code Application.getInstance()} * @author etf * @see Application#getInstance() */ private static final class DesignTimeApplication extends Application { protected DesignTimeApplication() { ApplicationContext ctx = getContext(); ctx.setApplicationClass(getClass()); ctx.setApplication(this); ResourceMap appResourceMap = ctx.getResourceMap(); appResourceMap.setPlatform(PlatformType.DEFAULT); } @Override protected void startup() { } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy