]> git.gag.com Git - debian/openrocket/commitdiff
Initial GUI for editing and saving component presets. See ComponentPresetPanel.main...
authorrodinia814 <rodinia814@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sat, 5 May 2012 02:23:14 +0000 (02:23 +0000)
committerrodinia814 <rodinia814@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sat, 5 May 2012 02:23:14 +0000 (02:23 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@647 180e2498-e6e9-4542-8430-84ac67f01cd8

12 files changed:
core/resources/l10n/messages.properties
core/src/net/sf/openrocket/gui/preset/ComponentPresetPanel.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/preset/ImagePreviewPanel.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/preset/MaterialModel.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/preset/PresetResultListener.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/print/PrintUnit.java
core/src/net/sf/openrocket/gui/util/FileHelper.java
core/src/net/sf/openrocket/preset/ComponentPreset.java
core/src/net/sf/openrocket/preset/xml/BaseComponentDTO.java
core/src/net/sf/openrocket/preset/xml/OpenRocketComponentSaver.java
core/src/net/sf/openrocket/rocketcomponent/Transition.java

index 4978126cb6934e55226e8851225922c6b890d71c..79f7a90a14cc2557a7f3f224200396edf3fdbdac 100644 (file)
@@ -25,7 +25,7 @@ RocketActions.DelCompAct.Delete = Delete
 RocketActions.DelCompAct.ttip.Delete = Delete the selected component.
 RocketActions.DelSimuAct.Delete = Delete
 RocketActions.DelSimuAct.ttip.Delete = Delete the selected simulation.
-RocketActions.DelAct.Delete = Delete 
+RocketActions.DelAct.Delete = Delete
 RocketActions.DelAct.ttip.Delete = Delete the selected component or simulation.
 RocketActions.CutAction.Cut = Cut
 RocketActions.CutAction.ttip.Cut = Cut this component or simulation to the clipboard and remove from this design
@@ -47,7 +47,7 @@ RocketActions.MoveDownAct.ttip.Movedown = Move this component downwards.
 RocketPanel.FigTypeAct.Sideview = Side view
 RocketPanel.FigTypeAct.ttip.Sideview = Side view
 RocketPanel.FigTypeAct.Backview = Back view
-RocketPanel.FigTypeAct.ttip.Backview = Rear view 
+RocketPanel.FigTypeAct.ttip.Backview = Rear view
 RocketPanel.FigViewAct.2D = 2D View
 RocketPanel.FigViewAct.ttip.2D = 2D View
 RocketPanel.FigViewAct.3D = 3D View
@@ -95,6 +95,7 @@ filetypes.pdf = PDF files (*.pdf)
 BasicFrame.SimpleFileFilter1 = All rocket designs (*.ork; *.rkt)
 BasicFrame.SimpleFileFilter2 = OpenRocket designs (*.ork)
 BasicFrame.SimpleFileFilter3 = RockSim designs (*.rkt)
+BasicFrame.SimpleFileFilter4 = OpenRocket presets (*.orc)
 filetypes.images = Image files
 
 
@@ -106,9 +107,9 @@ AboutDialog.lbl.version = Version
 ! - AboutDialog.lbl.translatorWebsite is a URL to the translator / group (may be empty)
 ! - AboutDialog.lbl.translatorIcon is the file name of an icon under pix/translators/ (may be empty)
 AboutDialog.lbl.translation = English translation by:
-AboutDialog.lbl.translator = 
-AboutDialog.lbl.translatorWebsite = 
-AboutDialog.lbl.translatorIcon =  
+AboutDialog.lbl.translator =
+AboutDialog.lbl.translatorWebsite =
+AboutDialog.lbl.translatorIcon =
 
 
 ! Print dialog
@@ -219,7 +220,7 @@ pref.dlg.but.reset = Reset
 pref.dlg.but.checknow = Check now
 pref.dlg.but.defaultmetric = Default metric
 pref.dlg.but.defaultimperial = Default imperial
-pref.dlg.title.Preferences = Preferences 
+pref.dlg.title.Preferences = Preferences
 pref.dlg.tab.Units = Units
 pref.dlg.tab.Defaultunits = Default units
 pref.dlg.tab.Materials = Materials
@@ -338,7 +339,7 @@ simedtdlg.lbl.ttip.Timestep1 = <html>The time between simulation steps.<br>A sma
 simedtdlg.lbl.ttip.Timestep2 = The 4<sup>th</sup> order simulation method is quite accurate with a time step of
 simedtdlg.but.ttip.resettodefault = Reset the time step to its default value (
 simedtdlg.border.Simlist = Simulator listeners
-simedtdlg.txt.longA1 = <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.  
+simedtdlg.txt.longA1 = <html><i>Simulation listeners</i> is an advanced feature that allows user-written code to listen to and interact with the simulation.
 simedtdlg.txt.longA2 = For details on writing simulation listeners, see the OpenRocket technical documentation.
 simedtdlg.lbl.Curlist = Current listeners:
 simedtdlg.lbl.Addsimlist = Add simulation listener
@@ -432,7 +433,7 @@ SimExpPan.Fileexists.desc1 = File \"
 SimExpPan.Fileexists.desc2 = \" exists.  Overwrite?
 SimExpPan.Fileexists.title = File exists
 SimExpPan.ExportingVar.desc1 = Exporting 1 variable out of
-SimExpPan.ExportingVar.desc2 = Exporting 
+SimExpPan.ExportingVar.desc2 = Exporting
 SimExpPan.ExportingVar.desc3 = variables out of
 SimExpPan.Col.Variable = Variable
 SimExpPan.Col.Unit = Unit
@@ -470,7 +471,7 @@ simplotpanel.but.Plotflight = Plot flight
 simplotpanel.lbl.Axis = Axis:
 simplotpanel.but.ttip.Removethisplot = Remove this plot
 simplotpanel.Desc = The data will be plotted in time order even if the X axis type is not time.
-simplotpanel.OptionPane.lbl1 = A maximum of 15 plots is allowed. 
+simplotpanel.OptionPane.lbl1 = A maximum of 15 plots is allowed.
 simplotpanel.OptionPane.lbl2 = Cannot add plot
 simplotpanel.AUTO_NAME = Auto
 simplotpanel.LEFT_NAME = Left
@@ -536,14 +537,14 @@ componentanalysisdlg.rollTableModel = Roll dynamics
 componentanalysisdlg.rollTableModel.ttip = Roll dynamics
 componentanalysisdlg.println.closingmethod = Closing method called:
 componentanalysisdlg.println.settingnam = SETTING NAN VALUES
-componentanalysisdlg.lbl.reflenght = Reference length: 
-componentanalysisdlg.lbl.refarea = Reference area: 
+componentanalysisdlg.lbl.reflenght = Reference length:
+componentanalysisdlg.lbl.refarea = Reference area:
 !componentanalysisdlg.But.close =Close
 componentanalysisdlg.TabStability.Col.Component = Component
 
 ! Custom Material dialog
 custmatdlg.title.Custommaterial = Custom material
-custmatdlg.lbl.Materialname = Material name: 
+custmatdlg.lbl.Materialname = Material name:
 custmatdlg.lbl.Materialtype = Material type:
 custmatdlg.lbl.Materialdensity = Material density:
 custmatdlg.checkbox.Addmaterial = Add material to database
@@ -656,7 +657,7 @@ RocketCompCfg.checkbox.Endcapped = End capped
 RocketCompCfg.ttip.Endcapped = Whether the end of the shoulder is capped.
 RocketCompCfg.title.Noseconeshoulder = Nose cone shoulder
 RocketCompCfg.title.Aftshoulder = Aft shoulder
-RocketCompCfg.border.Foreshoulder = Fore shoulder 
+RocketCompCfg.border.Foreshoulder = Fore shoulder
 !RocketCompCfg.lbl.Length = Length:
 
 ! BulkheadConfig
@@ -800,7 +801,7 @@ ParachuteCfg.lbl.Material = Material:
 ParachuteCfg.combo.MaterialModel = The component material affects the weight of the component.
 ParachuteCfg.lbl.longA1 = <html>Drag coefficient C<sub>D</sub>:
 ParachuteCfg.lbl.longB1 = <html>The drag coefficient relative to the total area of the parachute.<br>
-ParachuteCfg.lbl.longB2 = A larger drag coefficient yields a slowed descent rate.  
+ParachuteCfg.lbl.longB2 = A larger drag coefficient yields a slowed descent rate.
 ParachuteCfg.lbl.longB3 = A typical value for parachutes is 0.8.
 ParachuteCfg.but.Reset = Reset
 ParachuteCfg.lbl.Shroudlines = Shroud lines:
@@ -823,7 +824,7 @@ ParachuteCfg.lbl.Radialdirection = Radial direction:
 ParachuteCfg.but.Reset = Reset
 ParachuteCfg.lbl.plusdelay = plus
 
-! ShockCordConfig 
+! ShockCordConfig
 ShockCordCfg.lbl.Shockcordlength = Shock cord length:
 ShockCordCfg.lbl.Shockcordmaterial = Shock cord material:
 ShockCordCfg.lbl.Posrelativeto = Position relative to:
@@ -967,7 +968,7 @@ PlotDialog.lbl.Chart = Click and drag down+right to zoom in, up+left to zoom out
 
 ! "main" prefix is used for the main application dialog
 
-# FIXME: Rename the description keys 
+# FIXME: Rename the description keys
 
 main.menu.file = File
 main.menu.file.desc = File-handling related tasks
@@ -1104,7 +1105,7 @@ Shape.Ogive.desc1 = An ogive nose cone has a profile that is a segment of a circ
 Shape.Ogive.desc2 = An ogive transition has a profile that is a segment of a circle.   The shape parameter value 1 produces a <b>tangent ogive</b>, which has a smooth transition to the body tube at the aft end, values less than 1 produce <b>secant ogives</b>.
 Shape.Ellipsoid = Ellipsoid
 Shape.Ellipsoid.desc1 = An ellipsoidal nose cone has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.
-Shape.Ellipsoid.desc2 = An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.  If the transition is not clipped, then the profile is extended at the center by the corresponding radius.             
+Shape.Ellipsoid.desc2 = An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.  If the transition is not clipped, then the profile is extended at the center by the corresponding radius.
 Shape.Powerseries = Power series
 Shape.Powerseries.desc1 = A power series nose cone has a profile of <i>Radius</i>&nbsp;&times;&nbsp;(<i>x</i>&nbsp;/&nbsp;<i>Length</i>)<sup><i>k</i></sup> where <i>k</i> is the shape parameter.  For <i>k</i>=0.5 this is a <b>\u00BD-power</b> or <b>parabolic</b> nose cone, for <i>k</i>=0.75 a <b>\u00BE-power</b>, and for <i>k</i>=1 a <b>conical</b> nose cone.
 Shape.Powerseries.desc2 = A power series transition has a profile of <i>Radius</i>&nbsp;&times;&nbsp;(<i>x</i>&nbsp;/&nbsp;<i>Length</i>)<sup><i>k</i></sup> where <i>k</i> is the shape parameter.  For <i>k</i>=0.5 the transition is <b>\u00BD-power</b> or <b>parabolic</b>, for <i>k</i>=0.75 a <b>\u00BE-power</b>, and for <i>k</i>=1 <b>conical</b>.
@@ -1113,7 +1114,7 @@ Shape.Parabolicseries.desc1 = A parabolic series nose cone has a profile of a pa
 Shape.Parabolicseries.desc2 = A parabolic series transition has a profile of a parabola.  The shape parameter defines the segment of the parabola to utilize.  The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube at the aft end, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> transition.
 Shape.Haackseries = Haack series
 Shape.Haackseries.desc1 = The Haack series nose cones are designed to minimize drag.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> nose cone, which minimizes drag for fixed length and diameter, while a value of 0.333 produces an <b>LV-Haack</b> nose cone, which minimizes drag for fixed length and volume.
-Shape.Haackseries.desc2 = The Haack series <i>nose cones</i> are designed to minimize drag.  These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.              
+Shape.Haackseries.desc2 = The Haack series <i>nose cones</i> are designed to minimize drag.  These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.
 
 
 ! RocketComponent
@@ -1169,7 +1170,7 @@ MotorMount.IgnitionEvent.EJECTION_CHARGE = First ejection charge of previous sta
 MotorMount.IgnitionEvent.BURNOUT = First burnout of previous stage
 MotorMount.IgnitionEvent.NEVER = Never
 
-!ComponentIcons 
+!ComponentIcons
 ComponentIcons.Nosecone = Nose cone
 ComponentIcons.Bodytube = Body tube
 ComponentIcons.Transition = Transition
@@ -1224,7 +1225,7 @@ TCurveMotorCol.LENGTH = Length
 ! RocketInfo
 RocketInfo.lengthLine.Length = Length
 RocketInfo.lengthLine.maxdiameter = , max. diameter
-RocketInfo.massText1 = Mass with motors 
+RocketInfo.massText1 = Mass with motors
 RocketInfo.massText2 = Mass with no motors
 RocketInfo.at = at M=
 RocketInfo.cgText = CG:
@@ -1577,8 +1578,8 @@ GuidedTourSelectionDialog.btn.start = Start tour!
 ! Custom Fin BMP Importer
 CustomFinImport.button.label = Import from image
 CustomFinImport.badFinImage = Invalid fin image. Make sure the fin is a solid black or dark color and touching the bottom of the image.
-CustomFinImport.errorLoadingFile = Error loading file: 
-CustomFinImport.errorParsingFile = Error parsing fin image: 
+CustomFinImport.errorLoadingFile = Error loading file:
+CustomFinImport.errorParsingFile = Error parsing fin image:
 CustomFinImport.undo = Import freeform fin set
 CustomFinImport.error.title = Error loading fin profile
 CustomFinImport.error.badimage = Could not deduce fin shape from image.
diff --git a/core/src/net/sf/openrocket/gui/preset/ComponentPresetPanel.java b/core/src/net/sf/openrocket/gui/preset/ComponentPresetPanel.java
new file mode 100644 (file)
index 0000000..1a34b3d
--- /dev/null
@@ -0,0 +1,229 @@
+package net.sf.openrocket.gui.preset;
+
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.util.FileHelper;
+import net.sf.openrocket.gui.util.SwingPreferences;
+import net.sf.openrocket.l10n.ResourceBundleTranslator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.xml.OpenRocketComponentSaver;
+import net.sf.openrocket.startup.Application;
+
+import javax.swing.*;
+import javax.swing.table.DefaultTableModel;
+import javax.xml.bind.JAXBException;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A UI for editing component presets.  Currently this is a standalone application - run the main within this class.
+ * TODO: Full I18n
+ * TODO: Delete component
+ * TODO: Open .orc for editing
+ * TODO: Import .csv
+ * TODO: Export .csv
+ * TODO: proper mass unit conversion
+ */
+public class ComponentPresetPanel extends JPanel implements PresetResultListener {
+
+    private static final LogHelper log = Application.getLogger();
+
+    private static ResourceBundleTranslator trans = null;
+
+    static {
+        trans = new ResourceBundleTranslator("l10n.messages");
+        net.sf.openrocket.startup.Application.setBaseTranslator(trans);
+    }
+
+    private JTable table;
+    private DataTableModel model;
+    private boolean editingSelected = false;
+
+    /**
+     * Create the panel.
+     */
+    public ComponentPresetPanel() {
+        setLayout(new MigLayout("", "[82.00px][168.00px][84px][117.00px][][222px]", "[346.00px][29px]"));
+
+        model = new DataTableModel(new String[]{"Manufacturer", "Type", "Part No", "Description"});
+
+        table = new JTable(model);
+        JScrollPane scrollPane = new JScrollPane(table);
+        table.setFillsViewportHeight(true);
+        table.setAutoCreateRowSorter(true);
+        add(scrollPane, "cell 0 0 6 1,grow");
+
+        table.addMouseListener(new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+                if (e.getClickCount() == 2) {
+                    JTable target = (JTable) e.getSource();
+                    int row = target.getSelectedRow();
+                    editingSelected = true;
+                    new PresetEditorDialog(ComponentPresetPanel.this, (ComponentPreset) model.getAssociatedObject(row)).setVisible(true);
+                }
+            }
+        });
+        JButton addBtn = new JButton("Add");
+        addBtn.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseClicked(MouseEvent arg0) {
+                editingSelected = false;
+                new PresetEditorDialog(ComponentPresetPanel.this).setVisible(true);
+            }
+        });
+        add(addBtn, "cell 0 1,alignx left,aligny top");
+
+        JButton saveBtn = new JButton("Save...");
+        saveBtn.setHorizontalAlignment(SwingConstants.RIGHT);
+        add(saveBtn, "flowx,cell 5 1,alignx right,aligny center");
+        saveBtn.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(final ActionEvent e) {
+                try {
+                    saveAsORC();
+                }
+                catch (JAXBException e1) {
+                    //TODO
+                    e1.printStackTrace();
+                }
+                catch (IOException e1) {
+                    //TODO
+                    e1.printStackTrace();
+                }
+            }
+        });
+
+        JButton cancelBtn = new JButton("Cancel");
+        cancelBtn.setHorizontalAlignment(SwingConstants.RIGHT);
+        add(cancelBtn, "cell 5 1,alignx right,aligny top");
+        cancelBtn.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(final ActionEvent e) {
+                System.exit(0);
+            }
+        });
+
+    }
+
+    @Override
+    public void notifyResult(final ComponentPreset preset) {
+        if (preset != null) {
+            DataTableModel model = (DataTableModel) table.getModel();
+            if (!editingSelected) {
+                model.addRow(new String[]{preset.getManufacturer().getDisplayName(), preset.getType().name(), preset.getPartNo(), preset.get(ComponentPreset.DESCRIPTION)},
+                        preset);
+            }
+            else {
+                int row = table.getSelectedRow();
+                model.setValueAt(preset.getManufacturer().getDisplayName(), row, 0);
+                model.setValueAt(preset.getType().name(), row, 1);
+                model.setValueAt(preset.getPartNo(), row, 2);
+                model.setValueAt(preset.get(ComponentPreset.DESCRIPTION), row, 3);
+                model.associated.set(row, preset);
+            }
+        }
+        editingSelected = false;
+    }
+
+    /**
+     * Launch the test main.
+     */
+    public static void main(String[] args) {
+        try {
+            Application.setPreferences(new SwingPreferences());
+            JFrame dialog = new JFrame();
+            dialog.getContentPane().add(new ComponentPresetPanel());
+            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+            dialog.pack();
+            dialog.setVisible(true);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    class DataTableModel extends DefaultTableModel {
+
+        private List<Object> associated = new ArrayList<Object>();
+
+        /**
+         * Constructs a <code>DefaultTableModel</code> with as many columns as there are elements in
+         * <code>columnNames</code> and <code>rowCount</code> of <code>null</code> object values.  Each column's name
+         * will be taken from the <code>columnNames</code> array.
+         *
+         * @param columnNames <code>array</code> containing the names of the new columns; if this is <code>null</code>
+         *                    then the model has no columns
+         *
+         * @see #setDataVector
+         * @see #setValueAt
+         */
+        DataTableModel(final Object[] columnNames) {
+            super(columnNames, 0);
+        }
+
+        public void addRow(Object[] data, Object associatedData) {
+            super.addRow(data);
+            associated.add(getRowCount() - 1, associatedData);
+        }
+
+        public void removeRow(int row) {
+            super.removeRow(row);
+            associated.remove(row);
+        }
+
+        public Object getAssociatedObject(int row) {
+            return associated.get(row);
+        }
+
+        public boolean isCellEditable(int rowIndex, int mColIndex) {
+            return false;
+        }
+    }
+
+    private boolean saveAsORC() throws JAXBException, IOException {
+        File file = null;
+
+        final JFileChooser chooser = new JFileChooser();
+        chooser.addChoosableFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
+
+        chooser.setFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
+        chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
+
+        int option = chooser.showSaveDialog(ComponentPresetPanel.this);
+        if (option != JFileChooser.APPROVE_OPTION) {
+            log.user("User decided not to save, option=" + option);
+            return false;
+        }
+
+        file = chooser.getSelectedFile();
+        if (file == null) {
+            log.user("User did not select a file");
+            return false;
+        }
+
+        ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory());
+
+        file = FileHelper.forceExtension(file, "orc");
+
+        List<Material> materials = new ArrayList<Material>();
+        List<ComponentPreset> presets = new ArrayList<ComponentPreset>();
+
+        for (int x = 0; x< model.getRowCount(); x++) {
+            ComponentPreset preset = (ComponentPreset)model.getAssociatedObject(x);
+            if (!materials.contains(preset.get(ComponentPreset.MATERIAL))) {
+                materials.add(preset.get(ComponentPreset.MATERIAL));
+            }
+            presets.add(preset);
+        }
+
+        return FileHelper.confirmWrite(file, this) && new OpenRocketComponentSaver().save(file, materials, presets);
+    }
+}
diff --git a/core/src/net/sf/openrocket/gui/preset/ImagePreviewPanel.java b/core/src/net/sf/openrocket/gui/preset/ImagePreviewPanel.java
new file mode 100644 (file)
index 0000000..bab36ec
--- /dev/null
@@ -0,0 +1,105 @@
+
+package net.sf.openrocket.gui.preset;
+
+
+import javax.swing.*;
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+
+/**
+ * From a JavaLobby article by Michael Urban:  http://www.javalobby.org/java/forums/t49462.html
+ */
+public class ImagePreviewPanel extends JPanel
+        implements PropertyChangeListener {
+
+    private int width, height;
+    private ImageIcon icon;
+    private Image image;
+    private static final int ACCSIZE = 155;
+    private Color bg;
+
+    public ImagePreviewPanel() {
+        setPreferredSize(new Dimension(ACCSIZE, -1));
+        bg = getBackground();
+    }
+
+    public void propertyChange(PropertyChangeEvent e) {
+        String propertyName = e.getPropertyName();
+
+        // Make sure we are responding to the right event.
+        if (propertyName.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
+            File selection = (File)e.getNewValue();
+            String name;
+
+            if (selection == null)
+                return;
+            else
+                name = selection.getAbsolutePath();
+
+            /*
+             * Make reasonably sure we have an image format that AWT can
+             * handle so we don't try to draw something silly.
+             */
+            if ((name != null) &&
+                    name.toLowerCase().endsWith(".jpg") ||
+                    name.toLowerCase().endsWith(".jpeg") ||
+                    name.toLowerCase().endsWith(".gif") ||
+                    name.toLowerCase().endsWith(".png")) {
+                icon = new ImageIcon(name);
+                image = icon.getImage();
+                scaleImage();
+                repaint();
+            }
+        }
+    }
+
+    private void scaleImage() {
+        width = image.getWidth(this);
+        height = image.getHeight(this);
+        double ratio = 1.0;
+
+        /*
+         * Determine how to scale the image. Since the accessory can expand
+         * vertically make sure we don't go larger than 150 when scaling
+         * vertically.
+         */
+        if (width >= height) {
+            ratio = (double)(ACCSIZE-5) / width;
+            width = ACCSIZE-5;
+            height = (int)(height * ratio);
+        }
+        else {
+            if (getHeight() > 150) {
+                ratio = (double)(ACCSIZE-5) / height;
+                height = ACCSIZE-5;
+                width = (int)(width * ratio);
+            }
+            else {
+                ratio = (double)getHeight() / height;
+                height = getHeight();
+                width = (int)(width * ratio);
+            }
+        }
+
+        image = image.getScaledInstance(width, height, Image.SCALE_DEFAULT);
+    }
+
+    public void paintComponent(Graphics g) {
+        g.setColor(bg);
+
+        /*
+         * If we don't do this, we will end up with garbage from previous
+         * images if they have larger sizes than the one we are currently
+         * drawing. Also, it seems that the file list can paint outside
+         * of its rectangle, and will cause odd behavior if we don't clear
+         * or fill the rectangle for the accessory before drawing. This might
+         * be a bug in JFileChooser.
+         */
+        g.fillRect(0, 0, ACCSIZE, getHeight());
+        g.drawImage(image, getWidth() / 2 - width / 2 + 5,
+                getHeight() / 2 - height / 2, this);
+    }
+
+}
diff --git a/core/src/net/sf/openrocket/gui/preset/MaterialModel.java b/core/src/net/sf/openrocket/gui/preset/MaterialModel.java
new file mode 100644 (file)
index 0000000..a80e684
--- /dev/null
@@ -0,0 +1,120 @@
+package net.sf.openrocket.gui.preset;
+
+import net.sf.openrocket.database.Database;
+import net.sf.openrocket.database.DatabaseListener;
+import net.sf.openrocket.database.Databases;
+import net.sf.openrocket.gui.dialogs.CustomMaterialDialog;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.startup.Application;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * A material model specifically for presets.
+ */
+public class MaterialModel extends DefaultComboBoxModel implements DatabaseListener<Material> {
+
+    private static final String CUSTOM = "Custom";
+
+    private final Database<Material> database;
+
+    private static final Translator trans = Application.getTranslator();
+
+    private Component parent;
+    public MaterialModel(Component theParent, Material.Type type) {
+        parent = theParent;
+
+        switch (type) {
+            case LINE:
+                this.database = Databases.LINE_MATERIAL;
+                break;
+
+            case BULK:
+                this.database = Databases.BULK_MATERIAL;
+                break;
+
+            case SURFACE:
+                this.database = Databases.SURFACE_MATERIAL;
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unknown material type:" + type);
+        }
+
+        database.addDatabaseListener(this);
+    }
+
+    @Override
+    public void setSelectedItem(Object item) {
+        if (item == null) {
+            // Clear selection - huh?
+            return;
+        }
+
+        if (item == CUSTOM) {
+
+            // Open custom material dialog in the future, after combo box has closed
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    CustomMaterialDialog dialog = new CustomMaterialDialog(
+                            SwingUtilities.getWindowAncestor(parent),
+                            (Material) getSelectedItem(), true,
+                            //// Define custom material
+                            trans.get("MaterialModel.title.Defcustmat"));
+
+                    dialog.setVisible(true);
+
+                    if (!dialog.getOkClicked()) {
+                        return;
+                    }
+
+                    Material material = dialog.getMaterial();
+                    MaterialModel.super.setSelectedItem(material);
+                    if (dialog.isAddSelected()) {
+                        database.add(material);
+                    }
+                }
+            });
+
+        }
+        else if (item instanceof Material) {
+            super.setSelectedItem(item);
+        }
+        else {
+            throw new IllegalArgumentException("Illegal item class " + item.getClass() +
+                    " item=" + item);
+        }
+    }
+
+    @Override
+    public Object getElementAt(int index) {
+        if (index == database.size()) {
+            return CUSTOM;
+        }
+        else if (index >= database.size() + 1) {
+            return null;
+        }
+        return database.get(index);
+    }
+
+    @Override
+    public int getSize() {
+        return database.size() + 1;
+    }
+
+    ////////  Change listeners
+
+    @Override
+    public void elementAdded(Material element, Database<Material> source) {
+        this.fireContentsChanged(this, 0, database.size());
+    }
+
+    @Override
+    public void elementRemoved(Material element, Database<Material> source) {
+        this.fireContentsChanged(this, 0, database.size());
+    }
+
+}
diff --git a/core/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java b/core/src/net/sf/openrocket/gui/preset/PresetEditorDialog.java
new file mode 100644 (file)
index 0000000..1608aeb
--- /dev/null
@@ -0,0 +1,1636 @@
+package net.sf.openrocket.gui.preset;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.print.PrintUnit;
+import net.sf.openrocket.gui.util.SwingPreferences;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.motor.Manufacturer;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.ComponentPresetFactory;
+import net.sf.openrocket.preset.InvalidComponentPresetException;
+import net.sf.openrocket.preset.TypedPropertyMap;
+import net.sf.openrocket.rocketcomponent.Transition;
+import net.sf.openrocket.startup.Application;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.text.JTextComponent;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PresetEditorDialog extends JDialog implements ItemListener {
+
+    private static Translator trans = Application.getTranslator();
+
+    private static LogHelper log = Application.getLogger();
+
+    private static final String NON_NEGATIVE_DECIMAL_FIELD = "(\\d){1,10}\\.(\\d){1,10}";
+
+    /**
+     * Input of non-negative decimals.
+     */
+    final PresetInputVerifier NON_NEGATIVE_DECIMAL =
+            new PresetInputVerifier(Pattern.compile(NON_NEGATIVE_DECIMAL_FIELD));
+
+    private final JPanel contentPanel = new JPanel();
+    private JComboBox typeCombo;
+    private JTextField mfgTextField;
+    private JComboBox materialChooser;
+    private JComboBox massUnitCombo;
+    private JComboBox lenUnitCombo;
+
+    private JTextField ncPartNoTextField;
+    private JTextField ncDescTextField;
+    private JTextField ncLengthTextField;
+    private JCheckBox ncFilledCB;
+    private JComboBox ncShapeCB;
+    private JTextField ncAftDiaTextField;
+    private JTextField ncAftShoulderDiaTextField;
+    private JTextField ncAftShoulderLenTextField;
+    private JTextField ncMassTextField;
+    private ImageIcon ncImage;
+    private JButton ncImageBtn;
+
+    private JTextField trPartNoTextField;
+    private JTextField trDescTextField;
+    private JTextField trLengthTextField;
+    private JTextField trAftDiaTextField;
+    private JTextField trAftShoulderDiaTextField;
+    private JTextField trAftShoulderLenTextField;
+    private JTextField trForeDiaTextField;
+    private JTextField trForeShoulderDiaTextField;
+    private JTextField trForeShoulderLenTextField;
+    private JTextField trMassTextField;
+    private ImageIcon trImage;
+    private JCheckBox trFilledCB;
+    private JComboBox trShapeCB;
+    private JButton trImageBtn;
+
+    private JTextField btPartNoTextField;
+    private JTextField btDescTextField;
+    private JTextField btMassTextField;
+    private JTextField btInnerDiaTextField;
+    private JTextField btOuterDiaTextField;
+    private JTextField btLengthTextField;
+    private ImageIcon btImage;
+    private JButton btImageBtn;
+
+    private JTextField tcPartNoTextField;
+    private JTextField tcDescTextField;
+    private JTextField tcMassTextField;
+    private JTextField tcInnerDiaTextField;
+    private JTextField tcOuterDiaTextField;
+    private JTextField tcLengthTextField;
+    private ImageIcon tcImage;
+    private JButton tcImageBtn;
+
+    private JTextField bhPartNoTextField;
+    private JTextField bhDescTextField;
+    private JTextField bhOuterDiaTextField;
+    private JTextField bhLengthTextField;
+    private JTextField bhMassTextField;
+    private ImageIcon bhImage;
+    private JButton bhImageBtn;
+
+    private JTextField crPartNoTextField;
+    private JTextField crDescTextField;
+    private JTextField crOuterDiaTextField;
+    private JTextField crInnerDiaTextField;
+    private JTextField crThicknessTextField;
+    private JTextField crMassTextField;
+    private ImageIcon crImage;
+    private JButton crImageBtn;
+
+    private JTextField ebPartNoTextField;
+    private JTextField ebDescTextField;
+    private JTextField ebOuterDiaTextField;
+    private JTextField ebInnerDiaTextField;
+    private JTextField ebThicknessTextField;
+    private JTextField ebMassTextField;
+    private ImageIcon ebImage;
+    private JButton ebImageBtn;
+
+    private final JFileChooser imageChooser = createImageChooser();
+
+    private JPanel componentOverlayPanel;
+
+    private PresetResultListener resultListener;
+
+    private static Map<String, String> componentMap = new HashMap<String, String>();
+    private static Map<String, PrintUnit> lengthMap = new HashMap<String, PrintUnit>();
+
+    private static final String NOSE_CONE_KEY = "NoseCone.NoseCone";
+    private static final String BODY_TUBE_KEY = "BodyTube.BodyTube";
+    private static final String TUBE_COUPLER_KEY = "TubeCoupler.TubeCoupler";
+    private static final String TRANSITION_KEY = "Transition.Transition";
+    private static final String CR_KEY = "ComponentIcons.Centeringring";
+    private static final String BULKHEAD_KEY = "Bulkhead.Bulkhead";
+    private static final String EB_KEY = "ComponentIcons.Engineblock";
+
+
+    static {
+        componentMap.put(trans.get(NOSE_CONE_KEY), "NOSECONE");
+        componentMap.put(trans.get(BODY_TUBE_KEY), "BODYTUBE");
+        componentMap.put(trans.get(TUBE_COUPLER_KEY), "TUBECOUPLER");
+        componentMap.put(trans.get(TRANSITION_KEY), "TRANSITION");
+        componentMap.put(trans.get(CR_KEY), "CENTERINGRING");
+        componentMap.put(trans.get(BULKHEAD_KEY), "BULKHEAD");
+        componentMap.put(trans.get(EB_KEY), "ENGINEBLOCK");
+
+        lengthMap.put("m", PrintUnit.METERS);
+        lengthMap.put("cm", PrintUnit.CENTIMETERS);
+        lengthMap.put("mm", PrintUnit.MILLIMETERS);
+        lengthMap.put("in", PrintUnit.INCHES);
+        lengthMap.put("ft", PrintUnit.FOOT);
+    }
+
+    /**
+     * Launch the application.
+     */
+    public static void main(String[] args) {
+        try {
+            Application.setPreferences(new SwingPreferences());
+            PresetEditorDialog dialog = new PresetEditorDialog();
+            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+            dialog.setVisible(true);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Create the dialog.
+     */
+    public PresetEditorDialog() {
+        this(new PresetResultListener() {
+            @Override
+            public void notifyResult(final ComponentPreset preset) {
+            }
+        });
+    }
+
+    public PresetEditorDialog(PresetResultListener theCallback) {
+        this(theCallback, null);
+        typeCombo.setEditable(true);
+    }
+
+    public PresetEditorDialog(PresetResultListener theCallback, ComponentPreset toEdit) {
+        resultListener = theCallback;
+        getContentPane().setMinimumSize(new Dimension(200, 200));
+        setBounds(100, 100, 720, 610);
+        getContentPane().setLayout(new BorderLayout());
+        contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
+        getContentPane().add(contentPanel, BorderLayout.CENTER);
+        contentPanel.setLayout(new MigLayout("", "[][grow][94.00,grow][232.0,grow][130.00][grow]", "[][][20.00,grow][grow]"));
+        JLabel lblManufacturer = new JLabel("Manufacturer:");
+        contentPanel.add(lblManufacturer, "cell 2 0,alignx left,aligny center");
+
+        mfgTextField = new JTextField();
+        contentPanel.add(mfgTextField, "cell 3 0,growx");
+        mfgTextField.setColumns(10);
+
+        JLabel lenUnitLabel = new JLabel("Length Unit:");
+        contentPanel.add(lenUnitLabel, "cell 4 0,alignx left,aligny center");
+
+        lenUnitCombo = new JComboBox();
+        lenUnitCombo.setModel(new DefaultComboBoxModel(new String[]{"m", "cm", "mm", "in", "ft"}));
+        contentPanel.add(lenUnitCombo, "cell 5 0,growx");
+
+        JLabel typeLabel = new JLabel("Type:");
+        contentPanel.add(typeLabel, "cell 2 1,alignx left,aligny center");
+
+        typeCombo = new JComboBox();
+        typeCombo.addItemListener(this);
+        typeCombo.setModel(new DefaultComboBoxModel(new String[]{
+                trans.get(NOSE_CONE_KEY), trans.get(BODY_TUBE_KEY), trans.get(TUBE_COUPLER_KEY), trans.get(TRANSITION_KEY),
+                trans.get(CR_KEY), trans.get(BULKHEAD_KEY), trans.get(EB_KEY)}));
+        contentPanel.add(typeCombo, "cell 3 1,growx");
+
+        JLabel massUnitLabel = new JLabel("Mass Unit:");
+        contentPanel.add(massUnitLabel, "cell 4 1,alignx left,aligny center");
+
+        massUnitCombo = new JComboBox();
+        massUnitCombo.setModel(new DefaultComboBoxModel(new String[]{"kg", "g", "oz", "lb"}));
+        contentPanel.add(massUnitCombo, "cell 5 1,growx");
+
+        JLabel bhMaterialLabel = new JLabel("Material:");
+        contentPanel.add(bhMaterialLabel, "cell 2 2, alignx left");
+
+        materialChooser = new JComboBox(new MaterialModel(this, Material.Type.BULK));
+        contentPanel.add(materialChooser, "cell 3 2,growx");
+
+        componentOverlayPanel = new JPanel();
+        contentPanel.add(componentOverlayPanel, "cell 1 3 5 2,grow");
+        componentOverlayPanel.setLayout(new CardLayout(0, 0));
+
+        {
+            JPanel ncPanel = new JPanel();
+            componentOverlayPanel.add(ncPanel, "NOSECONE");
+            ncPanel.setLayout(new MigLayout("", "[61px][159.00,grow][35.00][109.00,grow][189.00,grow][grow]", "[16px][][][][][]"));
+            JLabel ncPartNoLabel = new JLabel("Part No:");
+            ncPanel.add(ncPartNoLabel, "cell 0 0,alignx left,aligny center");
+
+            ncPartNoTextField = new JTextField();
+            ncPanel.add(ncPartNoTextField, "cell 1 0,growx");
+            ncPartNoTextField.setColumns(10);
+
+            JLabel ncDescLabel = new JLabel("Description:");
+            ncPanel.add(ncDescLabel, "cell 3 0,alignx left,aligny center");
+
+            ncDescTextField = new JTextField();
+            ncPanel.add(ncDescTextField, "cell 4 0,growx");
+            ncDescTextField.setColumns(10);
+
+            ncFilledCB = new JCheckBox("Filled");
+            ncPanel.add(ncFilledCB, "cell 1 1");
+
+            JLabel ncMaterialLabel = new JLabel("Material:");
+            ncPanel.add(ncMaterialLabel, "cell 0 1,alignx left");
+
+            JLabel ncMassLabel = new JLabel("Mass:");
+            ncPanel.add(ncMassLabel, "cell 3 1,alignx left");
+
+            ncMassTextField = new JTextField();
+            ncMassTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            ncPanel.add(ncMassTextField, "cell 4 1,growx");
+            ncMassTextField.setColumns(10);
+
+            JLabel ncShapeLabel = new JLabel("Shape:");
+            ncPanel.add(ncShapeLabel, "cell 0 2,alignx left");
+
+            ncShapeCB = new JComboBox();
+            ncShapeCB.setModel(new DefaultComboBoxModel(new String[]{Transition.Shape.OGIVE.getName(),
+                    Transition.Shape.CONICAL.getName(), Transition.Shape.PARABOLIC.getName(),
+                    Transition.Shape.ELLIPSOID.getName(), Transition.Shape.HAACK.getName()}));
+            ncPanel.add(ncShapeCB, "cell 1 2,growx");
+
+            JLabel ncLengthLabel = new JLabel("Length:");
+            ncPanel.add(ncLengthLabel, "cell 3 2,alignx left");
+
+            ncLengthTextField = new JTextField();
+            ncLengthTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            ncPanel.add(ncLengthTextField, "cell 4 2,growx");
+            ncLengthTextField.setColumns(10);
+
+            JLabel ncAftDiaLabel = new JLabel("Aft Dia.:");
+            ncPanel.add(ncAftDiaLabel, "cell 0 3,alignx left, aligny top");
+
+            ncAftDiaTextField = new JTextField();
+            ncAftDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            ncPanel.add(ncAftDiaTextField, "cell 1 3,growx, aligny top");
+            ncAftDiaTextField.setColumns(10);
+
+            JLabel ncAftShoulderLenLabel = new JLabel("Aft Shoulder Len:");
+            ncPanel.add(ncAftShoulderLenLabel, "cell 0 4,alignx left, aligny top");
+
+            ncAftShoulderLenTextField = new JTextField();
+            ncAftShoulderLenTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            ncPanel.add(ncAftShoulderLenTextField, "cell 1 4,growx,aligny top");
+            ncAftShoulderLenTextField.setColumns(10);
+
+            JLabel ncAftShoulderDiaLabel = new JLabel("Aft Shoulder Dia.:");
+            ncPanel.add(ncAftShoulderDiaLabel, "cell 0 5,alignx left, aligny top");
+
+            ncAftShoulderDiaTextField = new JTextField();
+            ncAftShoulderDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            ncPanel.add(ncAftShoulderDiaTextField, "cell 1 5,growx, aligny top");
+            ncAftShoulderDiaTextField.setColumns(10);
+
+            JPanel panel = new JPanel();
+            panel.setMinimumSize(new Dimension(200, 200));
+            ncPanel.add(panel, "cell 4 3, span 1 3");
+            panel.setLayout(null);
+            ncImageBtn = new JButton("No Image");
+            ncImageBtn.setMaximumSize(new Dimension(75, 75));
+            ncImageBtn.setMinimumSize(new Dimension(75, 75));
+            panel.add(ncImageBtn);
+            ncImageBtn.setBounds(new Rectangle(6, 6, 132, 145));
+
+            ncImageBtn.addActionListener(new ActionListener() {
+                @Override
+                public void actionPerformed(final ActionEvent e) {
+                    int returnVal = imageChooser.showOpenDialog(PresetEditorDialog.this);
+
+                    if (returnVal == JFileChooser.APPROVE_OPTION) {
+                        File file = imageChooser.getSelectedFile();
+                        ncImage = scaleImage(new ImageIcon(file.getAbsolutePath()).getImage(), 155);
+                        ncImageBtn.setIcon(ncImage);
+                    }
+                }
+            });
+
+        }
+        {
+            JPanel trPanel = new JPanel();
+            componentOverlayPanel.add(trPanel, "TRANSITION");
+            trPanel.setLayout(new MigLayout("", "[][grow][][grow]", "[][][28.00][31.00][][]"));
+
+            JLabel trPartNoLabel = new JLabel("Part No:");
+            trPanel.add(trPartNoLabel, "cell 0 0,alignx left");
+
+            trPartNoTextField = new JTextField();
+            trPanel.add(trPartNoTextField, "cell 1 0,growx");
+            trPartNoTextField.setColumns(10);
+
+            JLabel trDescLabel = new JLabel("Description:");
+            trPanel.add(trDescLabel, "cell 2 0,alignx left");
+
+            trDescTextField = new JTextField();
+            trPanel.add(trDescTextField, "cell 3 0,growx");
+            trDescTextField.setColumns(10);
+
+            trFilledCB = new JCheckBox("Filled");
+            trPanel.add(trFilledCB, "cell 1 1");
+
+            JLabel trMassLabel = new JLabel("Mass:");
+            trPanel.add(trMassLabel, "cell 2 1,alignx left");
+
+            trMassTextField = new JTextField();
+            trMassTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            trPanel.add(trMassTextField, "cell 3 1,growx");
+            trMassTextField.setColumns(10);
+
+            JLabel trShapeLabel = new JLabel("Shape:");
+            trPanel.add(trShapeLabel, "cell 0 2,alignx left");
+
+            trShapeCB = new JComboBox();
+            trShapeCB.setModel(new DefaultComboBoxModel(new String[]{Transition.Shape.OGIVE.getName(),
+                    Transition.Shape.CONICAL.getName(), Transition.Shape.PARABOLIC.getName(),
+                    Transition.Shape.ELLIPSOID.getName(), Transition.Shape.HAACK.getName()}));
+            trPanel.add(trShapeCB, "cell 1 2,growx");
+
+            JLabel trLengthLabel = new JLabel("Length:");
+            trPanel.add(trLengthLabel, "cell 2 2,alignx left");
+
+            trLengthTextField = new JTextField();
+            trLengthTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            trPanel.add(trLengthTextField, "cell 3 2,growx");
+            trLengthTextField.setColumns(10);
+
+            JLabel trAftDiaLabel = new JLabel("Aft Dia.:");
+            trPanel.add(trAftDiaLabel, "cell 0 3,alignx left");
+
+            trAftDiaTextField = new JTextField();
+            trAftDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            trPanel.add(trAftDiaTextField, "cell 1 3,growx");
+            trAftDiaTextField.setColumns(10);
+
+            JLabel trForeDiaLabel = new JLabel("Fore Dia.:");
+            trPanel.add(trForeDiaLabel, "cell 2 3,alignx left");
+
+            trForeDiaTextField = new JTextField();
+            trForeDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            trPanel.add(trForeDiaTextField, "cell 3 3,growx");
+            trForeDiaTextField.setColumns(10);
+
+            JLabel trAftShouldDiaLabel = new JLabel("Aft Shoulder Dia.:");
+            trPanel.add(trAftShouldDiaLabel, "cell 0 4,alignx left");
+
+            trAftShoulderDiaTextField = new JTextField();
+            trAftShoulderDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            trPanel.add(trAftShoulderDiaTextField, "cell 1 4,growx");
+            trAftShoulderDiaTextField.setColumns(10);
+
+            JLabel trForeShouldDiaLabel = new JLabel("Fore Shoulder Dia.:");
+            trPanel.add(trForeShouldDiaLabel, "cell 2 4,alignx left");
+
+            trForeShoulderDiaTextField = new JTextField();
+            trForeShoulderDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            trPanel.add(trForeShoulderDiaTextField, "cell 3 4,growx");
+            trForeShoulderDiaTextField.setColumns(10);
+
+            JLabel trAftShoulderLenLabel = new JLabel("Aft Shoulder Len.:");
+            trPanel.add(trAftShoulderLenLabel, "cell 0 5,alignx left");
+
+            trAftShoulderLenTextField = new JTextField();
+            trAftShoulderLenTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            trPanel.add(trAftShoulderLenTextField, "cell 1 5,growx");
+            trAftShoulderLenTextField.setColumns(10);
+
+            JLabel lblForeShoulderLen = new JLabel("Fore Shoulder Len.:");
+            trPanel.add(lblForeShoulderLen, "cell 2 5,alignx left");
+
+            trForeShoulderLenTextField = new JTextField();
+            trForeShoulderLenTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            trPanel.add(trForeShoulderLenTextField, "cell 3 5,growx");
+            trForeShoulderLenTextField.setColumns(10);
+
+            JPanel panel = new JPanel();
+            panel.setMinimumSize(new Dimension(200, 200));
+            trPanel.add(panel, "cell 3 6");
+            panel.setLayout(null);
+            trImageBtn = new JButton("No Image");
+            trImageBtn.setMaximumSize(new Dimension(75, 75));
+            trImageBtn.setMinimumSize(new Dimension(75, 75));
+            panel.add(trImageBtn);
+            trImageBtn.setBounds(new Rectangle(6, 6, 132, 145));
+
+            trImageBtn.addActionListener(new ActionListener() {
+                @Override
+                public void actionPerformed(final ActionEvent e) {
+                    int returnVal = imageChooser.showOpenDialog(PresetEditorDialog.this);
+
+                    if (returnVal == JFileChooser.APPROVE_OPTION) {
+                        File file = imageChooser.getSelectedFile();
+                        trImage = scaleImage(new ImageIcon(file.getAbsolutePath()).getImage(), 155);
+                        trImageBtn.setIcon(trImage);
+                    }
+                }
+            });
+
+        }
+        {
+            JPanel btPanel = new JPanel();
+            componentOverlayPanel.add(btPanel, "BODYTUBE");
+            btPanel.setLayout(new MigLayout("", "[][grow][][grow]", "[][][][]"));
+            JLabel btPartNoLabel = new JLabel("Part No:");
+            btPanel.add(btPartNoLabel, "cell 0 0,alignx left");
+
+            btPartNoTextField = new JTextField();
+            btPanel.add(btPartNoTextField, "cell 1 0,growx");
+            btPartNoTextField.setColumns(10);
+
+            JLabel btDescLabel = new JLabel("Description:");
+            btPanel.add(btDescLabel, "cell 2 0,alignx left");
+
+            btDescTextField = new JTextField();
+            btPanel.add(btDescTextField, "cell 3 0,growx");
+            btDescTextField.setColumns(10);
+
+            JLabel btMassLabel = new JLabel("Mass:");
+            btPanel.add(btMassLabel, "cell 2 1,alignx left");
+
+            btMassTextField = new JTextField();
+            btMassTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            btPanel.add(btMassTextField, "cell 3 1,growx");
+            btMassTextField.setColumns(10);
+
+            JLabel btInnerDiaLabel = new JLabel("Inner Dia.:");
+            btPanel.add(btInnerDiaLabel, "cell 0 2,alignx left");
+
+            btInnerDiaTextField = new JTextField();
+            btInnerDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            btPanel.add(btInnerDiaTextField, "cell 1 2,growx");
+            btInnerDiaTextField.setColumns(10);
+
+            JLabel btOuterDiaLabel = new JLabel("Outer Dia.:");
+            btPanel.add(btOuterDiaLabel, "cell 2 2,alignx left");
+
+            btOuterDiaTextField = new JTextField();
+            btOuterDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            btPanel.add(btOuterDiaTextField, "cell 3 2,growx");
+            btOuterDiaTextField.setColumns(10);
+
+            JLabel btLengthLabel = new JLabel("Length:");
+            btPanel.add(btLengthLabel, "cell 0 1,alignx left");
+
+            btLengthTextField = new JTextField();
+            btLengthTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            btPanel.add(btLengthTextField, "cell 1 1,growx");
+            btLengthTextField.setColumns(10);
+
+            JPanel panel = new JPanel();
+            panel.setMinimumSize(new Dimension(200, 200));
+            btPanel.add(panel, "cell 3 3");
+            panel.setLayout(null);
+            btImageBtn = new JButton("No Image");
+            btImageBtn.setMaximumSize(new Dimension(75, 75));
+            btImageBtn.setMinimumSize(new Dimension(75, 75));
+            panel.add(btImageBtn);
+            btImageBtn.setBounds(new Rectangle(6, 6, 132, 145));
+
+            btImageBtn.addActionListener(new ActionListener() {
+                @Override
+                public void actionPerformed(final ActionEvent e) {
+                    int returnVal = imageChooser.showOpenDialog(PresetEditorDialog.this);
+
+                    if (returnVal == JFileChooser.APPROVE_OPTION) {
+                        File file = imageChooser.getSelectedFile();
+                        btImage = scaleImage(new ImageIcon(file.getAbsolutePath()).getImage(), 155);
+                        btImageBtn.setIcon(btImage);
+                    }
+                }
+            });
+
+        }
+        {
+            JPanel tcPanel = new JPanel();
+            componentOverlayPanel.add(tcPanel, "TUBECOUPLER");
+            tcPanel.setLayout(new MigLayout("", "[][grow][][grow]", "[][][][]"));
+            JLabel tcPartNoLabel = new JLabel("Part No:");
+            tcPanel.add(tcPartNoLabel, "cell 0 0,alignx left");
+
+            tcPartNoTextField = new JTextField();
+            tcPanel.add(tcPartNoTextField, "cell 1 0,growx");
+            tcPartNoTextField.setColumns(10);
+
+            JLabel tcDescLabel = new JLabel("Description:");
+            tcPanel.add(tcDescLabel, "cell 2 0,alignx left");
+
+            tcDescTextField = new JTextField();
+            tcPanel.add(tcDescTextField, "cell 3 0,growx");
+            tcDescTextField.setColumns(10);
+
+            JLabel tcMassLabel = new JLabel("Mass:");
+            tcPanel.add(tcMassLabel, "cell 2 1,alignx left");
+
+            tcMassTextField = new JTextField();
+            tcMassTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            tcPanel.add(tcMassTextField, "cell 3 1,growx");
+            tcMassTextField.setColumns(10);
+
+            JLabel tcInnerDiaLabel = new JLabel("Inner Dia.:");
+            tcPanel.add(tcInnerDiaLabel, "cell 0 2,alignx left, aligny top");
+
+            tcInnerDiaTextField = new JTextField();
+            tcInnerDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            tcPanel.add(tcInnerDiaTextField, "cell 1 2,growx, aligny top");
+            tcInnerDiaTextField.setColumns(10);
+
+            JLabel tcOuterDiaLabel = new JLabel("Outer Dia.:");
+            tcPanel.add(tcOuterDiaLabel, "cell 2 2,alignx left, aligny top");
+
+            tcOuterDiaTextField = new JTextField();
+            tcOuterDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            tcPanel.add(tcOuterDiaTextField, "cell 3 2,growx, aligny top");
+            tcOuterDiaTextField.setColumns(10);
+
+            JLabel tcLengthLabel = new JLabel("Length:");
+            tcPanel.add(tcLengthLabel, "cell 0 1,alignx left");
+
+            tcLengthTextField = new JTextField();
+            tcLengthTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            tcPanel.add(tcLengthTextField, "cell 1 1,growx");
+            tcLengthTextField.setColumns(10);
+
+            JPanel panel = new JPanel();
+            panel.setMinimumSize(new Dimension(200, 200));
+            tcPanel.add(panel, "cell 3 3");
+            panel.setLayout(null);
+            tcImageBtn = new JButton("No Image");
+            tcImageBtn.setMaximumSize(new Dimension(75, 75));
+            tcImageBtn.setMinimumSize(new Dimension(75, 75));
+            panel.add(tcImageBtn);
+            tcImageBtn.setBounds(new Rectangle(6, 6, 132, 145));
+
+            tcImageBtn.addActionListener(new ActionListener() {
+                @Override
+                public void actionPerformed(final ActionEvent e) {
+                    int returnVal = imageChooser.showOpenDialog(PresetEditorDialog.this);
+
+                    if (returnVal == JFileChooser.APPROVE_OPTION) {
+                        File file = imageChooser.getSelectedFile();
+                        tcImage = scaleImage(new ImageIcon(file.getAbsolutePath()).getImage(), 155);
+                        tcImageBtn.setIcon(tcImage);
+                    }
+                }
+            });
+
+
+        }
+        {
+            JPanel bhPanel = new JPanel();
+            componentOverlayPanel.add(bhPanel, "BULKHEAD");
+            bhPanel.setLayout(new MigLayout("", "[][157.00,grow 79][65.00][grow]", "[][][][]"));
+
+            JLabel bhPartNoLabel = new JLabel("Part No:");
+            bhPanel.add(bhPartNoLabel, "cell 0 0,alignx left");
+
+            bhPartNoTextField = new JTextField();
+            bhPanel.add(bhPartNoTextField, "cell 1 0,growx");
+            bhPartNoTextField.setColumns(10);
+
+            JLabel bhDescLabel = new JLabel("Description:");
+            bhPanel.add(bhDescLabel, "cell 2 0,alignx left");
+
+            bhDescTextField = new JTextField();
+            bhPanel.add(bhDescTextField, "cell 3 0,growx");
+            bhDescTextField.setColumns(10);
+
+            JLabel bhOuterDiaLabel = new JLabel("Outer Dia.:");
+            bhPanel.add(bhOuterDiaLabel, "cell 0 2,alignx left, aligny top");
+
+            bhOuterDiaTextField = new JTextField();
+            bhPanel.add(bhOuterDiaTextField, "cell 1 2,growx, aligny top");
+            bhOuterDiaTextField.setColumns(10);
+
+            JLabel bhMassLabel = new JLabel("Mass:");
+            bhPanel.add(bhMassLabel, "cell 2 1,alignx left");
+
+            bhMassTextField = new JTextField();
+            bhPanel.add(bhMassTextField, "cell 3 1,growx");
+            bhMassTextField.setColumns(10);
+
+            JLabel bhLengthLabel = new JLabel("Thickness:");
+            bhPanel.add(bhLengthLabel, "cell 0 1,alignx left");
+
+            bhLengthTextField = new JTextField();
+            bhPanel.add(bhLengthTextField, "cell 1 1,growx");
+            bhLengthTextField.setColumns(10);
+
+            JPanel panel = new JPanel();
+            panel.setMinimumSize(new Dimension(200, 200));
+            bhPanel.add(panel, "cell 3 2");
+            panel.setLayout(null);
+            bhImageBtn = new JButton("No Image");
+            bhImageBtn.setMaximumSize(new Dimension(75, 75));
+            bhImageBtn.setMinimumSize(new Dimension(75, 75));
+            panel.add(bhImageBtn);
+            bhImageBtn.setBounds(new Rectangle(6, 6, 132, 145));
+
+            bhImageBtn.addActionListener(new ActionListener() {
+                @Override
+                public void actionPerformed(final ActionEvent e) {
+                    int returnVal = imageChooser.showOpenDialog(PresetEditorDialog.this);
+
+                    if (returnVal == JFileChooser.APPROVE_OPTION) {
+                        File file = imageChooser.getSelectedFile();
+                        bhImage = scaleImage(new ImageIcon(file.getAbsolutePath()).getImage(), 155);
+                        bhImageBtn.setIcon(bhImage);
+                    }
+                }
+            });
+
+        }
+        {
+            JPanel crPanel = new JPanel();
+            componentOverlayPanel.add(crPanel, "CENTERINGRING");
+            crPanel.setLayout(new MigLayout("", "[][grow][][grow]", "[][][][]"));
+
+            JLabel crPartNoLabel = new JLabel("Part No:");
+            crPanel.add(crPartNoLabel, "cell 0 0,alignx left");
+
+            crPartNoTextField = new JTextField();
+            crPanel.add(crPartNoTextField, "cell 1 0, growx");
+            crPartNoTextField.setColumns(10);
+
+            JLabel crDescLabel = new JLabel("Description:");
+            crPanel.add(crDescLabel, "cell 2 0,alignx left");
+
+            crDescTextField = new JTextField();
+            crPanel.add(crDescTextField, "cell 3 0, growx");
+            crDescTextField.setColumns(10);
+
+            JLabel crMassLabel = new JLabel("Mass:");
+            crPanel.add(crMassLabel, "cell 2 1,alignx left");
+
+            crMassTextField = new JTextField();
+            crMassTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            crPanel.add(crMassTextField, "cell 3 1, growx");
+            crMassTextField.setColumns(10);
+
+            JLabel crOuterDiaLabel = new JLabel("Outer Dia.:");
+            crPanel.add(crOuterDiaLabel, "cell 0 2,alignx left");
+
+            crOuterDiaTextField = new JTextField();
+            crOuterDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            crPanel.add(crOuterDiaTextField, "cell 1 2, growx");
+            crOuterDiaTextField.setColumns(10);
+
+            JLabel crInnerDiaLabel = new JLabel("Inner Dia.:");
+            crPanel.add(crInnerDiaLabel, "cell 2 2,alignx left");
+
+            crInnerDiaTextField = new JTextField();
+            crInnerDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            crPanel.add(crInnerDiaTextField, "cell 3 2, growx");
+            crInnerDiaTextField.setColumns(10);
+
+            JLabel crThicknessLabel = new JLabel("Thickness:");
+            crPanel.add(crThicknessLabel, "cell 0 1,alignx left");
+
+            crThicknessTextField = new JTextField();
+            crThicknessTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            crPanel.add(crThicknessTextField, "cell 1 1, growx");
+            crThicknessTextField.setColumns(10);
+
+            JPanel panel = new JPanel();
+            panel.setMinimumSize(new Dimension(200, 200));
+            crPanel.add(panel, "cell 3 3");
+            panel.setLayout(null);
+            crImageBtn = new JButton("No Image");
+            crImageBtn.setMaximumSize(new Dimension(75, 75));
+            crImageBtn.setMinimumSize(new Dimension(75, 75));
+            panel.add(crImageBtn);
+            crImageBtn.setBounds(new Rectangle(6, 6, 132, 145));
+
+            crImageBtn.addActionListener(new ActionListener() {
+                @Override
+                public void actionPerformed(final ActionEvent e) {
+                    int returnVal = imageChooser.showOpenDialog(PresetEditorDialog.this);
+
+                    if (returnVal == JFileChooser.APPROVE_OPTION) {
+                        File file = imageChooser.getSelectedFile();
+                        crImage = scaleImage(new ImageIcon(file.getAbsolutePath()).getImage(), 155);
+                        crImageBtn.setIcon(crImage);
+                    }
+                }
+            });
+
+        }
+        {
+            JPanel ebPanel = new JPanel();
+            componentOverlayPanel.add(ebPanel, "ENGINEBLOCK");
+            ebPanel.setLayout(new MigLayout("", "[][grow][][grow]", "[][][][]"));
+            JLabel ebPartNoLabel = new JLabel("Part No:");
+            ebPanel.add(ebPartNoLabel, "cell 0 0,alignx left");
+
+            ebPartNoTextField = new JTextField();
+            ebPanel.add(ebPartNoTextField, "cell 1 0,growx");
+            ebPartNoTextField.setColumns(10);
+
+            JLabel ebDescLabel = new JLabel("Description:");
+            ebPanel.add(ebDescLabel, "cell 2 0,alignx left");
+
+            ebDescTextField = new JTextField();
+            ebPanel.add(ebDescTextField, "cell 3 0,growx");
+            ebDescTextField.setColumns(10);
+
+            JLabel ebMassLabel = new JLabel("Mass:");
+            ebPanel.add(ebMassLabel, "cell 2 1,alignx left");
+
+            ebMassTextField = new JTextField();
+            ebMassTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            ebPanel.add(ebMassTextField, "cell 3 1,growx");
+            ebMassTextField.setColumns(10);
+
+            JLabel ebOuterDiaLabel = new JLabel("Outer Dia.:");
+            ebPanel.add(ebOuterDiaLabel, "cell 0 2,alignx left");
+
+            ebOuterDiaTextField = new JTextField();
+            ebOuterDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            ebPanel.add(ebOuterDiaTextField, "cell 1 2,growx");
+            ebOuterDiaTextField.setColumns(10);
+
+            JLabel ebInnerDiaLabel = new JLabel("Inner Dia.:");
+            ebPanel.add(ebInnerDiaLabel, "cell 2 2,alignx left");
+
+            ebInnerDiaTextField = new JTextField();
+            ebInnerDiaTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            ebPanel.add(ebInnerDiaTextField, "cell 3 2,growx");
+            ebInnerDiaTextField.setColumns(10);
+
+            JLabel ebThicknessLabel = new JLabel("Thickness:");
+            ebPanel.add(ebThicknessLabel, "cell 0 1,alignx left");
+
+            ebThicknessTextField = new JTextField();
+            ebThicknessTextField.setInputVerifier(NON_NEGATIVE_DECIMAL);
+            ebPanel.add(ebThicknessTextField, "cell 1 1,growx");
+            ebThicknessTextField.setColumns(10);
+
+            JPanel panel = new JPanel();
+            panel.setMinimumSize(new Dimension(200, 200));
+            ebPanel.add(panel, "cell 3 3");
+            panel.setLayout(null);
+            ebImageBtn = new JButton("No Image");
+            ebImageBtn.setMaximumSize(new Dimension(75, 75));
+            ebImageBtn.setMinimumSize(new Dimension(75, 75));
+            panel.add(ebImageBtn);
+            ebImageBtn.setBounds(new Rectangle(6, 6, 132, 145));
+
+            ebImageBtn.addActionListener(new ActionListener() {
+                @Override
+                public void actionPerformed(final ActionEvent e) {
+                    int returnVal = imageChooser.showOpenDialog(PresetEditorDialog.this);
+
+                    if (returnVal == JFileChooser.APPROVE_OPTION) {
+                        File file = imageChooser.getSelectedFile();
+                        ebImage = scaleImage(new ImageIcon(file.getAbsolutePath()).getImage(), 155);
+                        ebImageBtn.setIcon(ebImage);
+                    }
+                }
+            });
+        }
+
+        JPanel buttonPane = new JPanel();
+        getContentPane().add(buttonPane, BorderLayout.SOUTH);
+        buttonPane.setLayout(new MigLayout("", "[130px][176.00px][131.00px]", "[29px]"));
+        JButton btnSaveAndNew = new JButton("Save and New");
+        btnSaveAndNew.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseClicked(MouseEvent arg0) {
+                saveResult();
+            }
+        });
+        buttonPane.add(btnSaveAndNew, "cell 0 0,alignx left,aligny top");
+
+        JButton okButton = new JButton("Save and Close");
+        okButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent event) {
+                saveResult();
+                dispose();
+            }
+        });
+        okButton.setActionCommand("OK");
+        buttonPane.add(okButton, "cell 1 0,alignx left,aligny top");
+        getRootPane().setDefaultButton(okButton);
+
+        JButton cancelButton = new JButton("Close");
+        cancelButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent event) {
+                dispose();
+            }
+        });
+        cancelButton.setActionCommand("Close");
+        buttonPane.add(cancelButton, "cell 6 0,alignx right,aligny top");
+
+        if (toEdit != null) {
+            fillEditor(toEdit);
+            typeCombo.setEditable(false);
+        }
+        else {
+            typeCombo.setEditable(true);
+        }
+    }
+
+    private JFileChooser createImageChooser() {
+        final JFileChooser chooser = new JFileChooser();
+        ImagePreviewPanel preview = new ImagePreviewPanel();
+        chooser.setAccessory(preview);
+        chooser.addPropertyChangeListener(preview);
+        chooser.setAcceptAllFileFilterUsed(false);
+        chooser.addChoosableFileFilter(new FileFilter() {
+            @Override
+            public boolean accept(final File f) {
+                return f.getName().endsWith(".png") || f.getName().endsWith(".jpg");
+            }
+
+            @Override
+            public String getDescription() {
+                return "Image Files";
+            }
+        });
+        return chooser;
+    }
+
+    private void fillEditor(ComponentPreset preset) {
+        ComponentPreset.Type t = preset.getType();
+
+        mfgTextField.setText(preset.get(ComponentPreset.MANUFACTURER).getDisplayName());
+        materialChooser.getModel().setSelectedItem(preset.get(ComponentPreset.MATERIAL));
+        switch (t) {
+            case BODY_TUBE:
+                typeCombo.setSelectedItem(trans.get(BODY_TUBE_KEY));
+                btDescTextField.setText(preset.get(ComponentPreset.DESCRIPTION));
+
+                if (preset.has(ComponentPreset.INNER_DIAMETER)) {
+                    btInnerDiaTextField.setText(preset.get(ComponentPreset.INNER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.LENGTH)) {
+                    btLengthTextField.setText(preset.get(ComponentPreset.LENGTH).toString());
+                }
+                if (preset.has(ComponentPreset.MASS)) {
+                    btMassTextField.setText(preset.get(ComponentPreset.MASS).toString());
+                }
+                if (preset.has(ComponentPreset.OUTER_DIAMETER)) {
+                    btOuterDiaTextField.setText(preset.get(ComponentPreset.OUTER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.IMAGE)) {
+                    btImage = new ImageIcon(byteArrayToImage(preset.get(ComponentPreset.IMAGE)));
+                    btImageBtn.setIcon(btImage);
+                }
+                btPartNoTextField.setText(preset.get(ComponentPreset.PARTNO));
+                break;
+            case BULK_HEAD:
+                typeCombo.setSelectedItem(trans.get(BULKHEAD_KEY));
+                bhDescTextField.setText(preset.get(ComponentPreset.DESCRIPTION));
+                if (preset.has(ComponentPreset.LENGTH)) {
+                    bhLengthTextField.setText(preset.get(ComponentPreset.LENGTH).toString());
+                }
+                if (preset.has(ComponentPreset.MASS)) {
+                    bhMassTextField.setText(preset.get(ComponentPreset.MASS).toString());
+                }
+                if (preset.has(ComponentPreset.OUTER_DIAMETER)) {
+                    bhOuterDiaTextField.setText(preset.get(ComponentPreset.OUTER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.IMAGE)) {
+                    bhImage = new ImageIcon(byteArrayToImage(preset.get(ComponentPreset.IMAGE)));
+                    bhImageBtn.setIcon(bhImage);
+                }
+                bhPartNoTextField.setText(preset.get(ComponentPreset.PARTNO));
+                break;
+            case CENTERING_RING:
+                typeCombo.setSelectedItem(trans.get(CR_KEY));
+                crDescTextField.setText(preset.get(ComponentPreset.DESCRIPTION));
+                if (preset.has(ComponentPreset.INNER_DIAMETER)) {
+                    crInnerDiaTextField.setText(preset.get(ComponentPreset.INNER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.LENGTH)) {
+                    crThicknessTextField.setText(preset.get(ComponentPreset.LENGTH).toString());
+                }
+                if (preset.has(ComponentPreset.MASS)) {
+                    crMassTextField.setText(preset.get(ComponentPreset.MASS).toString());
+                }
+                if (preset.has(ComponentPreset.OUTER_DIAMETER)) {
+                    crOuterDiaTextField.setText(preset.get(ComponentPreset.OUTER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.IMAGE)) {
+                    crImage = new ImageIcon(byteArrayToImage(preset.get(ComponentPreset.IMAGE)));
+                    crImageBtn.setIcon(crImage);
+                }
+                crPartNoTextField.setText(preset.get(ComponentPreset.PARTNO));
+                break;
+            case ENGINE_BLOCK:
+                typeCombo.setSelectedItem(trans.get(EB_KEY));
+                ebDescTextField.setText(preset.get(ComponentPreset.DESCRIPTION));
+                if (preset.has(ComponentPreset.INNER_DIAMETER)) {
+                    ebInnerDiaTextField.setText(preset.get(ComponentPreset.INNER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.LENGTH)) {
+                    ebThicknessTextField.setText(preset.get(ComponentPreset.LENGTH).toString());
+                }
+                if (preset.has(ComponentPreset.MASS)) {
+                    ebMassTextField.setText(preset.get(ComponentPreset.MASS).toString());
+                }
+                if (preset.has(ComponentPreset.OUTER_DIAMETER)) {
+                    ebOuterDiaTextField.setText(preset.get(ComponentPreset.OUTER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.IMAGE)) {
+                    ebImage = new ImageIcon(byteArrayToImage(preset.get(ComponentPreset.IMAGE)));
+                    ebImageBtn.setIcon(ebImage);
+                }
+                ebPartNoTextField.setText(preset.get(ComponentPreset.PARTNO));
+                break;
+            case NOSE_CONE:
+                typeCombo.setSelectedItem(trans.get(NOSE_CONE_KEY));
+                ncDescTextField.setText(preset.get(ComponentPreset.DESCRIPTION));
+                if (preset.has(ComponentPreset.AFT_OUTER_DIAMETER)) {
+                    ncAftDiaTextField.setText(preset.get(ComponentPreset.AFT_OUTER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.AFT_SHOULDER_DIAMETER)) {
+                    ncAftShoulderDiaTextField.setText(preset.get(ComponentPreset.AFT_SHOULDER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.AFT_SHOULDER_LENGTH)) {
+                    ncAftShoulderLenTextField.setText(preset.get(ComponentPreset.AFT_SHOULDER_LENGTH).toString());
+                }
+                if (preset.has(ComponentPreset.MASS)) {
+                    ncMassTextField.setText(preset.get(ComponentPreset.MASS).toString());
+                }
+                if (preset.has(ComponentPreset.SHAPE)) {
+                    ncShapeCB.setSelectedItem(preset.get(ComponentPreset.SHAPE).toString());
+                }
+                if (preset.has(ComponentPreset.FILLED)) {
+                    ncFilledCB.setSelected((preset.get(ComponentPreset.FILLED)));
+                }
+                if (preset.has(ComponentPreset.LENGTH)) {
+                    ncLengthTextField.setText(preset.get(ComponentPreset.LENGTH).toString());
+                }
+                if (preset.has(ComponentPreset.IMAGE)) {
+                    ncImage = new ImageIcon(byteArrayToImage(preset.get(ComponentPreset.IMAGE)));
+                    ncImageBtn.setIcon(ncImage);
+                }
+                ncPartNoTextField.setText(preset.get(ComponentPreset.PARTNO));
+                break;
+            case TRANSITION:
+                typeCombo.setSelectedItem(trans.get(TRANSITION_KEY));
+                trDescTextField.setText(preset.get(ComponentPreset.DESCRIPTION));
+                if (preset.has(ComponentPreset.AFT_OUTER_DIAMETER)) {
+                    trAftDiaTextField.setText(preset.get(ComponentPreset.AFT_OUTER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.AFT_SHOULDER_DIAMETER)) {
+                    trAftShoulderDiaTextField.setText(preset.get(ComponentPreset.AFT_SHOULDER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.AFT_SHOULDER_LENGTH)) {
+                    trAftShoulderLenTextField.setText(preset.get(ComponentPreset.AFT_SHOULDER_LENGTH).toString());
+                }
+                if (preset.has(ComponentPreset.FORE_OUTER_DIAMETER)) {
+                    trForeDiaTextField.setText(preset.get(ComponentPreset.FORE_OUTER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.FORE_SHOULDER_DIAMETER)) {
+                    trForeShoulderDiaTextField.setText(preset.get(ComponentPreset.FORE_SHOULDER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.FORE_SHOULDER_LENGTH)) {
+                    trForeShoulderLenTextField.setText(preset.get(ComponentPreset.FORE_SHOULDER_LENGTH).toString());
+                }
+                if (preset.has(ComponentPreset.MASS)) {
+                    trMassTextField.setText(preset.get(ComponentPreset.MASS).toString());
+                }
+                if (preset.has(ComponentPreset.SHAPE)) {
+                    trShapeCB.setSelectedItem(preset.get(ComponentPreset.SHAPE).toString());
+                }
+                if (preset.has(ComponentPreset.FILLED)) {
+                    trFilledCB.setSelected((preset.get(ComponentPreset.FILLED)));
+                }
+                if (preset.has(ComponentPreset.LENGTH)) {
+                    trLengthTextField.setText(preset.get(ComponentPreset.LENGTH).toString());
+                }
+                if (preset.has(ComponentPreset.IMAGE)) {
+                    trImage = new ImageIcon(byteArrayToImage(preset.get(ComponentPreset.IMAGE)));
+                    trImageBtn.setIcon(trImage);
+                }
+                trPartNoTextField.setText(preset.get(ComponentPreset.PARTNO));
+                break;
+            case TUBE_COUPLER:
+                typeCombo.setSelectedItem(trans.get(TUBE_COUPLER_KEY));
+                tcDescTextField.setText(preset.get(ComponentPreset.DESCRIPTION));
+                if (preset.has(ComponentPreset.INNER_DIAMETER)) {
+                    tcInnerDiaTextField.setText(preset.get(ComponentPreset.INNER_DIAMETER).toString());
+                }
+                if (preset.has(ComponentPreset.LENGTH)) {
+                    tcLengthTextField.setText(preset.get(ComponentPreset.LENGTH).toString());
+                }
+                if (preset.has(ComponentPreset.MASS)) {
+                    tcMassTextField.setText(preset.get(ComponentPreset.MASS).toString());
+                }
+                if (preset.has(ComponentPreset.OUTER_DIAMETER)) {
+                    tcOuterDiaTextField.setText(preset.get(ComponentPreset.OUTER_DIAMETER).toString());
+                }
+                tcPartNoTextField.setText(preset.get(ComponentPreset.PARTNO));
+                if (preset.has(ComponentPreset.IMAGE)) {
+                    tcImage = new ImageIcon(byteArrayToImage(preset.get(ComponentPreset.IMAGE)));
+                    tcImageBtn.setIcon(tcImage);
+                }
+                break;
+            default:
+        }
+    }
+
+    private void saveResult() {
+        String type = (String) typeCombo.getSelectedItem();
+
+        ComponentPreset result = null;
+
+        if (type.equals(trans.get(NOSE_CONE_KEY))) {
+            result = extractNoseCone();
+            if (result != null) {
+                clearNoseCone();
+            }
+        }
+        else if (type.equals(trans.get(TRANSITION_KEY))) {
+            result = extractTransition();
+            if (result != null) {
+                clearTransition();
+            }
+        }
+        else if (type.equals(trans.get(BODY_TUBE_KEY))) {
+            result = extractBodyTube();
+            if (result != null) {
+                clearBodyTube();
+            }
+        }
+        else if (type.equals(trans.get(TUBE_COUPLER_KEY))) {
+            result = extractTubeCoupler();
+            if (result != null) {
+                clearTubeCoupler();
+            }
+        }
+        else if (type.equals(trans.get(EB_KEY))) {
+            result = extractEngineBlock();
+            if (result != null) {
+                clearEngineBlock();
+            }
+        }
+        else if (type.equals(trans.get(CR_KEY))) {
+            result = extractCenteringRing();
+            if (result != null) {
+                clearCenteringRing();
+            }
+        }
+        else if (type.equals(trans.get(BULKHEAD_KEY))) {
+            result = extractBulkhead();
+            if (result != null) {
+                clearBulkhead();
+            }
+        }
+        resultListener.notifyResult(result);
+    }
+
+    private ComponentPreset extractNoseCone() {
+        TypedPropertyMap props = new TypedPropertyMap();
+        try {
+            PrintUnit lpu = lengthMap.get(lenUnitCombo.getSelectedItem());
+
+            props.put(ComponentPreset.TYPE, ComponentPreset.Type.NOSE_CONE);
+            if (!ncAftDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.AFT_OUTER_DIAMETER, lpu.toMeters(Double.parseDouble(ncAftDiaTextField.getText())));
+            }
+            if (!ncAftShoulderDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.AFT_SHOULDER_DIAMETER, lpu.toMeters(Double.parseDouble(ncAftShoulderDiaTextField.getText())));
+            }
+            if (!ncAftShoulderLenTextField.getText().equals("")) {
+                props.put(ComponentPreset.AFT_SHOULDER_LENGTH, lpu.toMeters(Double.parseDouble(ncAftShoulderLenTextField.getText())));
+            }
+            props.put(ComponentPreset.DESCRIPTION, ncDescTextField.getText());
+            props.put(ComponentPreset.PARTNO, ncPartNoTextField.getText());
+            props.put(ComponentPreset.MANUFACTURER, Manufacturer.getManufacturer(mfgTextField.getText()));
+            if (!ncLengthTextField.getText().equals("")) {
+                props.put(ComponentPreset.LENGTH, lpu.toMeters(Double.parseDouble(ncLengthTextField.getText())));
+            }
+            props.put(ComponentPreset.SHAPE, Transition.Shape.toShape((String) ncShapeCB.getSelectedItem()));
+            final Material material = (Material) materialChooser.getSelectedItem();
+            if (material != null) {
+                props.put(ComponentPreset.MATERIAL, material);
+            }
+            else {
+                JOptionPane.showMessageDialog(null, "A material must be selected.", "Error", JOptionPane.ERROR_MESSAGE);
+                return null;
+            }
+            if (!ncMassTextField.getText().equals("")) {
+                props.put(ComponentPreset.MASS, lpu.toMeters(Double.parseDouble(ncMassTextField.getText())));
+            }
+            props.put(ComponentPreset.FILLED, ncFilledCB.isSelected());
+            if (ncImage != null) {
+                props.put(ComponentPreset.IMAGE, imageToByteArray(ncImage.getImage()));
+            }
+
+            return ComponentPresetFactory.create(props);
+        }
+        catch (NumberFormatException nfe) {
+            JOptionPane.showMessageDialog(null, "Could not convert nose cone attribute.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        catch (InvalidComponentPresetException e) {
+            JOptionPane.showMessageDialog(null, "Mandatory nose cone attribute not set.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        return null;
+    }
+
+    private void clearNoseCone() {
+        ncAftDiaTextField.setText("");
+        ncAftShoulderDiaTextField.setText("");
+        ncAftShoulderLenTextField.setText("");
+        ncDescTextField.setText("");
+        ncPartNoTextField.setText("");
+        ncLengthTextField.setText("");
+        ncMassTextField.setText("");
+        ncFilledCB.setSelected(false);
+        ncImage = null;
+        ncImageBtn.setIcon(null);
+    }
+
+    private ComponentPreset extractTransition() {
+        TypedPropertyMap props = new TypedPropertyMap();
+        try {
+            PrintUnit lpu = lengthMap.get(lenUnitCombo.getSelectedItem());
+            props.put(ComponentPreset.TYPE, ComponentPreset.Type.TRANSITION);
+            if (!trAftDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.AFT_OUTER_DIAMETER, lpu.toMeters(Double.parseDouble(trAftDiaTextField.getText())));
+            }
+            if (!trAftShoulderDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.AFT_SHOULDER_DIAMETER, lpu.toMeters(Double.parseDouble(trAftShoulderDiaTextField.getText())));
+            }
+            if (!trAftShoulderLenTextField.getText().equals("")) {
+                props.put(ComponentPreset.AFT_SHOULDER_LENGTH, lpu.toMeters(Double.parseDouble(trAftShoulderLenTextField.getText())));
+            }
+            if (!trForeDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.FORE_OUTER_DIAMETER, lpu.toMeters(Double.parseDouble(trForeDiaTextField.getText())));
+            }
+            if (!trForeShoulderDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.FORE_SHOULDER_DIAMETER, lpu.toMeters(Double.parseDouble(trForeShoulderDiaTextField.getText())));
+            }
+            if (!trForeShoulderLenTextField.getText().equals("")) {
+                props.put(ComponentPreset.FORE_SHOULDER_LENGTH, lpu.toMeters(Double.parseDouble(trForeShoulderLenTextField.getText())));
+            }
+            props.put(ComponentPreset.DESCRIPTION, trDescTextField.getText());
+            props.put(ComponentPreset.PARTNO, trPartNoTextField.getText());
+            props.put(ComponentPreset.MANUFACTURER, Manufacturer.getManufacturer(mfgTextField.getText()));
+
+            if (!trLengthTextField.getText().equals("")) {
+                props.put(ComponentPreset.LENGTH, lpu.toMeters(Double.parseDouble(trLengthTextField.getText())));
+            }
+            props.put(ComponentPreset.SHAPE, Transition.Shape.toShape((String) trShapeCB.getSelectedItem()));
+            final Material material = (Material) materialChooser.getSelectedItem();
+            if (material != null) {
+                props.put(ComponentPreset.MATERIAL, material);
+            }
+            else {
+                JOptionPane.showMessageDialog(null, "A material must be selected.", "Error", JOptionPane.ERROR_MESSAGE);
+                return null;
+            }
+            if (!trMassTextField.getText().equals("")) {
+                props.put(ComponentPreset.MASS, lpu.toMeters(Double.parseDouble(trMassTextField.getText())));
+            }
+            props.put(ComponentPreset.FILLED, trFilledCB.isSelected());
+            if (trImage != null) {
+                props.put(ComponentPreset.IMAGE, imageToByteArray(trImage.getImage()));
+            }
+
+            return ComponentPresetFactory.create(props);
+        }
+        catch (NumberFormatException nfe) {
+            JOptionPane.showMessageDialog(null, "Could not convert transition attribute.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        catch (InvalidComponentPresetException e) {
+            JOptionPane.showMessageDialog(null, "Mandatory transition attribute not set.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        return null;
+    }
+
+    private void clearTransition() {
+        trAftDiaTextField.setText("");
+        trAftShoulderDiaTextField.setText("");
+        trAftShoulderLenTextField.setText("");
+        trForeDiaTextField.setText("");
+        trForeShoulderDiaTextField.setText("");
+        trForeShoulderLenTextField.setText("");
+        trDescTextField.setText("");
+        trPartNoTextField.setText("");
+        trLengthTextField.setText("");
+        trMassTextField.setText("");
+        trFilledCB.setSelected(false);
+        trImage = null;
+        trImageBtn.setIcon(null);
+    }
+
+    private ComponentPreset extractBodyTube() {
+        TypedPropertyMap props = new TypedPropertyMap();
+        try {
+            PrintUnit lpu = lengthMap.get(lenUnitCombo.getSelectedItem());
+            props.put(ComponentPreset.TYPE, ComponentPreset.Type.BODY_TUBE);
+            if (!btOuterDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.OUTER_DIAMETER, lpu.toMeters(Double.parseDouble(btOuterDiaTextField.getText())));
+            }
+            if (!btInnerDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.INNER_DIAMETER, lpu.toMeters(Double.parseDouble(btInnerDiaTextField.getText())));
+            }
+            props.put(ComponentPreset.DESCRIPTION, btDescTextField.getText());
+            props.put(ComponentPreset.PARTNO, btPartNoTextField.getText());
+            props.put(ComponentPreset.MANUFACTURER, Manufacturer.getManufacturer(mfgTextField.getText()));
+            if (!btLengthTextField.getText().equals("")) {
+                props.put(ComponentPreset.LENGTH, lpu.toMeters(Double.parseDouble(btLengthTextField.getText())));
+            }
+            final Material material = (Material) materialChooser.getSelectedItem();
+            if (material != null) {
+                props.put(ComponentPreset.MATERIAL, material);
+            }
+            else {
+                JOptionPane.showMessageDialog(null, "A material must be selected.", "Error", JOptionPane.ERROR_MESSAGE);
+                return null;
+            }
+            if (!btMassTextField.getText().equals("")) {
+                props.put(ComponentPreset.MASS, lpu.toMeters(Double.parseDouble(btMassTextField.getText())));
+            }
+            if (btImage != null) {
+                props.put(ComponentPreset.IMAGE, imageToByteArray(btImage.getImage()));
+            }
+            return ComponentPresetFactory.create(props);
+        }
+        catch (NumberFormatException nfe) {
+            nfe.printStackTrace();
+            JOptionPane.showMessageDialog(null, "Could not convert body tube attribute.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        catch (InvalidComponentPresetException e) {
+            JOptionPane.showMessageDialog(null, "Mandatory body tube attribute not set.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        return null;
+    }
+
+    private void clearBodyTube() {
+        btOuterDiaTextField.setText("");
+        btInnerDiaTextField.setText("");
+        btDescTextField.setText("");
+        btPartNoTextField.setText("");
+        btLengthTextField.setText("");
+        btMassTextField.setText("");
+        btImage = null;
+        btImageBtn.setIcon(null);
+    }
+
+    public ComponentPreset extractTubeCoupler() {
+        TypedPropertyMap props = new TypedPropertyMap();
+        try {
+            PrintUnit lpu = lengthMap.get(lenUnitCombo.getSelectedItem());
+            props.put(ComponentPreset.TYPE, ComponentPreset.Type.TUBE_COUPLER);
+            if (!tcOuterDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.OUTER_DIAMETER, lpu.toMeters(Double.parseDouble(tcOuterDiaTextField.getText())));
+            }
+            if (!tcInnerDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.INNER_DIAMETER, lpu.toMeters(Double.parseDouble(tcInnerDiaTextField.getText())));
+            }
+            props.put(ComponentPreset.DESCRIPTION, tcDescTextField.getText());
+            props.put(ComponentPreset.PARTNO, tcPartNoTextField.getText());
+            props.put(ComponentPreset.MANUFACTURER, Manufacturer.getManufacturer(mfgTextField.getText()));
+            if (!tcLengthTextField.getText().equals("")) {
+                props.put(ComponentPreset.LENGTH, lpu.toMeters(Double.parseDouble(tcLengthTextField.getText())));
+            }
+            final Material material = (Material) materialChooser.getSelectedItem();
+            if (material != null) {
+                props.put(ComponentPreset.MATERIAL, material);
+            }
+            else {
+                JOptionPane.showMessageDialog(null, "A material must be selected.", "Error", JOptionPane.ERROR_MESSAGE);
+                return null;
+            }
+            if (!tcMassTextField.getText().equals("")) {
+                props.put(ComponentPreset.MASS, lpu.toMeters(Double.parseDouble(tcMassTextField.getText())));
+            }
+            if (tcImage != null) {
+                props.put(ComponentPreset.IMAGE, imageToByteArray(tcImage.getImage()));
+            }
+
+            return ComponentPresetFactory.create(props);
+        }
+        catch (NumberFormatException nfe) {
+            JOptionPane.showMessageDialog(null, "Could not convert tube coupler attribute.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        catch (InvalidComponentPresetException e) {
+            JOptionPane.showMessageDialog(null, "Mandatory tube coupler attribute not set.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        return null;
+    }
+
+    private void clearTubeCoupler() {
+        tcOuterDiaTextField.setText("");
+        tcInnerDiaTextField.setText("");
+        tcDescTextField.setText("");
+        tcPartNoTextField.setText("");
+        tcLengthTextField.setText("");
+        tcMassTextField.setText("");
+        tcImage = null;
+        tcImageBtn.setIcon(null);
+    }
+
+    private ComponentPreset extractBulkhead() {
+        TypedPropertyMap props = new TypedPropertyMap();
+        try {
+            PrintUnit lpu = lengthMap.get(lenUnitCombo.getSelectedItem());
+            props.put(ComponentPreset.TYPE, ComponentPreset.Type.BULK_HEAD);
+            if (!bhOuterDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.OUTER_DIAMETER, lpu.toMeters(Double.parseDouble(bhOuterDiaTextField.getText())));
+            }
+            props.put(ComponentPreset.DESCRIPTION, bhDescTextField.getText());
+            props.put(ComponentPreset.PARTNO, bhPartNoTextField.getText());
+            props.put(ComponentPreset.MANUFACTURER, Manufacturer.getManufacturer(mfgTextField.getText()));
+            if (!bhLengthTextField.getText().equals("")) {
+                props.put(ComponentPreset.LENGTH, lpu.toMeters(Double.parseDouble(bhLengthTextField.getText())));
+            }
+            final Material material = (Material) materialChooser.getSelectedItem();
+            if (material != null) {
+                props.put(ComponentPreset.MATERIAL, material);
+            }
+            else {
+                JOptionPane.showMessageDialog(null, "A material must be selected.", "Error", JOptionPane.ERROR_MESSAGE);
+                return null;
+            }
+            if (!bhMassTextField.getText().equals("")) {
+                props.put(ComponentPreset.MASS, lpu.toMeters(Double.parseDouble(bhMassTextField.getText())));
+            }
+            if (bhImage != null) {
+                props.put(ComponentPreset.IMAGE, imageToByteArray(bhImage.getImage()));
+            }
+            return ComponentPresetFactory.create(props);
+        }
+        catch (NumberFormatException nfe) {
+            JOptionPane.showMessageDialog(null, "Could not convert bulkhead attribute.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        catch (InvalidComponentPresetException e) {
+            JOptionPane.showMessageDialog(null, "Mandatory bulkhead attribute not set.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        return null;
+    }
+
+    private void clearBulkhead() {
+        bhOuterDiaTextField.setText("");
+        bhDescTextField.setText("");
+        bhPartNoTextField.setText("");
+        bhLengthTextField.setText("");
+        bhMassTextField.setText("");
+        bhImage = null;
+        bhImageBtn.setIcon(null);
+    }
+
+    private ComponentPreset extractCenteringRing() {
+        TypedPropertyMap props = new TypedPropertyMap();
+        try {
+            PrintUnit lpu = lengthMap.get(lenUnitCombo.getSelectedItem());
+            props.put(ComponentPreset.TYPE, ComponentPreset.Type.CENTERING_RING);
+            if (!crOuterDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.OUTER_DIAMETER, lpu.toMeters(Double.parseDouble(crOuterDiaTextField.getText())));
+            }
+            if (!crInnerDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.INNER_DIAMETER, lpu.toMeters(Double.parseDouble(crInnerDiaTextField.getText())));
+            }
+            props.put(ComponentPreset.DESCRIPTION, crDescTextField.getText());
+            props.put(ComponentPreset.PARTNO, crPartNoTextField.getText());
+            props.put(ComponentPreset.MANUFACTURER, Manufacturer.getManufacturer(mfgTextField.getText()));
+            if (!crThicknessTextField.getText().equals("")) {
+                props.put(ComponentPreset.LENGTH, lpu.toMeters(Double.parseDouble(crThicknessTextField.getText())));
+            }
+            final Material material = (Material) materialChooser.getSelectedItem();
+            if (material != null) {
+                props.put(ComponentPreset.MATERIAL, material);
+            }
+            else {
+                JOptionPane.showMessageDialog(null, "A material must be selected.", "Error", JOptionPane.ERROR_MESSAGE);
+                return null;
+            }
+            if (!crMassTextField.getText().equals("")) {
+                props.put(ComponentPreset.MASS, lpu.toMeters(Double.parseDouble(crMassTextField.getText())));
+            }
+            if (crImage != null) {
+                props.put(ComponentPreset.IMAGE, imageToByteArray(crImage.getImage()));
+            }
+            return ComponentPresetFactory.create(props);
+        }
+        catch (NumberFormatException nfe) {
+            JOptionPane.showMessageDialog(null, "Could not convert centering ring attribute.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        catch (InvalidComponentPresetException e) {
+            JOptionPane.showMessageDialog(null, "Mandatory centering ring attribute not set.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        return null;
+    }
+
+    private void clearCenteringRing() {
+        crOuterDiaTextField.setText("");
+        crInnerDiaTextField.setText("");
+        crDescTextField.setText("");
+        crPartNoTextField.setText("");
+        crThicknessTextField.setText("");
+        crMassTextField.setText("");
+        crImage = null;
+        crImageBtn.setIcon(null);
+    }
+
+    public ComponentPreset extractEngineBlock() {
+        TypedPropertyMap props = new TypedPropertyMap();
+        try {
+            PrintUnit lpu = lengthMap.get(lenUnitCombo.getSelectedItem());
+            props.put(ComponentPreset.TYPE, ComponentPreset.Type.ENGINE_BLOCK);
+            if (!ebOuterDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.OUTER_DIAMETER, lpu.toMeters(Double.parseDouble(ebOuterDiaTextField.getText())));
+            }
+            if (!ebInnerDiaTextField.getText().equals("")) {
+                props.put(ComponentPreset.INNER_DIAMETER, lpu.toMeters(Double.parseDouble(ebInnerDiaTextField.getText())));
+            }
+            props.put(ComponentPreset.DESCRIPTION, ebDescTextField.getText());
+            props.put(ComponentPreset.PARTNO, ebPartNoTextField.getText());
+            props.put(ComponentPreset.MANUFACTURER, Manufacturer.getManufacturer(mfgTextField.getText()));
+            if (!ebThicknessTextField.getText().equals("")) {
+                props.put(ComponentPreset.LENGTH, lpu.toMeters(Double.parseDouble(ebThicknessTextField.getText())));
+            }
+            final Material material = (Material) materialChooser.getSelectedItem();
+            if (material != null) {
+                props.put(ComponentPreset.MATERIAL, material);
+            }
+            else {
+                JOptionPane.showMessageDialog(null, "A material must be selected.", "Error", JOptionPane.ERROR_MESSAGE);
+                return null;
+            }
+            if (!ebMassTextField.getText().equals("")) {
+                props.put(ComponentPreset.MASS, lpu.toMeters(Double.parseDouble(ebMassTextField.getText())));
+            }
+            if (ebImage != null) {
+                props.put(ComponentPreset.IMAGE, imageToByteArray(ebImage.getImage()));
+            }
+            return ComponentPresetFactory.create(props);
+        }
+        catch (NumberFormatException nfe) {
+            JOptionPane.showMessageDialog(null, "Could not convert engine block attribute.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        catch (InvalidComponentPresetException e) {
+            JOptionPane.showMessageDialog(null, "Mandatory engine block attribute not set.", "Error", JOptionPane.ERROR_MESSAGE);
+        }
+        return null;
+    }
+
+    private void clearEngineBlock() {
+        ebOuterDiaTextField.setText("");
+        ebInnerDiaTextField.setText("");
+        ebDescTextField.setText("");
+        ebPartNoTextField.setText("");
+        ebThicknessTextField.setText("");
+        ebMassTextField.setText("");
+        ebImage = null;
+        ebImageBtn.setIcon(null);
+    }
+
+    public void itemStateChanged(ItemEvent evt) {
+        CardLayout cl = (CardLayout) (componentOverlayPanel.getLayout());
+        cl.show(componentOverlayPanel, componentMap.get((String) evt.getItem()));
+
+    }
+
+    /**
+     * Convert an image to a byte array in png format.
+     *
+     * @param originalImage
+     * @return
+     */
+    private byte[] imageToByteArray(Image originalImage) {
+        byte[] imageInByte = null;
+        try {
+            BufferedImage bi = imageToBufferedImage(originalImage);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ImageIO.write(bi, "png", baos);
+            baos.flush();
+            imageInByte = baos.toByteArray();
+            baos.close();
+        }
+        catch (IOException e) {
+            log.error("Could not read image.");
+        }
+        return imageInByte;
+    }
+
+    private BufferedImage imageToBufferedImage(final Image originalImage) {
+        BufferedImage bi = new BufferedImage(
+                originalImage.getWidth(null),
+                originalImage.getHeight(null),
+                BufferedImage.TYPE_INT_RGB);
+
+        Graphics2D g2 = bi.createGraphics();
+        g2.drawImage(originalImage, 0, 0, null);
+        return bi;
+    }
+
+    private BufferedImage byteArrayToImage(byte[] src) {
+        // convert byte array back to BufferedImage
+        InputStream in = new ByteArrayInputStream(src);
+        try {
+            return ImageIO.read(in);
+        }
+        catch (IOException e) {
+            log.error("Could not convert image.");
+        }
+        return null;
+    }
+
+    private ImageIcon scaleImage(Image image, int targetDimension) {
+        int width = image.getWidth(this);
+        int height = image.getHeight(this);
+        double ratio = 1.0;
+
+        /*
+         * Determine how to scale the image. Since the accessory can expand
+         * vertically make sure we don't go larger than 150 when scaling
+         * vertically.
+         */
+        if (width >= height) {
+            ratio = (double) (targetDimension - 5) / width;
+            width = targetDimension - 5;
+            height = (int) (height * ratio);
+        }
+        else {
+            if (getHeight() > 150) {
+                ratio = (double) (targetDimension - 5) / height;
+                height = targetDimension - 5;
+                width = (int) (width * ratio);
+            }
+            else {
+                ratio = (double) getHeight() / height;
+                height = getHeight();
+                width = (int) (width * ratio);
+            }
+        }
+
+        return new ImageIcon(image.getScaledInstance(width, height, Image.SCALE_DEFAULT));
+    }
+
+    static class PresetInputVerifier extends InputVerifier {
+
+        /**
+         * Matches user input against a regular expression.
+         */
+        private Matcher matcher;
+
+        PresetInputVerifier(final Pattern thePattern) {
+            matcher = thePattern.matcher("");
+        }
+
+        /**
+         * Return true only if the untrimmed user input matches the regular expression provided to the constructor.
+         *
+         * @param aComponent must be an instance of JTextComponent.
+         */
+        public boolean verify(JComponent aComponent) {
+            JTextComponent textComponent = (JTextComponent) aComponent;
+            matcher.reset(textComponent.getText());
+            return matcher.matches();
+        }
+
+        /**
+         * Always returns <tt>true</tt>, in this implementation, such that focus can always transfer to another
+         * component whenever the validation fails.
+         * <p/>
+         * <P>If <tt>super.shouldYieldFocus</tt> returns <tt>false</tt>, then clear the text field.
+         *
+         * @param aComponent is a <tt>JTextComponent</tt>.
+         */
+        @Override
+        public boolean shouldYieldFocus(JComponent aComponent) {
+            if (!super.shouldYieldFocus(aComponent)) {
+                ((JTextComponent) aComponent).setText("");
+            }
+            return true;
+        }
+    }
+}
diff --git a/core/src/net/sf/openrocket/gui/preset/PresetResultListener.java b/core/src/net/sf/openrocket/gui/preset/PresetResultListener.java
new file mode 100644 (file)
index 0000000..a5c2e1b
--- /dev/null
@@ -0,0 +1,11 @@
+
+package net.sf.openrocket.gui.preset;
+
+import net.sf.openrocket.preset.ComponentPreset;
+
+/**
+ */
+public interface PresetResultListener {
+
+    void notifyResult(ComponentPreset preset);
+}
index c603f2f970c3dd4520bff50b99d5d7c8f7382d2a..310fba3206abc1948e686eb124c7759b3e4ed363 100644 (file)
@@ -7,6 +7,14 @@ package net.sf.openrocket.gui.print;
  * Utilities for print units.
  */
 public enum PrintUnit {
+    FOOT {
+        public double toInches(double d) { return d*12; }
+        public double toMillis(double d) { return d/FEET_PER_MM; }
+        public double toCentis(double d) { return d/(FEET_PER_MM*TEN); }
+        public double toMeters(double d) { return d/(FEET_PER_MM*TEN*TEN*TEN); }
+        public long   toPoints(double d) { return (long)(d * POINTS_PER_INCH * 12); }
+        public double convert(double d, PrintUnit u) { return u.toInches(d)/12; }
+    },
     INCHES {
         public double toInches(double d) { return d; }
         public double toMillis(double d) { return d/INCHES_PER_MM; }
@@ -50,13 +58,14 @@ public enum PrintUnit {
 
     // Handy constants for conversion methods
     public static final double INCHES_PER_MM = 0.0393700787d;
+    public static final double FEET_PER_MM = INCHES_PER_MM /12;
     public static final double MM_PER_INCH = 1.0d/INCHES_PER_MM;
     public static final long TEN = 10;
     /**
      * PPI is Postscript Point and is a standard of 72.  Java2D also uses this internally as a pixel-per-inch, so pixels
      * and points are for the most part interchangeable (unless the defaults are changed), which makes translating
      * between the screen and a print job easier.
-     * 
+     *
      * Not to be confused with Dots-Per-Inch, which is printer and print mode dependent.
      */
     public static final int POINTS_PER_INCH = 72;
index 937be15281839273c6a36945f1ec6b7b3d70d346..7fcdc2a4c0c337d42a80f9cb4ef4b5ad7e344372 100644 (file)
@@ -1,70 +1,73 @@
 package net.sf.openrocket.gui.util;
 
-import java.awt.Component;
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Locale;
-
-import javax.imageio.ImageIO;
-import javax.swing.JOptionPane;
-import javax.swing.filechooser.FileFilter;
-
 import net.sf.openrocket.l10n.L10N;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.startup.Application;
 
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import javax.swing.filechooser.FileFilter;
+import java.awt.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Locale;
+
 /**
  * Helper methods related to user-initiated file manipulation.
  * <p>
  * These methods log the necessary information to the debug log.
-* 
+*
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 public final class FileHelper {
        private static final LogHelper log = Application.getLogger();
        private static final Translator trans = Application.getTranslator();
-       
-       
+
+
        // TODO: HIGH: Rename translation keys
-       
+
        /** File filter for any rocket designs (*.ork, *.rkt) */
        public static final FileFilter ALL_DESIGNS_FILTER =
                        new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter1"),
                                        ".ork", ".ork.gz", ".rkt", ".rkt.gz");
-       
+
        /** File filter for OpenRocket designs (*.ork) */
        public static final FileFilter OPENROCKET_DESIGN_FILTER =
                        new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter2"), ".ork", ".ork.gz");
-       
+
        /** File filter for RockSim designs (*.rkt) */
        public static final FileFilter ROCKSIM_DESIGN_FILTER =
                        new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter3"), ".rkt", ".rkt.gz");
-       
+
+       /** File filter for OpenRocket components and presets (*.orc) */
+       public static final FileFilter OPEN_ROCKET_COMPONENT_FILTER =
+                       new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter4"), ".orc", ".orc.gz");
+
        /** File filter for PDF files (*.pdf) */
        public static final FileFilter PDF_FILTER =
                        new SimpleFileFilter(trans.get("filetypes.pdf"), ".pdf");
-       
+
        /** File filter for CSV files (*.csv) */
        public static final FileFilter CSV_FILE_FILTER =
                        new SimpleFileFilter(trans.get("SimExpPan.desc"), ".csv");
-       
-       
-       
-       
+
+
+
+
        private FileHelper() {
                // Prevent instantiation
        }
-       
-       
+
+
        public static FileFilter getImageFileFilter() {
                String[] extensions = ImageIO.getReaderFileSuffixes();
                for (int i = 0; i < extensions.length; i++) {
                        extensions[i] = extensions[i].toLowerCase(Locale.ENGLISH);
                }
                Arrays.sort(extensions);
-               
+
                StringBuilder sb = new StringBuilder();
                sb.append(trans.get("filetypes.images"));
                sb.append(" (");
@@ -75,31 +78,31 @@ public final class FileHelper {
                        }
                }
                sb.append(")");
-               
+
                return new SimpleFileFilter(sb.toString(), extensions);
        }
-       
-       
+
+
        /**
         * Ensure that the provided file has a file extension.  If the file does not have
         * any extension, append the provided extension to it.
-        * 
+        *
         * @param original              the original file
         * @param extension             the extension to append if none exists (without preceding dot)
         * @return                              the resulting file
         */
        public static File ensureExtension(File original, String extension) {
-               
+
                if (original.getName().indexOf('.') < 0) {
                        log.debug(1, "File name does not contain extension, adding '" + extension + "'");
                        String name = original.getAbsolutePath();
                        name = name + "." + extension;
                        return new File(name);
                }
-               
+
                return original;
        }
-       
+
        /**
         * Ensure that the provided file has the given file extension.  This differs from ensureExtension in that this
         * method guarantees that the file will have the extension, whereas ensureExtension only treats the extension
@@ -110,7 +113,7 @@ public final class FileHelper {
         * @return                              the resulting file
         */
        public static File forceExtension(File original, String extension) {
-               
+
                if (!original.getName().toLowerCase(Locale.ENGLISH).endsWith(extension.toLowerCase(Locale.ENGLISH))) {
                        log.debug(1, "File name does not contain extension, adding '" + extension + "'");
                        String name = original.getAbsolutePath();
@@ -122,15 +125,15 @@ public final class FileHelper {
                        }
                        return new File(name);
                }
-               
+
                return original;
        }
-       
-       
+
+
        /**
         * Confirm that it is allowed to write to a file.  If the file exists,
         * a confirmation dialog will be presented to the user to ensure overwriting is ok.
-        * 
+        *
         * @param file          the file that is going to be written.
         * @param parent        the parent component for the dialog.
         * @return                      <code>true</code> to write, <code>false</code> to abort.
@@ -149,23 +152,23 @@ public final class FileHelper {
                }
                return true;
        }
-       
-       
+
+
        /**
         * Display an error message to the user that writing a file failed.
-        * 
+        *
         * @param e                     the I/O exception that caused the error.
         * @param parent        the parent component for the dialog.
         */
        public static void errorWriting(IOException e, Component parent) {
-               
+
                log.warn(1, "Error writing to file", e);
                JOptionPane.showMessageDialog(parent,
                                new Object[] {
                                                trans.get("error.writing.desc"),
                                                e.getLocalizedMessage()
                                }, trans.get("error.writing.title"), JOptionPane.ERROR_MESSAGE);
-               
+
        }
-       
+
 }
index ca33af84a3cd8a6d735ee760baac60e751415fc6..12331495df47421e22aafa98ad6cca98ea1c76e3 100644 (file)
@@ -52,6 +52,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
                                ComponentPreset.SHAPE,
                                ComponentPreset.AFT_OUTER_DIAMETER,
                                ComponentPreset.AFT_SHOULDER_DIAMETER,
+                ComponentPreset.AFT_SHOULDER_LENGTH,
                                ComponentPreset.LENGTH} ),
 
                TRANSITION( new TypedKey<?>[] {
@@ -97,7 +98,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
                                ComponentPreset.INNER_DIAMETER,
                                ComponentPreset.OUTER_DIAMETER,
                                ComponentPreset.LENGTH} ),
-                               
+
                LAUNCH_LUG( new TypedKey<?>[] {
                                ComponentPreset.MANUFACTURER,
                                ComponentPreset.PARTNO,
@@ -114,7 +115,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
                                ComponentPreset.WIDTH,
                                ComponentPreset.THICKNESS,
                                ComponentPreset.MATERIAL} ),
-                               
+
                PARACHUTE( new TypedKey<?>[] {
                                ComponentPreset.MANUFACTURER,
                                ComponentPreset.PARTNO,
@@ -177,7 +178,7 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
        public final static TypedKey<Integer> LINE_COUNT = new TypedKey<Integer>("LineCount", Integer.class);
        public final static TypedKey<Double> LINE_LENGTH = new TypedKey<Double>("LineLength", Double.class, UnitGroup.UNITS_LENGTH);
        public final static TypedKey<Material> LINE_MATERIAL = new TypedKey<Material>("LineMaterial", Material.class);
-
+    public final static TypedKey<byte[]> IMAGE = new TypedKey<byte[]>("Image", byte[].class);
 
        public final static List<TypedKey<?>> orderedKeyList = Arrays.<TypedKey<?>>asList(
                        MANUFACTURER,
index c9278ecff6d8fa32b867d66f43bb3679feb4290e..9f49d9482cbd2b01ef97764a33f42fe5cf290de4 100644 (file)
@@ -1,7 +1,6 @@
 
 package net.sf.openrocket.preset.xml;
 
-import net.sf.openrocket.database.Databases;
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.motor.Manufacturer;
 import net.sf.openrocket.preset.ComponentPreset;
@@ -76,6 +75,9 @@ public abstract class BaseComponentDTO {
                if ( preset.has(ComponentPreset.FILLED) ) {
                        setFilled( preset.get(ComponentPreset.FILLED));
                }
+        if (preset.has(ComponentPreset.IMAGE) ) {
+            setImageData(preset.get(ComponentPreset.IMAGE));
+        }
        }
 
        public String getManufacturer() {
@@ -171,6 +173,9 @@ public abstract class BaseComponentDTO {
                if ( filled != null ) {
                        props.put(ComponentPreset.FILLED, getFilled());
                }
+        if (image != null) {
+            props.put(ComponentPreset.IMAGE, image);
+        }
        }
 
        protected Material find(List<MaterialDTO> materialList, AnnotatedMaterialDTO dto) {
@@ -195,7 +200,7 @@ public abstract class BaseComponentDTO {
                } else {
                        return null;
                }
-               
+
        }
 
        static class AnnotatedMaterialDTO {
index 708a1e093cd037b91002dbcba686c54ef966013f..6a70fe2c655384f75ef03692fadf10fc7174637a 100644 (file)
@@ -1,6 +1,17 @@
 package net.sf.openrocket.preset.xml;
 
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.preset.ComponentPreset;
+import net.sf.openrocket.preset.InvalidComponentPresetException;
+import net.sf.openrocket.startup.Application;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
 import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
@@ -10,16 +21,6 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Marshaller;
-import javax.xml.bind.Unmarshaller;
-
-import net.sf.openrocket.material.Material;
-import net.sf.openrocket.preset.ComponentPreset;
-import net.sf.openrocket.preset.InvalidComponentPresetException;
-import net.sf.openrocket.startup.Application;
-
 /**
  * The active manager class that is the entry point for reading and writing *.orc files.
  */
@@ -39,6 +40,16 @@ public class OpenRocketComponentSaver {
         }
     }
 
+    public boolean save(File file, List<Material> theMaterialList, List<ComponentPreset> thePresetList) throws
+                                                                                                     JAXBException,
+                                                                                                     IOException {
+        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
+        writer.write(marshalToOpenRocketComponent(theMaterialList, thePresetList));
+        writer.flush();
+        writer.close();
+        return true;
+    }
+
     /**
      * This method marshals a list of materials and ComponentPresets into an .orc formatted XML string.
      *
@@ -57,31 +68,31 @@ public class OpenRocketComponentSaver {
         StringWriter sw = new StringWriter();
 
         // We're going to sort the initial data since that makes the output much easier on the eyes.
-        
+
         Collections.sort(theMaterialList, new Comparator<Material>() {
 
                        @Override
                        public int compare(Material o1, Material o2) {
                                return o1.getName().compareTo( o2.getName() );
                        }
-               
+
         });
-        
+
         Collections.sort(thePresetList, new Comparator<ComponentPreset>() {
 
                        @Override
                        public int compare(ComponentPreset o1, ComponentPreset o2) {
                                int manucmp = o1.getManufacturer().getSimpleName().compareTo( o2.getManufacturer().getSimpleName() );
-                               
+
                                if ( manucmp != 0 ) {
                                        return manucmp;
                                }
-                               
+
                                return o1.getPartNo().compareTo( o2.getPartNo());
                        }
-               
+
         });
-        
+
         marshaller.marshal(toOpenRocketComponentDTO(theMaterialList, thePresetList), sw);
         return sw.toString();
 
@@ -90,8 +101,8 @@ public class OpenRocketComponentSaver {
     /**
      * This method unmarshals from a Reader that is presumed to be open on an XML file in .orc format.
      *
-     * @param is an open reader; StringBufferInputStream could not be used because it's deprecated and does not handle UTF
-     *           characters correctly
+     * @param is an open reader; StringBufferInputStream could not be used because it's deprecated and does not handle
+     *           UTF characters correctly
      *
      * @return a list of ComponentPresets
      *
@@ -127,13 +138,13 @@ public class OpenRocketComponentSaver {
      *
      * @param is an open Reader; assumed to be opened on a file of XML in .orc format
      *
-     * @return the OpenRocketComponentDTO that is a POJO representation of the XML; null if the data could not be read or
-     *         was in an invalid format
+     * @return the OpenRocketComponentDTO that is a POJO representation of the XML; null if the data could not be read
+     *         or was in an invalid format
      */
     private OpenRocketComponentDTO fromOpenRocketComponent(Reader is) throws JAXBException {
         /** The context is thread-safe, but unmarshallers are not.  Create a local one. */
         Unmarshaller unmarshaller = context.createUnmarshaller();
-        return (OpenRocketComponentDTO) unmarshaller.unmarshal(is);
+        return (OpenRocketComponentDTO) unmarshaller.unmarshal(is); //new StreamSource(is));
     }
 
     /**
index a0804b20c2ec16fc79d7055c00b253c69ab44afe..6b12f156ec15bc6963d109b0ffaa4cf453b67503 100644 (file)
@@ -1,10 +1,5 @@
 package net.sf.openrocket.rocketcomponent;
 
-import static java.lang.Math.sin;
-import static net.sf.openrocket.util.MathUtil.*;
-
-import java.util.Collection;
-
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.preset.ComponentPreset;
 import net.sf.openrocket.preset.ComponentPreset.Type;
@@ -12,19 +7,25 @@ import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.MathUtil;
 
+import java.util.Collection;
+
+import static java.lang.Math.sin;
+import static net.sf.openrocket.util.MathUtil.pow2;
+import static net.sf.openrocket.util.MathUtil.pow3;
+
 
 public class Transition extends SymmetricComponent {
        private static final Translator trans = Application.getTranslator();
        private static final double CLIP_PRECISION = 0.0001;
-       
+
 
        private Shape type;
        private double shapeParameter;
        private boolean clipped; // Not to be read - use isClipped(), which may be overriden
-       
+
        private double radius1, radius2;
        private boolean autoRadius1, autoRadius2; // Whether the start radius is automatic
-                       
+
 
        private double foreShoulderRadius;
        private double foreShoulderThickness;
@@ -34,25 +35,25 @@ public class Transition extends SymmetricComponent {
        private double aftShoulderThickness;
        private double aftShoulderLength;
        private boolean aftShoulderCapped;
-       
+
 
        // Used to cache the clip length
        private double clipLength = -1;
-       
+
        public Transition() {
                super();
-               
+
                this.radius1 = DEFAULT_RADIUS;
                this.radius2 = DEFAULT_RADIUS;
                this.length = DEFAULT_RADIUS * 3;
                this.autoRadius1 = true;
                this.autoRadius2 = true;
-               
+
                this.type = Shape.CONICAL;
                this.shapeParameter = 0;
                this.clipped = true;
        }
-       
+
        ////////  Length  ////////
        @Override
        public void setLength( double length ) {
@@ -66,7 +67,7 @@ public class Transition extends SymmetricComponent {
 
 
        ////////  Fore radius  ////////
-       
+
 
        @Override
        public double getForeRadius() {
@@ -83,39 +84,39 @@ public class Transition extends SymmetricComponent {
                }
                return radius1;
        }
-       
+
        public void setForeRadius(double radius) {
                if ((this.radius1 == radius) && (autoRadius1 == false))
                        return;
-               
+
                this.autoRadius1 = false;
                this.radius1 = Math.max(radius, 0);
-               
+
                if (this.thickness > this.radius1 && this.thickness > this.radius2)
                        this.thickness = Math.max(this.radius1, this.radius2);
-               
+
                clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
+
        @Override
        public boolean isForeRadiusAutomatic() {
                return autoRadius1;
        }
-       
+
        public void setForeRadiusAutomatic(boolean auto) {
                if (autoRadius1 == auto)
                        return;
-               
+
                autoRadius1 = auto;
 
                clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
-       
+
+
        ////////  Aft radius  /////////
-       
+
        @Override
        public double getAftRadius() {
                if (isAftRadiusAutomatic()) {
@@ -131,66 +132,66 @@ public class Transition extends SymmetricComponent {
                }
                return radius2;
        }
-       
-       
+
+
 
        public void setAftRadius(double radius) {
                if ((this.radius2 == radius) && (autoRadius2 == false))
                        return;
-               
+
                this.autoRadius2 = false;
                this.radius2 = Math.max(radius, 0);
-               
+
                if (this.thickness > this.radius1 && this.thickness > this.radius2)
                        this.thickness = Math.max(this.radius1, this.radius2);
 
                clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
+
        @Override
        public boolean isAftRadiusAutomatic() {
                return autoRadius2;
        }
-       
+
        public void setAftRadiusAutomatic(boolean auto) {
                if (autoRadius2 == auto)
                        return;
-               
+
                autoRadius2 = auto;
 
                clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
-       
+
+
 
        //// Radius automatics
-       
+
        @Override
        protected double getFrontAutoRadius() {
                if (isAftRadiusAutomatic())
                        return -1;
                return getAftRadius();
        }
-       
-       
+
+
        @Override
        protected double getRearAutoRadius() {
                if (isForeRadiusAutomatic())
                        return -1;
                return getForeRadius();
        }
-       
-       
+
+
 
 
        ////////  Type & shape  /////////
-       
+
        public Shape getType() {
                return type;
        }
-       
+
        public void setType(Shape type) {
                if (type == null) {
                        throw new IllegalArgumentException("setType called with null argument");
@@ -202,50 +203,50 @@ public class Transition extends SymmetricComponent {
                this.shapeParameter = type.defaultParameter();
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
+
        public double getShapeParameter() {
                return shapeParameter;
        }
-       
+
        public void setShapeParameter(double n) {
                if (shapeParameter == n)
                        return;
                this.shapeParameter = MathUtil.clamp(n, type.minParameter(), type.maxParameter());
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
+
        public boolean isClipped() {
                if (!type.isClippable())
                        return false;
                return clipped;
        }
-       
+
        public void setClipped(boolean c) {
                if (clipped == c)
                        return;
                clipped = c;
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-       
+
        public boolean isClippedEnabled() {
                return type.isClippable();
        }
-       
+
        public double getShapeParameterMin() {
                return type.minParameter();
        }
-       
+
        public double getShapeParameterMax() {
                return type.maxParameter();
        }
-       
-       
+
+
        ////////  Shoulders  ////////
-       
+
        public double getForeShoulderRadius() {
                return foreShoulderRadius;
        }
-       
+
        public void setForeShoulderRadius(double foreShoulderRadius) {
                if (MathUtil.equals(this.foreShoulderRadius, foreShoulderRadius))
                        return;
@@ -253,47 +254,47 @@ public class Transition extends SymmetricComponent {
                clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public double getForeShoulderThickness() {
                return foreShoulderThickness;
        }
-       
+
        public void setForeShoulderThickness(double foreShoulderThickness) {
                if (MathUtil.equals(this.foreShoulderThickness, foreShoulderThickness))
                        return;
                this.foreShoulderThickness = foreShoulderThickness;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public double getForeShoulderLength() {
                return foreShoulderLength;
        }
-       
+
        public void setForeShoulderLength(double foreShoulderLength) {
                if (MathUtil.equals(this.foreShoulderLength, foreShoulderLength))
                        return;
                this.foreShoulderLength = foreShoulderLength;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public boolean isForeShoulderCapped() {
                return foreShoulderCapped;
        }
-       
+
        public void setForeShoulderCapped(boolean capped) {
                if (this.foreShoulderCapped == capped)
                        return;
                this.foreShoulderCapped = capped;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
-       
+
+
 
 
        public double getAftShoulderRadius() {
                return aftShoulderRadius;
        }
-       
+
        public void setAftShoulderRadius(double aftShoulderRadius) {
                if (MathUtil.equals(this.aftShoulderRadius, aftShoulderRadius))
                        return;
@@ -301,45 +302,45 @@ public class Transition extends SymmetricComponent {
                clearPreset();
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public double getAftShoulderThickness() {
                return aftShoulderThickness;
        }
-       
+
        public void setAftShoulderThickness(double aftShoulderThickness) {
                if (MathUtil.equals(this.aftShoulderThickness, aftShoulderThickness))
                        return;
                this.aftShoulderThickness = aftShoulderThickness;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public double getAftShoulderLength() {
                return aftShoulderLength;
        }
-       
+
        public void setAftShoulderLength(double aftShoulderLength) {
                if (MathUtil.equals(this.aftShoulderLength, aftShoulderLength))
                        return;
                this.aftShoulderLength = aftShoulderLength;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
+
        public boolean isAftShoulderCapped() {
                return aftShoulderCapped;
        }
-       
+
        public void setAftShoulderCapped(boolean capped) {
                if (this.aftShoulderCapped == capped)
                        return;
                this.aftShoulderCapped = capped;
                fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
        }
-       
-       
+
+
 
 
        ///////////   Shape implementations   ////////////
-       
+
 
 
        /**
@@ -349,20 +350,20 @@ public class Transition extends SymmetricComponent {
        public double getRadius(double x) {
                if (x < 0 || x > length)
                        return 0;
-               
+
                double r1 = getForeRadius();
                double r2 = getAftRadius();
-               
+
                if (r1 == r2)
                        return r1;
-               
+
                if (r1 > r2) {
                        x = length - x;
                        double tmp = r1;
                        r1 = r2;
                        r2 = tmp;
                }
-               
+
                if (isClipped()) {
                        // Check clip calculation
                        if (clipLength < 0)
@@ -373,7 +374,7 @@ public class Transition extends SymmetricComponent {
                        return r1 + type.getRadius(x, r2 - r1, length, shapeParameter);
                }
        }
-       
+
        /**
         * Numerically solve clipLength from the equation
         *     r1 == type.getRadius(clipLength,r2,clipLength+length)
@@ -381,27 +382,27 @@ public class Transition extends SymmetricComponent {
         */
        private void calculateClip(double r1, double r2) {
                double min = 0, max = length;
-               
+
                if (r1 >= r2) {
                        double tmp = r1;
                        r1 = r2;
                        r2 = tmp;
                }
-               
+
                if (r1 == 0) {
                        clipLength = 0;
                        return;
                }
-               
+
                if (length <= 0) {
                        clipLength = 0;
                        return;
                }
-               
+
                // Required:
                //    getR(min,min+length,r2) - r1 < 0
                //    getR(max,max+length,r2) - r1 > 0
-               
+
                int n = 0;
                while (type.getRadius(max, r2, max + length, shapeParameter) - r1 < 0) {
                        min = max;
@@ -410,7 +411,7 @@ public class Transition extends SymmetricComponent {
                        if (n > 10)
                                break;
                }
-               
+
                while (true) {
                        clipLength = (min + max) / 2;
                        if ((max - min) < CLIP_PRECISION)
@@ -423,14 +424,14 @@ public class Transition extends SymmetricComponent {
                        }
                }
        }
-       
-       
+
+
        @Override
        public double getInnerRadius(double x) {
                return Math.max(getRadius(x) - thickness, 0);
        }
-       
-       
+
+
 
        @Override
        public Collection<Coordinate> getComponentBounds() {
@@ -454,7 +455,7 @@ public class Transition extends SymmetricComponent {
                        final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0);
                        volume += ringVolume(ir, 0, getForeShoulderThickness() );
                }
-               
+
                if (getAftShoulderLength() > 0.001) {
                        final double or = getAftShoulderRadius();
                        final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
@@ -464,7 +465,7 @@ public class Transition extends SymmetricComponent {
                        final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
                        volume += ringVolume(ir, 0, getAftShoulderThickness() );
                }
-               
+
                return volume;
        }
 
@@ -482,7 +483,7 @@ public class Transition extends SymmetricComponent {
                                        getForeShoulderThickness() - getForeShoulderLength(),
                                        getMaterial().getDensity()));
                }
-               
+
                if (getAftShoulderLength() > 0.001) {
                        final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
                        cg = cg.average(ringCG(getAftShoulderRadius(), ir, getLength(),
@@ -496,8 +497,8 @@ public class Transition extends SymmetricComponent {
                }
                return cg;
        }
-       
-       
+
+
        /*
         * The moments of inertia are not explicitly corrected for the shoulders.
         * However, since the mass is corrected, the inertia is automatically corrected
@@ -514,13 +515,13 @@ public class Transition extends SymmetricComponent {
                //// Transition
                return trans.get("Transition.Transition");
        }
-       
+
        @Override
        protected void componentChanged(ComponentChangeEvent e) {
                super.componentChanged(e);
                clipLength = -1;
        }
-       
+
        /**
         * Check whether the given type can be added to this component.  Transitions allow any
         * InternalComponents to be added.
@@ -534,7 +535,7 @@ public class Transition extends SymmetricComponent {
                        return true;
                return false;
        }
-       
+
        @Override
        public Type getPresetType() {
                return ComponentPreset.Type.TRANSITION;
@@ -548,7 +549,7 @@ public class Transition extends SymmetricComponent {
                if ( preset.has(ComponentPreset.FILLED ) ) {
                        presetFilled = preset.get( ComponentPreset.FILLED);
                }
-               
+
                if ( preset.has(ComponentPreset.SHAPE) ) {
                        Shape s = preset.get(ComponentPreset.SHAPE);
                        this.setType(s);
@@ -598,7 +599,7 @@ public class Transition extends SymmetricComponent {
         * @author Sampo Niskanen <sampo.niskanen@iki.fi>
         */
        public static enum Shape {
-               
+
                /**
                 * Conical shape.
                 */
@@ -616,7 +617,7 @@ public class Transition extends SymmetricComponent {
                                return radius * x / length;
                        }
                },
-               
+
                /**
                 * Ogive shape.  The shape parameter is the portion of an extended tangent ogive
                 * that will be used.  That is, for param==1 a tangent ogive will be produced, and
@@ -632,12 +633,12 @@ public class Transition extends SymmetricComponent {
                        public boolean usesParameter() {
                                return true; // Range 0...1 is default
                        }
-                       
+
                        @Override
                        public double defaultParameter() {
                                return 1.0; // Tangent ogive by default
                        }
-                       
+
                        @Override
                        public double getRadius(double x, double radius, double length, double param) {
                                assert x >= 0;
@@ -645,17 +646,17 @@ public class Transition extends SymmetricComponent {
                                assert radius >= 0;
                                assert param >= 0;
                                assert param <= 1;
-                               
+
                                // Impossible to calculate ogive for length < radius, scale instead
                                // TODO: LOW: secant ogive could be calculated lower
                                if (length < radius) {
                                        x = x * radius / length;
                                        length = radius;
                                }
-                               
+
                                if (param < 0.001)
                                        return CONICAL.getRadius(x, radius, length, param);
-                               
+
                                // Radius of circle is:
                                double R = MathUtil.safeSqrt((pow2(length) + pow2(radius)) *
                                                (pow2((2 - param) * length) + pow2(param * radius)) / (4 * pow2(param * radius)));
@@ -665,7 +666,7 @@ public class Transition extends SymmetricComponent {
                                return MathUtil.safeSqrt(R * R - (L - x) * (L - x)) - y0;
                        }
                },
-               
+
                /**
                 * Ellipsoidal shape.
                 */
@@ -673,7 +674,7 @@ public class Transition extends SymmetricComponent {
                ELLIPSOID(trans.get("Shape.Ellipsoid"),
                                //// An ellipsoidal nose cone has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.
                                trans.get("Shape.Ellipsoid.desc1"),
-                               //// An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.  If the transition is not clipped, then the profile is extended at the center by the corresponding radius.         
+                               //// An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.  If the transition is not clipped, then the profile is extended at the center by the corresponding radius.
                                trans.get("Shape.Ellipsoid.desc2"), true) {
                        @Override
                        public double getRadius(double x, double radius, double length, double param) {
@@ -684,7 +685,7 @@ public class Transition extends SymmetricComponent {
                                return MathUtil.safeSqrt(2 * radius * x - x * x); // radius/length * sphere
                        }
                },
-               
+
                //// Power series
                POWER(trans.get("Shape.Powerseries"),
                                trans.get("Shape.Powerseries.desc1"),
@@ -693,12 +694,12 @@ public class Transition extends SymmetricComponent {
                        public boolean usesParameter() { // Range 0...1
                                return true;
                        }
-                       
+
                        @Override
                        public double defaultParameter() {
                                return 0.5;
                        }
-                       
+
                        @Override
                        public double getRadius(double x, double radius, double length, double param) {
                                assert x >= 0;
@@ -714,29 +715,29 @@ public class Transition extends SymmetricComponent {
                                }
                                return radius * Math.pow(x / length, param);
                        }
-                       
+
                },
-               
+
                //// Parabolic series
                PARABOLIC(trans.get("Shape.Parabolicseries"),
                                ////A parabolic series nose cone has a profile of a parabola.  The shape parameter defines the segment of the parabola to utilize.  The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> nose cone.
                                trans.get("Shape.Parabolicseries.desc1"),
                                ////A parabolic series transition has a profile of a parabola.  The shape parameter defines the segment of the parabola to utilize.  The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube at the aft end, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> transition.
                                trans.get("Shape.Parabolicseries.desc2")) {
-                       
+
                        // In principle a parabolic transition is clippable, but the difference is
                        // negligible.
-                       
+
                        @Override
                        public boolean usesParameter() { // Range 0...1
                                return true;
                        }
-                       
+
                        @Override
                        public double defaultParameter() {
                                return 1.0;
                        }
-                       
+
                        @Override
                        public double getRadius(double x, double radius, double length, double param) {
                                assert x >= 0;
@@ -744,28 +745,28 @@ public class Transition extends SymmetricComponent {
                                assert radius >= 0;
                                assert param >= 0;
                                assert param <= 1;
-                               
+
                                return radius * ((2 * x / length - param * pow2(x / length)) / (2 - param));
                        }
                },
-               
+
                //// Haack series
                HAACK(trans.get("Shape.Haackseries"),
                                //// The Haack series nose cones are designed to minimize drag.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> nose cone, which minimizes drag for fixed length and diameter, while a value of 0.333 produces an <b>LV-Haack</b> nose cone, which minimizes drag for fixed length and volume.
                                trans.get("Shape.Haackseries.desc1"),
-                               //// The Haack series <i>nose cones</i> are designed to minimize drag.  These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.            
+                               //// The Haack series <i>nose cones</i> are designed to minimize drag.  These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.
                                trans.get("Shape.Haackseries.desc2"), true) {
-                       
+
                        @Override
                        public boolean usesParameter() {
                                return true;
                        }
-                       
+
                        @Override
                        public double maxParameter() {
                                return 1.0 / 3.0; // Range 0...1/3
                        }
-                       
+
                        @Override
                        public double getRadius(double x, double radius, double length, double param) {
                                assert x >= 0;
@@ -773,7 +774,7 @@ public class Transition extends SymmetricComponent {
                                assert radius >= 0;
                                assert param >= 0;
                                assert param <= 2;
-                               
+
                                double theta = Math.acos(1 - 2 * x / length);
                                if (MathUtil.equals(param, 0)) {
                                        return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2) / Math.PI);
@@ -781,7 +782,7 @@ public class Transition extends SymmetricComponent {
                                return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2 + param * pow3(sin(theta))) / Math.PI);
                        }
                },
-               
+
                //              POLYNOMIAL("Smooth polynomial",
                //                              "A polynomial is fitted such that the nose cone profile is horizontal "+
                //                              "at the aft end of the transition.  The angle at the tip is defined by "+
@@ -813,18 +814,18 @@ public class Transition extends SymmetricComponent {
                //                      }
                //              }
                ;
-               
+
                // Privete fields of the shapes
                private final String name;
                private final String transitionDesc;
                private final String noseconeDesc;
                private final boolean canClip;
-               
+
                // Non-clippable constructor
                Shape(String name, String noseconeDesc, String transitionDesc) {
                        this(name, noseconeDesc, transitionDesc, false);
                }
-               
+
                // Clippable constructor
                Shape(String name, String noseconeDesc, String transitionDesc, boolean canClip) {
                        this.name = name;
@@ -832,29 +833,29 @@ public class Transition extends SymmetricComponent {
                        this.noseconeDesc = noseconeDesc;
                        this.transitionDesc = transitionDesc;
                }
-               
-               
+
+
                /**
                 * Return the name of the transition shape name.
                 */
                public String getName() {
                        return name;
                }
-               
+
                /**
                 * Get a description of the Transition shape.
                 */
                public String getTransitionDescription() {
                        return transitionDesc;
                }
-               
+
                /**
                 * Get a description of the NoseCone shape.
                 */
                public String getNoseConeDescription() {
                        return noseconeDesc;
                }
-               
+
                /**
                 * Check whether the shape differs in clipped mode.  The clipping should be
                 * enabled by default if possible.
@@ -862,35 +863,35 @@ public class Transition extends SymmetricComponent {
                public boolean isClippable() {
                        return canClip;
                }
-               
+
                /**
                 * Return whether the shape uses the shape parameter.  (Default false.)
                 */
                public boolean usesParameter() {
                        return false;
                }
-               
+
                /**
                 * Return the minimum value of the shape parameter.  (Default 0.)
                 */
                public double minParameter() {
                        return 0.0;
                }
-               
+
                /**
                 * Return the maximum value of the shape parameter.  (Default 1.)
                 */
                public double maxParameter() {
                        return 1.0;
                }
-               
+
                /**
                 * Return the default value of the shape parameter.  (Default 0.)
                 */
                public double defaultParameter() {
                        return 0.0;
                }
-               
+
                /**
                 * Calculate the basic radius of a transition with the given radius, length and
                 * shape parameter at the point x from the tip of the component.  It is assumed
@@ -904,8 +905,8 @@ public class Transition extends SymmetricComponent {
                 * @return       The basic radius at the given position.
                 */
                public abstract double getRadius(double x, double radius, double length, double param);
-               
-               
+
+
                /**
                 * Returns the name of the shape (same as getName()).
                 */
@@ -913,5 +914,22 @@ public class Transition extends SymmetricComponent {
                public String toString() {
                        return name;
                }
+
+        /**
+         * Lookup the Shape given the localized name.  This differs from the standard valueOf as that looks up
+         * based on the canonical name, not the localized name which is an instance var.
+         *
+         * @param localizedName
+         * @return
+         */
+        public static Shape toShape(String localizedName) {
+            Shape[] values = Shape.values();
+            for (Shape value : values) {
+                if (value.getName().equals(localizedName)) {
+                    return value;
+                }
+            }
+            return null;
+        }
        }
 }