package tim.prune.save;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.Iterator;
import java.util.TreeSet;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import tim.prune.App;
import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.config.Config;
import tim.prune.data.Track;
import tim.prune.function.Export3dFunction;
import tim.prune.gui.DialogCloser;
import tim.prune.load.GenericFileFilter;
import tim.prune.threedee.ThreeDModel;
/**
* Class to export a 3d scene of the track to a specified Svg file
*/
public class SvgExporter extends Export3dFunction
{
private Track _track = null;
private JDialog _dialog = null;
private JFileChooser _fileChooser = null;
private double _phi = 0.0, _theta = 0.0;
private JTextField _phiField = null, _thetaField = null;
private JTextField _altitudeFactorField = null;
private JCheckBox _gradientsCheckbox = null;
private static final double _scaleFactor = 200.0;
/**
* Constructor
* @param inApp App object
*/
public SvgExporter(App inApp)
{
super(inApp);
_track = inApp.getTrackInfo().getTrack();
// Set default rotation angles
_phi = 30; _theta = 55;
}
/** Get the name key */
public String getNameKey() {
return "function.exportsvg";
}
/**
* Set the rotation angles using coordinates for the camera
* @param inX X coordinate of camera
* @param inY Y coordinate of camera
* @param inZ Z coordinate of camera
*/
public void setCameraCoordinates(double inX, double inY, double inZ)
{
// Calculate phi and theta based on camera x,y,z
_phi = Math.toDegrees(Math.atan2(inX, inZ));
_theta = Math.toDegrees(Math.atan2(inY, Math.sqrt(inX*inX + inZ*inZ)));
}
/**
* Show the dialog to select options and export file
*/
public void begin()
{
// Make dialog window to select angles, colours etc
if (_dialog == null)
{
_dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
_dialog.setLocationRelativeTo(_parentFrame);
_dialog.getContentPane().add(makeDialogComponents());
}
// Set angles
NumberFormat threeDP = NumberFormat.getNumberInstance();
threeDP.setMaximumFractionDigits(3);
_phiField.setText(threeDP.format(_phi));
_thetaField.setText(threeDP.format(_theta));
// Set vertical scale
_altitudeFactorField.setText("" + _altFactor);
// Show dialog
_dialog.pack();
_dialog.setVisible(true);
}
/**
* Make the dialog components to select the export options
* @return Component holding gui elements
*/
private Component makeDialogComponents()
{
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JLabel introLabel = new JLabel(I18nManager.getText("dialog.exportsvg.text"));
introLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4));
panel.add(introLabel, BorderLayout.NORTH);
// OK, Cancel buttons
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
JButton okButton = new JButton(I18nManager.getText("button.ok"));
okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
doExport();
_dialog.dispose();
}
});
buttonPanel.add(okButton);
JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
_dialog.dispose();
}
});
buttonPanel.add(cancelButton);
panel.add(buttonPanel, BorderLayout.SOUTH);
// central panel
JPanel centralPanel = new JPanel();
centralPanel.setLayout(new GridLayout(0, 2, 10, 4));
// rotation angles
JLabel phiLabel = new JLabel(I18nManager.getText("dialog.exportsvg.phi"));
phiLabel.setHorizontalAlignment(SwingConstants.TRAILING);
centralPanel.add(phiLabel);
_phiField = new JTextField("" + _phi);
_phiField.addKeyListener(new DialogCloser(_dialog));
centralPanel.add(_phiField);
JLabel thetaLabel = new JLabel(I18nManager.getText("dialog.exportsvg.theta"));
thetaLabel.setHorizontalAlignment(SwingConstants.TRAILING);
centralPanel.add(thetaLabel);
_thetaField = new JTextField("" + _theta);
centralPanel.add(_thetaField);
// Altitude exaggeration
JLabel altFactorLabel = new JLabel(I18nManager.getText("dialog.3d.altitudefactor"));
altFactorLabel.setHorizontalAlignment(SwingConstants.TRAILING);
centralPanel.add(altFactorLabel);
_altitudeFactorField = new JTextField("" + _altFactor);
centralPanel.add(_altitudeFactorField);
// Checkbox for gradients or not
JLabel gradientsLabel = new JLabel(I18nManager.getText("dialog.exportsvg.gradients"));
gradientsLabel.setHorizontalAlignment(SwingConstants.TRAILING);
centralPanel.add(gradientsLabel);
_gradientsCheckbox = new JCheckBox();
_gradientsCheckbox.setSelected(true);
centralPanel.add(_gradientsCheckbox);
// add this grid to the holder panel
JPanel holderPanel = new JPanel();
holderPanel.setLayout(new BorderLayout(5, 5));
JPanel boxPanel = new JPanel();
boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
boxPanel.add(centralPanel);
holderPanel.add(boxPanel, BorderLayout.CENTER);
panel.add(holderPanel, BorderLayout.CENTER);
return panel;
}
/**
* Select the file and export data to it
*/
private void doExport()
{
// Copy camera coordinates
_phi = checkAngle(_phiField.getText());
_theta = checkAngle(_thetaField.getText());
// OK pressed, so choose output file
if (_fileChooser == null)
{
_fileChooser = new JFileChooser();
_fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
_fileChooser.setFileFilter(new GenericFileFilter("filetype.svg", new String[] {"svg"}));
_fileChooser.setAcceptAllFileFilterUsed(false);
// start from directory in config which should be set
final String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));}
}
// Allow choose again if an existing file is selected
boolean chooseAgain = false;
do
{
chooseAgain = false;
if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
{
// OK pressed and file chosen
File file = _fileChooser.getSelectedFile();
if (!file.getName().toLowerCase().endsWith(".svg")) {
file = new File(file.getAbsolutePath() + ".svg");
}
// Check if file exists and if necessary prompt for overwrite
Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame,
I18nManager.getText("dialog.save.overwrite.text"),
I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
== JOptionPane.YES_OPTION)
{
// Export the file
if (exportFile(file))
{
// file saved - store directory in config for later
Config.setConfigString(Config.KEY_TRACK_DIR, file.getParentFile().getAbsolutePath());
}
else {
// export failed so need to choose again
chooseAgain = true;
}
}
else {
// overwrite cancelled so need to choose again
chooseAgain = true;
}
}
} while (chooseAgain);
}
/**
* Export the track data to the specified file
* @param inFile File object to save to
* @return true if successful
*/
private boolean exportFile(File inFile)
{
FileWriter writer = null;
// find out the line separator for this system
String lineSeparator = System.getProperty("line.separator");
try
{
// create and scale model
ThreeDModel model = new ThreeDModel(_track);
try
{
// try to use given altitude factor
_altFactor = Double.parseDouble(_altitudeFactorField.getText());
model.setAltitudeFactor(_altFactor);
}
catch (NumberFormatException nfe) {}
model.scale();
boolean useGradients = _gradientsCheckbox.isSelected();
// Create file and write basics
writer = new FileWriter(inFile);
writeStartOfFile(writer, useGradients, lineSeparator);
writeBasePlane(writer, lineSeparator);
// write out cardinal letters NESW
writeCardinals(writer, lineSeparator);
// write out points
writeDataPoints(writer, model, useGradients, lineSeparator);
writeEndOfFile(writer, lineSeparator);
// everything worked
UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
+ " " + _track.getNumPoints() + " " + I18nManager.getText("confirm.save.ok2")
+ " " + inFile.getAbsolutePath());
return true;
}
catch (IOException ioe)
{
JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
}
finally
{
// close file ignoring exceptions
try {
writer.close();
}
catch (Exception e) {}
}
return false;
}
/**
* Write the start of the Svg file
* @param inWriter Writer to use for writing file
* @param inUseGradients true to use gradients, false for flat fills
* @param inLineSeparator line separator to use
* @throws IOException on file writing error
*/
private static void writeStartOfFile(FileWriter inWriter, boolean inUseGradients,
String inLineSeparator)
throws IOException
{
inWriter.write("");
inWriter.write(inLineSeparator);
inWriter.write("");
inWriter.write(inLineSeparator);
inWriter.write("");
inWriter.write(inLineSeparator);
}
/**
* Write out all the data points to the file in the balls-and-sticks style
* @param inWriter Writer to use for writing file
* @param inModel model object for getting data points
* @param inUseGradients true to use gradients, false for flat fills
* @param inLineSeparator line separator to use
* @throws IOException on file writing error
*/
private void writeDataPoints(FileWriter inWriter, ThreeDModel inModel, boolean inUseGradients,
String inLineSeparator)
throws IOException
{
final int numPoints = inModel.getNumPoints();
TreeSet fragments = new TreeSet();
for (int i=0; i 0.0)
{
int[] baseCoords = convertCoordinates(inModel.getScaledHorizValue(i), inModel.getScaledVertValue(i), 0);
builder.append("");
builder.append(inLineSeparator);
}
// ball (different according to type)
if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_WAYPOINT)
{
// waypoint ball
builder.append("");
}
else
{
// normal track point ball
builder.append("");
}
builder.append(inLineSeparator);
// add to set
fragments.add(new SvgFragment(builder.toString(), coords[1]));
}
// Iterate over the sorted set and write to file
Iterator iterator = fragments.iterator();
while (iterator.hasNext()) {
inWriter.write(iterator.next().getFragment());
}
}
/**
* Check the given angle value
* @param inString String entered by user
* @return validated value
*/
private static double checkAngle(String inString)
{
double value = 0.0;
try {
value = Double.parseDouble(inString);
}
catch (Exception e) {} // ignore parse failures
return value;
}
}