/*
 * 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.display;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.opensourcephysics.controls.OSPLog;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.controls.XMLControlElement;
import org.opensourcephysics.controls.XMLProperty;
import org.opensourcephysics.controls.XMLTreeChooser;
import org.opensourcephysics.controls.XMLTreePanel;
import org.opensourcephysics.display.axes.DrawableAxes;
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.VideoTool;

/**
 *  Drawing Frame: a frame that contains a drawing panel.
 *
 * @author     Wolfgang Christian
 * @created    July 16, 2004
 * @version    1.1
 */
public class DrawingFrame extends OSPFrame implements ClipboardOwner {
  // protected static String resourcesPath = "/org/opensourcephysics/resources/display/";
  // protected static String defaultToolsFileName = "drawing_tools.xml";
  protected JMenu fileMenu, editMenu;
  protected JMenuItem copyItem, pasteItem, replaceItem;
  protected DrawingPanel drawingPanel;
  protected final static int MENU_SHORTCUT_KEY_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
  protected Window customInspector; // optional custom inspector for this frame
  protected Tool reply;

  /**
   * DrawingFrame constructor that creates a default DrawingPanel.
   *
   * The default DrawingPanel is an InteractivePanel.
   */
  public DrawingFrame() {
    this(DisplayRes.getString("DrawingFrame.DefaultTitle"), new InteractivePanel()); //$NON-NLS-1$
  }

  /**
   *  DrawingFrame constructor specifying the DrawingPanel that will be placed
   *  in the center of the content pane.
   *
   * @param  drawingPanel
   */
  public DrawingFrame(DrawingPanel drawingPanel) {
    this(DisplayRes.getString("DrawingFrame.DefaultTitle"), drawingPanel); //$NON-NLS-1$
  }

  /**
   *  DrawingFrame constructor specifying the title and the DrawingPanel that
   *  will be placed in the center of the content pane.
   *
   * @param  title
   * @param  _drawingPanel
   */
  public DrawingFrame(String title, DrawingPanel _drawingPanel) {
    super(title);
    // forces dependence on the DatasetTool class;
    // add or remove the next line if you do not want the DatasetTool in your jar
    // org.opensourcephysics.tools.DataTool.loadClass = true;
    drawingPanel = _drawingPanel;
    if(drawingPanel!=null) {
      getContentPane().add(drawingPanel, BorderLayout.CENTER);
    }
    getContentPane().add(buttonPanel, BorderLayout.SOUTH); // buttons are added using addButton method.
    pack();
    if(!OSPRuntime.appletMode) {
      createMenuBar();
    }
    // responds to data from the Data Tool
    reply = new Tool() {
      public void send(Job job, Tool replyTo) throws RemoteException {
        XMLControlElement control = new XMLControlElement();
        try {
          control.readXML(job.getXML());
        } catch(RemoteException ex) {}
        ArrayList<?> datasets = drawingPanel.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;
            }
          }
        }
        drawingPanel.repaint();
      }

    };
  }

  /**
   * Renders the drawing panel if the frame is showing and not iconified.
   */
  public void render() {
    if(isIconified()||!isShowing()) {
      return;
    }
    if(drawingPanel!=null) {
      drawingPanel.render();
    } else {
      repaint();
    }
  }

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

  /**
   *  Gets the drawing panel.
   *
   * @return    the drawingPanel
   */
  public DrawingPanel getDrawingPanel() {
    return drawingPanel;
  }

  /**
   *  Sets the label for the X (horizontal) axis.
   *
   * @param  label  the label
   */
  public void setXLabel(String label) {
    if(drawingPanel instanceof PlottingPanel) {
      ((PlottingPanel) drawingPanel).setXLabel(label);
    }
  }

  /**
   *  Sets the label for the Y (vertical) axis.
   *
   * @param  label  the label
   */
  public void setYLabel(String label) {
    if(drawingPanel instanceof PlottingPanel) {
      ((PlottingPanel) drawingPanel).setYLabel(label);
    }
  }

  /**
   *  Converts to polar coordinates.
   *
   * @param plotTitle String
   * @param deltaR double
   */
  public void setPolar(String plotTitle, double deltaR) {
    if(drawingPanel instanceof PlottingPanel) {
      ((PlottingPanel) drawingPanel).setPolar(plotTitle, deltaR);
    }
  }

  /**
   *  Converts to cartesian coordinates.
   *
   *
   * @param xLabel String
   * @param yLabel String
   * @param plotTitle String
   */
  public void setCartesian(String xLabel, String yLabel, String plotTitle) {
    if(drawingPanel instanceof PlottingPanel) {
      ((PlottingPanel) drawingPanel).setCartesian(xLabel, yLabel, plotTitle);
    }
  }

  /**
   * Limits the xmin and xmax values during autoscaling so that the mininimum value
   * will be no greater than the floor and the maximum value will be no
   * smaller than the ceil.
   *
   * Setting a floor or ceil value to <code>Double.NaN<\code> will disable that limit.
   *
   * @param floor the xfloor value
   * @param ceil the xceil value
   */
  public void limitAutoscaleX(double floor, double ceil) {
    drawingPanel.limitAutoscaleX(floor, ceil);
  }

  /**
   * Limits ymin and ymax values during autoscaling so that the mininimum value
   * will be no greater than the floor and the maximum value will be no
   * smaller than the ceil.
   *
   * Setting a floor or ceil value to <code>Double.NaN<\code> will disable that limit.
   *
   * @param floor the yfloor value
   * @param ceil the yceil value
   */
  public void limitAutoscaleY(double floor, double ceil) {
    drawingPanel.limitAutoscaleY(floor, ceil);
  }

  /**
   * Autoscale the drawing panel's x axis using min and max values.
   * from measurable objects.
   * @param autoscale
   */
  public void setAutoscaleX(boolean autoscale) {
    if(drawingPanel!=null) {
      drawingPanel.setAutoscaleX(autoscale);
    }
  }

  /**
   * Determines if the panel's x axis autoscale property is true.
   * @return <code>true<\code> if autoscaled.
   */
  public boolean isAutoscaleX() {
    if(drawingPanel!=null) {
      return drawingPanel.isAutoscaleX();
    }
    return false;
  }

  /**
   * Autoscale the y axis using min and max values.
   * from measurable objects.
   * @param autoscale
   */
  public void setAutoscaleY(boolean autoscale) {
    if(drawingPanel!=null) {
      drawingPanel.setAutoscaleY(autoscale);
    }
  }

  /**
   * Determines if the y axis autoscale property is true.
   * @return <code>true<\code> if autoscaled.
   */
  public boolean isAutoscaleY() {
    if(drawingPanel!=null) {
      return drawingPanel.isAutoscaleY();
    }
    return false;
  }

  /**
   * Sets the aspect ratio for horizontal to vertical to unity when <code>true<\code>.
   * @param isSquare boolean
   */
  public void setSquareAspect(boolean isSquare) {
    if(drawingPanel!=null) {
      drawingPanel.setSquareAspect(isSquare);
    }
  }

  /**
   * Sets Cartesian axes to log scale.
   *
   * @param  logX
   * @param  logY
   */
  public void setLogScale(boolean logX, boolean logY) {
    if((drawingPanel!=null)&&(drawingPanel instanceof PlottingPanel)) {
      ((PlottingPanel) drawingPanel).setLogScale(logX, logY);
    }
  }

  /**
   * Sets the scale using pixels per unit.
   *
   * @param enable boolean enable fixed pixels per unit
   * @param xPixPerUnit double
   * @param yPixPerUnit double
   */
  public void setPixelsPerUnit(boolean enable, double xPixPerUnit, double yPixPerUnit) {
    drawingPanel.setPixelsPerUnit(enable, xPixPerUnit, yPixPerUnit);
  }

  /**
   * Sets the drawing panel's preferred scale.
   * @param xmin
   * @param xmax
   * @param ymin
   * @param ymax
   */
  public void setPreferredMinMax(double xmin, double xmax, double ymin, double ymax) {
    if(drawingPanel!=null) {
      drawingPanel.setPreferredMinMax(xmin, xmax, ymin, ymax);
    }
  }

  /**
   * Sets the drawing panel's preferred scale in the vertical direction.
   * @param ymin
   * @param ymax
   */
  public void setPreferredMinMaxY(double ymin, double ymax) {
    if(drawingPanel!=null) {
      drawingPanel.setPreferredMinMaxY(ymin, ymax);
    }
  }

  /**
   * Sets the drawing panel's preferred scale in the horizontal direction.
   * @param xmin the minimum value
   * @param xmax the maximum value
   */
  public void setPreferredMinMaxX(double xmin, double xmax) {
    if(drawingPanel!=null) {
      drawingPanel.setPreferredMinMaxX(xmin, xmax);
    }
  }

  /**
   * Clears data and repaints the drawing panel within this frame.
   */
  public void clearDataAndRepaint() {
    clearData();
    drawingPanel.repaint();
  }

  /**
   * Clears Drawable objects added by the user from this frame.
   */
  public void clearDrawables() {
    drawingPanel.clear(); // removes all drawables
  }

  /**
   * Adds a drawable object to the frame's drawing panel.
   * @param drawable
   */
  public synchronized void addDrawable(Drawable drawable) {
    if(drawingPanel!=null) {
      drawingPanel.addDrawable(drawable);
    }
  }

  /**
   * Replaces a Drawable object with another Drawable.
   *
   * @param oldDrawable Drawable
   * @param newDrawable Drawable
   */
  public synchronized void replaceDrawable(Drawable oldDrawable, Drawable newDrawable) {
    if(drawingPanel!=null) {
      drawingPanel.replaceDrawable(oldDrawable, newDrawable);
    }
  }

  /**
   * Removes a drawable object to the frame's drawing panel.
   * @param drawable
   */
  public synchronized void removeDrawable(Drawable drawable) {
    if(drawingPanel!=null) {
      drawingPanel.removeDrawable(drawable);
    }
  }

  /**
   * Shows a message in a yellow text box in the lower right hand corner.
   *
   * @param msg
   */
  public void setMessage(String msg) {
    drawingPanel.setMessage(msg);
  }

  /**
   * Shows a message in a yellow text box at the given location.
   *
   * location 0=bottom left
   * location 1=bottom right
   * location 2=top right
   * location 3=top left
   *
   * @param msg
   * @param location
   */
  public void setMessage(String msg, int location) {
    drawingPanel.setMessage(msg, location);
  }

  /**
   * Gets objects of a specific class from the drawing panel.
   *
   * Assignable subclasses are NOT returned.  Interfaces CANNOT be specified.
   * The same objects will be in the drawable list and the cloned list.
   *
   * @param c the class of the object
   *
   * @return the list
   */
  public synchronized <T extends Drawable> ArrayList<T> getObjectOfClass(Class<T> c) {
    if(drawingPanel!=null) {
      return drawingPanel.getObjectOfClass(c);
    }
    return null;
  }

  /**
   * Gets Drawable previously objects added by the user.
   *
   * @return the list
   */
  public synchronized ArrayList<Drawable> getDrawables() {
    if(drawingPanel!=null) {
      return drawingPanel.getDrawables();
    }
    return new ArrayList<Drawable>(); // return an empty list
  }

  public DrawableAxes getAxes() {
    if(drawingPanel instanceof PlottingPanel) {
      return((PlottingPanel) drawingPanel).getAxes();
    }
    return null;
  }

  /**
   * Gets Drawable objects added by the user of an assignable type. The list contains
   * objects that are assignable from the class or interface.
   *
   * @param c the type of Drawable object
   *
   * @return the cloned list
   *
   * @see #getObjectOfClass(Class c)
   */
  public synchronized <T extends Drawable> ArrayList<T> getDrawables(Class<T> c) {
    if(drawingPanel!=null) {
      return drawingPanel.getDrawables(c);
    }
    return new ArrayList<T>(); // return an empty list
  }

  /**
   * Removes all objects of the given class from the drawable list.
   *
   * Assignable subclasses are NOT removed.  Interfaces CANNOT be specified.
   *
   * @param c the class
   */
  public synchronized <T extends Drawable> void removeObjectsOfClass(Class<T> c) {
    drawingPanel.removeObjectsOfClass(c);
  }

  /**
   * Sets the interactive mouse handler if the drawing panel is an interactive panel.
   *
   * Throws an invalid cast exception if the panel is not of the correct type.
   *
   * @param handler the mouse handler
   */
  public void setInteractiveMouseHandler(InteractiveMouseHandler handler) {
    ((InteractivePanel) drawingPanel).setInteractiveMouseHandler(handler);
  }

  /**
   *  Adds the drawing panel to the the frame. The panel is added to the center
   *  of the frame's content pane.
   *
   * @param  _drawingPanel
   */
  public void setDrawingPanel(DrawingPanel _drawingPanel) {
    if(drawingPanel!=null) { // remove the old drawing panel.
      getContentPane().remove(drawingPanel);
    }
    drawingPanel = _drawingPanel;
    if(drawingPanel!=null) {
      getContentPane().add(drawingPanel, BorderLayout.CENTER);
    }
  }

  /**
   * Sets the interior background color for the current drawing panel.
   * The interior of a PlottingaPanel is the area inside the axes where is displayed.
   * The interior of a DrawingPanel is the entire panel.
   */
  public void setInteriorBackground(Color color) {
    if(drawingPanel instanceof PlottingPanel) {
      ((PlottingPanel) drawingPanel).getAxes().setInteriorBackground(color);
    } else {
      drawingPanel.setBackground(color);
    }
  }

  /**
   *  This is a hack to fix a bug when the reload button is pressed in browsers
   *  running JDK 1.4.
   *
   * @param  g
   */
  public void paint(Graphics g) {
    if(!OSPRuntime.appletMode) {
      super.paint(g);
      return;
    }
    try {
      super.paint(g);
    } catch(Exception ex) {
      System.err.println("OSPFrame paint error: "+ex.toString()); //$NON-NLS-1$
      System.err.println("Title: "+this.getTitle());              //$NON-NLS-1$
    }
  }

  /**
   * Enables the paste edit menu item.
   * @param enable boolean
   */
  public void setEnabledPaste(boolean enable) {
    pasteItem.setEnabled(enable);
  }

  /**
   * Pastes drawables found in the specified xml control.
   *
   * @param control the xml control
   */
  protected void pasteAction(XMLControlElement control) {
    // get Drawables using an xml tree chooser
    XMLTreeChooser chooser = new XMLTreeChooser(DisplayRes.getString("DrawingFrame.SelectDrawables_chooser_title"), DisplayRes.getString("DrawingFrame.SelectDrawables_chooser_message"), this); //$NON-NLS-1$ //$NON-NLS-2$
    java.util.List<XMLProperty> props = chooser.choose(control, Drawable.class);
    if(!props.isEmpty()) {
      Iterator<XMLProperty> it = props.iterator();
      while(it.hasNext()) {
        XMLControl prop = (XMLControl) it.next();
        Drawable drawable = (Drawable) prop.loadObject(null);
        addDrawable(drawable);
      }
    }
    drawingPanel.repaint();
  }

  /**
   * Enables the replace edit menu item.
   * @param enable boolean
   */
  public void setEnabledReplace(boolean enable) {
    replaceItem.setEnabled(enable);
  }

  /**
   * Replaces the drawables with the drawables found in the specified XML control.
   * @param control XMLControlElement
   */
  public void replaceAction(XMLControlElement control) {
    clearDrawables();
    pasteAction(control);
  }

  /**
   * Copies objects found in the specified xml control.
   *
   * @param control the xml control
   */
  protected void copyAction(XMLControlElement control) {
    StringSelection data = new StringSelection(control.toXML());
    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    clipboard.setContents(data, this);
  }

  /**
   * Implementation of ClipboardOwner interface.
   *
   * Override this method to receive notification that data copied to the clipboard has changed.
   *
   * @param clipboard Clipboard
   * @param contents Transferable
   */
  public void lostOwnership(Clipboard clipboard, Transferable contents) {}

  /**
   * Enables the copy edit menu item.
   * @param enable boolean
   */
  public void setEnabledCopy(boolean enable) {
    copyItem.setEnabled(enable);
  }

  protected void refreshGUI() {
    createMenuBar();
    addMenuItems();
    pack();
  }

  /**
   * Adds Views menu items on the menu bar.
   * Override this method to add custom menu items.
   */
  protected void addMenuItems() {}

  /**
   * Creates a standard DrawingFrame menu bar and adds it to the frame.
   */
  private void createMenuBar() {
    JMenuBar menuBar = new JMenuBar();
    fileMenu = new JMenu(DisplayRes.getString("DrawingFrame.File_menu_item")); //$NON-NLS-1$
    JMenu printMenu = new JMenu(DisplayRes.getString("DrawingFrame.Print_menu_title"));        //$NON-NLS-1$
    JMenuItem printItem = new JMenuItem(DisplayRes.getString("DrawingFrame.Print_menu_item")); //$NON-NLS-1$
    printMenu.add(printItem);
    printItem.setAccelerator(KeyStroke.getKeyStroke('P', MENU_SHORTCUT_KEY_MASK));
    printItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        PrintUtils.printComponent(drawingPanel);
      }

    });
    JMenuItem printFrameItem = new JMenuItem(DisplayRes.getString("DrawingFrame.PrintFrame_menu_item")); //$NON-NLS-1$
    printMenu.add(printFrameItem);
    printFrameItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        PrintUtils.printComponent(DrawingFrame.this);
      }

    });
    JMenuItem saveXMLItem = new JMenuItem(DisplayRes.getString("DrawingFrame.SaveXML_menu_item")); //$NON-NLS-1$
    saveXMLItem.setAccelerator(KeyStroke.getKeyStroke('S', MENU_SHORTCUT_KEY_MASK));
    saveXMLItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        saveXML();
      }

    });
    //ExportTool menu item
    /*
    JMenuItem exportItem = new JMenuItem(DisplayRes.getString("DrawingFrame.Export_menu_item")); //$NON-NLS-1$
    exportItem.setAccelerator(KeyStroke.getKeyStroke('E', MENU_SHORTCUT_KEY_MASK));
    exportItem.addActionListener(new ActionListener() {
       public void actionPerformed(ActionEvent e) {
          try {
             ExportTool.getTool().send(new LocalJob(drawingPanel), null);
          } catch(RemoteException ex) {}
       }
    });*/
    // create export tool menu item if the tool exists in classpath
    JMenuItem exportItem = new JMenuItem(DisplayRes.getString("DrawingFrame.Export_menu_item")); //$NON-NLS-1$
    exportItem.setAccelerator(KeyStroke.getKeyStroke('E', MENU_SHORTCUT_KEY_MASK));
    Class<?> exportTool = null;
    if(OSPRuntime.loadExportTool) {
      try {
        exportTool = Class.forName("org.opensourcephysics.tools.ExportTool");        //$NON-NLS-1$
      } catch(Exception ex) {
        OSPRuntime.loadExportTool = false;
        OSPLog.finest("Cannot instantiate data export tool class:\n"+ex.toString()); //$NON-NLS-1$
        exportItem.setEnabled(false);
      }
    }
    final Class<?> tool = exportTool;
    exportItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        try {
          Method m = tool.getMethod("getTool", (Class[]) null); //$NON-NLS-1$
          Tool tool = (Tool) m.invoke(null, (Object[]) null);
          tool.send(new LocalJob(drawingPanel), reply);
        } catch(Exception ex) {}
      }

    });
    //Save menu item
    JMenu saveImage = new JMenu(DisplayRes.getString("DrawingFrame.SaveImage_menu_title"));      //$NON-NLS-1$
    JMenuItem epsMenuItem = new JMenuItem(DisplayRes.getString("DrawingFrame.EPS_menu_item"));   //$NON-NLS-1$
    JMenuItem jpegMenuItem = new JMenuItem(DisplayRes.getString("DrawingFrame.JPEG_menu_item")); //$NON-NLS-1$
    JMenuItem pngMenuItem = new JMenuItem(DisplayRes.getString("DrawingFrame.PNG_menu_item"));   //$NON-NLS-1$
    saveImage.add(epsMenuItem);
    saveImage.add(jpegMenuItem);
    saveImage.add(pngMenuItem);
    epsMenuItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GUIUtils.saveImage(drawingPanel, "eps", DrawingFrame.this); //$NON-NLS-1$
      }

    });
    jpegMenuItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GUIUtils.saveImage(drawingPanel, "jpeg", DrawingFrame.this); //$NON-NLS-1$
      }

    });
    pngMenuItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GUIUtils.saveImage(drawingPanel, "png", DrawingFrame.this); //$NON-NLS-1$
      }

    });
    JMenuItem inspectItem = new JMenuItem(DisplayRes.getString("DrawingFrame.InspectMenuItem")); //$NON-NLS-1$
    inspectItem.setAccelerator(KeyStroke.getKeyStroke('I', MENU_SHORTCUT_KEY_MASK));
    inspectItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        inspectXML(); // cannot use a static method here because of run-time binding
      }

    });
    if(OSPRuntime.applet==null) {
      fileMenu.add(printMenu);
      fileMenu.add(saveXMLItem);
      fileMenu.add(exportItem);
      fileMenu.add(saveImage);
    }
    fileMenu.add(inspectItem);
    menuBar.add(fileMenu);
    // edit menu
    editMenu = new JMenu(DisplayRes.getString("DrawingFrame.Edit_menu_title")); //$NON-NLS-1$
    menuBar.add(editMenu);
    copyItem = new JMenuItem(DisplayRes.getString("DrawingFrame.Copy_menu_item")); //$NON-NLS-1$
    copyItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        XMLControlElement control = new XMLControlElement(DrawingFrame.this);
        control.saveObject(null);
        copyAction(control);
      }

    });
    editMenu.add(copyItem);
    pasteItem = new JMenuItem(DisplayRes.getString("DrawingFrame.Paste_menu_item")); //$NON-NLS-1$
    pasteItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        try {
          Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
          Transferable data = clipboard.getContents(null);
          XMLControlElement control = new XMLControlElement();
          control.readXML((String) data.getTransferData(DataFlavor.stringFlavor));
          pasteAction(control);
        } catch(UnsupportedFlavorException ex) {}
        catch(IOException ex) {}
        catch(HeadlessException ex) {}
      }

    });
    pasteItem.setEnabled(false); // not supported yet
    editMenu.add(pasteItem);
    replaceItem = new JMenuItem(DisplayRes.getString("DrawingFrame.Replace_menu_item")); //$NON-NLS-1$
    replaceItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        try {
          Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
          Transferable data = clipboard.getContents(null);
          XMLControlElement control = new XMLControlElement();
          control.readXML((String) data.getTransferData(DataFlavor.stringFlavor));
          replaceAction(control);
        } catch(UnsupportedFlavorException ex) {}
        catch(IOException ex) {}
        catch(HeadlessException ex) {}
      }

    });
    replaceItem.setEnabled(false); // not supported yet
    editMenu.add(replaceItem);
    setJMenuBar(menuBar);
    // additonal menus
    loadDisplayMenu();
    loadToolsMenu();
    //help menu
    JMenu helpMenu = new JMenu(DisplayRes.getString("DrawingFrame.Help_menu_item")); //$NON-NLS-1$
    menuBar.add(helpMenu);
    JMenuItem aboutItem = new JMenuItem(DisplayRes.getString("DrawingFrame.AboutOSP_menu_item")); //$NON-NLS-1$
    aboutItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        OSPRuntime.showAboutDialog(DrawingFrame.this);
      }

    });
    helpMenu.add(aboutItem);
  }

  /**
   * Adds a Display menu to the menu bar.
   */
  protected JMenu loadDisplayMenu() {
    JMenuBar menuBar = getJMenuBar();
    if(menuBar==null) {
      return null;
    }
    JMenu displayMenu = new JMenu(DisplayRes.getString("DrawingFrame.Display_menu_title")); //$NON-NLS-1$
    menuBar.add(displayMenu);
    JMenu fontMenu = new JMenu(DisplayRes.getString("DrawingFrame.Font_menu_title")); //$NON-NLS-1$
    displayMenu.add(fontMenu);
    JMenuItem sizeUpItem = new JMenuItem(DisplayRes.getString("DrawingFrame.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(DisplayRes.getString("DrawingFrame.DecreaseFontSize_menu_item")); //$NON-NLS-1$
    sizeDownItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        FontSizer.levelDown();
      }

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

    });
    JMenu aliasMenu = new JMenu(DisplayRes.getString("DrawingFrame.AntiAlias_menu_title")); //$NON-NLS-1$
    displayMenu.add(aliasMenu);
    final JCheckBoxMenuItem textAliasItem = new JCheckBoxMenuItem(DisplayRes.getString("DrawingFrame.Text_checkbox_label"), false); //$NON-NLS-1$
    textAliasItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        drawingPanel.antialiasTextOn = textAliasItem.isSelected();
        drawingPanel.repaint();
      }

    });
    aliasMenu.add(textAliasItem);
    final JCheckBoxMenuItem shapeAliasItem = new JCheckBoxMenuItem(DisplayRes.getString("DrawingFrame.Drawing_textbox_label"), false); //$NON-NLS-1$
    shapeAliasItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        drawingPanel.antialiasShapeOn = shapeAliasItem.isSelected();
        drawingPanel.repaint();
      }

    });
    aliasMenu.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        textAliasItem.setSelected(drawingPanel.antialiasTextOn);
        shapeAliasItem.setSelected(drawingPanel.antialiasShapeOn);
      }

    });
    aliasMenu.add(shapeAliasItem);
    menuBar.add(displayMenu);
    return displayMenu;
  }

  /**
   * Adds a Tools menu to the menu bar.
   */
  protected JMenu loadToolsMenu() {
    JMenuBar menuBar = 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 Data Tool menu item if the tool exists in the 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(Exception ex) {
        OSPLog.finest("Cannot instantiate data analysis tool class:\n"+ex.toString()); //$NON-NLS-1$
        OSPRuntime.loadDataTool = false;
        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(drawingPanel), reply);
          if(tool instanceof OSPFrame) {
            ((OSPFrame) tool).setKeepHidden(false);
          }
          ((JFrame) tool).setVisible(true);
        } catch(Exception ex) {}
      }

    });
    // create Fourier Tool menu item if the tool exists in the classpath
    JMenuItem fourierToolItem = new JMenuItem(DisplayRes.getString("DrawingFrame.FourierTool_menu_item")); //$NON-NLS-1$
    toolsMenu.add(fourierToolItem);
    Class<?> fourierToolClass = null;
    if(OSPRuntime.loadFourierTool) {
      try {
        fourierToolClass = Class.forName("org.opensourcephysics.tools.FourierTool");      //$NON-NLS-1$
      } catch(Exception ex) {
        OSPLog.finest("Cannot instantiate Fourier analysis tool class:\n"+ex.toString()); //$NON-NLS-1$
        OSPRuntime.loadFourierTool = false;
        fourierToolItem.setEnabled(false);
      }
    }
    final Class<?> finalFourierToolClass = fourierToolClass; // class must be final for action listener
    fourierToolItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        try {
          Method m = finalFourierToolClass.getMethod("getTool", (Class[]) null); //$NON-NLS-1$
          Tool tool = (Tool) m.invoke(null, (Object[]) null);
          tool.send(new LocalJob(drawingPanel), reply);
          if(tool instanceof OSPFrame) {
            ((OSPFrame) tool).setKeepHidden(false);
          }
          ((JFrame) tool).setVisible(true);
        } catch(Exception ex) {}
      }

    });
    // create snapshot menu item
    JMenuItem snapshotItem = new JMenuItem(DisplayRes.getString("DisplayPanel.Snapshot_menu_item")); //$NON-NLS-1$
    if(OSPRuntime.applet==null) {
      toolsMenu.add(snapshotItem);
    }
    snapshotItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        SnapshotTool tool = SnapshotTool.getTool();
        if(drawingPanel!=null) {
          tool.saveImage(null, drawingPanel);
        } 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(Exception 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(drawingPanel.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
            drawingPanel.setVideoTool((VideoTool) tool);
            ((VideoTool) tool).setVisible(true);
            ((VideoTool) tool).clear();
          } catch(Exception ex) {
            OSPLog.finest("Cannot perform action to get video tool class:\n"+ex.toString()); //$NON-NLS-1$
          }
        } else {
          drawingPanel.getVideoTool().setVisible(true);
        }
      }

    });
    return toolsMenu;
  }

  /**
   * Sets a custom  properties inspector window.
   *
   * @param w the new inspector window
   */
  public void setCustomInspector(Window w) {
    if(customInspector!=null) {
      customInspector.setVisible(false); // hide the current inspector window
    }
    customInspector = w;
  }

  /**
   * Inspects the drawing frame by using an xml document tree.
   */
  public void inspectXML() {
    if(customInspector!=null) {
      customInspector.setVisible(true);
      return;
    }
    XMLControlElement xml = null;
    try {
      // if drawingPanel provides an xml loader, inspect the drawingPanel
      Method method = drawingPanel.getClass().getMethod("getLoader", (Class[]) null); //$NON-NLS-1$
      if((method!=null)&&Modifier.isStatic(method.getModifiers())) {
        xml = new XMLControlElement(drawingPanel);
      }
    } catch(NoSuchMethodException ex) {
      // this drawing panel cannot be inspected
      return;
    }
    // display a TreePanel in a modal dialog
    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 saveXML() {
    JFileChooser chooser = OSPRuntime.getChooser();
    int result = chooser.showSaveDialog(null);
    if(result==JFileChooser.APPROVE_OPTION) {
      OSPRuntime.chooserDir = chooser.getCurrentDirectory().toString();
      File file = chooser.getSelectedFile();
      // check to see if file already exists
      if(file.exists()) {
        int selected = JOptionPane.showConfirmDialog(null, DisplayRes.getString("DrawingFrame.ReplaceExisting_message")+file.getName() //$NON-NLS-1$
          +DisplayRes.getString("DrawingFrame.QuestionMark"),                           //$NON-NLS-1$
            DisplayRes.getString("DrawingFrame.ReplaceFile_option_title"),              //$NON-NLS-1$
              JOptionPane.YES_NO_CANCEL_OPTION);
        if(selected!=JOptionPane.YES_OPTION) {
          return;
        }
      }
      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$
      }
      try {
        // if drawingPanel provides an xml loader, save the drawingPanel
        Method method = drawingPanel.getClass().getMethod("getLoader", (Class[]) null); //$NON-NLS-1$
        if((method!=null)&&Modifier.isStatic(method.getModifiers())) {
          XMLControl xml = new XMLControlElement(drawingPanel);
          xml.write(fileName);
        }
      } catch(NoSuchMethodException ex) {
        // this drawingPanel cannot be saved
        return;
      }
    }
  }

  /**
   * Returns an XML.ObjectLoader to save and load data for this program.
   *
   * @return the object loader
   */
  public static XML.ObjectLoader getLoader() {
    return new DrawingFrameLoader();
  }

  static protected class DrawingFrameLoader implements XML.ObjectLoader {
    /**
     * createObject
     *
     * @param control XMLControl
     * @return Object
     */
    public Object createObject(XMLControl control) {
      DrawingFrame frame = new DrawingFrame();
      frame.setTitle(control.getString("title"));                                    //$NON-NLS-1$
      frame.setLocation(control.getInt("location x"), control.getInt("location y")); //$NON-NLS-1$ //$NON-NLS-2$
      frame.setSize(control.getInt("width"), control.getInt("height"));              //$NON-NLS-1$ //$NON-NLS-2$
      if(control.getBoolean("showing")) { //$NON-NLS-1$
        frame.setVisible(true);
      }
      return frame;
    }

    /**
     * Save data object's data in the control.
     *
     * @param control XMLControl
     * @param obj Object
     */
    public void saveObject(XMLControl control, Object obj) {
      DrawingFrame frame = (DrawingFrame) obj;
      control.setValue("title", frame.getTitle());                //$NON-NLS-1$
      control.setValue("showing", frame.isShowing());             //$NON-NLS-1$
      control.setValue("location x", frame.getLocation().x);      //$NON-NLS-1$
      control.setValue("location y", frame.getLocation().y);      //$NON-NLS-1$
      control.setValue("width", frame.getSize().width);           //$NON-NLS-1$
      control.setValue("height", frame.getSize().height);         //$NON-NLS-1$
      control.setValue("drawing panel", frame.getDrawingPanel()); //$NON-NLS-1$
    }

    /**
     * Loads the object with data from the control.
     *
     * @param control XMLControl
     * @param obj Object
     * @return Object
     */
    public Object loadObject(XMLControl control, Object obj) {
      DrawingFrame frame = ((DrawingFrame) obj);
      DrawingPanel panel = frame.getDrawingPanel();
      panel.clear();
      XMLControl panelControl = control.getChildControl("drawing panel"); //$NON-NLS-1$
      panelControl.loadObject(panel);
      panel.repaint();
      frame.setTitle(control.getString("title"));                                    //$NON-NLS-1$
      frame.setLocation(control.getInt("location x"), control.getInt("location y")); //$NON-NLS-1$ //$NON-NLS-2$
      frame.setSize(control.getInt("width"), control.getInt("height"));              //$NON-NLS-1$ //$NON-NLS-2$
      if(control.getBoolean("showing")) { //$NON-NLS-1$
        frame.setVisible(true);
      }
      return obj;
    }

  }

}

/*
 * 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
 */
