language selector, bug fixed
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Thu, 21 Jul 2011 18:08:42 +0000 (18:08 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Thu, 21 Jul 2011 18:08:42 +0000 (18:08 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@141 180e2498-e6e9-4542-8430-84ac67f01cd8

39 files changed:
ChangeLog
l10n/messages.properties
l10n/messages_de.properties
l10n/messages_es.properties
l10n/messages_fr.properties
pix-src/icons/edit-scale.xcf.gz [new file with mode: 0644]
pix/icons/copyright.txt
pix/icons/edit-scale.png [new file with mode: 0644]
src/net/sf/openrocket/document/Simulation.java
src/net/sf/openrocket/file/motor/MotorLoaderHelper.java
src/net/sf/openrocket/file/motor/RockSimMotorLoader.java
src/net/sf/openrocket/gui/dialogs/BugReportDialog.java
src/net/sf/openrocket/gui/dialogs/PrintDialog.java
src/net/sf/openrocket/gui/dialogs/ScaleDialog.java
src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java
src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java
src/net/sf/openrocket/gui/main/BasicFrame.java
src/net/sf/openrocket/gui/main/SimpleFileFilter.java [deleted file]
src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java
src/net/sf/openrocket/gui/plot/PlotDialog.java
src/net/sf/openrocket/gui/print/DesignReport.java
src/net/sf/openrocket/gui/print/PrintSimulationWorker.java
src/net/sf/openrocket/l10n/L10N.java [new file with mode: 0644]
src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java
src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java
src/net/sf/openrocket/rocketcomponent/FinSet.java
src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java
src/net/sf/openrocket/simulation/GUISimulationConditions.java
src/net/sf/openrocket/simulation/RK4SimulationStepper.java
src/net/sf/openrocket/startup/Application.java
src/net/sf/openrocket/startup/Startup.java
src/net/sf/openrocket/unit/Unit.java
src/net/sf/openrocket/util/FileHelper.java [new file with mode: 0644]
src/net/sf/openrocket/util/Icons.java
src/net/sf/openrocket/util/Named.java [new file with mode: 0644]
src/net/sf/openrocket/util/Prefs.java
src/net/sf/openrocket/util/SimpleFileFilter.java [new file with mode: 0644]
src/net/sf/openrocket/util/Utils.java [new file with mode: 0644]
test/net/sf/openrocket/IntegrationTest.java

index 774ccf1576b8f8a292b7df359c5b4f3f5d75175f..13de0125d54c9ea77ade393838afff4c96b3cc3a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2011-07-21  Sampo Niskanen
+
+       * [BUG] Converting triangular fin to freeform
+       * [BUG] Unit conversions in printout
+       * [BUG] Mass computations in printout
+       * [BUG] "Not a drop" exception in ComponentTreeTransferHandler
+       * [BUG] Plot annotation positioned wrong
+       * [BUG] Exception when writing PDF failed
+       * Language selector in preferences
+
 2011-07-18  Sampo Niskanen
 
        * Select motor type based on known manufacturers
index 6f14e49eeeddbe4fc9535cbe180cf5907580d365..9cfea75d0330ca7b158a7a6722e2090169c83f5d 100644 (file)
@@ -6,7 +6,11 @@
 #
 # className.ComponentType.componentName
 #
+#
+# Text tokens within braces should not be translated, e.g.
+#    "The file '{filename}' exists."
+# They are pieces that are inserted dynamically.
+#
 
 
 ! Set to the name of the current translation file (used for debugging purposes)
@@ -65,6 +69,15 @@ BasicFrame.WarningDialog.txt1 = The following problems were encountered while op
 BasicFrame.WarningDialog.txt2 = Some design features may not have been loaded correctly.
 BasicFrame.WarningDialog.title = Warnings while opening file
 
+
+! General error messages used in multiple contexts
+error.fileExists.title = File exists
+error.fileExists.desc = File '{filename}' exists.  Do you want to overwrite it?
+
+error.writing.title = Error writing file
+error.writing.desc = An error occurred while writing to the file:
+
+
 ! Labels used in buttons of dialog windows
 button.ok = OK
 button.cancel = Cancel
@@ -105,6 +118,7 @@ PrintDialog.error.preview.title = Unable to open preview
 PrintDialog.error.preview.desc1 = Unable to open PDF preview.
 PrintDialog.error.preview.desc2 = Please use the "Save as PDF" option instead.
 
+
 !PrintSettingsDialog
 PrintSettingsDialog.title = Print settings
 PrintSettingsDialog.lbl.Templatefillcolor = Template fill color:
@@ -130,6 +144,8 @@ bugreport.dlg.failedmsg2 = Please send the report manually to
 bugreport.dlg.failedmsg3 = Error sending report
 bugreport.reportDialog.txt = <html><b>You can report a bug in OpenRocket by filling in and submitting the form below.</b><br>You can also report bugs and include attachments on the project web site.
 bugreport.reportDialog.txt2 = <html><b>Please include a short description about what you were doing when the exception occurred.</b>
+bugreport.dlg.provideDescription = Please provide a description of the bug first.
+bugreport.dlg.provideDescription.title = Bug description missing
 
 
 ! Debug log dialog
@@ -186,7 +202,7 @@ matedtpan.but.ttip.delete = Delete a user-defined material
 matedtpan.but.ttip.revertall = Delete all user-defined materials
 matedtpan.title.Deletealluser-defined = Delete all user-defined materials?
 matedtpan.title.Revertall = Revert all?
-matedtpan.lbl.edtmaterials = <html><i>Editing materials will not affect existing rocket designs.</i>
+matedtpan.lbl.edtmaterials = Editing materials will not affect existing rocket designs.
 
 !MaterialModel
 MaterialModel.title.Material = Material
@@ -221,7 +237,7 @@ pref.dlg.lbl.Linedensity = Line density:
 pref.dlg.lbl.Motordimensions = Motor dimensions:
 pref.dlg.lbl.Surfacedensity = Surface density:
 pref.dlg.lbl.Distance = Distance:
-pref.dlg.lbl.Bulkdensity = Bulk density::
+pref.dlg.lbl.Bulkdensity = Bulk density:
 pref.dlg.lbl.Velocity = Velocity:
 pref.dlg.lbl.Surfaceroughness = Surface roughness:
 pref.dlg.lbl.Acceleration = Acceleration:
@@ -235,6 +251,7 @@ pref.dlg.lbl.Temperature = Temperature:
 pref.dlg.lbl.Momentofinertia = Moment of inertia:
 pref.dlg.lbl.Pressure = Pressure:
 pref.dlg.lbl.Stability = Stability:
+pref.dlg.lbl.FlightTime = Flight time:
 pref.dlg.lbl.effect1 = The effects will take place the next time you open a window.
 pref.dlg.lbl.Checkingupdates = Checking for updates...
 pref.dlg.lbl.msg1 = An error occurred while communicating with the server.
@@ -249,6 +266,9 @@ pref.dlg.PrefBooleanSelector2 = Confirm
 pref.dlg.Add = Add
 pref.dlg.DescriptionArea.Adddirectories = Add directories, RASP motor files (*.eng), RockSim engine files (*.rse) or ZIP archives separated by a semicolon (;) to load external thrust curves.  Changes will take effect the next time you start OpenRocket.
 
+PreferencesDialog.lbl.language = Interface language:
+PreferencesDialog.languages.default = System default
+PreferencesDialog.lbl.languageEffect = The language will change the next time you start OpenRocket.
 
 ! Simulation edit dialog
 simedtdlg.but.runsimulation = Run simulation
@@ -1254,7 +1274,9 @@ ScaleDialog.lbl.scaleFromTo.ttip = Define the scaling based on an original and r
 ScaleDialog.checkbox.scaleMass = Update explicit mass values
 ScaleDialog.checkbox.scaleMass.ttip = Scale mass component and override mass values by the cube of the scaling factor
 ScaleDialog.button.scale = Scale
-
+ScaleDialog.undo.scaleRocket = Scale rocket
+ScaleDialog.undo.scaleComponent = Scale component
+ScaleDialog.undo.scaleComponents = Scale components
 
 !icons
 Icons.Undo = Undo
index f3f6bbe1d25ceecf2e374bcd87d8698965960bd0..055f0518f7418811227a7b2a17a481a7d390ca1e 100644 (file)
@@ -191,7 +191,7 @@ matedtpan.but.ttip.delete = Benutzerdefiniertes Material l
 matedtpan.but.ttip.revertall = Alle benutzerdefinierten Materialien löschen\r
 matedtpan.title.Deletealluser-defined = Alle benutzerdefinierten Materialien löschen?\r
 matedtpan.title.Revertall = Alle löschen?\r
-matedtpan.lbl.edtmaterials = <html><i>Das Bearbeiten der Materialien beeinflusst keine bereits existierenden Raketendesigns.</i>\r
+matedtpan.lbl.edtmaterials = Das Bearbeiten der Materialien beeinflusst keine bereits existierenden Raketendesigns.\r
 \r
 !MaterialModel\r
 MaterialModel.title.Material = Material\r
index 7061bc4b569d4b969c3c20c3d7ae0b0fad07459b..36d368e8f33d54b3d1e8f7c5e9c51a2820ccf8a7 100644 (file)
@@ -195,7 +195,7 @@ matedtpan.but.ttip.delete = Borrar un material pre definido
 matedtpan.but.ttip.revertall = Borrar todos los materiales predefinidos
 matedtpan.title.Deletealluser-defined = ¿Borrar todos los materiales predefinidos?
 matedtpan.title.Revertall = ¿Revertir todo?
-matedtpan.lbl.edtmaterials = <html><i>Editar materiales que no afectaran los diseños existentes.</i>
+matedtpan.lbl.edtmaterials = Editar materiales que no afectaran los diseños existentes.
 
 !MaterialModel
 MaterialModel.title.Material = Material
index fe61e71e2810f0ca8117de643e942000dd7f7e45..a4013d084dc5d9c32e693dcc4c7d9da1442e3744 100644 (file)
@@ -186,7 +186,7 @@ matedtpan.but.ttip.delete = Supprimer un mat
 matedtpan.but.ttip.revertall = Supprimer tous les matériaux personnalisés\r
 matedtpan.title.Deletealluser-defined = Effacer tous les matériaux personalisés?\r
 matedtpan.title.Revertall = Revenir aux valeurs précédentes?\r
-matedtpan.lbl.edtmaterials = <html><i>Modifier les matériaux n'affectera pas les projets fusée existants.</i>\r
+matedtpan.lbl.edtmaterials = Modifier les matériaux n'affectera pas les projets fusée existants.\r
 \r
 !MaterialModel\r
 MaterialModel.title.Material = Matériau\r
diff --git a/pix-src/icons/edit-scale.xcf.gz b/pix-src/icons/edit-scale.xcf.gz
new file mode 100644 (file)
index 0000000..b0a1fd0
Binary files /dev/null and b/pix-src/icons/edit-scale.xcf.gz differ
index c70dca5d379bb7938f4f822da63772f7acc2c5c0..81429b3111d827de0acc09e4e597e5030a7d3c37 100644 (file)
@@ -20,6 +20,7 @@ edit-delete.png
 edit-paste.png
 edit-redo.png
 edit-undo.png
+edit-scale.png (modified from edit-copy.png)
 
 
 From the "Crystal" project:
diff --git a/pix/icons/edit-scale.png b/pix/icons/edit-scale.png
new file mode 100644 (file)
index 0000000..1478df8
Binary files /dev/null and b/pix/icons/edit-scale.png differ
index 7b4e0f251fc02541f57a2dd4c56e58fafce8da89..709ce4640f9df16e835e1dd9e45c3c0804f4b09d 100644 (file)
@@ -254,7 +254,12 @@ public class Simulation implements ChangeSource, Cloneable {
        
        
 
-
+       /**
+        * Simulate the flight.
+        * 
+        * @param additionalListeners   additional simulation listeners (those defined by the simulation are used in any case)
+        * @throws SimulationException  if a problem occurs during simulation
+        */
        public void simulate(SimulationListener... additionalListeners)
                                                throws SimulationException {
                mutex.lock("simulate");
index 377ade46b150458eddca4b863405f82e892c41dc..d3bbc3dc60e009686d9c8899793bb98cefb896f8 100644 (file)
@@ -11,12 +11,12 @@ import java.util.List;
 
 import net.sf.openrocket.file.iterator.DirectoryIterator;
 import net.sf.openrocket.file.iterator.FileIterator;
-import net.sf.openrocket.gui.main.SimpleFileFilter;
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.motor.ThrustCurveMotor;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.Pair;
+import net.sf.openrocket.util.SimpleFileFilter;
 
 public final class MotorLoaderHelper {
        
index 6206a85d77a5ada81c6153d5b098d16d091b4d40..d2bbcd265e0e83ec29d436a635716b4029f86fc8 100644 (file)
@@ -33,12 +33,10 @@ public class RockSimMotorLoader extends AbstractMotorLoader {
        public static final Charset CHARSET = Charset.forName(CHARSET_NAME);
        
 
-       /** Any delay longed than this will be interpreted as a plugged motor. */
+       /** Any delay longer than this will be interpreted as a plugged motor. */
        private static final int DELAY_LIMIT = 90;
        
        
-       // FIXME: Obtain default motor type from manufacturer info
-       
 
        @Override
        protected Charset getDefaultCharset() {
index 9966dbe50ada1695b4d6f331f5e32d7d6794efcf..f9001bf2426edb9ee1b8a46784d42cf04ae1b497 100644 (file)
@@ -43,9 +43,9 @@ public class BugReportDialog extends JDialog {
        
        private static final String REPORT_EMAIL = "openrocket-bugs@lists.sourceforge.net";
        private static final Translator trans = Application.getTranslator();
-
        
-       public BugReportDialog(Window parent, String labelText, String message) {
+       
+       public BugReportDialog(Window parent, String labelText, final String message, final boolean sendIfUnchanged) {
                //// Bug report
                super(parent, trans.get("bugreport.dlg.title"), Dialog.ModalityType.APPLICATION_MODAL);
                
@@ -67,7 +67,7 @@ public class BugReportDialog extends JDialog {
                panel.add(label, "gapleft para, wrap");
                
                //// Otherwise, send the text below to the address:
-               panel.add(new JLabel(trans.get("bugreport.dlg.otherwise") +" "),
+               panel.add(new JLabel(trans.get("bugreport.dlg.otherwise") + " "),
                                "gapleft para, split 2, gapright rel");
                panel.add(new SelectableLabel(REPORT_EMAIL), "growx, wrap para");
                
@@ -115,6 +115,13 @@ public class BugReportDialog extends JDialog {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                String text = textArea.getText();
+                               if (text.equals(message) && !sendIfUnchanged) {
+                                       JOptionPane.showMessageDialog(BugReportDialog.this,
+                                                       trans.get("bugreport.dlg.provideDescription"),
+                                                       trans.get("bugreport.dlg.provideDescription.title"), JOptionPane.ERROR_MESSAGE);
+                                       return;
+                               }
+                               
                                try {
                                        
                                        BugReporter.sendBugReport(text);
@@ -127,7 +134,7 @@ public class BugReportDialog extends JDialog {
                                                        "Bug report sent", JOptionPane.INFORMATION_MESSAGE);*/
                                        JOptionPane.showMessageDialog(BugReportDialog.this,
                                                        new Object[] { trans.get("bugreport.dlg.successmsg1"),
-                                                       trans.get("bugreport.dlg.successmsg2") },
+                                                                       trans.get("bugreport.dlg.successmsg2") },
                                                        trans.get("bugreport.dlg.successmsg3"), JOptionPane.INFORMATION_MESSAGE);
                                        
                                } catch (Exception ex) {
@@ -137,9 +144,9 @@ public class BugReportDialog extends JDialog {
                                                        new Object[] { trans.get("bugreport.dlg.failedmsg1"),
                                                                        ex.getClass().getSimpleName() + ": " + ex.getMessage(), " ",
                                                                        //// Please send the report manually to 
-                                                                       trans.get("bugreport.dlg.failedmsg2") +" " + REPORT_EMAIL },
+                                                                       trans.get("bugreport.dlg.failedmsg2") + " " + REPORT_EMAIL },
                                                                        //// Error sending report
-                                                                       trans.get("bugreport.dlg.failedmsg3"), JOptionPane.ERROR_MESSAGE);
+                                                       trans.get("bugreport.dlg.failedmsg3"), JOptionPane.ERROR_MESSAGE);
                                }
                        }
                });
@@ -197,10 +204,8 @@ public class BugReportDialog extends JDialog {
                sb.append("---------- End of bug report ----------\n");
                sb.append('\n');
                
-               BugReportDialog reportDialog =
-                               new BugReportDialog(parent,
-                                               //// <html><b>You can report a bug in OpenRocket by filling in and submitting the form below.</b><br>You can also report bugs and include attachments on the project web site.
-                                               trans.get("bugreport.reportDialog.txt"), sb.toString());
+               BugReportDialog reportDialog = new BugReportDialog(parent,
+                                               trans.get("bugreport.reportDialog.txt"), sb.toString(), false);
                reportDialog.setVisible(true);
        }
        
@@ -258,8 +263,8 @@ public class BugReportDialog extends JDialog {
                sb.append('\n');
                
                BugReportDialog reportDialog =
-                       //// <html><b>Please include a short description about what you were doing when the exception occurred.</b>
-                               new BugReportDialog(parent, trans.get("bugreport.reportDialog.txt2"), sb.toString());
+                               //// <html><b>Please include a short description about what you were doing when the exception occurred.</b>
+                               new BugReportDialog(parent, trans.get("bugreport.reportDialog.txt2"), sb.toString(), true);
                reportDialog.setVisible(true);
        }
        
index 123e0aaa65e0a85acb382e0d64f02f151b929e5c..924baace34a00de203146057f3de76edde215106 100644 (file)
@@ -23,13 +23,11 @@ import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.event.TreeSelectionEvent;
 import javax.swing.event.TreeSelectionListener;
-import javax.swing.filechooser.FileFilter;
 import javax.swing.tree.TreeNode;
 import javax.swing.tree.TreePath;
 
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.gui.main.ExceptionHandler;
 import net.sf.openrocket.gui.print.PrintController;
 import net.sf.openrocket.gui.print.PrintSettings;
 import net.sf.openrocket.gui.print.PrintableContext;
@@ -40,6 +38,7 @@ import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.FileHelper;
 import net.sf.openrocket.util.GUIUtil;
 import net.sf.openrocket.util.Prefs;
 
@@ -48,9 +47,6 @@ import net.sf.openrocket.util.Prefs;
  */
 public class PrintDialog extends JDialog implements TreeSelectionListener {
        
-       // FIXME:  Printouts use SI units even when imperial are selected
-       // FIXME:  Array out of bounds exception when printing
-       
        private static final LogHelper log = Application.getLogger();
        private static final Translator trans = Application.getTranslator();
        
@@ -322,40 +318,37 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
        private boolean onSavePDF() {
                
                JFileChooser chooser = new JFileChooser();
-               // Note: source for ExampleFileFilter can be found in FileChooserDemo,
-               // under the demo/jfc directory in the Java 2 SDK, Standard Edition.
-               FileFilter filter = new FileFilter() {
+               chooser.setFileFilter(FileHelper.PDF_FILTER);
+               
+               // Select initial directory
+               File dir = document.getFile();
+               if (dir != null) {
+                       dir = dir.getParentFile();
+               }
+               if (dir == null) {
+                       dir = Prefs.getDefaultDirectory();
+               }
+               chooser.setCurrentDirectory(dir);
+               
+               int returnVal = chooser.showSaveDialog(this);
+               File file = chooser.getSelectedFile();
+               if (returnVal == JFileChooser.APPROVE_OPTION && file != null) {
                        
-                       //Accept all directories and all pdf files.
-                       @Override
-                       public boolean accept(File f) {
-                               if (f.isDirectory())
-                                       return true;
-                               return f.getName().toLowerCase().endsWith(".pdf");
+                       file = FileHelper.ensureExtension(file, "pdf");
+                       if (!FileHelper.confirmWrite(file, this)) {
+                               return false;
                        }
                        
-                       //The description of this filter
-                       @Override
-                       public String getDescription() {
-                               return trans.get("filetypes.pdf");
-                       }
-               };
-               chooser.setFileFilter(filter);
-               int returnVal = chooser.showSaveDialog(this);
-               if (returnVal == JFileChooser.APPROVE_OPTION) {
-                       
                        try {
-                               String fname = chooser.getSelectedFile().getCanonicalPath();
-                               if (!getExtension(fname).equals("pdf")) {
-                                       fname = fname + ".pdf";
-                               }
-                               File f = new File(fname);
+                               
                                PrintSettings settings = Prefs.getPrintSettings();
                                // TODO: HIGH: Remove UIManager, and pass settings to the actual printing methods
                                TemplateProperties.setColors(settings);
-                               generateReport(f, settings);
+                               generateReport(file, settings);
+                               
                        } catch (IOException e) {
-                               ExceptionHandler.handleErrorCondition(e);
+                               FileHelper.errorWriting(e, this);
+                               return false;
                        }
                        return true;
                } else {
@@ -363,16 +356,4 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
                }
        }
        
-       /**
-        * Get the extension of a file.
-        */
-       private static String getExtension(String s) {
-               String ext = null;
-               int i = s.lastIndexOf('.');
-               
-               if (i > 0 && i < s.length() - 1) {
-                       ext = s.substring(i + 1).toLowerCase();
-               }
-               return ext != null ? ext : "";
-       }
 }
index d53a1a54c28636052b6165e0ab65f82e2b39845e..c6ccc23987561d22fba64fdcdd2469759e8cd16a 100644 (file)
@@ -206,8 +206,6 @@ public class ScaleDialog extends JDialog {
        
        private boolean changing = false;
        
-       // FIXME: Localize
-       
        /**
         * Sole constructor.
         * 
@@ -430,7 +428,7 @@ public class ScaleDialog extends JDialog {
                        
                        // Scale the entire rocket design
                        try {
-                               document.startUndo("Scale rocket");
+                               document.startUndo(trans.get("undo.scaleRocket"));
                                for (RocketComponent c : document.getRocket()) {
                                        scale(c, mul, scaleMass);
                                }
@@ -442,7 +440,7 @@ public class ScaleDialog extends JDialog {
                        
                        // Scale component and subcomponents
                        try {
-                               document.startUndo("Scale components");
+                               document.startUndo(trans.get("undo.scaleComponents"));
                                for (RocketComponent c : selection) {
                                        scale(c, mul, scaleMass);
                                }
@@ -454,7 +452,7 @@ public class ScaleDialog extends JDialog {
                        
                        // Scale only the selected component
                        try {
-                               document.startUndo("Scale component");
+                               document.startUndo(trans.get("undo.scaleComponent"));
                                scale(selection, mul, scaleMass);
                        } finally {
                                document.stopUndo();
index 183a02eb982c49f5ebb1b10915ff03eef0e24f77..a106ba32deeba26d300f3a891dc9b79e1891ed8e 100644 (file)
@@ -24,6 +24,8 @@ import net.sf.openrocket.database.Database;
 import net.sf.openrocket.database.Databases;
 import net.sf.openrocket.gui.adaptors.Column;
 import net.sf.openrocket.gui.adaptors.ColumnTableModel;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
 import net.sf.openrocket.gui.dialogs.CustomMaterialDialog;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.material.Material;
@@ -32,7 +34,7 @@ import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.unit.Value;
 
 public class MaterialEditPanel extends JPanel {
-
+       
        private final JTable table;
        
        private final JButton addButton;
@@ -40,12 +42,12 @@ public class MaterialEditPanel extends JPanel {
        private final JButton deleteButton;
        private final JButton revertButton;
        private static final Translator trans = Application.getTranslator();
-
+       
        
        public MaterialEditPanel() {
                super(new MigLayout("fill"));
                
-               
+
                // TODO: LOW: Create sorter that keeps material types always in order
                final ColumnTableModel model = new ColumnTableModel(
                                //// Material
@@ -61,6 +63,7 @@ public class MaterialEditPanel extends JPanel {
                                        public Object getValueAt(int row) {
                                                return getMaterial(row).getType().toString();
                                        }
+                                       
                                        @Override
                                        public int getDefaultWidth() {
                                                return 15;
@@ -86,22 +89,24 @@ public class MaterialEditPanel extends JPanel {
                                                        throw new IllegalStateException("Material type " + m.getType());
                                                }
                                        }
+                                       
                                        @Override
                                        public int getDefaultWidth() {
                                                return 15;
                                        }
+                                       
                                        @Override
                                        public Class<?> getColumnClass() {
                                                return Value.class;
                                        }
                                }
-               ) {
-                       @Override
-                       public int getRowCount() {
-                               return Databases.BULK_MATERIAL.size() + Databases.SURFACE_MATERIAL.size() +
-                                       Databases.LINE_MATERIAL.size();
-                       }
-               };
+                               ) {
+                                       @Override
+                                       public int getRowCount() {
+                                               return Databases.BULK_MATERIAL.size() + Databases.SURFACE_MATERIAL.size() +
+                                                               Databases.LINE_MATERIAL.size();
+                                       }
+                               };
                
                table = new JTable(model);
                model.setColumnWidths(table.getColumnModel());
@@ -109,7 +114,7 @@ public class MaterialEditPanel extends JPanel {
                table.setDefaultRenderer(Object.class, new MaterialCellRenderer());
                this.add(new JScrollPane(table), "w 200px, h 100px, grow 100");
                
-               
+
                //// New button
                addButton = new JButton(trans.get("matedtpan.but.new"));
                //// Add a new material
@@ -120,7 +125,7 @@ public class MaterialEditPanel extends JPanel {
                                CustomMaterialDialog dialog = new CustomMaterialDialog(
                                                        SwingUtilities.getWindowAncestor(MaterialEditPanel.this),
                                                        //// Add a custom material
-                                                       null, false, trans.get("matedtpan.title.Addcustmaterial"));
+                                               null, false, trans.get("matedtpan.title.Addcustmaterial"));
                                dialog.setVisible(true);
                                if (dialog.getOkClicked()) {
                                        Material mat = dialog.getMaterial();
@@ -155,7 +160,7 @@ public class MaterialEditPanel extends JPanel {
                                        dialog = new CustomMaterialDialog(
                                                        SwingUtilities.getWindowAncestor(MaterialEditPanel.this),
                                                        //// Add a custom material
-                                                       m, false, trans.get("matedtpan.title.Addcustmaterial"), 
+                                                       m, false, trans.get("matedtpan.title.Addcustmaterial"),
                                                        //// The built-in materials cannot be modified.
                                                        trans.get("matedtpan.title2.Editmaterial"));
                                }
@@ -196,7 +201,7 @@ public class MaterialEditPanel extends JPanel {
                });
                this.add(deleteButton, "gap rel rel para para, growx 1, top");
                
-               
+
                this.add(new JPanel(), "grow 1");
                
                //// Revert all button
@@ -206,27 +211,27 @@ public class MaterialEditPanel extends JPanel {
                revertButton.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
-                               int sel = JOptionPane.showConfirmDialog(MaterialEditPanel.this, 
+                               int sel = JOptionPane.showConfirmDialog(MaterialEditPanel.this,
                                                //// Delete all user-defined materials?
-                                               trans.get("matedtpan.title.Deletealluser-defined"), 
+                                               trans.get("matedtpan.title.Deletealluser-defined"),
                                                //// Revert all?
-                                               trans.get("matedtpan.title.Revertall"), 
+                                               trans.get("matedtpan.title.Revertall"),
                                                JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
                                if (sel == JOptionPane.YES_OPTION) {
                                        Iterator<Material> iterator;
-
+                                       
                                        iterator = Databases.LINE_MATERIAL.iterator();
                                        while (iterator.hasNext()) {
                                                if (iterator.next().isUserDefined())
                                                        iterator.remove();
                                        }
-
+                                       
                                        iterator = Databases.SURFACE_MATERIAL.iterator();
                                        while (iterator.hasNext()) {
                                                if (iterator.next().isUserDefined())
                                                        iterator.remove();
                                        }
-
+                                       
                                        iterator = Databases.BULK_MATERIAL.iterator();
                                        while (iterator.hasNext()) {
                                                if (iterator.next().isUserDefined())
@@ -237,7 +242,7 @@ public class MaterialEditPanel extends JPanel {
                                }
                        }
                });
-               this.add(revertButton, "gap rel rel para para, growx 1, bottom, wrap");
+               this.add(revertButton, "gap rel rel para para, growx 1, bottom, wrap unrel");
                
                setButtonStates();
                table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@@ -257,9 +262,9 @@ public class MaterialEditPanel extends JPanel {
                
                //// <html><i>Editing materials will not affect existing
                //// rocket designs.</i>
-               this.add(new JLabel(trans.get("matedtpan.lbl.edtmaterials")), "span");
-               
+               this.add(new StyledLabel(trans.get("matedtpan.lbl.edtmaterials"), -2, Style.ITALIC), "span");
                
+
        }
        
        
@@ -273,9 +278,9 @@ public class MaterialEditPanel extends JPanel {
                        
                case LINE:
                        return Databases.LINE_MATERIAL;
-
+                       
                default:
-                       throw new IllegalArgumentException("Material type invalid, m="+m);
+                       throw new IllegalArgumentException("Material type invalid, m=" + m);
                }
        }
        
@@ -296,18 +301,18 @@ public class MaterialEditPanel extends JPanel {
                } else {
                        deleteButton.setEnabled(false);
                }
-
+               
                // Revert button enabled if any user-defined material exists
                boolean found = false;
                
-               for (Material m: Databases.BULK_MATERIAL) {
+               for (Material m : Databases.BULK_MATERIAL) {
                        if (m.isUserDefined()) {
                                found = true;
                                break;
                        }
                }
                if (!found) {
-                       for (Material m: Databases.SURFACE_MATERIAL) {
+                       for (Material m : Databases.SURFACE_MATERIAL) {
                                if (m.isUserDefined()) {
                                        found = true;
                                        break;
@@ -315,7 +320,7 @@ public class MaterialEditPanel extends JPanel {
                        }
                }
                if (!found) {
-                       for (Material m: Databases.LINE_MATERIAL) {
+                       for (Material m : Databases.LINE_MATERIAL) {
                                if (m.isUserDefined()) {
                                        found = true;
                                        break;
@@ -346,25 +351,25 @@ public class MaterialEditPanel extends JPanel {
                if (row < n) {
                        return Databases.LINE_MATERIAL.get(row);
                }
-               throw new IndexOutOfBoundsException("row="+origRow+" while material count" +
-                               " bulk:" + Databases.BULK_MATERIAL.size() + 
+               throw new IndexOutOfBoundsException("row=" + origRow + " while material count" +
+                               " bulk:" + Databases.BULK_MATERIAL.size() +
                                " surface:" + Databases.SURFACE_MATERIAL.size() +
                                " line:" + Databases.LINE_MATERIAL.size());
        }
        
        
        private class MaterialCellRenderer extends DefaultTableCellRenderer {
-
+               
                /* (non-Javadoc)
                 * @see javax.swing.table.DefaultTableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
                 */
                @Override
                public Component getTableCellRendererComponent(JTable table, Object value,
                                boolean isSelected, boolean hasFocus, int row, int column) {
-                       Component c = super.getTableCellRendererComponent(table, value, isSelected, 
+                       Component c = super.getTableCellRendererComponent(table, value, isSelected,
                                        hasFocus, row, column);
                        if (c instanceof JLabel) {
-                               JLabel label = (JLabel)c;
+                               JLabel label = (JLabel) c;
                                Material m = getMaterial(row);
                                
                                if (isSelected) {
@@ -383,5 +388,5 @@ public class MaterialEditPanel extends JPanel {
                }
                
        }
-
+       
 }
index 65a09aa8296d757b745e03ffbd8c55c912c0e756..a13be044bc2382b973c575992b5563e0277e4d29 100644 (file)
@@ -8,7 +8,9 @@ import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 
 import javax.swing.AbstractListModel;
 import javax.swing.ComboBoxModel;
@@ -32,15 +34,19 @@ import net.sf.openrocket.communication.UpdateInfo;
 import net.sf.openrocket.communication.UpdateInfoRetriever;
 import net.sf.openrocket.gui.components.DescriptionArea;
 import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
 import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
-import net.sf.openrocket.gui.main.SimpleFileFilter;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.Unit;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Named;
 import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.util.SimpleFileFilter;
+import net.sf.openrocket.util.Utils;
+
 
 public class PreferencesDialog extends JDialog {
        private static final LogHelper log = Application.getLogger();
@@ -49,10 +55,10 @@ public class PreferencesDialog extends JDialog {
        
        private File defaultDirectory = null;
        private static final Translator trans = Application.getTranslator();
-
-       private PreferencesDialog() {
+       
+       private PreferencesDialog(Window parent) {
                //// Preferences
-               super((Window) null, trans.get("pref.dlg.title.Preferences"), Dialog.ModalityType.APPLICATION_MODAL);
+               super(parent, trans.get("pref.dlg.title.Preferences"), Dialog.ModalityType.APPLICATION_MODAL);
                
                JPanel panel = new JPanel(new MigLayout("fill, gap unrel", "[grow]", "[grow][]"));
                
@@ -60,13 +66,13 @@ public class PreferencesDialog extends JDialog {
                panel.add(tabbedPane, "grow, wrap");
                
                //// Units and Default units
-               tabbedPane.addTab(trans.get("pref.dlg.tab.Units"), null, unitsPane(), 
+               tabbedPane.addTab(trans.get("pref.dlg.tab.Units"), null, unitsPane(),
                                trans.get("pref.dlg.tab.Defaultunits"));
                //// Materials and Custom materials
                tabbedPane.addTab(trans.get("pref.dlg.tab.Materials"), null, new MaterialEditPanel(),
                                trans.get("pref.dlg.tab.Custommaterials"));
                //// Options and Miscellaneous options
-               tabbedPane.addTab(trans.get("pref.dlg.tab.Options"), null, optionsPane(), 
+               tabbedPane.addTab(trans.get("pref.dlg.tab.Options"), null, optionsPane(),
                                trans.get("pref.dlg.tab.Miscellaneousoptions"));
                
                //// Close button
@@ -98,14 +104,44 @@ public class PreferencesDialog extends JDialog {
        private JPanel optionsPane() {
                JPanel panel = new JPanel(new MigLayout("fillx, ins 30lp n n n"));
                
+
+               //// Language selector
+               Locale userLocale = Prefs.getUserLocale();
+               List<Named<Locale>> locales = new ArrayList<Named<Locale>>();
+               for (Locale l : Prefs.getSupportedLocales()) {
+                       locales.add(new Named<Locale>(l, l.getDisplayLanguage()));
+               }
+               Collections.sort(locales);
+               locales.add(0, new Named<Locale>(null, trans.get("languages.default")));
+               
+               final JComboBox languageCombo = new JComboBox(locales.toArray());
+               for (int i = 0; i < locales.size(); i++) {
+                       if (Utils.equals(userLocale, locales.get(i).get())) {
+                               languageCombo.setSelectedIndex(i);
+                       }
+               }
+               languageCombo.addActionListener(new ActionListener() {
+                       @Override
+                       @SuppressWarnings("unchecked")
+                       public void actionPerformed(ActionEvent e) {
+                               Named<Locale> selection = (Named<Locale>) languageCombo.getSelectedItem();
+                               Prefs.setUserLocale(selection.get());
+                       }
+               });
+               panel.add(new JLabel(trans.get("lbl.language")), "gapright para");
+               panel.add(languageCombo, "wrap rel, growx, sg combos");
+               
+               panel.add(new StyledLabel(trans.get("PreferencesDialog.lbl.languageEffect"), -3, Style.ITALIC), "span, wrap para*2");
+               
+
                //// Position to insert new body components:
                panel.add(new JLabel(trans.get("pref.dlg.lbl.Positiontoinsert")), "gapright para");
                panel.add(new JComboBox(new PrefChoiseSelector(Prefs.BODY_COMPONENT_INSERT_POSITION_KEY,
                                //// Always ask
                                //// Insert in middle
                                //// Add to end
-                               trans.get("pref.dlg.PrefChoiseSelector1"), 
-                               trans.get("pref.dlg.PrefChoiseSelector2"), 
+                               trans.get("pref.dlg.PrefChoiseSelector1"),
+                               trans.get("pref.dlg.PrefChoiseSelector2"),
                                trans.get("pref.dlg.PrefChoiseSelector3"))), "wrap para, growx, sg combos");
                
                //// Confirm deletion of simulations:
@@ -113,7 +149,7 @@ public class PreferencesDialog extends JDialog {
                panel.add(new JComboBox(new PrefBooleanSelector(Prefs.CONFIRM_DELETE_SIMULATION,
                                //// Delete
                                //// Confirm
-                               trans.get("pref.dlg.PrefBooleanSelector1"), 
+                               trans.get("pref.dlg.PrefBooleanSelector1"),
                                trans.get("pref.dlg.PrefBooleanSelector2"), true)), "wrap 40lp, growx, sg combos");
                
                //// User-defined thrust curves:
@@ -164,11 +200,11 @@ public class PreferencesDialog extends JDialog {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                JFileChooser chooser = new JFileChooser();
-                               SimpleFileFilter filter = 
-                                       new SimpleFileFilter(
-                                                       //// All thrust curve files (*.eng; *.rse; *.zip; directories)
-                                                       trans.get("pref.dlg.Allthrustcurvefiles"),
-                                               true, "eng", "rse", "zip");
+                               SimpleFileFilter filter =
+                                               new SimpleFileFilter(
+                                                               //// All thrust curve files (*.eng; *.rse; *.zip; directories)
+                                                               trans.get("pref.dlg.Allthrustcurvefiles"),
+                                                               true, "eng", "rse", "zip");
                                chooser.addChoosableFileFilter(filter);
                                //// RASP motor files (*.eng)
                                chooser.addChoosableFileFilter(new SimpleFileFilter(trans.get("pref.dlg.RASPfiles"),
@@ -222,8 +258,8 @@ public class PreferencesDialog extends JDialog {
 
 
                //// Check for software updates at startup
-               final JCheckBox softwareUpdateBox = 
-                       new JCheckBox(trans.get("pref.dlg.checkbox.Checkupdates"));
+               final JCheckBox softwareUpdateBox =
+                               new JCheckBox(trans.get("pref.dlg.checkbox.Checkupdates"));
                softwareUpdateBox.setSelected(Prefs.getCheckUpdates());
                softwareUpdateBox.addActionListener(new ActionListener() {
                        @Override
@@ -355,6 +391,7 @@ public class PreferencesDialog extends JDialog {
                combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_PRESSURE));
                panel.add(combo, "sizegroup boxes, wrap");
                
+
                //// Stability:
                panel.add(new JLabel(trans.get("pref.dlg.lbl.Stability")));
                combo = new JComboBox(new DefaultUnitSelector(UnitGroup.UNITS_STABILITY));
@@ -388,7 +425,7 @@ public class PreferencesDialog extends JDialog {
                
                //// The effects will take place the next time you open a window.
                panel.add(new StyledLabel(
-                               trans.get("pref.dlg.lbl.effect1"), -2),
+                               trans.get("pref.dlg.lbl.effect1"), -2, Style.ITALIC),
                                "spanx, wrap");
                
 
@@ -636,11 +673,11 @@ public class PreferencesDialog extends JDialog {
        
        private static PreferencesDialog dialog = null;
        
-       public static void showPreferences() {
+       public static void showPreferences(Window parent) {
                if (dialog != null) {
                        dialog.dispose();
                }
-               dialog = new PreferencesDialog();
+               dialog = new PreferencesDialog(parent);
                dialog.setVisible(true);
        }
        
index b6b74380506ea80044145020935da199905dc4fb..a9cbbfc0b00fdb3d466d595ba9df66d234997658 100644 (file)
@@ -51,7 +51,6 @@ import javax.swing.SwingUtilities;
 import javax.swing.border.TitledBorder;
 import javax.swing.event.TreeSelectionEvent;
 import javax.swing.event.TreeSelectionListener;
-import javax.swing.filechooser.FileFilter;
 import javax.swing.tree.DefaultTreeSelectionModel;
 import javax.swing.tree.TreePath;
 import javax.swing.tree.TreeSelectionModel;
@@ -92,6 +91,7 @@ import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.rocketcomponent.Stage;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.FileHelper;
 import net.sf.openrocket.util.GUIUtil;
 import net.sf.openrocket.util.Icons;
 import net.sf.openrocket.util.MemoryManagement;
@@ -114,23 +114,6 @@ public class BasicFrame extends JFrame {
        
        private static final Translator trans = Application.getTranslator();
        
-       // FileFilters for different types of rocket design files
-       private static final FileFilter ALL_DESIGNS_FILTER =
-                       //// All rocket designs (*.ork; *.rkt)
-                       new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter1"),
-                                       ".ork", ".ork.gz", ".rkt", ".rkt.gz");
-       
-       private static final FileFilter OPENROCKET_DESIGN_FILTER =
-                       //// OpenRocket designs (*.ork)
-                       new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter2"), ".ork", ".ork.gz");
-       
-       private static final FileFilter ROCKSIM_DESIGN_FILTER =
-                       //// RockSim designs (*.rkt)
-                       new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter3"), ".rkt", ".rkt.gz");
-       
-
-
-
        public static final int COMPONENT_TAB = 0;
        public static final int SIMULATION_TAB = 1;
        
@@ -612,9 +595,7 @@ public class BasicFrame extends JFrame {
 
 
                item = new JMenuItem(trans.get("main.menu.edit.resize"));
-               // FIXME: Icon
-               //item.setIcon(Icons.PREFERENCES);
-               //// Setup the application preferences
+               item.setIcon(Icons.EDIT_SCALE);
                item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.edit.resize.desc"));
                item.addActionListener(new ActionListener() {
                        @Override
@@ -638,7 +619,7 @@ public class BasicFrame extends JFrame {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                log.user("Preferences selected");
-                               PreferencesDialog.showPreferences();
+                               PreferencesDialog.showPreferences(BasicFrame.this);
                        }
                });
                menu.add(item);
@@ -1007,10 +988,10 @@ public class BasicFrame extends JFrame {
        private void openAction() {
                JFileChooser chooser = new JFileChooser();
                
-               chooser.addChoosableFileFilter(ALL_DESIGNS_FILTER);
-               chooser.addChoosableFileFilter(OPENROCKET_DESIGN_FILTER);
-               chooser.addChoosableFileFilter(ROCKSIM_DESIGN_FILTER);
-               chooser.setFileFilter(ALL_DESIGNS_FILTER);
+               chooser.addChoosableFileFilter(FileHelper.ALL_DESIGNS_FILTER);
+               chooser.addChoosableFileFilter(FileHelper.OPENROCKET_DESIGN_FILTER);
+               chooser.addChoosableFileFilter(FileHelper.ROCKSIM_DESIGN_FILTER);
+               chooser.setFileFilter(FileHelper.ALL_DESIGNS_FILTER);
                
                chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
                chooser.setMultiSelectionEnabled(true);
@@ -1234,7 +1215,7 @@ public class BasicFrame extends JFrame {
                log.info("Saving document to " + file);
                
                // Saving RockSim designs is not supported
-               if (ROCKSIM_DESIGN_FILTER.accept(file)) {
+               if (FileHelper.ROCKSIM_DESIGN_FILTER.accept(file)) {
                        file = new File(file.getAbsolutePath().replaceAll(".[rR][kK][tT](.[gG][zZ])?$",
                                        ".ork"));
                        
@@ -1262,7 +1243,7 @@ public class BasicFrame extends JFrame {
                StorageOptionChooser storageChooser =
                                new StorageOptionChooser(document, document.getDefaultStorageOptions());
                JFileChooser chooser = new JFileChooser();
-               chooser.setFileFilter(OPENROCKET_DESIGN_FILTER);
+               chooser.setFileFilter(FileHelper.OPENROCKET_DESIGN_FILTER);
                chooser.setCurrentDirectory(Prefs.getDefaultDirectory());
                chooser.setAccessory(storageChooser);
                if (document.getFile() != null)
@@ -1283,22 +1264,9 @@ public class BasicFrame extends JFrame {
                Prefs.setDefaultDirectory(chooser.getCurrentDirectory());
                storageChooser.storeOptions(document.getDefaultStorageOptions());
                
-               if (file.getName().indexOf('.') < 0) {
-                       log.debug("File name does not contain extension, adding .ork");
-                       String name = file.getAbsolutePath();
-                       name = name + ".ork";
-                       file = new File(name);
-               }
-               
-               if (file.exists()) {
-                       log.info("File " + file + " exists, confirming overwrite from user");
-                       int result = JOptionPane.showConfirmDialog(this,
-                                       "File '" + file.getName() + "' exists.  Do you want to overwrite it?",
-                                       "File exists", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
-                       if (result != JOptionPane.YES_OPTION) {
-                               log.user("User decided not to overwrite the file");
-                               return false;
-                       }
+               file = FileHelper.ensureExtension(file, "ork");
+               if (!FileHelper.confirmWrite(file, this)) {
+                       return false;
                }
                
                return saveAs(file);
diff --git a/src/net/sf/openrocket/gui/main/SimpleFileFilter.java b/src/net/sf/openrocket/gui/main/SimpleFileFilter.java
deleted file mode 100644 (file)
index 2d860b0..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-package net.sf.openrocket.gui.main;
-
-import java.io.File;
-
-import javax.swing.filechooser.FileFilter;
-
-/**
- * A FileFilter similar to FileNameExtensionFilter except that
- * it allows multipart extensions (.ork.gz), and also implements
- * the java.io.FileFilter interface.
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public class SimpleFileFilter extends FileFilter implements java.io.FileFilter {
-
-       private final String description;
-       private final boolean acceptDir;
-       private final String[] extensions;
-       
-       
-       /**
-        * Create filter that accepts files with the provided extensions that
-        * accepts directories as well.
-        * 
-        * @param description   the description of this file filter.
-        * @param extensions    an array of extensions that match this filter.
-        */
-       public SimpleFileFilter(String description, String ... extensions) {
-               this(description, true, extensions);
-       }
-       
-
-       /**
-        * Create filter that accepts files with the provided extensions.
-        * 
-        * @param description   the description of this file filter.
-        * @param acceptDir             whether to accept directories
-        * @param extensions    an array of extensions that match this filter.
-        */
-       public SimpleFileFilter(String description, boolean acceptDir, String ... extensions) {
-               this.description = description;
-               this.acceptDir = acceptDir;
-               this.extensions = new String[extensions.length];
-               for (int i=0; i<extensions.length; i++) {
-                       String ext = extensions[i].toLowerCase();
-                       if (ext.charAt(0) == '.') {
-                               this.extensions[i] = ext;
-                       } else {
-                               this.extensions[i] = '.' + ext;
-                       }
-               }
-       }
-       
-       
-       @Override
-       public boolean accept(File file) {
-               if (file == null)
-                       return false;
-               if (file.isDirectory())
-                       return acceptDir;
-               
-               String filename = file.getName();
-               filename = filename.toLowerCase();
-               for (String ext: extensions) {
-                       if (filename.endsWith(ext))
-                               return true;
-               }
-               
-               return false;
-       }
-
-       @Override
-       public String getDescription() {
-               return description;
-       }
-
-}
index f225f5e5a6fcf438c2ddcf8161505d3dfd210f4d..b998b83f2a93244f25e3ea7256018228c41dbf56 100644 (file)
@@ -103,6 +103,12 @@ public class ComponentTreeTransferHandler extends TransferHandler {
        @Override
        public boolean importData(TransferHandler.TransferSupport support) {
                
+               // We currently only support drop, not paste
+               if (!support.isDrop()) {
+                       log.warn("Import action is not a drop action");
+                       return false;
+               }
+               
                // Sun JRE silently ignores any RuntimeExceptions in importData, yeech!
                try {
                        
index 714a05303bb53d3150d069eac606727a461933da..da775482d640041a26a4c37e546c4a206497d89d 100644 (file)
@@ -69,7 +69,7 @@ public class PlotDialog extends JDialog {
        
        private static final float PLOT_STROKE_WIDTH = 1.5f;
        private static final Translator trans = Application.getTranslator();
-
+       
        private static final Color DEFAULT_EVENT_COLOR = new Color(0, 0, 0);
        private static final Map<FlightEvent.Type, Color> EVENT_COLORS =
                        new HashMap<FlightEvent.Type, Color>();
@@ -364,7 +364,7 @@ public class PlotDialog extends JDialog {
                                        }
                                }
                                
-                               final double xcoord;
+                               double xcoord;
                                if (a == 0) {
                                        xcoord = domain.get(tindex);
                                } else {
@@ -375,15 +375,25 @@ public class PlotDialog extends JDialog {
                                        FlightDataType type = config.getType(index);
                                        List<Double> range = branch.get(type);
                                        
-                                       final double ycoord;
+                                       // Image annotations are not supported on the right-side axis
+                                       // TODO: LOW: Can this be achieved by JFreeChart?
+                                       if (filled.getAxis(index) != SimulationPlotPanel.LEFT) {
+                                               continue;
+                                       }
+                                       
+                                       double ycoord;
                                        if (a == 0) {
                                                ycoord = range.get(tindex);
                                        } else {
                                                ycoord = a * range.get(tindex) + (1 - a) * range.get(tindex + 1);
                                        }
                                        
+                                       // Convert units
+                                       xcoord = config.getDomainAxisUnit().toUnit(xcoord);
+                                       ycoord = config.getUnit(index).toUnit(ycoord);
+                                       
                                        XYImageAnnotation annotation =
-                                                       new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
+                                                               new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
                                        annotation.setToolTipText(event);
                                        plot.addAnnotation(annotation);
                                }
@@ -448,7 +458,6 @@ public class PlotDialog extends JDialog {
                GUIUtil.setDisposableDialogOptions(this, button);
        }
        
-       
        private String getLabel(FlightDataType type, Unit unit) {
                String name = type.getName();
                if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
index 8bc1f0fd19dfd85f545cc6c52b49dbac29ec70ba..e131144477cdab6829b7db3cfc927c48216c7fd9 100644 (file)
@@ -3,30 +3,48 @@
  */
 package net.sf.openrocket.gui.print;
 
-import com.itextpdf.text.*;
-import com.itextpdf.text.Rectangle;
-import com.itextpdf.text.pdf.*;
+import java.awt.Graphics2D;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
 import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.gui.figureelements.FigureElement;
 import net.sf.openrocket.gui.figureelements.RocketInfo;
 import net.sf.openrocket.gui.scalefigure.RocketPanel;
 import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.masscalc.BasicMassCalculator;
+import net.sf.openrocket.masscalc.MassCalculator;
+import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
 import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.*;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.rocketcomponent.MotorMount;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.Stage;
 import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.simulation.exception.SimulationException;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.Unit;
 import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.Chars;
+import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.Prefs;
 
-import java.awt.*;
-import java.io.IOException;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.List;
+import com.itextpdf.text.Document;
+import com.itextpdf.text.DocumentException;
+import com.itextpdf.text.Element;
+import com.itextpdf.text.Paragraph;
+import com.itextpdf.text.Rectangle;
+import com.itextpdf.text.pdf.BaseFont;
+import com.itextpdf.text.pdf.DefaultFontMapper;
+import com.itextpdf.text.pdf.PdfContentByte;
+import com.itextpdf.text.pdf.PdfPCell;
+import com.itextpdf.text.pdf.PdfPTable;
+import com.itextpdf.text.pdf.PdfWriter;
 
 /**
  * <pre>
@@ -60,485 +78,494 @@ import java.util.List;
  * </pre>
  */
 public class DesignReport {
-
-    /**
-     * The logger.
-     */
-    private static final LogHelper log = Application.getLogger();
-
-    /**
-     * The OR Document.
-     */
-    private OpenRocketDocument rocketDocument;
-
-    /**
-     * A panel used for rendering of the design diagram.
-     */
-    final RocketPanel panel;
-
-    /**
-     * The iText document.
-     */
-    protected Document document;
-
-    /** The displayed strings. */
-    private static final String STAGES = "Stages: ";
-    private static final String MASS_WITH_MOTORS = "Mass (with motors): ";
-    private static final String MASS_WITH_MOTOR = "Mass (with motor): ";
-    private static final String MASS_EMPTY = "Mass (Empty): ";
-    private static final String STABILITY = "Stability: ";
-    private static final String CG = "Cg: ";
-    private static final String CP = "Cp: ";
-    private static final String MOTOR = "Motor";
-    private static final String AVG_THRUST = "Avg Thrust";
-    private static final String BURN_TIME = "Burn Time";
-    private static final String MAX_THRUST = "Max Thrust";
-    private static final String TOTAL_IMPULSE = "Total Impulse";
-    private static final String THRUST_TO_WT = "Thrust to Wt";
-    private static final String PROPELLANT_WT = "Propellant Wt";
-    private static final String SIZE = "Size";
-    private static final String ALTITUDE = "Altitude";
-    private static final String FLIGHT_TIME = "Flight Time";
-    private static final String TIME_TO_APOGEE = "Time to Apogee";
-    private static final String VELOCITY_OFF_PAD = "Velocity off Pad";
-    private static final String MAX_VELOCITY = "Max Velocity";
-    private static final String LANDING_VELOCITY = "Landing Velocity";
-    private static final String ROCKET_DESIGN = "Rocket Design";
-    private static final double GRAVITY_CONSTANT = 9.80665d;
-
-    /**
-     * Constructor.
-     *
-     * @param theRocDoc the OR document
-     * @param theIDoc   the iText document
-     */
-    public DesignReport (OpenRocketDocument theRocDoc, Document theIDoc) {
-        document = theIDoc;
-        rocketDocument = theRocDoc;
-        panel = new RocketPanel(rocketDocument);
-    }
-
-    /**
-     * Main entry point.  Prints the rocket drawing and design data.
-     *
-     * @param writer a direct byte writer
-     */
-    public void writeToDocument (PdfWriter writer) {
-        if (writer == null) {
-            return;
-        }
-        com.itextpdf.text.Rectangle pageSize = document.getPageSize();
-        int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
-        int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
-
-        PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
-
-        Rocket rocket = rocketDocument.getRocket();
-        final Configuration configuration = rocket.getDefaultConfiguration();
-        configuration.setAllStages();
-        PdfContentByte canvas = writer.getDirectContent();
-
-        final PrintFigure figure = new PrintFigure(configuration);
-
-        FigureElement cp = panel.getExtraCP();
-        FigureElement cg = panel.getExtraCG();
-        RocketInfo text = panel.getExtraText();
-
-        double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
-
-        canvas.beginText();
-        try {
-            canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
-                                                      BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
-        }
-        catch (DocumentException e) {
-            log.error("Could not set font.", e);
-        }
-        catch (IOException e) {
-            log.error("Could not create font.", e);
-        }
-        int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
-                .toPoints(1)));
-        final int diagramHeight = pageImageableHeight * 2 - 70 - (int) (figHeightPts);
-        canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
-        canvas.moveTextWithLeading(0, -16);
-
-        float initialY = canvas.getYTLM();
-
-        canvas.showText(rocketDocument.getRocket().getName());
-
-        canvas.newlineShowText(STAGES);
-        canvas.showText("" + rocket.getStageCount());
-
-
-        if (configuration.hasMotors()) {
-            if (configuration.getStageCount() > 1) {
-                canvas.newlineShowText(MASS_WITH_MOTORS);
-            }
-            else {
-                canvas.newlineShowText(MASS_WITH_MOTOR);
-            }
-        }
-        else {
-            canvas.newlineShowText(MASS_EMPTY);
-        }
-        canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
-
-        canvas.newlineShowText(STABILITY);
-        canvas.showText(text.getStability());
-
-        canvas.newlineShowText(CG);
-        canvas.showText(text.getCg());
-
-        canvas.newlineShowText(CP);
-        canvas.showText(text.getCp());
-        canvas.endText();
-
-        try {
-            //Move the internal pointer of the document below that of what was just written using the direct byte buffer.
-            Paragraph paragraph = new Paragraph();
-            float finalY = canvas.getYTLM();
-            int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
-
-            paragraph.setSpacingAfter(heightOfDiagramAndText);
-            document.add(paragraph);
-
-            String[] mids = rocket.getMotorConfigurationIDs();
-
-            List<Double> stages = getStageWeights(rocket);
-
-            for (int j = 0; j < mids.length; j++) {
-                String mid = mids[j];
-                if (mid != null) {
-                    final List<Motor> motorList = getMotorList(rocket, mid);
-
-                    PdfPTable parent = new PdfPTable(2);
-                    parent.setWidthPercentage(100);
-                    parent.setHorizontalAlignment(Element.ALIGN_LEFT);
-                    parent.setSpacingBefore(0);
-                    parent.setWidths(new int[]{1, 3});
-                    int leading = 0;
-                    //The first motor config is always null.  Skip it and the top-most motor, then set the leading.
-                    if (j > 1) {
-                        leading = 25;
-                    }
-                    addFlightData(rocket, mid, parent, leading);
-                    addMotorData(motorList, parent, stages);
-                    document.add(parent);
-                }
-            }
-        }
-        catch (DocumentException e) {
-            log.error("Could not modify document.", e);
-        }
-    }
-
-    /**
-     * Get the motor list for all motor mounts.
-     *
-     * @param theRocket the rocket object
-     * @param theMid    the motor id
-     *
-     * @return a list of Motor
-     */
-    private List<Motor> getMotorList (final Rocket theRocket, final String theMid) {
-        Iterator<RocketComponent> components = theRocket.deepIterator();
-        final List<Motor> motorList = new ArrayList<Motor>();
-        while (components.hasNext()) {
-            RocketComponent rocketComponent = components.next();
-            if (rocketComponent instanceof MotorMount) {
-                MotorMount mm = (MotorMount) rocketComponent;
-                final Motor motor = mm.getMotor(theMid);
-                if (motor != null) {
-                    motorList.add(motor);
-                }
-            }
-        }
-        return motorList;
-    }
-
-    /**
-     * Paint a diagram of the rocket into the PDF document.
-     *
-     * @param thePageImageableWidth  the number of points in the width of the page available for drawing
-     * @param thePageImageableHeight the number of points in the height of the page available for drawing
-     * @param theCanvas              the direct byte writer
-     * @param theFigure              the print figure
-     * @param theCp                  the center of pressure figure element
-     * @param theCg                  the center of gravity figure element
-     *
-     * @return the scale of the diagram
-     */
-    private double paintRocketDiagram (final int thePageImageableWidth, final int thePageImageableHeight,
-                                       final PdfContentByte theCanvas, final PrintFigure theFigure,
-                                       final FigureElement theCp, final FigureElement theCg) {
-        theFigure.clearAbsoluteExtra();
-        theFigure.clearRelativeExtra();
-        theFigure.addRelativeExtra(theCp);
-        theFigure.addRelativeExtra(theCg);
-        theFigure.updateFigure();
-
-        double scale =
-                (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
-
-        theFigure.setScale(scale);
-        /*
-         * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
-         */
-        theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
-        theFigure.updateFigure();
-
-
-        final DefaultFontMapper mapper = new DefaultFontMapper();
-        Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
-        g2d.translate(20, 120);
-
-        g2d.scale(0.4d, 0.4d);
-        theFigure.paint(g2d);
-        g2d.dispose();
-        return scale;
-    }
-
-    /**
-     * Add the motor data for a motor configuration to the table.
-     *
-     * @param motors       a motor configuration's list of motors
-     * @param parent       the parent to which the motor data will be added
-     * @param stageWeights the stageWeights of each stage, in order
-     */
-    private void addMotorData (List<Motor> motors, final PdfPTable parent, List<Double> stageWeights) {
-
-        PdfPTable motorTable = new PdfPTable(8);
-        motorTable.setWidthPercentage(68);
-        motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
-
-        final PdfPCell motorCell = ITextHelper.createCell(MOTOR, PdfPCell.BOTTOM);
-        final int mPad = 10;
-        motorCell.setPaddingLeft(mPad);
-        motorTable.addCell(motorCell);
-        motorTable.addCell(ITextHelper.createCell(AVG_THRUST, PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell(BURN_TIME, PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell(MAX_THRUST, PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell(TOTAL_IMPULSE, PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell(THRUST_TO_WT, PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell(PROPELLANT_WT, PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell(SIZE, PdfPCell.BOTTOM));
-
-        DecimalFormat df = new DecimalFormat("#,##0.0#");
-        for (int i = 0; i < motors.size(); i++) {
-            int border = Rectangle.BOTTOM;
-            if (i == motors.size() - 1) {
-                border = Rectangle.NO_BORDER;
-            }
-            Motor motor = motors.get(i);
-            double motorWeight = (motor.getLaunchCG().weight - motor.getEmptyCG().weight) * 1000;  //convert to grams
-
-            final PdfPCell motorVCell = ITextHelper.createCell(motor.getDesignation(), border);
-            motorVCell.setPaddingLeft(mPad);
-            motorTable.addCell(motorVCell);
-            motorTable.addCell(ITextHelper.createCell(df.format(motor.getAverageThrustEstimate()) + " " + UnitGroup
-                    .UNITS_FORCE
-                    .getDefaultUnit().toString(), border));
-            motorTable.addCell(ITextHelper.createCell(df.format(motor.getBurnTimeEstimate()) + " " + UnitGroup
-                    .UNITS_FLIGHT_TIME
-                    .getDefaultUnit().toString(), border));
-            motorTable.addCell(ITextHelper.createCell(df.format(motor.getMaxThrustEstimate()) + " " + UnitGroup
-                    .UNITS_FORCE.getDefaultUnit()
-                    .toString(), border));
-            motorTable.addCell(ITextHelper.createCell(df.format(motor.getTotalImpulseEstimate()) + " " + UnitGroup
-                    .UNITS_IMPULSE
-                    .getDefaultUnit().toString(), border));
-            double ttw = motor.getAverageThrustEstimate() / (getStageWeight(stageWeights, i) + (motor
-                    .getLaunchCG().weight * GRAVITY_CONSTANT));
-            motorTable.addCell(ITextHelper.createCell(df.format(ttw) + ":1", border));
-
-            motorTable.addCell(ITextHelper.createCell(df.format(motorWeight) + " " + UnitGroup.UNITS_MASS
-                    .getDefaultUnit().toString(), border));
-
-            final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS
-                    .getDefaultUnit();
-            motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
-                                                      "/" +
-                                                      motorUnit.toString(motor.getLength()) + " " +
-                                                      motorUnit.toString(), border));
-        }
-        PdfPCell c = new PdfPCell(motorTable);
-        c.setBorder(PdfPCell.LEFT);
-        c.setBorderWidthTop(0f);
-        parent.addCell(c);
-    }
-
-
-    /**
-     * Add the motor data for a motor configuration to the table.
-     *
-     * @param theRocket the rocket
-     * @param mid       a motor configuration id
-     * @param parent    the parent to which the motor data will be added
-     * @param leading   the number of points for the leading
-     */
-    private void addFlightData (final Rocket theRocket, final String mid, final PdfPTable parent, int leading) {
-        FlightData flight = null;
-        if (theRocket.getMotorConfigurationIDs().length > 1) {
-            Rocket duplicate = theRocket.copyWithOriginalID();
-            Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
-            simulation.getConditions().setMotorConfigurationID(mid);
-
-            flight = PrintSimulationWorker.doit(simulation);
-
-            if (flight != null) {
-                try {
-                    final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
-                    final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
-                    final Unit flightUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
-
-                    PdfPTable labelTable = new PdfPTable(2);
-                    labelTable.setWidths(new int[]{3, 2});
-                    final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
-                            theRocket.getMotorConfigurationNameOrDescription(mid)), PrintUtilities.BOLD);
-                    chunk.setLeading(leading);
-                    chunk.setSpacingAfter(3f);
-
-                    document.add(chunk);
-
-                    DecimalFormat df = new DecimalFormat("#,##0.0#");
-
-                    final PdfPCell cell = ITextHelper.createCell(ALTITUDE, 2, 2);
-                    cell.setUseBorderPadding(false);
-                    cell.setBorderWidthTop(0f);
-                    labelTable.addCell(cell);
-                    labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxAltitude()) + " " + distanceUnit,
-                                                              2, 2));
-
-                    labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 2, 2));
-                    labelTable.addCell(ITextHelper.createCell(df.format(flight.getFlightTime()) + " " + flightUnit, 2,
-                                                              2));
-
-                    labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
-                    labelTable.addCell(ITextHelper.createCell(df.format(flight.getTimeToApogee()) + " " + flightUnit, 2,
-                                                              2));
-
-                    labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
-                    labelTable.addCell(ITextHelper.createCell(df.format(
-                            flight.getLaunchRodVelocity()) + " " + velocityUnit, 2, 2));
-
-                    labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2));
-                    labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxVelocity()) + " " + velocityUnit,
-                                                              2, 2));
-
-                    labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2));
-                    labelTable.addCell(ITextHelper.createCell(df.format(
-                            flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
-
-                    //Add the table to the parent; have to wrap it in a cell
-                    PdfPCell c = new PdfPCell(labelTable);
-                    c.setBorder(PdfPCell.RIGHT);
-                    c.setBorderWidthTop(0);
-                    c.setTop(0);
-                    parent.addCell(c);
-                }
-                catch (DocumentException e) {
-                    log.error("Could not add flight data to document.", e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Strip [] brackets from a string.
-     *
-     * @param target the original string
-     *
-     * @return target with [] removed
-     */
-    private String stripBrackets (String target) {
-        return stripLeftBracket(stripRightBracket(target));
-    }
-
-    /**
-     * Strip [ from a string.
-     *
-     * @param target the original string
-     *
-     * @return target with [ removed
-     */
-    private String stripLeftBracket (String target) {
-        return target.replace("[", "");
-    }
-
-    /**
-     * Strip ] from a string.
-     *
-     * @param target the original string
-     *
-     * @return target with ] removed
-     */
-    private String stripRightBracket (String target) {
-        return target.replace("]", "");
-    }
-
-    /**
-     * From a list of Stages get the stage masses and convert to weight.
-     *
-     * @param rocket the rocket
-     *
-     * @return a sorted list of Stage weights (mass * gravity), in Newtons
-     */
-    private List<Double> getStageWeights (Rocket rocket) {
-        List<Double> stages = getStageMasses(rocket);
-
-        for (int i = 0; i < stages.size(); i++) {
-            Double stage = stages.get(i);
-            stages.set(i, stage * GRAVITY_CONSTANT);
-        }
-        return stages;
-    }
-
-    /**
-     * From a list of Stages get the stage masses.
-     *
-     * @param rocket  the rocket
-     *
-     * @return a sorted list of Stage masses
-     */
-    private List<Double> getStageMasses (final Rocket rocket) {
-        Double mass = 0d;
-
-        List<Double> stages = new ArrayList<Double>();
-        Iterator<RocketComponent> iter = rocket.deepIterator();
-        while (iter.hasNext()) {
-            RocketComponent rocketComponent = iter.next();
-            if (rocketComponent instanceof Stage) {
-                if (mass > 0d) {
-                    stages.add(mass);
-                    mass = 0d;
-                }
-            }
-            else {
-                mass += rocketComponent.getMass();
-            }
-        }
-        if (mass > 0d) {
-            stages.add(mass);
-        }
-        return stages;
-    }
-
-    /**
-     * Compute the total stage weight from a list of stage weights.  This sums up the weight of the given stage plus all
-     * stages that sit atop it (depend upon it for thrust).
-     *
-     * @param weights the list of stage weights, in Newtons
-     * @param stage   a stage number, 0 being topmost stage
-     *
-     * @return the total weight of the stage and all stages sitting atop the given stage, in Newtons
-     */
-    private double getStageWeight (List<Double> weights, int stage) {
-
-        double result = 0d;
-        for (int i = 0; i <= stage; i++) {
-            result += weights.get(i);
-        }
-        return result;
-    }
+       
+       /**
+        * The logger.
+        */
+       private static final LogHelper log = Application.getLogger();
+       
+       /**
+        * The OR Document.
+        */
+       private OpenRocketDocument rocketDocument;
+       
+       /**
+        * A panel used for rendering of the design diagram.
+        */
+       final RocketPanel panel;
+       
+       /**
+        * The iText document.
+        */
+       protected Document document;
+       
+       /** The displayed strings. */
+       private static final String STAGES = "Stages: ";
+       private static final String MASS_WITH_MOTORS = "Mass (with motors): ";
+       private static final String MASS_WITH_MOTOR = "Mass (with motor): ";
+       private static final String MASS_EMPTY = "Mass (Empty): ";
+       private static final String STABILITY = "Stability: ";
+       private static final String CG = "CG: ";
+       private static final String CP = "CP: ";
+       private static final String MOTOR = "Motor";
+       private static final String AVG_THRUST = "Avg Thrust";
+       private static final String BURN_TIME = "Burn Time";
+       private static final String MAX_THRUST = "Max Thrust";
+       private static final String TOTAL_IMPULSE = "Total Impulse";
+       private static final String THRUST_TO_WT = "Thrust to Wt";
+       private static final String PROPELLANT_WT = "Propellant Wt";
+       private static final String SIZE = "Size";
+       private static final String ALTITUDE = "Altitude";
+       private static final String FLIGHT_TIME = "Flight Time";
+       private static final String TIME_TO_APOGEE = "Time to Apogee";
+       private static final String VELOCITY_OFF_PAD = "Velocity off Pad";
+       private static final String MAX_VELOCITY = "Max Velocity";
+       private static final String LANDING_VELOCITY = "Landing Velocity";
+       private static final String ROCKET_DESIGN = "Rocket Design";
+       private static final double GRAVITY_CONSTANT = 9.80665d;
+       
+       /**
+        * Constructor.
+        *
+        * @param theRocDoc the OR document
+        * @param theIDoc   the iText document
+        */
+       public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc) {
+               document = theIDoc;
+               rocketDocument = theRocDoc;
+               panel = new RocketPanel(rocketDocument);
+       }
+       
+       /**
+        * Main entry point.  Prints the rocket drawing and design data.
+        *
+        * @param writer a direct byte writer
+        */
+       public void writeToDocument(PdfWriter writer) {
+               if (writer == null) {
+                       return;
+               }
+               com.itextpdf.text.Rectangle pageSize = document.getPageSize();
+               int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
+               int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
+               
+               PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
+               
+               Rocket rocket = rocketDocument.getRocket();
+               final Configuration configuration = rocket.getDefaultConfiguration();
+               configuration.setAllStages();
+               PdfContentByte canvas = writer.getDirectContent();
+               
+               final PrintFigure figure = new PrintFigure(configuration);
+               
+               FigureElement cp = panel.getExtraCP();
+               FigureElement cg = panel.getExtraCG();
+               RocketInfo text = panel.getExtraText();
+               
+               double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
+               
+               canvas.beginText();
+               try {
+                       canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
+                                                                                                               BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
+               } catch (DocumentException e) {
+                       log.error("Could not set font.", e);
+               } catch (IOException e) {
+                       log.error("Could not create font.", e);
+               }
+               int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
+                               .toPoints(1)));
+               final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts);
+               canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
+               canvas.moveTextWithLeading(0, -16);
+               
+               float initialY = canvas.getYTLM();
+               
+               canvas.showText(rocketDocument.getRocket().getName());
+               
+               canvas.newlineShowText(STAGES);
+               canvas.showText("" + rocket.getStageCount());
+               
+
+               if (configuration.hasMotors()) {
+                       if (configuration.getStageCount() > 1) {
+                               canvas.newlineShowText(MASS_WITH_MOTORS);
+                       } else {
+                               canvas.newlineShowText(MASS_WITH_MOTOR);
+                       }
+               } else {
+                       canvas.newlineShowText(MASS_EMPTY);
+               }
+               canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
+               
+               canvas.newlineShowText(STABILITY);
+               canvas.showText(text.getStability());
+               
+               canvas.newlineShowText(CG);
+               canvas.showText(text.getCg());
+               
+               canvas.newlineShowText(CP);
+               canvas.showText(text.getCp());
+               canvas.endText();
+               
+               try {
+                       //Move the internal pointer of the document below that of what was just written using the direct byte buffer.
+                       Paragraph paragraph = new Paragraph();
+                       float finalY = canvas.getYTLM();
+                       int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
+                       
+                       paragraph.setSpacingAfter(heightOfDiagramAndText);
+                       document.add(paragraph);
+                       
+                       String[] motorIds = rocket.getMotorConfigurationIDs();
+                       
+                       List<Double> stageMasses = getStageMasses(rocket);
+                       
+                       for (int j = 0; j < motorIds.length; j++) {
+                               String motorId = motorIds[j];
+                               if (motorId != null) {
+                                       PdfPTable parent = new PdfPTable(2);
+                                       parent.setWidthPercentage(100);
+                                       parent.setHorizontalAlignment(Element.ALIGN_LEFT);
+                                       parent.setSpacingBefore(0);
+                                       parent.setWidths(new int[] { 1, 3 });
+                                       int leading = 0;
+                                       //The first motor config is always null.  Skip it and the top-most motor, then set the leading.
+                                       if (j > 1) {
+                                               leading = 25;
+                                       }
+                                       addFlightData(rocket, motorId, parent, leading);
+                                       addMotorData(rocket, motorId, parent);
+                                       document.add(parent);
+                               }
+                       }
+               } catch (DocumentException e) {
+                       log.error("Could not modify document.", e);
+               }
+       }
+       
+       /**
+        * Get the motor list for all motor mounts.
+        *
+        * @param theRocket the rocket object
+        * @param theMid    the motor id
+        *
+        * @return a list of Motor
+        */
+       private List<Motor> getMotorList(final Rocket theRocket, final String theMid) {
+               Iterator<RocketComponent> components = theRocket.iterator();
+               final List<Motor> motorList = new ArrayList<Motor>();
+               while (components.hasNext()) {
+                       RocketComponent rocketComponent = components.next();
+                       if (rocketComponent instanceof MotorMount) {
+                               MotorMount mm = (MotorMount) rocketComponent;
+                               final Motor motor = mm.getMotor(theMid);
+                               if (motor != null) {
+                                       motorList.add(motor);
+                               }
+                       }
+               }
+               return motorList;
+       }
+       
+       /**
+        * Paint a diagram of the rocket into the PDF document.
+        *
+        * @param thePageImageableWidth  the number of points in the width of the page available for drawing
+        * @param thePageImageableHeight the number of points in the height of the page available for drawing
+        * @param theCanvas              the direct byte writer
+        * @param theFigure              the print figure
+        * @param theCp                  the center of pressure figure element
+        * @param theCg                  the center of gravity figure element
+        *
+        * @return the scale of the diagram
+        */
+       private double paintRocketDiagram(final int thePageImageableWidth, final int thePageImageableHeight,
+                                                                               final PdfContentByte theCanvas, final PrintFigure theFigure,
+                                                                               final FigureElement theCp, final FigureElement theCg) {
+               theFigure.clearAbsoluteExtra();
+               theFigure.clearRelativeExtra();
+               theFigure.addRelativeExtra(theCp);
+               theFigure.addRelativeExtra(theCg);
+               theFigure.updateFigure();
+               
+               double scale =
+                               (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
+               
+               theFigure.setScale(scale);
+               /*
+                * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
+                */
+               theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
+               theFigure.updateFigure();
+               
+
+               final DefaultFontMapper mapper = new DefaultFontMapper();
+               Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
+               g2d.translate(20, 120);
+               
+               g2d.scale(0.4d, 0.4d);
+               theFigure.paint(g2d);
+               g2d.dispose();
+               return scale;
+       }
+       
+       /**
+        * Add the motor data for a motor configuration to the table.
+        *
+        * @param rocket        the rocket
+        * @param motorId       the motor ID to output
+        * @param parent        the parent to which the motor data will be added
+        */
+       private void addMotorData(Rocket rocket, String motorId, final PdfPTable parent) {
+               
+               PdfPTable motorTable = new PdfPTable(8);
+               motorTable.setWidthPercentage(68);
+               motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
+               
+               final PdfPCell motorCell = ITextHelper.createCell(MOTOR, PdfPCell.BOTTOM);
+               final int mPad = 10;
+               motorCell.setPaddingLeft(mPad);
+               motorTable.addCell(motorCell);
+               motorTable.addCell(ITextHelper.createCell(AVG_THRUST, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(BURN_TIME, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(MAX_THRUST, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(TOTAL_IMPULSE, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(THRUST_TO_WT, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(PROPELLANT_WT, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(SIZE, PdfPCell.BOTTOM));
+               
+               DecimalFormat ttwFormat = new DecimalFormat("0.00");
+               
+               MassCalculator massCalc = new BasicMassCalculator();
+               
+               Configuration config = new Configuration(rocket);
+               config.setMotorConfigurationID(motorId);
+               
+               int totalMotorCount = 0;
+               double totalPropMass = 0;
+               double totalImpulse = 0;
+               double totalTTW = 0;
+               
+               int stage = 0;
+               double stageMass = 0;
+               
+               boolean topBorder = false;
+               for (RocketComponent c : rocket) {
+                       
+                       if (c instanceof Stage) {
+                               config.setToStage(stage);
+                               stage++;
+                               stageMass = massCalc.getCG(config, MassCalcType.LAUNCH_MASS).weight;
+                               // Calculate total thrust-to-weight from only lowest stage motors
+                               totalTTW = 0;
+                               topBorder = true;
+                       }
+                       
+                       if (c instanceof MotorMount && ((MotorMount) c).isMotorMount()) {
+                               MotorMount mount = (MotorMount) c;
+                               
+                               if (mount.isMotorMount() && mount.getMotor(motorId) != null) {
+                                       Motor motor = mount.getMotor(motorId);
+                                       int motorCount = c.toAbsolute(Coordinate.NUL).length;
+                                       
+
+                                       int border = Rectangle.NO_BORDER;
+                                       if (topBorder) {
+                                               border = Rectangle.TOP;
+                                               topBorder = false;
+                                       }
+                                       
+                                       String name = motor.getDesignation();
+                                       if (motorCount > 1) {
+                                               name += " (" + Chars.TIMES + motorCount + ")";
+                                       }
+                                       
+                                       final PdfPCell motorVCell = ITextHelper.createCell(name, border);
+                                       motorVCell.setPaddingLeft(mPad);
+                                       motorTable.addCell(motorVCell);
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getAverageThrustEstimate()), border));
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(motor.getBurnTimeEstimate()), border));
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getMaxThrustEstimate()), border));
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(motor.getTotalImpulseEstimate()), border));
+                                       
+                                       double ttw = motor.getAverageThrustEstimate() / (stageMass * GRAVITY_CONSTANT);
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       ttwFormat.format(ttw) + ":1", border));
+                                       
+                                       double propMass = (motor.getLaunchCG().weight - motor.getEmptyCG().weight);
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(propMass), border));
+                                       
+                                       final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit();
+                                       motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
+                                                                                                                               "/" +
+                                                                                                                               motorUnit.toString(motor.getLength()) + " " +
+                                                                                                                               motorUnit.toString(), border));
+                                       
+                                       // Sum up total count
+                                       totalMotorCount += motorCount;
+                                       totalPropMass += propMass * motorCount;
+                                       totalImpulse += motor.getTotalImpulseEstimate() * motorCount;
+                                       totalTTW += ttw * motorCount;
+                               }
+                       }
+               }
+               
+               if (totalMotorCount > 1) {
+                       int border = Rectangle.TOP;
+                       final PdfPCell motorVCell = ITextHelper.createCell("Total:", border);
+                       motorVCell.setPaddingLeft(mPad);
+                       motorTable.addCell(motorVCell);
+                       motorTable.addCell(ITextHelper.createCell("", border));
+                       motorTable.addCell(ITextHelper.createCell("", border));
+                       motorTable.addCell(ITextHelper.createCell("", border));
+                       motorTable.addCell(ITextHelper.createCell(
+                                               UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(totalImpulse), border));
+                       motorTable.addCell(ITextHelper.createCell(
+                                       ttwFormat.format(totalTTW) + ":1", border));
+                       motorTable.addCell(ITextHelper.createCell(
+                                               UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(totalPropMass), border));
+                       motorTable.addCell(ITextHelper.createCell("", border));
+                       
+               }
+               
+               PdfPCell c = new PdfPCell(motorTable);
+               c.setBorder(PdfPCell.LEFT);
+               c.setBorderWidthTop(0f);
+               parent.addCell(c);
+       }
+       
+       
+       /**
+        * Add the motor data for a motor configuration to the table.
+        *
+        * @param theRocket the rocket
+        * @param motorId   a motor configuration id
+        * @param parent    the parent to which the motor data will be added
+        * @param leading   the number of points for the leading
+        */
+       private void addFlightData(final Rocket theRocket, final String motorId, final PdfPTable parent, int leading) {
+               
+               // Perform flight simulation
+               Rocket duplicate = theRocket.copyWithOriginalID();
+               FlightData flight = null;
+               try {
+                       Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
+                       simulation.getConditions().setMotorConfigurationID(motorId);
+                       simulation.simulate();
+                       flight = simulation.getSimulatedData();
+               } catch (SimulationException e1) {
+                       // Ignore
+               }
+               
+               // Output the flight data
+               if (flight != null) {
+                       try {
+                               final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
+                               final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
+                               final Unit flightTimeUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
+                               
+                               PdfPTable labelTable = new PdfPTable(2);
+                               labelTable.setWidths(new int[] { 3, 2 });
+                               final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
+                                                       theRocket.getMotorConfigurationNameOrDescription(motorId)), PrintUtilities.BOLD);
+                               chunk.setLeading(leading);
+                               chunk.setSpacingAfter(3f);
+                               
+                               document.add(chunk);
+                               
+                               final PdfPCell cell = ITextHelper.createCell(ALTITUDE, 2, 2);
+                               cell.setUseBorderPadding(false);
+                               cell.setBorderWidthTop(0f);
+                               labelTable.addCell(cell);
+                               labelTable.addCell(ITextHelper.createCell(distanceUnit.toStringUnit(flight.getMaxAltitude()), 2, 2));
+                               
+                               labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 2, 2));
+                               labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getFlightTime()), 2, 2));
+                               
+                               labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
+                               labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2));
+                               
+                               labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
+                               labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));
+                               
+                               labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2));
+                               labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getMaxVelocity()), 2, 2));
+                               
+                               labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2));
+                               labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getGroundHitVelocity()), 2, 2));
+                               
+                               //Add the table to the parent; have to wrap it in a cell
+                               PdfPCell c = new PdfPCell(labelTable);
+                               c.setBorder(PdfPCell.RIGHT);
+                               c.setBorderWidthTop(0);
+                               c.setTop(0);
+                               parent.addCell(c);
+                       } catch (DocumentException e) {
+                               log.error("Could not add flight data to document.", e);
+                       }
+               }
+       }
+       
+       /**
+        * Strip [] brackets from a string.
+        *
+        * @param target the original string
+        *
+        * @return target with [] removed
+        */
+       private String stripBrackets(String target) {
+               return stripLeftBracket(stripRightBracket(target));
+       }
+       
+       /**
+        * Strip [ from a string.
+        *
+        * @param target the original string
+        *
+        * @return target with [ removed
+        */
+       private String stripLeftBracket(String target) {
+               return target.replace("[", "");
+       }
+       
+       /**
+        * Strip ] from a string.
+        *
+        * @param target the original string
+        *
+        * @return target with ] removed
+        */
+       private String stripRightBracket(String target) {
+               return target.replace("]", "");
+       }
+       
+       
+       /**
+        * Return a list of cumulative stage masses.  The latter masses include the mass
+        * of the upper stages as well.
+        *
+        * @param rocket        the rocket
+        * @return                      a list containing the cumulative stage masses
+        */
+       private List<Double> getStageMasses(final Rocket rocket) {
+               List<Double> masses = new ArrayList<Double>();
+               int stages = rocket.getStageCount();
+               
+               Configuration config = new Configuration(rocket);
+               MassCalculator calc = new BasicMassCalculator();
+               
+               for (int i = 0; i < stages; i++) {
+                       config.setToStage(i);
+                       masses.add(calc.getCG(config, MassCalcType.NO_MOTORS).weight);
+               }
+               
+               return masses;
+       }
+       
 }
index b0b31b35e70202a308e83a0e6ffd309a97bef25b..53a710c8003f0a207a388c0b9c38ca089d2d6b22 100644 (file)
@@ -12,38 +12,39 @@ import net.sf.openrocket.simulation.FlightData;
  * finished.  The worker can be cancelled if necessary.
  */
 public class PrintSimulationWorker {
-
-    public static FlightData doit (Simulation sim) {
-        return new InnerPrintSimulationWorker(sim).doit();
-    }
-
-    static class InnerPrintSimulationWorker extends SimulationWorker {
-
-        public InnerPrintSimulationWorker (Simulation sim) {
-            super(sim);
-        }
-
-        public FlightData doit() {
-            return doInBackground();
-        }
-        @Override
-        protected void simulationDone () {
-            // Do nothing if cancelled
-            if (isCancelled()) {
-                return;
-            }
-
-            simulation.getSimulatedData();
-        }
-
-        
-        /**
-         * Called if the simulation is interrupted due to an exception.
-         *
-         * @param t the Throwable that caused the interruption
-         */
-        @Override
-        protected void simulationInterrupted (final Throwable t) {
-        }
-    }
+       
+       public static FlightData doit(Simulation sim) {
+               return new InnerPrintSimulationWorker(sim).doit();
+       }
+       
+       static class InnerPrintSimulationWorker extends SimulationWorker {
+               
+               public InnerPrintSimulationWorker(Simulation sim) {
+                       super(sim);
+               }
+               
+               public FlightData doit() {
+                       return doInBackground();
+               }
+               
+               @Override
+               protected void simulationDone() {
+                       // Do nothing if cancelled
+                       if (isCancelled()) {
+                               return;
+                       }
+                       
+                       simulation.getSimulatedData();
+               }
+               
+               
+               /**
+                * Called if the simulation is interrupted due to an exception.
+                *
+                * @param t the Throwable that caused the interruption
+                */
+               @Override
+               protected void simulationInterrupted(final Throwable t) {
+               }
+       }
 }
\ No newline at end of file
diff --git a/src/net/sf/openrocket/l10n/L10N.java b/src/net/sf/openrocket/l10n/L10N.java
new file mode 100644 (file)
index 0000000..878b3c4
--- /dev/null
@@ -0,0 +1,57 @@
+package net.sf.openrocket.l10n;
+
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**
+ * Helper methods for localization needs.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public final class L10N {
+       
+       private L10N() {
+               // Prevent instantiation
+       }
+       
+       
+       /**
+        * Replace a text token by a replacement value.
+        * <p>
+        * A text token is a string portion that should be surrounded by
+        * braces, "{text}".
+        * 
+        * @param original              the original string.
+        * @param token                 the text token to replace.
+        * @param replacement   the replacement text.
+        * @return                              the modified string.
+        */
+       public static String replace(String original, String token, String replacement) {
+               return Pattern.compile(token, Pattern.LITERAL).matcher(original).replaceAll(replacement);
+       }
+       
+       
+       /**
+        * Convert a language code into a Locale.
+        * 
+        * @param langcode      the language code (<code>null</code> ok).
+        * @return                      the corresponding locale (or <code>null</code> if the input was <code>null</code>)
+        */
+       public static Locale toLocale(String langcode) {
+               if (langcode == null) {
+                       return null;
+               }
+               
+               Locale l;
+               String[] split = langcode.split("[_-]", 3);
+               if (split.length == 1) {
+                       l = new Locale(split[0]);
+               } else if (split.length == 2) {
+                       l = new Locale(split[0], split[1]);
+               } else {
+                       l = new Locale(split[0], split[1], split[2]);
+               }
+               return l;
+       }
+       
+}
index f1524e0407a17c0d26f6c33c5e6fac58b1ef5462..0142368d8f684e235ca807b471b05b233107374f 100644 (file)
@@ -24,7 +24,7 @@ import net.sf.openrocket.util.Prefs;
 public class StabilityDomain implements SimulationDomain {
        
        /*
-        * FIXME:  Should this rather inspect stability during flight
+        * TODO: HIGH:  Should this rather inspect stability during flight
         */
 
        private final double limit;
index 2dadf6f56dd0ba7e58f3ea1f634d2d87c0bb1402..0fa9497c75750e7ec949e0f8d6a485da99eb96e4 100644 (file)
@@ -7,70 +7,72 @@ import net.sf.openrocket.util.MathUtil;
 
 public class EllipticalFinSet extends FinSet {
        private static final Translator trans = Application.getTranslator();
-
-       public static final int POINTS = 21;
-
+       
+       private static final int POINTS = 31;
+       
+       // Static positioning for the fin points
        private static final double[] POINT_X = new double[POINTS];
        private static final double[] POINT_Y = new double[POINTS];
        static {
-               for (int i=0; i < POINTS; i++) {
-                       double a = Math.PI * (POINTS-1-i)/(POINTS-1);
-                       POINT_X[i] = (Math.cos(a)+1)/2;
+               for (int i = 0; i < POINTS; i++) {
+                       double a = Math.PI * (POINTS - 1 - i) / (POINTS - 1);
+                       POINT_X[i] = (Math.cos(a) + 1) / 2;
                        POINT_Y[i] = Math.sin(a);
                }
                POINT_X[0] = 0;
                POINT_Y[0] = 0;
-               POINT_X[POINTS-1] = 1;
-               POINT_Y[POINTS-1] = 0;
+               POINT_X[POINTS - 1] = 1;
+               POINT_Y[POINTS - 1] = 0;
        }
-
+       
 
        private double height = 0.05;
-
+       
        public EllipticalFinSet() {
                this.length = 0.05;
        }
-
-
+       
+       
        @Override
        public Coordinate[] getFinPoints() {
+               double len = MathUtil.max(length, 0.0001);
                Coordinate[] points = new Coordinate[POINTS];
-               for (int i=0; i < POINTS; i++) {
-                       points[i] = new Coordinate(POINT_X[i]*length, POINT_Y[i]*height);
+               for (int i = 0; i < POINTS; i++) {
+                       points[i] = new Coordinate(POINT_X[i] * len, POINT_Y[i] * height);
                }
                return points;
        }
-
+       
        @Override
        public double getSpan() {
                return height;
        }
-
+       
        @Override
        public String getComponentName() {
                //// Elliptical fin set
                return trans.get("EllipticalFinSet.Ellipticalfinset");
        }
-
-
+       
+       
        public double getHeight() {
                return height;
        }
-
+       
        public void setHeight(double height) {
                if (MathUtil.equals(this.height, height))
                        return;
                this.height = height;
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-
-
+       
+       
        public void setLength(double length) {
                if (MathUtil.equals(this.length, length))
                        return;
                this.length = length;
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-
+       
 
 }
index e388b4a6f00579dc397b8e8b8c97a47a8e704732..d628478a763bba342620e2b9d133ad18d6bd7b23 100644 (file)
@@ -15,8 +15,6 @@ import net.sf.openrocket.util.Transformation;
 public abstract class FinSet extends ExternalComponent {
        private static final Translator trans = Application.getTranslator();
        
-       // FIXME:  converting triangular fins to freeform fails
-       
        /**
         * Maximum allowed cant of fins.
         */
index 288726b1fd38c344405f0d09fe4a274074d260b2..a852b2fa054f9eb7e84aa8c0048f054390380650 100644 (file)
@@ -1,8 +1,12 @@
 package net.sf.openrocket.rocketcomponent;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
 
 /**
  * A set of trapezoidal fins.  The root and tip chords are perpendicular to the rocket
@@ -13,9 +17,9 @@ import net.sf.openrocket.util.Coordinate;
 
 public class TrapezoidFinSet extends FinSet {
        private static final Translator trans = Application.getTranslator();
-
-       public static final double MAX_SWEEP_ANGLE=(89*Math.PI/180.0);
-
+       
+       public static final double MAX_SWEEP_ANGLE = (89 * Math.PI / 180.0);
+       
        /*
         *           sweep   tipChord
         *           |    |___________
@@ -32,64 +36,67 @@ public class TrapezoidFinSet extends FinSet {
        private double tipChord = 0;
        private double height = 0;
        private double sweep = 0;
-
-
+       
+       
        public TrapezoidFinSet() {
-               this (3, 0.05, 0.05, 0.025, 0.05);
+               this(3, 0.05, 0.05, 0.025, 0.05);
        }
-
+       
        // TODO: HIGH:  height=0 -> CP = NaN
        public TrapezoidFinSet(int fins, double rootChord, double tipChord, double sweep,
                        double height) {
                super();
-
+               
                this.setFinCount(fins);
                this.length = rootChord;
                this.tipChord = tipChord;
                this.sweep = sweep;
                this.height = height;
        }
-
-
+       
+       
        public void setFinShape(double rootChord, double tipChord, double sweep, double height,
                        double thickness) {
-               if (this.length==rootChord && this.tipChord==tipChord && this.sweep==sweep &&
-                               this.height==height && this.thickness==thickness)
+               if (this.length == rootChord && this.tipChord == tipChord && this.sweep == sweep &&
+                               this.height == height && this.thickness == thickness)
                        return;
-               this.length=rootChord;
-               this.tipChord=tipChord;
-               this.sweep=sweep;
-               this.height=height;
-               this.thickness=thickness;
+               this.length = rootChord;
+               this.tipChord = tipChord;
+               this.sweep = sweep;
+               this.height = height;
+               this.thickness = thickness;
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-
+       
        public double getRootChord() {
                return length;
        }
+       
        public void setRootChord(double r) {
                if (length == r)
                        return;
-               length = Math.max(r,0);
+               length = Math.max(r, 0);
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-
+       
        public double getTipChord() {
                return tipChord;
        }
+       
        public void setTipChord(double r) {
                if (tipChord == r)
                        return;
-               tipChord = Math.max(r,0);
+               tipChord = Math.max(r, 0);
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-
+       
        /**
         * Get the sweep length.
         */
        public double getSweep() {
                return sweep;
        }
+       
        /**
         * Set the sweep length.
         */
@@ -99,7 +106,7 @@ public class TrapezoidFinSet extends FinSet {
                sweep = r;
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-
+       
        /**
         * Get the sweep angle.  This is calculated from the true sweep and height, and is not
         * stored separetely.
@@ -107,13 +114,14 @@ public class TrapezoidFinSet extends FinSet {
        public double getSweepAngle() {
                if (height == 0) {
                        if (sweep > 0)
-                               return Math.PI/2;
+                               return Math.PI / 2;
                        if (sweep < 0)
-                               return -Math.PI/2;
+                               return -Math.PI / 2;
                        return 0;
                }
-               return Math.atan(sweep/height);
+               return Math.atan(sweep / height);
        }
+       
        /**
         * Sets the sweep by the sweep angle.  The sweep is calculated and set by this method,
         * and the angle itself is not stored.
@@ -128,34 +136,37 @@ public class TrapezoidFinSet extends FinSet {
                        return;
                setSweep(sweep);
        }
-
+       
        public double getHeight() {
                return height;
        }
+       
        public void setHeight(double r) {
                if (height == r)
                        return;
-               height = Math.max(r,0);
+               height = Math.max(r, 0);
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-
-
+       
+       
 
        /**
         * Returns the geometry of a trapezoidal fin.
         */
        @Override
        public Coordinate[] getFinPoints() {
-               Coordinate[] c = new Coordinate[4];
-
-               c[0] = Coordinate.NUL;
-               c[1] = new Coordinate(sweep,height);
-               c[2] = new Coordinate(sweep+tipChord,height);
-               c[3] = new Coordinate(length,0);
-
-               return c;
+               List<Coordinate> list = new ArrayList<Coordinate>(4);
+               
+               list.add(Coordinate.NUL);
+               list.add(new Coordinate(sweep, height));
+               if (tipChord > 0.0001) {
+                       list.add(new Coordinate(sweep + tipChord, height));
+               }
+               list.add(new Coordinate(MathUtil.max(length, 0.0001), 0));
+               
+               return list.toArray(new Coordinate[list.size()]);
        }
-
+       
        /**
         * Returns the span of a trapezoidal fin.
         */
@@ -163,12 +174,12 @@ public class TrapezoidFinSet extends FinSet {
        public double getSpan() {
                return height;
        }
-
-
+       
+       
        @Override
        public String getComponentName() {
                //// Trapezoidal fin set
                return trans.get("TrapezoidFinSet.TrapezoidFinSet");
        }
-
+       
 }
index 334563e8f6f72663a1a966cfec612cb10997a7b0..d0b839112a02ebb9ef605a6ee77e694283fc6429 100644 (file)
@@ -2,6 +2,7 @@ package net.sf.openrocket.simulation;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Random;
 
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
@@ -445,8 +446,8 @@ public class GUISimulationConditions implements ChangeSource, Cloneable {
                conditions.setLaunchLatitude(getLaunchLatitude());
                
                PinkNoiseWindModel windModel = new PinkNoiseWindModel();
-               // FIXME:  Random seed
-               windModel.setSeed(1);
+               // TODO: HIGH: Randomness source for simulation
+               windModel.setSeed(new Random().nextInt());
                windModel.setAverage(getWindSpeedAverage());
                windModel.setStandardDeviation(getWindSpeedDeviation());
                conditions.setWindModel(windModel);
index a4a682207bd6aff973aff91eb54b5391ae1295d6..99fd475e9197ccd1c04704b2269b446a2645bbbb 100644 (file)
@@ -50,8 +50,8 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
        private static final double MIN_TIME_STEP = 0.001;
        
 
-       // FIXME: Random seed
-       private final Random random = new Random(10);
+       // TODO: HIGH: Randomness source from simulation
+       private final Random random = new Random();
        
        
 
index bb8b20f771c452493804f23888207acc372cf4b6..a7847d32b82692e6bbc04ca3fc1d73915e24a7e6 100644 (file)
@@ -89,6 +89,10 @@ public final class Application {
         * @return      a translator.
         */
        public static Translator getTranslator() {
+               if (baseTranslator instanceof DebugTranslator) {
+                       return baseTranslator;
+               }
+               
                Translator t = baseTranslator;
                t = new ClassBasedTranslator(t, 1);
                t = new ExceptionSuppressingTranslator(t);
index f4141f9b057746b22d928ce85da1bb0376cbd05b..a975894ca84daac37fb0ed6de875d2b3402743a6 100644 (file)
@@ -25,9 +25,9 @@ import net.sf.openrocket.file.motor.MotorLoaderHelper;
 import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
 import net.sf.openrocket.gui.main.BasicFrame;
 import net.sf.openrocket.gui.main.ExceptionHandler;
-import net.sf.openrocket.gui.main.SimpleFileFilter;
 import net.sf.openrocket.gui.main.Splash;
 import net.sf.openrocket.l10n.DebugTranslator;
+import net.sf.openrocket.l10n.L10N;
 import net.sf.openrocket.l10n.ResourceBundleTranslator;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.DelegatorLogger;
@@ -39,6 +39,7 @@ import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.motor.ThrustCurveMotor;
 import net.sf.openrocket.util.GUIUtil;
 import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.util.SimpleFileFilter;
 
 
 /**
@@ -123,19 +124,20 @@ public class Startup {
         * Initializes the localization system.
         */
        private static void initializeL10n() {
-               String locale = System.getProperty("openrocket.locale");
-               if (locale != null) {
-                       Locale l;
-                       String[] split = locale.split("[_-]", 3);
-                       if (split.length == 1) {
-                               l = new Locale(split[0]);
-                       } else if (split.length == 2) {
-                               l = new Locale(split[0], split[1]);
-                       } else {
-                               l = new Locale(split[0], split[1], split[2]);
-                       }
+               
+               String langcode = System.getProperty("openrocket.locale");
+               if (langcode != null) {
+                       Locale l = L10N.toLocale(langcode);
                        log.info("Setting custom locale " + l);
                        Locale.setDefault(l);
+               } else {
+                       Locale l = Prefs.getUserLocale();
+                       if (l != null) {
+                               log.info("Setting user-selected locale " + l);
+                               Locale.setDefault(l);
+                       } else {
+                               log.info("Using default locale " + Locale.getDefault());
+                       }
                }
                
                Translator t;
@@ -151,7 +153,6 @@ public class Startup {
        }
        
        
-
        private static void runMain(String[] args) {
                
                // Initialize the splash screen with version info
index 53e19c8038df14310a7aa42267473ca9f72a86dd..4703b31b4481f3ac31c8334085138a5253e960a4 100644 (file)
@@ -7,11 +7,11 @@ import net.sf.openrocket.util.Chars;
 public abstract class Unit {
        
        /** No unit with 2 digit precision */
-       public static final Unit NOUNIT2 = new GeneralUnit(1,""+Chars.ZWSP, 2);
-
-       protected final double multiplier;   // meters = units * multiplier
+       public static final Unit NOUNIT2 = new GeneralUnit(1, "" + Chars.ZWSP, 2);
+       
+       protected final double multiplier; // meters = units * multiplier
        protected final String unit;
-
+       
        /**
         * Creates a new Unit with a given multiplier and unit name.
         * 
@@ -26,7 +26,7 @@ public abstract class Unit {
                this.multiplier = multiplier;
                this.unit = unit;
        }
-
+       
        /**
         * Converts from SI units to this unit.  The default implementation simply divides by the
         * multiplier.
@@ -35,9 +35,9 @@ public abstract class Unit {
         * @return       Value in these units
         */
        public double toUnit(double value) {
-               return value/multiplier;
+               return value / multiplier;
        }
-
+       
        /**
         * Convert from this type of units to SI units.  The default implementation simply 
         * multiplies by the multiplier.
@@ -46,9 +46,9 @@ public abstract class Unit {
         * @return       Value in SI units
         */
        public double fromUnit(double value) {
-               return value*multiplier;
+               return value * multiplier;
        }
-
+       
        
        /**
         * Return the unit name.
@@ -111,10 +111,12 @@ public abstract class Unit {
                return unit;
        }
        
+       // TODO: Should this use grouping separator ("#,##0.##")?
+       
        private static final DecimalFormat intFormat = new DecimalFormat("#");
        private static final DecimalFormat decFormat = new DecimalFormat("0.##");
        private static final DecimalFormat expFormat = new DecimalFormat("0.00E0");
-
+       
        /**
         * Format the given value (in SI units) to a string representation of the value in this
         * units.  An suitable amount of decimals for the unit are used in the representation.
@@ -125,7 +127,7 @@ public abstract class Unit {
         */
        public String toString(double value) {
                double val = toUnit(value);
-
+               
                if (Math.abs(val) > 1E6) {
                        return expFormat.format(val);
                }
@@ -135,7 +137,7 @@ public abstract class Unit {
                if (Math.abs(val) <= 0.005) {
                        return "0";
                }
-
+               
                double sign = Math.signum(val);
                val = Math.abs(val);
                double mul = 1.0;
@@ -143,7 +145,7 @@ public abstract class Unit {
                        mul *= 10;
                        val *= 10;
                }
-               val = Math.rint(val)/mul * sign;
+               val = Math.rint(val) / mul * sign;
                
                return decFormat.format(val);
        }
@@ -168,7 +170,7 @@ public abstract class Unit {
        }
        
        
-       
+
        /**
         * Creates a new Value object with the specified value and this unit.
         * 
@@ -189,7 +191,7 @@ public abstract class Unit {
         * @return       Rounded value.
         */
        public abstract double round(double value);
-
+       
        /**
         * Return the next rounded value after the given value.
         * @param value  Value in these units.
@@ -224,13 +226,12 @@ public abstract class Unit {
                        return false;
                if (this.getClass() != other.getClass())
                        return false;
-               return ((this.multiplier == ((Unit)other).multiplier) && 
-                               this.unit.equals(((Unit)other).unit));
+               return ((this.multiplier == ((Unit) other).multiplier) && this.unit.equals(((Unit) other).unit));
        }
        
        @Override
        public int hashCode() {
                return this.getClass().hashCode() + this.unit.hashCode();
        }
-
+       
 }
diff --git a/src/net/sf/openrocket/util/FileHelper.java b/src/net/sf/openrocket/util/FileHelper.java
new file mode 100644 (file)
index 0000000..6a8216f
--- /dev/null
@@ -0,0 +1,115 @@
+package net.sf.openrocket.util;
+
+import java.awt.Component;
+import java.io.File;
+import java.io.IOException;
+
+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;
+
+/**
+ * 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: MEDIUM: 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 PDF files (*.pdf) */
+       public static final FileFilter PDF_FILTER =
+                       new SimpleFileFilter(trans.get("filetypes.pdf"), ".pdf");
+       
+       
+
+
+       private FileHelper() {
+               // Prevent instantiation
+       }
+       
+       /**
+        * 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 filen
+        */
+       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;
+       }
+       
+       
+       /**
+        * 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.
+        */
+       public static boolean confirmWrite(File file, Component parent) {
+               if (file.exists()) {
+                       log.info(1, "File " + file + " exists, confirming overwrite from user");
+                       int result = JOptionPane.showConfirmDialog(parent,
+                                       L10N.replace(trans.get("error.fileExists.desc"), "{filename}", file.getName()),
+                                       trans.get("error.fileExists.title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+                       if (result != JOptionPane.YES_OPTION) {
+                               log.user(1, "User decided not to overwrite the file");
+                               return false;
+                       }
+                       log.user(1, "User decided to overwrite the file");
+               }
+               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 9f786ec763b1ad1f2969c6c4a77a33f50ce85c54..7639bcdc7e1bf96769a05177b894cf4dc111d129 100644 (file)
@@ -18,7 +18,7 @@ import net.sf.openrocket.startup.Application;
 public class Icons {
        private static final LogHelper log = Application.getLogger();
        private static final Translator trans = Application.getTranslator();
-
+       
        static {
                log.debug("Starting to load icons");
        }
@@ -60,6 +60,7 @@ public class Icons {
        public static final Icon EDIT_COPY = loadImageIcon("pix/icons/edit-copy.png", "Copy");
        public static final Icon EDIT_PASTE = loadImageIcon("pix/icons/edit-paste.png", "Paste");
        public static final Icon EDIT_DELETE = loadImageIcon("pix/icons/edit-delete.png", "Delete");
+       public static final Icon EDIT_SCALE = loadImageIcon("pix/icons/edit-scale.png", "Scale");
        
        public static final Icon ZOOM_IN = loadImageIcon("pix/icons/zoom-in.png", "Zoom in");
        public static final Icon ZOOM_OUT = loadImageIcon("pix/icons/zoom-out.png", "Zoom out");
diff --git a/src/net/sf/openrocket/util/Named.java b/src/net/sf/openrocket/util/Named.java
new file mode 100644 (file)
index 0000000..b91ae46
--- /dev/null
@@ -0,0 +1,56 @@
+package net.sf.openrocket.util;
+
+import java.text.Collator;
+
+/**
+ * An object holder that provides a custom toString return value.
+ * <p>
+ * The class supports sorting by the name.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ * @param <T>  the holder type
+ */
+public class Named<T> implements Comparable<Named<T>> {
+       
+       private final T object;
+       private final String name;
+       
+       private Collator collator = null;
+       
+       /**
+        * Sole constructor.
+        * 
+        * @param object        the held object
+        * @param name          the value to return by toString().
+        */
+       public Named(T object, String name) {
+               this.object = object;
+               this.name = name;
+       }
+       
+       
+       /**
+        * Get the held object.
+        * 
+        * @return      the object.
+        */
+       public T get() {
+               return object;
+       }
+       
+       @Override
+       public String toString() {
+               return name;
+       }
+       
+       
+       @Override
+       public int compareTo(Named<T> other) {
+               if (collator == null) {
+                       collator = Collator.getInstance();
+               }
+               
+               return collator.compare(this.toString(), other.toString());
+       }
+       
+}
index 047db50c7ae3c1a65599be088552aacb9a87168b..16e338c4a7692597505dc76287a800d986d0d4a1 100644 (file)
@@ -8,9 +8,11 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.MissingResourceException;
 import java.util.Properties;
@@ -23,6 +25,7 @@ import net.sf.openrocket.database.Databases;
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.gui.main.ExceptionHandler;
 import net.sf.openrocket.gui.print.PrintSettings;
+import net.sf.openrocket.l10n.L10N;
 import net.sf.openrocket.l10n.Translator;
 import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.material.Material;
@@ -47,6 +50,16 @@ public class Prefs {
        private static final String SPLIT_CHARACTER = "|";
        
 
+       private static final List<Locale> SUPPORTED_LOCALES;
+       static {
+               List<Locale> list = new ArrayList<Locale>();
+               for (String lang : new String[] { "en", "de", "es", "fr" }) {
+                       list.add(new Locale(lang));
+               }
+               SUPPORTED_LOCALES = Collections.unmodifiableList(list);
+       }
+       
+
        /**
         * Whether to use the debug-node instead of the normal node.
         */
@@ -391,7 +404,24 @@ public class Prefs {
        //////////////////
        
 
-
+       public static List<Locale> getSupportedLocales() {
+               return SUPPORTED_LOCALES;
+       }
+       
+       public static Locale getUserLocale() {
+               String locale = getString("locale", null);
+               return L10N.toLocale(locale);
+       }
+       
+       public static void setUserLocale(Locale l) {
+               if (l == null) {
+                       putString("locale", null);
+               } else {
+                       putString("locale", l.toString());
+               }
+       }
+       
+       
 
        public static boolean getCheckUpdates() {
                return PREFNODE.getBoolean(CHECK_UPDATES, BuildPropertyHolder.DEFAULT_CHECK_UPDATES);
diff --git a/src/net/sf/openrocket/util/SimpleFileFilter.java b/src/net/sf/openrocket/util/SimpleFileFilter.java
new file mode 100644 (file)
index 0000000..008592a
--- /dev/null
@@ -0,0 +1,77 @@
+package net.sf.openrocket.util;
+
+import java.io.File;
+
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * A FileFilter similar to FileNameExtensionFilter except that
+ * it allows multipart extensions (.ork.gz), and also implements
+ * the java.io.FileFilter interface.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class SimpleFileFilter extends FileFilter implements java.io.FileFilter {
+
+       private final String description;
+       private final boolean acceptDir;
+       private final String[] extensions;
+       
+       
+       /**
+        * Create filter that accepts files with the provided extensions that
+        * accepts directories as well.
+        * 
+        * @param description   the description of this file filter.
+        * @param extensions    an array of extensions that match this filter.
+        */
+       public SimpleFileFilter(String description, String ... extensions) {
+               this(description, true, extensions);
+       }
+       
+
+       /**
+        * Create filter that accepts files with the provided extensions.
+        * 
+        * @param description   the description of this file filter.
+        * @param acceptDir             whether to accept directories
+        * @param extensions    an array of extensions that match this filter.
+        */
+       public SimpleFileFilter(String description, boolean acceptDir, String ... extensions) {
+               this.description = description;
+               this.acceptDir = acceptDir;
+               this.extensions = new String[extensions.length];
+               for (int i=0; i<extensions.length; i++) {
+                       String ext = extensions[i].toLowerCase();
+                       if (ext.charAt(0) == '.') {
+                               this.extensions[i] = ext;
+                       } else {
+                               this.extensions[i] = '.' + ext;
+                       }
+               }
+       }
+       
+       
+       @Override
+       public boolean accept(File file) {
+               if (file == null)
+                       return false;
+               if (file.isDirectory())
+                       return acceptDir;
+               
+               String filename = file.getName();
+               filename = filename.toLowerCase();
+               for (String ext: extensions) {
+                       if (filename.endsWith(ext))
+                               return true;
+               }
+               
+               return false;
+       }
+
+       @Override
+       public String getDescription() {
+               return description;
+       }
+
+}
diff --git a/src/net/sf/openrocket/util/Utils.java b/src/net/sf/openrocket/util/Utils.java
new file mode 100644 (file)
index 0000000..39b3e86
--- /dev/null
@@ -0,0 +1,20 @@
+package net.sf.openrocket.util;
+
+public class Utils {
+       
+       /**
+        * Null-safe equals method.
+        * 
+        * @param first         the first object to compare
+        * @param second        the second object to compare
+        * @return                      whether the two objects are both equal or both <code>null</code>
+        */
+       public static boolean equals(Object first, Object second) {
+               if (first == null) {
+                       return second == null;
+               } else {
+                       return first.equals(second);
+               }
+       }
+       
+}
index c864ec96e506616a8762e2621d813a13bd7fdb68..c3452c1c8fc8726b03316a0bd14ef27d88e951bb 100644 (file)
@@ -17,6 +17,7 @@ import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.file.GeneralRocketLoader;
 import net.sf.openrocket.file.RocketLoadException;
 import net.sf.openrocket.file.motor.GeneralMotorLoader;
+import net.sf.openrocket.l10n.ResourceBundleTranslator;
 import net.sf.openrocket.masscalc.BasicMassCalculator;
 import net.sf.openrocket.masscalc.MassCalculator;
 import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
@@ -72,6 +73,7 @@ public class IntegrationTest {
                db.startLoading();
                assertEquals(1, db.getMotorSets().size());
                Application.setMotorSetDatabase(db);
+               Application.setBaseTranslator(new ResourceBundleTranslator("l10n.messages"));
        }
        
        /**