/*
* 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.Dimension;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
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.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JTabbedPane;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.opensourcephysics.controls.ControlsRes;
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.XMLTree;
import org.opensourcephysics.controls.XMLTreeChooser;
import org.opensourcephysics.desktop.OSPDesktop;
import org.opensourcephysics.display.Data;
import org.opensourcephysics.display.DataFunction;
import org.opensourcephysics.display.Dataset;
import org.opensourcephysics.display.DatasetManager;
import org.opensourcephysics.display.DisplayColors;
import org.opensourcephysics.display.OSPFrame;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.display.TeXParser;
import org.opensourcephysics.display.TextFrame;
/**
* This provides a GUI for analyzing OSP Data objects.
*
* @author Douglas Brown
* @version 1.0
*/
@SuppressWarnings("serial")
public class DataTool extends OSPFrame implements Tool, PropertyChangeListener {
// static fields
@SuppressWarnings("javadoc")
public static boolean loadClass = false;
protected static Dimension dim = new Dimension(800, 540);
protected static final int defaultButtonHeight = 28;
protected static int buttonHeight = defaultButtonHeight;
protected static String[] delimiters = new String[] {" ", "\t", ",", ";"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
protected static TextFrame helpFrame;
// protected static String helpName = "data_tool_help.html"; //$NON-NLS-1$
// protected static String helpBase = "http://www.opensourcephysics.org/online_help/tools/"; //$NON-NLS-1$
protected static String helpName = "datatool/datatool_help.html"; //$NON-NLS-1$
protected static String helpBase = "http://www.compadre.org/osp/online_help/tools/"; //$NON-NLS-1$
private static ArrayList<Data> processedData = new ArrayList<Data>();
// instance fields
protected JTabbedPane tabbedPane;
protected boolean useChooser = true;
protected JPanel contentPane = new JPanel(new BorderLayout());
protected PropertyChangeSupport support;
protected XMLControlElement control = new XMLControlElement();
protected Data addableData = null;
protected boolean controlContainsData;
protected JMenuBar emptyMenubar;
protected JMenu emptyFileMenu;
protected JMenuItem emptyNewTabItem;
protected JMenuItem emptyOpenItem;
protected JMenuItem emptyExitItem;
protected JMenu emptyEditMenu;
protected JMenuItem emptyPasteMenu;
protected JMenuItem emptyPasteTabItem;
protected JMenuBar menubar;
protected JMenu fileMenu;
protected JMenuItem newTabItem;
protected JMenuItem openItem;
protected JMenuItem importItem;
protected JMenuItem exportItem;
protected JMenuItem saveItem;
protected JMenuItem saveAsItem;
protected JMenuItem closeItem;
protected JMenuItem closeAllItem;
protected JMenuItem printItem;
protected JMenuItem exitItem;
protected JMenu editMenu;
protected JMenuItem undoItem;
protected JMenuItem redoItem;
protected JMenu copyMenu;
protected JMenuItem copyImageItem;
protected JMenuItem copyTabItem;
protected JMenuItem copyDataItem;
protected JMenu pasteMenu;
protected JMenuItem pasteTabItem;
protected JMenuItem pasteColumnsItem;
protected JMenu displayMenu;
protected JMenu languageMenu;
protected JMenuItem[] languageItems;
protected JMenu fontSizeMenu;
protected JMenuItem defaultFontSizeItem;
protected ButtonGroup fontSizeGroup;
protected JMenu helpMenu;
protected JMenuItem helpItem;
protected JMenuItem logItem;
protected JMenuItem aboutItem;
protected DataBuilder dataBuilder;
protected boolean exitOnClose = false;
protected boolean saveChangesOnClose = false;
protected FitBuilder fitBuilder;
protected boolean isLoading = false;
protected JButton loadDataFunctionsButton, saveDataFunctionsButton;
protected boolean slopeExtended = false;
static {
DATATOOL = new DataTool();
}
/**
* A shared data tool.
*/
final static DataTool DATATOOL;
/**
* Gets the shared DataTool.
*
* @return the shared DataTool
*/
public static DataTool getTool() {
return DATATOOL;
}
/**
* Main entry point when used as application.
*
* @param args args[0] may be a data or xml file name
*/
public static void main(String[] args) {
DATATOOL.exitOnClose = true;
DATATOOL.saveChangesOnClose = true;
if((args!=null)&&(args.length>0)&&(args[0]!=null)) {
DATATOOL.setVisible(true);
DATATOOL.open(args[0]);
} else {
DATATOOL.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
if (DATATOOL.getTabCount()==0) {
DataToolTab tab = DATATOOL.createTab(null);
tab.setUserEditable(true);
DATATOOL.addTab(tab);
}
}
});
DATATOOL.setVisible(true);
}
}
/**
* Constructs a blank DataTool.
*/
public DataTool() {
this(ToolsRes.getString("DataTool.Frame.Title"), "DataTool"); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Constructs a DataTool and opens the specified xml file.
*
* @param fileName the name of the xml file
*/
public DataTool(String fileName) {
this();
open(fileName);
}
/**
* Constructs a DataTool and loads data from an xml control.
*
* @param control the xml control
*/
public DataTool(XMLControl control) {
this();
addTabs(control);
}
/**
* Constructs a DataTool and loads the specified data object.
*
* @param data the data
*/
public DataTool(Data data) {
this();
ArrayList<DataToolTab> tabs = createTabs(data);
for(DataToolTab tab : tabs) {
addTab(tab);
}
}
/**
* Sets the saveChangesOnClose flag.
*
* @param save true to save changes when exiting
*/
public void setSaveChangesOnClose(boolean save) {
saveChangesOnClose = save&&!OSPRuntime.appletMode;
}
/**
* Adds tabs loaded with data from an xml control.
*
* @param control the xml control
* @return a list of newly added tabs, or null if failed
*/
public ArrayList<DataToolTab> addTabs(XMLControl control) {
// if control is for DataToolTab class, load tab from control
if(DataToolTab.class == control.getObjectClass()) {
// store this DataTool in the control so DataToolTab loader can use it for instantiation
control.setValue("datatool", this); //$NON-NLS-1$
isLoading = true;
DataToolTab tab = (DataToolTab) control.loadObject(null);
addTab(tab);
tab.refreshGUI();
isLoading = false;
ArrayList<DataToolTab> tabs = new ArrayList<DataToolTab>();
tabs.add(tab);
return tabs;
}
// if control is for FourierToolTab, load the source data into a tab
if(control.getObjectClassName().endsWith("FourierToolTab")) { //$NON-NLS-1$
XMLControl child = control.getChildControl("source_data"); //$NON-NLS-1$
Data source = (Data)child.loadObject(null);
DataToolTab tab = createTab(source);
addTab(tab);
tab.refreshGUI();
ArrayList<DataToolTab> tabs = new ArrayList<DataToolTab>();
tabs.add(tab);
return tabs;
}
// otherwise load data from control into tabs
ArrayList<DataToolTab> tabs = loadTabsFromXML(control, useChooser);
for(DataToolTab tab : tabs) {
addTab(tab);
tab.refreshGUI();
}
return tabs;
}
/**
* Creates a tab for each Data object returned by DataTool.getDataList(source).
* The tab names will be those of the Data objects in the list if they define
* a getName() method.
*
* @param source the source Data
* @return a list of new tabs
*/
public ArrayList<DataToolTab> createTabs(Data source) {
ArrayList<Data> dataList = getSelfContainedData(source);
ArrayList<DataToolTab> tabList = new ArrayList<DataToolTab>();
for(Data next : dataList) {
DataToolTab tab = createTab(next);
if(tab!=null) {
tabList.add(tab);
}
}
return tabList;
}
/**
* Creates a tab for the specified Data object. The tab name will be
* that of the Data object if it defines a getName() method.
*
* @param data the Data
* @return the new tab
*/
protected DataToolTab createTab(Data data) {
// be sure fitBuilder is instantiated
fitBuilder = getFitBuilder();
DataToolTab tab = new DataToolTab(data, this);
if(data!=null) {
String name = data.getName();
if((name!=null)&&!name.equals("")) { //$NON-NLS-1$)
tab.setName(name);
}
}
return tab;
}
/**
* Removes the tab at the specified index.
*
* @param index the tab number
* @param saveChanges
* @return the removed tab, or null if none removed
*/
public DataToolTab removeTab(int index, boolean saveChanges) {
if((index>=0)&&(index<tabbedPane.getTabCount())) {
if(saveChanges&&!saveChangesAt(index)) {
return null;
}
DataToolTab tab = getTab(index);
fitBuilder.curveFitters.remove(tab.curveFitter);
fitBuilder.removePropertyChangeListener(tab.curveFitter.fitListener);
String title = tabbedPane.getTitleAt(index);
OSPLog.finer("removing tab "+title); //$NON-NLS-1$
tabbedPane.removeTabAt(index);
refreshTabTitles();
refreshMenubar();
refreshDataBuilder();
return tab;
}
return null;
}
/**
* Removes a specified tab.
*
* @param tab the tab
* @return the removed tab, or null if none removed
*/
public DataToolTab removeTab(DataToolTab tab) {
return removeTab(getTabIndex(tab), true);
}
/**
* Loads a Data object into existing tabs and/or newly created tabs as needed.
*
* @param data the Data
* @return a list of the loaded tabs
*/
public ArrayList<DataToolTab> loadData(Data data) {
DataToolTab tab = null;
ArrayList<DataToolTab> loadedTabs = new ArrayList<DataToolTab>();
for(Data next : getSelfContainedData(data)) {
tab = getTab(next); // tab may be null
if(tab!=null) {
tab.loadData(next, tab.replaceColumnsWithMatchingNames);
} else {
tab = createTab(next);
addTab(tab);
}
loadedTabs.add(tab);
}
if(tab!=null) {
setSelectedTab(tab);
}
return loadedTabs;
}
/**
* Loads multiple Data objects into a single existing or newly created tab.
*
* @param data one or more Data objects
* @return the loaded tab
*/
public DataToolTab loadData(Data... data) {
if(data==null) {
return null;
}
ArrayList<Data> selfContained = new ArrayList<Data>();
for(Data next : data) {
selfContained.addAll(DataTool.getSelfContainedData(next));
}
DataToolTab tab = null;
for(Data next : selfContained) {
// retrieve or create tab for first Data object
if(tab==null) {
tab = getTab(next); // may still be null
if(tab!=null) {
tab.loadData(next, tab.replaceColumnsWithMatchingNames);
} else {
tab = createTab(next);
addTab(tab);
}
}
// add additional columns to tab
else {
ArrayList<DataColumn> columns = getDataColumns(next);
columns.remove(0);
tab.addColumns(columns, false, false, false);
}
}
if(tab!=null) {
setSelectedTab(tab);
}
return tab;
}
/**
* Returns the tab associated with the specified Data object. May return null.
*
* @param data the Data
* @return the tab
*/
public DataToolTab getTab(Data data) {
int i = getTabIndex(data);
return(i>-1) ? getTab(i) : null;
}
/**
* Returns the tab at the specified index. May return null.
*
* @param index the tab index
* @return the tab
*/
public DataToolTab getTab(int index) {
return((index>-1)&&(index<tabbedPane.getTabCount())) ? (DataToolTab) tabbedPane.getComponentAt(index) : null;
}
/**
* Returns the tab count.
*
* @return the number of tabs
*/
public int getTabCount() {
return tabbedPane.getTabCount();
}
/**
* Returns a list of all open tabs.
*
* @return a list of DataToolTabs
*/
public List<DataToolTab> getTabs() {
List<DataToolTab> tabs = new ArrayList<DataToolTab>();
for (int i=0; i< getTabCount(); i++) {
tabs.add(getTab(i));
}
return tabs;
}
/**
* Opens an xml or data file specified by name.
*
* @param fileName the file name
* @return the file name, if successfully opened (datasets loaded)
*/
public String open(String fileName) {
OSPLog.fine("opening "+fileName); //$NON-NLS-1$
Resource res = ResourceLoader.getResource(fileName);
if(res!=null) {
Reader in = res.openReader();
String firstLine = readFirstLine(in);
// if xml, read the file into an XML control and add tab
if(firstLine.startsWith("<?xml")) { //$NON-NLS-1$
XMLControlElement control = new XMLControlElement(fileName);
ArrayList<DataToolTab> tabs = addTabs(control);
if(!tabs.isEmpty()) {
for(DataToolTab tab : tabs) {
refreshDataBuilder();
if(tabs.size()==1) {
tab.fileName = fileName;
}
tab.tabChanged(false);
}
try {
in.close();
} catch (IOException e) {
//e.printStackTrace();
}
return fileName;
}
}
// if not xml, attempt to import data and add tab
else if(res.getString()!=null) {
Data data = parseData(res.getString(), fileName);
if(data!=null) {
DataToolTab tab = createTab(data);
addTab(tab);
refreshDataBuilder();
tab.fileName = fileName;
tab.tabChanged(false);
return fileName;
}
}
}
OSPLog.finest("no data found"); //$NON-NLS-1$
return null;
}
/**
* Imports an xml or data file into an existing tab.
*
* @param tab the tab
* @param fileName the file name
* @return the file name, if successfully imported (datasets loaded)
*/
public String importFileIntoTab(DataToolTab tab, String fileName) {
OSPLog.fine("importing "+fileName); //$NON-NLS-1$
Resource res = ResourceLoader.getResource(fileName);
if(res!=null) {
Reader in = res.openReader();
String firstLine = readFirstLine(in);
// if xml, read the file into an XML control and add tab
if(firstLine.startsWith("<?xml")) { //$NON-NLS-1$
XMLControlElement control = new XMLControlElement(fileName);
ArrayList<Data> dataList = getSelfContainedData(control, false);
if(!dataList.isEmpty()) {
DatasetManager manager = new DatasetManager();
for(Data next : dataList) {
for(DataColumn column : getDataColumns(next)) {
manager.addDataset(column);
}
}
tab.addColumns(manager, true, true, true);
return fileName;
}
}
// if not xml, attempt to import data and add tab
else if(res.getString()!=null) {
Data data = parseData(res.getString(), fileName);
if(data!=null) {
tab.addColumns(data, true, true, true);
return fileName;
}
}
}
OSPLog.finest("no data found"); //$NON-NLS-1$
return null;
}
/**
* Sends a job to this tool and specifies a tool to reply to.
*
* @param job the Job
* @param replyTo the tool to notify when the job is complete (may be null)
* @throws RemoteException
*/
public void send(Job job, Tool replyTo) throws RemoteException {
XMLControlElement control = new XMLControlElement(job.getXML());
if(control.failedToRead()||(control.getObjectClass()==Object.class)) {
return;
}
// if control defines Data, construct the Data
if(Data.class.isAssignableFrom(control.getObjectClass())) {
Data data = (Data) control.loadObject(null, true, true);
// if self-contained, then send job to an existing tab or create a new tab
if(isSelfContained(data)) {
DataToolTab tab = getTab(data); // may be null
if(tab==null) {
tab = createTab(null);
String name = data.getName();
if((name!=null)&&!name.equals("")) { //$NON-NLS-1$)
tab.setName(name);
}
addTab(tab);
}
else {
setSelectedTab(tab);
}
tab.send(job, replyTo);
}
// if data is a container, then send a new job to an existing tab
// or create a new tab
else {
for(Data next : getSelfContainedData(data)) {
DataToolTab tab = getTab(next); // may be null
if(tab==null) {
tab = createTab(null);
String name = next.getName();
if((name!=null)&&!name.equals("")) { //$NON-NLS-1$)
tab.setName(name);
}
addTab(tab);
}
tab.send(new LocalJob(next), replyTo);
}
}
}
// else add tabs based on child Data objects, if any, within the control
else {
addTabs(control); // adds Data objects found in XMLControl
}
}
/**
* Sets the useChooser flag.
*
* @param useChooser true to load datasets with a chooser
*/
public void setUseChooser(boolean useChooser) {
this.useChooser = useChooser;
}
/**
* Gets the useChooser flag.
*
* @return true if loading datasets with a chooser
*/
public boolean isUseChooser() {
return useChooser;
}
/**
* Listens for property changes "function"
*
* @param e the event
*/
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if(name.equals("function")) { //$NON-NLS-1$
DataToolTab tab = getSelectedTab();
if(tab!=null) {
tab.tabChanged(true);
tab.dataTable.refreshTable();
tab.statsTable.refreshStatistics();
if(e.getNewValue() instanceof DataFunction) { // new function has been created
String funcName = e.getNewValue().toString();
tab.dataTable.getWorkingData(funcName);
}
if(e.getOldValue() instanceof DataFunction) { // function has been deleted
String funcName = e.getOldValue().toString();
tab.dataTable.removeWorkingData(funcName);
}
if(e.getNewValue() instanceof String) {
String funcName = e.getNewValue().toString();
if(e.getOldValue() instanceof String) { // function name has changed
String prevName = e.getOldValue().toString();
tab.columnNameChanged(prevName, funcName);
} else {
tab.dataTable.getWorkingData(funcName);
}
}
tab.refreshPlot();
tab.varPopup = null;
}
}
}
/**
* Determines if an array contains any duplicate or Double.NaN values.
*
* @param values the array
* @return true if at least one duplicate is found
*/
protected static boolean containsDuplicateValues(double[] values) {
for(int i = 0; i<values.length; i++) {
if(Double.isNaN(values[i])) {
return true;
}
int n = getIndex(values[i], values, i);
if(n>-1) {
return true;
}
}
return false;
}
/**
* Gets the first array index at which the specified value is found.
*
* @param value the value to find
* @param array the array to search
* @param ignoreIndex an array index to ignore
* @return the index, or -1 if not found
*/
protected static int getIndex(double value, double[] array, int ignoreIndex) {
for(int i = 0; i<array.length; i++) {
if(i==ignoreIndex) {
continue;
}
if(array[i]==value) {
return i;
}
}
return -1;
}
/**
* Returns an array of row numbers.
*
* @param rowCount length of the array
* @return the array
*/
protected static double[] getRowArray(int rowCount) {
double[] rows = new double[rowCount];
for(int i = 0; i<rowCount; i++) {
rows[i] = i;
}
return rows;
}
/**
* Parses a String into tokens separated by a specified delimiter.
* A token may be "".
*
* @param text the text to parse
* @param delimiter the delimiter
* @return an array of String tokens
*/
protected static String[] parseStrings(String text, String delimiter) {
Collection<String> tokens = new ArrayList<String>();
if(text!=null) {
// get the first token
String next = text;
int i = text.indexOf(delimiter);
if(i==-1) { // no delimiter
tokens.add(stripQuotes(next));
text = null;
} else {
next = text.substring(0, i);
text = text.substring(i+1);
while (" ".equals(delimiter) //$NON-NLS-1$
&& (text.startsWith(" ") || text.startsWith("\t"))) { //$NON-NLS-1$ //$NON-NLS-2$
// treat multiple spaces/tabs as a single delimiter
text = text.substring(1);
}
}
// iterate thru the tokens and add to token list
while(text!=null) {
tokens.add(stripQuotes(next));
i = text.indexOf(delimiter);
if(i==-1) { // no delimiter
next = text;
tokens.add(stripQuotes(next));
text = null;
} else {
next = text.substring(0, i).trim();
text = text.substring(i+1);
while (" ".equals(delimiter) //$NON-NLS-1$
&& (text.startsWith(" ") || text.startsWith("\t"))) { //$NON-NLS-1$ //$NON-NLS-2$
// treat multiple spaces/tabs as a single delimiter
text = text.substring(1);
}
}
}
}
return tokens.toArray(new String[0]);
}
/**
* Strips quotation marks around a string.
*
* @param text the text to strip
* @return the stripped string
*/
private static String stripQuotes(String text) {
if(text.startsWith("\"")) { //$NON-NLS-1$
String stripped = text.substring(1);
int n = stripped.indexOf("\""); //$NON-NLS-1$
if(n>-1 && n==stripped.length()-1) {
return stripped.substring(0, n);
}
}
return text;
}
/**
* Parses a String into doubles separated by a specified delimiter.
* Unparsable strings are set to Double.NaN.
*
* @param text the text to parse
* @param delimiter the delimiter
* @return an array of doubles
*/
protected static double[] parseDoubles(String text, String delimiter) {
String[] strings = parseStrings(text, delimiter);
return parseDoubles(strings, delimiter);
}
/**
* Parses a String array into doubles.
* Unparsable strings are set to Double.NaN.
*
* @param strings the String array to parse
* @param delimiter the delimiter that was used to parse the strings
* @return an array of doubles
*/
protected static double[] parseDoubles(String[] strings, String delimiter) {
double[] doubles = new double[strings.length];
for(int i = 0; i<strings.length; i++) {
if(strings[i].indexOf("\t")>-1) { //$NON-NLS-1$
doubles[i] = Double.NaN;
} else {
try {
doubles[i] = Double.parseDouble(strings[i]);
} catch(NumberFormatException e) {
// convert decimal separator commas with periods
if (strings[i].indexOf(",")>-1 && !delimiter.equals(",")) { //$NON-NLS-1$ //$NON-NLS-2$
strings[i] = strings[i].replace(",", "."); //$NON-NLS-1$ //$NON-NLS-2$
try {
doubles[i] = Double.parseDouble(strings[i]);
} catch(NumberFormatException e1) {
doubles[i] = Double.NaN;
}
}
else doubles[i] = Double.NaN;
}
}
}
return doubles;
}
/**
* Parses a String into tokens separated by specified row and column delimiters.
*
* @param text the text to parse
* @param rowDelimiter the column delimiter
* @param colDelimiter the column delimiter
* @return a 2D array of String tokens
*/
protected static String[][] parseStrings(String text, String rowDelimiter, String colDelimiter) {
String[] rows = parseStrings(text, rowDelimiter);
String[][] tokens = new String[rows.length][0];
for(int i = 0; i<rows.length; i++) {
tokens[i] = parseStrings(rows[i], colDelimiter);
}
return tokens;
}
/**
* Parses a String into doubles separated by specified row and column delimiters.
*
* @param text the text to parse
* @param rowDelimiter the column delimiter
* @param colDelimiter the column delimiter
* @return a 2D array of doubles
*/
protected static double[][] parseDoubles(String text, String rowDelimiter, String colDelimiter) {
String[][] strings = parseStrings(text, rowDelimiter, colDelimiter);
double[][] doubles = new double[strings.length][0];
for(int i = 0; i<strings.length; i++) {
double[] row = new double[strings[i].length];
for(int j = 0; j<row.length; j++) {
try {
row[j] = Double.parseDouble(strings[i][j]);
} catch(NumberFormatException e) {
row[j] = Double.NaN;
}
}
doubles[i] = row;
}
return doubles;
}
/**
* Parses character-delimited data from a string.
* This attempts to extract the following information from the string:
*
* 1. A title to be used for the tab name
* 2. One or more columns of double data values
* 3. Column names for the data columns
*
* @param dataString the data string
* @param fileName name of file containing the data string (may be null)
* @return DatasetManager with parsed data, or null if none found
*/
public static DatasetManager parseData(String dataString, String fileName) {
BufferedReader input = new BufferedReader(new StringReader(dataString));
final String gnuPlotComment = "#"; //$NON-NLS-1$
try {
String textLine = input.readLine();
for(int i = 0; i<DataTool.delimiters.length; i++) {
ArrayList<double[]> rows = new ArrayList<double[]>();
int columns = Integer.MAX_VALUE;
String[] columnNames = null;
String title = null;
int lineCount = 0;
while(textLine!=null) {
// process each line of text
if(textLine.startsWith("//")) { //$NON-NLS-1$
// ignore comments (lines starting with "//")
textLine = input.readLine();
continue;
}
if(textLine.contains(gnuPlotComment)) {
// trim gnuPlot comments
textLine = textLine.trim(); // added by W. Christian
}
// look for gnuPlot-commented name and/or columnNames
if(textLine.startsWith(gnuPlotComment)) {
int k = textLine.indexOf("name:"); //$NON-NLS-1$
if(k>-1) {
title = textLine.substring(k+5).trim();
}
k = textLine.indexOf("columnNames:"); //$NON-NLS-1$
if(k>-1) {
textLine = textLine.substring(k+12).trim();
} else {
textLine = input.readLine();
continue;
}
}
// skip Vernier Format 2 header lines
if((textLine.indexOf("Vernier Format")>-1 //$NON-NLS-1$
)||(textLine.indexOf(".cmbl")>-1)) { //$NON-NLS-1$
textLine = input.readLine();
continue;
}
String[] strings = DataTool.parseStrings(textLine, DataTool.delimiters[i]);
double[] rowData = DataTool.parseDoubles(strings, DataTool.delimiters[i]);
// set title if not yet set (null), String[] length > 0, all entries
// are NaN and only one entry is not ""
if(rows.isEmpty()&&(strings.length>0)&&(title==null)) {
String s = ""; //$NON-NLS-1$
for(int k = 0; k<strings.length; k++) {
if(Double.isNaN(rowData[k])&&!strings[k].equals("")) { //$NON-NLS-1$
if(s.equals("")) { //$NON-NLS-1$
s = strings[k];
} else {
s = ""; //$NON-NLS-1$
break;
}
}
}
if(!s.equals("")) { //$NON-NLS-1$
title = s;
textLine = input.readLine();
continue;
}
}
// set column names if not yet set (null), String[] length > 0,
// all entries are NaN, and no data yet loaded
if(rows.isEmpty()&&(strings.length>0)&&(columnNames==null)) {
boolean valid = true;
for(int k = 0; k<strings.length; k++) {
if(!Double.isNaN(rowData[k])) {
valid = false;
break;
}
}
if(valid) {
// replace "" with "?"
for (int k=0; k<strings.length; k++) {
if ("".equals(strings[k])) { //$NON-NLS-1$
strings[k] = "?"; //$NON-NLS-1$
}
}
columnNames = strings;
columns = strings.length;
textLine = input.readLine();
continue;
}
}
// add double[] of length 1 or longer to rows
if(strings.length>0) {
lineCount++;
boolean validData = true;
boolean emptyData = true;
for(int k = 0; k<strings.length; k++) {
// invalid if any NaN entries other than ""
if(Double.isNaN(rowData[k])&&!strings[k].equals("")) { //$NON-NLS-1$
validData = false;
}
// look for empty row--every entry is ""
if(!strings[k].equals("")) { //$NON-NLS-1$
emptyData = false;
}
}
// ignore blank lines (NaN data) that precede real data
// unless both title and column names are non-null
// and number of columns is 1
if(rows.isEmpty() && emptyData && title==null
&& (columnNames==null || columnNames.length!=1)) {
validData = false;
}
// add valid data
if(validData) {
rows.add(rowData);
if (columns==Integer.MAX_VALUE) {
columns = rowData.length;
}
else {
columns = Math.max(rowData.length, columns);
}
}
}
// abort processing if no data found in first several lines
if(rows.isEmpty()&&(lineCount>10)) {
break;
}
textLine = input.readLine();
} // end while loop
// create datasets if data found
if(!rows.isEmpty()&&(columns>0)) {
input.close();
// first reassemble data from rows into columns
double[][] dataArray = new double[columns][rows.size()];
for(int row = 0; row<rows.size(); row++) {
double[] rowData = rows.get(row);
for(int j = 0; j<columns; j++) {
dataArray[j][row] = rowData.length>j? rowData[j]: Double.NaN;
}
}
// then append data to datasets
DatasetManager data = new DatasetManager();
data.setName((title==null) ? XML.getName(fileName) : title);
double[] rowColumn = DataTool.getRowArray(rows.size());
for(int j = 0; j<columns; j++) {
Dataset dataset = data.getDataset(j);
String yColName = ((columnNames!=null)&&(columnNames.length>j)) ? columnNames[j] :
((j==0)&&(title!=null)) ? title : "?"; //$NON-NLS-1$
dataset.setXYColumnNames("row", yColName); //$NON-NLS-1$
dataset.setXColumnVisible(false);
dataset.append(rowColumn, dataArray[j]);
}
OSPLog.finest("data found using delimiter \"" //$NON-NLS-1$
+DataTool.delimiters[i]+"\""); //$NON-NLS-1$
return data;
}
// close the reader and open a new one
input.close();
input = new BufferedReader(new StringReader(dataString));
textLine = input.readLine();
}
} catch(IOException e) {
e.printStackTrace();
}
try {
input.close();
} catch(IOException ex) {
ex.printStackTrace();
}
return null;
}
//______________________________ protected methods ________________________
protected String readFirstLine(Reader in) {
BufferedReader input = null;
if(in instanceof BufferedReader) {
input = (BufferedReader) in;
} else {
input = new BufferedReader(in);
}
String openingLine;
try {
openingLine = input.readLine();
while((openingLine==null)||openingLine.equals("")) { //$NON-NLS-1$
openingLine = input.readLine();
}
} catch(IOException e) {
e.printStackTrace();
return null;
}
try {
input.close();
} catch(IOException ex) {
ex.printStackTrace();
}
return openingLine;
}
/**
* Gets a unique name.
*
* @param proposed the proposed name
* @return the unique name
*/
protected String getUniqueTabName(String proposed) {
if((proposed==null)||proposed.equals("")) { //$NON-NLS-1$
proposed = ToolsRes.getString("DataToolTab.DefaultName"); //$NON-NLS-1$
}
// collect existing tab names
ArrayList<String> taken = new ArrayList<String>();
for(int i = 0; i<getTabCount(); i++) {
DataToolTab tab = getTab(i);
taken.add(tab.getName());
}
if(!taken.contains(proposed)) {
return proposed;
}
// strip existing numbered subscript if any
String subscript = TeXParser.getSubscript(proposed);
try {
Integer.parseInt(subscript);
proposed = TeXParser.removeSubscript(proposed);
} catch(Exception ex) {}
// construct a unique name from proposed by adding digit
proposed += "_"; //$NON-NLS-1$
int i = 1;
String name = proposed+i;
while(taken.contains(name)) {
i++;
name = proposed+i;
}
return name;
}
/**
* Loads self-contained Data objects from an XMLControl.
*
* @param control the XMLControl
* @param useChooser true to present data choices to user
*
* @return a list of self-contained Data objects
*/
private static ArrayList<Data> getSelfContainedData(XMLControl control, boolean useChooser) {
ArrayList<Data> dataList = new ArrayList<Data>();
java.util.List<XMLProperty> xmlControls;
// first get the Data XMLControls
if(useChooser) {
// get user-selected Data XMLControls from an xml tree chooser
XMLTreeChooser chooser = new XMLTreeChooser(ToolsRes.getString("Chooser.Title"), //$NON-NLS-1$
ToolsRes.getString("Chooser.Label"), null); //$NON-NLS-1$
xmlControls = chooser.choose(control, Data.class);
} else {
// get all Data XMLControls
XMLTree tree = new XMLTree(control);
tree.setHighlightedClass(Data.class);
tree.selectHighlightedProperties();
xmlControls = tree.getSelectedProperties();
if(xmlControls.isEmpty()) {
JOptionPane.showMessageDialog(null, ToolsRes.getString("Dialog.NoDatasets.Message")); //$NON-NLS-1$
}
}
// load the Data XMLControls and collect Data objects
HashSet<Integer> IDs = new HashSet<Integer>();
for(XMLProperty prop : xmlControls) {
XMLControl next = (XMLControl) prop;
Data data = null;
if(next instanceof XMLControlElement) {
XMLControlElement element = (XMLControlElement) next;
data = (Data) element.loadObject(null, true, true);
} else {
data = (Data) next.loadObject(null);
}
if(data!=null) {
for(Data nextData : getSelfContainedData(data)) {
// check IDs to prevent duplicates
Integer id = new Integer(nextData.getID());
if(!IDs.contains(id)) {
IDs.add(id);
dataList.add(nextData);
// remove any previously added Datasets within DatasetManagers
if(nextData instanceof DatasetManager) {
for(Dataset dataset : nextData.getDatasets()) {
dataList.remove(dataset);
id = new Integer(dataset.getID());
IDs.add(id);
}
}
}
}
}
}
return dataList;
}
/**
* Loads data from an XMLControl into one or more tabs.
*
* @param control the XMLControl describing the data
* @param useChooser true to present data choices to user
*
* @return a list of loaded tabs
*/
private ArrayList<DataToolTab> loadTabsFromXML(XMLControl control, boolean useChooser) {
ArrayList<DataToolTab> loadedTabs = new ArrayList<DataToolTab>();
ArrayList<Data> dataList = getSelfContainedData(control, useChooser);
for(Data next : dataList) {
loadedTabs.add(createTab(next));
}
return loadedTabs;
}
/**
* Constructs a dataset from independent xColumn and yColumn datasets.
*
* @param xColumn the dataset containing data for the x column
* @param yColumn the dataset containing data for the y column
* @return the x-y dataset
*/
public static Dataset createDatasetFromYPoints(Dataset xColumn, Dataset yColumn) {
Dataset dataset = new Dataset();
dataset.setXYColumnNames(xColumn.getYColumnName(), yColumn.getYColumnName());
// dataset.setConnected(true);
dataset.setLineColor(yColumn.getLineColor());
dataset.setMarkerShape(yColumn.getMarkerShape());
dataset.setMarkerColor(yColumn.getFillColor(), yColumn.getEdgeColor());
double[] xPoints = xColumn.getYPoints();
double[] yPoints = yColumn.getYPoints();
if (xPoints.length != yPoints.length) {
int len = Math.min(xPoints.length, yPoints.length);
double[] newPoints = new double[len];
for (int i = 0; i < len; i++) {
newPoints[i] = len<xPoints.length? xPoints[i]: yPoints[i];
}
if (len<xPoints.length)
xPoints = newPoints;
else yPoints = newPoints;
}
dataset.append(xPoints, yPoints);
return dataset;
}
/**
* Gets a list of Datasets from a self-contained source Data object.
*
* @param source the self-contained Data
* @return a list of Datasets
*/
public static ArrayList<Dataset> getDatasets(Data source) {
// if the source supplies datasets, return them
ArrayList<Dataset> datasets = source.getDatasets();
if(datasets!=null) {
return datasets;
}
// else create an empty dataset list and populate it from data2D
datasets = new ArrayList<Dataset>();
double[][] data2D = source.getData2D();
if((data2D==null)||(data2D.length==0)||(data2D[0]==null)) {
return datasets; // return empty list
}
// get column names--minimum 2
// colNames[0] will be x-column name
// colNames[1] and up will be y-column names
String[] colNames = source.getColumnNames();
// if colNames is null, create colNames
if(colNames==null) {
colNames = new String[2];
// if only one point set in data2D, then first column is named "n"
if(data2D.length==1) {
colNames[0] = "n"; //$NON-NLS-1$
}
}
int n = Math.max(2, data2D.length); // number of columns
if(colNames.length>n) {
n++;
}
colNames = getColumnNames(colNames, n);
// create datasets for double[] columns
// if more names than double[] columns, then first double[] is row numbers
boolean xPointsAreRowNumbers = colNames.length>data2D.length;
double[] xPoints = xPointsAreRowNumbers ? getRowArray(data2D[0].length) : data2D[0];
for(int i = 1; i<colNames.length; i++) {
double[] yPoints = xPointsAreRowNumbers ? data2D[i-1] : data2D[i];
Dataset dataset = createDataset(xPoints, yPoints, colNames[0], colNames[i], i, source);
if(dataset!=null) {
datasets.add(dataset);
}
}
return datasets;
}
/**
* Gets a list of all Datasets from any Data object.
*
* @param source a self-contained or container Data object
* @return a list of all Datasets
*/
public static ArrayList<Dataset> getAllDatasets(Data source) {
ArrayList<Dataset> datasets = new ArrayList<Dataset>();
for(Data next : getSelfContainedData(source)) {
datasets.addAll(getDatasets(next));
}
return datasets;
}
/**
* Gets a list of self-contained Data objects.
*
* @param container the container Data
*
* @return a list of self-contained Data objects
*/
protected static ArrayList<Data> getSelfContainedData(Data container) {
processedData.clear();
ArrayList<Data> list = getSelfContainedDataWithTrap(container);
return list;
}
/**
* Gets a list of DataColumns from a self-contained Data object.
*
* @param source a self-contained Data object
* @return a list of DataColumns
*/
protected static ArrayList<DataColumn> getDataColumns(Data source) {
if(!isSelfContained(source)) {
return null;
}
ArrayList<DataColumn> columns = new ArrayList<DataColumn>();
// look for datasets in Data
ArrayList<Dataset> datasetList = source.getDatasets();
if (datasetList!=null) {
for (Dataset next : datasetList) {
// get new columns from next dataset
ArrayList<DataColumn> newColumns = createDataColumns(next);
for (DataColumn newCol: newColumns) {
// add new columns that are not exact duplicates of existing columns
boolean isDup = false;
for (DataColumn existing: columns) {
if (existing.getYColumnName().equals(newCol.getYColumnName())) {
double[] exPts = existing.getYPoints();
double[] nextPts = newCol.getYPoints();
if (exPts.length == nextPts.length) {
isDup = true;
for (int i = 0; i < exPts.length; i++) {
isDup = exPts[i]==nextPts[i] && isDup;
}
}
}
}
if (!isDup)
columns.add(newCol);
}
}
}
else {
double[][] data2D = source.getData2D();
if((data2D==null)||(data2D.length==0)||(data2D[0]==null)) {
return null;
}
// get column names
String[] colNames = source.getColumnNames();
// if colNames is null, create colNames
if(colNames==null) {
colNames = new String[2];
// if only one point set in data2D, then first column is named "n"
if(data2D.length==1) {
colNames[0] = "n"; //$NON-NLS-1$
}
}
int n = Math.max(2, data2D.length); // number of columns: minimum 2
if(colNames.length>n) {
n++;
}
colNames = getColumnNames(colNames, n);
// create a data column for each name
// if more names than double[] columns, then first double[] is row numbers
boolean includeRows = colNames.length>data2D.length;
int index = data2D[0].length;
for(int i = 0; i<data2D.length; i++) {
if(data2D[i]!=null) {
index = Math.max(index, data2D[i].length);
}
}
for(int i = 0; i<colNames.length; i++) {
double[] colData = includeRows ? (i==0) ? getRowArray(index) : data2D[i-1] : data2D[i];
DataColumn dataset = createDataColumn(colData, colNames[i], i, source);
if(dataset!=null) {
columns.add(dataset);
}
}
}
return columns;
}
/**
* Gets a list of all DataColumns from any Data object.
*
* @param source a self-contained or container Data object
* @return a list of all DataColumns
*/
protected static ArrayList<DataColumn> getAllDataColumns(Data source) {
ArrayList<DataColumn> columns = new ArrayList<DataColumn>();
for(Data next : getSelfContainedData(source)) {
columns.addAll(getDataColumns(next));
}
return columns;
}
/**
* Gets an array of column names.
*
* @param proposed the proposed names
* @param nameCount the required number of names
* @return the column names
*/
private static String[] getColumnNames(String[] proposed, int nameCount) {
String[] colNames = proposed;
if(colNames.length!=nameCount) {
colNames = new String[nameCount];
int len = Math.min(proposed.length, colNames.length);
System.arraycopy(proposed, 0, colNames, 0, len);
}
// deal with null names
ArrayList<String> taken = new ArrayList<String>();
char c = 'A';
for(int i = 0; i<nameCount; i++) {
String next = colNames[i];
if((next!=null)&&!taken.contains(next)) {
taken.add(next);
continue;
}
if(next==null) {
// replace null names with capital letters
next = String.valueOf(c++);
while(taken.contains(next)) {
next = String.valueOf(c++);
}
colNames[i] = next;
taken.add(next);
}
}
return colNames;
}
/**
* Gets a list of Data objects which provide actual data. Each Data object
* in the list is suitable for adding to a tab. This method traps for duplicate
* Data objects and prevents infinite loops.
*
* @param source the source Data object
* @return a list of data-producing Data objects
*/
private static ArrayList<Data> getSelfContainedDataWithTrap(Data source) {
ArrayList<Data> list = new ArrayList<Data>();
if((source==null)||processedData.contains(source)) {
return list;
}
processedData.add(source);
if(isSelfContained(source)) {
list.add(source);
} else {
for(Data next : source.getDataList()) {
ArrayList<Data> subList = getSelfContainedDataWithTrap(next);
list.addAll(subList);
}
}
return list;
}
/**
* Determines if a Data object is self-contained.
*
* @param data the Data object
* @return true if self-contained
*/
private static boolean isSelfContained(Data data) {
return data.getDataList()==null;
}
/**
* Creates data columns from the visible columns of a dataset.
*
* @param source the source dataset
* @return a list of data columns
*/
private static ArrayList<DataColumn> createDataColumns(Dataset source) {
ArrayList<DataColumn> columns = new ArrayList<DataColumn>();
if(source instanceof DataColumn) {
columns.add((DataColumn) source);
return columns;
}
String[] colNames = source.getColumnNames();
String rowName = "row"; //$NON-NLS-1$
for(int i = 0; i<2; i++) {
if((i==0)&&!source.isXColumnVisible()) {
continue;
}
if((i==1)&&!source.isYColumnVisible()) {
continue;
}
DataColumn column = new DataColumn();
column.setName(source.getName());
column.setXYColumnNames(rowName, colNames[i]);
column.setConnected(source.isConnected());
column.setLineColor(source.getLineColor());
column.setMarkerSize(source.getMarkerSize());
column.setMarkerShape(source.getMarkerShape());
column.setMarkerColor(source.getFillColor(), source.getLineColor());
column.setID(source.getID());
column.setColumnID(i);
column.setPoints((i==0) ? source.getXPoints() : source.getYPoints());
column.setXColumnVisible(false);
columns.add(column);
}
return columns;
}
/**
* Creates a data column from a double[] of data.
*
* @param data the data points
* @param columnName the name of the column
* @param columnID the column ID to assign the column
* @param source the Data source providing the ID and colors
* @return the data column dataset
*/
private static DataColumn createDataColumn(double[] data, String columnName, int columnID, Data source) {
if(data==null) {
return null;
}
DataColumn column = new DataColumn();
column.setXYColumnNames("row", columnName); //$NON-NLS-1$
column.setConnected(true);
Color[] lineColors = source.getLineColors();
if((lineColors!=null)&&(lineColors[columnID]!=null)) {
column.setLineColor(lineColors[columnID]);
} else {
column.setLineColor(DisplayColors.getLineColor(columnID));
}
column.setMarkerShape(Dataset.SQUARE);
Color[] fillColors = source.getFillColors();
if((lineColors!=null)&&(lineColors[columnID]!=null)&&(fillColors!=null)&&(fillColors[columnID]!=null)) {
column.setMarkerColor(fillColors[columnID], lineColors[columnID]);
} else {
column.setMarkerColor(DisplayColors.getMarkerColor(columnID), DisplayColors.getLineColor(columnID));
}
column.setID(source.getID());
column.setColumnID(columnID);
column.setPoints(data);
return column;
}
/**
* Creates a dataset from double[] of data.
*
* @param xPoints the x points
* @param yPoints the y points
* @param xName the name of the x column
* @param yName the name of the y column
* @param columnID the column ID
* @param source the Data source providing the ID and colors
* @return the dataset
*/
private static Dataset createDataset(double[] xPoints, double[] yPoints, String xName, String yName, int columnID, Data source) {
if(yPoints==null) {
return null;
}
Dataset dataset = new Dataset();
dataset.setXYColumnNames(xName, yName);
dataset.setConnected(true);
Color[] lineColors = source.getLineColors();
if((lineColors!=null)&&(lineColors[columnID]!=null)) {
dataset.setLineColor(lineColors[columnID]);
} else {
dataset.setLineColor(DisplayColors.getLineColor(columnID));
}
dataset.setMarkerShape(Dataset.SQUARE);
Color[] fillColors = source.getFillColors();
if((lineColors!=null)&&(lineColors[columnID]!=null)&&(fillColors!=null)&&(fillColors[columnID]!=null)) {
dataset.setMarkerColor(fillColors[columnID], lineColors[columnID]);
} else {
dataset.setMarkerColor(DisplayColors.getMarkerColor(columnID), DisplayColors.getLineColor(columnID));
}
dataset.setID(source.getID());
dataset.append(xPoints, yPoints);
return dataset;
}
/**
* Copies a dataset. If includeDataAndID is false, only the name and
* display properties are copied.
*
* @param source the source dataset
* @param target the target dataset (may be null)
* @param includeDataAndID true to copy data and ID
* @return the copy
*/
public static Dataset copyDataset(Dataset source, Dataset target, boolean includeDataAndID) {
if(target==null) {
target = new Dataset();
}
if(includeDataAndID) {
target.clear();
double[] x = source.getXPoints();
double[] y = source.getYPoints();
target.append(x, y);
target.setID(source.getID());
}
target.setName(source.getName());
target.setXYColumnNames(source.getXColumnName(), source.getYColumnName());
target.setMarkerShape(source.getMarkerShape());
target.setMarkerSize(source.getMarkerSize());
Color fill = source.getFillColor();
Color edge = source.getEdgeColor();
target.setMarkerColor(fill, edge);
target.setLineColor(source.getLineColor());
target.setConnected(source.isConnected());
target.setXColumnVisible(source.isXColumnVisible());
target.setYColumnVisible(source.isYColumnVisible());
return target;
}
/**
* Inserts a specified value into an array.
*
* @param input the value to insert
* @param array the array into which the value is inserted
* @param trend positive if array is ascending, negative if descending, 0 if neither
* @return an array containing the inserted value
*/
protected static double[] insert(double input, double[] array, int trend) {
int n = array.length;
double[] newArray = new double[n+1];
if(trend==0) { // append at end
System.arraycopy(array, 0, newArray, 0, n);
newArray[n] = input;
} else if(trend>0) { // append before first array value larger than input
for(int i = 0; i<n; i++) {
if(input<array[i]) {
System.arraycopy(array, 0, newArray, 0, i);
System.arraycopy(array, i, newArray, i+1, n-i);
newArray[i] = input;
return newArray;
}
}
// if newArray not yet returned, then append at end
System.arraycopy(array, 0, newArray, 0, n);
newArray[n] = input;
} else { // append before first array value smaller than input
for(int i = 0; i<n; i++) {
if(input>array[i]) {
System.arraycopy(array, 0, newArray, 0, i);
System.arraycopy(array, i, newArray, i+1, n-i);
newArray[i] = input;
return newArray;
}
}
// if newArray not yet returned, then append at end
System.arraycopy(array, 0, newArray, 0, n);
newArray[n] = input;
}
return newArray;
}
/**
* Adds a tab. The tab should be named before calling this method.
*
* @param tab a DataToolTab
*/
public void addTab(final DataToolTab tab) {
// remove single empty tab, if any
if(getTabCount()==1) {
DataToolTab prev = getTab(0);
if(prev.originatorID==0) {
prev.tabChanged(false);
removeTab(0, false);
}
}
tab.dataTool = this;
// assign a unique name (also traps for null name)
tab.setName(getUniqueTabName(tab.getName()));
OSPLog.finer("adding tab "+tab.getName()); //$NON-NLS-1$
tabbedPane.addTab("", tab); //$NON-NLS-1$
tab.setFontLevel(FontSizer.getLevel());
tabbedPane.setSelectedComponent(tab);
// validate();
refreshTabTitles();
refreshMenubar();
}
/**
* Offers to save changes to the tab at the specified index.
*
* @param i the tab index
* @return true unless canceled by the user
*/
protected boolean saveChangesAt(int i) {
if(OSPRuntime.appletMode) {
return true;
}
DataToolTab tab = getTab(i);
if(!tab.tabChanged) {
return true;
}
String name = tab.getName();
if(ToolsRes.getString("DataToolTab.DefaultName").equals(name) //$NON-NLS-1$
&&(tab.originatorID==0)) {
return true;
}
int selected = JOptionPane.showConfirmDialog(this, ToolsRes.getString("DataTool.Dialog.SaveChanges.Message1")+ //$NON-NLS-1$
" \""+name+"\" "+ //$NON-NLS-1$ //$NON-NLS-2$
ToolsRes.getString("DataTool.Dialog.SaveChanges.Message2"), //$NON-NLS-1$
ToolsRes.getString("DataTool.Dialog.SaveChanges.Title"), //$NON-NLS-1$
JOptionPane.YES_NO_CANCEL_OPTION);
if(selected==JOptionPane.CANCEL_OPTION) {
return false;
}
if(selected==JOptionPane.YES_OPTION) {
// save root and all owned nodes
if(save(tab, tab.fileName)==null) {
return false;
}
}
return true;
}
/**
* Gets the currently selected DataToolTab, if any.
*
* @return the selected tab
*/
public DataToolTab getSelectedTab() {
return(DataToolTab) tabbedPane.getSelectedComponent();
}
/**
* Selects a DataToolTab.
*
* @param tab the tab to select
*/
public void setSelectedTab(DataToolTab tab) {
tabbedPane.setSelectedComponent(tab);
}
/**
* Clears data by removing all tabs.
*/
public void clearData() {
removeAllTabs();
}
/**
* Sets the font level.
*
* @param level the level
*/
public void setFontLevel(int level) {
if (getJMenuBar()==null) return;
super.setFontLevel(level);
FontSizer.setFonts(emptyMenubar, level);
FontSizer.setFonts(fileMenu, level);
FontSizer.setFonts(editMenu, level);
double factor = FontSizer.getFactor(level);
buttonHeight = (int) (factor*defaultButtonHeight);
if(tabbedPane!=null) {
for(int i = 0; i<getTabCount(); i++) {
getTab(i).setFontLevel(level);
}
}
if(dataBuilder!=null) {
dataBuilder.setFontLevel(level);
}
if (fontSizeGroup!=null) {
Enumeration<AbstractButton> e = fontSizeGroup.getElements();
for (; e.hasMoreElements();) {
AbstractButton button = e.nextElement();
int i = Integer.parseInt(button.getActionCommand());
if(i==FontSizer.getLevel()) {
button.setSelected(true);
}
}
}
FontSizer.setFonts(OSPLog.getOSPLog(), level);
}
@Override
public void setVisible(boolean vis) {
// set preferred size the first time shown
if (contentPane.getPreferredSize().equals(dim)) {
double f = 1+(0.2*FontSizer.getLevel());
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
int w = Math.min(screen.width-40, (int)(dim.width*f));
int h = Math.min(screen.height-100, (int)(dim.height*f));
// add one pixel to width so no longer equals dim
contentPane.setPreferredSize(new Dimension(w, h+1));
pack();
// center this on the screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width-getBounds().width)/2;
int y = (dim.height-getBounds().height)/2;
setLocation(x, y);
}
super.setVisible(vis);
}
/**
* Gets the fit builder.
* @return the fit builder
*/
public FitBuilder getFitBuilder() {
if(fitBuilder==null) {
fitBuilder = new FitBuilder(this);
fitBuilder.setFontLevel(FontSizer.getLevel());
fitBuilder.setHelpPath("fit_builder_help.html"); //$NON-NLS-1$
}
return fitBuilder;
}
/**
* Writes text to a file with the specified name.
*
* @param text the text
* @param fileName the file name
* @return the path of the saved document or null if failed
*/
protected static String write(String text, String fileName) {
int n = fileName.lastIndexOf("/"); //$NON-NLS-1$
if(n<0) {
n = fileName.lastIndexOf("\\"); //$NON-NLS-1$
}
if(n>0) {
String dir = fileName.substring(0, n+1);
File file = new File(dir);
if(!file.exists()&&!file.mkdir()) {
return null;
}
}
try {
File file = new File(fileName);
// check to see if file already exists
if(file.exists()) {
if(!file.canWrite()) {
JOptionPane.showMessageDialog(null,
ControlsRes.getString("Dialog.ReadOnly.Message"), //$NON-NLS-1$
ControlsRes.getString("Dialog.ReadOnly.Title"), //$NON-NLS-1$
JOptionPane.PLAIN_MESSAGE);
return null;
}
int selected = JOptionPane.showConfirmDialog(null, ToolsRes.getString("Tool.Dialog.ReplaceFile.Message")+" "+file.getName()+"?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
ToolsRes.getString("Tool.Dialog.ReplaceFile.Title"), //$NON-NLS-1$
JOptionPane.YES_NO_CANCEL_OPTION);
if(selected!=JOptionPane.YES_OPTION) {
return null;
}
}
FileOutputStream stream = new FileOutputStream(file);
java.nio.charset.Charset charset = java.nio.charset.Charset.forName("UTF-8"); //$NON-NLS-1$
write(text, new OutputStreamWriter(stream, charset));
if(file.exists()) {
return file.getAbsolutePath();
}
} catch(IOException ex) {
ex.printStackTrace();
}
return null;
}
/**
* Writes text to a Writer.
*
* @param text the text
* @param out the Writer
*/
protected static void write(String text, Writer out) {
try {
Writer output = new BufferedWriter(out);
output.write(text);
output.flush();
output.close();
} catch(IOException ex) {
ex.printStackTrace();
}
}
/**
* Opens an xml or data file selected with a chooser.
*
* @return the name of the opened file
*/
protected String open() {
int result = OSPRuntime.getChooser().showOpenDialog(null);
if(result==JFileChooser.APPROVE_OPTION) {
OSPRuntime.chooserDir = OSPRuntime.getChooser().getCurrentDirectory().toString();
String fileName = OSPRuntime.getChooser().getSelectedFile().getAbsolutePath();
fileName = XML.getRelativePath(fileName);
return open(fileName);
}
return null;
}
/**
* Imports an xml or data file selected with a chooser into a specified tab.
*
* @param tab the tab to import into
* @return the name of the imported file
*/
protected String importFileIntoTab(DataToolTab tab) {
int result = OSPRuntime.getChooser().showOpenDialog(tab);
if(result==JFileChooser.APPROVE_OPTION) {
OSPRuntime.chooserDir = OSPRuntime.getChooser().getCurrentDirectory().toString();
String fileName = OSPRuntime.getChooser().getSelectedFile().getAbsolutePath();
fileName = XML.getRelativePath(fileName);
return importFileIntoTab(tab, fileName);
}
return null;
}
/**
* Saves the current tab to the specified file.
*
* @param fileName the file name
* @return the name of the saved file, or null if not saved
*/
protected String save(String fileName) {
return save(getSelectedTab(), fileName);
}
/**
* Saves a tab to the specified file.
*
* @param tab the tab
* @param fileName the file name
* @return the name of the saved file, or null if not saved
*/
protected String save(DataToolTab tab, String fileName) {
if((fileName==null)||fileName.equals("")) { //$NON-NLS-1$
return saveAs();
}
XMLControl control = new XMLControlElement(tab);
if(control.write(fileName)==null) {
return null;
}
tab.fileName = fileName;
tab.tabChanged(false);
return fileName;
}
/**
* Saves the current tab to a file selected with a chooser.
*
* @return the name of the saved file, or null if not saved
*/
protected String saveAs() {
int result = OSPRuntime.getChooser().showSaveDialog(this);
if(result==JFileChooser.APPROVE_OPTION) {
OSPRuntime.chooserDir = OSPRuntime.getChooser().getCurrentDirectory().toString();
File file = OSPRuntime.getChooser().getSelectedFile();
// check to see if file already exists
if(file.exists()) {
int selected = JOptionPane.showConfirmDialog(null, ToolsRes.getString("Tool.Dialog.ReplaceFile.Message")+" "+file.getName()+"?", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
ToolsRes.getString("Tool.Dialog.ReplaceFile.Title"), //$NON-NLS-1$
JOptionPane.YES_NO_CANCEL_OPTION);
if(selected!=JOptionPane.YES_OPTION) {
return null;
}
}
String fileName = file.getAbsolutePath();
if((fileName==null)||fileName.trim().equals("")) { //$NON-NLS-1$
return null;
}
// add .xml extension if none but don't require it
if(XML.getExtension(fileName)==null) {
fileName += ".xml"; //$NON-NLS-1$
}
return save(XML.getRelativePath(fileName));
}
return null;
}
/**
* Returns the index of the tab containing the specified Data object.
*
* @param data the Data
* @return the index, or -1 if not found
*/
protected int getTabIndex(Data data) {
for(int i = 0; i<tabbedPane.getTabCount(); i++) {
DataToolTab tab = (DataToolTab) tabbedPane.getComponentAt(i);
if(tab.isOwnedBy(data)) {
return i;
}
}
return -1;
}
/**
* Returns the index of a specified tab.
*
* @param tab the tab
* @return the index, or -1 if not found
*/
protected int getTabIndex(DataToolTab tab) {
for(int i = 0; i<tabbedPane.getTabCount(); i++) {
if(tab==tabbedPane.getComponentAt(i)) {
return i;
}
}
return -1;
}
/**
* Constructs a DataTool with title and name.
* @param title
* @param name
*/
protected DataTool(String title, String name) {
super(title);
setName(name);
createGUI();
Toolbox.addTool(name, this);
ToolsRes.addPropertyChangeListener("locale", new PropertyChangeListener() { //$NON-NLS-1$
public void propertyChange(PropertyChangeEvent e) {
refreshGUI();
}
});
}
/**
* Removes all tabs except the specified index.
*
* @param index the tab number
* @return true if tabs removed
*/
protected boolean removeAllButTab(int index) {
for(int i = tabbedPane.getTabCount()-1; i>=0; i--) {
if(i==index) {
continue;
}
if(!saveChangesAt(i)) {
return false;
}
String title = tabbedPane.getTitleAt(i);
OSPLog.finer("removing tab "+title); //$NON-NLS-1$
DataToolTab tab = getTab(i);
fitBuilder.curveFitters.remove(tab.curveFitter);
fitBuilder.removePropertyChangeListener(tab.curveFitter.fitListener);
tabbedPane.removeTabAt(i);
}
refreshTabTitles();
refreshDataBuilder();
return true;
}
/**
* Removes all tabs.
*
* @return true if all tabs removed
*/
protected boolean removeAllTabs() {
for(int i = tabbedPane.getTabCount()-1; i>=0; i--) {
if(!saveChangesAt(i)) {
return false;
}
String title = tabbedPane.getTitleAt(i);
OSPLog.finer("removing tab "+title); //$NON-NLS-1$
DataToolTab tab = getTab(i);
fitBuilder.curveFitters.remove(tab.curveFitter);
fitBuilder.removePropertyChangeListener(tab.curveFitter.fitListener);
tabbedPane.removeTabAt(i);
}
refreshMenubar();
refreshDataBuilder();
return true;
}
protected void refreshTabTitles() {
// show variables being plotted
String[] tabTitles = new String[tabbedPane.getTabCount()];
for(int i = 0; i<tabTitles.length; i++) {
DataToolTab tab = (DataToolTab) tabbedPane.getComponentAt(i);
String dataName = tab.getName();
tabTitles[i] = dataName;
}
// set tab titles
for(int i = 0; i<tabTitles.length; i++) {
tabbedPane.setTitleAt(i, tabTitles[i]);
}
}
protected void refreshMenubar() {
if(getTabCount()==0) {
emptyMenubar.add(displayMenu);
emptyMenubar.add(helpMenu);
setJMenuBar(emptyMenubar);
} else {
menubar.add(displayMenu);
menubar.add(helpMenu);
setJMenuBar(menubar);
}
}
/**
* Gets the data builder for defining custom data functions.
* @return the data builder
*/
protected FunctionTool getDataBuilder() {
if(dataBuilder==null) { // create new tool if none exists
dataBuilder = new DataBuilder(this);
dataBuilder.setFontLevel(FontSizer.getLevel());
dataBuilder.addPropertyChangeListener("function", this); //$NON-NLS-1$
}
refreshDataBuilder();
return dataBuilder;
}
/**
* Refreshes the data builder.
*/
protected void refreshDataBuilder() {
if(dataBuilder!=null) {
dataBuilder.refreshPanels();
}
}
/**
* Copies text to the clipboard.
*
* @param text the string to copy
*/
protected static void copy(String text) {
StringSelection data = new StringSelection(text);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(data, data);
}
/**
* Pastes from the clipboard and returns the pasted string.
*
* @return the pasted string, or null if none
*/
public static String paste() {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable data = clipboard.getContents(null);
if((data!=null)&&data.isDataFlavorSupported(DataFlavor.stringFlavor)) {
try {
String text = (String) data.getTransferData(DataFlavor.stringFlavor);
return text;
} catch(Exception ex) {
ex.printStackTrace();
}
}
return null;
}
/**
* Shows the DataTool help.
*/
protected static void showHelp() {
String fileName = helpName;
String helpPath = XML.getResolvedPath(fileName, helpBase);
if (ResourceLoader.getResource(helpPath)!=null) {
// show help in desktop browser
OSPDesktop.displayURL(helpPath);
}
else {
fileName = "data_tool_help.html"; //$NON-NLS-1$
String classBase = "/org/opensourcephysics/resources/tools/html/"; //$NON-NLS-1$
helpPath = XML.getResolvedPath(fileName, classBase);
if ((helpFrame==null)||!helpPath.equals(helpFrame.getTitle())) {
helpFrame = new TextFrame(helpPath);
helpFrame.enableHyperlinks();
helpFrame.setSize(800, 600);
// center on the screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width-helpFrame.getBounds().width)/2;
int y = (dim.height-helpFrame.getBounds().height)/2;
helpFrame.setLocation(x, y);
}
helpFrame.setVisible(true);
}
}
/**
* Overrides OSPFrame method. This converts EXIT_ON_CLOSE to
* DO_NOTHING_ON_CLOSE and sets the exitOnClose flag.
*
* @param operation the operation
*/
public void setDefaultCloseOperation(int operation) {
if((operation==JFrame.EXIT_ON_CLOSE)) {
exitOnClose = true;
operation = WindowConstants.DO_NOTHING_ON_CLOSE;
}
if((operation!=WindowConstants.DO_NOTHING_ON_CLOSE)) {
saveChangesOnClose = false;
}
super.setDefaultCloseOperation(operation);
}
/**
* Creates the GUI.
*/
protected void createGUI() {
// configure the frame
// set preferred size
double f = 1+0.25*FontSizer.getLevel();
Dimension used = new Dimension((int)(dim.width*f), (int)(dim.height*f));
contentPane.setPreferredSize(used);
setContentPane(contentPane);
JPanel centerPanel = new JPanel(new BorderLayout());
contentPane.add(centerPanel, BorderLayout.CENTER);
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
// add window listener to exit
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
exitItem.doClick(0);
}
});
// this.addComponentListener(new ComponentAdapter() {
// public void componentResized(ComponentEvent e) {
// DataToolTab tab = getSelectedTab();
// if(tab==null) {
// return;
// }
// if(!tab.propsCheckbox.isSelected()&&!tab.statsCheckbox.isSelected()) {
// tab.splitPanes[2].setDividerLocation(0);
// }
// }
//
// });
// create tabbed pane
tabbedPane = new JTabbedPane(SwingConstants.TOP);
centerPanel.add(tabbedPane, BorderLayout.CENTER);
tabbedPane.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
final DataToolTab tab = getSelectedTab();
if(tab!=null) {
tab.refreshData();
tab.dataTable.refreshTable();
tab.statsTable.refreshStatistics();
tab.propsTable.refreshTable();
tab.refreshPlot();
refreshGUI();
tab.dataTable.requestFocusInWindow();
if(tab.dataTable.workingData!=null) {
String var = tab.dataTable.workingData.getXColumnName();
var = TeXParser.removeSubscripting(var);
fitBuilder.setDefaultVariables(new String[] {var});
}
}
}
});
tabbedPane.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if(OSPRuntime.isPopupTrigger(e)) {
final int index = tabbedPane.getSelectedIndex();
// make popup with name change, clone and close items
JPopupMenu popup = new JPopupMenu();
JMenuItem item = new JMenuItem(ToolsRes.getString("DataTool.MenuItem.Name")); //$NON-NLS-1$
popup.add(item);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DataToolTab tab = getTab(index);
String name = tab.getName();
Object input = JOptionPane.showInputDialog(DataTool.this, ToolsRes.getString("DataTool.Dialog.Name.Message"), //$NON-NLS-1$
ToolsRes.getString("DataTool.Dialog.Name.Title"), //$NON-NLS-1$
JOptionPane.QUESTION_MESSAGE, null, null, name);
if(input==null) {
return;
}
// hide tab name so getUniqueTabName() not confused
tab.setName(""); //$NON-NLS-1$
tab.setName(getUniqueTabName(input.toString()));
tab.tabChanged(true);
refreshTabTitles();
refreshDataBuilder();
}
});
if(!getTab(index).dataManager.getDatasets().isEmpty()) {
popup.addSeparator();
item = new JMenuItem(ToolsRes.getString("DataTool.MenuItem.NewTab")); //$NON-NLS-1$
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
newTabItem.doClick(0);
}
});
popup.add(item);
JMenu cloneMenu = new JMenu(ToolsRes.getString("DataTool.Menu.Clone")); //$NON-NLS-1$
popup.add(cloneMenu);
final JMenuItem cloneTabItem = new JMenuItem(ToolsRes.getString("DataTool.MenuItem.Editable")); //$NON-NLS-1$
cloneMenu.add(cloneTabItem);
cloneTabItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// determine name of cloned tab
String name = getTab(index).getName();
String postfix = "_"+ToolsRes.getString("DataTool.Clone.Subscript"); //$NON-NLS-1$ //$NON-NLS-2$
int n = name.indexOf(postfix);
if(n>-1) {
name = name.substring(0, n);
}
name = name+postfix;
name = getUniqueTabName(name);
copyTabItem.doClick(0);
pasteTabItem.doClick(0);
getTab(getTabCount()-1).setName(name);
refreshTabTitles();
}
});
item = new JMenuItem(ToolsRes.getString("DataTool.MenuItem.Noneditable")); //$NON-NLS-1$
cloneMenu.add(item);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
cloneTabItem.doClick(0);
DataToolTab tab = getTab(getTabCount()-1);
tab.setUserEditable(false);
for(Dataset next : tab.dataManager.getDatasets()) {
if(next instanceof DataColumn) {
((DataColumn) next).deletable = false;
}
}
}
});
}
popup.addSeparator();
item = new JMenuItem(ToolsRes.getString("MenuItem.Close")); //$NON-NLS-1$
popup.add(item);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeTab(index, true);
}
});
item = new JMenuItem(ToolsRes.getString("MenuItem.CloseOthers")); //$NON-NLS-1$
popup.add(item);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeAllButTab(index);
}
});
item = new JMenuItem(ToolsRes.getString("MenuItem.CloseAll")); //$NON-NLS-1$
popup.add(item);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeAllTabs();
}
});
FontSizer.setFonts(popup, FontSizer.getLevel());
popup.show(tabbedPane, e.getX(), e.getY()+8);
}
}
});
int keyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
// create regular menubar
menubar = new JMenuBar();
fileMenu = new JMenu();
menubar.add(fileMenu);
MouseAdapter fileMenuChecker = new MouseAdapter() {
public void mouseEntered(MouseEvent e) {
mousePressed(e);
}
public void mousePressed(MouseEvent e) {
boolean empty = getSelectedTab().originatorID==0;
if(!OSPRuntime.appletMode) {
exportItem.setEnabled(!empty);
saveItem.setEnabled(!empty);
saveAsItem.setEnabled(!empty);
int[] selectedRows = getSelectedTab().dataTable.getSelectedRows();
int endRow = getSelectedTab().dataTable.getRowCount()-1;
if((selectedRows.length==0)||((selectedRows.length==1)&&(selectedRows[0]==endRow)&&getSelectedTab().dataTable.isEmptyRow(endRow))) {
exportItem.setText(ToolsRes.getString("DataTool.MenuItem.Export")); //$NON-NLS-1$
} else {
exportItem.setText(ToolsRes.getString("DataTool.MenuItem.ExportSelection")); //$NON-NLS-1$
}
}
}
};
fileMenu.addMouseListener(fileMenuChecker);
newTabItem = new JMenuItem();
newTabItem.setAccelerator(KeyStroke.getKeyStroke('N', keyMask));
newTabItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DataToolTab tab = createTab(null);
tab.userEditable = true;
addTab(tab);
tab.refreshGUI();
}
});
fileMenu.add(newTabItem);
if(!OSPRuntime.appletMode) {
openItem = new JMenuItem();
openItem.setAccelerator(KeyStroke.getKeyStroke('O', keyMask));
openItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
open();
}
});
fileMenu.add(openItem);
}
fileMenu.addSeparator();
closeItem = new JMenuItem();
closeItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int index = tabbedPane.getSelectedIndex();
removeTab(index, true);
}
});
fileMenu.add(closeItem);
closeAllItem = new JMenuItem();
closeAllItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeAllTabs();
}
});
fileMenu.add(closeAllItem);
fileMenu.addSeparator();
if(!OSPRuntime.appletMode) {
importItem = new JMenuItem();
importItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DataToolTab tab = getSelectedTab();
importFileIntoTab(tab);
}
});
fileMenu.add(importItem);
exportItem = new JMenuItem();
exportItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
getSelectedTab().saveTableDataToFile();
}
});
fileMenu.add(exportItem);
fileMenu.addSeparator();
// save item
saveItem = new JMenuItem();
saveItem.setAccelerator(KeyStroke.getKeyStroke('S', keyMask));
saveItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DataToolTab tab = getSelectedTab();
save(tab.fileName);
}
});
fileMenu.add(saveItem);
// save as item
saveAsItem = new JMenuItem();
saveAsItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
saveAs();
}
});
fileMenu.add(saveAsItem);
fileMenu.addSeparator();
}
printItem = new JMenuItem();
printItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SnapshotTool.getTool().printImage(DataTool.this);
}
});
printItem.setAccelerator(KeyStroke.getKeyStroke('P', keyMask));
fileMenu.add(printItem);
fileMenu.addSeparator();
exitItem = new JMenuItem();
exitItem.setAccelerator(KeyStroke.getKeyStroke('Q', keyMask));
exitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(!saveChangesOnClose||removeAllTabs()) {
if(exitOnClose) {
System.exit(0);
} else {
setVisible(false);
}
}
}
});
fileMenu.add(exitItem);
editMenu = new JMenu();
// create mouse listener to prepare edit menu
MouseAdapter editMenuChecker = new MouseAdapter() {
public void mouseEntered(MouseEvent e) {
mousePressed(e);
}
public void mousePressed(MouseEvent e) {
// ignore until menu is displayed
if(!editMenu.isPopupMenuVisible()&&!emptyEditMenu.isPopupMenuVisible()) {
return;
}
DataToolTab tab = getSelectedTab();
// undo and redo items
if(tab!=null) {
undoItem.setEnabled(tab.undoManager.canUndo());
redoItem.setEnabled(tab.undoManager.canRedo());
}
// enable paste menu if clipboard contains pastable data
boolean enabled = hasPastableData();
emptyPasteMenu.setEnabled(enabled);
pasteMenu.setEnabled(enabled);
// prepare copy menu
copyMenu.removeAll();
if(tab!=null) {
ArrayList<Dataset> list = tab.dataManager.getDatasets();
copyDataItem.setEnabled(!list.isEmpty());
if(!list.isEmpty()) {
copyTabItem.setText(ToolsRes.getString("DataTool.MenuItem.CopyTab")); //$NON-NLS-1$
copyMenu.add(copyTabItem);
copyMenu.addSeparator();
String s = ToolsRes.getString("DataTool.MenuItem.CopyData"); //$NON-NLS-1$
int[] selectedRows = getSelectedTab().dataTable.getSelectedRows();
int endRow = getSelectedTab().dataTable.getRowCount()-1;
boolean emptySelection = (selectedRows.length==1)&&(selectedRows[0]==endRow)&&getSelectedTab().dataTable.isEmptyRow(endRow);
if((selectedRows.length>0)&&!emptySelection) {
s = ToolsRes.getString("DataTool.MenuItem.CopySelectedData"); //$NON-NLS-1$
}
copyDataItem.setText(s);
copyMenu.add(copyDataItem);
copyMenu.addSeparator();
}
}
copyMenu.add(copyImageItem);
FontSizer.setFonts(copyMenu, FontSizer.getLevel());
}
};
editMenu.addMouseListener(editMenuChecker);
menubar.add(editMenu);
// undo and redo items
undoItem = new JMenuItem();
undoItem.setEnabled(false);
undoItem.setAccelerator(KeyStroke.getKeyStroke('Z', keyMask));
undoItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (getSelectedTab().undoManager.canUndo()) {
getSelectedTab().undoManager.undo();
}
}
});
editMenu.add(undoItem);
redoItem = new JMenuItem();
redoItem.setEnabled(false);
redoItem.setAccelerator(KeyStroke.getKeyStroke('Y', keyMask));
redoItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (getSelectedTab().undoManager.canRedo()) {
getSelectedTab().undoManager.redo();
}
}
});
editMenu.add(redoItem);
editMenu.addSeparator();
// copy menu
copyMenu = new JMenu();
editMenu.add(copyMenu);
copyTabItem = new JMenuItem();
copyTabItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int i = tabbedPane.getSelectedIndex();
String title = tabbedPane.getTitleAt(i);
OSPLog.finest("copying tab "+title); //$NON-NLS-1$
XMLControl control = new XMLControlElement(getSelectedTab());
copy(control.toXML());
}
});
copyDataItem = new JMenuItem();
copyDataItem.setAccelerator(KeyStroke.getKeyStroke('C', keyMask));
copyDataItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
getSelectedTab().copyTableDataToClipboard();
}
});
copyImageItem = new JMenuItem();
copyImageItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String tabName = getSelectedTab().getName();
OSPLog.finest("copying image of "+tabName); //$NON-NLS-1$
SnapshotTool.getTool().copyImage(DataTool.this);
}
});
MouseAdapter pasteMenuChecker = new MouseAdapter() {
public void mouseEntered(MouseEvent e) {
// ignore if menu is disabled or already displayed
if(!pasteMenu.isEnabled()||pasteMenu.isPopupMenuVisible()) {
return;
}
// enable pasteColumnsItem if clipboard contains pastable columns
if(hasPastableColumns(getSelectedTab())) {
pasteMenu.add(pasteColumnsItem);
} else {
addableData = null;
pasteMenu.remove(pasteColumnsItem);
}
FontSizer.setFonts(pasteMenu, FontSizer.getLevel());
}
};
pasteMenu = new JMenu();
pasteMenu.addMouseListener(pasteMenuChecker);
editMenu.add(pasteMenu);
pasteTabItem = new JMenuItem();
pasteTabItem.setAction(new AbstractAction() {
public void actionPerformed(ActionEvent e) {
boolean failed = false;
String dataString = paste();
if(dataString!=null) {
if(!dataString.startsWith("<?xml")) { //$NON-NLS-1$
// pasted string is not xml, so parse to import Data //$NON-NLS-1$
Data importedData = parseData(dataString, null);
if(importedData!=null) {
if (e.getSource() == pasteTabItem
|| e.getSource() == emptyPasteTabItem) {
OSPLog.finest("pasting imported clipboard data into new tab"); //$NON-NLS-1$
DataToolTab tab = createTab(importedData);
tab.userEditable = true;
addTab(tab);
tab.refreshGUI();
}
refreshDataBuilder();
return;
}
failed = true;
}
// pasted string is xml, so load into XMLControl
if(!failed) {
control = new XMLControlElement();
control.readXML(dataString);
if(control.failedToRead()) {
failed = true;
}
}
// we now have a valid XMLControl
if(!failed) {
OSPLog.finest("pasting clipboard XML into new tabs"); //$NON-NLS-1$
if(Data.class.isAssignableFrom(control.getObjectClass())) {
Data data = (Data) control.loadObject(null, true, true);
if(data==null) {
failed = true;
} else {
for(Data next : getSelfContainedData(data)) {
DataToolTab tab = createTab(next);
addTab(tab);
}
int i = getTabCount()-1;
tabbedPane.setSelectedIndex(i);
}
} else {
ArrayList<DataToolTab> tabs = addTabs(control);
for(DataToolTab tab : tabs) {
tab.setUserEditable(true);
}
int i = getTabCount()-1;
tabbedPane.setSelectedIndex(i);
}
}
if(!failed) {
refreshDataBuilder();
}
}
if(failed) {
JOptionPane.showMessageDialog(DataTool.this, ToolsRes.getString("Tool.Dialog.NoData.Message"), //$NON-NLS-1$
ToolsRes.getString("Tool.Dialog.NoData.Title"), //$NON-NLS-1$
JOptionPane.WARNING_MESSAGE);
}
}
});
pasteMenu.add(pasteTabItem);
pasteColumnsItem = new JMenuItem();
pasteColumnsItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(controlContainsData) {
ArrayList<Data> dataList = getSelfContainedData(control, useChooser);
if(!dataList.isEmpty()) {
DatasetManager manager = new DatasetManager();
for(Data next : dataList) {
for(DataColumn column : getDataColumns(next)) {
manager.addDataset(column);
}
}
addableData = manager;
}
}
if(addableData!=null) {
DataToolTab tab = getSelectedTab();
OSPLog.finest("pasting columns into "+tab.getName()); //$NON-NLS-1$
tab.addColumns(addableData, true, true, true);
}
}
});
pasteMenu.add(pasteColumnsItem);
displayMenu = new JMenu();
menubar.add(displayMenu);
languageMenu = new JMenu();
// get jar resource before installed locales so that launch jar is not null
String imagePath = "/org/opensourcephysics/resources/tools/images/open.gif"; //$NON-NLS-1$
ResourceLoader.getResource(imagePath);
final Locale[] locales = OSPRuntime.getInstalledLocales();
Action languageAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
String language = e.getActionCommand();
OSPLog.finest("setting language to "+language); //$NON-NLS-1$
for(int i = 0; i<locales.length; i++) {
if(language.equals(locales[i].toString())) {
ToolsRes.setLocale(locales[i]);
return;
}
}
}
};
ButtonGroup languageGroup = new ButtonGroup();
languageItems = new JMenuItem[locales.length];
for(int i = 0; i<locales.length; i++) {
languageItems[i] = new JRadioButtonMenuItem(OSPRuntime.getDisplayLanguage(locales[i]));
languageItems[i].setActionCommand(locales[i].toString());
languageItems[i].addActionListener(languageAction);
languageMenu.add(languageItems[i]);
languageGroup.add(languageItems[i]);
}
displayMenu.add(languageMenu);
fontSizeMenu = new JMenu();
displayMenu.add(fontSizeMenu);
fontSizeGroup = new ButtonGroup();
Action fontSizeAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
int i = Integer.parseInt(e.getActionCommand());
FontSizer.setLevel(i);
// setFontLevel(i);
}
};
for(int i = 0; i<4; i++) {
JMenuItem item = new JRadioButtonMenuItem("+"+i); //$NON-NLS-1$
if(i==0) {
defaultFontSizeItem = item;
item.setText(ToolsRes.getString("Tool.MenuItem.DefaultFontSize")); //$NON-NLS-1$
}
item.addActionListener(fontSizeAction);
item.setActionCommand(""+i); //$NON-NLS-1$
fontSizeMenu.add(item);
fontSizeGroup.add(item);
if(i==FontSizer.getLevel()) {
item.setSelected(true);
}
}
helpMenu = new JMenu();
menubar.add(helpMenu);
helpItem = new JMenuItem();
helpItem.setAccelerator(KeyStroke.getKeyStroke('H', keyMask));
helpItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showHelp();
}
});
helpMenu.add(helpItem);
helpMenu.addSeparator();
logItem = new JMenuItem();
logItem.setAccelerator(KeyStroke.getKeyStroke('L', keyMask));
logItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Point p0 = new Frame().getLocation();
JFrame frame = OSPLog.getOSPLog();
if((frame.getLocation().x==p0.x)&&(frame.getLocation().y==p0.y)) {
Point p = getLocation();
frame.setLocation(p.x+28, p.y+28);
}
frame.setVisible(true);
}
});
helpMenu.add(logItem);
helpMenu.addSeparator();
aboutItem = new JMenuItem();
aboutItem.setAccelerator(KeyStroke.getKeyStroke('A', keyMask));
aboutItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showAboutDialog();
}
});
helpMenu.add(aboutItem);
setJMenuBar(menubar);
// create the empty menu bar for use when no tabs are open
emptyMenubar = new JMenuBar();
emptyFileMenu = new JMenu();
emptyMenubar.add(emptyFileMenu);
emptyNewTabItem = new JMenuItem();
emptyNewTabItem.setAccelerator(KeyStroke.getKeyStroke('N', keyMask));
emptyNewTabItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DataToolTab tab = createTab(null);
tab.userEditable = true;
addTab(tab);
tab.refreshGUI();
}
});
emptyFileMenu.add(emptyNewTabItem);
emptyOpenItem = new JMenuItem();
emptyOpenItem.setAccelerator(KeyStroke.getKeyStroke('O', keyMask));
emptyOpenItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
open();
}
});
emptyFileMenu.add(emptyOpenItem);
emptyFileMenu.addSeparator();
emptyExitItem = new JMenuItem();
emptyExitItem.setAccelerator(KeyStroke.getKeyStroke('Q', keyMask));
emptyExitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
emptyFileMenu.add(emptyExitItem);
emptyEditMenu = new JMenu();
emptyEditMenu.addMouseListener(editMenuChecker);
emptyMenubar.add(emptyEditMenu);
emptyPasteMenu = new JMenu();
emptyEditMenu.add(emptyPasteMenu);
emptyPasteTabItem = new JMenuItem();
emptyPasteTabItem.addActionListener(pasteTabItem.getAction());
emptyPasteMenu.add(emptyPasteTabItem);
refreshGUI();
refreshMenubar();
setFontLevel(FontSizer.getLevel());
pack();
// center this on the screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width-getBounds().width)/2;
int y = (dim.height-getBounds().height)/2;
setLocation(x, y);
}
/**
* Refreshes the GUI.
*/
protected void refreshGUI() {
setTitle(ToolsRes.getString("DataTool.Frame.Title")); //$NON-NLS-1$
emptyFileMenu.setText(ToolsRes.getString("Menu.File")); //$NON-NLS-1$
emptyNewTabItem.setText(ToolsRes.getString("DataTool.MenuItem.NewTab")); //$NON-NLS-1$
emptyOpenItem.setText(ToolsRes.getString("MenuItem.Open")); //$NON-NLS-1$
emptyExitItem.setText(ToolsRes.getString("MenuItem.Exit")); //$NON-NLS-1$
emptyEditMenu.setText(ToolsRes.getString("Menu.Edit")); //$NON-NLS-1$
emptyPasteMenu.setText(ToolsRes.getString("MenuItem.Paste")); //$NON-NLS-1$
emptyPasteTabItem.setText(ToolsRes.getString("DataTool.MenuItem.PasteNewTab")); //$NON-NLS-1$
fileMenu.setText(ToolsRes.getString("Menu.File")); //$NON-NLS-1$
newTabItem.setText(ToolsRes.getString("DataTool.MenuItem.NewTab")); //$NON-NLS-1$
if(!OSPRuntime.appletMode) {
openItem.setText(ToolsRes.getString("MenuItem.Open")); //$NON-NLS-1$
importItem.setText(ToolsRes.getString("DataTool.MenuItem.Import")); //$NON-NLS-1$
saveItem.setText(ToolsRes.getString("DataTool.MenuItem.Save")); //$NON-NLS-1$
saveAsItem.setText(ToolsRes.getString("DataTool.MenuItem.SaveAs")); //$NON-NLS-1$
}
closeItem.setText(ToolsRes.getString("MenuItem.Close")); //$NON-NLS-1$
closeAllItem.setText(ToolsRes.getString("MenuItem.CloseAll")); //$NON-NLS-1$
printItem.setText(ToolsRes.getString("DataTool.MenuItem.Print")); //$NON-NLS-1$
exitItem.setText(ToolsRes.getString("MenuItem.Exit")); //$NON-NLS-1$
editMenu.setText(ToolsRes.getString("Menu.Edit")); //$NON-NLS-1$
undoItem.setText(ToolsRes.getString("DataTool.MenuItem.Undo")); //$NON-NLS-1$
redoItem.setText(ToolsRes.getString("DataTool.MenuItem.Redo")); //$NON-NLS-1$
copyMenu.setText(ToolsRes.getString("DataTool.Menu.Copy")); //$NON-NLS-1$
copyImageItem.setText(ToolsRes.getString("DataTool.MenuItem.CopyImage")); //$NON-NLS-1$
pasteMenu.setText(ToolsRes.getString("MenuItem.Paste")); //$NON-NLS-1$
pasteTabItem.setText(ToolsRes.getString("DataTool.MenuItem.PasteNewTab")); //$NON-NLS-1$
pasteColumnsItem.setText(ToolsRes.getString("DataTool.MenuItem.PasteNewColumns")); //$NON-NLS-1$
displayMenu.setText(ToolsRes.getString("Tool.Menu.Display")); //$NON-NLS-1$
languageMenu.setText(ToolsRes.getString("Tool.Menu.Language")); //$NON-NLS-1$
fontSizeMenu.setText(ToolsRes.getString("Tool.Menu.FontSize")); //$NON-NLS-1$
defaultFontSizeItem.setText(ToolsRes.getString("Tool.MenuItem.DefaultFontSize")); //$NON-NLS-1$
helpMenu.setText(ToolsRes.getString("Menu.Help")); //$NON-NLS-1$
helpItem.setText(ToolsRes.getString("DataTool.MenuItem.Help")); //$NON-NLS-1$
logItem.setText(ToolsRes.getString("MenuItem.Log")); //$NON-NLS-1$
aboutItem.setText(ToolsRes.getString("MenuItem.About")); //$NON-NLS-1$
Locale[] locales = OSPRuntime.getInstalledLocales();
for(int i = 0; i<locales.length; i++) {
if(locales[i].getLanguage().equals(ToolsRes.getLanguage())) {
languageItems[i].setSelected(true);
}
}
}
/**
* Determines if the clipboard has pastable data.
*
* @return true if data is pastable
*/
protected boolean hasPastableData() {
controlContainsData = false;
String dataString = paste();
boolean hasData = dataString!=null;
if(hasData) {
if(!dataString.startsWith("<?xml")) { //$NON-NLS-1$
addableData = parseData(dataString, null);
hasData = addableData!=null;
} else {
control = new XMLControlElement();
control.readXML(dataString);
Class<?> type = control.getObjectClass();
if(Data.class.isAssignableFrom(type)) {
addableData = (Data) control.loadObject(null);
} else if(!DataToolTab.class.isAssignableFrom(type)) {
// find all Data objects in the control
XMLTree tree = new XMLTree(control);
tree.setHighlightedClass(Data.class);
tree.selectHighlightedProperties();
if(!tree.getSelectedProperties().isEmpty()) {
controlContainsData = true;
}
}
hasData = (addableData!=null)||DataToolTab.class.isAssignableFrom(type)||controlContainsData;
}
}
return hasData;
}
/**
* Determines if the clipboard has columns that are pastable into a specified tab.
*
* @param tab the tab
* @return true if clipboard has pastable columns
*/
protected boolean hasPastableColumns(DataToolTab tab) {
boolean pastable = false;
if(addableData!=null) {
// columns are pastable if tab name is different or tab is empty
String dataName = addableData.getName();
if(tab.dataManager.getDatasets().isEmpty()||((dataName!=null)&&!dataName.equals(tab.getName()))) {
pastable = true;
}
}
return pastable||controlContainsData;
}
/**
* Shows the about dialog.
*/
protected void showAboutDialog() {
String date = OSPRuntime.getLaunchJarBuildDate();
if (date==null) date = ""; //$NON-NLS-1$
String aboutString = getName()+" "+date+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+"Open Source Physics Project\n" //$NON-NLS-1$
+"www.opensourcephysics.org"; //$NON-NLS-1$
JOptionPane.showMessageDialog(this, aboutString, ToolsRes.getString("Dialog.About.Title")+" "+getName(), //$NON-NLS-1$ //$NON-NLS-2$
JOptionPane.INFORMATION_MESSAGE);
}
/**
* Creates a button with a specified text.
*
* @param text the button text
* @return the button
*/
protected static JButton createButton(String text) {
JButton button = new JButton(text) {
public Dimension getMaximumSize() {
Dimension dim = super.getMaximumSize();
dim.height = buttonHeight;
return dim;
}
};
return button;
}
}
/*
* 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
*/