package net.xoetrope.swing.deploy;
import java.awt.Frame;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.lang.reflect.Method;
import javax.jnlp.ServiceManager;
import javax.jnlp.SingleInstanceListener;
import javax.jnlp.SingleInstanceService;
import javax.jnlp.UnavailableServiceException;
import javax.swing.Icon;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import net.xoetrope.debug.DebugLogger;
import net.xoetrope.swing.*;
import net.xoetrope.xui.XProject;
import net.xoetrope.xui.build.BuildProperties;
import org.jdesktop.jdic.tray.SystemTray;
import org.jdesktop.jdic.tray.TrayIcon;
/**
* <p>
* A manager class for tray actions. When a system tray icon is added the
* application can choose to stay resident in memory after the main window has
* been closed by setting the ExitOnClose startup property to false. The
* advantage of doing this is that the data model does not need to be
* reinitialized if the main window is just being redisplayed.
* </p>
* <p>
* The tray icon provides two menu items, one to open the window and the second
* to close the window and exit the JVM.
* </p>
* <p>This version of the class uses the SwingLabs version of the tray support
* and is therefore limited to Swing. If JDK 6 or later is being used then
* the JVM's tray support could be used for non Swing applications
* </p>
* <p>
* If the application is launched via JavaWebStart then the XSystemTrayManager
* is installed as a singleton and each subsequent invocation via JWS will
* cause a new activation. If the application has set an "ActivationObject"
* via the project.setObject( "ActivationObject", XActivation ) then the
* XActivation.activate method is invoked and the appliction can check the
* startup properties and perform the appropriate action.
* </p>
* <p>Copyright (c) Xoetrope Ltd., 2001-2007, see license.txt for details</p>
*/
public class XSystemTrayManager implements ActionListener, SingleInstanceListener
{
private static XSystemTrayManager instance;
private static TrayIcon trayIcon;
private static XProject currentProject;
private boolean stripSplashArgument;
/**
*
* Creates a new instance of XSystemTrayManager
*/
private XSystemTrayManager()
{
stripSplashArgument = false;
try {
SingleInstanceService singleInstanceService = (SingleInstanceService)ServiceManager.
lookup( "javax.jnlp.SingleInstanceService" );
// add the listener to this application!
singleInstanceService.addSingleInstanceListener( this );
}
catch ( UnavailableServiceException use ) {
DebugLogger.logWarning( "The JNLP singleton service is not available" );
}
if ( trayIcon == null ) {
SystemTray st = SystemTray.getDefaultSystemTray();
trayIcon = new TrayIcon( (Icon)currentProject.getIcon( "xui_icon.png" ), currentProject.getStartupParam( "Title" ));
trayIcon.addActionListener( this );
st.addTrayIcon( trayIcon );
JPopupMenu popup = new JPopupMenu();
JMenuItem defaultItem = new JMenuItem( "Exit" );
defaultItem.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent ae )
{
currentProject.setStatus( XProject.CLOSING );
currentProject.setStartupParam( "ExitOnClose", "true" );
Window window = currentProject.getAppWindow();
if ( window != null )
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ));
window.dispose();
System.gc();
}
});
JMenuItem openItem = new JMenuItem( "Open" );
openItem.addActionListener( this );
popup.add( openItem );
popup.addSeparator();
popup.add( defaultItem );
trayIcon.setPopupMenu( popup );
}
}
public static XSystemTrayManager getInstance( XProject project )
{
currentProject = project;
if ( instance == null )
instance = new XSystemTrayManager();
return instance;
}
/**
* Relaunch the application
*/
public void actionPerformed( ActionEvent e )
{
try {
Frame f = currentProject.getAppFrame();
// Check if the frame is in existance.
if (( f != null ) && f.isDisplayable()) {
f.setState( Frame.NORMAL );
f.toFront();
}
else {
currentProject.setStatus( XProject.RESTARTING );
Class clazz = Class.forName( currentProject.getStartupParam( "MainClass" ).trim());
Method mainMethod = clazz.getMethod( "main", new Class[] { String[].class } );
String[] startupArgs = (String[])currentProject.getObject( "StartupArgs" );
mainMethod.invoke( null, new Object[] { startupArgs });
}
}
catch ( Exception ex ) {
if ( BuildProperties.DEBUG )
ex.printStackTrace();
InternalError error = new InternalError( "Failed to invoke main method" );
error.initCause( ex );
throw error;
}
}
/**
* Specified by the JNLP SingleInstanceListener interface. Called when a new
* application instance is being started
* @param args The command line parameters used for this invocation
*/
public void newActivation( String[] args )
{
/**
* @todo add some way of distinguishing projects/startups
*/
if ( stripSplashArgument ) {
String[] appArgs = new String[ args.length -1 ];
for ( int i = 1; i < args.length; i++ )
appArgs[ i-1 ] = args[ i ];
args = appArgs;
}
currentProject.setObject( "StartupArgs", args );
if ( BuildProperties.DEBUG ) {
for ( int i = 0; i < args.length; i++ )
DebugLogger.trace( "Restart arg " + i + ": " + args[ i ] );
}
actionPerformed( null );
showActivationEvent();
}
/**
* Set a flag to strip the extra argument added when a splash screen is used
* at startup. The XSplashWindow class requires an extra argument of the
* 'main' class proper, but strips this before passing the arguments to that
* class. Setting this flag to true replicates that behavior for the
* startup via the system tray when a second JNLP invocation occurs. This is
* necessary as the new JNLP invocation provides a fresh set of startup
* arguments
* @param state true to strip the first argument
*/
public void setStripSplashArgument( boolean state )
{
stripSplashArgument = state;
}
/**
* Invoke any registered activation event
*/
public void showActivationEvent()
{
XActivation activationObject = (XActivation)currentProject.getObject( "ActivationObject" );
if ( activationObject != null )
activationObject.activate();
}
}