/*
* 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.FlowLayout;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEditSupport;
import org.opensourcephysics.controls.ListChooser;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.display.OSPRuntime;
/**
* This is a JDialog that displays and controls a checkbox jar tree.
*
* @author Douglas Brown
* @version 1.0
*/
public class JarTreeDialog extends JDialog {
protected File rootFile;
protected JarTreeModel jarModel;
protected JTree jarTree;
protected CheckTreeManager checkManager;
protected TreePath[] selectionPaths;
protected UndoableEditSupport undoSupport;
protected UndoManager undoManager;
protected JButton okButton, undoButton, redoButton, languagesButton;
protected boolean ignoreEvents;
protected int prevRow;
protected Icon jarIcon, jarFileIcon, jarFolderIcon, fileIcon;
/**
* Constructor.
*
* @param owner the owner frame
* @param root the root directory
*/
public JarTreeDialog(Frame owner, File root) {
super(owner, true);
rootFile = root;
createGUI();
// set up the undo system
undoManager = new UndoManager();
undoSupport = new UndoableEditSupport();
undoSupport.addUndoableEditListener(undoManager);
CheckTreeSelectionModel checkModel = checkManager.getSelectionModel();
checkModel.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if(ignoreEvents) {
return;
}
TreePath[] prev = (TreePath[]) e.getOldValue();
TreePath[] curr = (TreePath[]) e.getNewValue();
TreePath path = jarTree.getSelectionPath();
int row = jarTree.getRowForPath(path);
SelectionEdit edit = new SelectionEdit(prev, prevRow, curr, row);
undoSupport.postEdit(edit);
prevRow = row;
refresh();
}
});
refresh();
// 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);
}
/**
* Gets the selected paths as relative path strings.
*
* @return an array of string paths relative to the root directory
*/
public String[] getSelectionRelativePaths() {
if(selectionPaths==null) {
return null;
}
ArrayList<String> temp = new ArrayList<String>();
for(int i = 0; i<selectionPaths.length; i++) {
temp.add(getRelativePath(selectionPaths[i]));
}
return temp.toArray(new String[0]);
}
/**
* Sets the selected relative path strings.
*
* @param paths an array of string paths relative to the root directory
*/
public void setSelectionRelativePaths(String[] paths) {
ArrayList<TreePath> temp = new ArrayList<TreePath>();
for(int i = 0; i<paths.length; i++) {
temp.add(getTreePath(paths[i]));
}
TreePath[] treePaths = temp.toArray(new TreePath[0]);
setSelectionPaths(treePaths);
}
/**
* Gets the selected paths as TreePaths that start with the root.
*
* @return an array of selected TreePaths
*/
public TreePath[] getSelectionPaths() {
return selectionPaths;
}
/**
* Sets the selected TreePaths.
*
* @param treePaths an array of TreePaths that start with the root
*/
public void setSelectionPaths(TreePath[] treePaths) {
ignoreEvents = true;
CheckTreeSelectionModel checkModel = checkManager.getSelectionModel();
if(treePaths==null) {
checkModel.setSelectionPaths(new TreePath[0]);
} else {
checkModel.setSelectionPaths(treePaths);
}
ignoreEvents = false;
refresh();
}
/**
* Converts a TreePath to a relative path string.
* The TreePath is assumed to start with the root directory.
*
* @param path the TreePath
* @return the relative path
*/
private String getRelativePath(TreePath path) {
Object[] nodes = path.getPath();
StringBuffer buffer = new StringBuffer();
Object jarNode = rootFile;
// skip node[0] since it is the root
for(int j = 1; j<nodes.length; j++) {
if(buffer.toString().endsWith(".jar")) { //$NON-NLS-1$
buffer.append("!"); //$NON-NLS-1$
}
if(j>1) {
buffer.append("/"); //$NON-NLS-1$
}
buffer.append(nodes[j].toString());
jarNode = jarModel.getChild(jarNode, nodes[j].toString());
}
// add an end slash if jarNode is directory
if(((jarNode instanceof File)&&((File) jarNode).isDirectory())||((jarNode instanceof JarTreeModel.JarNode)&&!((JarTreeModel.JarNode) jarNode).isLeaf())) {
buffer.append("/"); //$NON-NLS-1$
}
return buffer.toString();
}
/**
* Gets the TreePath associated with a path relative to the rootFile
*
* @param relativePath the path
* @return the TreePath
*/
private TreePath getTreePath(String relativePath) {
String path = XML.forwardSlash(relativePath);
TreePath treePath = new TreePath(rootFile);
Object parent = rootFile;
while(parent!=null) {
Object child = null;
int n = path.indexOf("/"); //$NON-NLS-1$
if(n>-1) {
String name = path.substring(0, n);
path = path.substring(n+1);
if(name.endsWith("!")) { //$NON-NLS-1$
name = name.substring(0, name.length()-1);
}
child = jarModel.getChild(parent, name);
} else {
child = jarModel.getChild(parent, path);
}
if(child!=null) {
treePath = treePath.pathByAddingChild(child);
}
parent = child;
}
return treePath;
}
/**
* Refreshes the buttonbar.
*/
protected void refresh() {
CheckTreeSelectionModel checkModel = checkManager.getSelectionModel();
selectionPaths = checkModel.getSelectionPaths();
okButton.setEnabled(!checkModel.isSelectionEmpty());
undoButton.setEnabled(undoManager.canUndo());
redoButton.setEnabled(undoManager.canRedo());
}
/**
* Creates the GUI.
*/
protected void createGUI() {
setTitle(ToolsRes.getString("JarTreeDialog.Title")); //$NON-NLS-1$
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setPreferredSize(new Dimension(480, 320));
setContentPane(contentPane);
// create icons
String imageFile = "/org/opensourcephysics/resources/tools/images/jarfile.gif"; //$NON-NLS-1$
jarIcon = ResourceLoader.getIcon(imageFile);
imageFile = "/org/opensourcephysics/resources/tools/images/jarcontent.gif"; //$NON-NLS-1$
jarFileIcon = ResourceLoader.getIcon(imageFile);
imageFile = "/org/opensourcephysics/resources/tools/images/jarfolder.gif"; //$NON-NLS-1$
jarFolderIcon = ResourceLoader.getIcon(imageFile);
imageFile = "/org/opensourcephysics/resources/tools/images/whitefile.gif"; //$NON-NLS-1$
fileIcon = ResourceLoader.getIcon(imageFile);
// create the tree model and tree
jarModel = new JarTreeModel(rootFile);
jarTree = new JTree(jarModel);
jarTree.setSelectionRow(0);
jarTree.setCellRenderer(new JarRenderer());
// add checkboxes to the tree
checkManager = new CheckTreeManager(jarTree);
// put the tree in a scroller
JScrollPane scroller = new JScrollPane(jarTree);
// put titled border around tree
Border etched = BorderFactory.createEtchedBorder();
TitledBorder title = BorderFactory.createTitledBorder(etched, ToolsRes.getString("JarTreeDialog.Border.Title")); //$NON-NLS-1$
scroller.setBorder(title);
contentPane.add(scroller, BorderLayout.CENTER);
// create ok button
okButton = new JButton(ToolsRes.getString("JarTreeDialog.Button.OK")); //$NON-NLS-1$
okButton.setForeground(new Color(0, 0, 102));
okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
});
// create cancel button
JButton cancelButton = new JButton(ToolsRes.getString("JarTreeDialog.Button.Cancel")); //$NON-NLS-1$
cancelButton.setForeground(new Color(0, 0, 102));
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
selectionPaths = null;
setVisible(false);
}
});
// create undo button
undoButton = new JButton(ToolsRes.getString("JarTreeDialog.Button.Undo")); //$NON-NLS-1$
undoButton.setForeground(new Color(0, 0, 102));
undoButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
undoManager.undo();
}
});
// create redo button
redoButton = new JButton(ToolsRes.getString("JarTreeDialog.Button.Redo")); //$NON-NLS-1$
redoButton.setForeground(new Color(0, 0, 102));
redoButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
undoManager.redo();
}
});
// create languages button
languagesButton = new JButton(ToolsRes.getString("JarTreeDialog.Button.Languages")); //$NON-NLS-1$
languagesButton.setForeground(new Color(0, 0, 102));
languagesButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// create a list chooser
ListChooser dialog = new ListChooser(ToolsRes.getString("JarTreeDialog.Chooser.Languages.Title"), //$NON-NLS-1$
ToolsRes.getString("JarTreeDialog.Chooser.Languages.Message"), //$NON-NLS-1$
JarTreeDialog.this);
// create the lists and fill with Locales
ArrayList<Locale> choices = new ArrayList<Locale>();
ArrayList<String> names = new ArrayList<String>();
ArrayList<Locale> originals = new ArrayList<Locale>();
Locale[] locales = OSPRuntime.getInstalledLocales();
boolean[] selected = new boolean[locales.length];
boolean[] disabled = new boolean[locales.length];
Object[] rootPath = new Object[] {jarModel.getRoot()};
Collection<Object[]> jarPaths = jarModel.getDescendantPaths(rootPath);
CheckTreeSelectionModel checkModel = checkManager.getSelectionModel();
for(int i = 0; i<locales.length; i++) {
choices.add(locales[i]);
originals.add(locales[i]);
names.add(locales[i].getDisplayLanguage(ToolsRes.resourceLocale));
String lang = locales[i].getLanguage();
if(locales[i]==Locale.ENGLISH) {
selected[i] = true;
disabled[i] = true;
}
// look thru jar paths to see if locale is selected
else {
for(Iterator<Object[]> it = jarPaths.iterator(); it.hasNext(); ) {
// find paths containing ".properties" and "display_res_xx"
Object[] array = it.next();
String s = array[array.length-1].toString(); // file namae
if((s.indexOf(".properties")>-1 //$NON-NLS-1$
)&&(s.indexOf("display_res_"+lang)>-1)) { //$NON-NLS-1$
// see if path or ancestor is selected
TreePath thePath = new TreePath(array);
if(thePath.toString().indexOf(OSPRuntime.getLaunchJarName())>-1) {
selected[i] = checkModel.isPathOrAncestorSelected(thePath);
break;
}
}
}
}
}
// show the list chooser for user input
if(dialog.choose(choices, names, null, selected, disabled)) { // false if canceled
Collection<TreePath> removePaths = new ArrayList<TreePath>();
Collection<TreePath> addPaths = new ArrayList<TreePath>();
// compare choices with originals
for(Iterator<Locale> it = originals.iterator(); it.hasNext(); ) {
Locale next = it.next();
String lang = next.getLanguage();
for(Iterator<Object[]> pathIt = jarPaths.iterator(); pathIt.hasNext(); ) {
// find paths containing ".properties" and "_xx"
Object[] array = pathIt.next(); // get next path
String s = array[array.length-1].toString(); // get last path object
if((s.indexOf(".properties")>-1 //$NON-NLS-1$
)&&(s.indexOf("_"+lang)>-1)) { //$NON-NLS-1$
TreePath propPath = new TreePath(array);
// don't change paths in jars other than the launch jar
if(propPath.toString().indexOf(OSPRuntime.getLaunchJarName())>-1) {
// eliminate unselected languages
if(!choices.contains(next)) {
removePaths.add(propPath);
}
// add selected languages
else {
addPaths.add(propPath);
}
}
}
}
}
TreePath[] paths = removePaths.toArray(new TreePath[0]);
checkManager.getSelectionModel().removeSelectionPaths(paths);
paths = addPaths.toArray(new TreePath[0]);
checkManager.getSelectionModel().addSelectionPaths(paths);
refresh();
}
}
});
// create and add buttonbar at bottom
JPanel buttonbar = new JPanel(new FlowLayout());
buttonbar.setBorder(BorderFactory.createEmptyBorder(1, 0, 3, 0));
contentPane.add(buttonbar, BorderLayout.SOUTH);
// add buttons to buttonbar
buttonbar.add(languagesButton);
buttonbar.add(undoButton);
buttonbar.add(redoButton);
buttonbar.add(okButton);
buttonbar.add(cancelButton);
pack();
}
/**
* A class to undo/redo tree node selections.
*/
protected class SelectionEdit extends AbstractUndoableEdit {
TreePath[] undo;
TreePath[] redo;
int undoRow;
int redoRow;
/**
* Constructor SelectionEdit
* @param undoPaths
* @param redoPaths
*/
public SelectionEdit(TreePath[] undoPaths, TreePath[] redoPaths) {
undo = undoPaths;
redo = redoPaths;
}
/**
* Constructor SelectionEdit
* @param undoPaths
* @param undoRow
* @param redoPaths
* @param redoRow
*/
public SelectionEdit(TreePath[] undoPaths, int undoRow, TreePath[] redoPaths, int redoRow) {
undo = undoPaths;
redo = redoPaths;
this.undoRow = undoRow;
this.redoRow = redoRow;
}
public void undo() throws CannotUndoException {
super.undo();
ignoreEvents = true;
CheckTreeSelectionModel checkModel = checkManager.getSelectionModel();
checkModel.setSelectionPaths(undo);
jarTree.setSelectionRow(undoRow);
ignoreEvents = false;
refresh();
prevRow = undoRow;
}
public void redo() throws CannotUndoException {
super.redo();
ignoreEvents = true;
CheckTreeSelectionModel checkModel = checkManager.getSelectionModel();
checkModel.setSelectionPaths(redo);
jarTree.setSelectionRow(redoRow);
ignoreEvents = false;
refresh();
prevRow = redoRow;
}
public String getPresentationName() {
return "Change Selection"; //$NON-NLS-1$
}
}
/**
* A cell renderer to display jar and file nodes.
*/
protected class JarRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if(value instanceof File) {
File file = (File) value;
if(file.getName().endsWith(".jar")) { //$NON-NLS-1$
setIcon(jarIcon);
} else if(leaf) {
setIcon(fileIcon);
}
} else if(value instanceof JarTreeModel.JarNode) {
setIcon(leaf ? jarFileIcon : jarFolderIcon);
}
return this;
}
}
// // main method for testing
// public static void main(String[] args) {
// JFrame frame = new JFrame();
// frame.setSize(400,600);
// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// frame.setVisible(true);
// File root = new File(System.getProperty("user.home")); //$NON-NLS-1$
// JarTreeDialog tree = new JarTreeDialog(frame, root);
// tree.setVisible(true);
// String[] paths = tree.getSelectionRelativePaths();
// if (paths == null) System.out.println("no paths"); //$NON-NLS-1$
// else for (int i = 0; i < paths.length; i++) {
// System.out.println(paths[i]);
// }
// }
}
/*
* 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
*/