/*
* 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.tools;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.controls.XMLControlElement;
import org.opensourcephysics.controls.XMLTreePanel;
/**
* This is a dialog that displays a tree showing tabs to be saved.
*
* @author Douglas Brown
* @version 1.0
*/
public class LaunchSaver extends JDialog {
// instance fields
private String prevTabSetName;
private String prevTabSetBasePath;
private boolean prevTabSetSelfContained;
private Map<Node, String> prevNodeNames = new HashMap<Node, String>();
private Map<Node, Boolean> prevNodeSelfContains = new HashMap<Node, Boolean>();
private LaunchBuilder builder;
private DefaultTreeModel treeModel;
private JTree tree;
private ArrayList<TreePath> treePaths = new ArrayList<TreePath>();
private JTextField pathField;
protected JCheckBox saveStateCheckBox;
private JButton chooseButton;
private JButton inspectButton;
private Node root;
private JScrollPane treeScroller;
private boolean approved = false;
private Editor editor = new Editor();
private JDialog inspector;
private boolean active;
/**
* Constructor.
*
* @param builder the LaunchBuilder with tabs to save
*/
public LaunchSaver(LaunchBuilder builder) {
super(builder.frame, true);
createGUI();
setBuilder(builder); // creates tree for launch builder
if(FontSizer.getLevel()!=0) {
FontSizer.setFonts(getContentPane(), FontSizer.getLevel());
}
pack();
// center on screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width-getBounds().width)/2;
int y = (dim.height-getBounds().height)/2;
setLocation(x, y);
}
/**
* Sets the LaunchBuilder and creates a new tree.
*
* @param builder the LaunchBuilder
*/
public void setBuilder(LaunchBuilder builder) {
this.builder = builder;
createTree();
refresh();
approved = false;
}
/**
* Sets the selected node.
*
* @param node the node to select
*/
public void setSelectedNode(Node node) {
if(node==null) {
return;
}
tree.setSelectionPath(new TreePath(node.getPath()));
}
/**
* Gets the selected node.
*
* @return the selected node
*/
public Node getSelectedNode() {
TreePath path = tree.getSelectionPath();
if(path==null) {
return null;
}
return(Node) path.getLastPathComponent();
}
/**
* Returns true if user has clicked the save button, otherwise false.
*
* @return true to save
*/
public boolean isApproved() {
return approved;
}
// ______________________________ private methods _____________________________
/**
* Refreshes the GUI.
*/
private void refresh() {
inspectButton.setEnabled(getSelectedNode()!=null);
String path = null;
if(!Launcher.tabSetBasePath.equals("")) { //$NON-NLS-1$
path = Launcher.tabSetBasePath;
} else if(builder.jarBasePath!=null) {
path = builder.jarBasePath;
} else {
path = XML.forwardSlash(XML.getUserDirectory());
}
if(path.indexOf("javaws")>-1) { //$NON-NLS-1$
path = XML.forwardSlash(System.getProperty("user.home", ".")); //$NON-NLS-1$ //$NON-NLS-2$
Launcher.tabSetBasePath = path;
}
pathField.setText(path+"/"); //$NON-NLS-1$
pathField.setBackground(Color.white);
treeModel.nodeChanged(root);
tree.repaint();
}
/**
* Displays the properties of the selected node in an xml inspector.
*/
private void inspectSelectedNode() {
Node node = getSelectedNode();
if(node==null) {
node = root;
}
Object obj = node.node;
if(obj==null) {
obj = builder.new LaunchSet(builder, builder.tabSetName);
}
XMLControl xml = new XMLControlElement(obj);
XMLTreePanel treePanel = new XMLTreePanel(xml, false);
inspector.setContentPane(treePanel);
String name = (node!=root) ? node.node.getFileName() : builder.tabSetName;
name = XML.getResolvedPath(name, Launcher.tabSetBasePath);
inspector.setTitle(LaunchRes.getString("Inspector.Title.File")+" \""+name+"\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
inspector.setVisible(true);
}
/**
* Reverts to initial state.
*/
private void revert() {
Launcher.tabSetBasePath = prevTabSetBasePath;
builder.tabSetName = prevTabSetName;
builder.selfContained = prevTabSetSelfContained;
// set parentSelfContained property of tabs
for(int i = 0; i<builder.tabbedPane.getTabCount(); i++) {
LaunchNode root = builder.getTab(i).getRootNode();
root.parentSelfContained = prevTabSetSelfContained;
}
Iterator<Node> it = prevNodeNames.keySet().iterator();
while(it.hasNext()) {
Node node = it.next();
String path = prevNodeNames.get(node);
node.node.setFileName(path);
Boolean bool = prevNodeSelfContains.get(node);
node.node.setSelfContained(bool.booleanValue());
}
}
/**
* Sets the base path.
*
* @param path the path
*/
private void setTabSetBasePath(String path) {
path = XML.forwardSlash(path);
// strip leading and trailing slashes
boolean leadingSlash = false;
while(path.startsWith("/")) { //$NON-NLS-1$
leadingSlash = true;
path = path.substring(1);
}
// replace single leading slash if any found
if(leadingSlash) {
path = "/"+path; //$NON-NLS-1$
}
while(path.endsWith("/")) { //$NON-NLS-1$
path = path.substring(0, path.length()-1);
}
if(!path.startsWith("/")&&(path.indexOf(":")==-1)) { // not an absolute path //$NON-NLS-1$ //$NON-NLS-2$
String base = XML.forwardSlash(XML.getUserDirectory());
if(builder.jarBasePath!=null) {
base = builder.jarBasePath;
}
if(!path.equals("")) { //$NON-NLS-1$
base += "/"; //$NON-NLS-1$
}
path = base+path;
}
Launcher.tabSetBasePath = path;
refresh();
}
/**
* Sets the tabset name.
*
* @param proposed the proposed name
* @return the tabset name
*/
private String setTabSetName(String proposed) {
String name = XML.getName(proposed);
if(!"xset".equals(XML.getExtension(name))) { //$NON-NLS-1$
while(name.indexOf(".")>-1) { //$NON-NLS-1$
name = name.substring(0, name.length()-1);
}
name += ".xset"; //$NON-NLS-1$
}
builder.tabSetName = name;
refresh();
return name;
}
/**
* Creates the GUI.
*/
private void createGUI() {
// create xml inspector
inspector = new JDialog(this, false);
inspector.setSize(new java.awt.Dimension(600, 300));
// center inspector on screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width-inspector.getBounds().width)/2;
int y = (dim.height-inspector.getBounds().height)/2;
inspector.setLocation(x, y);
// create panel components
setTitle(LaunchRes.getString("Saver.Title")); //$NON-NLS-1$
JPanel panel = new JPanel(new BorderLayout());
panel.setPreferredSize(new Dimension(600, 300));
setContentPane(panel);
JPanel legend = new JPanel(new GridLayout(1, 4));
panel.add(legend, BorderLayout.NORTH);
JLabel label = new JLabel(LaunchRes.getString("Saver.Legend.New"), Launcher.greenFileIcon, SwingConstants.HORIZONTAL); //$NON-NLS-1$
label.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
legend.add(label);
label = new JLabel(LaunchRes.getString("Saver.Legend.Replace"), Launcher.yellowFileIcon, SwingConstants.HORIZONTAL); //$NON-NLS-1$
label.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
legend.add(label);
label = new JLabel(LaunchRes.getString("Saver.Legend.ReadOnly"), Launcher.redFileIcon, SwingConstants.HORIZONTAL); //$NON-NLS-1$
label.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
legend.add(label);
label = new JLabel(LaunchRes.getString("Saver.Legend.SelfContained"), Launcher.whiteFolderIcon, SwingConstants.HORIZONTAL); //$NON-NLS-1$
label.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
legend.add(label);
legend.setBorder(BorderFactory.createLoweredBevelBorder());
JPanel lower = new JPanel(new BorderLayout());
panel.add(lower, BorderLayout.SOUTH);
JToolBar toolbar = new JToolBar();
toolbar.setFloatable(false);
lower.add(toolbar, BorderLayout.NORTH);
label = new JLabel(LaunchRes.getString("Saver.Label.Base")); //$NON-NLS-1$
label.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 4));
toolbar.add(label);
pathField = new JTextField();
pathField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_ENTER) {
setTabSetBasePath(pathField.getText());
} else {
pathField.setBackground(Color.yellow);
}
}
});
pathField.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) {
setTabSetBasePath(pathField.getText());
}
});
toolbar.add(pathField);
// choose button
chooseButton = new JButton(LaunchRes.getString("Saver.Button.Choose")); //$NON-NLS-1$
chooseButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
chooseTabSet();
}
});
// inspect button
inspectButton = new JButton(LaunchRes.getString("MenuItem.Inspect")); //$NON-NLS-1$
inspectButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
inspectSelectedNode();
}
});
// saveState checkbox
saveStateCheckBox = new JCheckBox(LaunchRes.getString("Saver.Checkbox.SaveState")); //$NON-NLS-1$
saveStateCheckBox.setOpaque(false);
JToolBar buttonbar = new JToolBar();
buttonbar.setFloatable(false);
buttonbar.add(Box.createHorizontalGlue());
buttonbar.add(Box.createHorizontalGlue());
buttonbar.add(Box.createHorizontalGlue());
JPanel buttons = new JPanel();
buttons.setOpaque(false);
buttonbar.add(buttons);
buttons.add(saveStateCheckBox);
buttons.add(inspectButton);
buttons.add(chooseButton);
lower.add(buttonbar, BorderLayout.SOUTH);
// save button
JButton saveButton = new JButton(LaunchRes.getString("Saver.Button.Save")); //$NON-NLS-1$
buttons.add(saveButton);
saveButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
approved = true;
setVisible(false);
}
});
// cancel button
JButton cancelButton = new JButton(LaunchRes.getString("Saver.Button.Cancel")); //$NON-NLS-1$
buttons.add(cancelButton);
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
revert();
setVisible(false);
}
});
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
if(!isApproved()) {
revert();
}
}
});
}
/**
* Creates a new tree.
*/
private void createTree() {
active = false;
treePaths.clear();
// save initial values for revert method
prevNodeNames.clear();
prevNodeSelfContains.clear();
prevTabSetName = builder.tabSetName;
prevTabSetBasePath = Launcher.tabSetBasePath;
prevTabSetSelfContained = builder.selfContained;
// create root node
root = new Node();
treePaths.add(new TreePath(root.getPath()));
// add child nodes to root
int n = builder.tabbedPane.getTabCount();
for(int i = 0; i<n; i++) {
Node tab = new Node(builder.getTab(i).getRootNode());
if(tab.node.getFileName()!=null) {
root.add(tab);
addChildren(tab); // also saves tab file name and selfContained
} else {
LaunchNode[] nodes = tab.node.getChildOwnedNodes();
for(int k = 0; k<nodes.length; k++) {
Node node = new Node(nodes[k]);
root.add(node);
addChildren(node);
}
}
}
// create tree model and tree
treeModel = new DefaultTreeModel(root);
tree = new JTree(treeModel);
tree.setCellRenderer(new Renderer());
tree.setCellEditor(editor);
tree.setEditable(true);
tree.setShowsRootHandles(true);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if(path==null) {
editor.stopCellEditing();
}
}
});
tree.addTreeWillExpandListener(new ExpansionListener());
tree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
if(inspector.isVisible()) {
inspectSelectedNode();
}
}
});
// set tree row height
FontRenderContext frc = new FontRenderContext(null, // no AffineTransform
false, // no antialiasing
false); // no fractional metrics
Font font = editor.field.getFont();
Rectangle2D rect = font.getStringBounds("A", frc); //$NON-NLS-1$
int h = (int) rect.getHeight();
tree.setRowHeight(h+3);
tree.setBorder(BorderFactory.createEmptyBorder(4, 0, 2, 0));
// put tree in a scroller and add to dialog
if(treeScroller!=null) {
getContentPane().remove(treeScroller);
}
treeScroller = new JScrollPane(tree);
getContentPane().add(treeScroller, BorderLayout.CENTER);
setSelectedNode(root);
// collapse tree to show self-containment structure
ListIterator<TreePath> it = treePaths.listIterator();
while(it.hasNext()) {
TreePath path = it.next();
tree.expandPath(path);
}
while(it.hasPrevious()) {
TreePath path = it.previous();
Node node = (Node) path.getLastPathComponent();
if(isFolder(node)) {
tree.collapsePath(path);
}
}
active = true;
}
private void addChildren(Node node) {
// save tab file name and selfContained property
prevNodeNames.put(node, node.node.getFileName());
prevNodeSelfContains.put(node, new Boolean(node.node.selfContained));
treePaths.add(new TreePath(node.getPath()));
// add node's owned child nodes
LaunchNode[] nodes = node.node.getChildOwnedNodes();
for(int k = 0; k<nodes.length; k++) {
Node child = new Node(nodes[k]);
node.add(child);
addChildren(child);
}
}
private boolean isFolder(Node node) {
if(node==root) {
return builder.selfContained;
}
return node.node.selfContained&&!node.isLeaf();
}
/**
* Selects the tabset with a chooser.
*/
private void chooseTabSet() {
String fileName = XML.getResolvedPath(builder.tabSetName, Launcher.tabSetBasePath);
JFileChooser chooser = Launcher.getXMLChooser();
chooser.setSelectedFile(new File(fileName));
chooser.setFileFilter(Launcher.xsetFileFilter);
int result = chooser.showDialog(this, LaunchRes.getString("Saver.FileChooser.Title")); //$NON-NLS-1$
if(result==JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
fileName = file.getAbsolutePath();
setTabSetName(fileName);
setTabSetBasePath(XML.getDirectoryPath(fileName));
}
refresh();
}
private Icon getIcon(Node node) {
File file;
if(node==root) {
String path = XML.getResolvedPath(builder.tabSetName, Launcher.tabSetBasePath);
Resource res = ResourceLoader.getResource(path);
file = ((res==null) ? null : res.getFile());
} else {
file = node.node.getFile();
}
// green icon if creating
if(file==null) {
if(isFolder(node)) {
return Launcher.greenFolderIcon;
}
return Launcher.greenFileIcon;
}
// yellow icon if replacing
else if(file.canWrite()) {
if(isFolder(node)) {
return Launcher.yellowFolderIcon;
}
return Launcher.yellowFileIcon;
}
// red icon if read-only
else {
if(isFolder(node)) {
return Launcher.redFolderIcon;
}
return Launcher.redFileIcon;
}
}
/**
* A tree node.
*/
private class Node extends DefaultMutableTreeNode {
private LaunchNode node;
// Constructor.
Node() {
/** empty block */
}
// Constructor for launch node.
Node(LaunchNode node) {
this.node = node;
}
// returns the text displayed.
public String toString() {
if(node==null) {
return LaunchRes.getString("Saver.Tree.TabSet")+":"; //$NON-NLS-1$ //$NON-NLS-2$
} else if(node.isRoot()) {
return LaunchRes.getString("Saver.Tree.Tab")+" \""+node.name+"\":"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
} else {
return LaunchRes.getString("Saver.Tree.Node")+" \""+node.name+"\":"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
}
/**
* A class to render tree cells.
*/
class Renderer implements TreeCellRenderer {
JPanel panel = new JPanel(new BorderLayout());
JLabel label = new JLabel();
JTextField field = new JTextField();
// Constructor.
/**
* Constructor Renderer
*/
public Renderer() {
panel.add(label, BorderLayout.WEST);
panel.add(field, BorderLayout.CENTER);
panel.setOpaque(false);
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
field.setBorder(null);
field.setBackground(field.getSelectionColor());
}
// Gets the component to be displayed.
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
Node node = (Node) value;
String name = (node!=root) ? node.node.getFileName() : builder.tabSetName;
field.setText(name);
label.setIcon(getIcon(node));
label.setText(node.toString());
if(FontSizer.getLevel()!=0) {
FontSizer.setFonts(panel, FontSizer.getLevel());
}
field.setOpaque(selected ? true : false);
return panel;
}
}
/**
* A class to edit tree node names.
*/
private class Editor extends AbstractCellEditor implements TreeCellEditor {
JPanel panel = new JPanel(new BorderLayout());
JTextField field = new JTextField();
JLabel label = new JLabel();
// Constructor.
Editor() {
panel.add(label, BorderLayout.WEST);
panel.add(field, BorderLayout.CENTER);
panel.setOpaque(false);
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4));
field.setBorder(BorderFactory.createLineBorder(Color.black));
field.setEditable(true);
field.setColumns(30);
field.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
stopCellEditing();
}
});
field.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_ENTER) {
stopCellEditing();
} else {
field.setBackground(Color.yellow);
}
}
});
field.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) {
stopCellEditing();
}
});
}
// Gets the component to be displayed while editing.
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row) {
Node node = (Node) value;
String name = (node!=root) ? node.node.getFileName() : builder.tabSetName;
field.setText(name);
label.setIcon(getIcon(node));
label.setText(node.toString());
if(FontSizer.getLevel()!=0) {
FontSizer.setFonts(panel, FontSizer.getLevel());
}
return panel;
}
// Determines when editing starts.
public boolean isCellEditable(EventObject e) {
if(e instanceof MouseEvent) {
return true;
}
return false;
}
// Called when editing is completed.
public Object getCellEditorValue() {
field.setBackground(Color.white);
Node node = getSelectedNode();
String name = field.getText();
// prevent duplicate file names
String prev;
if(node.node!=null) {
prev = node.node.getFileName();
} else {
prev = builder.tabSetName;
}
if(builder.getOpenPaths().contains(name)&&!prev.equals(name)) {
JOptionPane.showMessageDialog(LaunchSaver.this, LaunchRes.getString("Dialog.DuplicateFileName.Message")+" \""+name+"\"", LaunchRes.getString("Dialog.DuplicateFileName.Title"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
refresh();
return null;
}
if(node.node!=null) {
node.node.setFileName(name);
} else {
name = setTabSetName(name);
}
return name;
}
}
private class ExpansionListener implements TreeWillExpandListener {
public void treeWillExpand(TreeExpansionEvent e) {
TreePath path = e.getPath();
set(path, false);
}
public void treeWillCollapse(TreeExpansionEvent e) {
TreePath path = e.getPath();
set(path, true);
}
private void set(TreePath path, boolean selfContained) {
if(active) {
Node node = (Node) path.getLastPathComponent();
if(node==root) {
builder.selfContained = selfContained;
// set parentSelfContained property of tabs
for(int i = 0; i<builder.tabbedPane.getTabCount(); i++) {
LaunchNode root = builder.getTab(i).getRootNode();
root.parentSelfContained = selfContained;
}
} else {
node.node.setSelfContained(selfContained);
}
if(inspector.isVisible()) {
inspectSelectedNode();
}
}
}
}
}
/*
* 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
*/