From bf771f9d4b5215b0036435eb19fc8c813f87c79d Mon Sep 17 00:00:00 2001 From: plaa Date: Thu, 21 Jul 2011 18:08:42 +0000 Subject: [PATCH] language selector, bug fixed git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@141 180e2498-e6e9-4542-8430-84ac67f01cd8 --- ChangeLog | 10 + l10n/messages.properties | 30 +- l10n/messages_de.properties | 2 +- l10n/messages_es.properties | 2 +- l10n/messages_fr.properties | 2 +- pix-src/icons/edit-scale.xcf.gz | Bin 0 -> 1499 bytes pix/icons/copyright.txt | 1 + pix/icons/edit-scale.png | Bin 0 -> 636 bytes .../sf/openrocket/document/Simulation.java | 7 +- .../file/motor/MotorLoaderHelper.java | 2 +- .../file/motor/RockSimMotorLoader.java | 4 +- .../gui/dialogs/BugReportDialog.java | 29 +- .../openrocket/gui/dialogs/PrintDialog.java | 67 +- .../openrocket/gui/dialogs/ScaleDialog.java | 8 +- .../preferences/MaterialEditPanel.java | 75 +- .../preferences/PreferencesDialog.java | 75 +- .../sf/openrocket/gui/main/BasicFrame.java | 56 +- .../ComponentTreeTransferHandler.java | 6 + .../sf/openrocket/gui/plot/PlotDialog.java | 19 +- .../sf/openrocket/gui/print/DesignReport.java | 1011 +++++++++-------- .../gui/print/PrintSimulationWorker.java | 69 +- src/net/sf/openrocket/l10n/L10N.java | 57 + .../domains/StabilityDomain.java | 2 +- .../rocketcomponent/EllipticalFinSet.java | 46 +- .../sf/openrocket/rocketcomponent/FinSet.java | 2 - .../rocketcomponent/TrapezoidFinSet.java | 95 +- .../simulation/GUISimulationConditions.java | 5 +- .../simulation/RK4SimulationStepper.java | 4 +- .../sf/openrocket/startup/Application.java | 4 + src/net/sf/openrocket/startup/Startup.java | 27 +- src/net/sf/openrocket/unit/Unit.java | 37 +- src/net/sf/openrocket/util/FileHelper.java | 115 ++ src/net/sf/openrocket/util/Icons.java | 3 +- src/net/sf/openrocket/util/Named.java | 56 + src/net/sf/openrocket/util/Prefs.java | 32 +- .../{gui/main => util}/SimpleFileFilter.java | 2 +- src/net/sf/openrocket/util/Utils.java | 20 + test/net/sf/openrocket/IntegrationTest.java | 2 + 38 files changed, 1178 insertions(+), 806 deletions(-) create mode 100644 pix-src/icons/edit-scale.xcf.gz create mode 100644 pix/icons/edit-scale.png create mode 100644 src/net/sf/openrocket/l10n/L10N.java create mode 100644 src/net/sf/openrocket/util/FileHelper.java create mode 100644 src/net/sf/openrocket/util/Named.java rename src/net/sf/openrocket/{gui/main => util}/SimpleFileFilter.java (98%) create mode 100644 src/net/sf/openrocket/util/Utils.java diff --git a/ChangeLog b/ChangeLog index 774ccf15..13de0125 100644 --- 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 diff --git a/l10n/messages.properties b/l10n/messages.properties index 6f14e49e..9cfea75d 100644 --- a/l10n/messages.properties +++ b/l10n/messages.properties @@ -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 = You can report a bug in OpenRocket by filling in and submitting the form below.
You can also report bugs and include attachments on the project web site. bugreport.reportDialog.txt2 = Please include a short description about what you were doing when the exception occurred. +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 = Editing materials will not affect existing rocket designs. +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 diff --git a/l10n/messages_de.properties b/l10n/messages_de.properties index f3f6bbe1..055f0518 100644 --- a/l10n/messages_de.properties +++ b/l10n/messages_de.properties @@ -191,7 +191,7 @@ matedtpan.but.ttip.delete = Benutzerdefiniertes Material l matedtpan.but.ttip.revertall = Alle benutzerdefinierten Materialien löschen matedtpan.title.Deletealluser-defined = Alle benutzerdefinierten Materialien löschen? matedtpan.title.Revertall = Alle löschen? -matedtpan.lbl.edtmaterials = Das Bearbeiten der Materialien beeinflusst keine bereits existierenden Raketendesigns. +matedtpan.lbl.edtmaterials = Das Bearbeiten der Materialien beeinflusst keine bereits existierenden Raketendesigns. !MaterialModel MaterialModel.title.Material = Material diff --git a/l10n/messages_es.properties b/l10n/messages_es.properties index 7061bc4b..36d368e8 100644 --- a/l10n/messages_es.properties +++ b/l10n/messages_es.properties @@ -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 = Editar materiales que no afectaran los diseños existentes. +matedtpan.lbl.edtmaterials = Editar materiales que no afectaran los diseños existentes. !MaterialModel MaterialModel.title.Material = Material diff --git a/l10n/messages_fr.properties b/l10n/messages_fr.properties index fe61e71e..a4013d08 100644 --- a/l10n/messages_fr.properties +++ b/l10n/messages_fr.properties @@ -186,7 +186,7 @@ matedtpan.but.ttip.delete = Supprimer un mat matedtpan.but.ttip.revertall = Supprimer tous les matériaux personnalisés matedtpan.title.Deletealluser-defined = Effacer tous les matériaux personalisés? matedtpan.title.Revertall = Revenir aux valeurs précédentes? -matedtpan.lbl.edtmaterials = Modifier les matériaux n'affectera pas les projets fusée existants. +matedtpan.lbl.edtmaterials = Modifier les matériaux n'affectera pas les projets fusée existants. !MaterialModel MaterialModel.title.Material = Matériau diff --git a/pix-src/icons/edit-scale.xcf.gz b/pix-src/icons/edit-scale.xcf.gz new file mode 100644 index 0000000000000000000000000000000000000000..b0a1fd07a8c9ddee0e32d67c7d4246d01bdf9e33 GIT binary patch literal 1499 zcmV<11tj_(iwFP!000001Jzc2Y*SSffA{rk>jo%H6aB+9A<-cYJ0$^vf0)Gt^e;t+ zngG&uU01e#&_R-OOcJ0=__O^<1m)U&_4@lM0-eYf`Y9jGeuhtE&62F^|Wg)bd$0K_Z^I3>B{7ON|ruBY7y zf_U`)CUp$oQb004WK+MN&Z*a8C75lPYyY;cu6CWbzvD`K`_hCjfh)L-GnMAvrvO;Oumug$HC-wJFAlIev)p0$LvP3 zBh$MvbIPoF4=t%m$rRZ&YZRlxX&hL|hN3KrP!9!36a|HI79|{T70jVBDIU!^FH#v47YEBJ9mOWlMoLS?6!;URdCFKzu}P?-I0a=;d;(-r ziHvjpM;UJgMC!55iXUl%-BeNbxaWCh3Xemm(vRk>l#@GETHDaGIL++Vi>&~ie3sN7Yd(864%Mem%+#O3kZV1oCDBiVO!WI;E3tz}e+rRR zU^16k-Ode;;wQPI#1r08a>|@BkzP2qXOih$=4>MFB)z@9TwP9G9FgF|TLLRt=%RgaUz)VK7V&^o^8OLm?UpjExO?f!EvH?JcQ> zY{)-4IOOT?AL#D%ShheqFgn=h=>a`-Z|C1V#SS<*;`Ma(c7ZOs`{wm7a|sL#5A=3+ z-vM{%t{WYlMP-ng7#r%p=K&rX#ppNBHLdz|W#LCF3qhgq;ffDdyu{Cc^)<)7y_&tT zZ}xt-HrKG=DlWY_|7GJ9TsnH`@G*GYIDg)65-EjcDDW%;k1b8;q|C6fhetMk7oR z1aHujf$085h|Cc>JUKBz2gW0f`ym&NgeF4)zdtxWMmN^OSOhH$je~J|!Z#XfsDTU} zoEV?<0Uzxj9SSz=L5~nwhGzgn$50aA7uI~WbH|P^ch-Vhp=NhY-6#BKYt}ZNYtglE z=jv8}Z1LjCuUl4IJs5Npn~Qp7m8^mMHT!+|*HH{fS(G>)l8|dZgLFv-xENMORZ8ur zp&Ig1jEOz2Dqs}_o6?xLb^?71NJfPal|+9wVH#6X zJ|u8Dju-wQi1qk;6Cnr}oPx#24YJ`L;(K){{a7>y{D4^00JaQL_t(I%Y~CYYZOrwhM#+9cCx#YL?Y3`q%iRt zKM-uxCTQVrurx%((nb+2>;yY4Yy=C_*@+m1v$l22qCk_wqOoJ#L${H63k&pbVcb`6DW|$etu}fDc z-Y?oeOVv!(RJFNu|M~RqxGC?wg;Y8njHT&6coI3IFUVq9z`|9T{_4O)IGg zs`huA0Y>%I8FaRU&CH;=gw(R*-ov*>&EDLf(Yy7 zO)9AndB@kKU%YtpnaczH7%uzx2HMsJ#5;P6<$=CpPf*yky_-}aC{&|7Q>A4@>jA2p z5-fYQpqWV115rrp9ohzhWyWSvafs4vGl(0G*V;A)X%o;!h81nw9O`88+E!>1G(_4F zMO(jx(~L+Bq)rxBQmw}oOtZVx2O7C(WjdQNX_)AUb8!>_You can report a bug in OpenRocket by filling in and submitting the form below.
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 = - //// Please include a short description about what you were doing when the exception occurred. - new BugReportDialog(parent, trans.get("bugreport.reportDialog.txt2"), sb.toString()); + //// Please include a short description about what you were doing when the exception occurred. + new BugReportDialog(parent, trans.get("bugreport.reportDialog.txt2"), sb.toString(), true); reportDialog.setVisible(true); } diff --git a/src/net/sf/openrocket/gui/dialogs/PrintDialog.java b/src/net/sf/openrocket/gui/dialogs/PrintDialog.java index 123e0aaa..924baace 100644 --- a/src/net/sf/openrocket/gui/dialogs/PrintDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/PrintDialog.java @@ -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 : ""; - } } diff --git a/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java b/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java index d53a1a54..c6ccc239 100644 --- a/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/ScaleDialog.java @@ -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(); diff --git a/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java b/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java index 183a02eb..a106ba32 100644 --- a/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java +++ b/src/net/sf/openrocket/gui/dialogs/preferences/MaterialEditPanel.java @@ -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 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 { //// Editing materials will not affect existing //// rocket designs. - 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 { } } - + } diff --git a/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java b/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java index 65a09aa8..a13be044 100644 --- a/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java @@ -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> locales = new ArrayList>(); + for (Locale l : Prefs.getSupportedLocales()) { + locales.add(new Named(l, l.getDisplayLanguage())); + } + Collections.sort(locales); + locales.add(0, new Named(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 selection = (Named) 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); } diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index b6b74380..a9cbbfc0 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -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/componenttree/ComponentTreeTransferHandler.java b/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java index f225f5e5..b998b83f 100644 --- a/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java +++ b/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeTransferHandler.java @@ -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 { diff --git a/src/net/sf/openrocket/gui/plot/PlotDialog.java b/src/net/sf/openrocket/gui/plot/PlotDialog.java index 714a0530..da775482 100644 --- a/src/net/sf/openrocket/gui/plot/PlotDialog.java +++ b/src/net/sf/openrocket/gui/plot/PlotDialog.java @@ -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 EVENT_COLORS = new HashMap(); @@ -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 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) && diff --git a/src/net/sf/openrocket/gui/print/DesignReport.java b/src/net/sf/openrocket/gui/print/DesignReport.java index 8bc1f0fd..e1311444 100644 --- a/src/net/sf/openrocket/gui/print/DesignReport.java +++ b/src/net/sf/openrocket/gui/print/DesignReport.java @@ -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; /** *
@@ -60,485 +78,494 @@ import java.util.List;
  * 
*/ 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 stages = getStageWeights(rocket); - - for (int j = 0; j < mids.length; j++) { - String mid = mids[j]; - if (mid != null) { - final List 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 getMotorList (final Rocket theRocket, final String theMid) { - Iterator components = theRocket.deepIterator(); - final List motorList = new ArrayList(); - 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 motors, final PdfPTable parent, List 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 getStageWeights (Rocket rocket) { - List 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 getStageMasses (final Rocket rocket) { - Double mass = 0d; - - List stages = new ArrayList(); - Iterator 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 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 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 getMotorList(final Rocket theRocket, final String theMid) { + Iterator components = theRocket.iterator(); + final List motorList = new ArrayList(); + 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 getStageMasses(final Rocket rocket) { + List masses = new ArrayList(); + 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; + } + } diff --git a/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java b/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java index b0b31b35..53a710c8 100644 --- a/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java +++ b/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java @@ -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 index 00000000..878b3c41 --- /dev/null +++ b/src/net/sf/openrocket/l10n/L10N.java @@ -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 + */ +public final class L10N { + + private L10N() { + // Prevent instantiation + } + + + /** + * Replace a text token by a replacement value. + *

+ * 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 (null ok). + * @return the corresponding locale (or null if the input was null) + */ + 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; + } + +} diff --git a/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java b/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java index f1524e04..0142368d 100644 --- a/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java +++ b/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java @@ -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; diff --git a/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java b/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java index 2dadf6f5..0fa9497c 100644 --- a/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java @@ -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); } - + } diff --git a/src/net/sf/openrocket/rocketcomponent/FinSet.java b/src/net/sf/openrocket/rocketcomponent/FinSet.java index e388b4a6..d628478a 100644 --- a/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -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. */ diff --git a/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java b/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java index 288726b1..a852b2fa 100644 --- a/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java @@ -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 list = new ArrayList(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"); } - + } diff --git a/src/net/sf/openrocket/simulation/GUISimulationConditions.java b/src/net/sf/openrocket/simulation/GUISimulationConditions.java index 334563e8..d0b83911 100644 --- a/src/net/sf/openrocket/simulation/GUISimulationConditions.java +++ b/src/net/sf/openrocket/simulation/GUISimulationConditions.java @@ -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); diff --git a/src/net/sf/openrocket/simulation/RK4SimulationStepper.java b/src/net/sf/openrocket/simulation/RK4SimulationStepper.java index a4a68220..99fd475e 100644 --- a/src/net/sf/openrocket/simulation/RK4SimulationStepper.java +++ b/src/net/sf/openrocket/simulation/RK4SimulationStepper.java @@ -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(); diff --git a/src/net/sf/openrocket/startup/Application.java b/src/net/sf/openrocket/startup/Application.java index bb8b20f7..a7847d32 100644 --- a/src/net/sf/openrocket/startup/Application.java +++ b/src/net/sf/openrocket/startup/Application.java @@ -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); diff --git a/src/net/sf/openrocket/startup/Startup.java b/src/net/sf/openrocket/startup/Startup.java index f4141f9b..a975894c 100644 --- a/src/net/sf/openrocket/startup/Startup.java +++ b/src/net/sf/openrocket/startup/Startup.java @@ -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 diff --git a/src/net/sf/openrocket/unit/Unit.java b/src/net/sf/openrocket/unit/Unit.java index 53e19c80..4703b31b 100644 --- a/src/net/sf/openrocket/unit/Unit.java +++ b/src/net/sf/openrocket/unit/Unit.java @@ -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 index 00000000..6a8216ff --- /dev/null +++ b/src/net/sf/openrocket/util/FileHelper.java @@ -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. + *

+ * These methods log the necessary information to the debug log. +* + * @author Sampo Niskanen + */ +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 true to write, false 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); + + } + +} diff --git a/src/net/sf/openrocket/util/Icons.java b/src/net/sf/openrocket/util/Icons.java index 9f786ec7..7639bcdc 100644 --- a/src/net/sf/openrocket/util/Icons.java +++ b/src/net/sf/openrocket/util/Icons.java @@ -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 index 00000000..b91ae46a --- /dev/null +++ b/src/net/sf/openrocket/util/Named.java @@ -0,0 +1,56 @@ +package net.sf.openrocket.util; + +import java.text.Collator; + +/** + * An object holder that provides a custom toString return value. + *

+ * The class supports sorting by the name. + * + * @author Sampo Niskanen + * @param the holder type + */ +public class Named implements Comparable> { + + 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 other) { + if (collator == null) { + collator = Collator.getInstance(); + } + + return collator.compare(this.toString(), other.toString()); + } + +} diff --git a/src/net/sf/openrocket/util/Prefs.java b/src/net/sf/openrocket/util/Prefs.java index 047db50c..16e338c4 100644 --- a/src/net/sf/openrocket/util/Prefs.java +++ b/src/net/sf/openrocket/util/Prefs.java @@ -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 SUPPORTED_LOCALES; + static { + List list = new ArrayList(); + 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 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/gui/main/SimpleFileFilter.java b/src/net/sf/openrocket/util/SimpleFileFilter.java similarity index 98% rename from src/net/sf/openrocket/gui/main/SimpleFileFilter.java rename to src/net/sf/openrocket/util/SimpleFileFilter.java index 2d860b00..008592a7 100644 --- a/src/net/sf/openrocket/gui/main/SimpleFileFilter.java +++ b/src/net/sf/openrocket/util/SimpleFileFilter.java @@ -1,4 +1,4 @@ -package net.sf.openrocket.gui.main; +package net.sf.openrocket.util; import java.io.File; diff --git a/src/net/sf/openrocket/util/Utils.java b/src/net/sf/openrocket/util/Utils.java new file mode 100644 index 00000000..39b3e861 --- /dev/null +++ b/src/net/sf/openrocket/util/Utils.java @@ -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 null + */ + public static boolean equals(Object first, Object second) { + if (first == null) { + return second == null; + } else { + return first.equals(second); + } + } + +} diff --git a/test/net/sf/openrocket/IntegrationTest.java b/test/net/sf/openrocket/IntegrationTest.java index c864ec96..c3452c1c 100644 --- a/test/net/sf/openrocket/IntegrationTest.java +++ b/test/net/sf/openrocket/IntegrationTest.java @@ -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")); } /** -- 2.47.2