/*
 * 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.controls;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
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.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.display.GUIUtils;
import org.opensourcephysics.display.OSPFrame;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.display.PrintUtils;
import org.opensourcephysics.tools.FontSizer;
import org.opensourcephysics.tools.ToolsRes;

/**
 *  A frame with menu items for saving and loading control parameters
 *
 * @author       Wolfgang Christian
 * @version 1.0
 */
abstract public class ControlFrame extends OSPFrame implements Control {
  final static int MENU_SHORTCUT_KEY_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
  protected Object model; // the object that will be controlled
  protected JMenuItem[] languageItems;
  protected JMenu languageMenu;
  protected JMenu fileMenu;
  protected JMenu editMenu;
  protected JMenu displayMenu;
  protected JMenuItem readItem, clearItem, printFrameItem, saveFrameAsEPSItem;
  protected JMenuItem saveAsItem;
  protected JMenuItem copyItem;
  protected JMenuItem inspectItem;
  protected JMenuItem logToFileItem;
  protected JMenuItem sizeUpItem;
  protected JMenuItem sizeDownItem;
  protected OSPApplication ospApp;
  protected XMLControlElement xmlDefault;

  protected ControlFrame(String title) {
    super(title);
    createMenuBar();
    setName("controlFrame"); //$NON-NLS-1$
  }

  private void createMenuBar() {
    JMenuBar menuBar = new JMenuBar();
    if(!OSPRuntime.appletMode) {
      setJMenuBar(menuBar);
    }
    fileMenu = new JMenu(ControlsRes.getString("ControlFrame.File")); //$NON-NLS-1$
    editMenu = new JMenu(ControlsRes.getString("ControlFrame.Edit")); //$NON-NLS-1$
    menuBar.add(fileMenu);
    menuBar.add(editMenu);
    readItem = new JMenuItem(ControlsRes.getString("ControlFrame.Load_XML"));       //$NON-NLS-1$
    saveAsItem = new JMenuItem(ControlsRes.getString("ControlFrame.Save_XML"));     //$NON-NLS-1$
    inspectItem = new JMenuItem(ControlsRes.getString("ControlFrame.Inspect_XML")); //$NON-NLS-1$
    clearItem = new JMenuItem(ControlsRes.getString("ControlFrame.Clear_XML"));     //$NON-NLS-1$
    clearItem.setEnabled(false);
    copyItem = new JMenuItem(ControlsRes.getString("ControlFrame.Copy"));                              //$NON-NLS-1$
    printFrameItem = new JMenuItem(DisplayRes.getString("DrawingFrame.PrintFrame_menu_item"));         //$NON-NLS-1$
    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$
    if(OSPRuntime.applet==null) {
      fileMenu.add(readItem);
    }
    if(OSPRuntime.applet==null) {
      fileMenu.add(saveAsItem);
    }
    fileMenu.add(inspectItem);
    fileMenu.add(clearItem);
    if(OSPRuntime.applet==null) {
      fileMenu.add(printMenu);
    }
    printMenu.add(printFrameItem);
    printMenu.add(saveFrameAsEPSItem);
    editMenu.add(copyItem);
    copyItem.setAccelerator(KeyStroke.getKeyStroke('C', MENU_SHORTCUT_KEY_MASK));
    copyItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        copy();
      }

    });
    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
      }

    });
    readItem.setAccelerator(KeyStroke.getKeyStroke('L', 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);
      }

    });
    clearItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        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();
      }

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

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

    });
    // display menu
    loadDisplayMenu();
    // help menu
    JMenu helpMenu = new JMenu(ControlsRes.getString("ControlFrame.Help")); //$NON-NLS-1$
    menuBar.add(helpMenu);
    JMenuItem aboutItem = new JMenuItem(ControlsRes.getString("ControlFrame.About")); //$NON-NLS-1$
    aboutItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        OSPRuntime.showAboutDialog(ControlFrame.this);
      }

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

    });
    helpMenu.add(sysItem);
    JMenuItem showItem = new JMenuItem(ControlsRes.getString("ControlFrame.Display_All_Frames")); //$NON-NLS-1$
    showItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        org.opensourcephysics.display.GUIUtils.showDrawingAndTableFrames();
      }

    });
    helpMenu.add(showItem);
    helpMenu.addSeparator();
    JMenuItem logItem = new JMenuItem(ControlsRes.getString("ControlFrame.Message_Log")); //$NON-NLS-1$
    logItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        OSPLog.showLog();
      }

    });
    if(OSPRuntime.applet==null) {
      helpMenu.add(logItem);
    }
    logToFileItem = new JCheckBoxMenuItem(ControlsRes.getString("ControlFrame.Log_to_File")); //$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());
      }

    });
    helpMenu.add(logToFileItem);
    validate();
  }

  /**
   * Adds a Display menu to the menu bar. Overrides OSPFrame method.
   *
   * @return the display menu
   */
  protected JMenu loadDisplayMenu() {
    JMenuBar menuBar = getJMenuBar();
    if(menuBar==null) {
      return null;
    }
    displayMenu = super.loadDisplayMenu();
    if(displayMenu==null) {
      displayMenu = new JMenu();
      displayMenu.setText(ControlsRes.getString("ControlFrame.Display")); //$NON-NLS-1$
      menuBar.add(displayMenu);
    }
    // language menu
    languageMenu = new JMenu();
    languageMenu.setText(ControlsRes.getString("ControlFrame.Language")); //$NON-NLS-1$
    Action languageAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        String language = e.getActionCommand();
        OSPLog.finest("setting language to "+language); //$NON-NLS-1$
        Locale[] locales = OSPRuntime.getInstalledLocales();
        for(int i = 0; i<locales.length; i++) {
          if(language.equals(locales[i].getDisplayName())) {
            ToolsRes.setLocale(locales[i]);
            return;
          }
        }
      }

    };
    Locale[] locales = OSPRuntime.getInstalledLocales();
    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);
      }
    }
    if(OSPRuntime.isAuthorMode()||!OSPRuntime.isLauncherMode()) {
      displayMenu.add(languageMenu); // add the menu if program is stand-alone or if user is authoring.
    }
    //font menu
    JMenu fontMenu = new JMenu(DisplayRes.getString("DrawingFrame.Font_menu_title")); //$NON-NLS-1$
    displayMenu.add(fontMenu);
    JMenuItem sizeUpItem = new JMenuItem(ControlsRes.getString("ControlFrame.Increase_Font_Size")); //$NON-NLS-1$
    sizeUpItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        FontSizer.levelUp();
      }

    });
    fontMenu.add(sizeUpItem);
    final JMenuItem sizeDownItem = new JMenuItem(ControlsRes.getString("ControlFrame.Decrease_Font_Size")); //$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);
      }

    });
    return displayMenu;
  }

  /**
   * Refreshes the user interface in response to display changes such as Language.
   */
  protected void refreshGUI() {
    super.refreshGUI();
    createMenuBar();
  }

  /** Saves a file containing the control parameters to the disk. */
  public void save() {
    ControlUtils.saveToFile(this, ControlFrame.this);
  }

  /** Loads a file containing the control parameters from the disk. */
  public void readParameters() {
    ControlUtils.loadParameters(this, ControlFrame.this);
  }

  /** Copies the data in the table to the system clipboard */
  public void copy() {
    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    StringSelection stringSelection = new StringSelection(this.toString());
    clipboard.setContents(stringSelection, stringSelection);
  }

  public void saveXML() {
    JFileChooser chooser = OSPRuntime.getChooser();
    if(chooser==null) {
      return;
    }
    String oldTitle = chooser.getDialogTitle();
    chooser.setDialogTitle(ControlsRes.getString("ControlFrame.Save_XML_Data")); //$NON-NLS-1$
    int result = chooser.showSaveDialog(null);
    chooser.setDialogTitle(oldTitle);
    if(result==JFileChooser.APPROVE_OPTION) {
      File file = chooser.getSelectedFile();
      // check to see if file already exists
      if(file.exists()) {
        int selected = JOptionPane.showConfirmDialog(null, ControlsRes.getString("ControlFrame.Replace_existing")+file.getName() //$NON-NLS-1$
          +ControlsRes.getString("ControlFrame.question_mark"), ControlsRes.getString("ControlFrame.Replace_File"), //$NON-NLS-1$ //$NON-NLS-2$
            JOptionPane.YES_NO_CANCEL_OPTION);
        if(selected!=JOptionPane.YES_OPTION) {
          return;
        }
      }
      OSPRuntime.chooserDir = chooser.getCurrentDirectory().toString();
      String fileName = file.getAbsolutePath();
      // 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);
    }
  }

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

  /**
   * Loads xml data into the model.
   *
   * @param xml
   * @param compatibleModel  true if model is known to be compatible with the app
   */
  public void loadXML(XMLControlElement xml, boolean compatibleModel) {
    if(xml==null) {
      OSPLog.finer("XML data not found in ControlFrame loadXML method."); //$NON-NLS-1$
      return;
    }
    // load xml into app if xml object class is an OSPApplication
    // if(xml.getObjectClass().isAssignableFrom(OSPApplication.class)) {
    if(OSPApplication.class.isAssignableFrom(xml.getObjectClass())) {
      ospApp = getOSPApp();
      ospApp.compatibleModel = compatibleModel;
      xml.loadObject(getOSPApp());
      ospApp.compatibleModel = false;
      if(model.getClass()==ospApp.getLoadedModelClass()) {                        // classes match so we have a new default
        xmlDefault = xml;
        clearItem.setEnabled(true);
      } else if(ospApp.getLoadedModelClass()!=null) {                             // a model was imported; create xml from current model
        xmlDefault = new XMLControlElement(getOSPApp());
        clearItem.setEnabled(true);
      } else {                                                                    // model is null
        xmlDefault = null;
        clearItem.setEnabled(false);
        JOptionPane.showMessageDialog(this, "Model specified in file not found.", //$NON-NLS-1$
          "Data not loaded.", JOptionPane.WARNING_MESSAGE);                       //$NON-NLS-1$
      }
    } else {
      JOptionPane.showMessageDialog(this, "Data for: "+xml.getObjectClass()+".",  //$NON-NLS-1$ //$NON-NLS-2$
        "OSP Application data not found.", JOptionPane.WARNING_MESSAGE);          //$NON-NLS-1$
    }
  }

  public void loadXML(String fileName) {
    if((fileName==null)||fileName.trim().equals("")) { //$NON-NLS-1$
      loadXML();
      return;
    }
    loadXML(new XMLControlElement(fileName), false);
  }

  public void loadXML() {
    JFileChooser chooser = OSPRuntime.getChooser();
    if(chooser==null) {
      return;
    }
    String oldTitle = chooser.getDialogTitle();
    chooser.setDialogTitle(ControlsRes.getString("ControlFrame.Load_XML_Data")); //$NON-NLS-1$
    int result = chooser.showOpenDialog(null);
    chooser.setDialogTitle(oldTitle);
    if(result==JFileChooser.APPROVE_OPTION) {
      OSPRuntime.chooserDir = chooser.getCurrentDirectory().toString();
      String fileName = chooser.getSelectedFile().getAbsolutePath();
      loadXML(new XMLControlElement(fileName), false);
    }
  }

  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);
  }

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

}

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