package org.autoplot; import java.awt.Component; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Logger; import javax.swing.Action; import javax.swing.JFrame; import javax.swing.JOptionPane; import org.das2.util.LoggerManager; /** * Count the number of open applications and call exit when there are zero. * * @author jbf */ public class AppManager { private static Logger logger= LoggerManager.getLogger("autoplot.appmanager"); private static AppManager instance; public synchronized static AppManager getInstance() { if ( instance==null ) { instance= new AppManager(); } return instance; } List apps= new ArrayList(); public void addApplication( Object app ) { this.apps.add(app); if ( app instanceof JFrame ) { ((JFrame)app).setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE ); } } /** * return true if the application is registered. This was introduced * so that ScriptContext could check that it hasn't been closed. (This * listens to window closing events.) * @param app * @return true if the app is still registered. */ public synchronized boolean isRunningApplication( Object app ) { for ( Object o: apps ) { if ( o==app ) { return true; } } return false; } public void closeApplication( Object app ) { if ( app instanceof AutoplotUI ) { // there's a bug here--we need to associate just with autoplot app. boolean resetMain= false; if ( ScriptContext.getViewWindow()==null ) { resetMain= true; } if ( requestClose(app) ) { this.appCloseCallbacks.remove(app); this.apps.remove(app); if ( this.apps.isEmpty() ) { quit(); } } if ( resetMain ) { for ( Object o: this.apps ) { if ( o instanceof AutoplotUI ) { ScriptContext.setView((AutoplotUI)o); //TODO: if there are running apps, this will cause problems... ScriptContext.setApplicationModel(((AutoplotUI)o).applicationModel); } } } } else { //TODO: why are there two branches? These should be nothing Autoplot-specific in here. this.apps.remove(app); if ( this.apps.size()==1 && this.apps.get(0) instanceof JFrame && ( (JFrame)this.apps.get(0) ).isVisible()==false ) { // when the PngWalkTool is started first, AutoplotUI is also started but hidden. this.apps.clear(); } if ( this.apps.isEmpty() ) { quit(); } } } public Object getApplication( int i ) { return this.apps.get(i); } public void quit( ) { System.exit(0); //TODO: findbugs DM_EXIT--and I wonder what happens when Autoplot is used on a Tomcat web server? Otherwise this is appropriate for swing apps. } /** * quit with the exit status. By convention, 0 means okay, and non-0 means something wrong. * @param status */ public void quit( int status ) { System.exit(status); //TODO: findbugs DM_EXIT--and I wonder what happens when Autoplot is used on a Tomcat web server? Otherwise this is appropriate for swing apps. } /** * returns true if quit can be called, exiting the program. If the callback throws an exception, then a warning is displayed. I expect * this will often occur in scripts. * @return */ public boolean requestQuit() { boolean okay= true; for ( Entry> closeCallbacks : appCloseCallbacks.entrySet() ) { Object app= closeCallbacks.getKey(); okay= okay && requestClose(app); } return okay; } /** * returns true if close can be called, exiting the program. If the callback throws an exception, then a warning is displayed. I expect * this will often occur in scripts. * @param app the app that is closing. * @return true if the callback okays the close. */ public boolean requestClose( Object app ) { boolean okay= true; Map closeCallbacks= appCloseCallbacks.get(app); if ( closeCallbacks==null ) return true; for ( Entry ent: closeCallbacks.entrySet() ) { try { if ( app instanceof Frame ) GuiSupport.raiseApplicationWindow( (Frame)app ); okay= okay && ent.getValue().checkClose(); } catch ( Exception e ) { Object parent = this.apps.size()>0 ? this.apps.get(0) : null; if ( ! ( parent instanceof Component ) ) { parent=null; } JOptionPane.showMessageDialog( (Component)parent, String.format( "Unable to call closeCallback id=\"%s\",
because of exception:
%s", ent.getKey(), e ) ); } } return okay; } public WindowListener getWindowListener( final Object app, final Action closeAction ) { return new WindowListener() { @Override public void windowOpened(WindowEvent e) { } @Override public void windowClosing(WindowEvent e) { boolean okay= requestClose(app); if ( !okay ) { return; } else { appCloseCallbacks.remove(app); // kludge, otherwise this is called twice. } if ( closeAction!=null ) { closeAction.actionPerformed( new ActionEvent(this,e.getID(),"close") ); } e.getWindow().dispose(); } @Override public void windowClosed(WindowEvent e) { closeApplication(app); } @Override public void windowIconified(WindowEvent e) { } @Override public void windowDeiconified(WindowEvent e) { } @Override public void windowActivated(WindowEvent e) { } @Override public void windowDeactivated(WindowEvent e) { } }; } public WindowListener getWindowListener( final Object app ) { return getWindowListener(app,null); } /** * return the number of running applications this AppManager is managing. * @return */ public int getApplicationCount() { return apps.size(); } /** * anything that wants an opportunity to cancel the close request should register here. */ public interface CloseCallback { /** * return true if the callback okays the close. * @return true if the callback okays the close. */ boolean checkClose(); } HashMap> appCloseCallbacks= new LinkedHashMap(); //HashMap appCloseCallbacks= new LinkedHashMap(); /** * add a close callback which can prevent a close. The callback * can open a dialog requesting that the user save a file, for example. * @param app to associate the callback. * @param id * @param c */ public synchronized void addCloseCallback( Object app, String id, CloseCallback c ) { Map appCallbacks= this.appCloseCallbacks.get(id); if ( appCallbacks==null ) { appCallbacks= new LinkedHashMap(); } appCallbacks.put(id,c); this.appCloseCallbacks.put( app, appCallbacks ); } }