/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.ejs.control;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.TextArea;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
import org.opensourcephysics.controls.Animation;
import org.opensourcephysics.controls.Calculation;
import org.opensourcephysics.controls.ControlUtils;
import org.opensourcephysics.controls.MainFrame;
import org.opensourcephysics.controls.OSPApplication;
import org.opensourcephysics.controls.OSPLog;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.controls.XMLControlElement;
import org.opensourcephysics.controls.XMLTreePanel;
import org.opensourcephysics.display.Dataset;
import org.opensourcephysics.display.DatasetManager;
import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.display.DrawingPanel;
import org.opensourcephysics.display.GUIUtils;
import org.opensourcephysics.display.Hidable;
import org.opensourcephysics.display.OSPFrame;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.display.PrintUtils;
import org.opensourcephysics.ejs.EjsRes;
import org.opensourcephysics.tools.FontSizer;
import org.opensourcephysics.tools.Job;
import org.opensourcephysics.tools.LocalJob;
import org.opensourcephysics.tools.SnapshotTool;
import org.opensourcephysics.tools.Tool;
import org.opensourcephysics.tools.ToolsRes;
import org.opensourcephysics.tools.VideoTool;

/**
 * EjsControlFrame defines an Easy Java Simulations control that is guaranteed to have a
 * parent frame.
 *
 * @author       Wolfgang Christian
 * @version 1.0
 */
public class EjsControlFrame extends ParsedEjsControl implements RootPaneContainer, MainFrame {
  final static int MENU_SHORTCUT_KEY_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
  protected Tool reply;
  protected JMenuItem[] languageItems;
  protected JMenuItem translateItem, snapshotItem, videoItem, clearItem;
  protected JMenu languageMenu;
  EjsFrame mainFrame = new EjsFrame();
  DrawingPanel defaultDrawingPanel;
  protected JFrame messageFrame = new JFrame(EjsRes.getString("EjsControlFrame.Messages_frame_title")); //$NON-NLS-1$
  TextArea messageArea = new TextArea(20, 20);
  Object model;
  JMenuBar menuBar;
  volatile protected XMLControlElement xmlDefault = null;
  protected PropertyChangeSupport support = new SwingPropertyChangeSupport(this);

  /**
   * Constructor EjsControlFrame
   *
   * @param _simulation
   */
  public EjsControlFrame(Object _simulation) {
    this(_simulation, "name=controlFrame;title=Control Frame;location=400,0;layout=border;exit=false; visible=true"); //$NON-NLS-1$
  }

  /**
   * Constructor EjsControlFrame
   *
   * @param _simulation
   * @param param
   */
  public EjsControlFrame(Object _simulation, String param) {
    super(_simulation);
    mainFrame.addChildFrame(messageFrame);
    model = _simulation;
    mainFrame.setName("controlFrame");    //$NON-NLS-1$
    addObject(mainFrame, "Frame", param); //$NON-NLS-1$
    createMenuBar();
    if(OSPRuntime.appletMode) {
      mainFrame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
    }
    messageFrame.getContentPane().add(messageArea);
    messageFrame.setSize(300, 175);
    // responds to data from the DatasetTool
    reply = new Tool() {
      public void send(Job job, Tool replyTo) throws RemoteException {
        if(defaultDrawingPanel==null) {
          return;
        }
        XMLControlElement control = new XMLControlElement();
        try {
          control.readXML(job.getXML());
        } catch(RemoteException ex) {}
        ArrayList<?> datasets = defaultDrawingPanel.getObjectOfClass(Dataset.class);
        Iterator<?> it = control.getObjects(Dataset.class).iterator();
        while(it.hasNext()) {
          Dataset newData = (Dataset) it.next();
          int id = newData.getID();
          for(int i = 0, n = datasets.size(); i<n; i++) {
            if(((Dataset) datasets.get(i)).getID()==id) {
              XMLControl xml = new XMLControlElement(newData);      // convert the source to xml
              Dataset.getLoader().loadObject(xml, datasets.get(i)); // copy the data to the destination
              break;
            }
          }
        }
        defaultDrawingPanel.repaint();
      }

    };
  }

  /**
   * Adds a child frame that depends on the main frame.
   * Child frames are closed when this frame is closed.
   *
   * @param mainFrame JFrame
   */
  public void addChildFrame(JFrame child) {
    if((mainFrame==null)||(child==null)) {
      return;
    }
    mainFrame.addChildFrame(child);
  }

  /**
   * Clears the child frames from the main frame.
   */
  public void clearChildFrames() {
    if(mainFrame==null) {
      return;
    }
    mainFrame.clearChildFrames();
  }

  /**
   * Gets a copy of the ChildFrames collection.
   * @return Collection
   */
  public Collection<JFrame> getChildFrames() {
    return mainFrame.getChildFrames();
  }

  /**
   * Gets the OSPFrame that contains the control.
   *
   * @return
   */
  public OSPFrame getMainFrame() {
    return mainFrame;
  }

  /**
   * Gets the OSPFrame that contains the control.  Replaced by getMainFrame to implement MainFrame interface.
   * @deprecated
   * @return
   */
  public OSPFrame getFrame() {
    return mainFrame;
  }

  private void createMenuBar() {
    menuBar = new JMenuBar();
    mainFrame.setJMenuBar(menuBar);
    JMenu fileMenu = new JMenu(EjsRes.getString("EjsControlFrame.File_menu")); //$NON-NLS-1$
    if(OSPRuntime.applet==null) {
      menuBar.add(fileMenu);
    }
    JMenuItem readItem = new JMenuItem(EjsRes.getString("EjsControlFrame.Read_menu_item")); //$NON-NLS-1$
    clearItem = new JMenuItem(EjsRes.getString("EjsControlFrame.Clear_menu_item")); //$NON-NLS-1$
    clearItem.setEnabled(false);
    JMenuItem saveAsItem = new JMenuItem(EjsRes.getString("EjsControlFrame.SaveAs_menu_item"));                  //$NON-NLS-1$
    JMenuItem inspectItem = new JMenuItem(EjsRes.getString("EjsControlFrame.Inspect_menu_item"));                //$NON-NLS-1$
    JMenuItem printFrameItem = new JMenuItem(DisplayRes.getString("DrawingFrame.PrintFrame_menu_item"));         //$NON-NLS-1$
    JMenuItem saveFrameAsEPSItem = new JMenuItem(DisplayRes.getString("DrawingFrame.SaveFrameAsEPS_menu_item")); //$NON-NLS-1$
    JMenu printMenu = new JMenu(DisplayRes.getString("DrawingFrame.Print_menu_title")); //$NON-NLS-1$
    fileMenu.add(readItem);
    fileMenu.add(saveAsItem);
    fileMenu.add(inspectItem);
    fileMenu.add(clearItem);
    fileMenu.add(printMenu);
    printMenu.add(printFrameItem);
    printMenu.add(saveFrameAsEPSItem);
    readItem.setAccelerator(KeyStroke.getKeyStroke('R', MENU_SHORTCUT_KEY_MASK));
    readItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        // readParameters(); // cannot use a static method here because of run-time binding
        loadXML((String) null);
        support.firePropertyChange("xmlDefault", null, xmlDefault); //$NON-NLS-1$
        mainFrame.repaint();
      }

    });
    clearItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        clearDefaultXML();
      }

    });
    saveAsItem.setAccelerator(KeyStroke.getKeyStroke('S', MENU_SHORTCUT_KEY_MASK));
    saveAsItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        saveXML();
      }

    });
    inspectItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        inspectXML(); // cannot use a static method here because of run-time binding
      }

    });
    // print item
    printFrameItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        PrintUtils.printComponent(mainFrame);
      }

    });
    // save as EPS item
    saveFrameAsEPSItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        try {
          PrintUtils.saveComponentAsEPS(mainFrame);
        } catch(IOException ex) {}
      }

    });
    loadDisplayMenu();
    loadToolsMenu();
    //help menu
    JMenu helpMenu = new JMenu(EjsRes.getString("EjsControlFrame.Help_menu")); //$NON-NLS-1$
    menuBar.add(helpMenu);
    JMenuItem aboutItem = new JMenuItem(EjsRes.getString("EjsControlFrame.About_menu_item")); //$NON-NLS-1$
    aboutItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        OSPRuntime.showAboutDialog(EjsControlFrame.this.getMainFrame());
      }

    });
    helpMenu.add(aboutItem);
    JMenuItem sysItem = new JMenuItem(EjsRes.getString("EjsControlFrame.System_menu_item")); //$NON-NLS-1$
    sysItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        ControlUtils.showSystemProperties(true);
      }

    });
    helpMenu.add(sysItem);
    helpMenu.addSeparator();
    JMenuItem logToFileItem = new JCheckBoxMenuItem(EjsRes.getString("EjsControlFrame.LogToFile_check_box")); //$NON-NLS-1$
    logToFileItem.setSelected(false);
    logToFileItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource();
        if(OSPRuntime.appletMode||(OSPRuntime.applet!=null)) {
          return; // applets cannot log to file
        }
        OSPLog.getOSPLog().setLogToFile(item.isSelected());
      }

    });
    if(OSPRuntime.applet==null) {
      helpMenu.add(logToFileItem);
    }
    JMenuItem logItem = new JMenuItem(EjsRes.getString("EjsControlFrame.MessageLog_menu_item")); //$NON-NLS-1$
    logItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        OSPLog.showLog();
      }

    });
    helpMenu.add(logItem);
    menuBar.add(helpMenu);
  }

  /**
   * Adds a Tools menu to the menu bar.
   */
  protected JMenu loadToolsMenu() {
    JMenuBar menuBar = mainFrame.getJMenuBar();
    if(menuBar==null) {
      return null;
    }
    // create Tools menu item
    JMenu toolsMenu = new JMenu(DisplayRes.getString("DrawingFrame.Tools_menu_title")); //$NON-NLS-1$
    menuBar.add(toolsMenu);
    // create dataset tool menu item if the tool exists in classpath
    JMenuItem datasetItem = new JMenuItem(DisplayRes.getString("DrawingFrame.DatasetTool_menu_item")); //$NON-NLS-1$
    toolsMenu.add(datasetItem);
    Class<?> datasetToolClass = null;
    if(OSPRuntime.loadDataTool) {
      try {
        datasetToolClass = Class.forName("org.opensourcephysics.tools.DataTool");      //$NON-NLS-1$
      } catch(ClassNotFoundException ex) {
        OSPRuntime.loadDataTool = false;
        OSPLog.finest("Cannot instantiate data analysis tool class:\n"+ex.toString()); //$NON-NLS-1$
        datasetItem.setEnabled(false);
      }
    }
    final Class<?> finalDatasetToolClass = datasetToolClass; // class must be final for action listener
    datasetItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        try {
          Method m = finalDatasetToolClass.getMethod("getTool", (Class[]) null); //$NON-NLS-1$
          Tool tool = (Tool) m.invoke(null, (Object[]) null);
          tool.send(new LocalJob(defaultDrawingPanel), reply);
          if(tool instanceof OSPFrame) {
            ((OSPFrame) tool).setKeepHidden(false);
          }
          ((JFrame) tool).setVisible(true);
        } catch(Exception ex) {
          System.out.println(ex);
        }
      }

    });
    // create snapshot menu item
    snapshotItem = new JMenuItem(DisplayRes.getString("DisplayPanel.Snapshot_menu_item")); //$NON-NLS-1$
    snapshotItem.setEnabled(false);
    if(OSPRuntime.applet==null) {
      toolsMenu.add(snapshotItem);
    }
    snapshotItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        SnapshotTool tool = SnapshotTool.getTool();
        if(defaultDrawingPanel!=null) {
          tool.saveImage(null, defaultDrawingPanel);
        } else {
          tool.saveImage(null, getContentPane());
        }
      }

    });
    // create video capture menu item
    JMenuItem videoItem = new JMenuItem(DisplayRes.getString("DrawingFrame.MenuItem.Capture")); //$NON-NLS-1$
    if(OSPRuntime.applet==null) {
      toolsMenu.add(videoItem);
    }
    Class<?> videoToolClass = null;
    if(OSPRuntime.loadVideoTool) {
      try {
        videoToolClass = Class.forName("org.opensourcephysics.tools.VideoCaptureTool"); //$NON-NLS-1$
      } catch(ClassNotFoundException ex) {
        OSPRuntime.loadVideoTool = false;
        OSPLog.finest("Cannot instantiate video capture tool class:\n"+ex.toString());  //$NON-NLS-1$
        videoItem.setEnabled(false);
      }
    }
    final Class<?> finalVideoToolClass = videoToolClass; // class must be final for action listener
    videoItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if(defaultDrawingPanel.getVideoTool()==null) {
          try {
            Method m = finalVideoToolClass.getMethod("getTool", (Class[]) null); //$NON-NLS-1$
            Tool tool = (Tool) m.invoke(null, (Object[]) null);                  // tool is a VideoTool
            defaultDrawingPanel.setVideoTool((VideoTool) tool);
            ((VideoTool) tool).setVisible(true);
            ((VideoTool) tool).clear();
          } catch(Exception ex) {}
        } else {
          defaultDrawingPanel.getVideoTool().setVisible(true);
        }
      }

    });
    return toolsMenu;
  }

  /**
   * Adds a Display menu to the menu bar.
   */
  protected void loadDisplayMenu() {
    JMenu displayMenu = new JMenu();
    displayMenu.setText(EjsRes.getString("EjsControlFrame.Display_menu")); //$NON-NLS-1$
    menuBar.add(displayMenu);
    // language menu
    languageMenu = new JMenu();
    languageMenu.setText(EjsRes.getString("EjsControlFrame.Language")); //$NON-NLS-1$
    translateItem = new JMenuItem();
    translateItem.setText(EjsRes.getString("EjsControlFrame.Translate")); //$NON-NLS-1$
    // added by D Brown 2007-10-17
    if(OSPRuntime.getTranslator()!=null) {
      translateItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          OSPRuntime.getTranslator().showProperties(model.getClass());
          if(OSPRuntime.getTranslator() instanceof Hidable) {
            ((Hidable) OSPRuntime.getTranslator()).setKeepHidden(false);
          }
          OSPRuntime.getTranslator().setVisible(true);
        }

      });
    }
    translateItem.setEnabled(OSPRuntime.getTranslator()!=null);
    languageMenu.add(translateItem, 0);
    final Locale[] locales = OSPRuntime.getInstalledLocales();
    Action languageAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        String language = e.getActionCommand();
        OSPLog.finest("setting language to "+language); //$NON-NLS-1$
        for(int i = 0; i<locales.length; i++) {
          if(language.equals(locales[i].getDisplayName())) {
            ToolsRes.setLocale(locales[i]);
            return;
          }
        }
      }

    };
    ButtonGroup languageGroup = new ButtonGroup();
    languageItems = new JMenuItem[locales.length];
    for(int i = 0; i<locales.length; i++) {
      languageItems[i] = new JRadioButtonMenuItem(locales[i].getDisplayName(locales[i]));
      languageItems[i].setActionCommand(locales[i].getDisplayName());
      languageItems[i].addActionListener(languageAction);
      languageMenu.add(languageItems[i]);
      languageGroup.add(languageItems[i]);
    }
    for(int i = 0; i<locales.length; i++) {
      if(locales[i].getLanguage().equals(ToolsRes.getLanguage())) {
        languageItems[i].setSelected(true);
      }
    }
    displayMenu.add(languageMenu);
    JMenu fontMenu = new JMenu(EjsRes.getString("EjsControlFrame.Font_menu")); //$NON-NLS-1$
    displayMenu.add(fontMenu);
    JMenuItem sizeUpItem = new JMenuItem();
    sizeUpItem.setText(EjsRes.getString("EjsControlFrame.IncreaseFontSize_menu_item")); //$NON-NLS-1$
    sizeUpItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        FontSizer.levelUp();
      }

    });
    fontMenu.add(sizeUpItem);
    final JMenuItem sizeDownItem = new JMenuItem();
    sizeDownItem.setText(EjsRes.getString("EjsControlFrame.DecreaseFontSize_menu_item")); //$NON-NLS-1$
    sizeDownItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        FontSizer.levelDown();
      }

    });
    fontMenu.add(sizeDownItem);
    JMenu aliasMenu = new JMenu(EjsRes.getString("EjsControlFrame.AntiAlias_menu")); //$NON-NLS-1$
    displayMenu.add(aliasMenu);
    final JCheckBoxMenuItem textAliasItem = new JCheckBoxMenuItem(EjsRes.getString("EjsControlFrame.Text_check_box"), //$NON-NLS-1$
      false);
    textAliasItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        defaultDrawingPanel.setAntialiasTextOn(textAliasItem.isSelected());
        defaultDrawingPanel.repaint();
      }

    });
    aliasMenu.add(textAliasItem);
    final JCheckBoxMenuItem shapeAliasItem = new JCheckBoxMenuItem(EjsRes.getString("EjsControlFrame.Drawing_check_box"), false); //$NON-NLS-1$
    shapeAliasItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        defaultDrawingPanel.setAntialiasShapeOn(shapeAliasItem.isSelected());
        defaultDrawingPanel.repaint();
      }

    });
    fontMenu.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        sizeDownItem.setEnabled(FontSizer.getLevel()>0);
      }

    });
    aliasMenu.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        if(defaultDrawingPanel==null) {
          textAliasItem.setEnabled(false);
          shapeAliasItem.setEnabled(false);
        } else {
          textAliasItem.setEnabled(true);
          textAliasItem.setEnabled(true);
          textAliasItem.setSelected(defaultDrawingPanel.isAntialiasTextOn());
          shapeAliasItem.setSelected(defaultDrawingPanel.isAntialiasShapeOn());
        }
      }

    });
    aliasMenu.add(shapeAliasItem);
    menuBar.add(displayMenu);
    ToolsRes.addPropertyChangeListener("locale", new PropertyChangeListener() { //$NON-NLS-1$
      public void propertyChange(PropertyChangeEvent e) {
        refreshGUI();
      }

    });
  }

  /**
   * Draws the frame into a graphics object suitable for printing.
   * @param g
   * @param pageFormat
   * @param pageIndex
   * @return status code
   * @exception PrinterException
   */
  public int printToGraphics(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException {
    if(pageIndex>=1) { // only one page available
      return Printable.NO_SUCH_PAGE;
    }
    if(g==null) {
      return Printable.NO_SUCH_PAGE;
    }
    Graphics2D g2 = (Graphics2D) g;
    double scalex = pageFormat.getImageableWidth()/mainFrame.getWidth();
    double scaley = pageFormat.getImageableHeight()/mainFrame.getHeight();
    double scale = Math.min(scalex, scaley);
    g2.translate((int) pageFormat.getImageableX(), (int) pageFormat.getImageableY());
    g2.scale(scale, scale);
    //frame.paintAll(g2);
    mainFrame.paint(g);
    return Printable.PAGE_EXISTS;
  }

  protected void refreshGUI() {
    createMenuBar();
    mainFrame.pack();
  }

  /**
   * Adds a PropertyChangeListener.
   *
   * @param listener the object requesting property change notification
   */
  public void addPropertyChangeListener(PropertyChangeListener listener) {
    support.addPropertyChangeListener(listener);
  }

  /**
   * Removes a PropertyChangeListener.
   *
   * @param listener the listener requesting removal
   */
  public void removePropertyChangeListener(PropertyChangeListener listener) {
    support.removePropertyChangeListener(listener);
  }

  /**
   * Prints a string in the control's message area followed by a CR and LF.
   * GUI controls will usually display messages in a non-editable text area.
   *
   * @param s
   */
  public void println(String s) {
    print(s+"\n"); //$NON-NLS-1$
  }

  /**
   * Prints a blank line in the control's message area.  GUI controls will usually display
   * messages in a non-editable text area.
   */
  public void println() {
    print("\n"); //$NON-NLS-1$
  }

  /**
   * Prints a string in the control's message area.
   * GUI controls will usually display messages in a non-editable text area.
   *
   * @param s
   */
  public void print(final String s) {
    if(s==null) {
      return; // nothing to print
    }
    messageFrame.setVisible(true);
    if(SwingUtilities.isEventDispatchThread()||Thread.currentThread().getName().equals("main")) { //$NON-NLS-1$
      messageArea.append(s);
      return;
    }
    Runnable doLater = new Runnable() {
      public void run() {
        messageArea.append(s);
      }

    };
    // Update from the event queue.
    java.awt.EventQueue.invokeLater(doLater);
  }

  /**
   *  Remove all text from the message area.
   */
  public void clearMessages() {
    if(SwingUtilities.isEventDispatchThread()||Thread.currentThread().getName().equals("main")) { //$NON-NLS-1$
      messageArea.setText("");                                                                    //$NON-NLS-1$
      return;
    }
    Runnable doLater = new Runnable() {
      public void run() {
        messageArea.setText(""); //$NON-NLS-1$
      }

    };
    // Update from the event queue.
    java.awt.EventQueue.invokeLater(doLater);
  }

  //  private void appendMessageX(final String str) {
  //    Runnable doRun = new Runnable() {
  //      public void run() {
  //        messageArea.append(str);
  //      }
  //    };
  //    if(SwingUtilities.isEventDispatchThread()) {
  //      doRun.run();
  //      }else{
  //      SwingUtilities.invokeLater(doRun);
  //      }
  //  }
  //

  /**
   * Stops the animation and prints a message.
   * @param message String
   */
  public void calculationDone(String message) {
    if((message==null)||message.trim().equals("")) { //$NON-NLS-1$
      return;
    }
    super.calculationDone(message);
  }

  /**
   * Renders the frame.  Subclass this method to render the contents of this frame in the calling thread.
   */
  public void render() {}

  /**
   * Adds an existing object to this control.
   * @param object Object
   * @param classname String
   * @param propList String
   * @return ControlElement
   */
  public ControlElement addObject(Object object, String classname, String propList) {
    if(object instanceof DrawingPanel) {
      defaultDrawingPanel = (DrawingPanel) object;
      if(snapshotItem!=null) {
        snapshotItem.setEnabled(true);
      }
      if(videoItem!=null) {
        videoItem.setEnabled(true);
      }
    }
    return super.addObject(object, classname, propList);
  }

  /**
   * Clears data from drawing objects within this frame.
   *
   * Override this method to clear objects that have data.
   */
  public void clearData() {
    if(defaultDrawingPanel!=null) {
      ArrayList<?> list = defaultDrawingPanel.getDrawables(Dataset.class);
      Iterator<?> it = list.iterator();
      while(it.hasNext()) { // copy only the objects of the correct type
        Dataset obj = (Dataset) it.next();
        obj.clear();
      }
      list = defaultDrawingPanel.getDrawables(DatasetManager.class);
      it = list.iterator();
      while(it.hasNext()) { // copy only the objects of the correct type
        DatasetManager obj = (DatasetManager) it.next();
        obj.clear();
      }
      defaultDrawingPanel.invalidateImage();
    }
  }

  /**
   * Clears data from drawing objects within this frame.
   *
   * Override this method to clear and repaint objects that have data.
   */
  public void clearDataAndRepaint() {
    clearData();
    if(defaultDrawingPanel!=null) {
      defaultDrawingPanel.repaint();
    }
  }

  /**
   * Gets the frame that contains the control.
   *
   * @return
   */
  public Container getTopLevelAncestor() {
    return mainFrame;
  }

  /**
   * Gets the frame's root pane.  Implementation of RootPaneContainer.
   *
   * @return
   */
  public JRootPane getRootPane() {
    return mainFrame.getRootPane();
  }

  /**
   * Gets the frame's content pane. Implementation of RootPaneContainer.
   *
   * @return content pane of the frame
   */
  public Container getContentPane() {
    return mainFrame.getContentPane();
  }

  /**
   * Sets the frame's content pane. Implementation of RootPaneContainer.
   * @param contentPane
   */
  public void setContentPane(Container contentPane) {
    mainFrame.setContentPane(contentPane);
  }

  /**
   * Implementation of RootPaneContainer.
   *
   * @see javax.swing.RootPaneContainer
   *
   * @return layeredPane of the frame
   */
  public JLayeredPane getLayeredPane() {
    return mainFrame.getLayeredPane();
  }

  /**
   * Implementation of RootPaneContainer.
   *
   * @see javax.swing.RootPaneContainer
   * @param layeredPane
   */
  public void setLayeredPane(JLayeredPane layeredPane) {
    mainFrame.setLayeredPane(layeredPane);
  }

  /**
   * Implementation of RootPaneContainer.
   *
   * @see javax.swing.RootPaneContainer
   *
   * @return glass pane component
   */
  public Component getGlassPane() {
    return mainFrame.getGlassPane();
  }

  /**
   * Implementation of RootPaneContainer.
   *
   * @see javax.swing.RootPaneContainer
   * @param glassPane
   */
  public void setGlassPane(Component glassPane) {
    mainFrame.setGlassPane(glassPane);
  }

  /*
   * Creates a menu in the menu bar from the given XML document.
   * @param xmlMenu name of the xml file with menu data
   */
  public void parseXMLMenu(String xmlMenu) {
    System.out.println("The parseXMLMenu method has been disabled to reduce the size OSP jar files."); //$NON-NLS-1$
    //parseXMLMenu(xmlMenu, null);
  }

  /*
   *  public void parseXMLMenu(String xmlMenu) {
   *     if(menuBar==null) {
   *        return;
   *     }
   *     XMLControl xml = new XMLControlElement(xmlMenu);
   *     if(xml.failedToRead()) {
   *        OSPLog.info("Tools menu not found: "+xmlMenu);   //$NON-NLS-1$
   *     } else {
   *        Class type = xml.getObjectClass();
   *        if((type!=null)&&org.opensourcephysics.tools.LaunchNode.class.isAssignableFrom(type)) {
   *           // load the xml data into a launch node and add the menu item
   *           org.opensourcephysics.tools.LaunchNode node = (org.opensourcephysics.tools.LaunchNode) xml.loadObject(null);
   *           // get the menu name and find or create the menu
   *           String menuName = node.toString();
   *           JMenu menu = null;
   *           for(int i = 0; i<menuBar.getMenuCount(); i++) {
   *              JMenu next = menuBar.getMenu(i);
   *              if(next.getText().equals(menuName)) {
   *                 menu = next;
   *                 break;
   *              }
   *           }
   *           if(menu==null) {
   *              menu = new JMenu(menuName);
   *              menuBar.add(menu);
   *              menuBar.validate();
   *           }
   *           // add the node item to the menu
   *           node.setLaunchObject(model);
   *           node.addMenuItemsTo(menu);
   *           OSPLog.finest("Tools menu loaded: "+xmlMenu); //$NON-NLS-1$
   *        }
   *     }
   *  }
   */
  // The following methods the an XML framework for OSPApplications.
  protected OSPApplication app;

  public void saveXML() {
    JFileChooser chooser = OSPRuntime.getChooser();
    int result = chooser.showSaveDialog(null);
    if(result==JFileChooser.APPROVE_OPTION) {
      File file = chooser.getSelectedFile();
      // check to see if file already exists
      if(file.exists()) {
        int selected = JOptionPane.showConfirmDialog(null, EjsRes.getString("EjsControlFrame.ReplaceExisting_dialog")+file.getName() //$NON-NLS-1$
          +EjsRes.getString("EjsControlFrame.question_mark"), EjsRes.getString( //$NON-NLS-1$
          "EjsControlFrame.RepalceFile_dialog_message"),                        //$NON-NLS-1$
            JOptionPane.YES_NO_CANCEL_OPTION);
        if(selected!=JOptionPane.YES_OPTION) {
          return;
        }
      }
      OSPRuntime.chooserDir = chooser.getCurrentDirectory().toString();
      String fileName = XML.getRelativePath(file.getAbsolutePath());
      if((fileName==null)||fileName.trim().equals("")) {                        //$NON-NLS-1$
        return;
      }
      int i = fileName.toLowerCase().lastIndexOf(".xml");                       //$NON-NLS-1$
      if(i!=fileName.length()-4) {
        fileName += ".xml";                                                     //$NON-NLS-1$
      }
      XMLControl xml = new XMLControlElement(getOSPApp());
      xml.write(fileName);
    }
  }

  /**
   * Gets the OSP Application controlled by this frame.
   */
  public OSPApplication getOSPApp() {
    if(app==null) {
      app = new OSPApplication(this, model);
    }
    return app;
  }

  /**
   * Loads the current XML default.
   */
  public void loadDefaultXML() {
    if(xmlDefault!=null) {
      xmlDefault.loadObject(getOSPApp());
      clearItem.setEnabled(true);
    }
  }

  /**
   * Clears the current XML default.
   */
  public void clearDefaultXML() {
    if((xmlDefault==null)||(model==null)) {
      return;
    }
    xmlDefault = null;
    clearItem.setEnabled(false);
    if(model instanceof Calculation) {
      ((Calculation) model).resetCalculation();
      ((Calculation) model).calculate();
    } else if(model instanceof Animation) {
      ((Animation) model).stopAnimation();
      ((Animation) model).resetAnimation();
      ((Animation) model).initializeAnimation();
    }
    GUIUtils.repaintOSPFrames();
  }

  public void loadXML(String fileName) {
    if((fileName==null)||fileName.trim().equals("")) { //$NON-NLS-1$
      loadXML();
      return;
    }
    XMLControlElement xml = null;
    try {
      xml = new XMLControlElement(fileName);
    } catch(Exception ex) {
      System.out.println("XML file not loaded: "+fileName); //$NON-NLS-1$
      System.out.println("EjsControlFrame Exception: "+ex); //$NON-NLS-1$
      //ex.printStackTrace();
      return;
    }
    loadXML(xml, false);
  }

  public void loadXML(XMLControlElement xml, boolean compatibleModel) {
    if(xml==null) {
      OSPLog.finer("XML not found in EjsControlFrame loadXML method."); //$NON-NLS-1$
      return;
    }
    // load xml into app if xml object class is an OSPApplication
    if(OSPApplication.class.isAssignableFrom(xml.getObjectClass())) {
      XMLControlElement mControl = (XMLControlElement) xml.getChildControl("model");   //$NON-NLS-1$
      if(mControl==null) {                                                             // control class cannot be found
        JOptionPane.showMessageDialog(mainFrame, "XML Control not found.",             //$NON-NLS-1$
          "Data not loaded.", JOptionPane.WARNING_MESSAGE);                            //$NON-NLS-1$
        return;
      }
      Class<?> modelClass = mControl.getObjectClass();
      if(modelClass==null) {                                                           // model class cannot be found in classpath
        JOptionPane.showMessageDialog(mainFrame, "Model specified in file not found.", //$NON-NLS-1$
          "Data not loaded.", JOptionPane.WARNING_MESSAGE);                            //$NON-NLS-1$
        return;
      }
      //System.out.println("model in xml " +modelClass);
      //System.out.println("this model " +model.getClass());
      boolean loaderMatch = XML.getLoader(modelClass).getClass()==XML.getLoader(model.getClass()).getClass();
      compatibleModel = compatibleModel||(modelClass==model.getClass())||              // identical model classes are always compatible
        (modelClass.isAssignableFrom(model.getClass())&&loaderMatch); // subclasses with identical loaders are assumed to be compatible
      //System.out.println("xml model Assignable to this model " +modelClass.isAssignableFrom(model.getClass()));
      //System.out.println("this model Assignable to xml model " +model.getClass().isAssignableFrom(modelClass));
      if(!compatibleModel) {
        JOptionPane.showMessageDialog(mainFrame, "Data not loaded. Data was created by: "+modelClass+".", //$NON-NLS-1$ //$NON-NLS-2$
          "Incompatible data file.", JOptionPane.WARNING_MESSAGE);                    //$NON-NLS-1$
        return;
      }
      app = getOSPApp();
      app.setCompatibleModel(compatibleModel);
      xml.loadObject(getOSPApp());
      app.setCompatibleModel(false);
      if(model.getClass()==app.getLoadedModelClass()) {                               // classes match so we have a new default
        xmlDefault = xml;
        clearItem.setEnabled(true);
      } else if(app.getLoadedModelClass()!=null) {                                    // a model was imported; create xml from current model
        xmlDefault = new XMLControlElement(getOSPApp());
        clearItem.setEnabled(true);
      } else {                                                                        // models are not compatible
        xmlDefault = null;
        clearItem.setEnabled(false);
      }
    } else {
      JOptionPane.showMessageDialog(mainFrame, "Data for: "+xml.getObjectClass()+".", //$NON-NLS-1$ //$NON-NLS-2$
        "OSP Application data not found.", JOptionPane.WARNING_MESSAGE);              //$NON-NLS-1$
    }
  }

  public void loadXML() {
    JFileChooser chooser = OSPRuntime.getChooser();
    int result = chooser.showOpenDialog(null);
    if(result==JFileChooser.APPROVE_OPTION) {
      String fileName = chooser.getSelectedFile().getAbsolutePath();
      loadXML(XML.getRelativePath(fileName));
    }
  }

  public void inspectXML() {
    // display a TreePanel in a modal dialog
    XMLControl xml = new XMLControlElement(getOSPApp());
    XMLTreePanel treePanel = new XMLTreePanel(xml);
    JDialog dialog = new JDialog((java.awt.Frame) null, true);
    dialog.setContentPane(treePanel);
    dialog.setSize(new Dimension(600, 300));
    dialog.setVisible(true);
  }

  public void loadXML(String[] args) {
    if(args!=null) {
      for(int i = 0; i<args.length; i++) {
        loadXML(args[i]);
      }
    }
  }

  class EjsFrame extends OSPFrame implements MainFrame {
    public OSPFrame getMainFrame() {
      return this;
    }

    public void render() {
      EjsControlFrame.this.render();
    }

    public void clearData() {
      EjsControlFrame.this.clearData();
    }

    public void clearDataAndRepaint() {
      EjsControlFrame.this.clearDataAndRepaint();
    }

    public void dispose() {
      messageFrame.setVisible(false);
      messageFrame.dispose();
      super.dispose();
    }

    /**
     * Invalidates image buffers if a drawing panel is buffered.
     */
    public void invalidateImage() {
      if(defaultDrawingPanel!=null) {
        defaultDrawingPanel.invalidateImage();
      }
    }

    public OSPApplication getOSPApp() {
      return EjsControlFrame.this.getOSPApp();
    }

  }

}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.
 *
 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
